19. 第 4/5 轮的最终读数:预测头有效,大模型(8.7M)训崩了
★★★
7.4k 字 ·
源文件 PROJECT.md 第 3473 行起 ·
ruler arch read external
#19.1 三个跑次的对照(同一预算:1000 步 × batch 128 = 205 万刻)
| 跑次 | 参数量 | 预测头 | 正确率 | 认字 | d | 全字集 | 空白屏 | 门(该开/该关) |
|---|---|---|---|---|---|---|---|---|
| s2(旧基线,B16×2500) | 1.31M | 无 | 15.6% | 55.2% | 1.58 | — | 0.0% | — |
| g_small(B128) | 1.31M | 无 | 52.1% | 77.1% | 0.75 | 54.2% | 0.0% | 0.13/0.23 |
| p_small(B128) | 1.31M | 有 | 62.5% | 79.2% | 0.47 | 51.0% | 2.1% | 0.11/0.24 |
| g_big(B128) | 8.71M | 无 | 0.0% | 0.0% | 3.30 | 0.0% | 0.0% | —/0.00 |
两条独立结论
#① ★★★ 把 batch 从 16 提到 128:15.6% -> 52.1%(3.3 倍)
总刻数还更多(205 万 vs 64 万)。也就是说:
§16 那些读数全都在"喂不饱"的区间里测的 —— 之前一半的算力是浪费的。
★ 门的时机也第一次真正分开了(该开 0.13 / 该关 0.23,s2 那会儿是 0.85/0.94)。
#② ★★★ 自监督预测头:52.1% -> 62.5%(+10.4pp,相对 +20%)
| g_small(无预测头) | p_small(有) | |
|---|---|---|
| 正确率 | 52.1% | 62.5% |
| 认字 | 77.1% | 79.2% |
| 平均距离 d | 0.75 | 0.47 |
| 预测误差 curve | — | 1.0 -> 0.476 |
★★ "自主学习能力"第一次转化成了可测的更强表现。 而且它不是用别的东西换来的:认字、距离、门时机全部同时变好。
读法:预测下一帧迫使循环核把"世界/我正在打什么"压进状态, 于是认字任务本身也受益 —— 两份目标共享同一个状态。
#19.2 ⚠️⚠️ g_big(8.7M 参数)训练到一半静默崩溃
| 步 | 正确率 | 认字 | ent | emit | 门 |
|---|---|---|---|---|---|
| 600 | 31.2% | 76.0% | 0.82 | 0.50 | 0.46/0.56 |
| 700 | 2.1% | 14.6% | 1.40 | 0.77 | 0.93/0.71 |
| 800 | 0.0% | 1.0% | 0.00 | 0.00 | nan/0.00 |
| 900 | 0.0% | 1.0% | 0.00 | 0.00 | nan/0.00 |
ent=0.00 且 emit=0.00 且 loss 打印成 +0.000。全程没有任何报错。
★★ 所以 §15.4 那条猜想"加参数是正收益"只说对了一半:
"能跑得动"不等于"能训得动"。 §15.2 那张表量的是每步耗时(前向+反传的墙钟),它说明算力有富余; 但它没有量训练稳定性。8.7M 参数在 lr 2e-3 下崩了。
已加 NaN/Inf 闸门(exp31.update() 里):参数或 loss 出现 NaN/Inf 立刻抛异常,
并在错误信息里带上三条最可能的原因。现场指纹写进了注释:
ent=0 且 emit=0 = 策略输出成了常数。
★ 为什么之前没有它:clip_grad_norm_ 不会发现参数已经变成 NaN ——
它只裁剪范数。而 NaN 参数会让后续 loss 全变 NaN,打印出来像"收敛到 0"。
这类"看起来像好事"的失效最贵。
#19.3 待验证:崩溃是不是学习率太大
tools/run_H_bigLR.ps1:同配置,lr ∈ {2e-4, 5e-4, 1e-3, 2e-3},700 步
(停在崩溃点 ~750 之前,这样扫描便宜且可比),--clip 0.5。
判据(跑之前写死):
- 若 2e-4 / 5e-4 不崩且分数接近 g_small -> 是 lr 问题,大模型可用,只要调 lr
- 若全都崩 -> 不是 lr,回去查 4096 units / backbone 256 的结构问题
#19.4 ★★★★ 判据 5 的分母重测:纯模仿到 97.9% —— 问题被彻底重新定义
exp31_imitation_b128:配置与 g_small 完全相同,只把 β 全程钉在 1(纯模仿,无 RL)。
| 步 | 正确率 | 认字 | d | 无回显 | 模仿对 | 门(该开/该关) |
|---|---|---|---|---|---|---|
| 300 | 19.8% | 57.3% | 1.78 | 22.9% | 71.5% | 0.13/0.48 |
| 400 | 52.1% | 81.2% | 0.92 | 39.6% | 90.0% | —/— |
| 500 | 85.4% | 96.9% | 0.26 | 79.2% | 99.3% | 0.06/0.23 |
| 600 | 97.9% | 100.0% | 0.03 | 97.9% | 99.3% | 0.00/0.22 |
| 700 | 97.9% | 100.0% | 0.02 | 100.0% | 100.0% | 0.00/0.23 |
| 800 | 100.0% | 100.0% | 0.00 | 100.0% | 100.0% | 0.00/0.22 |
(还在跑)
#★★★★ 这推翻了 §16.1,也推翻了 §19.4 的前一版结论
| 我先后以为 | 实测 | |
|---|---|---|
| 判据 5 的分母 | 30.2% | 100.0% |
| RL 模型(p_small) | 62.5% | 62.5% |
| "判据 5 完成度" | 207%(过线)→ 73%(没过) | 62.5%(没过) |
★★★★ "认字"这件事它已经全会了。
纯模仿:正确率 100%、认字 100%、关掉回显也 100%、平均距离 0.00、 门的时机 0.00/0.22(该开时不开、该关时关,完全正确)。
而且它连"拿屏幕当前缀内存"的毛病都没有 —— 无回显 100% 说明
--echo-drop 0.5把它治好了,而且治得很彻底。★ 真正坏掉的是
β: 1→0(放手)那一段。 它不是"学得慢",是在忘掉已经会的东西:从 100% 掉到 52~62%。
#这个重新定义为什么重要
之前几个月的工作都建立在"判据 5 = 撤掉老师后还剩 50%"这个框架上, 而那个框架默认了"纯模仿是不完整的、RL 要补上剩下的"。
现在数据显示反了:
纯模仿(β=1 全程):100% ← 已经够了
放手(β:1→0) :52~62% ← RL 在破坏
只加监督 + 预测头 :62.5% ← 比纯模仿差★★ 所以当前唯一的关键问题不是"怎么让它学得更多", 而是"为什么一放手它就忘"。
§19.5 的 E1/E4 实验就是为这个问题设计的,而且现在是最高优先级。
#一条副产品:--echo-drop 0.5 的效果比 §16.3 里看到的强得多
§16.3 里 drop=0.5 时无回显 22.9%(对比 drop=0 的 2.1%),看着只是"修好了一点"。 现在在训练充分的条件下,drop=0.5 的模型无回显 100% —— 和回显开时完全一样。
★ 说明:"照抄屏幕"这个病不是治不好,而是当时训练不够。 §16.3 那个"权衡"(认字随 drop 下降)也应该在训练充分后重测 —— 当时的所有读数都还在"欠训练"区间里。
#19.4b ★★★ 真正的瓶颈浮出来了:不是认字,是字体泛化
把所有跑次并排(tools/compare_runs.py,这一版把「留出专项」列修出来了):
| 跑次 | β | 正确率 | 认字 | d | 留出字体 | 无回显 | 预测误差 | 空白屏 |
|---|---|---|---|---|---|---|---|---|
| imitation_b128 | 1.00 | 100.0% | 100.0% | 0.00 | 29.2% | 100.0% | — | 0.0% |
| g_small | 0.00 | 52.1% | 77.1% | 0.75 | 20.8% | 50.0% | — | 0.0% |
| p_small | 0.00 | 62.5% | 79.2% | 0.47 | 20.8% | 58.3% | 0.22 | 2.1% |
| p_big(8.7M) | 0.08 | 51.0% | 80.2% | 1.14 | 27.1% | 6.2% | 0.15 | 0.0% |
| s2_emitc15(旧) | 0.00 | 15.6% | 55.2% | 1.58 | — | — | — | — |
| DE(最旧) | 0.00 | 13.5% | 36.5% | 2.28 | — | — | — | — |
#★★ 两件事同时成立
① 训练字符集已经饱和到 100%,但「留出字体」一直卡在 20~29%。
所有跑次:见过的字体 ~100% (imitation_b128 就是 100.0%)
所有跑次:没见过的字体 20~29% (楷体 / 等线 / 微软正黑)★★ "认字"这个任务本身已经解决了。剩下的是"换一种字体还认不认得出"。
而且这条瓶颈不在编码器上 —— §12.9 里视觉预热(编码器端)的留出字体是 90.5%。 是策略端没把编码器的泛化能力用起来。 这才是下一步该攻的地方。
判据:
--held-eval就是为它准备的,只是我之前一直没把它当主指标看。
② p_big(8.7M)这一次没有崩,而且有两个亮点
- 预测误差 0.15(p_small 是 0.22)—— 参数多确实让世界模型更准
- 留出字体 27.1%,高于 g_small / p_small 的 20.8%
★ 但它的 无回显 只有 6.2%(p_small 58.3%)—— 说明它还没学会不靠回显,
这和"训练步数不够 / β 还没降完"一致(它最后一行 β=0.08)。
★ 回顾 §19.2:g_big 那次崩在 lr 2e-3;这次 p_big 也是 lr 2e-3 却没崩
(步 600 时正确率 51%)。所以崩溃不是"大模型必然",而是"大模型 + 某个 lr 下的不稳定" ——
run_H_bigLR.ps1 的 lr 扫描会给出边界。
★ tools/compare_runs.py 的一个 bug 也记一笔:它认的是「留出字体」,
而日志里打的是「留出专项」—— 于是这一列一直是「—」,
我因此漏看了这条主线瓶颈整整几轮。正则已改成两个名字都收。
#19.4c ★★ loss「规律起伏」的确定答案(用 975 步的逐步曲线算出来的)
tools/loss_curve.py results/logs/curve_imitation_b128.json(975 步,逐步记录):
① 自相关(**已去趋势**,lag1..9):+0.91 +0.83 +0.72 +0.62 +0.52 +0.42 +0.33 +0.25 +0.18
参考:去趋势前 lag1 = +0.99 ← 那个数是**趋势**造成的,不是周期
② 扩池那几步的跳变:均值 1.950
池不变时的 |Δloss|:均值 0.038
★ 跳变 / 常态波动 = **52 倍**
③ 趋势(滑动平均 w=48):5.518 -> 0.020;平滑后下降步数占比 81%★★ 起伏的规律性来自课程扩池(52 倍于常态波动)。 这是设计方案,不是 bug,不需要修。 趋势明确向下(5.518 -> 0.020),看趋势就行,别看逐步。
★ 分析本身的三个坑(都已修,写下来免得重踩):
- 自相关必须先去掉趋势。 第一版直接对原序列算,得到 lag1 = +0.99 并据此报"有强周期" —— 而那个 0.99 完全是 loss 单调下降造成的。 去趋势后真正的自相关是 +0.91/+0.83/... (仍在衰减,但那是平滑造成的)。
np.diff比原序列短 1,掩码要跟着对齐(否则报boolean index did not match: 949 vs 950)。loss是「当前这一批字」的拟合误差,不是「整个字集」的误差。 池不变时换一批字,loss 自然抖 —— 这不是噪声,是任务的真实难度差异。
#19.4d ⚠️ 一个必须先说清的现状:认字任务已经被"做完了",但它太简单
| 值 | |
|---|---|
| 训练字符集正确率 | 100.0% |
| 认字(首键) | 100.0% |
| 平均编辑距离 | 0.00 |
| 模仿对 | 100.0% |
| 训练 loss | 0.020 |
| 留出字体 | 29.2% |
★ "在训练字符集上认字"这件事没有任何剩余空间了。 剩下的全部空间在字体泛化(29.2%)—— 那是唯一还没解决的部分。
结论:下一步的优化目标必须从"提高认字率"改成"提高留出字体", 否则任何实验都会顶在 100% 的天花板上,读不出差别。 这也是为什么 §19.4b 那条列修好之后,"留出专项"应该成为主指标。
#19.4e ★★★ 收尾:imitation_b128 跑满 1000 步的完整曲线
| 步 | 训练字符集 | 认字 | d | 无回显 | 留出字体 | 模仿对 |
|---|---|---|---|---|---|---|
| 600 | 97.9% | 100.0% | 0.03 | 97.9% | 25.0% | 99.3% |
| 700 | 97.9% | 100.0% | 0.02 | 100.0% | 25.0% | 100.0% |
| 800 | 100.0% | 100.0% | 0.00 | 100.0% | 29.2% | 100.0% |
| 900 | 100.0% | 100.0% | 0.00 | 100.0% | 39.6% | 100.0% |
| 1000 | 100.0% | 100.0% | 0.00 | 100.0% | 33.3% | 100.0% |
#★★★ 三条同时成立,且互不矛盾
- 训练字符集:100%,距离 0.00 —— 这个任务在训练分布上已经完全解决。
- 无回显:100% —— 它完全不依赖屏幕上的回显在打字。
(
--echo-drop 0.5治好了 §16.2 那个"拿屏幕当前缀内存"的病,而且治得彻底。) - 留出字体:25% -> 39.6% -> 33.3%,没有上升趋势,一直在抖 —— 这是唯一剩下的瓶颈。
★★★ 一句话:它把见过的 4 种字体"背下来"了,没有真正学会"认字形"。
而编码器端(§12.9 视觉预热)的留出字体是 90.5% —— 泛化能力在编码器里有,策略端没用起来。
★ 下一步的主指标因此必须换成留出字体。在训练字符集上已经 100%, 任何不改这个指标上限的实验都读不出差别(这解释了为什么 §16/§19 那些跑次"看起来都差不多")。