Skip to content

Add Vision Support for Minimax-M3#25113

Draft
timkhronos wants to merge 55 commits into
ggml-org:masterfrom
timkhronos:MSA-Vision
Draft

Add Vision Support for Minimax-M3#25113
timkhronos wants to merge 55 commits into
ggml-org:masterfrom
timkhronos:MSA-Vision

Conversation

@timkhronos

@timkhronos timkhronos commented Jun 28, 2026

Copy link
Copy Markdown
Contributor

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 embd
overall cosine : 0.999949
per-token cosine: mean=0.999454 min=0.963887 (worst token 95)
relative L2 err : 0.010137
abs 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:

  • Helped write and debug a small local python comparison script, that was used to dump MiniMax-M3-VL vision embeddings from the Hugging Face implementation for parity checks.
  • Helped review and organize explanations of the implementation while I was debugging.
  • Helped reason through code comments and possible wording.
  • The submitted llama.cpp implementation was not accepted blindly from AI output.
  • I can explain the implemented code paths and the MiniMax/HF behavior they are matching.

If a stricter or differently formatted disclosure is preferred, please specify the exact wording/fields expected.

danielhanchen and others added 30 commits June 22, 2026 15:34
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.
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>
@github-actions github-actions Bot added model Model specific testing Everything test related mtmd Related to multimodal functionality (video/image/audio) conversion labels Jun 28, 2026
@ggml-gh-bot

This comment was marked as resolved.

@ngxson ngxson left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

feels like > 90% of the code is AI-generated, do you really understand it?

Comment thread tools/mtmd/models/minimax_m3.cpp Outdated
Comment thread tools/mtmd/models/minimax_m3.cpp Outdated
Comment thread tools/mtmd/models/minimax_m3.cpp Outdated
@ngxson

ngxson commented Jun 29, 2026

Copy link
Copy Markdown
Collaborator

A standard ViT backbone (separate biased q/k/v/o, LayerNorm, GELU MLP, full bidirectional attention, no mask, no windowing) that diverges from vanilla CLIP in four ways:

* **Conv3D patch embed, run as summed Conv2D slices.** The HF model uses a Conv3D   patch embedding with `temporal_patch_size` slices, conversion splits the 5D weight into per-slice Conv2D kernels (`V_ENC_EMBD_PATCH` + `.weight.{t}`) and the graph sums the outputs. Exact for still images (video out of scope). No patch-embed bias (asserted).

* **Custom 3-axis (T/H/W) RoPE.** `axis_dim = 26`, `rope_dim = 3·26 = 78`, applied to the first 78 channels of each head with HF `rotate_half` semantics, tail passed through. Cos/sin are host-precomputed and fed as graph inputs (`minimax_cos`/`minimax_sin`). Since `rope_dim (78) < d_head (80)` this is partial rotary,  same pattern as the text tower, 3-axis.

* **2×2 spatial-merge token reduction.** Patches are reordered raster -> block (matching the HF flatten) and merged 2×2, so the projector consumes groups of 4. `spatial_merge_size` is emitted in conversion.

* **No class token, no absolute position table, no post-layernorm.** A `pre_layernorm` only; sinks / abs-pos / class-embedding all absent and asserted null.

@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
@ngxson

ngxson commented Jun 29, 2026

Copy link
Copy Markdown
Collaborator

I refuse to proceed with this PR until you being honest about AI usage

@timkhronos

Copy link
Copy Markdown
Contributor Author

@ngxson I expanded on the AI disclosure in the PR description, to cover precisely what AI was used to assist with.

Comment thread tools/mtmd/models/minimax-m3.cpp
Comment thread tools/mtmd/clip.cpp Outdated
Comment thread tools/mtmd/clip.cpp Outdated
@timkhronos

timkhronos commented Jul 1, 2026

Copy link
Copy Markdown
Contributor Author

For anyone testing, the latest changes require the mmproj file to be reconverted.

I uploaded one here.
Alternatively you can also convert a fresh one with this pr, without downloading the full model weights by using:

python convert_hf_to_gguf.py MiniMaxAI/MiniMax-M3 --mmproj --remote --outtype bf16 --outfile Minimax-mmproj.gguf

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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

conversion model Model specific mtmd Related to multimodal functionality (video/image/audio) testing Everything test related

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants