Skip to content

[Feature] Add fault tolerance with retry and MC2 scale-down on Ascend NPU - #16145

Open
fangyuchu wants to merge 42 commits into
vllm-project:mainfrom
fangyuchu:feature/scale-down
Open

fangyuchu wants to merge 42 commits into
vllm-project:mainfrom
fangyuchu:feature/scale-down

Conversation

@fangyuchu

@fangyuchu fangyuchu commented Sep 9, 2026 •

Copy link
Copy Markdown

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:

  1. Retry-based recovery for transient faults (foundation). Reuses the
    upstream GPUWorkerSentinel flow (clean worker state + DP-group
    re-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_timeout ascend config) and applies the CPU
    distributed timeout to DP-sync process groups, so peer failures surface as
    exceptions instead of hanging.

  2. 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_down flow while adapting the mask, routing and weight layers to
    Ascend's MC2 all2all backend:

    • npu_communicator: turns the no-op MC2 all2all manager into the owner of
      the dead-rank mask, encoded as the MC2 elastic_info tensor (flag, dense EP
      size, 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: adds 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 + weight reload mirroring
      process_weights_after_loading per slot (transpose, FRACTAL_NZ cast, quant
      scales) for unquantized and W8A8 dynamic schemes, plus
      densify_routing_table_physical_ids.
    • token_dispatcher / patch_distributed: pass elastic_info to MC2
      dispatch/combine and carry dead_dp_ranks so the DP allreduce neutralizes
      dead columns.
    • Clears a poisoned NPU stream on the first device error
      (fault_barrier_wrapper) so teardown cannot crash the worker before
      recovery runs.

Does this PR introduce any user-facing change?

Yes, when --enable-fault-tolerance is used on Ascend NPU:

  • A new ascend config ft_communication_abort_timeout controls how long a hung
    NPU operator (e.g. a communication op) is allowed to run before being aborted
    with a timeout exception. When > 0, it derives HCCL_EVENT_TIMEOUT /
    HCCL_EXEC_TIMEOUT and calls torch.npu.set_op_timeout_ms (default 0,
    disabled).
  • Scale-down recovery now supports MC2 deployments and requires EPLB with
    num_redundant_experts > 0 and the v2 model runner.
  • No API/CLI interface changes beyond the upstream FT flags.

Example startup command for a 4-NPU DP=4 Qwen3-30B-A3B-W8A8 deployment (rank 0):

# Rank 0
ASCEND_RT_VISIBLE_DEVICES=0 \
HCCL_BUFFSIZE=2048 \
HCCL_EXEC_TIMEOUT=1 \
vllm serve /home/xxxxx/Qwen3-30B-A3B-W8A8/ \
  --tensor-parallel-size 1 \
  --data-parallel-size 4 \
  --trust-remote-code \
  --gpu-memory-utilization 0.9 \
  --max-model-len 1024 \
  --enable-expert-parallel \
  --quantization ascend \
  --max-num-batched-tokens 32 \
  --cpu-distributed-timeout-seconds 30 \
  --enable-fault-tolerance \
  --enable-eplb \
  --compilation-config '{"cudagraph_mode":"FULL_AND_PIECEWISE","cudagraph_capture_sizes":[4,8,12,16,20,24,28,32]}' \
  --fault-tolerance-config '{"engine_recovery_timeout_sec":32000}' \
  --eplb-config '{"num_redundant_experts":64, "use_async": false}' \
  --data-parallel-rank 0 \
  --port 8111 \
  --additional-config '{"ft_communication_abort_timeout": 30}' 

# Ranks 1/2/3 use the same command with:
#   ASCEND_RT_VISIBLE_DEVICES={1,2,3}
#   --data-parallel-rank {1,2,3}
#   --port {8112,8113,8114}

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:

  • test_injected_fault_retry_recovers_all_ranks — injected fault on rank 3 drives all 4 engines UNHEALTHY; retry restores all ranks to HEALTHY and factual-prompt serving resumes.
  • test_scale_down_removes_dead_rank_and_recovers — SIGKILL of rank 3's worker marks survivors UNHEALTHY and the victim DEAD (DEAD rejects retry); scale_down re-hosts the dead rank's EPLB experts on the survivors, and all survivors return to HEALTHY and answer factual prompts correctly.

vLLM main: vllm-project/vllm@b2f6858

@github-actions

github-actions Bot commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

👋 Hi! Thank you for contributing to the vLLM Ascend project. The following points will speed up your PR merge:‌‌

  • A PR should do only one thing, smaller PRs enable faster reviews.
  • Every PR should include unit tests and end-to-end tests ‌to ensure it works and is not broken by other future PRs.
  • Write the commit message by fulfilling the PR description to help reviewer and future developers understand.

If CI fails, you can run linting and testing checks locally according Contributing and Testing.


Tip

💡 Consider Linking a Related Issue or RFC

Your 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:

  • Fixes #<issue_number>
  • Closes #<issue_number>
  • Resolves #<issue_number>
  • Refs #<rfc_or_issue_number> (for RFCs)

🙏 Thanks for helping us keep the project well-organized!

@gemini-code-assist

Copy link
Copy Markdown
Contributor

Summary of Changes

Hello, 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

  • Retry-based Recovery: Implemented retry-based recovery for transient faults, utilizing NPU-specific operator timeouts to prevent cluster hangs.
  • MC2 Scale-down Recovery: Added support for scale-down recovery in MC2 deployments, enabling dead-rank masking, expert redistribution, and weight reloading.
  • Elastic Information Integration: Integrated an elastic_info tensor into MC2 dispatch and combine operators to dynamically handle rank failures.
  • End-to-End Testing: Introduced a comprehensive test suite for fault tolerance on Ascend NPU, covering both retry and scale-down scenarios.
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
  • Ignored by pattern: .github/workflows/** (1)
    • .github/workflows/scripts/test_config.yaml
Using Gemini Code Assist

The 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 /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

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 .gemini/ folder in the base of the repository. Detailed instructions can be found here.

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

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution. ↩

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

Suggested PR Title:

[Ops][Feature] Implement Fault Tolerance and Scale-Down Support for Ascend NPU

Suggested 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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

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.

Suggested change
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()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

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.

Suggested change
sentinel.reset_device()
sentinel.reset_device(reinit_pg=False)

@205150940 205150940 Sep 11, 2026 •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

并未出现hang住情况,无需增加传参

Comment on lines +92 to +97
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()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

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).

Suggested change
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()

@205150940 205150940 Sep 11, 2026 •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

同上,无需增加配置项

Comment thread vllm_ascend/worker/model_runner_v1.py Outdated
Comment on lines +2095 to +2098
except Exception as e:
raise RuntimeError(
"prepare_inputs_event record skipped due to fault."
) from e

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

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

@205150940 205150940 Sep 11, 2026 •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

此处需要手动抛出异常,避免进程hang住

# + 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)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

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.

Suggested change
self._elastic_info_host = torch.zeros(size, dtype=torch.int32)
self._elastic_info_host = torch.zeros(size, dtype=torch.int32, pin_memory=True)

@github-actions

Copy link
Copy Markdown
Contributor

This pull request has conflicts, please resolve those before we can evaluate the pull request.

@github-actions

Copy link
Copy Markdown
Contributor

This pull request has conflicts, please resolve those before we can evaluate the pull request.

1 similar comment
@github-actions

Copy link
Copy Markdown
Contributor

This pull request has conflicts, please resolve those before we can evaluate the pull request.

@fangyuchu
fangyuchu marked this pull request as ready for review September 15, 2026 11:25
zWaNg3 and others added 11 commits September 20, 2026 10:30
…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>
…iable for NPU usage.

Signed-off-by: TianZhuo <2770730562@qq.com>
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>
fangyuchu and others added 12 commits September 20, 2026 11:18
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>
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>
@zWaNg3
zWaNg3 force-pushed the feature/scale-down branch 2 times, most recently from b426077 to 0e373f6 Compare September 20, 2026 07:56
205150940 and others added 4 commits September 20, 2026 16:40
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>
Signed-off-by: fangyuchu <fangyuchu@qq.com>
fangyuchu and others added 3 commits September 22, 2026 15:02
* 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>
@github-actions

Copy link
Copy Markdown
Contributor

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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants