已关闭
[RFC]: 推理大EP场景下支持DP域缩容运行 #575
yilunh创建于 9月8日关闭于 9月17日
9月8日 添加了label:rfc
wangyang
9月9日 评论:
9月9日 评论:
👋 您好,感谢向 mindie-motor 提交 Issue!
🎉 我们已收到您的反馈,感谢你对开源社区的支持!
📅 处理时效 维护团队将在工作日 24 小时内查看并回复您的问题。
🔍 自助排查(推荐优先查看) 在等待回复期间,您可以先查阅仓库README以及历史 Issue 中相似问题的解决方案,多数问题可快速解决。
💡 为了更快定位问题,请您确保 Issue 包含:
清晰的问题描述
可复现的操作步骤
相关日志、截图或环境信息
我们会尽快跟进,感谢您的理解与配合!


9月17日 关闭了 issue
9月17日 issue状态由 TODO 改变为 CLOSED
9月17日 添加了label:resolved
摘要
部分 DP rank 失效时,Motor 不再默认整实例隔离 / 重拉起 / Scale P2D。在引擎 FT、external-lb、EPLB 冗余专家等条件满足时,Controller 只移除故障原始 DP rank,存活 rank 重建通信域后继续服务,Coordinator 只消费投影后的服务视图。
1. 动机
当前容错主路径是:故障 → 隔离实例 → EngineRelaunch / Pod 重调度 / Scale P2D。对「单卡或单 DP rank 死、其余 rank 仍可集体恢复」的场景过重,恢复时间长,健康 Pod 被一起扰动。
DP 缩容要解决:
2. 架构结论
FaultManagerScaleDownContext、选择并启动策略、失败后升级DpScaleDownStrategyrequest_id,不维护真实专家拓扑FtRuntimeStoregeneration、dead_committed、门禁/引擎快照Instance.endpointsServingOverlayEngineFtService图中的
FaultManager → DpScaleDownStrategy不是绕过接口的组件调用,也不是另一条 apply 通道,只表示策略中心完成优先级判断后选择并启动该策略。全 active DP 均为UNHEALTHY且无DEAD只是调用is_applicable()的必要非充分条件;当前占位返回不适用。快恢内部协议可独立演进,但失败只能进入整实例重配置。FtGate不是独立服务或独立组件,而是DpScaleDownStrategy.execute()内部的第一阶段;它被画进事务边界,是为了表达“门禁失败直接回原 fallback”。职责边界:
motor/controller/fault_tolerance/dp_scale_down.py:领域模型(FtPhase、ScaleDownContext、FaultNormalizer、TopologyResolver、FtRuntimeStore)motor/controller/fault_tolerance/strategy/dp_scale_down.py:有副作用的单轮执行器motor/node_manager/core/engine_ft_service.py+ 四条 NM API:本机引擎适配3. 状态机与服务视图
3.1 阶段
DISABLED→NORMAL→DECIDING→WAITING_ENGINE_FAULT→SCALING_DOWN→POST_SCALE_CLEANUP→SCALED_DOWN_RUNNING,失败进入RECONFIGURING,再走冻结的原 fallback。事务窗口(
DECIDING/WAITING_ENGINE_FAULT/SCALING_DOWN/POST_SCALE_CLEANUP)抑制旧的 partial-loss 整实例 shutdown。SCALED_DOWN_RUNNING心跳只忽略dead_committed;新 stale survivor 仍进正常故障决策。RECONFIGURING不保护。3.2 权威拓扑 vs 服务视图
Instance.endpoints启动后不可变。存活集合只由 runtime 表达:提交前
pending不并入dead_committed。连续缩容仍用原始编号,例如{0,1,2,3}→{0,2,3}→{0,2},禁止把 2 改名为 1。ServingOverlay:DEL当前已发布视图(连续轮已不含既往dead_committed,避免 identity 校验失败)ADD只含存活 endpoint;EventPusher 的 READY/RESUME/周期 SET 走同一投影,防止全量刷新把死 rank 加回can_serve仅在NORMAL/SCALED_DOWN_RUNNING且serving_published=true4. 故障归一化
4.1 软件
对齐 vLLM:
healthy/unhealthy/dead。executor 已死上报DEAD→ 权威待删 rank;peer 感知故障上报UNHEALTHY→ 仅这些 survivor 可收 apply。UNHEALTHY本身不是待删集合。Mask 只观测,不反推 rank。上游 vLLM external-LB 模式把
engine_ranks_managed初始化为全局dp_rank,并以该值生成engines[].id。因此 Motor 的请求目标Endpoint.id和响应engines[].id必须严格相等:缺少目标 ID、出现重复/未知 ID,或仅返回一条但 ID 不匹配,都按协议失败进入原 fallback,不能把本地 rank 当全局 rank 做隐式兼容。EngineFtManager按其管理的 Endpoint ID 选择并校验对应条目,同时用该全局 ID 去重上报;轮询上报路径和事务查询路径必须遵守同一身份契约。
4.2 硬件
node + 该实例在节点上的全部 pod_ip + npu_name联合匹配Endpoint.device_infos.device_id,endpoint id 即原始 DP rank。同一故障在各 Pod 的映射先取并集;空卡名映射该节点上属于该实例的全部 DP;未知卡名格式不猜测,走原策略。多卡同fault_code用(fault_code, npu_name[, node_name])区分。可映射的 L4–L6 推迟旧
separate_instance,由缩容策略同步摘流,避免 EventPusher 异步 DEL 与成功 ADD 竞态。形成匹配 U+D 后缩容失败直接整实例重配置;无法完整映射或实例非 ACTIVE 时不进入相关性等待,继续原 ScaleP2D / 重配置路径。4.3 优先级(有限、不循环)
快恢执行中不可被缩容抢占,且快恢失败后直接整实例重配置,不再尝试 DP 缩容。全 active DP 均为
UNHEALTHY且不存在DEAD,只是调用快恢is_applicable()的结构性前置条件,不等价于一定可快恢,当前仅占位实现;设备类型、错误码等充分条件由快恢实现自行判断。当前is_applicable()恒为 false,因此只会继续等待DEAD,或在软件状态收集超时后进入整实例重配置。DP 缩容只接受完整的
UNHEALTHY + DEAD组合:DEAD是权威待删除 rank,UNHEALTHY是可接收 apply的 survivor;两类集合并集必须覆盖当前全部 active DP。硬件映射结果只用于验证 FT 框架是否感知到了同一故障,不直接进入removed_dp_ranks。连续缩容没有人工轮数上限,天然受“至少存在一个新 DEAD 且至少保留一个 survivor”的拓扑约束。整实例重配置复用terminate_instance_for_recovery(),不走PodLifecycle。Relaunch 成功保留dead_committed;整实例重建成功清空缩容历史。4.4 Controller 故障感知入口与相关性计时
Controller 不直接轮询 vLLM business port。当前故障信息通过以下入口汇聚到
FaultManager:EngineFtManager周期查询当前角色引擎的GET /v1/fault_tolerance/status,将新的UNHEALTHY/DEAD状态通过POST /controller/report_software_fault上报。ProcessSupervisor发现 EngineCore / executor 子进程退出后,独立于 EngineFtManager 的状态响应上报ENGINE_DEAD + endpoint_id。因此,即使 FT status 接口已不可访问,只要本地进程死亡监控仍工作,Controller 仍可取得软件 DEAD 候选。ResourceMonitor消费 MindCluster / Ascend Device Plugin 暴露的 Node 与 ConfigMap 故障信息,更新卡级或节点级硬件故障。这里的 ResourceMonitor 是 Motor 的监听适配组件,不是 MindCluster 内部组件。removed_dp_ranks,不能单独启动 DP 缩容。只有后续软件 DEAD 或可映射硬件故障形成权威 rank,才进入缩容事务。故障上报写入 FaultManager 后会刷新实例故障级别并
notify_all()唤醒策略中心;策略中心仍以strategy_center_check_interval(默认 10 秒)做兜底扫描。FaultManager 分别记录硬件首达时间、软件状态收集起点,以及每个 rank 首次出现UNHEALTHY/DEAD的时间;这些是进程内相关性证据,不写入etcd。软硬件处理规则如下:5 秒
hardware_ft_correlation_window_sec是“硬件先到后等待 FT 框架感知”的 Controller 决策窗口,不是 apply 重试,也不等价于 DpScaleDownStrategy 的恢复 deadline。只有 FaultManager 已取得完整 U+D快照并选择缩容后,策略才 guard NodeManager 并进入dp_scale_down_config.execution_deadline_sec:它会重新查询当前状态,吸收尚未进入首份上下文、但已在引擎侧变为DEAD的 rank,将其从 survivor 移入本轮累计待删集合;剩余 survivor 必须全部为可 apply 的UNHEALTHY。若 survivor 仍HEALTHY/recovering则只轮询,若 survivor 为空、出现失败状态、查询异常或 deadline 到期则本轮失败并整实例重配置。EngineFtManager只在引擎首次 readiness 成功后启动,默认每 1 秒轮询一次。进入运行期后,status 接口首次超时或连接失败即作为该 Endpoint 的DEAD证据上报。5. 单轮事务
dead_committed子集且阶段已是SCALED_DOWN_RUNNING→ 成功返回;否则失败。enable_dp_scale_down、引擎 FT、ft_capability.scale_down_supported、external-lb、关闭引擎 auto recovery、enable_eplb且num_redundant_experts > 0、至少一名 survivor。Motor 不把 fused MC2 作为门禁,也不把冗余专家数当成可删 DP 预算;算子与实际专家槽位是否支持本轮缩容由引擎在 apply 后最终判断。POST /node-manager/fault-tolerance/guard,携带本轮request_id与内部lease_sec。NM 记录当前事务主;同一 id 可续租,另一 id 在冻结未到期时拒绝。lease 由两段 recovery 窗口 + HTTP/poll 余量算出,不是客户开关。dead,将其加入本轮累计待删集合、重建 NodeManager 分组并继续检查;剩余 survivor 必须全部为unhealthy且无ft_state才可下发。healthy或unhealthy+recovering只轮询不下发;failed、缺失、引擎id与Endpoint.id不匹配、全部 rank 均 DEAD 或 deadline 到期则失败。此阶段可能进入WAITING_ENGINE_FAULT,但不重复 apply。request_id,body 含累计removed_dp_ranks与dp_master_ip=min(survivor).ip;dp_store_port由 NM 本地补齐。HTTP 202 只表示接收。任一 NM 未接受即本轮失败。healthy且ft_state空 →finalize(commit)(按request_id校验,否则视为 stale)→ Daemon 把 retired ranks 写入心跳 / EngineFtManager / 进程死亡过滤 → 写入累计dead_committed→ 若开了扩容开关则PodLifecycle.stop_recyclable_pods→SCALED_DOWN_RUNNING→ServingOverlay.publish。finally里对未 commit 的 NM 做一次finalize(abort)(过期/错 id 会被 NM 拒绝,避免解冻新事务),再交给冻结 fallback。提交后的失败语义:
finalize(commit)表示引擎缩容结果已经提交,后续 Controller 状态持久化、Pod stop 或 Coordinator 发布失败均不回滚引擎,也不重放 apply。SCALED_DOWN_RUNNING、serving_published=false和last_error,此时can_serve=false;后续由 EventPusher 的 SET 使用ServingOverlay收敛,发布成功后再恢复可服务。stop_recyclable_pods是 best-effort 的一次性通知。stop 失败不改变缩容成功结论、不由 Motor 重试,Pod 的最终回收和补充由 MindCluster 负责。策略抢占:更高优先级故障替换进行中的策略时,
FaultManager先stop(),并同时等待逻辑is_finished()与执行器Future.done(),避免旧事务finallyabort 与新事务 guard 重叠。Controller 重启:etcd 只持久
phase/generation/original_dp_ranks/dead_committed/fallback_strategy。中断在DECIDING|WAITING_ENGINE_FAULT|SCALING_DOWN的事务恢复为RECONFIGURING并走原 fallback,重启后不重放 apply。已知一致性窗口:当前顺序是 NodeManager
finalize(commit)在前、Controller 更新并持久化dead_committed在后。若 Controller 恰好在两者之间退出,NodeManager 内存中可能已经登记 retired rank,而 Controller 尚未提交对应 runtime。恢复时必须按中断事务处理:进入原 fallback,禁止凭旧 runtime 重复 apply。该窗口不通过持久化全部引擎状态消除,需在联调中注入 Controller 重启验证。6. NodeManager 契约
内部派生开关:
enable_dp_scale_down_proxy = enable_fault_tolerance && enable_dp_scale_down。它由 Controller 统一配置下发到 NodeManager,不是客户需要额外填写的第三个开关;关闭则四条路由 404,行为与现网一致。
/node-manager/fault-tolerance/statusendpoint_ids→ 引擎 FT status;engines[].id必须等于请求的全局 DP rank/node-manager/fault-tolerance/applyinstruction=scale_down;注入dp_store_port/node-manager/fault-tolerance/guardrequest_id+lease_sec∈(0,3600];同 id 续租,异 id 且仍冻结则 502/node-manager/fault-tolerance/finalizerequest_id+commit+ retired ids;id 不匹配视为 stale,不解冻新事务心跳:缩容关闭时仍要求完整 endpoint 集合;开启后用「期望集合精确相等」,允许且仅当本地 rank 全部已 commit 时上报空集合。commit 后的 retired id 同时从心跳、FaultReporter 和进程死亡上报中过滤。
注册与实例组装:NodeManager 注册信息携带引擎 FT capability,包括是否支持
scale_down以及门禁所需的静态能力。Controller 组装实例时要求同一实例各 Pod 的 FT capability 一致;信息缺失或不一致时失败封闭,不进入缩容编排。7. 配置
均在
fault_tolerance_config,对 Prefill/Decode 一视同仁。Controller 不因打开缩容而改引擎 EPLB 等原生配置;当前联调不启用 fused MC2,且 Motor 不再读取它作为缩容门禁。enable_fault_toleranceenable_dp_scale_downenable_dp_scale_down_proxyenable_dp_scale_updp_scale_down_config.request_timeout_secdp_scale_down_config.poll_interval_secdp_scale_down_config.execution_deadline_sec查询:
GET /controller/fault_tolerance/status。总开关或缩容关闭时返回phase=DISABLED。典型成功态响应中的实例 runtime 如下;实际接口可返回多个实例,排障时重点查看
phase、本轮request_id、累计删除集合、服务发布状态和最近错误:{ "instance_id": 7, "phase": "SCALED_DOWN_RUNNING", "original_dp_ranks": [0, 1, 2, 3], "request_id": "<scale-down-request-id>", "dead_committed": [1], "pending_removed_ranks": [], "alive_dp_ranks": [0, 2, 3], "fallback_strategy": "<original-fallback>", "gate": { "allowed": true, "reason_codes": [] }, "engine_statuses": {}, "serving_published": true, "last_error": null, "can_serve": true }失败排查规则:
gate.reason_codes表示静态准入失败原因;last_error表示编排、引擎或发布阶段最近一次失败;引擎缩容已提交但发布尚未收敛时,应看到phase=SCALED_DOWN_RUNNING、serving_published=false、can_serve=false。8. 测试与验收建议
已有 DT 覆盖:runtime 状态、FtGate、FaultManager 升级链、ServingOverlay、PodLifecycle、NM 代理 API、EventPusher 投影、心跳合约。
建议验收矩阵:
DEAD,survivorUNHEALTHYDEAD,survivor 持续HEALTHYDEADdp_master,通信域恢复,原始 rank 不重编号dead_committed;SET 不把历史死 rank 加回enable_dp_scale_up=falseenable_dp_scale_up=trueUNHEALTHY、无DEAD,is_applicable=trueUNHEALTHY、无DEAD,is_applicable=falseDEAD,其余 active DP 均UNHEALTHYdead_committedfailed/dead、查询异常或超时finalize(abort)后走 fallbackSCALING_DOWN或 NM commit/Controller commit 窗口重启RECONFIGURING并走原 fallbackenable_dp_scale_down=false每个 E2E 用例至少保存:故障注入命令和目标 rank、引擎与 Controller 状态时间线、apply body/HTTP 202、
dead_committed、Coordinator endpoint 集合、Pod stop 情况以及恢复后的推理结果。性能验收分别记录特性关闭时的基线开销、特性开启但无故障时的 status 查询开销,以及故障检测到恢复发布的分阶段时延;不在 RFC 固化单次环境的性能数值。9. 上游协议依据
core_client.py:external-LB 下由全局dp_rank构造engine_ranks_managed,FT status 的engines[].id继承该全局 rank。fault_tolerance/api_router.py:POST /v1/fault_tolerance/apply使用后台任务并返回 HTTP 202;202 表示请求被接收,不表示缩容已经完成,最终结果必须通过 GET status 轮询。
scale_down指令本身仍以最终采用的 vLLM/vLLM-Ascend PR 和构建为准。参考链接: