Transformers 文档
聊天模板
并获得增强的文档体验
开始使用
聊天模板
《聊天基础》指南介绍了如何存储聊天历史记录以及如何使用 TextGenerationPipeline 从聊天模型生成文本。
本指南面向更高级的用户,涵盖了底层类和方法,以及理解与模型进行聊天时实际发生情况的关键概念。
理解聊天模型的关键洞察是:所有因果语言模型,无论是否经过聊天训练,都会继续一个 token 序列。当因果语言模型进行训练时,训练通常始于在海量文本语料库上进行“预训练”,从而创建一个“基础”模型。然后,这些基础模型通常会针对聊天进行“微调”,这意味着使用格式化为消息序列的数据进行训练。然而,聊天仍然只是一个 token 序列!您传递给聊天模型的 role 和 content 字典列表会转换为一个 token 序列,通常带有像 <|user|>、<|assistant|> 或 <|end_of_message|> 这样的控制 token,这些 token 使模型能够看到聊天结构。有许多可能的聊天格式,不同的模型可能会使用不同的格式或控制 token,即使它们是从同一个基础模型微调而来!
但不要惊慌——您不需要记住所有可能的聊天格式才能使用聊天模型。聊天模型带有 **聊天模板**,它们指示了聊天应该如何格式化。您可以使用 apply_chat_template 方法访问这些模板。让我们看两个例子。这两个模型都从同一个 Mistral-7B 基础模型进行微调。
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 的输入应结构化为具有 role 和 content 键的字典列表。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_prompt 和 continue_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 列继续进行。