本文主要介绍了大模型时代下,如何通过 LoRA(Low-Rank Adaptation)这一参数高效微调技术,实现对大模型的轻量级定制。文章从微调的基本概念出发,详细阐述了 LoRA 的原理、优势与局限性,并结合本地原生实现(Transformers + PEFT)和百炼平台两种方式,展示了在小样本、低资源场景下的实战流程。结果表明,LoRA 能以极低的计算成本让通用大模型有效学习业务知识,显著提升其在特定任务中的表现,真正实现“让大模型懂业务”,推动 AI 从“可用”走向“好用”。
如今大模型时代,深刻重塑了很多行业的业务形态与技术架构。从智能客服到内容生成,从代码辅助到推荐系统,大模型正以前所未有的速度渗透到互联网技术的各个领域,成为驱动创新的核心引擎。其强大的泛化能力和上下文理解水平,使得许多过去需要复杂工程与规则设计的任务,如今只需一次“提示”即可完成。
然而,尽管大模型展现出惊人的通用能力,对大多数开发者和业务方而言,它仍是一个“黑盒”——我们只能调用其预训练时所掌握的知识与行为模式,难以干预其内部逻辑,也无法直接引导模型适应特定场景的表达习惯、术语体系或业务规则。在生产环境中,这种 “能力固定”的特性带来了明显的局限:模型输出可能偏离业务预期,难以保证一致性,更无法随业务演进而持续进化。
▐ 什么是微调
▐ 微调的分类
标准微调(任务微调):针对某⼀具体任务进⾏微调(如分类、问答、摘要等)。 领域微调:针对某⼀特定领域数据进⾏微调(如医疗、法律、⾦融等)。 指令微调:通过⼤量的「指令-响应」数据集(如Alpaca, Baize等)对模型进⾏微调,使其能够按照⾃然语⾔指令完成多种任务。
全参数微调(Full-parameterTuning):对所有模型参数进⾏更新。 部分参数微调(Repurposing):只更新部分参数,⽐如最后⼀层、分类头等。 参数⾼效微调(ParameterEfficientFine-Tuning, PEFT):引⼊少量额外可训练参数,冻结原模型绝⼤部分参数,如LoRA、Adapter、Prefix Tuning等,主要⽬的是⽤很少量新参数即实现任务适配。
▐ 常⻅微调⽅法
直接在⽬标数据集上继续训练所有模型参数,效果好但耗资源⼤,对硬件要求⾼,不易迁移。
LoRA(Low-Rank Adaptation):通过在部分参数上引⼊低秩矩阵,仅训练这些新增的低秩参数,⼤⼤减少训练所需参数量和显存消耗。 Adapter:在原有⽹络层之间插⼊⼩型新⽹络模块并仅训练这些模块参数。 PrefixTuning / PromptTuning:在输⼊或模型内部插⼊可训练的参数向量(前缀、提示),主⽹络参数保持不变。
▐ 什么是 LoRA
▐ 原理
原始基座模型的权重 全程保持冻结、未修改。 训练过程中,只有 LoRA 引⼊的少量新增参数(如 lora_A, lora_B)被更新。
保留原来预训练好的权重 W不动; 旁路插入一个低秩矩阵 BA,对 W 的输出做一个可学习的微小增量; 只训练 A、B 这两组很小的参数,就能让模型适配新任务。
原来若要微调 W,需要存和反向传播 d₁·d₂ 个参数; LoRA只训练 A(r × d₂)和 B(d₁ × r),参数量变成 2·r·max(d₁,d₂),当 r≪ min(d₁, d₂) 时可省 100× 乃⾄ 10 000× 的显存和梯度计算; W冻结 → 不必存动量/梯度,也不⽤进⾏前后两次 matmul⽐较差分,⼤幅降低显存与算⼒。
推理过程
模式 | 说明 | 特点 |
1. 推理时动态叠加(On-thefly) | 加载原始模型 + LoRA 权重,实时计算 W+ΔWW+ΔW | 可用,但慢 |
2. 合并后推理(MergedInference) | 将 LoRA 权重 合并到原始模型中,生成一个新模型,直接推理 | 推理更快、更稳定 |
优劣势
▐ 实战
原⽣微调
pip install --upgrade torch==2.1.0+cpu torchvision==0.16.0+cpu --index-url https://download.pytorch.org/whl/cpupip install -U transformers==4.38.2 peft==0.10.0 datasets==2.18.0 \ accelerate sentencepiece safetensors tqdm
2. 模型准备这里考虑到微调是在 mac本地运行,算力有限,所以使用了较小的 DeepSeek-R1-Distill-Qwen-1.5B作为基座模型。使用 Hugging Face或 Modelscope等下载模型到本地即可。{"instrucAon":"埋点时主要记录哪些事件?","response":"使⽤的是xx埋点框架,埋点有分为各种事件类型,主要的有曝光事件和点击事件,可以根据曝光和点击事件来统计数据,如点击率等"}{"instrucAon":"xxxxxxxxxxxxxxxxxx?","response":"xxxxxxxxxxxxxxx"}import jsonfrom datasets import Datasetfrom transformers import AutoTokenizer# 正确读取jsonlwith open("lora_seckill_qa.jsonl", "r", encoding="utf-8") as f:raw_data = [json.loads(line) for line in f if line.strip()]# 如果是单轮格式可直接用# dataset = Dataset.from_list(raw_data)# 若是conversation 格式(如 [{"conversation":[...]}]),需展开def conversation_to_list(item):out = []for turn in item["conversation"]:instr = turn.get("system", "") + "\n" + turn["input"] ifturn.get("system") else turn["input"]out.append({"instruction": instr.strip(),"response": turn["output"].strip()})return out# 如果你的raw_data 已经是单轮格式就跳过这一段all_samples = []if "conversation" in raw_data[0]:for d in raw_data:all_samples.extend(conversation_to_list(d))dataset = Dataset.from_list(all_samples)else:dataset = Dataset.from_list(raw_data)# 保存分词器tokenizer = AutoTokenizer.from_pretrained("/Users/shawn/Documents/AI-dev/models/deepseek/deepseek-ai/DeepSeek-R1-Distill-Qwen-7B",trust_remote_code=True,local_files_only=True)tokenizer.save_pretrained("./tokenizer")# 保存处理后的数据集dataset.save_to_disk("./processed_dataset_ms")print("预处理完成,已保存!")
import torchfrom datasets import load_from_diskfrom transformers import AutoTokenizer, AutoModelForCausalLM,TrainingArguments, Trainer, default_data_collatorfrom peft import get_peft_model, LoraConfigimport random# 1. 加载数据集dataset = load_from_disk("./processed_dataset_ms")# 2. 样本随机打乱dataset = dataset.shuffle(seed=42)# 3. 分词器tokenizer = AutoTokenizer.from_pretrained("./tokenizer", local_files_only=True)def generate_and_tokenize_prompt(batch):texts = [f"""<s>### Instruction:{instruction}### Response:{response}</s>"""for instruction, response in zip(batch["instruction"],batch["response"])]out = tokenizer(texts,max_length=256,padding="max_length",truncation=True,add_special_tokens=False,return_tensors=None,)# Loss 忽略paddingp.out["labels"] = [[tok if tok != tokenizer.pad_token_id else -100 for tok in label]for label in out["input_ids"]]return outtokenized_dataset = dataset.map(generate_and_tokenize_prompt,batched=True,remove_columns=dataset.column_names,desc="Tokenizing")# 4. 加载基座模型(如设备有限可指定CPU/其他device)model = AutoModelForCausalLM.from_pretrained("/Users/shawn/Documents/AI-dev/models/deepseek/deepseek-ai/DeepSeek-R1-Distill-Qwen-7B",torch_dtype=torch.bfloat16, # MPS、A100 等建议用bfloat16local_files_only=True,trust_remote_code=True)# 5. LoRA 配置lora_config = LoraConfig(r=8,lora_alpha=16,target_modules=["q_proj", "v_proj", "k_proj", "o_proj"],lora_dropout=0.05, # 或0, 稳定死记记忆;推荐小数据降dropoutbias="none",task_type="CAUSAL_LM")model = get_peft_model(model, lora_config)model.print_trainable_parameters()# 6. 训练参数training_args = TrainingArguments(output_dir="./results_ms",per_device_train_batch_size=2,gradient_accumulation_steps=1,learning_rate=3e-4,num_train_epochs=8,logging_dir="./logs_ms",save_steps=100,save_total_limit=3,logging_steps=10,overwrite_output_dir=True,report_to=None,fp16=False)trainer = Trainer(model=model,args=training_args,train_dataset=tokenized_dataset,data_collator=default_data_collator,)# 7. 训练与自动采样输出trainer.train()# 8. 保存模型与tokenizermodel.save_pretrained("./deepseek-7b-lora_ms")tokenizer.save_pretrained("./deepseek-7b-lora_ms")print("训练完成。")
含义:LoRA中低秩矩阵的秩(rank),⽤于近似原始权重矩阵。 作⽤:控制新增参数量,显著降低训练显存消耗。 影响:
r太⼩ → ⽋拟合,表达能⼒不足 r太⼤ → 接近全量微调,失去 LoRA优势
常⻅值:4~ 64,根据任务复杂度调整(如简单任务⽤ r=8,复杂任务可⽤ r=32)
含义:LoRA权重的缩放因⼦,控制低秩矩阵对输出的影响强度。 作⽤:调节LoRA分⽀的贡献程度,类似“学习率系数”。 影响:
推荐保持 alpha / r≥ 1(如 r=8, alpha=16→ ⽐值为 2) 值过⼤ → 容易过拟合;值过⼩ → 影响微弱
建议:初试设置 alpha = 2 × r
含义:指定在哪些模块上应⽤ LoRA(通常是注意⼒层的投影层)。 作⽤:精准控制适配范围,节省计算资源。 常⻅选择:
LLaMA系列:["q_proj", "k_proj", "v_proj", "o_proj"] BERT类:["query", "value", "key", "dense"]
影响:
仅作⽤于部分模块 → 可能限制模型适应能⼒ 可根据模型结构扩展(如加⼊ up_proj, down_proj)
含义:LoRA层的 Dropout概率,⽤于正则化。 作⽤:防⽌过拟合,提升泛化能⼒。 影响:
值越⼤ → 正则化越强 ⼩数据集建议:0.1~ 0.2 ⼤数据集可设为 0或 0.05
注意:若训练不稳定,可尝试增加 dropout
含义:是否在 LoRA层中引⼊偏置项。 选项:
"none":不加偏置(推荐起点) "all":所有模块加偏置 "lora_only":仅 LoRA层加偏置
影响:
加偏置可能略微提升效果,但增加参数量
建议:从 "none" 开始,效果不佳再尝试 "lora_only"
含义:指定任务类型,影响损失函数和标签处理⽅式。 常⽤类型:
"CAUSAL_LM":⾃回归语⾔模型(如 GPT、LLaMA⽣成任务) "SEQ_CLS":序列分类(如情感分析) "TOKEN_CLS":Token级分类(如命名实体识别) "SEQ_2_SEQ_LM":Seq2Seq任务(如翻译、摘要)
注意:必须与任务匹配,否则训练会出错
场景 | 建议配置 |
小数据集微调 | r=8 , alpha=16 , dropout=0.1 , epochs=5~10 |
大数据集微调 | r=16~32 , alpha=32 , dropout=0.05 , lr=2e-4 |
显存受限 | 减小 batch_size,启用 fp16,使用 gradient_accumulation |
快速实验 | overwrite_output_dir=True , logging_steps=10 , save_steps=50 |
import torchfrom transformers import AutoModelForCausalLM, AutoTokenizerfrom peft import PeftModel# 路径设置BASE_PATH = "/Users/shawn/Documents/AI-dev/models/deepseek/deepseekai/DeepSeek-R1-Distill-Qwen-7B"LORA_PATH = "./deepseek-7b-lora_ms"# 加载分词器tokenizer = AutoTokenizer.from_pretrained(BASE_PATH,local_files_only=True,trust_remote_code=True)# === 严格分离模型实例 ===# 原始模型model_base = AutoModelForCausalLM.from_pretrained(BASE_PATH,device_map="mps",torch_dtype=torch.bfloat16,local_files_only=True,trust_remote_code=True)# 微调后的模型(Base + LoRA 适配器)model_base_for_lora = AutoModelForCausalLM.from_pretrained(BASE_PATH,device_map="mps",torch_dtype=torch.bfloat16,local_files_only=True,trust_remote_code=True)model_lora = PeftModel.from_pretrained(model_base_for_lora,LORA_PATH)def format_prompt_one_round(user_input: str) -> str:return f"<s>### Instruction:\n{user_input}\n### Response:\n"def generate_single(model, prompt, tokenizer, max_new_tokens=200):inputs = tokenizer(prompt, return_tensors="pt")for k, v in inputs.items():inputs[k] = v.to(model.device)with torch.no_grad():outputs = model.generate(**inputs,max_new_tokens=max_new_tokens,temperature=0.7,top_p=0.9,do_sample=True,eos_token_id=tokenizer.eos_token_id)full_output = tokenizer.decode(outputs[0], skip_special_tokens=True)reply = full_output[len(prompt):]# 截断下一个分隔符,保证只输出新生成内容for sep in ["<s>", "</s>", "###"]:if sep in reply:reply = reply.split(sep)[0]return reply.strip()def main():print("="*40)print("DeepSeek 模型微调前/后 单轮对话对比(严格模型物理分离版)")print("="*40)print("输入exit 退出。\n")while True:user_input = input("你说:").strip()if user_input.lower() in {"exit", "quit"}:print("对话结束~ 再见!")breakprompt = format_prompt_one_round(user_input)# 原始模型推理base_reply = generate_single(model_base, prompt, tokenizer)# LoRA 微调后模型推理lora_reply = generate_single(model_lora, prompt, tokenizer)print("\n-------------------------------")print("【原模型 输出】↓\n" + base_reply)print("\n【微调后输出】↓\n" + lora_reply)print("-------------------------------")if __name__ == "__main__":main()
百炼平台微调
{“messages": [{"role": "system", "content": "You are a professional e-commercetitle analyst. Given a long product title, output ONLY the core product entityname (1-5 words) without any other text."}, {"role": "user", "content": "请从下列商品标题中提取最核心的商品主体,直接输出主体名,不要加其它词:【黑旗】心语肉松原味辣味1kg 烘焙面包蛋糕寿司原料商用肉松小贝"}, {"role": "assistant", "content": "肉松"}]}{"messages": [{"role": "system", "content": "You are a professional e-commercetitle analyst. Given a long product title, output ONLY the core product entityname (1-5 words) without any other text."}, {"role": "user", "content": "请从下列商品标题中提取最核心的商品主体,直接输出主体名,不要加其它词:夏季竹枕片成人藤凉席冰丝枕头套单人儿童竹枕席藤枕芯套一对拍2"}, {"role": "assistant", "content": "枕套"}]}⼈⼯评测
模型评测
基线评测
训练成本低,效果出色
显存占用少:仅需微调少量新增参数,可在消费级 GPU上完成训练,降低硬件门槛。 训练速度快:参数更新量小,支持快速迭代,适合业务团队敏捷试错。 性能接近全量微调:在多种任务中表现优异,效果逼近全参数微调,性价比极高
部署灵活,易于管理
适配器轻量化:LoRA 权重文件通常仅几十 MB,便于存储、传输与版本管理。 多任务共享主干:支持“一个基础模型 + 多个 LoRA适配器”的模式,实现不同业务线共用底座模型,提升资源利用率。
稳定可靠,风险可控
缓解过拟合:在小样本场景下表现更稳定,有效避免全参数微调带来的灾难性遗忘或过拟合问题。 保护原始模型:不修改主干参数,仅通过低秩增量进行适配,提升模型鲁棒与可维护性。
