为什么需要 Chat Template?

大语言模型本质上是文本续写器(text continuation model)。它们并不天然理解”对话”这个概念,只能处理纯文本。因此,我们需要一种标准化的方式来告诉模型:

  • 哪段文本是系统提示(system prompt)
  • 哪段文本是用户输入(user input)
  • 哪段文本是助手回复(assistant response)
  • 如何处理工具调用(tool calls)

Chat Template 就像是对话的格式说明书,确保训练和推理时使用完全一致的格式。


Chat Template 的结构

让我们看一个来自 Qwen3-4B-Instruct 模型的真实例子:

1
2
3
4
5
6
7
{
"chat_template": "{%- if tools %}\n {{- '<|im_start|>system\\n' }}...",
"eos_token": "<|im_end|>",
"pad_token": "<|endoftext|>",
"tokenizer_class": "Qwen2Tokenizer",
"model_max_length": 1010000
}

核心组件

1. chat_template 字段

这是一个 Jinja2 模板(Jinja2 template),包含了完整的格式化逻辑。它使用特殊标记(special tokens)来分隔不同的消息部分。

2. 特殊标记 (Special Tokens)

added_tokens_decoder 中可以看到模型定义的特殊标记:

1
2
3
4
5
6
{
"151644": {"content": "<|im_start|>"}, // 消息开始
"151645": {"content": "<|im_end|>"}, // 消息结束
"151657": {"content": "<tool_call>"}, // 工具调用开始
"151658": {"content": "</tool_call>"} // 工具调用结束
}

这些标记在训练时就被模型学习,用于识别消息边界和特殊语义。

3. 视觉和多模态标记

Qwen3 还支持多模态输入,因此包含了视觉相关的特殊标记:

1
2
3
4
5
6
{
"151652": {"content": "<|vision_start|>"},
"151653": {"content": "<|vision_end|>"},
"151655": {"content": "<|image_pad|>"},
"151656": {"content": "<|video_pad|>"}
}

Chat Template 的工作原理

示例 1: 简单对话

输入的结构化数据:

1
2
3
4
5
messages = [
{"role": "system", "content": "你是一个有帮助的AI助手"},
{"role": "user", "content": "什么是机器学习?"},
{"role": "assistant", "content": "机器学习是人工智能的一个分支..."}
]

经过 Chat Template 处理后:

1
2
3
4
5
6
<|im_start|>system
你是一个有帮助的AI助手<|im_end|>
<|im_start|>user
什么是机器学习?<|im_end|>
<|im_start|>assistant
机器学习是人工智能的一个分支...<|im_end|>

模型看到 <|im_start|>user 就知道”接下来是用户的输入”,看到 <|im_end|> 就知道”这段消息结束了”。

示例 2: 带工具调用的对话

当模型需要调用外部工具(如搜索、计算器)时,Chat Template 会生成更复杂的格式:

输入数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
tools = [
{
"name": "get_weather",
"description": "获取指定城市的天气",
"parameters": {
"city": {"type": "string", "description": "城市名称"}
}
}
]

messages = [
{"role": "user", "content": "上海今天天气如何?"},
{
"role": "assistant",
"content": "让我帮你查询",
"tool_calls": [
{"name": "get_weather", "arguments": {"city": "上海"}}
]
}
]

处理后的格式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<|im_start|>system
# Tools

You may call one or more functions to assist with the user query.

You are provided with function signatures within <tools></tools> XML tags:
<tools>
{"name": "get_weather", "description": "获取指定城市的天气", ...}
</tools>

For each function call, return a json object with function name and arguments within <tool_call></tool_call> XML tags:
<tool_call>
{"name": <function-name>, "arguments": <args-json-object>}
</tool_call><|im_end|>
<|im_start|>user
上海今天天气如何?<|im_end|>
<|im_start|>assistant
让我帮你查询
<tool_call>
{"name": "get_weather", "arguments": {"city": "上海"}}
</tool_call><|im_end|>

注意模板自动在系统消息中注入了工具使用说明,这让模型知道如何正确地调用工具。


Chat Template 的关键作用

1. 训练与推理一致性 (Training-Inference Consistency)

模型在训练时看到的格式和推理时必须完全一致。如果训练时使用 <|im_start|>,推理时用别的格式,模型性能会大幅下降。

2. 多轮对话的上下文管理 (Context Management)

Chat Template 确保整个对话历史以正确的格式拼接:

1
2
3
4
5
6
7
<|im_start|>user
第一个问题<|im_end|>
<|im_start|>assistant
第一个回答<|im_end|>
<|im_start|>user
第二个问题<|im_end|>
<|im_start|>assistant

模型可以清晰地看到对话的边界和顺序。

3. 功能扩展性 (Extensibility)

通过添加新的特殊标记,可以支持新功能而不改变模型架构:

  • 代码补全: <|fim_prefix|>, <|fim_middle|>, <|fim_suffix|> (FIM = Fill In the Middle)
  • 思维链: <think>...</think> 标记模型的内部推理过程
  • 多模态: <|vision_start|>, <|image_pad|> 处理图像输入

实战: 如何使用 Chat Template

使用 HuggingFace Transformers 库时,Chat Template 是自动应用的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from transformers import AutoTokenizer

tokenizer = AutoTokenizer.from_pretrained("Qwen/Qwen3-4B-Instruct")

messages = [
{"role": "system", "content": "你是一个有帮助的AI助手"},
{"role": "user", "content": "你好!"}
]

# apply_chat_template 会自动使用 tokenizer_config.json 中定义的模板
formatted_text = tokenizer.apply_chat_template(
messages,
tokenize=False, # 只返回文本,不进行 tokenization
add_generation_prompt=True # 添加 <|im_start|>assistant\n
)

print(formatted_text)

输出:

1
2
3
4
5
<|im_start|>system
你是一个有帮助的AI助手<|im_end|>
<|im_start|>user
你好!<|im_end|>
<|im_start|>assistant

模型会从最后的 <|im_start|>assistant\n 开始生成回复。


不同模型的 Chat Template 差异

不同模型家族使用不同的格式约定:

模型 格式风格 特殊标记
Qwen ChatML 风格 `<
Llama 3 特殊标记 `<
Mistral Instruct 格式 [INST], [/INST]
GPT-4 OpenAI 格式 无特殊标记(API 层面处理)

这就是为什么你不能随意混用不同模型的 tokenizer —— 它们的 Chat Template 完全不兼容!


参考

https://kq4b3vgg5b.feishu.cn/wiki/MLxWwTt2Bim4FwkdgIRcSkU0nZf