Conversation
|
👋 Hi! Thank you for contributing to the vLLM Ascend project. The following points will speed up your PR merge:
If CI fails, you can run linting and testing checks locally according Contributing and Testing. Tip 💡 Consider Linking a Related Issue or RFCYour PR title contains the [Feature] tag, indicating a bug fix or new feature. Linking a related issue or RFC in the PR description is strongly encouraged — it gives reviewers helpful context and speeds up the review. You can use any of these keywords:
🙏 Thanks for helping us keep the project well-organized! |
Summary of ChangesHello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request introduces a robust fault-tolerance framework for Ascend NPU deployments, specifically addressing hanging issues in MoE models during rank failures. It implements a two-layered recovery mechanism: retry-based recovery for transient errors and scale-down recovery for permanent rank failures in MC2 deployments. The changes include NPU-specific communication timeouts, dynamic expert redistribution, and weight reloading, ensuring cluster stability and seamless recovery without manual intervention. Highlights
New Features🧠 You can now enable Memory (public preview) to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console. Ignored Files
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize the Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counterproductive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here. Footnotes
|
There was a problem hiding this comment.
Code Review
Suggested PR Title:
[Ops][Feature] Implement Fault Tolerance and Scale-Down Support for Ascend NPUSuggested PR Summary:
### What this PR does / why we need it?
This pull request implements end-to-end fault tolerance and scale-down support for Ascend NPUs in vLLM. It introduces the `WorkerSentinel` for NPU, which manages device resets, collective process group re-initialization, and expert redistribution. It also adds an `All2All-manager` adapter (`_NpuAll2AllManager`) to handle elastic masking for MC2 dispatch/combine operators, and implements expert weight reloading from disk in `eplb_redistribute.py`. Additionally, it includes end-to-end tests to validate fault injection, retry recovery, and worker-kill scenarios.
Feedback on the implementation highlights several critical issues:
- Loading all expert weights into CPU memory during redistribution can cause CPU OOM; the loader should be restricted to only the required logical expert IDs.
- Resetting the device locally within the exception handler can cause deadlocks due to collective process group re-initialization; `reset_device` should support skipping this step during local quarantine.
- Wrapping caught exceptions in a generic `RuntimeError` in `synchronize_input_prep` hides the original exception type from recovery handlers.
- The host tensor `_elastic_info_host` should be allocated in pinned memory to ensure non-blocking asynchronous copies to the NPU.
### Does this PR introduce _any_ user-facing change?
Yes, it introduces fault-tolerance configuration options for Ascend NPU, such as `ft_communication_abort_timeout` in `AscendConfig`, and enables the `--enable-fault-tolerance` flag on Ascend platforms.
### How was this patch tested?
The patch was tested using the newly added end-to-end fault-tolerance tests on Ascend NPU (`test_fault_tolerance_e2e.py`), which cover injected fault retry recovery and worker process termination.|
|
||
| loader = DefaultModelLoader(vllm_config.load_config) | ||
| # Produce every expert, not just the ones local at startup. | ||
| loader.local_expert_ids = None |
There was a problem hiding this comment.
Setting loader.local_expert_ids = None forces the DefaultModelLoader to load all experts of the model from disk. For large MoE models, loading all expert weights into CPU memory simultaneously is highly inefficient and can easily trigger a CPU Out-Of-Memory (OOM) error.
Since the required logical expert IDs for this rank are already known and stored in local_slots, we should restrict the loader to only load those specific experts.
| loader.local_expert_ids = None | |
| loader.local_expert_ids = {logical_id for _, logical_id in local_slots} |
| sentinel.worker_faulted = True | ||
| logger.warning("[FT] Quarantining worker %d after fault: %s", self.rank, exc) | ||
| try: | ||
| sentinel.reset_device() |
There was a problem hiding this comment.
Calling sentinel.reset_device() locally within the exception handler will trigger a collective process group re-initialization. Since other ranks are not participating yet, this rank will block indefinitely inside reinit_process_group, causing the worker to hang.
We should disable process group re-initialization during the local quarantine reset, and only perform it during the coordinated retry call.
| sentinel.reset_device() | |
| sentinel.reset_device(reinit_pg=False) |
| def reset_device(self) -> None: | ||
| NPUPlatform.set_device(self.device) | ||
| torch_npu.npu.stop_device(self.device.index) | ||
| torch_npu.npu.restart_device(self.device.index) | ||
| torch_npu.distributed.reinit_process_group(None, False) | ||
| torch.npu.synchronize() |
There was a problem hiding this comment.
To prevent deadlocks during local quarantine resets, we should allow reset_device to skip the collective reinit_process_group call when requested. Re-initializing the process group is a collective operation that must only be performed when all ranks participate (i.e., during a coordinated retry).
| def reset_device(self) -> None: | |
| NPUPlatform.set_device(self.device) | |
| torch_npu.npu.stop_device(self.device.index) | |
| torch_npu.npu.restart_device(self.device.index) | |
| torch_npu.distributed.reinit_process_group(None, False) | |
| torch.npu.synchronize() | |
| def reset_device(self, reinit_pg: bool = True) -> None: | |
| NPUPlatform.set_device(self.device) | |
| torch_npu.npu.stop_device(self.device.index) | |
| torch_npu.npu.restart_device(self.device.index) | |
| if reinit_pg: | |
| torch_npu.distributed.reinit_process_group(None, False) | |
| torch.npu.synchronize() |
| except Exception as e: | ||
| raise RuntimeError( | ||
| "prepare_inputs_event record skipped due to fault." | ||
| ) from e |
There was a problem hiding this comment.
Wrapping the caught exception in a generic RuntimeError hides the original exception type from upstream fault-tolerance and recovery handlers. This can interfere with decision-making logic that relies on specific exception types (e.g., distinguishing between transient communication timeouts and fatal model execution errors).
We should re-raise the original exception directly to preserve its type and traceback.
except Exception:
raise| # + table2(dense->orig). num_physical_experts is derived from the | ||
| # dead set and num_local_experts on every rebuild. | ||
| size = 4 + 2 * ep_world_size | ||
| self._elastic_info_host = torch.zeros(size, dtype=torch.int32) |
There was a problem hiding this comment.
The host tensor self._elastic_info_host is copied to the NPU device tensor using non_blocking=True (at line 100). For non_blocking=True to be safe and actually asynchronous, the host tensor must be allocated in pinned memory (pin_memory()). Otherwise, the copy operation falls back to a synchronous copy, losing any asynchronous benefits.
| self._elastic_info_host = torch.zeros(size, dtype=torch.int32) | |
| self._elastic_info_host = torch.zeros(size, dtype=torch.int32, pin_memory=True) |
|
This pull request has conflicts, please resolve those before we can evaluate the pull request. |
ece13fc to
b43afcb
Compare
|
This pull request has conflicts, please resolve those before we can evaluate the pull request. |
1 similar comment
|
This pull request has conflicts, please resolve those before we can evaluate the pull request. |
…overy Introduce WorkerSentinel to handle fault tolerance commands (handle_command, query_mask, retry) on each worker, with automatic device restart and DP group re-initialization on retry. - Add WorkerSentinel class in npu_worker_sentinel.py with per-worker FT state - Add operator_timeout_ms config via additional_config - Integrate WorkerSentinel into NPUWorker init and distribute environment setup - Add handle_ft_command dispatch on NPUWorker for collective FT RPC - Set NPU operator timeout when fault_tolerance is enabled - Support TP > 1 in retry by using per-rank ports - Add model runner v2 support in _clean_worker_state Co-authored-by: fangyuchu <fangyuchu@qq.com> Co-authored-by: TianZhuo <2770730562@qq.com> Co-authored-by: a798347923 <2645302020@qq.com> Co-authored-by: yzchang-plus <1078477584@qq.com> Co-authored-by: Jade Zheng <zheng.shoujian@outlook.com> Signed-off-by: zWaNg3 <389750525@qq.com>
for more information, see https://pre-commit.ci
Signed-off-by: TianZhuo <2770730562@qq.com>
…iable for NPU usage. Signed-off-by: TianZhuo <2770730562@qq.com>
for more information, see https://pre-commit.ci
Signed-off-by: TianZhuo <2770730562@qq.com>
Signed-off-by: TianZhuo <2770730562@qq.com>
…mit (#102) Signed-off-by: TianZhuo <2770730562@qq.com>
* [Refactor] Drop FT_COMMUNICATION_OPS_ABORT_TIMEOUT_MS env var Signed-off-by: TianZhuo <2770730562@qq.com> * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Signed-off-by: TianZhuo <2770730562@qq.com> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Signed-off-by: TianZhuo <2770730562@qq.com>
Signed-off-by: TianZhuo <2770730562@qq.com>
d1e7e67 to
9dbb258
Compare
9dbb258 to
24a6410
Compare
Implement fault-tolerance scale-down for Ascend, mirroring the upstream GPU scale_down flow while adapting the platform-specific mask, routing and weight layers to the MC2 all2all backend. - npu_communicator: turn the no-op all2all manager into the owner of the dead-rank mask, encoded as the MC2 elastic_info tensor (flag, dense ep size, shared-expert count, physical-expert count plus the orig-to-dense and dense-to-orig rank tables), rebuilt in place so captured graphs stay valid; add update_mask / query_active_mask / query_fault / clean_buffers / set_num_physical_experts / to_densified_rank_table - npu_worker_sentinel: add scale_down with precondition validation (v2 model runner, EPLB redundancy, no fused_mc2 / hierarchy comm, dispatch_v2), dead EP-rank masking, expert redistribution with routing table refresh and densification, selective on-disk expert weight reload, MC2 physical-expert width shrink, and a dummy-batch runnability check - eplb_redistribute: NPU expert redistribution and weight reload that mirrors process_weights_after_loading per slot (transpose, FRACTAL_NZ cast, per-slot lists, quant scales) for the unquantized and W8A8 dynamic schemes, plus densify_routing_table_physical_ids to renumber the kernel-facing routing ids into the dense coordinate space - token_dispatcher: pass elastic_info to the MC2 dispatch/combine when fault tolerance is enabled so dead ranks are excluded via the mask - patch_distributed: carry dead_dp_ranks on the group coordinator so the DP allreduce can neutralize dead columns Signed-off-by: fangyuchu <fangyuchu@qq.com> Co-authored-by: zWaNg3 <389750525@qq.com> Co-authored-by: TianZhuo <2770730562@qq.com> Co-authored-by: a798347923 <2645302020@qq.com> Co-authored-by: yzchang-plus <1078477584@qq.com> Co-authored-by: Jade Zheng <zheng.shoujian@outlook.com> Co-authored-by: mengxingkongzhouhan <2529690987@qq.com> Co-authored-by: njuyuan <yuanjl19@samil.nju.edu.cn> Co-authored-by: ANKH-zhengyu <zhangzhengyu2333@gmail.com> Co-authored-by: 2017403603 <705328646@qq.com> Co-authored-by: lqh-hh <liuqihang6@huawei.com>
A dead DP peer makes the MC2 dispatch/combine allreduce over the EP group fail, which can poison the model stream: every later device op -- including tensor-teardown event records -- re-raises, and a teardown in a C++ destructor std::terminates the worker before scale-down recovery can run. - sentinel: add fault_barrier_wrapper, a decorator that quarantines the worker (worker_faulted) on any fault raised from a device-touching method and immediately resets the device to clear a poisoned stream before any teardown can crash the process, then returns an empty output - sentinel: while quarantined, wrapped methods short-circuit so in-flight steps never re-hit the broken stream; retry lifts the quarantine only after the DP/EP groups are rebuilt - worker: apply fault_barrier_wrapper to execute_model / sample_tokens Signed-off-by: zWaNg3 <389750525@qq.com> Co-authored-by: TianZhuo <2770730562@qq.com> Co-authored-by: a798347923 <2645302020@qq.com> Co-authored-by: yzchang-plus <1078477584@qq.com> Co-authored-by: Jade Zheng <zheng.shoujian@outlook.com>
Rebuild the elastic_info tensor with vectorized table construction and a single torch.cat instead of per-field index constants, computing the alive ranks once. No behavior change. Signed-off-by: zWaNg3 <389750525@qq.com> Co-authored-by: TianZhuo <2770730562@qq.com> Co-authored-by: a798347923 <2645302020@qq.com> Co-authored-by: yzchang-plus <1078477584@qq.com> Co-authored-by: Jade Zheng <zheng.shoujian@outlook.com>
- eplb_redistribute: add build_orig_to_dense_rank_table as the single source of the kernel's table1 (orig->dense) view; rewrite build_local_reload_plan's docstring to reflect the slot-reassignment semantics - npu_communicator: drop to_densified_rank_table and the elastic_info layout constants now that the rebuild owns that logic - npu_worker_sentinel: derive the dead set from the manager's cumulative active mask instead of this round's ranks, and read ep_world_size / ep_rank from the EP group, the authoritative placement Signed-off-by: zWaNg3 <389750525@qq.com>
- npu_worker_sentinel: scale_down now calls super().scale_down and only adds Ascend preconditions + a dummy-batch runnability check; the Ascend _redistribute_experts calls super() for the placement math and appends the Ascend tail (routing-table refresh, densified id renumbering, MC2 width shrink) - eplb_redistribute: reload_experts_from_disk mirrors the upstream signature (a set of (layer, logical) reassignments) and recovers the destination local slot from the freshly rebuilt logical_to_physical_map; drop build_local_reload_plan and the before/after p2l snapshot it needed - gpu_worker_sentinel: route the reload call inside the inherited _redistribute_experts to the Ascend reloader by rebinding the module-level name, so the shared flow writes Ascend's per-slot runtime expert layout Signed-off-by: zWaNg3 <389750525@qq.com>
- npu_communicator: replace set_num_physical_experts with set_num_local_physical_experts, storing only the per-rank slot count; the elastic_info rebuild derives num_physical_experts as alive_ranks * num_local, so it always stays in sync with the cumulative dead mask (no extra width setter needed after redistribution) - npu_worker_sentinel: set num_local before super().scale_down so the retry flow's update_mask rebuilds elastic_info with the shrunk width; drop the width shrink call from the redistribution tail Signed-off-by: zWaNg3 <389750525@qq.com>
…rrier - extract the MC2 routing-table densification after refresh_model_routing_tables into a dedicated _densify_routing_tables method - expand fault_barrier_wrapper's docstring to explain its role as a barrier between device faults and the async step loop Signed-off-by: zWaNg3 <389750525@qq.com>
Align the AscendW8A8DynamicFusedMoEMethod import with the relocated quantization module. Signed-off-by: zWaNg3 <389750525@qq.com>
for more information, see https://pre-commit.ci
Signed-off-by: TianZhuo <2770730562@qq.com>
Shorten class/module docstrings and inline comments in npu_communicator, patch_distributed, eplb_redistribute and npu_worker_sentinel; drop the redundant use_v2_model_runner mock in test_worker_v1. Signed-off-by: zWaNg3 <389750525@qq.com>
Initialize the MC2 all2all manager's per-rank physical expert count right after model loading instead of at scale_down time. The value is a process-lifetime constant derived from the EPLB physical layout; setting it at init fixes standalone retry rebuilding elastic_info with a zero expert count. Signed-off-by: zWaNg3 <389750525@qq.com>
b426077 to
0e373f6
Compare
Mirror the upstream scale-down e2e test for the Ascend implementation: - SIGKILL rank 3's worker; survivors (0,1,2) report UNHEALTHY with fault_info while the victim reports DEAD - the DEAD engine accepts retry at the HTTP layer (202) but the engine rejects it, recording 'status is DEAD' in ft_error - apply scale_down (removed_dp_ranks) to every survivor and verify all of them recover to healthy, serve completions, and answer factual prompts correctly with the re-hosted experts The test exercises the W8A8 path (--quantization ascend, no --dtype), FULL_AND_PIECEWISE cudagraphs with explicit capture sizes, EPLB redundancy 48 (44 slots/rank keeps 3*44 >= 128 logical experts after a 4 -> 3 shrink), and the /v1/fault_tolerance API. Skips cleanly on stacks without npu_moe_distribute_dispatch_v2 (CANN V3+). The previous worker-kill retry-rejection test is folded into this one to avoid duplicating the fault-injection phase. Signed-off-by: TianZhuo <2770730562@qq.com>
* test(ft) fix deepseekv4-flash bugs Signed-off-by: TianZhuo <2770730562@qq.com> * [Refactor] Fix bug Signed-off-by: TianZhuo <2770730562@qq.com> --------- Signed-off-by: TianZhuo <2770730562@qq.com>
…timeout * [Refactor] Adapt to upstream fault_tolerance changes. Signed-off-by: TianZhuo <2770730562@qq.com> * [Refactor] fix(ft): bound scale_down dummy batch with gloo cpu group timeout Signed-off-by: TianZhuo <2770730562@qq.com> --------- Signed-off-by: TianZhuo <2770730562@qq.com>
The three helpers re-exported from
vllm.v1.worker.sentinel.eplb_redistribute had no consumers: nothing in
vllm_ascend references them (they appear only in the import statement and
__all__), and the fork's sentinel code imports them from vllm's own module
rather than through vllm_ascend, so there is no reverse dependency either.
This was also the only import in vllm_ascend pointing at a fork-only vllm
module, which made mypy fail with:
Cannot find implementation or library stub for module named
"vllm.v1.worker.sentinel.eplb_redistribute" [import-not-found]
Signed-off-by: zWaNg3 <389750525@qq.com>
0e373f6 to
3295849
Compare
Signed-off-by: fangyuchu <fangyuchu@qq.com>
c29191c to
f0e0820
Compare
#137) Signed-off-by: fangyuchu <fangyuchu@qq.com>
* fix(ft): route AsyncOutput.get_output through the fault barrier Signed-off-by: TianZhuo <2770730562@qq.com> * refactor(ft): remove worker sentinel patches in favor of inheritance Move the import-time monkey patches out of npu_worker_sentinel.py and replace them with class-based wiring: - Re-class AsyncOutput results as AscendAsyncOutput (get_output behind the fault barrier) directly in NPUWorker.sample_tokens, only when fault tolerance is enabled - Override WorkerSentinel._redistribute_experts so reassigned expert weights reload through the Ascend layout-aware reloader without patching the upstream gpu_worker_sentinel module attribute No behavior change: the fault barrier still quarantines the worker on faults escaping get_output, and scale_down keeps the Ascend redistribution flow. Signed-off-by: TianZhuo <2770730562@qq.com> * test: align FT e2e test with required request_id for recovery rounds Signed-off-by: TianZhuo <2770730562@qq.com> --------- Signed-off-by: TianZhuo <2770730562@qq.com>
* perf(ft): optimize scale-down expert weight reload Rework the Ascend expert reload for fault-tolerance scale-down from a full-checkpoint scan with per-entry device ops into a header-only shard walk with quant-uniform batched reload: - Scan: walk safetensors shard headers and read only the tensors of the reassigned experts, instead of materializing the whole checkpoint. Matching runs on the normalized runtime name and works for Megatron-style layouts (e.g. DeepSeek-V4 .ffn.experts.<id>.) that the upstream local_expert_ids filter cannot parse. - Apply: group (layer, slot) pairs by quant scheme, weight shape and dtype, then reload in chunks of 32 with one stacked H2D, one format cast and non-blocking scatter copies per chunk, replacing thousands of per-entry synchronous device ops. - Assemble the checkpoint tensors of each chunk in parallel with a bounded thread pool; entries only read shared state and pool.map preserves order, so the scatter and the per-entry fallback are unchanged. - The batched path falls back to the per-entry reloaders when a chunk cannot be stacked; supported schemes are registered in _RELOADERS. Total scale-down time on a DP8xTP2 DeepSeek-V4-flash deployment drops from ~20s to ~3s. Signed-off-by: zWaNg3 <389750525@qq.com> * refactor(ft): drop silent fallbacks and private loader use in scale-down reload Reproduce the safetensors half of DefaultModelLoader._prepare_weights from the public helpers it wraps, so the reload no longer reaches into a loader's private method and no longer depends on load_format. The reload therefore also works under rfork/netloader, as long as the checkpoint is safetensors. Remove the fallbacks that guarded it. None of them could succeed: - Falling back to get_all_weights read the whole checkpoint to recover a handful of experts, and only DefaultModelLoader defines it, so the branch meant to cover rfork/netloader would have raised AttributeError instead of degrading. - The secondary_weights rescan repeated that read. - A failed torch.stack fell back to per-entry reloads; one uniform expert layout is a precondition of stacking, not something to route around. - A missing EPLB placement map was skipped, leaving the slot holding the previous occupant's weights while routing already pointed at the expert. - Grouping entries by layout papered over a modelling error: a chunk is stacked and cast with the first entry's dtype, so one layout is asserted. Signed-off-by: zWaNg3 <389750525@qq.com> * docs(ft): tighten scale-down reload comments The comments added for the expert reload restated the same contract in several places. Keep one statement per fact: - The module docstring drops its pointer to a precondition list that the reload_experts_from_disk docstring no longer spells out. The three things the reload refuses to handle are still named there. - reload_experts_from_disk keeps the runtime-layout rationale and the ordering requirement, and loses the bulleted requirements plus the Raises block that restated them. - _reload_chunk, _reload_batched and _collect_matching_weights keep what a caller cannot infer (cost per entry, layout uniformity, header-only reads) and lose the rest. - _SUPPORTED_QUANT_TYPES, _RELOAD_CHUNK_SIZE and _GATHER_MAX_WORKERS move up next to the other module constants. Comments and docstrings only; no behaviour change. Signed-off-by: zWaNg3 <389750525@qq.com> * style(ft): apply the ruff format fix pre-commit.ci requested ruff format rewrites the missing-placement-map error to a single line, merging the implicit string concatenation. This restores the change pre-commit.ci made in a19d9c3, which the previous force-push dropped. No behaviour change. Signed-off-by: zWaNg3 <389750525@qq.com> * refactor(ft): drop the entry triple and download path from the reload The reload's work item was a (layer_idx, logical_id, slot) triple whose three fields were never used together: assembly needs the pair, the scatter needs the slot, and two of the three consumers underscored the field they ignored. Carry the pair and resolve the slot from local_slots at write time, so the concept goes away instead of being documented. The checkpoint is now read from a local directory only. The download branch is gone along with the imports it needed, and a non-directory path raises instead of falling back to a fetch that could not have succeeded anyway. Also: - _resolve_safetensors_shards -> _collect_safetensors_shards, matching the _collect_matching_weights it feeds. - The expert-bias guard no longer exempts w8a8. No scheme assembles bias, so a quantized model with expert bias would have silently kept the previous occupant's bias rather than raising. - _reload_batched -> _reload_local_slots, _match_weight_name -> _resolve_expert_tensor, _assemble_entry -> _assemble_expert, and the module docstring gains a short glossary for slot and chunk. Signed-off-by: zWaNg3 <389750525@qq.com> --------- Signed-off-by: zWaNg3 <389750525@qq.com>
|
This pull request has conflicts, please resolve those before we can evaluate the pull request. |
…ut (#141) * feat(worker): warn when HCCL timeout env vars override operator timeout Signed-off-by: TianZhuo <2770730562@qq.com> * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Signed-off-by: TianZhuo <2770730562@qq.com> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
What this PR does / why we need it?
In DP+EP (Data Parallelism + Expert Parallelism) MoE deployments, a dead DP rank
makes the EP all2all on surviving ranks block indefinitely, hanging the whole
cluster. Upstream vLLM introduced a fault-tolerance framework
(vllm-project/vllm#44428) that detects the fault, aborts in-flight requests and
lets an external orchestrator trigger coordinated recovery, but Ascend NPU needs
its own backend.
This PR provides the complete Ascend NPU fault-tolerance backend in two layers:
Retry-based recovery for transient faults (foundation). Reuses the
upstream
GPUWorkerSentinelflow (clean worker state + DP-groupre-initialization on retry) and adds the NPU-specific pieces upstream cannot
cover: aborts hung NPU communication ops via
torch.npu.npu.set_op_timeout_ms(new
ft_communication_abort_timeoutascend config) and applies the CPUdistributed timeout to DP-sync process groups, so peer failures surface as
exceptions instead of hanging.
Scale-down recovery for MC2 (this PR's focus). When a rank is confirmed
dead, retry alone cannot recover - survivors keep routing MoE tokens to the
dead EP rank and the all2all keeps failing. This mirrors the upstream GPU
scale_downflow while adapting the mask, routing and weight layers toAscend's MC2 all2all backend:
npu_communicator: turns the no-op MC2 all2all manager into the owner ofthe dead-rank mask, encoded as the MC2
elastic_infotensor (flag, dense EPsize, shared/physical expert counts plus orig-to-dense and dense-to-orig rank
tables), rebuilt in place so captured graphs stay valid.
npu_worker_sentinel: addsscale_downwith precondition validation (v2model runner, EPLB redundancy, no
fused_mc2/hierarchy comm,dispatch_v2),dead EP-rank masking, expert redistribution with routing-table refresh and
densification, selective on-disk expert weight reload, MC2 physical-expert
width shrink, and a dummy-batch runnability check.
eplb_redistribute: NPU expert redistribution + weight reload mirroringprocess_weights_after_loadingper slot (transpose, FRACTAL_NZ cast, quantscales) for unquantized and W8A8 dynamic schemes, plus
densify_routing_table_physical_ids.token_dispatcher/patch_distributed: passelastic_infoto MC2dispatch/combine and carry
dead_dp_ranksso the DP allreduce neutralizesdead columns.
(
fault_barrier_wrapper) so teardown cannot crash the worker beforerecovery runs.
Does this PR introduce any user-facing change?
Yes, when
--enable-fault-toleranceis used on Ascend NPU:ft_communication_abort_timeoutcontrols how long a hungNPU operator (e.g. a communication op) is allowed to run before being aborted
with a timeout exception. When
> 0, it derivesHCCL_EVENT_TIMEOUT/HCCL_EXEC_TIMEOUTand callstorch.npu.set_op_timeout_ms(default0,disabled).
num_redundant_experts > 0and the v2 model runner.Example startup command for a 4-NPU DP=4 Qwen3-30B-A3B-W8A8 deployment (rank 0):
How was this patch tested?
Added DP=4 end-to-end coverage in tests/e2e/pull_request/four_card/fault_tolerance/test_fault_tolerance_e2e.py:
vLLM main: vllm-project/vllm@b2f6858