Chapter 06

指令微调 vs 对话微调:Chat Template

正确的格式是微调成功的基础。格式错误可以让再好的数据也白费功夫。

两种微调范式

SFT 指令微调

  • 单轮:一个指令 → 一个回答
  • Alpaca 格式
  • 适合:分类、提取、翻译、代码生成
  • 数据构建简单,但模式单一
  • 模型学会遵从特定指令格式

对话微调(Chat SFT)

  • 多轮:上下文感知的对话
  • ShareGPT / ChatML 格式
  • 适合:聊天机器人、客服、助手
  • 数据构建复杂,需要多轮标注
  • 模型学会角色扮演和上下文保持

主流 Chat Template 格式

ChatML(Qwen、Mistral-Instruct 常用)

<|im_start|>system
你是一个专业的法律助手。<|im_end|>
<|im_start|>user
什么是合同违约?<|im_end|>
<|im_start|>assistant
合同违约是指合同一方当事人不履行合同义务或履行不符合约定的行为...<|im_end|>

Llama 3 格式

<|begin_of_text|><|start_header_id|>system<|end_header_id|>

你是一个专业的法律助手。<|eot_id|><|start_header_id|>user<|end_header_id|>

什么是合同违约?<|eot_id|><|start_header_id|>assistant<|end_header_id|>

合同违约是指...<|eot_id|>

使用 apply_chat_template

from transformers import AutoTokenizer

tokenizer = AutoTokenizer.from_pretrained("meta-llama/Meta-Llama-3-8B-Instruct")

# 数据格式
messages = [
    {"role": "system",    "content": "你是专业的客服助手..."},
    {"role": "user",      "content": "我的订单什么时候发货?"},
    {"role": "assistant", "content": "您好!请提供您的订单号,我来帮您查询..."}
]

# 转换为模型训练文本
text = tokenizer.apply_chat_template(
    messages,
    tokenize=False,
    add_generation_prompt=False  # 训练时 False,推理时 True
)
print(text)

# 批量处理数据集
def format_dataset(examples):
    texts = []
    for convs in examples["conversations"]:
        text = tokenizer.apply_chat_template(
            convs, tokenize=False, add_generation_prompt=False
        ) + tokenizer.eos_token
        texts.append(text)
    return {"text": texts}

只对 Assistant 回答计算 Loss

关键技巧:在对话微调中,不应该对用户消息(human turn)的 token 计算 loss,否则模型会学习"模拟用户提问"而不是"给出高质量回答":

from unsloth.chat_templates import get_chat_template

# Unsloth 自动处理 response-only 训练
tokenizer = get_chat_template(
    tokenizer,
    chat_template="llama-3",
    map_eos_token=True
)

from trl import SFTTrainer
from unsloth import train_on_responses_only

trainer = SFTTrainer(...)
trainer = train_on_responses_only(
    trainer,
    instruction_part="<|start_header_id|>user<|end_header_id|>\n\n",
    response_part="<|start_header_id|>assistant<|end_header_id|>\n\n"
)
# 这样只有 assistant 的 token 会参与 loss 计算

评估微调效果

from unsloth import FastLanguageModel

# 加载微调后的模型做推理
model, tokenizer = FastLanguageModel.from_pretrained(
    model_name="outputs/checkpoint-500",
    max_seq_length=2048,
    load_in_4bit=True
)
FastLanguageModel.for_inference(model)  # 推理模式优化

def generate(prompt: str) -> str:
    inputs = tokenizer.apply_chat_template(
        [{"role": "user", "content": prompt}],
        tokenize=True, add_generation_prompt=True,
        return_tensors="pt"
    ).to("cuda")

    outputs = model.generate(
        input_ids=inputs,
        max_new_tokens=512,
        temperature=0.7,
        do_sample=True
    )
    return tokenizer.decode(outputs[0][len(inputs[0]):], skip_special_tokens=True)
本章小结 选对 Chat Template,使用 apply_chat_template 标准化数据,训练时只对 assistant 回答计算 loss。下一章进入全参数微调与分布式训练。