Skip to content

[None][perf] Add TRTLLM_DSV4_MEM_OPTS master switch for DSv4 memory optimizations - #14053

Closed
qiaoxj07 wants to merge 3 commits into
NVIDIA:feat/deepseek_v4from
qiaoxj07:feat/dsv4-mem-opts
Closed

[None][perf] Add TRTLLM_DSV4_MEM_OPTS master switch for DSv4 memory optimizations#14053
qiaoxj07 wants to merge 3 commits into
NVIDIA:feat/deepseek_v4from
qiaoxj07:feat/dsv4-mem-opts

Conversation

@qiaoxj07

@qiaoxj07 qiaoxj07 commented May 12, 2026

Copy link
Copy Markdown
Collaborator

Summary

Two related categories of changes for DSv4-Pro on GB300 with attention-DP (ADP) + MoE EP:

  1. Memory optimizations under a single env switch TRTLLM_DSV4_MEM_OPTS (the original scope of this PR).
  2. Autotuner deadlock fixes for DSv4-Pro ADP runs — pre/post tp_barrier around _run_autotuner_warmup and a collective-vote on cache-hit in AutoTuner.choose_one. These are unconditional (not gated on the mem-opts env switch).

A complementary rank-divergent-warmup-batch issue is addressed by upstream PR #14126; the local fix that previously lived in this branch has been reverted to defer to that PR.

With TRTLLM_DSV4_MEM_OPTS off (default), behavior is byte-identical to current source for the mem-opts code paths. The autotuner fixes are unconditional but only affect the autotuner warmup phase, not steady-state inference.

What's in the env switch (TRTLLM_DSV4_MEM_OPTS=1)

  1. Rope cos/sin table captensorrt_llm/_torch/attention_backend/interface.py adds RopeParams.from_config auto-cap of max_positions to runtime max_seq_len. Yarn cos/sin at position p depends only on p and yarn constants (which use original_max_position_embeddings, not table size), so truncating to max_seq_len yields identical values for indices < cap. tensorrt_llm/_torch/models/modeling_deepseekv4.py plumbs llm_args.max_seq_len onto the HF PretrainedConfig so from_config can see it.

  2. Attention workspace pre-allocationtensorrt_llm/_torch/attention_backend/trtllm.py sizes the workspace at AttentionMetadata.__init__ time from the live model_config using an empirically validated formula (2 × max_num_tokens × num_heads × head_size × bytes_per_elt × 1.25 for paged-context FMHA, MLA / ADP / FP8-KV aware). Without this, the C++ runtime's lazy workspace_.value().resize_(...) tries a single 0 → ~1.6 GiB jump during warmup at large max_num_tokens, fails under tight memory, and leaves CUDA state degraded enough that the MoE kernel later hits CUDA_ERROR_LAUNCH_FAILED. AttentionMetadata gains an optional model_config field; tensorrt_llm/_torch/pyexecutor/model_engine.py wires it in.

  3. MHC pool routingtensorrt_llm/_torch/modules/mhc/mhc_cuda.py routes _FusedHcWorkspaceCache outputs (residual_cur, post_mix_cur, etc.) through the global Buffers pool so MHC scratch can share backing memory with other layer-transient buffers. For DSv4-Pro ctx-only shapes the resulting alignment is naturally 128-byte friendly so TMA descriptor encoding does not fail.

Autotuner deadlock fixes (always on, gated by tp_size > 1)

4. Pre/post tp_barrier around _run_autotuner_warmup

Symptom: when Cache size after warmup is 0, the autotuner exits near-instantly on ranks that already had cache, while a slower rank is still inside its own autotuner. Faster ranks race ahead into the next collective-issuing _general_warmup step → mismatched collectives → silent hang.

Fix: bracket _run_autotuner_warmup with TP-group barriers so every rank enters and exits the autotuner together.

5. AutoTuner.choose_one collective vote on cache hit

Symptom: with ADP + MoE EP, different ranks see different per-expert routing shapes → is_cache_hit diverges across ranks. The slow path of choose_one ends in _maybe_sync_cache_data, which issues a TP/CP collective (allgather/broadcast). If some ranks early-return at the cache-hit shortcut while others enter the collective, the collective deadlocks.

Fix: insert an unconditional tp_cp_allgather(is_cache_hit) collective vote in choose_one. Every rank participates in the allgather regardless of local cache state. Only short-circuit when all ranks hit.

The collective must be unconditional (not gated on local is_cache_hit) — otherwise hit ranks call the allgather while miss ranks skip it, recreating the deadlock at the vote site.

Related upstream PR

NVIDIA/TensorRT-LLM#14126 ("Fail loudly on asymmetric warmup batch under attention-DP") addresses a separate but related rank-divergent failure mode where _create_warmup_request returns None on some ranks but not others. An earlier version of this branch had a local tp_allgather + min fix on curr_max_num_tokens for the same symptom (silent fix-and-proceed); it has been reverted in 7ff13466b7 to defer to #14126's fail-loudly approach and reduce review surface.

Validation

1-job baseline (single ctx-only run, batch=2, max_seq=8192, TP=4 EP=4, ADP on)

metric value
bench throughput 50,456 tok/s
gsm8k strict-match (1319 samples) 96.89%
gsm8k flexible-extract 96.89%

5-job parallel (same config, all required env on)

Test bench tok/s gsm8k avg Status
test1 50,681 96.40%
test2 ✗ Issue 5 (see follow-ups)
test3 50,427 96.47%
test4 50,667 96.02%
test5 50,441 96.40%

4/5 = 80% pass rate, up from ~0% without these fixes and ~60% with only expandable_segments.

Required env (for parallel-job runs)

export TRTLLM_DSV4_MEM_OPTS=1                              # mem-opts master switch
export PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True    # eliminates caching-allocator fragmentation at 16k warmup

(The PR code is correct without expandable_segments; that env reduces an upstream OOM-cascade failure mode in concurrent runs.)

Memory footprint (rank 0, DSv4-Pro GB300 TP=4)

stage with switch baseline
memory after model weight load 235.83 GiB 237.32 GiB (-1.49 GiB)
memory at stage_end_model_engine_warmup 250.08 GiB 254.97 GiB (-4.88 GiB)
workspace resize warnings 0 (or 1 small ~20 MiB) ~9

Test plan

  • DSv4-Pro ctx-only bench (TP=4 GB300, batch=2, max_seq=8192): 50,456 tok/s (single) / 50.4–50.7k tok/s (parallel) ✓
  • trtllm-eval gsm8k (1319 samples, max_batch=64, max_seq=2048): 96.02–96.89% ✓
  • 5-job parallel hang-fix validation: 4/5 pass (1 failure is independent MoE PARALLEL strategy issue tracked as follow-up)
  • OFF behavior byte-identical for mem-opts paths (verified via PyTorch memory snapshot comparison)
  • CI

Files touched

tensorrt_llm/_torch/attention_backend/interface.py | 36 ++
tensorrt_llm/_torch/attention_backend/trtllm.py    | 96 ++
tensorrt_llm/_torch/autotuner.py                   | 17 +
tensorrt_llm/_torch/models/modeling_deepseekv4.py  | 12 +
tensorrt_llm/_torch/modules/mhc/mhc_cuda.py        | 57 +
tensorrt_llm/_torch/pyexecutor/model_engine.py     | 24

Open follow-ups (not in this PR)

  • MoE PARALLEL strategy tactic-split desync — in _profile_runners, _maybe_parallelize_tactics distributes tactics across ranks (each rank profiles 1/N). MoE forward has internal NCCL all-to-all that requires all ranks at the same dispatch payload. Different per-rank tactics → mismatched payloads → dispatch timeout. Proposed 1-line fix: change MoERunner.tuning_config.distributed_tuning_strategy from PARALLEL to INDEPENDENT so all ranks profile all tactics in the same order. Validated as the root cause for the 1/5 failure in 5-job parallel; deferred to a follow-up PR to keep this one focused.
  • Bind getWorkspaceSizeForContext from C++ so the Python pre-alloc matches the runtime's request exactly (currently the heuristic undersizes by ~20 MiB and the C++ side does one quiet resize).
  • Harden Buffers._view_as to pad to a 128-byte aligned offset so the MHC pool is safe for all models, not just DSv4-Pro shapes.

🤖 Generated with Claude Code

@qiaoxj07
qiaoxj07 requested review from a team as code owners May 12, 2026 14:44
@qiaoxj07
qiaoxj07 requested review from brb-nv, joyang-nv and mikeiovine and removed request for a team May 12, 2026 14:44
@qiaoxj07
qiaoxj07 force-pushed the feat/dsv4-mem-opts branch 9 times, most recently from ec142b3 to cb66e08 Compare May 13, 2026 09:56
@qiaoxj07

Copy link
Copy Markdown
Collaborator Author

/bot run --disable-fail-fast

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #48162 [ run ] triggered by Bot. Commit: cb66e08 Link to invocation

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #48162 [ run ] completed with state SUCCESS. Commit: cb66e08
/LLM/main/L0_MergeRequest_PR pipeline #37984 completed with status: 'SUCCESS'

CI Report

Link to invocation

@qiaoxj07
qiaoxj07 requested a review from a team as a code owner May 14, 2026 06:22
@qiaoxj07
qiaoxj07 requested review from liji-nv and removed request for a team May 14, 2026 06:22
@lfr-0531
lfr-0531 force-pushed the feat/deepseek_v4 branch from 0a93d10 to 118e7a5 Compare May 14, 2026 07:44
@lfr-0531
lfr-0531 requested a review from a team as a code owner May 14, 2026 07:44
@lfr-0531
lfr-0531 requested a review from a team May 14, 2026 07:44
@lfr-0531
lfr-0531 requested a review from a team as a code owner May 14, 2026 07:44
@lfr-0531
lfr-0531 requested review from mlefeb01 and tburt-nv and removed request for a team May 14, 2026 07:44
qiaoxj07 added 2 commits May 14, 2026 16:08
…ptimizations

Introduce a single env switch TRTLLM_DSV4_MEM_OPTS that turns on three
sized-from-runtime-config memory optimizations together. Validated end
to end on DSv4-Pro on GB300 (bench 49,127 tok/s, gsm8k 96.25% on 1319
samples).

1. Rope cos/sin table cap. tensorrt_llm/_torch/attention_backend/interface.py
   adds RopeParams.from_config auto-cap of max_positions to runtime
   max_seq_len when the master switch is on. Yarn cos/sin at position p
   depends only on p and the yarn constants (which use
   original_max_position_embeddings, not the table size), so truncating
   to max_seq_len yields identical values for indices < cap.
   tensorrt_llm/_torch/models/modeling_deepseekv4.py plumbs
   llm_args.max_seq_len onto the HF PretrainedConfig so from_config can
   see it.

2. Attention workspace pre-allocation. tensorrt_llm/_torch/attention_backend/
   trtllm.py sizes the workspace at AttentionMetadata.__init__ time from
   the live model_config using an empirically validated formula
   (2 x max_num_tokens x num_heads x head_size x bytes_per_elt x 1.25
   for paged-context FMHA, with MLA / attention-DP / FP8-KV awareness).
   Without this, the C++ runtime's lazy `workspace_.value().resize_(...)`
   tries a single 0 -> ~1.6 GiB jump during warmup at large
   max_num_tokens, fails under tight memory, and leaves CUDA state
   degraded enough that the MoE kernel later hits CUDA_ERROR_LAUNCH_FAILED
   on a real forward. AttentionMetadata gains an optional model_config
   field; tensorrt_llm/_torch/pyexecutor/model_engine.py wires it in.

3. MHC pool routing. tensorrt_llm/_torch/modules/mhc/mhc_cuda.py routes
   _FusedHcWorkspaceCache outputs (residual_cur, post_mix_cur, etc.)
   through the global Buffers pool so MHC scratch can share backing
   memory with other layer-transient buffers. For DSv4-Pro ctx-only
   shapes the resulting alignment is naturally 128-byte friendly, so
   TMA descriptor encoding does not fail.

All three are gated on a single TRTLLM_DSV4_MEM_OPTS env (truthy = on).
There are no per-feature knobs; the optimizations are auto-sized from
the live config. With the switch off (default), behavior is
byte-identical to current source.

Reproduction config and per-stage PyTorch memory snapshots are recorded
out-of-tree under bench-mewtwo/claude_opt/optims_final_20260512.md and
bench-mewtwo/claude_opt/mem_opts_profile_compare_20260512.md.

Signed-off-by: Xianjie <5410381+qiaoxj07@users.noreply.github.com>
…id rank-divergent deadlock

With ADP + MoE EP, different ranks see different per-expert routing
shapes, so the result of profiling_cache.search_cache (is_cache_hit)
can diverge across ranks. The slow path below ends in
_maybe_sync_cache_data, which issues a TP/CP collective (allgather or
broadcast). If some ranks early-return at the cache-hit shortcut while
others enter the collective, the collective deadlocks.

This patch makes the early-return decision a collective vote: every
rank runs tp_cp_allgather(is_cache_hit) and only short-circuits if ALL
ranks hit. The allgather is unconditional (not gated on local
is_cache_hit) so that hit and miss ranks all participate symmetrically.

Validated on DSv4-Pro ctx-only with TP=4 EP=4, gpu_frac=0.5: 4/5 jobs
PASS bench (50.4-50.7k tok/s) + gsm8k (96.40-96.47%). The remaining
1/5 failure is a different rank-divergent issue inside
_profile_runners (PARALLEL strategy distributes tactics across ranks
and MoE forward calls on different tactics can desync on internal
all-to-all collectives); not addressed by this patch.

Signed-off-by: Xianjie <5410381+qiaoxj07@users.noreply.github.com>
@qiaoxj07
qiaoxj07 force-pushed the feat/dsv4-mem-opts branch from 7313f54 to 3886f92 Compare May 14, 2026 08:10
…ream PR NVIDIA#14126

NVIDIA#14126 ("Fail loudly on asymmetric warmup batch under
attention-DP") addresses the same rank-divergent warmup-config failure
mode this PR previously patched in `_get_max_shape_warmup_requests`. Its
philosophy (raise RuntimeError with diagnostics, ask user to bump
`--kv_cache_free_gpu_mem_fraction`) is more conservative than the
silent fix-and-proceed we had here, and keeps the warmup contract
explicit.

To avoid duplicating the upstream effort and to reduce review surface
in this PR, drop the local synchronization. The remaining fixes
(autotuner pre/post barriers and AutoTuner.choose_one collective vote)
are independent and stay.

Note: this revert is safe only if PR NVIDIA#14126 (or an equivalent) lands
before merging this branch into mainline. If neither lands, the
rank-divergent warmup-config deadlock remains observable on DSv4-Pro
ADP runs and would need to be re-introduced.

Signed-off-by: Xianjie <5410381+qiaoxj07@users.noreply.github.com>
@qiaoxj07

Copy link
Copy Markdown
Collaborator Author

/bot run --disable-fail-fast

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #48349 [ run ] triggered by Bot. Commit: 7ff1346 Link to invocation

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #48349 [ run ] completed with state SUCCESS. Commit: 7ff1346
/LLM/main/L0_MergeRequest_PR pipeline #38155 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

CI Agent Failure Analysis

Link to invocation

@qiaoxj07

Copy link
Copy Markdown
Collaborator Author

/bot run --disable-fail-fast

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #48407 [ run ] triggered by Bot. Commit: 7ff1346 Link to invocation

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #48407 [ run ] completed with state SUCCESS. Commit: 7ff1346
/LLM/main/L0_MergeRequest_PR pipeline #38209 completed with status: 'SUCCESS'

CI Report

Link to invocation

@lfr-0531

Copy link
Copy Markdown
Collaborator

/bot run --disable-fail-fast

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #48736 [ run ] triggered by Bot. Commit: 7ff1346 Link to invocation

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #48736 [ run ] completed with state FAILURE. Commit: 7ff1346
/LLM/main/L0_MergeRequest_PR pipeline #38504 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

CI Agent Failure Analysis

Link to invocation

lancelly added a commit to lancelly/TensorRT-LLM that referenced this pull request May 19, 2026
…s pool

Add an opt-in path in _FusedHcWorkspaceCache.get that routes the four
output buffers (residual_cur, post_mix_cur, comb_mix_cur,
layer_input_cur) and the three internal scratch buffers (y_acc_ws,
r_acc_ws, done_counter_ws) through get_memory_buffers() so MHC
scratch can share backing memory with other layer-transient buffers
(e.g., the attention workspace). The env switch TRTLLM_DSV4_MEM_OPTS
is read once at class definition (cached as _USE_POOL) so the lookup
doesn't run on every workspace request.

Pool routing is gated to decode-sized B (B <= _CACHE_MAX_B). For
B > _CACHE_MAX_B (prefill), the code falls through to fresh
torch.empty per call, matching the upstream uncached path; this
preserves prefill's no-aliasing semantics where layer N's *_prev
inputs and layer N+1's *_cur outputs live in distinct tensors. At
decode, upstream's existing per-runner LRU already returns the same
tensor across layers, so the kernel is already in-place safe; pool
routing inherits that aliasing without widening it.

With TRTLLM_DSV4_MEM_OPTS off (default), behavior is byte-identical
to current source.

Split out from PR NVIDIA#14053 (DSv4 mem-opts master switch) and from
PR NVIDIA#14241 (RoPE cap + MHC pool): this PR contains only the MHC pool
routing piece, still gated on TRTLLM_DSV4_MEM_OPTS so it can be
validated in isolation.

Signed-off-by: Lanyu Liao <lancelly@users.noreply.github.com>
@qiaoxj07 qiaoxj07 closed this May 20, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants