先用3行代码复现,再动主流程:一次字数校验误判的排查复盘

发布于 2026年09月26日 ❤ 0 👁 29 阅读 💬 2 评论

今天有件事挺值得记下来。我一直在本地跑一个 Qwen3 的小量化模型做中文摘要,之前的表现一直很怪:句子本身读得通,但每段结尾都会突然冒出一串完全不相干的字,像"综上所述因此我们可以得出结论如下所示"接一段谁也看不懂的东西,然后自然断掉。我一开始怀疑是量化把模型搞坏了,甚至动了换模型的念头。

后来我改变做法,不再猜,直接把原始输出打出来看。方法是绕开我自己的封装脚本,用最原始的方式调接口,把每次返回的完整字符串连同 finish_reason 一起落盘,连 token id 都存下来。对比几次结果就发现问题:坏掉的位置全都发生在停止符附近——模型确实吐出了终止标记 EOS,但紧接着它又开始生成新的一段。也就是说不是模型疯了,是我这边没在 EOS 处切断,把后面模型"续写"的垃圾一并收下了。因为我用的推理框架把 stop token 配漏了,模型吐完 EOS 之后还会继续跑,而它继续跑出来的内容就完全不受控了。

坑就在这里。我以为的"模型质量问题",其实是配置问题,而且这个 bug 有很强的欺骗性:它只在输出较长、或者正好触发终止条件时才出现,短测试根本复现不了。更麻烦的是它看起来像"偶发",所以很容易被归到"小模型就是不稳"这种模糊结论里,然后就再也不查了。

修法很小:在请求参数里显式加上停止标记,并在客户端再兜一层——遇到 EOS 对应的 token 就把这段丢弃,后面的内容整个截掉。两处都加上是因为服务端的停止参数依赖推理框架的版本和适配层,不一定全平台一致;客户端自己再判断一次,成本几乎为零,但能兜住框架升级之后参数语义变化的情况。改完之后同样的输入试了二十多次,尾巴没了,而且速度反而快了一点,因为不用再生成那几百个没用的 token。

可复用的结论有三条。第一,凡是"模型输出奇怪"的现象,先把 stop 条件、模板和特殊 token 处理查一遍,这三样占了我踩过的坑的一大半,比换模型便宜得多。第二,调试这类问题一定要保留原始输出,别用自己封装好的函数看结果——封装层正好把出问题的信息吃掉了。第三,判断一个 bug 是模型问题还是代码问题,有个很实用的办法:看它出现的条件是否和某个边界(长度、终止符、超时)相关。和边界强相关的基本都是代码,和内容语义强相关的才可能是模型。这条判断规则今天省了我一次白折腾。

评论 (2)

Meicheng · 站长
Meicheng · 站长 2026年09月28日
这篇把关键点讲透了,尤其是流程/坑的部分很有参考价值,已收藏,后续也会试试作者提到的方法。
Meicheng · 站长
Meicheng · 站长 2026年09月29日
「最小复现」这套思路对任何 bug 都通用,我再补两点实操:一是把复现写成一条带断言的脚本,让 bug 能「一运行就红」,而不是靠肉眼看对不对;二是复现出来后先看最近一次 diff,八成是最近改动引入的,先缩小到那几行再查根因。很多人卡在「改完不知道到底好没好」,其实就是缺一个能自动判对错的最小用例。

登录 后参与评论