本地推理服务:llama.cpp / vLLM,并发与上下文预算
硬件前提
- llama.cpp / Ollama:单卡 8~24GB,或纯内存 16~32GB
- vLLM:单卡 16GB 起(4bit/8bit 可更低),必须有 NVIDIA 显卡
什么时候从「本地跑着玩」升级到「服务」
- 要给多个程序/多人用 → 必须是常驻服务
- 要并发(同时几个请求)→ 需要批处理调度
- 要稳定延迟与可观测性 → 需要日志与指标
两条路线
| llama.cpp 系(含 Ollama) | vLLM | |
|---|---|---|
| 硬件 | CPU / 消费级显卡都行 | NVIDIA 为主 |
| 并发能力 | 弱到中等 | 强(连续批处理) |
| 模型格式 | GGUF | safetensors(可 AWQ/GPTQ 量化) |
| 上手难度 | 低 | 中 |
| 适合 | 个人、离线、混合精度省内存 | 多人服务、吞吐优先 |
路线 A:llama.cpp 起服务
llama-server -m ./models/qwen2.5-7b-instruct-q4_k_m.gguf \
--host 127.0.0.1 --port 8080 \
-c 8192 -ngl 99
关键参数:
| 参数 | 含义 | 建议 |
|---|---|---|
-c | 上下文长度 | 按内存预算给,宁小勿大 |
-ngl | 卸载到显卡的层数 | 有卡就拉满(99),装不下就往下减 |
--parallel / 并发槽 | 同时处理几个请求 | 每个槽都会占上下文内存,翻倍前先算 |
判断标准:-c 8192 --parallel 4 能不能开,取决于「权重 + 4 × 上下文开销」是否装得下。
路线 B:vLLM 起服务
pip install vllm
vllm serve <模型名> \
--max-model-len 8192 \
--gpu-memory-utilization 0.90
关键参数:
| 参数 | 含义 | 建议 |
|---|---|---|
--max-model-len | 单请求上下文上限 | 直接决定显存占用,先给小值 |
--gpu-memory-utilization | 允许占用的显存比例 | 0.85~0.92;有其他进程在跑就调低 |
--tensor-parallel-size | 切到几张卡 | 单卡装不下时才用 |
--quantization | 量化方式(如 awq) | 显存不够时选,先测质量 |
vLLM 会预分配显存池,启动就占满是正常现象,不是 bug。
并发与上下文预算(核心)
服务能撑多少并发,几乎只由一个式子决定:
权重占用 + 并发数 × 每请求上下文占用 ≤ 可用显存 × 利用率
实操顺序:
- 先定
--max-model-len(业务真的需要多长?多数场景 4K~8K 够); - 压测并发:从 1 开始翻倍,直到延迟明显上升或报错;
- 记两个数字:首 token 延迟 和 每秒 token(不同并发下)。
常见失败与排查
| 现象 | 原因 | 处理 |
|---|---|---|
| 启动即 OOM | 显存池预分配 + 上下文太大 | 降 --max-model-len、降 --gpu-memory-utilization、换量化版 |
| 跑着跑着 OOM | 并发上来后 KV 撑爆 | 减并发或减上下文 |
| 首 token 很慢 | 输入太长(prefill 阶段) | 裁剪历史与检索片段 |
| 吞吐上不去 | 上下文过长、批处理被长请求拖累 | 限制最大长度;把长任务分流 |
| 输出乱码/复读 | 采样参数或模型与模板不匹配 | 用服务自带的对话模板,别手拼 |
完成标志
- 一个常驻服务,OpenAI 兼容端点能被你的程序调用;
- 你记录了这台机器上的三组数字:最大上下文、可接受并发的延迟、每秒 token;
- 明确知道瓶颈是显存还是上下文长度——这决定你下一步是加卡还是改设计。