本文目录
同一个模型 checkpoint,放进不同推理引擎后,输出速度、可承载并发和部署复杂度可能完全不同。原因是模型权重只定义了“算什么”,没有定义请求如何排队、KV Cache 如何分配、多个请求怎样组成 batch、选哪个 GPU kernel,以及结果如何流式返回。
vLLM、SGLang 和 TensorRT-LLM 都覆盖了这条路径的大部分环节,功能也在持续交叉。把它们简单概括成“通用、Agent、极致性能”很方便,却不足以做工程决策。真正的选型问题是:在同一模型、同一硬件和同一流量下,哪套系统更稳定地满足延迟目标,并且团队能够长期维护。
本文按 2026-08-09 的官方文档校订。推理引擎变化很快,具体模型、量化和后端支持应在采用时重新查阅支持矩阵。文中不保留脱离测试条件的固定吞吐排名。
从一次请求看推理引擎的职责
用户发送一条 Chat Completions 请求后,至少经过下面几层:
HTTP / RPC request
│
▼
API validation + chat template + tokenizer
│
▼
request queue + admission control
│
▼
scheduler + KV cache manager
│
▼
model runner + attention/GEMM/communication kernels
│
▼
logits processing + sampling + detokenization
│
▼
streaming response + metrics + cancellation
推理引擎的核心工作,可以归结为持续重复两个动作:
- 从所有等待和运行中的请求里,决定这一轮计算哪些 token;
- 把这些 token 组织成 GPU 能高效执行的输入,并正确维护每个请求的状态。
这比离线执行一次 model.generate() 复杂得多。请求长度不同、到达时间不同,用户还可能中途取消;系统既要填满 GPU,又不能让排队时间无限增长。
五个指标描述的是不同体验
假设一个请求输入 1000 token,输出 200 token:
- TTFT(Time to First Token):从请求到达,到收到第一个输出 token;包含排队、tokenization 和 prefill。
- TPOT(Time per Output Token):开始生成后,相邻输出 token 的平均间隔;主要反映 decode 体验。
- ITL(Inter-token Latency):逐 token 间隔的分布,可发现偶发卡顿,不能总被平均 TPOT 代表。
- E2E latency:请求到完整响应结束的时间。
- Throughput:单位时间处理的 request 或 token 数。
在线服务还常使用 goodput:在给定 SLO 内完成的有效吞吐。例如要求 TTFT P99 小于 2 秒、TPOT P99 小于 80 ms,那么超时请求产生的 tokens/s 再高,也不算满足目标的容量。
这些指标会互相牵制。调度器等待更多请求组成大 batch,可以提高吞吐,却增加排队时间;长 prefill 与 decode 混跑能提高 GPU 利用率,却可能让正在流式输出的用户突然卡顿。
因此,“某引擎有 10 万 tokens/s”至少还缺少:输入/输出长度、请求率、并发、GPU、模型、精度、并行度和延迟分位数。没有这些条件,数字无法用于选型。
先定义工作负载,再讨论引擎
三个常见场景的最优目标就不相同。
在线聊天
请求持续到达,输入中等、输出较长,用户直接感知流式速度。重点是 P95/P99 TTFT、TPOT、取消响应和高负载下的稳定性,而不是无限队列下的峰值吞吐。
RAG 与 Agent
system prompt、工具 schema 或文档前缀可能在请求间重复;输入很长,单次输出未必长。前缀缓存命中、structured output、tool calling、长 prefill 的调度公平性和多轮状态会明显影响结果。
离线批处理
请求已提前准备,通常没有严格 TTFT。引擎可以使用更大的 batch,目标偏向总完成时间、tokens/s 和单位 token 成本。在线服务中的队列公平性在这里不一定重要。
此外还要明确:
- 模型是 dense、MoE 还是多模态;
- 权重、激活与 KV Cache 使用何种精度;
- 单机还是多机,互联是 PCIe、NVLink 还是网络;
- 是否需要 LoRA、推测解码、grammar、embeddings 或 rerank;
- 流量是稳定、突发,还是明显共享前缀。
只有这些条件固定后,引擎之间的比较才有意义。
三个引擎的设计重心
下表描述的是评估入口,不是功能边界。三者都在快速吸收其他系统的能力。
| 维度 | vLLM | SGLang | TensorRT-LLM |
|---|---|---|---|
| 主要入口 | Python/offline 与兼容多种 API 的 serving | server/runtime 与结构化 LLM program 生态 | LLM/serve/bench 等 NVIDIA 工具链 |
| 缓存重点 | 分页 KV、automatic prefix caching、多类 connector | RadixAttention 与 cache-aware 调度 | paged KV、reuse、offload/disaggregation 等 |
| 调度重点 | 通用 continuous batching 与广泛 workload | prefix/structured/agent workload 的运行时协同 | NVIDIA GPU 上的 executor、kernel 与通信协同 |
| 硬件范围 | CUDA 之外也有多类后端,但支持度因模型而异 | 重点围绕主流加速硬件,需查支持矩阵 | 深度绑定 NVIDIA GPU 特性 |
| 评估风险 | 功能组合多,具体 backend 可能回退 | 版本迭代快,server 与 runtime 参数变化 | 硬件/量化支持矩阵细,旧教程容易过时 |
vLLM:广覆盖的 serving runtime
vLLM 从 PagedAttention 和 continuous batching 起步,当前覆盖模型 serving、离线推理、分布式执行、量化、structured output、prefix caching、LoRA、多模态与多种 API。
它的优势不是“每个场景绝对最快”,而是常能用较短路径把 Hugging Face checkpoint 变成可用服务,并保留足够多的调优空间。OpenAI-compatible server 也已不只包含基础 Chat/Completions;实际兼容范围仍需逐 endpoint 和参数核对,不能把“compatible”理解为所有语义完全相同。
评估 vLLM 时要确认:
- 模型是否走原生实现,还是 remote/custom model path;
- attention、MoE 和 quantization 实际选择了哪个 kernel;
- V1 调度器与目标功能组合是否支持;
- 多机时使用什么 distributed backend 和 KV connector;
- 默认
generation_config是否改变了 sampling 参数。
SGLang:让前缀结构进入运行时决策
SGLang 的代表性设计是 RadixAttention:将 token 前缀组织成 radix tree,使运行时可以复用共享前缀的 KV Cache,并让调度器考虑缓存命中。对共享 system prompt、few-shot 示例、树状搜索和 Agent 工具定义等 workload,这个视角非常自然。
SGLang 同时提供结构化生成、模型 serving 与面向 LLM program 的上层能力。它并不只适合 Agent,也可以作为普通兼容 API server;相反,使用 Agent 框架也不意味着 SGLang 必然更快,收益取决于前缀是否真的在 token 级重复,以及调度能否保留这些缓存。
评估时应重点记录:
- prefix cache hit 的 token 比例,而不只是请求命中率;
- 请求路由后,共享前缀是否落在同一实例;
- grammar/tool calling 是否改变采样瓶颈;
- 目标模型、量化和并行组合的成熟度;
- cache-aware 调度是否牺牲了其他请求的公平性。
TensorRT-LLM:NVIDIA 软硬件协同路径
TensorRT-LLM 长期强调 NVIDIA GPU 上的定制 kernel、低精度、in-flight batching、多 GPU 通信和 executor。到本文校订时,官方最新迁移文档已说明 PyTorch 是唯一执行后端,旧式 TensorRT engine build、trtllm-build 与 checkpoint conversion 流程已从新路径移除。因此,不能再把它固定描述为“必须先离线编译 TensorRT engine”。
这个变化本身说明了为什么选型文档必须写明日期:同一个项目名的部署工作流可能经历架构级调整。TensorRT-LLM 的持续差异化仍来自 NVIDIA 平台的 kernel、量化和通信整合,而不是名称中的 TensorRT 三个字。
评估时要逐项核对:
- GPU compute capability 是否支持目标 FP8/FP4/NVFP4 路径;
- checkpoint 是否已经量化,还是需要 Model Optimizer 校准;
- 模型、KV Cache 和通信各自使用什么 dtype;
- 单机与多机的支持组合和已知限制;
- 当前文档属于 latest 还是旧版 legacy 路径。
功能勾选表为什么不够
“支持 prefix cache”至少可能有四种含义:
- 单进程内能复用完整 token blocks;
- 多 worker 能通过相同路由提高命中;
- 跨实例有外部 KV 存储或 connector;
- 多租户下提供缓存隔离。
同理,“支持 FP8”可能只指权重,也可能是 W8A8、KV Cache FP8 或特定 GPU 的 block scaling;“支持 MoE”也不代表任意 expert parallel、量化和推测解码组合都成熟。
选型表应记录实际准备上线的组合:
model_revision: exact repository + commit/revision
engine: name + version + commit
hardware: GPU model/count + interconnect
weight_dtype: bf16/fp8/int4/...
activation_dtype: ...
kv_cache_dtype: ...
parallelism: TP/PP/DP/EP
attention_backend: observed backend
features:
prefix_cache: on/off
speculative_decoding: method/config
structured_output: grammar/backend
lora: adapters/concurrency
如果一个组合只能在 issue 中找到实验说明,而不在官方支持矩阵或测试中出现,应把它视为集成风险,而不是已完成的复选框。
一套公平的对比实验
第一步:建立正确性基线
使用同一 tokenizer、chat template、checkpoint revision 和 sampling 参数。在 greedy decoding 下比较输出 token;若低精度造成差异,再用任务集检查质量,而不是直接把输出不同判为引擎错误。
还要检查 stop strings、EOS、structured output、tool calling 和 logprobs 的语义。API 能返回 200 不等于行为一致。
第二步:固定资源和容量
三个引擎使用相同 GPU 数、并行度与显存安全余量。不能让一个引擎用 FP8、另一个用 BF16,或一边启用 prefix cache、另一边关闭,再把结果归因于 runtime。
若默认参数不同,应同时报告两组:
- out-of-box:反映部署门槛;
- tuned under same constraints:反映优化潜力。
第三步:先测单请求路径
分别测短/长 prefill 和短/长 decode,确认没有异常回退。单请求数据用于理解 kernel 与固定开销,不代表在线容量。
第四步:扫描请求率
采用 open-loop 方式按目标到达率发送请求,不让客户端等待上一个请求结束才发送下一个。逐步提高 requests/s,直到 P99 SLO 或错误率失守。
每个点记录:
- TTFT、TPOT/ITL、E2E 的 P50/P95/P99;
- input/output tokens/s 与 requests/s;
- queue time、running/waiting requests;
- KV Cache usage、prefix hit、preemption 与 OOM;
- GPU 利用率、功耗和多机通信量。
第五步:回放真实分布
固定 1k 输入/128 输出的合成测试无法代表真实服务。应从脱敏日志得到输入长度、输出长度、到达间隔、共享前缀和取消行为的分布,再回放相同 token IDs 或等价数据。
不要只使用平均长度。90% 的短请求和 10% 的超长请求,与所有请求都取平均长度,对调度器和缓存的压力完全不同。
怎样从结果做决定
可以把结论拆成四层,而不是宣布一个总冠军。
可运行性
模型能否加载,目标量化、并行和 API 是否正确。这里不满足,性能再高也无意义。
SLO 容量
在给定 P99 TTFT/TPOT 下,每张 GPU 能接受的最大请求率。这是在线服务最接近业务价值的核心数据。
成本与弹性
包括单位有效 token 成本、冷启动、模型加载、扩缩容粒度、突发流量恢复和多模型资源碎片。
维护成本
团队能否调试 kernel 回退、构建镜像、升级版本、观察缓存/调度指标,以及在问题发生时回滚。少量 benchmark 优势可能不值得长期承担一条团队不熟悉的技术栈。
一个常见但并非绝对的起点是:
- 需要快速覆盖多种模型和 API,先以 vLLM 建立基线;
- 流量具有明显共享前缀、结构化生成或复杂 LLM program,重点比较 SGLang;
- 部署固定在 NVIDIA 平台,准备使用其最新低精度和通信路径,将 TensorRT-LLM 纳入同条件测试。
最终也不必全公司只保留一个引擎。离线批处理、在线聊天和超大 MoE 可以属于不同模型池,只要网关、指标与回滚策略保持一致。
一份可落地的选型记录
decision_date: 2026-08-09
workload:
input_tokens: p50/p95/p99
output_tokens: p50/p95/p99
request_arrival: steady/bursty + target rps
shared_prefix_ratio: ...
cancellation_ratio: ...
slo:
ttft_p99_ms: ...
tpot_p99_ms: ...
error_rate_max: ...
results:
vllm: {max_slo_rps: ..., cost: ..., notes: ...}
sglang: {max_slo_rps: ..., cost: ..., notes: ...}
tensorrt_llm: {max_slo_rps: ..., cost: ..., notes: ...}
decision:
selected: ...
reasons: [...]
rejected_tradeoffs: [...]
reevaluate_when: model/GPU/workload/major-version changes
这份记录比一张静态功能表更有价值:它保留了决策成立的条件。当模型、GPU 或流量发生变化时,可以重新跑同一实验,而不是依靠已经过期的印象。
参考资料
觉得有帮助?
分享给同样关注系统性能的人。