文章目录1 传统 prefill 和 decode 阶段中存在的问题2 Batching 的演进过程2.1 Static Batching2.2 Continuous Batching2.2.1 Iteration-Level Scheduling2.2.2 在 Iteration-Level Scheduling 中实现 Batching 的挑战2.2.3 Selective Batching2.2.3.1 为什么在 Batch 中混合 Prefill 和 Decode 请求可以提升性能2.2.3.2 Selective Batching 还存在什么问题2.3 Chunked-Prefills2.3.1 Stall-Free Scheduling 和其他调度策略的对比2.3.2 混合 Batch 的性能提升效果2.3.3 Chunked-Prefills 的开销2.3.4 如何确定最佳的 Chunk Size3 vLLM 中设置 Chunked-Prefills4 总结5 附录5.1 推理中衡量延迟的几个术语5.2 Batch 机制5.3 张量并行和流水线并行5.4 micro-batch 微批6 参考资料abstract:个人理解分块预填充就是prefill 分块和decode 做混合 batch减少 GPU 空泡提升 GPU 吞吐降低首 token 延时。个人理解什么是chunk prefill我的理解是不是就是做混合batch然后设定好每次混合batch的chunk size然后将prefill阶段也就是将prompt的计算拆分然后每次batch的chunksize里面包含了decode和分块后的一小部分prefill计算从而减小了gpu空泡减小了gpu空泡提升gpu的整体吞吐那么就能降低首token延时。个人理解chunk size不是prefill切分的size他是每次混合batch时候的最大size这个size里面包含了decode以及prefill的一部分。上面是看完视频后的个人理解以下文章内容主要来自该学习视频和文档AI INFRA 学习 05 - Chunked-Prefills 分块预填充(B站学习视频)Chunked-Prefills 分块预填充机制详解(github文档)1 传统 prefill 和 decode 阶段中存在的问题每个大语言模型LLM的推理请求都会经历两个阶段首先是prefill阶段模型会处理整个输入 prompt 并生成第一个输出 token随后进入decode阶段模型会一次生成一个 token直到输出完整的结果。prefill 阶段由于可以并行处理整个输入往往延迟较高但能充分利用 GPU 计算资源而 decode 阶段每次只处理一个 token延迟较低但 GPU 利用率也随之降低。**因此批处理batching在 decode 阶段尤为有效有助于提升整体吞吐量。**但在实际服务中多个请求的批量调度会导致 prefill 和 decode 阶段交错进行这为同时实现高吞吐和低延迟带来了不小的挑战。2 Batching 的演进过程2.1 Static BatchingStatic Batching是一种传统的大模型推理调度策略其核心特点是一旦构建了一个 batch其中的所有请求将统一执行直到全部完成后才释放资源并加入新的请求。Static Batching 虽然可以降低 TBT 延迟但也会牺牲整体系统吞吐量并导致 GPU 资源浪费。下图展示了使用 Static Batching 完成 4 个推理请求的过程。在第一轮图左每个请求根据提示词黄色生成一个 token蓝色。经过多轮迭代后图右每个请求的生成长度不同因为它们在不同轮次生成了结束标记红色。尽管请求 3 在第二轮就已完成Static Batching 仍要求整个 batch 等待最慢的请求完成此例中为第六轮的请求 2这导致 GPU 在后续迭代中无法被充分利用。下面这张动图可以更清晰地展示 Static Batching 的基本原理Static Batching 采用的是固定的调度流程调度器Scheduler每次从请求队列中取出一组请求例如图中的 x1 和 x2组成一个新的 batch并交由执行引擎Execution Engine统一进行推理。只有当执行引擎完成该 batch 中所有请求的推理后调度器才会开始处理下一轮 batch。由于 batch 中的所有请求必须一起行动我们管这种调度策略叫request-level schedule。图片来源Orca: A Distributed Serving System for Transformer-Based Generative Models以下是 Static Batching 的伪代码实现图片来源Taming Throughput-Latency Tradeoff in LLM Inference with Sarathi-Serve2.2 Continuous Batching论文Orca: A Distributed Serving System for Transformer-Based Generative Modelshttps://www.usenix.org/conference/osdi22/presentation/yu业界意识到传统方法存在效率低下的问题并提出了更优的方案。Orca: A Distributed Serving System for Transformer-Based Generative Models 是 OSDI ’22 上发表的一篇论文这是首个系统性解决该问题的工作。Orca 引入了iteration-level scheduling不再等待 batch 中所有序列生成完成而是每轮迭代动态决定 batch 大小。这样一来batch 中的某个序列一旦完成生成就可以立即被替换为新的请求从而相比 Static Batching 显著提升了 GPU 的利用率。下图展示了使用 Continuous Batching 完成 7 个推理请求的过程。左图展示的是第一轮迭代后的 batch右图展示的是多轮迭代后的情况。一旦某个请求生成了结束标记EOS token就将其替换为一个新的请求例如 S5、S6 和 S7。这种方式避免了等待所有请求完成后再处理新请求的情况因此能显著提升 GPU 的利用率。下面这张动图可以更清晰地展示 Continuous Batching 的基本原理2.2.1 Iteration-Level Scheduling下展示了 ORCA 采用迭代级调度iteration-level scheduling时的系统架构与整体工作流程① 调度器首先决定下一步要运行哪些请求。② 然后调用引擎对四个选中的请求x₁, x₂, x₃, x₄进行推理。对于首次被调度的请求调度器会提供其输入 token 给引擎处理。本例中x₃ 和 x₄ 尚未进行过任何迭代因此调度器为 x₃ 提供了 (x₃₁, x₃₂)为 x₄ 提供了 (x₄₁, x₄₂, x₄₃)。③ 接着引擎对这四个请求执行了一轮模型推理。④ 并返回每个请求生成的一个输出 tokenx₁₅, x₂₃, x₃₃, x₄₄。一旦某个请求完成推理请求池会移除该请求并通知终端返回响应。图片来源Orca: A Distributed Serving System for Transformer-Based Generative Models2.2.2 在 Iteration-Level Scheduling 中实现 Batching 的挑战在实践中应用 iteration-level scheduling 时我们面临的一个主要挑战就是如何实现批处理。为了达到高效执行的目的执行引擎应当能够对任意被选中的一组请求进行批处理执行。否则就只能一个一个地处理请求无法发挥 GPU 的大规模并行计算能力。然而即使只是两条请求也无法保证它们在下一轮迭代中能够合并执行。这是因为要实现批处理不仅需要多个请求处于相同的阶段还必须具有形状完全一致的输入张量。一对请求在以下 3 种情况下下一次迭代不能一起批处理两个请求都处于 prefill 阶段但输入 token 数量不同例如 x₃ 和 x₄。prefill 阶段的 Attention 是一次性并行处理整个 prompt 序列。如果两个请求的输入 token 长度不同它们的输入张量在长度维度L上不一致无法拼接成统一形状的 batch 张量[B, L, H]导致无法合批执行。请求 x₃ 的 prompt 长度是 2输入张量形状为[1, 2, H]请求 x₄ 的 prompt 长度是 3输入张量形状为[1, 3, H]无法拼接成一个[2, L, H]的张量因此不能合批执行。两个请求都处于 decode 阶段但正在生成不同位置的 token例如 x₁ 和 x₂。虽然 decode 阶段每次只处理一个 token输入张量形状都是[1, H]但此阶段的 Attention 会依赖之前生成的所有 token即使用 KV cache。如果请求的生成位置不同其 KV cache 长度也不同导致 Attention 的 Key/Value 张量形状不同。两个请求处于不同阶段一个在 prefill另一个在 decode例如 x₁ 和 x₃。prefill 的一次迭代会并行处理所有输入 token以提高效率而 decode 阶段的一次迭代则只处理一个 token。为了解决上述挑战一个可行的思路是尽可能寻找这些请求在计算过程中的共性以便将相同的部分合并执行从而最大化批处理效率对于差异部分则单独处理。2.2.3 Selective BatchingSelective Batching 的核心原理在于仅对适合批处理的操作执行批处理不适合批处理的操作则单独处理。具体来说对于preproj、postproj、FFN1和FFN2这类线性变换或归一化操作它们的计算与序列长度无关只是在 hidden_size 维度上做线性转换并且都需要从显存读取权重。因此可以将 batch 内所有 token 拉平成一个二维张量例如 x₃ 和 x₄ 的输入张量可以合并为一个形状为[ ∑ L , H ] [ 5 , H ] [∑L, H] [5, H][∑L,H][5,H]的二维张量一次性完成所有相关计算。这样不仅简化了操作还能显著提升权重加载的利用率降低 IO 次数提高整体执行效率。对于 Attention 操作由于每个请求的 mask、KV cache 和 token 位置可能不同导致其张量形状不一致无法直接合并处理。Selective Batching 会在进入 Attention 之前将 batch 拆分逐个请求单独计算 Attention 分数完成后再将结果合并回统一的张量以便继续执行后续操作。Attention 分数的计算并不依赖显存中的模型权重只需使用之前生成的 Q、K、V 向量即可因此拆分处理不会带来额外的 IO 开销。图片来源Orca: A Distributed Serving System for Transformer-Based Generative Models2.2.3.1 为什么在 Batch 中混合 Prefill 和 Decode 请求可以提升性能在 Batch 中混合 Prefill 和 Decode 请求可以提升性能原因在于这两种阶段对 GPU 资源的利用方式互补混合后能更充分地发挥硬件潜力prefill 阶段是计算密集型compute-bound主要时间花在大规模的线性变换和矩阵运算上算力利用率高但内存带宽利用率不高。即使 batch size 很小prefill 吞吐量也很快趋于饱和增大 batch size 对提升吞吐帮助有限比如 batch size 从 4 增加到 8甚至可能因算力饱和而下降。decode 阶段是内存密集型memory-bound大部分时间消耗在读取 KV cache 和模型权重上算力利用率很低。此时增大 batch size 可以显著提升吞吐因为可以合并多次权重和 KV cache 的读取减少 IO 次数让空闲的算力得到利用。混合批处理的优势在于prefill 阶段可以搭载piggyback在 decode 阶段未被充分利用的算力上提升整体算力利用率。decode 阶段可以和 prefill 阶段共享一次权重读取减少内存带宽压力提高带宽利用率。这样GPU 的计算单元和内存带宽都能被更充分利用整体吞吐和 QPS 明显提升。下展示了吞吐量随 batch size 变化的趋势。可以观察到对于 decode 阶段吞吐量几乎呈线性随 batch size 增长而 prefill 阶段即便只处理一个请求其吞吐量也已接近饱和进一步增大 batch size 效果有限。图片来源Taming Throughput-Latency Tradeoff in LLM Inference with Sarathi-Serveprefill 和 decode 阶段在吞吐量扩展性上的差异源于它们所执行的矩阵乘法形式不同**prefill 阶段执行的是批量的矩阵-矩阵乘法matrix-matrix multiplications而 decode 阶段执行的是向量-矩阵乘法vector-matrix multiplications。**众所周知当算术强度高于 GPU 的FLOPS:内存带宽FLOPSfloating-point operations per second表示模型在推理过程中执行的浮点运算次数比值时内核属于 compute-bound 类型能够高效地在 GPU 上运行。相反算术强度较低的内核则由于受限于内存带宽难以充分发挥 GPU 的计算能力属于 memory-bound 类型。下图展示了 prefill 和 decode 阶段中各个操作的算术强度arithmetic intensity。如下图所示在 prefill 阶段即使 batch size 为 1所有操作的算术强度依然很高。而在 decode 阶段这些操作的算术强度下降了两个数量级以上只有在 batch size 达到 256 这种极大值时decode 阶段才开始变得计算密集。然而将 batch size 扩展到如此之高在实际中几乎无法实现因为每条请求的 KV cache 占用非常大。例如在 LLaMA-13B 模型上使用 A6000 GPU在序列长度为 1K 的情况下最多只能容纳 18 条请求的 batch。因此在当前可行的 batch size 范围内decode 阶段仍然是内存瓶颈memory-bound。图片来源Taming Throughput-Latency Tradeoff in LLM Inference with Sarathi-Serve2.2.3.2 Selective Batching 还存在什么问题回顾 ORCA 的 Selective Batching 的策略就会发现其行为具有一定的随机性一个 batch 中包含多少条 prefill 请求、多少条 decode 请求并没有明确控制仅仅是按照“先到先服务”的策略动态拼装而成。这就带来一些问题若某个 batch 中包含大量 prefill 请求或某些 prefill 请求本身 token 很长就会导致 prefill tokens 占据大量计算资源使整个 batch 变得compute-bound相反若 batch 中以 decode 请求为主例如所有请求都处于推理阶段或没有新的输入序列可调度则该 batch 很可能是memory-bound的导致算力无法充分利用。在流水线并行中同样可能产生气泡。虽然流水线并行Pipeline Parallelism可以扩展大模型的并行能力但也引入了一个典型问题流水线气泡pipeline bubbles。所谓“气泡”是指由于不同阶段间计算不均衡或等待导致的 GPU 空闲时间从而造成资源浪费和吞吐下降。Orca 系统尝试通过迭代级调度iteration-level scheduling来缓解这一问题但在实际推理中仍然可能出现气泡主要原因包括PB1连续 micro-batch 的 prefill token 数量差异大。例如若 AB 和 CD 分别是两个 micro-batch且 AB 的 token 总数显著多于 CD。当 GPU1 完成 Cp 和 Dp 的 prefill 后必须等待 GPU2 完成 AB 的 prefill才能继续执行 Ad1 和 Bd1 的 decode。GPU1 在此期间处于空转状态形成 PB1 类型气泡。PB2prefill 阶段和 decode 阶段计算负载差异大。PB2 类型气泡出现在 prefill 和 decode 阶段相继执行时。以 Ad1 和 Bd1 为例它们的 decode 阶段每次仅处理一个 token计算时间极短而此时 GPU2 正在处理 Cp 和 Dp 的 prefill涉及多个 token耗时较长导致 GPU1 无法及时执行后续任务资源被浪费形成 PB2 气泡。PB3decode 阶段上下文长度差异导致计算时间不均。decode 阶段的计算开销受上下文长度即 KV cache 长度影响较大。不同 micro-batch 中请求的上下文长度不一导致 decode 阶段耗时不同从而在流水线上产生等待形成 PB3 类型气泡。图片来源SARATHI: Efficient LLM Inference by Piggybacking Decodes with Chunked-Prefills2.3 Chunked-Prefills论文SARATHI: Efficient LLM Inference by Piggybacking Decodes with Chunked-Prefills https://arxiv.org/abs/2308.16369Taming Throughput-Latency Tradeoff in LLM Inference with Sarathi-Servehttps://arxiv.org/abs/2403.02310Githubhttps://github.com/microsoft/sarathi-serve为了进一步解决上述问题Sarathi-Serve 提出了一种兼顾吞吐量与延迟的调度机制其中包括两个核心设计思想chunked-prefills分块预填充和stall-free scheduling无阻塞调度。chunked-prefills 将一个 prefill 请求拆分为计算量基本相等的多个块chunk并在多轮调度迭代中逐步完成整个 prompt 的 prefill 过程每次处理一部分 token。而 stall-free scheduling 则允许新请求在不阻塞 decode 的前提下动态加入正在运行的 batch通过将所有 decode 请求与新请求的一个或多个 prefill chunk 合并构造出满足预设大小chunk size的混合批次。Sarathi-Serve 建立在 iteration-level batching 的基础上但有一个重要区别它在接纳新请求的同时限制每轮迭代中 prefill token 的数量。这样不仅限制了每轮迭代的延迟还使其几乎不受输入 prompt 总长度的影响。通过这种方式Sarathi-Serve 将新 prefill 的计算对正在进行的 decode 阶段的 TBT 影响降到最低从而同时实现了高吞吐量和较低的 TBT 延迟。此外Sarathi-Serve 构建的混合批次包含 prefill 和 decode token具有近似均衡的计算需求。结合流水线并行pipeline-parallelism这使我们能够创建基于微批处理micro-batching的均衡调度从而显著减少流水线气泡pipeline bubbles提升 GPU 利用率实现高效且可扩展的部署。图片来源SARATHI: Efficient LLM Inference by Piggybacking Decodes with Chunked-Prefills在实际调度过程中Sarathi-Serve 会优先调度正在进行的 decode 请求因为每个 decode 仅消耗一个 token且对延迟最为敏感调度器会根据 KV cache 的容量判断是否仍可继续添加 decode 请求。随后系统会在剩余的 token 预算范围内处理尚未完成的 prefill 请求优先填满一个 prefill 请求中的 token再继续处理下一个在预算允许的情况下可连续处理多个 prefill 请求。若仍有剩余 token 预算则进一步接纳新的 prefill 请求加入当前批次。系统会确保当前调度轮次中 decode 和 prefill 的 token 总数不超过预设的 chunk size。注论文中提到了两个相关概念一是 token budget这个概念较为明确用于决定每轮迭代中允许处理的最大 token 数即 chunk size 的上限二是 chunk size其使用较为模糊有时指代 token budget有时又表示实际 chunk 中包含的 token 数量。在 Sarathi-Serve 的实现代码中chunk_size 明确用于表示每轮迭代中 token 数的上限即 token budget。为了避免混淆本文中所提到的 chunk size 均指 每轮迭代的 token 上限。除了固定的 chunk size默认值是 512 个 token该值是 Sarathi 论文中基于硬件特性和性能分析实验计算出的在单个 batch 中能够最大化 GPU 计算饱和度的 token 数量。之外Sarathi-Serve 还提供了**动态 chunk size **机制这是一种渐进式的分块策略旨在平衡首 token 延迟和整体吞吐量。该机制将长提示词的处理过程划分为多个阶段在早期阶段使用较大的 chunk size如 2048 个 token来快速推进处理减少首 token 延迟随着处理进度的推进逐步减小 chunk size最终降至 256 个 token避免长提示词的后续处理阻塞其他请求。固定 chunk size 是包含 prefill decode token 的总数。例如512 token 的 batch 可能包含2 个 decode 请求各 1 token prefill 请求 1400 个 token prefill 请求 2110 个 token 512 个 token。而动态 chunk size 对于不同阶段的 prefill 请求是不一样的比如 chunk_sizes 列表是 [1024, 512, 256]一个 batch 可能包含 2 个 decode 请求各 1 个 token prefill 请求 1250 个 token阶段 3 prefill 请求 2772 个 token阶段 11024-2-250772 1024 个 token。2.3.1 Stall-Free Scheduling 和其他调度策略的对比**在 Sarathi-Serve 的设计理念下无论是 prefill 阶段还是 decode 阶段都不会产生停滞stall。**从作者的观点来看其余推理系统如 vLLM、Orca、FasterTransformer在调度策略上或多或少都牺牲了一方的性能以保全另一方prefill 优先的调度策略prefill-prioritized schedulesvLLM 会优先调度尽可能多的 prefill 请求只有在完成这些 prefill 后才恢复 decode从而造成 decode 阶段的阻塞导致 TBT 延迟上升。Orca 和 vLLM 都采用 FCFS先来先服务的 iteration-level batching 策略并同样优先处理 prefill 请求。但在 batch 组成策略上有所不同vLLM 仅支持纯 prefill 或纯 decode 的 batch而 Orca 支持 prefill 和 decode 的混合 batch。尽管如此Orca 的混合 batch 在包含长 prompt 时执行时间依然较长decode 阶段依旧受到影响无法避免 decode 阻塞。decode 优先的调度策略decode-prioritized schedulesFasterTransformer 采用 request-level batching 策略在当前请求的 decode 阶段全部完成之前不会调度任何新的请求。例如在下图中请求 C 和 D 的 prefill 将被阻塞直到请求 A 和 B 完全退出系统。该策略虽然可以显著降低 TBT 延迟但也牺牲了系统整体吞吐量。无阻塞stall-free的调度策略Sarathi-Serve 同样支持 prefill 和 decode 的并行执行但相比 Orca它通过精细控制每个 batch 中 prefill token 的数量确保 decode 几乎不受影响。与 FasterTransformer 相比Sarathi 的 decode 时间只略有延长把 Sarathi-Serve 的绿色块和 FasterTransformer 的红色块相比可以发现绿色块只长了一点却显著提升了吞吐量实现了低延迟与高吞吐的兼得。图片来源Taming Throughput-Latency Tradeoff in LLM Inference with Sarathi-Serve注目前 vLLM 已支持 chunked-prefills 和混合 batch以上关于 vLLM 的描述是基于论文撰写时的实现情况。2.3.2 混合 Batch 的性能提升效果下图展示了在 A6000 GPU 上运行 LLaMA-13B 模型不同 batch 组合方式下每个 token 的处理时间单位毫秒仅包含 prompt 的请求prompt 长度为 1024batch 大小为 4仅包含 decode 的请求batch 大小为 4序列长度为 1024一个混合 batch包括 1 个长度为 1021 的 prefill 请求和 3 个 decode 请求。结果表明混合 batch 能将每个 token 的解码时间显著降低一个数量级大幅提升整体推理效率同时prefill 阶段的耗时几乎没有变化。图片来源SARATHI: Efficient LLM Inference by Piggybacking Decodes with Chunked-Prefills2.3.3 Chunked-Prefills 的开销chunked-prefills 的开销主要来自两个方面第一当 chunk size 变小时chunked-prefills 的算术强度会下降进而降低 GPU 利用率从而影响 prefill 阶段的效率。下图展示了在 Yi-34B 模型中chunking 操作对整体 prefill 时延带来的影响。如预期所示chunk 的划分越细例如 size 为 512带来的开销越大但整体来看开销的增长始终控制在 1.25 倍以内属于可接受的范围。图片来源Taming Throughput-Latency Tradeoff in LLM Inference with Sarathi-Serve第二chunked-prefills 会对 Attention 计算造成轻微开销因为每个 chunk 在执行 Attention 时需要重复从 GPU 内存中读取该请求之前所有 chunk 的 KV cache。如下图所示在 prompt 的前向传递结束前所有 chunked-prefills 操作的 FFN 计算量是相同的但从第二个 chunk 开始每一个 Attention kernel 都必须重新读取之前所有 token 的 KV 对。例如如果将一个 prefill 序列切分为 N 个 chunk则第一个 chunk 的 KV cache 会被读取 N 次第二个读取 N−1 次依此类推。图片来源SARATHI: Efficient LLM Inference by Piggybacking Decodes with Chunked-Prefills尽管这种额外的 Attention 计算时间带来了一定开销但由于 Attention 在整个前向传递中所占比例较小如下图所示因此它对端到端 prefill 效率的影响并不显著。下图将 prefill 和 decode 阶段的计算时间细分为线性操作linear、注意力机制Attention以及其他部分并展示了它们各自的耗时占比。可以看出线性操作占据了绝大部分的执行时间。尽管 Attention 的开销会随序列长度呈平方增长但即使在较长的序列下线性操作仍占据超过 80% 的总耗时。因此优化线性操作对于提升大模型推理效率至关重要。而我们前面提到像 preproj、postproj、FFN1 和 FFN2 这类线性操作恰恰是可以通过批处理来提升效率的。图片来源Taming Throughput-Latency Tradeoff in LLM Inference with Sarathi-Serve2.3.4 如何确定最佳的 Chunk Size在延迟TBT目标与 prefill 开销之间寻求平衡较小的 chunk size 有助于减少 TBT 延迟因为每轮 iteration 涉及的 prefill token 更少执行速度更快。但如果 chunk size 过小也会带来一系列问题每个 chunk 的 Attention 操作都需重复读取此前的 KV cache增加内存访问负担算术强度下降GPU 利用率降低kernel 启动的固定开销更频繁影响整体效率。因此在确定 chunk size 时需要在 prefill 的计算开销与 decode 的延迟之间做出合理权衡。可以通过一次性对不同 token 数量的 batch 进行 profiling找出在不违反 TBT SLO 的前提下单个 batch 可容纳的最大 token 数从而设定合适的 chunk size。论文中借助工具 Vidur 自动化完成这一过程确保最终配置既能最大化吞吐量又能有效控制延迟。避免 tile quantization 效应GPU 执行矩阵乘法时通常采用 tile 分块机制例如 tile size 128只有当矩阵维度是 tile 的整数倍时资源利用率才最高。如果 chunk size 刚好超过 tile size 的倍数例如 257就会导致 thread blocks 内部部分线程空闲或执行无效计算即“空转”从而引发突发性的计算时间激增。下图展示了这一现象当序列长度从 256 增加到 257仅增加 1 个 token延迟却从 69.8ms 飙升至 92.33ms涨幅高达 32%。图片来源SARATHI: Efficient LLM Inference by Piggybacking Decodes with Chunked-Prefills当序列长度恰好是 tile size128的整数倍时如 128、256、384 等运行时间上升相对平稳而一旦略微超过 tile 边界例如从 256 到 257计算时间则会急剧增加。这是因为 GPU 的矩阵乘法是按 tile 并行执行的如果维度不是 tile 的整数倍部分 tile 无法充分利用导致计算资源浪费这就是所谓的 tile quantization overhead。为避免这种问题推荐的做法是选择合适的 chunk size并使其与搭载piggyback 的 decode token 数之和是 tile size 的整数倍从而保持矩阵维度对齐确保计算效率最优。3 vLLM 中设置 Chunked-Prefills在 vLLM v1 中chunked-prefills 是默认启用的。在 vLLM 中可以通过调整max_num_batched_tokens参数来优化性能设置较小的值例如 2048可以提升token 间延迟ITLInter-Token Latency 也就是 TPOT因为每轮调度中 prefill token 更少不会拖慢 decode 的执行设置较大的值可以提升首 token 响应时间TTFTTime To First Token因为每轮可以处理更多的 prefill token如果追求最佳吞吐量建议将max_num_batched_tokens设置大于 8096特别是在使用小模型和大显存 GPU 的场景下。fromvllmimportLLM# Set max_num_batched_tokens to tune performancellmLLM(modelmeta-llama/Llama-3.1-8B-Instruct,max_num_batched_tokens16384)你可以使用 benchmark_throughput.py 脚本在你的 GPU 服务器上测试不同参数组合从而找到最优配置。可以执行以下命令安装 vLLM 并下载测试数据集# 安装 uv管理 python 虚拟环境curl-LsSf https://astral.sh/uv/install.sh|shsource$HOME/.local/bin/env# 安装 GPU Driverwgethttps://cn.download.nvidia.com/tesla/565.57.01/NVIDIA-Linux-x86_64-565.57.01.runshNVIDIA-Linux-x86_64-565.57.01.run --silent# 安装 CUDA Toolkit如 nvcc、include、lib64sudoaptupdatesudoaptinstall-y nvidia-cuda-toolkit# 创建 python 虚拟环境uv venv vllm-demo --python3.12--seedsourcevllm-demo/bin/activate# 安装 vLLMuv pipinstallvllm# 安装 benchmark 所需依赖uv pipinstallpandas datasets# 下载测试数据集gitclone https://github.com/vllm-project/vllm.gitcdvllm/benchmarkwgethttps://huggingface.co/datasets/anon8231489123/ShareGPT_Vicuna_unfiltered/resolve/main/ShareGPT_V3_unfiltered_cleaned_split.jsonbenchmark_throughput.py的主要参数如下--model指定要测试的模型。--dataset指定测试数据集。--max-model-len设置单个请求的最大序列长度默认值: 使用模型配置中的值比如 deepseek-ai/DeepSeek-R1-Distill-Llama-8B 模型中这个参数的值是 131072这个长度至少需要 16.00 GiB 的显存用于存储 KV cache如果 GPU 显存不够可以调小这个参数。当输入提示长度超过该值时vLLM 会抛出错误并拒绝处理该请求。--max-num-batched-tokens设置单次迭代中处理的最大 token 总数默认值: 对于启用分块预填充的情况为 2048否则为 max(max_model_len, 2048)。max-num-batched-tokens也就是 token budget决定了单次迭代中 chunk size 的上限而实际的 chunk size 会根据当前批处理中的其他序列和剩余的 token budget 动态计算。--num-prompts指定基准测试中要处理的 promt 数量。默认值: 1000# vLLM v1 中默认开启 chunked-prefills可以通过 --no-enable-chunked-prefill 参数禁用python benchmark_throughput.py\--backend vllm\--modeldeepseek-ai/DeepSeek-R1-Distill-Llama-8B\--dataset ShareGPT_V3_unfiltered_cleaned_split.json\--max-model-len8192\--max-num-batched-tokens2048\--num-prompts1000\# 输出结果Throughput:4.85requests/s,2007.66total tokens/s,962.92output tokens/s Total num prompt tokens:215196Total num output tokens:1983434 总结文章介绍了大模型推理中 prefill 与 decode 阶段在资源利用上的差异所带来的调度挑战并回顾了从 Static Batching 到 Continuous Batching 的策略演进。为解决传统静态或迭代调度中存在的资源浪费与延迟问题Sarathi-Serve 提出了 chunked-prefills 和 stall-free scheduling 机制通过将长 prompt 拆分为多个小块并与 decode 请求混合调度从而实现高吞吐与低延迟的平衡。5 附录5.1 推理中衡量延迟的几个术语TTFTTime To First Token指从用户发出请求到模型生成第一个 token 所花费的时间用于衡量 prefill 阶段的性能。TBTTime Between Tokens指连续生成两个 token 之间所花费的时间反映每个 token 的生成速度。TPOTTime Per Output Token指所有输出 token 的平均生成时间有时也称为 ITLInter-Token Latency反映整体生成效率。整体响应延迟可用以下公式计算Latency TTFT ( TPOT × 生成 token 数 ) \text{Latency} \text{TTFT} (\text{TPOT} \times \text{生成 token 数})LatencyTTFT(TPOT×生成token数)5.2 Batch 机制在经典的批处理机制中每次迭代时Transformer 层会接收一个形状为[B, L, H]的三维输入张量该张量是通过将一个 batch 中多个请求的[L, H]输入张量拼接而成的。其中B表示 batch 大小请求数量。L表示每个请求中一起处理的 token 数。H表示模型的隐藏层维度hidden size。假设我们有一个 batch里面包含 2 个请求每个请求当前都需要处理 3 个 token模型的 hidden size 为 10。那么这两个请求的输入张量分别是请求 1 的输入张量[3, 10][ [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0], ← 第 1 个 token [1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8, 1.9, 2.0], ← 第 2 个 token [2.1, 2.2, 2.3, 2.4, 2.5, 2.6, 2.7, 2.8, 2.9, 3.0] ← 第 3 个 token ]请求 2 的输入张量[3, 10][ [0.0, 0.1, 0.0, 0.1, 0.0, 0.1, 0.0, 0.1, 0.0, 0.1], [0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2], [0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3] ]合并后整体输入张量为[2, 3, 10][ [ ← 请求 1 [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0], [1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8, 1.9, 2.0], [2.1, 2.2, 2.3, 2.4, 2.5, 2.6, 2.7, 2.8, 2.9, 3.0] ], [ ← 请求 2 [0.0, 0.1, 0.0, 0.1, 0.0, 0.1, 0.0, 0.1, 0.0, 0.1], [0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2], [0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3] ] ]这个[B2, L3, H10]的张量就可以作为 Transformer 一层的输入参与批处理计算。5.3 张量并行和流水线并行随着大语言模型LLM规模不断扩大推理时必须跨多张 GPU 或多节点部署。为了解决单张 GPU 显存不足、单节点容量受限的问题模型并行Model Parallelism成为必要手段。而流水线并行PP是当前跨节点部署模型的主流方式其核心优势是通信开销小、扩展性好。在模型并行的常见方式中张量并行Tensor ParallelismTP将每一层的权重切分到多个 GPU 上各 GPU 协同完成每一层的计算KV cache 也被均匀分配。但这种方式每层都需要执行两次 All Reduce 操作Attention 和 FFN 各一次通信量大、延迟高只适合在同节点内、如 NVLink 连接的高带宽环境下使用。流水线并行Pipeline ParallelismPP则将整个模型按层切分每个 GPU 负责一部分连续的层多个 micro-batch 像“流水线”一样依次通过每个 GPU。相较 TPPP 只需要在层与层之间做一次激活值的通信通信开销更小尤其适合集群间带宽有限的环境。PP 的另一个优势是能释放部分 GPU 显存支持更大的 batch size从而提升 decode 阶段的吞吐效率。因此在缺乏高速互联如 NVLink的跨节点部署中PP 是唯一可行且高效的模型并行方式能将每节点的最大 batch size 提高 2–3 倍大幅增强推理吞吐。5.4 micro-batch 微批micro-batch微批是将一个完整的 batch 拆分成多个更小的子批次用于提升硬件资源利用率尤其在流水线并行Pipeline Parallelism中非常常见。当大模型被划分为多个阶段并分布在不同 GPU 上时如果直接处理整个 batch会导致部分 GPU 处于空闲等待状态。为了解决这个问题我们将 batch 拆分成多个 micro-batch并让它们像“流水线”一样在各阶段依次推进。这样每个阶段的 GPU 都可以同时处理不同的 micro-batch大幅提高并行度和吞吐量减少资源浪费。举个例子如果一个 batch 有 64 个样本可以被拆成 8 个 micro-batch每个包含 8 个样本在模型各阶段中交错处理从而避免 GPU 空转提高执行效率。每个阶段表示模型中一部分连续的层由一个 GPU 负责计算。例如在一个 12 层的 Transformer 模型中若使用 4 个 GPU则每个阶段可能包含 3 层。6 参考资料图解大模型计算加速系列分离式推理架构2模糊分离与合并边界的Chunked-Prefillshttps://zhuanlan.zhihu.com/p/710165390大模型推理核心技术之Continuous Batching和我的WXG往事https://zhuanlan.zhihu.com/p/676109470How continuous batching enables 23x throughput in LLM inference while reducing p50 latencyhttps://www.anyscale.com/blog/continuous-batching-llm-inferencevllm调度笔记chunked prefill调度策略https://zhuanlan.zhihu.com/p/711209924vLLM调度器解密下chunked prefill是如何进一步优化的https://zhuanlan.zhihu.com/p/6144374775vLLM Optimization and Tuninghttps://docs.vllm.ai/en/latest/configuration/optimization.html[EXTERNAL] OSS Chunked Prefill Evaluationhttps://docs.google.com/document/d/1W6t6wouQKgl1QivS7gbkkY5xtR9Q6wvXbmOL-1onMmk/edit?tabt.0#headingh.8um4c511b0b0[RFC] Upstream Chunked Prefillhttps://github.com/vllm-project/vllm/issues/3130NVIDIA NIM LLMs Benchmarkinghttps://docs.nvidia.com/nim/benchmarking/llm/latest/metrics.html#inter-token-latency-itlMatrix-vector and Matrix-matrix Multiplicationhttps://www.youtube.com/watch?v7CBkZq3eQ_0Paradigms of Parallelismhttps://colossalai.org/docs/concepts/paradigms_of_parallelism