数据处理的工程化时间
- 数据清洗:去除噪声数据(比如乱码/重复文本),对不平衡数据进行重采样
- 高效预处理:使用 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 Facetransformers中用来配置 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 注意事项
- 有效学习率:当你使用梯度累积时,相当于增大了批次大小,需要注意保持或调整学习率。例如,如果你原先 batch size=16,现在用
gradient_accumulation_steps=4并把单次 batch size 改成4,总有效 batch size 就依然是16。这样你的学习率设置就不用大幅修改。 - 训练时间:梯度累积并不会减少计算量,对每个batch依然要做前向和反向,但它能帮助你在有限显存下进行较大的 batch 训练。
- 分布式训练配合:如果同时使用分布式训练(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=True或bf16=True之一。
2.3 优势与注意点
- 显存占用降低:以 FP16 为例,理论上可以减少约一半的激活/梯度显存。
- 速度提升:现代 GPU 对半精度计算有 Tensor Cores 等硬件加速,推理和训练都能加速。
- 数值稳定性:
- 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 注意事项
- 训练速度:激活检查点会增加额外的前向计算,训练速度会变慢,一般可达 20% ~ 30% 的速度损失。
- 层数越多,效果越明显:对于深层模型,激活数量大,通过 checkpointing 可以显著减少内存占用;浅层模型则收益有限。
- 兼容性:大多数 Transformer 模型默认都支持该特性,但需要确认你所使用的模型类型中是否实现了 gradient checkpointing。
4. 综合对比与搭配使用
- 梯度累积
- 主要目标:模拟更大 batch size,提升收敛表现或满足大 batch 需求;
- 内存/显存节省:可以在一个较小的 batch 大小下实现等效大 batch 的效果,不会额外节省激活占用(因为每次 forward 依然用同样的计算图),但你无需在一次forward就放下一整个大batch;
- 影响训练速度:没有直接加速或减速,只是分多次迭代同一个真实 batch,总体计算量不变;但确实提升了内存使用效率。
- 混合精度
- 主要目标:减少模型参数、激活和梯度的存储占用,并利用硬件加速让训练更快;
- 常用:建议总是尝试使用混合精度(尤其是有 Tensor Core 的新 GPU),通常可以显著减小内存/显存,提升速度。
- 激活检查点
- 主要目标:在前向时不保存全部激活,在反向时重算,显著减少激活占用;
- 适用场景:当模型极深且显存不足,而训练又必须使用较大 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

最低0.47元/天 解锁文章
4018

被折叠的 条评论
为什么被折叠?



