Skip to content

model: add MiniMax-M3.1 - #320

Open
danielhanchen wants to merge 4 commits into
masterfrom
minimax-m31
Open

danielhanchen wants to merge 4 commits into
masterfrom
minimax-m31

Conversation

@danielhanchen

@danielhanchen danielhanchen commented Oct 23, 2026 •

Copy link
Copy Markdown
Member

Adds MiniMax-M3.1-Flash: https://huggingface.co/MiniMaxAI/MiniMax-M3.1-Flash

  • Converter for the mixed MXFP8 + NVFP4 checkpoint: routed experts stay native NVFP4 with a per-expert 1/global_scale tensor (or are dequantized exactly to BF16 / F16 / F32 / Q8_0 with --outtype, --fp8-as-q8 for Q8_0 dense weights); MXFP8 tensors decode exactly
  • Sparse attention (MSA) on every layer, including the dense-lead layers: a layer runs MSA iff it carries indexer tensors, so MiniMax-M3 GGUFs behave exactly as before
  • MSA batch path chunked over tokens so no intermediate reaches 2^31 elements (large prompts with a large -ub)
  • BPE tokenizer applies tokenizer.ggml.normalizer.nfc like the Unigram one already does (M3.1 declares an NFC normalizer); GGUFs without the key are unaffected
  • DSpark speculative drafter: DSparkMiniMaxDraftModel converter, swigluoai in dflash.cpp and per-layer target taps
  • Fix: the Gemma 1 + w norm add runs in F32, so F16 sources no longer lose the low bits of w

Tested against transformers in float64 with exact dequantization, on fixtures with the real tokenizer and storage formats: mean KL 5.8e-7 (BF16 source) and 5.6e-7 (NVFP4 + MXFP8), top-1 100%; the MiniMax-M3 layout gives 1.8e-6. The tokenizer matches HF on 3,221 documents and 605 rendered chats. On the real weights, the native NVFP4 GGUF agrees with a reference GPU implementation on 95.5% to 99.1% of top-1 tokens over its own greedy continuations (chat, math, code), and greedy decoding with the DSpark drafter is identical to plain greedy. MiniMax-M3 and MiniMax-M2 GGUFs convert byte-identically and give bitwise-identical logits on this branch. GGUFs: https://huggingface.co/unsloth/MiniMax-M3.1-Flash-GGUF

Run with llama-server -m MiniMax-M3.1-Flash.gguf --jinja -fa on -np 1 (sparse attention needs one slot or --no-kv-unified); for DSpark add --spec-type draft-dspark -md MiniMax-M3.1-Flash-DSpark-BF16.gguf --spec-draft-p-min 0.

The branch is based on upstream b11835, after the BPE tokenize() rewrite in ggml-org#30974.

AI Usage

Used Claude and Local Models for testing, iteration and code design - manual verification of model / PR usage

…lead layers, DSpark draft, BPE NFC)

- convert: MiniMax-M3.1 checkpoints (MXFP8 dense weights, NVFP4 routed experts). Experts pass through
  as native NVFP4 with a per-expert .scale tensor by default; --fp8-as-q8 and --outtype f32/bf16/f16/q8_0
  dequantize exactly. Raw config.json is read for the sparse attention fields (transformers >= 5.15).
- minimax-m3: a layer runs MSA iff it carries indexer tensors (M3.1 enables it on the dense-lead layers),
  NVFP4 expert scales are passed to build_moe_ffn, and the MSA batch path is chunked below 2^31 elements.
- vocab: the BPE session applies tokenizer.ggml.normalizer.nfc, as the Unigram session already does.
- DSpark: DSparkMiniMaxDraftModel converter, swigluoai in dflash, per-layer target taps in minimax-m3.
@danielhanchen
danielhanchen requested a review from CISC as a code owner October 23, 2026 13:32
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 23, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-23T13:40:58.551126Z a9a210a PR opened
🔒 Security Review ✅ Completed 2026-10-23T13:35:21.037643Z a9a210a PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a9a210a1ea

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread conversion/minimax.py
raise ValueError(f"MiniMax DSpark: scale {name!r} has no weight")
w = self.model_tensors[weight_name]
s = self.model_tensors.pop(name)
self.model_tensors[weight_name] = lambda w=w, s=s: dequant_mxfp8(w(), s())

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Mark dequantized DSpark weights for Q8_0

When converting the new DSpark checkpoint with --fp8-as-q8, this override dequantizes each MXFP8 weight but never adds weight_name to _fp8_dequantized. ModelBase.tensor_force_quant() only selects Q8_0 for names in that set, so the CLI option silently writes these weights in the inferred F16/BF16 type instead; add the same bookkeeping used by MiniMaxM3Model.dequant_model().

Useful? React with 👍 / 👎.

Comment thread src/llama-vocab.cpp
try {
std::string canonical;
canonical.reserve(text.size());
for (const uint32_t cpt : unicode_cpts_normalize_nfc(unicode_cpts_from_utf8(text))) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Detect malformed UTF-8 before NFC normalization

For an NFC-enabled BPE vocabulary, malformed UTF-8 never reaches the intended catch path because unicode_cpts_from_utf8() catches invalid sequences internally and substitutes U+FFFD. The new normalizer therefore replaces raw invalid bytes before byte-level BPE processes them, whereas the previous tokenizer preserved and tokenized those bytes, so non-UTF-8 prompt data now changes tokenization despite the stated fallback.

Useful? React with 👍 / 👎.

Comment thread conversion/minimax.py
return self._nvfp4_pack_np(t[0], t[1])
w = self._dequant_nvfp4_eager(t[0], t[1], t[2])
# torch's float -> bf16 / f16 casts round to nearest even, like gguf.quants
return w.to(self._DEQUANT_RAW[self.ftype][1]).contiguous().view(torch.uint8).numpy()

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Byte-swap raw expert values for big-endian GGUF

When --bigendian is used with an explicit F32 or F16 output type, this converts the expert values to typed Torch data and then exposes them to GGUFWriter as uint8. The writer's endian conversion consequently performs a one-byte byteswap, leaving the little-endian multi-byte values unchanged while marking the GGUF as big-endian, so all routed-expert weights are corrupted on load. Preserve a typed NumPy representation or explicitly swap each 2/4-byte value before passing the raw buffer.

Useful? React with 👍 / 👎.

Comment thread conversion/minimax.py
Comment on lines +321 to +323
if m is not None and (self._nvfp4_native or self.ftype in self._DEQUANT_RAW):
experts.setdefault((int(m.group(1)), m.group(3)), {})[int(m.group(2))] = base
continue

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Reject fused experts until MiniMax-M3 can load them

With --outtype q8_0 --fuse-gate-up-exps, Q8_0 is not in _DEQUANT_RAW, so the new packed experts fall through to the inherited conversion path, where the gate and up tensors are replaced by one ffn_gate_up_exps tensor. The MiniMax-M3 tensor loader still requires separate ffn_gate_exps and ffn_up_exps, and its graph passes no fused tensor to build_moe_ffn, so the resulting GGUF fails to load; reject this option combination or add fused-expert loader and graph support.

Useful? React with 👍 / 👎.

Upcast the norm weight to F32 before adding 1 instead of relying on the
incoming dtype. prepare_tensors already turns BF16 tensors into F32 before
modify_tensors, so BF16 checkpoints convert byte-identically (M3.1 and
MiniMax-M3 layout fixtures, DSpark draft, all outtypes); the change keeps the
add exact for F16 sources and for callers that pass a raw BF16 tensor, and
matches the DSpark draft converter which already adds 1 in F32.
danielhanchen added a commit that referenced this pull request Oct 25, 2026
@danielhanchen
danielhanchen changed the base branch from base/upstream-f64a49819 to base/upstream-b3fa69955 October 25, 2026 14:31
danielhanchen added a commit that referenced this pull request Oct 25, 2026
pr-set: repin Inkling, #298 and #320 onto b11871
@danielhanchen
danielhanchen changed the base branch from base/upstream-b3fa69955 to master October 25, 2026 23:32
@danielhanchen danielhanchen changed the title model : MiniMax-M3.1 (MXFP8 + NVFP4 checkpoint conversion, sparse attention on the dense-lead layers, DSpark draft, BPE NFC) model: add MiniMax-M3.1 Oct 26, 2026

This branch has not been deployed

No deployments
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.

1 participant