Skip to content

DRAFT proof: Kimi K2.5 + Eagle3 shadow-failover stack (do not merge) - #13394

Draft
galletas1712 wants to merge 17 commits into
NVIDIA:mainfrom
galletas1712:schwinns/gms-refactor-20260423
Draft

DRAFT proof: Kimi K2.5 + Eagle3 shadow-failover stack (do not merge)#13394
galletas1712 wants to merge 17 commits into
NVIDIA:mainfrom
galletas1712:schwinns/gms-refactor-20260423

Conversation

@galletas1712

@galletas1712 galletas1712 commented Apr 23, 2026

Copy link
Copy Markdown
Collaborator

DO NOT MERGE - proof build only

This draft PR exists to trigger NVIDIA/TensorRT-LLM external-contributor CI and to host the TRT-LLM-side patches needed by the Dynamo TRT-LLM shadow-engine failover proof.

Head: galletas1712/TensorRT-LLM:schwinns/gms-refactor-20260423 @ 9ba5ee40
Base: NVIDIA/TensorRT-LLM:main

Dynamo-side orchestration and runtime pytest live in ai-dynamo/dynamo#8621.

Patch Rationale

  • GMS backend prototype for TRT-LLM weights. Primary workers need to publish model weights through GPU Memory Service, and standby workers need to restore them in RO mode without loading a second checkpoint copy. This is the core memory-sharing primitive for hot shadow failover.
  • GMS weight mapping park/restore for parked engines. Parked RO shadows must drop local GMS weight VA mappings when configured with TRTLLM_GMS_RELEASE_WEIGHTS_ON_SLEEP=all. The backing store remains resident once in GMS, but the parked process releases its local mappings so duplicate shadow footprint is lower.
  • GMS load-format normalization. LoadFormat.GMS, string forms like gms, and serialized enum forms must round-trip through TorchLlmArgs and MPI startup. Without this, TP workers can silently miss the GMS path.
  • MPI environment propagation. GMS_*, ENGINE_ID, and FAILOVER_LOCK_PATH must reach MPI workers, not only the parent process. Otherwise GMS mode and failover-lock behavior diverge across TP ranks.
  • Expose GMS weight byte accounting. Kimi K2.5 + EAGLE3 showed that TRT-LLM's KV sizing cannot treat GMS-resident weights as ordinary free memory. The calibration code needs to know how much weight memory is backed by GMS so primary and RO standby measurements are comparable.
  • Calibrate KV cache sizing for GMS RO shadows. A standby sees weights already resident in GPU memory. The KV estimator corrects for GMS weights, process-local non-torch overhead, MoE workspace accounting, and temporary KV so the promoted engine can target nearly the same KV size as the original primary.
  • Add a GMS shadow peer-memory reserve. Large CUDA graph ladders need startup/capture headroom while an already-prepared peer is still parked. TRTLLM_GMS_SHADOW_PEER_MEMORY_RESERVE_GIB / _BYTES subtracts explicit headroom before applying free_gpu_memory_fraction; this keeps pre-capture from OOMing without lowering the serving memory fraction below 0.85.
  • Relax the VMM OOM retry polling interval. The GMS failover path intentionally blocks and retries VMM materialization while peer KV memory is being released. The pod-validated setting uses a 0.2s retry interval to avoid tight polling across TP ranks while preserving the staggered block-on-OOM behavior.
  • Respect explicit max_gpu_total_bytes. Once calibration produces a byte budget, the resource manager must honor it directly; otherwise the later KV manager can remeasure a different free-memory state and resize inconsistently.
  • Remove deferred GMS shadow warmup/CUDA graph capture. The earlier park-full-KV/deferred-warmup experiment worked mechanically but is not acceptable for shadow failover: warmup and CUDA graph capture must not run in promotion/wakeup. This PR now requires those steps to complete before the shadow parks.
  • Prevent lazy runtime CUDA graph capture in GMS serving mode. If a promoted GMS engine sees a missing graph key, it falls back to eager execution instead of capturing the graph on the post-failover request. This prevents an uncaptured shape from silently moving graph capture into the failover hot path.
  • Preserve CUDA graphs on GMS shadow sleep. Non-GMS sleep can keep its previous graph-release behavior, but gms_weights shadow-failover sleep preserves captured inference graphs. Releasing them made Qwen's first promoted-shadow response recapture graphs and jump from about 1s to about 13s after SIGKILL.
  • Add opt-in CUDA graph memory snapshots. TRTLLM_CUDA_GRAPH_MEMORY_SNAPSHOTS=1 logs per-key graph-capture memory/timing snapshots around eager warmup, graph capture, postprocess, and graph storage. This was needed to prove bs128's incremental graph reserve and distinguish graph memory from GMS weights/KV VMM.
  • Flush allocator state after profiling and sleep. Some standby footprint is cached allocator state rather than live model state. Explicit cleanup after KV estimation and during sleep keeps cached blocks from blocking later VMM materialization.
  • Release held VMM MemPool proxy references. Sleeping a shadow must actually drop cached virtual-memory pool references; otherwise the standby still holds memory even after tagged blobs are released.
  • Clear TRT-LLM reusable memory buffers on sleep. memory_buffer_utils.Buffers.bytes_by_name() and clear() let sleep account for and release reusable buffers that are safe to rebuild after promotion.
  • Return distributed sleep/wakeup results from all MPI workers. The executor request queue now gathers per-rank control action results, not only errors. Dynamo uses those results to log the memory ledger for each TP worker and to prove cleanup ran on MPI children.
  • Add opt-in GMS shadow failover diagnostics. TRTLLM_GMS_LOG_PER_TAG_SLEEP_RELEASE=1 breaks sleep cleanup down by VMM tag, TRTLLM_GMS_LOG_SLEEP_PHASES=1 can snapshot memory immediately after GMS weight parking, and wakeup reports per-rank phase timings.
  • Add the shadow_failover SleepConfig preset. Dynamo should not hardcode TRT-LLM's internal sleep tags. TRT-LLM owns the preset expansion for KV, model, draft model, executor extras, MoE comms, speculative resources, drafter, sampler, guided decoder state, and GMS weights.
  • Make SleepConfig pickleable. Dynamo passes LLMArgs through MPI startup at TP=8. The previous defaultdict(lambda: ...) restore-mode default was not pickleable, so worker startup failed before GMS restore. Default restore modes are now a plain dict.
  • Add MoE/MNNVL release and restore hooks. Large MoE runs can allocate private communication/workspace state outside shared GMS weights. These hooks allow sleep to release those resources and wake to restore them when the selected backend exposes releasable state.
  • Add TLLM_AUTOTUNER_USE_CUDA_GRAPH for autotuner profiling. Kimi K2.5 + EAGLE3 showed that autotuner profiling can allocate about 37-38 GiB/rank in CUDA graph private pools even when inference CUDA graphs are disabled. This env gate disables only the autotuner's internal torch.cuda.CUDAGraph() profiling wrapper; inference CUDA graphs remain controlled by cuda_graph_config.
  • Add focused unit coverage. Tests pin SleepConfig pickle/preset behavior, GMS load-format serialization, MPI env propagation, GMS backend byte accounting, RO GMS mapping release/restore, GMS KV calibration including peer reserve, and MoE/MNNVL workspace release/restore behavior.

Current Behavior

The accepted path now preserves the failover invariant: warmup and CUDA graph capture happen before the standby parks, and promotion/wakeup does not run deferred warmup or graph capture.

The latest Kimi K2.5 + EAGLE3 TP=8 bs128 run used one standby, free_gpu_memory_fraction=0.85, a pre-populated autotuner cache, TLLM_AUTOTUNER_USE_CUDA_GRAPH=0, explicit CUDA graph batch sizes [1,2,4,8,16,32,64,128], enable_padding=true, and TRTLLM_GMS_SHADOW_PEER_MEMORY_RESERVE_GIB=10. It passed with the standby already pre-captured and parked before the primary served traffic.

standby failover ready:        5.86s
post-restore response latency: 0.57s
SIGKILL -> response:           6.43s
primary KV blocks:             58510
promoted standby KV blocks:    60232
primary KV bytes:              61.27 GiB main + 3.01 GiB draft
standby KV bytes:              63.07 GiB main + 3.10 GiB draft

The promoted-standby wakeup hot path was dominated by GMS weight restore/import/access setup: restore_gms_weights=4.91-5.03s, while materialize_vmm=0.01-0.04s. No deferred warmup/CUDA graph bucket was present.

The no-reserve control run failed before failover: the standby sized to roughly 75 GiB/rank KV, captured bs128, then OOMed starting bs64 with rank-0 driver free down to 0.01 GiB. The 10 GiB peer reserve is therefore startup/capture headroom, not a serving-memory-fraction cheat.

sequenceDiagram
    participant P as Primary TRT-LLM
    participant G as GMS
    participant S as Shadow TRT-LLM
    participant V as TRT-LLM VMM/KV/Graphs
    participant D as Dynamo Locks/Router

    P->>G: publish weights RW
    P->>V: autotune/cache-hit warmup and CUDA graph capture
    P->>V: sleep(shadow_failover), preserve graphs, release KV/VMM/caches
    S->>G: map weights RO from shared GMS store
    S->>V: calibrate KV with GMS weights + peer reserve
    S->>V: allocate KV, run warmup, capture CUDA graphs before parking
    S->>V: sleep(shadow_failover), preserve graphs, release KV/VMM/caches
    S->>D: park and wait on failover lock
    P->>V: wake for serving path and allocate full KV
    P->>D: serve traffic, then SIGKILL releases lock
    S->>D: acquire failover lock
    S->>G: reconnect/remap GMS weights RO
    S->>V: wake and materialize KV VMM; no warmup or graph capture
    S->>D: publish model metadata
    D->>S: route post-failover inference for manual inspection
Loading

Validation

TRT-LLM build/setup notes:

  • Kernel/FATBIN artifacts are LFS-backed; this proof branch did not regenerate kernel blobs.
  • The nscale pod used the TRT-LLM devcontainer plus this fork branch installed into the runtime. The final committed TRT-LLM Python files were copied into both /workspace-dev/TensorRT-LLM and /usr/local/lib/python3.12/dist-packages/tensorrt_llm before the final run.

Checks from this commit:

  • git diff --check - clean.
  • Local python3 -m py_compile for edited TRT-LLM runtime files - passed.
  • Local grep for deferred-warmup/capture symbols (TRTLLM_GMS_DEFER, run_deferred, capture_deferred, defer_warmup_once, defer_cuda_graph_warmup_once, etc.) - no matches in the edited pyexecutor/test paths.
  • Pod installed-runtime python3 -m py_compile for edited TRT-LLM runtime files - passed.
  • Targeted local pytest could not run because local Python lacks pytest; the nscale pod ran the runtime failover pytest against the synced installed code.

Dynamo runtime validation with this TRT-LLM code synced into the pod:

  • Qwen/Qwen3-0.6B, TP=1, two shadows, free_gpu_memory_fraction=0.85 - 1 passed in 158.00s; failover-ready 0.71s, post-restore response 0.33s, kill-to-response 1.04s; primary/promoted KV blocks [43485, 0] / [43589, 0].
  • Qwen/Qwen3-0.6B, TP=8, two shadows, free_gpu_memory_fraction=0.85 - 1 passed in 198.33s; failover-ready 3.34s, post-restore response 0.50s, kill-to-response 3.84s; primary/promoted KV blocks [344905, 0] / [345103, 0].
  • Kimi K2.5 + EAGLE3 TP=8, one standby, CUDA graphs disabled, autotuner disabled - 1 passed in 360.40s; failover-ready 5.21s, post-restore response 1.95s, kill-to-response 7.15s.
  • Kimi K2.5 + EAGLE3 TP=8, one standby, no-deferral bs128 pre-capture, autotuner cache hit, TLLM_AUTOTUNER_USE_CUDA_GRAPH=0, peer reserve 10 GiB - 1 passed in 320.54s; failover-ready 5.86s, post-restore response 0.57s, kill-to-response 6.43s; primary/promoted KV blocks 58510/60232; manual response ended in failover-ok.
  • No-reserve bs128 pre-capture control - failed during standby graph pre-capture before failover, proving the peer reserve is needed for startup/capture headroom at free_gpu_memory_fraction=0.85.
  • Autotuner cache reuse, inference CUDA graphs disabled - both primary and standby loaded the pre-populated cache with 0 Profiled runner= lines; test passed in 299.07s; kill-to-response 9.53s; primary/promoted KV blocks 70660/72380.
  • Historical/rejected park-full-KV/deferred-warmup experiments passed through bs128/bs256 and greedy bs512, but they are no longer target behavior because they move warmup/CUDA graph capture into promotion.

Artifacts/worklog are archived locally under /Users/schwinns/dynamo-worktrees/trtllm-failover-artifacts-20260427, especially new-runs-20260428-precapture-guard/INDEX.md and trtllm_failover_artifacts_with_precapture_guard_20260428.tar.gz.

Known Follow-Ups

  • Reduce the restore_gms_weights hot-path bucket. Current evidence points at CUDA handle import/access setup, not weight data copy or KV VMM materialization.
  • Evaluate the keep-RO-mapped tradeoff again with the no-deferral graph-preserving path. It may reduce promotion latency, but it increases parked duplicate footprint.
  • Kimi has been validated with one standby for large-model runs. Multiple Kimi standbys and automatic shadow replenishment need additional capacity work.
  • Add a true autotuner cache-builder/cache-only mode so serving replicas consume prebuilt artifacts without cache-producer residue.
  • Continue CUDA graph memory work for very large batch sizes and advanced sampling. The bs128 explicit ladder passes with peer reserve; bs512 advanced-sampling graph capture still needs a lower-memory or sampling-mode-specific TRT-LLM fix.

This PR is not intended for merge as-is. It is a proof-build branch for the Dynamo shadow-failover stack and should be closed once the relevant pieces land through their intended upstream path.

Signed-off-by: Schwinn Saereesitthipitak schwinns@nvidia.com

Reproducibility: nscale Devpod Setup

Pod / container

  • Kubernetes context: nv-prd-dgxc.teleport.sh-dynamo-nscale-dev-cluster
  • Namespace / pod: schwinns / trtllm-gms-failover-devpod
  • Node: cluster-0967a26d-pool-14bee067-prctr-s2877
  • Container image: nvcr.io/nvidia/tensorrt-llm/devel:1.3.0rc12
  • Pod resources: requests cpu=48, memory=384Gi; limits cpu=96, memory=768Gi
  • Shared HF model cache PVC: volume hf-cache, PVC shared-model-cache, mounted at /home/dynamo/.cache/huggingface
  • Workspace volume: workspace-dev, mounted at /workspace-dev
  • Shared memory volume: emptyDir.medium=Memory, mounted at /dev/shm
  • Hardware/runtime observed in the run logs: 8x NVIDIA B200, ~182633 MiB visible per GPU, CUDA 13.1.1.006, driver 590.48.01, Python 3.12.3, PyTorch 2.11.0a0+eb65b36914.nv26.02, NGC PyTorch 26.02, TensorRT-LLM package 1.3.0rc11, gpu-memory-service==0.9.0.

The source directories in the pod were synced into /workspace-dev/dynamo and /workspace-dev/TensorRT-LLM without .git metadata. The PR SHAs used for the synced source/build were:

  • Dynamo: ai-dynamo/dynamo:schwinns/gms-refactor-20260423 at b1fb89cf2cc37be4a49c5cdad7d89a3b0058a6d6
  • TensorRT-LLM fork: galletas1712/TensorRT-LLM:schwinns/gms-refactor-20260423 at 9ba5ee407b3e1fe4a48b427d6e6bcc10824e629b

Installed packages in the accepted pod:

ai-dynamo                         1.1.0   /workspace-dev/dynamo
ai-dynamo-runtime                 1.1.0   /workspace-dev/dynamo/lib/bindings/python
gpu-memory-service                0.9.0   /workspace-dev/dynamo/lib/gpu_memory_service
tensorrt_llm                      1.3.0rc11
tensorrt                          10.15.1.29

Build / install steps

TRTLLM had C++ changes, so the wheel was rebuilt inside the pod with the native B200 arch and ccache:

cd /workspace-dev/TensorRT-LLM
./scripts/build_wheel.py --clean --use_ccache --cuda_architectures=native
# Install the generated TensorRT-LLM wheel into the dev container environment.

Dynamo and GMS were installed editable from the synced Dynamo branch:

cd /workspace-dev/dynamo
uv pip install -e '.[trtllm]'
uv pip install -e lib/gpu_memory_service
# At minimum, `uv pip install gpu-memory-service` is required if not using the editable in-tree GMS package.

Detached kubectl exec runs needed the Python wheel .libs directories on LD_LIBRARY_PATH; without this, the run could fail to load libnixl-c7102487.so / libetcd-cpp-api-core-cac80504.so:

PYLIB_DIRS=$(find /usr/local/lib/python3.12/dist-packages -maxdepth 1 \
  -type d \( -name '*.libs' -o -name '.*.libs' \) -printf '%p:' 2>/dev/null)
export LD_LIBRARY_PATH=/usr/local/lib:${PYLIB_DIRS}${LD_LIBRARY_PATH:-}

Accepted TRTLLM shadow-failover pytest command

The main accepted Kimi K2.5 + EAGLE3 run used the shared HF cache offline and pre-captured CUDA graphs before parking the standby. Promotion does not defer warmup or CUDA graph capture into the failover hot path.

cd /workspace-dev/dynamo

export HF_HOME=/home/dynamo/.cache/huggingface
export HF_HUB_CACHE=/home/dynamo/.cache/huggingface/hub
export HF_HUB_OFFLINE=1
export DYN_TRTLLM_SHADOW_FAILOVER_MODEL=/home/dynamo/.cache/huggingface/hub/models--nvidia--Kimi-K2.5-NVFP4/snapshots/c0285e649c34d4386b01e38abca642c06cbe014e
export DYN_TRTLLM_SHADOW_FAILOVER_SERVED_MODEL_NAME=Kimi-K2.5-NVFP4-Eagle3
export DYN_TRTLLM_SHADOW_FAILOVER_ENGINE_YAML=/workspace-dev/dynamo/tests/gpu_memory_service/trtllm_kimi_k25_eagle3_engine.yaml
export DYN_TRTLLM_SHADOW_FAILOVER_TP=8
export DYN_TRTLLM_SHADOW_FAILOVER_SHADOW_COUNT=1
export DYN_TRTLLM_SHADOW_FAILOVER_FREE_GPU_MEMORY_FRACTION=0.85
export DYN_TRTLLM_SHADOW_FAILOVER_MAX_KV_BLOCK_DELTA_FRACTION=0.05
export DYN_TRTLLM_SHADOW_FAILOVER_MAX_READY_SECONDS=1200
export DYN_TRTLLM_SHADOW_FAILOVER_MAX_POST_KILL_RESPONSE_SECONDS=1200
export DYN_TRTLLM_SHADOW_FAILOVER_POST_RESTORE_RESPONSE_TIMEOUT_SECONDS=1200
export DYN_TRTLLM_SHADOW_FAILOVER_RESPONSE_MAX_TOKENS=128
export DYN_TRTLLM_SHADOW_FAILOVER_POST_RESTORE_MAX_TOKENS=128
export DYN_TRTLLM_SHADOW_FAILOVER_POST_RESTORE_PROMPT='Say exactly: failover-ok'
export DYN_TRTLLM_SHADOW_FAILOVER_EXTRA_ARGS='--disable-torch-compile --override-engine-args '\''{"enable_autotuner":true,"max_batch_size":128,"cuda_graph_config":{"batch_sizes":[1,2,4,8,16,32,64,128],"enable_padding":true}}'\'''
export TLLM_AUTOTUNER_CACHE_PATH=/tmp/kimi_autocache_reuse_nograph_1777335866/autotune-cache.json
export TLLM_AUTOTUNER_USE_CUDA_GRAPH=0
export TLLM_AUTOTUNER_LOG_LEVEL_DEBUG_TO_INFO=1
export TRTLLM_CLEAR_MEMORY_BUFFERS_ON_SLEEP=1
export TRTLLM_CUDA_GRAPH_MEMORY_SNAPSHOTS=1
export TRTLLM_CUDA_GRAPH_MEMORY_SYNC=1
export TRTLLM_GMS_LOG_PER_TAG_SLEEP_RELEASE=1
export TRTLLM_GMS_LOG_SLEEP_PHASES=1
export TRTLLM_GMS_RELEASE_WEIGHTS_ON_SLEEP=all
export TRTLLM_GMS_SHADOW_MEMORY_CALIBRATION=1
export TRTLLM_GMS_SHADOW_PEER_MEMORY_RESERVE_GIB=10
export PYTORCH_ALLOC_CONF=garbage_collection_threshold:0.99999
export DYN_LOG=debug

python -m pytest -s tests/gpu_memory_service/test_trtllm_shadow_failover.py::test_gms_shadow_engine_failover_trtllm --tb=short

Notes on that configuration:

  • TRTLLM_GMS_SHADOW_PEER_MEMORY_RESERVE_GIB=10 reserves headroom for the already parked peer while the promoted engine sizes KV and captures CUDA graphs. This is currently a conservative explicit reserve; a follow-up is to replace it with measured peer footprint where possible.
  • TLLM_AUTOTUNER_USE_CUDA_GRAPH=0 avoids the autotuner's CUDA-graph profiling pool, which was observed to create a private per-rank memory spike around 37-38 GiB in failed exploratory runs.
  • The accepted graph-capture shape set is explicit powers of two up to bs=128 with padding enabled. The default/expanded graph batch set put too much pressure on the parked-shadow setup.
  • The pytest asserts failover mechanics and timing; response quality was manually inspected from pytest.log for the failover-ok response, rather than asserting semantic coherence in pytest.

Timing / validation results

Run Artifact Result Failover timing KV capacity
Qwen3-0.6B TP=1 default /Users/schwinns/dynamo-worktrees/trtllm-failover-artifacts-20260427/qwen3_06b_tp1_final_1777080565/pytest.log 1 passed in 158.00s post-restore inference completed and response manually inspected primary/promoted KV blocks 43485/43589
Qwen3-0.6B TP=8 /Users/schwinns/dynamo-worktrees/trtllm-failover-artifacts-20260427/qwen3_06b_tp8_accept_1777078131/pytest.log 1 passed in 198.33s post-restore inference completed and response manually inspected primary/promoted KV blocks 344905/345103
Kimi K2.5 NVFP4 + EAGLE3 TP=8, autotune cache reuse, CUDA graphs bs128 /Users/schwinns/dynamo-worktrees/trtllm-failover-artifacts-20260427/new-runs-20260428-precapture-guard/logs/kimi_precapture_cgbs128_reserve10_guard_snap_1777360695/pytest.log 1 passed in 320.54s standby ready 5.86s; post-restore response 0.57s; SIGKILL-to-response 6.43s; response manually inspected as failover-ok primary/promoted KV blocks 58510/60232; primary approx 61.27 GiB main + 3.01 GiB draft; promoted standby approx 63.07 GiB main + 3.10 GiB draft
Kimi K2.5 NVFP4 + EAGLE3 TP=8 tensor audit follow-up /Users/schwinns/dynamo-worktrees/trtllm-failover-artifacts-20260427/new-runs-20260428-tensor-audit/tensor_audit_1777364433/pytest.log 1 passed in 355.83s standby ready 5.91s; post-restore response 0.55s; SIGKILL-to-response 6.46s primary/promoted KV blocks 58510/60232; parked footprint approx 9.5-9.7 GiB/rank; local tensor storage approx 4.282 GiB/rank (3.437 GiB CUDA graph runner + 0.845 GiB shape-classified local tensors)

The hot wake path in the accepted Kimi run was dominated by remapping/restoring GMS weights, not KV VMM materialization:

restore_gms_weights: ~4.91-5.03s/rank
materialize_vmm:     ~0.01-0.04s/rank
restore_moe:         ~0.01s/rank
total rank wake:     ~5.63-5.67s/rank

Artifact index

Local artifact/worklog roots:

  • Worklog: /Users/schwinns/dynamo-worktrees/trtllm-failover-artifacts-20260427/WORKLOG.md
  • CUDA graph investigation: /Users/schwinns/dynamo-worktrees/trtllm-failover-artifacts-20260427/new-runs-20260427-cuda-graphs/CUDA_GRAPH_MEMORY_INVESTIGATION.md
  • Tensor audit summary: /Users/schwinns/dynamo-worktrees/trtllm-failover-artifacts-20260427/new-runs-20260428-tensor-audit/TENSOR_AUDIT_SUMMARY.md
  • Accepted Kimi bs128 run: /Users/schwinns/dynamo-worktrees/trtllm-failover-artifacts-20260427/new-runs-20260428-precapture-guard/logs/kimi_precapture_cgbs128_reserve10_guard_snap_1777360695/
  • Accepted tensor audit run: /Users/schwinns/dynamo-worktrees/trtllm-failover-artifacts-20260427/new-runs-20260428-tensor-audit/tensor_audit_1777364433/

Pod artifact root for the latest tensor-audit run:

  • /workspace-dev/trtllmfailover-artifacts/tensor_audit_1777364433/pytest.log
  • /workspace-dev/trtllmfailover-artifacts/tensor_audit_1777364433/run.env
  • /workspace-dev/trtllmfailover-artifacts/tensor_audit_1777364433/tensor_audit/*.json

Signed-off-by: Schwinn Saereesitthipitak <schwinns@nvidia.com>
Signed-off-by: Schwinn Saereesitthipitak <schwinns@nvidia.com>
Introduce the GPU Memory Service (GMS) weight-loading prototype for
Dynamo's cross-engine zero-copy weight sharing:

- `tensorrt_llm/_torch/memory/gpu_memory_backend.py` — `GMSBackend`
  wrapper around `gpu_memory_service.client.torch.allocator` providing
  publish/materialize primitives (`materialize_module`,
  `defer_finalize_write`, `move_untracked_params`) and `mem_pool_scope`
  for directing `torch.empty`/`torch.zeros` allocations into GMS-backed
  virtual memory.
- `tensorrt_llm/_torch/pyexecutor/model_loader.py` — extend
  `LoadFormat` with `GMS` and branch on `gms_backend.is_rw` to publish
  weights (RW) or materialize an existing layout zero-copy (RO).
- `tensorrt_llm/llmapi/llm_args.py` — new `gms_mode`, `gms_tag`,
  `gms_socket_path` fields; API stability YAML + test coverage.
- `tensorrt_llm/_torch/modules/linear.py` — mark `Linear` modules that
  materialized zero-copy from a committed GMS layout with
  `_weights_presharded = True`; gate re-sharding in `load_weight_shard`
  itself via an optional `module=` kwarg so every call site (not just
  the three unquantized helpers) is protected.

The `_weights_presharded` gate lives in `load_weight_shard` to avoid
scattering the same ternary across ~90 callers (quantized scales, MoE
expert loaders, fused/triton linear). Callers opt in by passing
`module=module`; a presharded module forces `tensor_parallel_size=1`,
`tensor_parallel_rank=0` and returns the tensor unchanged. Existing
callers without `module=` retain legacy behavior.

Tests: `tests/unittest/_torch/memory/test_gms_backend.py`,
`tests/unittest/_torch/modules/test_load_weight_shard.py`,
`tests/unittest/llmapi/test_gms_args.py`.

Signed-off-by: Schwinn Saereesitthipitak <schwinns@nvidia.com>
Signed-off-by: Schwinn Saereesitthipitak <schwinns@nvidia.com>
Signed-off-by: Schwinn Saereesitthipitak <schwinns@nvidia.com>
Signed-off-by: Schwinn Saereesitthipitak <schwinns@nvidia.com>
One-model speculative configs keep draft weights in a separate checkpoint
directory pointed at by `SpeculativeConfig.speculative_model`. The AUTO
load path calls `model.load_draft_weights` after the target load
(model_loader.py:476-489), but the GMS RW branch skipped that step,
publishing uninitialized draft weights into GMS.

For Kimi K2.5 + Eagle3, DeepSeekV3's weight loader explicitly drops any
tensor prefixed `draft_model.*` (modeling_deepseekv3.py) and
`_call_load_weights` reports those as 'skipped'. The allocate-weights
pass (model_loader.py:359-389) still creates CUDA tensors for the draft
midlayer (Eagle3DecoderLayer / fc / norms), so the RW snapshot gets
committed to GMS with garbage bytes for those ranges. RO clients then
zero-copy the garbage. Correctness still holds for the target decode
path, but draft acceptance collapses, so this reads as a silent perf
regression rather than a crash.

Mirror the AUTO path's draft-weight load inside the GMS RW branch. The
draft tensors allocated earlier under `gms_backend.mem_pool_scope`
already live in the GMS pool; calling `model.load_draft_weights` runs
`copy_()` into them so they hold real weights at publish time.

Signed-off-by: Schwinn Saereesitthipitak <schwinns@nvidia.com>
@galletas1712
galletas1712 force-pushed the schwinns/gms-refactor-20260423 branch from 18d83f4 to 8974e44 Compare April 23, 2026 21:24
galletas1712 added a commit to ai-dynamo/dynamo that referenced this pull request Apr 23, 2026
…pile

Opened NVIDIA/TensorRT-LLM#13394 to produce an official CI tarball for
the galletas1712/TensorRT-LLM schwinns/gms-refactor-20260423 branch. That
pipeline publishes wheels to
urm.nvidia.com/artifactory/sw-tensorrt-generic/llm-artifacts/LLM/main/L0_MergeRequest_PR/<PR#>/,
which is orders of magnitude faster than having the Dynamo BuildKit pool
compile the fork wheel on every run (the from-source path hits scheduling
or OOM limits on the dynamo-builder-fallback node pool).

Add a new, preferred path that downloads and extracts that tarball
instead of compiling:

- .github/workflows/build-on-demand.yml: expose
  'trtllm_artifact_url' (amd64) and 'trtllm_artifact_gh200_url' (arm64/Grace)
  as workflow_dispatch inputs and forward them to trtllm-pipeline.

- .github/workflows/build-flavor.yml: accept and forward the same two
  inputs to the build-flavor composite action.

- .github/actions/build-flavor/action.yml: extend the 'Prebuild
  TensorRT-LLM wheel' step to prefer the per-arch artifact URL when set.
  It downloads the tarball, extracts tensorrt_llm-*.whl, and drops it
  into the same buildx build-context directory the Dockerfile expects
  (both at OUTPUT_DIR/ and OUTPUT_DIR/build/ for compatibility). If no
  URL is provided, the step falls back to the existing from-source
  compile via container/build_trtllm_wheel.sh.

- container/build_trtllm_wheel.sh: default TRTLLM_CUDA_ARCHS to '100-real'
  (SM100 / B200) instead of '90-real'. nscale single-node failover
  targets B200; SM90 was the earlier Kimi-on-H200 assumption. Callers
  can still override via the env var.

- container/context.yaml: bump github_trtllm_commit to
  8974e4470aafe7d73f7eee07d55769c81f3c396f, the re-signed-off tip of
  galletas1712/TensorRT-LLM schwinns/gms-refactor-20260423 required by
  NVIDIA/TensorRT-LLM#13394's DCO gate. Same tree as the previous
  18d83f4f commit, just with Signed-off-by trailers added.

Signed-off-by: Schwinn Saereesitthipitak <schwinns@nvidia.com>
@galletas1712
galletas1712 marked this pull request as ready for review April 23, 2026 21:29
@galletas1712
galletas1712 requested review from a team as code owners April 23, 2026 21:29
@galletas1712
galletas1712 marked this pull request as draft April 23, 2026 21:29
@galletas1712

Copy link
Copy Markdown
Collaborator Author

/bot run

@coderabbitai

coderabbitai Bot commented Apr 23, 2026

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

Introduces GPU Memory Service (GMS) integration with virtual memory management, including a new weight loading format, backend abstraction for GPU memory operations, OOM retry mechanisms for virtual memory, KV cache sleep/wakeup control, pre-sharded weight detection in linear modules, and lifecycle management for GMS-backed weight loading.

Changes

Cohort / File(s) Summary
GMS Backend Implementation
tensorrt_llm/_torch/memory/gpu_memory_backend.py, tensorrt_llm/_torch/memory/__init__.py
Introduces GPUMemoryBackend protocol and GMSBackend class for GPU memory service operations: socket connection, RW/RO mode switching, weight materialization, tensor mapping, GMS client lifecycle management, and mem_pool_scope context management.
Model Loading with GMS Support
tensorrt_llm/_torch/pyexecutor/model_loader.py, tensorrt_llm/_torch/modules/linear.py
Extends ModelLoader with GMS backend integration, weight mapper state, and conditional RW/RO loading paths; adds _weights_presharded flag to Linear module to skip tensor-parallel sharding when weights are pre-shared; introduces GMS write finalization and cleanup methods.
Virtual Memory Retry and Sleep/Wakeup
tensorrt_llm/_torch/virtual_memory.py, tensorrt_llm/executor/worker.py, tensorrt_llm/executor/ray_gpu_worker.py
Adds run_with_oom_retry helper for OOM recovery with CUDA cleanup; implements sleep()/wakeup() worker methods for tagged resource release/materialization; adds prefix-cache reset on KV cache wakeup.
Configuration and Executor Support
tensorrt_llm/llmapi/llm_args.py, tensorrt_llm/_torch/pyexecutor/py_executor_creator.py, tensorrt_llm/_torch/model_config.py, tensorrt_llm/llmapi/llm.py
Adds LoadFormat.GMS enum; extends TorchLlmArgs with GMS configuration fields (gms_socket_path, gms_mode, gms_tag); centralizes KV cache manager building with OOM retry wrapping; improves shared-filesystem lock error detection; adds collective RPC fallback to direct executor methods.
C++ Virtual Memory Rollback
cpp/tensorrt_llm/runtime/virtualMemory.cpp, cpp/tests/unit_tests/runtime/virtualMemoryTest.cpp
Refactors materializeWithTag exception rollback to use shared rollbackMemory lambda with early-exit for already-released chunks; updates test to verify setup-time failures and correct rollback behavior without bad-handle retention.
Unit Tests for GMS and Virtual Memory
tests/unittest/_torch/memory/test_gms_backend.py, tests/unittest/_torch/misc/test_py_executor_creator_retry.py, tests/unittest/_torch/misc/test_virtual_memory_retry.py, tests/unittest/_torch/modules/test_load_weight_shard.py, tests/unittest/_torch/test_model_config.py
Comprehensive test suites validating GMS backend construction, connection, RW/RO modes, finalize flow, OOM retry mechanics, presharded weight loading, and shared-filesystem lock error handling.
Integration and API Tests
tests/unittest/_torch/ray_orchestrator/single_gpu/test_llm_sleep.py, tests/unittest/executor/test_ray_gpu_worker_sleep.py, tests/unittest/llmapi/test_gms_args.py, tests/unittest/api_stability/references/llm.yaml
End-to-end tests for sleep/wakeup cycles and prefix-cache resets; validation of GMS argument parsing and cross-field validation; updated API stability reference.
Documentation
DYNAMO_FORK.md
Introduces fork documentation specifying repository ownership, branch synchronization model, PR targeting, and consumption mechanics via ai-dynamo/dynamo's configuration pins.

Sequence Diagram(s)

sequenceDiagram
    participant ML as ModelLoader
    participant GMS as GMSBackend
    participant GMSClient as GMS Client
    participant CUDA as CUDA/Torch
    
    ML->>GMS: connect()
    GMS->>GMSClient: Initialize socket connection
    GMSClient-->>GMS: Connected (RW mode)
    GMS-->>ML: Connection success
    
    rect rgba(0, 128, 255, 0.5)
    Note over ML,CUDA: GMS RW Mode - Load Weights
    ML->>GMS: mem_pool_scope()
    GMS->>GMSClient: Activate memory pool
    ML->>CUDA: Allocate & load weights
    ML->>GMS: move_untracked_params()
    GMS->>GMSClient: Map parameter tensors to GMS
    ML->>GMS: defer_finalize_write()
    GMS->>GMS: Store pending model
    end
    
    rect rgba(0, 200, 0, 0.5)
    Note over ML,CUDA: Executor Pre-Start
    ML->>GMS: finalize_pending_write()
    GMS->>GMSClient: Register tensors & finalize write
    GMS->>CUDA: Synchronize & transition to RO
    GMS-->>ML: Committed GiB
    end
Loading
sequenceDiagram
    participant Executor as Executor
    participant Worker as Worker
    participant VirtualMem as Virtual Memory
    participant CUDA as CUDA
    
    rect rgba(100, 150, 255, 0.5)
    Note over Executor,CUDA: Sleep Operation
    Executor->>Worker: sleep(sleep_tags)
    Worker->>VirtualMem: release_with_tag(*tags)
    VirtualMem->>CUDA: Release tagged resources
    end
    
    rect rgba(150, 200, 100, 0.5)
    Note over Executor,CUDA: Wakeup Operation
    Executor->>Worker: wakeup(wakeup_tags)
    Worker->>VirtualMem: materialize_with_tag(*tags)
    loop OOM Retry
        VirtualMem->>CUDA: Materialize resources
        alt OOM Error
            CUDA-->>VirtualMem: RuntimeError (OOM)
            VirtualMem->>CUDA: Synchronize & cleanup
            VirtualMem->>VirtualMem: sleep(1.0)
            VirtualMem->>CUDA: Retry materialization
        else Success
            CUDA-->>VirtualMem: Success
        end
    end
    alt KV_CACHE in tags
        Worker->>CUDA: reset_prefix_cache()
    end
    Worker->>CUDA: Synchronize
    end
Loading
sequenceDiagram
    participant GMS as GMSBackend
    participant GMSClient as GMS Client
    participant Engine as Engine
    
    rect rgba(200, 150, 100, 0.5)
    Note over GMS,Engine: GMS RO Mode - Materialize Weights
    GMS->>Engine: post_load_weights()
    GMS->>Engine: prebind_shared_draft_submodules()
    GMS->>GMS: materialize_module()
    GMS->>GMSClient: Materialize via GMS
    GMSClient-->>GMS: Module weights materialized
    GMS->>Engine: post_load_weights()
    GMS->>GMS: Mark _is_rw = False
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 11.41% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the PR's primary purpose: a draft/proof-of-concept build for the Kimi K2.5 + Eagle3 shadow-failover stack with an explicit 'do not merge' marker.
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.
Description check ✅ Passed PR description is detailed and explains the proof-of-concept nature, rationale for all changes, validation results, and known follow-ups comprehensively.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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

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

Actionable comments posted: 7

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/model_config.py (1)

54-75: ⚠️ Potential issue | 🟠 Major

Don’t catch caller OSErrors as lock failures.

This try/except still wraps the yield, so any OSError or PermissionError raised inside with config_file_lock(): is treated as a lock-acquisition problem. In the new OSError path that can fall into the tempdir/unlocked retry logic and even attempt a second yield, which obscures the real config-load failure and can trip contextmanager runtime errors. Limit the retry handling to lock acquire/release only.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tensorrt_llm/_torch/model_config.py` around lines 54 - 75, The context
manager config_file_lock currently wraps the inner yield in a broad try/except,
so exceptions raised by the caller inside "with config_file_lock()" (e.g.,
OSError or PermissionError) are being mistaken for lock acquisition failures and
retried; change the logic so only the lock acquisition/release phases are
guarded for filelock.Timeout/PermissionError/OSError (checking
_is_shared_fs_lock_error for OSError), while the actual yielded block is
executed outside that except handling (i.e., perform lock.acquire() in a
try/except, on failure fall back to tmp_lock acquisition or unlocked path, but
once a lock is successfully acquired use a plain try/finally around
lock.release() and perform the yield without catching exceptions from the
caller), referencing config_file_lock, lock, tmp_lock and
_is_shared_fs_lock_error to locate and refactor the guarded sections.
tensorrt_llm/_torch/pyexecutor/model_loader.py (1)

441-456: ⚠️ Potential issue | 🟠 Major

Avoid calling post_load_weights() before GMS RO materialization.

When meta init succeeds, the LoadFormat.GMS path skips the generic meta→CUDA allocation branch, so this call can run while parameters are still meta tensors. The RO branch then runs post_load_weights() a second time after materialize_module(). Several implementations in tensorrt_llm/_torch/modules/linear.py rewrite parameter storage in post_load_weights(), so this can either fail before import or repack the imported tensors twice. This needs a separate pre-materialization hook, not a double post_load_weights() pass.

Also applies to: 551-560

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tensorrt_llm/_torch/pyexecutor/model_loader.py` around lines 441 - 456, The
current code calls post_load_weights() before GMS RO materialization which
allows post_load_weights() to run on meta tensors (or causes a second run after
materialize_module()), so change the logic to skip calling post_load_weights()
for the LoadFormat.GMS path until after materialize_module() completes: detect
the GMS branch (LoadFormat.GMS) and avoid invoking post_load_weights() in the
pre-materialization phase (the branch that uses init_meta_tensor and
model._apply), then invoke a dedicated post-materialize hook (or call
post_load_weights() once) immediately after materialize_module() finishes so
modules like those in tensorrt_llm/_torch/modules/linear.py only run their
storage-rewriting code once on concrete CUDA storage.
🧹 Nitpick comments (3)
tensorrt_llm/_torch/virtual_memory.py (1)

17-19: Minor: __all__ is not sorted.

Static analysis indicates __all__ should be sorted alphabetically.

♻️ Proposed fix
 __all__ = [
-    "RestoreMode", "maybe_scope", "scope", "release_with_tag",
-    "materialize_with_tag", "run_with_oom_retry"
+    "RestoreMode", "materialize_with_tag", "maybe_scope", "release_with_tag",
+    "run_with_oom_retry", "scope"
 ]
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tensorrt_llm/_torch/virtual_memory.py` around lines 17 - 19, The __all__ list
in this module is unsorted; update the __all__ assignment so the exported names
are in alphabetical order (e.g., sort the strings "RestoreMode", "maybe_scope",
"materialize_with_tag", "release_with_tag", "run_with_oom_retry", "scope") to
satisfy static analysis; modify the __all__ declaration (the variable named
__all__) to contain the same items sorted lexicographically while preserving the
existing string names and commas.
tensorrt_llm/_torch/memory/__init__.py (1)

4-6: Minor: __all__ is not sorted alphabetically.

♻️ Proposed fix
-from .gpu_memory_backend import GMSBackend, GPUMemoryBackend
+from .gpu_memory_backend import GMSBackend, GPUMemoryBackend

-__all__ = ["GPUMemoryBackend", "GMSBackend"]
+__all__ = ["GMSBackend", "GPUMemoryBackend"]
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tensorrt_llm/_torch/memory/__init__.py` around lines 4 - 6, The exported
names in __all__ are not alphabetized; update the __all__ list to be sorted
alphabetically so "GMSBackend" comes before "GPUMemoryBackend" (i.e., change
__all__ = ["GPUMemoryBackend", "GMSBackend"] to __all__ = ["GMSBackend",
"GPUMemoryBackend"]) to keep exports consistent and easier to maintain.
tensorrt_llm/llmapi/llm_args.py (1)

3549-3554: Constrain gms_mode at the field type instead of validating a free-form string later.

gms_mode only accepts three values, so making it a Literal["auto", "rw", "ro"] gives you tighter schema generation and earlier Pydantic validation. The model validator can then stay focused on the load_format cross-field logic.

♻️ Suggested change
-    gms_mode: Optional[str] = Field(
+    gms_mode: Literal["auto", "rw", "ro"] = Field(
         default="auto",
         description="GMS operating mode: 'auto', 'rw', or 'ro'. "
         "Only used when load_format='GMS'.",
         status="prototype",
     )
@@
     `@model_validator`(mode="after")
     def validate_gms_config(self) -> 'TorchLlmArgs':
-        if self.load_format == LoadFormat.GMS and self.gms_mode not in (
-                "auto", "rw", "ro"):
-            raise ValueError(
-                f"gms_mode must be 'auto', 'rw', or 'ro', got '{self.gms_mode}'."
-            )
-
         if self.gms_socket_path is not None and self.load_format != LoadFormat.GMS:
             logger.warning(
                 "gms_socket_path is set but load_format is '%s', not 'GMS'. "
                 "The gms_socket_path will be ignored. Set load_format='GMS' "
                 "to enable GPU Memory Service.", self.load_format.name)

As per coding guidelines, "Use Literal[\"value1\", \"value2\"] instead of str in Python Pydantic fields when only certain values are allowed".

Also applies to: 3775-3781

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tensorrt_llm/llmapi/llm_args.py` around lines 3549 - 3554, Change the
gms_mode Field from an unconstrained Optional[str] to a
Literal["auto","rw","ro"] (keep the default "auto" and existing
description/status) and import Literal (from typing or typing_extensions as used
elsewhere) so Pydantic enforces allowed values at schema/validation time; update
or remove any redundant runtime checks in validators that only validated
gms_mode, and apply the same Literal refactor to the other similar field noted
in the file (the second occurrence with the same three allowed values) so both
use Literal for stricter typing and earlier validation.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@DYNAMO_FORK.md`:
- Around line 41-46: Update the comment for the trtllm YAML block to remove the
incorrect claim that github_trtllm_commit is "Used by install_tensorrt.sh";
instead either state where that variable is actually consumed (e.g., the
pipeline/job or script that checks out the trtllm repo or the installer that
builds the wheel) or delete the misleading usage note entirely. Locate the
trtllm block and the identifier github_trtllm_commit in DYNAMO_FORK.md and edit
the descriptive line so it accurately reflects the real consumer (or omits the
installer reference to install_tensorrt.sh).

In `@tensorrt_llm/_torch/memory/gpu_memory_backend.py`:
- Around line 134-142: Change has_committed_weights to rely on the internal
_is_rw flag instead of reading self._client.granted_lock_type: return False if
self._client is None else return not self._is_rw (or equivalent logic that
treats _is_rw==False as "committed"). Ensure finalize_write already flips
self._is_rw (no change) and update cleanup() to reset self._is_rw = False so
connection state is consistent after teardown; also replace other helpers that
currently inspect self._client.granted_lock_type with the same _is_rw-based
check to keep behavior consistent (referenced symbols: has_committed_weights,
finalize_write, cleanup, _is_rw, self._client.granted_lock_type).

In `@tensorrt_llm/_torch/virtual_memory.py`:
- Around line 150-168: run_with_oom_retry currently loops forever on OOMs; make
it accept a configurable max retry count (e.g., max_retries: int =
DEFAULT_MAX_OOM_RETRIES) or read from a module-level constant, increment retries
as now but break when retries > max_retries and raise a clear exception; keep
existing behavior for non-OOM errors via _is_oom_error, still call
_cleanup_after_oom() and sleep using _OOM_RETRY_INTERVAL_SECONDS between
attempts, and log an error when the retry limit is reached before raising so
callers know it exhausted retries (update the function signature and logs
referencing run_with_oom_retry, _is_oom_error, _cleanup_after_oom, and
_OOM_RETRY_INTERVAL_SECONDS).

In `@tensorrt_llm/executor/ray_gpu_worker.py`:
- Around line 298-299: The call to self.engine.reset_prefix_cache when
ExecutorMemoryType.KV_CACHE is in tags can raise AttributeError because some
engine implementations lack that method; mirror the sibling check in worker.py
by guarding the call with hasattr(self.engine, "reset_prefix_cache") so only
call self.engine.reset_prefix_cache() if that attribute exists, keeping the
conditional block using ExecutorMemoryType.KV_CACHE and the same behavior
otherwise.

In `@tests/unittest/_torch/misc/test_py_executor_creator_retry.py`:
- Around line 1-5: Add the standard NVIDIA copyright header to the top of this
new Python source file (above the existing imports), including the correct year
of latest meaningful modification; ensure the header matches the project's other
headers exactly and remains as the first lines of the file so symbols like the
imported virtual_memory and py_executor_creator stay unchanged.

In `@tests/unittest/_torch/misc/test_virtual_memory_retry.py`:
- Around line 1-57: Add the required NVIDIA copyright header to the top of this
test file (the module containing test_materialize_with_tag_retries_oom and
test_materialize_with_tag_does_not_retry_non_oom) so the file begins with the
standard NVIDIA license/comment block; ensure the header precedes any imports
and retains the existing imports and test code unchanged.

In `@tests/unittest/executor/test_ray_gpu_worker_sleep.py`:
- Around line 1-5: This new test module test_ray_gpu_worker_sleep.py is missing
the required NVIDIA SPDX/copyright header; add the same SPDX-License-Identifier
line and full NVIDIA copyright/license header block used in other new Python
files in the repo at the top of this file (before the imports), updating the
year to the latest meaningful modification year; ensure the header is a Python
comment block and appears above the existing imports (the file imports
tensorrt_llm.executor.ray_gpu_worker and ExecutorMemoryType from
tensorrt_llm.llmapi.llm_args so place the header before those lines).

---

Outside diff comments:
In `@tensorrt_llm/_torch/model_config.py`:
- Around line 54-75: The context manager config_file_lock currently wraps the
inner yield in a broad try/except, so exceptions raised by the caller inside
"with config_file_lock()" (e.g., OSError or PermissionError) are being mistaken
for lock acquisition failures and retried; change the logic so only the lock
acquisition/release phases are guarded for
filelock.Timeout/PermissionError/OSError (checking _is_shared_fs_lock_error for
OSError), while the actual yielded block is executed outside that except
handling (i.e., perform lock.acquire() in a try/except, on failure fall back to
tmp_lock acquisition or unlocked path, but once a lock is successfully acquired
use a plain try/finally around lock.release() and perform the yield without
catching exceptions from the caller), referencing config_file_lock, lock,
tmp_lock and _is_shared_fs_lock_error to locate and refactor the guarded
sections.

In `@tensorrt_llm/_torch/pyexecutor/model_loader.py`:
- Around line 441-456: The current code calls post_load_weights() before GMS RO
materialization which allows post_load_weights() to run on meta tensors (or
causes a second run after materialize_module()), so change the logic to skip
calling post_load_weights() for the LoadFormat.GMS path until after
materialize_module() completes: detect the GMS branch (LoadFormat.GMS) and avoid
invoking post_load_weights() in the pre-materialization phase (the branch that
uses init_meta_tensor and model._apply), then invoke a dedicated
post-materialize hook (or call post_load_weights() once) immediately after
materialize_module() finishes so modules like those in
tensorrt_llm/_torch/modules/linear.py only run their storage-rewriting code once
on concrete CUDA storage.

---

Nitpick comments:
In `@tensorrt_llm/_torch/memory/__init__.py`:
- Around line 4-6: The exported names in __all__ are not alphabetized; update
the __all__ list to be sorted alphabetically so "GMSBackend" comes before
"GPUMemoryBackend" (i.e., change __all__ = ["GPUMemoryBackend", "GMSBackend"] to
__all__ = ["GMSBackend", "GPUMemoryBackend"]) to keep exports consistent and
easier to maintain.

In `@tensorrt_llm/_torch/virtual_memory.py`:
- Around line 17-19: The __all__ list in this module is unsorted; update the
__all__ assignment so the exported names are in alphabetical order (e.g., sort
the strings "RestoreMode", "maybe_scope", "materialize_with_tag",
"release_with_tag", "run_with_oom_retry", "scope") to satisfy static analysis;
modify the __all__ declaration (the variable named __all__) to contain the same
items sorted lexicographically while preserving the existing string names and
commas.

In `@tensorrt_llm/llmapi/llm_args.py`:
- Around line 3549-3554: Change the gms_mode Field from an unconstrained
Optional[str] to a Literal["auto","rw","ro"] (keep the default "auto" and
existing description/status) and import Literal (from typing or
typing_extensions as used elsewhere) so Pydantic enforces allowed values at
schema/validation time; update or remove any redundant runtime checks in
validators that only validated gms_mode, and apply the same Literal refactor to
the other similar field noted in the file (the second occurrence with the same
three allowed values) so both use Literal for stricter typing and earlier
validation.
🪄 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: 79e51e54-db55-4139-9971-cba9cff4971f

📥 Commits

Reviewing files that changed from the base of the PR and between 56b0cbf and 8974e44.

📒 Files selected for processing (23)
  • DYNAMO_FORK.md
  • cpp/tensorrt_llm/runtime/virtualMemory.cpp
  • cpp/tests/unit_tests/runtime/virtualMemoryTest.cpp
  • tensorrt_llm/_torch/memory/__init__.py
  • tensorrt_llm/_torch/memory/gpu_memory_backend.py
  • tensorrt_llm/_torch/model_config.py
  • tensorrt_llm/_torch/modules/linear.py
  • tensorrt_llm/_torch/pyexecutor/model_loader.py
  • tensorrt_llm/_torch/pyexecutor/py_executor_creator.py
  • tensorrt_llm/_torch/virtual_memory.py
  • tensorrt_llm/executor/ray_gpu_worker.py
  • tensorrt_llm/executor/worker.py
  • tensorrt_llm/llmapi/llm.py
  • tensorrt_llm/llmapi/llm_args.py
  • tests/unittest/_torch/memory/test_gms_backend.py
  • tests/unittest/_torch/misc/test_py_executor_creator_retry.py
  • tests/unittest/_torch/misc/test_virtual_memory_retry.py
  • tests/unittest/_torch/modules/test_load_weight_shard.py
  • tests/unittest/_torch/ray_orchestrator/single_gpu/test_llm_sleep.py
  • tests/unittest/_torch/test_model_config.py
  • tests/unittest/api_stability/references/llm.yaml
  • tests/unittest/executor/test_ray_gpu_worker_sleep.py
  • tests/unittest/llmapi/test_gms_args.py

Comment thread DYNAMO_FORK.md
Comment on lines +41 to +46
```yaml
trtllm:
pip_wheel: tensorrt-llm==<version> # PyPI pin (upstream)
trtllm_wheel_image: <wheel image> # Release image (upstream)
github_trtllm_commit: <sha-or-tag> # Used by install_tensorrt.sh
```

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.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Check if install_tensorrt.sh references github_trtllm_commit

# Search for github_trtllm_commit in the install_tensorrt.sh script
rg -n 'github_trtllm_commit' docker/common/install_tensorrt.sh

# Also check if there are any other scripts that might use it
rg -n 'github_trtllm_commit' --type=sh

Repository: NVIDIA/TensorRT-LLM

Length of output: 45


Line 45 incorrectly claims github_trtllm_commit is used by install_tensorrt.sh.

The comment states github_trtllm_commit is "Used by install_tensorrt.sh", but this variable is not referenced in that script or any other shell script in the repository. Update the comment to clarify where and how github_trtllm_commit is actually consumed, or remove the misleading reference.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@DYNAMO_FORK.md` around lines 41 - 46, Update the comment for the trtllm YAML
block to remove the incorrect claim that github_trtllm_commit is "Used by
install_tensorrt.sh"; instead either state where that variable is actually
consumed (e.g., the pipeline/job or script that checks out the trtllm repo or
the installer that builds the wheel) or delete the misleading usage note
entirely. Locate the trtllm block and the identifier github_trtllm_commit in
DYNAMO_FORK.md and edit the descriptive line so it accurately reflects the real
consumer (or omits the installer reference to install_tensorrt.sh).

Comment on lines +134 to +142
def has_committed_weights(self) -> bool:
if self._client is None:
return False
try:
from gpu_memory_service.common.locks import GrantedLockType

return self._client.granted_lock_type == GrantedLockType.RO
except Exception:
return False

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.

⚠️ Potential issue | 🟠 Major

has_committed_weights() can stay false after a successful publish.

finalize_write() only flips self._is_rw, but has_committed_weights() still reads self._client.granted_lock_type. If the client object does not update that field in place, the writer will report “no committed weights” immediately after commit. Base this helper on _is_rw instead, and reset _is_rw in cleanup() so the connection state stays internally consistent.

Also applies to: 215-215, 283-285

🧰 Tools
🪛 Ruff (0.15.11)

[warning] 141-141: Do not catch blind exception: Exception

(BLE001)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tensorrt_llm/_torch/memory/gpu_memory_backend.py` around lines 134 - 142,
Change has_committed_weights to rely on the internal _is_rw flag instead of
reading self._client.granted_lock_type: return False if self._client is None
else return not self._is_rw (or equivalent logic that treats _is_rw==False as
"committed"). Ensure finalize_write already flips self._is_rw (no change) and
update cleanup() to reset self._is_rw = False so connection state is consistent
after teardown; also replace other helpers that currently inspect
self._client.granted_lock_type with the same _is_rw-based check to keep behavior
consistent (referenced symbols: has_committed_weights, finalize_write, cleanup,
_is_rw, self._client.granted_lock_type).

Comment thread tensorrt_llm/_torch/virtual_memory.py Outdated
Comment on lines +150 to +168
def run_with_oom_retry(action: Callable[[], T], *, description: str) -> T:
retries = 0
while True:
try:
result = action()
if retries:
logger.info("%s succeeded after %d retries", description,
retries)
return result
except Exception as error:
if not _is_oom_error(error):
raise

retries += 1
logger.warning(
"%s hit OOM, waiting for capacity before retry %d: %s",
description, retries, error)
_cleanup_after_oom()
time.sleep(_OOM_RETRY_INTERVAL_SECONDS)

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.

⚠️ Potential issue | 🟠 Major

Unbounded retry loop may cause infinite hangs under persistent OOM.

run_with_oom_retry retries indefinitely on OOM errors. If GPU memory cannot be freed (e.g., due to a memory leak or external process), this will loop forever with 1-second sleeps. Consider adding a configurable maximum retry count.

🛡️ Proposed fix to add retry limit
+_OOM_MAX_RETRIES = 10  # or expose as parameter
+
 def run_with_oom_retry(action: Callable[[], T], *, description: str) -> T:
     retries = 0
     while True:
         try:
             result = action()
             if retries:
                 logger.info("%s succeeded after %d retries", description,
                             retries)
             return result
         except Exception as error:
             if not _is_oom_error(error):
                 raise
 
             retries += 1
+            if retries > _OOM_MAX_RETRIES:
+                logger.error("%s failed after %d retries", description, retries)
+                raise
             logger.warning(
                 "%s hit OOM, waiting for capacity before retry %d: %s",
                 description, retries, error)
             _cleanup_after_oom()
             time.sleep(_OOM_RETRY_INTERVAL_SECONDS)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tensorrt_llm/_torch/virtual_memory.py` around lines 150 - 168,
run_with_oom_retry currently loops forever on OOMs; make it accept a
configurable max retry count (e.g., max_retries: int = DEFAULT_MAX_OOM_RETRIES)
or read from a module-level constant, increment retries as now but break when
retries > max_retries and raise a clear exception; keep existing behavior for
non-OOM errors via _is_oom_error, still call _cleanup_after_oom() and sleep
using _OOM_RETRY_INTERVAL_SECONDS between attempts, and log an error when the
retry limit is reached before raising so callers know it exhausted retries
(update the function signature and logs referencing run_with_oom_retry,
_is_oom_error, _cleanup_after_oom, and _OOM_RETRY_INTERVAL_SECONDS).

Comment on lines +298 to +299
if ExecutorMemoryType.KV_CACHE in tags:
self.engine.reset_prefix_cache()

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.

⚠️ Potential issue | 🟡 Minor

Missing hasattr check for reset_prefix_cache.

The sibling implementation in tensorrt_llm/executor/worker.py:97-98 guards the call with hasattr(self.engine, 'reset_prefix_cache'). This check is missing here, which could raise AttributeError if the engine doesn't implement reset_prefix_cache.

🛡️ Proposed fix for consistency with worker.py
             if ExecutorMemoryType.KV_CACHE in tags:
-                self.engine.reset_prefix_cache()
+                if hasattr(self.engine, 'reset_prefix_cache'):
+                    self.engine.reset_prefix_cache()
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tensorrt_llm/executor/ray_gpu_worker.py` around lines 298 - 299, The call to
self.engine.reset_prefix_cache when ExecutorMemoryType.KV_CACHE is in tags can
raise AttributeError because some engine implementations lack that method;
mirror the sibling check in worker.py by guarding the call with
hasattr(self.engine, "reset_prefix_cache") so only call
self.engine.reset_prefix_cache() if that attribute exists, keeping the
conditional block using ExecutorMemoryType.KV_CACHE and the same behavior
otherwise.

Comment on lines +1 to +5
import pytest

from tensorrt_llm._torch import virtual_memory
from tensorrt_llm._torch.pyexecutor import py_executor_creator

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.

⚠️ Potential issue | 🟡 Minor

Add the required NVIDIA file header.

This is a new Python source file, so it should include the standard NVIDIA copyright header used elsewhere in the repo.

📝 Suggested change
+# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
+# SPDX-License-Identifier: Apache-2.0
+
 import pytest
 
 from tensorrt_llm._torch import virtual_memory

As 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".

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import pytest
from tensorrt_llm._torch import virtual_memory
from tensorrt_llm._torch.pyexecutor import py_executor_creator
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
import pytest
from tensorrt_llm._torch import virtual_memory
from tensorrt_llm._torch.pyexecutor import py_executor_creator
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/unittest/_torch/misc/test_py_executor_creator_retry.py` around lines 1
- 5, Add the standard NVIDIA copyright header to the top of this new Python
source file (above the existing imports), including the correct year of latest
meaningful modification; ensure the header matches the project's other headers
exactly and remains as the first lines of the file so symbols like the imported
virtual_memory and py_executor_creator stay unchanged.

Comment on lines +1 to +57
import pytest

from tensorrt_llm._torch import virtual_memory


class _RetryingManager:

def __init__(self):
self.calls = 0

def materialize_with_tag(self, tag: str) -> int:
self.calls += 1
if self.calls == 1:
raise RuntimeError(f"{tag} out of memory")
return 1


def test_materialize_with_tag_retries_oom(monkeypatch):
manager = _RetryingManager()
sleeps: list[float] = []
sync_calls: list[None] = []
empty_cache_calls: list[None] = []
gc_calls: list[int] = []

monkeypatch.setattr(virtual_memory, "get_virtual_memory_manager",
lambda: manager)
monkeypatch.setattr(virtual_memory.torch.cuda, "synchronize",
lambda: sync_calls.append(None))
monkeypatch.setattr(virtual_memory.torch.cuda, "empty_cache",
lambda: empty_cache_calls.append(None))
monkeypatch.setattr(virtual_memory.gc, "collect",
lambda: gc_calls.append(1) or 0)
monkeypatch.setattr(virtual_memory.time, "sleep", sleeps.append)

assert virtual_memory.materialize_with_tag("kv_cache") == 1
assert manager.calls == 2
assert sleeps == [1.0]
assert len(sync_calls) == 1
assert len(empty_cache_calls) == 1
assert gc_calls == [1]


def test_materialize_with_tag_does_not_retry_non_oom(monkeypatch):
class _FailingManager:

def materialize_with_tag(self, tag: str) -> int:
raise RuntimeError(f"{tag} permission denied")

sleeps: list[float] = []
monkeypatch.setattr(virtual_memory, "get_virtual_memory_manager",
lambda: _FailingManager())
monkeypatch.setattr(virtual_memory.time, "sleep", sleeps.append)

with pytest.raises(RuntimeError, match="permission denied"):
virtual_memory.materialize_with_tag("kv_cache")

assert not sleeps

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.

⚠️ Potential issue | 🟡 Minor

Missing NVIDIA copyright header.

Per coding guidelines, all source files should include the NVIDIA copyright header.

📄 Proposed fix
+# SPDX-FileCopyrightText: Copyright (c) 2022-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
+# SPDX-License-Identifier: Apache-2.0
+
 import pytest

 from tensorrt_llm._torch import virtual_memory
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import pytest
from tensorrt_llm._torch import virtual_memory
class _RetryingManager:
def __init__(self):
self.calls = 0
def materialize_with_tag(self, tag: str) -> int:
self.calls += 1
if self.calls == 1:
raise RuntimeError(f"{tag} out of memory")
return 1
def test_materialize_with_tag_retries_oom(monkeypatch):
manager = _RetryingManager()
sleeps: list[float] = []
sync_calls: list[None] = []
empty_cache_calls: list[None] = []
gc_calls: list[int] = []
monkeypatch.setattr(virtual_memory, "get_virtual_memory_manager",
lambda: manager)
monkeypatch.setattr(virtual_memory.torch.cuda, "synchronize",
lambda: sync_calls.append(None))
monkeypatch.setattr(virtual_memory.torch.cuda, "empty_cache",
lambda: empty_cache_calls.append(None))
monkeypatch.setattr(virtual_memory.gc, "collect",
lambda: gc_calls.append(1) or 0)
monkeypatch.setattr(virtual_memory.time, "sleep", sleeps.append)
assert virtual_memory.materialize_with_tag("kv_cache") == 1
assert manager.calls == 2
assert sleeps == [1.0]
assert len(sync_calls) == 1
assert len(empty_cache_calls) == 1
assert gc_calls == [1]
def test_materialize_with_tag_does_not_retry_non_oom(monkeypatch):
class _FailingManager:
def materialize_with_tag(self, tag: str) -> int:
raise RuntimeError(f"{tag} permission denied")
sleeps: list[float] = []
monkeypatch.setattr(virtual_memory, "get_virtual_memory_manager",
lambda: _FailingManager())
monkeypatch.setattr(virtual_memory.time, "sleep", sleeps.append)
with pytest.raises(RuntimeError, match="permission denied"):
virtual_memory.materialize_with_tag("kv_cache")
assert not sleeps
# SPDX-FileCopyrightText: Copyright (c) 2022-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
import pytest
from tensorrt_llm._torch import virtual_memory
class _RetryingManager:
def __init__(self):
self.calls = 0
def materialize_with_tag(self, tag: str) -> int:
self.calls += 1
if self.calls == 1:
raise RuntimeError(f"{tag} out of memory")
return 1
def test_materialize_with_tag_retries_oom(monkeypatch):
manager = _RetryingManager()
sleeps: list[float] = []
sync_calls: list[None] = []
empty_cache_calls: list[None] = []
gc_calls: list[int] = []
monkeypatch.setattr(virtual_memory, "get_virtual_memory_manager",
lambda: manager)
monkeypatch.setattr(virtual_memory.torch.cuda, "synchronize",
lambda: sync_calls.append(None))
monkeypatch.setattr(virtual_memory.torch.cuda, "empty_cache",
lambda: empty_cache_calls.append(None))
monkeypatch.setattr(virtual_memory.gc, "collect",
lambda: gc_calls.append(1) or 0)
monkeypatch.setattr(virtual_memory.time, "sleep", sleeps.append)
assert virtual_memory.materialize_with_tag("kv_cache") == 1
assert manager.calls == 2
assert sleeps == [1.0]
assert len(sync_calls) == 1
assert len(empty_cache_calls) == 1
assert gc_calls == [1]
def test_materialize_with_tag_does_not_retry_non_oom(monkeypatch):
class _FailingManager:
def materialize_with_tag(self, tag: str) -> int:
raise RuntimeError(f"{tag} permission denied")
sleeps: list[float] = []
monkeypatch.setattr(virtual_memory, "get_virtual_memory_manager",
lambda: _FailingManager())
monkeypatch.setattr(virtual_memory.time, "sleep", sleeps.append)
with pytest.raises(RuntimeError, match="permission denied"):
virtual_memory.materialize_with_tag("kv_cache")
assert not sleeps
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/unittest/_torch/misc/test_virtual_memory_retry.py` around lines 1 - 57,
Add the required NVIDIA copyright header to the top of this test file (the
module containing test_materialize_with_tag_retries_oom and
test_materialize_with_tag_does_not_retry_non_oom) so the file begins with the
standard NVIDIA license/comment block; ensure the header precedes any imports
and retains the existing imports and test code unchanged.

Comment on lines +1 to +5
from contextlib import contextmanager

import tensorrt_llm.executor.ray_gpu_worker as ray_gpu_worker
from tensorrt_llm.llmapi.llm_args import ExecutorMemoryType

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.

⚠️ Potential issue | 🟡 Minor

Add the required SPDX header to this new test file.

This file is new, but it does not include the NVIDIA copyright/license header used by other new Python files in the repo and in this PR.

As per coding guidelines, "All TensorRT-LLM source files must contain an NVIDIA copyright header with the year of latest meaningful modification".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/unittest/executor/test_ray_gpu_worker_sleep.py` around lines 1 - 5,
This new test module test_ray_gpu_worker_sleep.py is missing the required NVIDIA
SPDX/copyright header; add the same SPDX-License-Identifier line and full NVIDIA
copyright/license header block used in other new Python files in the repo at the
top of this file (before the imports), updating the year to the latest
meaningful modification year; ensure the header is a Python comment block and
appears above the existing imports (the file imports
tensorrt_llm.executor.ray_gpu_worker and ExecutorMemoryType from
tensorrt_llm.llmapi.llm_args so place the header before those lines).

@2ez4bz

2ez4bz commented Apr 23, 2026

Copy link
Copy Markdown
Collaborator

/bot help

@github-actions

Copy link
Copy Markdown

GitHub Bot Help

/bot [-h] ['run', 'kill', 'skip', 'reuse-pipeline'] ...

Provide a user friendly way for developers to interact with a Jenkins server.

Run /bot [-h|--help] to print this help message.

See details below for each supported subcommand.

Details

run [--reuse-test (optional)pipeline-id --disable-fail-fast --skip-test --stage-list "A10-PyTorch-1, xxx" --gpu-type "A30, H100_PCIe" --test-backend "pytorch, cpp" --add-multi-gpu-test --only-multi-gpu-test --disable-multi-gpu-test --post-merge --extra-stage "H100_PCIe-TensorRT-Post-Merge-1, xxx" --detailed-log --debug(experimental) --high-priority]

Launch build/test pipelines. All previously running jobs will be killed.

--reuse-test (optional)pipeline-id (OPTIONAL) : Allow the new pipeline to reuse build artifacts and skip successful test stages from a specified pipeline or the last pipeline if no pipeline-id is indicated. If the Git commit ID has changed, this option will be always ignored. The DEFAULT behavior of the bot is to reuse build artifacts and successful test results from the last pipeline.

--disable-reuse-test (OPTIONAL) : Explicitly prevent the pipeline from reusing build artifacts and skipping successful test stages from a previous pipeline. Ensure that all builds and tests are run regardless of previous successes.

--disable-fail-fast (OPTIONAL) : Disable fail fast on build/tests/infra failures.

--skip-test (OPTIONAL) : Skip all test stages, but still run build stages, package stages and sanity check stages. Note: Does NOT update GitHub check status.

--stage-list "A10-PyTorch-1, xxx" (OPTIONAL) : Only run the specified test stages. Supports wildcard * for pattern matching (e.g., "*PerfSanity*" matches all stages containing PerfSanity). Examples: "A10-PyTorch-1, xxx", "PerfSanity". Note: Does NOT update GitHub check status.

--gpu-type "A30, H100_PCIe" (OPTIONAL) : Only run the test stages on the specified GPU types. Examples: "A30, H100_PCIe". Note: Does NOT update GitHub check status.

--test-backend "pytorch, cpp" (OPTIONAL) : Skip test stages which don't match the specified backends. Only support [pytorch, cpp, tensorrt, triton]. Examples: "pytorch, cpp" (does not run test stages with tensorrt or triton backend). Note: Does NOT update GitHub pipeline status.

--only-multi-gpu-test (OPTIONAL) : Only run the multi-GPU tests. Note: Does NOT update GitHub check status.

--disable-multi-gpu-test (OPTIONAL) : Disable the multi-GPU tests. Note: Does NOT update GitHub check status.

--add-multi-gpu-test (OPTIONAL) : Force run the multi-GPU tests in addition to running L0 pre-merge pipeline.

--post-merge (OPTIONAL) : Run the L0 post-merge pipeline instead of the ordinary L0 pre-merge pipeline.

--extra-stage "H100_PCIe-TensorRT-Post-Merge-1, xxx" (OPTIONAL) : Run the ordinary L0 pre-merge pipeline and specified test stages. Supports wildcard * for pattern matching. Examples: --extra-stage "H100_PCIe-TensorRT-Post-Merge-1, xxx", --extra-stage "Post-Merge".

--detailed-log (OPTIONAL) : Enable flushing out all logs to the Jenkins console. This will significantly increase the log volume and may slow down the job.

--debug (OPTIONAL) : Experimental feature. Enable access to the CI container for debugging purpose. Note: Specify exactly one stage in the stage-list parameter to access the appropriate container environment. Note: Does NOT update GitHub check status.

--high-priority (OPTIONAL) : Run the pipeline with high priority. This option is restricted to authorized users only and will route the job to a high-priority queue.

kill

kill

Kill all running builds associated with pull request.

skip

skip --comment COMMENT

Skip testing for latest commit on pull request. --comment "Reason for skipping build/test" is required. IMPORTANT NOTE: This is dangerous since lack of user care and validation can cause top of tree to break.

reuse-pipeline

reuse-pipeline

Reuse a previous pipeline to validate current commit. This action will also kill all currently running builds associated with the pull request. IMPORTANT NOTE: This is dangerous since lack of user care and validation can cause top of tree to break.

@2ez4bz

2ez4bz commented Apr 23, 2026

Copy link
Copy Markdown
Collaborator

/bot run --skip-test

@svc-trtllm-gh-bot svc-trtllm-gh-bot added the Community want to contribute PRs initiated from Community label Apr 23, 2026
@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #45252 [ run ] triggered by Bot. Commit: 8974e44 Link to invocation

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #45252 [ run ] completed with state FAILURE. Commit: 8974e44
/LLM/main/L0_MergeRequest_PR pipeline #35511 (Partly Tested) completed with status: 'FAILURE'

CI Report

⚠️ Action Required:

  • Please check the failed tests and fix your PR
  • If you cannot view the failures, ask the CI triggerer to share details
  • Once fixed, request an NVIDIA team member to trigger CI again

Link to invocation

Copy link
Copy Markdown
Collaborator Author

Porting plan for this proof branch

This PR was useful as the working shadow-failover proof, but it should not be rebased wholesale onto current main. It is based on an older TRT-LLM point and contains an early GMS loader prototype that is now better covered by newer upstream work.

The intended upstream path is to keep this PR as the proof/artifact reference, then port the pieces into a smaller stack on top of:

Proposed stack

  1. Base: GMS weight sharing

    Use [TRTLLM-12440][feat] Add GMS-only weight sharing support #13926 as the base. Do not port this PR's old aaf8a194 feat(torch): add GMS weight-loading prototype implementation as-is. Adapt later failover code to [TRTLLM-12440][feat] Add GMS-only weight sharing support #13926's llm_args.gms_config shape rather than the older flat gms_mode / gms_tag / gms_socket_path fields.

  2. SleepConfig / MPI-safe config

    Depend on or cherry-pick [None][fix] Make SleepConfig picklable by replacing closure lambda in defaultdict #13918 for the pickle fix. When adding the failover preset, add tests for:

    • pickle.loads(pickle.dumps(SleepConfig(...)))
    • pickle.loads(pickle.dumps(TorchLlmArgs(..., sleep_config=SleepConfig(...))))
    • shadow_failover preset expansion
  3. GMS-aware sleep/wake

    Add reversible GMS behavior inside the existing sleep() / wake() path:

    • GMSBackend.total_bytes() for memory accounting
    • RO mapping release on GMS sleep, without deleting server-held weight memory
    • RO reconnect/materialize on wake
    • restore_gms_weights timing
    • preserve CUDA graphs for GMS sleep; do not release graphs in the failover path

    This is an implementation detail of sleep/wake, not a new public lifecycle.

  4. MPI/env propagation and control-action plumbing

    Port the pieces needed to make failover state reach all MPI workers:

    • ENGINE_ID
    • GMS_SOCKET_DIR
    • FAILOVER_LOCK_PATH
    • any needed control-action return values/timing summaries

    This should be a separate PR because missing propagation to MPI workers was one of the main failure modes during pod testing.

  5. VMM wake retry / blocking-on-OOM behavior

    Port the final retry behavior, including the pod-tested retry interval update:

    • C++ VMM retry in cpp/tensorrt_llm/runtime/virtualMemory.cpp
    • Python retry handling in tensorrt_llm/_torch/virtual_memory.py
    • final retry interval from the pod delta: 0.2s

    This is needed so promotion does not crash if the old primary's KV cache is still being released while the promoted engine materializes its own KV cache.

  6. KV memory calibration for GMS shadows

    Port the GMS shadow KV accounting from this proof branch, adapted to [TRTLLM-12440][feat] Add GMS-only weight sharing support #13926:

    • account for GMS-resident weights via GMSBackend.total_bytes()
    • preserve user-provided max_gpu_total_bytes
    • support TRTLLM_GMS_SHADOW_MEMORY_CALIBRATION
    • support TRTLLM_GMS_SHADOW_PEER_MEMORY_RESERVE_BYTES/GIB

    Longer-term, replace the coarse peer reserve with measured parked-footprint accounting, but the tested Kimi bs128 path used TRTLLM_GMS_SHADOW_PEER_MEMORY_RESERVE_GIB=10.

  7. Parked-footprint reduction / MoE memory handling

    Port the useful parts of the parked-footprint work:

    • release/restore MoE communication workspaces
    • release/restore legacy MNNVL MoE workspaces
    • clear reusable memory buffers where safe
    • keep tests for MoE workspace release/restore

    This is what got parked Kimi shadows down to the ~9.6 GiB/rank range in the accepted run.

  8. CUDA graph and autotuner guardrails

    Port the final no-deferral behavior, not the rejected deferred-warmup experiment:

    • TLLM_AUTOTUNER_USE_CUDA_GRAPH=0 to disable autotuner-internal CUDA graph profiling without disabling inference CUDA graphs
    • guard against lazy CUDA graph capture on the first post-failover request
    • graph memory diagnostics/snapshots behind env flags
    • preserve pre-captured inference graphs across GMS sleep

    Do not port the deferred warmup / deferred graph capture behavior as production behavior. It was useful experimentally, but violates the failover invariant because it moves warmup/capture work into promotion.

Validation target from this proof branch

The reference successful run was:

  • Kimi K2.5 + EAGLE3, TP=8, one standby
  • free_gpu_memory_fraction=0.85
  • explicit CUDA graph batch ladder [1,2,4,8,16,32,64,128]
  • autotuner cache hit
  • TLLM_AUTOTUNER_USE_CUDA_GRAPH=0
  • TRTLLM_GMS_SHADOW_PEER_MEMORY_RESERVE_GIB=10
  • no deferred warmup/capture during promotion

Observed acceptance numbers from the artifacts:

  • standby failover ready: ~5.86s
  • SIGKILL -> response: ~6.43s
  • promoted wake dominated by restore_gms_weights: ~4.9-5.0s
  • materialize_vmm: ~0.01-0.04s
  • primary/promoted KV blocks: 58510 / 60232 (~+2.94% standby)
  • response was available post-failover and manually inspected as coherent (failover-ok)

Explicit non-goals for the port

  • Do not port the old GMS loader prototype over [TRTLLM-12440][feat] Add GMS-only weight sharing support #13926.
  • Do not port deferred warmup/cuda graph capture as production behavior.
  • Do not require Dynamo-side wrapper churn for TRT-LLM-owned memory accounting unless absolutely necessary.
  • Keep debug-only tensor audits/logging behind env flags or leave them in artifacts only.

The goal is to turn this proof branch into a reviewable sequence: first establish clean GMS loading, then add reversible GMS sleep/wake, then add MPI propagation, memory calibration, VMM retry, MoE footprint reduction, and CUDA graph/autotuner guardrails.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Community want to contribute PRs initiated from Community

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants