Transformers 文档

聊天模板

Hugging Face's logo
加入 Hugging Face 社区

并获得增强的文档体验

开始使用

聊天模板

聊天基础》指南介绍了如何存储聊天历史记录以及如何使用 TextGenerationPipeline 从聊天模型生成文本。

本指南面向更高级的用户,涵盖了底层类和方法,以及理解与模型进行聊天时实际发生情况的关键概念。

理解聊天模型的关键洞察是:所有因果语言模型,无论是否经过聊天训练,都会继续一个 token 序列。当因果语言模型进行训练时,训练通常始于在海量文本语料库上进行“预训练”,从而创建一个“基础”模型。然后,这些基础模型通常会针对聊天进行“微调”,这意味着使用格式化为消息序列的数据进行训练。然而,聊天仍然只是一个 token 序列!您传递给聊天模型的 rolecontent 字典列表会转换为一个 token 序列,通常带有像 <|user|><|assistant|><|end_of_message|> 这样的控制 token,这些 token 使模型能够看到聊天结构。有许多可能的聊天格式,不同的模型可能会使用不同的格式或控制 token,即使它们是从同一个基础模型微调而来!

但不要惊慌——您不需要记住所有可能的聊天格式才能使用聊天模型。聊天模型带有 **聊天模板**,它们指示了聊天应该如何格式化。您可以使用 apply_chat_template 方法访问这些模板。让我们看两个例子。这两个模型都从同一个 Mistral-7B 基础模型进行微调。

Mistral
Zephyr
from transformers import AutoTokenizer

tokenizer = AutoTokenizer.from_pretrained("mistralai/Mistral-7B-Instruct-v0.1")
chat = [
  {"role": "user", "content": "Hello, how are you?"},
  {"role": "assistant", "content": "I'm doing great. How can I help you today?"},
  {"role": "user", "content": "I'd like to show off how chat templating works!"},
]

tokenizer.apply_chat_template(chat, tokenize=False)
<s>[INST] Hello, how are you? [/INST]I'm doing great. How can I help you today?</s> [INST] I'd like to show off how chat templating works! [/INST]

Mistral-7B-Instruct 使用 [INST][/INST] token 来指示用户消息的开始和结束,而 Zephyr-7B 使用 <|user|><|assistant|> token 来指示说话者角色。这就是为什么聊天模板很重要——如果控制 token 错误,这些模型的性能会急剧下降。

使用 apply_chat_template

apply_chat_template 的输入应结构化为具有 rolecontent 键的字典列表。role 键指定说话者,content 键包含消息。常见的角色是

  • user 代表来自用户的消息
  • assistant 代表来自模型的消息
  • system 代表关于模型应如何运作的指令(通常放在聊天的开头)

apply_chat_template 接收此列表并返回一个格式化的序列。如果您想对序列进行 token 化,请将 tokenize 设置为 True

import torch
from transformers import AutoModelForCausalLM, AutoTokenizer

tokenizer = AutoTokenizer.from_pretrained("HuggingFaceH4/zephyr-7b-beta")
model = AutoModelForCausalLM.from_pretrained("HuggingFaceH4/zephyr-7b-beta", device_map="auto", dtype=torch.bfloat16)

messages = [
    {"role": "system", "content": "You are a friendly chatbot who always responds in the style of a pirate",},
    {"role": "user", "content": "How many helicopters can a human eat in one sitting?"},
 ]
tokenized_chat = tokenizer.apply_chat_template(messages, tokenize=True, add_generation_prompt=True, return_tensors="pt")
print(tokenizer.decode(tokenized_chat[0]))
<|system|>
You are a friendly chatbot who always responds in the style of a pirate</s>
<|user|>
How many helicopters can a human eat in one sitting?</s>
<|assistant|>

将 token 化后的聊天传递给 generate() 以生成响应。

outputs = model.generate(tokenized_chat, max_new_tokens=128) 
print(tokenizer.decode(outputs[0]))
<|system|>
You are a friendly chatbot who always responds in the style of a pirate</s>
<|user|>
How many helicopters can a human eat in one sitting?</s>
<|assistant|>
Matey, I'm afraid I must inform ye that humans cannot eat helicopters. Helicopters are not food, they are flying machines. Food is meant to be eaten, like a hearty plate o' grog, a savory bowl o' stew, or a delicious loaf o' bread. But helicopters, they be for transportin' and movin' around, not for eatin'. So, I'd say none, me hearties. None at all.

一些 token izer 会添加特殊的 <bos><eos> token。聊天模板应已包含所有必需的特殊 token,并且添加额外的特殊 token 通常是不正确或重复的,会损害模型性能。当您使用 apply_chat_template(tokenize=False) 格式化文本时,请确保将 add_special_tokens=False 设置为 False,以避免重复这些 token。如果您使用 apply_chat_template(tokenize=True),则不会出现此问题,这意味着它通常是更安全的选择!

add_generation_prompt

您可能已经注意到了上面示例中的 add_generation_prompt 参数。此参数会在聊天末尾添加 token,指示 assistant 回复的开始。请记住:在所有聊天抽象之下,聊天模型仍然只是继续 token 序列的语言模型!如果您包含告诉它现在是 assistant 回复的 token,它将正确地编写一个回复,但如果您不包含这些 token,模型可能会感到困惑并做一些奇怪的事情,例如**继续**用户的消息而不是回复它!

让我们通过一个示例来理解 add_generation_prompt 实际上在做什么。首先,让我们格式化一个不带 add_generation_prompt 的聊天。

tokenized_chat = tokenizer.apply_chat_template(messages, tokenize=False, add_generation_prompt=False)
tokenized_chat
<|im_start|>user
Hi there!<|im_end|>
<|im_start|>assistant
Nice to meet you!<|im_end|>
<|im_start|>user
Can I ask a question?<|im_end|>

现在,让我们使用 add_generation_prompt=True 来格式化同一个聊天。

tokenized_chat = tokenizer.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)
tokenized_chat
<|im_start|>user
Hi there!<|im_end|>
<|im_start|>assistant
Nice to meet you!<|im_end|>
<|im_start|>user
Can I ask a question?<|im_end|>
<|im_start|>assistant

add_generation_prompt=True 时,会在末尾添加 <|im_start|>assistant 来指示 assistant 消息的开始。这会让模型知道接下来是 assistant 的回复。

并非所有模型都需要生成提示,有些模型(如 Llama)在 assistant 回复之前没有任何特殊 token。在这种情况下,add_generation_prompt 没有效果。

continue_final_message

continue_final_message 参数控制是否应继续聊天中的最后一条消息,而不是开始新消息。它会移除结束序列 token,以便模型可以从最后一条消息继续生成。

这对于“预填充”模型响应很有用。在下面的示例中,模型生成的文本会继续 JSON 字符串,而不是开始新消息。当您知道如何开始模型的回复时,它对于提高指令遵循的准确性非常有用。

chat = [
    {"role": "user", "content": "Can you format the answer in JSON?"},
    {"role": "assistant", "content": '{"name": "'},
]

formatted_chat = tokenizer.apply_chat_template(chat, tokenize=True, return_dict=True, continue_final_message=True)
model.generate(**formatted_chat)

您不应该同时使用 add_generation_promptcontinue_final_message。前者会添加启动新消息的 token,而后者会移除结束序列 token。同时使用它们会返回错误。

TextGenerationPipeline 默认将 add_generation_prompt 设置为 True 以启动新消息。但是,如果聊天中的最后一条消息的角色是 assistant,它会假定消息是预填充并切换到 continue_final_message=True。这是因为大多数模型不支持多个连续的 assistant 消息。要覆盖此行为,请将 continue_final_message 参数显式地传递给 pipeline。

模型训练

使用聊天模板训练模型是确保模板与模型训练的 token 匹配的好方法。将聊天模板作为预处理步骤应用于数据集。将 add_generation_prompt 设置为 False,因为用于提示 assistant 回复的额外 token 在训练期间没有帮助。

下面展示了一个预处理数据集的聊天模板的示例。

from transformers import AutoTokenizer
from datasets import Dataset

tokenizer = AutoTokenizer.from_pretrained("HuggingFaceH4/zephyr-7b-beta")

chat1 = [
    {"role": "user", "content": "Which is bigger, the moon or the sun?"},
    {"role": "assistant", "content": "The sun."}
]
chat2 = [
    {"role": "user", "content": "Which is bigger, a virus or a bacterium?"},
    {"role": "assistant", "content": "A bacterium."}
]

dataset = Dataset.from_dict({"chat": [chat1, chat2]})
dataset = dataset.map(lambda x: {"formatted_chat": tokenizer.apply_chat_template(x["chat"], tokenize=False, add_generation_prompt=False)})
print(dataset['formatted_chat'][0])
<|user|>
Which is bigger, the moon or the sun?</s>
<|assistant|>
The sun.</s>

在此步骤之后,您可以按照因果语言模型的 训练指南,使用 formatted_chat 列继续进行。

在 GitHub 上更新

© . This site is unofficial and not affiliated with Hugging Face, Inc.