把大模型跑起来容易,把大模型跑得又快又省显存很难。HuggingFace Transformers 的朴素实现吞吐低、显存浪费大,根本无法支撑生产流量。于是专门为 LLM 设计的推理引擎成为 LLMOps 基础设施的核心一环。目前工业界使用最广的三款是 vLLM、TensorRT-LLM 与 SGLang,它们各自代表了一条技术路线。
本文先讲清三者的架构差异,再用可比的基准数据说明性能差距,最后给出参数调优与选型建议。理解这三款引擎,是做好 LLMOps 全景 中部署与成本两环的前提。
为什么通用框架撑不起生产
先量化问题。以一个 13B 模型在 A100 80GB 上的朴素推理为例:
- 显存被 KV Cache 大量占用,且因预分配最大长度而产生严重碎片,实测可用显存利用率常常只有 20% 到 40%。
- 请求必须等最长的那个生成完才能返回,短请求被长请求阻塞。
- 批大小固定,无法随请求到达动态调整。
这三个问题分别对应三个工程答案:分页式 KV Cache 管理、连续批处理、动态调度。三大引擎都在这三点上做了优化,只是实现路径不同。
vLLM 架构解析
vLLM 是伯克利团队开源的推理引擎,当前稳定版本为 0.6.3。它的两大支柱是 PagedAttention 与连续批处理。
PagedAttention 显存管理
PagedAttention 借鉴操作系统的虚拟内存分页思想,把 KV Cache 切分为固定大小的 block(默认 16 个 token),通过 block table 建立逻辑序列到物理 block 的映射。
这一设计带来三个收益:
- 显存碎片从朴素实现的 60% 到 80% 浪费,降到不足 4%。
- 相同显存下可以容纳更多并发序列,吞吐显著提升。
- 支持前缀共享,相同系统提示的请求可以共享物理 block。
其原理细节在 连续批处理与 PagedAttention 中有完整推导。
连续批处理与调度
vLLM 的调度器在每次迭代(iteration)级别决定哪些序列进入本批次。当一个序列生成结束,它占用的槽位立即被新到达的请求填补,而不是等待整个批次结束。这使 GPU 计算单元几乎时刻保持饱和。
调度器还实现了 preemption 机制:显存不足时,低优先级序列会被换出(swap 到 CPU)或重算(recompute),等显存释放后再恢复。
vLLM 的取舍
vLLM 的强项是易用性与通用性,几乎所有主流模型开箱即用,社区生态最大。它的短板在极端优化场景:对特定硬件的算子级调优不如 TensorRT-LLM 深入,复杂控制流(如结构化生成)需要额外集成。
TensorRT-LLM 架构解析
TensorRT-LLM 是 NVIDIA 官方推出的推理库,当前版本 0.13.0。它的核心思想是编译期优化:把模型图在部署前就编译成针对特定 GPU 架构深度优化的引擎。
编译期图优化
TensorRT-LLM 的编译流程包含:
- 算子融合(将 LayerNorm、Attention、FFN 的多个算子合并)
- 精度校准(FP8、INT8、INT4 的量化与校准)
- 内核自动调优(针对具体 GPU 型号选择最优实现)
- 内存规划(静态分配显存,减少运行时开销)
编译产出的引擎是高度特化的,换 GPU 型号需要重新编译,这是它的灵活性代价。
In-flight Batching
TensorRT-LLM 的 In-flight Batching 与 vLLM 的连续批处理目标一致,都是让批次在运行时动态重组。区别在于它构建在高度优化的 CUDA 内核之上,配合 paged KV Cache 支持,能在同等硬件上压榨出更高的利用率。
TensorRT-LLM 的取舍
它的强项是极致性能与 NVIDIA 生态整合,尤其在 H100 上配合 FP8 能取得最佳吞吐。短板是编译流程复杂、调试困难、对非 NVIDIA 硬件不适用,且模型支持滞后于社区。
SGLang 架构解析
SGLang 当前版本 0.4.1,最初来自 LMSYS 团队。它的两大特色是 RadixAttention 与前端 DSL。
RadixAttention
RadixAttention 用一个基数树(radix tree)管理 KV Cache,使得任意长度的公共前缀都能被复用。这比 vLLM 的 block 级前缀共享更细粒度。
典型受益场景:
- 多轮对话,历史前缀被反复复用。
- Few-shot 提示,所有请求共享同一段示例。
- 树状推理(如思维链分支、Beam Search),分支间共享前缀。
实测在强前缀共享场景下,SGLang 的缓存命中率与吞吐可以显著超过 vLLM。
前端 DSL
SGLang 提供了一套 Python 前端 DSL,让复杂生成流程(分支、循环、并行、约束解码)用声明式代码表达,并在运行时自动利用前缀共享。这让它在 Agent 与结构化生成场景中特别顺手。
SGLang 的取舍
强项是高并发前缀共享与复杂生成流程。短板是生态规模小于 vLLM,部分长尾模型的适配不如 vLLM 及时。
性能对比
以下数据为公开基准与实测的综合参考,硬件配置为单机 8 卡。数值随驱动、CUDA 版本、模型量化方式波动,请以自身环境实测为准。
Llama-3-8B,输入 512 token,输出 256 token:
| 引擎 | 硬件 | 并发 | 吞吐 tokens/s | TTFT P50 | TTFT P99 |
|---|---|---|---|---|---|
| vLLM 0.6.3 | A100 80G | 64 | 2450 | 45ms | 120ms |
| vLLM 0.6.3 | H100 80G | 64 | 3900 | 32ms | 85ms |
| TensorRT-LLM 0.13.0 | A100 80G | 64 | 2900 | 38ms | 95ms |
| TensorRT-LLM 0.13.0 | H100 80G (FP8) | 64 | 5200 | 22ms | 60ms |
| SGLang 0.4.1 | A100 80G | 64 | 2600 | 40ms | 100ms |
| SGLang 0.4.1 | H100 80G | 64 | 4100 | 28ms | 72ms |
Llama-3-70B,TP=4,输入 1024 token,输出 512 token:
| 引擎 | 硬件 | 并发 | 吞吐 tokens/s | TTFT P50 | TTFT P99 |
|---|---|---|---|---|---|
| vLLM 0.6.3 | 4x A100 80G | 32 | 780 | 180ms | 420ms |
| vLLM 0.6.3 | 4x H100 80G | 32 | 1450 | 120ms | 300ms |
| TensorRT-LLM 0.13.0 | 4x H100 80G (FP8) | 32 | 2100 | 95ms | 240ms |
| SGLang 0.4.1 | 4x H100 80G | 32 | 1520 | 115ms | 290ms |
从数据可以看出几条规律:
- H100 相对 A100 在同等配置下有 1.5 到 1.6 倍吞吐提升。
- TensorRT-LLM 在 H100 加 FP8 的组合下优势最明显,可达 vLLM 的 1.3 倍以上。
- SGLang 在前缀共享强的场景(如多轮对话)会进一步拉开差距,上表为弱共享基准。
- 70B 模型必须张量并行,TTFT 明显高于 8B,这是显存带宽与通信开销共同决定的。
更系统的硬件与引擎基准方法可参考 推理基准测试 。
关键参数详解
无论用哪个引擎,下面这些参数都是调优的核心。以 vLLM 的命名为主,其他引擎有对应概念。
| 参数 | 含义 | 推荐值 | 影响 |
|---|---|---|---|
| gpu-memory-utilization | 预留给模型的显存比例 | 0.9 | 太高易 OOM,太低浪费并发 |
| max-num-seqs | 单批次最大序列数 | 256 | 提高吞吐但增加 TTFT |
| max-model-len | 最大上下文长度 | 按业务设定 | 直接决定 KV Cache 上限 |
| tensor_parallel_size | 张量并行度 | 按显存需求 | 跨卡切分,引入通信开销 |
| enable-chunked-prefill | 分块预填充 | 开启 | 降低长提示对 TTFT 的冲击 |
| max-num-batched-tokens | 单批最大 token 数 | 8192 | 与 chunked prefill 配合 |
| block-size | KV Cache 块大小 | 16 | 影响碎片与调度粒度 |
几个要点:
- gpu-memory-utilization 0.9 是常用起点,但要在真实负载下压测。设太高会导致长上下文请求 OOM,设太低则并发受限。
- max-num-seqs 256 适合中等规模并发。盲目调高会让单请求 TTFT 上升,因为批越大,每个序列分到的算力越少。
- max-model-len 与显存强相关。8B 模型在 80GB 卡上,max-model-len 从 8K 提到 32K 会显著压缩可用并发数。
- enable-chunked-prefill 对长提示场景几乎是必开项,它把长预填充拆成多个 chunk 插入批次,避免长提示阻塞短请求。
选型决策
选型不是选最强,而是选最合适。下表给出决策路径:
| 场景 | 推荐引擎 | 理由 |
|---|---|---|
| 快速上线、模型种类多 | vLLM | 开箱即用,社区支持最好 |
| 追求极致吞吐、只用 NVIDIA | TensorRT-LLM | 编译优化与 FP8 收益最大 |
| 多轮对话、Agent、结构化生成 | SGLang | RadixAttention 前缀复用强 |
| 需要 FP8 且硬件是 H100 | TensorRT-LLM 或 vLLM | 两者都支持,前者更优 |
| 需要跨云或非 NVIDIA 硬件 | vLLM | 兼容性最好 |
| 已有大量自定义 CUDA 算子 | TensorRT-LLM | 编译期融合更彻底 |
一个常见的实践是混合部署:主流量走 vLLM 保证稳定,性能敏感的核心路径走 TensorRT-LLM。但混合部署会带来运维复杂度,中小团队应谨慎。
实战 启动命令与客户端
下面给出 vLLM 与 SGLang 的启动命令,以及一个通用的 OpenAI 兼容客户端。
vLLM 启动 Llama-3-8B,单卡,开启 chunked prefill:
python -m vllm.entrypoints.openai.api_server \
--model meta-llama/Meta-Llama-3-8B-Instruct \
--served-model-name llama-3-8b \
--tensor-parallel-size 1 \
--gpu-memory-utilization 0.9 \
--max-num-seqs 256 \
--max-model-len 8192 \
--enable-chunked-prefill \
--max-num-batched-tokens 8192 \
--port 8000
vLLM 启动 Llama-3-70B,四卡张量并行:
python -m vllm.entrypoints.openai.api_server \
--model meta-llama/Meta-Llama-3-70B-Instruct \
--served-model-name llama-3-70b \
--tensor-parallel-size 4 \
--gpu-memory-utilization 0.92 \
--max-num-seqs 128 \
--max-model-len 16384 \
--enable-chunked-prefill \
--port 8000
SGLang 启动,利用 RadixAttention:
python -m sglang.launch_server \
--model-path meta-llama/Meta-Llama-3-8B-Instruct \
--tp-size 1 \
--mem-fraction-static 0.88 \
--max-running-requests 256 \
--context-length 8192 \
--port 30000
TensorRT-LLM 的典型流程是先构建引擎再启动服务:
trtllm-build --checkpoint_dir ./llama3-8b-ckpt \
--output_dir ./llama3-8b-engine \
--gemm_plugin float16 \
--use_paged_context_fmha enable \
--max_batch_size 256 \
--max_input_len 8192 \
--max_seq_len 10240
python -m tensorrt_llm.serve \
--engine_dir ./llama3-8b-engine \
--port 8000
统一的 OpenAI 兼容客户端,可同时指向任意一个引擎:
import time
from openai import OpenAI
client = OpenAI(base_url="http://localhost:8000/v1", api_key="EMPTY")
def bench(prompt: str, n: int = 32) -> dict:
ttfts, tps = [], []
for _ in range(n):
t0 = time.perf_counter()
stream = client.chat.completions.create(
model="llama-3-8b",
messages=[{"role": "user", "content": prompt}],
stream=True,
temperature=0.0,
max_tokens=256,
)
first = None
out = 0
for chunk in stream:
if chunk.choices and chunk.choices[0].delta.content:
if first is None:
first = time.perf_counter()
out += 1
ttfts.append((first - t0) * 1000)
tps.append(out / (time.perf_counter() - t0))
ttfts.sort()
return {
"ttft_p50_ms": round(ttfts[len(ttfts) // 2], 1),
"ttft_p99_ms": round(ttfts[-1], 1),
"tokens_per_s": round(sum(tps) / len(tps), 1),
}
if __name__ == "__main__":
print(bench("用三句话解释 PagedAttention 的原理。"))
这段客户端对三个引擎通用,因为 vLLM、SGLang 与 TensorRT-LLM 都提供 OpenAI 兼容接口。这也意味着切换引擎的成本主要在下游部署,而非上游应用代码。
进阶优化技术
三大引擎在基础调度之上,还共享一批进阶优化。理解这些技术有助于判断某个引擎是否适合你的场景。
投机解码
投机解码(Speculative Decoding)用一个小的草稿模型先快速生成若干候选 token,再由大模型并行验证。由于大模型的前向是并行的,验证多个候选的成本接近验证一个,因此在草稿命中率高时能显著加速。
- 草稿模型质量是关键,命中率低于 60% 时收益可能为负。
- vLLM 支持
--speculative-model参数指定草稿模型。 - 另一种变体是 n-gram 或 EAGLE 草稿,无需额外模型。
前缀缓存
前缀缓存让相同前缀的请求复用已计算的 KV Cache。三个引擎的实现粒度不同:
- vLLM 以 block 为粒度做前缀共享。
- SGLang 的 RadixAttention 以任意长度公共前缀为粒度。
- TensorRT-LLM 在 paged KV Cache 上支持前缀复用。
在系统提示固定、少样本示例固定的应用中,前缀缓存能省掉大量预填充计算,直接降低 TTFT。
分块预填充
长提示的预填充会一次性占用大量算力,阻塞正在解码的短请求。分块预填充(Chunked Prefill)把长预填充拆成多个小块,与解码请求混批执行,从而平滑 TTFT 的长尾。vLLM 的 --enable-chunked-prefill 即此功能。
量化推理
量化把权重与激活从 FP16 降到 FP8 或 INT8、INT4,减少显存占用并提升算力利用率。FP8 在 H100 上有原生支持,收益最大。INT4 主要用于显存极度受限的场景,需要评估质量损失。
| 精度 | 显存占用(相对 FP16) | 典型加速 | 质量影响 |
|---|---|---|---|
| FP16 | 100% | 基线 | 无 |
| FP8 | 50% | 1.3 到 1.8 倍 | 极小 |
| INT8 | 50% | 1.2 到 1.6 倍 | 较小 |
| INT4 | 25% | 1.5 到 2.0 倍 | 需评估 |
量化细节可参考 FP8 推理专题。
预填充解码分离
预填充阶段是算力密集的,解码阶段是显存带宽密集的。把两者放在同一张卡上,会互相干扰。预填充解码分离(PD Disaggregation)把两个阶段部署到不同的实例,各自用最优配置,从而同时提升吞吐与降低 TTFT。这是当前大厂在生产环境的主流方向,但实现复杂,中小团队可暂缓。
生产部署形态
引擎选定后,部署形态同样影响最终表现。
单机单卡
最简单,适合 8B 级别模型与中小流量。瓶颈是显存与单卡算力。
单机多卡张量并行
70B 级别模型的标配。张量并行把权重切到多卡,代价是每层都要做 AllReduce 通信。卡间带宽(NVLink 远优于 PCIe)直接决定扩展效率。
多副本横向扩展
单实例吞吐到顶后,横向加副本,前接负载均衡。注意副本间的前缀缓存不共享,若要共享需要外部 KV 缓存服务。
与 Kubernetes 结合
生产环境通常用 Kubernetes 编排推理服务,配合 GPU 调度、健康检查与自动扩缩容。这部分涉及 GPU 共享与弹性伸缩,属于另一个专门的工程领域。
部署形态对比
| 形态 | 适用规模 | 优点 | 缺点 |
|---|---|---|---|
| 单卡 | 8B 以下 | 简单、无通信开销 | 显存与算力受限 |
| 张量并行 | 70B 级 | 单实例可跑大模型 | 通信开销、扩展性受限 |
| 多副本 | 高并发 | 线性扩展吞吐 | 缓存不共享、成本线性增长 |
| PD 分离 | 大规模生产 | 吞吐与延迟兼优 | 架构复杂、运维成本高 |
硬件与引擎匹配
引擎的能力上限受硬件约束。同一引擎在不同 GPU 上的表现差异,往往大于不同引擎在同一 GPU 上的差异。
| GPU | 显存 | 适用模型规模 | 引擎建议 | 备注 |
|---|---|---|---|---|
| A100 40G | 40GB | 7B 到 13B | vLLM | FP8 支持有限 |
| A100 80G | 80GB | 13B 到 34B | vLLM、SGLang | 性价比高 |
| H100 80G | 80GB | 34B 到 70B | TensorRT-LLM、vLLM | FP8 原生支持 |
| H200 141G | 141GB | 70B 以上 | TensorRT-LLM | 大显存优势明显 |
| L40S 48G | 48GB | 7B 到 13B | vLLM | 推理性价比之选 |
匹配原则:
- 显存决定单卡能放多大的模型与多长的上下文。
- 算力(FLOPS)决定预填充速度,影响 TTFT。
- 显存带宽决定解码速度,影响 TPOT。
- 卡间带宽(NVLink 与 PCIe 的差距可达数倍)决定张量并行的扩展效率。
很多团队在升级到 H100 后期待吞吐线性增长,但若模型瓶颈在显存带宽而非算力,提升会低于预期。做容量规划时,先判断当前瓶颈在哪一类资源。
常见坑清单
坑一 只看吞吐不看 TTFT
吞吐与延迟是一对矛盾。把 max-num-seqs 拉到很大可以刷高吞吐,但单请求 TTFT 会明显变差。生产环境必须两个指标一起看。
坑二 gpu-memory-utilization 设得过高
0.95 以上的设置看似能榨出更多显存,实则把 OOM 风险留给了长上下文请求。建议从 0.9 起步,压测后再微调。
坑三 忽略 KV Cache 显存预算
max-model-len 设得过大,会让 KV Cache 上限吃光显存,导致并发数骤降。要根据业务真实上下文长度设定,而不是一律拉满。
坑四 张量并行度拍脑袋
张量并行度应与模型大小和卡数匹配。70B 用 TP=8 在 8 卡机上可行,但若换成 TP=4 会 OOM;反过来 8B 用 TP=4 纯属浪费,还引入不必要的通信开销。
坑五 用错量化格式
FP8 在 H100 上收益显著,但在 A100 上支持有限。INT4 能省显存但可能损害质量。量化选择要与硬件和精度要求一起评估,可参考本组的量化与显存专题。
坑六 不做前缀缓存规划
如果应用有大量共享系统提示,却没开启前缀缓存,等于白白浪费算力。多轮对话场景尤其要注意,可参考本组的 KV Cache 专题。
坑七 忘记压测真实流量分布
基准测试常用均匀的输入长度,而真实流量是长尾分布。用真实流量回放压测,才能暴露长提示阻塞等调度问题。
坑八 忽视版本升级的行为变化
推理引擎版本迭代快,升级可能改变默认调度策略或量化行为,导致性能与输出发生非预期变化。升级前应在影子环境跑一遍回归评测。
坑九 把基准数字当成承诺
厂商标称的吞吐往往基于最优输入长度与最大批。把它当作容量规划的依据会严重高估。容量规划应基于自身流量分布的实测值。
版本演进与兼容性
推理引擎的版本迭代速度远快于传统中间件,理解版本差异对稳定性很重要。
| 引擎 | 本文版本 | 关键能力节点 | 升级注意 |
|---|---|---|---|
| vLLM | 0.6.3 | 连续批处理成熟、支持 chunked prefill | 注意调度参数默认值变化 |
| TensorRT-LLM | 0.13.0 | FP8 与 paged KV 完善 | 引擎需随驱动与 CUDA 重编 |
| SGLang | 0.4.1 | RadixAttention 与前端 DSL 稳定 | 前端 DSL 偶有破坏性变更 |
升级策略建议:
- 锁定版本,避免生产环境被自动升级。
- 建立升级回归流程,覆盖性能基准与质量评测。
- 关注引擎的发布说明,尤其是默认参数与 API 的变化。
- 对 TensorRT-LLM,把编译产物纳入版本管理,保证可复现。
小结
三大引擎代表三条技术路线:vLLM 以 PagedAttention 与连续批处理实现通用高效,易用性与生态最好;TensorRT-LLM 以编译期图优化与 In-flight Batching 追求极致性能,代价是编译复杂与硬件绑定;SGLang 以 RadixAttention 与前端 DSL 在强前缀共享与复杂生成场景中占优。
选型的关键是把场景说清楚:模型种类多、要快速上线选 vLLM;性能极致、硬件统一选 TensorRT-LLM;多轮对话与 Agent 选 SGLang。无论选哪个,参数调优的核心都是吞吐与延迟的平衡,以及显存预算的精确规划。
理解了引擎层之后,下一步应该深入 PagedAttention 与连续批处理的内部机制,见本组的连续批处理专题;显存与量化的进一步优化见量化与显存专题。
最后要提醒的是,引擎只是整条链路的一环。再强的引擎,如果被上游的低效检索或下游的串行调用拖累,端到端体验依然糟糕。把引擎选型放在整体架构中权衡,而不是孤立地追求单点性能,才是正确的做法。
换句话说,推理引擎的优化上限由架构决定,而架构的合理性由业务场景决定。先想清楚业务,再谈引擎。
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。