PD 分离
预填充/解码(PD)分离将部署拆为独立运行的角色:prefill 计算提示词的 KV 缓存,decode 根据缓存生成 token,前方的 router 将每个请求串联到这两个阶段。KV 缓存通过网络在两者之间传输,而无需重新计算。
两个阶段的资源需求相反。Prefill 受计算能力限制,希望采用较大的张量并行;decode 受内存带宽限制,希望处理大量并发序列。单体部署必须为两者选用同一组参数。分离后,可以根据各自实际任务独立确定规模和调优,并按工作负载调整二者比例:提示词占比较高的工作负载需要更多 prefill,生成较长的工作负载需要更多 decode。
它也带来实际成本:请求在处理过程中需要跨网络传输,需要维持的角色从一个增加为三个,组的可用性取决于其中可用性最低的角色。只有两个阶段的需求确实冲突时,分离才值得采用。
何时有效,何时无效
以下三个问题决定是否适用:
- Prefill 和 decode 是否需要不同参数? 如果一组引擎参数就能让两阶段达到可接受的效果,分离只会增加复杂性,不会增加吞吐量。
- 提示词与生成内容的比例是否远离 1:1? 收益来自两侧独立调整规模,只有负载明显不同时才值得。
- KV 传输是否足够快? 每次请求都需要通过网络传输提示词的 KV 缓存。在低速或共享链路上,传输时间会抵消节省的 prefill 时间。
单 GPU,或提示词和生成内容都较短的工作负载,通常在不分离时更快。
部署分离模型
- 按常规方式部署模型,然后展开
Disaggregation部分并启用。 - 选择 PD 模式。一个模式就是一套完整方案,包括引擎、KV 连接器和 router,因此连接器、握手变量和端口都由这一选择确定,不会单独出现在表单中。可用模式取决于后端;针对其他引擎的模式会显示为禁用,并说明原因。
- 设置每个角色的副本数。它决定比例:2 个 prefill 和 1 个 decode 构成 2P1D 组。
- 在两侧需求不同时,覆盖各角色设置。保持继承的字段使用部署级值,因此只需填写不同之处。
- 保存。
Router 由平台管理:镜像、命令、对端地址和健康检查路径均来自模式方案,表单无需填写这些信息。需要填写的是方案无法预知的内容,即 router 自身的参数和环境;平台推导的连接参数也会预填到同一列表,便于查看实际生成结果。未修改的行会在保存时再次移除,由 GPUStack Server 继续生成。
Router 在结构上只有一个副本,因此无需设置数量;第二个副本会拆分前缀缓存,并使组具有两个地址。
如果需要自己的 router,请选择 custom 模式,并在部署而不是 router 角色上设置镜像和运行命令,角色会继承这两项。因此,一个镜像同时包含 router 和引擎,并按角色选择执行内容;工作量与在两处指定它相同,但不再需要维护第二处同步。
组的启动方式
启动顺序经过设计,了解它有助于理解组启动时显示的状态:
- Prefill 和 decode 实例在同一事务中同时创建。支持 gang admission 时,它们也会一同准入,因为只启动一半的组无法提供服务。
- 两个角色各自都有运行中的成员后,才创建 router。它最后创建,是因为其命令行包含对端地址,而对端启动前这些地址尚不存在。
- Router 启动后注册组端点。只注册 router;prefill 和 decode 成员虽在各自端口提供 OpenAI 形式的 API,但都无法独立完成整个请求。
因此,组在开始服务之前会短暂报告 Partially ready,这是正常流程,而非故障。
理解组状态
| 显示内容 | 含义 |
|---|---|
Pending | 尚无运行中的成员。 |
Partially ready | 已有成员启动,但组仍无法服务:某个角色没有就绪成员,或 router 的端点尚未注册。 |
Running | 每个角色至少有一个就绪成员,且端点已注册。 |
Ratio 2:1 (currently 1:1) | 组正在服务,但尚未达到请求的比例。显示在状态旁边,不替代状态。 |
时钟图标 / Config changed | 运行中的成员早于当前显示的配置;重启之前,角色无法扩容。参见应用配置更改。 |
未达到请求比例属于降级,而不是独立状态:请求 3P1D、实际运行 2P1D 的组仍可服务,只是吞吐量达不到规划值。
应用配置更改
编辑分离部署不会使其重启。更改会被保存,组标记为正在运行旧配置;重启前不会发生变动。
这是有意为之。逐个重启成员会在一段时间内将使用新配置的 prefill 与使用旧配置的 decode 配对,而引擎会接受这种配对。例如,上下文长度不匹配时,握手能完成,KV 能成功传输,短提示词也能正确回答;只有提示词超过 decode 侧窗口时才会失败,而此时 prefill 计算成本已经付出。错误答案比重启更糟,因此重新启动任何成员之前,会先停止整个组。
在部署上使用 Restart 应用更改。它会停止整个组,并让其按当前配置重新组成。不提供仅重启部分角色的操作,因为这正会产生上述新旧混配。
如果没有待应用的配置更改,伸缩会修改各角色的实例数,而无需重启整个组。Router 行为取决于 pd-modes.yaml 中的 router.membership_update:
| 策略 | 内置模式 | 伸缩或成员恢复时的行为 |
|---|---|---|
dynamic | vLLM NIXL、SGLang Mooncake/NIXL、vLLM Ascend Mooncake | GPUStack 通过成员管理 API 更新 router。 |
restart | vLLM Mooncake | 新增 GPU 成员就绪后,GPUStack 替换 router,刷新启动时的对端和 bootstrap 元数据。移除或重启成员时,只要两侧均有可用对端,就立即触发刷新。保留的 GPU 实例继续运行,但服务会短暂不可用,正在处理的请求可能失败。 |
manual | Custom | 操作人员更新 router 对端和 bootstrap 元数据,并在需要时重启 router。 |
用户自定义的 router 运行命令采用手动成员管理,即使所选方案支持自动更新。部署表单和副本编辑器显示来自目录的策略。删除实例时,如果恢复需要替换 router 或由操作人员处理,也会给出警告。
静态 router 快照包含实例身份、重启代次、HTTP 地址和具名端口。因此,即使替换或重启发生在相同地址,仍会刷新 Mooncake bootstrap 元数据。没有快照的现有 router,会在对端就绪时替换一次。
存在已保存但尚未重启应用的更改时,角色不能扩容。 新成员将根据已编辑配置创建,与未采用该配置的旧成员并行运行,形成上述新旧混配。伸缩设置会在重启时与配置更改一同生效。
缩容仍立即生效,无论是否存在待应用更改,因为减少成员不会产生不匹配的配对。
结合共享 KV 缓存使用
分离部署与共享 KV 缓存互补,而非相互替代。GPUStack 会自动组合两者:按常规方式接入缓存服务,两个连接器就会合并为引擎接受的一份配置。
每个接入缓存的 vLLM 角色都会生成 MultiConnector[PD, Store]。包装连接器在 prefill 上使用 kv_producer,在 decode 上使用 kv_consumer。各子连接器保留其传输或存储所需角色。
| 模式 | 无缓存 | 进程内缓存 | 缓存服务 |
|---|---|---|---|
vllm-nixl(NVIDIA 默认) | NixlConnector | NixlConnector + LMCacheConnectorV1 | 仅 prefill:NixlConnector + LMCache;仅支持 LMCache |
vllm-mooncake(NVIDIA) | MooncakeConnector | MooncakeConnector + LMCacheConnectorV1 | MooncakeConnector + MooncakeStoreConnector;仅支持 Mooncake |
vllm-ascend-mooncake | 根据模型选择的 Mooncake PD 连接器 | PD 连接器 + SimpleCPUOffloadConnector | PD 连接器 + AscendStoreConnector;仅支持 Mooncake |
进程内 LMCache 子连接器在两个角色上都使用 kv_both。对于 NIXL,共享 LMCache 仅支持 prefill。即使 MultiConnector 将请求的加载分配给 NIXL,其 MP 连接器仍可能加载缓存前缀;第二次异步完成可能使 decode 崩溃(vLLM #53049)。NVIDIA Mooncake Store 在 prefill 上使用 kv_both,在 decode 上使用 kv_consumer。昇腾 Store 在 prefill 上使用 kv_producer,在 decode 上使用 kv_consumer。这些配置遵循 vLLM 0.29 Mooncake Store 和 vLLM-Ascend 0.23 KV pool。
昇腾为标准 KV 布局选择 MooncakeConnectorV1,包括 Qwen3、GLM-5 和 DeepSeek V3.1;为 DeepSeek V4、V4 Flash 和 V4 Pro 等混合布局选择 MooncakeHybridConnector。选择依据是预训练配置,包括压缩注意力和循环层,而非部署名称。两个角色使用同一份保存的选择。如果布局元数据不可用,混合连接器负责回退处理标准布局。GPUStack 会针对部署的模型输入保留此选择,避免某成员启动后元数据才变得可用,导致其他成员使用不同连接器。
NIXL 缓存组合使用 vLLM 0.26 起提供的真实块契约。PD 方案声明支持的缓存提供商和连接器设置,不定义或检查缓存服务版本或客户端包要求。缓存提供商负责自身服务与客户端的兼容性。Runner 依赖仍为 0.5.4。两种 NVIDIA 模式都以 vLLM 0.27.1 或更高版本为内置 runner 基线:这些 CUDA runner 提供从引擎镜像运行的 vllm-router 0.1.15。昇腾模式基于相同原因采用 CANN 0.23 基线。这些最低版本描述 GPUStack 内置镜像,而非每个连接器首次出现在上游的版本。
NVIDIA Mooncake 配合共享 Store 时,还有额外的 0.29 基线。GPUStack 0.29 runner 包含被拒绝加载的修复:Store 子连接器必须跳过已分配给 PD 子连接器的加载。MultiConnector 向未选中的 Store 传递零个外部 token,修复会在发出待处理加载前检查 can_load。这也适用于 decode 侧缓存命中。原版 vLLM 0.29 需要回移此修复,上游 0.30 已包含它。参见 runner 修复。
请为每个需要缓存的角色单独接入服务。下拉菜单会按模式、提供商和角色筛选服务,API 也会强制执行这些兼容规则。PD 引擎版本范围属于建议:选择超出范围的内置引擎版本时,会针对模式或已启用缓存显示警告,但仍允许部署和注入缓存。自定义版本和显式指定的镜像不会显示此类警告。 启动失败通过实例状态和日志报告。
Auto 当前根据后端、加速器运行时和平台选择 runner,不会按 PD 模式或缓存组合的版本范围筛选。SGLang 通过参数和配置文件接入 LMCache 或 MeshFusion,而不使用 vLLM MultiConnector。
NVIDIA Mooncake 要求推理镜像中包含其 transfer-engine 运行时和 RDMA 支持。GPUStack runner 镜像还暴露 Mooncake 传输/过期指标,传输计数器在 prefill 上读取。GPUStack 分配唯一的 prefill bootstrap 端口,并传递给 router。使用 vllm-router 0.1.15 时,成员变化会重启 router 以刷新 bootstrap 元数据,可能短暂中断流量。
网络要求
Kubernetes:Pod 需要集群提供的能力
组成员同时需要主机网络、hostPort、主机 IPC 和设备挂载,超出了 baseline Pod Security Admission 级别允许的范围。GPUStack 会据此为自己管理的命名空间设置标签。其对自备集群的含义,以及不涵盖的内容,参见命名空间与 Pod Security Admission。
KV 传输接口
每个请求都要通过网络传输 KV 缓存,所用接口至关重要。未干预时,传输层可能选择容器网桥(docker0、br-*、flannel.1),并公布对端无法访问的地址,最终在握手时表现为连接器错误,而非配置错误。
GPUStack 按 Worker 推导接口:
- 只有一个可路由接口——直接使用,无需配置。
- 有多个可路由接口——GPUStack 不会猜测。请在该 Worker 上设置
--kv-transfer-ifname(或 Worker 配置键kv_transfer_ifname,或GPUSTACK_KV_TRANSFER_IFNAME),指定承载 KV 流量的接口。Worker 日志会列出发现的候选接口。
对于具有多个网络互连的机器,请显式设置。常见情况是一块管理网卡加一块 RDMA 网卡,管理网卡几乎从来都不是正确选择。
端口
每个角色需要自己的 HTTP 端口,以及连接器要求的端口。GPUStack 从 Worker 的服务端口范围中按连续端口段分配,确保同一主机上的两个成员不会冲突。
默认范围包含 64 个端口,对大多数部署足够,但并非所有部署都适用:使用每 rank 一个端口的连接器时,张量并行大小为 8 的成员会占用九个端口。如果每台主机运行多个此类成员,请扩大 --service-port-range。
GPUStack 无法管理的三类端口
某些连接器会绑定由引擎在运行时选择的端口,并未在 GPUStack 能读取的任何位置声明:
- SGLang 的 rank 端口,来自主机临时端口范围;
- Mooncake 每 rank 的 transfer-engine 握手端口;
- Mooncake 的 segment 端口。
对于这些端口,配置文件中写入的值不是实际绑定端口,因此 GPUStack 无法预留它们,也无法将其声明为主机端口。观测到的范围约为 15000–17000 和 20000–20800,因此默认服务范围(40000–40063)不会与它们相遇;避免冲突靠的是范围分离,而非预留。
因此,不要将 --service-port-range 移入这些范围。 实测中,当连接器端口段从 20001 开始时,成员自身的第二个 rank 尝试绑定 20003,却发现该端口已被引擎刚刚自行选择的 transfer-engine 端口占用。同样的部署将端口段放在默认范围后,第一次就成功启动。
如果成员反复重启且始终无法达到 Running,请检查引擎日志中是否有 Address already in use。
端口去重也仅覆盖一个 Worker 进程。同一主机上的第二个 GPUStack Worker,或手动启动的容器,都不在其管理范围内。
约束
可能导致组返回错误答案的配置,会在保存时被拒绝,而非等到运行时:
- Prefill 和 decode 必须声明相同的上下文长度。 没有引擎会检查这一点,不匹配在长提示词到来之前不可见。
- Decode 的张量并行大小必须至少等于 prefill。 引擎确实会对此进行断言,但报告的是 decode 内部错误,而非配置问题。
- 两侧的数据类型、KV 缓存数据类型、块大小和 KV 缓存布局必须一致。
- 角色引擎必须是所选模式能够配置的引擎。 跨角色混用引擎需要
custom模式,并由你提供连接参数。 - 使用
custom模式的分离部署不能同时使用扩展 KV 缓存。 其他模式都会自动组合两者(参见结合共享 KV 缓存使用);custom完全不注入连接器配置,因此没有可以组合缓存的配置,需要自行编写组合配置。 - 定时伸缩与分离部署不能组合使用。
对于 vllm-nixl,兼容性检查包括角色专用环境变量。VLLM_KV_CACHE_LAYOUT 接受等价别名(NHD/LBNHC 和 HND/LBHNC)。对于 MLA 模型,VLLM_MLA_DISABLE 必须产生兼容的潜在 KV 表示。在支持的运行时中,NIXL 循环状态传输要求 VLLM_SSM_CONV_STATE_LAYOUT=DS。对于分块局部注意力,会结合 HMA 参数检查 VLLM_ALLOW_CHUNKED_LOCAL_ATTN_WITH_HYBRID_KV_CACHE。显式选择的注意力后端也会比较。
角色未设置环境时,继承部署环境;显式字典会替换它,空字典也不例外。依赖模型的检查使用源配置和角色覆盖值。未知配置、运行时版本和传输布局通过 pairing_unverified 报告。角色之间的网络地址、端口、设备名称和性能设置可以不同。实验性的布局置换仍受引擎传输方向和 HMA 限制约束。
限制
- Router 以单副本运行。
customrouter 完全按所写配置部署。 内置模式会将上游 router 的熔断器设置为默认阈值,即十次失败请求;自定义 router 保留自身附带的默认值。- vLLM 模式下,返回错误的 prefill 成员不会从轮转中移除。 上游 router 只将传输错误视为 prefill 失败,因此 prefill 返回 HTTP 500 时只会记录日志,请求仍继续发往 decode 并返回 200。实测使用模拟 prefill,让十二次请求全部失败,均未触发熔断。SGLang 模式则会把此类错误计入熔断器。无论哪种模式,组面板中的 PD Effectiveness 指标才是发现某成员已停止贡献的依据;router 自身的请求计数器无法反映。
- 组空闲期间死亡的成员,要到下一次请求时才会被发现。 检测沿请求路径执行,没有流量就没有检测机会;router 后台扫描每分钟运行一次。空闲期后的首批请求会重试,然后绕过死亡成员路由。
- 组内所有成员必须使用相同 GPU 类型;混用卡型的组无法原子准入。
- 角色仅限 prefill、decode 和 router。
- 感知前缀的路由用于选择 prefill,不用于选择 decode。 Prefill 才是共享前缀能够带来收益的位置,因此内置模式按近似前缀匹配路由;decode 在整个生成过程中持有请求,因此采用轮询保持负载均衡。
vllm-ascend-mooncake模式两侧都保留 router 自身的默认值。