Hugging Face Chat Templates
Hugging Face 推出聊天模板以防止性能隐形下降
Hugging Face 为分词器(tokenizer)引入了 chat_template 属性,旨在解决“隐形性能杀手”问题——即当使用与模型训练时不同的格式对聊天模型进行提示时,会导致严重的模型性能下降。通过将格式化逻辑作为 Jinja 模板存储在分词器内部,开发人员可以确保对话历史被转换为模型预期的精确字符串格式,从而消除因格式错误导致的分布偏移(distribution shifts)。
聊天格式中分布偏移的问题
在标准语言模型中,从同一个检查点(checkpoint)加载分词器和模型通常可以防止分布偏移。然而,聊天模型需要将一系列消息(包含 "user"、"assistant" 和 "system" 等角色)转换为单个可分词的字符串。由于这种转换目前没有行业标准,不同的模型使用截然不同的格式,例如:
- 简单标签:
User: [text] \n Bot: [text] - 特殊标记:
[USER] [text] [/USER] \n [ASST] [text] [/ASST] - 边界标记:
<|im_start|>user \n [text]<|im_end|>
使用错误的格式是一种“隐形错误”,因为它不会触发 Python 异常或明显的失败;相反,模型只是表现得明显变差,这使得问题难以调试。
通过 Jinja 模板实现技术方案
聊天模板是以 Jinja 模板字符串的形式实现的,并随分词器一起保存和加载。选择这种方法而不是更简单的系统(如针对每个角色的前缀和后缀),是因为模板化具有足够的灵活性,可以支持所有已知的消息格式,并允许将逻辑和检查直接编码到模板中。
模板逻辑示例
对于使用边界标记的格式,Jinja 模板可能如下所示:
{% for message in messages %}
{{'<|im_start|>' + message['role'] + '\n' + message['content'] + '<|im_end|>' + '\n'}}
{% endfor %}
与 Transformers 库的集成
聊天模板直接集成到分词器中,以遵循“预处理信息应与模型的分词逻辑保持一致”的原则。
从类级格式化过渡
此前,聊天格式化是在类级别处理的(例如,所有 LLaMA 检查点都在 transformers 库中使用相同的硬编码逻辑)。为了保持向后兼容性,模型类现在拥有默认聊天模板。然而,Hugging Face 强烈建议为每个聊天模型显式设置 chat_template,以避免脆弱性,并确保未来默认模板的更改不会破坏模型的性能。
使用与部署
开发人员可以使用 tokenizer.apply_chat_template() 方法应用这些模板。如果分词器缺少 chat_template 属性,它将回退到类默认设置,这可能会导致上述提到的隐形错误。鼓励用户从模型卡(model cards)中识别正确的格式,并提交拉取请求(pull requests)为 Hugging Face Hub 上的检查点添加 chat_template 属性。
对新模型的建议
虽然 Hugging Face 承认单一的标准格式最为理想,但现有训练模型的多样性使得硬编码标准变得不可能。对于正在训练新聊天模型的开发者,Hugging Face 推荐使用 OpenAI 创建的 ChatML 格式,它使用 <|im_start|> 和 <|im_end|> 标记。这种格式在角色处理上非常灵活,可以通过单行模板赋值来实现:
tokenizer.chat_template = "{% for message in messages %}{{'<|im_start|>' + message['role'] + '\n' + message['content'] + '<|im_end|>' + '\n'}}{% endfor %}"