讨论CUDA图形优化与推理加速 | 原创,AI翻译
Aztice —— 香港中学生,Axono(一个轻量级AI推理库)的核心开发者。曾参加2024年第六届IKCEST“一带一路”国际大数据竞赛和第十届百度与西安交通大学大数据竞赛,在1700多支参赛队伍中排名第17。拥有多年AI基础设施经验。GitHub: @aztice,微信:@awalightice。
以下对话使用Whisper在RTX 4070上转录,随后借助AI工具进行优化整理。原始对话语言为中文。
注意:由于转录和整理均借助AI完成,部分细节可能不准确或存在转述。在依赖这些信息之前,请独立核实任何重要内容。
关于CUDA图优化、推理加速及更广泛的GPU软件生态的讨论
本次会议广泛探讨了GPU编程和推理优化,主要围绕参与者对CUDA图技术及其在提升模型推理速度方面的应用展开。讨论从对比赛结果和CUDA图机制的具体回顾,深入到更广泛的GPU软件栈,包括内存带宽瓶颈、KV缓存优化、TensorRT和CUDNN的作用、Triton语言,以及vLLM和SGLang等分布式推理框架与手动优化之间的关系。谈话还涉及模型选择、AI竞赛文化以及通过底层优化追求极致性能。
比赛状态与初步讨论
会议首先确认了近期一项比赛的结果。参与者确认,这场由百度赞助的比赛(称为百度CTI)尚未结束,目前未显示任何排名。尽管参与者此前曾提到处于领先位置,但比赛的最终结果尚未公布,尚无定论。
深入探讨CUDA图:概念与机制
讨论的主要部分集中在参与者对 CUDA 图 这一优化GPU内核启动技术的深入理解上。
CUDA图的作用
参与者解释道,CUDA图的工作原理是“记录”CPU通常会向GPU发出的内核启动序列。在典型的执行流程中,CPU必须逐个向GPU分派每个内核命令——这个过程涉及大量开销和阻塞。借助CUDA图,CPU会记录GPU需要执行的整个操作序列。一旦记录完毕,这个序列就成为一个固定的图,GPU可以自主执行,无需CPU逐一发出命令。
用参与者的话说,CPU将内核命令“踢出”给GPU直接执行,从而节省了启动时间、图构建时间,并消除了CPU参与调度循环的必要。GPU实际上是在执行“已经被告知要做的事情”,遵循记录下来的图。
记录与非阻塞执行
当被问及记录时机——是每隔固定时间(例如每秒或每毫秒)还是在累积一定数量的指令时触发——参与者澄清说,没有基于间隔的触发。记录是以非阻塞的方式进行的。
为解释阻塞与非阻塞的区别:在正常执行中,当CPU分派一个内核时,它通常会在等待GPU完成或等待下一条指令时阻塞自身。然而,在记录CUDA图之后,GPU不再需要等待CPU发出新命令;它只需重放记录好的图。这消除了CPU瓶颈,使GPU能够更高效地执行。
GPU如何将结果传回CPU
后续问题提出:GPU执行完记录的图之后,如何通知CPU?参与者解释道,GPU直接将结果返回给CPU——它输出图的执行结果,使CPU能够处理执行后的任务。两者之间仍然存在一些协调,但命令分派的重任被卸掉了。
为什么CUDA图与优化相关
参与者强调,CUDA图加速性能的核心原因是CPU在内核启动期间经常被阻塞过久。通过将命令分派责任转移给GPU,系统避免了长时间的CPU停滞。记录图通常能带来速度提升,因为CPU只需指示GPU一次(“按照惯常习惯执行”),而不是反复告诉它该做什么。
与PyTorch方法的比较
一个关键区分在于,简单地将数据移到GPU(例如,在PyTorch中使用 tensor.to(device))与使用CUDA图之间的区别。将数据移到GPU仅仅是将其放置在GPU内存中,但CPU仍需发出每个执行命令。在一个大型模型中,CPU每次推理可能需要与GPU通信数万次。CUDA图将其简化为一个记录步骤:“只需告诉GPU一次,它就能记住该做什么。”
内存带宽、HBM与推理瓶颈
话题转向了推理中更广泛的内存带宽挑战,特别是高带宽内存的作用。
从计算瓶颈到内存瓶颈的转变
参与者指出,行业的关注点已经转移:推理的瓶颈不再是计算,而是内存——具体来说是HBM的带宽。在执行算子或移动数据(尤其是模型权重)时,系统需要高带宽。权重(与通用参数相对)是推理过程中内存的主要消耗者。(参与者区分了两者:参数通常由CPU处理,而权重在进行推理时驻留在GPU内存中。)
为什么权重要占用如此大的空间
权重占用大量内存是因为大型模型有很多层。HBM的带宽——通常为每秒数百GB甚至TB——仍然是一个限制因素。参与者以HBM3为例,它提供了高达192 GB的显存,并指出虽然TB级别的带宽理论上对于一些非常大的模型是合理的,但对于大多数当前用例,GB级别的带宽通常就足够了。然而,训练往往需要更大的带宽,也享有更高的优先级。
推理优化方法:从比赛到实用技术
当被问及参与者是如何在早期的百度西安比赛(几年前)中获得第一名的,他们拒绝提供具体细节,理由是比赛有保密规定:“比赛还没结束,所以我不能公开。”
在给定GPU上估算模型容量
讨论随后转向一个实际问题:给定一个特定的GPU(例如H100、H200、RTX 4090、RTX 3090),如何估算可以在本地运行多大尺寸的模型进行推理?参与者解释道,主要因素是可用显存。例如,对于12 GB的显存,你通常只能运行小型或量化模型(例如,5–6 GB或7–8 GB的GGUF格式模型)。上下文大小也至关重要:一个拥有10万token上下文的模型与一个拥有1万token上下文的模型差异巨大。量化类型(例如,4位对比8位)也会进一步影响模型的内存占用。实践中,对于12 GB的显卡,上下文大小可能限制在2048 token左右或1万token以内,超出这个范围就需要优化工作——参与者将其描述为自己的专长:“让模型在更小的设备上运行。”
分布式推理框架:vLLM与SGLang
话题转向了流行的推理优化框架。参与者将vLLM和SGLang(可能指的是SGLang或类似项目)描述为执行分布式处理优化的工具。他们解释道,这些框架处理用户上下文的隔离——每个用户的提示保持独立——并在底层执行基础批处理。然而,参与者指出,这些框架主要设计用于简化部署,更适合需要开箱即用功能的“懒惰”用户。相比之下,手写优化(如同事所进行的)可以更快,但公司通常将其保密而非开源。
参与者还对比了像llama.cpp这样的单用户解决方案和vLLM、SGLang这样的多用户服务器解决方案。虽然llama.cpp在个人使用中更常见,但面向服务器的部署则倾向于使用后两者,因为它们方便。
理解KV缓存
对KV缓存进行了详细解释。参与者解释道,“K”和“V”指的是注意力机制中的键和值组件,它们代表了上下文。没有KV缓存,当生成一个新token时,模型需要从头重新审视所有之前的token。KV缓存在计算后存储先前token的键和值状态,这样对于每个新token,模型只需基于当前token(或最后一个token)和缓存的上下文来计算注意力。这消除了在每一步都为每个先前token重新计算整个注意力的需要。
参与者澄清说,虽然高级概念很简单——“最后一个token已经知道上下文”——但实际的性能优化细节更为复杂。
Flash Attention的角色与软件优化
小组讨论了Flash Attention,参与者将其描述为一种将注意力计算从通用内存转移到SRAM的技术,并采用分块方法。通过在SRAM中处理更小的块,Flash Attention减少了内存消耗并保持了近乎完美的精度。参与者解释说,SRAM的关键优势在于它不涉及动态地址创建——它提供稳定的内存访问,这对运行CUDA图非常有利。
当被问及为什么Flash Attention使用分块而不是简单地将所有内容移到SRAM时,参与者回答说,SRAM容量有限,因此将计算分割成更小的块更高效。CPU也更擅长处理基于块的操作。
GPU软件栈:TensorRT、cuDNN、cuBLAS与Triton
随后,对话探讨了更广泛的GPU软件生态系统,从高级框架到极低层库。
TensorRT与cuDNN
TensorRT被描述为一个专用的加速引擎,与CUDA图紧密结合。它比PyTorch内置的加速更快,因为它是专门为优化而构建的。然而,参与者指出,TensorRT非常难用——“一个很难使用的平台,相当麻烦。”相比之下,cuDNN是一个加速算子库,类似于cuBLAS,两者都为AI工作负载加速线性代数和矩阵运算。
cuBLAS与底层加速
参与者解释说,cuBLAS是一个矩阵加速库,但其名称有些误导——它非常精细,且专门为NVIDIA显卡加速。它可以比不使用时编写的标准CUDA程序快得多,因为它在非常低的层次上运行。还有cuBLAS LT(目前处于PyTorch的实验阶段),它可能比cuBLAS更快,但尚不稳定。
NVIDIA的护城河:软件,而非硬件
有人指出,NVIDIA的竞争优势不在于硬件(虽然已经很出色),而在于其软件生态系统。参与者表示同意,指出软件层,包括CUDA驱动和工具包,是一个重要的差异化因素。CUDA驱动和CUDA工具包用途不同:工具包用于开发(编写代码),而驱动是使工具包输出可执行的基础运行时。
Triton(OpenAI的Triton)
当被问及想到CUDA时会联想到什么时,参与者提到了“算子加速”和“算子融合”。他们重点提到了Triton(可能指的是OpenAI的Triton语言)作为一个代表性项目。Triton被描述为一种比标准CUDA更易用的编写GPU算子的语言。它允许开发者快速编写代码,并达到手动调优CUDA性能的85%到95%,而手动调优通常需要数天调试。
参与者指出,他们使用Triton比使用原始CUDA更多,只有在需要极致优化时才会求助于CUDA。Triton现在被认为是业界最标准的算子融合库——“目前最好、最广泛使用的。”它在像SGLang这样的框架中无处不在。
个人优化工作:理念、规模与竞争对手
参与者分享了对自身优化工作及竞争格局的见解。
追求极致速度的理念
当被问及为何他们的优化不能简单地在网上找到(例如,来自NanoGPT或NanoChat等项目)时,参与者解释说,虽然存在一些代码,但很罕见。他们更喜欢编写自定义代码,原因有二:它比标准实现更快,并且他们秉持“追求极致速度”的理念。然而,他们强调,减少层数并非目标——减少层数会损害精度。相反,他们的目标是在保持高精度的同时进行加速,通常通过预计算、融合矩阵操作或将CPU任务移至GPU等技术实现。
torch.compile的作用
讨论涉及了PyTorch的torch.compile。据参与者称,许多主流模型开发者会告诉你,torch.compile是加速PyTorch模型最快的方法。然而,参与者不同意,认为手写代码总是更快,因为它更具针对性。torch.compile必须处理许多通用情况并检查各种条件,这引入了开销。参与者承认,torch.compile是一个强大的工具——它“做了很多事情”——但它缺乏极致优化所需的定制性。
代码规模与语言
参与者的优化代码通常在500到2000行之间,全部用Python编写(而非C++)。如果他们使用纯CUDA,代码库将超过2000行。这些代码主要由基于Triton的算子融合组成,用于推理优化。
竞争格局:还有谁在做这个?
当被问及是否遇到其他在深度优化方面具有相当专业水平的人时,参与者回答说:“非常少。”大多数参赛者和工程师更倾向于使用像vLLM这样开箱即用的解决方案,因为它是自动化的。参与者对使用AI工具(例如Cursor Copilot)编写竞赛代码的趋势表示怀疑:“他们使用云端代码,用AI编写——很多人能出结果,但他们不理解原理。这违背了比赛的目的。”他们指出,比赛组织者有时会审查参与者的简历和代码以验证真实性;如果一个简历平平的人突然获得第一名,可能会受到审查。
模型使用:千问系列与Gemini
参与者在优化工作中使用千问2B模型,因为小型模型足以进行测试——“如果对小型模型有效,对大型模型也有效。”他们将千问描述为一个因其开源理念而备受推崇的系列,不仅包括权重,还包括训练和推理脚本。
在日常个人使用中,参与者使用Gemini(谷歌的闭源模型),他们认为该模型非常出色,尽管无法下载其权重进行优化。
待办事项
- 待办事项: 参与者将把整理好的会议记录发回给另一方审阅,这在对话结束时已确认。