* [Model] Add MiniMax M3 text backbone skeleton + SwiGLU-OAI clamp activation
Port the MiniMax M3 (text backbone) into vLLM's custom model layout:
- Add MiniMaxM3SparseForCausalLM under vllm/models/minimax_m3/nvidia with the
decoder/model/causal-LM wiring; attention and MoE bodies plus weight loading
are left as stubs. Dense MiniMaxM3MLP is fully ported.
- Add MiniMaxM3SparseForConditionalGeneration as a minimal LM-routing wrapper
(KimiK25-style init_vllm_registered_model on text_config) and register both
architectures.
- Add MiniMaxM3Config (model_type minimax_m3_vl) wrapping MiniMaxM3TextConfig
so config.get_text_config() extracts the backbone; register in the config
registries.
Generalize silu_and_mul_with_clamp to SwiGLU-OAI:
- Add alpha (scales the activation's sigmoid) and beta (added to the
non-activated half) to the CUDA kernel, ops.h, and torch_bindings schema.
Defaults alpha=1.0, beta=0.0 are bitwise-identical to the previous
silu(gate)*up, so existing callers (DeepSeek V4) are unaffected.
- SiluAndMulWithClamp(alpha, beta) used by MiniMaxM3MLP with alpha=swiglu_alpha,
beta=1.0, matching the reference gate*sigmoid(alpha*gate)*(up+1).
AI assistance (Claude) was used for this change.
Signed-off-by: Yongye Zhu <yongye@inferact.ai>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Yongye Zhu <zyy1102000@gmail.com>
* [Model] MiniMax M3: implement MoE block + weight-name mapping
Implement the sigmoid-routed MoE block for MiniMax M3 and map module
names to the checkpoint so weight loading works for the ported modules.
MoE block (MiniMaxM3MoE):
- fp32 router via GateLinear (bf16 activations upcast to fp32; fp32
weights and logits), matching minimax_m2/sglang precision.
- FusedTopKBiasRouter routing (scoring_func from config, sigmoid +
e_score_correction_bias + renormalize), verified to match sglang's
TopK (select-with-bias, weight-without-bias, routed_scaling on output).
- swigluoai activation (from config.hidden_act) + swiglu_limit; shared
expert fused into FusedMoE so the shared partial is reduced with the
routed output.
Weight loading:
- Name the MoE submodule `block_sparse_moe` (dense stays `mlp`) to match
the checkpoint; decoder forward selects per layer.
- load_weights handles gate_up fusion (dense MLP + shared experts) and
expert w1/w2/w3 -> w13/w2 fusion; wrappers delegate via
AutoWeightsLoader, skipping vision/mm/mtp. Not-yet-ported modules
(attention) are skipped until they land.
The expert GEMM/activation kernel correctness and attention/weight
loading for the remaining modules are not part of this change.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Yongye Zhu <zyy1102000@gmail.com>
* [MiniMax M3] Port attention modules and wire up MXFP8 checkpoint loading
Port the weight-bearing dense (MiniMaxM3Attention) and sparse
(MiniMaxM3SparseAttention) attention modules so the checkpoint's
self_attn.* tensors map onto real params (forward still stubbed;
this targets weight loading). Add qkv stacked mapping and the
weight_scale_inv -> weight_scale remap in load_weights.
Load MiniMax-style MXFP8 checkpoints (quant_method: "mxfp8" +
ignored_layers) via the ModelOpt MXFP8 config: register "mxfp8" in
method_to_config and normalize the minimal checkpoint schema to the
ModelOpt schema in ModelOptMxFp8Config.from_config (same on-disk
format). Use setdefault for online shorthands so the checkpoint
config wins over the "mxfp8" online shorthand.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Yongye Zhu <yongye@inferact.ai>
Signed-off-by: Yongye Zhu <zyy1102000@gmail.com>
* [MiniMax M3] Add DeepGEMM MXFP8 MoE backend with swigluoai support
Extend DeepGemmExperts to support MXFP8 activations (FP8 e4m3 + UE8M0
1x32 block scales) via the grouped GEMM with recipe (1, 32), reusing the
oracle/fp8 weight-conversion path. Generalize
deepgemm_post_process_fp8_weight_block to derive the transform recipe
from the block shape ((1, 1, 32) for MXFP8) and accept uint8 E8M0 scales.
Unify the fused gated-activation+quant triton kernels around
y = (up + beta) * gate * sigmoid(alpha * gate): silu is alpha=1, beta=0
(bit-identical to before); swigluoai uses alpha/beta from config. Thread
gemm1_alpha/gemm1_beta from the FusedMoE layer through the MXFP8 quant
config into the kernels, and add swiglu_alpha/swiglu_beta to the layer
and MiniMax M3 config/model (beta sourced from config, not hardcoded).
Wire Fp8MoeBackend.DEEPGEMM into the MXFP8 oracle (selectable via
--moe-backend deep_gemm), resolving directly to DeepGemmExperts (the
Triton fallback cannot handle the 1x32 scheme). Advertise SWIGLUOAI in
_supports_activation so swigluoai selects DeepGEMM rather than falling
through to another backend; gate the MXFP8 scheme to Blackwell (SM100).
Verified on GB200: packed-kernel parity (silu defaults unchanged,
swigluoai matches torch ref), (1,32) weight-prep transform, and a TP=4
launch selecting the DEEPGEMM MXFP8 backend with full weight load (the
run then stops at the still-stubbed attention forward, as expected).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Yongye Zhu <yongye@inferact.ai>
Signed-off-by: Yongye Zhu <zyy1102000@gmail.com>
* [MiniMax M3] Implement dense attention forward
Implement MiniMaxM3Attention.forward (dense path): qkv projection, split,
per-head QK norm (GemmaRMSNorm, qk_norm_type="per_head"), partial RoPE,
attention, and output projection. Mirrors the sglang reference dense path
and vLLM's canonical per-head-norm convention. attention_output_gate is
False for M3, so the gate branch is omitted.
The sparse attention forward (index branch) remains stubbed.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Yongye Zhu <yongye@inferact.ai>
Signed-off-by: Yongye Zhu <zyy1102000@gmail.com>
* [MiniMax M3] Drop dead index value/output projection branch
For M3, sparse_disable_index_value matches sparse_attention_freq exactly
([0,0,0,1,...,1]): the only layers with the flag unset (0-2) are the
non-sparse layers built as MiniMaxM3Attention. Every layer that constructs
MiniMaxM3SparseAttention therefore always disables the index value/output
projections, so index_{v,o}_proj are never created.
Remove the unreachable else branch, the disable_index_value parameter and
field, and the now-unused _disable_index_value_layer_ids helper.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Yongye Zhu <yongye@inferact.ai>
Signed-off-by: Yongye Zhu <zyy1102000@gmail.com>
* [MiniMax M3] Add sparse-attention backend + merged attention layer
Scaffold the lightning-indexer sparse-attention path:
- MiniMaxM3SparseBackend (registered as MINIMAX_M3_SPARSE): block-sparse GQA
backend; get_kv_cache_shape serves both the main K/V cache and the
single-vector index-key side cache.
- MiniMaxM3IndexerCache: side KV cache for per-token index keys, key-only so it
uses a single-vector MLAAttentionSpec rather than a K+V FullAttentionSpec.
- MiniMaxM3SparseMetadata (+ prefill/decode sub-metadata) and its builder,
splitting the batch via split_decodes_and_prefills.
- MiniMaxM3SparseImpl: subclasses AttentionImplBase so it can take a custom
forward(query, index_query, kv_cache, index_kv_cache); no alibi / sliding
window / logits soft cap. forward is a stub pending the kernel port.
MiniMaxM3SparseAttention is merged into a single AttentionLayerBase: it owns the
projections, per-head QK norm and RoPE, binds the backend + impl, registers the
main K/V cache, and holds the index cache. Its forward computes q/k/v and the
index q/k, pre-inserts K/V and index-K into their caches, then calls the sparse
impl with only the queries.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Yongye Zhu <yongye@inferact.ai>
Signed-off-by: Yongye Zhu <zyy1102000@gmail.com>
* [MiniMax M3] Implement sparse-attention forward + MXFP8 DeepGEMM MoE e2e
Port the MiniMax M3 lightning-indexer sparse attention to a Triton backend and
fix the MXFP8 DeepGEMM MoE path so the model runs end to end.
Attention (vllm/v1/attention/ops/minimax_m3_sparse_ops.py + backend):
- Triton kernels (paged, page == sparse block == 128): index block-score +
bitonic top-k, and GQA block-sparse flash attention over the selected blocks.
- MiniMaxM3SparseImpl.forward: decode-first split, dispatching the same kernels
per phase (a decode token is a 1-token prefill). Index and main caches use
separate block tables.
- Dedicated MiniMaxM3IndexerBackend for the key-only index cache so the main
GQA cache (num_kv_heads==1 at TP>=4) is not mistaken for the index layout.
- get_supported_kernel_block_sizes()==[128] (one sparse block per KV page).
MXFP8 DeepGEMM MoE:
- Prepare-phase activation quant emits float32 per-(1,32) group scales for the
DeepGEMM backend (use_deep_gemm_packed_mxfp8 on the quant config), matching
the FP8 128-block path with group=32.
- deepgemm_moe_permute / ep_scatter take a block_size so the activation-scale
group (32) is honored through the expert permute.
- workspace_shapes uses the contiguous-layout M alignment (not block_shape[0],
which is 1 for MXFP8 and under-sized the workspace).
GSM8K (5-shot, TP=4) flexible-extract 0.921 / strict 0.919.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Yongye Zhu <yongye@inferact.ai>
Signed-off-by: Yongye Zhu <zyy1102000@gmail.com>
* [MiniMax M3] Enable decode CUDA graphs + dedicated split-K decode kernels (#7)
Two changes to the sparse-attention backend:
1. Full decode CUDA-graph support. The metadata builder now declares
AttentionCGSupport.UNIFORM_SINGLE_TOKEN_DECODE and precomputes all
per-step kernel args (cu_seqlens_q, prefix_lens, max_query_len,
num_actual_tokens) in build(), removing the .item() host sync and the
per-step torch.zeros/cumsum/diff from the impl forward. Derived decode
prefix lengths are written into a persistent buffer so the captured
graph reads stable addresses across replays.
2. Dedicated split-K decode kernels (mirroring the sglang reference)
instead of reusing the prefill kernels with BLOCK_SIZE_Q=1, which left
the GPU idle at decode (one query token per request). The index score
now splits over seq blocks and the GQA attention splits over the
selected top-k blocks with an LSE merge (flash-decoding). Chunk counts
depend only on shape constants, so the grid is fixed within a CUDA
graph.
Verified: a parity test against the prior (GSM8K 92.1) prefill-as-decode
path matches exactly on top-k selection and on attention output (bf16
noise) across seq lengths 128-2048.
Signed-off-by: Yongye Zhu <zyy1102000@gmail.com>
* Init
Signed-off-by: Jee Jee Li <jeejeelee@inferact.ai>
* Move
Signed-off-by: Jee Jee Li <jeejeelee@inferact.ai>
* FIX
Signed-off-by: Jee Jee Li <jeejeelee@inferact.ai>
* Addresss conflict
Signed-off-by: Jee Jee Li <jeejeelee@inferact.ai>
---------
Signed-off-by: Yongye Zhu <zyy1102000@gmail.com>
Signed-off-by: Jee Jee Li <jeejeelee@inferact.ai>
Co-authored-by: Yongye Zhu <zyy1102000@gmail.com>