Skip to content

[None][feat] preserve native MoE A2A graph VAs across restore#16632

Open
hhzhang16 wants to merge 3 commits into
NVIDIA:mainfrom
hhzhang16:hannahz/dep-1082-port-flashinfer-stable-va-lifecycle-for-native-moe-all-to
Open

[None][feat] preserve native MoE A2A graph VAs across restore#16632
hhzhang16 wants to merge 3 commits into
NVIDIA:mainfrom
hhzhang16:hannahz/dep-1082-port-flashinfer-stable-va-lifecycle-for-native-moe-all-to

Conversation

@hhzhang16

@hhzhang16 hhzhang16 commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

Summary by CodeRabbit

  • New Features

    • Added checkpoint preparation and restoration for MNNVL-backed MoE communication workflows.
    • Preserved memory layouts while safely detaching and remapping workspace handles during checkpoints.
    • Added validation to prevent communication operations when required workspace mappings are unavailable.
  • Bug Fixes

    • Improved cleanup of partially created or detached memory allocations.
    • Added safeguards against restoring checkpoints with incompatible communicator layouts.
  • Tests

    • Added coverage for memory lifecycle, checkpointing, mapping failures, and invalid communication states.

Description

This change ports the FlashInfer stable-VA lifecycle to TensorRT-LLM's native MoE all-to-all resources. FlashInfer implemented this lifecycle for its TRT-LLM-style MoE all-to-all workspace in flashinfer-ai/flashinfer#3727.

Test Coverage

PR Checklist

Please review the following before submitting your PR:

  • PR description clearly explains what and why. If using CodeRabbit's summary, please make sure it makes sense.

  • PR Follows TRT-LLM CODING GUIDELINES to the best of your knowledge.

  • Test cases are provided for new code paths (see test instructions)

  • If PR introduces API changes, an appropriate PR label is added - either api-compatible or api-breaking. For api-breaking, include BREAKING in the PR title.

  • Any new dependencies have been scanned for license and vulnerabilities

  • CODEOWNERS updated if ownership changes

  • Documentation updated as needed

  • Update tava architecture diagram if there is a significant design change in PR.

  • The reviewers assigned automatically/manually are appropriate for the PR.

  • Please check this after reviewing the above items as appropriate for this PR.

GitHub Bot Help

To see a list of available CI bot commands, please comment /bot help.

Signed-off-by: Hannah Zhang <hannahz@nvidia.com>
Signed-off-by: Hannah Zhang <hannahz@nvidia.com>
@coderabbitai

coderabbitai Bot commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

MNNVL memory now tracks mapped allocation records, separates virtual-address reservations from backing handles, and supports checkpoint detach/restore. MoE communication paths expose checkpoint lifecycle methods, enforce mapped workspaces, refresh metadata, and reset dispatch state.

Changes

MNNVL checkpoint lifecycle

Layer / File(s) Summary
Allocation records and handle mapping
tensorrt_llm/_mnnvl_utils.py, tests/unittest/_torch/test_mnnvl_memory_lifecycle.py
Structured allocation records and centralized handle mapping support FABRIC and non-FABRIC paths with cleanup on partial failures.
Memory teardown and checkpoint state
tensorrt_llm/_mnnvl_utils.py, tests/unittest/_torch/test_mnnvl_memory_lifecycle.py
Teardown conditionally releases mapped handles, while checkpointing preserves VA reservations and restores fresh mappings for both MoE workspaces.
Communication checkpoint APIs
tensorrt_llm/_torch/distributed/moe_alltoall.py, tensorrt_llm/_torch/modules/fused_moe/communication/*
MoE All-to-All and NVLink classes add checkpoint preparation and restoration, metadata refresh, synchronization, and dispatch-state reset behavior.
Mapped-state enforcement and dispatch cleanup
tensorrt_llm/_torch/distributed/moe_alltoall.py, tensorrt_llm/_torch/modules/fused_moe/communication/*, tests/unittest/_torch/test_mnnvl_memory_lifecycle.py
Communication entry points reject unmapped workspaces, and two-sided combine clears retained dispatch state.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant MoeAlltoAll
  participant MnnvlMemory
  participant NVLinkCommunication
  participant Communicator
  MoeAlltoAll->>MnnvlMemory: checkpoint_prepare()
  MnnvlMemory-->>NVLinkCommunication: backing handles detached
  MoeAlltoAll->>MnnvlMemory: checkpoint_restore(comm)
  MnnvlMemory-->>NVLinkCommunication: fresh handles remapped
  NVLinkCommunication->>Communicator: synchronize and barrier
  NVLinkCommunication-->>MoeAlltoAll: communication state reset
Loading

Suggested reviewers: rosong11, sunnyqgg

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description states the goal and change, but the Test Coverage section is empty and the checklist isn't fully addressed. Add the relevant tests under Test Coverage and note any API-impact/label decisions or other checklist items relevant to this PR.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise, specific, and matches the main change: preserving native MoE A2A graph VAs across restore.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai 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.

🧹 Nitpick comments (1)
tensorrt_llm/_torch/modules/fused_moe/communication/nvlink_one_sided.py (1)

703-722: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider mirroring the _require_mapped() guard here for cross-implementation consistency.

MoeAlltoAll.get_combine_payload_tensor_in_workspace() calls self._require_mapped() before the phase check, but this NVLink one-sided variant does not. In practice the phase != "dispatched" guard already blocks reaching this while handles are detached (checkpointing requires the idle phase), so this is a defense-in-depth consistency nit rather than a live bug. Aligning both keeps the two frontends symmetric if the phase semantics ever change.

♻️ Optional consistency tweak
     def get_combine_payload_tensor_in_workspace(
         self, runtime_max_tokens_per_rank: int, hidden_size: int, dtype: torch.dtype
     ) -> torch.Tensor:
         ...
+        self._require_mapped()
         if self._dispatch_state.get("phase") != "dispatched":
             raise RuntimeError(
                 "get_combine_payload_tensor_in_workspace called before a successful dispatch"
             )
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tensorrt_llm/_torch/modules/fused_moe/communication/nvlink_one_sided.py`
around lines 703 - 722, Update get_combine_payload_tensor_in_workspace to call
the existing _require_mapped() guard before checking the dispatch phase,
matching MoeAlltoAll’s implementation while preserving the current phase
validation and behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@tensorrt_llm/_torch/modules/fused_moe/communication/nvlink_one_sided.py`:
- Around line 703-722: Update get_combine_payload_tensor_in_workspace to call
the existing _require_mapped() guard before checking the dispatch phase,
matching MoeAlltoAll’s implementation while preserving the current phase
validation and behavior.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: bc5812ec-a3d9-4c6c-9695-5f1fa61aa472

📥 Commits

Reviewing files that changed from the base of the PR and between 445742c and 297d098.

📒 Files selected for processing (5)
  • tensorrt_llm/_mnnvl_utils.py
  • tensorrt_llm/_torch/distributed/moe_alltoall.py
  • tensorrt_llm/_torch/modules/fused_moe/communication/nvlink_one_sided.py
  • tensorrt_llm/_torch/modules/fused_moe/communication/nvlink_two_sided.py
  • tests/unittest/_torch/test_mnnvl_memory_lifecycle.py

@chienchunhung chienchunhung left a comment

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.

Thanks for working on this. I left two lifecycle questions inline.

return
torch.cuda.synchronize()
record.comm.barrier()
cls._unmap_and_release_handles(record)

@chienchunhung chienchunhung Jul 23, 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.

Could we make this lifecycle transition fail closed?

If _unmap_and_release_handles() raises after releasing only some ranks, record.mapped remains True, so a later _require_mapped() can treat a partially unmapped workspace as usable.

The inverse can happen during restore: the allocation is marked mapped before the frontend has reinitialized its metadata and completed collective readiness.

Would it make sense to track an explicit transitioning or broken state, reject data-path access while in that state, and only mark the workspace ready after initialization and the final barrier have completed?

A focused failure-injection test for an error partway through unmap or restore would also help protect this contract.

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 explicit allocation states in _mnnvl_utils.py in this MR hhzhang16/TensorRT-LLM#1, with data-path access allowed only in MAPPED. The shared lifecycle publishes a restored allocation only after frontend initialization, synchronization, and protocol reset, and marks failed transitions BROKEN. Included failure-injection coverage in the lifecycle tests.

The local phase and watchdog checks are defense-in-depth only; they do
not prove that every wrapper sharing this workspace is quiescent.
"""
if self._dispatch_state.get("phase") != "idle":

@chienchunhung chienchunhung Jul 23, 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.

Could we move this preflight to the shared workspace owner? The phase and watchdog checks here cover only this instance, but mnnvl_mem is shared by every wrapper using the same workspace. An idle instance could therefore detach the handles while a sibling is still dispatched or has a watchdog using the workspace. The docstring correctly calls out the need for global quiescence, but I could not find a Communication, ConfigurableMoE, or executor-level path that establishes it before this call. Could we register and preflight all shared owners before the first unmap, then expose the transition through the lifecycle owner?

PS: The shared instances pattern in PR #15895 may be reusable here. A two-owner test where one remains active would help verify that no detach occurs.

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.

In MR hhzhang16/TensorRT-LLM#1: added _MnnvlAlltoAllWorkspaceLifecycle as the allocation-scoped owner. Both frontends register with it, and it checks every registered owner plus rank-wide readiness before the first unmap. The MR also includes two-owner coverage where one owner remains active.

if self._state.phase != "idle":
raise RuntimeError(
"Cannot checkpoint during an active MoE All-to-All phase")
if self._alltoall_watchdog is not 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.

should we fail if there is a watchdog? why not just tear it down during checkpoint_prepare?

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.

Good call, I changed it in this MR: hhzhang16#1 so that the shared lifecycle now owns the workspace watchdog. It suspends the watchdog after readiness preflight and before unmapping, then recreates it with the same configuration after metadata refresh and collective restore readiness.

cls.current_rank_stride,
cls.current_mem_offset,
)
# all_handles_data like b'\x00\x00\x00 \x00\x00\x00\x00\x8f\xec\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\t\x00\x00\x00\x00\x00\x1d\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' # noqa: E501

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.

not sure what this comment is doing here. I know this was in the FI PR but do we need this here?

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.

🤷

Signed-off-by: Hannah Zhang <hannahz@nvidia.com>
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.

3 participants