6、对话模板与模型输入
上一节得到的数据仍是一条条 user 输入和 assistant 参考答案。接下来看:训练工具读取这些消息后,怎样把它们交给模型。
6.1 对话模板的作用
继续用第 1 节的图书馆例子。我们能通过 role 分清哪段是用户的问题、哪段是助手的答案,模型也需要识别这些边界。
**Chat Template(对话模板)**就是组织消息的规则:怎样标出用户消息的开始和结束,怎样标出助手回答的位置。不同模型使用的规则可能不同,因此训练工具需要选择与模型匹配的模板。
这个概念并不只存在于代码里。第 31 章要使用的 LLaMA-Factory 页面,就有一个“对话模板”选项:
模型路径决定加载哪份模型,对话模板决定怎样组织消息。
它与第 13 章的提示词和消息模板分工不同:之前是在填写任务说明、变量和对话内容;这里是按模型要求,给消息加上角色边界。
6.2 查看模板与转换结果
先找到模板规则。 第 28 章介绍过模型目录中的权重、配置和分词器文件。以本课程的 Qwen/Qwen3-0.6B 为例,打开 tokenizer_config.json,搜索 chat_template,就能找到模型发布者提供的对话模板。
暂时没有下载模型,也可以打开官方模型文件页,用浏览器查找 chat_template。对照下图找到文件名和模板字段:
这一项保存了不同角色和条件的处理规则,由工具读取使用。
再看同一条消息转换前后有什么变化。 下面使用模型自带的官方模板处理第 1 节的图书馆教学样本,关闭思考模式,观察消息的组织方式。
对照左右两边,文章和关键词答案都还在,变化主要是消息的边界表示:
<|im_start|>user:用户消息开始,后面接文章与任务说明。<|im_end|>:这一条消息结束。<|im_start|>assistant:助手消息开始,后面接参考答案。
这里的关键词来自样本中的 assistant.content;图中的空 <think> 区块由官方模板添加。模板负责组织已有消息,答案质量仍由数据审核保证。
还要区分两种“结束”:</think> 表示思考区结束,后面仍可继续写答案;消息结束标记则用于划分消息。推理程序还需要配置生成停止条件,识别何时结束助手的输出。常见的 **EOS(End of Sequence,序列结束标记)**以 token 编号参与这项判断,具体编号和用途要与模型、模板及推理引擎对应,不能把 </think> 直接当成回答结束。第 32 章再结合重复输出与生成停止检查实际现象。
本图用官方模板说明转换过程;实际训练以训练工具的预处理结果为准。 第 31 章使用 LLaMA-Factory 的 qwen3_nothink,与官方模板的处理可能不同,不能直接沿用本图的 token 数量。第 30 章第 4.2 节会带你核对实际输入。
最后,把文字变成编号。 前面提到的 **Tokenizer(分词器)**会把文本和标记转换为模型可以计算的编号。每个基本单位叫 Token(词元),不一定对应一个汉字。
例如,用同一份 Qwen3 分词器单独处理“市图书馆”,得到:
| 分词后可读的内容 | 对应编号 |
|---|---|
| 市 | 22697 |
| 图书馆 | 106036 |
四个汉字在这个例子里是两个 token,对应输入编号 [22697, 106036]。<|im_start|> 这样的特殊标记也有自己的编号。input_ids 指的就是模型的输入编号序列。
模型输入的转换过程是:从 JSONL 读出消息 → 按模板组织消息 → 转成 token 编号。 第一环节还需要告诉工具去哪个字段找消息,这叫字段映射;第 31 章登记数据集时再实际填写。后两步通常可以由工具一起完成,不需要另外保存一份中间文本。
6.3 训练与推理的输入区别
同一条图书馆消息,准备训练样本和实际向模型提问时,输入并不相同:
| 对比项 | 准备训练样本 | 准备一次推理 |
|---|---|---|
| 提供哪些消息 | user 输入和 assistant 参考答案 | 只提供 user 输入 |
| 助手位置放什么 | 已有的参考答案及结束标记 | 回答的起始位置,等待模型续写 |
前面的用户文章相同,重点比较助手部分的末尾。下面两段都是官方模板的实际处理结果;显示出来的空行也是模板的一部分。
训练样本包含参考答案:
<|im_start|>assistant
<think>
</think>
市图书馆;儿童阅读课;公众号预约<|im_end|>推理输入停在准备回答的位置:
<|im_start|>assistant
<think>
</think>模型接下来才从这里生成关键词。实际提问时,不能先把参考答案塞进输入,再把后续输出当成模型独立答对的结果。
完成第 31 章的环境准备与模型下载后,可运行下面的代码查看模板输出。
在 AutoDL 的 JupyterLab 文件面板中打开 /root/autodl-tmp/LLaMA-Factory/,新建文本文件并命名为 preview_chat_template.py,将下面的 Python 代码粘贴进去并保存。把 model_dir 换成下载完成时返回的实际目录,该目录需要包含 tokenizer_config.json 和分词器文件。下面只加载分词器,不加载模型权重,也不启动推理或训练。
from pathlib import Path
from transformers import AutoTokenizer
model_dir = Path("/root/.cache/modelscope/models/Qwen--Qwen3-0.6B/snapshots/master")
tokenizer = AutoTokenizer.from_pretrained(model_dir, local_files_only=True) # 只读取本地分词器文件
messages = [
{
"role": "user",
"content": "市图书馆周末开设儿童阅读课,读者可通过公众号预约。\n"
"请提取关键词,只输出关键词,并使用英文分号分隔。",
},
{"role": "assistant", "content": "市图书馆;儿童阅读课;公众号预约"},
]
for name, chat, generation_prompt in [
("训练样本", messages, False),
("推理输入", messages[:1], True),
]:
text = tokenizer.apply_chat_template(
chat,
tokenize=False, # 返回模板处理后的文字,方便观察消息边界
add_generation_prompt=generation_prompt, # 推理时补上助手回答的起始位置;训练样本不补
enable_thinking=False, # 关闭思考模式
)
token_ids = tokenizer.apply_chat_template(
chat,
tokenize=True, # 将模板处理后的文字转换为 token 编号
return_dict=False, # 直接返回编号列表,便于用 len() 统计长度
add_generation_prompt=generation_prompt,
enable_thinking=False,
)
print(f"\n{name},共 {len(token_ids)} 个 token:")
print(text)
ids = tokenizer.encode("市图书馆", add_special_tokens=False) # 不额外添加特殊标记,只观察短语本身
print("短语编号:", ids)
print("逐个解码:", [tokenizer.decode([token_id]) for token_id in ids])保存后,新开一个 AutoDL 的 JupyterLab 终端,执行:
cd /root/autodl-tmp/LLaMA-Factory
source .venv/bin/activate
python preview_chat_template.py终端会依次打印「训练样本」「推理输入」和短语的分词结果。若提示找不到文件,检查 Python 文件的保存目录;若找不到分词器,核对 model_dir。
同一组消息调用两次模板:第一次看文字,第二次看编号。return_dict=False 明确要求返回列表,因此 len(token_ids) 统计的是 token 数量。参数说明见 Transformers 官方文档。
示例条件: Transformers 4.57.6、Qwen3-0.6B 版本 c1899de 的官方分词器文件。图书馆样本的训练输入为 51 个 token,推理输入为 40 个 token,均包含文章与模板标记;空 <think> 区块由该官方模板在关闭思考时加入。
第 31 章使用 Transformers 5.8.0,运行时以自己加载的分词器和输出为准,不必为对齐这里的数字更换训练环境。官方模板演示与工具训练输入的区别见第 6.2 节;实现细节可查 LLaMA-Factory 模板源码。
如果想观察课程里的真实样本,可以在循环之前,用下面代码替换教学用的 messages,其余部分保持不变:
import json
data_file = Path(
"/root/autodl-tmp/LLaMA-Factory/data/keywords-clean/keywords_train.jsonl"
)
with data_file.open(encoding="utf-8") as source:
record = next(json.loads(line) for number, line in enumerate(source, 1) if number == 484)
messages = record["conversations"]这条记录是第 1 节图片展示的“高氟铍矿石”样本,在 small 文件中位于第 1 条,划分后位于课程训练集第 484 条。上述固定模板下,训练输入为 139 个 token、推理输入为 121 个 token。
6.4 模板选择与注意事项
后续关键词练习在 LLaMA-Factory 中选择 qwen3_nothink,并关闭思考设置。先完成下面三项检查;实际回答是否符合要求,到第 32 章再验证。
- 模型与模板要匹配。 更换模型后重新核对模板,训练与推理时保持匹配。
- JSONL 中保留正常消息。 不要把上面打印的整段模板文本塞回
user.content,也不要手工补上角色标记,否则工具处理时可能重复包装。 - 检查训练工具的实际输入。 训练前检查预处理后的长度、角色和答案位置,方法见第 30 章“检查实际训练长度”。
关闭思考设置仍不保证没有分析段。课程固定版本中,两种模板添加空思考区块的方式不同;遇到这一现象,再读第 32 章的模板对照,无需在本章先排查推理细节。
章节思考题:
JSON 和 JSONL 在文件保存方式上有什么区别?把一个样本展开成十几行展示,保存时就变成了十几条训练数据吗?
参考思路: JSON 表示一个完整的 JSON 值;JSONL 则要求每个非空物理行分别是一个完整 JSON 值,本课每行保存一个样本对象。讲解时展开换行仍是同一个样本,写入本课 JSONL 时应放回一条物理行;消息内部的换行用转义形式保存,不能按展示行数计算样本数。
同一条“文章 → 关键词”样本使用 Alpaca 和 ShareGPT 保存时,任务要求、文章和参考答案分别放在哪里?
参考思路: 本课 Alpaca 示例将任务要求放在 instruction、文章放在 input、参考答案放在 output。ShareGPT 示例将任务与文章放在 user 消息,把参考关键词放在 assistant 消息,并用 conversations 列表组织消息。改变的是组织方式,任务与答案不变;多轮消息仍应保持在同一条样本的对话中。
登记表中的字段映射、Chat Template 和 Tokenizer 分别负责什么?一份能解析的 ShareGPT 数据怎样成为模型输入?
参考思路: 字段映射告诉训练工具去哪里读消息、怎样识别角色;Chat Template 按模型要求组织角色标记、内容和对话边界;Tokenizer 再把文本转换成 token 编号。文件能解析只完成了读取的前提,还要确认角色映射正确、模板与模型匹配,以及转换后的输入符合预期。
训练时会提供参考关键词,独立推理时应提供哪些内容?怎样检查自己没有把答案提前放进输入?
参考思路: 训练序列包含任务、文章和助手参考答案,用于后续计算学习目标。独立推理只提供需要的任务与上下文,在助手待回答的位置开始生成,参考答案另存用于比较。检查第 6.3 节展示的转换结果,确认推理输入没有参考关键词,也没有把已经套好的模板再次包装进用户消息。
图书馆原文写的是儿童阅读课,参考答案却写成“市图书馆;成人培训”。分号正确,为什么仍要修订?两位标注者对“公众号预约”是否拆开有分歧时怎样处理?
参考思路: “成人培训”没有原文依据,属于内容错误;分隔符正确不能弥补它。短语拆分要按第 2.3 节的标注规则统一口径,说明本例整体保留的依据,再复查相关样本。脚本适合检查结构和格式,专业含义与标注分歧仍需人工审核。
训练集、验证集和测试集分别用于什么?比较训练一轮还是三轮时用哪份数据,最终检查又用哪份?
参考思路: 训练集参与参数更新,验证集用于比较训练方案和选择检查点,测试集用于方案确定后的独立检查。本例按 80%/10%/10% 划分,但比例要随任务确定。若测试结果已经被反复用于开发,应另备未参与选择的数据,不能靠重新随机划分恢复其独立性。
同一篇文章改写出两条不同问法,一条进入训练集,另一条进入测试集,为什么精确去重仍可能发现不了泄漏?
参考思路: 精确去重只能识别其规则覆盖的重复,问法不同可能使两条记录不再相同,但它们仍共享来源和答案信息。应按原文或关联材料分组划分,再检查集合交叉。本课完整数据与 small 文件也有重叠,不能分别当作训练集和独立测试集。
训练数据大多是活动通知,而验证时农业摘要经常漏掉品种名,下一批数据应怎样准备?
参考思路: 先对照原文确认错误,检查农业主题、完整名称、长文本等场景是否缺少可靠示范,再补充经过审核的相关样本。若名称标注本身有分歧,先统一规则;重复增加同类通知并不能补齐这些缺口。数据修改后,用验证结果检查目标错误是否减少。
清洗、划分完成后,应交给后续训练哪些文件?怎样说明这批数据已处理什么、还有什么待检查?
参考思路: 交接训练、验证、测试三份 JSONL,配套登记表、清洗报告和 manifest。核对文件位置、条数、划分记录、重复与集合交叉,抽看实际样本,并说明自动检查范围、人工审核结果和待处理项。文件生成成功只能证明流程产出了文件,不能证明所有答案都已通过语义审核。
用 Easy Dataset 从文档制作问答,为什么要经历“检查分块 → 生成问答 → 对照来源审核 → 导出检查”?
参考思路: 分块影响生成时能看到的上下文,生成结果可能遗漏限制条件或写错事实,所以要回到来源检查数字、条件和答案依据,保存修订后再导出。打开导出文件核对条数、角色和修改后的答案,确认审核结果确实进入了后续数据。
工具调用训练样本与普通一问一答有什么不同?只有“查询成功,还有 6 个名额”这句回答,缺少了什么?
参考思路: 还需用户上下文、工具说明、调用名称与参数、对应工具结果和后续回答;必要时包含澄清过程。工具结果应来自真实执行或明确标注且核验过的模拟环境,并覆盖无需调用、调用失败等情况。单轮关键词脚本不能直接校验这些消息之间的对应关系。
本章小结:
- JSONL 规定每行保存一个 JSON 值,本课每行是一条样本;Alpaca 和 ShareGPT 则规定任务、输入、答案或消息怎样组织。文件格式与样本结构要分开理解。
- 训练答案既要能读取,也要有原文依据并符合标注规则。数据应覆盖实际任务的主题、长度和专业名称,格式清洗不能替代语义审核。
- 训练集用于参数更新,验证集用于选方案,测试集用于独立检查。清洗、去重和来源分组共同减少泄漏,精确去重不能发现所有同源近似样本。
- 字段映射帮助工具读取角色和内容,对话模板按模型要求组织文本,分词器将其转换为 token 编号。训练提供参考答案,独立推理只提供待处理内容与必要上下文。
- 清洗后保留三份数据、登记表、清洗报告和 manifest,并核对实际内容。文档问答需要对照来源审核,工具调用还需要检查请求、执行结果与后续回答的对应关系。
建议下一步: 对照第 5.4 节核查生成的文件,抽看三份数据,说明已处理什么、还有什么待审核。后续跟做使用正文约定的 keywords-clean/,自己的复现目录单独保留。带着这份数据说明进入第 30 章,理解模型怎样利用这些答案更新参数。