推理这件事,难在哪
LLM 推理看起来简单:输入 prompt,生成 token,拼成回答。但真要把它做成生产服务,会踩到三个硬坑——
显存瓶颈
一个 7B 模型 FP16 就要 14GB,KV cache 还要留一大半。序列越长、batch 越大,越容易 OOM。
长尾拉跨吞吐
一个 batch 里 99% 的请求只要 50 token,但 1% 要 2000 token。GPU 得陪着那个最慢的跑完,前面的早就空转了。
GPU 利用率低
nvidia-smi 看 GPU 利用率 30%-40% 是常态。不是模型跑得慢,是时间都花在等输入、等输出、padding 对齐上。
痛点 1:KV cache 是显存的头号杀手
Transformer 解码时每生成一个 token,都要把前面所有 token 的 Key / Value 存下来作为上下文。这份缓存叫 KV cache。
一条 Llama-2-13B 序列的 KV cache 大小公式: 2 (K+V) × 层数 × 头数 × 头维度 × 序列长度 × 精度 = 2 × 40 × 40 × 128 × 2048 × 2 byte ≈ 1.6 GB / 条请求
一张 A100-80GB 装进 13B 模型本体后大约剩 54GB,按传统做法要给每条请求预留 max_seq_len 的连续 KV 显存——能同时跑的请求数很快撞上上限。
更糟的是浪费
预留按最大长度算,实际序列短得多。测试显示传统推理里 60%-80% 的 KV 显存是空的——被 padding 和预留占着,却没装任何 token。
预留按最大长度算,实际序列短得多。测试显示传统推理里 60%-80% 的 KV 显存是空的——被 padding 和预留占着,却没装任何 token。
痛点 2:静态 batch 等最慢的那个
HuggingFace 默认的 model.generate(batch) 要求所有序列同进同出:
请求 A:prompt=20 → 生成 50 token (40ms 完成) 请求 B:prompt=30 → 生成 200 token (160ms) 请求 C:prompt=10 → 生成 2000 token (1.6s) ───────────────────────────────────────────── 整个 batch 结束时间 = max(40, 160, 1600) = 1.6s A 的用户等了 1.6s ——本来只要 40ms
静态 batch 的 GPU 利用率像心电图:开头所有请求一起涌,中段只剩长尾请求,GPU 算力在空转。
痛点 3:吞吐与延迟的矛盾
想提升吞吐就加大 batch size,想降低延迟就减小 batch size——传统架构只能二选一:
| 策略 | 吞吐 QPS | p95 延迟 | 问题 |
|---|---|---|---|
| batch=1(流式) | 10 | 0.8s | GPU 利用率极低,成本高 |
| batch=32(静态) | 80 | 3.2s | 尾部请求等得难受 |
| batch=128 | 110 | 8s+ | 长请求拖垮整个 batch |
vLLM 是怎么打破僵局的
vLLM 2023 年在 UC Berkeley 由 Kwon 等人提出(SOSP'23 论文 "Efficient Memory Management for Large Language Model Serving with PagedAttention"),核心就两招:
- PagedAttention:KV cache 不再预留连续显存,切成固定大小的 block,像操作系统管内存一样按需分配、复用、共享。显存利用率从 40% 拉到 96%。
- Continuous Batching:每一步 decode 都可以插入新请求、踢掉完成的请求,GPU 没有空隙。不用再等最慢的那个。
效果有多夸张?
同一张 A100-40GB 跑 Llama-13B:HuggingFace 吞吐约 8 req/s,vLLM 约 160 req/s,20× 提升。相同 SLA 下,云成本直接降到 1/10-1/20。
同一张 A100-40GB 跑 Llama-13B:HuggingFace 吞吐约 8 req/s,vLLM 约 160 req/s,20× 提升。相同 SLA 下,云成本直接降到 1/10-1/20。
vLLM 在生态里的位置
| 方案 | 定位 | 适合场景 |
|---|---|---|
| HF Transformers | 研究 / 单次调用 | 实验、少量推理 |
| Text Generation Inference (TGI) | HF 官方推理服务 | HF 生态深度集成,企业订阅 |
| vLLM | 开源吞吐王 | 自建推理服务、成本敏感 |
| TensorRT-LLM | NVIDIA 深度优化 | 延迟极致 + 肯花时间编 engine |
| llama.cpp / Ollama | 本地 / 边缘 | Mac / CPU / 消费级 GPU |
| SGLang | 新秀,RadixAttention | 多轮对话、前缀共享场景极强 |
选型一句话:自建 LLM 服务、要吞吐、开源,默认上 vLLM;极致低延迟且团队有 CUDA 背景再考虑 TensorRT-LLM。
vLLM 能做什么
- OpenAI 兼容 API:启动即可
curl http://localhost:8000/v1/chat/completions,直接用 openai SDK - 支持主流开源模型:Llama、Qwen、Mistral、DeepSeek、Gemma、Phi、Yi、GLM 等几十种
- 量化推理:AWQ / GPTQ / FP8 / BitsAndBytes,70B 挤进单卡
- 投机解码:小模型押大模型,延迟减半
- 多 LoRA 热插拔:一个 base 模型 + N 个 adapter 同时服务
- 张量并行 / 流水并行:把 70B+ 切到 4-8 张卡
- 前缀缓存:相同 system prompt 多请求共享 KV
- 结构化输出:JSON Schema / Regex / GBNF 约束生成
- 多模态:Llava / Qwen-VL / InternVL 等 vision-language 模型
vLLM 不是银弹
有些场景它不擅长,提前知道省得踩坑:
| 场景 | 建议 |
|---|---|
| 单条请求极致低延迟 | TensorRT-LLM 或 Llama.cpp,vLLM 调度层开销可感 |
| CPU / Mac 本地部署 | Ollama / llama.cpp,vLLM 0.6 才刚加 CPU 后端 |
| 训练 / fine-tune | 用 unsloth / axolotl / transformers,vLLM 只做推理 |
| 超长上下文前缀共享 | SGLang 的 RadixAttention 更省,vLLM 前缀缓存也有但粒度粗 |
一个 30 秒 demo 感受下
# 启服务 docker run --gpus all -p 8000:8000 \ vllm/vllm-openai:latest \ --model meta-llama/Llama-3-8B-Instruct \ --gpu-memory-utilization 0.9
from openai import OpenAI client = OpenAI(base_url="http://localhost:8000/v1", api_key="not-used") resp = client.chat.completions.create( model="meta-llama/Llama-3-8B-Instruct", messages=[{"role": "user", "content": "用一句话介绍 vLLM"}], ) print(resp.choices[0].message.content)
跟 OpenAI API 完全同款——你的业务代码一行不改就能切到自建模型。
本章小结
- 传统 LLM 推理卡在三处:KV cache 预留浪费、静态 batch 等长尾、GPU 利用率低
- vLLM 用 PagedAttention(像 OS 虚拟内存管 KV)+ Continuous Batching(每步插入/踢出请求)解决
- 同硬件相较 HuggingFace 吞吐可达 20 倍,成本降到 1/10
- OpenAI 兼容 API、支持主流开源模型、量化 / LoRA / 多卡一站式
- 不擅长场景:单请求低延迟、CPU 部署、训练——选别的工具