1. 从"手写汇编"到"代理驱动":算子优化范式正在换挡
2026年过半,如果你还在用纯手工的方式去调一个LLM推理场景下的算子kernel,大概率会被团队里的年轻人用一套代理驱动的自动化流程按在地上摩擦。这不是危言耸听,而是我这半年在一线做算子优化时最直观的感受。所谓LLM代理成为一等优化手段,说白了就是:过去我们把大模型当成"被优化的对象",现在我们把大模型当成"参与优化的工程师"。它不只是帮你补全几行CUDA代码,而是能自主提出优化假设、生成候选kernel、跑benchmark、分析profiling数据、再迭代下一轮方案——整个闭环里,人只负责设定目标和兜底审查。
这篇文章面向的是这样一群人:做GPU算子优化、推理加速、训练框架底层的工程师,或者对AI编译器、kernel自动调优感兴趣的技术人。不管你是刚接触CUDA不久的新手,还是已经手撸过FlashAttention变体的老手,我都会尽量把"代理怎么参与优化""它到底强在哪""哪些坑必须人来兜"讲清楚。核心关键词就三个:GPU算子kernel优化、LLM代理、自动调优闭环。我会结合2026下半年这个时间点上比较成熟的实践路径,把方案选型、实操步骤、参数计算、排查技巧全部摊开讲,让你看完能直接在自己项目里搭一套类似的流程。
先说结论性的判断:代理不是替代人,而是把人的精力从"重复试参数"里解放出来,集中到"定义问题和判断方向"上。一个合格的代理优化流程,能把kernel调优的迭代周期从"天"压缩到"小时",但前提是你得把约束、评测、回滚机制设计对。下面我按整体设计、核心细节、实操过程、问题排查四个大块来拆。
2. 整体设计与思路拆解:为什么是代理,而不是传统自动调优
2.1 传统autotuning的天花板在哪
在代理介入之前,算子优化主要靠两类手段。一类是模板化autotuning,比如TVM的Ansor、Triton的autotune装饰器,思路是预先定义好一个搜索空间(tile大小、线程块维度、向量化宽度、流水线级数等),然后暴力或启发式地遍历。另一类是手工调优,靠工程师读PTX、看Nsight Compute的指标,凭经验改。
这两类都有明显天花板。模板化autotuning的问题是搜索空间是人写死的,你没想到的优化方向它永远搜不到。比如某个算子其实适合用warp specialization配合异步拷贝,但你的模板里压根没这个维度,那再搜也是白搭。手工调优的问题是慢且不可复制,一个资深工程师调一个复杂fused kernel可能要一周,换个人、换个shape又得重来。
我实测过一个典型的LLM decode阶段的attention kernel,用传统autotune在预定义空间里搜了大概4000个配置,最好的比基线快1.8倍。但后来让代理介入,它提出的一个"按序列长度动态切换split策略"的方案,直接到了2.6倍。差距不在算力,在于搜索空间的维度本身。
2.2 代理作为一等优化手段的核心逻辑
代理之所以能突破天花板,关键在于它把"优化"从"在固定空间里找最优"变成了"不断扩展和重构搜索空间"。具体来说,代理承担了四件事:
- 假设生成:基于算子语义、硬件特性和profiling数据,提出"这里可能是瓶颈,可以这样改"的假设。
- 代码合成:把假设翻译成可编译、可运行的kernel代码或编译配置。
- 结果分析:跑完benchmark后,读profiling指标,判断假设是否成立,失败原因是什么。
- 策略迭代:根据分析结果决定是微调参数、换方向,还是回退。
这四件事形成一个闭环,而人只在两个位置介入:闭环入口设定目标函数和硬约束,闭环出口做正确性审查和最终验收。这就是"一等优化手段"的含义——它不是辅助工具,而是流程的主干。
2.3 方案选型:三种代理介入深度
2026下半年比较成熟的实践里,代理介入深度大致分三档,选哪档取决于你的场景和风险容忍度。
| 介入档位 | 代理职责 | 人的职责 | 适用场景 | 迭代速度 |
|---|---|---|---|---|
| 轻量档 | 只生成候选参数配置,不碰代码 | 写模板、定搜索空间 | 成熟算子微调 | 快,分钟级 |
| 中量档 | 生成kernel代码片段,人审查后合并 | 定接口、做正确性验证 | 新算子探索 | 中,小时级 |
| 重量档 | 全流程自主,含编译、跑测、迭代 | 只设目标和兜底 | 研究性调优、离线任务 | 慢但深,天级 |
我的建议是:生产环境从轻量档起步,研究环境直接上重量档。轻量档风险最低,代理只动参数不动逻辑,出问题也好回滚。重量档适合那种"反正也没现成方案,让它自己折腾"的场景,但必须配严格的正确性校验,否则它可能给你一个跑得飞快但算错的结果。
2.4 为什么2026下半年这个时间点特别关键
有几个条件在这半年同时成熟了。第一,主流代理框架对代码执行和工具调用的支持稳定了,代理能真正跑代码、读结果,而不是空想。第二,GPU profiling工具的输出格式更结构化,代理解析起来不容易出错。第三,社区里积累了大量算子优化的"经验文本",代理的领域知识够用了。第四,硬件侧的新特性(比如更细粒度的异步执行、更灵活的内存层级控制)让优化空间变大,反而更需要代理来探索。
这几个条件叠加,导致代理从"能用的玩具"变成了"好用的工具"。我身边做推理加速的团队,今年下半年基本都在往这个方向转。
3. 核心细节解析与实操要点:代理优化闭环怎么搭
3.1 目标函数设计:别只盯着延迟
代理优化的第一件事是定义"什么叫好"。新手最容易犯的错是只写一个延迟指标,结果代理给你调出一个延迟极低但占用显存爆炸、或者数值误差超标的kernel。
一个靠谱的目标函数至少包含四维:
- 性能:延迟、吞吐,要区分冷启动和稳态。
- 资源:显存占用、寄存器压力、shared memory使用量。
- 正确性:与参考实现的数值误差,通常用相对误差和绝对误差双阈值。
- 稳定性:多次运行的方差,避免调出"偶尔快"的配置。
我一般会写成一个加权评分,但正确性和资源是硬约束,不参与加权——不满足直接判负,不给代理任何"用正确性换性能"的空间。这一点非常重要,因为代理在追求性能指标时会非常激进,你不设硬约束它就敢越线。
提示:目标函数里的阈值不要拍脑袋定。数值误差阈值建议参考你下游任务的容忍度,比如LLM推理里attention输出的相对误差通常要控制在1e-2以内,具体看你的精度要求。
3.2 评测环境的隔离与可复现
代理会高频跑benchmark,如果评测环境不干净,结果全是噪声,代理的迭代方向就会被带偏。我踩过的坑:一开始直接在开发机上跑,后台有其他任务抢GPU,代理连续几轮都得出"这个方向不行"的结论,其实是环境干扰。
正确做法是给代理一个独占的、可复现的评测沙箱。具体包括:
- 固定GPU型号和驱动版本,记录在案。
- 每次评测前清空缓存、重置时钟频率(如果允许)。
- 用固定的输入shape和随机种子,避免数据分布变化影响结果。
- 跑多轮取中位数,而不是单次结果。
这套东西搭起来不复杂,但省下来的排查时间非常可观。我现在的做法是写一个评测脚本,代理只能通过这个脚本拿结果,脚本内部负责所有隔离和统计逻辑,代理看不到也改不了。
3.3 代理的"记忆"怎么管
代理迭代几十轮后,会产生大量中间结论。如果不管理,它会重复试已经失败过的方向,浪费算力。我一般给代理配一个结构化的"实验日志",记录每轮的假设、改动、结果、结论。
这个日志有两个作用。一是让代理在提新假设前先查历史,避免重复。二是让人能快速review代理的思考路径,判断它是不是在瞎撞。日志格式我推荐用简单的表格或JSON,字段包括:轮次、假设描述、改动摘要、性能变化、是否通过约束、结论。
注意:日志不要让代理自己随便写,最好用固定schema,否则它写出来的东西人看不懂,检索也不好做。
3.4 代码生成的安全边界
中量档和重量档里,代理会生成实际执行的kernel代码。这里的安全边界必须划清楚。我的做法是:
- 代理生成的代码先过静态检查,禁止危险操作(比如越界写、无限循环)。
- 编译在隔离环境里做,编译失败不影响主流程。
- 运行前做小规模正确性测试,通过了才跑完整benchmark。
- 所有生成的代码版本化管理,能随时回滚。
这几步看起来繁琐,但比起让一段错误代码把整个评测环境搞崩,成本低太多了。我见过一次代理生成的kernel里有shared memory越界,直接把进程搞挂,还好是在隔离环境里。
3.5 硬件特性知识的注入
代理的通用知识很强,但对具体硬件的细节未必清楚。比如某个架构上shared memory的bank conflict规则、某个指令的吞吐特性,这些如果代理不知道,它提出的假设可能方向就错了。
解决办法是在系统提示里注入一份"硬件特性摘要"。这份摘要不用很长,把关键约束和常见陷阱列清楚就行。比如:
- 该架构的warp大小、最大寄存器数、shared memory容量。
- 常见的性能陷阱(bank conflict、warp divergence、低occupancy)。
- 推荐的优化模式(向量化加载、异步拷贝、warp specialization)。
我实测下来,注入这份摘要后,代理提出有效假设的比例明显上升,无效尝试减少了不少。
4. 实操过程与核心环节实现:搭一套能跑的代理优化流程
4.1 环境准备与依赖清单
先列一下我这套流程用到的组件,都是2026下半年比较主流的选择,具体版本你按自己环境调整。
- 代理框架:支持工具调用和多轮执行的通用代理框架,能跑代码、读文件、调外部命令。
- GPU环境:单卡或双卡,驱动和CUDA工具链装好,profiling工具可用。
- 编译工具:对应算子语言的编译器,比如CUDA的nvcc、Triton的编译器。
- 评测脚本:自己写的benchmark和正确性校验脚本。
- 版本管理:git,用来管理生成的kernel代码。
环境准备阶段最容易出问题的是profiling工具的权限和输出解析。有些环境下profiling需要特殊权限,代理跑的时候会失败。建议提前手动跑一次profiling,确认输出格式,再让代理去解析。
4.2 第一步:建立基线
任何优化都要有基线。我会先手写或用一个已知可用的kernel实现作为baseline,跑出性能、资源、正确性三组数据,存下来。
基线的作用有两个。一是给代理一个参照,让它知道"现在是什么水平"。二是作为正确性校验的参考实现,代理生成的kernel要和基线对比数值误差。
基线不要选太差的实现,否则代理随便改改就"提升巨大",掩盖了真实问题。我一般选一个"合理但未深度优化"的实现作为基线,这样提升空间真实可见。
4.3 第二步:定义搜索目标和约束
把前面说的目标函数写成代理能理解的格式。我通常用一段结构化描述,包含:
- 优化目标:最小化稳态延迟,同时吞吐不低于某阈值。
- 硬约束:数值相对误差小于阈值,显存占用不超过上限,寄存器数不超过上限。
- 可调范围:允许代理改哪些东西(比如tile大小、线程配置、是否用异步拷贝)。
- 禁止事项:不允许改接口签名,不允许引入外部依赖。
这段描述要写得明确,不能有歧义。代理对模糊指令的解读往往和你预期不一致。
4.4 第三步:让代理跑第一轮
第一轮我一般让代理先做"诊断"而不是直接改代码。让它读基线的profiling数据,分析瓶颈在哪,提出三到五个优化假设,按预期收益排序。
这一步的价值在于,你能看到代理的思考是否合理。如果它第一轮就提出明显离谱的假设,说明你的提示或硬件摘要有问题,先修这个再往下走。
假设排序后,让代理挑收益最高的一个去实现。实现完编译、跑正确性、跑benchmark,记录结果。
4.5 第四步:迭代与收敛判断
接下来就是循环:分析结果、提新假设、实现、评测。这里有几个实操要点。
收敛判断:不要设固定的轮数上限就完事,要设"连续N轮无显著提升就停"。我一般设连续3轮提升小于2%就停,避免浪费算力。
方向切换:如果某个方向连续失败,让代理换方向,而不是在死胡同里微调。这个判断可以写进提示里,也可以让代理自己根据日志决定。
人工抽查:每隔几轮我会抽查一次代理生成的代码和结论,确认它没有走偏。完全放手不管是有风险的,尤其是重量档。
4.6 一个具体的参数计算示例
假设我们在优化一个矩阵乘相关的kernel,基线用的是128x128的tile,线程块256线程。代理提出把tile改成256x128,线程块512线程。这个改动合不合理,我们可以算一下。
shared memory占用:如果每个元素是2字节(半精度),256x128的tile需要 256×128×2 = 65536字节 = 64KB 用于存A的一块,加上B的一块,总共可能超过单块shared memory上限。这时候代理的方案就不可行,需要拆分或者用更小的tile。
寄存器压力:512线程的块,如果每个线程要存256x128/512 = 64个累加器元素,每个元素4字节,那就是256字节的寄存器,远超单线程寄存器上限。这个方案直接判负。
所以代理提出方案后,先做资源可行性计算,再决定要不要实现。这一步能省掉大量无效编译和评测。我把这个计算逻辑写成了一个检查函数,代理提方案后自动跑一遍,不通过就打回。
4.7 结果记录与对比
每轮结果我都记进实验日志,格式大概是这样:
| 轮次 | 假设 | 关键改动 | 延迟变化 | 显存变化 | 误差 | 结论 |
|---|---|---|---|---|---|---|
| 1 | 增大tile | 128→256 | -8% | +15% | 通过 | 有效但显存涨 |
| 2 | 异步拷贝 | 加cp.async | -12% | 持平 | 通过 | 有效 |
| 3 | 调整线程块 | 256→512 | 编译失败 | - | - | 寄存器超限 |
这种表格一眼就能看出哪些方向有效、哪些无效,代理后续迭代也能参考。
5. 常见问题与排查技巧实录
5.1 代理反复提同样的失败方案
这是最常见的问题。原因通常是代理的"记忆"没管好,或者日志检索没生效。解决办法是强制代理在提假设前先查日志,并且把失败方案明确标记为"禁止重复"。
我还会在提示里加一句:"如果你要重试一个之前失败的方向,必须说明这次和上次有什么本质不同。"这句话能挡掉大部分无意义的重复。
5.2 评测结果波动大,代理判断失准
前面提过环境隔离,这里补充一个细节:GPU的时钟频率会随温度变化,长时间跑benchmark会导致降频,结果越跑越慢。解决办法是控制单次评测时长,中间留冷却时间,或者锁定频率(如果环境允许)。
另一个波动源是输入数据。如果每次评测用的输入不同,结果没法比。固定随机种子和输入shape是基本要求。
5.3 代理生成的代码能跑但结果错
这类问题最危险,因为性能数据看起来很好,容易蒙混过关。根源通常是代理对数值精度的理解不到位,比如用了低精度累加、或者跳过了某些边界处理。
对策是正确性校验必须独立于代理。校验脚本由人写,代理不能改。校验要覆盖边界情况:极端shape、特殊值输入、累加溢出场景。我一般会准备一组"刁钻"的测试用例,专门用来抓这类问题。
5.4 优化到一定程度就卡住
这是正常的,说明当前搜索空间已经挖得差不多了。这时候有两个选择:一是让代理跳出当前空间,从更宏观的角度重构方案(比如换算法、换数据布局);二是接受当前结果,转去优化别的算子。
判断标准是看边际收益。如果连续多轮提升都小于1%,基本可以停了。继续投入的性价比很低。
5.5 常见问题速查表
| 问题现象 | 可能原因 | 排查方向 | 解决手段 |
|---|---|---|---|
| 代理重复失败方案 | 日志未生效 | 检查日志检索逻辑 | 强制查历史+标记禁止 |
| 结果波动大 | 环境干扰/降频 | 检查后台任务和温度 | 隔离环境+冷却+锁频 |
| 代码能跑但结果错 | 精度/边界问题 | 跑独立正确性校验 | 人工写校验+刁钻用例 |
| 优化卡住 | 搜索空间挖尽 | 看边际收益 | 换宏观方向或停止 |
| 编译频繁失败 | 资源超限 | 检查寄存器和shared memory | 加资源预检查函数 |
| 代理理解偏差 | 提示模糊 | 检查目标描述 | 明确约束+硬件摘要 |
5.6 几个我踩过的坑
第一个坑是过早追求全自动。一开始我想让代理从零到一全包,结果它在环境配置上就卡住了,浪费大量时间。后来改成"人搭好环境,代理只做优化",效率高多了。
第二个坑是目标函数太单一。只优化延迟,代理调出一个显存翻倍的方案,下游直接OOM。后来加了资源硬约束才解决。
第三个坑是忽视代理的"幻觉"。代理有时会声称某个改动带来了提升,但实际数据对不上。对策是所有结论必须以评测脚本的输出为准,代理的自述只作参考。
第四个坑是没有版本管理。代理改了几十版代码,想回退到某一版发现找不到了。后来强制每版都commit,问题解决。
6. 代理优化的边界与人的价值
聊了这么多代理怎么用,最后说说它不能干什么,以及人的价值在哪。
代理不擅长的是定义问题。它不知道你的业务里什么指标最重要,不知道哪些约束是硬的哪些是软的,不知道一个看似性能很好的方案会不会在真实负载下崩掉。这些判断需要人来下。
代理也不擅长跨层优化。它能优化单个kernel,但算子之间的融合、内存布局的全局设计、整个推理流程的调度,这些需要人从系统层面考虑。代理可以在局部做到极致,但全局最优往往需要人来做取舍。
所以我的体会是:代理把算子优化的"执行成本"大幅降低了,但"决策成本"反而更集中在人身上。你得想清楚要什么、约束是什么、怎么验证,然后让代理去跑。想不清楚这些,代理只会帮你更快地跑偏。
这半年我最大的收获,不是某个kernel快了多少倍,而是整个优化流程的节奏变了。以前是"想一周,调一天",现在是"想一天,调一小时"。省下来的时间,可以去做更有价值的探索。这个转变,才是代理成为一等优化手段的真正意义。