Add Vision Support for Minimax-M3#25113
Conversation
Text-only port that re-uses existing components: MiniMax-M2 style GQA with per-head QK-norm and partial rotary, DeepSeek-V3 style leading-dense and routed/shared experts, and swigluoai activation. Sparse attention is not yet supported (dense fallback); vision tower and MTP heads are dropped.
…ch per group block picking
4-way paths. Full debug harness remains at <8136a9c68ed7a5eb009aa67bba3fda8062f4648f> for reproducing the selection-parity validation.
Note: All GGUFs generated before this change will need to be regenerated.
Co-authored-by: Sigbjørn Skjæret <1629204+CISC@users.noreply.github.com>
Co-authored-by: Sigbjørn Skjæret <1629204+CISC@users.noreply.github.com>
Co-authored-by: Sigbjørn Skjæret <1629204+CISC@users.noreply.github.com>
Co-authored-by: Sigbjørn Skjæret <1629204+CISC@users.noreply.github.com>
This comment was marked as resolved.
This comment was marked as resolved.
ngxson
left a comment
There was a problem hiding this comment.
feels like > 90% of the code is AI-generated, do you really understand it?
@timkhronos did you even read what AI generates here? and ask yourself how wrong it is? there is no "vanilla CLIP" in mtmd, this model is just qwen-vl with some subtle differents |
Rewrite code comment based on feedback and to better reflect the actual architecture, and reuse existing build_vit
|
I refuse to proceed with this PR until you being honest about AI usage |
|
@ngxson I expanded on the AI disclosure in the PR description, to cover precisely what AI was used to assist with. |
|
For anyone testing, the latest changes require the mmproj file to be reconverted. I uploaded one here.
|
Log indexer cache size on launch Disallow ctx shift Support prompt caching
Fully rewrote minimax-m3.cpp for speed and buffer size gains: Unified the 4-way + decode, 1 FA call per layer instead of 4, with the groups mapped onto ne[3] Custom CPU op now emits block-level mask, expanded on GPU, which causes CPU to GPU transfer to shrinks at prefill Decode: ~25 nodes/layer vs ~50, no per-group concats/conts Unified selection semantics, so both regimes rank bs + local bias (position-anchored local force), which means prefill/decode can no longer disagree on selection can_reuse on the MSA bias input. Graph reuse at decode restored (was rebuilding the full graph every token) In-place mask adds, shrinking compute buffer ~6.8 to ~4.2 GiB at ub2048/62k Multi-stream: MSA now runs with -np N when kv_unified=false. Decode stays batched across streams (still 1 FA call), prefill loops per stream. dense fallback only for --kv-unified + multi-seq Measured effect on expert offload bound setup: decode 6.2(4WAY)–7.15(MSA_decode) -> 7.7~7.8 t/s, flat from 5k to 60k+. prefill around 10% faster. buffer about 20% smaller, multi-user support.
Overview
Implement MiniMax-M3 vision support. The vision tower itself is a Qwen2.5-VL style ViT (now reuses build_vit). The major differences are that M3 uses a 3-axis (T/H/W) RoPE, a gate-less GELU-erf FFN and a two-stage patch-merge projector.
Stacked on #24908, so the full diff carries the MSA base until that merges. The vision-only changes are [here]
Additional information
The preprocessing matches Qwen2.5-VL's. Further, in the graph the summed-temporal-Conv2D patch embed, the 2×2 spatial-merge reorder, separate biased q/k/v attention, and pre-LN should also match.
Expanding a bit on the differences, the most substantial one is the 3-axis RoPE. The 3 bands are laid out as cat([f,f]) with a HF split half pairing and an axis_dim keyed frequency schedule. I don't think this can reuse the existing qwen ggml_rope_multi and the ggml_rope_type_vision, as the existing op can't express it without a q/k weight permute at conversion plus a vision mode that doesn't exist. The graph-level cos/sin matches HF directly, and uses the same approach build_rope_2d already uses for the 2-axis vision rope, generalized to 3 axes. T is the temporal axis, and for still images it's coordinate 0, but the layout should stay so H/W keep the same channels as HF.
Vision MLP is a plain GELU-erf, while qwen2.5vl uses a gated FFN.
The projector itself is a two-stage projector. Uses per patch MLP (mm.1 / mm.2), 2×2 group concat, then merge MLP (mm.merge.fc1 / fc2), both using GELU-erf, while qwen uses a single post-merge MLP.
There is also no post-layer norm and no window attention, only pre_layernorm.
Validation
The metrics below are for the pre build_vit change. Will retest.
Generated vision embeddings vs the HF reference on an identical sample image:
shape : 256 tokens x 6144 embdoverall cosine : 0.999949per-token cosine: mean=0.999454 min=0.963887 (worst token 95)relative L2 err : 0.010137abs err : mean=0.03815 max=15.16844(The high max-abs is most likely a single high-magnitude channel; cosine and relative-L2 are the
embedding-level metrics.)
Requirements
AI assistance disclosure
AI assistance was used during development, but the code is not an unreviewed AI-generated code drop.
Scope of AI assistance:
If a stricter or differently formatted disclosure is preferred, please specify the exact wording/fields expected.