ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

LLaMA-Factory实战:从数据准备到LoRA微调完整指南

LLaMA-Factory实战:从数据准备到LoRA微调完整指南

把跑通微调的经验沉淀一下,是个挺磨人的过程。我最初是拿现成脚本改,改来改去总会遇到数据格式不对、学习率不合适、一爆显存就得重新来一遍这类问题。后来换到LLaMA-Factory,整体链路才算是理顺了——数据准备、模型加载、LoRA训练、评测、推理、导出合并,它基本都替你封装好了,命令行和网页界面都能用。这篇文章顺着“再探模型训练”的思路,把用LLaMA-Factory实现LLM微调的完整路线拆开讲,内容包括选型原因、环境与数据准备、实战训练流程、调参经验和排错记录,希望能给打算系统上手微调的朋友一份能直接照着做的参考。

适合读这篇的人有两类:一类是已经跑通过一次微调,但用的是自己拼出来的Trainer脚本,想找一个更规范、更好维护的框架;另一类是已经在用LLaMA-Factory,但效果不稳定,想搞清楚调参逻辑和训练时那些日志到底在提示什么。下面按我实际操作的顺序展开。

1. 为什么是LLaMA-Factory?先把选型思路说清楚

1.1 微调工具全家桶对比

做LLM微调,市面上的路线其实不少,我大概分了三类:直接用Transformers写脚本、用平台化工具、以及用社区里那些专项框架。

第一类是“裸写”。依赖transformers、peft、trl这些库,自己写数据处理函数、训练循环、参数组配置。优点是灵活、能精确控制每个环节,但代价是样板代码多。我早期踩坑最多的就是数据collator处理多轮对话时字段错位,一个batch跑起来才发现形状不对。

第二类是各种云平台。界面友好,按量付费,但在数据隐私和模型自由度上容易受限。

第三类就是LLaMA-Factory这类开源框架。它做的事是把前面裸写的那些重复部分全部收敛起来,同时对常见模型架构和微调方式做了兼容。我用的感受是:训练流程确实被简化了,但底层仍然是transformers和peft在跑,并没有黑魔法。

下面用表格简单对比一下:

方案上手成本可控性显存优化适合场景
Transformers裸写高最高手动开gradient_checkpointing等研究原型、自定义训练逻辑
云平台低一般平台已优化快速验证效果
LLaMA-Factory中低高内置LoRA/QLoRA/gradient checkpointing常规SFT、快速迭代、模型导出

1.2 微调方式:全参、LoRA还是QLoRA

LLaMA-Factory支持full、freeze、LoRA、QLoRA这些微调方式,我在项目里最常用的是LoRA和QLoRA。

全参微调理论上效果上限最高,但一个7B模型光权重就占用不少显存,再加上优化器状态和激活值,普通单卡基本扛不住。而且全参训练的风险在于容易把预训练学到的通用能力覆盖掉,训练数据稍有偏斜,模型就变得只会说某个狭窄场景的话了,恢复起来很麻烦。

LoRA的思路是冻结原模型权重,只训练一小部分低秩矩阵作为增量。这让显存占用大幅下降,训练速度也快很多。我理解它的本质是:在原有参数空间附近寻找一个低秩方向来“微调模型行为”,而不是重新塑造整个参数空间。

QLoRA又进一步,用4bit量化后的基座模型加载,再叠加LoRA适配器。它是把一个25G左右的中型模型压到大约6G到8G显存可用,对于那些只有一块消费级显卡的人来说非常实用。效果上,我做领域适配时感觉4bit量化对最终结果影响不大,关键还是看训练数据质量。

1.3 为什么我弃用裸脚本换到它

说实话,我一开始对这类全家桶工具也有点犹豫,担心封装太重、出了问题不好查。用了一段时间后,真正让我留下的理由有三个。

第一是数据预处理内置。alpaca和sharegpt这两种主流格式都能直接吃,只要把字段映射写好,不用自己写对话拼接逻辑。要知道数据拼接一旦出错,训练出来的模型回复会带着夹心般的重复内容和错位标点,Loss再低也没用。

第二是模板体系。不同模型有不同的对话模板,像ChatML、Qwen、Llama各有差异,LLaMA-Factory内置了模板映射。这个细节影响巨大。模板配错了,模型可能训练时loss很低,推理时却吐出一串格式混乱的文本。

第三是WebUI支持,方便做小实验。不过我跑批量训练时还是用命令行加YAML配置,因为可复制、可记录。WebUI适合第一次上手感受整个流程。

当然它也有短板:版本迭代快,不同版本的配置项可能不兼容,升级后旧配置文件可能报错。我的习惯是用virtualenv把环境隔离,并且锁定版本,项目沉淀下来之后尽量不频繁升。

2. 准备阶段:环境、数据格式、模型路径

2.1 硬件要求和显存估算思路

做微调先要解决的是“我的卡能不能跑”。以7B模型为例,BF16精度下权重本身大约14GB,如果你还加载优化器状态和梯度,全参训练基本要超过40GB,这显然不是消费级单卡能承受的。LoRA冻结原模型后,模型主体只做forward,不需要保存梯度,显存大头就变成模型权重加激活值。我的经验是:

  • 7B模型用LoRA,单卡16GB能跑,但cutoff_len别超过2048,per_device_train_batch_size设成1;
  • 7B模型用QLoRA,8GB到12GB也能训练,适合在低显存环境里做小批量迭代。

显存不够时最直接的操作是减小batch_size、开gradient_checkpointing、激活层offload,以及把序列长度cutoff_len压短。很多人舍不得压max_length,但实际上训练数据里大部分样本都不会用到那么长的上下文,略短一点对速度影响很大,效果影响有限。

2.2 安装与版本锁定

我的标准做法是创建一个独立虚拟环境,避免把base环境搞乱。

python -m venv llama_env source llama_env/bin/activate git clone https://github.com/hiyouga/LLaMA-Factory.git cd LLaMA-Factory pip install -e .

安装完可以验证一下:

llamafactory-cli version

不同分支的代码行为差异不小,尤其是新版本加了新参数后,老配置可能直接不识别。所以我在项目里会记录一份requirements和commit号,恢复环境时直接按那份来装。

2.3 数据格式:alpaca和sharegpt

LLaMA-Factory对数据的抽象做得不错,最常用的两种格式是alpaca和sharegpt。

alpaca格式适合单轮指令微调,结构是这样:

[ { "instruction": "为一个短视频写一句宣传文案", "input": "产品是一款便携咖啡机", "output": "三十秒,一杯纯正手冲,把咖啡馆装进口袋。" } ]

input可以为空,没有额外输入就写成空字符串。

sharegpt格式适合多轮对话:

[ { "conversations": [ { "from": "human", "value": "你好,我手机丢了,怎么挂失?" }, { "from": "gpt", "value": "别急,你可以先联系运营商……" }, { "from": "human", "value": "那怎么补卡?" }, { "from": "gpt", "value": "带上身份证去营业厅办理即可。" } ] } ]

数据文件放好后,还要在dataset_info.json里注册。这个注册文件相当于数据集的描述表,告诉工具数据在哪、是什么格式、字段怎么映射:

{ "my_data": { "file_name": "my_data.json", "format": "alpaca", "columns": { "instruction": "instruction", "input": "input", "output": "output" } } }

如果不写columns,默认按alpaca的minimal字段去取;如果字段名不一致,这里必须显式映射。我一开始就是因为没映射自定义字段,训练时发现loss对不上、数据一直被跳过,后面才明白问题出在dataset_info.json。

2.4 模型下载与目录组织

模型可以放在HuggingFace或Modelscope,国内环境下用Modelscope下载更稳。建议下载到本地统一目录,比如/data/models/qwen2.5-7b-instruct。目录里包含config、tokenizer和权重文件。路径建议全英文、不要用软链绕来绕去,容器或训练框架对中文路径支持偶尔会出问题。

3. 完整实操:从零微调一个领域模型

3.1 造一份最小可用数据集

先用小数据把流程跑通再扩量,是我踩过不少坑后的习惯。假如我要做一个客服问答场景,先收集大概几十组规范问答,格式尽量干净,覆盖典型问题、异常问题和闲聊拒绝场景。不需要一开始就追求规模,先把链路跑通,确认训练后模型真的学会了格式,再慢慢加数据。

小数据集要注意一点:样本数太少时loss收敛很快,容易让人误以为效果很好。实际上模型可能只是记住了这批样本的格式,并没有泛化能力。所以小数据阶段重点看流程是否正常,而不是看指标。

3.2 WebUI还是命令行

LLaMA-Factory的WebUI启动很简单:

CUDA_VISIBLE_DEVICES=0 llamafactory-cli webui

浏览器打开后可以选模型、选数据、配参数,训练过程中还能看到loss曲线。第一次跑通流程用WebUI很直观。但我一旦开始批量跑实验,就改用命令行+YAML配置。原因很简单:YAML可以提交到git里做版本管理,每次实验改了什么一目了然;WebUI点出来的配置不容易追溯,换个环境还得重新点一遍。

3.3 训练配置逐项解读

下面是我常用的一份LoRA训练配置:

model_name_or_path: /data/models/qwen2.5-7b-instruct template: qwen stage: sft finetuning_type: lora dataset: my_data cutoff_len: 1024 per_device_train_batch_size: 2 gradient_accumulation_steps: 8 learning_rate: 1.0e-4 num_train_epochs: 3.0 lr_scheduler_type: cosine warmup_ratio: 0.05 lora_rank: 16 lora_alpha: 32 lora_target: all save_steps: 200 output_dir: output/qwen7b_custom

逐个说下关键项:

  • model_name_or_path:基座模型路径。如果要跑QLoRA,再加一项quantization_bit: 4。
  • template:必须和模型匹配。用错模板,对话格式就乱了。
  • stage:做指令微调就是sft,如果需要继续预训练才用pt。
  • finetuning_type:这里是lora。
  • cutoff_len:单条样本最大长度,超出部分截断。我一般设1024或2048,太长训练慢,太短会丢信息。
  • per_device_train_batch_size和gradient_accumulation_steps:二者相乘构成有效batch size。显存小的卡把per_device调成1,用梯度累积把有效batch拉上去。
  • learning_rate:LoRA训练常用1e-5到1e-4之间。起步我习惯用1e-4,数据量大或模型容易训崩时降到5e-5。
  • lora_rank和lora_alpha:LoRA核心参数。rank越大,可学习的参数越多,效果空间越大,但也更容易过拟合;alpha控制缩放比例,我一般让alpha等于2倍rank,比如16对32。
  • lora_target:指定适配哪些模块,新版本可以直接设all,省去手工枚举注意力模块名称的麻烦。

3.4 启动训练与监控

配置写好后,跑训练:

llamafactory-cli train config.yaml

训练过程会输出train/loss,观察它是否平滑下降。loss下降很快说明模型在学习当前数据的模式;如果loss震荡很厉害,优先怀疑学习率过高或batch不稳定。训练中每隔save_steps会保存一次adapter,这些checkpoint后面都能单独加载做推理,也可以挑loss最低的那一步继续评估。

Loss只是参考指标。我见过很多次loss从3降到0.4,生成质量却完全不可用的情况,大多是数据本身有问题,比如标注答案冗长、格式不一致、答案互相冲突。所以训练完一定要真实调用一次模型做定性判断。

3.5 训练后的验证和合并导出

先加载adapter做对话验证:

llamafactory-cli chat \ --model_name_or_path /data/models/qwen2.5-7b-instruct \ --adapter_name_or_path output/qwen7b_custom \ --template qwen \ --finetuning_type lora

验证没问题后,把adapter合并回基座模型,导出一个新的完整模型。这一步的好处是之后部署或再训练,不用每次同时挂基座和adapter:

llamafactory-cli export \ --model_name_or_path /data/models/qwen2.5-7b-instruct \ --adapter_name_or_path output/qwen7b_custom \ --template qwen \ --finetuning_type lora \ --export_dir output/qwen7b_custom_merged

导出的时候记得用与训练时一致的模型路径和template,否则合并出来的模型行为可能和训练时有偏差。

4. 进阶调参思路:影响效果的几个关键点

4.1 LoRA rank、alpha和target_modules怎么定

rank决定可训练参数量。对7B模型,8到64都有见过,多数场景16到32已经够用。我的经验是:如果数据量只有几千条,rank=16比较稳;数据量大且任务复杂,可以上32或64,但要注意过拟合。

alpha可以简单理解成对低秩增量的放大倍数。常见组合是rank=16, alpha=32, 或者rank=32, alpha=64。这个组合不是绝对的,但保持alpha不小于rank通常不会出大问题。

target_modules如果不设all,就要指定q、k、v、o这些投影矩阵。设all在多数情况下能带来更好的效果,但训练和显存开销也稍微大一点。常规任务不追求极致效果时,设all省心。

4.2 学习率、epoch与过拟合

我跑领域微调时,常用的学习率范围是1e-5到1e-4。学习率太大,LoRA增量会把原模型的知识带跑偏;太小,领域特征学不进去。

epoch同样需要警惕。很多初次上手的人习惯用5或10,结果训练完后通用能力掉得很严重。领域数据少的时候,模型跑2到3个epoch基本就把该学的都学了,再多就是反复背样本。

防止过拟合的几条经验:

  • 数据量少时,epoch控制在3以内,或者用early stopping;
  • 数据量多时,一个epoch甚至就够了;
  • 训练集和验证集的loss都看,如果验证loss先升,马上停;
  • 在训练数据里掺一部分通用数据,能明显缓解“只会领域话术、不会正常对话”的退化问题。

4.3 模板和系统提示词的作用

LLaMA-Factory在训练时会按template把对话样本拼成模型需要的格式。这个“拼格式”的过程非常关键,因为推理阶段模型依赖这种格式来区分“用户消息”和“模型回答”。

系统提示词方面,我踩过一个小坑:训练数据里有的样本带system prompt,有的不带,导致模型学的时候很困惑,推理时就时而带、时而不带。现在我的做法是统一风格,要么全部走省事的简约模板,要么全部带上同一句system prompt。

4.4 数据质量大于数据数量

如果说调参决定下限,数据质量决定上限。领域微调真正费时间的环节往往不是训练,而是清洗和整理数据。

我在整理时重点做这几件事:

  • 去除重复样本。重复样本会让模型对个别模式异常敏感,相当于人为放大了它们的权重。
  • 统一标点和格式。答案里的英文、空行、编号风格不一致,模型会学着输出混乱的样式。
  • 处理冲突答案。同一个问题多条互相矛盾的答案会让模型平均化,回答变得模糊。
  • 补全上下文。单轮问答如果缺少前置信息,模型只能从问题本身猜,效果自然差。

把这几步做好,哪怕只有几千条高质量数据,效果也可能超过几万条噪音数据。

5. 常见问题与排查手册

5.1 训练阶段问题

训练阶段的问题大多集中在显存、loss和格式这三方面。下面这份速查表来自我多次实战中遇到的真实情况:

现象常见原因处理方式
CUDA out of memorybatch过大或cutoff_len过长调小batch;开启gradient_checkpointing;降低cutoff_len;改用QLoRA
loss不降且震荡学习率太高或数据杂乱把learning_rate降到2e-5;清洗数据
loss快速到0但效果差数据重复或模型背题去重;减少epoch;加入通用数据
训练报错template不匹配模型和template映射错误按模型类型选template,如qwen、llama
加载模型时OOM基座模型占用过大加quantization_bit: 4;用QLoRA

5.2 推理阶段问题

训练结束后,推理阶段也可能出现各种“看起来训练成功了但实际不可用”的情况。我这里列几个高频问题:

模型回复乱码或输出格式错乱,大概率是template选错了,导致模型把特殊token当成普通文本生成。输出内容总是带英文或重复句子,往往是训练数据里有大量同类坏样本,模型学到了重复的说话习惯。模型只会复读训练数据中的答案,说明数据多样性不够或epoch过高。

还有一种常见情况:微调后通用能力明显下降。通常是因为领域数据占比太高且epoch太大,预训练得到的通用知识被覆盖了。解决办法是在数据集中掺入适量百科问答、日常对话等通用数据,并且严格限制训练轮次。

5.3 我的几条实用建议

第一,任何训练前先把batch size调到1跑几十步,确认程序能正常启动,再恢复真实配置。很多配置错误都能在这一步现形。

第二,保存的adapter体积一般只有几百MB,不要每次都导出合并模型,先用chat模式验证好效果再合并。

第三,训练日志要留。每个实验对应一份YAML、一份日志、几条验证对话,后面对比效果时能省很多时间。

第四,对Loss要有合理预期。领域微调的loss曲线只代表模型对训练集的学习程度,最终要回到业务场景做评估,用真实数据抽样验证才是硬道理。


最后聊一点个人感受。我最初总觉得微调是个靠技术硬碰硬的事,后来发现真正拉开效果差距的,往往是对数据的理解和训练过程的细节把控。LLaMA-Factory把流程标准化之后,省出来的时间几乎都该花在“数据该怎么做、模型到底要学什么”这两个问题上。做领域微调时,别急着堆数据,先拿小样本跑通全流程、确定模板正确、验证效果方向对,再放大规模,这条路走得比我一开始硬着头皮全量训练顺畅得多。希望这篇文章能帮你少走几个弯路。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进