从一次推理到高效服务:理解 vLLM / vLLM-Ascend 的核心特性
从一次推理到高效服务:理解 vLLM / vLLM-Ascend 的核心特性
第一篇文章从一次 generate() 出发,追踪了一个请求怎样经过 Prefill、Decode、KV Cache、采样,最后返回结果并释放缓存块。这个过程说明了模型怎样完成一次推理。
但当我们把模型部署成服务,新的问题就出现了:有人输入几句话,有人上传一整份文档;有人只要一个短答案,有人要求生成几千字。请求不断到来,设备内存却是有限的。模型能够正确回答,并不意味着服务能够及时回答所有人。
推理框架中的许多特性,正是为解决这些问题而设计的。Prefix Cache 减少重复前缀的计算,Chunked Prefill 调整长输入的处理节奏,卸载改变数据存放的位置,并行策略则重新分配多张卡上的工作。理解它们,可以从一个更具体的问题开始:当前的时间和内存,到底花在哪里?
本文用一个文档问答服务串起这些机制,是一篇原理综述。文中的小数字和时序用于解释原理,不是性能实测。初稿资料核对于 2026-09-28~29,并行相关讲解与示意图补充于 2026-10-11;文中同时涉及通用机制、特定实现和研究方向,不代表所有特性都在同一版本、模型或硬件上可用。
1. 一个文档问答服务,会遇到哪些瓶颈?
假设我们部署了一个技术资料问答助手。用户 A 提交一份长文档,请模型总结;用户 B 随后提交同一份文档,询问其中的风险;用户 C 只问一句很短的问题。
从单个请求来看,模型先处理输入,再逐步生成。Prefill 会计算输入 token 的表示并建立 KV;Decode 则利用已有 KV 继续生成。随着服务负载增加,这两个阶段会与其他请求争夺设备时间和内存。
A 的长文档可能让 C 等很久。A 和 B 的共同文档可能被重复计算。几个长请求的 KV 加起来,可能使设备无法接纳新请求。即使设备还有算力,CPU 提交工作或跨卡通信也可能让它处于等待状态。
因此,“推理慢”至少包含几种不同现象:开始回答慢、回答过程中停顿、单位时间处理的请求太少,以及内存不足导致请求排队或失败。评估特性时,首先要把这些现象分开。
| 指标 | 它描述什么 | 读者感受到的现象 |
|---|---|---|
| TTFT,首 token 延迟 | 从提交请求到收到首个输出 token 的时间 | 等多久才开始回答 |
| ITL,相邻输出 token 间隔 | 连续两个输出 token 之间的时间 | 回答是否卡顿 |
| TPOT,平均每输出 token 时间 | 生成阶段的平均速度,需明确统计口径 | 开始回答后,平均有多快 |
| 吞吐量 | 单位时间完成的请求数或 token 数 | 服务总共能处理多少工作 |
| 容量 | 能容纳的模型、上下文与并发规模 | 长文档和多人同时使用时是否撑得住 |
这些指标会互相牵制。例如,把更多请求放在同一批中,可能提高设备利用率,却未必降低每个请求的等待时间。把 KV 移到主机内存,可以容纳更长的上下文,也可能引入额外传输。
接下来按问题展开。下图只是分类地图,一个特性可能影响多个方向。
2. 请求越来越多:怎样安排每一轮计算?
2.1 Continuous Batching:让完成的请求及时退出
先考虑最简单的固定组批。A、B、C 一起开始生成,假设它们分别需要 2、5、3 轮。第二轮结束后,A 已经完成;如果必须等这一整批全部结束才能接纳新请求,那么 A 留下的机会就不能及时利用。
连续组批(Continuous Batching)把调度粒度推进到迭代边界。某个请求结束后可以退出,新的请求则在满足条件时加入。批次成员可以随着执行不断变化。
下面只画请求成员,省略 Prefill 与 token 预算,示意连续组批的组织方式:
第 1 轮:A、B、C
第 2 轮:A、B、C → A 完成
第 3 轮:B、C、D → C 完成,D 已加入
第 4 轮:B、D、E
这需要调度器持续维护每个请求的进度和资源:哪些请求还在运行,哪些已结束,新请求需要多少 token 预算和 KV 块。第一篇的双请求实验已经观察到批次成员变化;实际接纳时机还取决于调度策略和资源约束。
批次不断变化,也意味着每个请求的缓存必须能够独立增长和释放。这就把调度问题与后面要讲的分页 KV 管理连接了起来。vLLM 官方介绍
2.2 Chunked Prefill:给长输入安排分段处理的机会
连续组批解决了请求加入和退出的问题,但请求之间的工作量仍可能相差很大。
回到文档问答服务:A 已经开始回答,每轮只推进少量 Decode 工作;B 此时带着一份长文档进入。如果 B 的 Prefill 一次占据很长的执行时间,A 的下一段输出就可能被拖慢。
Chunked Prefill 将长输入拆成多个计算片段,交给调度器分轮安排。例如,假设某个教学案例每轮为 B 分配最多 1,000 个输入 token,那么 4,000 个 token 可以分成四段;每轮还可以安排已有请求的 Decode 工作。
第 1 轮:A 的 Decode + B 的输入片段 1
第 2 轮:A 的 Decode + B 的输入片段 2
第 3 轮:A 的 Decode + B 的输入片段 3
第 4 轮:A 的 Decode + B 的输入片段 4
这里是把 B 的输入分轮计算。后续片段仍然需要正确使用此前的上下文,不能把四段当成四个互不相关的请求。具体每轮分多少,要由实际 token 预算和调度路径决定。
chunk 较小时,调度器有更多机会在片段之间推进其他请求,但 B 完成 Prefill 可能需要更多轮;chunk 较大时,单轮工作更集中,也可能增加其他请求的等待。需要结合 TTFT、ITL 和吞吐一起评价。Chunked Prefill 文档
2.3 异步调度与抢占:处理等待和资源不足
设备计算之外,CPU 还要维护请求、准备输入和处理输出。如果这些工作完全串行执行,就可能出现“设备算完了,但下一批还没准备好”的间隙。
异步调度尝试让部分主机工作与设备执行重叠。能提前的工作提前做,依赖实际结果的工作仍然要等待。例如,普通 Decode 下一步要用到刚采样出的 token,不能因为采用异步执行就假定它已经产生。理解异步机制,需要看清哪些状态可以预测,哪些结果必须确认。
另一类问题是资源不足。即使还有请求等待执行,KV 空间也可能无法继续分配。框架可能暂停某些请求,释放部分资源,在后续重算或恢复它们的状态。这就是抢占与恢复所要处理的情形。它让系统有机会继续运行,但反复抢占会增加额外工作和尾延迟。vLLM 抢占与调优说明
当服务有多个实例,还要决定请求送到哪里。最空闲的实例未必拥有这个请求需要的前缀缓存;缓存命中最多的实例也可能已经排起长队。因此,请求路由往往要在负载与缓存局部性之间权衡。
这里还需要区分具体实现。例如 Ascend 的 Balance Schedule 公开设计会协调 DP rank 的运行计数与接纳行为,它不能被直接理解为“把请求搬到最空闲的卡”。Balance Schedule 设计
3. 文档重复出现:怎样管理和复用 KV?
3.1 Paged KV:让逻辑连续与物理连续分开
一个请求会逐步变长。假如一开始就为它预留最大长度的连续 KV 空间,实际只生成很短的内容时,大量空间就会闲置。反过来,随着请求增长反复申请更大的连续空间,又会增加内存管理压力。
分页管理把 KV 拆成固定容量的块。请求用块表记录自己的逻辑块对应哪些物理块,因此逻辑上相邻的 token,不必占据物理上连续的区域。
假设一个教学示例的块容量为 4 个 token,请求当前包含 9 个已经计算过 KV 的 token,那么需要三个块。它们的物理编号完全可以是 [7, 2, 11]:
逻辑 token 位置 0~3 → 物理块 7
逻辑 token 位置 4~7 → 物理块 2
逻辑 token 位置 8 → 物理块 11 的第一个槽位
容量 4 只是便于画图的示例。请求增长时补充块,结束时归还引用,使多个请求可以灵活共享整个块池。末块仍可能存在未使用槽位,所以分页管理减少浪费,并不意味着所有空间都能百分之百利用。
Paged KV 描述这种缓存组织方式;PagedAttention 则强调注意力计算如何访问分页布局中的 K、V。框架必须把逻辑位置正确映射到缓存地址,算子也必须按这些元数据读取数据。PagedAttention 官方介绍
3.2 Prefix Cache:同一份资料不必反复从头计算
现在,用户 A 和 B 都提交同一份项目资料:
A:[共同资料] + 请总结主要内容
B:[共同资料] + 请分析技术风险
只使用请求内部的 KV Cache 时,A 在自己的 Decode 过程中会复用已有 KV,但 B 仍可能从头计算共同资料。Prefix Cache 把复用扩展到请求之间:如果前缀匹配、相关缓存仍在,并且计算条件兼容,B 可以接着已有前缀 KV 处理后面的新问题。
它复用的是共同部分的计算状态。B 的问题不同,后续仍然要进行自己的计算和生成。因此,Prefix Cache 主要减少重复 Prefill 工作,不直接替代生成答案的 Decode 工作。Automatic Prefix Caching
为什么强调“前缀”?因为某个 token 的表示与它前面的上下文有关。
A:今天讨论的城市是北京。请介绍交通。
B:今天讨论的城市是上海。请介绍交通。
即使末尾都有“请介绍交通”,前面的城市已经不同,这段后缀也不能简单视为可直接复用的同一段 KV。常规精确前缀缓存需要识别从开头连续匹配的部分;模型、适配器等影响计算的条件也需要纳入匹配约束。vLLM 的公开设计通过块及其前缀的信息组织缓存标识。前缀缓存设计
请求结束后还要区分两种状态:没有请求继续引用某个块,与该块保存的内容已被覆盖,并不是同一件事。缓存管理器可以让无活跃引用的块暂时保留可复用内容;当它被重新分配或淘汰时,相应的缓存记录才需要失效。
这样就能理解“块可回收,但内容暂时仍可命中”。具体命中粒度和最后一段如何处理,要看模型缓存类型与版本,不能仅凭 prompt 字数计算。
3.3 外部 KV 缓存池:把复用范围扩大到设备之外
本地 Prefix Cache 能保留的内容受设备内存限制。文档种类变多后,早先计算过的资料可能已被淘汰;如果 B 被路由到另一台实例,也未必能看到 A 的本地缓存。
外部缓存池尝试把 KV 保存到更大的存储层级,并通过连接器完成查找、加载和保存。请求可以先检查本地命中,再尝试从外部获取额外可复用部分。Ascend KV 缓存池设计
但加载同样需要时间。对某个请求,值得比较的是:重新计算这部分前缀要多久,从外部查找并搬回来又要多久。短前缀、慢传输或高拥塞情况下,更多命中未必带来更低延迟。
到这里,三个问题已经分开:分页 KV 负责组织缓存空间,Prefix Cache 识别可复用的计算,外部缓存池扩大可保存和获取缓存的范围。
4. 设备装不下:量化、卸载和稀疏化怎样取舍?
内存里不只有 KV。模型权重、各请求的 KV、临时激活、算子工作区和图执行相关缓冲区,都可能占用设备内存。
权重大小主要由模型和表示方式决定;KV 通常随活跃请求的上下文增长。一个较小模型也可能被大量长请求的 KV 撑满。定位问题时,应先确认是哪一部分在增长,再选择特性。
4.1 量化:让每个数占用更少空间
权重量化改变模型参数的数值表示,激活量化影响运行时中间数据,KV 量化则改变缓存的表示。它们可以组合,但不是同一个开关,也不一定使用相同精度。
用一个只算原始数据的例子:相同数量的元素,从每个 2 字节变为每个 1 字节,原始存储量减半。真实实现还包含 scale、布局对齐和其他缓冲区,因此整卡内存不会自动减半。量化方法还需要匹配具体层和算子。Ascend 量化适配
较低精度会引入表示误差,量化和反量化也可能增加工作。对 KV 来说,误差会进入后续注意力计算,因此需要检查任务质量和长上下文表现。容量改善与执行加速应分开测量。KV Cache 量化
4.2 卸载:把一部分数据放到主机侧
卸载改变数据放置的位置。设备上只保留当前需要或经常使用的数据,其他部分放在主机内存等位置,需要时再搬回来。
权重卸载处理模型参数,KV 卸载处理请求状态。前者要安排各层权重何时到达,后者还要跟踪请求增长、缓存更新和释放。上游的一种权重卸载路径就以每次 forward 的 CPU—设备传输为代价扩展可用容量;Ascend 和边缘设备的具体路径需要独立核对。vLLM 权重卸载参数说明
设一次必须搬运的数据量为 D,有效带宽为 B,那么 D/B 给出一个理想化传输时间估计。实际还有提交、同步和争用开销。如果每轮都必须先等待这次传输,增加的容量就可能伴随更长延迟。
于是出现了预取:计算当前层时,提前加载下一层的数据。它能否隐藏等待,取决于两段工作能否重叠、计算是否足够长,以及是否争用同一资源。提前发出复制操作,并不等于搬运已经完成。
4.3 Prefill 逐层卸载:少量缓冲区轮流服务多层
模型按层执行时,可以考虑让少量设备缓冲区轮流承载不同层的 KV。当前层需要的数据就绪后进行计算,相关数据保存完成后,缓冲区才可交给后续层复用。
这里真正困难的是生命周期。一个缓冲区何时可以覆盖,取决于此前的读取、写入和传输是否全部完成。若后续层过早复用,就可能把仍在使用的数据改掉。公开的 Ascend 设计将逐层加载、计算、保存与缓冲区复用组织成流水。逐层与稀疏 KV 卸载设计
为了理解这种问题,可以把两块临时缓冲区画成 A、B:当前层使用 A,下一层的数据提前加载到 B;切换后,是否能够重新使用 A,仍需等待它之前承担的全部操作完成。双缓冲提供了重叠的空间,却不会自动保证正确的依赖关系。
4.4 稀疏化与 Decode 卸载:先决定需要哪些历史,再决定搬哪些
Decode 每步推进的 token 数往往很少。如果每一步都从主机搬回庞大的完整历史 KV,传输可能成为主要等待。
稀疏注意力将问题进一步拆开:当前 query 实际访问哪些历史位置?在支持这种机制的模型中,选择器或索引器先确定候选位置,再执行对应的注意力计算。完整历史可以继续保存在其他位置,设备侧只保留部分热数据或本步需要的数据。
例如,一个纯示意案例从 10,000 个历史位置中选出 500 个,其中 300 个已在设备上、200 个需要加载。这里有三件不同的工作:选出 500 个、查出缺失的 200 个、将缺失数据搬到正确位置。只有最后一步属于本步缺失 KV 的搬运。它们分别可能成为瓶颈。稀疏 Decode 卸载设计
因此,稀疏化优化“访问哪些历史”,卸载优化“这些历史存在哪里、何时搬运”。更改选择策略后,需要评估任务质量;只优化同一组选中数据的搬运时,则应先固定选择结果,检验数值和状态是否保持正确。
也不能因为一次只读取部分 KV,就认为完整历史存储自动减少。是否丢弃历史、保存到哪里、怎样恢复,是另外的设计决定。
| 机制 | 主要改变什么 | 需要付出的代价 |
|---|---|---|
| 量化 | 每个数的表示精度和字节数 | 数值误差、转换与算子适配 |
| 卸载 | 数据所在的存储位置 | 传输、同步与缓冲区管理 |
| 稀疏化 | 本次计算访问的数据集合 | 选择成本、索引处理与质量评估 |
5. 数据已经准备好:每一轮还能怎样算得更高效?
5.1 Graph:减少重复组织执行的开销
模型的一轮执行包含许多操作。主机需要发起这些操作,设备再依次或按依赖执行。对于一些小规模计算,操作本身并不长,反复提交和调度的开销却可能占据明显比例。
Graph 的思路是捕获可复用的执行安排,在满足条件时重放。可以把它理解为:固定一段可重复使用的操作流程,然后把新一轮的数据交给这段流程。它缓存的是执行安排,不是此前请求的答案,也不是 Prefix Cache 保存的前缀 KV。
真实请求的长度和批次会变化,图执行因此需要处理不同运行形状。常见办法包括预先捕获若干大小的图,将运行输入匹配到合适的形状,并正确更新动态元数据。Ascend 的 ACL Graph 在这些环节还有平台相关约束。ACL Graph 设计
例如,假设某个教学案例只准备了容量为 4 和 8 的执行形状,而本轮有 5 个有效项,就可能需要匹配更大的形状,并正确区分有效数据与填充部分。具体是否如此匹配、哪些形状可用,要由实际实现决定。
Graph 的收益通常来自减少执行组织开销,也会引入捕获时间、额外内存和形状管理。第一篇采用 Eager 便于观察逐轮执行,不能据此得出 Eager 与 Graph 的性能结论。
5.2 算子融合:减少中间结果的反复读写
再看操作内部。假设一段计算依次执行加法、归一化和其他变换。如果每一步都把中间结果写回内存,下一步再读出来,就会产生额外访存和启动成本。
算子融合尝试把适合的操作组合起来,让部分中间数据在一次执行中被继续使用。编译优化还可能消除多余复制、调整布局,或选择更适合当前形状的实现。Ascend 的 Npugraph_ex 包含基于 FX 图的变换与融合能力。Npugraph_ex 设计
Graph 与融合可以配合:前者关注怎样重复提交一段执行,后者关注这段执行内部包含哪些操作。但融合也有适用边界。不同数据类型、形状和中间结果用途,可能决定某个融合是否成立。
Attention 后端和自定义算子属于同一层面的重要选择。它们要在缓存布局、数值精度和硬件约束下完成计算。某个算子在一种形状上更快,并不能直接证明整个服务更快;它可能只占总时间的一小部分。
5.3 推测解码与 MTP:让一次验证推进更多 token
普通自回归生成中,新 token 依赖此前的结果,因此生成过程逐步推进。推测解码尝试用一个较便宜的提议过程先给出多个候选,再由目标模型验证,争取在一次验证后接受多个 token。
用一个只说明流程的例子:提议过程给出 [a, b, c],验证后接受前两个,第三个未通过,那么系统要按相应算法修正后续输出,并处理未接受候选留下的临时状态。这里省略了具体采样与接受规则,不能把它实现成简单的字符串比较。
MTP(Multi-Token Prediction)可作为模型相关的候选提议路径之一;其他方案还可以使用独立草稿模型等。模型是否具备相应结构、框架怎样接入,都影响实际可用性。vLLM 推测解码
这类方法把“减少串行生成轮数”与“增加提议、验证工作”放在一起权衡。候选常被接受时,额外工作可能值得;接受率低、验证批次太大或设备已很繁忙时,也可能得不偿失。Ascend 推测解码说明
候选 KV 的生命周期同样重要:哪些已成为真实上下文,哪些只是暂时计算过,拒绝后哪些状态需要回退。将推测解码与缓存共享或卸载组合时,这些边界尤其需要验证。
5.4 CPU、NUMA 与重叠:设备之外的等待也需要解释
即使模型算子足够快,主机线程争抢 CPU、频繁迁移,或访问距离较远的内存,也可能拖慢输入准备与数据传输。CPU 绑定与 NUMA 放置尝试让工作线程和相关内存更贴近合适的硬件资源。Ascend CPU 绑定设计
通信、数据搬运与计算重叠也有类似目的:把原本串行等待的工作安排到可并发的时间区间。但时间线上两段任务看起来重叠,不代表总时间一定减少。如果它们争抢带宽,重叠后的每一段都可能变长。
分析这类问题时,profiler 的价值在于提供实际时间线:谁在运行,谁在等,哪个依赖阻止了下一步。仅看设备利用率高低,很难确定应当修改哪一处。
6. 一张卡不够:多卡究竟怎样分工?
多卡并行不只有一种切法。要理解一项策略,最先问的应是:被分给多张卡的,究竟是请求、层、张量、专家,还是上下文?
6.1 DP:让多个副本分担请求
数据并行(Data Parallelism)在最直观的服务形态下,是准备多个模型副本,让不同副本处理不同请求。它适合扩展总服务能力:文档请求 A 交给一组设备,问题 B 交给另一组。
DP 并不会自动把同一个请求的模型计算拆开。若单个模型本身装不下,还需要与其他切分策略配合。副本多了之后,请求路由、排队长度以及缓存是否集中在某个副本,也成为调度问题。
MoE 等部署中,DP 还可能与专家并行组合,因此实际参数的含义应结合执行组来看,不能把所有配置都理解成互不通信的完整副本。
6.2 TP:多张卡共同完成一层计算
TP(Tensor Parallelism,张量并行)让多张卡合作计算同一层。 假设模型有 4 层、使用 2 张卡,两张卡都会参与这 4 层的执行,各自持有部分权重、承担部分计算。例如,一次线性变换需要产生 8 个输出分量,可以让卡0计算前4个,卡1计算后4个;后续按计算需要交换、合并或继续保留分片结果。
同一份输入
↓
卡0 卡1
第1层: 部分计算 ←── 协作 ──→ 部分计算
↓ ↓
第2层: 部分计算 ←── 协作 ──→ 部分计算
↓ ↓
第3层: 部分计算 ←── 协作 ──→ 部分计算
↓ ↓
第4层: 部分计算 ←── 协作 ──→ 部分计算
↓
输出
图中每一行是一层,两张卡共同完成这一层,再推进下一层。“协作”概括按切分方式所需的通信,不表示每层只有一次通信或必须汇集完整结果。
TP 分担了存储和计算,也引入层内通信。更多卡减少了单卡工作量,但通信成本可能抵消收益,因此卡数增加不保证单个请求更快。
6.3 PP:把不同层放在不同阶段
PP(Pipeline Parallelism,流水线并行)让不同卡负责不同层。 同样是 4 层模型、2 张卡,可以这样分工:
卡0:输入 → 第1层 → 第2层
↓ 传递中间结果(hidden states)
卡1: 第3层 → 第4层 → 输出
对同一份输入,两段依次执行;有多个可调度微批次时,卡1处理 A 的后半段,卡0就能处理 B 的前半段,从而同时工作。工作不足或阶段耗时不均时,会留下空闲等待,即“流水线气泡”。普通自回归 Decode 仍受 token 依赖约束,不能提前计算尚未生成的后续 token。
记住:TP 切一层内部,PP 切层与层之间。 两者可以组合,例如 4 张卡配置 TP=2、PP=2:卡0和卡1合作计算第1~2层,再把中间结果交给卡2和卡3合作计算第3~4层。实际收益取决于划分、负载与通信拓扑。vLLM 并行指南
6.4 EP 与 EPLB:专家分开了,还要处理负载不均
MoE 模型包含多个专家模块,一个 token 通常只被路由到其中一部分专家。专家并行(Expert Parallelism)把专家分布在不同设备上,再把 token 送到对应设备计算,并合并结果。
同一 MoE 层的输入 token
↓
路由器选择逻辑专家
↙ ↘
卡0: 专家 E0、E1 专家 E2、E3 :卡1
处理分发来的 token 处理分发来的 token
↘ ↙
按原 token 归集专家结果
↓
该层的输出
图中切分的是同一 MoE 层里的专家;传到专家侧的是 token 的中间表示。一个 token 若选中多个专家,需要按路由权重合并其结果。
问题在于,专家被选中的频率可能差很多。即使各张卡存放相同数量的专家,也可能有一张卡特别忙,其他卡等待它结束。
EPLB(Expert Parallel Load Balancing)围绕这种不均衡调整物理放置或副本分担。理解时应分开“模型决定使用哪个逻辑专家”和“框架把这个专家安排到哪个物理位置”。重排还会涉及权重迁移和映射更新,必须保证执行看到的是一致状态。EPLB 设计
调整前:发往热门专家 E0 的工作 → 卡0上的 E0(忙)
卡1(较空闲)
调整后:发往同一逻辑专家 E0 的工作
├─ 一部分 → 卡0上的 E0 副本
└─ 另一部分 → 卡1上的 E0 副本
这是有冗余专家容量时的副本分担示意:逻辑上仍使用 E0,由不同物理副本承担工作。实际策略也可能调整专家放置,需付出存储和迁移成本。
6.5 CP:按上下文分担存储和计算
CP(Context Parallelism,上下文并行)解决的一个核心问题是:单个请求的上下文很长,历史 KV 的存储、读取和相关注意力计算给单卡带来较大压力。 下面用 Decode 中分担历史 KV 的方案说明。
假设用户输入了一篇长文档,模型已经处理完输入,正在生成回答。在普通完整上下文注意力中,当前 query(当前位置用于查询上下文的向量)需要访问此前历史的 KV。上下文越长,要保存的 KV 越多,每步注意力需要读取和处理的历史也越多:设备可能装不下,或者虽然装得下,读取与计算仍然很重。
先看单卡承担全部历史的概念基线。用 8 个位置代表一段长上下文:
没有 CP:
卡0:当前 query → 读取历史 [0 1 2 3 4 5 6 7] 的 KV
↓
完成注意力计算
加入两卡 CP 后,将历史 KV 按上下文位置分片,让两张卡分别处理自己负责的部分,再通过通信合并结果:
加入 CP: 同一个当前 query
↙ ↘
卡0:读取 [0 1 2 3] 的 KV 读取 [4 5 6 7] 的 KV :卡1
↓ ↓
计算这一部分 计算这一部分
↘ ↙
通信并正确合并局部结果
↓
覆盖完整目标上下文的注意力输出
两张卡服务的是同一个请求、同一层、同一个 query,分担的是不同历史位置。 它们不会各读半篇文档后生成半个答案;合并的是模型内部的注意力结果,之后继续同一个请求的生成。合并需要考虑各部分的归一化信息,不能直接平均局部输出。
| CP 前的痛点 | 分片后带来的改进 | 仍需考虑的代价 |
|---|---|---|
| 单卡持有较长历史的 KV,容量紧张 | 每卡持有一部分历史 KV,缓解单卡缓存压力 | 临时缓冲区、其他模型状态仍占内存 |
| 单卡读取和计算的历史较多 | 将相关 KV 读取与注意力工作分给多卡 | query 等信息的交换、局部结果合并与同步 |
因此,CP 可以帮助承载更长上下文,并有机会降低这部分注意力工作的耗时,但不保证整个模型按卡数等比例提速。短上下文时,通信成本甚至可能超过分担计算的收益。
图中连续分片仅为便于理解,实际布局也可以交错分片;KV 占用也不一定随 CP 度数精确等分。CP 分担上下文位置,EP 分担专家,TP 分担层内张量计算,PP 分担不同层。
Ascend 的公开 DCP 设计在 TP 组内沿序列维度分片 KV,包含局部注意力结果与归一化信息的合并。因此,DCP 不一定意味着再额外增加一组独立设备;计算总卡数要看进程组关系。Ascend CP 设计
6.6 动态 CP:资源如何随着请求条件变化?
本节介绍可研究的策略方向,不表示 vLLM-Ascend 已支持下面列出的全部动态调整方式。
静态配置先确定了并行布局,但实际请求长短不同、负载不断变化,于是自然会提出一个问题:是否应该给不同请求不同的上下文并行资源?
“动态”可能指请求入场时选择一个资源组,也可能指阶段切换时调整,还可能指请求已经运行后改变分片方式。这三者的复杂度不同。运行中改变分片,可能涉及已有 KV 的迁移、同步及执行状态调整;入场选择则主要涉及路由和资源分配。
因此,讨论动态 CP 时必须先说明调整发生的时机与范围,再核实目标实现支持哪些动作。研究这类策略时,需要比较调整带来的后续收益,能否抵消决策与切换成本;一份静态 CP 的设计说明,并不能证明运行中重分片已经实现。
6.7 CPP:让分块工作更适合流水线
Chunked Prefill 表示把长输入分块执行,本身不要求固定块长。“块长度”是这一块包含的新输入 token 数。在 PP 场景中,还要考虑这些 chunk 穿过不同阶段时是否均衡。
固定分块按 token 数安排工作,动态 CPP 则根据预测耗时选择块大小。 同样数量的新 token,随着历史增长,需要关注的 KV 更多,后面的块往往更耗时。为了让各块耗时接近,动态 CPP 通常把后面的块切小一些;完整输入没有减少,只是切分边界改变了。它在执行前预测块大小,并非算到规定时间就暂停;实际块长还受对齐、最小长度和调度预算限制,不保证严格递减。
公开的动态分块流水线并行方案(CPP)通过性能剖析、耗时预测与校准调整 chunk 大小,目标是减少这种不均衡带来的等待。预测和调度本身有成本,流水线也仍有填充与排空过程。CPP 设计
下面用两张卡、三个 chunk(A、B、C)画一个理想化时序。卡0负责前半部分层,卡1负责后半部分层;同一个 chunk 必须先经过卡0,再交给卡1。
固定分块:A、B、C 的 token 数相同
历史逐渐增长 → 假设每个阶段处理它们分别需要 1、2、3 格时间
时间格 1 2 3 4 5 6 7 8 9
卡0(前半) [A][B][B][C][C][C][空][空][空]
卡1(后半) [等][A][等][B][B][等][C][C][C]
↑ ↑
等 B 到达 等 C 到达
动态分块:调整同一段输入的切分边界,A、B、C 的 token 数可以不同
按耗时预测选大小 → 假设每个阶段处理每块都需要 2 格时间
时间格 1 2 3 4 5 6 7 8
卡0(前半) [A][A][B][B][C][C][空][空]
卡1(后半) [等][等][A][A][B][B][C][C]
↑
中间阶段能连续接上工作
图中每格表示相同时间长度;两种方案处理相同的完整输入,但 chunk 边界不同。CPP 尝试让工作按更接近的耗时抵达下一阶段,减少中途等待。这里假设两个阶段计算能力相近并忽略通信,只说明调度原理;实际 chunk 大小由测量和预测决定,启动等待与流水线排空仍然存在,不能把示意图当作实测加速比例。
因此,Chunked Prefill 是分块执行基础,CPP 是面向流水线的进一步策略;动态 CP 则关心上下文并行资源与布局。名称中都有“动态”或“分块”,不代表它们调整的是同一个对象。本文的 CPP 专指上述公开方案;阅读其他系统时,应先核对缩写的实际含义。
6.8 KVPP:按层分配 KV 的持有者
在部分 MLA/SFA 模型的 TP 路径中,多个 rank 会重复保存历史 KV,带来容量压力。KVPP 的公开设计针对这类重复存储,在 TP 组内为不同层分配持久缓存的所有者,其他 rank 需要这一层数据时,通过广播获取到临时缓冲区,并尝试预取后续层。
可以把它理解为“谁长期保存哪一层 KV”的分工。它保留模型已有的执行并行方式,再改变缓存所有权与获取流程。容量收益需要与广播和缓冲区成本一起评价。KVPP 设计
CP 主要沿上下文维度分担,KVPP 的上述设计沿层分配 KV 所有权。它们都涉及多卡缓存,但不能直接互换,也不能仅凭概念判断组合可用。
以同一 TP 组内两张卡、四层模型为例,假设原先各卡重复保存这些层的历史 KV。下面的层归属仅作示意:
原先:两张卡各自长期保存一份
卡0:[第1层 KV][第2层 KV][第3层 KV][第4层 KV]
卡1:[第1层 KV][第2层 KV][第3层 KV][第4层 KV]
KVPP:按层指定长期持有者
卡0:[第1层 KV][第3层 KV] + 临时接收缓冲区
卡1:[第2层 KV][第4层 KV] + 临时接收缓冲区
计算第1层时:
卡0:持有第1层 KV ──广播──→ 卡1:临时接收第1层 KV
↓ ↓
计算第1层的分工 计算第1层的分工
计算第2层时:
卡0:临时接收第2层 KV ←──广播── 卡1:持有第2层 KV
↓ ↓
计算第2层的分工 计算第2层的分工
分开的是 KV 的长期存放位置,两张卡仍按原来的 TP 分工参与各层计算。 临时缓冲区在数据不再被使用后复用,也可配合预取后续层。收益是减少长期重复存储,代价是广播与临时空间;不能把它理解成 PP 那样每张卡只计算自己持有 KV 的层。
6.9 P/D 分离:让两种阶段使用不同执行实例
文档问答服务中,Prefill 和 Decode 的工作形态不同。一份长文档的 Prefill 与许多持续生成的请求放在一起,可能造成阶段间干扰。
P/D 分离把两个阶段安排给不同实例:P 实例处理输入并产生 KV,D 实例取得这些状态后继续生成。这样可以分别调整两侧资源和并行策略,也更容易独立管理两种阶段的负载。P/D 分离设计
用户:提交问题和长文档
│
▼
请求路由 / 协调
│
▼
P 实例:处理输入 → 经过模型各层 → 建立输入的 KV Cache
│
│ 交接 KV 与续接所需状态
▼
D 实例:接收并确认可用 → 逐步 Decode → 持续返回答案
│
└─ 每轮经过模型各层,读取并扩展 KV
交接完成后,不同请求可以同时推进:
P 实例:处理请求 B 的输入
D 实例:继续生成请求 A 的答案
这里按推理阶段分工,P 和 D 都需要执行完整模型的计算。 每个实例可以由一张或多张卡组成,不是 PP 中“P 算前几层、D 算后几层”。图中省略首 token 的具体返回路径;它可能由 P 侧产生,交接后 D 侧继续生成,具体以实现为准。
代价是系统多了一次明确的数据交接。D 必须确认需要的 KV 已可用,P 也不能在接收方仍依赖它时过早复用缓冲区。部署还需要处理路由、两侧容量比例以及传输失败。
因此,Chunked Prefill 通过调度缓解阶段干扰,P/D 分离通过部署分工改变干扰关系。后者并不保证每个请求都更快:小请求的计算很短时,额外路由和传输可能变得突出。
7. 性能之外,推理框架还有哪些服务特性?
前面主要关注执行效率,但“支持某项特性”也可能意味着服务能接受新的请求类型,或者能提供不同的输出形式。
7.1 LoRA 与多模态:扩展能服务的任务
LoRA 服务允许在共享基础模型的同时,为请求选择相应适配器。它有助于承载不同任务或定制行为,但也带来适配器加载、驻留和批次组织的问题。复用模型底座并不意味着这些附加状态没有成本。LoRA Adapters
多模态则把图片、音频等输入接入推理流程。输入预处理、编码器计算及其中间结果都可能产生新的缓存和调度问题。于是,同样一个“首 token 慢”,在纯文本与多模态服务中,时间可能花在不同位置。vLLM 多模态输入
7.2 结构化输出与工具调用:约束输出怎样被使用
业务系统有时需要模型返回符合 JSON Schema 的结果。结构化输出机制通过相应约束控制生成格式;格式有效仍不代表字段里的事实正确,任务质量需要另外评估。Structured Outputs
工具调用则让模型输出可被应用解释的调用请求,再由应用执行并把结果送回。模型提出调用、服务解析输出和应用实际执行,是不同环节。它们扩展了交互能力,但并不直接解决 Attention 或 KV 的性能瓶颈。vLLM 工具调用
7.3 生命周期与可观测性:让服务能运行,也能解释
加载、睡眠与唤醒、资源释放等能力影响服务怎样启动、驻留和切换。指标、trace、profiler 和质量评测,则帮助我们判断某项优化是否真的生效。vLLM 功能目录
例如,Prefix Cache 开启后,要看实际命中了多少和少算了什么;卸载开启后,要看设备内存、传输量与等待;并行开启后,要看所有 rank 的工作与通信。只记录配置值,很难解释真实行为。
读文档时也要区分三个层次:GQA、MLA、MoE 属于模型结构;Prefix Cache、分块调度和 Graph 属于运行机制;Platform、Worker、Runner、Connector 则是承载这些机制的实现组件。它们之间存在联系,但不适合放在同一张“加速开关清单”里直接比较。
8. 回到文档问答:这些特性怎样一起发挥作用?
现在重新看 A、B、C 三个请求。A 和 B 使用同一份长资料,C 的问题很短。下面是一条用于分析的思路,实际选择仍需当前版本和负载验证。
先用连续组批及时利用已经结束的请求留下的资源。如果长文档 Prefill 干扰了其他请求的生成,可以通过分块处理调整调度节奏。B 与 A 共享的前缀若能命中,就进一步减少重复计算;若命中内容保存在外部,还要比较加载与重算的成本。
如果此时主要问题转为 KV 容量,就检查分页浪费、缓存精度与驻留方式。量化、卸载和稀疏化分别改变表示、放置和访问集合,选择哪一个取决于容量来源及质量要求。若瓶颈在主机启动或中间数据读写,再考虑图执行和融合。
当服务扩展到多卡,则继续问:是更多独立请求需要副本分担,还是模型本身需要切分,或是长上下文需要分担?这决定 DP、TP、PP、EP、CP 等策略的角色。若两个阶段的干扰始终明显,还可以研究 P/D 分离。
这条分析链中,前一个特性会改变后一个特性的条件。例如:
- Prefix Cache 减少 Prefill 计算后,原先用于隐藏搬运的计算窗口可能缩短,需要重新检查卸载流水。
- 推测解码增加每轮验证工作,也改变临时 KV 和批次形状,可能影响 Graph、内存预算与调度。
- 并行布局改变后,每张卡持有的数据和通信量都会变化,单卡下的缓存与性能结论不能直接沿用。
这些是需要实验检验的交互关系,而不是已经测得的收益。两个特性分别有效,也不能据此推断组合后必然更快。
9. 怎样把一项特性写成有证据的技术文章?
特性介绍最容易停在“它能加速、能省内存”。要进一步理解,可以围绕一个固定场景持续追问:原先的瓶颈是什么,开启后少做了哪些工作,又新增了哪些工作?
以 Prefix Cache 为例,一篇后续实验文章可以固定模型和请求,依次观察关闭缓存、开启后的首次请求,以及开启后的重复前缀请求。先核对命中与实际计算量,再检查输出,最后在独立的计时实验中评价延迟。这样读者能够把机制与证据对应起来。
不同方向的首个验证问题可以整理如下:
| 特性方向 | 首先证明什么 | 容易遗漏的代价 |
|---|---|---|
| 连续组批与分块 Prefill | 请求如何逐轮获得计算 | 排队、公平性与尾延迟 |
| Prefix Cache | 相同前缀实际复用了哪些状态 | 查找、缓存占用、淘汰与外部加载 |
| 量化 | 表示改变后容量和质量如何变化 | 转换、scale 与算子支持 |
| KV/权重卸载 | 数据按时到达,缓冲区安全复用 | 搬运、同步与主机内存 |
| 稀疏化 | 选择集合正确,任务质量可接受 | 选择器、索引和数据聚集 |
| Graph 与融合 | 目标路径生效且结果正确 | 捕获、额外内存与形状回退 |
| 推测解码 | 接受/拒绝与状态处理正确 | 提议、验证和临时缓存 |
| 多卡与 P/D 分离 | 切分、传输和合并正确 | 通信、阶段等待与资源不均 |
第一篇留下的真实观察——请求逐轮推进、KV 写入、双请求组批、跨块和正常回收——为这些问题提供了执行基础。本文建立的是特性之间的关系;后续每个专题再将其中一条关系落到固定版本源码和可复现实验。
下一篇具体实验可以从“相同文档的第二个问题,模型到底少算了多少”开始。它既能检验 Prefix Cache 的核心价值,也会把缓存命中、调度预算与 KV 生命周期重新连接起来。
鲲鹏昇腾开发者社区是面向全社会开放的“联接全球计算开发者,聚合华为+生态”的社区,内容涵盖鲲鹏、昇腾资源,帮助开发者快速获取所需的知识、经验、软件、工具、算力,支撑开发者易学、好用、成功,成为核心开发者。
更多推荐


所有评论(0)