[None][perf] Close Mamba hybrid warmup gap in autotuner warmup - #16177
Conversation
📝 WalkthroughWalkthroughThis PR expands autotuner warmup in the PyTorch model engine to sweep multiple context/generation shape combinations controlled by environment variables (TLLM_AUTOTUNE_SWEEP, TLLM_MAMBA_MULTISEQ_WARMUP), replacing the prior single-shape warmup, and adds a corresponding Mamba2Metadata override (TLLM_MAMBA_WARMUP_FORCE_INITSTATES) to force initial-state flags during warmup passes. ChangesAutotuner Warmup Sweep and Mamba Init-State Override
Estimated code review effort: 3 (Moderate) | ~25 minutes Sequence Diagram(s)sequenceDiagram
participant ModelEngine as PyTorchModelEngine
participant AutoTuner
participant Mamba2Metadata
ModelEngine->>ModelEngine: build autotune_shapes list
loop each shape
ModelEngine->>ModelEngine: set TLLM_MAMBA_WARMUP_FORCE_INITSTATES if needed
ModelEngine->>ModelEngine: create warmup request
ModelEngine->>Mamba2Metadata: prepare()
Mamba2Metadata->>Mamba2Metadata: override has_initial_states_cpu if env set
ModelEngine->>ModelEngine: run forward pass
ModelEngine->>AutoTuner: cache_pp_recv()
ModelEngine->>AutoTuner: cache_pp_send()
ModelEngine->>AutoTuner: clean_pp_flag()
ModelEngine->>ModelEngine: torch.cuda.synchronize()
end
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@tensorrt_llm/_torch/modules/mamba/mamba2_metadata.py`:
- Around line 423-425: `Mamba2Metadata.prepare()` is reading
`TLLM_MAMBA_WARMUP_FORCE_INITSTATES` globally on every call, which makes the
init-state override affect non-warmup requests too. Update `prepare()` to take
and use an explicit warmup-only flag from metadata, and apply the
`has_initial_states_cpu` override only when that flag is set; keep the change
scoped around the `Mamba2Metadata.prepare` logic that sets
`has_initial_states_cpu[:num_contexts]`.
In `@tensorrt_llm/_torch/pyexecutor/model_engine.py`:
- Around line 1272-1275: The mixed warmup shapes added in model_engine.py need
to account for speculative draft tokens before checking the 4/16 thresholds. In
the warmup shape logic that appends the mixed ctx+gen tuples, use the same token
accounting as _create_warmup_request() by subtracting
self.max_total_draft_tokens from the available generation budget or otherwise
ensuring the context budget stays non-negative. Update the guards around the
autotune_shapes.append calls so they only add shapes when the resulting
context/gen split is valid in speculative modes.
🪄 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: ee69f8c8-f247-4525-bedf-dfc3207a8291
📒 Files selected for processing (2)
tensorrt_llm/_torch/modules/mamba/mamba2_metadata.pytensorrt_llm/_torch/pyexecutor/model_engine.py
Address CodeRabbit review on PR NVIDIA#16177: Mamba2Metadata.prepare() was reading TLLM_MAMBA_WARMUP_FORCE_INITSTATES from os.environ on every call. A stray shell export, an inherited env in a forked worker, or an exception-skipped context-manager finally would silently force real prefills onto the HAS_INITSTATES=True path with has_initial_states set for cache-miss contexts, corrupting SSD chunk metadata. Replace the env var with a class-scoped context manager on Mamba2Metadata: * Mamba2Metadata gains a private class attribute _warmup_force_initial_states (default False) and a classmethod contextmanager force_initial_states_for_warmup() that flips it for the wrapped scope and restores the previous value in finally. * prepare() reads Mamba2Metadata._warmup_force_initial_states instead of os.environ. * PyTorchModelEngine._run_mamba_hybrid_warmup() drops the local _force_mamba_initstates_env helper and wraps the force-initstates warmup shape with Mamba2Metadata.force_initial_states_for_warmup(). * Drops `import os` in mamba2_metadata.py (no other users). Behavior is identical to the previous env-var handshake for the warmup shapes; only the coupling mechanism changes. Nothing outside those two warmup forward passes can set the flag. Signed-off-by: Chenfei Zhang <chenfeiz@nvidia.com> Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
8fac3c2 to
b14af3d
Compare
Address CodeRabbit review on PR NVIDIA#16177: Mamba2Metadata.prepare() was reading TLLM_MAMBA_WARMUP_FORCE_INITSTATES from os.environ on every call. A stray shell export, an inherited env in a forked worker, or an exception-skipped context-manager finally would silently force real prefills onto the HAS_INITSTATES=True path with has_initial_states set for cache-miss contexts, corrupting SSD chunk metadata. Replace the env var with a class-scoped context manager on Mamba2Metadata: * Mamba2Metadata gains a private class attribute _warmup_force_initial_states (default False) and a classmethod contextmanager force_initial_states_for_warmup() that flips it for the wrapped scope and restores the previous value in finally. * prepare() reads Mamba2Metadata._warmup_force_initial_states instead of os.environ. * PyTorchModelEngine._run_mamba_hybrid_warmup() drops the local _force_mamba_initstates_env helper and wraps the force-initstates warmup shape with Mamba2Metadata.force_initial_states_for_warmup(). * Drops `import os` in mamba2_metadata.py (no other users). Behavior is identical to the previous env-var handshake for the warmup shapes; only the coupling mechanism changes. Nothing outside those two warmup forward passes can set the flag. Signed-off-by: Chenfei Zhang <chenfeiz@nvidia.com> Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
b14af3d to
6d81735
Compare
Address CodeRabbit review on PR NVIDIA#16177: Mamba2Metadata.prepare() was reading TLLM_MAMBA_WARMUP_FORCE_INITSTATES from os.environ on every call. A stray shell export, an inherited env in a forked worker, or an exception-skipped context-manager finally would silently force real prefills onto the HAS_INITSTATES=True path with has_initial_states set for cache-miss contexts, corrupting SSD chunk metadata. Replace the env var with a class-scoped context manager on Mamba2Metadata: * Mamba2Metadata gains a private class attribute _warmup_force_initial_states (default False) and a classmethod contextmanager force_initial_states_for_warmup() that flips it for the wrapped scope and restores the previous value in finally. * prepare() reads Mamba2Metadata._warmup_force_initial_states instead of os.environ. * PyTorchModelEngine._run_mamba_hybrid_warmup() drops the local _force_mamba_initstates_env helper and wraps the force-initstates warmup shape with Mamba2Metadata.force_initial_states_for_warmup(). * Drops `import os` in mamba2_metadata.py (no other users). Behavior is identical to the previous env-var handshake for the warmup shapes; only the coupling mechanism changes. Nothing outside those two warmup forward passes can set the flag. Signed-off-by: Chenfei Zhang <chenfeiz@nvidia.com> Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
6d81735 to
ffdcbdf
Compare
|
/bot run --disable-fail-fast --stage-list "*PerfSanity*" |
1 similar comment
|
/bot run --disable-fail-fast --stage-list "*PerfSanity*" |
|
PR_Github #58858 [ run ] triggered by Bot. Commit: |
|
PR_Github #58858 [ run ] completed with state
|
|
/bot run --disable-fail-fast --stage-list "GB200-8_GPUs-2_Nodes-PyTorch-Disagg-PerfSanity-CTX1-NODE1-GPU1-GEN1-NODE1-GPU2-Post-Merge-3,DGX_B200-8_GPUs-PyTorch-PerfSanity-Post-Merge-2" |
|
PR_Github #58938 [ run ] triggered by Bot. Commit: |
|
PR_Github #58938 [ run ] completed with state |
Address Wanli Jiang's review on PR NVIDIA#16177: `_run_mamba_hybrid_warmup` runs two extra multi-seq forward passes on top of the standard autotuner warmup, both under the multi-seq / HAS_INITSTATES=True path that allocates more chunk-varlen SSD scratch than the single-seq warmup. Under a tight `--kv_cache_free_gpu_mem_fraction` an OOM here would abort init instead of degrading gracefully. Wrap the per-shape batch-context + forward + sync in a `try / except torch.OutOfMemoryError`, log a warning with the shape tuple, `torch.cuda.empty_cache()`, and continue to the next shape. Matches `_general_warmup_impl` / `_general_warmup_encoder`. No MoE reset needed — Mamba hybrid models covered by this path (Nemotron 3 Super 120B, Nemotron-Nano-12B-v2) are not MoE. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> Signed-off-by: Chenfei Zhang <chenfeiz@nvidia.com>
|
PR_Github #60109 [ run ] triggered by Bot. Commit: |
|
PR_Github #60109 [ run ] completed with state
|
|
/bot run --disable-fail-fast |
|
PR_Github #60148 [ run ] triggered by Bot. Commit: |
|
PR_Github #60148 [ run ] completed with state
|
|
/bot run --disable-fail-fast |
|
PR_Github #60170 [ run ] triggered by Bot. Commit: |
|
PR_Github #60170 [ run ] completed with state
|
Mamba hybrid models (e.g. Nemotron 3 Super 120B, Nemotron-Nano-12B-v2)
skip _general_warmup entirely because can_run_general_warmup is False
when the KV cache manager is a MambaHybridCacheManager. The default
_run_autotuner_warmup then issues a single least_requests=True prefill
= 1 sequence with num_cached_tokens_per_seq=0, which only exercises
the num_seqs==1 / HAS_INITSTATES=False / IS_CONT_BATCHED=False signature
of the Mamba SSD Triton kernels.
The first real serve iteration with chunked prefill and multiple
context requests then triggers autotune of the 12 missing kernel
variants (_chunk_state_varlen_kernel x5 configs,
_state_passing_fwd_kernel x4 across HAS_INITSTATES x IS_CONT_BATCHED,
_chunk_scan_fwd_kernel x2 across HAS_INITSTATES,
_cu_seqlens_triton_kernel x1) mid-inference, stalling for
~30 s and inflating P99 E2EL.
Fix (two changes):
* Add a warmup-only env-var hook TLLM_MAMBA_WARMUP_FORCE_INITSTATES in
Mamba2Metadata.prepare(): when set, override has_initial_states_cpu
to True for context requests so the HAS_INITSTATES=True kernel
variants compile during warmup instead of at first real request.
* Add PyTorchModelEngine._run_mamba_hybrid_warmup(), called from
warmup() after _run_autotuner_warmup. Runs two extra forward passes
for Mamba hybrid models only: (1) least_requests=False for multi-seq
path (compiles _cu_seqlens_triton_kernel and the multi-seq varlen
SSD kernels); (2) same as (1) plus TLLM_MAMBA_WARMUP_FORCE_INITSTATES
set (compiles HAS_INITSTATES=True variants). Fires regardless of
enable_autotuner. Wraps in autotune() when the autotuner is enabled
so op-level (M,N,K) caches also get primed. Set
TLLM_MAMBA_MULTISEQ_WARMUP=0 to disable. Non-Mamba models get a
free early return.
Verified:
Nemotron-3-Super-120B on GB200
(nvfp4-serve-pytorch, maxbs 512, maxnt 2048, kv_frac 0.8, in/out 1024,
reqs 640, con 128, ep/tp 4), cold 2-run:
before after delta
Run 1 tok/s 8142 9604 +18%
Run 1 P99 E2EL 29980 ms 18704 ms -38%
Run 1 P99 TPOT 30.28 ms 19.26 ms -36%
Run 2 tok/s 8181 9735 +19%
Run 2 P99 E2EL 29650 ms 17788 ms -40%
Nemotron-Nano-12B-v2 on B300
(bench-pytorch-streaming-bfloat16, maxbs 512, maxnt 2048,
in/out 500,2000, con 250), 3-rep:
before after
rep1 tok/s 7481 (to be filled after re-verification)
rep2 tok/s 9472
rep3 tok/s 9612
CV 13.9%
Supersedes PR NVIDIA#15876 (same root cause, narrower scope, function-level
hook on Mamba2Mixer).
Signed-off-by: Chenfei Zhang <chenfeiz@nvidia.com>
Address CodeRabbit review on PR NVIDIA#16177: Mamba2Metadata.prepare() was reading TLLM_MAMBA_WARMUP_FORCE_INITSTATES from os.environ on every call. A stray shell export, an inherited env in a forked worker, or an exception-skipped context-manager finally would silently force real prefills onto the HAS_INITSTATES=True path with has_initial_states set for cache-miss contexts, corrupting SSD chunk metadata. Replace the env var with a class-scoped context manager on Mamba2Metadata: * Mamba2Metadata gains a private class attribute _warmup_force_initial_states (default False) and a classmethod contextmanager force_initial_states_for_warmup() that flips it for the wrapped scope and restores the previous value in finally. * prepare() reads Mamba2Metadata._warmup_force_initial_states instead of os.environ. * PyTorchModelEngine._run_mamba_hybrid_warmup() drops the local _force_mamba_initstates_env helper and wraps the force-initstates warmup shape with Mamba2Metadata.force_initial_states_for_warmup(). * Drops `import os` in mamba2_metadata.py (no other users). Behavior is identical to the previous env-var handshake for the warmup shapes; only the coupling mechanism changes. Nothing outside those two warmup forward passes can set the flag. Signed-off-by: Chenfei Zhang <chenfeiz@nvidia.com> Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Address Wanli Jiang's review on PR NVIDIA#16177: `_run_mamba_hybrid_warmup` runs two extra multi-seq forward passes on top of the standard autotuner warmup, both under the multi-seq / HAS_INITSTATES=True path that allocates more chunk-varlen SSD scratch than the single-seq warmup. Under a tight `--kv_cache_free_gpu_mem_fraction` an OOM here would abort init instead of degrading gracefully. Wrap the per-shape batch-context + forward + sync in a `try / except torch.OutOfMemoryError`, log a warning with the shape tuple, `torch.cuda.empty_cache()`, and continue to the next shape. Matches `_general_warmup_impl` / `_general_warmup_encoder`. No MoE reset needed — Mamba hybrid models covered by this path (Nemotron 3 Super 120B, Nemotron-Nano-12B-v2) are not MoE. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> Signed-off-by: Chenfei Zhang <chenfeiz@nvidia.com>
Mirror _general_warmup_impl's OOM handler: if the mamba hybrid warmup forward OOMs between dispatch() and combine(), the MoE all-to-all state (MoeAlltoAll / NVLinkOneSided) is stuck in ``dispatched`` and the next warmup pass or first real request fails with ``dispatch called twice``. Reset before falling through to the next shape. Signed-off-by: Chenfei Zhang <chenfeiz@nvidia.com>
The multi-seq warmup pass added in this PR was crashing DGX_H100 CI tests that combine a MambaHybridCacheManager model (Qwen3.5-4B) with spec decoding on a small KV cache pool (259 blocks total for max_num_tokens=8192). ``_run_autotuner_warmup`` fit because it uses ``least_requests=True`` (1-2 long sequences). ``_run_mamba_hybrid_warmup`` uses ``least_requests=False``, which fans the token budget across batch_size (32) short sequences. When each sequence's length lands on a block boundary AND the target KV cache manager has extra tokens per request (spec decoding paths), ``add_token`` allocates one extra block per sequence that ``_create_warmup_request``'s ``blocks_to_use`` check doesn't account for, tipping allocation over the pool ceiling and crashing with "Can't allocate new blocks for window size N". Two changes, both narrow: 1. Cap the multi-seq token budget at ``min(curr_max_num_tokens, 4096)``. The warmup's job is only to compile ``num_seqs > 1`` + ``HAS_INITSTATES=True`` SSD kernel variants — a modest token count achieves that with far more block headroom. 2. Catch ``RuntimeError`` alongside ``torch.OutOfMemoryError``. C++ KV cache block-allocation failures surface as RuntimeError, not torch OOM. If any shape still doesn't fit, log and skip — the model then JIT-compiles the missing kernel variants lazily on the first real request (pre-fix behavior). Fixes the DGX_H100-PyTorch-6 stage failure on ``accuracy/test_llm_api_pytorch.py::TestQwen3_5_4B::test_dflash``. Signed-off-by: Chenfei Zhang <chenfeiz@nvidia.com>
Release-Check flagged the multi-line ``logger.warning(...)`` f-string
in ``_run_mamba_hybrid_warmup``'s exception handler. Reflow to the
single-anchor continuation style that ``yapf --style '{based_on_style: pep8,
column_limit: 80}'`` produces so pre-commit passes.
No behavior change.
Signed-off-by: Chenfei Zhang <chenfeiz@nvidia.com>
074e7e1 to
e93b50c
Compare
|
/bot run --disable-fail-fast |
|
PR_Github #60227 [ run ] triggered by Bot. Commit: |
|
PR_Github #60227 [ run ] completed with state
|
|
/bot run --disable-fail-fast |
|
PR_Github #60345 [ run ] triggered by Bot. Commit: |
|
PR_Github #60345 [ run ] completed with state
|
|
/bot run --disable-fail-fast |
|
PR_Github #60397 [ run ] triggered by Bot. Commit: |
|
PR_Github #60397 [ run ] completed with state
|
|
/bot run --disable-fail-fast |
|
PR_Github #60533 [ run ] triggered by Bot. Commit: |
|
PR_Github #60533 [ run ] completed with state |
Summary
Fixes 5 unstable perf cases across 4 clusters (GB200 / GB300 / B300) on Nemotron 3 Super 120B and Nemotron Nano 12B v2. For each case, the tables below show run 1 and run 2 within a single Slurm job before and after the fix.
Nemotron 3 Super 120B (and other
MambaHybridCacheManagermodels — includingnemotron_nano_12b_v2) trail on their first serve/bench iterations because the warmup path leaves 12 Mamba SSD Triton kernel variants uncompiled. The first mixed chunked-prefill iter with cached tokens (typically iter 3) then stalls ~30 s JIT-compiling them mid-inference, producing a large run-1 throughput deficit and P99 spike.Root cause chain (all three must hold):
_general_warmupis skipped entirely for Mamba hybrid models —can_run_general_warmupisFalsewhenisinstance(kv_cache_manager, MambaHybridCacheManager)._run_autotuner_warmupissues a singleleast_requests=Trueprefill = 1 sequence ofcurr_max_num_tokens.cu_seqlens_to_chunk_indices_offsets_tritontakes itsnum_seqs == 1fast path and_cu_seqlens_triton_kernelnever launches.num_cached_tokens_per_seq = 0, souse_initial_statesisFalseand everyHAS_INITSTATES=Truekernel variant remains uncompiled.The 12 missing kernels:
_chunk_state_varlen_kernel× 5 configs,_state_passing_fwd_kernel× 4 (HAS_INITSTATES×IS_CONT_BATCHED),_chunk_scan_fwd_kernel× 2 (HAS_INITSTATES),_cu_seqlens_triton_kernel× 1.This PR supersedes #15876 (which pre-JIT'd Mamba SSD
HAS_INITSTATES=Truekernels fornemotron_nano_12b_v2on B300). The autotuner-warmup approach here fixes both model families and both cluster classes with one code path.Fix
tensorrt_llm/_torch/pyexecutor/model_engine.py— extend_run_autotuner_warmupwith a small shape sweep for all models (pure-ctx-max plus mixed ctx+gen), gated byTLLM_AUTOTUNE_SWEEP(default on). For Mamba hybrid models specifically, add two more passes that build multi-sequence prefill batches (least_requests=False) and forceHAS_INITSTATES=TrueviaTLLM_MAMBA_WARMUP_FORCE_INITSTATES, gated byTLLM_MAMBA_MULTISEQ_WARMUP(default on).tensorrt_llm/_torch/modules/mamba/mamba2_metadata.py— add theTLLM_MAMBA_WARMUP_FORCE_INITSTATEShook inMamba2Metadata.prepare()so warmup can force theHAS_INITSTATES=Truecode path without changing real-inference behavior.Both env-var gates default on; nothing else changes behavior for non-Mamba models beyond the extra autotune shapes.
Verification — 5 unstable perf cases across 4 clusters
Each case ran back-to-back reps in a single Slurm job (isolated output dirs / caches per side). "Before" = no-fix baseline; "After" = PR head (
8fac3c21c9). Serve-mode tests keep a singletrtllm-serveprocess across reps, so warmup is amortized after run 1 — the "before" side shows the classic run-1 << run-2 gap that the fix closes. Bench-mode tests spawn a fresh worker per rep, so the JIT stall recurs each run and shows up primarily as elevated TTFT + reduced steady-state throughput on both baseline reps; the fix restores both.Case 1 — GB200 (lyris)
nemotron_3_super_120b_nvfp4-serve reqs:640 con:128 ep:4 tp:4 gpus:4— serve modeBefore-fix within-job: r2 is +31 % faster than r1 (warmup gap). After fix: r2/r1 = +0.05 % (essentially identical — warmup gap closed). After-fix run 1 is also +53 % faster than before-fix run 1 and +17 % faster than before-fix run 2.
Case 2 — B300 (bia)
nemotron_nano_12b_v2-bench-pytorch-streaming-bfloat16 in/out 500/2000 con:250— bench modeBench mode restarts the worker per rep, so both baseline runs pay the JIT stall equally (r1 ≈ r2 both slow); the fix restores both runs equally too. Mean TTFT drops 3798 → 1362 ms (−64 %), which is where the mamba warmup gap most directly manifests. Aggregate throughput improves modestly (+3 %) because the JIT cost is amortized across a 130 s / 512-request run.
Case 3 — GB300 (lyris)
nemotron_nano_12b_v2-bench-pytorch-bfloat16 in/out 1000/1000 con:250— bench modeBench mode → same-job r1 ≈ r2 on both sides. Baseline avg 6469 → fix avg 7441 tok/s = +15 %; wall time drops ~12 s per rep.
Case 4 — GB200 (lyris)
nemotron_nano_12b_v2-bench-pytorch-bfloat16 in/out 512/512— bench modeBench mode → same-job r1 ≈ r2 on both sides. Baseline avg 7318 → fix avg 8346 tok/s = +14 %.
Case 5 — B300 (bia)
nemotron_3_super_120b_nvfp4-serve reqs:160 con:32 ep:4 tp:4 gpus:4— serve mode (3 reps per job)Before-fix within-job: r1 is −33 % slower than the r2/r3 warmed steady state (classic warmup deficit). After fix: r1 ≈ r2 ≈ r3 (max spread 2 %). After-fix run 1 is +77 % over before-fix run 1 and +20 % over the warmed r2/r3 baseline.
Cluster / commit metadata
4be213c9c(main HEAD at time of test); baseline uses that wheel with no overlay, fix overlays the two patched files.54cffcd2fb(post-HMAC-revert main HEAD, x86_64 aarch64 respectively)._run_autotuner_warmupfor these models:[(2048, 0, True, False), (2048, 0, False, False), (2048, 0, False, True)].Test plan
TLLM_AUTOTUNE_SWEEP=0TLLM_MAMBA_MULTISEQ_WARMUP/TLLM_MAMBA_WARMUP_FORCE_INITSTATESwhen unset (default off for the metadata hook when unset)Summary by CodeRabbit
New Features
Bug Fixes