Chat template
Message and model
以下代码定义了一条消息,包括用户和大模型两个角色。
1 | messages = [ |
模型包括两个部分:model(decoder)和 tokenizer。
1 | from transformers import AutoModelForCausalLM, AutoTokenizer |
注意这里的 setup_chat_format 方法,通过添加特殊的 token 来设置模型的聊天模板。
Apply chat template
使用 tokenizer 的 apply_chat_template 方法将定义的 message(list)转换为带特殊 token 的字符串:
1 | input_text = tokenizer.apply_chat_template(messages, tokenize=False) |
结果为:
<|im_start|>user
Hello, how are you?<|im_end|>
<|im_start|>assistant
I’m doing well, thank you! How can I assist you today?<|im_end|>
如果将 tokenize 参数设置为 true,则会将 token 映射为 token 表中的 id。可以使用 tokenizer 的 decode 方法将 id 转换为 token。
1 | input_text = tokenizer.apply_chat_template( |
此外,apply_chat_template 还有一个参数 为 add_generation_prompt,作用是添加下一条消息的开头,即下面结果的最后一行 <|im_start|>assistant 。
Conversation decoded: <|im_start|>user
Hello, how are you?<|im_end|>
<|im_start|>assistant
I’m doing well, thank you! How can I assist you today?<|im_end|>
<|im_start|>assistant
Process datatset for SFT
为现有的数据集添加聊天模板。
案例 1
首先加载数据集并打印
1 | from datasets import load_dataset |
结果为如下,包含两个部分,分别为测试集和训练集。
DatasetDict({
train: Dataset({
features: ['full_topic', 'messages'],
num_rows: 2260
})
test: Dataset({
features: ['full_topic', 'messages'],
num_rows: 119
})
})
这里的 message 就是一个包含了用户和大模型之间对话的 list,内容如下,可以直接应用聊天模板。
[{‘content’: ‘Hi there’, ‘role’: ‘user’}, {‘content’: ‘Hello! How can I help you today?’, ‘role’: ‘assistant’}, {‘content’: “I’m looking for a beach resort for my next vacation. Can you recommend some popular ones?”, ‘role’: ‘user’}, {‘content’: “Some popular beach resorts include Maui in Hawaii, the Maldives, and the Bahamas. They’re known for their beautiful beaches and crystal-clear waters.”, ‘role’: ‘assistant’}, {‘content’: ‘That sounds great. Are there any resorts in the Caribbean that are good for families?’, ‘role’: ‘user’}, {‘content’: ‘Yes, the Turks and Caicos Islands and Barbados are excellent choices for family-friendly resorts in the Caribbean. They offer a range of activities and amenities suitable for all ages.’, ‘role’: ‘assistant’}, {‘content’: “Okay, I’ll look into those. Thanks for the recommendations!”, ‘role’: ‘user’}, {‘content’: “You’re welcome. I hope you find the perfect resort for your vacation.”, ‘role’: ‘assistant’}]
应用聊天模板的代码为:
1 | def process_dataset(sample): |
上述代码定义一个 process_dataset 函数,用于处理数据集。map 方法对数据集中的每个样本都执行给定的函数。此外,map 是更新式映射,希望返回一个字典,添加到原有的数据集字典中。上述代码的结果为:
DatasetDict({
train: Dataset({
features: ['full_topic', 'messages', 'chat'],
num_rows: 2260
})
test: Dataset({
features: ['full_topic', 'messages', 'chat'],
num_rows: 119
})
})
可以看到,原有的数据集字典中新增了一个字段 chat,内容为添加了聊天模板的字符串。
案例 2
本数据集包含了一个问题和相应的回答,所以在 process_dataset 函数中要先定义一个 message,然后再将 message 转换为带头聊天模板的字符串。
1 | ds = load_dataset("openai/gsm8k", "main", cache_dir="D:\study\smol-course\data") |
结果为:
DatasetDict({
train: Dataset({
features: ['question', 'answer'],
num_rows: 7473
})
test: Dataset({
features: ['question', 'answer'],
num_rows: 1319
})
})
DatasetDict({
train: Dataset({
features: ['question', 'answer', 'chat'],
num_rows: 7473
})
test: Dataset({
features: ['question', 'answer', 'chat'],
num_rows: 1319
})
})
Supervised Fine-tuning with SFTTrainer
Preparation
包含以下部分:
- device
- model
- tokenizer
- 设置聊天模板
1 | # Import necessary libraries |
Generate with base model
构建 message,然后使用 tokenizer 的 apply_chat_template 方法先将形式为字典的 message 转换为带特殊符号的字符串。
1
2
3
4
5
6
7
8prompt = "Write me a haiku about programming"
# Format with template
messages = [{"role": "user", "content": prompt}]
formatted_prompt = tokenizer.apply_chat_template(messages, tokenize=False)
# <|im_start|>user
Write me a haiku about programming<|im_end|>然后再进行 tokenize 处理,tokenizer 返回字典,包括 token 的编码以及一个 mask,用于表示哪些位置是 padding
1 | inputs = tokenizer(formatted_prompt, return_tensors="pt").to(device) |
- 生成回答:model.generate 需要多个参数,** 就是自动帮你把字典拆开一个个传进去。
- skip_special_tokens=True 表示 decode 时忽略 chat template 中的特殊字符
- 生成的 outputs 为一个 list,需要取第 0 个元素(why?),同样为 token 的 编号,通过 decode 方法映射到文本。
1
2
3outputs = model.generate(**inputs, max_new_tokens=100)
print(tokenizer.decode(outputs[0], skip_special_tokens=True))
Dataset
给定的数据集中已经有 dict 格式的 message,只需要对每条 message 做处理,将其生成为一个带特殊符号的字符串即可。注意最后的结果是多了一个 chat 字段。
1 | def process_dataset(sample): |
Train model
设置 SFTConfig 和 SFTTrainer。注意,尽量将所有的参数都写在 SFTConfig 中。
1 | # Configure the SFTTrainer |
训练并保存模型
1 | # Train the model |
Generate with trained model
代码和前面的相似
1 | model_path = "D:/study/smol-course/1_instruction_tuning/notebooks/SmolLM2-FT-MyDataset" |
注意,训练后的模型就不需要 setup_chat_format 了。因为在训练之前已经设置过,并保存到了相应的 json 文件中。验证如下:
1 | ori_tokenizer = AutoTokenizer.from_pretrained(pretrained_model_name_or_path="D:\study\smol-course\data\SmolLM2-135M") |