Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
90 commits
Select commit Hold shift + click to select a range
7b84f33
[MiniMax-M3.1] MXFP4 sparse KV pool and index-cache declaration
ganyi1996ppo Nov 4, 2026
7f45385
[MiniMax-M3.1] MXFP4 write path: one fused kernel writes K, V and the…
ganyi1996ppo Nov 4, 2026
82c136d
[MiniMax-M3.1] Score the MXFP4 index cache with aiter's FlyDSL scorer…
ganyi1996ppo Nov 4, 2026
f8db5ae
[MiniMax-M3.1] MXFP4 KV attention through aiter pa_decode; ATOM_M3_IN…
ganyi1996ppo Nov 4, 2026
2e700ec
[MiniMax-M3.1] MXFP4 review fixes: padded replay, TBO plans, FP8-q re…
ganyi1996ppo Nov 4, 2026
13f413d
[MiniMax-M3.1] Build the selector bounds from the captured batch on a…
ganyi1996ppo Nov 4, 2026
bb1719e
[MiniMax-M3.1] MXFP4 round 4: per-ubatch TBO score buffers, padded sc…
ganyi1996ppo Nov 4, 2026
77b5ee9
[MiniMax-M3.1] MXFP4: size each TBO ubatch's score buffers for its ha…
ganyi1996ppo Nov 4, 2026
55070fb
[MiniMax-M3.1] MXFP4: select the KV cache with --kv-cache-dtype mxfp4…
ganyi1996ppo Nov 4, 2026
3157448
[MiniMax-M3.1] MXFP4: take the public cache dtype as fp4, not mxfp4
ganyi1996ppo Nov 4, 2026
bf89638
[MiniMax-M3.1] MXFP4: drop pass-through aliases and duplicate index-d…
ganyi1996ppo Nov 4, 2026
26e1402
[MiniMax-M3.1] config: keep the literal deepseek_v41_text check the g…
ganyi1996ppo Nov 4, 2026
1fe8b16
[MiniMax-M3.1] MXFP4 tests: cover KV-only and index-only fp4 writes a…
ganyi1996ppo Nov 4, 2026
1aacf2c
[MiniMax-M3.1] MXFP4: resolve the index cache dtype through aiter's f…
ganyi1996ppo Nov 4, 2026
9f42485
[MiniMax-M3.1] MXFP4: hold the index cache as "fp4x2" from init, reso…
ganyi1996ppo Nov 4, 2026
4ec437c
[MiniMax-M3.1] MXFP4: keep the index cache "fp4" inside ATOM, name it…
ganyi1996ppo Nov 4, 2026
1f1eb0b
[MiniMax-M3.1] MXFP4: name the sparse KV pool format "fp4", not "mxfp4"
ganyi1996ppo Nov 4, 2026
1abc945
[MiniMax-M3.1] MXFP4: review follow-ups -- docs, error wording, M3 ga…
ganyi1996ppo Nov 4, 2026
b06f398
[MiniMax-M3.1] DSpark calibration identity: tell MXFP4 KV from fp8 KV
ganyi1996ppo Nov 4, 2026
87ef9f9
[MiniMax-M3.1] MXFP4: M3's startup checks move to models/minimax_m3/c…
ganyi1996ppo Nov 4, 2026
a1f6a1e
[MiniMax-M3.1] MXFP4 KV needs a bf16 model: refuse an fp16 one at sta…
ganyi1996ppo Nov 4, 2026
3172fd4
[MiniMax-M3.1] MXFP4 index score buffer: size rows for the step width…
ganyi1996ppo Nov 5, 2026
d7d86db
[MiniMax-M3.1] Address review: single fp4 dtype source, shared plan c…
ganyi1996ppo Nov 5, 2026
487b695
Fix RUF028: keep the fmt: skip on a statement in the eager-width inde…
ganyi1996ppo Nov 5, 2026
e8ce8ea
Merge origin/main into ganyi/m31-mxfp4-main
ganyi1996ppo Nov 5, 2026
09f14bb
Simplify MiniMax-M3 FP4 KV dtype routing
ganyi1996ppo Nov 5, 2026
6c790ca
WIP: route MiniMax-M3 paged KV through MXFP4
ganyi1996ppo Nov 5, 2026
6ffa554
Skip DSpark MXFP4 tests when aiter is unavailable
ganyi1996ppo Nov 5, 2026
04b0b2d
Revert unvalidated all-layer MiniMax-M3 MXFP4 expansion
ganyi1996ppo Nov 5, 2026
16c940a
Reject MiniMax-M3 --kv-cache-dtype fp4; keep the MXFP4 index cache only
ganyi1996ppo Nov 5, 2026
a874ae0
Skip the MXFP4 index tests on an aiter without the FP4 index ops
ganyi1996ppo Nov 5, 2026
468d4f3
Drop the thin FlyDSL wrappers around the MXFP4 index scorers
ganyi1996ppo Nov 5, 2026
af92260
Add MiniMax-M3.1 MXFP4 KV cache with separate DSpark draft dtype
ganyi1996ppo Nov 5, 2026
16aa94d
Restore the MiniMax-M3.1 MXFP4 KV tree; port only the M3 gate and dra…
ganyi1996ppo Nov 6, 2026
94fb548
Review 5797687431: one decode row rule for every index cache, draft K…
ganyi1996ppo Nov 9, 2026
10cbc61
Review 5781200929 B14/B15: read index_cache_dtype directly, fix the m…
ganyi1996ppo Nov 9, 2026
b643e36
Merge origin/main into ganyi/m31-mxfp4-main
ganyi1996ppo Nov 9, 2026
453a7ad
Resolve duplicate kv_cache_dtype entries left by merging origin/main
ganyi1996ppo Nov 9, 2026
05d2969
Merge origin/main into ganyi/m31-mxfp4-main
ganyi1996ppo Nov 9, 2026
a70e5d5
Review 5797687431 items 1/2: refuse fp4 KV beside a non-fp4 index; sc…
ganyi1996ppo Nov 9, 2026
4e4dca9
MiniMax-M3: an unspecified dtype stays bf16, fp4 only when asked for
ganyi1996ppo Nov 9, 2026
cc42800
Copilot review: calibration without a draft, fill_blocks=0, KvLayout.…
ganyi1996ppo Nov 9, 2026
e8c8cbf
MXFP4 index scorer: allocate the scores per call, keep only the work …
ganyi1996ppo Nov 9, 2026
efb1f0f
MXFP4 index scorer: the work map is a builder attribute, not a forwar…
ganyi1996ppo Nov 9, 2026
dccfeaa
Merge origin/main into ganyi/m31-mxfp4-main (resolve conflicts)
ganyi1996ppo Nov 10, 2026
7bbdd07
Drop IndexWorkMap/IndexWorkMapStep and the eager block-count shrink
ganyi1996ppo Nov 10, 2026
97f7aac
Inline decode_rows_batch at its only call site
ganyi1996ppo Nov 10, 2026
cd12c4d
Decide "does the draft own a KV pool" once, off the selector in plugi…
ganyi1996ppo Nov 10, 2026
7a3ef18
Key the MXFP4 row rule on the index cache, not on a kwargs dict's truth
ganyi1996ppo Nov 10, 2026
62307b2
Carry the work map's block bound on the sparse metadata instead of re…
ganyi1996ppo Nov 10, 2026
68fc39c
Give sparse_pa_mxfp4 an out= and zero the DP pad tail once, like SHUFFLE
ganyi1996ppo Nov 10, 2026
e2f7175
Drop the redundant fp4 factor from compute_hash and read spec.kv_cach…
ganyi1996ppo Nov 10, 2026
e2dc60c
Say which dtype and which cause the DSpark q_out dtype warning is about
ganyi1996ppo Nov 10, 2026
c741992
Delete the empty index_mxfp4.py and bring the stale docs and leftover…
ganyi1996ppo Nov 10, 2026
620692b
Pass the MXFP4 decode scorer a persistent, unfilled, contiguous out=
ganyi1996ppo Nov 10, 2026
fd64ce3
Score the MXFP4 decode index into the indexers' LogitsWorkspace
ganyi1996ppo Nov 10, 2026
d7da994
Decide "the draft owns a KV pool" from the attention family, not by r…
ganyi1996ppo Nov 11, 2026
669a967
Settle DeepSeek-V4's fp8 -> bf16 KV fallback in Config, before the dr…
ganyi1996ppo Nov 11, 2026
773be3b
Answer "the draft owns a KV pool" for EAGLE3 as well as DSpark
ganyi1996ppo Nov 11, 2026
b587f52
Carry the MXFP4 index scorer's map, bound and workspace as one record…
ganyi1996ppo Nov 11, 2026
785e40f
Give the MXFP4 prefill scorer the shared, unfilled score workspace too
ganyi1996ppo Nov 11, 2026
294e392
Offer only the draft KV dtypes a draft can have: --spec-decode-kv-cac…
ganyi1996ppo Nov 11, 2026
543e7f6
One source for M3's 128-token block, and one geometry check of an MXF…
ganyi1996ppo Nov 11, 2026
a75813c
Revert diff noise: moved test lines and a split one-line import
ganyi1996ppo Nov 11, 2026
1ef8661
Say how the M3 K/V binding covers fp4 in its comment
ganyi1996ppo Nov 11, 2026
a7c2aca
State M3's fp4 rules once: in validate_runtime_config and the configu…
ganyi1996ppo Nov 11, 2026
ab7432c
Give IndexScoreBuffers an import-free home so the non-GPU suite can b…
ganyi1996ppo Nov 11, 2026
9371309
Build the EAGLE3 draft at the dtype its pool was allocated at
ganyi1996ppo Nov 11, 2026
4cc42e7
Skip the whole-batch MXFP4 scorer map on a decode that splits
ganyi1996ppo Nov 11, 2026
224ae84
Address the 4cc42e7ed review: scorer allocation out of the MHA builde…
ganyi1996ppo Nov 12, 2026
e236bbc
Test that another index cache allocates no scorer, without aiter
ganyi1996ppo Nov 12, 2026
5c76e58
Import FlyDSL where it is selected; ask the plugin question before re…
ganyi1996ppo Nov 12, 2026
da0c003
Merge origin/main into ganyi/m31-mxfp4-main
ganyi1996ppo Nov 12, 2026
c48f677
Clamp a pad row's causal length where the block-table emission takes …
ganyi1996ppo Nov 12, 2026
75ffc24
Drop two tests that repeat a stricter test or the stdlib
ganyi1996ppo Nov 12, 2026
0c514f9
Inline kv_pages_leave_pool, a one-line wrapper over bool(kv_transfer_…
ganyi1996ppo Nov 12, 2026
2e3790a
Shorten the MXFP4 decode scorer docstring to what the buffers' docstr…
ganyi1996ppo Nov 12, 2026
2995ae4
Record the aiter minimum the MiniMax-M3 index paths need (valarLip: b…
ganyi1996ppo Nov 12, 2026
70bd1ad
Name aiter's writer formats from one table (B15, the mapping half)
ganyi1996ppo Nov 12, 2026
b60961e
Name aiter#7057, the fused MXFP4 writer, in the minimum-aiter note
ganyi1996ppo Nov 12, 2026
212f185
Move --spec-decode-kv-cache-dtype out of this PR (valarLip 11-12 item 6)
ganyi1996ppo Nov 12, 2026
2f174c2
MiniMax-M3.1: --kv-cache-dtype fp4 defaults the index cache to fp4; n…
ganyi1996ppo Nov 12, 2026
96b7627
Trim the tests this PR adds: one test per behaviour, no plumbing or d…
ganyi1996ppo Nov 12, 2026
ec212f6
Shorten comments and docstrings that restate the code (no code change)
ganyi1996ppo Nov 12, 2026
880044c
Keep the test that pins the draft-pool family table to the backends t…
ganyi1996ppo Nov 12, 2026
2808171
Refuse the MXFP4 caches under decode context parallelism
ganyi1996ppo Nov 12, 2026
015f202
Let the sparse impl declare its KV layout; type KvGeometry.layout; do…
ganyi1996ppo Nov 12, 2026
7fe048d
Test: a graph capture must not allocate the scorer buffers it already…
ganyi1996ppo Nov 12, 2026
bd8fd40
Type KvGeometry.layout inside pool_layout; trim the comment and docs …
ganyi1996ppo Nov 12, 2026
ececbeb
Declare kv_layout once on the impl base; refuse MXFP4 KV under sequen…
ganyi1996ppo Nov 12, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 75 additions & 2 deletions atom/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -1380,6 +1380,10 @@ class SpeculativeConfig:
# probability that the first i+1 draft tokens are all accepted), filled in by
# __post_init__. None => real draft/target rejection sampling.
synthetic_acceptance_rates: list[float] | None = None
# The draft's KV dtype: None until `Config` settles it (`resolve_draft_kv_dtype`,
# once the target's dtype is known); every reader (the draft's pool, its
# attention, the calibration identity, the compile hash) reads this one.
draft_kv_dtype: str | None = field(default=None, init=False)

# model_type → mtp_model_type mapping
_MTP_TYPE_MAP: ClassVar[dict[str, str]] = {
Expand Down Expand Up @@ -1941,6 +1945,24 @@ def qrep_for_step(
return qrep_enabled and not use_seg_mla and (not is_prefill or prefill_qrep)


def _check_fp4_kv(kv_cache_dtype: str, *, is_m3: bool, is_v41: bool) -> None:
"""``--kv-cache-dtype fp4`` is MiniMax-M3.1's to ask for and DeepSeek-V4.1's own;
refused for every other model. The guard admits the MiniMax-M3 architecture and
``minimax_m3.config.validate_runtime_config`` then refuses what is not M3.1
(a model with dense attention layers)."""
if kv_cache_dtype == "fp4" and not (is_m3 or is_v41):
raise ValueError(
"--kv-cache-dtype fp4 is supported by MiniMax-M3.1 and DeepSeek-V4.1 only"
)


def resolve_draft_kv_dtype(target_dtype: str, *, own_pool: bool) -> str:
"""The KV dtype of a speculative draft's pages: the target's, except bf16 for a
draft with a pool of its own (``own_pool``) under an fp4 target, which has no
draft layout. A draft in the target's pool has the target's dtype."""
return "bf16" if own_pool and target_dtype == "fp4" else target_dtype


def indexer_cp_unsupported_reason(
arches,
tp_size: int,
Expand All @@ -1949,6 +1971,7 @@ def indexer_cp_unsupported_reason(
dcp_size: int,
enable_tbo: bool,
plugin_mode: bool = False,
mxfp4_caches: bool = False,
) -> str | None:
"""Why MiniMax-M3 indexer-only context parallelism cannot run here, or None.

Expand Down Expand Up @@ -1976,6 +1999,8 @@ def indexer_cp_unsupported_reason(
# error. ``indexer_cp_enabled`` refuses them at runtime; this clears the
# flag so nothing downstream (log line, compile hash) disagrees.
return "plugin mode (the vLLM/SGLang bridges keep the TP indexer path)"
if mxfp4_caches:
return "the MXFP4 caches are not wired to the CP scorer"
if dcp_size > 1:
return (
"decode_context_parallel_size > 1 (KV-cache DCP owns the context "
Expand Down Expand Up @@ -2798,10 +2823,33 @@ def __post_init__(self):
str(a).startswith(("DeepseekV4For", "DeepseekV4MTP", "DeepseekV4DSpark"))
for a in arches
)
# The same test the attention builder and the offload layout use, so the
# MXFP4 gate cannot disagree with them on an export that names itself
# only in `text_config` or `model_type`.
is_m3 = _is_minimax_m3_config(self.hf_config)
_check_fp4_kv(
self.kv_cache_dtype,
is_m3=is_m3,
is_v41=self.hf_config.model_type == "deepseek_v41_text",
)
if is_deepseek_v4:
v4_block_size = 256
if self.kv_cache_block_size != v4_block_size:
self.kv_cache_block_size = v4_block_size
if self.kv_cache_dtype == "fp8" and self.plugin_config is None:
# aiter's V4 fp8 (2buff) KV exists only on gfx950 / gfx1250; elsewhere it
# is bf16, decided here before the draft's dtype is resolved from it.
from aiter.jit.utils.chip_info import get_gfx

gfx = get_gfx()
if gfx not in ("gfx950", "gfx1250"):
logger.warning(
"DeepSeek-V4 --kv_cache_dtype fp8 (2buff) is only supported "
"on gfx950 / gfx1250 (aiter op4/op5); got %r. Falling back to "
"a bf16 KV cache.",
gfx,
)
self.kv_cache_dtype = "bf16"

# GLM-5.3-Flash's indexer caches one pooled key per `index_kpool`
# tokens, so a KV block of B tokens needs only B // index_kpool index
Expand Down Expand Up @@ -2872,6 +2920,7 @@ def __post_init__(self):
self.decode_context_parallel_size,
self.enable_tbo or self.enable_tbo_decode,
is_plugin_mode(),
"fp4" in (self.index_cache_dtype, self.kv_cache_dtype),
)
if indexer_cp_off is not None:
logger.warning(
Expand Down Expand Up @@ -2913,7 +2962,25 @@ def __post_init__(self):
# load. Overriding it here would accept the flag and ignore it.
self.index_cache_dtype = "fp8"
else:
# M3's fp4 KV needs the fp4 index (the MXFP4 attention reads the
# selector's rows, which only that scorer sizes to match), which is
# what following the KV dtype gives; an explicit other index dtype
# is refused by `validate_runtime_config`.
self.index_cache_dtype = self.kv_cache_dtype
if self.speculative_config is not None:
from atom.spec_decode.draft_kv import draft_owns_kv_pool

spec = self.speculative_config
# The one place the draft's pool and dtype are settled: everything
# downstream (the draft builder, the proposer, the calibration identity)
# reads `spec.draft_kv_dtype`.
spec.draft_kv_dtype = resolve_draft_kv_dtype(
self.kv_cache_dtype, own_pool=draft_owns_kv_pool(self)
)
if is_m3:
from atom.models.minimax_m3.config import validate_runtime_config

validate_runtime_config(self)

if self.hf_config.model_type == "deepseek_v41_text":
# CSA2's index plane is paged by the same scorer GLM's is, so a
Expand Down Expand Up @@ -3269,7 +3336,7 @@ def compute_hash(self) -> str:
# This reads the field AFTER __post_init__, which is the only correct
# value: it already folds in the ATOM_M3_INDEXER_CP override AND every
# indexer_cp_unsupported_reason fallback (plugin mode, TP != kv heads,
# block != 128, DCP, TBO). Two topologies that both fall back therefore
# block != 128, DCP, TBO, an MXFP4 KV or index cache). Two topologies that both fall back therefore
# share one artifact, as they should.
factors.append(bool(getattr(self.dcp_config, "indexer_dcp_only", False)))
factors.append(self.enable_dp_attention)
Expand Down Expand Up @@ -3344,7 +3411,13 @@ def compute_hash(self) -> str:
# steer sampling and stay out.
spec = self.speculative_config
factors.append(
None if spec is None else (spec.method, spec.num_speculative_tokens)
None
if spec is None
else (
spec.method,
spec.num_speculative_tokens,
spec.draft_kv_dtype,
)
)

hash_str = hashlib.md5(
Expand Down
20 changes: 3 additions & 17 deletions atom/kv_transfer/offload/mp/deployment.py
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,9 @@ def _config_has_fully_replicated_tp_pages(config: Any) -> bool:
registering any cache memory.
"""

if _config_has_own_pool_draft(config):
from atom.spec_decode.draft_kv import draft_owns_kv_pool

if draft_owns_kv_pool(config):
# A DSpark draft whose backend owns a KV pool appends per-rank-sharded
# PAGE regions (`draft_kv.py` declares factor 1), so the complete PAGE
# object is not replicated even when the target's is.
Expand All @@ -187,22 +189,6 @@ def _config_has_fully_replicated_tp_pages(config: Any) -> bool:
return getattr(hf_config, "kv_lora_rank", None) is not None


def _config_has_own_pool_draft(config: Any) -> bool:
"""Whether a speculative draft caches into a KV pool of its own.

Mirrors `spec_decode.draft_kv.draft_kv_builder`, which only DSpark calls:
the draft's own backend answers through `DRAFT_OWNS_KV_POOL`.
"""

speculative = getattr(config, "speculative_config", None)
draft_hf = getattr(speculative, "draft_model_hf_config", None)
if getattr(speculative, "method", None) != "dspark" or draft_hf is None:
return False
from atom.utils.selector import attn_family, get_attn_backend

return bool(get_attn_backend(attn_family(draft_hf)).DRAFT_OWNS_KV_POOL)


def _tp_replication_factor(config: Any) -> int:
"""Return the PAGE rank-collapse factor selected before workers start.

Expand Down
9 changes: 6 additions & 3 deletions atom/model_engine/arg_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -320,10 +320,12 @@ def add_cli_args(parser: argparse.ArgumentParser) -> argparse.ArgumentParser:
)
parser.add_argument(
"--kv_cache_dtype",
choices=["bf16", "fp8"],
choices=["bf16", "fp8", "fp4"],
Comment thread
Copilot marked this conversation as resolved.
Resolved
type=str,
default="bf16",
help="KV cache type. Default is 'bf16'.",
help="KV cache type. Default is 'bf16'. 'fp4' is MiniMax-M3.1's MXFP4 K/V "
"or DeepSeek-V4.1's own FP4 KV; other models refuse it. M3's conditions "
"are in the configuration guide.",
)
parser.add_argument(
"--index-cache-dtype",
Expand All @@ -333,7 +335,8 @@ def add_cli_args(parser: argparse.ArgumentParser) -> argparse.ArgumentParser:
default=None,
help="Index cache type. Native single-node DeepSeek-V4 defaults to "
"'fp4' except on gfx942, which defaults to 'fp8'; other models "
"default to --kv_cache_dtype.",
"default to --kv_cache_dtype. 'fp4' is MiniMax-M3's MXFP4 index "
"cache or another model's own FP4 layout; see the configuration guide.",
)
parser.add_argument(
"--block-size", type=int, default=16, help="KV cache block size."
Expand Down
Loading
Loading