Re0-01: HuggingFace Transformers Trainer
配套代码:
trainer.py
前言:为什么需要微调?
这组文章从可运行的微调流程讲起。
预训练的大语言模型已经读过大量公开文本,但直接使用时仍会遇到这些边界:
- 它可能不知道你公司的内部规定
- 它可能不了解你特定领域的专业知识
- 它可能不会按照你期望的格式回答问题
微调(Fine-tuning) 用任务数据约束模型行为,让输出更贴近具体场景。
本章学习目标
本章覆盖:
- Hugging Face 生态系统 的基本组成
- 监督微调(SFT) 的基本概念
- Loss Masking 在 SFT 中的作用
- 量化 和 PEFT/LoRA 如何让普通显卡也能微调大模型
- DataCollator 的作用和选择
1. Hugging Face 生态系统概览
1.1 核心组件
Hugging Face 生态系统架构
graph TD
A[Transformers
模型加载] --> C[Trainer
训练循环管理]
B[Datasets
数据加载] --> C
D[PEFT
参数高效微调] --> C
| 组件 | 作用 | 本教程使用 |
|---|---|---|
transformers |
加载和使用预训练模型 | [是] |
datasets |
加载和处理数据集 | [是] |
peft |
参数高效微调(LoRA等) | [是] |
bitsandbytes |
模型量化(4-bit/8-bit) | [是] |
trl |
强化学习和高级训练 | 下一章 |
1.2 什么是 Trainer?
Trainer 是 Hugging Face 提供的训练管理器,它帮你处理:
- 训练循环(前向传播、反向传播、参数更新)
- 评估和验证
- 模型保存和加载
- 日志记录
- 分布式训练
准备好数据和配置后,Trainer 会接管主要训练流程。
2. 监督微调(SFT)的核心概念
2.1 什么是 SFT?
监督微调(Supervised Fine-Tuning, SFT) 是用带标签的数据训练模型的过程。
在对话任务中,我们的数据通常是这样的:
对话示例:
- 用户:请帮我总结这段对话的内容…
- 助手:这段对话主要讨论了…
2.2 Loss Masking:SFT 的核心
注意 这是本章最重要的概念!
在训练时,模型需要预测下一个 token。但问题是:
- 模型应该学习生成回答,而不是学习生成问题
- 如果对整个序列计算 Loss,模型会试图”背诵”用户的问题
解决方案:Loss Masking
| 输入序列 | [用户问题] | [助手回答] |
|---|---|---|
| 标签 | [-100] | [实际token] |
| Loss 计算 | [否] 不计算 | [是] 计算 |
labels = -100表示这个位置不计算 Loss(PyTorch 的默认忽略值)- 只有助手回答的部分会被用于训练
2.3 代码中的 Loss Masking
在 trainer.py 的 process_func() 函数中:
1 | # 1. 初始化所有 labels 为 -100(全部忽略) |
效果:
- 用户问题部分:
labels = [-100, -100, -100, ...]→ 不计算 Loss - 助手回答部分:
labels = [token1, token2, token3, ...]→ 计算 Loss
3. 让普通显卡运行大模型:量化与 LoRA
3.1 量化(Quantization)
问题:一个 7B 参数的模型,用 FP32 存储需要 28GB 显存!
解决方案:将参数从高精度压缩到低精度
| 精度 | 每参数位数 | 7B模型大小 | 速度 |
|---|---|---|---|
| FP32 | 32 bit | 28 GB | 基准 |
| FP16 | 16 bit | 14 GB | 更快 |
| INT8 | 8 bit | 7 GB | 更快 |
| INT4 | 4 bit | 3.5 GB | 最快 |
在代码中使用 4-bit 量化:
1 | from transformers import BitsAndBytesConfig |
3.2 为什么量化后全参数训练仍需大量显存?
这里容易产生误解。
常见判断是:“模型已经量化成 4-bit 了,训练时应该不需要很多显存才对”
但量化主要减少模型权重的存储空间,训练时还要存储权重之外的内容。
显存占用详解
以 8B 参数模型的全参数训练为例:
| 组成部分 | 大小 | 说明 |
|---|---|---|
| 1. 模型权重(量化后) | 4 GB | 8B × 0.5字节(INT4) = 4 GB |
| 2. 梯度 | 16 GB | 8B × 2字节(FP16) = 16 GB |
| 3. 优化器状态 | 64 GB | 8B × 4字节(FP32) × 2 = 64 GB |
| 4. 参数副本 | 16 GB | 8B × 2字节(FP16) = 16 GB |
| 总计 | 100 GB | 远超普通消费级显卡! |
为什么需要这么多?
梯度不能量化!
- 梯度用于更新参数,必须保持 FP16/FP32 精度
- 如果梯度也是 4-bit,参数更新会严重失真,模型无法收敛
优化器状态不能量化!
- AdamW 优化器需要存储一阶动量(m)和二阶动量(v)
- 这些动量必须保持 FP32 精度,否则优化效果会大幅下降
参数副本需要高精度!
- 更新参数时需要在 FP16/FP32 精度下计算
- 不能直接在 4-bit 权重上进行数值运算
结论
量化只是让模型”加载”更容易,但”训练”仍然需要大量显存!
想要有效降低训练显存,必须减少可训练参数的数量 → 这就是 LoRA 的作用
3.3 LoRA(Low-Rank Adaptation)
问题:即使量化后,微调所有参数仍然需要大量显存(如上所述,约 100 GB)
解决方案:只训练一小部分参数
LoRA 的显存优势
同样的 8B 模型,使用 LoRA 训练时:
| 组成部分 | 全参数训练 | LoRA 训练 |
|---|---|---|
| 量化权重(冻结) | 4 GB | 4 GB |
| 可训练参数 | 16 GB | 0.16 GB (1%) |
| 梯度 | 16 GB | 0.16 GB (1%) |
| 优化器状态 | 64 GB | 0.64 GB (1%) |
| 总计 | 100 GB | ~5 GB + 激活值 |
结论:LoRA 将显存需求从 100 GB 降到约 10-15 GB(含激活值),普通显卡也能训练!
LoRA 的工作原理:
1 | 原始模型权重 W(冻结,不训练) |
关键点:
- 原始权重 W 被冻结,不参与训练
- 只训练两个小矩阵 A 和 B(r << d,通常 r = 8, 16, 32, 64)
- B·A 的结果是一个低秩矩阵,作为原始权重的”调整量”
- α 是缩放因子,控制 LoRA 的影响程度
参数量对比:
- 原始:4096 × 4096 = 16,777,216 参数
- LoRA:4096 × 64 + 64 × 4096 = 524,288 参数(减少 97%!)
代码配置:
1 | from peft import LoraConfig, get_peft_model |
4. DataCollator:数据整理器
4.1 什么是 DataCollator?
DataCollator 负责将多个样本组合成一个 batch,只有填充为等长度的结构,torch中的矩阵运算方法才能够加速训练,每个 batch 使用样本的一维tensor组成2维的tensor,再进行后面的正向传播,损失计算和反向传播:
1 | 样本1: [1, 2, 3, 4] → padding → [1, 2, 3, 4, 0, 0] |
4.2 两种关键的 DataCollator
| DataCollator | 适用场景 | labels 处理 |
|---|---|---|
DataCollatorForLanguageModeling |
预训练 | 自动复制 input_ids 作为 labels |
DataCollatorForSeq2Seq |
SFT | 保留预处理好的 labels,padding 用 -100 |
注意 重要:SFT 必须使用 DataCollatorForSeq2Seq,因为它尊重你预处理好的 labels(包含 Loss Masking),DataCollatorForLanguageModeling 则不尊重。
5. 完整训练流程
5.1 流程图
SFT 训练完整流程
加载数据集
1
dataset = load_dataset("数据集名称")
加载模型 + 量化
1
2
3
4
5model = AutoModelForCausalLM.from_pretrained(
"Qwen/Qwen3-8B",
quantization_config=quant_config,
device_map="auto"
)数据预处理(Loss Masking)
1
train_dataset = preprocess_dataset(tokenizer, max_length, seed, dataset['train'])
添加 LoRA 适配器
1
model = get_peft_model(model, lora_config)
配置 Trainer
1
trainer = Trainer(model, dataset, data_collator, args)
开始训练
1
trainer.train()
保存模型
1
trainer.save_model()
5.2 关键代码摘要
1 | # 1. 加载数据集 |
6. 运行代码
6.1 环境准备
1 | pip install transformers datasets peft bitsandbytes accelerate |
如果你使用 uv 管理环境,可以在 code/ 目录自建 pyproject.toml;注意 torch 与 CUDA 版本的匹配。
6.2 运行训练
1 | python trainer.py |
6.3 修改配置
在 trainer.py 底部找到配置参数:
1 | COLLATOR_TYPE = "visual_check" # 推荐用于学习,会打印详细信息 |
7. 本章小结
| 概念 | 说明 |
|---|---|
| SFT | 用标注数据微调模型,而不是预训练中对每一个后续token计算损失 |
| Loss Masking | 只对助手回答计算 Loss,这是SFT和预训练的核心区别 |
| 量化 | 压缩模型参数存储空间大小,让普通显卡能放下 |
| LoRA | 只训练少量参数,进一步大幅减少显存需求 |
| DataCollator | 组合样本成 batch,是本节除了 Loss 以外需要重点理解的组件 |
8. 动手练习
- 运行
trainer.py,观察VisualCheckDataCollator打印的信息 - 尝试将
COLLATOR_TYPE改为"seq2seq",对比输出 - 修改
MAX_STEPS,观察训练时间和效果的变化
下一章预告
在本章中,我们手动实现了 Loss Masking,这需要:
- 计算 prompt 长度
- 手动设置 labels
- 选择正确的 DataCollator
问题:能不能让这些全部自动化?
答案:可以!
在下一章,我们将学习 TRL 库的 SFTTrainer,它可以:
- 自动处理 Loss Masking
- 自动选择 DataCollator
- 开始使用 Flash Attention 加速
附录:常见问题
Q: 为什么我的模型训练后说话停不下来?
A: 可能是数据预处理时没有正确添加 EOS token。使用apply_chat_template会自动处理。Q: 显存不够怎么办?
A: 1) 减小 batch_size 2) 使用 gradient_accumulation 3) 减小 LoRA 的 r 值
参考资料
- Title: Re0-01: HuggingFace Transformers Trainer
- Author: Hyacehila
- Created at : 2025-12-27 14:00:00
- Link: https://hyacehila.github.io//blog/2025/12/27/Re0HF-01/
- License: This work is licensed under CC BY-NC-SA 4.0.