Chapter 12

模型导出:merge / GGUF / 推理服务

📌 commit 1cf145c070ea 训完到部署的最后一公里

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"]

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)

用途:

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 自动完成):

  1. 把 4bit base + LoRA 合并成 fp16;
  2. 自动下载 / 编译 llama.cpp;
  3. convert_hf_to_gguf.py 转 fp16 GGUF;
  4. 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

推荐:

一次出多份

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导出清单

正式发布模型前的最小检查:

  1. config.json 有正确的 chat_template;
  2. tokenizer.json 完整复制;
  3. ✅ 不带 adapter 直接推理也对(merged 测试);
  4. ✅ Modelfile 里的 stop tokens 写全(<|eot_id|> 等);
  5. ✅ 跑一次评测对比 base 模型;
  6. README.md 说明训练数据、license、prompt 范例;
  7. ✅ 上 HF Hub 时打 tag(base / language / size)。

12.13这章带走的