ARTICLE / ai

让 Agent 自己跑研究循环:45 轮 Muon 调参实战复盘

傍晚六点半,我给一个编码 Agent 立了三条规矩:只准改训练脚本、每次训练五分钟、指标只看验证集 bpb。然后我离开了键盘。到晚上十一点半,这个 Agent 自己改了 45 版训练代码,把一个小语言模型的验证集 bpb 从 2.036 压到了 1.342——降幅 34%。整个过程里,人没有参与任何一次决策。

这不是概念演示。45 轮实验的每一次提交、每一个数字、每一次保留与回退,都完整落在一个 TSV 流水文件和一条 git 分支上。这篇文章把这条流水摊开:哪些方向真赚了,哪些方向真亏了,以及这套循环最容易被忽视的一个问题——跑得越快,判定的统计严谨性就越成为瓶颈。

一、为什么值得关注

调参这件事的成本结构是错配的:最贵的资源是人的判断力,最便宜的资源是机器时间。一个工程师认真跑一组对照实验,从改代码到读结果通常以小时计;而一次五分钟的训练,机器跑完只需要五分钟。自主研究循环要解决的就是这个错配——把「改什么、怎么判断」固化成规则,让机器去消费机器时间。

这件事的原始思路来自 Karpathy 开源的 autoresearch 项目:让 LLM 在一个规则受限的环境里自己做研究。本文的实践是它在 Apple Silicon 与 MLX 框架上的移植版本,全部训练原生跑在统一内存架构上,不依赖 PyTorch 和 CUDA。相比原版,这次复盘多了一层别人很少公开的东西:45 轮里 34 轮失败的完整清单,以及循环结束之后回头看才发现的统计问题。

对读者来说,值得带走的是三样东西:一套可以直接抄的循环规则设计;一份小模型短训场景下有效与无效方向的实测清单;一个关于「自主循环的判定严谨性」的清醒认识。

二、规则设计:把研究循环关进程序化的笼子

整个系统只有三层,每层的约束都刻意做得很硬。

层硬约束设计意图
程序层只准改 train.py;prepare.py 只读;训练预算固定 300 秒;同等收益选更简单的实现控制变量:时间恒定、评测恒定、归因清晰
启动指令NEVER STOP 持续循环;单实验约 7 分钟,超 10 分钟强杀判失败;results.tsv 记录每一轮流水无人值守下保持节奏,失败也有记录
执行层改代码 → git commit → 跑训练 → 读 val_bpb → 改善则保留,变差则 git reset 回退git 分支就是实验状态机

这几条约束里最有讲究的是三条。

固定五分钟预算是整个循环的基石。 很多人以为短预算测不出东西,恰恰相反——调参比的不是「谁最终收敛更低」,而是「谁收敛得更快」。在固定时间预算下,凡是能让损失降得更快的改动都会真实地反映在指标上。时间恒定,比较才有意义;如果预算浮动,一个改动可能只是因为多训了三十秒而「赢」。

单一指标消除了争议空间。 评测函数固定在只读文件里,Agent 无权修改,val_bpb 是唯一裁判。Agent 可以对「为什么有效」编出任何故事,但数字只有一个来源。

简约性准则防止过度工程。 规则里写明:0.001 的提升如果伴随二十行丑陋代码,不值得保留;删代码带来的等值提升,反而优先保留。这条准则让循环不会一路滑向脆弱的技巧堆叠。

还有一个容易被忽略的细节:results.tsv 被 git ignore 掉,不进版本库。实验流水与代码演进是两条线——代码库里只有被保留下来的改动,流水文件里才有全部 45 轮的真相。混在一起的话,回退操作会把失败记录也删掉。

三、45 轮的完整轨迹:11 个 keep

数据快照于当晚 23:30:流水文件共 45 行(1 行基线 + 44 轮实验),11 个 keep,34 个 discard。会话从 18:39 持续到 23:34 以后,平均约 6 到 7 分钟一轮,含改代码和读结果的时间。

下表是完整的保留链,单步增益相对上一个 keep 计算:

顺序改动val_bpb单步增益内存
基线初始配置2.0361—8.4GB
1矩阵参数学习率降到 0.011.6696−18.0%8.4GB
2总 batch 减半到 327681.6419−1.7%8.4GB
3引入 Muon(LR 0.04 + decay 0.05)1.4280−13.0%8.4GB
4warmdown 比例扩到 0.91.3881−2.8%8.4GB
5移除 logit soft cap1.3734−1.1%6.9GB
6batch 再减半到 163841.3639−0.7%3.8GB
7两个优化器都保 FP32 主权重1.3627−0.1%3.8GB
8注意力分块 512 + 裁剪掩码键区1.3554−0.5%3.8GB
9Muon 按矩阵形状分批归一化1.3503−0.4%3.8GB
10编译梯度计算并预建掩码1.3418−0.6%3.8GB

整条曲线的形状很典型:前三步砍掉了大部分空间(合计 −29.9%),中段是若干个 1% 到 3% 的工程性收益,后段进入小幅磨的阶段——这个「磨」字的含义,第六节会展开讲,它比看起来更有意思。

四、真正有效的五个方向

矩阵参数学习率是最大的单杠杆。 把矩阵参数的学习率调到 0.01,单步 −18%,是全程最大的一次收益。这一步发生在一切花哨改动之前——在动优化器、动架构之前,先确认学习率量级没有数量级级别的错配,是任何调参场景的第一刀。

batch 减半的本质是买更多步数。 固定五分钟里,batch 从 65536 减到 32768,训练步数从 310 涨到 800 以上(后续轮次实测 800 到 930 步)。小模型加短训的场景下,高频小幅更新占优;这一判断的直接证据是第二次减半(到 16384)仍然有正收益。它的通用表述是:固定预算下,缩短每步消耗就等于变相增加训练量。

Muon 是第二个大杠杆。 矩阵类参数改用 Muon 优化器更新——用 Newton-Schulz 迭代把更新方向正交化,embedding、unembedding 和标量参数仍留给 AdamW。LR 0.04、权重衰减 0.05,单步 −13%。后续两轮继续在 Muon 上挖出收益:warmdown 比例扩到 0.9(前 10% 的训练里就把学习率降下来,对短训尤其合适),以及移除 logit soft cap。

移除 logit soft cap 的启示是「稳定项可能是多余约束」。 soft cap 是防止 logits 无界增长的保护机制,但在小模型短训场景里,它本身成了天花板。去掉之后单步 −1.1%,内存还从 8.4GB 降到 6.9GB。不是所有防坏机制的保险都免费。

后段三项是吞吐优化的收益。 注意力分块、按矩阵形状分批做 Muon 更新、编译梯度计算——这三项机制上都是让同样的五分钟跑更多步。它们的收益量级小(每项不到 1%),方向却是可靠的:预算恒定时,吞吐就是精度。

五、34 个负结果:失败清单同样值钱

44 轮实验里 34 轮被回退,失败率 76%。这张清单的价值不亚于 keep 链——它标记了这个场景下「看起来合理但不赚钱」的方向,防止后来者重复踩坑。

方向代表实验结果注记
embedding 学习率大幅上调LR 0.2 / 0.3均被回退
加深网络depth 2/3/5depth 3 跑 962 步仍 1.3647,没赢
KV 头共享(GQA 式压缩)两查询头共享一 KV 头val 冲到 1.470,大幅回退(但见下文干扰说明)
Muon Newton-Schulz 减步5 步减到 3 步被回退,正交化精度不能省
绑定 embedding 与输出投影tie + 输出投影独立 LR被回退
编译纯 Adam 更新路径编译优化器 step严重后期吞吐劣化——编译不是万能加速
同端点 cosine warmdown与线性 warmdown 对照被回退
可学习注意力 log-scales逐头正标量可学习被回退
输出头独立小学习率LR 0.008被回退
更小 batch8192两轮尝试均被回退

两点补充说明,都关于「怎么读这张表」。

第一,discard 不等于方案被证伪。KV 头共享那一轮冲到 1.470,看起来是惨败,但该轮只跑了 480 步(正常应有 800 步以上),流水注记里明确标了「严重间歇性降速」——系统负载干扰把 val_bpb 推高了。这个方案在这个场景下大概率确实不占优,但 1.470 这个数字本身是被污染的。循环的做法是把受扰轮次标记出来择机复测,而不是把干扰当定论。

第二,76% 的失败率就是这个循环的常态。没有失败清单的自主实验报告才值得怀疑——如果一轮循环宣称大部分实验都成功,要么是判定标准太松,要么是在追噪声(见下一节)。

六、单次采样的代价:噪声、运行最小值与统计严谨性

这是整篇复盘里最值钱的一节,也是回头才看清的问题。

同一个 train.py 原样重跑,val_bpb 会波动多少?答案是约 0.03——即使随机种子固定,GPU 归约的非确定性也会带来这个量级的抖动。现在回头看保留链的尾段:1.3734 之后连续五个 keep,每步绝对增益分别为 0.009、0.001、0.007、0.005、0.009——全部落在单次噪声带以内。

问题的机制在于循环的判定规则:只有当新结果低于历史最优时才保留,且保留后从不再复测。这意味着记录下来的曲线是一条乐观的运行最小值——哪怕一个改动毫无效果,它也有约一半的概率因为噪声恰好偏低而被保留。跑的轮数越多,这条曲线被噪声抬得越漂亮,跟真实效果的偏差反而越大。换一个说法:判定不带统计检验时,轮数越多不是越可信,而是更快地追噪声。

仓库里其实已经为此准备了工具:一个显著性门控脚本,支持对同一配置跑多个种子、用 bootstrap 估计候选优于当前最优的概率、并对配置哈希去重防止重复实验。但这次的 45 轮会话跑的是单采样快线,没有启用它。

诚实的结论是分层的:头部收益完全可信——矩阵学习率单步绝对降幅 0.367、Muon 单步绝对降幅 0.214,分别是噪声带的 12 倍和 7 倍,怎么测都在;中段 1% 到 3% 的收益大概率真实,但量级存在 ±0.03 的不确定度;尾段五个 keep 的排序应当视为暂定,它们方向上说得通(都是吞吐或数值稳定性改进),精确收益需要多种子复测才能定论。

对想做自主循环的人,这是一个比「怎么跑起来」更重要的认知:循环的生成速度上来之后,判定严谨性是下一个瓶颈。 五分钟一轮的产能,配得上多种子复测的成本——对头部改动之后的小额收益,一轮复测只多花五分钟,却能把「追噪声」变成「追真话」。

七、工程细节里的坑

系统负载会污染实验,步数是报警器。 会话中段,桌面并发任务把训练吞吐从每秒约 5.5 万 token 打到 5 千,步数直接腰斩,val_bpb 虚高。识别方法不是看指标,而是看流水里的步数是否异常——预期 800 步的轮次只跑了 130 步,这一轮的指标就不可信。受扰轮次标记 discard 择机复测,不要把干扰当结论。这也解释了为什么流水注记里反复出现「后期降速」「间歇性降速」——Agent 自己在给每一轮标注可疑信号。

显存叙事要按表格说话,不要按直觉。 一个反直觉的实测细节:给两个优化器都引入 FP32 主权重(BF16 前向计算,FP32 累积更新),显存没有推高,持平在 3.8GB。而整个会话显存从 8.4GB 降到 3.8GB,主要来自移除 soft cap 和 batch 减半两步。「加了精度更高的状态显存反而降」这个流传的说法,按流水表逐格核对后应表述为:FP32 主权重在显存已降下来之后引入,未推高显存;降显存的功劳属于另外两步。

编译收益巨大但行为不稳。 mx.compile 对吞吐影响显著(最终版编译梯度计算后单轮步数升到 931),但编译纯 Adam 更新路径那一轮出现了严重的后期吞吐劣化。编译每一处都要单独验证,不存在「编了就快」。

日志要重定向,不要用 tee。 进度条输出经过 tee 会污染日志解析,重定向到文件再用正则抽取 val_bpb 才是稳定读法。

八、成本账

平均 6 到 7 分钟一轮,4 小时 55 分钟跑了 45 轮。照这个速率,一晚 8 小时大约 60 到 70 轮。作为对照,一个工程师人工跑对照实验,认真做一天大概 5 到 10 组,而且其中大半时间花在机械环节:改参数、启动训练、抄数字。

这笔账的本质是:机器时间便宜,人的判断贵。自主循环的做法是把人的判断一次性写进规则文件(什么能改、怎么判好坏、什么算更简单),然后让机器消费机器时间。人最终拿到的是一张 45 行的流水表——包括 34 行失败记录。这些失败记录的另一个用途是塑造后续搜索:Agent 后期的实验明显避开了早期已失败的方向,方向空间是被失败清单共同塑形的。

九、常见误区

误区一:轮数越多越可信。 判定不带统计检验时,多轮只是更快地追噪声——运行最小值曲线会随轮数系统性偏离真实效果。正确姿势是头部改动用单采样快筛,小额收益用多种子复测定案。

误区二:discard 就是方案错了。 至少要检查该轮步数是否正常。受系统负载干扰的轮次,指标虚高与方案质量无关,直接回退会把好方向误杀(附带损失:下次还会有人再试一遍)。

误区三:五分钟预算太短,测不出真实训练的表现。 短预算测的是收敛速度的差异,而调参恰恰主要在调收敛速度。它测不了的是长训才能暴露的问题(比如后期不稳定),这是边界而不是缺陷——知道边界在哪,才知道哪些结论可以外推。

误区四:循环会自己发现所有方向。 方向空间由初始规则、失败清单和简约性准则共同框定。这次 45 轮全部围绕优化器超参、batch 尺寸和吞吐工程打转,没有一轮尝试数据侧改动——因为数据管线在只读文件里。想让它探索什么,就把那扇门在规则里打开。

十、总结

这场五小时的无人值守实验,一句话概括:规则写的不是「怎么训练」,而是「怎么判断」。 五分钟固定预算、单一只读评测、单文件修改权、简约性准则、git 状态机——每一条都是为了把「改善」这个模糊词变成可机判的谓词。剩下的 45 轮,机器自己跑完了。

三个最值得抄走的实践:其一,调参先砍学习率量级错配,这次 −18% 的最大单步收益来自最朴素的一刀;其二,固定预算下 batch 减半等于买步数,小模型短训场景屡试不爽;其三,自主循环跑到两位数轮次之后,给判定补上多种子显著性检验——生成速度和判定严谨性必须配套升级,否则循环只是在高效率地产出一条漂亮的假曲线。

有效方向与失败方向的两张清单,比结论本身更长期有用:它们是这台「研究机器」跑过之后留下的地图。


数据快照声明:本文全部数字取自实验流水文件(45 行)与 git 提交历史(快照时点 2026-10-02 23:30,会话仍在进行中,后续轮次不影响已有记录)。单步增益由流水数字计算;噪声带幅度(约 0.03)引自仓库内显著性工具的文档注释,为该环境下的实测口径。所有实验在本地 Apple Silicon 统一内存环境完成,未做跨平台复现;尾段保留链的排序属暂定结论,待多种子复测。