[TRTLLM-11851][feat] Add MX-only P2P checkpoint loading support for TRTLLM#13531
Conversation
05ce987 to
d6f0384
Compare
|
/bot run --disable-fail-fast |
|
PR_Github #45846 [ run ] triggered by Bot. Commit: |
📝 WalkthroughWalkthroughThis PR introduces MX (ModelExpress) peer-to-peer weight transfer support for checkpoint loading. A new Changes
Sequence Diagram(s)sequenceDiagram
participant Client as Client/ModelLoader
participant Loader as MXCheckpointLoader
participant MX as ModelExpress<br/>(P2P)
participant HF as HuggingFace<br/>(Disk)
participant Model as Model Instance
Client->>Loader: load_weights(checkpoint_dir,<br/>mapping, model=...)
alt MX Server & Model Reference Available
Loader->>MX: MxLiveWeightLoader.transfer_weights()
alt Transfer Success
MX-->>Loader: weights_dict (empty or partial)
Loader->>Model: Direct parameter writes<br/>(P2P succeeded = true)
alt Partial Transfer (non-empty dict)
Loader->>HF: Full disk load fallback<br/>(P2P succeeded = false)
HF-->>Loader: complete weights
Loader-->>Client: merged weights
else Complete Transfer (empty dict)
Loader-->>Client: P2P weights only
end
else Transfer Fails
MX--XLoader: Exception
Loader->>HF: Fallback to disk load<br/>(P2P succeeded = false)
HF-->>Loader: weights from disk
Loader-->>Client: disk weights
end
else Missing Config or modelexpress
Loader->>HF: Fallback to disk load<br/>(P2P succeeded = false)
HF-->>Loader: weights from disk
Loader-->>Client: disk weights
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
tensorrt_llm/_torch/modules/linear.py (1)
1-1:⚠️ Potential issue | 🟠 MajorAdd required NVIDIA copyright/SPDX header to this modified Python source file.
This file was modified but still lacks the required header block at the top.
Proposed fix
+# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + from __future__ import annotationsAs per coding guidelines, "All TensorRT-LLM source files must contain an NVIDIA copyright header with the year of latest meaningful modification" and "Include NVIDIA copyright header on all new files; update year on modified files".
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@tensorrt_llm/_torch/modules/linear.py` at line 1, Add the required NVIDIA copyright/SPDX header block at the very top of tensorrt_llm/_torch/modules/linear.py (before the existing "from __future__ import annotations" line); the header must include the NVIDIA copyright line with the year of latest meaningful modification and the SPDX-License-Identifier (e.g., SPDX-License-Identifier: Apache-2.0) as used across the repo so the file complies with project coding guidelines.setup.py (1)
1-1:⚠️ Potential issue | 🟠 MajorUpdate SPDX copyright year for this modified file.
setup.pywas changed in 2026, but the header still ends at 2025.🔧 Proposed fix
-# SPDX-FileCopyrightText: Copyright (c) 2022-2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-FileCopyrightText: Copyright (c) 2022-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.As per coding guidelines, “All TensorRT-LLM source files must contain an NVIDIA copyright header with the year of latest meaningful modification” and “update year on modified files.”
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@setup.py` at line 1, Update the SPDX copyright header line that currently reads "SPDX-FileCopyrightText: Copyright (c) 2022-2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved." to reflect the latest modification year (2026); locate the header by the unique string "SPDX-FileCopyrightText" in setup.py and change the year range to "2022-2026" (or to a single year "2026" if preferred by project convention) so the file header matches the most recent modification.
🧹 Nitpick comments (1)
tests/unittest/_torch/pyexecutor/test_model_loader_mx.py (1)
97-157: Add regressions for MX success +reload()and non-default preshard strategy.These tests only exercise the
"per_module"happy path. The production branch also ownsself.weight_mappersetup and strategy-specific skip logic, so a pure MX load followed byreload()ormx_preshard_strategy="global"can regress without this suite noticing. QA list updates look unnecessary here because this is unit-only coverage.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@tests/unittest/_torch/pyexecutor/test_model_loader_mx.py` around lines 97 - 157, Tests only cover the "per_module" MX preshard path and miss regressions for a subsequent reload() call and for the "global" mx_preshard_strategy; extend unit tests (e.g., add cases alongside test_mx_success_marks_main_linears_and_skips_weight_mapping and test_mx_fallback_runs_standard_weight_mapping) to simulate: (1) calling loader.reload(...) after a successful MX load to ensure loader.weight_mapper and skip logic still behave, and (2) running loader.load with mx_preshard_strategy="global" (or by configuring loader.weight_mapper to use global strategy) to assert preshard marking/skipping behaves as expected for main modules vs draft_model modules; reuse _make_loader and checkpoint_loader mocks (set checkpoint_loader.p2p_succeeded True/False and checkpoint_loader.load_weights return values) and assert loader._call_load_weights counts, model.*_weights_presharded flags, and event order just like the existing tests.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@tensorrt_llm/_torch/pyexecutor/model_loader.py`:
- Around line 421-445: When mx_p2p_succeeded is true the code marks Linear
modules presharded but never initializes self.weight_mapper nor validates
mx_preshard_strategy, yet reload() later expects self.weight_mapper; update the
mx_p2p_succeeded branch to always set self.weight_mapper via
checkpoint_loader.get_initialized_weight_mapper(model, config) (same as the
non-fast path) and validate config.mx_preshard_strategy (e.g., raise or handle
if it's not "per_module") before marking modules so non-"per_module" strategies
fail fast; keep using model.load_weights with self.weight_mapper so reload() can
safely consume it.
---
Outside diff comments:
In `@setup.py`:
- Line 1: Update the SPDX copyright header line that currently reads
"SPDX-FileCopyrightText: Copyright (c) 2022-2025 NVIDIA CORPORATION &
AFFILIATES. All rights reserved." to reflect the latest modification year
(2026); locate the header by the unique string "SPDX-FileCopyrightText" in
setup.py and change the year range to "2022-2026" (or to a single year "2026" if
preferred by project convention) so the file header matches the most recent
modification.
In `@tensorrt_llm/_torch/modules/linear.py`:
- Line 1: Add the required NVIDIA copyright/SPDX header block at the very top of
tensorrt_llm/_torch/modules/linear.py (before the existing "from __future__
import annotations" line); the header must include the NVIDIA copyright line
with the year of latest meaningful modification and the SPDX-License-Identifier
(e.g., SPDX-License-Identifier: Apache-2.0) as used across the repo so the file
complies with project coding guidelines.
---
Nitpick comments:
In `@tests/unittest/_torch/pyexecutor/test_model_loader_mx.py`:
- Around line 97-157: Tests only cover the "per_module" MX preshard path and
miss regressions for a subsequent reload() call and for the "global"
mx_preshard_strategy; extend unit tests (e.g., add cases alongside
test_mx_success_marks_main_linears_and_skips_weight_mapping and
test_mx_fallback_runs_standard_weight_mapping) to simulate: (1) calling
loader.reload(...) after a successful MX load to ensure loader.weight_mapper and
skip logic still behave, and (2) running loader.load with
mx_preshard_strategy="global" (or by configuring loader.weight_mapper to use
global strategy) to assert preshard marking/skipping behaves as expected for
main modules vs draft_model modules; reuse _make_loader and checkpoint_loader
mocks (set checkpoint_loader.p2p_succeeded True/False and
checkpoint_loader.load_weights return values) and assert
loader._call_load_weights counts, model.*_weights_presharded flags, and event
order just like the existing tests.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Enterprise
Run ID: b240e53f-4bf3-42cf-965b-c5c28cfd7c1f
📒 Files selected for processing (19)
setup.pytensorrt_llm/_torch/models/checkpoints/__init__.pytensorrt_llm/_torch/models/checkpoints/auto_mapper.pytensorrt_llm/_torch/models/checkpoints/base_weight_loader.pytensorrt_llm/_torch/models/checkpoints/hf/config_loader.pytensorrt_llm/_torch/models/checkpoints/hf/weight_loader.pytensorrt_llm/_torch/models/checkpoints/hf/weight_mapper.pytensorrt_llm/_torch/models/checkpoints/mx/__init__.pytensorrt_llm/_torch/models/checkpoints/mx/checkpoint_loader.pytensorrt_llm/_torch/modules/linear.pytensorrt_llm/_torch/pyexecutor/model_engine.pytensorrt_llm/_torch/pyexecutor/model_loader.pytensorrt_llm/_torch/pyexecutor/py_executor_creator.pytensorrt_llm/executor/base_worker.pytensorrt_llm/llmapi/llm_args.pytests/unittest/_torch/models/checkpoints/mx/test_mx_checkpoint_loader.pytests/unittest/_torch/pyexecutor/test_model_loader_mx.pytests/unittest/api_stability/references/llm.yamltests/unittest/llmapi/test_mx_args.py
|
PR_Github #45846 [ run ] completed with state |
d6f0384 to
4b00f9d
Compare
|
PR LGTM - lack approval privileges |
tburt-nv
left a comment
There was a problem hiding this comment.
No problem with the setup.py comments
4b00f9d to
7cd32b2
Compare
|
PR_Github #46696 [ run ] triggered by Bot. Commit: |
juney-nvidia
left a comment
There was a problem hiding this comment.
Approved from API perspective.
|
PR_Github #46696 [ run ] completed with state
|
|
/bot run --disable-fail-fast |
|
PR_Github #46819 [ run ] triggered by Bot. Commit: |
Signed-off-by: Chien-Chun Hung <2679986+chienchunhung@users.noreply.github.com> Co-authored-by: Cursor <cursoragent@cursor.com>
cc644e0 to
7a02edc
Compare
|
/bot run --disable-fail-fast |
|
PR_Github #46858 [ run ] triggered by Bot. Commit: |
|
PR_Github #46858 [ run ] completed with state
|
|
/bot run --disable-fail-fast |
|
PR_Github #46930 [ run ] triggered by Bot. Commit: |
|
PR_Github #46930 [ run ] completed with state |
Signed-off-by: Chien-Chun Hung <2679986+chienchunhung@users.noreply.github.com>
|
/bot skip --comment "CI succeeded earlier; skip another CI run after rebasing with tiny conflict resolution." |
Signed-off-by: Chien-Chun Hung <2679986+chienchunhung@users.noreply.github.com>
Signed-off-by: Chien-Chun Hung <2679986+chienchunhung@users.noreply.github.com>
|
/bot skip --comment "CI succeeded earlier; skip another CI run after rebasing with tiny conflict resolution." |
2 similar comments
|
/bot skip --comment "CI succeeded earlier; skip another CI run after rebasing with tiny conflict resolution." |
|
/bot skip --comment "CI succeeded earlier; skip another CI run after rebasing with tiny conflict resolution." |
|
PR_Github #47031 [ skip ] triggered by Bot. Commit: |
|
PR_Github #47031 [ skip ] completed with state |
…RTLLM (NVIDIA#13531) Signed-off-by: Chien-Chun Hung <2679986+chienchunhung@users.noreply.github.com>
…e/target
## What this is and why we want it
Cold-starting a large MoE model in TRT-LLM is dominated by weight loading,
not by engine init. On Kimi K2.5 (TP=8, 685 GB) we see ~15-20 minutes per
worker to load weights from a `shared-model-cache` PVC, with all GPUs idle
during the load. For DeepSeek-V3.2, GLM-5, and other ~1 TB MoE models the
penalty is even worse, and it's paid every time a replica scales up,
restarts, or migrates. That stall is the bottleneck for HPA-driven
autoscale, multi-tenant serving, and any deployment where the model size
exceeds local-disk read throughput.
ModelExpress (MX) is the upstream P2P weight transfer system in
`ai-dynamo/modelexpress`. It runs a small gRPC server + Redis sidecar in
the cluster, advertises which workers hold which model versions, and lets
cold-starting workers pull weights via NIXL RDMA directly from a peer
that's already serving. In practice that turns a ~20 minute disk load
into ~2 seconds per rank at 360-500 Gbps (validated on Kimi K2.5 TP=8,
GCP GB200, 4x 400G RoCE).
Two upstream pieces landed in the last six weeks to make this work end-
to-end:
- **TRT-LLM PR #13531** (merged 2026-05-06, ships in 1.3.0rc15+): adds
native `MXCheckpointLoader` for `checkpoint_format="MX"` in TRT-LLM's
PyTorch backend. Does the actual RDMA receive on the target side,
publishes via `publish_model_params(model)` on the source side, and
falls back to HF disk loading when MX is unavailable.
- **ModelExpress PR #202 + #267** (merged): provides `MxClient`,
`MxLiveWeightLoader`, `publish_from_worker`, and `MX_POOL_REG`
allocation-based NIXL pool registration on the client side.
This PR is the Dynamo-side glue that lets users opt in from the command
line. It adds `--model-express-url` to the TRT-LLM backend; when set, the
engine probes the MX server at startup and auto-detects whether to act as
the source (no peers yet, load from disk and publish) or target (peers
already serving, pull via RDMA). No `--mx-role source|target` flag — same
DGD spec works for both, which is what unlocks the HPA-driven scale-up
case (every replica uses identical config; the auto-detect handles which
role each one plays).
There's no Linear/JIRA ticket; the workstream is tracked in the
companion PRs below.
## How auto-detect works
1. Engine calls `_has_existing_sources()` which probes the MX server via
`list_sources()`
2. **Sources found** -> target mode: sets `checkpoint_format="MX"` so
TRT-LLM's upstream `MXCheckpointLoader` does the NIXL RDMA receive
3. **No sources** -> source mode: sets `MODEL_EXPRESS_URL` env var,
workers auto-publish via `publish_from_worker()` after disk load
## Changes
* **components/src/dynamo/trtllm/backend_args.py**: Add
`--model-express-url` CLI argument (env `MODEL_EXPRESS_URL`).
* **components/src/dynamo/trtllm/engine.py**: Plumb `model_express_url`
through `TensorRTLLMEngine` and `get_llm_engine()`; when set,
configure `checkpoint_format="MX"` and seed `MODEL_EXPRESS_URL` env
so the upstream `MXCheckpointLoader` and
`modelexpress.publish_from_worker` take over.
* **components/src/dynamo/trtllm/workers/llm_worker.py**: Pass
`model_express_url` from backend args to engine constructor.
Use `config.exclude_tools_when_tool_choice_none` directly (drop the
defensive getattr/hasattr guard; the field is part of the
DynamoRuntimeConfig contract; .ai/python-guidelines.md flags
defensive access on known types). Construct `RequestHandlerConfig`
directly (drop the inspect.signature filter; the inspect import was
inside the function body and the silent kwarg filtering hid contract
mismatches; .ai/python-guidelines.md requires fail-fast).
* **container/{context.yaml, templates/args.Dockerfile,
templates/trtllm_runtime.Dockerfile}**: Add `ENABLE_MODELEXPRESS_P2P`
+ `MODELEXPRESS_REF` build args; install modelexpress from git when
enabled. The new `RUN` step uses the same `--mount=type=cache,target=
/home/dynamo/.cache/uv,uid=1000,gid=0,mode=0775,sharing=shared` cache
mount as the rest of the file.
* **recipes/deepseek-v3.2/trtllm/disagg/dep8x2/deploy.yaml**: DeepSeek-
V3.2 disaggregated TP=8 prefill + TP=8 decode recipe with
`--model-express-url` wired into both components.
* **recipes/deepseek-v3.2/README.md** (new): Documents the deviation
from `recipes/CONTRIBUTING.md` standard structure - this recipe
intentionally has no `model-cache/` since MX P2P bypasses local
cache for fast scale-up. Points operators at sibling DeepSeek
recipes if they need the standard model-cache flow.
## Validation
Validated end-to-end on GCP GB200 (Kimi K2.5 TP=8, 2 nodes per
component): 16 target ranks x 90.75 GB transferred at 365-509 Gbps,
end-to-end disaggregated inference verified. Same workload on a single-
replica HPA-aggregated deploy: first replica ~22 minutes from disk,
subsequent HPA-driven replicas ~5 minutes via RDMA at 361-583 Gbps/rank.
## Companion PRs
* TRT-LLM PR #13531: `MXCheckpointLoader` (merged 2026-05-06,
NVIDIA/TensorRT-LLM#13531)
* ModelExpress PR #202: `MxLiveWeightLoader`, `publish_from_worker`
(merged, ai-dynamo/modelexpress#202)
* ModelExpress PR #267: `MX_POOL_REG` allocation-based registration
(merged, ai-dynamo/modelexpress#267)
* ModelExpress PR #271 / #272: deployment examples + MPI worker log
flush fix (open, in review, do not block this PR)
Signed-off-by: Kavin Krishnan <kavink@nvidia.com>
Sync the in-tree design doc with the standalone version kept offline
(2026-05-31 last update). Substantive additions / corrections:
- Foundation references section: split into merged (TRTLLM-11851, -12440,
end-to-end prototype) and inflight (TRTLLM-13077 prep PR, MX-team
Delegate-to-ModelExpress refactor proposal) sub-sections.
- Audience and intent section: name the two intended readers (MX/GMS
upstream engineers + TRT-LLM code owners) and what each needs to take
away.
- P1 precondition reframed: source-identity matching is fundamentally a
TRT-LLM concern (only TRT-LLM knows which knobs affect layout), so the
API surface lives in TRT-LLM as `tllm.disagg.compute_source_identity()`
/ `is_source_compatible()` and is consumed by both MX and GMS. The
opaque-bytes design protects transport libraries from churn when
TRT-LLM adds new layout-affecting parameters.
- Wave 4 scope updated to land the TRT-LLM source-identity API (~80 LOC)
as a foundational sub-step, used by both MX and GMS receiver paths.
- New "Coordination with MX and GMS" section:
- Source-identity API directionality (TRT-LLM-owned, MX/GMS-consumed).
- Scope of MX checkpoint loading: discusses the wholesale vs.
transport-only delegation question raised by the MX-team refactor
proposal, with TRT-LLM advocating transport-only (fallback /
validation / telemetry concerns are TRT-LLM-internal and parallel
the NIXL/UCX division-of-labor model in the disagg KV-cache
transceiver).
- What this asks of MX vs. what this asks of GMS as separate bullets.
- Status / Created / Last-updated header brought to 2026-05-31.
All PR-number cross-references (NVIDIA#13531, NVIDIA#13926, NVIDIA#13045, NVIDIA#14770, NVIDIA#14151)
deliberately omitted to keep this docs-and-plans branch from triggering
back-references on the public NVIDIA/TensorRT-LLM PRs; cited via JIRA
tickets and descriptive titles instead. Only external cross-ref
retained is ai-dynamo/dynamo PR NVIDIA#7053 (different repo/org).
Signed-off-by: Chien-Chun Hung <2679986+chienchunhung@users.noreply.github.com>
Summary by CodeRabbit
Release Notes
mx_server_urlfor MX server endpoint andmx_preshard_strategyto control weight sharding behavior.Description
Summary
This PR is the MX-only first slice split out from PR #13045.
It adds
checkpoint_format="MX"support to TRT-LLM's PyTorch backend using upstreammodelexpress.trtllm_live_transfer.MxLiveWeightLoaderandpublish_model_params. GMS is intentionally excluded so reviewers can validate MX first.Follow-up PRs will add:
LoadFormat.GMS,GMSBackend, GMS args/tests)What This PR Adds
MX Checkpoint Loader
Adds
MXCheckpointLoaderundertensorrt_llm/_torch/models/checkpoints/mx/.Behavior:
HfCheckpointLoader, so HF disk fallback is inherited.MxLiveWeightLoader(mx_server=url).load_weights(checkpoint_dir, mapping=..., model=...).publish_model_params(model)beforepost_load_weights()for source workers.p2p_succeededsoModelLoadercan skip normal weight mapping on full P2P success.fallback_weights, keep P2P-delivered tensors and merge only the returned fallback tensors through the standard disk pipeline.MX Config
Adds MX-only prototype fields:
mx_server_urlmx_server_query_timeout_smx_preshard_strategyBehavior:
MODEL_EXPRESS_URLis used as fallback formx_server_urlwhencheckpoint_format="MX".mx_server_query_timeout_slets deployments size source-discovery wait time.MX_SOURCE_QUERY_TIMEOUT=30for fast disk fallbackmx_preshard_strategy="global"fails fast untilLoadFormat.PRESHARDEDexists upstream.ModelLoader Integration
Updates only the existing
LoadFormat.AUTOpath:model=modelto checkpoint loaders. Generic HF loaders ignore it; MX uses it for direct P2P writes.self.weight_mapper, including the MX fast path, soreload()remains safe.Linearmodules as_weights_presharded=Trueafter MX success.fallback_weightsthrough the standard weight-loading path for partial MX fallback.Linear Marker
Adds
_weights_presharded = FalsetoLinear.This PR does not route presharded tensors back through
load_weight_shard()helpers; the marker is set after successful MX direct writes and is kept for the current ModelLoader path plus futureLoadFormat.PRESHARDEDwork.What This PR Excludes
This PR intentionally does not include:
LoadFormat.GMSGMSBackendgms_socket_path,gms_mode,gms_tag[gms]/[dynamo]packaging extrasPackaging Note
This PR does not add a
[mx]extra yet.For prototype testing:
pip install "modelexpress>=0.3.0,<0.4.0"modelexpressis on PyPI but still needs NVIDIA OSS allowlist onboarding (tracked as MX-7). Once complete, restoringpip install tensorrt_llm[mx]is a smallsetup.pychange.Running MX
Optional timeout override:
Python API:
Test Coverage
Added MX-only unit tests:
tests/unittest/llmapi/test_mx_args.pytests/unittest/_torch/models/checkpoints/mx/test_mx_checkpoint_loader.pytests/unittest/_torch/pyexecutor/test_model_loader_mx.pyCoverage includes:
MODEL_EXPRESS_URLfallbackmx_server_query_timeout_smx_preshard_strategyvalidationMODEL_NAMEresolutionPR 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)
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.