
1. 这篇论文到底在解决什么问题大模型推理服务的工程化落地这两年从“能跑起来”迅速卷到了“跑得省、跑得稳、跑得密”。但真正做过线上服务的人都知道实验室里那张漂亮的吞吐量曲线和机房里凌晨三点告警群里的真实表现往往是两回事。这篇《LLM Serving in the Wild: An Empirical Study of Frameworks, Methods, and System Designs》做的事情就是把市面上主流的推理框架拉到真实负载下做一次系统性的“体检”然后告诉你哪些设计在纸面上很美、一上量就崩哪些看起来朴素的选择反而是生产环境的定海神针。我先把结论性的东西摆前面方便你判断要不要继续往下读。这篇论文的核心价值不在于提出了某个新算法而在于它做了一件很多人想做但没精力做的事——横向对比多个主流服务框架在真实流量特征下的表现并把差异归因到具体的系统设计决策上。它回答的问题包括连续批处理continuous batching到底在什么负载下收益最大PagedAttention 这类显存管理方案在长上下文场景里是不是万能药张量并行和流水线并行在中小规模部署里谁更划算量化到底省的是显存还是算力还是两者都省但延迟反而涨了适合读这篇内容的人我大致分三类。第一类是正在做推理服务选型的工程师你手里有一批卡要决定用哪个框架、开哪些开关、怎么配并行策略这篇能帮你少走至少两三个月的弯路。第二类是做大模型应用层但需要自己搭推理后端的开发者你可能不关心内核实现但你关心“我这个 QPS 和上下文长度到底该租什么卡、开什么配置”。第三类是想理解 LLM 系统设计取舍的研究者或学生论文里的归因分析比单纯的 benchmark 数字有价值得多。需要提前说明的是我下面讲的内容一部分来自论文本身的结论一部分是我自己在实际部署和调优中踩过的坑两者我会尽量区分开。论文没写透的地方我会基于常见工程实践做合理补全并明确标注这是补充推断不是论文原文。2. 核心框架与方法的设计取舍拆解2.1 为什么“框架对比”这件事比想象中难做很多人以为对比框架就是跑个 benchmark 脚本把 tokens/s 拉出来排个序。真做过就知道这里面的坑深得很。第一个问题是负载不可比。框架 A 默认开连续批处理框架 B 默认静态批处理你直接跑同样的请求集出来的数字根本没有可比性因为批处理策略不同导致的有效 batch size 完全不同。论文在这点上处理得比较严谨它把对比维度拆成了“框架默认配置”和“调优后配置”两组这样既能看出开箱即用的差距也能看出调优天花板在哪。第二个问题是指标不可比。吞吐量throughput和延迟latency是一对天然矛盾体。你把 batch size 拉大吞吐上去了但首 token 延迟TTFT和每 token 延迟TPOT都会涨。论文用了 TTFT、TPOT、端到端延迟、吞吐量四个指标联合刻画而不是只看一个。这点很关键因为线上服务里用户感知最强的是 TTFT而成本核算看的是吞吐两者必须一起看。第三个问题是硬件不可比。A100、H100、L40S、甚至消费级 4090在不同框架下的表现排序可能完全不一样。论文主要聚焦在数据中心级 GPU 上这点要注意如果你用的是消费卡结论不能直接照搬。提示自己做框架对比时一定要固定请求集的输入长度分布和输出长度分布。只报平均长度是耍流氓因为长尾请求对显存和调度的压力远大于平均值体现出来的。2.2 连续批处理收益边界在哪里连续批处理是这两年推理框架的标配vLLM、TensorRT-LLM、SGLang 都支持。它的核心思想是不等一个 batch 里所有请求都生成完再换下一批而是某个请求一结束就立刻把新请求塞进空出来的槽位。这样 GPU 的算力利用率能显著提升尤其是在输出长度方差很大的场景下。但论文的实测揭示了一个容易被忽略的边界当输出长度分布很集中时连续批处理相对静态批处理的优势会大幅缩小。道理不难理解如果所有请求都生成差不多数量的 token那静态批处理本来就不会浪费太多等待时间连续批处理的调度开销反而成了纯负担。我自己的经验也印证了这点在一些固定模板输出的场景比如结构化抽取、分类打标输出长度基本固定在几十个 token这时候连续批处理带来的吞吐提升可能只有个位数百分比但调度器的 CPU 开销和实现复杂度是实打实的。反过来在开放域对话、代码生成、长文写作这类输出长度方差极大的场景连续批处理的收益非常明显论文里给出的数据是吞吐量提升可以达到数倍。所以选型时先问自己我的输出长度分布是什么形状2.3 显存管理PagedAttention 的适用与不适用PagedAttention 是 vLLM 的招牌技术核心思路借鉴了操作系统的虚拟内存分页把 KV Cache 切成固定大小的 block 来管理从而减少显存碎片、支持更灵活的共享。论文对它的评价总体正面但也指出了几个适用边界。第一在极长上下文场景下分页带来的间接寻址开销会变得不可忽略。当上下文长度到几万甚至十几万 token 时KV Cache 的 block 数量巨大注意力计算时的 block 查表开销会侵蚀掉一部分分页带来的收益。论文里提到在某些超长上下文配置下精心实现的分页方案和朴素的连续分配方案差距会缩小。第二前缀共享prefix sharing才是 PagedAttention 真正的高光场景。当大量请求共享同一个系统提示词或同一段长文档时分页机制可以让这些请求共享同一份 KV Cache block显存节省非常可观。论文里对多轮对话和 RAG 场景的分析都指向这个结论。我实际做 RAG 服务时也发现把系统提示词和检索到的公共文档做前缀缓存显存占用能降一大截这个收益比单纯的分页碎片优化大得多。第三分页的 block size 是个需要调的参数。block 太小查表开销大block 太大碎片和浪费又多。论文没有给出一个万能值但从实测看16 到 32 这个区间在多数场景下比较平衡。这个值跟你的平均序列长度强相关序列越长越倾向于用大一点的 block。2.4 并行策略张量并行、流水线并行怎么选当模型大到单卡放不下时就得上并行。论文对比了张量并行TP和流水线并行PP在不同规模下的表现结论比较符合工程直觉但细节值得说。张量并行是把每一层的矩阵运算切到多卡上通信发生在每一层内部所以通信频率极高对卡间带宽要求很苛刻。NVLink 环境下 TP 表现很好但如果是 PCIe 互联TP 的通信开销会迅速吃掉并行带来的收益。论文里有个数据我印象很深在 PCIe 环境下TP 规模超过 4 之后加速比就明显偏离线性了。流水线并行是把不同层放到不同卡上通信只发生在层与层之间频率低得多对带宽友好。但它的代价是流水线气泡bubble也就是各阶段负载不均衡导致的空闲。论文指出PP 的微批次数micro-batch需要足够大才能把气泡填满而微批次一多延迟又会上升。所以 PP 更适合吞吐优先、对延迟不敏感的场景。我自己的经验是单机 8 卡 NVLink 环境优先 TP跨机或者 PCIe 环境优先 PP 或者 TPPP 混合。论文的结论和这个经验基本一致。另外补充一点论文没细说但很实用的现在很多框架支持 TP 和 PP 混合比如 TP4、PP2这种配置在中等规模部署里往往比纯 TP 或纯 PP 更划算因为它同时平衡了通信开销和气泡问题。2.5 量化省的是显存还是算力量化是另一个被广泛讨论的方向INT8、INT4、FP8 各有拥趸。论文的实测给出了一个比较清醒的结论量化的首要收益是显存算力收益取决于硬件是否原生支持对应的低精度运算。在支持 FP8 原生运算的新一代 GPU 上FP8 量化确实能同时降显存和提算力。但在老一代 GPU 上INT4 权重虽然省了显存但反量化回高精度再计算的过程反而可能增加延迟。论文里提到某些配置下 INT4 量化的吞吐反而不如 FP16就是因为反量化的开销盖过了显存节省带来的 batch size 提升收益。还有一个容易被忽略的点量化对精度的影响在不同任务上差异巨大。论文没有深入做精度评测但从工程实践看分类、抽取这类任务对量化比较鲁棒而数学推理、代码生成这类任务对量化就敏感得多。我踩过的坑是在一个代码补全服务上用了 INT4 量化吞吐是上去了但生成代码的可用率明显下降最后还是退回了 FP16。所以量化一定要在自己的任务上做精度回归不能只看通用 benchmark。3. 真实负载下的实操要点与配置方法3.1 先搞清楚你的负载画像在动手配任何框架之前我强烈建议先花半天时间把你的负载画像搞清楚。论文反复强调的一点是没有普适的最优配置只有匹配负载的最优配置。负载画像至少包含这几个维度。维度需要采集的信息影响的设计决策输入长度平均值、P50、P95、P99、最大值显存预算、block size、是否开前缀缓存输出长度平均值、P50、P95、P99、最大值批处理策略、调度器选择请求到达率平均 QPS、峰值 QPS、突发特征并发上限、排队策略、是否自动扩缩共享前缀系统提示词长度、公共文档复用率前缀缓存开关、显存分配延迟要求TTFT 上限、TPOT 上限batch size 上限、并行策略这张表不是论文原文是我根据论文的分析框架和实际经验整理的。采集方法很简单在网关层打日志记录每个请求的输入 token 数、输出 token 数、到达时间戳、首 token 时间、完成时间。跑一周就能看出分布形状。别嫌麻烦这一步做扎实了后面的调优才有方向。3.2 显存预算怎么算显存是推理服务最硬的约束算清楚显存预算是所有配置的前提。一个粗略但实用的估算公式是这样的总显存 模型权重显存 KV Cache 显存 激活值显存 框架开销模型权重显存好算参数量乘以每参数字节数。FP16 是 2 字节INT8 是 1 字节INT4 是 0.5 字节。比如一个 70B 模型FP16 权重就是 140GB 左右单卡 80GB 放不下必须并行。KV Cache 显存是变量公式是KV Cache 2 × 层数 × 注意力头数 × 头维度 × 序列长度 × batch size × 每元素字节数这里的 2 是因为 Key 和 Value 各一份。以 Llama 2 70B 为例80 层64 个注意力头用 GQA 的话实际 KV 头数更少头维度 128FP16 存储。假设平均序列长度 2048batch size 32那 KV Cache 大概是2 × 80 × 8GQA 的 KV 头数× 128 × 2048 × 32 × 2 字节 ≈ 21.5 GB注意这里我用了 GQA 的 KV 头数 8而不是 64因为 Llama 2 70B 用了分组查询注意力KV 头数远少于查询头数。这个细节很多人算错导致显存预算偏差巨大。论文里也强调了 GQA 和 MQA 对 KV Cache 的压缩效果这是长上下文服务能跑起来的关键前提之一。激活值显存和框架开销比较难精确算一般留 10% 到 20% 的余量。实际配置时我习惯先把权重和 KV Cache 算出来剩下的留给激活和框架如果余量不足 10%就说明配置太激进得降 batch size 或者上并行。3.3 批处理参数的调优路径批处理参数是影响吞吐和延迟最直接的旋钮。论文的实测给了一条比较清晰的调优路径我结合自己的经验整理成下面这个顺序。第一步确定最大 batch size 的显存上限。用上面的公式反推在满足延迟要求的前提下显存能撑住多大的 batch。注意这里的 batch 是“有效 batch”也就是连续批处理下同时在跑的请求数不是一次前向的静态 batch。第二步调 TTFT 和 TPOT 的平衡。如果你对 TTFT 敏感比如交互式对话就要限制单次前向的 token 数让新请求能更快被调度到。如果你对吞吐敏感比如离线批处理就可以把单次前向的 token 数拉大让 GPU 算得更满。论文里提到很多框架有max_num_batched_tokens这类参数就是控制这个的。第三步观察 GPU 利用率和排队长度。如果 GPU 利用率长期低于 70%说明 batch 没喂饱可以适当加大如果排队长度持续增长说明吞吐跟不上到达率要么加卡要么降延迟要求。论文里有个细节值得注意GPU 利用率高不等于服务健康如果利用率高但排队也在涨说明系统在过载边缘这时候加 batch size 只会让延迟更糟。注意调 batch size 时一定要看 P99 延迟不能只看平均值。平均值好看但 P99 爆炸线上用户体验会很差因为用户记住的往往是那几次卡顿。3.4 前缀缓存的落地方法前缀缓存是我认为性价比最高的优化之一尤其适合 RAG 和多轮对话场景。落地方法不复杂但有几个细节要注意。首先把系统提示词和公共上下文放在请求的最前面并且保证这部分内容在不同请求间完全一致。任何细微差异比如多一个空格都会导致缓存失效。我见过有人把时间戳拼进系统提示词结果缓存命中率直接归零这种坑一定要避免。其次框架层面要开启前缀缓存功能。vLLM 有enable_prefix_cachingSGLang 有 RadixAttentionTensorRT-LLM 也有对应的前缀共享机制。开启之后框架会自动识别公共前缀并复用 KV Cache。第三监控缓存命中率。这个指标直接反映优化效果。命中率高显存节省和 TTFT 改善都会很明显命中率低就要检查是不是前缀不一致或者请求路由把相同前缀的请求打散到了不同实例上。后者在多实例部署时很常见解决办法是在网关层做一致性哈希把相同前缀的请求路由到同一实例。论文里对前缀共享的分析主要集中在单实例场景多实例的路由问题没有展开但这是实际部署中绕不开的。我自己的做法是在网关层对系统提示词的哈希值做一致性哈希效果比较稳。4. 常见问题与排查技巧实录4.1 吞吐上不去先查这四个地方吞吐不达标是最常见的问题排查顺序我建议按下面来。第一看 GPU 利用率。如果利用率低说明 GPU 在等数据瓶颈可能在 CPU 侧的调度、tokenize、或者网络传输。论文里提到某些框架在高 QPS 下CPU 侧的调度开销会成为瓶颈尤其是 Python 实现的调度器。解决办法是看框架有没有异步 tokenize、多进程调度之类的优化。第二看显存是否成为 batch size 的瓶颈。如果显存快满了框架会自动限制 batch size吞吐自然上不去。这时候要么量化要么上并行要么缩短上下文。第三看是否有长尾请求拖累。一个超长请求占着显存和计算资源会让其他请求排队。论文里提到对超长请求做单独处理比如单独的路由或限流能显著改善整体吞吐。我实际做过这个优化把超过某个长度阈值的请求分流到专门的实例主实例的 P99 延迟改善很明显。第四看并行通信是否成为瓶颈。如果是 TP 部署检查卡间带宽利用率。如果通信时间占比过高考虑换 PP 或者减少 TP 规模。4.2 延迟毛刺的排查思路延迟毛刺比持续高延迟更难查因为它往往是间歇性的。论文里提到的几个原因我按出现频率排序。最常见的是显存碎片导致的偶发 OOM 重试。分页机制能缓解这个问题但如果 block size 设置不合理碎片依然会出现。排查方法是看日志里有没有重试记录以及显存占用是否呈现锯齿状。其次是调度器的批处理抖动。连续批处理在请求到达不均匀时batch 大小会剧烈波动导致延迟也跟着抖。论文建议对到达率做平滑或者在调度器里加一个小的等待窗口攒一批再发。这个等待窗口的大小需要权衡太大增加延迟太小起不到平滑作用。第三是GC 或者后台任务干扰。Python 框架的 GC 停顿、日志刷盘、监控采集都可能造成毛刺。论文没有细说这块但实际部署中这是很常见的。我的做法是把日志和监控采集放到独立进程避免和推理主进程抢资源。4.3 量化后精度下降怎么办量化后精度下降是另一个高频问题。排查和处理思路如下。先确认下降幅度是否在可接受范围。做一次任务相关的评测而不是只看困惑度。困惑度下降一点点任务指标可能掉很多也可能几乎不变取决于任务。如果下降明显先尝试只量化权重不量化激活。权重对量化的敏感度通常低于激活W4A16 这类配置往往能在省显存和保精度之间取得较好平衡。论文里也提到激活量化的精度风险普遍高于权重量化。如果还不够尝试混合精度量化也就是对敏感层保留高精度对其他层量化。哪些层敏感不同模型不一样一般注意力输出层和最后的 LM head 比较敏感。这个需要做消融实验比较费时间但效果通常比全局量化好。最后考虑量化感知训练或者蒸馏但这已经超出推理优化的范畴了成本也高得多。多数情况下W4A16 加上混合精度已经够用。4.4 多实例部署的路由陷阱单实例调优好了一上多实例又出问题这是很典型的场景。论文主要聚焦单实例但多实例的问题在实际生产中更普遍我补充几个踩过的坑。第一个坑是前缀缓存失效。前面提过相同前缀的请求如果被路由到不同实例缓存就白做了。解决办法是一致性哈希。第二个坑是负载不均。简单的轮询路由在请求长度方差大时会导致实例间负载差异巨大。一个实例全是长请求另一个全是短请求整体利用率就很差。解决办法是感知负载的路由把新请求发给当前负载最轻的实例。论文里没有展开这块但这是生产环境的必修课。第三个坑是扩缩容时的冷启动。新实例起来时显存是空的前缀缓存也是空的如果直接把流量切过去新实例的延迟会明显高于老实例。解决办法是预热在接流量前先跑一批代表性请求把缓存建起来。5. 系统设计层面的经验总结5.1 论文给工程实践的三条启示把论文的结论抽象一下我觉得对工程实践最有价值的是三条。第一条没有银弹只有匹配。连续批处理、分页显存、量化、并行每一项技术都有它的适用边界。论文最大的贡献不是告诉你哪个最好而是告诉你每个技术在什么条件下好、什么条件下不好。选型时先搞清楚自己的负载特征再去找匹配的技术而不是反过来。第二条瓶颈会转移。你优化了显存瓶颈可能跑到通信优化了通信瓶颈可能跑到 CPU 调度优化了调度瓶颈可能跑到网络。论文里多处提到这种瓶颈转移现象。所以优化是个持续迭代的过程不能指望一次配置到位。第三条端到端指标才是真指标。框架内部的吞吐数字再漂亮如果端到端延迟不达标、如果 P99 毛刺严重对用户来说就是不可用。论文坚持用多指标联合刻画这个方法论值得每个做服务的人学习。5.2 我自己的配置决策清单最后分享一份我在实际选型时会走的决策清单算是把论文的框架落到操作层面。先看模型能不能单卡放下。放不下就上并行NVLink 优先 TPPCIe 或跨机优先 PP 或混合。再看输出长度分布。方差大就开连续批处理方差小就可以考虑静态批处理省调度开销。再看是否有共享前缀。有就开前缀缓存并且确保路由一致。再看延迟要求。TTFT 敏感就限制单次前向 token 数吞吐敏感就拉大。最后看显存余量。余量紧张就上量化优先 W4A16并且做任务相关的精度回归。这套流程不是论文原文是我把论文的分析框架和自己的经验揉在一起形成的。它不一定适用于所有场景但至少能让你在选型时有个清晰的思考路径而不是对着十几个参数瞎调。论文里还有一个我觉得值得单独提的点它对“框架默认配置”和“调优后配置”做了区分对比。这个视角很实用因为很多时候我们没精力把每个框架都调到最优那默认配置的表现就很重要。论文的实测显示有些框架默认配置就很接近调优后的水平有些则差距很大。如果你团队人手有限优先选默认配置就靠谱的框架能省很多事。另外论文对系统设计的分析里反复出现一个词是“鲁棒性”。真实负载和 benchmark 最大的区别就是真实负载有突发、有长尾、有异常请求。一个框架在平稳负载下表现好不代表在突发下也稳。论文里有些框架在平稳负载下吞吐领先但一遇到突发流量就出现明显的延迟抖动。所以选型时除了看稳态指标一定要做突发测试和异常请求测试。我自己的做法是构造一个包含 1% 超长请求、5% 突发流量的测试集专门看框架在这种“脏”负载下的表现这个测试比干净的 benchmark 更能反映生产实际。关于长上下文论文的结论也值得强调。长上下文对显存的压力是超线性的因为 KV Cache 随序列长度线性增长而注意力计算随序列长度平方增长。论文里提到在超长上下文场景下单纯堆显存不是办法必须结合前缀缓存、稀疏注意力、或者分块处理等手段。我实际做长文档问答时最有效的还是前缀缓存加上合理的分块把长文档切成段分别处理而不是硬塞进一个超长上下文。最后说一个论文里提到但容易被忽略的细节tokenize 和 detokenize 的开销在高 QPS 下不可忽视。尤其是中文和代码场景tokenize 的 CPU 开销可能占到总延迟的相当比例。论文建议用 Rust 或 C 实现的 tokenizer或者至少做异步化。我实测下来把 tokenizer 换成高性能实现在高 QPS 下 TTFT 能改善百分之十几这个收益在 CPU 资源紧张时很可观。这些经验有的是论文直接给的有的是我在实际部署中踩坑踩出来的。论文的价值在于提供了一个系统性的分析框架而真正的调优还是得结合自己的负载一点点磨。希望这些内容能帮你在 LLM 推理服务的选型和调优上少走点弯路。