Unsloth微调

微信公众号

数据处理的工程化时间

  • 数据清洗:去除噪声数据(比如乱码/重复文本),对不平衡数据进行重采样
  • 高效预处理:使用 HuggingFace Datasets 库实现流水线处理
  • 内存优化:对于超大规模数据集,我这里建议使用内存映射文件(MMAP)技术

LoRA各种策略

减少显存占用

bitsandbytes库简介

  • 通过将权重从 FP32 或 FP16 量化为更低比特的表示(如 4-bit、8-bit),大大减小模型的显存占用,从而让你能够在同样的硬件环境下加载更大的模型,或者用相同规模的模型获得更高批量大小(batch size)。
# 这里推荐使用 bitsandbytes 量化库降低显存占用
from transformers import BitsAndBytesConfig

quant_config = BitsAndBytesConfig(
    load_in_4bit=True, # 将模型权重量化为4-bit
    bnb_4bit_use_double_quant=True # 在量化时进行双重量化过程,可以进一步减少内存暂用、降低量化误差
)

model = AutoModel.from_pretrained("Llama-2-7b", quantization_config=quant_config)
  • BitsAndBytesConfig 是一个在 Hugging Face transformers 中用来配置 bitsandbytes 量化参数的类。你可以在创建模型时,将这个配置以 quantization_config 参数的形式传给 from_pretrained() 方法。
训练过程的精细化控制

学习率的三阶段策略

  • 预热阶段(前 10% steps):线性增长至 2e-5
    • 学习率从一个较低的初始值(通常可以是 0,也可以是非常小的值)线性上升到目标最大学习率(例:2e-5)。
    • 在训练的前期,让学习率从0(或一个小值)线性增长到设定的最大值(2e-5),可以避免训练初期学习率过大而导致梯度爆炸,同时也能让模型在一开始快速适应任务。
      • 常见做法:Warmup Steps 占总训练步数的 5% ~ 10%。
      • 参数:num_warmup_steps 表示预热步数。
  • 稳定阶段:余弦退火调节(中间 85% 的训练步数)
    • 学习率围绕最大值做余弦退火,从而逐渐衰减。
    • 在剩余的训练步数中,学习率会按照余弦曲线从最大学习率逐渐衰减到一个较低的值。余弦退火相较线性衰减,有时可以带来更平滑的效果,减轻在训练后期振荡过大的问题。
      • 对于 get_cosine_schedule_with_warmup,当超过 num_warmup_steps 后,学习率会根据余弦函数从当前最大值逐渐衰减至 0(或者一个 eta_min,依不同实现而定)。
  • 微调阶段(最后 5% steps):降至 1e-6
    • 在训练接近尾声时,将学习率下降到更小的值(例:1e-6),可以用线性或者继续让余弦退火到一个更低的值,帮助模型在后期收敛得更平滑或做小步调整。
optimizer = AdamW(model.parameters(), lr=2e-5, weight_decay=0.01)
scheduler = get_cosine_schedule_with_warmup(
    optimizer, 
    num_warmup_steps=100, # 预热步数
    num_training_steps=1000 # 整个训练过程的步数
)
  • AdamW 是 Adam 优化器的一个变体,融合了 L2 正则/权重衰减(Weight Decay)在更新中的合理处理方式。
显存优化的三大技巧

下面我们就来详细探讨一下深度学习训练过程中常用的 显存优化 手段。这些方法在大模型微调(Fine-tuning)、分布式训练或硬件资源有限的情况下尤其常见。你提到的三大技巧包括:

1. 梯度累积(Gradient Accumulation)
1.1 原理

在默认的训练循环中,每处理一个批次(batch)后会立即计算梯度并进行一次参数更新。如果你的 GPU 显存有限,无法一次性放下较大的 batch,那么就可以通过“多次前向 + 反向”累积梯度,再进行一次参数更新,从而“模拟”较大批次。

简而言之:

  • 正常训练:1批次 → 1次反向传播 → 1次优化器更新
  • 梯度累积:N个批次 → 累计梯度N次 → 1次优化器更新
1.2 具体做法

使用 Hugging Face TrainingArguments 时,可以设置 gradient_accumulation_steps。比如:

training_args = TrainingArguments(
    ...,
    gradient_accumulation_steps=4
)

这表示在内部会将每 4 个 batch 的梯度相加后再执行一次 optimizer.step()。这样做的好处是:

  • 在同等显存下,相当于使用了“4 倍批次大小”的效果;
  • 对一些需要较大 batch size 才能收敛更好的任务(如语言模型预训练、特定 NLP 任务等),这是一个关键技巧。
1.3 注意事项
  1. 有效学习率:当你使用梯度累积时,相当于增大了批次大小,需要注意保持或调整学习率。例如,如果你原先 batch size=16,现在用 gradient_accumulation_steps=4 并把单次 batch size 改成4,总有效 batch size 就依然是16。这样你的学习率设置就不用大幅修改。
  2. 训练时间:梯度累积并不会减少计算量,对每个batch依然要做前向和反向,但它能帮助你在有限显存下进行较大的 batch 训练。
  3. 分布式训练配合:如果同时使用分布式训练(DDP),批次大小会再乘以 worker 数,需综合计算。

2. 混合精度训练(Mixed Precision)
2.1 原理

混合精度训练指的是使用更低的数值表示(如 FP16 / BF16)来存储和计算模型的部分权重、激活或梯度,从而减小显存占用并加速运算。

  • FP16 (float16):半精度浮点,可在大多数现代 GPU(如 V100、A100、T4 等)上运行。
  • BF16 (bfloat16):Google 提出的 16-bit 格式,数值范围比 FP16 更大,减少梯度溢出的风险,在 NVIDIA A100 等支持 bfloat16 的硬件上效果更佳。
2.2 在 Hugging Face 中的使用

TrainingArguments 中可以直接启用混合精度。

  • FP16

    training_args = TrainingArguments(
        ...,
        fp16=True
    )
    
  • BF16(如果硬件支持,如 A100):

    training_args = TrainingArguments(
        ...,
        bf16=True
    )
    

注意:同一时间你只能启用一种精度模式,比如只能启用 fp16=Truebf16=True 之一。

2.3 优势与注意点
  1. 显存占用降低:以 FP16 为例,理论上可以减少约一半的激活/梯度显存。
  2. 速度提升:现代 GPU 对半精度计算有 Tensor Cores 等硬件加速,推理和训练都能加速。
  3. 数值稳定性:
    • FP16 的动态范围较小,可能导致梯度爆炸/下溢等问题,通常需要配合 梯度缩放(Gradient Scaling) 来保持稳定。Hugging Face 的 Trainer 和 PyTorch AMP 会自动处理。
    • BF16 对数值范围更友好,一般更稳定一些。如果硬件支持,BF16 通常优于 FP16。

3. 激活检查点(Activation Checkpointing)
3.1 原理

在前向传播时,模型会生成中间激活值,这些激活在反向传播时需要用来计算梯度。激活检查点技术的核心思想是:在前向传播中,不保存所有中间激活(以节省显存),而是在反向传播时重新计算一部分激活(以增加一些计算量)。

  • 内存与计算间的权衡:牺牲额外的计算时间来换取显存的节省。
3.2 在 Hugging Face 中的使用

transformers 为例,如果模型支持激活检查点,一般可以用:

model.gradient_checkpointing_enable()

其中 model 通常是 AutoModel 或者类似基于 transformers 的自定义模型。在开启后,对支持该功能的某些层(如 GPT-2、BERT 等 Transformer 层),会把它们的前向计算拆分成多个段,并在反向时重新前向计算来获取激活。

3.3 注意事项
  1. 训练速度:激活检查点会增加额外的前向计算,训练速度会变慢,一般可达 20% ~ 30% 的速度损失。
  2. 层数越多,效果越明显:对于深层模型,激活数量大,通过 checkpointing 可以显著减少内存占用;浅层模型则收益有限。
  3. 兼容性:大多数 Transformer 模型默认都支持该特性,但需要确认你所使用的模型类型中是否实现了 gradient checkpointing。

4. 综合对比与搭配使用
  1. 梯度累积
    • 主要目标:模拟更大 batch size,提升收敛表现或满足大 batch 需求;
    • 内存/显存节省:可以在一个较小的 batch 大小下实现等效大 batch 的效果,不会额外节省激活占用(因为每次 forward 依然用同样的计算图),但你无需在一次forward就放下一整个大batch;
    • 影响训练速度:没有直接加速或减速,只是分多次迭代同一个真实 batch,总体计算量不变;但确实提升了内存使用效率。
  2. 混合精度
    • 主要目标:减少模型参数、激活和梯度的存储占用,并利用硬件加速让训练更快
    • 常用:建议总是尝试使用混合精度(尤其是有 Tensor Core 的新 GPU),通常可以显著减小内存/显存,提升速度。
  3. 激活检查点
    • 主要目标:在前向时不保存全部激活,在反向时重算,显著减少激活占用;
    • 适用场景:当模型极深且显存不足,而训练又必须使用较大 batch size 或较长序列长度时;
    • 带来的折中:多次前向计算导致训练速度变慢
4.1 同时使用

这三种技术可以协同使用(也是大模型训练中常见的组合):

  • 梯度累积 + 混合精度:非常常见,对显存并不充足的环境很有效;
  • 混合精度 + 激活检查点:在超大模型微调时也很常见;
  • 梯度累积 + 激活检查点:在很极端的显存情况下,需要高效大 batch + 深层模型时必然要用到。
4.2 实践中的建议
  • 先试混合精度:省内存、提速度,几乎没有特别的坏处;
  • 再用梯度累积:如果还需要更大 batch size 或显存仍不足;
  • 最后考虑激活检查点:只在训练速度可以接受的情况下开启,以释放更多显存做更深/更大序列的训练。

数据格式

指令式微调(Instruction Tuning)

代码

数据格式化
def process_func(example):
    MAX_LENGTH = 384    # Llama分词器会将一个中文字切分为多个token,因此需要放开一些最大长度,保证数据的完整性
    # 输入id序列、注意力掩码和标签(训练时用来计算损失)
    input_ids, attention_mask, labels = [], [], []
    # 构造'指令'部分(prompt)的token编码
    instruction = tokenizer(f"<|im_start|>system\n现在你要扮演皇帝身边的女人--甄嬛<|im_end|>\n<|im_start|>user\n{example['instruction'] + example['input']}<|im_end|>\n<|im_start|>assistant\n", add_special_tokens=False)  # add_special_tokens 不在开头加 special_tokens,因为这些特殊token已经在字符串中手动指定了
    # 对期望回答进行编码,同样不添加特殊token
    response = tokenizer(f"{example['out
### 如何对 Unsloth 模型进行微调 对于希望利用 Unsloth 对大型语言模型进行微调的用户来说,可以通过以下方式来操作: #### 准备环境 Unsloth 提供了预配置好的 Notebook 环境,允许用户无需复杂设置即可快速上手。这些 Notebook 已经集成了必要的依赖库以及预训练模型,可以直接用于实验和开发工作[^1]。 #### 数据准备 为了使微调更加贴合特定应用场景,在开始之前需要准备好相应的训练数据集。这通常意味着收集并整理一批具有代表性的样本,确保它们能够充分反映目标领域内的特征。当涉及到具体的数据处理逻辑时,则可以根据实际需求自定义读取、清洗及转换函数[^2]。 #### 修改超参数与配置文件 在启动微调任务前,建议先仔细阅读官方文档中的指导说明,并根据个人情况适当调整一些重要的超参数选项(比如学习率、批次大小等)。此外,还可以编辑 `config.yaml` 文件来自定义更多高级特性,如量化级别(4-bit 或者 16-bit)、优化器种类以及其他影响最终效果的因素[^3]。 #### 执行微调过程 一旦完成了上述准备工作之后,就可以正式开启微调环节了。此时只需按照提示运行指定单元格即可自动完成整个流程——从加载基础权重直至保存更新后的版本至云端存储空间(例如 Google Drive),期间所有步骤均被封装好以便于理解和管理。 ```python from unsloth import Trainer, load_dataset # 加载本地或者远程的数据集 dataset = load_dataset('path/to/your/data') trainer = Trainer( model_name='llama', # 可选其他支持的模型名称 dataset=dataset, output_dir='./results', ) # 启动微调 trainer.finetune() ``` #### 验证成果 最后一步便是评估经过改进后的模型表现如何。为此可选用一部分预留出来的测试样本来做预测对比分析,进而得出结论关于新旧两版之间的差异程度及其优劣之处。如果满意的话便能考虑将其部署上线投入使用;反之则继续迭代优化直到达到预期标准为止。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值