数据中心需求的爆炸式增长促使开发人员竭尽全力寻求更高的效率。DeepSeek 团队对 Nvidia 的 CUDA 库并不满意, 他们使用虚拟化的汇编语言 (PTX) 编写内核代码,以加速其 AI 计算。其他团队也尝试使用 AI 生成优化的内核,但一些结果受到了质疑(有关各种尝试,另请参阅此处、此处、此处、此处和此处)。
为什么编写峰值速度的 GPU 代码如此困难?编写真正快速的代码一直很困难,但对于现代 GPU 来说尤其如此。
为了理解这些问题,我和同事对三家 GPU 供应商的八种不同 GPU 型号的 GPU 内核性能进行了详细研究[1]。我们考虑的测试用例是低精度矩阵乘法,这是 LLM 训练中一项资源密集型运算。我们进行了大量的实验,以了解导致性能波动的原因,以及为什么内核的运行速度有时会比预期的要慢。
就我们所研究的案例而言,我们发现了大约六个不同的因素,但结论是:像 GPU 这样的现代处理器已经变得非常复杂——尤其是它们的多层级内存子系统——以至于很难在用户实际运行的所有问题规模上获得一致的高性能。因此,目标问题的性能可能会令人惊讶且神秘地低于所讨论操作所宣传的峰值性能。原因可能显而易见——例如缓存行未对齐——也可能更加难以捉摸。对于矩阵乘法的情况,诸如预取、缓存、平铺和块大小选择等各种问题,使得内核开发人员难以针对用户可能指定的每个输入规模进行优化。
下面是我们论文中的一个示例图。颜色表示使用库调用在代表性 GPU 上执行降低精度矩阵乘法的浮点运算速率 (FLOP)。横轴和纵轴表示问题的矩阵维数(详见论文)。虽然有些区域的性能接近理论峰值(红色),但其他紧邻区域的问题规模却大幅缩减——实际上,只有峰值性能的一半左右,甚至更低。据推测,这是因为单个内核的性能或库使用的内核选择并非最优。最终结果是,如果你的问题落在一个“糟糕”的区域,你将会大吃一惊,你的性能将远低于预期,而你可能还不明白为什么。我们测试的所有高性能 GPU 都表现出类似这样的异常行为 [2] [3]。
在过去,这并非总是个问题。像 Sun Sparc 或 Cray 矢量处理器这样的老架构虽然很复杂,但其实相当简单,一个经过合理调优的计算内核,即使不能完全处理所有输入,也能在大多数输入上良好运行 [4]。如今,性能预测难度加大,并且会根据所请求问题的规模而发生很大变化。
这对库开发者来说是一个艰巨的挑战。每当一个新的 GPU 模型系列发布时,都需要进行新的内核优化和调优,以提供(希望)更持续的高性能。由于客户需求和开发者资源有限,某些情况会比其他情况得到更多的开发者关注。因此,不常用的操作往往得不到太多关注,但它们可能正是你特定情况下所需要的 [5]。
有一些工具可以帮助优化特定情况。优秀的 Nvidia CUTLASS 库比标准的 cuBLAS 库提供了更多细粒度的选项。胆子够大的人可以尝试在 PTX 或(不寒而栗)SASS 级别上对 Nvidia GPU 进行编程。超级优化可能会有所帮助,但仅适用于非常小的代码片段,即使如此,也可能有太多影响性能的外部因素使其无法有效。
自动调优是一种很有前景的方法,尽管它似乎尚未在生产环境中充分发挥其潜力。人工智能或许能在这方面大有裨益[6];在我们自己的论文中,我们成功地运用决策树和随机森林等机器学习方法,将性能建模为问题规模的函数,尽管我们的工作还处于探索阶段,尚未投入生产。要打造一个精心设计的通用解决方案,似乎需要付出巨大的努力才能做好。代码的可持续性和维护也至关重要;需要一个可持续的工作流程,以便在新的 GPU、新的 CUDA 版本,甚至特定于站点和系统的设置(例如 GPU 功率和频率上限策略)上进行重新训练。
近期大多数人工智能驱动的研究都侧重于针对单一或少数问题规模进行性能优化。真正达到生产级质量的通用工具能够提供 100% 准确的结果,并针对任何输入问题规模(即使是极端情况)或数据类型实现最佳性能。这需要优化的 GPU 内核和用于内核选择的最优内核调度器。此外,该方法还需要对生产运行中的功耗和频率变化等问题保持稳健。这目前似乎是一个尚未解决的问题。解决这一问题将对超大规模计算社区带来巨大的益处。
笔记
[1] 有关从稍微不同的角度开展的相关工作,请参阅 Matt Sinclair 实验室的这项优秀工作。
[2] 事实证明,这项研究对我们的生产运行很有帮助,帮助我们解决了在尝试百亿亿次运行时遇到的奇怪的性能难题(参见此处、此处)。
[3] 顺便提一句,这个例子也表明了使用过于简单的基准测试套件来衡量 GPU 代码性能的弊端。除非基准测试能够捕捉到真正庞大且多样化的输入案例,否则任何新提出的优化方法都可能在测试中人为地导致性能“过拟合”,最终在许多感兴趣的用户案例中仍然表现糟糕。
[4] 我曾经为Sparc处理器编写了一个一维小波卷积核,使用循环寄存器缓冲区和循环展开来最小化加载和存储操作,从而实现了接近峰值的性能。该代码可以正确地从C语言编译为汇编语言,并且对于给定问题的性能几乎可以精确预测。那是在复杂的内存层次结构出现之前。
[5] 我知道有一家供应商曾经接受客户的请求,手动调整昂贵的库调用,并使它们在特定的客户问题规模下快速运行。
[6] LLM 内核生成似乎是天作之合,尤其是在 LLM 生成的代码质量近几个月大幅提升的情况下。内核选择以及块大小、平铺等参数的选择,或许可以通过直接训练机器学习模型或类似方法得到更好的解决。对此进行对比研究将会很有启发。
文章“为什么 CUDA 内核难以优化?”最先出现在John D. Cook上。
原文: https://www.johndcook.com/blog/2025/08/27/why-are-cuda-kernels-hard-to-optimize/