Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions vllm/model_executor/layers/fused_moe/modular_kernel.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import torch

import vllm.envs as envs
from vllm._aiter_ops import rocm_aiter_ops
from vllm.logger import init_logger
from vllm.model_executor.layers.fused_moe.activation import (
MoEActivation,
Expand Down Expand Up @@ -1394,6 +1395,15 @@ def apply(
expert_tokens_meta=expert_tokens_meta,
)

# Aiter don't use temp buffer, so can skip copy if output is contiguous
if (
rocm_aiter_ops.is_fused_moe_enabled()
and not self.inplace
and fused_out.shape == output.shape
and fused_out.is_contiguous()
):
output = fused_out
Comment on lines +1399 to +1405

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.

critical

Reassigning output = fused_out is highly dangerous because fused_out is a view into the transient workspace memory (allocated via current_workspace_manager). Returning this tensor means the MoE layer's output can be silently overwritten by any subsequent operation that requests workspace buffers, leading to data corruption or non-deterministic results. The copy from the workspace buffer to the persistent output tensor (allocated at line 1350) is mandatory to ensure the result persists correctly throughout the model's execution.

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.

Reusing fused_out for output is dangerous beyond the fact that it might point to the temporary buffers. Doing this basically forces all the finalize methods to operate inplace which may or may not be supported.

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.

Added platform.is_rocm(), only effect in rocm's finalize

@Rohan138 Rohan138 Apr 1, 2026

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.

I think this check should be rocm_aiter_ops.is_fused_moe_enabled(). For other MOE backends, ROCm devices that don't support AITER, AITER fused_moe explicitly disabled, etc. we still want to keep the default behavior.

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.

Yeah thanks. Should work only when AITER enabled.


return self._finalize(
output,
fused_out,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -437,4 +437,4 @@ def apply(
num_local_tokens=num_local_tokens,
output_dtype=output.dtype,
)
output.copy_(result)
output.data = result

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

Using output.data = result is unsafe and discouraged. This shallow pointer swap is incompatible with CUDA graphs, as the graph capture records the memory address of the tensor's storage. If result is a new allocation (which it appears to be, as rocm_aiter_fused_experts returns a new tensor), its memory address will change in subsequent iterations, invalidating the captured graph. Additionally, it detaches the tensor from the pre-allocated buffer provided by the modular kernel's workspace management. To safely avoid a copy, the underlying AITER kernel should be modified to accept the destination buffer as an argument and write into it directly.

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.

Aiter will use torch.empty to create a new tensor, and it's competible with cuda graph I think.

Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,8 @@ def apply(
f"But got output={output.size()}, "
f"used_expert_output={fused_expert_output.size()}"
)
output.copy_(fused_expert_output, non_blocking=True)
if output.data_ptr() != fused_expert_output.data_ptr():
output.copy_(fused_expert_output, non_blocking=True)
return output


Expand Down
Loading