原文出处:Hands-On Exercises: Fine-Tuning SmolLM3 原作者:Hugging Face · 许可证:Apache-2.0 License 中文译本由诸葛AI学院翻译整理,仅供学习参考,版权归原作者与 Hugging Face 所有。
动手练习:微调 SmolLM3
欢迎来到实操部分!这里你会把学过的聊天模板(chat template)和有监督微调(supervised fine-tuning)知识,在 SmolLM3 上亲手用一遍。练习从基础概念逐步走向进阶技巧,让你攒下指令微调(instruction tuning)的实战经验。
学习目标
完成这些练习后,你将能够: - 掌握 SmolLM3 的聊天模板体系 - 分别用 Python API 和命令行工具,在真实数据集上微调 SmolLM3 - 上手训练原版模型时用过的 SmolTalk2 数据集 - 对比基座模型(base model)与微调后模型的表现 - 把你的模型部署到 Hugging Face Hub - 了解规模化微调的生产级工作流
练习 1:探索 SmolLM3 的聊天模板
目标:理解 SmolLM3 如何处理不同的对话格式与推理模式。
SmolLM3 是一个混合推理模型(hybrid reasoning model):它既能直接遵循指令作答,也能生成用于"推理"复杂问题的字块(token)。后训练(post-training)做得好,模型会在难题上展开推理,在简单问题上直接给出回答。
环境准备
- 训练需要至少 8GB 显存(VRAM)的 GPU。CPU/MPS 可以跑格式化和数据集探索,但训练较大的模型多半会失败。
- 首次运行会下载数 GB 的模型权重,请确保有 15GB 以上的剩余磁盘和稳定的网络。
- 如果需要访问私有仓库,用
login()登录 Hugging Face Hub。
先来配置环境。
```bash
安装所需依赖包(在 Colab 或你的本地环境运行)
pip install "transformers>=4.36.0" "trl>=0.7.0" "datasets>=2.14.0" "torch>=2.0.0" pip install "accelerate>=0.24.0" "peft>=0.7.0" "trackio" ```
然后导入需要的库,确定加速器设备。下面的代码会判断我们用的是 Nvidia GPU、Apple Metal 加速器还是 CPU。实际上模型没法在 CPU 上训练,所以要靠加速器。
```python
导入必要的库
import torch from transformers import AutoModelForCausalLM, AutoTokenizer, pipeline from datasets import load_dataset import json from typing import Optional, Dict, Any
if torch.cuda.is_available(): device = "cuda" print(f"Using CUDA GPU: {torch.cuda.get_device_name()}") print(f"GPU memory: {torch.cuda.get_device_properties(0).total_memory / 1e9:.1f}GB") elif hasattr(torch.backends, 'mps') and torch.backends.mps.is_available(): device = "mps" print("Using Apple MPS") else: device = "cpu" print("Using CPU - you will need to use a GPU to train models")
登录 Hugging Face(可选,用于私有模型)
from huggingface_hub import login
login() # 需要访问私有模型时取消注释
```
记下你使用的设备和可用的 GPU 显存。如果低于 8GB,有些练习你做不了。
输出
```python output Using CUDA GPU: NVIDIA A100-SXM4-40GB GPU memory: 42.5GB ```加载 SmolLM3 模型
现在加载基座模型和 instruct 模型,方便后面对比。
```python
加载基座模型与 instruct 模型,便于对比
base_model_name = "HuggingFaceTB/SmolLM3-3B-Base" instruct_model_name = "HuggingFaceTB/SmolLM3-3B"
加载分词器(tokenizer)
base_tokenizer = AutoTokenizer.from_pretrained(base_model_name) instruct_tokenizer = AutoTokenizer.from_pretrained(instruct_model_name)
加载模型(用低精度节省显存)
base_model = AutoModelForCausalLM.from_pretrained( base_model_name, dtype=torch.bfloat16, device_map="auto" )
instruct_model = AutoModelForCausalLM.from_pretrained( instruct_model_name, dtype=torch.bfloat16, device_map="auto" )
print("Models loaded successfully!") ```
这一步会把模型和分词器从 Hugging Face Hub 下载到你的本地机器,内容包括模型的参数权重、分词器,以及模型作者定义的其他配置。
输出
你应该能看到加载模型权重的绿色进度条,可能要花几分钟。 ```python output tokenizer_config.json: 50.4k/? [00:00<00:00, 5.09MB/s] tokenizer.json: 100% 17.2M/17.2M [00:02<00:00, 10.7MB/s] special_tokens_map.json: 100% 151/151 [00:00<00:00, 21.5kB/s] tokenizer_config.json: 50.4k/? [00:00<00:00, 5.45MB/s] tokenizer.json: 100% 17.2M/17.2M [00:00<00:00, 472kB/s] special_tokens_map.json: 100% 289/289 [00:00<00:00, 35.0kB/s] chat_template.jinja: 5.60k/? [00:00<00:00, 577kB/s] config.json: 100% 943/943 [00:00<00:00, 121kB/s] model.safetensors.index.json: 26.9k/? [00:00<00:00, 2.81MB/s] Fetching 2 files: 100% 2/2 [00:32<00:00, 32.11s/it] model-00001-of-00002.safetensors: 100% 4.97G/4.97G [00:31<00:00, 247MB/s] model-00002-of-00002.safetensors: 100% 1.18G/1.18G [00:17<00:00, 57.2MB/s] Loading checkpoint shards: 100% 2/2 [00:01<00:00, 1.18it/s] generation_config.json: 100% 126/126 [00:00<00:00, 17.1kB/s] config.json: 1.92k/? [00:00<00:00, 229kB/s] model.safetensors.index.json: 26.9k/? [00:00<00:00, 3.14MB/s] Fetching 2 files: 100% 2/2 [00:32<00:00, 32.38s/it] model-00002-of-00002.safetensors: 100% 1.18G/1.18G [00:17<00:00, 92.1MB/s] model-00001-of-00002.safetensors: 100% 4.97G/4.97G [00:31<00:00, 182MB/s] Loading checkpoint shards: 100% 2/2 [00:01<00:00, 1.14it/s] generation_config.json: 100% 182/182 [00:00<00:00, 21.0kB/s] Models loaded successfully! ```探索聊天模板的格式化
现在来探索聊天模板的格式化。我们构造几种不同类型的对话来测试。
```python
构造几种不同类型的对话用于测试
conversations = { "simple_qa": [ {"role": "user", "content": "What is machine learning?"}, ],
"with_system": [
{"role": "system", "content": "You are a helpful AI assistant specialized in explaining technical concepts clearly."},
{"role": "user", "content": "What is machine learning?"},
],
"multi_turn": [
{"role": "system", "content": "You are a math tutor."},
{"role": "user", "content": "What is calculus?"},
{"role": "assistant", "content": "Calculus is a branch of mathematics that deals with rates of change and accumulation of quantities."},
{"role": "user", "content": "Can you give me a simple example?"},
],
"reasoning_task": [
{"role": "user", "content": "Solve step by step: If a train travels 120 miles in 2 hours, what is its average speed?"},
]
}
for conv_type, messages in conversations.items(): print(f"--- {conv_type.upper()} ---")
# 不带生成提示词的格式化(用于已完成的对话)
formatted_complete = instruct_tokenizer.apply_chat_template(
messages,
tokenize=False,
add_generation_prompt=False
)
# 带生成提示词的格式化(用于推理)
formatted_prompt = instruct_tokenizer.apply_chat_template(
messages,
tokenize=False,
add_generation_prompt=True
)
print("Complete conversation format:")
print(formatted_complete)
print("\nWith generation prompt:")
print(formatted_prompt)
print("\n" + "="*50 + "\n")
```
输出
```python output --- SIMPLE_QA --- Complete conversation format: <|im_start|>system ## Metadata Knowledge Cutoff Date: June 2025 Today Date: 03 September 2025 Reasoning Mode: /no_think ## Custom Instructions You are a helpful AI assistant named SmolLM, trained by Hugging Face. <|im_start|>user What is machine learning?<|im_end|> With generation prompt: <|im_start|>system ## Metadata Knowledge Cutoff Date: June 2025 Today Date: 03 September 2025 Reasoning Mode: /no_think ## Custom Instructions You are a helpful AI assistant named SmolLM, trained by Hugging Face. <|im_start|>user What is machine learning?<|im_end|> <|im_start|>assistant对比基座模型与 instruct 模型的回答
这一节把同一个提示词分别喂给基座模型和 instruct 变体,观察格式上的差异,以及聊天模板对生成质量和风格的影响。
```python
在两个模型上测试同一个提示词
test_prompt = "Explain quantum computing in simple terms."
为基座模型准备提示词(不套聊天模板)
base_inputs = base_tokenizer(test_prompt, return_tensors="pt").to(device)
为 instruct 模型准备提示词(套聊天模板)
instruct_messages = [{"role": "user", "content": test_prompt}] instruct_formatted = instruct_tokenizer.apply_chat_template( instruct_messages, tokenize=False, add_generation_prompt=True ) instruct_inputs = instruct_tokenizer(instruct_formatted, return_tensors="pt").to(device)
生成回答
print("=== Model comparison ===\n")
print("🤖 BASE MODEL RESPONSE:") with torch.no_grad(): base_outputs = base_model.generate( **base_inputs, max_new_tokens=150, temperature=0.7, do_sample=True, pad_token_id=base_tokenizer.eos_token_id ) base_response = base_tokenizer.decode(base_outputs[0], skip_special_tokens=True) print(base_response[len(test_prompt):]) # 只展示生成的部分
print("\n" + "="50) print("Instruct model response:") with torch.no_grad(): instruct_outputs = instruct_model.generate( *instruct_inputs, max_new_tokens=150, temperature=0.7, do_sample=True, pad_token_id=instruct_tokenizer.eos_token_id ) instruct_response = instruct_tokenizer.decode(instruct_outputs[0], skip_special_tokens=True) # 只提取 assistant 的回答 assistant_start = instruct_response.find("<|im_start|>assistant\n") + len("<|im_start|>assistant\n") assistant_response = instruct_response[assistant_start:].split("<|im_end|>")[0] print(assistant_response) ```
看下面的输出,能发现两种模型的差别。简单说:基座模型是在续写字符串,instruct 模型则按聊天模板来回答。比如基座模型的开头是 " What are the differences between the classical bit and the quantum bit?"(接着你的话往下提问),而 instruct 模型一上来就回答问题:"Quantum computing is a type of computing that uses quantum bits"。
输出
```python output === Model comparison === 🤖 BASE MODEL RESPONSE: Why is it thought to be superior to our current technology? How is it superior? What is it's limit? Quantum computing is based on the fact that in quantum mechanics, a particle can be in multiple states at the same time. This is called superposition. But a single particle can not be in multiple locations at the same time. That is called entanglement. So, how can you have a particle in multiple locations at the same time? Quantum mechanics says that if you measure the location of a particle, it will randomly jump to a particular location. So, if you have 1000 particles, you can have each particle in 1000 different locations at the same time. This is very useful for solving problems. For example ================================================== Instruct model response: nowledge Cutoff Date: June 2025 Today Date: 03 September 2025 Reasoning Mode: /no_think ## Custom Instructions You are a helpful AI assistant named SmolLM, trained by Hugging Face. user Explain quantum computing in simple terms. assistant测试双模式推理
这里用数学题和比例题来试探 SmolLM3 的推理模式:把温度(temperature)调低保证结果稳定,并且只从聊天格式的输出里提取 assistant 的回答。
```python
测试 SmolLM3 的推理能力
reasoning_prompts = [ "What is 15 × 24? Show your work.", "A recipe calls for 2 cups of flour for 12 cookies. How much flour is needed for 30 cookies?", "If I have $50 and spend $18.75 on lunch and $12.30 on a book, how much money do I have left?" ]
print("=== TESTING REASONING CAPABILITIES ===\n")
for i, prompt in enumerate(reasoning_prompts, 1): print(f"Problem {i}: {prompt}")
messages = [{"role": "user", "content": prompt}]
formatted_prompt = instruct_tokenizer.apply_chat_template(
messages, tokenize=False, add_generation_prompt=True
)
inputs = instruct_tokenizer(formatted_prompt, return_tensors="pt").to(device)
with torch.no_grad():
outputs = instruct_model.generate(
**inputs,
max_new_tokens=200,
temperature=0.3, # 调低温度,让推理结果更稳定
do_sample=True,
pad_token_id=instruct_tokenizer.eos_token_id
)
response = instruct_tokenizer.decode(outputs[0], skip_special_tokens=True)
assistant_start = response.find("<|im_start|>assistant\n") + len("<|im_start|>assistant\n")
assistant_response = response[assistant_start:].split("<|im_end|>")[0]
print(f"Answer: {assistant_response}")
print("\n" + "-"*50 + "\n")
```
看下面的输出,你会发现 instruct 模型的混合推理正以 /no_think 模式运行。该模式激活时,模型会把思考过程包在 <think> 标签里,用这些字块探索可能的解法再作答。思考过程结束后,模型给出最终答案,我们这里用聊天模板或字符串截取就能把它提取出来。
输出
```python output === TESTING REASONING CAPABILITIES === Thinking prompt: /no_think Problem 1: What is 15 × 24? Show your work. Answer: nowledge Cutoff Date: June 2025 Today Date: 03 September 2025 Reasoning Mode: /no_think ## Custom Instructions You are a helpful AI assistant named SmolLM, trained by Hugging Face. user What is 15 × 24? Show your work. assistant验证
跑一遍上面的代码,确认你能看到: 1. 不同对话类型对应的聊天模板格式 2. 基座模型与 instruct 模型回答之间的明显差异 3. SmolLM3 推理能力的实际表现
练习 2:为 SFT 处理数据集
目标:学会用 SmolTalk2 及其他数据集,为有监督微调(SFT,supervised fine-tuning)处理和准备数据。
探索 SmolTalk2 数据集
我们加载 SmolTalk2 的 SFT 数据划分(split),检查它的结构和几个样本,弄清楚字段(比如 messages)和可用的子集,然后再为训练准备数据。
```python
加载并探索 SmolTalk2 数据集
print("=== EXPLORING SMOLTALK2 DATASET ===\n")
加载 SFT 子集
dataset_dict = load_dataset("HuggingFaceTB/smoltalk2", "SFT") print(f"Total splits: {len(dataset_dict)}") print(f"Available splits: {list(dataset_dict.keys())}") print(f"Number of total rows: {sum([dataset_dict[d].num_rows for d in dataset_dict])}") print(f"Dataset structure: {dataset_dict}") ```
看下面的输出可以看到数据集的结构:共 25 个划分,总行数 3,383,242。
输出
```python output === EXPLORING SMOLTALK2 DATASET === Resolving data files: 100% 124/124 [00:00<00:00, 9963.48it/s] Resolving data files: 100% 113/113 [00:00<00:00, 57.54it/s] Resolving data files: 100% 113/113 [00:00<00:00, 114.07it/s] Loading dataset shards: 100% 105/105 [00:00<00:00, 2570.62it/s] Total splits: 25 Available splits: ['LongAlign_64k_Qwen3_32B_yarn_131k_think', 'OpenThoughts3_1.2M_think', 'aya_dataset_Qwen3_32B_think', 'multi_turn_reasoning_if_think', 's1k_1.1_think', 'smolagents_toolcalling_traces_think', 'smoltalk_everyday_convs_reasoning_Qwen3_32B_think', 'smoltalk_multilingual8_Qwen3_32B_think', 'smoltalk_systemchats_Qwen3_32B_think', 'table_gpt_Qwen3_32B_think', 'LongAlign_64k_context_lang_annotated_lang_6_no_think', 'Mixture_of_Thoughts_science_no_think', 'OpenHermes_2.5_no_think', 'OpenThoughts3_1.2M_no_think_no_think', 'hermes_function_calling_v1_no_think', 'smoltalk_multilingual_8languages_lang_5_no_think', 'smoltalk_smollm3_everyday_conversations_no_think', 'smoltalk_smollm3_explore_instruct_rewriting_no_think', 'smoltalk_smollm3_smol_magpie_ultra_no_think', 'smoltalk_smollm3_smol_rewrite_no_think', 'smoltalk_smollm3_smol_summarize_no_think', 'smoltalk_smollm3_systemchats_30k_no_think', 'table_gpt_no_think', 'tulu_3_sft_personas_instruction_following_no_think', 'xlam_traces_no_think'] Number of total rows: 3383242 Dataset structure: DatasetDict({ LongAlign_64k_Qwen3_32B_yarn_131k_think: Dataset({ features: ['messages', 'chat_template_kwargs', 'source'], num_rows: 7526 }) OpenThoughts3_1.2M_think: Dataset({ features: ['messages', 'chat_template_kwargs', 'source'], num_rows: 1133524 }) aya_dataset_Qwen3_32B_think: Dataset({ features: ['messages', 'chat_template_kwargs', 'source'], num_rows: 15222 }) multi_turn_reasoning_if_think: Dataset({ features: ['messages', 'chat_template_kwargs', 'source'], num_rows: 28217 }) s1k_1.1_think: Dataset({ features: ['messages', 'chat_template_kwargs', 'source'], num_rows: 835 }) smolagents_toolcalling_traces_think: Dataset({ features: ['messages', 'chat_template_kwargs', 'source'], num_rows: 9079 }) smoltalk_everyday_convs_reasoning_Qwen3_32B_think: Dataset({ features: ['messages', 'chat_template_kwargs', 'source'], num_rows: 2057 }) smoltalk_multilingual8_Qwen3_32B_think: Dataset({ features: ['messages', 'chat_template_kwargs', 'source'], num_rows: 244736 }) smoltalk_systemchats_Qwen3_32B_think: Dataset({ features: ['messages', 'chat_template_kwargs', 'source'], num_rows: 27436 }) table_gpt_Qwen3_32B_think: Dataset({ features: ['messages', 'chat_template_kwargs', 'source'], num_rows: 13201 }) LongAlign_64k_context_lang_annotated_lang_6_no_think: Dataset({ features: ['messages', 'chat_template_kwargs', 'source'], num_rows: 6249 }) Mixture_of_Thoughts_science_no_think: Dataset({ features: ['messages', 'chat_template_kwargs', 'source'], num_rows: 86110 }) OpenHermes_2.5_no_think: Dataset({ features: ['messages', 'chat_template_kwargs', 'source'], num_rows: 384900 }) OpenThoughts3_1.2M_no_think_no_think: Dataset({ features: ['messages', 'chat_template_kwargs', 'source'], num_rows: 435193 }) hermes_function_calling_v1_no_think: Dataset({ features: ['messages', 'chat_template_kwargs', 'source'], num_rows: 8961 }) smoltalk_multilingual_8languages_lang_5_no_think: Dataset({ features: ['messages', 'chat_template_kwargs', 'source'], num_rows: 254047 }) smoltalk_smollm3_everyday_conversations_no_think: Dataset({ features: ['messages', 'chat_template_kwargs', 'source'], num_rows: 2260 }) smoltalk_smollm3_explore_instruct_rewriting_no_think: Dataset({ features: ['messages', 'chat_template_kwargs', 'source'], num_rows: 30391 }) smoltalk_smollm3_smol_magpie_ultra_no_think: Dataset({ features: ['messages', 'chat_template_kwargs', 'source'], num_rows: 406843 }) smoltalk_smollm3_smol_rewrite_no_think: Dataset({ features: ['messages', 'chat_template_kwargs', 'source'], num_rows: 53262 }) smoltalk_smollm3_smol_summarize_no_think: Dataset({ features: ['messages', 'chat_template_kwargs', 'source'], num_rows: 96061 }) smoltalk_smollm3_systemchats_30k_no_think: Dataset({ features: ['messages', 'chat_template_kwargs', 'source'], num_rows: 33997 }) table_gpt_no_think: Dataset({ features: ['messages', 'chat_template_kwargs', 'source'], num_rows: 13203 }) tulu_3_sft_personas_instruction_following_no_think: Dataset({ features: ['messages', 'chat_template_kwargs', 'source'], num_rows: 29970 }) xlam_traces_no_think: Dataset({ features: ['messages', 'chat_template_kwargs', 'source'], num_rows: 59962 }) }) ```处理不同类型的数据集
SmolTalk2 是把多个开源数据集汇总在一起的集合,方便大家取用。它收录了后训练常用的各种场景,比如工具调用(tool use)、长上下文(long context)等,全部是聊天格式,拿来训练很方便。但并非所有数据集都以一致的格式发布,所以常常需要把数据处理成统一的聊天 messages 结构。
这个练习会把多种数据集格式标准化为统一的聊天 messages 结构。我们为问答(QA)类和指令类数据集各定义一个轻量处理器,再用 GSM8K 走一遍具体例子。
```python
处理不同数据集格式的函数
def process_qa_dataset(examples, question_col, answer_col): """Process Q&A datasets into chat format""" processed = []
for question, answer in zip(examples[question_col], examples[answer_col]):
messages = [
{"role": "user", "content": question},
{"role": "assistant", "content": answer}
]
processed.append(messages)
return {"messages": processed}
def process_instruction_dataset(examples): """Process instruction-following datasets""" processed = []
for instruction, response in zip(examples["instruction"], examples["response"]):
messages = [
{"role": "user", "content": instruction},
{"role": "assistant", "content": response}
]
processed.append(messages)
return {"messages": processed}
示例:处理 GSM8K 数学数据集
print("=== PROCESSING GSM8K DATASET ===\n")
gsm8k = load_dataset("openai/gsm8k", "main", split="train[:100]") # 取小子集做演示 print(f"Original GSM8K example: {gsm8k[0]}")
转换为聊天格式
def process_gsm8k(examples): processed = [] for question, answer in zip(examples["question"], examples["answer"]): messages = [ {"role": "system", "content": "You are a math tutor. Solve problems step by step."}, {"role": "user", "content": question}, {"role": "assistant", "content": answer} ] processed.append(messages) return {"messages": processed}
gsm8k_processed = gsm8k.map(process_gsm8k, batched=True, remove_columns=gsm8k.column_names) print(f"Processed example: {gsm8k_processed[0]}") ```
下面能看到:两个不同数据集的样本,被统一成了同一种格式。
输出
```python output === PROCESSING GSM8K DATASET === README.md: 7.94k/? [00:00<00:00, 572kB/s] main/train-00000-of-00001.parquet: 100% 2.31M/2.31M [00:01<00:00, 42.6kB/s] main/test-00000-of-00001.parquet: 100% 419k/419k [00:00<00:00, 813kB/s] Generating train split: 100% 7473/7473 [00:00<00:00, 321312.49 examples/s] Generating test split: 100% 1319/1319 [00:00<00:00, 97120.71 examples/s] Original GSM8K example: {'question': 'Natalia sold clips to 48 of her friends in April, and then she sold half as many clips in May. How many clips did Natalia sell altogether in April and May?', 'answer': 'Natalia sold 48/2 = <<48/2=24>>24 clips in May.\nNatalia sold 48+24 = <<48+24=72>>72 clips altogether in April and May.\n#### 72'} Map: 100% 100/100 [00:00<00:00, 4792.50 examples/s] Processed example: {'messages': [{'content': 'You are a math tutor. Solve problems step by step.', 'role': 'system'}, {'content': 'Natalia sold clips to 48 of her friends in April, and then she sold half as many clips in May. How many clips did Natalia sell altogether in April and May?', 'role': 'user'}, {'content': 'Natalia sold 48/2 = <<48/2=24>>24 clips in May.\nNatalia sold 48+24 = <<48+24=72>>72 clips altogether in April and May.\n#### 72', 'role': 'assistant'}]} ```为数据集套用聊天模板
消息格式统一之后,接下来套用模型的聊天模板,把每条样本转换成纯文本的训练串(text 列),供 SFT 语言建模使用。
```python
给处理好的数据集套用聊天模板的函数
def apply_chat_template_to_dataset(dataset, tokenizer): """Apply chat template to dataset for training"""
def format_messages(examples):
formatted_texts = []
for messages in examples["messages"]:
# 套用聊天模板
formatted_text = tokenizer.apply_chat_template(
messages,
tokenize=False,
add_generation_prompt=False # 保留完整对话
)
formatted_texts.append(formatted_text)
return {"text": formatted_texts}
return dataset.map(format_messages, batched=True)
应用到我们处理好的 GSM8K 数据集
gsm8k_formatted = apply_chat_template_to_dataset(gsm8k_processed, instruct_tokenizer) print("=== FORMATTED TRAINING DATA ===") print(gsm8k_formatted[0]["text"]) ```
输出
```python output === PROCESSING GSM8K DATASET === README.md: 7.94k/? [00:00<00:00, 572kB/s] main/train-00000-of-00001.parquet: 100% 2.31M/2.31M [00:01<00:00, 42.6kB/s] main/test-00000-of-00001.parquet: 100% 419k/419k [00:00<00:00, 813kB/s] Generating train split: 100% 7473/7473 [00:00<00:00, 321312.49 examples/s] Generating test split: 100% 1319/1319 [00:00<00:00, 97120.71 examples/s] Original GSM8K example: {'question': 'Natalia sold clips to 48 of her friends in April, and then she sold half as many clips in May. How many clips did Natalia sell altogether in April and May?', 'answer': 'Natalia sold 48/2 = <<48/2=24>>24 clips in May.\nNatalia sold 48+24 = <<48+24=72>>72 clips altogether in April and May.\n#### 72'} Map: 100% 100/100 [00:00<00:00, 4792.50 examples/s] Processed example: {'messages': [{'content': 'You are a math tutor. Solve problems step by step.', 'role': 'system'}, {'content': 'Natalia sold clips to 48 of her friends in April, and then she sold half as many clips in May. How many clips did Natalia sell altogether in April and May?', 'role': 'user'}, {'content': 'Natalia sold 48/2 = <<48/2=24>>24 clips in May.\nNatalia sold 48+24 = <<48+24=72>>72 clips altogether in April and May.\n#### 72', 'role': 'assistant'}]} ```(下篇继续)