资讯动态

CAKE:AI Agent与编译器协同,重塑GPU Kernel优化范式

发布时间:2026/10/9 20:18:11 来源:尧图企业网站定制
GPU Kernel 优化这摊事圈里人应该都有共识越是看着简单的算子越能把人折腾到怀疑人生。专家手写一个 CUDA Kernel从 launch config 调到 shared memory 布局再磨指令级流水短则三五天长则两三个星期最后拿到的性能数字还未必干得过 cuBLAS。所以当我看到 CAKE 这篇论文时第一反应是终于有人把 Agent 和编译器放到同一个闭环里了而不是让大模型“凭空生成一个 kernel 就算完事”。这篇论文的核心主张很直接——让 Agent 负责大胆探索 kernel 结构让编译器负责冷静评估每次修改的代价两者在循环里互相学习、共同进化最终写出超过领域专家的 GPU Kernel。这篇文章我不打算复述论文的每一张图表而是想把 CAKE 这套思路拆开揉碎它解决的是什么本质问题架构里的每个环节为什么非这么做不可以及如果想在本地复现一个简化版应该从哪里入手、会踩到哪些坑。适合正在做算子优化、AI 编译器、或者想用 Agent 做代码生成但发现“生成容易、优化难”的工程师参考。1. 为什么我们需要 CAKEKernel 优化问题的本质与 Agent 化的必然性1.1 专家手写 Kernel 的瓶颈在哪很多人第一次接触 CUDA 优化时以为把 grid、block 调大一点、多用点 shared memory 就能跑得快。真正上手后才发现Kernel 性能是一个超高维函数任何一个参数都可能成为瓶颈。block 大小影响占用率和寄存器分配循环展开影响指令级并行bank conflict 会在 32 个线程同时访问 shared memory 时让性能直接打三折寄存器溢出会被编译器默默 spill 到 local memory程序还能跑但已经慢到没法看。专家能靠经验快速排除大部分错误方向但面对新的硬件架构、新的算子形状经验本身也会失灵。卡点不只是“找参数”。更麻烦的是Kernel 层面的优化往往存在结构性分叉同一个算子既可以用 warp per row 的写法也可以换成 split-K 的并行归约策略还可以把计算重排成完全不同的数据流。这些结构之间不是连续变化的而是离散的。调参可以靠搜索换结构必须靠洞察。专家手写 Kernel 的瓶颈就在于洞察的生成速度太慢而且每个人擅长的算子类型有限。1.2 传统编译器自动化的天花板编译器领域一直在做自动调优我们熟悉的 TVM Ansor、Halide autoscheduler 都是这个方向的代表。它们的做法是先定义一个模板化的搜索空间再用成本模型评估不同调度组合的预期性能最后通过 beam search 或强化学习找到较优解。这方案看起来很合理但天花板也很明显——搜索空间是人工预设的模板里没写进去的优化手段再聪明的搜索算法也学不会。另一个问题是成本模型和真实硬件之间的鸿沟。编译器做调度决策时得预估 shared memory 访问延迟、cache 命中率、指令吞吐但 GPU 的物理行为太复杂simulator 都未必算得准更别说简化模型。结果就是编译器选出的调度经常不是最优的甚至比不过专家拍脑袋写出来的版本。传统的编译优化本质是在“预设的调度模板”内做数值搜索不具备生成全新 Kernel 结构的能力。1.3 Agent 进场从“搜代码”到“搜遍优化空间”大模型出现之后思路开始变了。一个具备代码生成能力的 Agent理论上可以把“调优 Kernel”当作一次次的代码改写任务。它天生适合处理离散的结构变化能够模仿专家写 kernel 的模式生成一个能跑的 baseline然后再做迭代式优化。这比传统编译器的模板搜索空间更灵活因为它可以直接在源码层面重组循环、更换访存模式、插入新的同步逻辑。但纯 Agent 路线有一个非常致命的问题Agent 不知道自己的 Kernel 到底差在哪。它生成一个 kernel编译通过、结果正确然后呢如果没有 profiling 反馈它只能在黑暗中随机尝试。一次两次还行几百次迭代都在绕圈子GPU 集群的成本可不会等你想明白。Agent 如果把编译报告当作反馈那反馈也只是一堆“warning: this may be slow”级别的提示信息量根本不够。这时候 CAKE 的决定性想法就出现了编译器不应该只告诉 Agent“对不对”还应该告诉它“慢在哪、为什么慢、怎么改可能更快”。这正是编译器在整个闭环里最值钱的部分。2. CAKE 的核心架构拆解Agent 与编译器如何“双脑并行”2.1 整体闭环生成、编译、反馈、进化CAKE 整条流水线可以概括成四步循环。第一步Agent 基于当前的经验状态生成一个或者一批候选 Kernel 源码。第二步编译器管线和轻量级 profiler 对这些候选做编译、运行、耗时统计、瓶颈分类。第三步编译器把原始性能数据转换成结构化反馈比如“访存受限shared memory 使用率仅 12%”“存在明显 bank conflict建议调整数据填充”“循环展开不足导致指令延迟未能隐藏”。第四步Agent 消化这些反馈更新自己的先验知识和策略权重开始下一轮生成。这个闭环最关键的地方是把“编译”从单向的代码翻译过程变成了双向的信息交换接口。传统上编译器输出的是可执行文件现在它输出的是“可学习的反馈”。Agent 输出的是代码但它在下一轮生成之前已经吸收了编译器的评价。两个系统在循环里各自变强Agent 越来越会写能被编译器认可的 kernel编译器也越来越擅长把 profiling 语义提炼成 Agent 能直接消化的优化建议。这里有个值得注意的设计选择CAKE 并没有让 Agent 去逐行阅读原始的 profiling 计数器。原生的 ncu 输出动辄几百行全是 hex 地址和计数器名称LLM 理论上能读但读进去之后也未必能形成高层次的判断。CAKE 的做法是先让编译器模块做一次“翻译”把原始计数器聚合成人类能理解、Agent 也能高效利用的语义标签。反馈的粒度太粗Agent 得不到有效指导粒度太细Agent 的注意力会被噪声淹没。结构化的中间表示才是共同进化的真正接口。2.2 Agent 组件策略选择、权重更新与候选生成CAKE 里的 Agent 不是一个裸的 LLM。它更像一个被优化策略包裹的代码生成器。论文里很重要的一个思路是Agent 维护着一套分层策略宏观层决定接下来尝试哪种 kernel 结构比如是保持 split-K 还是切回单 block 归约微观层决定具体的展开因子、向量宽度、block 大小这类数值参数。这种分层是有讲究的。如果所有决策混在一起Agent 很容易出现“改一个参数、性能倒退一次、再回滚”的低效振荡。分层之后宏观结构的选择可以保持稳定微观参数在小范围内扰动反馈信号也更干净——你至少能知道性能变化是结构变化带来的还是参数变化带来的。我自己的经验是这种分治思路不仅让 Agent 收敛更快也让排查问题容易得多某个候选 kernel 性能爆炸你可以马上定位是结构选择错了而不是参数组合走到了死胡同。候选生成方面CAKE 每轮不是生成一个 kernel而是生成一批。这背后的逻辑等同于传统自动调优里的“种群”。因为编译器和 profiler 给出的反馈是带噪声的单次运行时间可能波动 5% 到 10%如果只生成一个候选反馈里的真实信号会被噪声淹没。生成一批候选相当于从多个方向同时探测优化空间然后编译器再把这批候选的性能数据汇总Agent 就能看到更平滑的梯度信息。权重更新机制也值得展开说。CAKE 并没有在训练过程中反向传播更新大模型参数那成本谁都扛不住。它维护的是一个轻量的记忆库每个优化策略对应一个置信度权重本轮某个策略产生了性能提升权重就上调连续几轮没有改善权重就衰减。这本质上是一种 bandit 式的在线学习但底层的代码生成能力来自预训练的 LLM。我理解这是工程上非常合理的解耦AGI 级别的代码生成能力是现成的CAKE 只负责学会“在哪个方向上去用”。2.3 编译器组件不只是报错器更是超级评估器编译器模块在 CAKE 里的地位被提到前所未有的高度。传统编译器只要做语法检查、类型检查、指令选择现在它还要承担三层责任静态分析、动态 profiling、语义反馈生成。静态分析这层CAKE 会在编译过程中提取循环结构、访存模式、指令级并行度这些特征。比如它能看到循环里有一个明显的串行依赖链或者看到两个数组的访问 stride 存在冲突风险。这些信息不需要真正运行 kernel 就能获得计算成本很低可以在生成候选之后立刻做一轮“初筛”。初筛过不了的候选根本不需要编译到 PTX/SASS更不需要真正上 GPU 跑省下来的时间可以多跑几轮好候选。动态 profiling 这层CAKE 会对通过筛选的候选执行实际 benchmark。论文里做得很聪明的一点是不是每个候选都完整跑完一遍完整数据。对于超大输入它会先用一个缩小的 shape 做快速验证再在大 shape 上复测 top-k 候选因为小 shape 上的相对性能差距通常能比较好的映射到大 shape 上虽然不完全准确但足以筛掉最差的一批。语义反馈生成是这中间最有价值的部分。原始 profiler 给出的计数器维度非常多achieved occupancy、stall reasons、warp state、memory throughput、L1/L2 hit rate。但 Agent 真正需要的不是这些数字本身而是“我应该把注意力放在哪个优化方向上”。CAKE 的编译器模块会做规则映射比如当 load stall 占比超过 40%就把问题归类为访存受限同时给出具体建议增加 unroll 以提升内存级并行或者改用__ldg只读缓存路径。映射规则初始是人工定义的但论文里也提到反馈文本本身可以反过来微调——可以考虑用一个小的文本生成模型优化反馈措辞让 Agent 更容易理解。3. 实操视角怎么复现和验证 CAKE 的效果3.1 实验环境与 Benchmark 选择如果想把 CAKE 的思路在本地做一个可运行的迷你版我的建议是先别碰复杂的 FlashAttention更别一上来就去跟 cuBLAS 硬刚 GEMM。先把闭环里的每一环跑通再逐步加难度。环境准备方面GPU 最好是 Ampere 以上架构因为反馈质量跟硬件计数器丰富程度直接相关。软件栈用 CUDA Toolkit 加 Python再配一个轻量级的 profiling 采集前端。论文里没有绑定某一个具体框架但根据我复现这类系统的经验用 PyTorch 的torch.utils.benchmark做统一计时入口会比较省事它能把 CPU warmup、GPU sync、内存分配开销都处理干净。Agent 这块可以使用任意支持代码补全的 LLM本地部署或者 API 调用都行关键是你要能够接受结构化 prompt 并在多轮对话中注入反馈文本。Benchmark 选择有个原则从小算子、形状固定的场景入手。比如一个 1024x1024 的 matmul或者一个 4096 元素的 reduction。形状固定这件事很重要因为如果输入 shape 经常变化反馈信号的方差会变得很大Agent 很难分辨性能变化到底来自优化策略还是来自任务本身。我实测下来Elementwise 类算子最适合验证编译反馈管线因为它没有复杂的数据依赖任何一个性能问题是访存还是计算受限profiler 一看便知规则映射也不容易出错。3.2 关键参数、迭代预算与成本控制复现 CAKE 时你会发现最大的问题不是代码写不出来而是 GPU 时间烧不起。所以几个关键参数必须提前设计好。每轮生成的候选数量是个起点。论文思路里几个候选起步比较合理太少了信号噪声压不住太多了单轮成本线性增长。我经验上刚开始用 4 到 6 个候选比较顺手跑一轮大概会产生足够明确的排序信息成本也可控。采样温度相当于探索与利用的旋钮。温度调得太低Agent 每轮生成的 kernel 几乎一模一样反馈再多也白搭温度调得太高Agent 会频繁推出结构完全不同的方案性能波动巨大根本看不出收敛趋势。建议在 0.6 到 0.8 这个区间起步后续根据收敛曲线动态调整。现实点说你不太可能每轮都手动改温度但可以让代码在“连续 N 轮无改善”时自动把温度拉高 0.1这算是模拟退火的简易版。迭代预算方面一个简化版 CAKE 跑 200 到 300 轮就已经相当可观。以每个候选编译加 profiling 平均 20 秒算6 个候选一轮就是两分钟300 轮就是十个小时。用上静态初筛之后很多候选在编译早期就被杀掉实际会省掉一半以上的 profiling 时间。另一个省时间的技巧是每次只对 top-2 候选做完整 profiling其余候选只验正确性和粗略计时反馈质量差距不大成本骤降。下面这个表是我整理出来的一份参数速查适合第一轮实验直接抄作业参数建议值调节方向候选数量4-6性能噪声大则调大预算紧张则调小采样温度0.6-0.8收敛慢则调大振荡明显则调小静态初筛阈值低优先级候选直接淘汰放宽则保留多样性收紧则更省时间完整 profiling 候选数top-2反馈质量优先可调大最大迭代轮数200-300根据算子和预算调整冒烟测试 shape原 shape 的 1/4 到 1/16需要保证相对性能排序大致保持3.3 效果对比的正确打开方式评估 CAKE 类系统最大的误区是只盯着最终绝对性能。单个 kernel 的最终性能当然重要但它远不是全部。你要看三个维度最终性能、样本效率、时间效率。最终性能好理解就是 agent 优化出的 kernel 跟 cuBLAS、cutlass、专家手写版本比差距有多大。样本效率指的是达到某个性能水位前总共生成了多少个候选。为什么这个重要因为每个候选背后都是编译和 profiling 成本。同样是跑到 90% 的 cuBLAS 性能A 方案用了 50 个候选B 方案用了 800 个候选工程上的价值完全不同。时间效率更直白就是墙上时钟时间它把编译速度、GPU 排队时间、LLM 生成速度都算进去了。论文里报告的实验比较对象也是分层次的。除了跟直接手写 kernel 对比还重点对比了纯 LLM agent有正确性反馈、没有深度性能反馈和传统 auto-tuner。我看到的一个核心结论是CAKE 在样本效率上有数量级的优势尤其是面对结构变化需求较高的算子时纯 LLM agent 因为缺少语义反馈经常在一个局部区域反复横跳而 CAKE 能较快跳出局部最优。真正复现下来你会发现所谓的“超越专家”也不是发生在一夜之间。第一轮生成的 kernel 可能只有专家版本的 60% 性能前 50 轮改善很快后面就是漫长的爬坡。能做到超越专家的基本是中等复杂度的算子比如自定义 activation 加融合 bias或者非规则的 reduction。这些算子恰恰是库函数覆盖不完善、专家又懒得仔细优化的地方这也是 CAKE 最有实际价值的应用场景。4. 我踩过的坑与 CAKE 的边界4.1 Agent 失控只改参数不动结构复现过程中我遇到的第一个坑是Agent 很快学会了“熟练地原地打转”。几轮之后它生成出的 kernel 看起来每个版本都不一样但对比 diff 发现实际变化的只有 block 数和 unroll 因子这些表层参数循环结构和访存布局完全没动。性能曲线自然是一条平线。我后来分析问题出在反馈的抽象层级设计上如果编译器给的反馈里“调整 block 数”“调整 unroll”这类信号的频率远高于结构性建议Agent 会理所当然地认为那些方向才是有用的。解决方式是调整反馈合成逻辑把结构性建议单独拎出来在反馈文本里用更明确的措辞比如“当前 kernel 没有利用 split-K 并行结构”“建议尝试 vectorized load 替代逐元素访问”。说白了反馈不只是信息它本身就是调度 Agent 注意力的一种手段。4.2 编译器反馈的三道坎第二类坑集中在编译器反馈本身。第一道坎是反馈延迟太高我前几轮实验用的 profiler 会做全量计数器采集每跑一个 kernel 要等十几秒。后来发现根本没必要每个候选都采全量静态初筛加粗粒度计时就够用只有进入 top-2 的候选才值得上全量 profiler。第二道坎是反馈的置信度问题。有次编译器把某个候选判定为“shared memory bank conflict”我照着建议改了布局性能反而更差。回查原始计数器才发现访存冲突只占该 kernel 总 stall 的很小一部分真正的瓶颈在尾数处理分支导致的 divergence。规则映射不能只按某个计数器的绝对数值做判断至少要结合 stall breakdown 里的相对占比。置信度低的方向反馈里应该降权而不是有影没影都当成铁律。第三道坎是编译器的静态分析太容易被误导。有些明显可以向量化的循环因为指针别名分析无法证明无依赖编译器选择保守策略。CAKE 如果完全依赖编译器的反馈就会错过这类优化空间。论文里对这块的处理我理解是动态 profiling 的结果会反过来修正静态分析结论否则静态初筛会不断把好候选误杀。4.3 什么时候别用 CAKE聊完坑想给 CAKE 画一条明确的能力边界。它擅长的是中等复杂度的算子是那种结构上有明显优化空间、但专家不一定愿意花两周去手工打磨的 kernel。反过来如果你是拿它去优化一个 cuBLAS 已经做到极致的大 GEMM那大概率是浪费时间。cuBLAS 背后是十几年积累的调优经验靠几百轮在线反馈想超越它现实的概率很低。还有一种情况也不适合直接用 CAKE——算子本身在算法层面存在问题。比如你写了一个计算复杂度为 O(n^2) 的朴素实现再怎么调 kernel 结构也不可能比 O(n log n) 的算法快。这种问题应该在算法层面解决而不是让 Agent 在实现层面反复试探。论文里虽然没有明说但实践者必须自己具备这种判断力。另外CAKE 的闭环本身是持续消耗 GPU 资源的如果一个 kernel 只跑一次、性能要求也不高或者整个项目只有三五个 kernel 要优化搭建这么一套系统的投入产出比是很低的。先用一次性脚本手调或者干脆用 Triton 自动调优顶上去可能更务实。结尾把 CAKE 这套系统从论文搬到本地的过程中我最深的体会其实是Agent 和编译器的关系不该是“生成者”和“检查者”的对立而更像一组长期配合的搭档。编译器越能把自己的诊断翻译成人话Agent 就越能把计算资源用在刀刃上Agent 越能写出结构多样的候选编译器积累的反馈规则就越完备。我自己后来在做一个融合 bias 的 GELU 算子时只按这个思路做了一个极简版——Agent 生成候选、编译器粗筛、两行关键反馈注入 prompt——收敛速度就比我之前“跑完一轮只给一堆耗时数字”快了一倍还多。对于想试水的朋友我的建议是别急着完整复现整篇论文先把你手头 profiling 输出里最精华的三到五句话抽出来作为 Agent 下一轮生成的系统提示词看看收敛曲线是否立刻变好。如果变好了说明你已经踩在了 CAKE 的核心逻辑上接下来再去铺设完整的反馈管线就水到渠成了。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑