☰
大模型微调全流程:LLaMA-Factory从SFT到PPO、量化与评估实操指南
2026/10/1 11:21:00 网站建设 项目流程

有一段时间,我每次做大模型微调都处于一种"半手工"状态:数据清洗用一个脚本,SFT 用 LLaMA-Factory 跑一遍,reward model 再单独折腾一套环境,PPO 又要换 trl 的版本,最后想上线了还得另找时间做量化、剪枝和评测。整个链路下来,光环境适配和任务衔接就吞掉一大半时间。后来我把整套流程搬到了 CubeStudio 的大模型任务模板上,用 LLaMA-Factory 统一跑 SFT / reward / PPO,再做蒸馏、剪枝、量化,最后接 OpenCompass 和安全评估,才算是把这条流水线真正捋顺。这篇就重点拆解我在这个平台上完成"微调—瘦身—验收"全过程的实操细节,包括每个阶段的命令、参数理由、显存估算和踩过的坑,适合正在做模型落地、想规范训练流程的工程师参考。

1. 为什么偏偏是"平台一站式":从裸脚本到模板化流水线

先说个很实在的问题:微调链路远不止"跑一个训练脚本"这么简单。你至少需要处理数据格式、模型存取、分布式环境、多阶段任务串联、模型评估、导出部署这些环节。每个环节背后都是一套独立的依赖关系。LLaMA-Factory 虽然本身已经把 SFT、reward modeling、PPO 封装得很友好,但你在裸机上用,仍需要自己解决 CUDA、PyTorch、transformers、peft、trl 这些库的版本兼容。我曾经在一个项目里因为 trl 版本不兼容,导致 PPO trainer 初始化时直接报KeyError: 'clip_range',排查了一整天,最后发现是 trl 0.9 和 0.10 之间的 API 变化。

CubeStudio 这类平台上的大模型任务模板,本质是把最繁琐的运行环境和任务编排固化成"半成品净菜"。它预置了 LLaMA-Factory 的运行环境、数据集目录约定、任务产出的传递方式,你只需要像填表单一样把模型地址、数据集路径、训练参数一填,剩下的事情交给平台。我特别想说清楚一点:平台模板不是黑盒。它跑的还是 LLaMA-Factory 那一套 CLI 命令,只是帮你把环境准备、资源申请、任务之间的依赖关系做成了可复用的模板。换句话说,你在这里学会的东西,迁回裸机照样能用;反过来,裸机跑通的命令,填进模板也就行了。

还有很重要的一点是任务串联。我们经常忽略,微调不是一个独立动作,而是一条链:SFT 的产出是 reward model 的底座,reward model 和 SFT 模型一起参与 PPO,PPO 之后进蒸馏或剪枝,最后还要做评测。在裸脚本里,这个链条靠人肉记录路径,经常发生"下一个任务找不到上一个任务的输出"这种情况。平台模板会把每一步的产物保存在约定好的目录里,下一个任务直接引用,这比我之前自己维护一堆软链接可靠得多。如果你经历过训练任务跑了一半发现路径不对、或者在不同服务器之间来回拷贝模型的痛苦,就会明白这一步省了多少事。

2. 任务模板的初始配置:模型、数据、算力怎么选

2.1 基础模型选型:基座还是指令版

很多人第一步就选错了模型。如果你计划做完整的 SFT + reward + PPO 对齐,我建议从**基座模型(base)**开始,而不是直接用 Chat/Instruct 版。原因很简单:instruct 模型已经经过一轮对齐,你再拿它做指令微调,相当于在别人装修过的房子里再改水电,很容易把原有风格带偏,出现重复微调后"回答啰嗦""语气怪异"的问题。反过来,从 base 模型出发,SFT 阶段你完全掌控数据风格,后面的 RM 和 PPO 也更干净。

平台模板通常会让你填model_name_or_path,这里可以直接填模型社区里能访问的模型 ID,也可以填平台共享存储里的本地路径。我习惯用本地路径,因为多阶段任务之间要反复引用同一个底座模型,本地路径稳定性更高,也不用每次启动任务都去拉权重。显存方面,7B 量级的 base 模型做 LoRA 微调,单卡 A100 40G 是舒服的;如果只有 24G 显存,就开 QLoRA(4bit 量化加载),再配合梯度累积。

2.2 数据集格式:提前对齐 LLaMA-Factory 的约定

LLaMA-Factory 对数据格式的约定比较死,但也就那么几种。最常用的是 alpaca 格式,一个 JSON 数组,每条包含instruction、input、output三个字段;多轮对话则用conversations字段。我强烈建议在进平台之前,先用一个 Python 脚本把原始数据统一转成这种格式,不要直接拿脏数据上去试。模板虽然有数据校验,但格式错误提示往往要到训练开始后才会暴露出来。

[ { "instruction": "请解释什么是梯度累积", "input": "", "output": "梯度累积是将多个小 batch 的梯度累加后一次性更新参数,用来在显存受限时模拟更大的 batch size。" } ]

数据处理有两点容易忽略。一是system字段要不要保留。LLaMA-Factory 的 alpaca 格式支持system,如果你的任务有固定的角色设定,建议每条样本都带上(或者在数据集配置里指定系统提示词),否则训练时会把系统提示词丢掉,上线后模型行为会不一致。二是数据质量要按"训练集—验证集—评估集"拆分,别把一个 CSV 从头到尾全喂进去。平台模板一般允许你在数据集配置里指定val_size,我习惯取 5% 做验证,剩下的做训练。

2.3 算力和批大小估算

LoRA 训练 7B 模型,用 bf16 + per_device_train_batch_size=2 + gradient_accumulation_steps=8,等效 batch size 是 16,显存占用大约 18G 到 24G。如果你用 QLoRA(4bit 加载底座),同样的配置大概只需要 12G 左右。平台模板里一般会暴露per_device_train_batch_size、gradient_accumulation_steps、finetuning_type这些参数,我的建议是:

  • 显存充足就 batch size 尽量开到 4,减少累积步数,训练更稳;
  • 显存紧张就保持 batch 2,但把gradient_checkpointing打开,这能显著降低激活值显存;
  • 学习率和 LoRA rank 的配合要谨慎,rank 到 64 之后,学习率建议降到 1e-4 左右,否则容易震荡。

3. LLaMA-Factory 三阶段微调实操:SFT、reward、PPO

3.1 阶段一:SFT 指令微调

SFT 是整个对齐链路的地基。在模板里创建一个"LLaMA-Factory 训练"任务,stage 选sft,命令本质上长这样:

llamafactory-cli train \ --model_name_or_path /models/Qwen2.5-7B-Base \ --stage sft \ --dataset my_instruction_data \ --template qwen \ --finetuning_type lora \ --lora_target q,k,v,o \ --output_dir /outputs/sft_model \ --num_train_epochs 3 \ --per_device_train_batch_size 2 \ --gradient_accumulation_steps 8 \ --learning_rate 2e-4 \ --lr_scheduler_type cosine \ --warmup_ratio 0.03 \ --bf16 True \ --gradient_checkpointing True

这里最值得解释的是--lora_target。只选 q,k,v,o 是 LoRA 微调的"标准保守配置",改动参数少、训练稳定,适合数据量不太大的场景。如果数据量足够大,可以加上gate_proj、up_proj、down_proj,模型能学到更多前馈网络的知识,但显存和训练时间也会涨。至于学习率,LoRA 一般用 1e-4 到 3e-4 之间,取 2e-4 是平衡点。全量微调的典型学习率是 1e-5 到 2e-5,差一个数量级,这个别记混。

跑完之后,模板会产出 adapter 权重(LoRA 的小几百 MB 文件)。LLaMA-Factory 支持把它和底座合并导出,也可以用export命令导出完整模型。我一般在微调完先合并,再用合并后的完整模型去跑 RM 和 PPO,这样后续阶段不用每次加载都带 adapter,路径干净,也避免多阶段任务之间 LoRA 加载混乱。

3.2 阶段二:reward model 训练

reward model(RM)不是所有场景都需要,但只要你做 PPO,RM 就是必需品。RM 的输入是一条 prompt 和两个回答——一个 chosen(更优),一个 rejected(更差),模型需要学会给 chosen 打出比 rejected 更高的分数。LLaMA-Factory 里 stage 选rm,使用的数据集是 preference 格式:

[ { "instruction": "用户指令", "input": "", "chosen": "这是更优的回答内容", "rejected": "这是较差的回答内容" } ]

训练命令和 SFT 很像,差别在于 stage 和输出:

llamafactory-cli train \ --model_name_or_path /outputs/sft_model \ --stage rm \ --dataset my_preference_data \ --template qwen \ --finetuning_type lora \ --lora_target q,k,v,o \ --output_dir /outputs/rm_model \ --num_train_epochs 1 \ --per_device_train_batch_size 1 \ --gradient_accumulation_steps 16 \ --learning_rate 1e-5 \ --bf16 True

RM 训练有三个容易踩的坑。第一,RM 不需要训练太多 epoch,我试过 3 个 epoch 之后验证集上排序准确率不升反降,开始对训练集过拟合,1 个 epoch 通常足够。第二,chosen 和 rejected 必须严格控制"除了回答质量外其他条件一致",如果 chosen 比 rejected 长一大截、或者格式不同,RM 学到的是长度偏好而不是内容质量,后面 PPO 会放大这个 bias。第三,一个 batch 里只能放一条 prompt 对应的 pair(batch size 1),这样模型在 batch 内比较时才不会跨样本混在一起。你会在 RM 训练日志里看到一个类似 acc 的指标,代表 pair 判断准确率,跑几步之后就心里有数了。

3.3 阶段三:PPO 对齐

PPO 阶段就是把 SFT 模型(policy)和一个训练好的 RM 拼在一起,用强化学习让模型在保持语言能力的前提下,学会偏向 RM 认可的回答。LLaMA-Factory 的 stage 选ppo,命令会稍微复杂一点,因为它要同时加载 policy、reference model 和 reward model:

llamafactory-cli train \ --model_name_or_path /outputs/sft_model \ --adapter_name_or_path /outputs/ppo_adapters \ --stage ppo \ --reward_model /outputs/rm_model \ --dataset my_prompt_data \ --template qwen \ --finetuning_type lora \ --lora_target q,k,v,o \ --output_dir /outputs/ppo_model \ --per_device_train_batch_size 1 \ --gradient_accumulation_steps 8 \ --learning_rate 1e-5 \ --bf16 True \ --kl_coef 0.1

PPO 里最关键的直觉是:光追求 RM 分数高,模型会慢慢忘掉人类语言的分布,甚至出现"奖励黑客"——输出一串让 RM 高兴但实际不可读的内容。为了防止这个,PPO 会在 reward 里减去一个 KL 散度惩罚项,kl_coef就是控制这个惩罚强度的旋钮。kl_coef设太大,模型几乎不偏离 SFT 分布,PPO 白跑;设太小,奖励黑客风险直线上升。从 0.1 起步是比较稳妥的,观察几轮训练后生成的文本再调。

PPO 阶段平台模板一般会要求你在任务里同时指定 policy 模型、RM、参考数据三样东西,记牢这个结构,别漏。如果你发现 PPO 训练中 reward 一路飙升但文本质量肉眼可见地变差,第一件事就是加大kl_coef,而不是等它自己收敛。

3.4 三阶段任务串联的经验

在平台模板里,比较省心的做法是把 SFT、RM、PPO 做成三个独立的训练任务,通过产出目录进行上下游引用:SFT 输出目录 → RM 的 base model;SFT 输出 + RM 输出 → PPO 的模型输入。这样任何一个阶段失败,都可以单独重跑,不会污染整条链。我之前在裸脚本里图省事,把三步写进一个 shell 脚本,结果 RM 训练后忘了保存 adapter,PPO 加载时直接报模型不匹配,全部重来。模板化的任务编排恰好解决了这种"人肉传参容易断"的问题。

4. 模型瘦身:蒸馏、剪枝、量化的衔接顺序

别看到"瘦身"就想着直接量化。我自己的经验是,先把蒸馏和剪枝做完,最后压缩位宽,效果最稳。顺序的逻辑是:蒸馏改变的是模型内部的知识分布,剪枝干的活是去掉冗余参数,量化是把参数的数值精度降低。如果你先量化再剪枝,剪枝过程中会对数值分布敏感,精度损失会被放大;如果你先剪枝再蒸馏,学生模型学的老师本身就带伤,效果打折。

4.1 蒸馏:把大模型的知识灌进小模型

蒸馏最朴素的做法,是用大模型(teacher)生成一批高质量回答,小模型(student)用这些回答做 SFT。LLaMA-Factory 的蒸馏入口一般有两种:一种是在训练任务里配置 teacher 模型路径,让训练过程直接对齐 teacher 的 logits;另一种更简单——先让 teacher 批量跑你的指令集,把输出存成标准数据集,再让 student 用普通 SFT 训练这批数据。

我常用后面这种 response distillation,操作成本低,也容易排查问题。大致流程是:

  1. 准备一份高质量指令集,覆盖你想要的核心场景;
  2. 用微调好的大模型批量生成答案,temperature 调到 0.7 左右,保证多样性;
  3. 人工或规则过滤掉空答、跑题、乱码的样本;
  4. 把过滤后的数据转成 alpaca 格式,交给小模型做 SFT。

注意,蒸馏时 teacher 不一定非要是百亿级的大模型。如果你的业务场景有限,7B teacher 蒸馏 3B student 也能拿到不错的收益。关键是数据质量,跑偏的 teacher 只会把错误"蒸馏"得更彻底。

4.2 剪枝:先看稀疏度对困惑度的影响

剪枝这步,很多团队直接跳过,因为收益不如量化明显,但如果你要部署的设备存储或内存卡得很死,剪枝就很有用。我的操作流程是先用 Wanda 这类无需训练的重构剪枝方法,在给定的稀疏度下剪掉一层,马上看模型的困惑度(perplexity)上涨多少,如果 PPL 涨得离谱,就降低稀疏度。

python prune_llm.py \ --model /outputs/ppo_model \ --sparsity_ratio 0.2 \ --sparsity_type unstructured \ --prune_method wanda \ --eval_ppl

0.2意味着剪掉 20% 的权重,是一个相对温和的起点。结构化剪枝(比如按整行整列去剪)对硬件更友好,但精度损失通常比非结构化更大,需要在业务容忍范围内多试几档。剪完之后跑一遍基本的生成测试,别只盯着 PPL 看,PPL 下降不代表真实问答更好。

4.3 量化:AWQ 和 GGUF 怎么选

量化这一步和你的部署环境强相关。如果你用 vLLM 做在线推理,可以走 AWQ 4bit 量化,吞吐高,显存占用小;如果你要在 CPU 或者边缘设备跑,GGUF 搭配 llama.cpp / Ollama 更现实。

以 AWQ 为例,LLaMA 系列模型可以这样量化:

python -m awq.entry \ --model_path /outputs/ppo_model \ --tasks quantize \ --calib_data pileval \ --group_size 128 \ --bits 4 \ --output_dir /outputs/ppo_model_awq

这里calib_data是校准集,作用是统计权重和激活值的分布,从而决定量化参数。校准集选择有个很容易犯的错:拿评测集去做校准,或者让校准集和评测集有重叠,量化后的分数会虚高,看着漂亮,一上真实业务就露馅。我一般会从真实业务样本里抽取一小批,单独留作校准,不求多,几百条就够。

在平台模板里,模型瘦身这一步也可以做成串联任务:蒸馏任务产出小模型 → 剪枝任务产出稀疏模型 → 量化任务产出量化模型。每一步都做一次快速评测,记录 PPL 和关键业务指标的变化,方便定位是哪一步掉的精度。

5. 验收环节:OpenCompass 评测与安全评估

5.1 OpenCompass 基础评测

微调和瘦身做完,最怕的事情是"自我感觉良好,上线就翻车"。我习惯用 OpenCompass 跑一遍标准评测,至少覆盖 MMLU、C-Eval 这类通用基准,再加自己的业务评测集。OpenCompass 的使用方式比较直接,核心是配置模型和数据集。

python run.py \ --models hf /outputs/ppo_model \ --datasets mmlu_gen ceval_gen \ --output /outputs/eval_result \ --reuse

跑完会生成每个任务的分数字典,我一般拿三组数对比:原始底座模型、微调后的模型、量化后的模型。重点看两个东西:微调有没有把通用能力打掉,量化让通用能力跌了多少。如果你的模型是专用助手,MMLU 掉几分可能无所谓,但如果是通用场景,掉 5 分以上就得回头检查训练数据和量化校准了。

OpenCompass 还可以做主观评测,比如alignbench或者你自建的主观问答集。我的习惯是每次把微调前后的模型输出并排抽 50 条,在真实业务 prompt 下人工盲评,"赢、平、输"三档统计胜率。这个虽然累,但比任何指标都更能反映真实体验。

5.2 安全评估:不能只靠通用榜单

安全评估在标题里占了半壁江山,但很多项目实际是上线后才开始补的。我的做法是分成三层跑。

第一层,用现成的安全评测集,比如内容安全、有害指令识别类的题库,批量跑一遍模型,计算"有害内容拒答率"。第二层,自己构造对抗样本,包括恶意指令、隐私窃取、诱导输出违规内容等场景,逐条看模型的反应。第三层,做"多轮越狱"测试——单轮测试拦住不代表多轮引导拦得住,比如通过上下文包装、角色扮演等方式尝试绕过限制。

这里有个平衡点要说明白:安全评估不是在"越严格越好"和"越宽松越好"之间选边,而是看业务定位。完全拒绝所有敏感请求,模型会变成复读机,误伤大量正常问题;完全不设防,出问题的是你自己。我的做法是设一个"红线测试集"和一个"误伤测试集",同时统计拒答率和正常回答率,取一个可接受的工作点。平台模板如果带了安全评估能力的入口,喂同样的测试集,跑完看报表就行;如果没带,自己用脚本批量调用模型接口,统计也很快。

另外,OpenCompass 出来的通用榜单分数,和安全评估没有必然关系。通用榜单高不代表内容安全过关,两件事必须分开测、分开盯。我见过不止一次模型量化后通用能力几乎不掉,但安全拒答能力明显退化,原因就是量化校准集里没有覆盖安全相关的敏感分布。所以量化之后,安全测试集必须重新跑一遍。

6. 我踩过的坑和补救办法

版本对齐问题。LLaMA-Factory 升级速度不算慢,transformers、peft、trl 三个库又各有自己节奏。跑 RM 和 PPO 时,经常出现训练能启动但某个 callback 调用了不存在的 API 的问题。解决办法很土:在平台上把运行环境锁定在一个已知可用版本组合,不要每次都改成最新版。我自己固定过的一个组合是 transformers 4.43.x + peft 0.12.x + trl 0.9.x,跑完整个链路没出幺蛾子。

显存不足问题。PPO 阶段同时加载 policy、reference、RM,显存压力比 SFT 大不少。第一次跑 7B 模型 PPO,我在 24G 卡上开 batch size 1 也 OOM 了,后来把gradient_checkpointing打开,并且顺手把--output_dir里的历史 checkpoint 清理了一下,才算稳定跑完。如果你在平台模板里看到显存相关参数,不用犹豫,先开 checkpointing。

重复微调 instruct 模型问题。有一版我用了 Qwen 的 Instruct 底座做 SFT,训练 loss 倒是正常收敛,但生成结果总带着一股"既想讨好用户又想遵循原本风格"的别扭感。换成 Base 底座重跑之后,风格干净多了。如果你的目标任务已经有一个明确的对齐需求,从 Base 开始会少很多麻烦。

校准集污染评测集问题。之前量化后模型在评测集上分数意外地高,后来一查,量化校准集里混了一部分和评测集同源的数据。重做校准后,分数回落到合理区间,真实业务表现反而比虚高的时候更稳。从那以后我的校准集单独放在一个固定目录,而且评测集和校准集永远不共用。

数据集里的隐性 bias 问题。RM 训练时,我一度发现模型对长回答的偏好特别明显,排查发现 chosed 样本普遍更长,模型根本是在学"写长文=高分"。后来用长度匹配策略重新筛选 pair,把两边长度分布对齐,RM 的排序准确率才真正反映了内容质量问题。

最后留一个习惯性动作

我现在每次拿到一个新模型或新任务,都会先跑一个最小规模的完整链路:小数据量 SFT → 小 batch PPO → 快速量化 → 10 条业务用例人工检查。这一步不花多少时间,但能暴露 80% 的环境和流程问题。平台模板解决的是流程编排的问题,至于效果能不能达标,还得靠你对数据、指标和评估集的较真。把这套链路跑顺之后,再面对更大规模的模型,剩下的就只是资源和耐心的问题了。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询