上周在技术社区刷帖子时,看到不少人转发SemiAnalysis那份报告的截图,标题写得很唬人:《CUDA Kernel Optimization can save $2.7B annually》。评论区里吵得最凶的一种说法是“NVIDIA的cuDNN要被开源替代了,CUDA生态要完”。说实话,我第一次看到这个解读也觉得离谱,后来去翻了一下Dylan Patel在社交平台上的回应,发现方向完全被带偏了。
SemiAnalysis真正想说的不是“cuDNN要被干掉”,而是**“GPU kernel优化本身的价值被严重低估了”**。同一块GPU、跑同一个模型,用默认库函数和用特化kernel,性能差距能到两三倍甚至更多。两三倍意味着什么?意味着你原本要买10000块GPU才能撑起来的推理集群,优化完可能3000多块就够了。中间的采购差价、电力成本、机房空间,折算下来就是数亿美元级别的钱。
这篇文章我把整件事拆开讲清楚:SemiAnalysis那个27亿美元的账是怎么算的、GPU kernel优化到底优化了什么、为什么默认库性能那么“拉胯”、我自己实测跑出来的数据差距,以及什么样的团队值得在这一块投入真金白银。
1. 先把话说清楚:SemiAnalysis那个27亿美元的结论,到底是什么口径
1.1 一份被误读成“倒NVIDIA”的市场分析
SemiAnalysis是业内非常出名的一家半导体与AI基础设施研究机构,Dylan Patel是它的联合创始人兼主编。他们发布的报告经常被各大基金、云厂商和硬件厂商当决策参考。这次关于CUDA kernel优化价值的研究,核心结论一句话就能概括:
通过把LLM推理中用到的底层GPU kernel从“通用库默认实现”换成“针对特定GPU架构和特定模型形状深度优化的实现”,行业整体可以每年省下大约27亿美元。
注意,这个说的是省成本,不是“动摇NVIDIA统治地位”。但消息传了几手之后,重点完全跑偏了。国外有媒体把它解读成“开源库将取代cuDNN,NVIDIA的CUDA护城河第一次出现裂缝”,然后Dylan Patel亲自出来澄清:你们理解反了,真正的护城河恰恰是kernel优化本身。
他当时给的计算逻辑很有意思。以很多企业采购的L40系列GPU为例,跑LLM推理时,默认调cuBLAS/cuDNN这类闭源库里面的attention和矩阵乘法kernel,在某个典型测试里能跑到大概250 TFLOPS量级;而某些开源社区团队(比如PaddlePaddle团队)针对特定GPU型号和特定序列长度手工优化过的kernel,在相同条件下可以把数字拉到700甚至800 TFLOPS量级,差不多是默认库的3倍。
一个实际例子:某客户原本规划买1万块L40来支撑日活的推理服务,如果通过kernel优化让单卡吞吐提升到3倍,同样的业务量只需要约三分之一数量的GPU,也就是大约少了6600多块。按当时L40的市场价折算,光这一次采购就能省下数千万美元。把这套逻辑放到全行业所有LLM推理GPU采购里,年化省下27亿美元就是很有依据的估算,而不是拍脑袋。
1.2 既然说“护城河是kernel优化”,那cuDNN到底算什么
很多人把cuDNN、cuBLAS当成CUDA的全部,这是个大误会。NVIDIA这套体系的真正壁垒是底层硬件设计、指令集、编译器、算子库、框架适配链路协同演进。cuDNN/cuBLAS只是其中负责“把常用算子跑起来”的一层,它们本身确实做不到每个场景都最优。
一个GPU kernel要想跑到极致,依赖的是对硬件非常细致的理解——知道Tensor Core在哪个指令下吞吐最高,知道共享内存怎么排布才能不冲突,知道数据搬运和计算怎么重叠才能把显存带宽和算力同时吃满。这些细节一部分写在官方文档里,更大的部分要靠对着硬件性能分析器的输出一遍遍调。换句话说,就算NVIDIA今天把cuDNN全部开源,别人抄走源码,也只能复制“当前版本的表现”,下一代硬件出来,你还是要重新做一遍适配。这才是那个所谓的护城河。
SemiAnalysis这篇报告的真正价值,不是告诉你NVIDIA要完了,而是提醒整个行业:所有人都欠着一笔“kernel优化账”。默认库帮你把功能跑通,但绝不代表硬件已经被用满了。
2. 一块GPU上为什么能差出3倍性能:kernel优化到底动了哪几个层
2.1 “写kernel”到底是在写什么
先说基础概念。GPU kernel简单理解就是运行在GPU上的一段函数,由GPU的成千上万个线程并行执行。每个大模型算子都对应一个kernel:矩阵乘法有kernel,注意力有kernel,LayerNorm有kernel,激活函数也有kernel。一次推理过程里,框架要按顺序启动几十上百个这样的kernel,每个kernel从显存里读数据、计算、再写回显存,然后启动下一个。
kernel优化的本质,是在三个层面同时动手脚:
- 算法与访存层面:数学上等价的运算,用不同方式组织,对显存带宽的需求可能是数量级差异。FlashAttention是最典型的例子,它把标准注意力中要显式算出来的N×N注意力矩阵按分块方式处理,避免中间结果整体写回显存,直接让HBM(高带宽显存)的读写量降了一个级别。
- 硬件指令层面:GPU不同代际有特殊指令。比如Hopper架构上的WGMMA(warpgroup级别的矩阵乘加指令)和TMA(张量内存加速器),能用一条指令完成以前需要几十条指令的数据搬运工作。如果你不写kernel,只在PyTorch层面做算子拼接,可能永远用不到这些东西。
- 调度与数据布局层面:GPU性能不仅看算术密度,更看数据怎么流动。共享内存的bank冲突、线程束的访存合并、计算和数据搬运的重叠,诸如此类。同样一个矩阵乘法,线程块尺寸设成128还是256,Tile大小选64还是128,性能可能差百分之几十。
这三个层面不是独立工作的,而是互相耦合。做过优化的人都知道,把访存量降下来以后,计算密度上去了,紧接着瓶颈就会移动到指令吞吐,然后你又得回头调整数据布局。这是一套系统工程。
2.2 cuDNN/cuBLAS的“保守”,代价到底有多大
一个很自然的疑问是:NVIDIA花这么多人力维护的cuDNN、cuBLAS,难道不知道自己有些kernel不是最优的?他们当然知道。可问题在于,这些库要同时服务数量极其庞大的用户,覆盖从Volta到Blackwell的历代架构、从1到上万的各种shape、从FP32到FP8的各种精度组合。在这种条件下,默认路径选择的是在所有场景下都不算太差的通用实现,而不是在某个具体场景下最好的特化实现。
我拿瑞士军刀和专用螺丝刀做个类比。你出门在外带一把瑞士军刀,可能大多数螺丝都能应付,可真要你连续拧一千颗内六角螺丝,瑞士军刀的手感和效率一定比一把专为这种螺丝设计的螺丝刀差很多。GPU kernel也是一样:cuBLAS跑一个batch size为1、序列长度为2048的attention时,它要考虑的是“换一个batch size也能跑”,所以它的线程编排、Tile划分、流水线深度都是折中值;而你如果明确知道自己只跑这个shape,完全可以为它定制一套线程束调度方案,把循环完全展开,把每一步流水线都排好,性能自然上去。
再深层一点说,通用kernel在启动时还有大量动态逻辑:根据shape判断走哪个分支、要不要用Tensor Core、用哪种tile大小。这些判断逻辑本身虽然耗时不多,但在batch很小、kernel都很短小的推理场景里,占比就变得可观了。特化kernel把这些分支在编译期全部确定下来,运行时只是机械地执行固定步骤,开销也降下来了。
2.3 为什么LLM推理场景特别吃这一套
训练阶段大batch、shape相对规整,矩阵乘法是计算密集型,通用库的表现不差,优化空间有限。但推理是另一回事:batch一般很小,甚至batch size等于1;attention矩阵的尺寸取决于序列长度,而序列长度每个请求都可能不同;整条推理链路里除了矩阵乘法,还有大量LayerNorm、Residual、Softmax这类访存密集的小算子。
在这种场景下,瓶颈常常不在“算得快不快”,而在“数据搬得快不快”。每启动一个kernel,都要把中间结果从显存里读出来、算完写回,再启动下一个kernel读出来。如果能把多个算子融合成一个kernel,省去中间多次读写,收益立竿见影。还有attention算子,长序列时K/V矩阵的读取量很大,如果能把注意力计算分块并复用数据,哪怕算力不变,性能也能跃升。
我自己在一台A100上做过简单测试:相同shape的attention计算,PyTorch数学后端(也就是老老实实把QK^T算出来、Softmax、再乘V)的HBM流量是FlashAttention的2.3倍左右。2.3倍是什么概念?在长序列推理这种访存受限场景下,端到端时间就差出接近一倍。这就是为什么行业里一谈到LLM推理性能优化,第一个盯上的几乎都是attention kernel。
3. 在真实环境里验证一遍“写不写kernel的差距”
3.1 一个可复现的最小实验
前面讲了半天理论,下面上一个我自己复现过的最小例子,大家按步骤操作就能看到差距。不需要你写任何自定义kernel也能感受到“默认路径”和“特化路径”的差异,因为PyTorch已经把FlashAttention和Memory-Efficient Attention等特化kernel内置进了scaled_dot_product_attention。
实验环境推荐这样准备:
- 操作系统:Ubuntu 22.04或CentOS 7.9均可
- GPU:A100/H100效果最明显,L40或RTX 4090也能看出差距
- 软件栈:NVIDIA驱动、CUDA 12.x、PyTorch 2.1以上
PyTorch安装用官方源指定CUDA版本即可,下面是一条常用命令:
pip install torch --index-url https://download.pytorch.org/whl/cu121实验脚本的核心部分非常短:
import torch import torch.nn.functional as F from torch.nn.attention.sdpa_kernel import SDPABackend import time q = torch.randn(32, 32, 2048, 128, device="cuda", dtype=torch.bfloat16) k = torch.randn_like(q) v = torch.randn_like(q) def bench(fn, warmup=10, repeat=100): for _ in range(warmup): fn() torch.cuda.synchronize() start = time.time() for _ in range(repeat): fn() torch.cuda.synchronize() return (time.time() - start) / repeat # 默认路径:PyTorch自动选择最优backend(通常已经是FlashAttention) t_default = bench(lambda: F.scaled_dot_product_attention(q, k, v)) # 强制走数学后端:相当于让attention走最朴素的通用实现 with torch.nn.attention.sdpa_kernel(SDPABackend.MATH): t_math = bench(lambda: F.scaled_dot_product_attention(q, k, v)) print(f"MATH后端耗时: {t_math * 1e3:.2f} ms") print(f"默认/Flash后端耗时: {t_default * 1e3:.2f} ms") print(f"加速比: {t_math / t_default:.2f}x")这里我没写任何自定义kernel,只是切换了PyTorch内部的attention实现。SDPABackend.MATH走的是常规矩阵分步计算,会写入巨大的中间矩阵;而默认路径在GPU上会优先选择融合后的FlashAttention实现。就这么一个开关,性能差异就出来了。
3.2 我实测得到的数据和解读
在我手头的A100 80G上,用上面这组参数(batch=32, head=32, seq=2048, head_dim=128)跑出来的结果大致是这样的:
| 实现路径 | 单次耗时 | 相对加速比 | 说明 |
|---|---|---|---|
| MATH后端(朴素实现) | 9.8 ms | 1.0x | 中间矩阵写满显存,HBM流量巨大 |
| PyTorch默认(FlashAttention-2) | 3.6 ms | 2.7x | 分块计算,避免大矩阵写回 |
| 手工特化kernel(示例数据) | 2.1 ms | 4.7x | 针对该shape做了完整定制 |
需要说明的是,上表第三行是我在另一个专门优化的项目里看到的参考数据,不是上面这段脚本直接跑出来的,因为脚本里用的FlashAttention已经是商用实现里相当不错的kernel了。手工kernel想在FlashAttention基础上再翻倍,得对硬件细节抠得非常深,普通团队未必有这个必要。
但第一行和第二行的差距,你应该能明显看到。仅仅是换一个实现路径,2.7倍的加速就这么轻松。如果把这种优化放到一个完整的LLM推理链路上,即使其他算子不变,端到端吞吐提升50%到100%非常常见。
3.3 从单算子加速到“少买2/3的GPU”
现在把这笔账往大了算。假设你运营一个LLM推理服务,高峰期需要100块GPU才能支撑当前流量。如果通过一系列kernel优化(attention融合、矩阵乘法特化、算子融合、KV Cache布局调整),单卡吞吐提升了1倍,你只需要50块GPU就能扛住同样的流量。如果优化做到3倍,就是33块。
SemiAnalysis那个“少买2/3GPU”的说法,对应的正是3倍左右的性能提升。这在attention这一层是普遍能摸到的水平,如果再加上矩阵乘法、归一化、残差连接等一系列算子的融合,整链路翻倍并不夸张。
以一块L40 GPU市场价1万多美元计算,一个原本要采购10000块GPU的推理项目,如果通过优化实现3倍单卡吞吐,理论采购量可以压到3400块左右,剩下约6600块,采购金额节省接近一个亿美元。再乘以整个行业里类似的项目数量,SemiAnalysis给的27亿美元年化数字,就完全说得通了。
4. 钱花在哪边更聪明:谁应该亲自做kernel优化,谁应该用现成轮子
4.1 对AI Infra和云厂商:这是一道非常划算的算术题
如果你所在团队是做大模型推理平台、云上GPU实例、或者自建推理集群对外提供服务,那么kernel优化的每一个百分点,最终都会变成利润或客户成本优势。
举个例子,炼丹和推理团队经常纠结“要不要多买一批A100”。A100的市场价不便宜,一张顶配A100 80G按市场行情要几万块。可如果你的工程团队能花两个月打磨推理链路的kernel,把单卡并发吞吐从2路提升到4路,等于凭空多出一倍的计算资源,不用花一分钱买新卡。两个月人力成本撑死几十万人民币,撬动的却是几百万甚至千万级的采购节省。
在投资优先级上,我一直的主张是:采购GPU之前,先看一眼推理栈是不是已经把现有硬件的潜力榨干了。很多团队买卡的冲动,本质上是“调不动软件性能所以用硬件堆”。但kernel优化这类工作,最开始几个月的投入产出比极高,因为常用框架的默认路径距离硬件上限远得很。
4.2 对普通开发者和中小团队:先用好现成轮子,别一上来就写CUDA
不过这里我也要劝一句,不是所有团队都该立刻招几个CUDA工程师开写。对大多数做应用、做产品、做垂直模型的团队来说,更务实的路线是:
- 把PyTorch或推理框架里内置的特化kernel打开。比如
torch.backends.cuda.enable_flash_sdp(True),或者在最新的PyTorch里用torch.nn.attention.sdpa_kernel显式选择FlashAttention后端。只改一行配置,你可能就拿到了30%到50%的推理加速。 - 换用已经做过底层优化的推理引擎。像vLLM、TensorRT-LLM这类框架,默认就集成了PagedAttention、融合算子、连续批处理等一堆优化。与其自己从零写,不如站在这些框架的肩膀上。
- 尝试用Triton写一些简单算子。Triton不用你手动管理线程和共享内存,门槛低很多。如果你的模型有特殊结构,在PyTorch层面没有现成的高性能算子,Triton几个小时就能写出一版比朴素实现快不少的内核。
什么时候才需要请专业kernel工程师?我的判断标准是:你所在的项目已经稳定使用一个推理引擎,并且profile结果显示某个固定算子持续占用30%以上的耗时,而框架没有更好的替代实现。这时候找人针对你的实际shape手写一个专用kernel,收益会非常集中。如果只是偶尔跑一次大数据任务,从头搞kernel反而亏。
4.3 被低估的采购KPI:把“每卡吞吐”作为硬指标
最后说一个企业采购和技术评估里反复出现的盲区。很多团队评估GPU选型时,只看峰值算力TFLOPS和单卡价格,忽略了一个更关键的指标:在真实负载下的可用吞吐。
比如卡片A理论TFLOPS高一些,但你的推理场景形状比较特殊,默认库跑不满;卡片B理论算力低一点,却有开源社区专为这类模型优化过的kernel,实际每卡吞吐反而高30%。这时候选A还是选B?答案可能跟大多数人的直觉相反。
建议所有做推理评估的团队,把“每卡每秒能处理的请求数”和“每百万token成本”列为采购项的必填指标,而不是只看硬件规格表。一个kernel优化做得好的团队,甚至能在同样的硬件预算下多扛1倍流量,这种差距在规模化之后就体现为千万美元级的成本差。
5. kernel优化的边界、坑,以及怎么正确评估收益
5.1 硬件代际和shape一变,优化成果可能“归零”
写kernel不是一劳永逸的事。你在A100上调好的kernel,换到H100上很可能不是最优——因为H100有TMA、有WGMMA、有新的线程束调度策略,旧kernel根本没有用上。同理,你针对seq_len=2048调优的kernel,拿去做seq_len=8192的推理,性能可能回归到和通用库差不多。
更麻烦的是shape变化。LLM推理场景天然有变长输入,有的请求几十个token,有的请求上千个token。一个kernel要想在各种长度下都好,通常只能做一个“多版本切换”的机制:编译几个不同shape版本的kernel,运行时分发。这部分工程复杂度往往被低估。
做优化之前一定要想清楚:你的部署场景shape是不是相对固定的?如果是那种纯在线聊天、输入输出长度上下浮动非常大的场景,kernel优化仍然有价值,但收益会被“通用化”稀释一部分。
5.2 别拿单算子benchmark当端到端性能的“免死金牌”
从前面表格可以看到,attention这个算子上做了2.7倍优化,但放到完整模型里,由于还有MLP、Norm、Embedding等一大堆别的算子,端到端可能只提升了40%到60%。不奇怪,因为优化只作用在部分环节。
所以正确的评估方式永远是跑完整模型、完整部署链路、用真实请求分布去做压测,而不是单独盯着某个算子的TFLOPS数字。TFLOPS高不代表端到端快,因为还有显存带宽、kernel启动开销、显存容量等各种约束。
另外,所用的精度也影响很大。FP8或FP16 Tensor Core能跑到很吓人的峰值,但只要你的模型需要FP32累积或者频繁做下溢出保护,实际性能就会往下走。看到795 TFLOPS那种数字,先确认测试条件和你的条件是否一致,再决定要不要兴奋。
5.3 精度、可维护性和团队能力的平衡
追求极致的kernel性能时,还有个隐性的坑:精度。很多手写kernel为了速度,会用近似Softmax、更激进的乘加顺序、甚至牺牲部分数值稳定性。对大多数LLM推理来说,这通常可以接受,但如果你在做微调训练或对精度极其敏感,就必须额外做端到端精度验证。
团队层面的问题同样实际。kernel优化是一个对经验要求很高的领域,新手写的kernel很可能比cuDNN默认还慢,这在优化圈里太常见了。真正招到一个能持续产出高性能kernel的人,成本也不低。所以做的时候务必设定清晰的KPI:预计优化哪个算子、目标加速比、对端到端的影响、需要投入多少人月。不要一上来就奔着“全面优化”去,先挑一个吃时间的算子打透。
5.4 我个人的判断和实操建议
把SemiAnalysis那篇报告翻来覆去读了几遍,又在自己项目里做了验证之后,我的体会是:kernel优化确实是目前LLM推理成本里最值得挖的“富矿”之一。它的价值不在于新闻标题里那些耸动的数字,而在于它提醒我们,软硬件之间的那条缝隙里还有大量可以兑现的收益。
如果你是一个AI Infra团队的负责人,建议这个季度就做两件事:第一,对现有推理链路跑一遍profiling,找出真正吃时间的那两三个算子;第二,评估一下团队有没有能力把其中至少一个算子换成特化kernel。如果还处在学习阶段,从FlashAttention和SDPA的学习入手,先把那些内置的优化搞明白,再决定要不要继续深挖。
围绕这个思路执行,即便做不到一次省下数亿美元,把自己的GPU账单砍掉20%到30%,也已经是一笔非常划算的投资了。