Chapter 12
模型导出:merge / GGUF / 推理服务
Unsloth 训出的模型本质是"4bit 基座 + LoRA adapter",不能直接部署。 本章覆盖几条主流"出口":merge 成完整 HF 模型、转 GGUF 跑 Ollama / llama.cpp、转 vLLM、转 GPTQ / AWQ。
12.1训完之后的三条路
flowchart LR
Train["训完: 4bit base + LoRA adapter"]
Train --> A["A. 不 merge
带 adapter 推理"] Train --> B["B. merge
HF 完整模型"] Train --> C["C. merge + 量化
GGUF / GPTQ / AWQ"] A --> A1["FastLanguageModel +
load_adapter()"] B --> B1["transformers / vLLM / SGLang"] C --> C1["Ollama / llama.cpp /
TensorRT-LLM"]
带 adapter 推理"] Train --> B["B. merge
HF 完整模型"] Train --> C["C. merge + 量化
GGUF / GPTQ / AWQ"] A --> A1["FastLanguageModel +
load_adapter()"] B --> B1["transformers / vLLM / SGLang"] C --> C1["Ollama / llama.cpp /
TensorRT-LLM"]
12.2导出 API 总览
| 方法 | 产物 | 用途 |
|---|---|---|
model.save_pretrained("lora_model") | 只有 LoRA adapter(~50-200 MB) | 给别人复用 / hub 上传 |
model.save_pretrained_merged("merged_16bit", tok, save_method="merged_16bit") | 完整 fp16 HF 模型(~16 GB / 8B) | vLLM / TGI / 通用推理 |
model.save_pretrained_merged("merged_4bit", tok, save_method="merged_4bit_forced") | 合并后 4bit(约 5 GB) | 显存极紧的部署 |
model.save_pretrained_gguf("gguf_q4", tok, quantization_method="q4_k_m") | GGUF 量化(~5 GB) | Ollama / llama.cpp / 移动端 |
model.save_pretrained_gguf(..., quantization_method=["q4_k_m","q5_k_m","q8_0"]) | 多份不同量化 | 一次性出多套 |
model.push_to_hub_merged(...) | 推到 HF Hub | 公开分享 |
model.push_to_hub_gguf(...) | GGUF 推 HF Hub | 同上 |
12.3路径 A:不 merge 直接推理
训完后:
model.save_pretrained("lora_model")
tokenizer.save_pretrained("lora_model")
下次加载推理:
from unsloth import FastLanguageModel
# 加载 base 模型(Unsloth 优化版)
model, tokenizer = FastLanguageModel.from_pretrained(
"unsloth/llama-3-8b-bnb-4bit",
max_seq_length=2048, load_in_4bit=True,
)
# 挂上自己的 LoRA
model.load_adapter("lora_model")
# 切推理模式
FastLanguageModel.for_inference(model)
用途:
- 多个 LoRA 切换(多 task / 多用户);
- 不想占盘上传 16GB;
- 开发期反复迭代。
12.4路径 B:merge 成完整 HF 模型
model.save_pretrained_merged(
"merged_16bit",
tokenizer,
save_method = "merged_16bit", # 完整 fp16
)
会得到标准 HF 目录:
merged_16bit/
├── config.json
├── generation_config.json
├── model.safetensors.index.json
├── model-00001-of-00004.safetensors
├── ...
├── tokenizer.json
├── tokenizer_config.json
└── special_tokens_map.json
合并参数说明
save_method | 含义 | 产物大小(8B) |
|---|---|---|
"merged_16bit" | 合并后 fp16 / bf16 | 16 GB |
"merged_4bit_forced" | 合并后再量化 4bit(精度略损) | 5 GB |
"lora" | 只存 LoRA adapter | 200 MB |
用合并后模型推理
from transformers import AutoModelForCausalLM, AutoTokenizer
import torch
tok = AutoTokenizer.from_pretrained("merged_16bit")
model = AutoModelForCausalLM.from_pretrained(
"merged_16bit",
torch_dtype=torch.bfloat16,
device_map="auto",
)
# 不需要 Unsloth,原生 HF 就能跑
vLLM serve
vllm serve ./merged_16bit \
--tensor-parallel-size 1 \
--max-model-len 8192 \
--port 8000
12.5路径 C:GGUF 量化部署
GGUF 是 llama.cpp 的统一量化格式,Ollama / LM Studio / mobile 端都用它。 Unsloth 一行导出:
model.save_pretrained_gguf(
"gguf_q4",
tokenizer,
quantization_method = "q4_k_m",
)
底层流程(Unsloth 自动完成):
- 把 4bit base + LoRA 合并成 fp16;
- 自动下载 / 编译 llama.cpp;
- 调
convert_hf_to_gguf.py转 fp16 GGUF; - 用
llama-quantize量化到目标精度。
支持的量化方法
| method | 位数 | 精度 vs fp16 | 大小(8B) |
|---|---|---|---|
q8_0 | 8 bit | 0.1% | 8 GB |
q6_k | 6 bit | 0.3% | 6.5 GB |
q5_k_m | 5 bit | 0.5% | 5.5 GB |
q4_k_m | 4 bit | 1% | 5 GB |
q4_0 | 4 bit (legacy) | 2% | 4.5 GB |
q3_k_m | 3 bit | 3-5% | 4 GB |
q2_k | 2 bit | 10%+ | 3 GB |
推荐:
- q4_k_m:平衡精度 / 速度 / 大小,默认首选;
- q5_k_m:在意精度的话;
- q8_0:精度极致;
- q3_k_m / q2_k:手机 / 边缘设备。
一次出多份
model.save_pretrained_gguf(
"gguf_multi",
tokenizer,
quantization_method = ["q4_k_m", "q5_k_m", "q8_0"],
)
# 会生成 3 个文件,分别对应三种精度
12.6Ollama 部署
第 10 章演示过。完整版 Modelfile(含 system prompt):
# Modelfile
FROM ./gguf_q4/unsloth.Q4_K_M.gguf
TEMPLATE """{{ if .System }}<|start_header_id|>system<|end_header_id|>
{{ .System }}<|eot_id|>{{ end }}{{ if .Prompt }}<|start_header_id|>user<|end_header_id|>
{{ .Prompt }}<|eot_id|>{{ end }}<|start_header_id|>assistant<|end_header_id|>
{{ .Response }}<|eot_id|>"""
SYSTEM """你是一个友好的中文 AI 助手。"""
PARAMETER stop "<|eot_id|>"
PARAMETER stop "<|end_of_text|>"
PARAMETER stop "<|start_header_id|>"
PARAMETER stop "<|end_header_id|>"
PARAMETER temperature 0.7
PARAMETER top_p 0.9
PARAMETER num_ctx 4096
ollama create my-llama3 -f Modelfile
ollama run my-llama3
ollama list
ollama push my-llama3 # 推到 Ollama Hub
12.7llama.cpp 直接部署
不用 Ollama 也行,llama.cpp 自己有 server:
cd llama.cpp
./build/bin/llama-server \
-m /path/to/gguf_q4/unsloth.Q4_K_M.gguf \
--port 8080 \
-c 4096 \
--chat-template llama3
会起一个 OpenAI 兼容的 HTTP server,curl 测试:
curl http://localhost:8080/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{
"model": "default",
"messages": [{"role": "user", "content": "你好"}]
}'
12.8vLLM 部署(推荐用 merged_16bit)
vllm serve ./merged_16bit \
--tensor-parallel-size 1 \
--gpu-memory-utilization 0.9 \
--max-model-len 8192 \
--port 8000
vLLM 比 llama.cpp 吞吐高 5-10×(continuous batching + PagedAttention),但只支持 GPU 推理。 单用户场景 llama.cpp 也够用;高并发 API 服务用 vLLM。
12.9GPTQ / AWQ 量化(替代 GGUF)
vLLM 不支持 GGUF(截至 0.10),但支持 GPTQ / AWQ。从 Unsloth merged 模型转:
# 用 AutoGPTQ
from auto_gptq import AutoGPTQForCausalLM, BaseQuantizeConfig
quantize_config = BaseQuantizeConfig(bits=4, group_size=128, desc_act=False)
model = AutoGPTQForCausalLM.from_pretrained("./merged_16bit", quantize_config)
model.quantize(calibration_data)
model.save_quantized("./gptq_4bit")
或者用 AWQ:
from awq import AutoAWQForCausalLM
model = AutoAWQForCausalLM.from_pretrained("./merged_16bit")
model.quantize(tokenizer, quant_config={"q_group_size": 128, "w_bit": 4})
model.save_quantized("./awq_4bit")
# vLLM serve
vllm serve ./awq_4bit --quantization awq --max-model-len 8192
12.10推送到 HuggingFace Hub
# 推 LoRA
model.push_to_hub("yourname/llama3-zh-lora", token="hf_xxx")
tokenizer.push_to_hub("yourname/llama3-zh-lora", token="hf_xxx")
# 推合并后完整模型
model.push_to_hub_merged(
"yourname/llama3-zh-chat",
tokenizer,
save_method="merged_16bit",
token="hf_xxx",
)
# 推 GGUF
model.push_to_hub_gguf(
"yourname/llama3-zh-chat-gguf",
tokenizer,
quantization_method=["q4_k_m", "q8_0"],
token="hf_xxx",
)
12.11导出体积 / 速度对照
Llama-3-8B 训完导出对比(4090 / 同一 chat 任务):
| 格式 | 体积 | 推理速度 | 典型用途 |
|---|---|---|---|
| LoRA only | 200 MB | 需要 base 在线 | 多 task 切换 |
| merged_16bit | 16 GB | 50 tok/s(vLLM) | vLLM / TGI |
| merged_4bit_forced | 5 GB | 30 tok/s | 显存紧 |
| GGUF q8_0 | 8 GB | 40 tok/s(llama.cpp) | 本地 chat |
| GGUF q4_k_m | 5 GB | 50 tok/s | 本地 chat(推荐) |
| GPTQ 4bit | 5 GB | 80 tok/s(vLLM) | vLLM 高并发 |
| AWQ 4bit | 5 GB | 90 tok/s(vLLM) | vLLM 最快 |
12.12导出清单
正式发布模型前的最小检查:
- ✅
config.json有正确的 chat_template; - ✅
tokenizer.json完整复制; - ✅ 不带 adapter 直接推理也对(merged 测试);
- ✅ Modelfile 里的 stop tokens 写全(
<|eot_id|>等); - ✅ 跑一次评测对比 base 模型;
- ✅
README.md说明训练数据、license、prompt 范例; - ✅ 上 HF Hub 时打 tag(base / language / size)。
12.13这章带走的
- 训完不能直接用,必须 merge 或导出 GGUF;
- 三条出口:纯 LoRA / merged HF / GGUF 量化;
- GGUF q4_k_m 是部署到 Ollama / llama.cpp 的默认选择;
- vLLM 高吞吐场景用 merged_16bit 或 AWQ;
- Unsloth 一行
save_pretrained_gguf包办下载 + 编译 + 转换 + 量化; - 导出后必须验证不带 adapter 推理也对。