【LLM实操系列06】模型微调:用100条数据打造专属AI
开始微调之前,建议先完成第02篇(环境配置)和第03篇(模型基础)的学习。
硬件方面,你需要至少8GB GPU显存(使用QLoRA可降至6GB)。
理论方面,了解Transformer基本架构会有帮助(第01篇有介绍)。
5分钟理解微调
什么时候需要微调?
defneed_finetuning():"""判断是否需要微调"""if"特定领域术语多"and"Prompt优化无效":return"需要微调"elif"需要特定输出格式"and"Few-shot效果差":return"需要微调"elif"隐私数据不能用API":return"需要微调"else:return"继续优化Prompt"
微调 vs 其他方案(选择指南)
| 方案 | 成本 | 效果 | 适用场景 | 参考章节 |
|---|
| Prompt工程 | 低 | 一般 | 通用任务 | 第04篇 |
| RAG | 中 | 好 | 知识问答 | 第05篇 |
| 微调 | 高 | 最好 | 专属任务 | 本篇 |
| 从头训练 | 极高 | - | 不推荐 | - |
LoRA:高效微调神器
原理:只训练少量参数
# 传统微调:训练所有参数(7B = 70亿参数)# LoRA微调:只训练0.1%参数(7M参数)# LoRA数学原理# W_new = W_original + ΔW# ΔW = A × B (A和B是小矩阵)
15分钟跑通LoRA
# pip install transformers peft datasets accelerate bitsandbytesfromtransformersimport(AutoModelForCausalLM,AutoTokenizer,TrainingArguments,Trainer)frompeftimportLoraConfig,get_peft_model,TaskTypeimporttorch# 1. 加载基座模型(以Qwen为例)model_name="Qwen/Qwen2.5-1.5B"# 小模型,适合测试model=AutoModelForCausalLM.from_pretrained(model_name,torch_dtype=torch.float16,device_map="auto")tokenizer=AutoTokenizer.from_pretrained(model_name)# 2. 配置LoRAlora_config=LoraConfig(task_type=TaskType.CAUSAL_LM,r=8,# LoRA秩,越大效果越好但越慢lora_alpha=16,# LoRA缩放参数lora_dropout=0.1,# Dropout比例target_modules=["q_proj","v_proj"],# 要微调的层)# 3. 创建PEFT模型model=get_peft_model(model,lora_config)model.print_trainable_parameters()# 输出: trainable params: 4,194,304 || all params: 1,544,454,144 || trainable%: 0.27# 4. 准备数据defprepare_data():"""准备训练数据"""# 格式:instruction-output对data=[{"instruction":"翻译成英文:你好世界","output":"Hello World"},{"instruction":"生成SQL:查询年龄大于18的用户","output":"SELECT * FROM users WHERE age > 18"}]returndatadefformat_prompt(example):"""格式化训练样本"""prompt=f"""<|im_start|>user{example['instruction']}<|im_end|> <|im_start|>assistant{example['output']}<|im_end|>"""return{"text":prompt}# 5. 训练training_args=TrainingArguments(output_dir="./lora_model",num_train_epochs=3,per_device_train_batch_size=4,gradient_accumulation_steps=4,warmup_steps=100,logging_steps=10,save_strategy="epoch",learning_rate=1e-4,fp16=True,)trainer=Trainer(model=model,args=training_args,train_dataset=train_dataset,tokenizer=tokenizer,)trainer.train()
QLoRA:4bit量化微调
显存对比
# 模型大小对比configs={"全精度":{"7B模型":"28GB显存","可行性":"A100"},"LoRA":{"7B模型":"16GB显存","可行性":"3090/4090"},"QLoRA":{"7B模型":"6GB显存","可行性":"2080Ti"},# ← 推荐}
QLoRA实现
fromtransformersimportBitsAndBytesConfig# QLoRA配置:4bit量化bnb_config=BitsAndBytesConfig(load_in_4bit=True,bnb_4bit_compute_dtype=torch.float16,bnb_4bit_quant_type="nf4",bnb_4bit_use_double_quant=True,)# 加载4bit模型model=AutoModelForCausalLM.from_pretrained(model_name,quantization_config=bnb_config,device_map="auto")# 准备训练frompeftimportprepare_model_for_kbit_training model=prepare_model_for_kbit_training(model)# 应用LoRAmodel=get_peft_model(model,lora_config)# 现在可以在6GB显存上微调7B模型!
数据准备最佳实践
1. 数据格式
# 方式1:指令微调格式instruction_data={"instruction":"任务描述","input":"输入内容(可选)","output":"期望输出"}# 方式2:对话格式conversation_data={"conversations":[{"from":"human","value":"用户问题"},{"from":"assistant","value":"助手回答"}]}# 方式3:纯文本续写text_data={"text":"这是一段完整的文本..."}
2. 数据处理工具
classDataProcessor:"""数据预处理工具"""def__init__(self,tokenizer,max_length=512):self.tokenizer=tokenizer self.max_length=max_lengthdefprocess_dataset(self,raw_data):"""处理数据集"""processed=[]foriteminraw_data:# 构建对话text=self.format_conversation(item)# 分词tokens=self.tokenizer(text,max_length=self.max_length,truncation=True,padding="max_length",return_tensors="pt")processed.append({"input_ids":tokens["input_ids"][0],"attention_mask":tokens["attention_mask"][0],"labels":tokens["input_ids"][0]# 自回归训练})returnprocesseddefformat_conversation(self,item):"""格式化为模型输入"""# Qwen格式returnf"""<|im_start|>system You are a helpful assistant.<|im_end|> <|im_start|>user{item['instruction']}<|im_end|> <|im_start|>assistant{item['output']}<|im_end|>"""defvalidate_data(self,dataset):"""数据质量检查"""issues=[]foridx,iteminenumerate(dataset):# 检查长度iflen(item['text'])>self.max_length*4:# 估算issues.append(f"样本{idx}过长")# 检查格式ifnotitem.get('instruction'):issues.append(f"样本{idx}缺少instruction")returnissues
3. 数据增强
defaugment_data(original_data):"""数据增强技巧"""augmented=[]foriteminoriginal_data:# 1. 原始样本augmented.append(item)# 2. 改写instructionrephrased={"instruction":f"请{item['instruction']}","output":item['output']}augmented.append(rephrased)# 3. 添加思维链cot_version={"instruction":item['instruction'],"output":f"让我思考一下。{item['output']}"}augmented.append(cot_version)returnaugmented
训练监控与评估
实时监控
fromtransformersimportTrainerCallbackimportwandbclassTrainingMonitor(TrainerCallback):"""训练监控回调"""defon_log(self,args,state,control,logs=None,**kwargs):iflogs:# 打印关键指标print(f"Step{state.global_step}: Loss={logs.get('loss',0):.4f}")# 上传到wandb(可选)ifwandb.run:wandb.log(logs)defon_epoch_end(self,args,state,control,**kwargs):# 每个epoch结束后评估print(f"Epoch{state.epoch}completed")
评估指标
defevaluate_model(model,test_data):"""模型评估"""fromrougeimportRougefrombert_scoreimportscore predictions=[]references=[]foritemintest_data:# 生成预测input_text=item['instruction']pred=model.generate(input_text,max_length=100)predictions.append(pred)references.append(item['output'])# ROUGE分数rouge=Rouge()rouge_scores=rouge.get_scores(predictions,references,avg=True)# BERTScoreP,R,F1=score(predictions,references,lang="zh")return{"rouge":rouge_scores,"bertscore":{"P":P.mean(),"R":R.mean(),"F1":F1.mean()}}
部署微调模型
1. 合并LoRA权重
frompeftimportPeftModel# 加载基座模型base_model=AutoModelForCausalLM.from_pretrained(model_name)# 加载LoRA权重model=PeftModel.from_pretrained(base_model,"./lora_model")# 合并权重merged_model=model.merge_and_unload()# 保存完整模型merged_model.save_pretrained("./final_model")tokenizer.save_pretrained("./final_model")
2. 量化部署
# GGUF格式(用于llama.cpp/Ollama)fromtransformersimportAutoModelForCausalLMimporttorch# 转换为GGUFmodel=AutoModelForCausalLM.from_pretrained("./final_model")model.save_pretrained("./gguf_model",safe_serialization=True)# 使用llama.cpp量化# python convert.py ./final_model --outfile model.gguf# ./quantize model.gguf model_q4_k_m.gguf q4_k_m
3. API服务
fromfastapiimportFastAPIfrompydanticimportBaseModel app=FastAPI()# 加载模型model=AutoModelForCausalLM.from_pretrained("./final_model")tokenizer=AutoTokenizer.from_pretrained("./final_model")classQuery(BaseModel):text:strmax_length:int=100@app.post("/generate")asyncdefgenerate(query:Query):inputs=tokenizer(query.text,return_tensors="pt")outputs=model.generate(**inputs,max_length=query.max_length)response=tokenizer.decode(outputs[0],skip_special_tokens=True)return{"response":response}
成本与效果对比
| 方法 | 数据量 | 训练时间 | 显存 | 效果提升 |
|---|
| Prompt | 0 | 0 | 0 | 基准 |
| Few-shot | 5-10 | 0 | 0 | +10% |
| LoRA | 100-1K | 1-4小时 | 16GB | +30% |
| QLoRA | 100-1K | 2-6小时 | 6GB | +25% |
| 全量微调 | 10K+ | 1-7天 | 80GB | +35% |
调试技巧
# 常见问题快速诊断defdiagnose_training_issues():checks={"Loss不下降":["检查学习率(试试1e-4到5e-5)","增加训练轮数","检查数据格式"],"显存爆炸":["减小batch_size","使用gradient_accumulation_steps","启用gradient_checkpointing"],"效果不好":["增加LoRA rank (r=8→16)","增加训练数据","调整lora_alpha"],"推理错误":["检查tokenizer配置","确认special tokens","验证prompt模板"]}returnchecks