Unsloth微调后模型如何保存与加载?看这篇就行
微调完一个大模型,最常被问到的问题就是:训练好的模型怎么保存?保存后又该怎么加载使用?尤其是用 Unsloth 这种主打“快+省”的框架时,很多人发现——明明训练跑通了,但一到保存和加载环节就卡住:模型打不开、报错找不到权重、推理结果乱码、甚至连 tokenizer 都加载失败……别急,这不是你操作错了,而是 Unsloth 的保存加载逻辑和标准 Hugging Face 流程有关键差异。
这篇文章不讲原理、不堆参数,只聚焦一件事:手把手带你把 Unsloth 微调后的模型,稳稳当当地存下来,再清清楚楚地加载回来,还能直接跑推理。全程基于真实命令、可复制代码、常见报错解析,小白照着做就能成功。
1. 为什么 Unsloth 的保存加载不能照搬 Hugging Face?
先说结论:Unsloth 默认不生成标准的pytorch_model.bin和config.json,它走的是自己的高效序列化路径。如果你直接用AutoModel.from_pretrained("your_output_dir")去加载 Unsloth 保存的模型,90% 的概率会报错:
OSError: Can't load config for 'xxx'. Make sure the config file is in the correct format.KeyError: 'model.layers.0.self_attn.q_proj.weight'- 或者加载成功但推理输出全是乱码、空字符串、重复 token
原因很简单:Unsloth 在训练中做了大量底层优化(比如 fused kernels、4-bit 量化嵌入、自定义 attention 实现),这些优化无法被原生transformers库直接识别。它保存的不是“通用格式”,而是“自己能读、别人难懂”的紧凑结构。
所以,保存必须用 Unsloth 的方式,加载也必须用 Unsloth 的方式。记住这个铁律,后面所有步骤都围绕它展开。
2. 正确保存:两步到位,不漏任何文件
Unsloth 提供了两种保存方式,推荐使用model.save_pretrained()+tokenizer.save_pretrained()组合。这是最稳妥、兼容性最好、后续迁移最方便的方式。
2.1 保存前的关键准备
确保你的训练脚本末尾包含以下三行(这是很多教程遗漏但极其关键的一步):
# 确保模型处于评估模式(避免 dropout 等影响推理) model.eval() # 强制将 LoRA 权重合并进基础模型(可选,但强烈建议) model = model.merge_and_unload() # 保存模型和分词器 model.save_pretrained("output/unsloth_finetuned") tokenizer.save_pretrained("output/unsloth_finetuned")为什么必须
merge_and_unload()?
Unsloth 默认以 LoRA 形式微调,即只训练少量适配层,原始大模型权重保持冻结。如果不合并,保存下来的只是一个“带补丁的壳”,加载时必须同时加载基础模型 + LoRA 适配器 + PEFT 配置,极易出错。而merge_and_unload()会把 LoRA 的增量更新物理写入到原始权重中,生成一个“开箱即用”的完整模型,体积略大但绝对稳定、零依赖。
2.2 完整保存示例(含注释)
下面是一段可直接运行的保存代码,整合在训练脚本末尾:
# ====== 训练结束后,执行保存 ====== if __name__ == "__main__": # 1. 切换为评估模式(关闭 dropout/batch norm 更新) model.eval() # 2. 合并 LoRA 权重(核心!) # 注意:这一步会消耗显存,确保 GPU 有足够空间 model = model.merge_and_unload() # 3. 创建保存目录(自动创建) save_directory = "output/unsloth_qwen2_05b_huanhuan" os.makedirs(save_directory, exist_ok=True) # 4. 保存模型(Unsloth 专用方法) model.save_pretrained( save_directory, save_method="merged_16bit", # 保存为 16-bit 合并权重,平衡精度与体积 # save_method="lora" # 若想保留 LoRA 结构,用此选项(不推荐新手) ) # 5. 保存分词器(必须!且必须和模型一起保存) tokenizer.save_pretrained(save_directory) print(f" 模型已成功保存至:{save_directory}") print(f" 目录内应包含:config.json, pytorch_model.bin, tokenizer_config.json, tokenizer.model 等文件")保存成功后,你的output/unsloth_qwen2_05b_huanhuan目录里应该有这些文件:
| 文件名 | 说明 |
|---|---|
config.json | 模型结构配置,Unsloth 已自动适配 |
pytorch_model.bin | 合并后的 16-bit 权重文件(核心!) |
tokenizer_config.json | 分词器配置 |
tokenizer.model | 分词器模型文件(如 sentencepiece) |
special_tokens_map.json | 特殊 token 映射(如 `< |
常见错误排查
- 报错
RuntimeError: merge_and_unload() failed→ GPU 显存不足。解决方案:先del model清理内存,或改用save_method="lora"保存(但加载更复杂)。- 保存后没有
pytorch_model.bin→ 检查是否漏掉model.save_pretrained(),或误用了trainer.save_model()(这是 Hugging Face 的,对 Unsloth 不适用)。
3. 正确加载:三行代码,零配置启动
加载比保存更简单,但必须用 Unsloth 提供的FastLanguageModel.from_pretrained(),而不是AutoModel.from_pretrained()。
3.1 加载代码(极简版)
from unsloth import FastLanguageModel # 一行加载模型 + 分词器(自动识别保存格式) model, tokenizer = FastLanguageModel.from_pretrained( model_name = "output/unsloth_qwen2_05b_huanhuan", max_seq_length = 384, # 必须和训练时一致! dtype = None, # 自动推断,一般不用设 load_in_4bit = False, # 因为已合并为 16-bit,设为 False ) # 可选:启用推理加速(Unsloth 特有) FastLanguageModel.for_inference(model)就这么三行,模型和分词器就 ready 了。不需要手动AutoTokenizer.from_pretrained(),也不需要AutoModelForCausalLM—— Unsloth 全包了。
3.2 完整推理示例(验证加载是否成功)
加载后立刻跑个推理,确认一切正常:
# 构造一个测试 prompt(注意:必须用训练时的相同模板!) prompt = """<|im_start|>system 现在你要扮演皇帝身边的女人--甄嬛<|im_end|> <|im_start|>user 皇上今日气色不佳,可是朝中出了什么事?<|im_end|> <|im_start|>assistant """ # 编码输入 inputs = tokenizer(prompt, return_tensors="pt").to("cuda") # 生成回答 outputs = model.generate( **inputs, max_new_tokens = 128, use_cache = True, temperature = 0.7, top_p = 0.9, ) response = tokenizer.decode(outputs[0], skip_special_tokens=True) print(" Prompt:") print(prompt) print("\n 模型回答:") print(response.split("<|im_start|>assistant")[-1].strip())如果看到类似这样的输出,说明加载完全成功:
“皇上今日气色不佳,可是朝中出了什么事?”
“回皇上,臣妾观您眉间微蹙,似有心事。莫非是西北军报未至,抑或户部银钱调度不畅?”
3.3 加载时的三个必填参数(新手最容易错)
| 参数 | 必须设置? | 为什么重要 | 常见错误 |
|---|---|---|---|
model_name | 是 | 指向你保存的目录路径,不能是模型ID(如 "Qwen2.5-0.5B-Instruct") | 写成"Qwen2.5-0.5B-Instruct"→ 找不到本地文件 |
max_seq_length | 是 | 必须和训练时FastLanguageModel.from_pretrained(..., max_seq_length=384)的值完全一致 | 训练用 384,加载写 512 → 报错sequence length mismatch |
load_in_4bit | 是 | 训练时若用了load_in_4bit=True,这里也要设True;但如果你执行了merge_and_unload(),则必须设False | 合并后还设True→ 加载失败 |
小技巧:如何快速确认训练时的
max_seq_length?
查看你训练脚本中FastLanguageModel.from_pretrained()这一行,或者打开保存目录下的config.json,搜索"max_position_embeddings"字段,它的值就是你需要的max_seq_length。
4. 进阶场景:如何保存/加载 LoRA 适配器(不合并)?
虽然推荐合并保存,但有些场景你确实需要保留 LoRA 结构,比如:
- 想在多个任务上复用同一个基础模型,只切换不同 LoRA 适配器;
- 模型太大,合并后显存爆满,只能边加载边合并;
- 需要后续继续微调(LoRA 支持增量训练)。
4.1 保存 LoRA 适配器(不合并)
# 训练完成后,不 merge,直接保存 LoRA model.save_pretrained( "output/unsloth_lora_huanhuan", save_method = "lora", # 关键!指定保存为 LoRA 格式 ) tokenizer.save_pretrained("output/unsloth_lora_huanhuan")此时目录下不会生成pytorch_model.bin,而是:
adapter_model.bin(LoRA 权重)adapter_config.json(LoRA 配置)non_lora_trainables.bin(如果有额外训练的非 LoRA 参数)
4.2 加载 LoRA 适配器(需两步)
from unsloth import FastLanguageModel from peft import PeftModel # 第一步:加载基础模型(必须是原始模型,不是你微调的目录!) base_model_path = "/root/autodl-tmp/qwen/Qwen2.5-0.5B-Instruct" model, tokenizer = FastLanguageModel.from_pretrained( model_name = base_model_path, max_seq_length = 384, load_in_4bit = True, # 基础模型加载方式 ) # 第二步:用 PEFT 加载 LoRA 适配器(注意:这里用的是标准 PEFT API) model = PeftModel.from_pretrained( model, "output/unsloth_lora_huanhuan", device_map = "auto" ) # 可选:合并进内存(不写入磁盘,仅用于当前推理) model = model.merge_and_unload()注意:这种方式加载更慢、依赖更多、出错率更高,仅建议高级用户或特定需求时使用。
5. 常见问题速查表(5分钟定位故障)
| 问题现象 | 最可能原因 | 一句话解决 |
|---|---|---|
OSError: Can't load config for 'xxx' | 用了AutoModel.from_pretrained()加载 Unsloth 模型 | 改用FastLanguageModel.from_pretrained() |
| 加载成功但推理输出为空/乱码 | 分词器没加载,或skip_special_tokens=False | 确保tokenizer.save_pretrained()且解码时加skip_special_tokens=True |
ValueError: max_seq_length must be <= 384 | 加载时max_seq_length小于训练值 | 查config.json中max_position_embeddings,设为相等值 |
CUDA out of memory在merge_and_unload() | GPU 显存不足 | 先del model,或改用save_method="lora" |
保存后目录里只有adapter_model.bin,没有pytorch_model.bin | 忘记merge_and_unload()或save_method="lora" | 检查保存代码,确认save_method="merged_16bit"且调用了merge_and_unload() |
| 推理结果和训练时差异很大 | Prompt 模板不一致(如少写了 `< | im_start |
6. 总结:记住这三条铁律
微调只是开始,保存和加载才是交付的关键。对 Unsloth 而言,只需牢记这三点,就能避开 95% 的坑:
6.1 保存铁律
必须
merge_and_unload()+model.save_pretrained(..., save_method="merged_16bit")+tokenizer.save_pretrained()
三者缺一不可。不要图省事跳过合并,也不要混用 Hugging Face 的保存方法。
6.2 加载铁律
必须
FastLanguageModel.from_pretrained(),且max_seq_length必须和训练时完全一致。
把它当成密码——输错一位,门就打不开。
6.3 验证铁律
加载后第一件事:用训练时完全相同的 prompt 模板跑一次推理。
不看日志,不看参数,只看输出是否合理、是否连贯、是否符合预期。这是唯一真实的验收标准。
做到这三点,你的 Unsloth 模型就真正“活”起来了——不再是训练日志里的一串数字,而是可以部署、可以集成、可以创造价值的 AI 能力。
获取更多AI镜像
想探索更多AI镜像和应用场景?访问 CSDN星图镜像广场,提供丰富的预置镜像,覆盖大模型推理、图像生成、视频生成、模型微调等多个领域,支持一键部署。