
我在实际用 llama-factory 微调 gemma-3-12b-instruct 时遇到过最折腾的一步不是训练本身而是最后那个“导出模型”。训练流程走通、loss 降得挺漂亮、checkpoint 也正常保存但一点导出按钮各种报错就冒出来了有说显存不够的有提示找不到文件的还有导出完模型根本加载不了的。这篇文章就把我排查的经验完整梳理一遍希望能帮你少走弯路。1. 问题背景微调顺利完成导出却反复失败先说下我的环境Ubuntu 22.04单卡 RTX 4090 24GCUDA 12.1llama-factory 当时用的版本是 0.9.xtransformers 4.46 左右。微调方式用的 QLoRA4bit 量化基础模型 LoRA adapter训练数据是几万条指令数据batch size 调得比较小训练过程大概一个多小时跑完checkpoint 正常生成在saves/gemma-3-12b-instruct/lora/train_2025xxxx/目录下。训练完成后我想把 LoRA adapter 合并回基础模型导出成一个完整的、可以直接部署的 FP16 模型文件方便后续用 vLLM 或者转成 GGUF 给 Ollama 用。结果在 llama-factory 的 Web UI 里点击“导出模型”后界面提示“Export failed”但日志只给了一行简短的报错后面没有更多信息。这类导不出、导出后不可用的问题我先后遇到了至少 5 种不同的情况查了很多资料才逐个解决。在开始排查前我建议你先明确一件事模型微调完成 ≠ 模型部署就绪。llama-factory 默认保存的 LoRA adapter 只是一个很小的增量权重必须和基础模型合并才能得到一个可以在各个推理框架中直接使用的标准模型。导出的本质就是“合并权重 重新保存”。这个过程中任何一个环节不匹配都会导致失败。2. 理解 llama-factory 导出模型的底层逻辑2.1 导出的本质是什么我们用 LoRA/QLoRA 微调时训练过程并不会修改基础模型的原始权重而是在模型的部分层旁边加了一些低秩矩阵即 adapter。llama-factory 保存的 checkpoint 里只包含了这些 adapter 参数以及训练时的配置信息。以 gemma-3-12b-instruct 为例完整 FP16 权重大概需要 24GB 存储空间而 LoRA adapter 通常只有几百 MB。导出模型的作用就是把 adapter 和基础模型合并起来生成一个完整的模型文件。llama-factory 底层调用的核心逻辑大致是加载基础模型根据你填写的模型名称或路径加载 LoRA checkpoint把 adapter 权重加到基础模型的对应参数上将合并后的模型保存到指定目录同时保存 tokenizer、config 等文件。如果你使用的是 QLoRA即基础模型是 4bit 量化加载的合并逻辑会更复杂一些。llama-factory 需要先把 4bit 权重反量化回半精度或全精度再执行合并所以我后面会重点强调显存问题。2.2 llama-factory 提供哪些导出能力在 Web UI 中导出入口在顶部导航栏的“工具” - “导出模型”核心参数包括模型名称必须是基础模型即微调前你选择的那个模型不要选成 checkpoint 路径。适配器路径checkpoint 路径选择你训练保存的 adapter 目录。导出目录合并后新模型的存放位置必须是一个不存在的空目录或者不存在。导出格式huggingface 格式、vLLM 格式、GGUF 格式等。导出量化等级可选择 none保持原精度、8bit、4bit 等即导出后的模型是否量化。max_shard_size分片大小默认 2GB一般不需要改。命令行对应的导出命令大致如下llama-factory 0.9.x 版本llamafactory-cli export \ --model_name_or_path /path/to/gemma-3-12b-instruct \ --adapter_name_or_path /path/to/saves/gemma-3-12b-instruct/lora/train_2025xxxx \ --template gemma \ --finetuning_type lora \ --export_dir /path/to/exported_model \ --export_size 2 \ --export_quantization_bit 4 \ --export_device auto如果你不使用命令行也可以直接在 Web UI 上操作但命令行能输出更详细的堆栈信息排查问题会方便很多这一点我后面会反复提到。3. 核心原因排查12 个高频错误逐一分析3.1 显存不足最典型的“训练能过导出却 OOM”这是我在 24G 显存的 4090 上遇到的第一个拦路虎。训练时 QLoRA 把基础模型压到 4bit显存占用大约只有 8GB 左右但导出时要把 12B 参数全部加载回来做合并FP16 精度下光模型权重就需要 24GB 显存再加上优化器状态、临时变量等24G 显存通常是不够的。你可能会想训练时用了 4bit 加载导出时也勾选 4bit 量化导出不就行了实际上导出时的 4bit 量化是合并完成后再量化中间过程仍然需要先以 FP16 或 BF16 精度加载完整模型。如果你显存不够有几种可行方案方案 A让模型加载到 CPU 上进行合并。llama-factory 的导出命令有一个--export_device参数可以设置为cpu。这样合并过程在内存中进行显存完全不受限制。缺点是速度慢12B 模型合并可能要多等十几分钟但只要内存大于 32GB基本都能跑完。方案 B换一台显存更大的机器只做导出这一步。训练可以在小显存机器上完成导出可以拷贝 checkpoint 到云服务器或朋友的机器上执行不一定非要同一台机器。方案 C直接改为 LoRA非 QLoRA微调。如果你显存足够大至少 24G 以上可以考虑直接以 FP16 精度微调导出的合并过程相对轻量。但这条只适合显存充足的情况。我个人建议优先尝试方案 A。导出是一次性操作慢一点没关系关键是稳定。3.2 基础模型路径与训练时不一致llama-factory 导出时会根据你填的“模型名称”加载一个基础模型然后在此基础上合并 adapter。如果你训练时用的模型是某个 Hugging Face 仓库如google/gemma-3-12b-it导出时却选择了本地另一个路径或者本地缓存不完整就会出现结构不匹配的报错常见的表现是KeyError: model.layers.0.self_attn.q_proj.lora_A.weight或者提示某个共享层不存在。这时候的排查思路很直接导出时选择的模型路径必须和训练时完全一致。你可以到saves/gemma-3-12b-instruct/lora/train_2025xxxx/目录下查看adapter_config.json里面会记录base_model_name_or_path字段这就是训练时用的基础模型路径。导出时原样填回即可。3.3 checkpoint 路径选择错误这个听起来很低级但很容易搞混。llama-factory 保存 checkpoint 的目录结构通常是saves/ └── gemma-3-12b-instruct/ └── lora/ └── train_2025xxxx/ ├── adapter_config.json ├── adapter_model.safetensors └── training_args.bin正确的 adapter_path 应该指向train_2025xxxx这一层而不是 lora 这一层更不是 saves 这一层。如果你选到了外层目录llama-factory 会找不到adapter_config.json。此外如果目录下同时存在adapter_model.safetensors和adapter_model.bin优先选 safetensors 格式速度快且更安全。3.4 导出目录已经存在且非空llama-factory 在导出时如果发现目标目录已存在且包含文件通常会直接报错提示“Export directory is not empty”。这个设计是为了防止覆盖原有模型。解决办法很简单换一个全新目录或者手动删掉旧目录里的文件。别抱着侥幸心理试图让它直接覆盖实测大概率会报错。3.5 llama-factory 或 transformers 版本太旧导致 gemma-3 兼容性问题gemma-3 是相对较新的模型老版本的 transformers 或 llama-factory 可能缺少对应的模型结构。具体表现是加载基础模型时报 key 不匹配或者在合并时出现 schema 错误。我的建议是如果你要微调 gemma-3 系列务必把 llama-factory 升级到较新的版本同时更新 transformers、peft、accelerate、tokenizers 等核心依赖。可以在项目虚拟环境里执行pip install -U llama-factory transformers peft accelerate tokenizers注意升级前最好看一下你训练时记录的依赖版本避免 checkpoint 和旧版本产生的中间文件冲突。我遇到过一次使用旧版 transformers 加载新版模型权重报错提示Some weights of Gemma3ForCausalLM were not initialized把所有相关库升到最新后问题才消失。3.6 磁盘空间不足导致导出中断12B 模型在 FP16 下大小约 24GB如果你导出为 8bit 或 4bit 量化版本文件会小一些但合并过程中会生成临时文件也需要一定的磁盘空间。如果你在训练时保存了多个 checkpoint磁盘可能已经占了不少。导出失败后我查看dmesg才发现是磁盘写满导致的。建议在导出前用df -h检查目标磁盘的剩余空间至少保留 30GB 以上。如果你的 checkpoint 数量很多也可以考虑先删除中间 checkpoint只保留最终版本。3.7 Gemma 系列分词器加载异常gemma-3 的分词器有一些特殊逻辑在某些旧版本 transformers 下tokenizer 加载会出现参数解析错误或者导出的 tokenizer_config.json 信息不完整。表现是导出时没报错但导出的模型在推理时提示 tokenizer 无法加载。解决方法是升级 transformers 到最新版本。如果升级后仍然异常可以手动从基础模型目录复制tokenizer.model、tokenizer.json、tokenizer_config.json到导出目录覆盖相关文件。这通常能解决大部分 tokenizer 兼容性问题。3.8 微调时添加了自定义 token但导出时未处理如果你在微调时使用了自定义数据集里面包含特殊 token比如|begin|、|end|llama-factory 可能已经帮你扩展了词表大小合并后的模型 embedding 维度也随之改变。但如果导出时没有把带扩展词表的 tokenizer 一起导出推理时 embedding 矩阵和 tokenizer 就对不上典型报错是RuntimeError: Error(s) in loading state_dict for Gemma3ForCausalLM: size mismatch for model.embed_tokens.weight: copying a param with shape torch.Size([30000, 2304]) from checkpoint, the shape in current model is torch.Size([256000, 2304]).这种情况需要在导出后特别检查导出的tokenizer_config.json和added_tokens.json是否存在。如果缺失可以从训练 checkpoint 目录找回或者用transformers的AutoTokenizer加载后再次保存。3.9 导出 GGUF 格式时缺依赖或网络异常如果你尝试直接从 llama-factory 导出 GGUF 格式它需要调用 llama.cpp 的转换脚本。这个过程中经常因为以下原因失败本地没有安装llama-cpp-python或相关转换工具转换时需要下载一些组件但网络不通或下载源很慢GGUF 转换脚本对 gemma-3 这类较新模型支持不完善。我的建议是先用 llama-factory 导出成 Hugging Face 格式再手动用 llama.cpp 的 convert_hf_to_gguf.py 脚本转成 GGUF。这样虽然步骤多一些但每一步都更容易排查。网上好多教程默认“一键导出 GGUF”其实在 gemma-3 这种新模型上并不一定可靠。3.10 显存碎片化导致的内存分配失败有时候你明确看了显存发现总占用并不高但导出依然报CUDA out of memory。这可能是显存碎片化导致的。特别是在长时间训练过程中PyTorch 的缓存分配器可能保留了大量不连续的内存块导致后续无法分配整块连续显存。处理办法重启程序或者重启终端清空 CUDA 缓存。如果你用的是 Web UI重启 llama-factory 服务再重新打开页面导出。别小看这一步我实测能解决不少“看起来显存明明够却报 OOM”的情况。3.11 导出后模型加载时提示 config 缺失这种问题是导出过程其实成功了但导出的目录里缺少关键的配置文件。可能原因是你手动挪动过文件或者导出过程中断。检查导出目录是否包含以下关键文件config.jsongeneration_config.jsontokenizer.model、tokenizer.json、tokenizer_config.json以.safetensors结尾的模型分片文件model.safetensors.index.json如果分片保存如果缺文件最直接的办法是从基础模型目录中复制缺失的 config 和 tokenizer 文件但要注意 config 里的vocab_size等字段必须与合并后的权重匹配。如果只有模型权重文件缺失那就需要重新导出。3.12 Windows 路径过长或存在特殊字符如果你在 Windows 下操作模型路径过长尤其是有多层嵌套目录或者包含中文、空格、特殊符号可能会导致文件读写失败。这个问题的隐蔽性很强因为报错信息往往不直接指向路径而是一堆莫名其妙的 python 异常。解决方法是把整个工作目录放在一个简短、纯英文的路径下比如D:\llm\llama-factory。导出目录也尽量用短路径避免使用桌面这类带空格的位置。4. 实操排查流程教你一步步定位问题4.1 先看完整日志别只看 Web UI 的报错提示Web UI 的报错信息比较简略通常只显示Export failed不告诉你真正原因。所以我强烈建议你用命令行方式执行导出。第一步可以先跑一个“空转测试”把 adapter 指向一个最小 checkpoint 或者直接不指定 adapter看看基础导出流程是否能通llamafactory-cli export \ --model_name_or_path /path/to/gemma-3-12b-instruct \ --export_dir /tmp/test_export \ --template gemma如果这一步成功说明基础模型路径、磁盘空间、依赖库版本都没有问题。如果这一步就报错那问题大概率出在环境或基础模型本身而不是你的微调结果。4.2 确认训练时的 adapter_config.json 内容打开你训练的 checkpoint 目录下的adapter_config.json重点看三个字段{ base_model_name_or_path: google/gemma-3-12b-it, r: 16, lora_alpha: 32, target_modules: [q_proj, k_proj, v_proj, o_proj] }其中base_model_name_or_path是你导出时必须填的基础模型路径。如果模型路径是 Hugging Face 仓库名但本地没有缓存llama-factory 启动时会尝试联网下载如果网络受限就会失败。可以把基础模型先下载到本地再把路径改成本地目录。4.3 逐步缩小范围先排除 adapter 的问题如果能成功导出基础模型下一步再带上 adapter 执行合并导出llamafactory-cli export \ --model_name_or_path /path/to/gemma-3-12b-instruct \ --adapter_name_or_path /path/to/saves/gemma-3-12b-instruct/lora/train_2025xxxx \ --template gemma \ --finetuning_type lora \ --export_dir /path/to/exported_model如果这一步报错注意读一下堆栈信息中是否提到了某个具体层。比如KeyError: base_model.model.model.layers.0.self_attn.q_proj.lora_A.weight这通常说明 adapter 里的权重和基础模型的层结构对不上原因可能是基础模型路径选错或者微调时使用了非标准target_modules。4.4 显存不足时的操作顺序在 24G 显存机器上导出 12B 模型我建议的顺序是先尝试直接导出观察是否报 OOM如果 OOM修改命令行参数llamafactory-cli export \ --model_name_or_path /path/to/gemma-3-12b-instruct \ --adapter_name_or_path /path/to/saves/gemma-3-12b-instruct/lora/train_2025xxxx \ --template gemma \ --finetuning_type lora \ --export_dir /path/to/exported_model \ --export_device cpu如果 CPU 合并内存也不够32GB 内存可能会爆可以考虑用--export_quantization_bit 4减少导出后模型大小但合并过程仍然需要加载 FP16 权重因此内存需求依然很大。4.5 常见问题速查表症状可能原因解决思路报 CUDA out of memory显存不足或碎片化换 CPU 导出、重启清缓存、换大显存机器提示 adapter 文件不存在checkpoint 路径选错检查 adapter_config.json 所在目录KeyError 权重维度不匹配基础模型路径不一致检查 adapater_config 中的 base_model_name_or_path导出目录已存在目标目录非空换新目录或清理旧文件导出时无法下载依赖网络受限提前下载依赖包或手动安装导出后 tokenizer 无法加载transformers 版本过旧升级 transformers 或手动补全 tokenizer 文件导出后 embedding 尺寸不匹配自定义 token 未正确保存检查 added_tokens.json 并同步合并GGUF 导出卡住或失败llama.cpp 工具链不完整先导出 HF 格式再手动转 GGUF报Some weights were not initialized本地模型文件不完整重新下载模型使用与训练一致的版本导出过程中断后目录残缺磁盘满或进程被杀清理空间重新导出到新目录5. 实战记录我遇到的 3 个具体问题5.1 问题一24G 显存下直接 OOM第一次导出我在 Web UI 里填好参数点击导出界面很快报错。查看完整日志定位到关键行torch.OutOfMemoryError: CUDA out of memory. Tried to allocate 24.00 GiB这个很好理解12B 模型 FP16 权重就要 24GB4090 显存全部给它都不够。我当时的解决办法是改用命令行加上--export_device cpu然后把--export_dir指向了一个剩余空间 100GB 的机械硬盘。合并过程大概跑了 20 分钟顺利结束。合并完成之后我再次启动一个 Python 脚本验证模型能否正常加载和聊天from transformers import AutoModelForCausalLM, AutoTokenizer model_path /path/to/exported_model tokenizer AutoTokenizer.from_pretrained(model_path) model AutoModelForCausalLM.from_pretrained(model_path, device_mapauto, torch_dtypeauto)模型正常加载能生成回复。这条路走通了。5.2 问题二导出时提示找不到model.embed_tokens相关权重第二次我在另一台 40G 显存的机器上做实验这次没有 OOM但出现了新的报错KeyError: base_model.model.model.embed_tokens.weight排查过程比较折腾。我先检查了 adapter_config.json发现基础模型路径指向的是一个临时路径/tmp/gemma-3-12b-it而当前机器上并没有这个目录。训练时我用的是 Hugging Face 缓存训练结束后缓存被清理过再次导出时 llama-factory 解析不到对应的本地模型文件导致 layer 结构错位。解决办法是重新把google/gemma-3-12b-it下载到本地指定目录然后用--model_name_or_path指向这个完整目录同时保持 adapter_config.json 不变。导出成功。你如果也遇到这种问题可以用huggingface-cli或modelscope先把模型完整拉到本地huggingface-cli download google/gemma-3-12b-it --local-dir /data/models/gemma-3-12b-it再执行导出命令基础模型路径改成/data/models/gemma-3-12b-it即可。5.3 问题三导出成功后推理平台加载仍然报错有一次导出过程完全正常模型目录文件齐全但用 FastAPI transformers 加载时一直报错tokenizer_config.json: file not found。我检查后发现导出目录里确实没有 tokenizer_config.json只有 tokenizer.model。原因是我在 Web UI 中选了一个比较旧版本的 llama-factory导出逻辑里对 gemma-3 的 tokenizer 处理不完整。解决办法有两种直接从基础模型目录复制tokenizer_config.json和tokenizer.json到导出目录或者在导出时勾选“包含 tokenizer”一类的选项不同版本叫法略有差异保证 tokenizer 文件被完整保存。手动复制文件的方式最直接cp /path/to/gemma-3-12b-it/tokenizer* /path/to/exported_model/注意如果基础模型和导出模型词表维度不一致直接复制 tokenizer 可能会导致 embedding 对应关系错位。但 gemma-3 系列大多数微调不扩展词表复制后可以正常使用。如果你加了自定义 token就需要同步修改tokenizer_config.json中对应字段并确认added_tokens.json存在且正确。6. 不同推理框架下的导出策略选择6.1 使用 Hugging Face transformers 推理最简单的方案直接使用上述导出的标准 Hugging Face 格式模型目录配合AutoModelForCausalLM加载即可。这种方式灵活性高适合二次开发、调试和测试。但缺点是显存占用较大且推理并发性能不如 vLLM。6.2 使用 vLLM 推理vLLM 对模型格式要求比较严格通常要求是完整的 Hugging Face 格式且最好有统一的config.json。你可以直接用我上面导出的 HF 格式模型目录作为 vLLM 的模型输入vllm serve /path/to/exported_model \ --served-model-name gemma-3-12b-it \ --tensor-parallel-size 2如果你的导出过程中使用了--export_quantization_bit 4或 8bitvLLM 加载时要注意指定对应的量化方式在 llama-factory 中直接导出 vLLM 格式它也会存入 HF 格式因此一般不需要额外转换。6.3 使用 Ollama / llama.cpp 推理Ollama 需要 GGUF 格式的模型。建议先用 llama-factory 导出 HF 格式再手动使用 llama.cpp 的转换脚本git clone https://github.com/ggerganov/llama.cpp cd llama.cpp pip install -r requirements.txt python convert_hf_to_gguf.py /path/to/exported_model \ --outfile /path/to/gemma-3-12b-it.gguf \ --outtype f16转换完成后再写一个 Modelfile 给 Ollama 使用。这个过程比“一键导出”多几步但每一步都能看到实际的中间结果方便排查问题。6.4 量化导出时的额外提醒如果你打算导出 4bit 量化模型优先考虑使用--export_quantization_bit 4实际上底层调用的可能是 GPTQ 或 AWQ需要额外安装auto-gptq或autoawq库。如果没有安装导出会失败。在导出前可以先确认这些依赖是否已经装好pip install auto-gptq注意gemma-3 这种新模型在 GPTQ 量化时偶尔会出现层名不匹配的问题遇到这种情况建议升级 auto-gptq 到最新版本。7. 几点避坑心得训练前就规划好导出方案。如果你知道自己最终要部署到 Ollama 或 vLLM训练时就尽量使用稳定的基础模型路径最好把模型先拉到本地避免每次导出都面临路径不一致的问题。不要同时在训练进程存活的机器上用同一块 GPU 做导出。即使显存够也可能因为共用 GPU 显存或内存而出现意外。导出的模型一定要单独验证。导出成功不等于模型可用。最少要跑一次完整的生成测试确认输出的回复质量和格式正常。我遇到过导出后基础对话没问题但多轮对话模板错乱的情况原因就是模板参数没有正确传进去。保留一个“最小可复现”的环境变量。把导出的命令行脚本保存成一个.sh文件放到项目目录里方便下次一键执行。在排查问题时这个脚本也能帮你快速复现 bug。关注 llama-factory 官方更新和 issue。新版对 gemma-3 这类新模型的支持会持续优化遇到问题先去 issue 里搜一下往往能找到官方回复或临时 workaround。从整体来看导出问题虽然烦人但只要把“基础模型路径、adapter 路径、显存/内存、依赖版本”这四个关键点逐一确认大部分问题都能在 10 分钟内定位。如果你也卡在导出这一步建议先按第 4 节的流程走一遍日志排查少走弯路。