LLaMa-Factory继续训练指南:从断点续训到流程接力 说到LLaMa-Factory很多人第一反应是“一个界面点一点就能微调大模型的工具”。再往里问一句“训练到一半断了怎么办”或者“我想拿上一轮的训练结果接着跑下一轮数据怎么搞”不少人就开始卡壳了。这个操作对应到代码里就是 Resuming Training——继续训练。我第一次认真研究它是因为某个领域知识问答项目跑增量预训练到第三天凌晨节点掉卡训练进程直接挂掉。如果不懂从checkpoint继续那前三天投进去的算力和时间就全白费了。这篇文章就把LLaMa-Factory里的“继续训练”完整讲透重点覆盖两种场景训练中断后的断点续训以及基于已有检查点开启新一轮训练任务。适合那些已经在用LLaMa-Factory做微调、但一碰到“接着训”就不知所措的开发者尤其是跑过增量预训练、SFT却还没把整个训练生命周期串起来的同学。1. 继续训练不是“重新开始”先把场景分清1.1 断点续训训练过程中的“读档”断点续训是最容易理解的继续训练模型已经跑到第800步结果GPU掉了、服务器重启了、或者你手动CtrlC想改参数训练停了。断点续训要做的就是从最近一次保存的checkpoint位置“读档”把模型权重、优化器状态、学习率调度器状态、随机数状态全部恢复然后接着第800步往后跑而不是从第0步重来。用游戏存档来类比非常贴切。从头训练相当于新建游戏从第1关开始打断点续训相当于读取第800步那个存档装备、技能、敌人进度全都在继续打第801关。很多人忽略的一点是checkpoint里不仅存模型权重还存优化器的动量和学习率调度器的进度。如果只把模型权重加载回来训练确实能跑但Adam的动量是空的学习率也会从初始值重新走那样的“继续训练”实际上是“换了个初始化重新训练”前800步积累的优化状态全丢了。在LLaMa-Factory里断点续训一般通过配置 resume_from_checkpoint 来实现。你不需要手动去指定到底是哪个checkpoint它会自动在输出目录下找到最近的一次保存记录然后从那个位置把状态拉起来。早期版本里很多人会手动加载模型路径那样不是真正的续训只是换了个起点重新训练这两者差别非常大。1.2 基于已有检查点继续新任务第二种继续训练更常被叫作“二次训练”或“基于已有结果继续前进”。典型场景有三个增量预训练通用模型在领域语料上继续做自回归训练让模型具备领域词汇和知识这类任务的stage是pt。继续指令微调前一轮SFT数据不够或者质量不高补齐数据后在上一轮SFT结果的基础上继续跑几轮stage仍是sft。流程接力先在领域语料上做增量预训练再在这个结果上用指令数据做SFT之后还可以接DPO偏好优化每一段都以上一段的结果为起点。这类“继续训练”和断点续训不一样。它更像是“通关之后换个地图继续玩”虽然也加载之前的权重和checkpoint但任务目标变了、数据集变了、甚至stage都可能变了所以优化器状态不是必须恢复的学习率调度器也往往需要重置。LLaMa-Factory对这种场景的常规做法是把上一轮的checkpoint目录作为模型起点或adapter起点传给下一次训练任务。这里有个容易踩的坑上一轮如果用的是LoRA微调这一轮继续训练时你要加载的应该是保存下来的adapter路径而不是原始基座模型路径。很多人觉得反正adapter是插在基座上的我把基座模型传进去再重新微调不就等于继续了吗不对那样前期训练出来的LoRA增量就完全没有被用上。1.3 两种“继续训练”在LLaMa-Factory里的入口差异LLaMa-Factory给这两种场景提供了不同操作路径。断点续训走的是“恢复现场”路线在训练配置里打开resume开关训练器会自动读取trainer_state.json里的step、epoch信息并且从对应checkpoint加载优化器状态。续训前后的实验是连续的数据集、模板、微调方法一般不能改动。基于已有检查点开始新任务走的则是“模型加载”路线。比如微调方法选LoRA那么把上一轮的checkpoint目录填到adapter_name_or_path里基座模型还是填原来的基座如果上一轮是全量微调那么直接把checkpoint目录填到model_name_or_path里。这种做法的本质是把上一轮的产物当作本轮训练的初始化条件而不是恢复训练过程。很多人在社区提问“为什么我resume之后loss不对”多半是把这两种思路混在一起了他明明想基于上一个任务的结果开始新任务却开了resume_from_checkpoint结果数据集换了、模板换了训练曲线乱七八糟。所以动手之前先问自己一句我是要接着上次没跑完的步骤继续还是要把上次跑完的结果当新起点来用这个判断决定了后面所有配置。2. LLaMa-Factory的检查点与状态文件理解续训的地基2.1 checkpoint目录里到底放了什么在LLaMa-Factory里每次周期性保存都会生成一个目录一般在output_dir下命名类似 checkpoint-500、checkpoint-1000对应的就是训练到第500步、第1000步时保存的快照。打开这个目录通常能看到checkpoint-800/ ├── config.json ├── model.safetensors ├── optimizer.pt ├── scheduler.pt ├── rng_state.pt ├── trainer_state.json ├── training_args.bin ├── adapter_config.json # LoRA训练时才有 └── adapter_model.safetensors # LoRA训练时才有model.safetensors是模型权重也就是网络参数本身。LoRA训练时保存的是adapter_model.safetensors它记录的是一组低秩矩阵的增量体积很小比如7B模型可能只有几十MB到一两百MB。optimizer.pt存储的是优化器的内部状态Adam优化器里有每个参数的一阶动量矩和二阶动量矩这些就是所谓的“动量”它告诉优化器之前参数往哪个方向更新过、更新幅度大概是多少。scheduler.pt记录学习率调度器当前的位置比如现在到了第800步当前学习率应该是从初始值衰减后的某个值这些都要存下来。rng_state.pt记录的是随机数生成器状态包括torch、numpy、Python内置random的种子状态用来保证继续训练时数据打乱、dropout等随机行为能够按原顺序延续。trainer_state.json则是一个纯文本状态文件里面记录了global_step、epoch、最佳指标、日志历史等是判断“该从哪一步续”的关键索引。LLaMa-Factory和底层Trainer会依靠这个文件找到最近的checkpoint。2.2 优化器状态和训练状态决定你是“续跑”还是“重跑”为什么断点续训必须同时恢复optimizer.pt和scheduler.pt因为深度模型训练不是简单的梯度下降Adam在更新参数时走的实际步长不仅取决于当前梯度还取决于历史梯度积累的动量信息。比如前800步已经走了很长一段平滑下降的路径此刻动量的方向本身就代表一种“惯性”。如果只恢复模型权重不恢复动量训练一开始梯度方向很可能和之前不一致loss容易出现明显的抬升之后要花不少步数重新建立动量等于浪费了续训的意义。调度器状态同样重要。很多训练配置使用cosine或linear衰减策略学习率会随着训练步数从初始值逐渐降到接近0。如果不恢复scheduler.pt续训时学习率从初始值重新开始那么训练动态就和之前完全不同甚至可能因为学习率过大导致loss爆炸。所以判断一个继续训练操作到底是“续跑”还是“重跑”最直接的方法就是看optimizer.pt和scheduler.pt有没有被加载。在LLaMa-Factory里用resume_from_checkpoint实现的是“续跑”而通过model_name_or_path或adapter_name_or_path加载上一轮结果时只是加载了权重优化器状态、调度器状态都会重新初始化那是“重跑”。2.3 给output_dir做版本规划省去80%的续训报错大多数续训异常根因不是代码问题而是目录混乱。LLaMa-Factory会在output_dir下自动按模型名和启动时间生成子目录时间戳一多你根本分不清哪个目录是上一轮跑完的、哪个是跑一半崩掉的。我现在的做法是在配置里手动规划output_dir用“模型简称_日期_训练阶段_版本”作为目录名。比如我之前做某领域问答项目目录结构是这样的output/ ├── qwen2.5-7b_domain_pt_20250101/ ├── qwen2.5-7b_sft_v2_20250105/ └── qwen2.5-7b_dpo_20250108/命名本身就是一份实验记录。谁在前谁在后、谁是谁的输入扫一眼目录名就清楚了。续训报错里有一种特别常见的情况你本来想加载上一轮已经完成的checkpoint-1200结果output_dir写错了启动时自动匹配到了几周前跑了一半、只有checkpoint-300的目录整个训练曲线全乱了。另外训练过程不要随便手动删除或移动checkpoint目录内的文件。尤其是LoRA场景下LLaMa-Factory在adapter_config.json里记录了基座模型路径等信息你把checkpoint目录挪到一个新的绝对路径基座路径可能还能用绝对路径指向但一旦文件缺了adapter_model后续加载就会报错。规范命名加不随意挪动是续训最便宜也最有效的保障。3. 实操断点续训与基于检查点开启新一轮训练3.1 断点续训标准姿势resume_from_checkpoint在LLaMa-Factory中断点续训的标准配置是在训练YAML里加上一行resume_from_checkpoint。假设你原本的SFT配置如下model_name_or_path: qwen2.5-7b template: qwen stage: sft finetuning_type: lora lora_rank: 8 lora_target: all dataset: sft_train_v1.json cutoff_len: 2048 per_device_train_batch_size: 2 gradient_accumulation_steps: 8 learning_rate: 2e-4 num_train_epochs: 3.0 output_dir: output/qwen2.5-7b_sft_v1_20250105训练到第1000步时中断了现在要续训只需要在该配置基础上增加resume_from_checkpoint: true其他训练参数不要动尤其是数据集、模板、cutoff_len、num_train_epochs这些必须和之前一致。然后重新启动llamafactory-cli train resume_sft.yaml训练器会自动在output_dir下找到最近的checkpoint目录恢复模型权重、优化器状态和训练步数并从第1000步继续。注意一个关键点num_train_epochs仍然要填3.0而不是已经跑过的“剩余epoch数”。因为trainer_state里记录的是总目标和当前进度Trainer会根据“目标3个epoch”和“已完成1.2个epoch”自动计算剩余量。如果你自作聪明填了1.8系统可能认为训练总目标变了调度器和步数计算就会错乱这是非常典型的续训参数坑。还要注意如果你的运行环境发生了明显变化比如换了一台GPU机器、PyTorch版本不一致optimizer.pt的加载可能报错这种情况下可以被迫退化为“只加载权重重新跑”但学习率要记得调低曲线波动也会更明显。我的经验是尽量在同一套环境和同一版本镜像里做续训跨环境续训带来的隐性风险远高于表面看到的便利。3.2 从上一轮结果继续增量预训练之后接指令微调这是LLaMa-Factory里最常用到的流程也是“继续训练”最能发挥价值的场景。某项目需要模型掌握垂直领域的术语和业务知识只靠通用基座做SFT效果一般通常做法是先做增量预训练再做指令微调。第一步用领域纯文本语料做增量预训练。配置文件大致长这样model_name_or_path: qwen2.5-7b template: qwen stage: pt finetuning_type: lora lora_rank: 16 lora_target: all dataset: domain_corpus.json cutoff_len: 2048 per_device_train_batch_size: 2 gradient_accumulation_steps: 16 learning_rate: 2e-4 num_train_epochs: 1.0 output_dir: output/qwen2.5-7b_domain_pt_20250101跑完后目录里会保存基于LoRA训练的adapter文件。第二步要在这个基础上做指令微调配置文件里最关键的是adapter_name_or_pathmodel_name_or_path: qwen2.5-7b adapter_name_or_path: output/qwen2.5-7b_domain_pt_20250101/checkpoint-1000 template: qwen stage: sft finetuning_type: lora lora_rank: 16 lora_target: all dataset: sft_train_v1.json cutoff_len: 2048 per_device_train_batch_size: 2 gradient_accumulation_steps: 8 learning_rate: 1e-4 num_train_epochs: 2.0 output_dir: output/qwen2.5-7b_sft_v2_20250105这里有一个非常重要的细节增量预训练时的lora_rank、lora_target必须和后续SFT完全一致。如果第一轮LoRA的rank是16第二轮把rank改成了8那么上一轮adapter的形状和这一轮的新adapter无法对齐加载会直接报错。lora_target也一样第一轮如果只target了q和k第二轮改成all同样会因为模块不匹配导致加载失败。另一个细节是这个新任务的learning_rate。我通常会在前一轮基础上适当调低比如从2e-4降到1e-4。因为上一轮已经让模型参数到了一个相对较优的区域第二轮再用太快的学习率容易破坏已有知识。3.3 续训前必须核对的一致性清单不管是哪种继续训练开始前都要过一遍配置一致性。我自己整理过一份清单每次续训前对照检查基座模型路径和上一轮是否一致。不一致会导致加载上下文错乱尤其当adapter是从某个特定基座训练出来的换基座后无法加载。微调方法LoRA就还是LoRA全量就还是全量。LoRA和全量参数微调的产物不能互相加载。LoRA关键参数rank、lora_target、lora_alpha等要保持一致。模板上一轮用qwen模板这一轮换成了别的模板对话格式不匹配loss会出现异常波动。数据格式增量预训练用纯文本SFT用指令对格式混用轻则报错重则训练出来的模型行为完全不对。tokenizer如果换了一个不同的tokenizer模型词表维度都变了权重加载大概率失败。这份清单看着简单但我在实际项目里见过好几次因为模板或数据格式不一致导致的隐性错误。训练Loss看起来还在降模型生成出来的内容却完全不是那么回事。4. 关键参数逐项拆解续训调参不再靠猜4.1 学习率、warmup和调度器直接决定续训曲线是否正常学习率是续训中最重要的超参数。断点续训时会恢复调度器状态学习率自动从当时的位置继续不需要你手动干预但前提是resume_from_checkpoint生效。如果你发现续训后学习率打印出来的值和中断前的差很远就要怀疑调度器状态没有正确加载。基于检查点开新任务时情况不同。学习率调度器是重新初始化的warmup步数也会重新计算。我的经验是新任务的学习率不要直接沿用上一轮的初始学习率。上一轮结束时的学习率已经被cosine或linear调度衰减到比较低的水平继续训练本质上是希望在一个已经接近收敛的区域小步调整所以把初始学习率降为上一轮的0.3到0.5倍是比较稳的选择。如果上一轮是1e-4这一轮可以用3e-5到5e-5再配上较短的warmup比如100到200步让新任务快速进入平稳状态。warmup参数也很容易被忽视。基于上一轮结果继续训练时如果你还把warmup_ratio设成0.1按总步数算出来的warmup步数可能非常长前几千步都在低学习率下“热车”既浪费时间也会让曲线显得迟迟不下降。续训新任务时我一般会把warmup_steps设成固定值而不是warmup_ratio。4.2 训练轮次、保存策略与缓存设置继续训练里最容易搞混的是num_train_epochs。断点续训时这个值要和之前完全一致代表总轮数基于检查点开新任务时这个值代表新任务跑多少轮和上一轮已经跑过多少轮没有关系。两者含义不同但LLaMa-Factory的界面展示的都是“训练轮次”不细看就会填错。保存策略在续训场景下需要单独说。save_steps决定每隔多少步保存一次这个参数设置得越小checkpoint越密集磁盘占用越大但可恢复的粒度越细。我通常设置save_steps200或save_steps500配合save_total_limit限制保留数量。比如save_total_limit: 3意思是最多只保留最近3个checkpoint旧的会被自动清理。这个参数在长训练中非常重要7B模型全量微调时一个checkpoint就有十几个GB不做清理磁盘很快就满了。关于缓存LLaMa-Factory会对tokenized数据集做缓存减少每次训练前重复分词的时间。续训如果是同一份数据可以继续用缓存。一旦换了数据集、改了字段名或换了tokenizer就必须让分词缓存失效否则训练器可能还在用旧缓存。常见的做法是修改output_dir或者清空cache目录强迫重新分词。我遇到过一种情况新增了一些训练样本但缓存没刷新训练了十几个小时发现新样本根本没被吃进去这个坑很难察觉。4.3 stage切换、模板与tokenizer的一致性要求在增量预训练接SFT的流程里stage从pt切换成sft模型加载路径和训练目标都变了这是预期行为。但另一个细节是pt数据集的格式和sft数据集的格式不能混在一起。LLaMa-Factory在配置里通过dataset字段引用数据集如果配置的是pt stage数据集格式应该是文本行如果是sft stage需要是对话对或指令对。搞混时训练器可能不报错但loss的形态会很怪模型学出来的也是一堆不连贯内容。模板一致性值得再强调一次。LLaMa-Factory的template参数决定模型在输入侧如何拼对话结构。如果你的基座是qwen2.5模板一般选qwen如果你上一轮用了qwen模板这一轮换成了llama模板模型会把输入格式理解错。对tokenizer来说同样不能轻易变化。增量预训练阶段如果扩展过词表那后续SFT必须复用扩展后的词表和对应embedding否则新的token无法被正确表示。流程接力的正确姿势是先把上一轮checkpoint导出成完整模型或者明确用adapter复用上一轮LoRA再启动下一阶段。不要边训练边改词表、边训练边换模板这类改动几乎总是导致训练失效。5. 常见问题与排查实录5.1 恢复后loss跳变或曲线偏移断点续训后loss和中断前相比有小幅波动是正常的因为数据加载顺序可能受环境变化影响随机种子状态恢复后大多数情况下应该能保持一致但多卡环境、数据集缓存不完全一致时仍会有细小差异。如果loss出现明显的跳升比如从1.2涨回2.0优先检查这几项是否真的恢复了optimizer.pt和scheduler.pt。打开训练日志看加载状态有没有显示“resumed from checkpoint”。num_train_epochs是不是被改掉了。数据集文件是否和中断前一致文件内容有没有改过。是否误开了overwrite_output_dir把历史checkpoint清了。排查顺序建议是从trainer_state.json开始看这个文件记录了全局步数和历史loss直接能定位训练器认为自己“跑到了哪里”。5.2 报错模型权重与adapter不匹配这类报错信息通常包含“size mismatch”或“unexpected key”字样十有八九是LoRA参数不一致。比如上一轮lora_rank16这一轮改成了lora_rank8就会报shape不匹配。还有一种是上一轮LoRA训练时target了部分模块第二轮加载时发现新增模块需要重新初始化也会出现key缺失或冗余。处理方式很简单回到上一轮配置把lora_rank、lora_target、lora_alpha原样复制到这一轮配置里。如果确实想改那就不要用adapter方式继续而是先把上一轮LoRA合并导出成完整模型再基于完整模型开始新一轮不同rank的训练。5.3 增量预训练续训后loss不下降增量预训练续训后loss长时间不降通常不是代码问题而是数据或超参数问题。一种情况是新增数据里包含了大量模型已经非常熟悉的通用文本模型已经把它预测得很准loss自然降不动。另一种情况是训练数据填充了大量重复内容模型陷入“死记硬背”而不是学习新的领域知识。我遇到过最典型的一个场景是领域语料里专业术语很多但单条样本太短tokenize之后每条只有几十个token大部分都是padding或拼接分割符模型很难学到有效上下文。解决方法是把短文本按章节拼接成更长的连续语料cutoff_len尽量接近模型最大长度让模型在更长上下文里学习术语之间的关联。loss不降时还可以尝试把学习率调低比如2e-5甚至1e-5配合更长的训练轮数。增量预训练往往不是一轮就能见效需要多个epoch慢慢渗透。另外要注意用独立的验证集观察困惑度变化而不是只盯训练loss。5.4 显存不足与断点恢复的取舍训练中断有一种最常见的原因就是OOM显存不够。断点续训时如果还按原来的batch_size恢复有可能还是会OOM。但你完全可以在续训配置里调整per_device_train_batch_size和gradient_accumulation_steps因为batch size不改变模型参数结构只影响每次更新用的样本量。只要保证调整后的“等效batch size”和原来相同训练质量基本不受影响。比如原来per_device_train_batch_size4、gradient_accumulation_steps8等效batch size是4乘以8等于32。显存不够时可以先改成per_device_train_batch_size2、gradient_accumulation_steps16等效batch size仍然接近32但单卡显存占用会明显下降。另一个建议是开启gradient_checkpointing这是用计算换显存的经典做法。LLaMa-Factory配置里打开gradient_checkpointing训练速度会慢一些但显存占用能降不少。断点恢复前把梯度检查点开上再配合小batch基本能解决大部分OOM问题。5.5 checkpoint目录加载失败加载checkpoint报“not found”或“can‘t load adapter”多数是路径指错了。最常见的错误是把output_dir当成checkpoint目录传进去。举个例子你训练的output_dir是output/qwen2.5-7b_sft_v1_20250105里面的checkpoint-1200才是真正要加载的目录。如果你传的是output/qwen2.5-7b_sft_v1_20250105LLaMa-Factory会认为这是一个模型目录尝试读取config.json结果发现格式不对或文件缺失自然报错。另外路径里不要出现中文或特殊字符有些加载逻辑对路径中的空格、中文支持不好。移动checkpoint目录后adapter_config.json里记录的base_model_name_or_path可能失效所以尽量保持绝对路径不变或者在移动后手动编辑adapter_config.json里的路径。我把常见的续训问题整理成了一张表方便直接对照现象最可能原因处理办法恢复后loss跳升优化器状态没加载确认resume_from_checkpoint生效尺寸不匹配报错LoRA参数改变保持rank/target一致loss长时间不降数据重复或学习率不当调低lr检查语料长度OOM中断batch过大改小batch开gradient_checkpointing加载checkpoint报not found路径指向output_dir而不是checkpoint子目录指向checkpoint-xxx训练日志显示步数从0开始误用了overwrite_output_dir重新指定输出目录6. 关于续训一些零散但实用的经验我做了很多次续训之后最大的感触是续训失败很少是因为参数不会填而是因为你对自己上一次跑的东西没有留下足够清晰的记录。哪份数据、哪个模板、哪个LoRA配置、跑到哪一步、当时learning rate是多少这些看似琐碎的信息续训时缺一个都可能导致整个实验作废。所以我现在每次启动训练前都会顺手写一个实验说明文件记录配置命令、数据版本、输出目录。LLaMa-Factory本身已经很方便但训练状态的连续性还得靠使用者自己维护。另一个很实用的习惯是设置好save_total_limit和定期检查磁盘占用很多长训练不是被模型跑崩的而是被磁盘或显存耗尽的。最后分享一个小技巧如果条件允许尽量用固定节点名和固定镜像来跑训练。我经历过换机器后optimizer状态加载报错、被迫从头重跑的情况当时浪费了整整一天算力。从那以后续训前我都会先确认运行环境和上一轮一致。继续训练这个能力本身不难难的是你愿意为它建立一套纪律。