Skip to content

[NPU]fix: restore MoE ALLTOALL expert IDs after NPU memory reuse - #376

Merged
CalvinXKY merged 1 commit into
vllm-project:ascendfrom
miracle0517:fix/moe_a3_acc_v2
Aug 3, 2026
Merged

[NPU]fix: restore MoE ALLTOALL expert IDs after NPU memory reuse#376
CalvinXKY merged 1 commit into
vllm-project:ascendfrom
miracle0517:fix/moe_a3_acc_v2

Conversation

@miracle0517

@miracle0517 miracle0517 commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Summary

Fix an MoE accuracy issue on Ascend A3 when running Qwen3-30B-A3B in colocated mode with expert parallelism and ALLTOALL communication.

Problem

After a colocated weight update and NPU memory reuse, rollout inference could produce incorrect first-token log probabilities and repetitive outputs. The issue did not reproduce in disaggregated mode.
The expert_ids_per_ep_rank tensor used by the ALLTOALL token dispatcher could retain invalid contents after the vLLM sleep/wake and weight-update lifecycle. This caused received tokens to be assigned to the wrong local experts even though the model weights and ALLTOALL payload were correct.

Fix

  • Track the MoE expert-ID template generation across weight updates and wake-up operations.
  • Limit the workaround to Ascend A3 colocated workers.
  • Preserve existing behavior for other devices and communication paths.

Test

  • End-to-end Qwen3-30B-A3B colocated training on Ascend A3
image image

Signed-off-by: wuxiang <498160096@qq.com>

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

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code Review

This pull request updates the Qwen3-30B-A3B NPU test configuration by reducing the rollout GPUs per engine and enabling the colocate flag. It also introduces a monkey-patching mechanism to restore the ALLTOALL expert-ID template after colocated memory reuse on Ascend A3 devices. The review feedback suggests wrapping third-party imports in a try-except block to handle potential API changes gracefully, and adding defensive checks to prevent AttributeErrors if the expert-ID tensor is uninitialized.

Comment on lines +289 to +297
from vllm_ascend.utils import AscendDeviceType, get_ascend_device_type

if get_ascend_device_type() != AscendDeviceType.A3:
return

from vllm_ascend.ops.fused_moe.token_dispatcher import TokenDispatcherWithAll2AllV

if getattr(TokenDispatcherWithAll2AllV, "_vime_expert_ids_patched", False):
return

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

Monkey-patching internal APIs of third-party libraries (like vllm_ascend) is highly prone to breaking when those libraries are updated. If the import paths or attributes change in future versions, this will cause a hard crash during worker startup. Wrapping these imports and checks in a try...except block ensures defensive execution and prevents startup failures.

Suggested change
from vllm_ascend.utils import AscendDeviceType, get_ascend_device_type
if get_ascend_device_type() != AscendDeviceType.A3:
return
from vllm_ascend.ops.fused_moe.token_dispatcher import TokenDispatcherWithAll2AllV
if getattr(TokenDispatcherWithAll2AllV, "_vime_expert_ids_patched", False):
return
try:
from vllm_ascend.utils import AscendDeviceType, get_ascend_device_type
if get_ascend_device_type() != AscendDeviceType.A3:
return
from vllm_ascend.ops.fused_moe.token_dispatcher import TokenDispatcherWithAll2AllV
except (ImportError, AttributeError):
return
if getattr(TokenDispatcherWithAll2AllV, "_vime_expert_ids_patched", False):
return

Comment on lines +302 to +312
def _patched_dispatch_preprocess(self, hidden_states, topk_ids):
generation = TokenDispatcherWithAll2AllV._vime_expert_ids_generation
if self.num_local_experts > 1 and getattr(self, "_vime_seen_expert_ids_generation", -1) != generation:
expert_ids = self.expert_ids_per_ep_rank
self.expert_ids_per_ep_rank = torch.arange(
self.num_experts,
device=expert_ids.device,
dtype=expert_ids.dtype,
).remainder(self.num_local_experts)
self._vime_seen_expert_ids_generation = generation
return original_dispatch_preprocess(self, hidden_states, topk_ids)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

If self.expert_ids_per_ep_rank is None or not yet initialized, accessing expert_ids.device or expert_ids.dtype will raise an AttributeError. Adding a defensive check ensures that we only attempt to recreate the tensor if it is already present.

Suggested change
def _patched_dispatch_preprocess(self, hidden_states, topk_ids):
generation = TokenDispatcherWithAll2AllV._vime_expert_ids_generation
if self.num_local_experts > 1 and getattr(self, "_vime_seen_expert_ids_generation", -1) != generation:
expert_ids = self.expert_ids_per_ep_rank
self.expert_ids_per_ep_rank = torch.arange(
self.num_experts,
device=expert_ids.device,
dtype=expert_ids.dtype,
).remainder(self.num_local_experts)
self._vime_seen_expert_ids_generation = generation
return original_dispatch_preprocess(self, hidden_states, topk_ids)
def _patched_dispatch_preprocess(self, hidden_states, topk_ids):
generation = TokenDispatcherWithAll2AllV._vime_expert_ids_generation
if self.num_local_experts > 1 and getattr(self, "_vime_seen_expert_ids_generation", -1) != generation:
expert_ids = getattr(self, "expert_ids_per_ep_rank", None)
if expert_ids is not None:
self.expert_ids_per_ep_rank = torch.arange(
self.num_experts,
device=expert_ids.device,
dtype=expert_ids.dtype,
).remainder(self.num_local_experts)
self._vime_seen_expert_ids_generation = generation
return original_dispatch_preprocess(self, hidden_states, topk_ids)

@miracle0517 miracle0517 changed the title fix: restore MoE ALLTOALL expert IDs after NPU memory reuse [NPU]fix: restore MoE ALLTOALL expert IDs after NPU memory reuse Aug 3, 2026
NPUWorker._npu_worker_patched = True

@staticmethod
def _patch_a3_moe_alltoall_expert_ids() -> None:

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Could this cause garbled characters in single inference? I think this issue should also be reproducible in single inference as well.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

We noticed that a colleague has already submitted a relevant fix PR in the vllm-ascend repository. vllm-project/vllm-ascend#13352, from a version control perspective, it is reasonable for us to apply a patch in vime to override the older version of vllm-ascend in order to resolve the precision issues. This approach is both justified and manageable.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Could this cause garbled characters in single inference? I think this issue should also be reproducible in single inference as well.

Not in our current reproduction. Single or low-concurrency requests take the MC2 path and remain normal. The issue only appears when high concurrency triggers the Vime IPC mixed prefill/decode + ALLTOALL path.

We also verified that direct vLLM-Ascend serving can use ALLTOALL normally, so this does not look like a general single-inference or ALLTOALL accuracy issue. See the lightweight comparison in [#374]

@CalvinXKY
CalvinXKY merged commit e5aa5e7 into vllm-project:ascend Aug 3, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants