什么时候该微调 先泼一盆冷水:大部分场景不需要微调 。
如果你的需求是让模型掌握特定领域的知识(比如公司产品资料),用 RAG 方案 就够了。如果你的需求是让模型按特定格式输出,好好写 System Prompt 通常能解决。
微调真正有价值的场景是:
风格/人设固化 — 你想让模型说话方式固定为某种风格,不依赖复杂的 prompt
特定任务优化 — 模型在某个具体任务上表现不够好,需要用高质量数据专门提升
推理效率 — 通过微调把原本需要长 prompt 引导的行为”烧”进模型权重,减少每次推理的 token 消耗
指令遵循强化 — 让模型更好地遵循你的自定义指令格式
如果你属于上面某种情况,那继续往下看。
准备工作 硬件要求(以 Hermes-3 8B 为例):
微调方式
最低显存
推荐显卡
LoRA / QLoRA
12-16 GB
RTX 3090 / RTX 4090
全参数微调
80+ GB
A100 80GB
个人玩家请直接上 LoRA 或 QLoRA,全参数微调是有集群资源的团队才需要考虑的。
软件环境:
1 2 3 pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu121 pip install transformers datasets accelerate peft bitsandbytes pip install wandb
数据格式:ChatML 是关键 Hermes 系列模型使用 ChatML 格式(Chat Markup Language)。你的训练数据必须符合这个格式,否则微调效果会很差。
ChatML 的结构长这样:
1 2 3 4 5 6 <|im_start|>system 你是一个专业的法律顾问。<|im_end|> <|im_start|>user 劳动合同到期不续签,公司需要赔偿吗?<|im_end|> <|im_start|>assistant 根据《劳动合同法》第四十六条的规定...<|im_end|>
对应到 JSON 训练数据格式:
1 2 3 4 5 6 7 8 9 [ { "conversations" : [ { "role" : "system" , "content" : "你是一个专业的法律顾问。" } , { "role" : "user" , "content" : "劳动合同到期不续签,公司需要赔偿吗?" } , { "role" : "assistant" , "content" : "根据《劳动合同法》第四十六条的规定,劳动合同期满后,用人单位不续签的,应当向劳动者支付经济补偿。补偿标准为每满一年支付一个月工资。不满六个月的,支付半个月工资。" } ] } ]
数据质量比数量重要得多 微调的一个常见误区是”数据越多越好”。实际上,500 条高质量数据的效果往往好于 5000 条低质量数据 。
高质量数据的标准:
答案准确 — 错误的数据会让模型学到错误的知识
多样化 — 覆盖你目标场景的各种变体
长度适中 — 回答不要太短也不要太长
格式一致 — 如果你想让模型输出 JSON,训练数据里的 assistant 回复都应该是 JSON
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 import jsondef validate_dataset (filepath: str ) -> dict : """校验训练数据格式""" with open (filepath, "r" , encoding="utf-8" ) as f: data = json.load(f) stats = {"total" : len (data), "valid" : 0 , "invalid" : 0 , "issues" : []} for i, item in enumerate (data): conversations = item.get("conversations" , []) if not conversations: stats["issues" ].append(f"第{i} 条: 缺少conversations字段" ) stats["invalid" ] += 1 continue roles = [c["role" ] for c in conversations] if roles[0 ] == "system" : roles = roles[1 :] expected = ["user" , "assistant" ] * (len (roles) // 2 ) if roles != expected[:len (roles)]: stats["issues" ].append(f"第{i} 条: 角色顺序不正确" ) stats["invalid" ] += 1 continue has_empty = any (not c.get("content" , "" ).strip() for c in conversations) if has_empty: stats["issues" ].append(f"第{i} 条: 存在空内容" ) stats["invalid" ] += 1 continue stats["valid" ] += 1 return stats
用 LLaMA-Factory 微调(推荐新手) LLaMA-Factory 是目前最易用的微调工具,支持 Web UI 操作,中文文档齐全。
1 2 3 git clone https://github.com/hiyouga/LLaMA-Factory.git cd LLaMA-Factorypip install -e ".[torch,metrics]"
LoRA 微调配置文件(train_hermes.yaml):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 model_name_or_path: NousResearch/Hermes-3-Llama-3.1-8B template: chatml stage: sft do_train: true finetuning_type: lora lora_rank: 64 lora_alpha: 128 lora_dropout: 0.05 lora_target: all dataset: my_legal_data cutoff_len: 2048 preprocessing_num_workers: 8 output_dir: ./output/hermes3-legal-lora num_train_epochs: 3 per_device_train_batch_size: 4 gradient_accumulation_steps: 4 learning_rate: 2.0e-4 lr_scheduler_type: cosine warmup_ratio: 0.1 max_grad_norm: 1.0 bf16: true report_to: wandb
1 llamafactory-cli train train_hermes.yaml
关键参数解释:
lora_rank: 64 — LoRA 的秩,越大表达能力越强但训练越慢
lora_alpha: 128 — 通常设为 rank 的 2 倍
learning_rate: 2e-4 — LoRA 微调的学习率通常比全参数微调高一个数量级
num_train_epochs: 3 — 数据量小(<1000条)3-5 个 epoch,数据量大(>5000条)1-2 个
用 Axolotl 微调(更灵活) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 base_model: NousResearch/Hermes-3-Llama-3.1-8B model_type: LlamaForCausalLM datasets: - path: ./my_training_data.json type: sharegpt conversation: chatml chat_template: chatml adapter: lora lora_r: 64 lora_alpha: 128 lora_dropout: 0.05 lora_target_linear: true sequence_len: 2048 sample_packing: true micro_batch_size: 4 gradient_accumulation_steps: 4 num_epochs: 3 learning_rate: 2e-4 optimizer: adamw_torch lr_scheduler: cosine warmup_ratio: 0.1 bf16: auto flash_attention: true output_dir: ./output/hermes3-lora
1 accelerate launch -m axolotl.cli.train hermes_lora.yml
QLoRA:显存不够时的救星 QLoRA 先把基座模型量化到 4-bit(只读),然后在上面加 LoRA adapter 做训练。显存占用大幅降低——8B 模型在一张 12GB 的 RTX 3060 上就能训练。
在 LLaMA-Factory 里只需改一个参数:
1 2 quantization_bit: 4 quantization_method: bitsandbytes
合并和导出 训练完成后,LoRA adapter 是一个独立的小文件。你可以直接用 adapter 做推理,也可以合并进基座模型:
1 2 3 4 5 6 7 8 9 10 11 12 13 from peft import PeftModelfrom transformers import AutoModelForCausalLM, AutoTokenizerbase_model = AutoModelForCausalLM.from_pretrained( "NousResearch/Hermes-3-Llama-3.1-8B" , torch_dtype="auto" , device_map="auto" ) model = PeftModel.from_pretrained(base_model, "./output/hermes3-legal-lora" ) merged_model = model.merge_and_unload() merged_model.save_pretrained("./hermes3-legal-merged" ) tokenizer = AutoTokenizer.from_pretrained("NousResearch/Hermes-3-Llama-3.1-8B" ) tokenizer.save_pretrained("./hermes3-legal-merged" )
合并后的模型就是一个标准的 HF 模型,可以直接转成 GGUF 做 量化 和部署。
训练过程的监控和调试 1. Loss 曲线
正常的 loss 曲线应该是:前期快速下降 -> 中期缓慢下降 -> 后期趋于平稳。
loss 不下降:学习率可能太小,或数据格式有问题
loss 下降后又上升:过拟合了,减少 epoch 数或增加数据量
loss 剧烈波动:学习率太大或 batch size 太小
2. 过拟合检测
微调很容易过拟合,尤其是数据量小的时候。建议留出 10-20% 的数据做验证集:
1 2 val_size: 0.1 eval_steps: 50
如果训练 loss 持续下降但验证 loss 开始上升,就该停止了。
3. 训练后快速验证
1 2 3 4 5 6 7 8 9 10 test_messages = [ {"role" : "system" , "content" : "你是一个专业的法律顾问。" }, {"role" : "user" , "content" : "公司能因为员工怀孕而辞退吗?" } ] input_text = tokenizer.apply_chat_template(test_messages, tokenize=False , add_generation_prompt=True ) inputs = tokenizer(input_text, return_tensors="pt" ).to(model.device) outputs = model.generate(**inputs, max_new_tokens=512 , temperature=0.3 , do_sample=True ) response = tokenizer.decode(outputs[0 ][inputs["input_ids" ].shape[1 ]:], skip_special_tokens=True ) print (response)
微调的常见坑 1. 模板不匹配 — Hermes 用 ChatML 模板,训练代码必须用同样的模板。用了错误的模板效果急剧下降。
2. 学习率设太高 — LoRA 微调建议 1e-5 到 5e-4。设太高模型容易”忘掉”基座能力。
3. 数据泄露 — 确保训练数据和测试数据没有重叠。
4. 只看 loss 不看实际输出 — loss 低不代表模型好用。一定要用真实场景的问题做定性测试。
微调是一个需要反复实验的过程。从小数据集开始试,确认流程跑通后再逐步加大数据量和训练时长。如果你在过程中遇到问题,cocoloop 社区里有不少微调经验丰富的同学可以交流。