
如果你这几年一直在跟 GPU 内核打交道肯定能明显感觉到一个趋势AI 加速器越来越“多”了。NVIDIA 的 A100、H100 还没吃透AMD 的 MI300 又来了更别说各家云厂商和芯片公司手里那批架构各异的 NPU 和 ASIC。每出现一个新硬件算子库就得多维护一套用 CUDA、ROCm 或者私有 DSL 写的内核。这活有多费劲亲手调过 GEMM 的内核工程师都懂很多时候不是“写不写得出来”的问题而是“写出来跑不跑得快”的问题。Meta 这篇 KernelEvolve 论文做的就是把这件“写内核”的事交给多个 Agent 协作完成。整体思路很好记Planning Agent 负责做方案Coding Agent 负责写代码Review Agent 负责在真实硬件上跑测试、测性能然后把性能数据反馈回去继续改形成一个自动化闭环。论文展示的流程里开发周期可以从几个月压缩到几天性能能够逼近甚至追平手写算子库。这篇解读除了梳理 KernelEvolve 的架构和核心逻辑之外我更想带你把它的思路搬到自己项目里——哪怕是跑一个大模型自动生成 CUDA 内核的 demo也会发现这套“规划—实现—审查”的多 Agent 工作流特别适合想做 AI 辅助算子开发、又不想只停留在“问 ChatGPT 要一段代码”层面的朋友。1. 异构AI加速器内核开发的困境为什么非要用Agent在讲 KernelEvolve 之前得先把问题摆清楚。它不是一个“为了用 Agent 而用 Agent”的炫技项目而是被异构加速器内核开发的现实成本逼出来的方案。1.1 算子内核的手工开发成本到底有多高我先给不常写内核的朋友讲个背景。所谓“内核”在 GPU 编程里通常指一段在设备端执行的函数比如矩阵乘、卷积、归一化这些算子都要写成能在 GPU 上千个线程上并行运行的内核代码。AI 加速器的软件栈里算子库是灵魂没有高效的矩阵乘内核训练和推理的性能就上不去。手工开发一个算子内核完整流程大致是先根据数学定义和硬件特性确定分块策略再写 CUDA 或 ROCm 代码然后编译、调试、查错误最后进入最耗时的阶段——性能调优。调优不是改一个参数就好而是要同时考虑线程块大小、共享内存占用、寄存器分配、访存模式、指令级并行度、流水线开销这些东西。以矩阵乘为例一个最普通的 GEMM 内核新手写出来可能在硬件峰值性能的 5% 以下经验丰富的工程师调上几周能到 80%而要逼近 cuBLAS 那样的商用实现往往需要数月的迭代。更麻烦的是这套经验基本是“不可移植”的。你在 NVIDIA 的 H100 上为某种 tile 大小调出来的最优配置换到 AMD 的 MI300 上可能完全不奏效因为寄存器文件大小、共享内存容量、访存带宽、线程调度方式全都不一样。这就是所谓的“异构”难题。AI 加速器的异构性不只是芯片型号多还包括每个型号背后的编程模型差异。一个算子要在不同硬件上各写一版每一版都要重新优化。算子种类还在快速增长Flash Attention、各种融合算子、新的激活函数都要有人去实现。算子×硬件×精度×shape 的组合几乎是爆炸式的完全靠人力堆成本高得离谱。1.2 编译器、自动调优和单模型写代码为什么都差一口气既然手工开发贵那业界早就想过用自动化方案来缓解。过去这些年主要有三条路线但每一条都有明显短板。第一条路线是 DSL 和编译器方案代表是 Triton 和 TVM。它们把用户从 CUDA 的细粒度细节里解放出来让你用类 Python 的语法描述算子的计算逻辑再由编译器生成底层代码。Triton 在很多时候能写出比手工 CUDA 慢不了太多的内核而且开发效率高。但问题在于编译器生成的代码再聪明也是在“通用策略”的框架里做选择在高手眼中会丢掉不少硬件特异的优化空间。真要追求极致性能到头来还是得有人直接写 CUDA。第二条路线是自动调优Autotuning。TVM 里的 AutoTVM、Ansor 等工具会生成一批候选内核然后在目标硬件上试跑挑出性能最好的。这条路对参数搜索很有效但它本质上搜索的是“已有模板里的参数”比如 tile 大小、展开因子。它不能凭空发明一种新的数据流方式也不能把一个算子从数学上重构成更高效的等价形式。第三条路线是大模型辅助生成代码。让 LLM 直接写 CUDA 确实可行在一些简单算子上效果还不错。但我在实际测试中的感受是单轮生成的代码往往只是“能编译”离“高性能”差得很远。而且大模型会一本正经地编造不存在的 API运行之后错误反馈还得靠人手动捞出来喂回去。整个过程缺少一个自动化的、以真实硬件性能为准的反馈闭环。KernelEvolve 恰好把这三条路线的优点拼在一起用大模型来设计、用代码生成来写实现、用真实硬件评测来做判断并且通过多个 Agent 的分工和反馈循环把这几步串成了一个自动化流水线。它不是想替代编译器或自动调优而是把这些工具全部纳入一个由 Agent 主导的循环里让它们协同工作。这一点很关键它不是又一个“生成内核的工具”而是一个“管理内核生成流程的框架”。2. KernelEvolve的架构拆解三个Agent是如何协作的KernelEvolve 的核心是一个多 Agent 系统。论文里把它组织成一个代理团队每个成员各司其职。整个流程听上去像一个软件团队在开发内核只是团队成员全是 AI。2.1 Planning Agent把“写什么内核”变成“怎么写”的设计师Planning Agent 的角色相当于架构师。它接收的是任务描述比如“请为矩阵乘算子实现一个高效的 CUDA 内核矩阵维度 M4096N4096K4096数据类型为 FP16”。这个 Agent 要做的事情是在动手写代码之前先产出一份实现策略。它输出的东西通常包括几个层次的决策分块策略是选 128×128 还是 64×64每个线程块里放多少个线程数据加载用多大的向量宽度共享内存里怎么切分数据 tile累加器用 FP32 还是 FP16主循环怎么安排流水线来隐藏访存延迟。这一步很容易被低估但它恰恰是整套流程里最见功力的环节。直接让模型生成最终代码模型往往会“不管三七二十一先写个能跑的版本”结果就是一堆平庸的代码。但先让它写方案模型就会被迫去检索自己学过的 cuBLAS、cutlass、FlashAttention 这些高性能实现的知识产出一个有根据的设计。从我的理解来看Planning Agent 的另一个价值是它输出的策略文本可以作为后续 Coding Agent 的“语境锚点”。如果后续生成的代码性能不达标Review Agent 反馈回来的信息可以和最初的策略做对比这样能更精准地判断问题到底出在“策略选错了”还是“实现没跟上策略”。实际操作中把这份方案做得越结构化越好。与其让模型用一段自然语言描述方案不如让它按照固定格式输出比如“Tile 大小128×128线程布局8 warp向量化宽度float4共享内存策略双缓冲主循环累加器预加载”。这样做的好处是后续环节可以通过自动化脚本解析这些字段把它组装到提示词里喂给 Coding Agent整个流程的可编程性高很多。2.2 Coding Agent把设计方案翻译成可以在硬件上跑的内核Coding Agent 的角色就是实现工程师。它的输入是 Planning Agent 产出的设计策略输出是一段完整的、可编译的内核代码。在 KernelEvolve 里Coding Agent 并不是简单地生成一个 .cu 文件就完事。它需要保证代码风格和接口约定一致比如 kernel 的签名、指针参数顺序、内存布局约定、精度类型转换这些都必须和测试 harness 能对上。这也是多 Agent 流程里特别容易出现 bug 的地方——设计方案写得很好代码生成得也对但接口对不上无法编译。这一环能不能成功很大程度上取决于 Planning 阶段输出的策略是否足够具体。我举一个简单例子如果方案只写“使用共享内存优化”Coding Agent 每一步都得自己猜生成结果就不可预测但方案写到“每个线程块负责输出 128×128 的 tile线程块内组织为 8 个 warp每次循环迭代从全局内存加载 128×32 的 A 分块和 32×128 的 B 分块到共享内存”代码生成器基本上就是在做“翻译工作”确定性高很多。Coding Agent 的工作还会和编译过程耦合。生成的代码不会被当作最终答案直接接受而是要交给下游的编译验证环节。编译错误信息会被反馈回来让 Coding Agent 修正。这就像一个真实工程师在看到编译器报错后改代码而不是一次性写完就完事。这个“编译反馈循环”通常要跑很多轮是耗时的主要部分所以在设计整个流程时最好提前把编译时间算进成本里尤其是大 kernel 文件编译一次可能要几十秒到几分钟。我在跑类似流程时发现Coding Agent 最容易犯的错有两类一类是在不应该用递归或动态分配的地方用了导致 GPU 代码没法运行另一类是混淆了 CPU 端代码和 GPU 端代码的职责比如在 device 函数里调用 printf 或者 host 函数。这些错误在反馈提示词里写清楚原因模型下一轮基本都能改对。2.3 Review Agent用真实硬件数据推进优化的“裁判”Review Agent 是整套系统里最接近“QA 和性能测试工程师”的角色。它的工作方式是拿到 Coding Agent 生成的代码后把代码编译成可执行文件在真实硬件上跑基准测试同时做正确性验证然后把评测结果整理成反馈返回给 Coding Agent。论文展示的流程里这一步不是跑一次就结束而是一个循环。Review Agent 会衡量当前 kernel 和参考实现比如 cuBLAS或目标性能之间的差距把差距转化为具体的优化建议。比如“当前性能只有峰值带宽的 60%主要瓶颈可能是共享内存 bank conflict建议调整数据布局”。这里面的关键设计是反馈信息必须以真实测量数据为准。很多 AI 生成代码的方案效果不行就是因为缺少一个“铁面无私的裁判”——代码生成了运行效果没人管或者得靠人去判断。Review Agent 把这个判断自动化了并且把判断结果翻译成下一个循环可用的指令。为了让裁判有效测试 harness 需要认真设计。测量要能区分“正确性错误”“编译错误”和“性能不达标”三类问题因为这三类问题对应的反馈策略是完全不同的。正确的做法是先做正确性验证再测性能正确性不过关就直接返工不用浪费算力测性能。性能测量上可以用 CUDA event 计时重复多次取中位数或最小值尽量排除系统调度波动的影响。Review Agent 的一轮反馈信息最好按照固定格式输出例如编译是否通过、正确性是否通过、运行时间、达到参考实现的百分比、瓶颈分析建议。这样后续无论是让 Coding Agent 修改还是让整个系统记录日志、判断是否该停下逻辑都非常清晰。多跑几轮循环之后你还能从这些结构化记录里看出模型在哪些类型的算子上一轮就能做对哪些要磨很多轮这也是评估这套系统价值的重要数据。3. 把KernelEvolve式工作流用在自己的项目里论文的架构说起来不复杂但想真正落到自己的项目里需要解决不少工程细节。这一节我从实操角度讲一套最精简的复现方案。我自己用这套思路跑过类似的多 Agent 内核生成循环踩了不少坑下面这些步骤是可以直接照着做的。3.1 最小可复现实验的硬件、软件和算子选型先别一上来就挑战 Flash Attention 那种复杂的融合算子从壁垒最低的 GEMM 开始。为什么选 GEMM因为它公式简单、资料多、参考实现好找而且性能瓶颈非常有代表性——既要考虑访存优化又要考虑计算优化还能体现分块策略、共享内存使用这些核心技巧。硬件方面手头有一块 NVIDIA 的消费级卡就行比如 RTX 3090、4070 或者 A100 更好。KernelEvolve 论文里的实验覆盖了多种加速器包括 NVIDIA 和 AMD 的 GPU我们复现时不需要那么全先用一个平台把循环跑通。软件方面主要是 CUDA 工具链以及支持 OpenAI 接口的大模型 API。算子选型建议固定一个具体场景别用“大规模矩阵乘”这种模糊描述。比如“M4096N4096K4096FP16 输入FP32 累加”。这样后续所有反馈信息里的性能数据才有参照。参考实现可以直接用 cuBLAS 的 sgemm/hgemm注意要在同样输入规模和数据类型下对比否则性能结论没有意义。整个项目建议用 Python 写 orchestration 脚本调用 CUDA 工具链做编译运行。我用下来最顺手的结构是project/ ├── generated/ # Agent生成的kernel文件 ├── benchmark/ # 基准测试和正确性验证的C/CUDA代码 ├── prompts/ # 提示词模板 ├── logs/ # 每轮反馈记录 └── run_pipeline.py # 主流程Agent循环调度这个结构把“生成”“测试”“记录”三块分离方便排查是 Agent 的问题还是 harness 的问题。我自己刚开始的时候图省事把所有代码塞在一个脚本里结果日志和生成文件混在一起迭代了几轮就分不清某次性能数据对应的是哪个版本的代码了。分离目录后问题一下子清晰很多强烈建议从一开始就维护好每次生成的代码版本和测试结果。3.2 Agent协作循环的搭建与核心接口设计整个流程的主循环可以用一段伪代码来表达strategy planning_agent(task_description) for i in range(max_iterations): kernel_code coding_agent(strategy, feedback) write_code_to_file(kernel_code, fgenerated/kernel_v{i}.cu) result run_benchmark_and_validation(fgenerated/kernel_v{i}.cu) log_result(result, flogs/run_v{i}.json) if result[correct] and result[performance_ratio] target: break feedback review_agent(strategy, kernel_code, result)这段伪代码对应的是最核心的流程。编排脚本要管好几件事把当前迭代次数、已尝试过的优化方向、上次的失败原因都记录在上下文里调用 LLM 接口时控制好上下文长度因为多轮反馈之后提示词会越来越长每一步都要加超时保护和错误重试机制。接口设计上最重要的是保证 Agent 的输入输出都是结构化的。LLM 天然输出自然语言但我们希望流程里传递的关键信息可以用程序处理。比如让 Planning Agent 输出一个 YAML 格式的方案让 Review Agent 输出一个 JSON 格式的评测报告。你可以在提示词里要求“必须输出合法 JSON”然后在代码里做解析校验解析失败就重试。如果 Agent 输出的格式非法直接让它重新输出不要在原输出上做启发式解析那样会把错误惯性带进后续流程。另一个重要的设计决策是谁来承担“正确性验证”和“性能测试”职责我建议这部分完全不要交给 LLM而是用固定代码实现。Review Agent 只负责解读固定代码返回的结果提出优化建议不负责编造测量数据。这样可以避免 Agent 在中间环节引入混乱。毕竟评测逻辑需要稳定可靠而大模型的输出天然带随机性把裁判的“判罚标准”交给裁判自己定就容易出问题。3.3 提示词模板和反馈信息的设计技巧提示词是多 Agent 系统里最能体现工程经验的部分。我总结出几个关键点系统提示要确立角色任务提示要给出足够具体的约束反馈提示要把失败原因转换成可操作的修改指令。给一个我自己在用的简化模板供参考你是 CUDA 内核优化专家。现在需要实现一个高效内核。 任务要求 - 算子矩阵乘 C A * B - 尺寸M4096, N4096, K4096 - 数据类型FP16 输入FP32 累加 - 不允许调用 cuBLAS 等库函数必须用 CUDA C 实现 - 请先给出实现策略包括 tile 大小、线程块配置、共享内存布局、 向量化宽度和主循环流水线设计然后用完整代码实现。 如果这是修改请求你会收到上一轮的性能反馈。请基于反馈修改策略和代码。 反馈格式如下 - 编译是否通过 - 正确性是否通过 - 运行耗时 - 性能相对参考实现的比例 - 可能瓶颈分析这个模板有几个细节要注意一是明确禁止调用库函数否则 Agent 会走捷径直接调 cuBLAS看起来性能无敌实际上完全跑偏二是要求先给策略再给代码这样后续反馈能定位到策略层的问题三是反馈格式固定让 Agent 知道每次修改要沿着什么方向去思考。当 Review Agent 返回性能不达标的反馈时反馈内容的质量决定下一轮代码能不能“进化”。举个例子与其写“性能不理想请优化”不如写“当前内核耗时 12.3ms参考实现 3.1ms性能约 25%。基于 roofline 模型分析当前访存受限建议优先优化数据复用尝试更大的 tile 和双缓冲”。前者等于没说后者给定了明确的优化方向。另外建议保留每一轮的反馈和代码快照。多 Agent 系统跑的时间长了你会想分析“为什么这个算子 3 轮就达标那个算子 20 轮还不达标”。没有历史记录这种复盘根本做不了。我在实际中会把每轮完整的输入输出、评测数据、耗时都存成 JSON 文件后续分析方便很多调试 Agent 疯狂循环的时候也能定位问题出在哪一环。4. 实操中最容易踩的坑来自真实复现的经验把多 Agent 系统真正跑起来之后真正的麻烦才开始。这一节整理的是我在复现过程中遇到的高频问题每一个都是真金白银换来的经验。4.1 基准测试数据飘忽不定优化方向全乱多 Agent 循环里最痛苦的场景就是明明代码没改性能数据却在上下跳动。上一轮测得 8ms这一轮同一个代码测得 9.5ms那么 Agent 收到反馈后会开始乱改把一个本来没问题的实现改坏。这类波动的来源很典型一是 GPU 核心频率动态变化尤其是消费级卡温度上去之后频率会降二是后台其他任务抢占资源三是基准测试本身的设计有问题把数据传输时间也算进去了。解决办法有几个。测量 kernel 执行时间时用 CUDA event 包住 kernel launch不要用 end-to-end 计时。跑多次取最小值或中位数不要取平均值因为平均值容易被偶发卡顿拉高。条件允许的话可以用 nvidia-smi 锁定 GPU 频率虽然这会牺牲一部分性能表现但换来的是测量稳定性。具体锁定命令大致是这样nvidia-smi -lgc 1500,1500锁定之后测量数据会稳定很多。等调试完成、要跑最终报告时再解除锁定。我在跑循环时还会要求 Review Agent 每次连续测三次把三次结果都记录到反馈里。如果一个版本的代码测量数据最小值和最大值差距超过 15%直接判为“测量不稳定”要求重新跑测试而不是基于不可靠数据让 Coding Agent 修改。这个策略极大减少了无效迭代。4.2 正确性验证不能只信Agent自己的判断Agent 生成的代码可能会用一些“看起来正确”的方式通过验证但实际数值有问题。比如矩阵乘的累加顺序不同结果会有浮点误差如果参考实现用 FP32 累加Agent 生成的代码为了省寄存器偷偷用 FP16 累加误差可能超出容差范围。最稳妥的做法是在正确性验证时和多组参考实现对比包括朴素 CPU 实现和 GPU 上的高精度参考库。验证的测试用例也要覆盖边界情况全零矩阵、单位矩阵、大规模数值、包含 NaN 和 Inf 的情况。特别是像 Softmax、LayerNorm 这类归一化算子很大概率出现上溢出导致的 Inf 或 NaN必须提前做好防护。正确性和性能的验证顺序很重要。我踩过一个大坑某轮 Agent 生成的代码正确性明明没过但因为 harness 设计得不好正确性检查和性能测量没有严格分离导致 Review Agent 返回了一个高性能结果于是流程认为“达标”并停下来了。后来人工 review 才发现错误代码在某几个测试用例上因为编译优化产生了“巧合正确”的结果。从那以后我的 pipeline 里强制要求先单独跑正确性测试通过之后才能进入性能测试两个测试在逻辑上是两个独立阶段。4.3 性能“虚高”的代码是怎么骗过你的还有一个特别隐蔽的坑Agent 生成的内核在 benchmark 里成绩惊人但真实业务里跑不出这个性能。这种情况往往不是 Agent 在作弊而是 benchmark 代码写得有漏洞。最常见的问题是死代码消除。如果 kernel 写入了输出矩阵但下游代码没有读这个输出矩阵某些情况下编译器优化会认为这些写入操作无副作用直接把整个 kernel 的工作“优化”掉了结果 benchmark 测出来是极短的耗时。为了防这个必须在 benchmark 里加入一个校验机制比如对输出矩阵计算一个 checksum确保数据确实被写入。另一个问题是内存分配方式的差异。Agent 生成的代码如果在 kernel 里使用动态分配或者显式初始化大量数据时间开销会被统计进去但如果 benchmark 把初始化也算进 kernel 计时或者干脆漏掉就会出现不一致。严格来说kernel 计时应该只覆盖 kernel 本身不包含内存拷贝和初始化这一点要在 harness 里明确控制。我在 Review Agent 的反馈模板里专门加了一项“请检查输出 checksum 是否有效。若 checksum 为 0 或异常可能是编译器优化掉了 kernel 计算本次性能数据无效。”这个提示帮我们挡住过好几轮虚假的“性能突破”。如果你发现 Agent 生成的代码性能离谱地好比如说比 cuBLAS 还快 3 倍第一反应不应该是高兴而是去查 harness 是不是出问题了。5. KernelEvolve真正改变的是什么开发流程与更多可能KernelEvolve 的价值不只是把内核生成这件事自动化更在于它把“写内核”这个高度依赖个人经验的工作变成了一条可以被迭代、被记录、被复用的流水线。这背后对开发方式的影响可能比几个自动化脚本深远得多。5.1 从手写内核到审查Agent算子库团队的角色转变如果 KernelEvolve 这类系统大规模落地算子库团队的工作重心会发生明显变化。过去资深内核工程师的大部分时间花在写代码和调优上未来人类的核心工作可能会集中到三块定义清晰的问题规范设计高难度的测试用例审查 Agent 在关键场景下的实现。这其实是很多工程领域都经历过的演进。编译器出现之后汇编工程师没有消失但工作变成了给编译器写后端同样的Agent 能写内核之后内核工程师的工作会向“Agent 不好搞定的场景”迁移——比如全新的硬件架构、非常规的矩阵稀疏性、断层性能要求的混合精度推理。对个人开发者来说KernelEvolve 式工作流最大的吸引力是降低了进入门槛。以前想优化一个算子你得对 CUDA 的底层机制有很深的积累否则面对 profiler 输出根本无从下手。现在有了 Agent 循环哪怕你对分块策略的经验没那么丰富也能通过“生成—测试—反馈—修改”的闭环逐步逼近一个不错的内核实现。前提是你能看懂 Agent 生成代码里的关键设计并判断它的优化方向是否合理。所以我的看法是Agent 不是让内核工程师失业而是把他们的门槛从“写得出”提升到“看得懂、审得准”。5.2 未来还能怎么演进KernelEvolve 这套思路还能顺着几个方向自然延伸。一个方向是引入更细粒度的硬件性能剖析数据。现在 Review Agent 拿到的反馈主要是运行时间和正确性但如果能把 profiler 的输出也喂给它比如共享内存 bank conflict 次数、occupancy 比率、访存吞吐率、指令 mix 分布Agent 就能做出更有针对性的优化决策。这相当于把一个经验丰富工程师用 Nsight Compute 分析 kernel 的过程也自动化了。另一个方向是让 Agent 生成更抽象的中间表示而不是直接生成最终 CUDA 代码。如果先让 Coding Agent 生成类似 Triton 的 DSL 代码经过编译器再生成底层代码整个系统的可移植性会大幅加强换一个新硬件时只需要把编译器后端换掉而不是让 Agent 从头学一套新的编程模型。还有一个很值得探索的方向是让多个 Agent 承担不同算子的优化然后互相对比和学习。比如 A 组 Agent 调 GEMMB 组 Agent 调 GEMV两者在共享内存策略上有启发就可以通过一个“知识交换”的机制把 A 组试出来的优秀策略作为 B 组下一轮优化的候选方案。这种跨任务的迁移学习在多 Agent 系统里实现起来比传统自动调优自然得多因为知识传输的载体就是自然语言和结构化方案文本。回到工程实践上我個人認為最有价值的是先把“规划—实现—审查”这个闭环跑通、跑稳。哪怕第一步只在你手头的一个算子上跑通也会积累到很多关于提示词设计、评测 harness、反馈格式的实战经验。这些经验在未来迁移到更多算子、更多硬件平台时都是可复用的资产。如果你自己动手去搭这套流程我的建议是先别追求一步到位做出一个完整的“Agent 团队”而是从单 Agent 加一个固定反馈循环开始把基准测试、正确性验证、日志记录这些基础设施打磨好。基础设施稳了再把 Planning、Coding、Review 拆成三个 Agent 协作效果会好很多。我在实际中就是这么做的——第二版 pipeline 比第一版更复杂但反而更稳定因为底层的评测 harness 已经足够可靠Agent 之间的协作不会被不可信的测量数据带偏。