ARTICLE / ai
自主研究循环续跑 144 轮:97 连败与四个真实杠杆
前一篇复盘写到 45 轮:那个编码 Agent 用五小时把一个小语言模型的验证集 bpb 从 2.036 压到 1.342,然后我按下了「暂停写作」,循环本身没有停。到第二天下午三点,results.tsv 已经积累到 189 行——续跑的 144 轮里只保留(keep)了 7 次,把最优值从 1.3418 磨到了 1.3173,而在最后一次保留之后,是长达 97 轮的连续失败。
这篇文章把续跑段摊开来看。它比前 45 轮更有意思的地方在于:简单杠杆已经摘完,剩下的都是要动架构的改动;失败不再是零散的试错,而是成片的方向性阵亡——97 连败不是循环失灵,恰恰是循环在如实报告「这个方向的低垂果实已经摘完了」。
先交底:数据口径
续跑段的全部数字来自三处:results.tsv 流水文件(189 行实验记录)、git 提交历史(每个实验一个 commit,时间戳可查)、train.py 的 diff(每个保留的改动都能看到具体代码)。快照时点为 2026-10-03 15:00,此后会话仍在进行,新增轮次不在本文范围内。
口径说明:上一篇的快照是前 45 行(最优 1.341781),本文覆盖第 46 到 189 行,共 144 轮,其中 keep 7 次、discard 137 次,失败率 95%。整个会话从基线 2.036 到现在,累计下降 35.3%;续跑段单段下降 1.82%——一段典型的「深水区」收益曲线。
7 次保留:一条完整的收益链
续跑段 7 次 keep 按时间排开,恰好构成一条从「超参数」滑向「架构」的轨迹:
| 提交 | 时间 | 改动 | val_bpb | 单步增益 |
|---|---|---|---|---|
| af31199 | 23:54 | 零初始化 FP32 词表 bias,挂到 head 学习率组 | 1.339563 | −0.0022 |
| d47e329 | 00:00 | 短注意力窗 1024→512 | 1.335648 | −0.0039 |
| b44e708 | 00:06 | 短注意力窗 512→256 | 1.327178 | −0.0085 |
| ecb6421 | 00:24 | Muon momentum 0.95→0.90 | 1.326745 | −0.0004 |
| 1a2aebc | 00:30 | Muon momentum 0.90→0.85 | 1.322885 | −0.0039 |
| 4f4f135 | 03:43 | 局部窗砍到 64 + query chunk 512→256 | 1.317519 | −0.0054 |
| 985b4e5 | 04:18 | Value Embedding 从隔层放置改为首层+末层 | 1.317328 | −0.0002 |
前五个 commit 挤在半小时内,后两个隔了三个多小时。这个时间分布本身就是信息:超参类改动(动量、窗口比例)验证快、收益立现;架构类改动(注意力分块组合、VE 位置)要靠更长的失败铺垫。三次 hour-级空转后面细说。
四个真实杠杆
杠杆一:注意力短窗还能再砍一半,再砍一半
窗口比例的演进路径是 长窗//2 → //4 → //8 → //32(对应 1024 → 512 → 256 → 64 token)。每砍一次都有正收益,其中 512→256 那一刀(−0.0085)是续跑段最大的单步增益。
这个模型 12 层,窗口模式 SSSL——9 个短窗层、3 个长窗层,只有最后保持全局 2048。直觉上「看得到更多上下文总没错」,但在这个规模的短训任务里,序列长度本身只有 2048:短窗层砍到 256 甚至 64,信息损失的代价远小于省下的注意力计算和优化噪声。真正反直觉的是 64 token 这个量级——单独砍到 64 是一次 near miss(1.325819,比当时最优差 0.008),配上 query chunk 256 的分块方式才落地成 keep。
也就是说,方向对了一半不够。「窗砍小」和「怎么按窗分块算注意力」是两个耦合的决策,单独调前者会得到「收益递减」的假象,组合起来才跳到新平台。
杠杆二:momentum 的连续小步降档
Muon 优化器的 momentum 从 0.95 降到 0.90(−0.0004),再降到 0.85(−0.0039)。第三次尝试 0.80 被拒(1.325359,比 0.85 差)。
两次 keep 的形态很典型:第一刀几乎看不见(0.0004,远在同 config 重跑的噪声带 ±0.03 之内),第二刀才显出来。这种「第一刀在噪声里、第二刀出信号」的模式,单看任何一次都会误判——它是这两次改动方向一致性的证据,而不是单次改动的证据。Agent 的判定规则(低于历史最优才保留)把 0.90 那次也留下了,事后看是对的:它为 0.85 铺了路。但严格说,0.90 那一格数字本身不可信,可信的是「降档方向」这个整体判断。
杠杆三:Value Embedding 放哪里,比放不放更关键
Value Embedding(VE)是给注意力层的 value 通路外挂的可学习嵌入表。改动前它是隔层放置(偶数层有、奇数层无),改动后只在首层和末层放置——只动了 has_ve() 这个两行函数,换来 −0.0002 的微小但确定(按判定规则)的改善。
真正值得注意的是围绕它的 21 次失败。续跑段里 Agent 围绕 VE 反复做文章:共享嵌入表、独立学习率、去掉门控、砍门控通道数、用 bigram 表替换首层 VE、砍 value head 宽度……21 次尝试全军覆没,只有「位置调整」这一次成功。这个分布说明:VE 这套机制的有效性主要不由「表怎么训」决定,而由「信息在哪几层注入」决定。首层负责把词形信息早早注入残差流,末层负责在输出前做最后一次修正——中间层夹着它反而是干扰。
杠杆四:零初始化的 FP32 词表 bias
在输出 logits 上加一个零初始化的 FP32 偏置向量,挂到 head 学习率组。改动三个 token 的词表偏置听起来平平无奇,但它让 12 万输出通道各自从零开始学一个全局偏移,相当于给 softmax 前的分布留了一个免费的校准自由度。−0.0022 的收益不大,但这是全文最便宜的改动——两行代码。
97 连败:失败的方向性比失败本身更值钱
最后一次 keep 之后,循环又跑了 97 轮,全部 discard。把 97 轮按方向聚类,阵亡地图是这样的:
| 方向 | 尝试数 | 代表性失败 |
|---|---|---|
| VE/嵌入侧变体 | 22 | bigram 表替换 VE、砍 value head、共享表、调门控 |
| x0 混合系数 | 8 | 零初始化、冻结、调学习率、调 epsilon |
| 编译路径扩展 | 5 | 整步编译、优化器步编译——全部后段吞吐劣化 |
| LR warmdown 变体 | 5 | 平方根、线性、只对嵌入表——没有一个超过现方案 |
| z-loss | 2 | 最接近的一次:1.3205,距最优 0.003 |
| 注意力/其他 | 102 | 大 batch、one-KV-head、深度换宽度等 |
三个观察。
第一,97 连败不是循环失灵,是方向空间在收口。 五分钟预算的短训里,能被超参摘动的收益在前 90 轮就基本摘完了;剩下的都是要动架构的硬骨头。最后 97 轮里有 4 次「差 0.006 以内」的 near-miss——z-loss(训练时加 log-partition 正则、评测时用普通交叉熵)拿到 1.3205,如果单采样噪声带是 ±0.03,它和最优值在统计上不可区分。这些不是「错了」,是「分不出来」。
第二,失败集群在替 Agent 画边界。 编译路径五连败的形态高度一致:编译纯 Adam 更新、整步编译带 live 系数——全部出现「后段吞吐劣化」(晚段步速掉到每秒几千 token,轮内步数从 900+ 掉到 600-800)。这验证了前一篇的结论:mx.compile 每一处都要单独验证,「编了就快」不存在,而且编译坑的模式是可复用的知识——同一形态的失败第二次出现就该直接跳过。
「低于历史最优才保留」的判据灰色地带在扩大。 单采样判定在前 45 轮是好规则,因为它避免复测省时间;到 97 连败段,near-miss 密度上来了——97 轮里 4 次进 0.006 带宽,其中 2 次在 0.004 以内。这些轮次全部被单采样判死,但它们很可能和最优值统计等价。仓库里早就备好了 rigor.py(多种子采样 + bootstrap 显著性门控,同 config 重跑波动 ~0.03 的场景下判「真改善」),但这个会话依然没有启用它——97 连败的最大教训不是「方向穷尽了」,而是**「单采样判定在这个阶段已经付不起误杀的代价」**。
30 连败与一次破局
keep#55(momentum 0.85,00:30)到 keep#86(局部窗 64 + query chunk 256,03:43)之间,是整整 30 轮、约 3 小时的空转。这段时间里 Agent 在做什么:momentum 0.80、Muon 衰减系数、学习率组合、x0 系数初始化、嵌入初始化方差……全是围绕已摘完的超参空间打转。
破局的方式值得注意:它不是「想出了新方向」,而是把两个已经分别验证过的组件做了组合——「局部窗 64」单用是 near miss(1.325819),「query chunk 256」单用是 near-tie(1.327413),组合之后 1.3175,一步跳过 0.0054。这种「两个次优组合成最优」的模式在前 45 轮里没有出现过,是深水区特有的:单变量实验全部收敛之后,组合空间成了唯一还没扫过的地皮。代价是组合空间是平方级的,Agent 试了 3 次组合才命中(window64+chunk256 命中前,还试过 window256+chunk256 和 window64 单用)。
续跑段改判了前一篇的两个结论
「边际收益递减」要分方向说。 前一篇笼统地写「越往后收益越小」。续跑段的数据支持一个更精确的版本:超参方向的收益确实枯竭了(momentum 第三刀 0.80 直接被拒),但架构方向在 90 轮之后还能一次给出 −0.0054——比续跑段任何一次超参 keep 都大。收益递减的不是「调参」这件事,是「已经扫过的那个子空间」。
「固定预算下 batch 减半 = 买步数」有边界。 前 45 轮里 batch 16384→8192 两连涨;续跑段试着继续加大:batch 12288(1301 步)拿到 1.3195——离最优 0.002,但没过线;batch 8192×device4(1506 步)1.3530,反而更差。步数买得动的区间到头了,再往上不是「更多步数 = 更好收敛」,是优化器状态和步长的匹配问题。任何「某某招数屡试不爽」的经验都有适用半径,这个半径只有靠继续跑才能画出来。
边界
这篇的结论全部来自一个 12 层、序列 2048、五分钟预算的短训设置,语料是固定的爬取语料分片。三个明确的边界:
短窗 64、VE 首末层这些「架构杠杆」的价值高度依赖「短训 + 小模型」前提——长训任务里注意力窗的信息损失会复利放大,不能外推。所有 keep/discard 判定仍是单采样,噪声带 ±0.03(引自仓库显著性工具的文档注释)之下,0.005 量级的 keep 排序是暂定的——这一点和前一篇的声明完全一致,且续跑段让它更刺眼:97 连败里 5 次 near-miss 都在噪声带里。数据快照时点 2026-10-03 15:00,results.tsv 第 189 行的 353ca4c 提交(整步 CE128 + FP32 嵌入 scatter)还没有对应的结果行,这个实验的结论不在本文范围内。
数据快照声明:本文全部数字取自 results.tsv(189 行,快照时点 2026-10-03 15:00)与 git 提交历史(每个 keep/discard 有对应 commit 时间戳),单步增益由相邻 keep 的 bpb 差值现算。会话仍在进行中,第 190 行之后的轮次不影响本文数据。实验环境为本地统一内存架构,未做跨平台复现。