大模型部署4个案例说清楚
4 个案例讲清楚自研大模型的部署过程
很多人训完模型会卡在一个问题:
“我的
model.safetensors训出来了,怎么让别人用上?”
答案不是“部署”两个字,而是先回答几个问题: - 跑在服务器还是本机还是手机? - 是 LLM 还是 ASR / 视觉小模型? - 用 Python 服务,还是 C++ / Kotlin / Swift 直接跑? - 用 HuggingFace 格式、GGUF、ONNX 还是 NCNN?
下面用 4 个案例把“训练格式 → 部署格式 → 推理引擎 → 服务/端侧调用”讲清楚。
4个案例覆盖了训练模型的常用场景
1 | |
主要的注意事项
- 训练格式不管是 safetensors 还是 pytorch_model.bin,都不要直接部署。
- HuggingFace 格式给服务器,GGUF 给本地/端侧 LLM,ONNX 是中间格式,NCNN 是端侧 C++ 落地格式。
- FastAPI / Kotlin / Swift 都不“懂模型”,它们调用推理引擎;推理引擎才读模型文件。
案例 1:服务器部署自研 7B LLM(vLLM)
1. 训练产物是什么
1 | |
这就是标准 HuggingFace 格式: -
config.json:模型结构 - tokenizer*:分词器 -
model.safetensors:权重
说明:vLLM 不需要 ONNX,也不需要 GGUF。它直接读 HuggingFace 格式,因为 vLLM 本身就是服务器级 LLM 推理引擎,内部自己做算子优化、PagedAttention、批处理。
2. 为什么不直接用
model.generate() 上线
transformers 的 model.generate() 能跑,但:
- 不支持高并发 - 没有高效 KV Cache 管理 - 没有 OpenAI 兼容接口 -
工程化能力弱
所以生产里通常用 vLLM / TGI 这种“推理服务引擎”。
3. 启动 vLLM 服务
1 | |
参数含义: - --model:HuggingFace 模型目录,不是单个文件
- --host 0.0.0.0:允许其他机器访问;localhost
只能本机 - --port 8000:HTTP 服务端口 -
--dtype auto:自动选 fp16/bf16 -
--max-model-len 4096:最大上下文,防止显存爆掉
启动后你会看到类似: 1
INFO: Uvicorn running on http://0.0.0.0:8000
意思是:
模型已经加载进显存,vLLM 已经把模型包装成一个 HTTP
服务了。
4. 怎么调用这个“跑起来的模型”
vLLM 提供 OpenAI 兼容接口:
1 | |
请求参数含义: - model:服务里加载的模型名 -
messages:多轮对话格式 -
temperature:随机性,0 最稳,1 最发散 -
top_p:核采样,控制候选词范围 -
max_tokens:最多生成多少 token
也可以用 Python: 1
2
3
4
5
6
7
8
9
10
11
12from openai import OpenAI
client = OpenAI(
api_key="EMPTY", # vLLM 默认不校验 key
base_url="http://localhost:8000/v1"
)
resp = client.chat.completions.create(
model="./my-7b-sft",
messages=[{"role": "user", "content": "你好"}]
)
print(resp.choices[0].message.content)
5. FastAPI 在这里干什么
FastAPI 不加载模型,而是: - 做鉴权 - 拼系统提示 - 多模型路由 - 限流 - 转发给 vLLM
1 | |
结论:服务器 LLM = HuggingFace 格式 + vLLM 起服务 + FastAPI 做业务层。
案例 2:本地 / 开发机用 Ollama 跑量化 LLM
1. 为什么要转 GGUF
HuggingFace 格式适合训练和服务端加载,但不适合: - 笔记本 - 单 CPU / 小显存 - 快速本地试跑
所以需要两步: 1. 转 GGUF:从训练生态进入 llama.cpp 推理生态 2. 量化:把 16-bit 权重压成 4-bit,省显存/内存
2. 第一步:HF → FP16 GGUF
1 | |
参数说明: - ./my-7b:HuggingFace 模型目录 -
--outfile:输出的 GGUF 文件 -
--outtype f16:权重保持 16 位浮点,不做量化
👉 这一步只是“换容器”,不是压缩
3. 第二步:FP16 GGUF → Q4_K_M GGUF
1 | |
参数说明: - 输入:FP16 GGUF - 输出:量化后 GGUF -
Q4_K_M:4-bit 左右、质量/体积平衡最好的量化档位之一
- 7B 模型:FP16 约 14GB,Q4_K_M 约 4–5GB
重要:永远从 FP16 GGUF 再量化,不要从 Q4 再量化成 Q2,否则误差会叠加。
4. Modelfile 是干什么的
Modelfile 不是模型文件,也不是镜像,而是“模型 + 行为配置”的说明书,类似 Dockerfile。
1 | |
逐行说明:
FROM:底座模型,这里直接指向本地 GGUFSYSTEM:每次对话都带上的“人设 / 规则”TEMPLATE:把 system / user / assistant 拼成模型训练时见过的格式
👉 模板错了,模型就会“不听指令”temperature:创造性top_p:采样范围num_ctx:上下文窗口大小repeat_penalty:防复读
5. 注册并运行
1 | |
Ollama 默认起在: 1
http://localhost:11434
OpenAI 兼容接口: 1
POST http://localhost:11434/v1/chat/completions
FastAPI / 前端 / LangChain 都可以直接连。
结论:Ollama = 本地 LLM 运行时;GGUF = 模型文件;Modelfile = 行为配置。
案例 3:Android 端侧跑 LLM
1. 为什么端侧不能用 HuggingFace 格式直接跑
Android 上常见问题: - 没有 Python - 显存不存在,只有 RAM / NPU - 需要 APK 内嵌模型 - 要 C++ 层做高性能推理
所以路径是: 1
safetensors → GGUF → llama.cpp(C++) → JNI → Kotlin
2. 转换过程补充
1 | |
Q4_0:端侧常用,体积小,速度块,质量略降- 1.5B Q4 大概 1GB 出头,适合手机
3. Android 项目结构
1 | |
4. Kotlin 通过 JNI 调 C++
1 | |
JNI 层: 1
2
3
4
5
6
7extern "C" JNIEXPORT jstring JNICALL
Java_com_example_LlamaEngine_generate(JNIEnv* env, jobject, jstring prompt) {
const char* p = env->GetStringUTFChars(prompt, nullptr);
// llama.cpp 加载 GGUF、跑 decode
std::string out = llama_generate(p);
return env->NewStringUTF(out.c_str());
}
关键点: > Kotlin 不“理解” GGUF,它只是通过 JNI 调用 C++ 推理引擎;真正读 GGUF 的是 llama.cpp。
5. 如果 Android 想用 ONNX 而不是 GGUF
那就不走 llama.cpp,改走 ONNX Runtime Mobile:
1 | |
Kotlin: 1
2
3
4
5val env = OrtEnvironment.getEnvironment()
val session = env.createSession(assets.open("model.onnx").readBytes())
val input = OnnxTensor.createTensor(env, inputIds, longArrayOf(1, seqLen))
val result = session.run(mapOf("input_ids" to input))
但注意: - ONNX 更适合分类 / 检测 / OCR / 小模型 - 端侧自回归 LLM 通常 GGUF + llama.cpp 更成熟
案例 4:端侧 ASR(Conformer → ONNX → NCNN)
1. Conformer 模型和前面 3 个一样吗
不一样在任务域:
| 案例 | 模型 | 任务 |
|---|---|---|
| 1 | Decoder LLM | 文本生成 |
| 2 | Decoder LLM | 本地对话 |
| 3 | 小 LLM | 端侧对话 |
| 4 | Conformer / Zipformer | 语音识别 |
Conformer 也是 PyTorch 训出来的,但: - 输入是 log-Mel 声学特征 - 输出是 token / CTC / RNNT - 不是聊天模型
2. PyTorch → ONNX
1 | |
目的: > 把“PyTorch 动态图”变成“静态计算图”,方便跨框架和端侧引擎加载。
3. ONNX → NCNN
1 | |
产物: - .param:网络结构 - .bin:权重
4. C++ 直接跑
1 | |
这里没有 Python、没有 Docker、没有 HTTP,就是 C++ 读文件、跑矩阵运算。
