【LLM实操系列06】模型微调:用100条数据打造专属AI 开始微调之前,建议先完成第02篇(环境配置)和第03篇(模型基础)的学习。 硬件方面,你需要至少8GB GPU显存(使用QLoRA可降至6GB)。 理论方面,了解Transformer基本架构会有帮助(第01篇有介绍)。
5分钟理解微调 什么时候需要微调? def need_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 bitsandbytes from transformersimport ( AutoModelForCausalLM, AutoTokenizer, TrainingArguments, Trainer) from peftimport LoraConfig, get_peft_model, TaskTypeimport torch# 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. 配置LoRA lora_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. 准备数据 def prepare_data ( ) : """准备训练数据""" # 格式:instruction-output对 data= [ { "instruction" : "翻译成英文:你好世界" , "output" : "Hello World" } , { "instruction" : "生成SQL:查询年龄大于18的用户" , "output" : "SELECT * FROM users WHERE age > 18" } ] return datadef format_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实现 from transformersimport BitsAndBytesConfig# 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" ) # 准备训练 from peftimport prepare_model_for_kbit_training model= prepare_model_for_kbit_training( model) # 应用LoRA model= 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. 数据处理工具 class DataProcessor : """数据预处理工具""" def __init__ ( self, tokenizer, max_length= 512 ) : self. tokenizer= tokenizer self. max_length= max_lengthdef process_dataset ( self, raw_data) : """处理数据集""" processed= [ ] for itemin raw_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 ] # 自回归训练 } ) return processeddef format_conversation ( self, item) : """格式化为模型输入""" # Qwen格式 return f"""<|im_start|>system You are a helpful assistant.<|im_end|> <|im_start|>user { item[ 'instruction' ] } <|im_end|> <|im_start|>assistant { item[ 'output' ] } <|im_end|>""" def validate_data ( self, dataset) : """数据质量检查""" issues= [ ] for idx, itemin enumerate ( dataset) : # 检查长度 if len ( item[ 'text' ] ) > self. max_length* 4 : # 估算 issues. append( f"样本 { idx} 过长" ) # 检查格式 if not item. get( 'instruction' ) : issues. append( f"样本 { idx} 缺少instruction" ) return issues3. 数据增强 def augment_data ( original_data) : """数据增强技巧""" augmented= [ ] for itemin original_data: # 1. 原始样本 augmented. append( item) # 2. 改写instruction rephrased= { "instruction" : f"请 { item[ 'instruction' ] } " , "output" : item[ 'output' ] } augmented. append( rephrased) # 3. 添加思维链 cot_version= { "instruction" : item[ 'instruction' ] , "output" : f"让我思考一下。 { item[ 'output' ] } " } augmented. append( cot_version) return augmented训练监控与评估 实时监控 from transformersimport TrainerCallbackimport wandbclass TrainingMonitor ( TrainerCallback) : """训练监控回调""" def on_log ( self, args, state, control, logs= None , ** kwargs) : if logs: # 打印关键指标 print ( f"Step { state. global_step} : Loss= { logs. get( 'loss' , 0 ) : .4f } " ) # 上传到wandb(可选) if wandb. run: wandb. log( logs) def on_epoch_end ( self, args, state, control, ** kwargs) : # 每个epoch结束后评估 print ( f"Epoch { state. epoch} completed" ) 评估指标 def evaluate_model ( model, test_data) : """模型评估""" from rougeimport Rougefrom bert_scoreimport score predictions= [ ] references= [ ] for itemin test_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 ) # BERTScore P, R, F1= score( predictions, references, lang= "zh" ) return { "rouge" : rouge_scores, "bertscore" : { "P" : P. mean( ) , "R" : R. mean( ) , "F1" : F1. mean( ) } } 部署微调模型 1. 合并LoRA权重 from peftimport PeftModel# 加载基座模型 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) from transformersimport AutoModelForCausalLMimport torch# 转换为GGUF model= 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服务 from fastapiimport FastAPIfrom pydanticimport BaseModel app= FastAPI( ) # 加载模型 model= AutoModelForCausalLM. from_pretrained( "./final_model" ) tokenizer= AutoTokenizer. from_pretrained( "./final_model" ) class Query ( BaseModel) : text: str max_length: int = 100 @app. post ( "/generate" ) async def generate ( 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%
调试技巧 # 常见问题快速诊断 def diagnose_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模板" ] } return checks