☰
CAKE:AI Agent与编译器协同进化,生成超越专家的GPU Kernel
2026/10/1 21:50:45 网站建设 项目流程

最近在社群看到不少人在讨论"AI 能不能直接写出 GPU Kernel"这个话题。我自己的测试结论很一致:大模型能轻松生成一个"看起来正确"的 CUDA 代码,但离专家手写版往往还差一截,尤其是当算子稍微复杂一点、并发调优要求高一点的时候。而这次看到的论文 CAKE(Compiler-Assisted Kernel Evolution)恰好切中这个痛点,它把 Agent 和编译器放在同一条进化轨道上,让两者互相反馈、互相迭代,最终目标是生成超越专家水平的 GPU Kernel。这篇文章就把我对 CAKE 的整体理解和思考拆开聊聊,适合在做 GPU 编程、编译器优化、AI Agent 应用的朋友作为参考。

1. 背景与问题:GPU Kernel 为什么这么难写

1.1 AI 编程浪潮下的 GPU 内核开发

最近两年,用大模型写代码已经从"玩具级"走向"工程级"。Python 脚本、SQL 查询、甚至部分业务逻辑,实测下来都能省不少时间。但 GPU Kernel 是个例外,它不是普通的顺序代码,而是要在成千上万个线程上做并行计算,同时要考虑访存局部性、占用率、寄存器压力、bank conflict 这些东西。一个 CUDA Kernel 跑起来很容易,跑得快很难,跑得比专家手写版本更快更是难上加难。

我在实际项目里写过各类算子,包括矩阵乘、LayerNorm、Softmax、各种 pooling 和 scan 操作。说实话,哪怕是看起来很简单的"向量加",优化的空间都比想象中大——是否用向量化加载,shared memory 怎么放,线程块大小怎么定,循环展开的程度,这些组合起来就是一个巨大的搜索空间。而 LLM 生成 Kernel 的问题也在于此:它见过很多代码,能拼出一个合理骨架,但它并不清楚目标 GPU 上到底哪种组合最优。所以大家很快发现,纯靠 LLM 读题和凭经验生成,得到的结果大概率只能算"能跑",离"最优"还有距离。

1.2 编译器在这个问题里被严重低估

传统上,写 GPU Kernel 的流程是"写代码 -> 编译 -> 跑 -> 看性能 -> 改"。这个流程的反馈信号极其稀疏:编译器只告诉你"能不能编译过"或者"性能计数器大概多少"。不会有太多人把编译器内部做的优化过程当作可学习的信息,但编译器本身就是最了解底层硬件的组件。

LLVM 或者 NVCC 的优化流水线知道指令选择了什么、寄存器分配情况、循环展开了几轮、向量宽度够不够、哪些访存被合并了。问题是这些信息都被深深埋在中层表示(IR)和调试文件里,普通开发者不会去看,更不要说让大模型去看了。这就造成了一个断层:大模型和编译器各自掌握一部分知识,但没有形成联动。

1.3 "共同进化"到底说的是什么

CAKE 这篇论文的核心观点就是:不要再把 Agent 当成"一次性代码生成器",把编译器当成"只报错不解释"的裁判,而是让 Agent 和编译器在一个循环里共同进化。

类比一下生物进化就很好懂了。Agent 负责变异,它不断生成各种 Keyboard variants;编译器负责选择压力,它把偶发性能优先的候选从衡量中凸显出来。变异和选择交替进行,经过若干代之后,种群的平均水平会越来越高,甚至可能在某几个算子上超过由人类专家留下的特定位实现。这正是 CAKE 这个框架里"共同进化"的含义,Agent 提供探索的广度,编译器提供反馈的精度,两者缺一不可。

2. CAKE 的系统设计与核心组件拆解

2.1 总体架构的四个关键环节

CAKE 的总体架构并不神秘,拆开看就是四个部分的闭环:生成器(Agent)、编译器插桩层、评估器、进化引擎。我用一个表格先做个整体轮廓梳理:

组件核心职责对应我的理解
Agent(生成器)根据反馈生成/修改 Kernel 候选代码不是一次性写代码,而是"听取反馈再改"
编译器插桩层把编译过程的优化信息、错误信息结构化导出把"编译器知道但没说出口的秘密"暴露出来
评估器在真实硬件上跑,得出正确性和性能指标唯一能拍板"是否超越专家"的角色
进化引擎选择、保留、变异候选,维持种群多样性防止过早收敛到局部最优

这四个组件缺一个都跑不起来。没有 Agent 就没有探索能力,没有编译器插桩就是盲人摸象,没有评估器就没法判断好坏,没有进化引擎就会在同一个死胡同里反复纠结。

2.2 Agent 端:不止猜代码,更要学会读取反馈

我前面说过,普通 LLM 生成 Kernel 的问题在于单轮生成、没有反馈。CAKE 里面的 Agent 被设计成一个能持续学习的角色:每一轮生成后,它会收到来自编译器和评估器的反馈,把反馈消化之后,在下一轮生成时调整策略。

这里的关键点是"反馈的消化方式",不能简单地把编译错误信息复制粘贴给 Agent 就完事。CAKE 的设计中,Agent 需要读到的反馈至少包含几类:

  • 编译错误和警告的精炼摘要
  • 优化 pass 的触发情况和未触发情况
  • IR 层面发生的变化摘要,比如某个循环被自动向量化了、某个访存被合并了
  • 编译产物和运行时统计,例如寄存器用量、Shared Memory 使用率、实际执行时间

我把这类信息统称为"结构化的编译反馈"。它的好处是让 Agent 知道自己的改动在编译器眼里产生了什么效果,从而在下一次变异时朝着正确的方向走。比如 Agent 发现上一轮因为一个数组没有对齐导致编译器无法生成向量化访存,它下一轮就会尝试加对齐属性,或者改循环结构。

这里想补充一个我在本地实验里的体会:把编译器的原始错误信息直接丢给大模型,效果很差。原因是原始信息非常碎,有的 slot 可能有几百行,噪声很大。CAKE 的思路是把这些信息压缩成"有语义的要点"再给 Agent 读,相当于帮 Agent 提炼了编译器的诊断结果。这个细节我觉得比框架本身更值得借鉴。

2.3 编译器端:让内部知识外化成文本特征

一般开发者接触到的编译器,就是一个"黑盒翻译器"——代码进去,二进制出来,错了就报个错。但在 CAKE 的设计里,编译器被赋予了一个新角色:反馈发生器。为了实现这个目标,需要在编译器上做插桩,通常是在 LLVM/Clang/NVCC 的优化流水线上增加 log 输出,把 IR 的变化过程记录下来。

这样做的好处是:Agent 能看到编译器的"推理过程"。举个例子,编译器在做循环展开时,它可能会评估展开因子是 4 还是 8 对指令调度的好处。普通开发者只看到最终性能数字,但 CAKE 中的 Agent 能看到"展开因子 8 导致寄存器压力过大,spill 增多"这样的中间信息。这已经超出了人类手工调优时能获取的信息量,等于给 Agent 配了一副显微镜。

当然,这个插桩层也有实现成本。不同编译器版本、不同 GPU 后端,日志格式可能完全不同。论文里的做法是把这些信息统一格式化为文本摘要,以便 Agent 用语言模型理解。我的理解是,这个插桩层越通用越好,因为 GPU 架构迭代很快,如果每次换硬件都要重写反馈逻辑,那这套系统的维护成本就非常高了。

2.4 进化与选择:如何避免在优化空间里迷路

进化引擎听起来高大上,实际上做的是非常决策性的事:每一轮产生一批候选 Kernel,到底谁留下、谁淘汰、以什么变异策略产生下一代。

这里有个我不止一次踩过的坑:如果每一轮都只保留性能最好的那个候选,很容易陷入局部最优。比如矩乘计算,可能某个候选用了很激进的 tiling 方式,在某个矩阵尺寸下表现好,但稍微换一下输入尺寸就崩了。CAKE 的做法是维持一个候选集合,保留一定程度的多样性,让最终的种群既包含当前最优解,也包含一些"看起来不是最优但保留了不同优化思路"的个体。

评估器的标准也要仔细设计。除了运行时间,必须同时考虑正确性,否则 Agent 很容易学歪。我见过太多案例:某个 Kernel 跑得极快,但算出来的结果是错的,因为它在快速路径里跳过了边界处理。浮点误差也是另一个坑,GPU 上运算顺序变化会带来误差,判断正确性时不能直接用等值比较,要用容差来判定。

所以 CAKE 的进化引擎里的选择压力是"多维度的正确性 + 性能 + 收敛速度",不是简单地比谁耗时短。

3. 效果评测:怎么证明"超越专家"不是吹的

3.1 评测基准的选择逻辑

CAKE 想让别人信服"超越专家",就不可能只在某一个算子上表现好。评测基准得覆盖 GPU Kernel 的主要类型:访存密集型(比如 vector add、copy、stencil)、计算密集型(比如 GEMM 矩阵乘、卷积)、访存-计算混合型(比如 softmax、layernorm)以及带 scan/前缀和这类有数据依赖的算子。

我特别关注的是,基准里必须包含"专家手写优化版本"作为 baseline。这个 baseline 通常来自 CUB、CUTLASS 这类经过工业级调优的库,或者来自论文作者团队手写的优化版本。如果只是拿"没优化过的朴素实现"来比,那超越专家的门槛就太低了,参考价值不大。

另外,还要看评测显卡的范围。单张卡上表现好不代表所有卡都表现好,因为不同 GPU 的 SM 数量、显存带宽、寄存器文件大小、L2 缓存行为完全不同。CAKE 的实验如果能覆盖两三代硬件,那说服力就强很多,至少说明这套框架不是针对某个硬件专门重写的。

3.2 超越专家到底体现在哪个维度

"超越专家"这个词很容易被误解成"所有算子都比专家快"或"每个尺寸都最快"。从我看到的论文实验逻辑来理解,更合理的表达是:

  • 在大部分测试算子/尺寸上,CAKE 生成的 Kernel 达到或接近专家版本性能
  • 在若干特色算子和规模下,通过 Agent 和编译器的组合变异,发现了专家平时不会想到的优化组合,从而明显超过专家版本
  • 在自动化和鲁棒性上,系统能在无人干预情况下自动搜索和收敛

这种"局部超越 + 整体追平"的结果,才是现实中有价值的目标。毕竟专家 Kernel 库是几代人调优的成果,一个大模型加编译器进化系统能在一个晚上搜索到同等水平,这本身就是革新。

这里还想提一句:论文实验里展示的"超越"很多是百分比提升,比如 10%-30%。但实际工程里,不同算子的时间占比不一样,如果一个算子只占整个模型的 2%,那即便提升 30%,对端到端影响也不大。所以在解读结果时,不能只看相对提升率,还得看算子本身的热度。

3.3 成本与资源约束:工程化最绕不开的现实问题

Agent + 编译 + 运行,这个循环看起来很美,但每一次迭代都是有成本的。Agent 调用需要 GPU 算力来跑大模型推理,每次 Kernel 要重新编译,编译完还要在真实 GPU 上跑评测。如果一个算子需要搜 100 轮,每轮 20 个候选,那就是 2000 次编译和运行。

我自己在做一个简化版框架的时候,最初的版本非常粗糙:一轮生成一个候选,编译一次,跑一次。结果发现大量时间浪费在等待上,尤其是集中在编译过程中。后来我学到的经验是"批量编译,充分复用增量信息",把多个候选的 Kernel 放进一个翻译单元,编译器就能只花一次开销处理多种变体。CAKE 应该也有类似的考虑,否则进化速度跟不上大模型推理和编译的时间成本。

所以,CAKE 的价值不仅在于找到好 Kernel,也在于它把整个搜索过程的成本控制在一个可以接受的范围。如果搜一个算子要花一周,那还不如让专家手写三天。只有当搜索成本大幅降低时,这套系统才有工程化的空间。

4. 从 CAKE 到我们自己的实践:怎么落地一个简化版循环

4.1 最小闭环:Agent + 脚本 + 编译报告

我没有办法直接复制 CAKE 的完整代码,但里面的核心思想完全可以用一个最小闭环在本地跑通。说白了就是三步:让 Agent 生成 Kernel,用脚本编译并运行,把结果反馈给 Agent 再生成。

这里的"最小闭环"我用 Python 脚本就能搭起来,循环里做四件事:

  1. 调用大模型的 API,给定算子描述和上一轮反馈,生成一个或多个 CUDA Kernel 候选
  2. 把候选写入 .cu 文件,用 nvcc 编译成可执行文件,同时收集编译器的 warning 和优化日志
  3. 执行可执行程序,用固定输入跑多次,取最小耗时,并校验输出结果
  4. 整理成"反馈文本",注入下一轮的 prompt

代码框架大概是这样,实际部署时可以按自己的情况替换:

import subprocess, json, re from openai import OpenAI client = OpenAI(base_url="your_llm_endpoint", api_key="your_key") def build_feedback(build_log, run_result): lines = [] if build_log and "error" in build_log.lower(): # 提取出错行和错误类型 lines.append("COMPILE_ERROR: " + extract_error(build_log)) elif run_result and run_result.get("passed"): lines.append(f"OK elapsed={run_result['time_ms']:.3f}ms") lines.append("Compiler notes: " + summarize_compiler_notes(build_log)) else: lines.append("RUNTIME_ERROR: " + str(run_result.get("error"))) return "\n".join(lines) prompt_template = """ 你是一位 GPU Kernel 优化专家。请根据下面的基线代码和上一轮的编译/运行反馈,改进这个 CUDA Kernel。 要求:保持正确性,减少运行时间。只输出 CUDA 代码,不要解释。 算子说明:{spec} 上一轮反馈:{feedback} 请输出改进后的完整 CUDA kernel: """ current_code = baseline_cuda_code for round_idx in range(15): prompt = prompt_template.format(spec=op_spec, feedback=last_feedback or "无,这是第一轮") resp = client.chat.completions.create(messages=[{"role": "user", "content": prompt}], model="your_model") current_code = extract_code(resp.choices[0].message.content) write_file("kernel.cu", current_code) build_log = run(["nvcc", "-arch=sm_80", "kernel.cu", "-o", "kernel"]) run_result = run_kernel_and_validate("kernel", expected_output, threshold=1e-5) last_feedback = build_feedback(build_log, run_result)

这个循环本身不复杂,真正要想清楚的是反馈格式。我一开始直接把 nvcc 的完整 stderr 丢进下一轮 prompt,结果模型开始纠结一些无意义 warning,效果很差。后来学乖了,用正则把错误提取成几行关键信息,效果立刻提升。这和论文里讲"把编译器信息提炼成特征"是同一个道理。

4.2 反馈的格式、粒度与信噪比

如果要给这个循环排优先级,我觉得反馈设计的优先级高于生成策略。因为 Agent 再有想象力,如果接收到的反馈是"编译失败"这种没头没尾的信息,它也只会随机乱改。

我在实践里总结出几条可以抄的规则:

  • 编译错误必须带行号和错误类型,比如"第 24 行:变量未声明"比"编译失败"有用得多
  • 运行结果必须区分错误类型,段错误、浮点异常、结果错误要分开描述
  • 性能数据要多次运行取中位数或最小值,减少噪声干扰
  • 编译器优化日志要挑"关键变化"说,不要全文复制
  • 如果 Agent 连续三轮没有改进,把种群内其他候选的信息也一并送给 Agent 作为参考

这最后一条很有意思。我发现一个现象:Agent 在单条路径上如果连续失败,往往会陷入重复修改同一个地方。这时给它看"种群里的另一个候选为什么更快",等于给它新的搜索方向。这其实就是 CAKE 进化引擎里"多样性保持"在 prompt 层面的体现。

4.3 正确性与浮点容差:容易被忽略的细节

在代码生成循环里,性能指标很显眼,正确性验证却很容易做糊。GPU 上的并行规约、矩阵乘等运算,浮点数计算顺序一变,结果就会有微小差异。如果验证脚本直接用相等比较,会有大量误判。

我现在的做法是提前做好一个"参考实现",用 double 精度在 CPU 上跑一遍,拿到参考输出。GPU 结果和参考输出做相对误差比较,容差放宽到 1e-4 或者 1e-5,同时处理 NaN 和无穷大。这个阈值可以根据算子的数值特性调整,但切忌用正好相等的判断。

另外还有一个冷门细节:随机数的种子。如果验证时使用了随机输入,每次测试要用同一个种子,否则即使 Kernel 本身没问题,之前失败的候选也可能在下一轮"突然通过",导致反馈信号不可靠。

4.4 工具选型:Clang 的优化报告比想象中有用

CAKE 类系统的关键在于从编译器拿到反馈。除了修改编译器源码,自己动手时其实还有更轻量的路子。Clang/LLVM 本身就支持-Rpass系列选项,可以在编译时把优化 pass 的触发信息打印出来。

我用过一个命令组合,效果很好:

clang++ -O3 -Rpass=loop-vectorize -Rpass-missed=loop-vectorize -Rpass-analysis=loop-vectorize -c kernel.cu -o /dev/null 2>&1

这会把哪些循环被向量化、哪些循环没有向量化、为什么没有向量化(比如"无法证明无别名")都打出来。这些信息结构化成文本后,Agent 就明白"下一轮要把指针加 restrict"或者"要加对齐 hint"。这比我最初直接把 nvcc 的标准输出丢给模型靠谱太多了。

如果你用的是 N 卡的 CUDA 工具链,nvcc的--ptxas-options=-v可以额外打印寄存器用量和 shared memory 使用量,也是很好的反馈源。比如它的输出里显示某个 kernel 用了 255 个寄存器且 spill 了很多,那 Agent 下一轮就知道要降低并行度或者减少每个线程的工作量。

5. 落地过程中的常见误区和避坑指南

5.1 从"我踩过的坑"里整理出的对照表

前面讲了具体实现,这里把我在本地实践 CAKE 简化版时遇到的最典型问题列成一张速查表,方便大家对照自查:

误区典型表现后果正确做法
反馈不结构化把 nvcc 完整 stderr 扔给模型Agent 被无关 warning 干扰,乱改用正则提炼"行号+错误类型+关键信息"
只测性能不验正确性某候选运行时间最短但算错Agent 会把错误结果当成"正确的最优"必须用参考实现 + 容差对比
每轮只保留最优个体种群多样性归零容易收敛到局部最优同时保留前几名的不同思路候选
不考虑编译时间每轮单候选独立编译大量时间浪费,迭代太慢多候选合并到一个编译单元,用增量编译
使用固定输入尺寸只在一个 shape 上优化换个 shape 性能骤降多种输入尺寸轮流测试
忽略浮点误差结果对比用等值比较误判正确性,反馈不可靠固定随机种子,使用 1e-4 级相对误差
编译器日志冗余把优化 pass 日志全文输出Prompt 过长,关键信号被淹没只挑向量化、寄存器、占用率等摘要信息

这里面最核心的还是反馈信噪比。我意识到一件事:AI 生成代码的质量,很大程度上取决于你给它提供的"上下文"质量。编译器内部的优化日志本来是为工程师设计的,直接当 prompt 的一部分用是不可行的,因为干扰太多。做一层摘要和过滤,本质上就是把编译器知识翻译成 Agent 能理解的语言。

5.2 算力消耗与超时控制

Agent + Kernel 搜索循环真正跑起来后,最大的现实问题是超时。某一次编译可能因为错误的模板膨胀变得极慢,某个生成的 Kernel 可能因为极端 block 配置导致 GPU 驱动超时。我在实践里给编译、执行分别设置了超时时间,编译超时 60 秒就杀,执行超时 5 秒就杀。

如果用的是公有云 GPU 实例,还要注意计费和并发问题。建议本地调试时用最小参数量模型验证循环逻辑,确认反馈有效之后,再切到大模型做正式搜索。直接上大模型 + 大批量候选,很容易把资源耗尽而且难以定位问题。

5.3 泛化性:一个算子的经验如何迁移

写了一个优化循环,不能只在一个算子上有效就欢呼。我发现让 Agent 在不同算子之间做迁移学习,是 CAKE 这套思路里很好用的能力。比如 Agent 在优化 GEMM 时学会了"对 shared memory 做 padding 避免 bank conflict",这个经验在优化卷积时同样有效。

实现方式也很简单:把历史有效的反馈 + 优化动作组合成"经验案例",在下一个新算子的第一轮 prompt 里附上这些案例。这等于给 Agent 建了一个"可复用的调优记忆库",虽然比 CAKE 完整版朴素一些,但已经能解决很多重复劳动的问题。

6. 从更广的角度看 CAKE 的意义

6.1 对 Agent 开发模式的影响

CAKE 对 Agent 开发者的启发比单纯"生成 GPU 代码"更大。它把 Agent 从一个"对话式代码生成器"变成了"可进化的优化系统"。这种 AI Agent 的开发模式,跟现在很多 agent 框架里的 ReAct 循环是一脉相承的,但其中的反馈设计和工具调用深度,要比大多数偏向网页搜索和 API 调用的 agent 项目扎实得多。

我理解 CAKE 的中心思想不是"用 Agent 替代编译器",而是"Agent 和编译器之间建立高频双向通信"。这个思想可以迁移到所有"Agent 生成代码"的场景:数据库查询优化、CPU 内在函数优化、甚至 Makefile/CMake 配置优化。只要编译器能够提供结构化反馈,Agent 就有可能在闭环中逐步逼近最优解。

6.2 编译器作为一个观察仪器

这几年大家讨论 AI 编译器时,方向大多集中在怎么用编译器来优化神经网络算子。但 CAKE 提醒我们,编译器除了做优化,还有一个重要功能是观察——观察代码如何被翻译成硬件指令,观察哪些优化假设成立,观察寄存器压力和访存调度。

这种"编译器作为观察仪器"的思路,我认为会是未来 AI 辅助编程的重要方向。如果编译器能把它的推理过程以更结构化的方式暴露出来,那么 Agent 就能理解代码在硬件层面的真实表现,而不只是停留在语义层面。

6.3 对未来编程工具链的展望

把 CAKE 的思路推到极限,可以看到一种未来的可能:开发者只描述算子意图,Agent 在编译器引导下自动探索最优实现,最后生成的代码不仅经过真机验证,还有完整的优化轨迹记录。人类专家的角色从"写码者"变成"设目标和审核者"。

这种前景很诱人,但离真正成熟还有很长的路。当前的 GPU 生态还分散在 CUDA、HIP、OpenCL、WebGPU 等多种编程模型上,编译器反馈格式也没有统一标准,更不用说 Agent 本身的可复现性和稳定性问题。但方向已经很明确,值得持续观察。

7. 最后的落地经验

CAKE 论文对我来说最大的价值,不是让我拥有一个一键生成超优 Kernel 的工具,而是让我重新理解了 Agent 和编译器的关系。在此之前,我总把两者分开看:Agent 负责"创意",编译器负责"执行"。CAKE 的实验结果则说明,把编译器的内部反馈暴露给 Agent,让 Agent 的创意在硬件知识引导下搜索,系统的能力上限会远高于任何单一方。

如果你想尝试,我的建议是不要一上来就复刻整套 CAKE。先用一个小算子(比如 Softmax 或者 LayerNorm),搭一个我前面描述的 15 轮循环,看看 Agent 能不能在编译器反馈的帮助下稳定超过 baseline。等这个循环稳定了,再引入多候选、种群保留、编译器插桩,逐步丰富系统的能力。

我在实际操作中最后的体会是:这个系统最大的瓶颈不是模型不够聪明,而是反馈回路设计得不够好。模型的能力是固定的,但只要你把反馈做得结构化、及时、信噪比高,模型的输出质量就会肉眼可见地提升。哪怕不写 GPU 代码,把这个原则用于日常的代码生成工作流,也会觉得"AI 编程"这件事变得更靠谱。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询