☰
LLaMA-Factory继续训练:从检查点恢复微调实战指南
2026/10/22 9:48:09 网站建设 项目流程

做微调训练最怕什么?我最怕的就是训到一半突然断掉。机器重启、显存不够被系统杀掉、机房断电、甚至你自己不小心把进程Ctrl+C了……任何一个意外,都可能让几十个小时的算力投入直接清零。LLaMA-Factory的继续训练(Resume Training)功能,就是专门用来对付这种“意外”的。它允许你从最近的检查点(checkpoint)接着往下训,而不是重新跑一遍数据,既省时间也省预算。

这篇文章我会把我实际用LLaMA-Factory做继续训练的完整经验整理出来,包括底层机制、参数配置、命令行和WebUI两种操作路径,以及我在反复训练中踩过的坑。适合正在用LLaMA-Factory做LoRA或全量微调、又担心训练长跑翻车的人参考。不管你是刚入门微调的新手,还是已经在做多个模型训练任务的老手,这篇文章应该都能帮你少走一些弯路。

1. 继续训练到底在解决什么问题

1.1 训练中断是常态,不是意外

大模型微调不像写个小脚本跑几分钟就有结果。哪怕是单卡跑一个7B模型的LoRA训练,几十万条样本跑上四五个epoch,通常也要好几个小时;你要是做全量微调或QLoRA,时间更是不敢算。这么长的时间窗口里,出点什么状况太正常了。

我自己碰过的就有好几种:

  • 训练服务器因为内存压力被操作系统OOM杀掉,container直接退出;
  • 在同一台机器上并发跑另一个任务,显存撞车,cuda out of memory,主进程崩溃;
  • 远程SSH断连,训练进程跟着挂掉;
  • 机房统一维护,节点直接被回收。

最难受的不是训练报错,而是报错之后发现没有保存检查点,所有进度白跑。这时候你就知道“继续训练”不是锦上添花的功能,而是保命功能。

1.2 LLaMA-Factory的继续训练能力画像

LLaMA-Factory是目前社区里用得相当广泛的大模型微调框架之一,支持从数据处理、模型加载到LoRA/QLoRA/全量微调、评估推理的一整套流程。它的继续训练能力,本质上继承自Hugging Face Transformers的Trainer机制,但做了不少贴合实际使用的封装。

具体来说,它支持两种容易被混淆的“接着训”:

一种是从训练中断点恢复,也就是本文的主角。训练过程中自动保存的checkpoint目录里包含了模型权重、优化器状态和训练步数等信息,恢复训练时会把这些内容全部加载回来,接着上一次的step继续。

另一种是基于已训练好的LoRA adapter继续做增量训练。这种场景是你之前完成了一轮训练,得到了一个adapter文件夹,现在想用新数据继续在上面训练。这种操作本质上不是“恢复”而是“接着升级”。

这两者千万别搞混。前者走的是resume_from_checkpoint路线,后者走的是adapter_name_or_path路线。如果你把这两种参数用反了,轻则训练步数重置,重则直接报维度不匹配之类的错误。

1.3 哪些场景必须依赖继续训练

从我的经验看,下面这几种场景你几乎绕不开继续训练:

  • 长周期全量微调。全量微调每个step都更新全部参数,显存占用高,跑的时间长,中断概率也高。没有检查点恢复机制,基本等于赌博。
  • 共享训练集群环境。很多实验室和公司只有几块GPU,排队排半天才能轮到你的任务。如果训练中断后需要重新抢资源,再浪费一轮排队时间,心态容易崩。
  • 分阶段训练策略。比如我先用小学习率跑预热,再切到大学习率,或者先在通用语料上训一部分,再切到垂直领域数据。这种多处运行、多次加载的场景里,每次都能从指定step继续,整个流程才有可操作性。
  • 预算敏感的云上训练。按小时计费的话,中断后从头跑一次就是白烧钱。

所以,如果你正在做需要长时间训练的项目,先把继续训练玩明白,再开跑大任务,这是我在实操里最大的一个建议。

2. 继续训练背后的机制与关键参数

2.1 检查点目录里到底存了什么东西

很多人以为“保存检查点”就是把模型权重存一份。其实不止。Transformers Trainer保存的checkpoint目录,在LLaMA-Factory的output_dir下面通常是checkpoint-xxx这种名字,xxx表示训练步数。

这个目录里的内容比较核心的是这几类:

  • 模型权重文件(pytorch_model.bin或safetensors格式);
  • 优化器状态(optimizer.pt);
  • 调度器状态(scheduler.pt);
  • 训练状态(trainer_state.json,记录当前步数、损失等);
  • 如果有LoRA,还会score相关的adapter权重文件(adapter_model.bin/safetensors、adapter_config.json)。

其中optimizer.pt和scheduler.pt是能否真正“无缝继续”的关键。如果只存了模型权重没存优化器状态,恢复训练后优化器的动量、学习率调度器的位置全丢了,虽然也能接着训,但效果和从头训一个新任务没太大差别,前面的预热基本白费。

我在实际项目里就碰到过一种情况:因为设置了save_only_model=True,训练数小时中断后从checkpoint恢复,loss曲线直接从头下探,明显优化器没有带上。所以如果你依赖继续训练,这个参数千万不要开。

2.2 决定恢复边界的几个配置项

LLaMA-Factory的配置主要在训练参数里,和原生Transformers保持一致,配置项看起来不少,但真正和继续训练强相关的就那么几个。

save_strategy:什么时候保存检查点,可选no、steps、epoch。要做继续训练,这个就不能是no。按epoch保存适合短训练,每轮结束才存一次,如果一轮要跑很久,中途断掉还是会丢大量进度。我倾向于按steps保存,并且间隔不要太大。

save_steps:配合save_strategy=steps使用,控制每隔多少步存一次。这个值直接影响恢复精度。举个例子,你每500步存一次,如果训练在第1001步崩了,恢复后只能回到第1000步,等于损失了1步的进度。如果每5000步存一次,崩在4900步,恢复后就丢4900步的进度,可能白跑很久。所以实际训练中,我一般把save_steps设成总步数的1%-5%,既不会太频繁拖慢训练,也能把丢失范围控制在可接受区间。

save_total_limit:控制最多保留多少个检查点。这个参数很实用,因为每个checkpoint动辄几个GB甚至几十GB。如果无限制保存,几个checkpoint就能撑爆磁盘。我曾经遇到过磁盘被checkpoint填满、导致训练进程自己崩掉的情况,后来设置save_total_limit=3,老checkpoint自动清理。

resume_from_checkpoint:这是继续训练的开关。传一个checkpoint目录路径,Trainer会加载对应检查点,并把trainer_state里的步数恢复。注意,这里传给它的路径一般是output目录/checkpoint-xxx,而不是output根目录。

这几个参数的配合逻辑可以用一句话总结:用save策略“决定存多少、存多勤”,用resume参数“决定从哪继续”。它们互相配合,组成了继续训练的完整闭环。

2.3 与原生Transformers和LoRA的适配细节

LLaMA-Factory在处理LoRA恢复时有一些自己封装好的逻辑。你只要给它--resume_from_checkpoint ./output/checkpoint-1380,它会判断当前训练方式:

  • 如果是LoRA,checkpoint里保存的是adapter权重,加载完后不需要合并到base模型,直接继续训adapter;
  • 如果是全量微调,checkpoint保存的是完整模型权重,加载完后接着更新全部参数。

这个封装对使用者非常友好,因为我早期直接用原生Transformers脚本时,LoRA恢复要手动处理adapter加载、模型初始化等很多细节,经常搞混。而LLaMA-Factory把这些都收敛到了统一的参数入口里,至少省掉了八成踩坑概率。

另外要提醒一句:继续训练时传入的model_name_or_path、dataset这些配置,最好和首次启动时保持一致。Trainer恢复时主要依赖的是checkpoint内部状态,但如果你换了数据集或模型路径,很多隐性问题会在几个step后才暴露出来,比如loss异常波动、显存逐步增长等。配置一致性是继续训练最容易被忽视的前提。

3. 实操全流程:从配置到断点恢复

3.1 环境准备与初始配置

我假设你已经装好了LLaMA-Factory,并且手头有一份能跑的微调配置。如果你还没装,直接看官方README,按环境克隆、安装依赖就行。这里我以自己的实践环境为例,大致是:

  • 一台有至少一块24GB显存GPU的服务器(我做的是7B模型QLoRA);
  • CUDA 12.x,PyTorch 2.x;
  • LLaMA-Factory的main分支版本。

我做的任务是领域指令微调,训练数据是整理好的json格式,包含instruction和output字段。数据集配置放在LLaMA-Factory的data/dataset_info.json里注册。这个注册步骤不能漏,漏了的话框架找不到数据集,连首次训练都跑不起来。

首次启动我用的命令大致长这样:

CUDA_VISIBLE_DEVICES=0 python src/train.py \ --stage sft \ --model_name_or_path /path/to/base_model \ --dataset my_instruction_data \ --template default \ --finetuning_type lora \ --output_dir ./output/my_sft_run \ --overwrite_output_dir \ --per_device_train_batch_size 2 \ --gradient_accumulation_steps 8 \ --learning_rate 2e-4 \ --num_train_epochs 3.0 \ --lr_scheduler_type cosine \ --logging_steps 10 \ --save_steps 200 \ --save_total_limit 3 \ --save_strategy steps \ --plot_loss True

注意这里我故意写了--overwrite_output_dir,因为这是第一次全新开跑。等跑起来之后,output目录下面会开始出现日志和checkpoint-200、checkpoint-400这样的目录。

有个细节值得留意:save_steps 200对于几千步的训练来说是合理的间隔,既能细粒度恢复,也不会频繁写盘拖慢训练。如果你的训练只有几百步,可以把间隔缩小到50甚至100。

3.2 模拟一次中断:fuser与kill

为了测试继续训练,我自己实践时专门把训练停掉了一次。比较文明的停法是发SIGINT,让Trainer有机会在退出前做一次检查点保存;但在真实世界里,更多是直接崩溃,根本没机会优雅退出。所以我干脆用kill直接杀掉进程,模拟最恶劣的情况。

# 找到训练进程PID ps aux | grep train.py # 直接杀死进程,模拟意外崩溃 kill -9 <pid>

进程杀掉之后,看output目录,发现检查点还停留在checkpoint-600(如果杀之前刚好跑到600步),说明进程没有机会保存更多进度,这是意料之中的。这种情况下,我们能恢复的最远位置就是checkpoint-600。

3.3 从检查点恢复训练

接下来就是重头戏。我把启动命令里的输出目录改成“不覆盖”,并加上--resume_from_checkpoint参数:

CUDA_VISIBLE_DEVICES=0 python src/train.py \ --stage sft \ --model_name_or_path /path/to/base_model \ --dataset my_instruction_data \ --template default \ --finetuning_type lora \ --output_dir ./output/my_sft_run \ --resume_from_checkpoint ./output/my_sft_run/checkpoint-600 \ --per_device_train_batch_size 2 \ --gradient_accumulation_steps 8 \ --learning_rate 2e-4 \ --num_train_epochs 3.0 \ --lr_scheduler_type cosine \ --logging_steps 10 \ --save_steps 200 \ --save_total_limit 3 \ --save_strategy steps \ --plot_loss True

关键变化就两个:一是去掉了--overwrite_output_dir,二是加了--resume_from_checkpoint。

启动后观察日志,很快能看到类似这样的输出:

Continuing training from checkpoint: ./output/my_sft_run/checkpoint-600 Current training epoch: 0.67 (current step: 600)

看到这个提示,基本说明恢复成功。Trainer会接着从第600步继续向后训练,学习率调度器也会根据第600步对应的位置重新规划后续的衰减曲线,而不是从0重新计算。

训练跑下去之后,我特意再检查了loss曲线整体走向。正常情况下,恢复点前后的loss不会有跳变,曲线平滑过渡。如果loss在恢复后明显飙升,那多半是数据集顺序换了、优化器状态没保存、或base模型路径不一致。

3.4 用WebUI方式做继续训练

如果你偏好图形界面,LLaMA-Factory的WebUI也支持继续训练。启动WebUI:

python src/webui.py

打开浏览器进入训练页,其他配置照常填,关键在“训练参数”或“保存/恢复”相关区域,勾选或填写“resume_from_checkpoint”,把路径填成output目录下的checkpoint-600。同时确认“overwrite_output_dir”不要勾选。

WebUI的优点是参数可视化、不容易漏项;缺点是如果你要跑批量、自动化的训练脚本,命令行更高效。我的建议是:小规模实验用WebUI快速验证,正式长时间跑任务用命令行脚本,配合日志监控更稳。

3.5 验证恢复结果是否正常

恢复训练后,不要直接放任它跑完就收工。我一般会做三个层面的验证:

第一,看日志步数是否连续。恢复后的第一行日志步数应该从600开始,而不是从0开始。如果从0开始,说明加载的路径可能没生效,或者checkpoint里缺少trainer_state。

第二,看loss曲线是否连续。把第一次运行的loss曲线和恢复后的loss curve放在一起看,恢复点附近不应该有明显断层。训练中期出现小幅波动是正常的,但如果loss突然翻倍甚至变成NaN,就要立即停掉排查。

第三,看最终产物是否可用。训练完成后,LLaMA-Factory会在output目录生成adapter权重文件。我在微调完成后会打开Chat/推理页面加载这个adapter,问几个和领域相关的问题,确认输出不是胡言乱语。这一步虽然和继续训练本身关系不大,但能验证整个流程的最终结果是真的可以用的。

3.6 YAML配置方式:适合自动化流水线

除了纯命令行参数,LLaMA-Factory也支持YAML配置文件方式。它的好处是可以把整套训练参数固化成一个文件,配合版本管理,调试和复现都很方便。对于需要反复做继续训练的实验,我会这样组织:

# resume_train.yaml stage: sft model_name_or_path: /path/to/base_model dataset: my_instruction_data template: default finetuning_type: lora output_dir: ./output/my_sft_run resume_from_checkpoint: ./output/my_sft_run/checkpoint-600 per_device_train_batch_size: 2 gradient_accumulation_steps: 8 learning_rate: 2e-4 num_train_epochs: 3.0 lr_scheduler_type: cosine logging_steps: 10 save_steps: 200 save_total_limit: 3 save_strategy: steps plot_loss: true

然后启动:

python src/train.py examples/resume_train.yaml

这样整个恢复流程就变成一条命令的事。我在同时管理多个训练任务时,基本都靠这种配置方式,而不是每次去拼一长串命令行参数。

4. 常见的坑与排查经验

4.1 恢复后训练步数不对

这是一个特别容易让人觉得“是不是我操作错了”的问题。如果你传给resume_from_checkpoint的是output根目录,或者是一个不包含trainer_state.json的目录,Trainer可能不会报错,但也不会恢复步数。我之前就干过这种事:把resume_from_checkpoint填成./output/my_sft_run而不是./output/my_sft_run/checkpoint-600,结果日志显示从0开始重新计数。

排查方法很简单:在启动日志里搜索“Continuing training from checkpoint”这行提示,确认它后面带的路径是你想要的checkpoint目录;再看“Current step”是否是预期数值。

4.2 检查点被自动清理,恢复点没了

save_total_limit如果设得太小,老的checkpoint会被自动删除。设想一个极端情况:你设了save_total_limit=2,训练到800步时,磁盘里只有checkpoint-600和checkpoint-800。如果训练崩在第1000步,你只能恢复到800步,还算好。但如果你设了save_total_limit=1,崩的时候磁盘里只有最新的checkpoint,恢复点可能离崩溃点很远,丢失进度很多。

所以,对于长训练,我通常把save_total_limit设到3-5个,并且保证磁盘余量充足。千万不能因为省磁盘而把恢复路径给“省”没了。磁盘满了崩掉是最冤的一种崩法。

4.3 LoRA继续训练时adapter加载异常

LLaMA-Factory对LoRA恢复本身封装得很好,但如果你在checkpoint目录里发现adapter文件缺失(比如只有模型权重没有adapter_config.json),那就要检查首次训练时是否把finetuning_type设置成了full或lora以外的类型。全量微调产生的checkpoint和LoRA产生的checkpoint内容完全不同,混用来恢复大概率报错。

还有一个容易忽略的点:如果你上一次训练用的是QLoRA,这次恢复时也要保持相同的量化参数(bits、double_quant等)。改动量化配置等于要求模型以不同的位宽重新展开,通常会出现意料之外的loss异常。

4.4 显存占用比首次训练更高

有朋友问过我,为什么恢复训练后显存占用比首次训练多了几个GB。这个现象通常发生在恢复点之后不久,常见原因是优化器状态完整加载后,占据了更大的驻留显存。首次训练启动时优化器是逐步增长的,恢复时则一次性加载全部状态,瞬时峰值会更高。

解决办法很简单:恢复启动时把per_device_train_batch_size临时调小一点,等训练稳定后如果显存有余量再调回去。虽然不能完全避免,但能降低被OOM杀掉的概率。

4.5 数据集顺序变化导致恢复后loss跳变

Transformers的Trainer在恢复训练时会重新从数据集的当前step对应的位置开始读取。如果你的数据加载逻辑里包含shuffle,并且没有固定随机种子,恢复后数据顺序会和崩溃前的预期顺序不一致,这理论上会导致loss出现阶梯式跳变。

LLaMA-Factory在数据处理上做了不少封装,大多数情况下shuffle是可控的,但我在实践中还是建议:要么固定seed,要么显式关闭shuffle(针对有序训练数据)。稳定的数据顺序,是保证“恢复后loss连续”的前提之一。

下面这个表是我整理的继续训练常见问题速查:

现象大概率原因排查/解决方案
恢复后步数从0开始resume路径给错或没生效检查log中的Continuing提示,确认路径到checkpoint-xxx
loss在恢复点大幅跳变数据集顺序或seed不一致,优化器状态丢失固定seed,确认save_only_model=False
报找不到optimizer.pt保存策略没存优化器检查save_only_model等参数
checkpoint目录总被删save_total_limit太小调大到3-5,同时监控磁盘余量
恢复后OOM优化器状态一次性加载峰值高临时调小batch_size,稳定后再调回
adapter_config.json缺失首次训练不是LoRA/QLoRA确认finetuning_type一致

4.6 检查checkpoint完整性的小技巧

为了不等到恢复时才发现checkpoint坏了,我养成的一个习惯是:训练过程中的每个保存点,在后台定期检查目录大小和关键文件是否存在。

ls -lh ./output/my_sft_run/checkpoint-*/

至少确认每个checkpoint里有trainer_state.json和模型权重文件。如果发现某个checkpoint只有几MB大小,基本可以断定写入不完整,这种checkpoint即使能加载,恢复后的状态也是残缺的。LLaMA-Factory的checkpoint写入是写临时文件再重命名,理论上原子性还行,但分布式环境里偶尔还是会有意外,多留个心眼总没坏处。

5. 一些想特别分享的经验

说了不少配置和步骤,最后说点我在实操中沉淀下来的习惯,可能比参数表更有用。

第一个习惯是把训练脚本设计成可重入的。也就是说,任何时候杀掉脚本重新启动,它都能从最近的checkpoint自动接着跑,而不是每次都要手动改路径。实现方式很简单:写一个启动脚本,动态检测output目录下最新的checkpoint目录,把它作为resume_from_checkpoint传给训练命令。这一步能大大减少长训练的心智负担。

我经常用的一段bash逻辑大概是这样的:

latest_ckpt=$(ls -d ./output/my_sft_run/checkpoint-* 2>/dev/null | tail -n 1) if [ -n "$latest_ckpt" ]; then RESUME_ARG="--resume_from_checkpoint $latest_ckpt" else RESUME_ARG="" fi python src/train.py ... $RESUME_ARG

只要保持输出目录不变,任何时候崩溃了,重新跑一下这个脚本就行。这个模式在定时任务、自动重试场景里尤其好用。

第二个习惯是观察恢复后的前几十步。恢复训练的头几十个step是最容易暴露问题的窗口。我会在这段时间密切关注loss和日志,如果前100步内出现异常,立刻停掉排查;如果前100步平稳,基本可以放心挂机让它多跑一会儿。这比无脑挂机、跑完再看结果要稳得多。

第三个习惯是训练前先做一次“预演中断”。我拿到一个新数据集或新配置时,会故意先跑一小段(比如100步),然后杀掉进程,再恢复训练。这一步花费的时间不足半小时,但能把环境问题、路径问题、配置问题全部提前暴露出来。真正开始长训练时,心里会踏实很多。

第四个经验是保存频率要和你的KPI对齐。如果你的训练目标是一条平滑的loss曲线,那么太稀疏的保存会让恢复后的曲线出现小平台;如果你的目标只是最终能出一个可用的adapter,那保存频率低一些也无妨。关键是你得清楚自己在意的是“过程”还是“结果”,然后再决定save_steps。

回到开头那个问题——训练中断是不是最可怕的事?我觉得,最可怕的不是中断本身,而是没有能力优雅地应对中断。LLaMA-Factory的继续训练功能,加上一套合理的保存策略和恢复脚本,能让训练这件事从“听天由命”变成“尽在掌握”。至少对我来说,在搭好这套机制之后,再遇到GPU被抢占或者进程被杀,我不再会心跳加速,顶多骂一句,然后重新跑一遍启动脚本,继续该干嘛干嘛。

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

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

立即咨询