点击下方卡片,关注“人工智能陈小白”
视觉/大模型/图像重磅干货,第一时间送达!
在了解 LoRA 之前,我们先回顾一下传统的模型微调方法:
| 全参数微调 | |||
| LoRA 微调 |
LoRA(Low-Rank Adaptation,低秩适配) 的核心思想是:在不改变原始模型参数的情况下,通过添加少量可训练的低秩矩阵来微调模型。
原始模型前向传播: h = W × xLoRA 改进后: h = W × x + (ΔW) × x h = W × x + BA × x其中:- W:原始模型的预训练权重(冻结,不更新)- B、A:新增的可训练低秩矩阵(r × d 和 d × r)- r:秩(Rank),通常设为 8、16、32✅ 参数效率高:只需训练约 0.1%-1% 的参数✅ 显存需求低:4bit 量化 + LoRA 可在消费级 GPU 上训练✅ 部署便捷:只需保存增量权重(几十MB),无需保存整个模型✅ 效果出色:在多项任务上可达到接近全参数微调的效果
本项目使用指令微调(Instruction Tuning) 方式,数据格式为三元组:
{"instruction":"基于《三国演义》文档回答问题,必须标注信息来源(格式:文件名+位置),答案需严格匹配文档内容,不添加外部知识","input":"桃园三结义的三位人物是谁?","output":"桃园三结义的三位人物分别是刘备、关羽和张飞。三人在桃园中祭告天地,结为异姓兄弟,约定同心协力共图大业。信息来源:《三国演义》.txt第8行、《三国演义》.docx第5段"}三个字段的含义:
instruction | ||
input | ||
output |
本项目的训练数据基于中国古典名著:
defload_triple_data(data_path):"""加载三元组训练数据"""ifnot os.path.exists(data_path):raise FileNotFoundError(f"三元组数据文件不存在:{data_path}")withopen(data_path, 'r', encoding='utf-8') as f: data = json.load(f)# 验证数据格式 required_keys = ["instruction", "input", "output"]for i, item inenumerate(data):ifnotall(key in item for key in required_keys):raise ValueError(f"第{i}条数据格式错误,需包含{required_keys}")print(f"成功加载三元组数据,共{len(data)}条样本")return data
将三元组数据转换为模型可训练的指令格式:
defformat_train_text(example):""" 将三元组转换为模型训练格式(指令微调常用格式) 格式:"### 指令:{instruction}\n### 输入:{input}\n### 输出:{output}" """returnf"""### 指令:{example['instruction']}### 输入:{example['input']}### 输出:{example['output']}"""转换效果:
原始数据:
{"instruction":"基于《三国演义》文档回答问题...","input":"桃园三结义的三位人物是谁?","output":"桃园三结义的三位人物分别是刘备、关羽和张飞..."}转换后:

defload_base_model(model_path):"""加载基础模型(DeepSeek-7B-base),启用4位量化节省内存"""# 4位量化配置 bnb_config = BitsAndBytesConfig( load_in_4bit=True, # 开启 4bit 量化 bnb_4bit_use_double_quant=True, # 双重量化,进一步压缩 bnb_4bit_quant_type="nf4", # 使用 NF4 量化类型 bnb_4bit_compute_dtype=torch.bfloat16 # 计算时使用 bf16 )# 加载分词器 tokenizer = AutoTokenizer.from_pretrained(model_path, use_fast=False) tokenizer.pad_token = tokenizer.eos_token # 设置 pad_token# 加载模型(4bit 量化) model = AutoModelForCausalLM.from_pretrained( model_path, quantization_config=bnb_config, device_map="auto", # 自动分配设备 trust_remote_code=True )# 准备模型用于量化训练 model = prepare_model_for_kbit_training(model)return model, tokenizer关键参数说明:
load_in_4bit=True | ||
double_quant=True | ||
device_map="auto" | ||
prepare_model_for_kbit_training |
defsetup_lora(model, r=8, lora_alpha=32, lora_dropout=0.05):"""配置LoRA参数并应用到模型"""# 找到模型中所有线性层 modules = find_all_linear_names(model)# LoRA 配置 lora_config = LoraConfig( r=r, # 秩(Rank),控制低秩矩阵维度 lora_alpha=lora_alpha, # LoRA 缩放参数 target_modules=modules, # 要应用 LoRA 的模块 lora_dropout=lora_dropout, # Dropout 比例,防止过拟合 bias="none", # 不训练 bias task_type="CAUSAL_LM", # 任务类型:因果语言模型 )# 应用 LoRA 到模型 model = get_peft_model(model, lora_config) model.print_trainable_parameters() # 打印可训练参数比例return modelLoRA 参数详解:
r | ||
lora_alpha | ||
target_modules | ||
lora_dropout |
典型配置对比:
deftokenize_function(example):"""将训练文本转换为模型输入格式"""# 格式化训练文本 text = format_train_text(example)# Tokenize 编码 result = tokenizer( text, truncation=True, # 截断超长文本 max_length=1024, # 最大长度 1024 token padding="max_length", # 填充到最大长度 return_tensors="pt"# 返回 PyTorch 张量 )# 处理张量维度(去除批次维) result["input_ids"] = result["input_ids"].squeeze() result["attention_mask"] = result["attention_mask"].squeeze()# 对于 Causal LM,labels 就是 input_ids result["labels"] = result["input_ids"].clone()return result# 处理数据集tokenized_dataset = dataset.map( tokenize_function, batched=False, remove_columns=dataset.column_names # 移除原始列)数据处理流程:
原始数据 (JSON) ↓ format_train_text()格式化文本 (带指令格式的字符串) ↓ tokenizer()Token IDs + Attention Mask + Labels (PyTorch Tensor) ↓ dataset.map()HuggingFace Dataset (可用于训练)training_args = TrainingArguments( output_dir="./lora_results", # 输出目录 per_device_train_batch_size=2, # 每设备批次大小 gradient_accumulation_steps=4, # 梯度累积步数 learning_rate=2e-4, # 学习率(LoRA 常用值) num_train_epochs=3, # 训练轮次 logging_steps=10, # 日志打印间隔 save_steps=50, # 模型保存间隔 warmup_ratio=0.1, # 学习率预热比例 optim="paged_adamw_8bit", # 8位优化器,节省显存 report_to="none", # 不使用外部日志 push_to_hub=False# 不推送到 Hub)关键参数说明:
per_device_train_batch_size | ||
gradient_accumulation_steps | ||
learning_rate | ||
num_train_epochs | ||
optim |
执行
python main.py

| 可训练参数比例 | 0.27% |
##8、使用微调后的模型
from peft import PeftModelfrom transformers import AutoModelForCausalLM, AutoTokenizer# 加载基础模型base_model = AutoModelForCausalLM.from_pretrained("/root/models/deepseek-llm-7b-base/deepseek-ai/deepseek-llm-7b-base", quantization_config=bnb_config, device_map="auto", trust_remote_code=True)# 加载 LoRA 权重lora_model = PeftModel.from_pretrained( base_model,"/root/autodl-fs/class-2/lora_results/lora_weights")# 合并权重(可选,用于推理)merged_model = lora_model.merge_and_unload()# 构建输入prompt = "### 指令:基于《三国演义》文档回答问题,必须标注信息来源\n### 输入:刘备在长坂坡发生了什么?\n### 输出:"# 生成inputs = tokenizer(prompt, return_tensors="pt").to("cuda")outputs = model.generate(**inputs, max_new_tokens=200)answer = tokenizer.decode(outputs[0], skip_special_tokens=True)print(answer)错误:
RuntimeError: CUDA out of memory解决方案:
per_device_train_batch_sizegradient_accumulation_steps错误:
ValueError: Couldn't instantiate the backend tokenizer解决方案:
tokenizer = AutoTokenizer.from_pretrained(model_path, use_fast=False)# 或安装 sentencepiecepip install sentencepiece错误:
json.decoder.JSONDecodeError解决方案:
])instruction, input, output 三个字段pip install torch transformers peft bitsandbytes datasets sentencepieceload_triple_data() | |
format_train_text() | |
find_all_linear_names() | |
load_base_model() | |
setup_lora() | |
tokenize_function() | |
train_lora() |
