AI 基建
0%
第九部分 · 基础设施、算力与前沿 · 第 64 章

编译器、内核与 CUDA 护城河

作者Changkun Ou
阅读时长约 11 分钟

上一章结束时,它的两场争论都指向框架之下的那台编译器。本章顺着这个指向走下去。被捕获的计算图终究要变成设备代码,而朴素的设备代码与好的设备代码之间,差的不是几个百分点,而是接近一个数量级,差距由一个成本模型支配:在现代加速器上,搬运字节比对字节做算术更贵。这个模型解释了为什么融合算子是编译器最根本的优化,为什么最快的注意力实现更应被理解为一个算法而不是一个内核,也解释了为什么这个行业最深的护城河不是硅片,而是硅片上沉积了十九年的软件。读完本章,读者能把一行 Python 一路追到 GPU 执行的指令,说出性能是在这条线的哪一段赢下来的,并解释这条线为何穿过一家厂商的领地、大家为此付出什么代价,以及模型自己写内核可能怎样改变它。生产环境里服务模型的量化内核在 第 34 章 另有专章;本章讲的是让内核得以产生的那套机器。

数字节,别数 FLOPs

在 GPU 的词汇里,内核(kernel)指发射到设备上的一个程序:一次矩阵乘、一次逐元素加法、一次 softmax。内核跑得快不快,几乎完全由一个比值决定,即它的算术强度:每从高带宽显存(HBM)搬运一个字节,它做多少 FLOPs 的算术。屋顶线模型把这个比值画成一张图:可达吞吐量是两者的较小值,一边是硬件的峰值算术速率,另一边是显存带宽乘以内核的算术强度,于是性能随强度线性爬升,直到撞上平的算力天花板 (Williams et al. 2009)。两片屋顶相交的拐角,用一个数字概括了这台机器的性格。一块 H100 有约 989 稠密 BF16 teraFLOPs 的算力,对着 3.35 TB/s 的 HBM 带宽,拐角落在每字节约 295 FLOPs 附近:任何算术密度低于此的内核都受限于显存,运行时间就是搬运的字节数除以带宽。

Transformer 里除了矩阵乘之外的几乎一切,都远远落在拐角之下。一次逐元素加法每个输出元素做一个 FLOP,半精度下却要搬六个字节,强度约 0.17,差了三个数量级。把每个这样的运算都作为独立内核运行,张量就要在每一步之间往返一趟 HBM,于是「减最大值、取指数、求和、相除」这样的链条要付四趟路费,其实一趟就够。解法是融合(fusion):把链条编译成单个内核,输入读一次,中间值留在片上寄存器里,结果写一次。算术分毫未变,变的只有流量;而既然流量才是价钱,融合就是这一层最接近免费午餐的东西 (He 2022)。对真实训练的测量证实了这笔账单里有多大一块归它管:一项对 Transformer 训练的研究发现,占据运行时间的是受限于显存的运算而不是矩阵乘,仅靠数据搬运优化就拿回了三分之一的时间 (Ivanov et al. 2021)。还有一笔较小的税把设计推向同一个方向:每次内核发射都要花微秒级的驱动开销,这正是运行时把整段发射序列录制下来、作为 CUDA 图整体重放,而不是逐个发射内核的原因 (Gray 2019)。

下面的可运行单元把这笔账算得具体:对一个大矩阵做 softmax,拆成四个内核执行与融合成一个内核执行,FLOPs 相同,字节数差三倍,而且两者的算术强度都远低于屋顶线拐角,预测的运行时间就是流量除以带宽。

N, d, B = 4096, 4096, 4  # 一张 fp32 分数矩阵,每元素字节数
flops = 5 * N * d        # 取最大值、减、取指数、求和、相除:每元素约各一次
# 不融合:四个内核,每个都从 HBM 读写整个 N*d 矩阵
unfused = (2 + 3 + 2 + 3) * N * d * B          # 各内核的读写量相加
# 融合:输入读一次,输出写一次
fused = 2 * N * d * B
for name, traffic in [("不融合", unfused), ("融合  ", fused)]:
    print(f"{name}: 搬运 {traffic/1e6:6.1f} MB, "
          f"算术强度 {flops/traffic:5.2f} FLOP/字节, "
          f"H100 上受带宽限制的时间: {traffic/3.35e12*1e6:5.0f} 微秒")
print("FLOPs 相同;融合内核快出来的,恰好是它省掉的那些流量")

经典一课:FlashAttention

如果说融合是这一层的日常动作,FlashAttention 注意力内核(FlashAttention)就是这一层的代表作。它值得单独一节,因为它改变了这个领域对「内核能是什么」的认知。注意力的教科书写法要具体化一张 N×NN \times N 的分数矩阵,序列一长,这张矩阵既放不进片上的快速内存,它逼出来的那些 HBM 往返也不值得付。FlashAttention 计算的是精确注意力,没有任何近似,靠的是分块:让键和值的块流过容量很小的片上 SRAM,维护一个运行中的最大值与归一化项,使 softmax 能在一趟里增量算完,这个技巧几年前就以在线 softmax 的名义发表过 (Milakov and Gimelshein 2018),而那张分数矩阵从头到尾都不写到任何地方 (Dao et al. 2022)。内存从序列长度的平方级降到线性,论文还证明了它的字节数对精确注意力而言在整个 SRAM 尺寸范围内渐近最优。让这一课立住的是一组对照:更早的一种省内存注意力达到了同样的内存界,却没有围绕存储层级来编排计算,其论文自己的表格显示它比基线更慢 (Rabe and Staats 2021)。省内存从来不是重点;编排字节才是。

续作把这一课磨得更锋利,而它对可移植性的含义也更令人不安。FlashAttention-2 把利用率从峰值的 25%-40% 提到最高 73%,靠的不是新想法,而是把同一份计算在 GPU 的工作单元之间重新切分 (Dao 2023);FlashAttention-3 达到 Hopper BF16 峰值的 85%,代价是彻底拥抱那块芯片最新的硬件,专用的异步拷贝引擎与 warp-group 矩阵指令,于是最快的注意力如今是用一种架构的私有词汇写成的 (Shah et al. 2024)。事实证明,峰值性能不可移植。这个事实塑造着接下来两节的一切。

下沉流水线

在框架捕获的图与斤斤计较字节的内核之间,隔着一套编译器栈,到 2026 年它的形状已经稳定得可以画出来。PyTorch 的 torch.compile 像上一章讲的那样从 Python 字节码捕获计算图,交给 TorchInductor,后者能融合的都融合,再为 GPU 生成 Triton;在 180 个真实模型上的测量显示,这条流水线买到了推理 2.27 倍、训练 1.41 倍的几何平均加速,本质上就是把本章的成本模型自动化 (Ansel et al. 2024)。JAX 那条路生来就是编译的:被追踪的程序先下沉到一种可移植的算子方言,再进入 XLA,也就是自 2017 年起喂养 TPU、2023 年拆分为 OpenXLA 后开始面向多家硅片的那台编译器。两条路在底部汇合:从 PTX 开始由 NVIDIA 的编译器接手,这个由驱动翻译到物理指令集的虚拟指令集,正是下文几个护城河论点共同的枢纽。

lowering py 模型代码(Python) dyn TorchDynamo 字节码捕获,守卫 py->dyn jaxpr jaxpr(追踪所得) py->jaxpr fx FX 图 dyn->fx ind TorchInductor 融合与调度 fx->ind tri Triton 分块级内核 ind->tri ptx PTX(虚拟指令集) tri->ptx hlo StableHLO jaxpr->hlo xla XLA 融合、布局、集合通信 hlo->xla xla->ptx sass SASS(硅片指令集) 由驱动翻译 ptx->sass
图 64.1. 下沉流水线:两条道汇合到同一家厂商的地板上。守卫与断图住在顶部,那里要保住 Python 语义;硬件特异性向底部积累,性能也在那里产生。

这套栈的思想谱系值得交代,因为它解释了分工。图像处理语言 Halide 贡献了奠基性的分离:把「算什么」与「怎样调度到机器上」分开描述,调度便可以被搜索而不必手工推导 (Ragan-Kelley et al. 2013)。TVM 把这个分离带进深度学习,并让搜索自动化,用一个学习到的成本模型去探索人类不会去枚举的调度 (Chen et al. 2018)。MLIR 把公共的底层设施工业化:一个由可组合方言构成的编译器框架,如今 XLA、Triton 的内部和多数后来者都共用它 (Lattner et al. 2021)。而 Triton 定下了整个生态最终收敛的抽象层级:程序员按分块,也就是一小块矩阵,各写一个程序,访存合并、共享内存暂存与处理器内部的调度交给编译器,那些恰恰是 CUDA 让程序员自己扛的难活 (Tillet et al. 2019)。TorchInductor 生成内核用的就是 Triton,它的后端如今瞄准 NVIDIA、AMD 与 Intel 的硅片,生态对可移植性的希望因此都压在它身上。然而反面的证据也压在它身上:为了追逐 Hopper 与 Blackwell 的峰值,Triton 长出了 warp 特化,随后又长出 Gluon,一个把架构特有控制原原本本暴露出来的更低层方言,而那些控制正是这层抽象当初要藏起来的东西。FlashAttention-3 的模式在编译器层面重演了:写法的可移植做得到;峰值性能的可移植总在溜走。

护城河

CUDA 于 2007 年发布,名字听着像一门语言,但这个名字盖住的是一整根垂直的栈:驱动与运行时、二者之间的 PTX 契约,以及最重要的库,做线性代数的 cuBLAS、承担 第 62 章 那些集合通信的 NCCL,还有 cuDNN,它 2014 年的发布恰好在深度学习工业化的时刻,把那批年轻的框架焊在了 NVIDIA 的硅片上 (Chetlur et al. 2014; Nickolls et al. 2008)。二十年的沉积,如今支撑着号称六百万的 CUDA 开发者。这条护城河既是技术的也是法律的:NVIDIA 的许可证禁止把编译产物翻译到其他厂商的硬件上运行;唯一一次认真的二进制兼容层尝试 ZLUDA,先后经历企业资助、法律施压、匿名复活,到 2026 年年中又退回到业余项目的状态。竞争者因此只能攻击二进制之上的那一层。AMD 当前的加速器在内存上领先,288 GB 的 HBM3E 和 第 34 章 里讨论的那些数值格式,它的 ROCm 栈也能跑上游 PyTorch 与主要的服务引擎;然而对 MI300X 一代最细致的独立基准测试得出的结论是,用公开软件训练的吞吐量落后 NVIDIA 超过 2.5 倍,报告标题就叫「CUDA 护城河还活着」(SemiAnalysis 2024)。真正逃出去的那一派,靠的是从未进来:Google 的 TPU 线,用 XLA 编译器喂养的稠密脉动阵列,在这条河得名之前就在训练前沿模型 (Jouppi et al. 2017);AWS 的 Trainium 沿同一条 XLA 血统走到了五十万芯片的规模。耐人寻味的是,两条逃生路线在用户撞到「只靠编译器能给多少」的天花板之后,都各自长出了类 Triton 的内核语言。

下层约束

这一层一路向上,够到第一部分的架构选择。Hooker 的「硬件彩票」(hardware lottery)点明了机制:一个研究想法胜出,不只因为它更好,还因为它更贴合现存的软件与硬件,于是几十年里偏离矩阵乘那条路的想法根本没得到过试演的机会 (Hooker 2021)。开彩的地方就是内核与编译器这一层。一个架构的内层循环若没有融合好、调优过的内核,无论数学多漂亮,测出来就是慢;这也是 第 9 章 里状态空间模型的扫描运算要先有定制内核才谈得上与注意力竞争的部分原因,更是「铁板一块的优化内核让根本不同的架构连被评估都变得昂贵」这一论断的出处 (Barham and Isard 2019)。编译器栈能高效下沉什么,就悄悄决定了这个领域能尝试什么。

模型写的内核

这一层最新的变量,是本书的主角掉头对准了自己的地基。给这个问题定框架的基准 KernelBench,要求模型写出正确且快于 PyTorch 的 GPU 内核;2025 年初,前沿模型单次尝试能解出的任务不足五分之一 (Ouyang et al. 2025)。但「单次尝试」与「对着校验器迭代」之间的差距,是本书反复出现的主题,在这里同样成立:把编译与计时当作奖励信号,经强化学习训练或由搜索驱动的系统如今能稳定产出正确内核,并在受限于显存的负载上击败 PyTorch 的即时算子,有时是数倍 (Baronio et al. 2025)。警世故事也如期而至:一个广为宣传、声称百倍加速的「AI CUDA 工程师」,几天内就被发现钻了自己评测框架的空子,复用了缓存的正确结果而不是真正算出来,正是 第 27 章 解剖的那种奖励欺骗(reward hacking)的教科书案例 (Sakana AI 2025)。到 2026 年年中,清醒的读法是:生成的内核正在成为覆盖长尾可融合运算的廉价手段,但还没有人展示过模型能在受限于算力的前沿达到 FlashAttention-3 级别的工程水准,而计时环境本身是否可靠,已经上升为头等关切。生产环境的服务栈反映着同一种分工:vLLM 默认编译算子之间那些可融合的黏合部分并在 CUDA 图下重放,而注意力与量化矩阵乘仍是编译器视作黑盒的手写内核 (Ansel et al. 2024)。编译器管住了地板;峰顶仍是手艺活。

争议所在

护城河有没有在被侵蚀,取决于说的是哪一个命题。「一份 Triton 源文件如今能瞄准四家厂商的硬件」已成定论,Meta 也确实通过这条路在自研加速器上服务生产模型。「可移植的路能追平原生的路」在 2026 年年中则还不成立:最干净的测量来自 PyTorch 团队自己的全 Triton Llama 服务栈,在 H100 上达到 CUDA 内核路径性能的 76%-78%。一方把趋势线,编译器优先的栈、多厂商的 Triton、模型生成内核,读作 NVIDIA 软件优势的复利式折旧;另一方则观察到,每一代新硬件都在往下面添私有机制,异步引擎、新数值格式、受保护的互连,添得快过抽象层吸收的速度,于是护城河从底部回灌的速度不低于顶部流失的速度。两边都举 FlashAttention-3 当自己的证据。

延伸阅读

  • He, “Making Deep Learning Go Brrrr From First Principles” (对这套成本模型最平易而正确的入门), 2022. horace.io
    对 GPU 性能最平易而正确的入门:任何负载不是受限于显存带宽、就是受限于算力或调度开销,而算子融合是深度学习编译器最重要的优化。
  • Williams et al., “Roofline: An Insightful Visual Performance Model for Multicore Architectures” (本章立足的单图成本模型), 2009. doi.org
    屋顶线模型:可达吞吐量是峰值算力与「显存带宽乘以算术强度」二者的较小值,让「受限于显存还是算力」成为一张图,而不是一场争论。
  • Nickolls et al., “Scalable Parallel Programming with CUDA” (缔造者亲述的执行模型), 2008. queue.acm.org
    CUDA 执行模型的正典描述,线程网格、线程块、warp 与 SIMT,由其架构师在发布次年写就。
  • Milakov & Gimelshein, “Online normalizer calculation for softmax” (让注意力可以分块的四页小技巧), 2018. arXiv:1805.02867
    单趟计算的 softmax:边扫描边维护运行中的最大值与归一化项,这个四页纸的技巧几年后使分块精确注意力成为可能。
  • Dao et al., “FlashAttention: Fast and Memory-Efficient Exact Attention with IO-Awareness” (把「数字节」当作一个算法思想), 2022. arXiv:2205.14135
    FlashAttention 是一种 IO 感知的精确注意力算法,通过分块(tiling)与重计算减少高带宽显存(HBM)访问次数,实现更快的实际训练速度并将显存占用降至序列长度线性级别。
  • Shah et al., “FlashAttention-3: Fast and Accurate Attention with Asynchrony and Low-precision” (榨干一块特定芯片是什么样子), 2024. arXiv:2407.08608
    FlashAttention-3 靠拥抱 Hopper 芯片的私有机制,异步拷贝引擎与 warp-group 矩阵指令,达到其 BF16 峰值的 85
  • Ragan-Kelley et al., “Halide: A Language and Compiler for Optimizing Parallelism, Locality, and Recomputation in Image Processing Pipelines” (「算法与调度分离」的出处), 2013. dl.acm.org
    Halide 把「算什么」与「怎样调度到机器上」分离开来,这是每一个张量编译器做调度搜索背后的奠基思想。
  • Chen et al., “TVM: An Automated End-to-End Optimizing Compiler for Deep Learning” (第一个端到端、以学习驱动搜索的张量编译器), 2018. arXiv:1802.04799
    TVM 把 Halide 的算法与调度分离带入深度学习,用学习到的成本模型让调度搜索自动化,其算子融合分类法沿用至今。
  • Tillet et al., “Triton: An Intermediate Language and Compiler for Tiled Neural Network Computations” (成为内核通用语的分块抽象), 2019. dl.acm.org
    Triton 把静态形状的分块(tile)作为 GPU 编程的基本单元:程序员为每个分块写一个程序,合并访存、共享内存与处理器内调度交给编译器。
  • Ansel et al., “PyTorch 2: Faster Machine Learning Through Dynamic Python Bytecode Transformation and Graph Compilation” (给即时执行框架追加装配的编译器), 2024. docs.pytorch.org
    如何给即时执行框架追加装配编译器:TorchDynamo 从 Python 字节码捕获计算图,TorchInductor 生成 Triton,在 180 个真实模型上取得推理 2.27 倍、训练 1.41 倍的几何平均加速。
  • Lattner et al., “MLIR: Scaling Compiler Infrastructure for Domain Specific Computation” (XLA、Triton 等共用的底层管道), 2021. arXiv:2002.11054
    MLIR 是由可组合方言与渐进下沉构成的编译器框架,XLA、Triton 内部、Mosaic 以及多数较新的机器学习编译器如今都构建在它之上。
  • Chetlur et al., “cuDNN: Efficient Primitives for Deep Learning” (护城河的立河文书), 2014. arXiv:1410.0759
    把 2010 年代所有框架焊在 NVIDIA 硅片上的库:Caffe、Torch 及其后继者底下的深度学习原语,恰好出现在这个领域工业化的时刻。
  • Hooker, “The Hardware Lottery” (为什么想法靠贴合硅片而取胜), 2021. arXiv:2009.06489
    一个研究想法可能因为贴合现有软硬件而胜出,而非因为它更优越;当硅片围绕矩阵乘法特化,偏离这条路的想法连被评估都要付出越来越高的代价。
  • Ouyang et al., “KernelBench: Can LLMs Write Efficient GPU Kernels?” (前沿一节那个开放问题的量尺), 2025. arXiv:2502.10517
    检验模型能否写出正确且快于 PyTorch 的 GPU 内核的基准:250 个负载、一个考虑加速比的指标,以及前沿模型单次尝试击败基线的比例不足 20

评论

登录后评论