
凌晨两点十七分Agent 的第 37 次迭代跑完分数从第 23 名跳到了第 15 名。我盯着终端里的榜单刷新结果看了整整十秒确认不是缓存确认不是错觉然后截了个图发给还在群里讨论“Agent 到底能不能干正经事”的朋友。这一发整个群都炸了。这个榜单不是随便什么排行榜是围绕 NVIDIA GPU 平台上的内核优化展开的社区榜单参赛者提交的是经过调优的内核模块、驱动路径上的改动或者 GPU 相关系统代码评估方会在一组固定的基准负载上打分综合吞吐、延迟、稳定性以及功耗表现。标题里的“24 小时冲上 NVIDIA kernel 榜单第 15”说的不是我一夜猛肝而是我把整个寻优过程交给了一套 Agent 系统让它在 24 小时窗口内自主完成编译、评测、分析、改码、再编译的闭环最终拿下了第 15 名。这篇文章我不想复述什么伟光正的结论只想把这一天的完整过程摊开来讲为什么我会选择用 Agent 做这种事、Agent 系统的架构是怎么搭的、实际跑起来踩了哪些差点让整个窗口报废的坑以及最终那些真正让分数涨上去的优化点到底长什么样。1. 这个榜单是什么以及为什么我一定得用 Agent先说清楚榜单本身的玩法否则后面所有内容都没有根基。这类内核优化榜单的评估口径通常不是单一指标。拿我这次参与的 NVIDIA kernel 榜单来说它有一套固定的基准负载集覆盖了内存分配、页表操作、DMA 缓冲管理、中断处理路径、锁竞争场景等多个维度。每个负载会记录三组数据单位时间内的完成事务数、P99 延迟、以及功耗计数器读数。最后综合出一个加权分。提交物是一个编译好的内核模块外加一份改动说明。这个评估方式有个很要命的特点它不是一次性打分而是多次采样的统计结果。也就是说哪怕你拿到了一个理论上非常漂亮的补丁只要它在某些采样点触发了偶发的锁超时或者内存分配抖动你的排名就会被拖下去。这就引出了第二个问题人工调优在这种赛道上很难赢。1.1 人工调优的困境两难的选择我过去做过几轮手工调优最大的痛苦不是找不到优化方向而是无法快速验证方向。内核模块的编译时间虽然不长但每次评测循环跑下来至少需要十分钟到二十分钟。手工调优的典型节奏是改一个参数编译跑评测看数据再改。如果思路清晰一天能验证二十种组合就算不错了。但真正的问题在于内核优化不是单一参数的寻优而是多参数组合的问题。锁粒度、CPU 核绑定、内存池大小、中断合并阈值、预取距离、DMA 缓冲复用策略这些变量之间存在严重的交互效应。你今天把锁粒度调细吞吐上去了但 P99 延迟可能立刻恶化你把内存池调大缓存命中率上去了但功耗读数又变差了。人工在这种高维交互空间里做全局寻优几乎就是盲人摸象。1.2 24小时窗口的真正价值这个榜单的玩法还有一个特别的地方每个参赛者在一个自然日内有一个活跃的提交窗口窗口内可以无限次提交但最终得分以窗口关闭前的最后一次有效提交为准。也就是说24 小时不是让你用来慢慢试的而是逼你在有限时间内尽可能多地完成假设-验证-修正的循环。在这个约束下人工跑循环的效率劣势被放到了最大——你睡一觉的时间一套自动化系统可能已经完成了几十个迭代。所以我从一开始就想得很清楚这个赛道的核心竞争力不是某一个人的优化直觉而是谁的寻优循环跑得快、跑得稳。2. 开局前的两小时环境、基线与评估脚本Agent 不是魔法。它能在 24 小时内高效工作前提是开局之前把基础设施铺好。这一节说的就是我在计时开始前做的准备也是最容易被忽略、最不性感、但决定成败的部分。2.1 先把 kernel 构建链路打穿第一件事是确保内核模块的构建链路是通的。这次我用的是一台 Ubuntu 系统搭配 NVIDIA GPU 的测试机就是要跑 GPU 相关内核路径的优化。别看平时装个驱动好像很简单一旦涉及从源码构建自定义内核模块坑立刻就来了。最常见的报错长这样[rootlocalhost src]# make common.mk:82: *** kernel header files not in any这个报错的字面意思是make 系统在查找内核头文件时找不到目标目录。但实际原因往往不是目录不存在而是内核头文件版本与当前运行内核不一致或者/usr/src/linux这个软链接没有指向正确的内核源码目录。我的处理方式很简单先查当前内核版本然后确保/lib/modules/$(uname -r)/build这个链接指向真实存在的源码树。uname -r ls -l /lib/modules/$(uname -r)/build ls /usr/src/linux-headers-$(uname -r) 2/dev/null || echo missing headers如果发现自己用的是精简版系统内核 headers 没有默认安装那还需要sudo apt install linux-headers-$(uname -r)这一步我花了大概二十分钟但它是整个 24 小时里最值得的二十分钟。因为如果构建链路不稳固Agent 跑起来之后每编译一次就报一次环境错误寻优循环直接瘫痪。2.2 驱动与 kernel header 匹配问题构建链路通了之后第二个大坑是 NVIDIA 驱动与内核版本之间的匹配。这次测试机的 GPU 是常见的 NVIDIA 移动版芯片但驱动加载老是有问题要么是nvidia-smi正常但内核模块不工作要么是开机后驱动版本和内核模块版本不一致。我的做法是彻底放弃 Ubuntu 仓库里自带的驱动包转而使用官方驱动安装器的标准流程先卸载旧驱动再用dkms机制重新编译内核模块。dkms的好处是当内核升级时它会自动重新构建驱动模块避免内核更新后驱动掉链子的经典问题。这里还要注意一个容易忽略的细节如果你要修改 GPU 相关内核路径的代码修改完后必须重新注册驱动模块而不是只重新编译模块文件sudo dkms remove nvidia/版本号 --all sudo dkms install nvidia/版本号 -k $(uname -r) sudo modprobe nvidia nvidia-smi这一套走完nvidia-smi应该能正常输出 GPU 信息同时内核模块路径上的改动才能被真正加载。我见到很多人在这一步栽了跟头模块编译通过了但modprobe一直失败查下来才发现是签名校验问题。如果你开启了 Secure Boot还要额外处理 module signing 的环节。为了不把时间耗费在这上面我直接关掉了 Secure Boot 之后重新拉起了系统。2.3 建立可信的基线分数环境跑通之后紧接着要做的事是建立一个可信的基线分数。这并不是说拿到一个数就完事了而是要连续跑至少五轮评测观察分数的浮动范围。为什么要做这件事因为评测系统本身有噪声。如果你不知道噪声底噪有多大Agent 后面做出的很多优化其实只是噪声本身。在我这次的评估环境里同一份代码的分数波动大约是 ±2.5%。这意味着如果某个改动带来的提升不足 3%它可能根本不是真实优化。我给 Agent 设定的规则很简单只有连续三次评测都比基线提升超过 3%才算一个有效补丁。这个门槛过滤掉了大量假阳性。3. Agent 自动寻优系统的设计与实现环境就绪之后真正的重头戏来了Agent 系统的架构。3.1 架构选型不是纯 Harness也不是裸 LLM关于 Agent 开发圈子里的讨论经常在两个极端之间摇摆一端是纯 Harness 式的流程编排把每一步都固定成模板Agent 只负责执行另一端是直接丢给大模型一个终端让它自由操作一切。我的选择是两个都不要。纯 Harness 式编排的问题在于它对不可预见的优化点完全没有适应能力。模板化的流程只能做模板内的事而内核优化恰恰经常出人意料——某个性能瓶颈可能藏在你根本没预设的地方。裸 LLM 自由操作的问题则在于不可控性。它可能突然执行一个危险的系统调用或者在一个分支上反复试探几十次浪费窗口时间。我的方案是一个带约束的自治循环Agent 可以自由决定改哪些参数、提交哪些补丁但所有操作都必须在编译-评测-反馈-变异这一条主循环内进行。给它一个沙盒沙盒里什么都能做沙盒的边界本身就是寻优策略。3.2 搜索空间建模把 kernel 优化变成可枚举的决策这是整个系统里最关键的一步也是我花最多时间思考的部分。内核优化的搜索空间是连续的、高维的、非线性的。为了让 Agent 能有效地寻优我必须把这种空间转换成一种 Agent 可以理解、可以推理、可以修改的表示。我采用的是配置项束的方式。每一个配置项束包括锁类型与粒度自旋锁还是互斥锁锁的作用域是全局还是 per-CPUCPU 核绑定策略中断处理绑定到哪些核关键线程的亲和性如何设置内存池参数缓冲区的大小、对齐方式、预取距离。编译选项是否启用 LTO是否启用特定 CPU 指令集。中断合并阈值硬件中断在什么频率下聚合处理。Agent 每一次迭代就是从当前配置项束出发选择一个维度做变异然后跑完整的编译-评测循环。这个设计有一个非常重要的细节Agent 不应该每次只改一个变量。现实中的优化往往来自多个变量的协同变化。如果限制它一次只改一处虽然方便归因但会错过交互效应。我的做法是让它有概率同时变异 2-3 个相关维度并在反馈里同时记录所有变量的变化这样既保留了组合探索的能力又能通过多轮迭代逐步分离归因。3.3 迭代闭环编译-评测-反馈-变异Agent 运行时的主循环可以用一个很简单的伪代码描述# 简化的 Agent 主循环实际代码比这个复杂 while not window_closed: patch agent_propose(current_best, history) result build_and_evaluate(patch) if is_valid_improvement(result): current_best patch agent_learn(result) else: agent_learn(failure) history.append(result)但关键在于agent_propose和agent_learn这两个函数内部做了什么。agent_propose不是随机生成。它接收的信息包括当前最优的配置、历史所有补丁的效果、评测数据中的性能明细吞吐和延迟分别怎么变化、以及我自己注入的领域提示语。领域提示语是基于对内核优化的理解写死的规则比如当锁竞争指数高时优先考虑 per-CPU 化、当 DMA 分配耗时占比高时优先考虑缓冲复用。agent_learn则分为两层一层是短期记忆记录最近的补丁效果避免反复探索同一个失败方向一层是长期记忆把那些被验证有效的规则反馈到下一轮的agent_propose的上下文里。这套系统的本质是把人类对内核优化的先验知识做成提示模板把 LLM 当做一个可以理解复杂文本反馈的变异算子再用进化论的逻辑跑循环。我试过在一些配置下让 Agent 完全只靠评测分数做自我反馈效果很差。因为原始评测数据是一堆数字LLM 很难从中直接领悟出应该去试一下锁拆分这种抽象判断。而加上了领域提示语之后Agent 的探索效率有了质的提升尤其在找到第一个正向补丁之后后续的寻优速度明显加快——它开始能理解什么样的改动在这个平台上是有效的。4. 24小时内Agent实际打出的关键优化组合整个寻优过程跑了 24 小时记录了 47 次完整迭代其中有效改进 9 次最终成绩冲到了榜单第 15 名。这一节我来复盘那些真正让分数涨上去的关键优化点。4.1 第一个有效补丁评测噪声里捞出来的信号第一个有效补丁出现得比预期早大约在窗口启动后的第 4 个小时。当时 Agent 处于比较保守的探索阶段变异的方向集中在内存池大小和编译选项上。前 11 次迭代没有任何改进分数都在基线附近波动。直到第 12 次迭代Agent 在 DMA 缓冲区的对齐值上做了变异从默认的 256 字节对齐改成了 64 字节对齐同时调整了预取距离。这个组合改动带来了大约 5.2% 的吞吐提升。重要的事实是在连续三次评测中这个提升都稳定存在没有跌回基线。我回看这次变异它其实有明显的运气成分——DMA 对齐值本身不太可能是瓶颈。但运气好归运气好Agent 的机制保证了它不会放过任何一个统计上显著的正向信号这恰恰是人工调优容易忽视的。4.2 锁粒度与核绑定一次收益最大的改动窗口跑到第 13 个小时Agent 做了一组让它真正跃升到榜单前列的改动。那次迭代中Agent 同时变异了两个维度一是把一组全局共享的锁从单一的自旋锁拆分成 per-CPU 的拆分锁大幅降低了多核竞争时的自旋等待时间。二是将中断处理线程固定到了特定的物理核上同时把用户态工作线程绑定到了另一组核上减少了缓存争用。这两个改动的组合拳带来了接近 11% 的综合评分提升。最值得注意的是 P99 延迟下降非常明显这意味着评测系统里的高方差事务大幅度减少了。Agent 的反馈记录显示它做这次变异并不是靠什么高深的推理而是因为长期记忆里已经积累了两条有效规则一是锁竞争是主要瓶颈的判断来自前几轮评测数据里自旋等待时间占比高的指标二是核绑定有效的结论来自另一个小补丁的部分改进。它把这两条线索组合在一起做出了一个交叉变异。这个案例很好地展示了组合寻优的价值单一方向的优化很难带来质变但多变量的协同调整往往能打开局面。手动调优时人往往会因为一次改一个变量的洁癖而错过这种机会。Agent 没有这种洁癖。4.3 组合回归防止 Agent 自我欺骗不过这里要泼一点冷水并不是所有有效改进都能叠加。窗口跑到第 16 个小时Agent 尝试把之前验证有效的那两个改动再叠加一个新优化点——中断合并阈值调整。结果排名不升反降吞吐上去了但功耗指标大幅恶化综合分掉了 4 个点。如果不是系统强制要求每次提交时保存当前最优配置而不是直接覆盖这个退步就会污染掉之前的最优状态。这就是我在系统里设计的组合回归机制每次 Agent 提出新的改进时我不会直接信任它对历史补丁的叠加效果而是强制重新评测完整的提交物——不是测某个改动本身而是测当前最优配置再加上这个改动的整体。这种做法会损失一些时间每轮迭代的耗时增加 3-5 分钟但换来的是绝对不会出现单点有效、组合翻车的假象。这也是我认为很多 Agent 自动优化系统做得不够扎实的地方它们在单点实验上跑得很欢却忽视了现实中提交物是一个整体任何叠加都可能在交互中带来意想不到的连锁反应。5. 踩坑实录几乎让整个窗口报废的几个问题24 小时窗口听起来很长但实际跑起来你就会发现每一个小问题都会吞噬掉大量有效时间。这一节是我最想分享的内容因为这些教训不是教程里能学到的。5.1 kernel header 缺失与驱动加载失败第一个大坑发生在窗口启动后的第 6 个小时当时 Agent 正常跑了几轮迭代之后突然开始批量报编译错误。排查后发现问题出在一处代码改动引发了内核头文件的依赖变动——新代码引用了一个之前没引用过的内核 API而对应的头文件路径在当前的构建配置里没有被引入。更麻烦的是因为环境是长时间运行的某些编译产物已经污染make有时候会错误地跳过一些中间步骤。这个问题的根因其实是我在开局准备时的一个小小的疏忽我没有为内核模块构建单独建立一个干净的构建目录镜像。当我发现问题时Agent 已经浪费了大约四十分钟在反复编译失败上。解决办法很简单我把 Agent 的编译步骤改成在一个干净的临时目录里进行每次编译前先清空再完整构建。虽然会让单次编译耗时变大但消除了增量编译带来的状态污染。5.2 评测环境的状态污染这个坑比编译问题更深也更隐蔽。跑到第 9 个小时Agent 的迭代出现了一批假阳性——某些补丁第一次评测时分数提升很高但连续评测就会跌回去。如果你只看第一次评测的结果你会以为找到了一个很厉害的优化。但事实是上一次评测的进程没有完全退出GPU 频率还没有回落到基准状态此时紧跟着跑下一次评测分数会虚高。这类问题在手工调优时也会遇到但手工情况下人对评测间隔有直觉感知。Agent 没有这种直觉它只会机械地执行脚本因此更容易被评测状态污染误导。我的修复方式是给评测步骤加上了强制冷却机制每轮评测结束后强制 sleep 60 秒清空 GPU 缓存并且检查是否还有残留进程确认干净后才允许进入下一轮。这个改动直接减少了大约四分之一的无效迭代。我建议所有做自动基准测试的读者都要把评测环境的原子性和隔离性写在最核心的工程规范里。5.3 Agent 幻觉补丁与无效探索第三个问题是 LLM 特有的幻觉。有一段时间Agent 反复尝试使用一些看起来很合理但实际并不存在的内核 API。它给出的补丁在代码审查时一眼就能看出问题——它引用了一个根本不存在的结构体字段或者调用了一个早已废弃的函数。但因为在自动编译中错误信息不够明确Agent 没能及时收敛。有一次它甚至是杜撰了一个编译选项导致make直接失败。我可以想象它在这一步上的心理活动如果选项名字看起来合理那就像是有这么一回事。我的应对措施是在 Agent 的反馈链路中加入了一个编译错误信息解释层。这个解释层不是简单地转发错误而是把错误信息分类如果是符号不存在、字段不存在的错误就明确告诉 Agent你引用了不存在的符号请检查代码是否基于真实的内核头文件。如果是 API 类型不匹配就告诉 Agent建议查找当前内核版本中该接口的真实签名而不是猜测。加了解释层之后Agent 的幻觉补丁数量大幅下降无效迭代的占比从将近 50% 降到了不到 20%。这个经验其实可以推广到所有 AI Agent 开发场景不要直接把冰冷的编译错误日志甩给模型而要做一层人类会做的那种错误转译。一个资深的开发看到编译错误不会只看字面意思而是会结合上下文猜测问题的性质。这一步转译就是让 Agent 从字面处理升级到语义理解的关键。6. 冲上第 15 名之后我对 Agent 寻优的重新认识24 小时结束时的排行榜定格在第 15 名。对我来说这个成绩的意义不在于名次本身而在于它验证了一件事当环境准备好、反馈链路畅通、搜索空间表达合理时一个 Agent 可以在一天之内完成人类几天甚至几周才能完成的探索量。但我也得诚实地说Agent 不是银弹。这 24 小时里有太多时刻如果没有人的干预整个窗口早就报废了。6.1 Agent 真正省时间的环节我认为 Agent 真正不可替代的价值是抵抗疲劳和保持全局记忆。人工调优到第十个小时人基本上已经麻木了——你会倾向于复用之前的思路你会不自觉地忽略那些看起来没有成功希望的方向。但 Agent 不会。它每一次迭代都是从头开始结合所有历史经验做决策既不疲倦也不固执。尤其在多变量组合寻优这件事上Agent 的探索方式是人类无法复制的。它会同时尝试锁拆分、核绑定、预取距离的组合调整这种组合在整个搜索空间里可能极其稀疏但一旦命中收益往往是单点优化无法企及的。6.2 人工兜底的位置不过如果你的目标是机器人自主完成一切那我建议你降低预期。在这次实战中人类仍然在几个关键节点上兜了底开局时的环境准备、评测脚本的可靠性验证、以及中途遇到幻觉补丁时的错误解释层设计。这些工作看起来不酷但它们是整个自动寻优系统能转起来的前提。我甚至认为最优的工作分配不是人做一部分、Agent 做一部分而是人做系统设计与故障处理Agent 做高带宽探索。人负责搭建一个可靠的循环基础设施Agent 在这个基础设施内高速运转。两者不是替代关系而是不同层级的配合。6.3 下一次迭代的方向这次实战之后我在思考四件事第一把领域提示模板做成可学习的。当前的提示模板是靠我手动维护的规则下一次我希望 Agent 能从历史数据中自动提炼规则再反馈给下一轮搜索。第二增加评测采样策略的智能化。当前是固定阈值判断有效性下一次可以引入贝叶斯方法动态决定跑几轮评测才能确认一个差异是否真实节省大量无效的重复评测时间。第三补丁生成从变异现有配置升级到重构部分代码路径。现在 Agent 的补丁主要是参数调节下一步我想让它通过检索相似代码模式生成针对性的代码路径替换。第四把整个系统容器化。这次的环境状态污染问题本质上是因为共享同一个宿主机环境如果每次迭代都在一个干净的容器里完成编译和评测隔离性会好很多。最后再分享一个我在这次实战里学到的、最具有迁移价值的小技巧给每轮迭代打上带时间戳和配置哈希的唯一标识。无论是评测数据、编译日志还是 Agent 的决策记录都要保存下来。因为当你需要回滚或者分析一句为什么这轮分数掉得厉害的时候没有一份完整的、可追溯的日志你会发现自己面对的就是一堆无头案。这次如果没有完整的迭代日志我也没办法在这篇文章里把第 12 次迭代、第 21 次迭代这些细节讲得这么清楚。如果你是做 Agent 开发、内核优化或者任何形式的自动寻优系统我希望这篇拆解能给你一个具体的、可参考的框架。这套方法的应用边界并不只是 NVIDIA kernel 榜单。任何一个拥有明确评估函数、可自动化构建与验证、搜索空间合理表达的领域都可以用类似的思路把人的精力从重复的试错中解放出来集中到真正需要判断力的环节上。