Skip to content

[None][feat] Add Rubin (sm_107) MiniMax-M3.1 low-latency decode targets - #21136

Closed
peihu-nv wants to merge 55 commits into
NVIDIA:mainfrom
peihu-nv:peihengh/m31-vr-decode-main
Closed

peihu-nv wants to merge 55 commits into
NVIDIA:mainfrom
peihu-nv:peihengh/m31-vr-decode-main

Conversation

@peihu-nv

Copy link
Copy Markdown
Collaborator

Targeting main early (draft). This is the same VR (sm_107) decode work as #21038, placed on top of the MiniMax-M3.1 GB300 bring-up branch (brb-nv:user/brb/m3.1-flash-gb300-initial). Until the M3.1 initial bring-up PR merges into main, this diff also contains that bring-up and its GB300 commits; we will rebase onto main after it lands. The prefill PR is stacked on top of this one.

@coderabbitai summary

Description

Depends on #20917 (GB300 MiniMax-M3.1 decode targets). This PR builds on its shared DSpark and runtime-hook integration; until #20917 merges, the diff against feat/minimax-m3.1-flash also shows its commit. The context (prefill) target for Rubin is a separate PR stacked on this one.

MiniMax-M3.1 NVFP4 decode on Rubin (sm_107) currently falls back to the built-in model. This PR adds three modeling_v2 targets, routed on sm_107 by TP width:

Target Topology What it serves
minimax_m31_nvfp4__sm_107__tp8 TP8, experts TP8 low-latency K7 decode
minimax_m31_nvfp4__sm_107__tp4 TP4, experts TP4 low-latency K7 decode
minimax_m31_nvfp4__sm_107__tp2 TP2, experts TP2 decode baseline (aggregated serving and disaggregated generation)

Each target follows the layout of the GB300 targets in #20917:

  • per-TP staircase targets;
  • runtime hooks that stay inert unless a target arms them;
  • every item default ON, with a TRTLLM_M31_DISABLE_<ITEM>=1 kill switch (m31_switches); items that are still opt-in use TRTLLM_M31_ENABLE_<ITEM>=1;
  • compiled ops under cpp/tensorrt_llm/kernels/minimaxM3Sm107Tp{2,4,8} and thop, built only for sm_10x, with fake (meta) registrations;
  • bundled tactic pins (m31_tuning/) and ahead-of-time CuTe DSL objects (cute_aot_data/) shipped as package data, each guarded by the native build / toolchain it was produced with.

It also restores two DecoderRunner.__init__ defaults that #20917 dropped (guided_decoder and lora_model_config, both None); without them every server start fails in warmup with an AttributeError.

Shared changes armed by the sm_107 targets

These are inert unless a target arms them through RuntimeHooks (or an explicit config option):

  • runtime hooks: step-input pack and prologue op, draft block-offset patching, fused spec offsets, first-content emission, one-event iteration timing, a late-compile detector, the MXFP8 pinned-tactic table, log-only work counters;
  • DFlash / DSpark drafter APIs (fc TP split, fused all-reduce + RMSNorm, windowed-decode cap, full-slot context arena) and a parallel DSpark context advance;
  • MSA backend forward_staging, MSA KV-cache fixes (pool zero-init, exact KV-size estimate), standalone-drafter KV pool fixes, saturating FP8 casts;
  • disaggregated serving: the DSpark drafter's context window travels with the target KV (DisaggregatedParams.draft_ctx_state);
  • indexer_kv_dtype='nvfp4' (prototype): NVFP4 index-K pages for the MSA implementation;
  • native ops: TOPP_DET v3 list-path / wide-histogram variants (schema arguments default off), a slot-indexed chain speculative sampling variant, REJ v3 selected only on sm_107, a cluster-16 router GEMV, an FC-shard GEMM;
  • requirements.txt pins nvidia-cutlass-dsl[cu13]==4.8.0 (the release, same as upstream main) instead of 4.8.0.dev0.

Shared paths taken only by the sm_107 targets

These shared paths are off by default for every model and GPU. Routing arms them, process-wide, when it selects an sm_107 MiniMax-M3.1 target, before the model is built (arm_sm107_shared_opt_ins). Each keeps its kill switch, and the same paths are available to other models as RuntimeHooks opt-ins.

Path Kill switch
KV GPU prefix prefetch (py_executor): waiting context requests have their host-tier prefix staged to the GPU and are admitted after a short rank-agreed hold (KV cache manager V2 + block reuse). TRTLLM_M31_DISABLE_KV_GPU_PREFETCH=1
DFlash / DSpark draft KV reserve + guard page (kv_cache_manager_v2): the draft KV manager reserves the drafter's block width per step and keeps page 0 as a guard, so drafter writes through unallocated block-table entries cannot clobber another request's page. TRTLLM_M31_DISABLE_DRAFT_KV_RESERVE=1, TRTLLM_M31_DISABLE_DRAFT_KV_GUARD=1
Context-only prompt tokens as an int32 array (llm_request; host arrays only). TRTLLM_M31_DISABLE_L1_TOKEN_ARRAY=1
MSA NVFP4 sparse prefill BF16X / E4M3X fork (msa_prefill), when the fmha_sm100 fork is installed and the geometry has 16 query heads per KV head. TRTLLM_M31_DISABLE_TP2CTX_BF16X=1
MSA indexer proxy max-score buffer reuse across sparse layers (msa_indexer). TRTLLM_M31_DISABLE_PROXY_MS_REUSE=1
Context one-hot draft probabilities via index_fill_ (speculative/interface; same elements, no host sync). TRTLLM_M31_DISABLE_ONEHOT_INDEX_FILL=1
DFlash / DSpark host-side context allocation when the draft block table counts full rows (dflash). TRTLLM_DFLASH_DISABLE_HOST_CTX_ALLOC=1
Shipped ahead-of-time CuTe DSL objects (cute_aot): an unarmed process compiles every kernel in-process with no lookup or export. TRTLLM_M31_CUTE_AOT_DISABLE=1

toppRenormDetV3Sm103's TOPP_V6 chain-prefetch summation runs only on sm_107 devices; GB300 callers of the #20917 targets keep the staged loop (TRTLLM_M31_DISABLE_TOPP_V6=1 turns it off on sm_107 too).

Other shared behaviour changes (no kill switch; correctness fixes and validation):

  • DFlash / DSpark context one-hot draft probabilities clear the full draft-probability width, not the draft logits' width, so a vocab-sharded drafter cannot leave stale columns for new requests.
  • The one-model speculative sampler rejects requests with a logits_processor (it was silently never called), and the disaggregated peer check rejects replicated roles whose per-layer cache sizes differ (e.g. different indexer_kv_dtype).
  • The scheduler reads a request's prompt length from num_input_tokens when present (same value, no token-list build).

Items and numerics

Unless noted, an item is exact: bitwise against its stock path in eager and graph mode, gated by kernel oracles. These items are not bitwise: same inputs and rounding points, different fp32 reduction order:

Item What changes
VR_GEMV48 / LINEAR_CUBLASLT 33..64-row MXFP8 projections on the same quantized activation
S9B drafter all-reduce fused with add + RMSNorm
MXDRAFT drafter MXFP8 GEMMs; changes only the draft, never the accepted-token rule (ON at TP8 and TP2, OFF at TP4)
LC_ONEDIV (TP4) up to 16 query rows, the long-context sparse-decode split-K merge divides once per output (fixed order, no atomics; within the float64 reference's error band)

S2 (drafter fc row split), S5 (window-capped drafter decode), MCAG, S9B_MC, REJ_SLOTS and SLOT_IDS_CHANGED are compiled in but default OFF.

Measured results (VR200, end to end)

AgentX-style multi-turn agentic workload (SemiAnalysis InferenceX client, agentx-harness 1.0.12 preset), K7 DSpark speculation, sampled (T 1, top-p 0.95, rejection sampling), 3600 s per cell. Acceptance: synthetic (forced) K7 AL 4.04; natural AL not demonstrated. (The acceptance counters read 4.039 on every generation endpoint, which reflects the forcing.) Y = output-inclusive total token throughput per GPU; X = per-user generation tokens/s.

Cell Topology Y per GPU X median X P90 TTFT p50
agg c20 1x TP2 (2 GPUs) 76.87K 535.8 479.3 0.17 s
agg c32 1x TP2 128.38K 410.4 282.0 0.25 s
agg c56 1x TP2 212.97K 262.2 200.9 0.23 s
agg c68 1x TP2 243.10K 209.4 163.6 0.34 s
disagg c16 ctx 1x TP2 + gen 1x TP8 (batch 1) 11.86K 1310.6 1227.2 0.60 s
disagg c24 ctx 1x TP2 + gen 2x TP4 (batch 1) 19.64K 1024.7 980.6 0.36 s
disagg c64 ctx 1x TP2 + gen 4x TP2 (batch 2) 50.72K 658.3 611.5 1.34 s
  • The aggregated cells ran a build whose decode code differs from this commit only by the fake (meta) registrations of the native SwiGLU-OAI / MoE-finalize ops and their unit tests. The disaggregated cells ran this decode code exactly, with the context role served by the stacked prefill PR's target.
  • Two files in this commit are formatting-only changes (identical AST) relative to the measured build: catalog/spec_decode/dspark_ctx_advance.py and tests/unittest/_torch/thop/parallel/test_minimax_m31_dspark_glue_ops.py.
  • The disaggregated TP8 / TP4 generation cells run one request per generation server by configuration (latency cells).
  • Native builds are not byte-reproducible; the measured images are the builds the tactic pins were recorded on (the pins carry a native-build guard).

Disclosures and known gaps

  • The disaggregated c24 cell had 3 failed requests out of 4,049. Two come from the generation server's first verify after KV receive using stale draft probabilities (fixed in the follow-up commit "Disaggregated generation: one-hot draft probs on the first verify under rejection sampling" on this PR; the measured cells ran without it); each was followed by repeated stock post-processing errors on the same request. The third is a client transport error.
  • One DSpark drafter-input kernel is loaded from disk on first use rather than at warmup (a fix is parked).
  • The measured runs pre-seeded Triton / FlashInfer / Inductor JIT caches from a named seed; without it these compile at startup.
  • TP4 / TP8 generation: the dense SwiGLU above 8 rows runs the Triton kernel (the native SwiGLU-OAI / MoE-finalize ops are TP2-only); the TP4 / TP8 cells above ran batch 1.
  • The context-phase CTXNORMQ boundary is not taken on sm_107 (the op from [None][perf] Add GB300 MiniMax-M3.1 low-latency decode targets #20917 is built for SM103 only).
  • With max_seq_len 1,048,576, set max_batch_size explicitly: at the default (2048) the MSA backend's worst-case plan overflows its int32 KV offsets at startup. A fix (int64 offsets or a clamp) follows.
  • The sm_107 context target loads no bundled tactic pins yet, so it autotunes at startup.
  • Drafter determinism above 8 concurrent requests relies on the draft KV reserve + guard above.
  • MSA vendoring and the optional flashinfer-cubin dependency for Rubin are in the stacked prefill PR.

Updates on this PR after the first commit

Landed:

  • Disaggregated generation first-verify fix: a request received from the context server enters with dummy draft tokens and a one-hot draft-probability row under rejection sampling, so its first verify cannot read a stale or uninitialized row (CPU unit test test_disagg_gen_first_verify_onehot.py).
  • TOPP_V6 limited to sm_107 devices, and the LlmRequest token-array fast path limited to host arrays (a device array takes the sequence cast, which rejects it, as before the fast path).
  • The shared paths above armed only for the sm_107 targets, from routing, so the target directories (the shipped AOT objects' source digests) are unchanged.
  • DSpark drafter-inputs kernel: no Triton specialization on the batch-dependent accepted-token stride and view alignment, so a batch shape warmup never ran no longer compiles after the server is ready. No kill switch: it is a compile-specialization hint with no alternate path (bitwise unchanged).
  • The iteration-stats RPC is awaited instead of blocking the event loop (TRTLLM_M31_DISABLE_A4_ASYNC_STATS=1 restores the blocking call).
  • Two diagnostics, off unless their environment variable is set: a phase-armed Nsight capture window (TRTLLM_M31_PHASE_NSYS_ARM) and a per-rank host-path probe (TRTLLM_M31_HOST_PATH_PROBE).
  • TP2 sequence-parallel router: each rank computes its owned half of the rows with a native TF32 GEMM (trtllm::m31_router_half_pin) at the full-step row counts qualified bitwise against the full-row reference; other layouts keep the full-row path (TRTLLM_M31_DISABLE_ROUTER_HALF_PIN=1). The measured effect is small (about 0.02-0.03% of mixed-step time; no decode-step change).
  • The executor's iteration timing uses native GPU timestamps on the sm_107 targets instead of timed CUDA events (TRTLLM_M31_DISABLE_GPU_TIMESTAMPS=1).
  • The GPU byte oracles for the router pin and the drafter-inputs kernel need an sm_107 GPU and run locally; their CPU counterparts are in the CPU test list.
  • Validated on VR200 on a rebuilt image: native, speculative, top-p (1,002 sampling oracles, bitwise), opt-in, context-target and disaggregated unit-test suites, and a serve smoke of the aggregated and context roles with engagement, tactic pins and ahead-of-time objects unchanged from the measured build.

Pending:

  • Bundled tactic pins for the TP4 / TP8 / TP2 disaggregated generation roles.
  • The MSA int32 KV-offset fix above.

Test Coverage

  • tests/unittest/_torch/modeling_v2/models/minimax_m3/minimax_m31_nvfp4__sm_107__tp{2,4,8}/: kernel oracles (bitwise or reduction-order), dispatch, kill switches, routing, prebuild and work-counter tests.
  • tests/unittest/_torch/thop/parallel/ native-op tests (incl. fake registrations), tests/unittest/_torch/multi_gpu/test_minimax_m31_{f3_ar_prepushed,s2_moe_tail}.py (bitwise to the stock all-reduce chains).
  • CPU tests added to l0_cpu.yml: test_m31_pins.py, test_cute_aot.py, test_flashinfer_autotune_cache.py; the two TP2 multi-GPU tests are listed in l0_dgx_b200.yml.
  • tests/unittest/disaggregated/: draft-KV transfer, rank-info, hook-off and switch tests.
  • On VR200 at the measured build: the native-op and speculative-decoding unit-test suites passed, and a serve smoke test passed for the aggregated, context and generation roles (pins loaded on every rank, no late compiles after ready).

PR Checklist

Please review the following before submitting your PR:

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

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

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

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

  • Any new dependencies have been scanned for license and vulnerabilities

  • CODEOWNERS updated if ownership changes

  • Documentation updated as needed

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

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

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

GitHub Bot Help

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

pranav-nvidia and others added 30 commits October 22, 2026 18:03
MXFP8 non-expert projections with NVFP4 routed experts on the existing model:
model_config expands the packaging's expert recipe into the mixed per-layer
config, the weight mapper renames the packed expert tensors and inverts their
global scales, MXFP8 index projections and fused MXFP8 shards load their own
block-scale rows, the sparse cache manager takes the layer schedule from the
checkpoint and counts index-K bytes, the router GEMM runs in FP32 cuBLAS, and
the VL input processor loads the HF processor only for image requests.

Signed-off-by: Pranav Shrestha <pshrestha@nvidia.com>
MiniMaxM3Gate keeps a BF16 split of its FP32 router weight (three terms
along K, built at load_weights, the multi-term emulation of CUTLASS's 3xTF32
GEMMs). BF16 inputs run one cuBLAS BF16 GEMM with FP32 accumulation over the
replicated input instead of an FP32 cast plus a SIMT SGEMM; FP32 inputs keep
the FP32 cuBLAS GEMM. Top-k parity against an FP64 reference is tested.

Signed-off-by: Pranav Shrestha <pshrestha@nvidia.com>
KV pool and block-scale views are built once per layer and layout, the fused
QKV/index producer caches its per-layer-invariant arguments, block-id rows are
copied from the page-index buffers, the MSA metadata builders do their
batch-sized index arithmetic in numpy, and the NVFP4 decode and CSR prefill
wrappers resolve the device capability and the fmha_sm100 API once. Same
kernels, same arguments; adds prefix-reuse and BF16-cache decode tests.

Signed-off-by: Pranav Shrestha <pshrestha@nvidia.com>
…SM107

The MiniMax-M3 Triton NVFP4 sparse decode kernels run on SM107, so keep
NVFP4 KV for that path instead of falling back to FP8 and include SM107
in the decode capability list and test guards.

Signed-off-by: Pranav Shrestha <pshrestha@nvidia.com>
…deo tokens per patch

trtllm-serve and the multimodal prompt builders mark their rendered TextPrompt
with chat_template_applied; the input processor uses it verbatim instead of
wrapping it in a second user turn, normalises a framed marker, and rejects a
marker/media count mismatch before the HF processor runs. Media arrive as
uint8 numpy, and video tokens count one start/end bracket per temporal patch.

Signed-off-by: Pranav Shrestha <pshrestha@nvidia.com>
…king on multimodal requests

Forward the pre-fusion token ids as orig_input_ids so the spec worker
(DSpark/DFlash, MTP, Eagle) sees them after fuse_input_embeds drops
input_ids, and slice the vision embeddings with find_input_mm_embeds so a
cached prefix or a chunk boundary inside a placeholder span no longer
trips the token-count check.

Signed-off-by: Pranav Shrestha <pshrestha@nvidia.com>
The input processor implements the token-ID expansion hooks but never
set the opt-in flag, so a prompt_token_ids + image request was
detokenized and re-templated, doubling the image marker and failing the
marker count check. Set the flag and test the dispatch end to end.

Signed-off-by: Pranav Shrestha <pshrestha@nvidia.com>
The drafter's nested HF config is flattened, its checkpoint tensors renamed,
MXFP8 linears dequantized and Gemma norm weights folded at load. Its KV pool
uses its own page size, is funded for its attention window plus the chunk in
flight, and the unpaired draft mirror is sized from the target request and
keeps the chunk start as history; the block decode reads a rebased table.

Signed-off-by: Pranav Shrestha <pshrestha@nvidia.com>
… generation-init requests

A generation worker receives the prompt KV instead of prefilling it and
starts the drafter on a fresh slot, so the draft mirror never holds K/V
for the transferred prompt. Treat that prompt as history on gen-init so
the drafter's window trims the mirror to its tail plus headroom, instead
of asking for a prompt-sized chunk that exceeds the pool funding.

Signed-off-by: Pranav Shrestha <pshrestha@nvidia.com>
…ligned reused prefix

On an unpaired draft pool the block decode reads a view rebased by the rounded-up base of a reused prefix.
While the stored tail is shorter than that base, entry 0 of the view lies past the request's allocation and resolves to page 0, so the block's own K/V landed in another request's page and the drafter stayed blind until the context caught up.
The view is now shifted only to the page holding the context length on those steps; the shortened read length is unchanged, so nothing below the base is read.
Adds a CPU test that walks every context length of that window.

Signed-off-by: Pranav Shrestha <pshrestha@nvidia.com>
…sing in step

Resolve the parser's mode from thinking_mode or the prompt-resolved relay instead
of the bare thinking flag, so a prefilled <mm:think> block is read as reasoning.
Report truncated reasoning as reasoning in the non-streaming parse and make the
streaming parser emit the same split: unopened </mm:think> is a delimiter, text
before an opening tag is content, partial tags are withheld at chunk boundaries.

Signed-off-by: Pranav Shrestha <pshrestha@nvidia.com>
The deployment guide gains a linkable "Serving MiniMax-M3.1-Flash" section
(bundled DSpark drafter, KV cache reuse, NVFP4 KV cache on SM107, thinking_mode
and reasoning parsing) and states that both MoE backends load the NVFP4 expert
packaging with the same static recipe. The supported-models matrices and the
sparse-attention page now say block reuse is supported and mixed MXFP8/NVFP4 loads natively.

Signed-off-by: Pranav Shrestha <pshrestha@nvidia.com>
…okenization cache

`TransformersTokenizer` delegates `num_special_tokens_to_add` to the wrapped
tokenizer (the base class raises), so the MiniMax-M3 input processor's
tokenization-cache check runs. `resolve_hf_chat_template` falls through to the
tokenizer's template when the processor's `chat_template` is None.

Signed-off-by: Pranav Shrestha <pshrestha@nvidia.com>
….4.0a1

The side-branch release job derives its wheel and container version from tensorrt_llm/version.py, so the branch gets its own alpha base instead of sharing the main nightlies' 1.4.0rc1 line.

Signed-off-by: Pranav Shrestha <pshrestha@nvidia.com>
…#20564)

Signed-off-by: Yanchao Lu <yanchaol@nvidia.com>
(cherry picked from commit ee7c59a)
Signed-off-by: Yanchao Lu <yanchaol@nvidia.com>
Use the 1.4.0a0 base required by the side-branch release convention and keep the README badge and example dependency constraint aligned.

Signed-off-by: Yanchao Lu <yanchaol@nvidia.com>
Signed-off-by: Tyler Burt <195370667+tburt-nv@users.noreply.github.com>
(cherry picked from commit c962edd)
Signed-off-by: Yanchao Lu <yanchaol@nvidia.com>
Add SM103 TP8 single-request decode through eight rows and TP4 decode through 32 rows, with shared native kernels and guarded DSpark runtime integration.

Signed-off-by: peihengh <259410613+peihu-nv@users.noreply.github.com>
Signed-off-by: peihengh <259410613+peihu-nv@users.noreply.github.com>
Signed-off-by: peihengh <259410613+peihu-nv@users.noreply.github.com>
…3.1 TP4 target

op16.py loads m3_moe16_kernel.py by path at prebuild time, but the file was
not part of the target, so every modeling_v2 TP4 server failed during
post_load_weights with FileNotFoundError. Add the 9..16-row MoE kernel that
op16.py's M3_CONFIG contract (m_max, num_ctas, nb2_cap, fused_router, shared,
timing) was written against.

Signed-off-by: Balaram Buddharaju <169953907+brb-nv@users.noreply.github.com>
…nfig

DecoderRunner.__init__ stopped setting guided_decoder and
lora_model_config, so warmup of any model without a guided decoder
(e.g. the built-in MiniMax-M3 path with modeling_v2 off) failed with
AttributeError on self.guided_decoder. Restore both defaults.

Signed-off-by: Balaram Buddharaju <169953907+brb-nv@users.noreply.github.com>
…pping

msa_backend._build_msa_fields passes device_tables=not h2d_cache.enabled(),
but build_paged_kv_slot_mapping had no such parameter, so every built-in
MiniMax-M3 MSA step failed with TypeError. Add the parameter: with
device_tables=False the device req_to_token/slot_ids tables are skipped and
out_cache_loc is returned pinned on the host for the caller to stage.

Signed-off-by: Balaram Buddharaju <169953907+brb-nv@users.noreply.github.com>
…prefix dimension (NVIDIA#19975)

Signed-off-by: Zheyu Fu <zheyuf@nvidia.com>
(cherry picked from commit 0c9b463)
Signed-off-by: Balaram Buddharaju <169953907+brb-nv@users.noreply.github.com>
…slower than FP8 (cherry-pick of NVIDIA#19974)

Signed-off-by: Zheyu Fu <zheyuf@nvidia.com>
(cherry picked from commit d68ea55)

Conflict resolution against the MiniMax-M3.1 branch:
- cache_manager.py: keep the branch's _cached_pool_view getters (they already
  cache every K/V and scale view), so upstream's _kv_buffers/_block_scale_buffers
  dicts are dropped; the eager per-layer view resolution at the end of __init__
  now just warms that cache. copy_batch_block_offsets returns early when there are
  no attention-op pools, before the branch's unchanged-copy guard.
- modeling_minimaxm3.py: keep the branch's cached _fused_producer_plan; the
  explicit kv_scale_cache from the piecewise producer op is threaded into the
  plan (and its cache key), and the plan resolves it from the manager otherwise.

Signed-off-by: Balaram Buddharaju <169953907+brb-nv@users.noreply.github.com>
…erate managers without is_nvfp4_layer

_cached_pool_view keyed views by the raw kv_layout, so get_buffers(i) and
get_buffers(i, "HND") built two views of the same pool and missed the
views warmed at construction. Key by the resolved layout instead.
_resolve_fused_producer_plan again treats a missing manager or one without
is_nvfp4_layer as non-NVFP4 instead of raising AttributeError.

Signed-off-by: Balaram Buddharaju <169953907+brb-nv@users.noreply.github.com>
…n-tree kernel

ops/gqamix_sparse_decode.py must stay a line-for-line copy of the in-tree
triton_sparse_decode.py apart from its specialization (enforced by
test_modeling_v2_m31_gqamix.py). It predated the SM107 NVFP4 capability
change and the per-device capability cache, so the copy test failed.
Re-copy the in-tree module and re-apply the two specialization edits.

Signed-off-by: Balaram Buddharaju <169953907+brb-nv@users.noreply.github.com>
…ode sampler ops

Every TP rank runs the speculative-decoding sampler (draft sampling and the
rejection test) on its own and relies on bit-identical results across ranks.
top_p_renorm_probs_op and compute_probs_from_logits_op called FlashInfer's
top_p_renorm_probs in its default, non-deterministic mode, so the renormed
probs could differ in the last bits per rank; near-threshold accept/sample
decisions then occasionally differed, the ranks' token counts drifted apart
and, once a request finished on some ranks only, the TP group deadlocked.
Pass is_deterministic=True, as the other FlashInfer sampling calls in this
file already do.

Signed-off-by: Balaram Buddharaju <169953907+brb-nv@users.noreply.github.com>
… GQAMIX

The GB300 MiniMax-M3.1 modeling_v2 targets (tp4, tp8) register their prefill
tile select (TSEL) on MsaIndexer.selector_hook and their target-local sparse
decode (GQAMIX) on MsaDecodeFmha.sparse_decode_hook, but neither attribute
existed in the MSA backend, so both registrations were no-ops.

Add both attributes (default None). MsaIndexer._select offers the
KV-head-reduced scores to the selector hook and keeps the built-in select when
the hook is unset or returns None; MsaDecodeFmha._run_sparse calls the decode
hook in place of minimax_m3_sparse_attn_decode with identical arguments when
set. With no hook installed the behaviour is unchanged.

Signed-off-by: Balaram Buddharaju <169953907+brb-nv@users.noreply.github.com>
Add sparse_decode_tp2_policy with the GB300-measured split-K, query pairing,
producer warps/stages and merge warps for the NVFP4 sparse decode at two KV
heads per rank and decode_query_len 4, keyed on the local batch, and consult
it from minimax_m3_sparse_attn_decode. Unlisted batches, other KV-head counts,
other decode_query_len values (e.g. DSpark K=7) and non-SM103 devices keep the
default routing. M3_O8_SPARSE_TABLES=0 or M3AX_STACK_A=0 disables it;
M3_O8_PAIR=0 selects the no-pairing table.

Signed-off-by: Balaram Buddharaju <169953907+brb-nv@users.noreply.github.com>
brb-nv and others added 25 commits October 24, 2026 21:49
…se-decode variant at warmup

Ports the GB300 stall-fix prewarm (m3ax_msa_prewarm) to the built-in MiniMax-M3 MSA path:

- core: enumerates every fmha_sm100 indexer-proxy variant (unsplit q 1..512 and the
  KV-split segmented family) with the library's own dispatch functions and compiles
  the missing ones in parallel under the library's file locks, before the stock
  split-proxy prewarm loads them. The dense family is dropped (MiniMax-M3.1 has no
  dense attention layers).
- sdwarm: records the Triton sparse-decode call shapes during warmup and, when warmup
  finishes, warms every local batch 1..max_batch_size, every pointer-alignment
  signature eager mixed steps produce, and every decode query length the runner can
  use (recorded, 1 + max draft len, dynamic draft lens; DSpark K3 -> 4, K7 -> 8).
- Serve-time observers log the first use of each fmha_sm100 variant, any inline
  fmha_sm100 compile, and every Triton compile after warmup.
- DecoderRunner.warmup and _prewarm_minimax_m3_msa_indexer drive it; non-MSA runners
  are untouched.

Switches (default on, as in the original): M3AX_STACK_A, M3AX_MSA_PREWARM_VARIANTS,
M3AX_SPARSE_DECODE_PREWARM.

Signed-off-by: Balaram Buddharaju <169953907+brb-nv@users.noreply.github.com>
… for GB300 TP2

Adds torch.ops.trtllm.minimax_m3_cute_sparse_decode, a block-sparse GQA
verify-decode kernel for the TP2 NVFP4 shape on SM103: one CTA per (top-k
split, KV head, query row), clusters merging over DSMEM, a bulk-copy smem
ring feeding m16n8k16 HMMAs with NVFP4 dequantized in registers.

With M3_CSD=1, minimax_m3_sparse_attn_decode routes a call to it when the
device is SM103, the rank holds two KV heads, the (decode_query_len, local
batch) key is in the built-in table (dql 4 at b3-b45, dql 6 at b2-b25) and
the operands are covered; every other call (including dql 8) keeps Triton.
M3_CSD_TABLE=<json> replaces the table for benchmarking. Default off.

Ported from the GB300 optimized stack, iteration 4 (lever 05).

Signed-off-by: Balaram Buddharaju <169953907+brb-nv@users.noreply.github.com>
…undary all-reduce, router fast path, NVFP4 hoist, long select)

Built-in MiniMax-M3 / M3.1 model path, decode on SM100/SM103. Off by default:
the master switch is M3AX_L5=1; under it each piece has its own switch, on
unless set to 0 (M3AX_L5_MC_BOUNDARY, M3AX_L5_ROUTER_TC, M3AX_L5_NVFP4_HOIST,
M3AX_L5_SELECT). Row ranges: M3AX_L5_MC_ROWS (1-48),
M3AX_L5_ROUTER_TC_ROWS (2-48), M3AX_L5_SELECT_ROWS (1-72),
M3AX_L5_HOIST_MAX_ROWS (1024).

- mc_boundary: at each post-feed-forward boundary of a decode CUDA-graph
  forward of <= 48 rows, the shared + routed add, the
  AllReduce(RESIDUAL_RMS_NORM) and the next layer's MXFP8 input quantize
  become one minimax_m3_mc_allreduce_norm call. The next layer's QKV
  runs only its GEMM on the published MXFP8 copy, through a new
  prequantized slot in MXFP8LinearMethod. The residual gets one BF16
  rounding instead of two (ulp-level difference). The workspaces reuse
  the M3.1 modeling_v2 tp4 multicast helpers.
- router_tc: decode MoE forwards of 2..48 rows run minimax_m3_router_tc
  for the router logits and the experts' NVFP4 input in one kernel.
- nvfp4_hoist: otherwise the experts' NVFP4 quantize runs on the
  shared-expert aux stream ahead of the shared expert, off the router
  chain (bitwise).
- select: top-16 selects of 1..72 rows over 129..9216 blocks use
  minimax_m3_select_long (bitwise except on > 1024-way ties at the 16th
  score).

At TP2 with DSpark K=3 (4 rows per request) the 48-row parts cover
batch <= 12 and select covers batch <= 18. The NVFP4 pieces apply only
to the TRT-LLM-op TRTLLM-gen NVFP4 experts with in-kernel routing, one
chunk, no DP, comm or EPLB. Eagle3/DSpark capture layers, the last
layer, eager, prefill and breakable-graph forwards keep the stock path.

Signed-off-by: Balaram Buddharaju <169953907+brb-nv@users.noreply.github.com>
… workspace failures across ranks

router_tc computes TF32 router logits, less precise than this model's
FP32-grade MiniMaxM3Gate, so it now needs an explicit M3AX_L5_ROUTER_TC=1.
Workspace creation also records ValueError/OSError as a local failure so
that every rank still reaches tp_agree_built instead of one rank leaving
the collective.

Signed-off-by: Balaram Buddharaju <169953907+brb-nv@users.noreply.github.com>
…mixed-step host trims

Context (prefill) rows of the built-in MSA path, sm_103:

- Block select: context rows take torch.ops.trtllm.minimax_m31_select_prefill_tile
  (one CTA per KV head and 8 queries, each score sector read once) instead of
  minimax_m3_select_blocks. Same table bit for bit, including tie order and
  init / local forcing. Falls back to the stock select off sm_103, without the
  op, under capture / compile, for scores without unit query stride, at or
  below 128 blocks (where the stock bitonic select is faster), past 5856
  blocks (about 737K tokens) and below TRTLLM_M3_PREFILL_SELECT_TILE_MIN_ROWS
  (256) context tokens. Off with TRTLLM_M3_PREFILL_SELECT_TILE=0. In a mixed
  step it writes the context rows straight into the joined head-major table.
- Proxy max score: the context proxy pass writes into an uninitialized
  [heads, max_k_tiles, tokens] block instead of fmha_sm100's -inf filled one;
  the proxy writes every entry the select reads.
- k2q CSR: the NVFP4 prefill builds its CSR worklist and schedule into buffers
  kept on the metadata and reused by every layer, with the schedule sizing done
  once, instead of build_k2q_csr allocating and sizing them per layer.
- per_token_valid_blocks runs in NumPy.

The three host trims are off with TRTLLM_M3_MSA_PREFILL_HOST_TRIMS=0.

Signed-off-by: Balaram Buddharaju <169953907+brb-nv@users.noreply.github.com>
…-input cat

MiniMaxM3Gate builds FP32-grade logits from BF16 activations as
cat([x] * 3, -1) @ split_weight.t(). At 16K prefill rows the cat of
3 x [16K, 6144] costs more than the GEMM. A Triton kernel computes the same
product reading x per term and chaining the BF16 products into one FP32
accumulator in the concatenated K order, which matches the cuBLAS kernel bit
for bit on SM103 for every row count in 8625..16384 (exhaustively checked;
below that cuBLAS alternates between kernels with a different K order).

GB300, real gate weights: 16384 rows 0.298 -> 0.140 ms per layer
(~9 ms per 16K step over 57 MoE layers). Rows outside the range, other
SMs and torch.compile keep the stock path. M3_ROUTER_TRITON_ROWS=off
disables it; it also takes other lo-hi ranges.

Signed-off-by: Balaram Buddharaju <169953907+brb-nv@users.noreply.github.com>
The drafter's context store read the per-row draft block counts back from
the device (.tolist()), blocking the host until the GPU finished the step,
so the drafter, lm_head and sampling tail launched late on every mixed step.
With KVCacheManagerV2's shared-page-table copy every copied row holds no
negative entry, so the count is the row width, known on the host; other
writers keep the device read. TRTLLM_DFLASH_SYNC_CTX_ALLOC=1 restores it.

DSv4 DSpark window seeding reads the chunk's first position from the host
position ids instead of .item().

Signed-off-by: Balaram Buddharaju <169953907+brb-nv@users.noreply.github.com>
A forward waits on the KV tier copies its scheduling issued: offloads that
free GPU slots for its new pages and onboarding of reused prefixes. Both can
be issued earlier, after the previous forward is launched, so they overlap
its compute. All of it is opt-in:

* TRTLLM_M3_ASYNC_OFFLOAD=1: an OffloadHeadroom per v2 pool with a lower tier
  holds TRTLLM_M3_ASYNC_OFFLOAD_TOKENS (default max_num_tokens) of GPU slots
  in a private, stats-excluded cache on a side stream between launch and the
  next schedule. Backing it evicts LRU pages; releasing it leaves free slots
  whose ready event follows those offloads, so the next step's allocations
  take them without evicting.
* TRTLLM_KV_ONBOARD_PREFETCH=<n>: up to n not-yet-admitted first-chunk
  context requests per step have their reused prefix onboarded through a
  transient cache (KvCache.prefetch to the GPU level). Requests arriving
  during a step are activated right after its launch so their copies get
  the lead (TRTLLM_KV_ONBOARD_PREFETCH_EARLY_FETCH=0 disables; off with
  attention DP).
* TRTLLM_KV_ONBOARD_PREFETCH_DEFER_TOKENS=<t>: an arrival whose onboarding
  brings at least t reused tokens to the GPU is staged before its first
  schedule and held out of it for one iteration.

Page ordering stays with the core's per-page and per-slot ready events.

Signed-off-by: Balaram Buddharaju <169953907+brb-nv@users.noreply.github.com>
…attention

TRTLLM_M3_MSA_V1_DECODE=1 routes sparse generation rows to MiniMax MSA's
CUTLASS Q8KV4 decode kernel over the existing NVFP4 pool, page table and
top-k pages; calls outside its contract keep the Triton/CuTe kernels.
Needs an MSA build that reads the M3 4x4 token-quad V-scale layout.

Signed-off-by: Balaram Buddharaju <169953907+brb-nv@users.noreply.github.com>
… NVFP4 index-K mirror

TRTLLM_M3_MSA_V1_INDEXER=1 (with indexer_kv_dtype fp8) widens each sparse
layer's INDEX_KEY page to hold an NVFP4 copy of the E4M3 index-K, refreshed
from this step's slots in run_indexer, and scores pure decode/verify steps
with MiniMax MSA's q8kv4 indexer. The table is sorted back to the ascending,
-1-padded contract; other steps keep the CuTe DSL scorer and FP8 prefill.

Signed-off-by: Balaram Buddharaju <169953907+brb-nv@users.noreply.github.com>
…TRTLLM_M3_CHEAP_PAD)

CUDA-graph padding rows get zeroed logits before rejection sampling, so their
probs are uniform. A uniform row is the worst case of FlashInfer's radix top-p
renormalization (the whole vocabulary falls in one bucket) and one such row
sets the duration of every top-p launch in the step: on MiniMax-M3.1 TP2 DSpark
K6 a padded decode step pays ~9-10 ms in top-p (6 radix passes at 1.2-2 ms
instead of ~0.13 ms), independent of how many padding rows there are.

With TRTLLM_M3_CHEAP_PAD=1, padding rows get a one-hot logits row instead,
which the filters pass through cheaply, and the uniform probs that zeroed
logits give are written back after the filters, so rejection still accepts any
stale draft token on a padding row. Real rows are untouched. Default off.

Signed-off-by: Balaram Buddharaju <169953907+brb-nv@users.noreply.github.com>
…ding rows

Padding rows now always get a one-hot logits row, with uniform probs
restored after the filters, instead of only under TRTLLM_M3_CHEAP_PAD=1.
Real rows are bit-identical, and padding rows end with the same probs as
before, so the change is lossless; it removes 9-10 ms of radix top-p work
from every padded decode step.

Signed-off-by: Balaram Buddharaju <169953907+brb-nv@users.noreply.github.com>
- write_context_onehot_draft_probs: index_fill_ instead of a Python scalar
  assigned through an advanced index, which staged a pageable host tensor and
  synchronized the stream on every step with context rows.
- NVFP4 MSA prefill attention: validate inputs once per step geometry and
  combine straight into the output (no per-layer validation or copy).
- Cache the KV pool device and the MSA-v1 device-capability check.

Signed-off-by: Balaram Buddharaju <169953907+brb-nv@users.noreply.github.com>
(cherry picked from commit 7e90b63)
…ide torch.compile

The registered custom op launches the same compiled kernel behind a Python
custom-op dispatch and an input-description string formatted on every call;
_cutedsl_score runs once per sparse layer of every eager mixed step.

Signed-off-by: Balaram Buddharaju <169953907+brb-nv@users.noreply.github.com>
(cherry picked from commit 2fa0a79)
…task scores

GSM8KInferenceX sets no scores_filter, so the average ran over the 'alias'/'name'
string fields and raised a numpy dtype error after the results were already saved.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: Balaram Buddharaju <169953907+brb-nv@users.noreply.github.com>
(cherry picked from commit 6f7b009)
… over an NVFP4 index-K mirror"

This reverts commit 3860fdd.
…t; agree workspace failures across ranks"

This reverts commit 3780c9b.
…icast boundary all-reduce, router fast path, NVFP4 hoist, long select)"

This reverts commit 8bb33b4.
…-decode launch table

Signed-off-by: Balaram Buddharaju <169953907+brb-nv@users.noreply.github.com>
… prewarm, prefill select, router and DSpark paths

Signed-off-by: Balaram Buddharaju <169953907+brb-nv@users.noreply.github.com>
Signed-off-by: Balaram Buddharaju <169953907+brb-nv@users.noreply.github.com>
Squash of the MiniMax-M3.1 Rubin (sm_107) decode work onto this branch:
the TP2 / TP4 / TP8 low-latency decode targets, the shared sm_107 opt-ins
they arm at routing (shipped CuTe DSL AOT objects, drafter KV reserve and
guard page, host-tier KV prefetch, int32 context token array, BF16X / E4M3X
sparse-prefill fork, proxy max-score reuse, context one-hot index_fill,
DFlash host context allocation, GPU timestamps), the TP2 router half-row
TF32 pin, the TP2 BF16 draft gather with its native Markov-epilogue consumer
(kill switch TRTLLM_M31_DISABLE_DRAFT_AG_BF16), and the fixes and tests that
came with them. Every opt-in keeps
its kill switch.

Where this branch changed the same shared code, both behaviours are kept:
- The sm_107 routing also arms msa_indexer.arm_sm107_msa_paths (no split
  context proxy plan, no prefill host trims, no prefill tile select, no MSA
  warmup prewarm) and interface.arm_sm107_spec_paths (CUDA-graph padding rows
  stay zeroed, FlashInfer's default top-p renorm mode). Every other process
  keeps this branch's defaults.
- DFlash context allocation: armed processes use the host allocation the
  sm_107 targets were validated with; others use _ctx_alloc_tokens.
- Context one-hot rows: index_fill_ everywhere; an armed process's kill
  switch restores the advanced-index write.
- MSA prefill / indexer / backend: the segmented proxy plan, the reused CSR
  workspace and the direct NVFP4 attention launch run where the holds above
  are off; the policy proxy splits, proxy oracle, NVFP4 index-K and the
  BF16X / E4M3X / Q8KV4 fork are unchanged.

Source commits:
- 9d97565 [None][fix] Restore DecoderRunner optional state initialization
- 162e93e [None][feat] Add Rubin (sm_107) MiniMax-M3.1 low-latency decode targets
- bc6d981 [None][fix] Disaggregated generation: one-hot draft probs on the first verify under rejection sampling
- fd38234 [None][fix] Gate TOPP_V6 to sm_107 and keep the token-array copy to host arrays
- 0f4ab31 [None][fix] Arm the shared MiniMax-M3.1 sm_107 paths only for the targets that opt in
- 0c88f87 [None][test] Expect a device token array to be rejected by LlmRequest
- 3f48ba4 [None][test] Cover the KV prefetch startup state when the model does not opt in
- 6aaa96e [None][fix] Arm the shared MiniMax-M3.1 sm_107 opt-ins at routing
- bb04179 [None][fix] Avoid batch-shape specialization in DSpark drafter inputs
- b3f097d [None][test] Cover DSpark input strides and context-view specializations
- 6355f11 [None][fix] Await the iteration-stats RPC instead of blocking the event loop
- 75dfb89 [None][feat] Add a phase-armed Nsight window and a per-rank host-path probe
- a3f86ac [None][test] Run the iteration-stats RPC test in the CPU test list
- 6cb186f [None][perf] Run the MiniMax-M3.1 TP2 router as an exact half-row TF32 GEMM at qualified row counts
- 41cd32f [None][test] Cover the MiniMax-M3.1 TP2 router half-row pin
- 82c5e77 [None][perf] Time the executor with GPU timestamps on the MiniMax-M3.1 sm_107 targets
- f0af3b6 [None][fix] Drop the Triton stand-in stamp from the executor's GPU timestamps
- 8b1d98e [None][chore] Drop the unused separate-library loader from the TP2 router half-row pin
- 1d02b9279c [None][chore] Clarify the MiniMax-M3.1 MXDRAFT projection counts in the sm_107 routing log
- e28c8a3d0b Add guarded M3.1 TP2 BF16 draft gather and native consumer gate
- dfd23153a8 Relocate draft BF16 gather and preparation outside AOT digests
- f8c9e69d72 Prewrite CI-listed B1 arm and native byte oracle tests
- 2433ec82fa [None][perf] Widen M3.1 BF16 draft gather to measured decode rows

Signed-off-by: peihengh <259410613+peihu-nv@users.noreply.github.com>
@peihu-nv

Copy link
Copy Markdown
Collaborator Author

Closing as a duplicate: #21038 / #21039 are retargeted to main instead.

@peihu-nv peihu-nv closed this Oct 27, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants