4.3 常见坑:模板不一致与训练推理错位


4.3 常见坑:模板不一致与训练推理错位

本节摘要:模板错位是全流水线最贵的 bug——模型、数据、代码都没有错,只是三处各自对"序列长什么样"持不同意见,于是模型收到一段训练时从未见过的 token 排布,当场"失语"。本节复现三种典型错位:①换了模板没重训(症状:开头永远接特殊标记、答非所问);②推理少拼 assistant 起始标记(症状:替用户续写);③词表/终止符 id 错位(症状:不停或腰斩)。每种给"一行复现、三步诊断、根治手段",最后立纪律:模板是"一份定义、三处消费"(训练数据渲染、推理提示拼装、部署侧配置)的单一事实源,并用回环校验把它焊死——第 6 章 llama.cpp 部署正是第三处消费点。

学习目标

阅读完本节,你应当能够:

  1. 复现并识别三种模板错位的症状签名。
  2. 用"打印前 50 个 token"三板斧诊断任意错位。
  3. 建立"一份模板、三处消费"的单一事实源纪律与回环校验。

一、为什么模板错位最贵

模型不"理解"消息结构,它只是对 token 序列的条件分布极其敏感。SFT 数万条样本教会它"见到这套标记排布就扮演助手";推理时排布变了一个 token,分布就漂到训练分布之外——没有报错、没有异常日志,只有沉默的胡说八道。对比第 3 章的三故障:OOM 有红字,loss 不降有曲线,模板错位只有"模型突然不会聊天"这个主观症状,排查常常从错误的方向(调采样、回滚数据)开始,一烧就是半天。

二、三种典型错位

错位一:换了模板没有重训

训练时序列: <|beginmessage|>user 你好 <|endmessage|><|beginmessage|>assistant ... 推理时序列: [INST] 你好 [/INST] ← 换成了别家模板(Llama 风格示意)

症状签名:输出以特殊标记或方括号开头(模型在模仿它唯一熟悉的"开场");或直接续写用户的话(把 [INST] 当普通文本续写)。一行复现:推理代码用另一个 tokenizer 的 chat_template 渲染即可。

诊断三板斧(对本节三种错位通用):

# diagnose_template.py —— 模板错位诊断三板斧(写法示意) from transformers import AutoTokenizer tok = AutoTokenizer.from_pretrained("runs/sft_124m/final") ids = tok.apply_chat_template( [{"role": "user", "content": "你好"}], tokenize=True, add_generation_prompt=True) print("① 前 50 token:", tok.convert_ids_to_tokens(ids[:50])) # 逐个看开头标记 print("② 终止符 id:", tok.convert_tokens_to_ids("<|endoftext|>"), " eos_token id:", tok.eos_token_id) # 两者应指向同一 id print("③ 模板哈希:", hash(tok.chat_template)) # 训练/推理两处对比

① 看开头排布是否与训练数据渲染一致(拿一条训练样本并排打印);② 看终止符 id 与 eos 配置是否同指;③ 训练脚本与推理脚本各打一次哈希,不相等即模板被换过。

根治:换模板 = 换数据 = 必须重训(至少重跑 SFT)。模板不是提示词工程的一部分,而是模型权重的一部分语义。

错位二:推理少拼 assistant 起始标记

正确: ... <|endmessage|><|beginmessage|>assistant ← add_generation_prompt=True 错误: ... <|endmessage|> ← 直接等模型续写

症状签名:模型接在 user 消息后继续"替用户说话"——0.1 节 base 模型的老毛病在 SFT 模型身上复发,但只在推理代码漏拼时出现。这个标记为什么不可省:训练序列里 assistant 回答永远紧跟在它后面(4.1 的前缀关系),模型学到的"我来开口"的触发器就是这个标记,不给触发器它就退回续写模式。

根治:推理侧永远用 apply_chat_template(..., add_generation_prompt=True),不要手拼字符串(手拼是错位一的温床);把 4.1 的 startswith assert 放进服务启动自检。

错位三:词表或终止符 id 错位

两种形态:词表漂移(训练后往词表加了 token 却没 resize_token_embeddings,或加载了另一版分词器——id 与 embedding 错行,输出乱码级错乱);终止符错位(部署侧配置的 stop token id 与训练用的 <|endoftext|> id 不一致)。

症状签名:词表漂移 → 输出无关乱码;终止符错位 → 永不停(回答后接乱码)或腰斩(提前停)。第二形态是第 6 章 GGUF 转换的高频事故:转换工具的 --chat-template/停止符配置与训练不一致——本章先把诊断方法备好,部署侧实操见第 6 章。

根治:词表、权重、模板三者同进同出同一个目录(如本书的 runs/sft_124m/final/),加载时永远同源;终止符 id 用诊断脚本的 ② 打出来配进部署配置,不手填数字。

三、纪律:一份模板、三处消费

┌────────────────────────────┐ │ 模板单一事实源 │ │ tokenizer 目录 + chat_template + 终止符 id │ └──────────┬─────────────────┘ ┌─────────────────┼─────────────────┐ ▼ ▼ ▼ ① 训练数据渲染 ② 推理提示拼装 ③ 部署侧配置 (3.1 掩码求差) (add_generation_prompt)(第 6 章 GGUF/llama.cpp) └──── 三处引用同一来源,禁止各自手写 ────┘

回环校验(把三处焊在一起的最小手段):

  1. 训练前:assert 渲染(含回答).startswith(渲染(生成提示))——4.1 的断言;
  2. 推理前:诊断三板斧跑一遍(前 50 token 对齐训练样本开头);
  3. 部署前:用同一段 messages 分别经推理引擎与 tokenizer 渲染,token 序列逐位相等。

三条校验合计不到 10 行代码,能拦下本节全部三种错位。nanochat 把训练与推理放在同一套代码里(同一分词器对象、同一渲染函数)正是这个纪律的工程化表达——结构上让错位无从发生,比事后校验更彻底。

💡 交接提示(给第 6~8 章):llama.cpp 部署时模板配置错误是本章错位三的部署版;中文改造重设模板时是错位一的改造版——两章动模板前,先回来把"一份模板、三处消费"再读一遍。

四、全书故障地图收拢

至此,后半程的故障排查体系完整了:

故障 章节
数据 脏样本/重复/质量差 2.1~2.2
训练 OOM / loss 不降 / 灾难性遗忘 / 过拟合 3.2~3.3
协议 模板三种错位 本节
手感 采样四症状 4.2
交付 部署侧量化/终止符/实时性 第 6~7 章

拿到任何"模型不对劲"的报案,先归层、再对表——这是全书反复强调后半程工程感的落点:每一层都有表可查,没有玄学。

本节要点回顾

  1. 模板错位贵在无症状:不报错、不挂日志,只有沉默的胡说八道;三板斧(前 50 token、终止符 id、模板哈希)是通用诊断。
  2. 三种错位:换模板没重训(换模板=换数据=重训)、漏拼起始标记(永远用 add_generation_prompt)、词表/终止符错位(三者同目录同源)。
  3. 纪律:模板是单一事实源,训练/推理/部署三处消费同一份定义;回环校验 10 行拦下全部事故。

至此第 24 章的 SFT 主干闭环:数据(第 2 章)→ 训练(第 3 章)→ 协议与手感(第 4 章)。接下来:第 5 章给模型正式打分,第 67 章把它部署到你自己的机器上、接上语音,第 8 章让它说好中文。


作者与出处
原作者: 灏天文库
整理: 灏天文库整理
本站整理收录,版权归原作者/开源协议所有;欢迎通过原文链接访问源仓库。
发布者: 作者: 灏天文库 转发
评论区 (0)
U