我读这篇LLM Serving实证研究论文时,第一反应是:终于有人把各个推理框架放在同一套测试标准下做横向对比了。过去我们选框架基本靠社区口碑和官方博客,遇到性能问题只能一个个框架试,耗费大量精力。这篇论文的价值在于用一套统一的方法论,把当前主流的serving框架、优化方法和系统设计选型放在同一起跑线上对比,得出了很多反直觉的结论——比如吞吐量和延迟在某些配置下并不冲突,投机解码在低并发下收益显著、高并发下却可能拖慢速度,量化带来的收益不一定符合你的直觉预期。
无论你是做大模型应用的平台工程师、做推理优化研究的同学,还是刚接触serving方向想搭一套推理服务的入门者,这篇论文都能提供可参考的路线图。我在实际部署中踩过不少坑,结合论文里的发现,把自己的一些实测经验一并整理出来。
1. 论文在研究什么:一次性说清它的核心命题与研究方法
1.1 为什么需要一篇系统性的实证研究
LLM serving这个方向起步时间不长,但框架迭代非常快。从早期的TGI、FasterTransformer,到后来的vLLM、SGLang、TensorRT-LLM,新框架几乎每隔几个月就会刷新性能记录。问题在于:这些性能数字通常是各团队在自己的测试负载、自己的GPU型号、自己定义的延迟指标下测出来的,彼此之间无法直接对比。
论文从一个核心问题出发:如果把这些框架放到统一环境、统一负载、统一评估标准下,它们的真实差距有多大?哪些方法带来的提升是真实的,哪些只是测试工况下的特例?这个问题看似基础,实际上对工程选型非常关键。我在帮某公司做推理平台选型时,就遇到过同一套代码在不同框架间性能和稳定性差异巨大的情况,各自官方宣称的延迟数据和实测完全是两回事。
1.2 论文的实验设计思路拆解
实证研究的说服力在于实验设计是否严谨。论文搭建了一套完整的测试基准,核心变量包括:
- 模型规模:覆盖小规模到中大规模的稠密模型,不同规模对应不同的显存占用和计算强度
- 并发负载:从低并发到高并发,模拟真实场景下用户请求的分布
- 请求特征:区分短输入长输出、长输入短输出、动态输入的混合比例
- 多轮对话场景:考虑了历史上下文的持续增长
评估指标也不只盯吞吐量,而是同时监控首token延迟、单token生成延迟、端到端延迟以及吞吐量,这四个维度缺一不可。只看吞吐量容易忽略实际业务中用户对响应速度的感知,只看延迟又可能选到在资源效率上很差的方案。
论文的负载注入方式也做得很细:既有效稳态的持续压测,也有模拟突发流量的脉冲式攻击。这套方法论本身就很值得借鉴,我在做框架对比测试时就采用了类似的思路,后面第5节会给出可复用的测试模板。
2. 框架对比的核心发现:不同架构路线的真实表现
2.1 主流框架的架构基因差异
当前主流的LLM serving框架大体分为三类路线。
第一类是以vLLM为代表的高性能调度路线,核心是PagedAttention的显存管理机制加上continuous batching调度。PagedAttention解决了KV cache显存碎片化问题,把显存利用率大幅提升,使得在相同显存条件下可以容纳更多并发请求。vLLM的核心思路是让GPU尽量忙起来,减少空闲等待。
第二类是以SGLang为代表的融合调度路线,在vLLM的基础上进一步级了请求调度,引入了RadixAttention的prefix共享机制。多轮对话或者共享系统提示词的场景下,相同的前缀token不需要重复计算,直接复用之前的KV缓存。论文的测试数据显示,在包含系统提示词和长对话场景下,前缀缓存的命中能够直接带来数十甚至数百倍的prefill计算量缩减。
第三类是以TensorRT-LLM为代表的编译优化路线,不强调动态调度,而是把模型的计算图做深度编译,融合kernel、减少启动开销。这类框架在低并发下可以把单请求的延迟压得非常低,因为静态编译能消除大量Python调度开销。但它的代价是动态性不足,请求特征变化大的场景下需要重新编译或者做较复杂的配置。
各有各的基因。论文测试结果的规律是:高并发、高动态性的场景下,vLLM类方案的吞吐优势明显;低并发、对单请求延迟极度敏感的场景下,TensorRT-LLM的极限延迟表现更好;多轮对话和共享前缀频繁的业务场景,SGLang的收益能拉开一个数量级。
2.2 实测数据中值得注意的“反直觉”结论
论文有个很有意思的发现:在相同GPU资源下,部分框架的吞吐量差距并没有社区宣传的那么大。在低并发区间,各框架吞吐差异基本在20%以内;真正拉开差距的是高并发区间,这时调度效率和显存管理能力决定了你能同时处理多少请求。
反过来,延迟排在最后的框架并不一定是吞吐最低的那个。有些框架为了追求极致吞吐,会牺牲单个请求的响应速度,导致E2E延迟显著增加。这在真实业务里是个很现实的权衡——如果你的业务是交互式的,用户等不了太久;如果你的业务是离线批量分析,那么吞吐优先完全合理。
论文中特别点出了一个现象:有些框架在burst流量注入时出现明显的延迟尖峰,恢复时间也长。这一现象的根因是:突发流量瞬间拉满显存,KV cache的回收和重新分配需要时间,调度器在这段时间内只能降低新请求的准入速率。这个发现提醒我们,框架选型不能只看稳态性能,还要关注它在流量冲击下的弹性表现。我自己的压测经验里,这个维度确实最容易出问题,看起来平均延迟很低,一旦模拟真实用户突增场景就原形毕露。
| 对比维度 | vLLM类调度路线 | SGLang融合调度路线 | TensorRT-LLM编译路线 |
|---|---|---|---|
| 核心机制 | PagedAttention + continuous batching | prefix共享 + 联合调度 | 计算图编译 + kernel融合 |
| 高并发吞吐 | 优秀 | 优秀 | 中上 |
| 低并发延迟 | 中 | 中 | 低 |
| 多轮/前缀复用 | 一般 | 优秀 | 一般 |
| 动态请求适配 | 强 | 强 | 中等,部分场景需重新配置 |
| 适合场景 | 高并发在线服务 | 高并发 + 共享上下文业务 | 对延迟极敏感的确定性场景 |
3. 核心方法的原理拆解:这些优化到底做了什么
3.1 continuous batching:从静态到完全动态的调度跃迁
传统batching是静态的:一批请求同时进入,必须全部生成完成后才一起退出,下一批才能进入。这在token级生成场景下效率极低,因为每个请求的生成长度不同,短的生成完了必须干等长的。continuous batching则把调度粒度缩小到token级,每生成一个token就检查当前批次,已经完成的请求立即移出,腾出的GPU资源马上分配给排队中的新请求。
论文的实验结果验证了这种方式带来的吞吐提升:在模拟真实混合长度的负载下,continuous batching相比传统静态batching能提升约一倍的吞吐量。实际部署中我遇到过的情况与此一致——长尾生成请求会严重拖低整体吞吐,而continuous batching能有效对冲这种波动。它对调度器提出了更高要求,需要在每个step都做一次准入判断,这也是为什么vLLM的调度器是它的核心资产。
3.2 投机解码:低并发加速明显,高并发未必划算
投机解码的思路是用一个小模型先草拟若干个候选token,再由大模型一次性验证。如果小模型猜得准,大模型的一个forward就能生成多个token,相当于绕过了逐个token生成的串行瓶颈。
论文给出了一个关键数据:在batch size为1或2的低并发下,投机解码可以把单请求的生成速度提升1.5到2倍;但在批量达到几十个请求以上时,收益明显缩小,甚至出现负优化。原因不难理解:大模型验证阶段的显存带宽消耗本来就大,批量大时验证成本被摊薄到单个token上的比例下降,同时小模型的推测过程本身也占用了部分调度资源。
这个结论对我的选型判断影响很大。如果你的业务是API网关后面接多个实时用户请求,batch size天然比较高,那么投机解码优先级可以放低;如果是企业内部的一次性长报告生成,单请求低并发跑投机解码就很划算。分布式投机解码可能是突破这个瓶颈的方向,但目前工程成熟度还不足以进入生产推荐。
3.3 量化:收益取决于你的瓶颈是算力还是显存
量化在大模型推理中已经是标配手段。论文系统评测了INT8和INT4量化的效果,结论很清晰:在算力足够强劲、并发足够高的场景下,量化带来的收益主要体现在显存节省和batch size扩大能力上;在显存充裕但GPU利用率已经很高的场景下,量化反而会降低单token生成质量,甚至因为反量化开销抵消部分吞吐收益。
论文还指出一个容易被忽略的点:量化显著的收益体现在长上下文场景。上下文越长,KV cache占用越大,量化KV cache后能把缓存容量提升数倍,让长输入请求不再因为显存不足而被迫降低并发。我实测跑128K上下文时,FP16的KV cache很快吃满单卡显存,INT8量化后直接能塞下多路请求,这个是立竿见影的收益。但要注意,量化KV cache会增加CPU/GPU之间的控制复杂度,部署前建议先在目标上下文长度下做一轮效果验证。
3.4 prefix caching:多轮对话和文档问答的隐藏提速器
前缀缓存是很多团队容易忽略的一个优化点。它的原理很简单:请求的起始几个token往往与之前请求相同(比如所有请求都带上相同的system prompt,或者多轮对话中历史上下文大部分重复),这些相同前缀的prefill结果不重新计算,直接查找缓存并复用。
论文测试了不同框架的前缀缓存实现效果,其中RadixAttention的做法最为激进:它把KV缓存组织成前缀树结构,相同公共前缀的多个请求共享缓存分支。在模拟系统提示词复用的多轮对话负载下,prefill延迟缩短超过了90%。这个机制在文档问答场景同样有效——同一个文档被不同用户提问时,文档部分的编码结果可以跨请求复用。
4. 系统设计层面的权衡与决策:吞吐、延迟与资源效率如何取舍
4.1 吞吐与延迟协同优化:为什么说这不是一个零和博弈
传统认知里,吞吐和延迟是对立的。想提高吞吐就并行处理更多请求,每个请求感受到的延迟自然增加。但论文的实证数据揭示了一个更细腻的关系:当并发从低到中提升时,吞吐增长迅速,延迟增长缓慢;当并发超过某个拐点之后,延迟急剧恶化,而吞吐增速放缓甚至回退。这个拐点就是系统设计的核心参数。
我在实际测试中复现了这个现象。用一个32B模型做在线推理,并发在10以内时每个请求的生成速度基本稳定;并发提到20以上时,平均生成速度开始下降,但整体吞吐还在涨;到并发40左右,部分请求的超时率突然飙升。找到这个拐点,并把它作为系统的并发上限约束,比盲目调高max_num_seqs更可靠。
论文建议了两个方向协同优化:一是通过动态的并发控制,让系统始终工作在高吞吐与可接受延迟的平衡区;二是利用优先级调度,把延迟敏感的小请求和高吞吐需求的大批量请求混跑时,优先满足前者。
4.2 突发负载下的显存弹性与调度策略
生产环境中流量永远是波动的。论文用脉冲式负载做测试后发现一个共性规律:大部分框架在突发流量到来时都会出现一段短暂的“准入饥饿”——调度器来不及为蜂拥而至的请求分配KV cache,导致新请求在队列里滞留着等待显存释放。
论文里给出的解法思路分为主动与被动的组合。主动方式是预先规划预留一部分KV cache buffer池作为突发缓冲;被动方式则是在调度器中实现动态的显存回收机制,当突发流量到达时,找出长期未活跃的会话缓存,清出空间接纳新请求。
我自己的部署经验是,这个问题的严重程度与业务形态高度相关。如果是to C的实时对话产品,突发流量就是日常;如果是内部平台的离线批量任务,突发概率很低但单次突发量巨大。后者更需要关注显存碎片的长期积累问题。定期监控KV cache碎片率,在负载低谷时做一次缓存清理,能避免长时间运行后性能逐渐劣化的“温水煮青蛙”问题。
4.3 prefill与decode分离:值得认真考虑的长上下文方案
prefill阶段是密集的矩阵乘法,decode阶段则是存储带宽受限的串行token生成,两者对GPU资源的消耗特征完全不同,把它们混在一起跑,调度上很难两全。论文观察到的现象是:长输入请求的prefill会阻塞同批次中其他请求的decode,导致延迟飙升。
prefill/decode分离架构(PD分离)把两个阶段拆到不同的计算资源上:prefill节点负责处理长输入的低并发、高算力任务;decode节点负责处理生成阶段的低算力、高内存带宽任务。论文在长上下文场景下的测试显示,PD分离能把长输入请求的TTFT减少超过一半,同时不牺牲decode阶段的吞吐。
当然,PD分离不是银弹。跨节点传输KV cache的带宽开销不可忽视,小规模部署下网络传输的延迟可能抵消掉分离带来的收益。我的判断是:上下文长度超过32K、请求量较大的场景下,PD分离的收益会比较显著;短上下文场景做PD分离反而增加系统复杂度。
4.4 KV cache量化与显存管理策略
KV cache在长上下文场景下是显存消耗的大头。以7B模型为例,一个length=64K的请求光KV cache就可能占用超过10GB显存。论文中对比了FP16存储与INT8量化存储在相同显存限制下的吞吐差距,结果显示:量化为INT8存储时,相同显存内可容纳的请求数几乎翻倍,整体吞吐提升约1.5倍。
这里要提醒一个细节:KV cache量化的影响随着上下文长度增加而累积。论文的实验数据是在8K上下文下测的,扩展到64K甚至128K上下文后,量化误差在长链路生成中会逐步累积,可能导致质量下降。具体到什么程度需要看模型对数值精度的敏感度,建议针对自己的模型做一次质量对比测试再上线。
另外显存管理还有一个容易被忽略的点:KV cache的预分配策略。有些框架按最大长度预分配,忙时表现稳定但显存浪费严重;有些框架采用按需增长,显存利用率高但频繁扩容时出现短暂的推理停滞。论文推荐的做法是带预分配阈值和动态扩容相结合的混合策略,可惜目前开源框架中这个方案实现得都不够完善,需要自己做二次开发。
5. 从论文到生产环境:框架选型与关键参数配置实操
5.1 基于负载画像选择框架的决策流程
论文的实证数据最终要落到代码和配置里。我建议先把自己的负载特征量化出来,再做框架选型。要采集的核心数据包括:
- 请求到达速率:高峰期QPS、均值QPS、突发倍数
- token特征:平均输入长度、平均输出长度、长尾分布(P90、P99)
- 上下文结构:多轮对话比例、系统提示词复用量、文档检索场景占比
- 延迟要求:面向交互式用户的TTFT和单token生成速度要求
拿到这些数据后,按论文的结论套用选型逻辑。举例来说,如果P99输入长度在1K以内,并发度要求高、单请求延迟要求中等,vLLM类方案通常是最好的起点。如果业务大量使用公共知识库文档回答问题,那么一定要选带RadixAttention前缀缓存的SGLang。如果是端侧或小规模的专用模型服务,延迟优先级极高,TensorRT-LLM的编译优化会更有优势。
我参与过一个知识库问答系统的选型过程,当时在vLLM和SGLang之间纠结了很久。最后用真实业务数据做了对比测试,同样的8卡A800环境,SGLang因为prefix缓存机制在文档内容反复复用的场景下吞吐高了近4倍,这个差距直接决定了选型结果。
5.2 性能调优的关键参数:从论文到可落地的配置指导
结合论文对框架行为特征的解析和我的实测,挑出几个实际调优性价比最高的参数。
max_num_batched_tokens控制单个batch接收的最大token总数。这个参数直接影响GPU的利用率上限。调小导致GPU空闲等待,调大则导致延迟尖峰。合理的起点是把它设为单卡算力能支撑的最大KV cache容量对应的token数,然后观察平均延迟和吞吐的拐点去微调。
max_num_seqs控制调度器中同时存在的最大请求数。论文的发现是:这个值并非越大越好,过大时调度器的排队开销会反噬吞吐。我的经验是先从较小值起测,逐步加码,直到吐出吞吐不再上升反而下降的拐点,取拐点附近的值为宜。
gpu_memory_utilization决定KV cache能使用的显存比例。论文强调:预留足够比例给计算图和动态请求缓冲,不能一味拉高。我的经验是0.85到0.9是一个合理范围,超出后容易出现OOM和频繁swap。
连续运行时还值得关注chunked prefill参数的开关状态。对于长输入较多的业务,开启chunked prefill可以让长请求的prefill分块插入到decode间隔中,避免整段prefill阻塞其他请求的生成。
提示:以上参数的具体最优值强烈依赖模型大小、显存容量和负载特征。论文结论是趋势参考,真实部署必须以自己的压测数据为准。
5.3 一个可复用的框架性能测试实测方法
论文的实验设计可以直接借用来做框架对比测试。我的做法分为三步。
第一步构建覆盖业务特征的负载集。用范围工具生成请求trace,混合不同输入长度、不同输出长度、多轮会话带前缀的请求,录制一段时间内的到达时间戳,让它按真实节奏打进来。
第二步定义统一指标。核心是TTFT(请求发起之后到首个token返回的时间)、TPOT(平均单token生成时间)、E2E延迟和吞吐(单位时间完成的请求数或生成的token数)。用统一的压测脚本采集,避免人工统计偏差。
第三步多轮对比。先跑稳态负载,观察各框架平均表现;再跑脉冲负载,观察延迟尖峰高度和恢复时间;最后跑长时稳定性测试,跑2小时以上,观察是否存在性能随时间衰减。实测完成后记录四张表:稳态吞吐对比、延迟分布对比、突发负载延迟尖峰对比、长时稳定性对比。
这套方法做下来,选型结论基本不会偏差太大,也能为后续调优保留一份基准数据。
5.4 生产环境监控指标与稳定性观测
论文中没有系统展开监控部分,但这是部署环节最不能省的工作。基于实测经验,我建议至少监控以下指标:GPU利用率(计算vs显存带宽利用率分开看)、KV cache分配量与碎片率、排队请求数、TTFT和TPOT的P50/P95/P99、以及请求的成功率和超时率。
其中最具预警价值的是排队请求数。当排队请求数开始增长时,意味着系统已经接近瓶颈,此时TTFT往往还没明显恶化,但接下来的P95延迟会跟着抬升。我习惯在监控面板上把排队数和TTFT放在同一张图上,观察它们的时间错位关系,这比单纯盯着延迟阈值更能提前干预。
另一个值是碎片率和显存分配量的增长曲线。长时运行后,某些请求的KV cache释放不彻底会造成分配量缓慢增长,累积到一定程度就会触发OOM。这类问题的排查成本很高,建议在系统上预设一个定期的cache整理任务,当碎片率超过阈值时自动执行清理。
6. 常见问题与排查技巧实录
| 问题现象 | 可能原因 | 排查与解决思路 |
|---|---|---|
| 高并发下TTFT急剧上升 | 调度器准入速度跟不上请求到达 | 调低max_num_seqs,增加gpu_memory_utilization预留空间,考虑chunked prefill |
| TPOT持续偏高且GPU利用率不饱和 | 显存带宽成为瓶颈,计算unit等待数据 | 优先做KV cache量化,评估是否启用投机解码 |
| 多轮对话越聊越慢,最终超时 | 前缀未命中,上下文prefill反复计算 | 切换支持RadixAttention的框架,或开启前缀缓存功能 |
| 突发流量后长时间恢复不了 | KV cache碎片率高,回收效率低 | 在低峰期执行缓存整理,考虑预留buffer池,必要时重启worker |
| 长上下文请求直接OOM | KV cache占用超限 | 启用量化KV cache,限制单请求最大上下文长度,或启用PD分离 |
| 量化后生成质量不稳定 | 长链路中KV cache量化误差累积 | 改用8bit只量化KV cache,模型权重保持高精度,做质量A/B测试 |
我再分享一个很实际的排查经验。有一次同事反映服务在运行几小时后性能明显劣化,所有请求的TPOT都涨了一截,但重启后又恢复正常。第一反应是显存碎片或内存泄漏。后来逐项排查发现,某个业务会话的上下文特别长,它的KV cache占据了大量显存却长时间不活跃,调度器认为它还在活跃就把大批新请求拒之门外。定位问题后做了两步修正:一是为超长会话设置了闲置回收超时,二是把gpu_memory_utilization调低了5个百分点给突发请求留余量,问题就此解决。
这类问题光靠看论文里的结论发现不了,必须结合自己的业务特征去排查。论文的价值在于它把框架的行为规律系统描述了一遍,让你在遇到问题时能快速定位可能的方向,而不是全靠猜。
7. 学习这篇论文后,我的实际收获与补充
论文整体的实证结论里,对我的工程决策影响最大的有三条。第一,框架选型必须以自己的真实负载来测,基于社区测试数据的选型决定在真实场景中很容易翻车。第二,优化手段的价值高度依赖使用场景,投机解码在低并发场景是神器,在高并发下反而可能变成负担。第三,系统设计的弹性比极限吞吐更重要,突发流量下的恢复能力直接决定了用户是否流失。
我在实际部署中还发现论文没有充分展开的一点:多框架混合部署的策略。论文的所有对比都是把每个框架作为唯一serving引擎来评估的,但生产环境中完全可以按业务类型拆分——延迟敏感型请求走编译优化路线,高吞吐型请求走动态调度路线,通过在网关层做路由隔离,获得比单一框架更优的整体表现。我给某平台做方案时,就采用了vLLM和TensorRT-LLM混合部署的结构,实测下来整体吞吐与延迟两项指标都优于单纯使用任一框架。
如果你也在研究LLM serving方向,建议把这篇论文作为进入这个领域的知识地图来读。先掌握它的方法论,再用它提供的方法测你自己的业务,最后结合业务特征选型与调优。这样一套流程走下来,你对serving系统的理解会比单纯看源码深入得多。我自己在读完这篇论文后重读vLLM的调度器源码时,很多之前看不懂的设计决策都找到了依据。