干翻显存黑洞:vLLM 凭什么成了大模型推理的扛把子?横评 TRT-LLM / SGLang / TGI / llama.cpp
七十亿、几百亿参数往显存里一倒,卡时如流水。更要命的是,显卡动不动就报 OOM(Out of Memory)趴窝。很多团队刚上手那会儿,以为吞吐量低是因为算力卡不够,拼命加机器。其实路走歪了。卡住脖子的,多半不是算力,是那堆被浪费得不成样子的键值缓存(KV Cache)。
大模型这玩意儿,训出来是一回事,跑线上撑住并发又是另一回事。
七十亿、几百亿参数往显存里一倒,卡时如流水。更要命的是,显卡动不动就报 OOM(Out of Memory)趴窝。很多团队刚上手那会儿,以为吞吐量低是因为算力卡不够,拼命加机器。其实路走歪了。卡住脖子的,多半不是算力,是那堆被浪费得不成样子的键值缓存(KV Cache)。
vLLM 当年一出来就杀疯了,靠的就是把操作系统的虚拟内存搬到了显卡里。
不过现在江湖早就变了。英伟达亲儿子 TensorRT-LLM 在后面狂追,专门为复杂提示词而生的 SGLang 杀将出来,还有老牌的 TGI 和跑在笔记本上的 llama.cpp。
今天把这几个主流引擎扒开底子,一次性讲透。
1. 🧮 显存里到底卡着什么鬼?
搞大模型推理,逃不开两个截然不同的步骤:一个是把用户的输入一整截吞进去(Prefill / Prompt 阶段),另一个是吐字,一个词一个词往外挤(Decode 阶段)。
问题全出在挤字这一步。
每挤出一个新词,模型就得回头去翻前面所有词的键值向量。为了不重复算,这些向量全得留在显存里,这就是 KV Cache。
KV 缓存(Key-Value Cache)
自回归模型每生成一个新 Token,都需要将自注意力机制中的 Key 和 Value 矩阵保存在显存里。随着对话轮数变多、并发量飙上去,KV Cache 占用的显存会迅速反超模型本身的权重文件,成为最先挤爆显存的吃卡大户。
传统做法很粗暴:只要来了一个请求,管你真正聊几句,显卡先划出一块连着的、够塞 4096 甚至 8192 个词的巨大显存地皮。
结果呢?
有人问一句“今天天气怎么样”,模型五句话答完。剩下的几千个槽位就这么干晾着。别的请求进不来,自己又占着茅坑不拉屎。以前显存的有效利用率,经常连三成都不到。全是气泡和碎渣。
#### 破局之手:PagedAttention vLLM 的这帮伯克利作者干了一件极聪明的事:把 Linux 操作系统的分页内存照搬过来了。
PagedAttention(分页注意力机制)
不再要求显存必须整块连在一起。它把 KV 缓存切成固定大小的小格子(物理块,一般是 16 或 32 个 Token)。你的对话在逻辑上连贯,在显存里随便找空位塞。调度器手里拿着一张“块表(Block Table)”,随时查随时拼。显存碎片率直接被按死在 4% 以下。
逻辑输入 (Tokens 0-31): [ 块 0 (0-15) ] ──────► [ 块 1 (16-31) ]
│ │
查找表 (Block Table): │ 塞进物理块 #7 │ 塞进物理块 #2
▼ ▼
显存池 (GPU RAM): [ 物理块 #7 ] ······ [ 物理块 #2 ] ······ [ 随便哪个空位 ]
数学公式写出来看着吓人,骨子里其实就是带索引的查表点积:
显存不用再整块空等。吞吐量当场翻了两到四倍。
#### 顺手解决的另一个痛点:连续批处理 以前批处理叫静态 Batch。八个人一起问,必须等废话最多的那个说完,大家才能同时退场,蠢得很。
连续批处理(Continuous / In-Flight Batching)
把颗粒度拆到单个生成步骤(Iteration)。哪个请求蹦出结束符了,立马踢出去结账,腾出空位直接把排队的新人塞进来。流水线一秒钟都不空转。
2. ⚔️ 五大门派切磋:直接上硬核对比
光吹原理没用,工程落地看的是实打实的取舍。
在硬件通用性、极致吞吐量与开发自由度的权衡中,五大主流推理引擎展现出截然不同的架构偏向。
| 核心维度 | vLLM (伯克利) | TensorRT-LLM (英伟达) | SGLang (LMSYS) | TGI (Hugging Face) | llama.cpp (纯C游侠) |
|---|---|---|---|---|---|
| 底层硬功夫 | Python + C++ / Triton 内核 | 纯 C++ / CUDA (暴力榨取) | Python + FlashInfer / Triton | Rust 前端 + Python/CUDA | 纯 C / C++ (毫无依赖) |
| KV 显存管理 | PagedAttention (物理分页) | 专有 In-Flight KV 分页池 | RadixAttention (基数树前缀) | PagedAttention 移植版 | 静态环形缓冲 |
| 多轮前缀复用 | APC (哈希单链匹对) | 静态 Prompt Cache | 多路前缀树分支共享 (独一档) | 基础前缀匹配 | 靠手动拼接对齐 |
| 手写算子融合 | FlashAttention-2/3, Triton | 手写 CUTLASS (天花板级别) | FlashInfer, Triton, FlashAttn | FlashAttention, AWQ | 自研 CPU SIMD / ggml |
| 并发吞吐峰值 | ⭐️⭐️⭐️⭐️ (极高,通用王者) | ⭐️⭐️⭐️⭐️⭐️ (极值,英伟达原生) | ⭐️⭐️⭐️⭐️½ (超高,复杂提示词更猛) | ⭐️⭐️⭐️½ (中规中矩) | ⭐️⭐️ (单机/边缘低并发) |
| 首字时延 (TTFT) | ⭐️⭐️⭐️⭐️ (很快) | ⭐️⭐️⭐️⭐️⭐️ (快到变态) | ⭐️⭐️⭐️⭐️⭐️ (命中树节点时起飞) | ⭐️⭐️⭐️½ (偏稳) | ⭐️⭐️⭐️ (CPU 算力所限) |
| 上手部署难度 | 🟢 舒服 (pip 一装直接当 OpenAI 用) | 🔴 痛苦 (编译折腾,踩坑无数) | 🟢 顺手 (Pythonic,好改好调) | 🟡 中等 (Docker 镜像拉起即用) | 🟢 极爽 (单文件扔进去就能跑) |
| 非 N 卡异构能力 | AMD ROCm, Intel Gaudi, TPU 都能跑 | ❌ 买英伟达全家桶才给伺候 | AMD ROCm 适配中 | ROCm, Gaudi 部分支持 | 纯跨界 (Mac Metal, CPU 通吃) |
3. 🥊 贴脸硬刚:三场关键战役
脱离开实际场景谈引擎优劣,纯属耍流氓。
#### 第一战:vLLM vs. TensorRT-LLM 这俩本质上是“软件工程效率”和“硬件极致性能”的决斗。
TRT-LLM 是英伟达自家人写的。这帮人对 GPU 底层寄存器、共享内存摸得太透了。他们把算子拼了命地融在一起(Kernel Fusion),省去了一大堆在显存来回倒腾数据的开销。
算子融合(Kernel Fusion)
把前后好几道工序(比如矩阵乘法算完紧接着算偏置和激活函数)揉进同一个 GPU 计算核里干完。不用把半成品写回大显存再重新读出来。省下的访存带宽,全变成了真金白银的速度。
在固定的英伟达显卡集群上跑成熟模型,TRT-LLM 确实能比 vLLM 多榨出 15% 到 35% 的吞吐量。
可惜,代价太大了。
配置复杂,编译繁重。模型架构要是稍微冷门点,或者开源社区今天刚蹦出一个新结构,TRT-LLM 根本跑不起来。改动一行代码,重新编译能让你喝半天咖啡。
vLLM 就讨喜得多。跟 Hugging Face 贴得死紧,社区一出新模型,过两天甚至当天下午就能无缝拉起。改源码调参更是轻轻松松。
#### 第二战:vLLM vs. SGLang 这一仗非常精彩。
vLLM 当初把精力全扑在“怎么把单条请求切碎放好”上了。它的自动前缀缓存(APC)就是单线哈希,对付普通多轮对话还行。
但在做 Agent 循环、思维链推演、少样本(Few-shot)或者硬性让模型输出 JSON 格式时,请求根本不是一条直线,而是一张到处分叉的大树。
SGLang 直接祭出了基数树(Radix Tree)。
RadixAttention(基数树注意力缓存)
把所有跑过的内容用前缀树(Radix Tree)管起来。十个不同的 Agent 共享同一个上万字的长设定,SGLang 在内存里只留一份公共根节点。后续生成各自长出枝丫;显存吃紧时,按 LRU 算法先砍掉最末梢的树叶,树干永远留在显存里。
在多 Agent 和结构化生成场景下,SGLang 靠着惊人的缓存命中率,把首字延迟(TTFT)打得极低。实测比 vLLM 还要顺滑得多。
#### 第三战:vLLM vs. llama.cpp 完全不是一条道上的选手。
llama.cpp 简直是单机与本地极客的福音。纯 C++ 撸穿一切,配合量化好的 GGUF 文件,往 MacBook 或普通台式机上一放,不吃显存也能跑得飞快。
但它从设计之初就没打算扛住机房里成千上万的高并发请求。
数据中心成排的机器要打满利用率,还得老老实实上 vLLM 这种重型装甲车。
4. 📐 账本算一算:为什么算力账单总超标?
很多架构师做预算,往往只按模型参数来算显存。大错特错。
全系统实际消耗的 KV Cache 显存容量,是由下面这个公式死死钉住的:
这里头:
- \(L_{\text{layer}}\) 是模型层数;
- \(N_{\text{head}}\) 是注意力头数量;
- \(D_{\text{head}}\) 是每个头的维度;
- \(S\) 是上下文平均长度;
- \(B\) 是同一秒扛住的并发数;
- \(P_{\text{bytes}}\) 是精度(FP16 占 2 字节,FP8 占 1 字节)。
显卡总显存就那么大,扣掉模型权重和临时计算空间,剩下的地盘全留给 KV 缓存池了。
显存碎片惩罚因子
传统系统里由于内存无法分页,必须乘上一个高达 1.6 到 2.0 的碎片浪费系数(\(\epsilon_{\text{frag}}\));vLLM 把这个系数降到了 1.04。也就是说,同样的物理硬件,vLLM 能把安全并发上限 \(B_{\max}\) 凭空往上推两到三倍。
这就是省钱的全部秘密。
5. 🎯 怎么选?一张图说明白
别跟风,看你的真实业务长在哪:
- 图省心、保通用、团队节奏快:闭眼选 vLLM。坑最少,社区最热闹,文档最齐。
- 做复杂 Agent、结构化抽取、长上下文多轮互动:果断切 SGLang。试过基数树缓存的人基本都回不去了。
- 业务规模庞大、模型几个月都不换一次、手里全是 H100/H200:咬着牙上 TensorRT-LLM。这才是压低单 Token 边际成本的终极武器。
📚 参考文献与真实凭据
1. Kwon, W., Li, Z., Zhuang, S., Sheng, Y., Zheng, L., Yu, C. H., ... & Stoica, I. (2023). *Efficient memory management for large language model serving with PagedAttention*. In Proceedings of the 29th ACM Symposium on Operating Systems Principles (SOSP '23), pp. 611-626. (vLLM 原理奠基之作). 2. Yu, G. I., Jeong, J. S., Kim, G. W., Kim, S., & Chun, B. G. (2022). *Orca: A distributed serving system for Transformer-Based generative models*. In 16th USENIX Symposium on Operating Systems Design and Implementation (OSDI '22), pp. 521-538. (迭代级调度 Continuous Batching 开山论文). 3. Zheng, L., Yin, L., Xie, Z., Sun, C., Huang, C. H., Yu, C. H., ... & Stoica, I. (2024). *SGLang: Efficient Execution of Structured Language Model Programs*. arXiv preprint arXiv:2312.07104. (RadixAttention 基数树前缀缓存规范). 4. NVIDIA Corporation (2023–2026). *TensorRT-LLM Architecture and In-Flight Batching Developer Guide*. NVIDIA Docs. 5. Gerganov, G. (2023–2026). *llama.cpp: High-performance inference of LLaMA model in pure C/C++*. GitHub.