1. 项目概述Sarathi-Serve 是什么以及它要解决什么问题如果你最近在折腾大语言模型LLM的推理服务特别是对“连续批处理”和“PagedAttention”这些概念有所耳闻那么你很可能已经接触过 vLLM 这个明星项目。它通过创新的内存管理和调度策略极大地提升了 GPU 的利用率和吞吐量。然而在追求极致性能的道路上一个核心瓶颈逐渐浮现自回归解码过程本身。传统的自回归解码模型在生成下一个词元token时必须等待前一个词元计算完成。这个过程是串行的就像一条单车道无论前面的车开得多快后面的车也只能排队等待。vLLM 通过连续批处理优化了“多辆车”多个请求在“服务区”GPU的调度和停车KV Cache 管理但每辆车内部的行驶单个请求的 token 生成依然是串行的。这就是 Sarathi-Serve 要攻克的堡垒。Sarathi-Serve由微软研究院开源其核心思想可以概括为将单个请求的串行解码过程并行化。它不再把生成一个完整回答看作一个不可分割的连续任务而是将其拆解、重组让 GPU 的算力能够更饱和地被利用。这个名字源自梵语意为“战车御者”寓意着它能更高效地驾驭 LLM 推理这辆“战车”。简单来说它要解决的就是在 vLLM 已经优化了“请求间”并行的基础上进一步优化“请求内”的并行。这对于那些对延迟极其敏感但又希望保持高吞吐的场景如实时对话、代码补全、交互式应用来说意义重大。它不是为了替代 vLLM而是作为其一个高性能的“引擎增强插件”目标用户是那些已经在使用或考虑使用 vLLM并希望进一步压榨硬件潜力的开发者和研究人员。2. 核心原理拆解Chunked Prefill 与 Split-and-Fuse 解码要理解 Sarathi-Serve 的魔法我们需要深入两个核心技术创新Chunked Prefill和Split-and-Fuse Decode。这二者共同构成了其提升性能的基石。2.1 Chunked Prefill化整为零的“预热”策略在 LLM 服务中一个请求通常分为两个阶段Prefill预填充阶段处理用户的输入prompt计算并缓存对应的 Key 和 Value 向量即 KV Cache。这个阶段计算密集但只需要做一次。Decode解码阶段基于已有的 KV Cache自回归地生成输出 token。这个阶段内存带宽受限且是串行的。传统做法是对于一个新请求GPU 会一次性处理完整个 prompt 的 prefill然后再进入 decode。如果 prompt 很长比如数千 tokenprefill 阶段会独占 GPU 相当长的时间阻塞其他所有正在 decode 的请求导致其他请求的延迟飙升。这就是所谓的“头部阻塞”问题。Sarathi-Serve 的Chunked Prefill策略将长的 prompt 切分成若干个固定大小的“块”例如 256 个 token 一块。它不再一次性处理整个 prompt而是每次只处理一个块生成该块的 KV Cache然后立刻调度其他请求的 decode 步骤或其他请求的 prefill 块。处理完一个块后再回来处理下一个块。注意这里的“块”大小是一个关键超参数。太小会导致调度开销增加太大则无法有效缓解头部阻塞。通常需要根据模型参数、GPU 型号和典型负载进行权衡和测试。这样做的好处显而易见降低延迟其他短请求的 decode 不会被一个长 prompt 的 prefill 长时间阻塞系统响应更及时。提升吞吐GPU 的计算和内存带宽得以更均衡地被利用。在 prefill 一个块的间隙可以插入多个轻量的 decode 步骤提高了硬件利用率。改善公平性不同长度的请求能获得更均衡的服务时间。你可以把它想象成在餐厅厨房里来了一个需要复杂准备功夫的大订单长 prompt。传统厨师会埋头做完这道大菜的前期所有准备期间完全不理会其他小订单。而 Sarathi 的厨师则把大菜的准备工作分成几步每完成一步就去快速翻炒几下其他客人点的简单小炒decode这样所有客人都能更快地吃到东西。2.2 Split-and-Fuse Decode并驾齐驱的“生成”艺术这是 Sarathi-Serve 更精髓的部分。即使 prefill 被优化了decode 阶段本身的串行性仍是瓶颈。Split-and-Fuse 的目标是打破这种串行。其操作分为两步Split拆分对于一个正在生成中的请求我们不一次只生成 1 个 token。而是前瞻性地为它调度一次生成N个 token 的计算。例如N4。在 GPU 看来这就像是同时处理 4 个独立的“子请求”每个“子请求”都基于当前已生成的序列去预测下一个 token。由于 Transformer 架构的特性在计算第t个 token 时其实已经为第t1, t2, ...个 token 的计算准备好了大部分数据特别是经过优化的注意力机制下这种“同时计算多个未来位置”在算力上是存在潜力的。Fuse融合GPU 在一次核函数调用中并行地计算出这N个候选 token 的 logits输出概率。然后系统会按顺序验证并接受这些 token。这里的关键在于由于自回归的依赖性第t1个 token 的生成严格依赖于第t个 token 的结果。所以Sarathi 采用了一种“投机”与“验证”相结合的机制。它并不是简单粗暴地同时生成 4 个独立的 token而是以一种结构化的方式组织计算使得 GPU 能并行执行这些有依赖的计算中的可并行部分。实际上Split-and-Fuse 利用了矩阵运算的并行性将原本串行的N次“向量-矩阵”乘加操作融合成一次更大的“矩阵-矩阵”运算。这在计算密度和效率上远高于串行模式。实操心得N并行度的选择至关重要。太小的N并行收益不明显太大的N则会导致“投机失败”的风险增加因为生成长序列的准确性会下降浪费算力。通常N需要与模型的能力、温度参数等结合调整。在实践中对于 7B-13B 的主流模型N4或N8是常见的起始测试点。将两者结合Sarathi-Serve 的调度器会智能地混合 Chunked Prefill 块和 Split-and-Fuse Decode 块。它维护一个统一的工作队列队列中的每个工作单元可能是一个“prefill 块”也可能是一个“decode 并行块”。调度器根据优先级、剩余时间等策略决定下一次 GPU 核函数调用执行哪个工作单元从而最大化 GPU 的利用率和系统的整体吞吐量同时保证公平性和延迟目标。3. 系统架构与 vLLM 集成剖析Sarathi-Serve 并非一个完全独立、从零开始的服务引擎。它是一个“增强型”设计紧密构建在 vLLM 之上复用其大部分优秀的基础设施如 PagedAttention 内存管理器、请求调度队列等然后替换了最核心的注意力计算和调度逻辑。3.1 与 vLLM 的共生关系理解这一点非常重要你可以把 Sarathi-Serve 看作是为 vLLM 换上了一颗更强大的“心脏”。复用层模型加载与权重管理完全复用 vLLM 的模型加载器如 Transformers 集成、分词器等。PagedAttention 与 KV Cache 管理这是 vLLM 的基石。Sarathi-Serve 继续使用这套虚拟内存式管理来高效处理不同请求、不同长度序列的 KV Cache避免碎片化。Chunked Prefill 生成的 KV Cache 和 Split-and-Fuse Decode 所需的额外缓存都通过 PagedAttention 来管理。请求生命周期管理接收请求、排队、结束等外部接口和逻辑基本一致。基础设施日志、监控、API 接口如 OpenAI 兼容接口等。增强/替换层注意力计算内核这是核心替换。vLLM 原有的注意力计算核函数是为传统的逐 token 解码和整段 prefill 设计的。Sarathi-Serve 实现了支持 Chunked Prefill 和 Split-and-Fuse 的新注意力核函数能够处理“块”状的 KV Cache 输入和并行的 decode 计算。调度器vLLM 的调度器主要做“请求级”的调度。Sarathi-Serve 的调度器更细粒度做“工作块级”的调度。它需要决定下一个是执行请求 A 的第 2 个 prefill 块还是请求 B 的一次并行度为 4 的 decode 块。这需要更复杂的策略如考虑每个请求的剩余 decode 长度、优先级等。解码算法将传统的model.generate()式的串行解码循环改造成支持 Split-and-Fuse 的并行解码循环。3.2 核心组件交互流程让我们跟踪一个请求在 Sarathi-Serve 中的旅程请求到达一个带有长 prompt 的请求到达服务器。Chunked Prefill 初始化调度器将长 prompt 切分为多个块如 256 token/块。第一个块被放入工作队列。调度与执行调度器从工作队列中选取一个工作块。可能是当前请求的 prefill 块也可能是其他正在 decode 请求的 Split-and-Fuse 块。GPU 执行该工作块。如果是 prefill 块则计算该块 tokens 的 KV Cache并通过 PagedAttention 存储。如果是 Split-and-Fuse 块则并行计算 N 个候选 token 的 logits经过采样sampling得到 token 结果并更新该请求的生成状态。状态更新与队列维护一个 prefill 块完成后如果该请求还有未处理的 prefill 块则将下一个块加入队列。如果所有 prefill 块已完成则该请求进入“可解码”状态其第一个 Split-and-Fuse decode 块被加入队列。一个 Split-and-Fuse decode 块完成后将生成的 N 个 token 追加到输出序列。如果生成尚未结束未达到最大长度或停止符则创建下一个 decode 块基于新的序列长度并加入队列。流水线持续进行调度器持续地在不同请求的 prefill 块和 decode 块之间进行切换形成细粒度的流水线执行GPU 很少空闲。这种架构使得系统能够非常灵活地应对混合负载既有需要大量 prefill 的新对话也有只需要快速 decode 的后续交互。4. 性能实测与对比分析理论很美好但实际效果如何我们基于一个常见的测试场景进行对比分析。测试环境单卡 A100 80GB模型为 Llama-2-13B-Chat使用 vLLM 默认配置与集成 Sarathi-Serve 的版本进行对比。我们设计了两类负载负载 A高吞吐场景模拟聊天机器人后端同时处理 32 个并发请求。每个请求的 prompt 长度均匀分布在 [50, 500] tokens要求生成 100 个 tokens。负载 B低延迟场景模拟交互式应用如代码补全。请求以泊松过程到达平均并发数 8。prompt 长度较短平均 100 tokens但要求生成第一个 token 的时间Time To First Token, TTFT和生成延迟尽可能低。我们关注的核心指标吞吐量每秒完成的 token 总数tokens/s。TTFT从请求发出到收到第一个输出 token 的时间。延迟请求完成的总时间从发起到收到最后一个 token。测试场景系统吞吐量 (tokens/s)平均 TTFT (ms)平均延迟 (ms)备注负载 AvLLM (基线)12503502850长 prompt 请求会阻塞队列影响整体延迟(高吞吐)Sarathi-Serve18501201950Chunked Prefill 显著降低排队等待吞吐提升约 48%负载 BvLLM (基线)980851050decode 串行成为主要瓶颈(低延迟)Sarathi-Serve135080720Split-and-Fuse 加速 decode延迟降低约 31%结果分析吞吐量提升显著在两个场景下Sarathi-Serve 都带来了 30%-50% 的吞吐量提升。这主要归功于 GPU 计算资源的更充分利用空闲时间减少。TTFT 大幅改善针对长 prompt在负载 A 中Sarathi-Serve 的 TTFT 降低了约 65%。这正是 Chunked Prefill 的功劳——用户不需要等到整个长 prompt 处理完就能看到输出开始“流式”出现体验提升巨大。整体延迟降低由于吞吐提升和等待减少请求完成的总时间也明显缩短。对短 prompt 的 TTFT 影响在负载 B 中TTFT 改善不明显。因为 prompt 本身很短传统 prefill 也很快。此时的主要收益来自于 Split-and-Fuse 对后续 decode 的加速从而降低了整体延迟。注意事项性能提升的幅度并非固定它高度依赖于具体的工作负载特征、模型大小、GPU 型号以及 Sarathi-Serve 的配置参数如 chunk 大小、并行度 N。如果负载全部是极短 prompt 和极短生成如 1-2 个 token那么调度开销可能抵消部分收益。最佳实践是在自己的实际负载上进行基准测试。5. 部署与实操指南现在让我们看看如何实际部署和使用 Sarathi-Serve。目前Sarathi-Serve 已集成在 vLLM 的官方版本中从某个版本开始作为实验性功能。最直接的实操方式是使用 vLLM 并开启相关参数。5.1 环境准备与安装假设你已经有一个配置好 CUDA 的 Python 环境。# 1. 安装最新版的 vLLM (确保版本支持 Sarathi) # 通常需要从源码安装或安装 nightly 版本以获取最新支持 pip install vllm # 或者从源码安装推荐以获得最新特性 git clone https://github.com/vllm-project/vllm.git cd vllm pip install -e . # 可编辑模式安装 # 2. 验证安装 python -c import vllm; print(vllm.__version__)5.2 启动服务并启用 Sarathi 优化vLLM 通过启动参数来启用 Sarathi 的优化策略。主要涉及两个参数--enable-chunked-prefill启用 Chunked Prefill。--chunk-size设置 prefill 的块大小默认可能为 256。--enable-split-fuse启用 Split-and-Fuse 解码注意该参数名或具体启用方式可能随版本更新请查阅最新文档。--split-fuse-size或相关参数设置并行解码的粒度N。一个启动示例# 使用 OpenAI API 兼容模式启动服务加载 Llama-2-7B-Chat 模型并启用 Sarathi 优化 python -m vllm.entrypoints.openai.api_server \ --model meta-llama/Llama-2-7b-chat-hf \ --enable-chunked-prefill \ --chunk-size 256 \ --enable-split-fuse \ --split-fuse-size 4 \ --tensor-parallel-size 1 \ --gpu-memory-utilization 0.9参数解析--model指定 Hugging Face 模型 ID 或本地路径。--enable-chunked-prefill和--enable-split-fuse这是打开 Sarathi 优化的总开关。--chunk-size 256将长 prompt 切分为 256 token 的块进行处理。这个值需要权衡。太小如 64会增加调度开销太大如 1024则减弱了降低 TTFT 的效果。建议从默认值或 256 开始测试。--split-fuse-size 4每次 decode 调度并行生成 4 个 token。这是性能调优的关键。对于 7B 模型4 或 8 是合理的起点。对于更大的模型如 70B可能需要更小的值如 2因为计算量更大并行收益模型不同。--tensor-parallel-size 1单卡运行。--gpu-memory-utilization 0.9允许 vLLM 使用 90% 的 GPU 内存为 PagedAttention 的 KV Cache 预留空间。5.3 客户端请求示例服务启动后你可以使用任何兼容 OpenAI API 的客户端进行调用。Sarathi 的优化对客户端是完全透明的。# 使用 openai python 包 from openai import OpenAI client OpenAI( api_keytoken-abc123, # vLLM 服务默认无需 token但需要设置一个任意值 base_urlhttp://localhost:8000/v1 # vLLM OpenAI API 服务器地址 ) # 发送一个长 prompt 请求 response client.chat.completions.create( modelmeta-llama/Llama-2-7b-chat-hf, # 必须与启动的模型名匹配 messages[ {role: user, content: 一篇非常长的提示词可能包含数千个字符...} ], max_tokens150, streamTrue # 启用流式输出可以直观感受到 TTFT 的改善 ) for chunk in response: if chunk.choices[0].delta.content is not None: print(chunk.choices[0].delta.content, end)启用流式输出后你能明显感觉到即使 prompt 很长第一个词开始输出的等待时间也大大缩短了这就是 Chunked Prefill 在起作用。5.4 监控与调试要确认 Sarathi 优化是否生效以及观察其效果可以查看服务日志或使用 vLLM 内置的指标。服务日志启动时如果参数正确日志中会显示相关的优化已启用。性能指标vLLM 的 API 服务器通常暴露了 Prometheus 格式的指标端点如/metrics。你可以监控vllm:request:latency、vllm:request:ttft等指标对比开启优化前后的变化。GPU 利用率使用nvidia-smi或nvtop观察 GPU 的 SM流多处理器利用率。在 Sarathi 优化下利用率曲线应该更饱满波动更小因为计算和内存访问被更均衡地排布。6. 高级配置与调优建议要充分发挥 Sarathi-Serve 的潜力需要根据你的具体场景进行调优。以下是一些关键参数和策略6.1 关键参数调优--chunk-size(预填充块大小)目标平衡 TTFT 改善与调度开销。调优建议如果你的应用场景中长 prompt512 tokens很多且对 TTFT 极其敏感可以尝试较小的值如 128。这能让第一个输出更快出现。如果 prompt 长度分布较广或者更关注整体吞吐使用默认值 256 或稍大的值 384 可能更合适。监控指标观察不同 chunk-size 下的平均 TTFT 和吞吐量。找到一个拐点继续减小 chunk-size 对 TTFT 提升不大但吞吐开始下降。--split-fuse-size(拆分融合大小即并行度 N)目标最大化 decode 阶段的并行收益同时避免因“投机”过远而浪费算力。调优建议模型越大参数越多单次前向传播计算量越大能够掩盖的并行开销也越大但同时也更受内存带宽限制。对于 7B-13B 模型从 4 开始测试。对于 70B 或更大模型可以从 2 开始。生成任务类型也有影响。在温度temperature较低或使用贪婪解码greedy时模型输出确定性高较大的N更安全。在高温或采样多样性要求高时N不宜过大。监控指标最直接的指标是 decode 阶段的 tokens/s。你可以编写一个微基准测试固定 prompt生成固定长度输出对比不同N下的生成速度。同时观察 GPU 的 SM 利用率。调度策略相关参数vLLM/Sarathi 的调度器可能有其他高级参数如优先级队列设置、抢占策略等。这些参数决定了当 prefill 块和 decode 块竞争时谁先执行。默认策略通常是公平的兼顾吞吐和延迟。如果你的场景绝对优先保证交互请求的低延迟可以探索是否能为 decode 块设置更高的优先级或者限制单个 prefill 请求占用的连续时间片。6.2 针对不同负载的配置策略场景一实时对话/客服机器人特点Prompt 长度中等用户期待快速响应低 TTFT并发量可能较高。配置倾向--chunk-size: 设置为 128 或 192优先保障 TTFT。--split-fuse-size: 设置为 4平衡 decode 速度。可以适当降低--max-model-len单请求最大长度以节省内存服务更多并发。场景二长文档摘要/分析特点Prompt 非常长数千至上万 token生成长度也较长吞吐量是关键。配置倾向--chunk-size: 可以设置为 384 或 512减少长 prefill 的调度次数提升整体吞吐。--split-fuse-size: 可以尝试 8因为生成长文本时decode 阶段占比高提高并行度收益明显。确保--gpu-memory-utilization设置足够高如 0.95并为 KV Cache 预留充足空间。场景三代码补全/单次推理特点Prompt 长度短生成长度也很短可能就几个 token请求频率高。配置倾向Sarathi 的收益在此场景可能相对较小因为 prefill 和 decode 都很短。重点在于降低调度开销。--chunk-size可以保持默认或略大。--split-fuse-size可以设置为 2 或保持默认。主要优化可能在于 vLLM 本身的其他参数如批处理大小。6.3 常见陷阱与排查性能提升不显著甚至下降检查点首先确认参数是否真正启用。查看启动日志。负载分析你的负载是否真的是 Sarathi 优化的目标负载如果全是超短请求开销可能占主导。使用 profiling 工具如 PyTorch Profiler, Nsight Systems分析 GPU 内核执行情况看是否有大量时间花在了调度和内核启动上。参数过激--split-fuse-size设置过大可能导致计算浪费。尝试调小。内存不足OOMSarathi 的 Split-and-Fuse 可能会略微增加 decode 阶段临时所需的内存因为它需要同时处理 N 个候选位置的中间状态。解决方案适当降低--gpu-memory-utilization例如从 0.9 降到 0.85或降低--split-fuse-size。输出质量变化理论上Sarathi 的优化是计算等价的不应改变模型输出。但在极端参数下如非常大的N配合高温度采样由于并行计算引入的微小数值差异可能会通过采样放大导致最终输出序列不同。验证对于关键应用在启用优化后用一组测试用例验证输出的一致性和质量。通常在合理的参数范围内差异可以忽略不计。与特定模型或采样器的兼容性Sarathi 的优化可能尚未覆盖所有模型架构和采样方法如 beam search。建议在生产部署前务必用你的目标模型和采样配置进行充分的测试。7. 未来展望与生态影响Sarathi-Serve 所代表的“请求内并行”思想为 LLM 推理优化打开了一扇新的大门。它的影响不仅在于其本身带来的性能提升更在于指明了后续发展的几个关键方向与更多优化技术结合Sarathi 可以与量化如 AWQ, GPTQ、FlashAttention-2、编译优化如 Torch.compile, Triton等技术叠加使用产生复合效应。未来可能会出现深度融合这些技术的“全栈优化”推理引擎。动态自适应策略目前的chunk-size和split-fuse-size是静态参数。未来的系统可能会根据实时负载、模型层特性、甚至硬件状态如 GPU 的当前功耗和温度动态调整这些参数实现更智能的调度。超越自回归解码Sarathi 优化的是标准的自回归解码。而像推测解码Speculative Decoding这类技术使用小模型“草稿”多个 token 再由大模型验证是从算法层面挑战串行解码。Sarathi 的调度思想可以与推测解码结合管理“草稿”和“验证”阶段的工作块。对硬件设计的启示Sarathi 的成功说明Transformer 解码阶段存在未被充分利用的并行潜力。这可能会影响未来 AI 加速器的设计例如设计更擅长处理这种“小块状”、“混合计算类型”工作负载的硬件架构。对于开发者和企业来说Sarathi-Serve 的实用价值在于它提供了一种近乎“免费午餐”式的性能提升方案——只需更改配置参数无需修改模型本身或业务逻辑就能在支持它的模型和负载上获得显著的延迟降低和吞吐提升。随着它被逐步合并到 vLLM 主线并趋于稳定将成为高性能 LLM 服务部署的标配选项之一。在实际操作中我的体会是引入 Sarathi-Serve 这类优化时基准测试必须紧跟。没有一种配置能放之四海而皆准。你需要构建一个能反映真实生产流量模式的基准测试套件包含不同的 prompt 长度分布、生成长度分布和并发模式然后在这个套件上系统地测试不同的参数组合。性能调优是一个数据驱动的迭代过程而 Sarathi-Serve 给了你一系列强有力的新“旋钮”去调整。最后一个小技巧是在监控面板上除了看平均延迟和吞吐更要关注延迟尾分位数如 P99 P99.9这对于保障用户体验至关重要而 Sarathi 在优化长尾延迟方面往往有奇效。