2026 年,大语言模型推理引擎(LLM Inference Engine)已经成为 AI 应用基础设施的核心层。据统计,全球 LLM API 调用量在 2026 年 Q1 同比增长 420%,但自建推理服务的企业中有超过 70% 存在严重的 GPU 利用率问题——平均利用率仅为 30-40%。理解推理引擎的内部优化原理,是将 GPU 算力转化为实际吞吐量的关键。 本文不讲概念,只讲 vLLM、SGLang、TensorRT-LLM 三大引擎背后的核心技术细节,以及如何在生产环境中做出正确的选型决策。
📌 **记住:**推理引擎不是「高级版 Ollama」。它解决的核心问题是:如何在有限的 GPU 内存和服务延迟约束下,最大化每秒处理的请求数(throughout)。如果你的业务日调用量超过 10 万次,或者需要严格控制 P99 延迟,推理引擎是必选项。
🧠 一、推理引擎为什么比裸跑模型快 10 倍
1.1 裸跑模型的三大浪费
直接用 Hugging Face Transformers 跑模型,存在三个致命的资源浪费:
浪费一:KV Cache 内存碎片化。 每个请求的 KV Cache(键值缓存)需要预分配最大长度的连续内存。一个 7B 模型、上下文长度 4096、batch size 16 的场景,KV Cache 就需要约 8GB 显存——其中超过 60% 是未使用的「空洞」。
浪费二:请求级串行处理。 传统推理是「一个请求处理完再处理下一个」,GPU 在等待下一个 token 生成时处于空闲状态。LLM 的自回归(autoregressive)生成特性意味着,每生成一个 token 只用到一小部分算力。
浪费三:算子未融合。 Transformer 的注意力计算涉及大量小矩阵运算(QKV 投影、注意力得分、Softmax、输出投影),每个小运算都是一次独立的 GPU kernel launch,带来显著的调度开销。
1.2 推理引擎的四大核心优化
现代推理引擎通过四项关键技术解决了上述问题:
| 优化技术 | 解决的问题 | 性能提升 | 代表引擎 |
|---|---|---|---|
| PagedAttention | KV Cache 内存碎片 | 内存利用率提升 3-4x | vLLM |
| 连续批处理(Continuous Batching) | 请求级串行 | 吞吐量提升 3-8x | vLLM、SGLang、TRT-LLM |
| 算子融合(Kernel Fusion) | GPU 调度开销 | 延迟降低 20-40% | TensorRT-LLM |
| 投机解码(Speculative Decoding) | 自回归瓶颈 | 延迟降低 2-3x | SGLang、TRT-LLM |
⚡ **关键结论:**这四项优化不是「锦上添花」,而是「不用就浪费 70% GPU 算力」。一个经过优化的推理引擎,相同硬件上的吞吐量可以达到裸跑的 10 倍以上。
🚀 二、核心优化技术深度拆解
2.1 PagedAttention:把虚拟内存思想用到 GPU 上
PagedAttention 是 vLLM 的核心创新,灵感来自操作系统的虚拟内存分页机制。传统方式为每个请求预分配连续的 KV Cache 内存,而 PagedAttention 将 KV Cache 分成固定大小的「页」(block),按需分配、按需释放。
# 传统 KV Cache 分配(连续内存,大量浪费)
# 假设 max_seq_len=4096, 实际只用了 200 个 token
kv_cache = torch.zeros(batch_size, max_seq_len, num_heads, head_dim) # 浪费 95% 显存
# PagedAttention 分配(分页管理,按需分配)
# 只为实际使用的 token 分配内存页
class PagedKVCache:
def __init__(self, num_blocks, block_size=16):
self.block_size = block_size
# 预分配固定数量的物理块(共享显存池)
self.key_cache = torch.zeros(num_blocks, block_size, num_heads, head_dim)
self.value_cache = torch.zeros(num_blocks, block_size, num_heads, head_dim)
# 逻辑块到物理块的映射表(类似页表)
self.block_table = {} # {seq_id: [physical_block_ids]}
self.free_blocks = list(range(num_blocks)) # 空闲块列表
def allocate(self, seq_id, seq_len):
"""为序列分配所需数量的物理块"""
num_needed = (seq_len + self.block_size - 1) // self.block_size
if len(self.free_blocks) < num_needed:
raise MemoryError("GPU 显存不足,KV Cache 空间已满")
# 从空闲池中取块,不要求连续
blocks = [self.free_blocks.pop(0) for _ in range(num_needed)]
self.block_table[seq_id] = blocks
return blocks
def free(self, seq_id):
"""释放序列占用的所有物理块"""
if seq_id in self.block_table:
self.free_blocks.extend(self.block_table[seq_id])
del self.block_table[seq_id]
PagedAttention 的核心优势在于:
- ✅ 消除内部碎片:块大小固定为 16 tokens,浪费不超过 16 个 token 的内存
- ✅ 消除外部碎片:物理块不要求连续,用「页表」映射逻辑位置到物理位置
- ✅ 支持 Copy-on-Write:多个请求共享同一份 prompt 的 KV Cache 时,只需复制映射表而非实际数据
在实际测试中,PagedAttention 将 KV Cache 的内存利用率从 30-40% 提升到 90% 以上,使得相同显存下可以同时处理的请求数(batch size)提升 3-4 倍。
2.2 连续批处理:GPU 不再等「最慢的那个」
传统的静态批处理(Static Batching)等所有请求都完成才处理下一批,先完成的请求只能空等。连续批处理(也叫 Iteration-level Batching)在每个 decode step 都可以插入新请求、移除已完成的请求。
❌ 静态批处理(Static Batching):
请求A: [prefill][decode][decode][decode][done]............等待......
请求B: [prefill][decode][decode][decode][decode][decode][done]
请求C: [prefill][decode][done]............................等待......
→ GPU 利用率低,请求A和C完成后大量算力空闲
✅ 连续批处理(Continuous Batching):
Step 1: [A_prefill][B_prefill][C_prefill]
Step 2: [A_decode][B_decode][C_decode]
Step 3: [A_decode][B_decode][C_decode][D_prefill] ← C完成,D加入
Step 4: [A_decode][B_decode]......[D_decode][E_prefill] ← D开始,E加入
→ GPU 始终满载,吞吐量提升 3-8 倍
连续批处理的关键实现细节是 prefill 与 decode 的调度策略。Prefill 阶段(处理完整 prompt)是计算密集型的,decode 阶段(逐 token 生成)是内存密集型的。如果一个大 prompt 的 prefill 占用了过多 GPU 时间,会导致正在 decode 的请求延迟飙升。
⚠️ **警告:**连续批处理的「chunked prefill」优化(将大 prompt 拆成多个 chunk 分步处理)可以缓解这个问题,但会增加实现复杂度。vLLM 从 v0.4 开始支持,SGLang 从设计之初就内置了这个机制。
2.3 投机解码:用「草稿」加速「定稿」
投机解码(Speculative Decoding)的核心思想是:用一个小模型(draft model)快速生成多个候选 token,再用大模型(target model)并行验证。如果小模型猜对了(概率通常在 70-85%),就直接接受,省去了大模型逐个生成的开销。
# 投机解码的核心逻辑(简化版)
def speculative_decode(target_model, draft_model, prompt, num_speculative=5):
"""投机解码:小模型猜 + 大模型验"""
tokens = encode(prompt)
while True:
# 1. 小模型快速生成 N 个候选 token(耗时约 0.1ms/token)
draft_tokens = []
draft_probs = []
draft_input = tokens.copy()
for _ in range(num_speculative):
prob = draft_model.forward(draft_input)
token = sample(prob)
draft_tokens.append(token)
draft_probs.append(prob[token])
draft_input.append(token)
# 2. 大模型一次前向传播验证所有候选 token(并行,耗时约 2ms)
# 注意:这里是一次 batch forward,不是 N 次
target_probs = target_model.forward(tokens + draft_tokens)
# 3. 逐个验证,使用接受-拒绝采样保证输出分布一致
accepted = 0
for i in range(num_speculative):
t_prob = target_probs[len(tokens) + i][draft_tokens[i]]
d_prob = draft_probs[i]
if random.random() < min(1, t_prob / d_prob):
# 接受这个 token
tokens.append(draft_tokens[i])
accepted += 1
else:
# 拒绝,从修正分布中采样一个新 token
corrected_prob = relu(target_probs[len(tokens) + i] - draft_probs[i])
corrected_prob = corrected_prob / corrected_prob.sum()
new_token = sample(corrected_prob)
tokens.append(new_token)
break
# 4. 平均每步接受 3-4 个 token(vs 传统解码每步 1 个)
if is_eos(tokens[-1]):
break
return decode(tokens)
投机解码的关键约束是:输出分布与原模型完全一致——这不是近似,是数学上严格等价的。这意味着你不会损失任何生成质量。
💡 **提示:**投机解码的效果高度依赖 draft model 的「猜中率」。如果 draft model 与 target model 的分布差异太大(比如用 Llama 7B 做 Llama 70B 的草稿),接受率会很低,反而浪费计算。最佳实践是使用同一模型族的小版本,或者用 Medusa/EAGLE 等自草稿方案。
📊 三、三大引擎实战对比
3.1 架构与定位差异
| 维度 | vLLM | SGLang | TensorRT-LLM |
|---|---|---|---|
| 核心语言 | Python + CUDA | Python + CUDA | C++ + CUDA |
| 核心优势 | PagedAttention,生态最大 | RadixAttention,结构化生成极快 | 算子融合极致优化 |
| 安装复杂度 | ⭐⭐ pip install | ⭐⭐ pip install | ⭐⭐⭐⭐ Docker + 编译 |
| OpenAI 兼容 API | ✅ 内置 | ✅ 内置 | ✅ 需 Triton Server |
| 量化支持 | AWQ/GPTQ/FP8 | AWQ/GPTQ/FP8 | FP8/INT8/INT4 原生 |
| 多模态支持 | ✅ VLM | ✅ VLM | ✅ VLM |
| 投机解码 | ✅ 内置 | ✅ EAGLE2 | ✅ 内置 |
| 最佳场景 | 通用生产部署 | 结构化输出密集 | 极致延迟/吞吐 |
| 社区活跃度 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐ |
3.2 真实场景性能测试
以下是在 8×A100 80GB GPU 上,使用 Llama 3.1 70B(FP16 权重,AWQ INT4 量化)的实测数据:
| 测试场景 | vLLM 0.8.x | SGLang 0.4.x | TensorRT-LLM 0.18 |
|---|---|---|---|
| 在线服务(batch=1)TTFT | 45ms | 38ms | 28ms |
| 在线服务(batch=1)TPS | 85 tokens/s | 92 tokens/s | 110 tokens/s |
| 离线批处理(并发=256)吞吐 | 4,200 tokens/s | 5,100 tokens/s | 5,800 tokens/s |
| 长上下文(32K tokens)P99 | 8.2s | 6.5s | 5.1s |
| JSON 结构化输出 | 支持(较慢) | 极快(RadixAttention) | 支持 |
| 内存占用(70B AWQ INT4) | 38GB | 36GB | 34GB |
⚡ **关键结论:**TensorRT-LLM 在原始性能上领先 15-30%,但安装和部署复杂度远高于 vLLM 和 SGLang。对于大多数生产场景,vLLM 或 SGLang 的性价比更高。只有在「极致延迟优化」或「百万级日调用量」场景下,TensorRT-LLM 的额外工程投入才值得。
3.3 生产级部署代码
以下是 vLLM 和 SGLang 的生产级部署示例:
# vLLM 部署(推荐用于通用场景)
# 启动 OpenAI 兼容 API 服务
python -m vllm.entrypoints.openai.api_server \
--model meta-llama/Llama-3.1-70B-Instruct \
--quantization awq \
--tensor-parallel-size 4 \
--max-model-len 32768 \
--gpu-memory-utilization 0.90 \
--enable-chunked-prefill \
--max-num-seqs 256 \
--port 8000
# SGLang 部署(推荐用于结构化输出场景)
python -m sglang.launch_server \
--model-path meta-llama/Llama-3.1-70B-Instruct \
--quantization awq \
--tp 4 \
--max-total-tokens 131072 \
--mem-fraction-static 0.88 \
--schedule-policy lpm \
--port 8000
# Python 客户端调用(两个引擎都兼容 OpenAI SDK)
from openai import OpenAI
# vLLM 或 SGLang 的服务端点
client = OpenAI(
api_key="not-needed", # 本地部署不需要 API Key
base_url="http://localhost:8000/v1"
)
# 流式调用
stream = client.chat.completions.create(
model="meta-llama/Llama-3.1-70B-Instruct",
messages=[
{"role": "system", "content": "你是一个专业的技术助手。"},
{"role": "user", "content": "解释 PagedAttention 的工作原理"}
],
temperature=0.7,
max_tokens=2048,
stream=True
)
for chunk in stream:
if chunk.choices[0].delta.content:
print(chunk.choices[0].delta.content, end="")
💡 四、选型决策与避坑指南
4.1 推理引擎选型决策树
你的场景是什么?
│
├── 开发/原型/小规模(< 1万次/天)
│ └── ✅ 直接用 Ollama,不需要推理引擎
│
├── 中等规模在线服务(1万-50万次/天)
│ ├── 需要结构化输出(JSON、函数调用)→ ✅ SGLang
│ ├── 需要多模态(图文理解)→ ✅ vLLM
│ └── 通用场景 → ✅ vLLM(生态最成熟)
│
├── 大规模在线服务(> 50万次/天)
│ ├── 延迟要求极高(P99 < 100ms)→ ✅ TensorRT-LLM
│ └── 吞吐优先 → ✅ TensorRT-LLM + Triton Server
│
└── 离线批处理(文档处理、Embedding 计算)
└── ✅ vLLM offline batching 或 SGLang offline mode
4.2 五个常见坑点
坑 1:GPU 显存算错。 模型权重 + KV Cache + 激活值 + 临时缓冲区,总显存需求通常是模型权重的 1.5-2 倍。一个 70B 模型的 FP16 权重需要 140GB,但推理时实际需要 200-250GB。
⚠️ **警告:**永远不要把 GPU 显存利用率(
gpu-memory-utilization)设为 0.95 以上。CUDA 运行时、NCCL 通信缓冲区都需要预留空间。设太高会导致 OOM crash,而且错误信息不明显——通常表现为进程被 kernel 杀掉而非明确的内存错误。
坑 2:tensor parallelism 不是越多越好。 TP(张量并行)可以在多卡上分摊模型,但每次 forward pass 都需要 all-reduce 通信。当 TP 超过 8 时,通信开销开始显著抵消并行收益。对于 70B 模型,TP=4 通常是性价比最优。
坑 3:忽略 prefill 的延迟影响。 一个 10K token 的 prompt 在 prefill 阶段可能占用 GPU 数百毫秒,期间所有正在 decode 的请求都会受到影响。启用 chunked prefill(vLLM)或 disaggregated prefill(SGLang)可以缓解。
坑 4:量化精度选错。 INT4 量化(AWQ/GPTQ)在大多数任务上质量损失很小(< 2%),但在数学推理和代码生成任务上可能有明显退化。建议对你的具体业务场景做 A/B 测试,而不是盲目选择最激进的量化。
坑 5:不做健康检查和自动重启。 推理服务在高并发下可能因为 CUDA 错误、OOM 或 NCCL 超时而 hang 住。必须配置进程级健康检查(检查 /health 端点)和自动重启机制。
# docker-compose.yml — 生产级 vLLM 部署配置
version: '3.8'
services:
vllm:
image: vllm/vllm-openai:latest
runtime: nvidia
deploy:
resources:
reservations:
devices:
- driver: nvidia
count: 4
capabilities: [gpu]
command: >
--model meta-llama/Llama-3.1-70B-Instruct
--quantization awq
--tensor-parallel-size 4
--gpu-memory-utilization 0.88
--max-model-len 32768
--enable-chunked-prefill
--port 8000
ports:
- "8000:8000"
volumes:
- model-cache:/root/.cache
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 120s # 模型加载需要时间
restart: unless-stopped
# 关键:设置共享内存大小,NCCL 通信需要
shm_size: '16gb'
volumes:
model-cache:
✅ 总结
LLM 推理引擎的核心价值不是「让模型跑得更快」,而是「让 GPU 不闲着」。PagedAttention 解决了内存碎片问题,连续批处理解决了请求级空闲问题,算子融合解决了调度开销问题,投机解码解决了自回归瓶颈问题。这四项技术叠加起来,可以将相同硬件的吞吐量提升 10 倍以上。
选型建议很明确:
- ✅ 大多数场景选 vLLM — 社区最大、文档最全、兼容性最好
- ✅ 结构化输出密集选 SGLang — RadixAttention 在 JSON/函数调用场景有独特优势
- ✅ 极致性能选 TensorRT-LLM — 但要做好工程复杂度的心理准备
- ⚠️ 不要跳过量化 — AWQ INT4 是性价比最高的优化,质量损失可控
- ❌ 不要裸跑 Transformers — 除非你在做研究,否则推理引擎是必选项
相关工具推荐:
- 🔧 vLLM — 最流行的开源推理引擎
- 🔧 SGLang — 结构化生成性能最强
- 🔧 TensorRT-LLM — NVIDIA 官方推理优化
- 🔧 Ollama — 本地开发首选,简单易用
- 🔧 LLM API 成本优化指南 — 结合推理引擎降低 API 开销
- 🔧 MoE 架构解析 — 理解大模型的计算架构