开源大模型微调技术详解:以DeepSeek开源模型为例
随着大规模预训练模型(Large Language Model, LLM)的广泛应用,如何在已有的开源大模型基础上进行高效、低成本的微调,已成为工业与学术界关注的焦点。本文将首先对大模型的微调方法做系统介绍,然后以DeepSeek开源模型为例,给出微调过程的示例代码与详细解释,帮助读者理解并掌握微调关键技术。
一、大模型微调技术概述
1. 全量微调(Full Fine-tuning)
- 定义:对预训练模型的全部参数进行训练,更新所有权重。
- 特点:
- 对硬件及数据需求高;
- 训练时间长,成本大;
- 最终模型可以完全适配目标任务,表现通常更好。
- 适用场景:数据量充足、对精度要求极高,且有足够算力的情况。
2. 参数高效微调(Parameter-Efficient Fine-tuning)
由于全量微调对资源需求过高,学术界和工业界提出了多种参数高效微调策略,让在相对有限资源下,也能充分挖掘大模型的能力。
-
Adapter
- 在模型的每个层之间插入一小段可训练的“适配器”模块,固定预训练模型原有参数,仅训练这些额外插入的层;
- 额外训练参数量通常只占总量的很小一部分,资源占用显著下降。
-
LoRA (Low-Rank Adaptation)
- 利用矩阵低秩分解的思想,仅更新变换矩阵的一小部分低秩分量;
- 无需在整个网络上插入适配器,代码改动小,训练速度快;
- 已被广泛应用于ChatGPT等大型模型的指令微调(Instruction Tuning)。
-
Prefix Tuning / P-Tuning
- 在输入Embedding阶段,为模型加上可训练的“前缀向量”;
- 无需改变模型结构,仅在输入端增加少量可学习参数即可完成迁移到下游任务。
-
Prompt Tuning / Prompt Engineering
- 不一定需要改动模型参数;
- 通过在输入中构造合理的提示(Prompt),让模型针对不同任务给出更符合需求的输出;
- 虽然简单但依赖经验,迭代试错成本高。
3. 指令微调(Instruction Tuning)
- 目的:让模型学会遵循自然语言形式的“指令”进行回答,对话式模型或多任务场景尤为有效。
- 方法:用成千上万条带有指令-答案对的数据进行训练或微调,模型逐渐学会理解并执行各种自然语言指令。
4. 人类反馈强化学习(RLHF)
- 原理:结合人工评估数据(人类对模型输出的打分),通过强化学习调整模型参数,使得输出更贴近人类偏好;
- 应用:ChatGPT、GPT-4等大模型大多使用了RLHF来显著提升对话质量与安全性。
二、DeepSeek开源模型微调案例
DeepSeek是近年快速崛起的开源AI平台,提供了多模态(CV + NLP)大模型及工具链,其NLP模型也具备与主流Transformer框架兼容的接口。以下将以“DeepSeek-LM”为例,演示如何使用参数高效微调技术(LoRA)在一个自然语言问答任务上完成微调。所示代码基于Hugging Face Transformers及peft库进行说明,供读者学习参考。
注意:以下示例采用的是通用的Hugging Face + PEFT工作流程,假设DeepSeek开源模型兼容该生态;实际操作中,请结合DeepSeek官方提供的文档或训练脚本进行调试。
1. 环境准备
# 建议使用Python 3.8+环境
pip install torch transformers datasets peft accelerate
torch
:深度学习框架(Pytorch)transformers
:Hugging Face的Transformers库datasets
:用于加载与处理数据集peft
:Parameter-Efficient Fine-Tuning库accelerate
:多GPU加速工具
2. 数据准备
此处以一个简化的指令问答任务为例。假设我们有一个JSON或CSV数据文件,其中包含指令(instruction)与期望输出(response),类似下表所示:
instruction | response |
---|---|
用一句话描述人工智能的核心 | 人工智能是通过模拟或超越人类智能来解决复杂任务的技术。 |
请总结一下区块链技术的特点 | 去中心化、不可篡改、可追溯是区块链的三个主要特点。 |
如何提高神经网络的泛化性能? | 通常通过数据增广、正则化和合适的网络结构设计来提高泛化性能。 |
可以使用datasets
库来加载本地文件,也可以直接加载现有的公开数据集。下面以本地JSON文件为例:
from datasets import load_dataset
dataset = load_dataset('json', data_files={
'train': 'train_data.json',
'validation': 'val_data.json'
})
# 打印一些样本
print(dataset['train'][0])
3. 加载DeepSeek-LM模型与Tokenizer
from transformers import AutoTokenizer, AutoModelForCausalLM
model_name_or_path = "DeepSeek/DeepSeek-LM" # 假设DeepSeek开源模型在Hugging Face上可直接下载
tokenizer = AutoTokenizer.from_pretrained(model_name_or_path, use_fast=True)
model = AutoModelForCausalLM.from_pretrained(model_name_or_path)
如果DeepSeek模型并未发布在Hugging Face仓库,需要自行下载或通过DeepSeek官方提供的方式加载,保证返回的模型结构兼容Transformer即可。
4. 使用LoRA进行参数高效微调
PEFT(Parameter-Efficient Fine-Tuning)库提供了LoRA、Prefix Tuning等多种微调方法。此处以LoRA为例,示例代码如下:
from peft import LoraConfig, TaskType, get_peft_model
# Step 1: 设置LoRA超参数
lora_config = LoraConfig(
r=8, # LoRA秩
lora_alpha=32, # LoRA缩放系数
lora_dropout=0.05, # Dropout
bias="none", # 是否使用bias
task_type=TaskType.CAUSAL_LM # 因果语言建模场景
)
# Step 2: 将LoRA配置附加到模型上
model = get_peft_model(model, lora_config)
# 检查LoRA注入的可训练参数
model.print_trainable_parameters()
此时,模型的可训练参数仅为LoRA相关的部分,大大减少了训练所需的显存与计算量。
5. 数据处理与分批(Collation)
我们需要将instruction
和response
拼接成模型的输入。对因果语言模型而言,常见做法是:
- 将“指令 + 分隔符 + 回复”当做模型的输入序列;
- 模型预测该序列每个Token的概率;
- 只计算回复部分的loss,避免对指令文字进行损失回传。
示例代码:
def format_example(example, tokenizer, max_length=128):
prompt = f"指令:{example['instruction']}\n答复:"
target = example['response']
# 将prompt与target拼接在一起
input_text = prompt + target
# 进行分词
encoding = tokenizer(
input_text,
truncation=True,
max_length=max_length,
padding="max_length"
)
# 我们需要制作一个label,只对“答复”部分计算loss
# 首先找到“答复”的起始位置
answer_start_index = len(tokenizer(prompt)['input_ids']) - 1 # -1因为末尾有个token可能是eos或\n
# label与input_ids相同,但对于指令部分要mask为-100
labels = encoding["input_ids"].copy()
for i in range(answer_start_index):
labels[i] = -100 # 指令部分不计算损失
encoding["labels"] = labels
return encoding
def preprocess_data(examples):
return format_example(examples, tokenizer)
train_dataset = dataset["train"].map(preprocess_data, batched=False)
val_dataset = dataset["validation"].map(preprocess_data, batched=False)
train_dataset.set_format(type="torch", columns=["input_ids", "attention_mask", "labels"])
val_dataset.set_format(type="torch", columns=["input_ids", "attention_mask", "labels"])
6. 训练配置与启动微调
from transformers import TrainingArguments, Trainer
training_args = TrainingArguments(
output_dir="./ds_lora_finetuned",
evaluation_strategy="steps",
save_strategy="steps",
logging_steps=50,
eval_steps=200,
save_steps=200,
num_train_epochs=3,
per_device_train_batch_size=4,
per_device_eval_batch_size=4,
learning_rate=2e-4,
warmup_steps=100,
fp16=True, # 如果GPU支持混合精度,可加速训练
report_to="none", # 不向WandB等平台报告
)
trainer = Trainer(
model=model,
args=training_args,
train_dataset=train_dataset,
eval_dataset=val_dataset
)
trainer.train()
7. 保存微调后模型
微调完成后,会在output_dir
生成一系列检查点。LoRA微调的特点是只保存LoRA权重而非整个基础模型权重,可以再结合基础模型进行推理。示例:
trainer.save_model("./ds_lora_finetuned/checkpoint-final")
# 推理时,可以加载:
# 1. 基础模型
# 2. LoRA权重
三、关键步骤与原理解释
-
LoRA原理
- 将原始模型中的权重矩阵
W
分解为W + BA
,其中B
和A
是低秩矩阵,可训练的参数仅限于B
和A
; - 在推理时,会将这部分低秩矩阵叠加回原模型权重中;
- 相比于训练
W
的全部参数,可训练参数量大幅下降。
- 将原始模型中的权重矩阵
-
为什么要单独mask指令部分?
- 在因果语言模型场景下,如果我们想要训练模型更好地生成回答,则只需对答案部分计算损失,避免模型在“复制指令”时额外增加loss。
- 这种做法也能促使模型将输入指令视为上下文提示,学习到更合理的回答策略。
-
评估与调优
- 使用
eval_dataset
可以实时观察模型在验证集上的损失变化; - 若验证集任务目标是回答质量,可进一步用BLEU、ROUGE、BERTScore等指标评测,也可人工抽样评价。
- 使用
-
扩展到其他微调方法
- 可以将LoRA替换为
Prefix Tuning
或Adapter
等模式,只需在peft
库中切换对应的task_type
和配置; - 对于对话场景,可以考虑结合指令微调(Instruction Tuning)和人类反馈强化学习(RLHF),提升模型响应的可控性与安全性。
- 可以将LoRA替换为
四、总结与展望
-
微调方式多元化
- 从全量微调到参数高效微调,再到指令微调、RLHF,各种技术路径在目标任务与资源限制不同的前提下,各有取舍。
- 结合实际需求选择合适的微调策略,可以在保障模型质量的同时,显著降低硬件与时间成本。
-
DeepSeek开源模型价值
- DeepSeek的开源策略与对边缘部署、跨模态处理的优化,使得中小型企业和个人开发者也能接入强大的NLP能力。
- 在此基础上进行LoRA等参数高效微调,对定制化业务场景尤为友好。
-
未来趋势
- 多模态融合:将LoRA或Adapter机制扩展到图像、语音等模态,提升跨模态任务的适用性。
- 训练与推理优化:硬件加速、量化(Quantization)、剪枝(Pruning)等手段将进一步减少显存占用与推理延时。
- 可持续AI:更少的能耗、更低碳的算力成为AI落地的重要衡量指标;参数高效微调切中了这一需求。
总之,通过对大模型微调技术的系统介绍和对DeepSeek开源模型微调示例的实际演示,可以看到:合适的微调方案不仅能在短期内完成对模型的快速定制,也能大幅降低开发成本。随着开源社区的日益壮大,微调技术将持续迭代,为各行各业的AI应用提供更多可能性。
【哈佛博后带小白玩转机器学习】 哔哩哔哩_bilibili
总课时超400+,时长75+小时