如何修复会复述不停的大模型:Qwen3.8-Whittle-MoE-27B-A17.8B 从69%循环率到8%的完整训练方法论
Qwen3.8-Whittle-MoE-27B-A17.8B 是一个把 Qwen3.8-27B 切分出来的开源 MoE 大模型项目,它完整公开了修复大模型"复述不停"故障的全过程:单轮循环率从 69% 一路压到 8%,方法、数据集和所有失败记录全部透明。如果你的模型也在无限复述、列表越列越长、或者突然只回一个词就沉默,这套训练方法论值得仔细看一遍。
一、先量化故障:69%循环率是怎么测出来的
谈修复之前,项目方先回答了一个看似简单的问题:模型到底有多少次在循环?
这个 MoE 模型直接从 Qwen3.8-27B 切分而来,基线的单轮循环率是 69%——几乎每 10 次长回答里有 7 次会陷入重复。但得出这个数字的过程有两个大坑。
1. 旧指标对最常见的复述是"盲"的
最初用的指标是rep-4gram(重复 4-gram 占比),它能抓"同一句话逐字重复"的情况,比如某次生成把 "Providing help with gaming" 连续输出了 6 遍。
但更常见的故障是模板式复述:模型反复使用同一个句式骨架,只轮换结尾的词——
- Helping with financial planning
- Helping with health and wellness
- Helping with travel and tourism
- ……
这些行两两之间没有一个 4-gram 是相同的,rep-4gram 打分是干净的 0.000。指标完全"看不见"这种故障。这就是为什么此前多轮修复都在数据上"看起来还行",用户体验却依旧复述不停——不是训练的错,是仪表看不见缺陷。
2. 换上一套"三件套"新指标
团队改用三模式判定,任一模式越线即判该次生成失败(实现见仓库中的 loop_test.py):
| 检测模式 | 阈值 | 抓什么 |
|---|---|---|
| 4-gram 重复(rep4) | > 0.15 | 逐字重复同一句 |
| 整行重复(line_dup) | > 0.20 | 去掉序号/符号后整行重复 |
| 模板头重复(tmpl_rep) | > 0.40 | 列表项共用同一句首两词 |
配套的铁律写进了发现日志:任何指标用来下结论之前,先拿已知故障样本和已知干净样本验证过。实测中干净样本打 0.200,真实故障全部 ≥ 0.609,0.40 的阈值两侧都有真实余量。
完整的测试脚本就是 loop_test.py:12 条单轮提示词 × 3 个随机种子、6 条结构化输出、2 段各 7 轮的多轮对话,共约 90 次生成。项目公布的所有循环率数字都可以用它复现或挑战。
二、核心发现:复述和停止是同一条轴
这是整个项目最有价值的结论:复述与提前截断是同一种行为的两个端面,不是两个独立的 bug。
五轮实验的数据地图
在最终解法之前,团队做了 5 轮不同的训练配置(同一套测试集、同一个基线,记录在 WHITTLE_FINDINGS.md):
| 轮次 | 数据策略 | 教师目标中的 EOS(结束符) | 单轮循环率 |
|---|---|---|---|
| 基线 | 不修 | 保留 | 69% |
| A | 仅长文档蒸馏 | 保留 | 长回答仍在循环 |
| B | 增加 on-policy 循环样本(8 倍权重) | 保留 | ~0%,但 6/16 回答被截断 |
| C | 循环样本减半 + 全局屏蔽 EOS | 全部屏蔽 | 90% |
| D | 8 倍循环样本,仅对循环行屏蔽 EOS | 部分屏蔽 | 75% |
规律一目了然:越接近"教会模型停",截断越严重;越保护截断,循环越回来。
机制在回头看时几乎是显然的:在循环样本上,学生模型刚开始复述的那一刻,教师模型的意见几乎都是"该停了"。这是整批数据中最大的分歧点,梯度被它主导。训练久了,模型学到的就不只是"在循环点停下",而是泛化成"到处都早点停"——有的回答只剩一个 "Sure",有的直接是空的。
推理端验证:EOS 偏置剂量曲线
为了证明这是模型本身的性质而非训练事故的产物,团队直接在推理端做了实验:给 EOS 对数概率加正偏置,人为"推模型停下",从 0 一路加到 +9。
结果很说明问题:循环确实从 11/12 降到 0/12,但中位回答长度也从 278 词掉到 7 词。这个旋钮从"全循环"一路滑向"全沉默",中间根本没有健康的工作点。
这等于证明了一件事:"答案完了就该停"的知识完全不在权重里。任何推理端的全局压力、神经元修补都救不回缺失的信息——只能靠训练把它放进权重。
三、v2 方案:蒸馏"完整答案",教会模型何时停
想清楚缺什么之后,解法就明确了:蒸馏那种"因为写完了而结束"的完整答案。
教师数据集:245 份完整答案
让父模型 Qwen3.8-27B 回答同一批曾触发循环的提示词,并在每个位置采集前 32 个 token 的完整概率分布(不止正确答案,还有教师在每个位置的概率判断)。数据集刻意覆盖所有长度尺度:
- 一句话就结束的事实型短答案(7 个 token)
- 中等长度的解释
- 数到第 N 项就结束的长列表(最长 4000 token 的结构化文档)
- 代码回答
- 多轮对话——且每段对话的最后一轮故意要求"总结整段对话",因为实测中模型退化最严重的正是后段轮次
有两个细节救了这一轮,也最值得学习者注意:
- 先生成数据前先做泄漏审计。最初测试用的 12 条提示词里有 11 条已经出现在训练列表里——拿它们训练再拿去测试,循环率必然下降,但数字毫无意义。最终训练集与测试集完全重划。
- 只用长答案的数据集教会的是"长度先验"而不是"停止"。如果每条答案都 150–400 词,模型学到的捷径是"到 300 词左右就停"——那只是把 EOS 偏置曲线用数据重造了一遍。必须混入全长度尺度,才能教会"依据内容判断何时结束"。
v2 的训练适配器就保存在仓库的 v2-adapter/ 中。
四、v2.2 停止门:0.33M 参数打赢 1.26 亿参数的 LoRA
如果说 v2 教会了模型"完整答案长什么样",v2.2 又加装了一小块"硬件":停止门(stop gate)。
Qwen3.5-MoE 风格的检查点里,每层都有一组出厂时是随机初始化的张量shared_expert_gate——共 64 个、合计 0.33M 参数,只占整个模型的 0.001%。它位于每个 token 都必然经过的共享专家路径上。
做法是:用父模型蒸馏 + 在"预测终止符的那个位置"上做显式监督,把这 64 个门训练成每个 token 都生效的停止信号。由于标准 llama.cpp 本来就会应用这个张量,权重直接可用、无需改任何代码。
实测效果(README.md):
| 指标 | v2.1 | v2.2 |
|---|---|---|
| 计数列表能终止的比例 | 18/36 | 27/36 |
| 对话轮次能终止的比例(48 轮) | 27/48 | 41/48 |
| 最坏窗口重复度 | 0.237 | 0.048 |
对照实验同样关键:用同一目标训练一个125.8M 参数的 shared-expert LoRA,只做到 25/36 终止、超长率 3.24x——0.33M 的停止门打赢了380 倍参数量的方案。道理也在点子上:停止信号必须放在"永远活跃"的路径上,而不是放在随机才被选中的专家分支里。
⚠️ 这组门张量还有一个教训:第一次蒸馏采集差了一位——采集在终止符前一格就停了,于是权重最高的训练位置恰好是教师预测"换行"的位置,模型被放大了 32 倍地学会了"继续说"。规则:用蒸馏 logits 训练停止行为时,务必断言权重最高的位置真的携带终止符标签。
旧版 v2.2 门张量归档在 v2.2/shared_expert_gate.safetensors,方便对照。
五、修复效果全景:循环率从 69% 到 8%
最终数字(与基线相同的提示词、种子和测试脚本,README.md):
| 指标 | 修复前基线 | v2.1 修复后 |
|---|---|---|
| 单轮循环率(12 提示词 × 3 种子) | 69% | 8% |
| 多轮对话循环率 | 64% | 7% |
| 后段轮次(第 5 轮起)循环率 | 56% | 8% |
| 结构化输出(SQL/HTML/表格)循环率 | ~75% | 22% |
| 静默或截断的回答 | 未测 | 0 |
| 知识题库(39 题) | 28/39 | 28/39 |
| 中位回答长度 | 268 词 | 388 词 |
📊 循环率降到原来的九分之一,知识分没有受损,中位回答长度反而变长了——说明模型不是学会了"用沉默来停",而是真正学会了"说完该说的再停"。
六、踩坑清单:训练"停止"行为的 6 个陷阱
这个项目的价值不只在于最终模型,更在于它把错误完整公开了(13 条完整记录见 WHITTLE_FINDINGS.md),这里挑 6 条最通用的:
- 只测故障的验收关卡,会被坏模型通过。只回一个词的模型,重复度得分是 0.000——你会发布一个只说 "Sure" 的大模型。规则:每个验收关卡至少配一个"修复过头时会更差"的指标(这里是回答字数下限)。
- 全局屏蔽 EOS = 摧毁停止能力。起初为了去掉"该停"的有害梯度,把教师目标里的 EOS 全部屏蔽,结果循环率反弹到 90%——因为在干净文档结尾,EOS 恰恰是正确答案。规则:一个信号在某种上下文有害、在另一种上下文正确时,用条件化处理,不要全局删除。
- 门控提示词泄漏进训练集。训练和测试用同一批提示词,数字就变成了记忆测试。规则:先生成数据前审计重叠,不要等训练完才发现。
- 够不着的检查点。一次约 28 步的训练设了
save_steps=25,只存一个检查点,中途崩了等于白跑。后来固定为"保存间隔 ≤ 总步数 / 3"。 - 悄悄吃显存的优化器。默认 fp32 AdamW 占 8.2GB 动量状态,换 paged 8bit AdamW 省出 3.7GB——正好够把每步 token 预算翻倍。
- 专家权重静默加载成随机噪声。键名不匹配时权重被随机初始化,日志一切正常、输出全是胡话。规则:每次加载后断言专家权重非平凡。
七、如何部署运行 Qwen3.8-Whittle-MoE-27B-A17.8B
架构上,它是从 Qwen3.8-27B 切出的"事后 MoE":每层 17408 宽的稠密 FFN 被切成 64 个宽度 192 的专家碎片 + 1 个永远在线的宽度 5120 共享专家(64×192 + 5120 = 17408,算术完全精确,没有发明任何新权重)。路由器每个 token 只选 16 个碎片,每个 token 实际激活 17.8B 参数(总参数 27B)——量化后 24GB 显存可跑,还能切分到两张 12GB 显卡。
当前版本是 v2.2.1(根目录权重,config.json 为模型配置),修复了开启思考模式时在思维链内部提前结束回合的问题。用 llama.cpp 部署:
llama-server -m Whittle-MoE-27B-A18B-v2.2-Q4_K_M.gguf --host 0.0.0.0 --port 8090 -ngl 99 -c 8192 -fa on --jinja📌 注意:GGUF 量化版尚未用 v2.2.1 的新门张量重建,用量化版时请先关闭思考模式。
启动后可以用仓库自带的验收脚本自测循环率:
python loop_test.py http://localhost:8090八、诚实的局限性与版本选择建议
项目对残留问题毫不含糊:
- 结构化输出仍弱:SQL、HTML、markdown 表格的生成仍有 39% 退化(比 75% 好,但还不够好);
- 能终止 ≠ 能计数:v2.2 能可靠结束列表,但不一定在要求的数量上结束。30 项以上的精确计数疑似是单遍 transformer 生成的架构深度上限——如果你的场景是"给我恰好 N 项",请固定使用 v2.1;
- 算术继承家族弱点:实测样本可见偶发算错(arith-shexp-lora/arith_eval_shexp.json);
- 所有数字都由一个人在小型测试台上测得,请当作工坊测量而非基准测试。
仓库里保留了完整谱系:v1/(早期权重)、v2/(v2 的 bf16 权重)、v2.1/adapter/、router-heal-adapter/(让模型从胡话恢复到 27/39 的 router 修复 LoRA),以及规划下一轮工作的 ROADMAP.md。
写给新手的方法论总结
如果你要接手类似的"模型行为修复"任务,从这个项目可以直接带走四条经验:
- 先测量,再修复。"它在循环"不是一个数字。先建一套可复现的测试台(像 loop_test.py),并拿已知正/负样本验证过指标,再谈方案。
- 每个验收关卡都要配"防过头"指标。单一修复指标总能被退化解满足(变沉默);必须搭配长度下限这类健康度检查。
- 缺失的知识只能靠数据放进权重。复述的本质是"不知道何时停";推理参数、神经元消融、加大专家数量都救不了缺失的知识,蒸馏"因为完成而结束"的完整答案才是解药。
- 把失败也发布出来。这个项目的真正价值,是连同所有错误——包括比不修还糟的 90% 循环率——一起把数字公开了。
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考