2、全量微调基础:什么是全量微调?基本原理与数学直觉。

好,咱们进入正题。

上一章我们聊了 LoRA 和全量微调的整体对比,算是打了个底。这一章,我打算把全量微调这个「老大哥」彻底拆开,看看它到底是怎么工作的。

说白了,全量微调就是最直接、最暴力的模型适配方式。你有一个预训练好的大模型,比如 LLaMA 或者 GPT,它已经读完了全网的文本,学会了通用的语言规律。但你想让它变成你的专属客服,或者一个法律顾问,怎么办?

嗯,那就把模型所有的参数都拿出来,用你的业务数据再训练一遍。这就是全量微调。

2.1 核心思想:从「通才」到「专才」

我个人习惯把预训练模型想象成一个「通才大学生」。他学过数学、物理、文学、历史,什么都知道一点,但什么都不精。

全量微调,就是把他送进一个「专业研究院」,只让他看某一领域的书,做某一领域的题。比如,你想让他成为法律专家,那就给他看十万份法律文书、判决书。在这个过程中,他大脑里所有的「神经元连接」(也就是模型的权重)都会根据这些新知识进行调整。

你想想看,这个过程其实很符合直觉:

  • 起点:一个已经具备通用语言能力的模型。
  • 过程:用特定领域的数据,更新所有参数。
  • 终点:一个在特定任务上表现优异的专用模型。

一句话总结:全量微调 = 在预训练模型的基础上,用新数据对整个网络进行「全身按摩」,让每个参数都适应新任务。

2.2 数学直觉:梯度下降的「全面战争」

聊完了概念,咱们得看看数学上是怎么玩的。别怕,我不打算堆公式,咱们只讲直觉。

假设我们的模型参数总量是 Θ(Theta),也就是一个巨大的向量,里面包含了所有层的权重和偏置。对于 LLaMA-7B 来说,这个 Θ 大概有 70 亿个数字。

全量微调的数学目标很简单:

min L(Θ; D_target)

翻译成人话就是:找到一组参数 Θ,使得模型在目标数据集 D_target 上的损失函数 L 最小。

怎么找?靠梯度下降。每次迭代,我们计算损失函数对每一个参数的梯度:

Θ_new = Θ_old - η * ∇L(Θ_old)

这里的 ∇L(Θ_old) 就是梯度向量,它和 Θ 的维度一样大,也是 70 亿个数字。每个数字都告诉模型:「这个参数应该往哪个方向调,调多少,才能让损失降得更低。」

我的经验: 我在项目中第一次做全量微调时,看到显存占用直接飙升到 80GB,心里咯噔一下。因为你要同时存储模型参数、优化器状态(Adam 需要动量和方差)、以及梯度,这三者加起来,显存需求大约是模型参数量的 16-20 倍。7B 的模型,全量微调至少需要 140GB 显存,这可不是开玩笑的。

说白了,全量微调就是一场「全面战争」。你的优化器(比如 AdamW)要管理每一个参数的更新历史,你的显存要装下每一个参数的梯度。这就像你要同时指挥 70 亿个士兵,每个士兵都要单独下达指令。

2.3 全量微调的「三驾马车」:参数、梯度、优化器状态

为了让你更直观地理解为什么全量微调这么「吃」资源,我画了一张图,展示了训练过程中显存里到底放了些什么。

全量微调显存占用示意图(以 7B 模型为例) 模型参数 ~ 14 GB (FP16) 梯度 ~ 14 GB (FP16) 优化器状态 (Adam) ~ 28 GB (FP32) 合计显存需求 ≈ 56 GB (实际还需加上激活值、中间变量等,通常需要 80-140 GB) 注:FP16 半精度训练可节省一半显存,但优化器状态通常需 FP32 精度。 这也是为什么全量微调 7B 模型,至少需要 4 张 A100 (80GB) 的原因。

看到没?模型参数只占了一小部分。真正吃显存的大头是优化器状态。Adam 优化器需要为每个参数保存两份额外的状态(动量和方差),而且通常用 FP32 精度,这就直接干掉了 28GB。

我曾经踩过的坑: 有一次我试图用单张 A100 (40GB) 全量微调一个 6.7B 的模型。我算了算,参数 13GB,梯度 13GB,优化器 26GB,加起来 52GB,超了。但我心想:「没事,我用梯度检查点(Gradient Checkpointing)省点激活值显存。」结果跑起来直接 OOM。后来我才意识到,激活值在训练过程中也是显存大户,尤其是长序列训练时。所以,全量微调前,一定要先算好显存账

2.4 全量微调的典型流程

好了,理论讲完了,咱们看看实际操作中,全量微调到底长什么样。我习惯把它分成四步:

  1. 加载预训练模型:用 Hugging Face 的 from_pretrained() 把模型权重拉下来,加载到 GPU 上。
  2. 准备数据集:把你的业务数据(比如客服对话、法律文书)整理成模型能吃的格式。通常是 {"input": "...", "output": "..."} 这样的键值对。
  3. 配置训练参数:设置学习率、batch size、epoch 数、优化器类型等。这里有个小技巧——全量微调的学习率通常比预训练小很多,一般在 1e-5 到 5e-5 之间。
  4. 开始训练:跑起来,等。等多久?7B 模型在 8 张 A100 上微调 1 个 epoch,大概需要几小时到一天不等,取决于数据量。

下面是一个极简的代码示例,让你感受一下全量微调的样子:

from transformers import AutoModelForCausalLM, AutoTokenizer, Trainer, TrainingArguments

# 1. 加载模型和分词器
model = AutoModelForCausalLM.from_pretrained("meta-llama/Llama-2-7b-hf")
tokenizer = AutoTokenizer.from_pretrained("meta-llama/Llama-2-7b-hf")

# 2. 准备数据集(假设你已经有了)
train_dataset = YourDataset(...)

# 3. 配置训练参数
training_args = TrainingArguments(
    output_dir="./my_finetuned_model",
    per_device_train_batch_size=1,      # 全量微调 batch size 通常很小
    gradient_accumulation_steps=8,      # 梯度累积来模拟更大的 batch
    learning_rate=2e-5,                 # 学习率要小
    num_train_epochs=3,
    fp16=True,                          # 混合精度训练,省显存
    save_steps=500,
    logging_steps=100,
)

# 4. 开始训练
trainer = Trainer(
    model=model,
    args=training_args,
    train_dataset=train_dataset,
)

trainer.train()

我个人的建议: 如果你刚接触全量微调,别一上来就搞 7B、13B 的模型。先用 1B 左右的模型跑通流程,感受一下显存占用和训练时间。等熟悉了,再上大模型。否则,一个 OOM 错误就能让你怀疑人生。

2.5 全量微调的优缺点(快速一览)

为了让你心里有个谱,我把全量微调的优缺点列了个表。这样你以后选方案时,可以快速对照:

维度 说明
优点
  • 效果上限高:所有参数都参与调整,理论上能达到最好的任务适配效果。
  • 无信息损失:不像 LoRA 那样有低秩近似,全量微调保留了模型的全部表达能力。
  • 简单直接:不需要设计额外的适配器结构,代码实现最朴素。
缺点
  • 显存需求巨大:7B 模型至少需要 80GB+ 显存,普通玩家玩不起。
  • 训练时间长:更新全部参数,计算量比 LoRA 大得多。
  • 容易过拟合:如果数据量不够大,全量微调很容易把模型「带偏」,忘记预训练学到的通用知识。
  • 部署成本高:每个微调任务都要保存一份完整的模型权重(14GB+),不像 LoRA 只保存几 MB 的适配器。

嗯,看到这里,你应该对全量微调有了一个比较立体的认识。它就像一把「重剑」,威力巨大,但也不是谁都能挥得动。下一章,我们会聊聊全量微调在实际项目中的「坑」和「避坑指南」,到时候我会分享一些我亲身经历的翻车案例。


专注资料整理