【大模型】大模型微调(上)

一、概念与背景

微调(Fine-tuning)是一种迁移学习的方法,通过在已有的预训练模型基础上,利用目标任务的少量标注数据对模型进行二次训练,使其更好地适应特定任务的需求。预训练阶段模型通常使用大规模通用语料(如维基百科、新闻语料)进行无监督或自监督训练,学习通用的语言表示;微调阶段则使用特定任务数据进行有监督学习,实现从通用到专用的知识迁移。

  • 预训练(Pre-training): 在大规模无标签语料上训练,目标往往是语言建模(LM)、掩码语言建模(MLM)或序列到序列的自编码任务。
  • 微调(Fine-tuning): 在特定任务(如文本分类、序列标注、文本生成、问答等)上,用标注数据对预训练模型的全部或部分参数做进一步训练。

二、微调的典型流程

  1. 选择预训练模型

    • 根据任务需求与计算资源选择:BERT、RoBERTa、GPT、T5、BART 等。
  2. 准备任务数据

    • 构建或获取标注数据集,划分训练集/验证集/测试集。
    • 数据格式示例(文本分类):
      {"text": "今天天气很好!", "label": 1}
      
  3. 搭建模型架构

    • 加载预训练权重。
    • 在顶层添加任务特定的输出层(classification head、CRF 层、decoder 等)。
  4. 设置训练配置

    • 超参数:学习率(一般 2e-55e-5)、批量大小(832)、训练轮数(3~10)。
    • 优化器:AdamW。
    • 学习率调度:线性衰减、Warmup。
  5. 训练与验证

    • 在训练集上迭代更新可训练参数(全模型或部分)。
    • 每轮或固定步数在验证集上评估,监控 Loss、Accuracy、F1 等指标。
  6. 模型保存与部署

    • 保存最优权重(根据验证集指标)。
    • 导出模型:TorchScript、ONNX 或 HuggingFace Hub。

三、微调的策略与技巧

  1. 冷启动 vs. 热启动

    • 冷启动(Cold Start):从头训练,仅利用预训练模型初始化。
    • 热启动(Warm Start):在已有微调检查点基础上继续调整。
  2. 冻结策略

    • 冻结底层参数,仅微调顶层(task-specific head)。
    • 分层解冻(Layer-wise Unfreezing):先训练顶层,然后逐步解冻更底层。
  3. 学习率微调

    • 差异化学习率:给预训练层和新加层设置不同的学习率,新层可适当高一点。
  4. 正则化与防过拟合

    • Dropout、权重衰减(weight decay)。
    • 增加数据增强(EDA、回译增强)。
  5. 混合精度训练(AMP)

    • 使用 16-bit 混合精度加速训练、减少显存占用。

四、示例:使用 HuggingFace Transformers 微调 BERT

from transformers import BertTokenizer, BertForSequenceClassification
from transformers import Trainer, TrainingArguments
from datasets import load_dataset

# 1. 加载数据集
dataset = load_dataset('json', data_files={'train':'train.json','valid':'valid.json'})
# 2. 加载分词器与模型
tokenizer = BertTokenizer.from_pretrained('bert-base-chinese')
model = BertForSequenceClassification.from_pretrained('bert-base-chinese', num_labels=2)

# 3. 分词处理
def preprocess(example):
    tokens = tokenizer(example['text'], truncation=True, padding='max_length', max_length=128)
    tokens['labels'] = example['label']
    return tokens

dataset = dataset.map(preprocess, batched=True)

# 4. 训练参数
training_args = TrainingArguments(
    output_dir='./checkpoints',
    evaluation_strategy='steps',
    eval_steps=500,
    save_steps=1000,
    learning_rate=3e-5,
    per_device_train_batch_size=16,
    per_device_eval_batch_size=32,
    num_train_epochs=3,
    weight_decay=0.01,
    load_best_model_at_end=True,
)

# 5. Trainer 训练
trainer = Trainer(
    model=model,
    args=training_args,
    train_dataset=dataset['train'],
    eval_dataset=dataset['valid'],
)
trainer.train()

五、常见挑战与应对

  • 数据量有限:可使用数据增强、交叉验证、PEFT 等。
  • 过拟合风险:早停(early-stopping)、正则化、冻结参数。
  • 不匹配的预训练域:可尝试继续在中间语料上做语言建模再微调。
  • 计算资源受限:使用混合精度、梯度累积、PEFT、分布式训练。

六、小结

微调是连接大规模预训练与实际下游任务的关键环节,通过合理的策略与技巧,能够在有限标注数据和资源下,快速获得高性能模型。掌握微调的流程与最佳实践,将大幅提升 NLP 应用的开发效率与模型质量。

七、疑问解答

全参数微调,究竟需要多少显存?

在做 全参数微调(full fine-tuning)时,显存(GPU memory)主要被以下几部分“瓜分”:

  1. 模型权重及优化器状态

    • 假设模型有 P P P个参数,每个参数占用 4 4 4字节(32 bit),那么仅存储一份权重就要 4 P 4P 4P字节。
    • 对于典型的 A d a m / A d a m W Adam/AdamW Adam/AdamW优化器,还会为每个权重维护两个动量矩阵(momentum 和 variance),共计再额外 2 × 4 P 2 \times 4P 2×4P字节。
    • 因此,参数+优化器状态一共占大约
      4 P    +    2 × 4 P    =    12 P 字节 4P \;+\; 2\times4P \;=\; 12P\quad\text{字节} 4P+2×4P=12P字节
      如果你用的是 A d a m Adam Adam之外的优化器,系数会有所不同,但大致都会是「权重本身」的 2 – 4 2–4 2–4倍。
  2. 反向传播中间激活(activations)

    • 每一层前向计算产生的激活值,需要在反向传播时保存以便计算梯度。
    • 激活的内存开销取决于: b a t c h s i z e × s e q u e n c e l e n g t h × batch size \times sequence length \times batchsize×sequencelength×激活维度。
    • 简单估算:一个大小为 B B B(batch size)、序列长度 L L L、hidden size H H H的 Transformer 层,激活张量大约是 B × L × H B\times L\times H B×L×H个浮点数,每个 4 4 4字节。
  3. 梯度缓存(gradient buffers)

    • 一般情况下,梯度会占跟权重一样的大小,即再加 4 P 4P 4P字节。
  4. 其他开销

    • 比如显存碎片、CUDA kernel 临时缓存、多卡通信缓存(如果做分布式)等,通常预留 10–20%。

总显存需求大致为:

4 P ⏟ 权重    +    2 × 4 P ⏟ Adam 动量 (m, v)    +    4 P ⏟ 梯度    +    α   B   L   H × 4 ⏟ 激活    +    overhead \underbrace{4P}_{\text{权重}} \;+\; \underbrace{2\times4P}_{\substack{\text{Adam 动量}\\\text{(m, v)}}} \;+\; \underbrace{4P}_{\text{梯度}} \;+\; \underbrace{\alpha\,B\,L\,H\times4}_{\text{激活}} \;+\; \text{overhead} 权重 4P+Adam 动量(m, v) 2×4P+梯度 4P+激活 αBLH×4+overhead

其中 α \alpha α是层数和计算图深度带来的放大系数(一般介于 1 – 2 1–2 1–2之间)。
合并权重/优化器/梯度,一共约 12 P 12P 12P字节(大约 3 3 3倍于纯权重)。

为什么SFT之后感觉LLM傻了?

“SFT 之后感觉模型变傻”并不是模型本身性能下降,而是微调策略、数据分布、训练目标等多重因素造成的“能力偏移”与“遗忘”效应。通过合理的微调设计、正则化手段和后续强化学习,可以在保留预训练能力的同时,让模型更好地执行指令。

领域模型Continue PreTrain ,如何让模型在预训练过程中就学习到更多的知识?

  1. 多任务学习:在预训练过程中,可以引入多个任务,使得模型能够学习到更多的知识。这些任务可以是领域相关的任务,也可以是通用的语言理解任务。通过同时训练多个任务,模型可以学习到更多的语言规律和知识。
  2. 多领域数据:收集来自不同领域的数据,包括目标领域和其他相关领域的数据。将这些数据混合在一起进行预训练,可以使得模型在不同领域的知识都得到学习和融合。
  3. 大规模数据:使用更大规模的数据进行预训练,可以让模型接触到更多的语言和知识。可以从互联网上爬取大量的文本数据,或者利用公开的语料库进行预训练。
  4. 数据增强:在预训练过程中,可以采用数据增强的技术,如随机遮挡、词替换、句子重组等,来生成更多的训练样本。这样可以增加模型的训练数据量,使其能够学习到更多的知识和语言规律。
  5. 自监督学习:引入自监督学习的方法,通过设计一些自动生成的标签或任务,让模型在无监督的情况下进行预训练。例如,可以设计一个掩码语言模型任务,让模型预测被掩码的词语。这样可以使模型在预训练过程中学习到更多的语言知识。

预训练和微调哪个阶段注入知识的?

预训练阶段,模型通过大规模的未标注数据进行训练,从中学习语言的统计特征、语义知识和语言规律。预训练的目的是让模型建立起对语言的基本理解和概念,并学习到一般性的语言表示。这种预训练过程注入了通用的语言知识,并可以迁移到各种下游任务中

微调阶段,预训练的模型通过在特定任务上使用带标注的数据进行微调,以适应具体任务的要求。在微调过程中,模型通过接触任务特定的数据和标签,进一步调整和优化模型的参数,使其更好地适应任务的特定特征和要求。微调阶段注入的是与任务相关的知识和信息。

综上所述,预训练阶段主要注入通用的语言知识和表示能力,而微调阶段则注入与任务相关的知识和特定要求。预训练阶段使得模型具备了一定的语言理解能力,而微调阶段则将这种能力与具体任务相结合,使模型在任务上表现更好。预训练和微调两个阶段的结合,可以有效地提高模型的性能和泛化能力。

预训练和SFT操作有什么不同?

大语言模型的预训练和有监督微调(Supervised Fine-Tuning)是两个不同的操作,它们在目标、数据和训练方式等方面存在一些区别。
目标

  • 预训练的目标是通过无监督学习从大规模的文本语料库中学习语言模型的表示能力和语言知识。预训练的目标通常是通过自我预测任务,例如掩码语言模型(Masked Language Model,MLM)或下一句预测(Next Sentence Prediction,NSP)等,来训练模型。
  • 有监督微调的目标是在特定的任务上进行训练,例如文本分类、命名实体识别等。在有监督微调中,模型会利用预训练阶段学到的语言表示和知识,通过有监督的方式调整模型参数,以适应特定任务的要求。
    数据
  • 在预训练阶段,大语言模型通常使用大规模的无标签文本数据进行训练,例如维基百科、网页文本等。这些数据没有特定的标签或任务信息,模型通过自我预测任务来学习语言模型。
  • 在有监督微调中,模型需要使用带有标签的任务相关数据进行训练。这些数据通常是人工标注的,包含了输入文本和对应的标签或目标。模型通过这些标签来进行有监督学习,调整参数以适应特定任务。
    训练方式
  • 预训练阶段通常使用无监督的方式进行训练,模型通过最大化预训练任务的目标函数来学习语言模型的表示能力。
  • 有监督微调阶段则使用有监督的方式进行训练,模型通过最小化损失函数来学习任务相关的特征和模式。在微调阶段,通常会使用预训练模型的参数作为初始参数,并在任务相关的数据上进行训练。
    总的来说,预训练和有监督微调是大语言模型训练的两个阶段,目标、数据和训练方式等方面存在差异。预训练阶段通过无监督学习从大规模文本数据中学习语言模型,而有监督微调阶段则在特定任务上使用带有标签的数据进行有监督学习,以适应任务要求。

样本量规模增大,训练出现OOM报错如何解决?

当在大语言模型训练过程中,样本量规模增大导致内存不足的情况出现时,可以考虑以下几种解决方案:

  1. 减少批量大小(Batch Size):将批量大小减小可以减少每个训练步骤中所需的内存量。较小的批量大小可能会导致训练过程中的梯度估计不稳定,但可以通过增加训练步骤的数量来弥补这一问题。
  2. 分布式训练:使用多台机器或多个GPU进行分布式训练可以将训练负载分散到多个设备上,从而减少单个设备上的内存需求。通过分布式训练,可以将模型参数和梯度在多个设备之间进行同步和更新。
  3. 内存优化技术:使用一些内存优化技术可以减少模型训练过程中的内存占用。例如,使用混合精度训练(Mixed Precision Training)可以减少模型参数的内存占用;使用梯度累积(Gradient Accumulation)**可以减少每个训练步骤中的内存需求。
  4. 减少模型规模:如果内存问题仍然存在,可以考虑减少模型的规模,例如减少模型的层数、隐藏单元的数量等。虽然这可能会导致模型性能的一定损失,但可以在一定程度上减少内存需求。
  5. 增加硬件资源:如果条件允许,可以考虑增加硬件资源,例如增加内存容量或使用更高内存的设备。这样可以提供更多的内存空间来容纳更大规模的训练数据。
  6. 数据处理和加载优化:优化数据处理和加载过程可以减少训练过程中的内存占用。例如,可以使用数据流水线技术来并行加载和处理数据,减少内存中同时存在的数据量。

综上所述,当在大语言模型训练中遇到内存不足的问题时,可以通过减小批量大小、分布式训练、内存优化技术、减少模型规模、增加硬件资源或优化数据处理等方式来解决。具体的解决方案需要根据具体情况进行选择和调整。

你可能感兴趣的:(大模型实战,深度学习,机器学习,人工智能)