——基于 DeepSeek-7B 的 LoRA 微调训练实战教程)
基于 DeepSeek-7B 的 LoRA 微调训练实战教程1、什么是 LoRA 微调1.1 传统微调 vs LoRA 微调在了解 LoRA 之前我们先回顾一下传统的模型微调方法方法特点显存需求训练时间全参数微调调整模型所有参数极高~28GB很长LoRA 微调只训练少量新增参数较低~6GB较短1.2 LoRA 原理详解LoRALow-Rank Adaptation低秩适配的核心思想是在不改变原始模型参数的情况下通过添加少量可训练的低秩矩阵来微调模型。原始模型前向传播 h W × x LoRA 改进后 h W × x (ΔW) × x h W × x BA × x 其中 - W原始模型的预训练权重冻结不更新 - B、A新增的可训练低秩矩阵r × d 和 d × r - r秩Rank通常设为 8、16、321.3 LoRA 的优势✅参数效率高只需训练约 0.1%-1% 的参数✅显存需求低4bit 量化 LoRA 可在消费级 GPU 上训练✅部署便捷只需保存增量权重几十MB无需保存整个模型✅效果出色在多项任务上可达到接近全参数微调的效果2、数据集准备2.1 三元组数据格式本项目使用指令微调Instruction Tuning方式数据格式为三元组{instruction:基于《三国演义》文档回答问题必须标注信息来源格式文件名位置答案需严格匹配文档内容不添加外部知识,input:桃园三结义的三位人物是谁,output:桃园三结义的三位人物分别是刘备、关羽和张飞。三人在桃园中祭告天地结为异姓兄弟约定同心协力共图大业。信息来源《三国演义》.txt第8行、《三国演义》.docx第5段}三个字段的含义字段含义作用instruction任务指令告诉模型要做什么input用户输入具体的问题或请求output期望输出模型应该生成的答案2.2 数据集来源本项目的训练数据基于中国古典名著《三国演义》《水浒传》《西游记》2.3 数据加载代码defload_triple_data(data_path):加载三元组训练数据ifnotos.path.exists(data_path):raiseFileNotFoundError(f三元组数据文件不存在{data_path})withopen(data_path,r,encodingutf-8)asf:datajson.load(f)# 验证数据格式required_keys[instruction,input,output]fori,iteminenumerate(data):ifnotall(keyinitemforkeyinrequired_keys):raiseValueError(f第{i}条数据格式错误需包含{required_keys})print(f成功加载三元组数据共{len(data)}条样本)returndata3、训练流程详解3.1 整体流程图3.2 格式化训练文本将三元组数据转换为模型可训练的指令格式defformat_train_text(example): 将三元组转换为模型训练格式指令微调常用格式 格式### 指令{instruction}\n### 输入{input}\n### 输出{output} returnf### 指令{example[instruction]}### 输入{example[input]}### 输出{example[output]}转换效果原始数据{instruction:基于《三国演义》文档回答问题...,input:桃园三结义的三位人物是谁,output:桃园三结义的三位人物分别是刘备、关羽和张飞...}转换后4、核心代码讲解4.1 基础模型加载defload_base_model(model_path):加载基础模型DeepSeek-7B-base启用4位量化节省内存# 4位量化配置bnb_configBitsAndBytesConfig(load_in_4bitTrue,# 开启 4bit 量化bnb_4bit_use_double_quantTrue,# 双重量化进一步压缩bnb_4bit_quant_typenf4,# 使用 NF4 量化类型bnb_4bit_compute_dtypetorch.bfloat16# 计算时使用 bf16)# 加载分词器tokenizerAutoTokenizer.from_pretrained(model_path,use_fastFalse)tokenizer.pad_tokentokenizer.eos_token# 设置 pad_token# 加载模型4bit 量化modelAutoModelForCausalLM.from_pretrained(model_path,quantization_configbnb_config,device_mapauto,# 自动分配设备trust_remote_codeTrue)# 准备模型用于量化训练modelprepare_model_for_kbit_training(model)returnmodel,tokenizer关键参数说明参数作用说明load_in_4bitTrue4bit 量化将模型压缩到 4bit显存降低 75%double_quantTrue双重量化进一步压缩节省约 0.4 bit/参数device_mapauto自动设备分配自动将模型分配到 GPU/CPUprepare_model_for_kbit_training准备量化训练冻结非量化参数添加梯度支持4.2 LoRA 配置defsetup_lora(model,r8,lora_alpha32,lora_dropout0.05):配置LoRA参数并应用到模型# 找到模型中所有线性层modulesfind_all_linear_names(model)# LoRA 配置lora_configLoraConfig(rr,# 秩Rank控制低秩矩阵维度lora_alphalora_alpha,# LoRA 缩放参数target_modulesmodules,# 要应用 LoRA 的模块lora_dropoutlora_dropout,# Dropout 比例防止过拟合biasnone,# 不训练 biastask_typeCAUSAL_LM,# 任务类型因果语言模型)# 应用 LoRA 到模型modelget_peft_model(model,lora_config)model.print_trainable_parameters()# 打印可训练参数比例returnmodelLoRA 参数详解参数默认值说明r秩8LoRA 矩阵的维度越大表达能力越强但参数量也增加lora_alpha32LoRA 缩放参数通常设为 r 的 2-4 倍target_modules-要应用 LoRA 的层通常是 QKV 注意力层lora_dropout0.05LoRA 层的 Dropout 比例典型配置对比配置rlora_alpha可训练参数比例轻量级416~0.1%平衡型832~0.27%强力型1664~0.5%4.3 训练数据处理deftokenize_function(example):将训练文本转换为模型输入格式# 格式化训练文本textformat_train_text(example)# Tokenize 编码resulttokenizer(text,truncationTrue,# 截断超长文本max_length1024,# 最大长度 1024 tokenpaddingmax_length,# 填充到最大长度return_tensorspt# 返回 PyTorch 张量)# 处理张量维度去除批次维result[input_ids]result[input_ids].squeeze()result[attention_mask]result[attention_mask].squeeze()# 对于 Causal LMlabels 就是 input_idsresult[labels]result[input_ids].clone()returnresult# 处理数据集tokenized_datasetdataset.map(tokenize_function,batchedFalse,remove_columnsdataset.column_names# 移除原始列)数据处理流程原始数据 (JSON) ↓ format_train_text() 格式化文本 (带指令格式的字符串) ↓ tokenizer() Token IDs Attention Mask Labels (PyTorch Tensor) ↓ dataset.map() HuggingFace Dataset (可用于训练)4.4 训练参数配置training_argsTrainingArguments(output_dir./lora_results,# 输出目录per_device_train_batch_size2,# 每设备批次大小gradient_accumulation_steps4,# 梯度累积步数learning_rate2e-4,# 学习率LoRA 常用值num_train_epochs3,# 训练轮次logging_steps10,# 日志打印间隔save_steps50,# 模型保存间隔warmup_ratio0.1,# 学习率预热比例optimpaged_adamw_8bit,# 8位优化器节省显存report_tonone,# 不使用外部日志push_to_hubFalse# 不推送到 Hub)关键参数说明参数值说明per_device_train_batch_size2每 GPU 批次大小7B 模型建议 2-4gradient_accumulation_steps4梯度累积实际批次 2 × 4 8learning_rate2e-4LoRA 推荐学习率比全参数微调大num_train_epochs3小数据集 3-5 轮即可optimpaged_adamw_8bit8 位 AdamW优化器显存减半5、运行与结果5.1 运行命令# encodingutf-8importosimportjsonimporttorchimporttransformersfromdatasetsimportDatasetfromtransformersimport(AutoModelForCausalLM,AutoTokenizer,TrainingArguments,BitsAndBytesConfig)frompeftimportLoraConfig,get_peft_model,prepare_model_for_kbit_trainingimportbitsandbytesasbnb# 加在三元组训练数据defload_triple_data(data_path):ifnotos.path.exists(data_path):raiseFileNotFoundError(f三元组数据文件不存在{data_path}请先准备数据)withopen(data_path,r,encodingutf-8)asf:datajson.load(f)# 验证数据格式required_keys[instruction,input,output]fori,iteminenumerate(data):ifnotall(keyinitemforkeyinrequired_keys):raiseValueError(f第{i}条数据格式错误需包含{required_keys})print(f成功加载三元组数据共{len(data)}条样本)returndatadefformat_train_text(example): 将三元组转换为模型训练格式指令微调常用格式 格式### 指令{instruction}\n### 输入{input}\n### 输出{output} returnf### 指令{example[instruction]}### 输入{example[input]}### 输出{example[output]}deffind_all_linear_names(model):找到模型中所有线性层用于LoRA适配clsbnb.nn.Linear4bit lora_module_namesset()forname,moduleinmodel.named_modules():ifisinstance(module,cls):namesname.split(.)lora_module_names.add(names[0]iflen(names)1elsenames[-1])iflm_headinlora_module_names:# 排除lm_head避免影响输出lora_module_names.remove(lm_head)returnlist(lora_module_names)defload_base_model(model_path):加载基础模型DeepSeek-7B-base启用4位量化节省内存# 4位量化配置bnb_configBitsAndBytesConfig(load_in_4bitTrue,bnb_4bit_use_double_quantTrue,bnb_4bit_quant_typenf4,bnb_4bit_compute_dtypetorch.bfloat16)# 加载模型和分词器tokenizerAutoTokenizer.from_pretrained(model_path,use_fastFalse)tokenizer.pad_tokentokenizer.eos_token# 设置pad_tokenmodelAutoModelForCausalLM.from_pretrained(model_path,quantization_configbnb_config,device_mapauto,# 自动分配设备优先GPU无GPU则用CPUtrust_remote_codeTrue)# 准备模型用于量化训练modelprepare_model_for_kbit_training(model)returnmodel,tokenizerdefsetup_lora(model,r8,lora_alpha32,lora_dropout0.05):配置LoRA参数并应用到模型# 找到所有线性层modulesfind_all_linear_names(model)# LoRA配置lora_configLoraConfig(rr,# 秩控制LoRA矩阵维度越小参数越少lora_alphalora_alpha,target_modulesmodules,lora_dropoutlora_dropout,biasnone,task_typeCAUSAL_LM,)# 应用LoRA到模型modelget_peft_model(model,lora_config)model.print_trainable_parameters()# 打印可训练参数比例通常1%returnmodel# 训练lora模型deftrain_lora(model,tokenizer,dataset,output_dir./lora_results):# 处理数据集tokenize训练文本deftokenize_function(example):# 单个 example 格式{instruction: ..., input: ..., output: ...}textformat_train_text(example)resulttokenizer(text,truncationTrue,max_length1024,paddingmax_length,return_tensorspt)# 确保返回正确的格式result[input_ids]result[input_ids].squeeze()result[attention_mask]result[attention_mask].squeeze()# 对于 Causal LMlabels 就是 input_idsresult[labels]result[input_ids].clone()returnresult tokenized_datasetdataset.map(tokenize_function,batchedFalse,remove_columnsdataset.column_names# 移除原始文本列只保留tokenized数据)# 训练参数配置轻量级微调适合CPU/GPUtraining_argsTrainingArguments(output_diroutput_dir,per_device_train_batch_size2,# 批次大小gradient_accumulation_steps4,# 梯度累积模拟大批次learning_rate1e-4,# 降低学习率更稳定num_train_epochs10,# 增加训练轮次logging_steps5,# 每5步打印一次日志save_steps20,# 每20步保存一次模型warmup_steps10,# 预热步数optimpaged_adamw_8bit,# 8位优化器节省内存report_tonone,# 不使用wandb等日志工具push_to_hubFalse,# 不推送到hubsave_total_limit3,# 最多保存3个检查点)# 开始训练model.train()trainertransformers.Trainer(modelmodel,argstraining_args,train_datasettokenized_dataset)trainer.train()# 保存LoRA权重仅保存增量参数约几十MBmodel.save_pretrained(os.path.join(output_dir,lora_weights))print(fLoRA微调完成权重保存至{os.path.join(output_dir,lora_weights)})returnoutput_dir# 主函数LoRA微调全流程if__name____main__:# 配置路径BASE_MODEL_PATH/root/models/deepseek-llm-7b-base/deepseek-ai/deepseek-llm-7b-base# 基础模型路径TRIPLE_DATA_PATH/root/autodl-fs/class-2/triple_data.json# 三元组数据文件路径OUTPUT_DIR/root/autodl-fs/class-2/lora_results# 微调结果保存路径# 加载三元组数据triple_dataload_triple_data(TRIPLE_DATA_PATH)datasetDataset.from_list(triple_data)# 转换为HuggingFace Dataset格式# 加载基础模型model,tokenizerload_base_model(BASE_MODEL_PATH)# 配置LoRAmodelsetup_lora(model)# 开始微调train_lora(model,tokenizer,dataset,OUTPUT_DIR)执行python main.py5.2 运行输出5.3 训练结果统计指标数值训练样本数27 条基础模型DeepSeek-LLM-7B-base可训练参数18,739,200总参数6,929,104,896可训练参数比例0.27%训练轮次3 epochs训练时间66.73 秒最终 Loss13.17LoRA 权重大小~20 MB##8、使用微调后的模型6.1 加载 LoRA 权重frompeftimportPeftModelfromtransformersimportAutoModelForCausalLM,AutoTokenizer# 加载基础模型base_modelAutoModelForCausalLM.from_pretrained(/root/models/deepseek-llm-7b-base/deepseek-ai/deepseek-llm-7b-base,quantization_configbnb_config,device_mapauto,trust_remote_codeTrue)# 加载 LoRA 权重lora_modelPeftModel.from_pretrained(base_model,/root/autodl-fs/class-2/lora_results/lora_weights)# 合并权重可选用于推理merged_modellora_model.merge_and_unload()6.2 测试微调效果# 构建输入prompt### 指令基于《三国演义》文档回答问题必须标注信息来源\n### 输入刘备在长坂坡发生了什么\n### 输出# 生成inputstokenizer(prompt,return_tensorspt).to(cuda)outputsmodel.generate(**inputs,max_new_tokens200)answertokenizer.decode(outputs[0],skip_special_tokensTrue)print(answer)7、常见问题与解决问题 1显存不足错误RuntimeError: CUDA out of memory解决方案减小per_device_train_batch_size增大gradient_accumulation_steps启用 4bit 量化问题 2Tokenizer 报错错误ValueError: Couldnt instantiate the backend tokenizer解决方案tokenizerAutoTokenizer.from_pretrained(model_path,use_fastFalse)# 或安装 sentencepiecepip install sentencepiece问题 3数据格式错误错误json.decoder.JSONDecodeError解决方案检查 JSON 文件是否完整是否有缺失的]确保每条数据都有instruction,input,output三个字段8、总结8.1 技术要点回顾LoRA 原理通过低秩矩阵实现参数高效微调4bit 量化大幅降低显存需求消费级 GPU 可训练指令微调使用三元组数据教会模型回答格式数据处理格式化 Tokenize Labels 准备8.2 训练效果仅用 27 条样本训练 66 秒可训练参数比例仅 0.27%生成约 20MB 的 LoRA 权重文件后续可加载权重进行问答测试8.3 后续优化方向增加训练数据量建议 100 条调整 LoRA 参数r16 提升效果使用更大基础模型DeepSeek-67B添加验证集评估模型效果附录完整代码环境依赖pipinstalltorch transformers peft bitsandbytes datasets sentencepiece核心函数清单函数名功能load_triple_data()加载并验证三元组数据format_train_text()格式化训练文本find_all_linear_names()查找模型中的线性层load_base_model()加载基础模型4bit 量化setup_lora()配置并应用 LoRAtokenize_function()Tokenize 训练数据train_lora()执行训练流程