Prhub

#38390 [Model Runner v2] E/P/D disaggregation support

原始 PR 作者 yewentao256 合并时间 2026-08-04 23:52 文件变更 7 提交数 23 评论 23 代码增减 +145 / -8

执行摘要

MRv2 新增 E/P/D 分离部署的 EC 传输支持,PR 最终关闭未合并

PR body 声明目标是 "E/P/D disaggregation support for MRv2",并给出 Qwen2.5-VL-3B-Instruct 在 GPU_E=1、GPU_PD=2 下的单图验证(TTFT 约 971 ms,总吞吐约 777 tok/s)。vllm/config/vllm.py_get_v2_model_runner_unsupported_features() 原本把 EC transfer 列为 MRv2 不支持并注释指向本 PR,本 PR 正是移除该限制的落地尝试。WoosukKwon 则认为 "this feature is not in a usable state atm... hold this until we have a better design and implementation",作者在多次 merge 冲突后选择关闭,并说明 "we don't expect to support EPD for v2 currently";随后 omerpaz95 在评论区为 EC connector 争取保留价值,指向 issue #47999。

值得精读 ec_connector.py 的 no-op 抽象与生命周期管理(绑定 metadata、load/save、finally 收敛),这是理解 MRv2 如何扩展外部传输的模板;njhill 的评审展现了'最小化 model_runner 侵入'的维护哲学。但不要直接基于此实现复用:竞态处理、测试覆盖、mixin 与 connector 的职责划分都未定稿,建议跟踪 issue #47999 的后续实现。

讨论亮点

核心争议是 WoosukKwon 的 hold 决定:他认为功能不可用,要求更好的设计与实现后再推进,作者追问定义未果后关闭 PR。njhill 主导了设计收敛:要求最小化 model_runner 改动、把配置判断收口为 VllmConfig property、用独立的 ec_connector.py 替代 mixin,均被作者采纳。gemini 评审提出 consumer 侧 start_load_cachesget_mm_embeddings 之间的竞态问题,作者以 "Same logic with v1. Sync logic" 回应,但疑虑未闭环。此外还有命名分歧(maybe_get_output vs get_output)与冗余 assert/mixin 清理。

实现拆解

  1. 配置层能力上提vllm/config/vllm.py):新增 VllmConfig.is_ec_producer_onlyis_encoder_only 两个只读属性,统一收口角色判断;同时从 _get_v2_model_runner_unsupported_features() 删除 "EC transfer" 条目,允许 MRv2 启动时启用 EC 传输。
  2. 连接器抽象(新增 vllm/v1/worker/gpu/ec_connector.py):ECConnector 基类提供默认 no-op 的 maybe_get_output,使不启用 EC 时 execute_model 零改动走原路径(与 kv_connectorNO_OP_KV_CONNECTOR 模式同构);ActiveECConnector 在上下文管理器内完成绑定 SchedulerOutput.ec_connector_metadata、consumer 侧 start_load_caches、producer 侧增量 save_cachesfinallyget_finishedclear_connector_metadataget_ec_connector() 工厂按环境条件返回 no-op 或 active 实例。
  3. 运行器集成vllm/v1/worker/gpu/model_runner.py):__init__ 中创建 self.ec_connectorexecute_modelwith self.ec_connector.maybe_get_output(scheduler_output) 包裹 get_mm_embeddings 调用,encoder-only 时构造空输出并附带 ec_connector_output;同时为 is_encoder_only 增加多条捷径:get_kv_cache_spec 返回空 dict、_dummy_run 直接返回空张量、profile_run 提前同步并重置 encoder cache、capture_model 直接返回 0(跳过 CUDA graph 捕获)。
  4. 边界修补block_table.pywarmup.pymm/encoder_runner.py):BlockTablesapply_staged_writesgather_block_tablescompute_slot_mappings 三个方法支持 num_kv_cache_groups == 0warmup_kernels 在 encoder-only 时整体跳过;prepare_mm_inputs 跳过已在 encoder_outputs 中命中的特征 hash,避免 consumer 侧重复编码。
  5. 测试与配套:无任何直接对应的测试文件;示例脚本 disagg_1e1pd_example.sh 把服务等待超时从 12000 秒降到 300 秒,是本次唯一的配套调整。
文件 模块 状态 重要度
vllm/v1/worker/gpu/ec_connector.py 编码器传输 added 8.52
vllm/config/vllm.py 配置校验 modified 6.79
vllm/v1/worker/gpu/model_runner.py 模型运行器 modified 6.68
vllm/v1/worker/gpu/block_table.py 块表管理 modified 4.79
vllm/v1/worker/gpu/warmup.py 预热逻辑 modified 4.19
vllm/v1/worker/gpu/mm/encoder_runner.py 编码器运行 modified 4.09
examples/disaggregated/disaggregated_encoder/disagg_1e1pd_example.sh 示例脚本 modified 2.31

关键符号

VllmConfig.is_ec_producer_only VllmConfig.is_encoder_only ECConnector.maybe_get_output ActiveECConnector.__init__ ActiveECConnector.maybe_get_output get_ec_connector GPUModelRunner.execute_model EncoderRunner.prepare_mm_inputs BlockTables.compute_slot_mappings warmup_kernels

分析完成后,这里会展示 LLM 生成的相对完整源码片段和详细注释。

评论区精华

功能可用性与 MRv2 路线决策 设计

WoosukKwon 表示 feature 不可用,要求 hold 到有更好的设计与实现;作者追问 "usable state" 的定义未果,后在多次 merge 冲突下主动关闭 PR,并说明 "we don't expect to support EPD for v2 currently"。

结论:PR 关闭,社区诉求转移到 issue #47999 继续追踪 EC connector 支持。 · closed

consumer 侧 cache 加载竞态 正确性

gemini-code-assist[bot] 指出 start_load_caches 是异步操作,get_mm_embeddings 前没有显式等待,可能导致 consumer 重复编码甚至因缺少编码器权重而失败。作者回复 "Same logic with v1. Sync logic",主张与 MRv1 行为一致。

结论:未彻底闭环:作者以与 v1 一致为由回应,但评审建议的阻塞等待机制未在最终代码中体现。 · unresolved

producer 重复 prepare_mm_inputs 性能

gemini-code-assist[bot] 指出 producer 侧 prepare_mm_inputs 被调用两次(一次算 mm_hashes_to_save,一次在 get_mm_embeddings 内部),建议重构为只调用一次并传递结果。

结论:无明显修复证据,PR 关闭前该建议未落地。 · unresolved

配置属性上提到 VllmConfig 设计

njhill 建议把 is_ec_producer_only 与 is_encoder_only 定义为 VllmConfig 的 @property,简化 model_runner 代码。

结论:已采纳,作者将其收口到 vllm/config/vllm.py。 · 已解决

MRv2 自持 EC 连接器替代 mixin 设计

njhill 建议在 ec_connector.py 中提供基于配置的上下文管理器工厂,默认 no-op,从而避免在 model_runner 中引入 mixin,并尽可能减少 model_runner.py 的侵入。

结论:已采纳,新增 ECConnector/ActiveECConnector 架构并移除对 mixin 的依赖。 · 已解决

命名与冗余代码清理 style

njhill 建议把 maybe_get_output 简化为 get_output,作者辩护 maybe_get_output 更能表达可能返回 None 的语义;另外 njhill 质疑 assert self.encoder_cache is not None 的用途与 mixin 文件是否还需改动,作者均以 "Nice catch, fixed" 清理。

结论:命名保留 maybe_get_output,冗余 assert 与 mixin 改动被清理。 · 已解决

风险与影响

  1. 竞态隐患:consumer 侧 start_load_caches 后没有显式等待屏障,若 cache 未就绪,get_mm_embeddings 会尝试重复编码,而 consumer 节点可能没有编码器权重,可能直接失败。作者声称与 MRv1 逻辑一致,但缺少测试证明。
  2. 缺少测试覆盖:新增的 num_kv_cache_groups == 0 路径、encoder-only 的 _dummy_run/profile_run/capture_model 捷径均无单测或集成测试,回归风险高。
  3. 核心路径改动execute_model 是每 step 必经路径,finally 中的 get_finished/clear_connector_metadata 的幂等性未验证,任何连接器异常都会影响所有推理请求。
  4. 设计未定稿:PR 关闭意味着实现未经真实环境全量验证,ECConnector 的 API 形态(命名、上下文语义、与调度器 metadata 的契约)在 issue #47999 的后续实现中很可能调整。

对用户/系统:若落地,多模态 LLM 可用 1 个 encoder 节点加多个 prefill/decode 节点分离部署,encoder 节点 GPU 利用率可独立伸缩,Qwen2.5-VL 单图验证 TTFT 约 971 ms、总吞吐约 777 tok/s。对团队/社区:PR 被 hold 并关闭后,社区在 issue #47999 继续施压,说明 EC connector 已被认为是有价值的方向,MRv2 路线需要成熟的竞态处理与运行器/调度器契约设计。影响范围覆盖 vllm/v1 下的运行器、块表、预热、编码器缓存 4 个模块与配置层,属于 MRv2 核心路径。

核心路径变更 缺少测试覆盖 存在未闭环的竞态疑虑 未合并,设计未定稿 边界条件分支(0 KV 组)

关联 Issue

未识别关联 Issue

当前没有检测到明确关联的 Issue 链接,后续同步到相关引用后会出现在这里。

完整报告

参与讨论