vllm.models.minimax_m3.nvidia.indexer_nvfp4_decode_cp
¶
Context-parallel decode for the MiniMax M3 NVFP4 indexer (opt-in, TP4).
At TP4 each rank owns one of the 4 index heads, but the index-K cache is
replicated, so every rank reads the whole cache per sparse layer. With
--attention-config '{"indexer_decode_cp": true}', rank r
instead:
- exchanges index-q heads so it holds all 4 (
_q_exchange_kernel); - scores all 4 heads over pages
j * CP + rwith the stock Q8KV4 kernel on a compact block table, reading 1/CP of the index-K; - takes the exact local top 16 per (query, head) and sends it to the head's
owner (
_local_topk_push_kernel); - merges the owner's
16 * CPcandidates into the indexer output: 15 pages by (score desc, page asc), then the own page (_merge_kernel).
The merge is exact: the global top 16 lies in the union of per-rank top 16s. The stock decode ranks 16-bit quantized scores, so the two may differ at ties.
The chunking is written against one contract, the MSA Q8KV4 decode scorer's:
eight query slots per request, (seq_len - 1) / 128 pages scored, and only
the pages below a query's own page stored. The three places that depend on it
are _compact_block_table_kernel (the length it is handed),
_local_topk_push_kernel (the cut that length drops) and _set_width
(room for the page that length invents).
Both exchanges are NVLink stores into symmetric buffers whose payload is the
flag (0xFF E4M3 NaN for queries, -1 for keys); consumers restore it.
Buffers are per layer, shapes are fixed per batch size, and nothing syncs with
the host, so the path is CUDA-graph safe. It needs symmetric memory across the
tensor-parallel ranks and falls back to the stock decode without it.
The three Triton kernels are PDL-launched and wait for their producer, so each starts its prologue in the tail of the kernel before it. Only the push releases its dependent early, right after its own wait, which is what lets the merge overlap it; the exchange must not, because the score kernel after it reads the whole tile.
Classes:
-
Nvfp4DecodeCPLayer–One sparse layer's symmetric buffers.
-
Nvfp4DecodeCPPlan–Per padded batch size: the 4-head score wrapper and its buffers.
-
Nvfp4IndexerDecodeCP–Per-process state shared by the builder and every sparse layer.
Functions:
-
get_nvfp4_indexer_decode_cp–The process's decode-CP state, or None (stock decode). Built once.
Nvfp4DecodeCPLayer
dataclass
¶
One sparse layer's symmetric buffers.
Source code in vllm/models/minimax_m3/nvidia/indexer_nvfp4_decode_cp.py
Nvfp4DecodeCPPlan
dataclass
¶
Per padded batch size: the 4-head score wrapper and its buffers.
Source code in vllm/models/minimax_m3/nvidia/indexer_nvfp4_decode_cp.py
Nvfp4IndexerDecodeCP
¶
Per-process state shared by the builder and every sparse layer.
Methods:
-
new_layer–Collective: every TP rank builds its sparse layers in the same order.
-
plan–Outside CUDA graph capture, once per step.
Source code in vllm/models/minimax_m3/nvidia/indexer_nvfp4_decode_cp.py
307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 | |
_set_width(max_pages)
¶
Fix the compact page width and the two-level top-k shape.
One column past the pages a rank can own, so the page the compact
length invents stays inside the score buffer and the chunk's last page
is stored (see _compact_block_table_kernel). Both levels of the
top-k select over power-of-two tiles that cover the width.
Source code in vllm/models/minimax_m3/nvidia/indexer_nvfp4_decode_cp.py
new_layer()
¶
Collective: every TP rank builds its sparse layers in the same order.
Source code in vllm/models/minimax_m3/nvidia/indexer_nvfp4_decode_cp.py
plan(block_table, seq_lens)
¶
Outside CUDA graph capture, once per step.
Source code in vllm/models/minimax_m3/nvidia/indexer_nvfp4_decode_cp.py
_compact_block_table_kernel(bt_ptr, seq_lens_ptr, cbt_ptr, cseq_ptr, seq_out_ptr, P, W, stride_bt, stride_cbt, CP, RANK, BLOCK)
¶
Compact page j = global page j * CP + RANK, and the length the score
kernel is handed for it.
That length is a lie: the scorer stores only the pages below a query's own
page, so n_local * 128 + 8 puts all eight query slots one page past
the chunk and stores every owned page, uncausally. The real cut comes back
in _local_topk_push_kernel. W keeps the invented page addressable,
since a scorer that clamps the own page to its last score column would
drop the chunk's last page.
Source code in vllm/models/minimax_m3/nvidia/indexer_nvfp4_decode_cp.py
_local_topk_push_kernel(score_ptr, seq_lens_ptr, recv_ptrs, rows_max, stride_sb, stride_sr, H, CP, RANK, QLEN, WIDTH, CHUNK, NUM_GROUPS, GROUP)
¶
Per (query, head): exact top 16 of the owned pages, sent to the head's owner. Long rows use a two-level select (top 16 groups, then top 16).
PDL-launched: the wait is for the scores, and releasing the merge only after it keeps the merge's own wait a guarantee that the score kernel has read the q tile.
Source code in vllm/models/minimax_m3/nvidia/indexer_nvfp4_decode_cp.py
_merge_kernel(seq_lens_ptr, recv_ptrs, tile_ptrs, out_ptr, rows_max, out_stride, H, CP, RANK, QLEN)
¶
Merge all ranks' candidates for this rank's head and restore sentinels.
PDL-launched after every push program has started, so polling here never blocks a push.
Source code in vllm/models/minimax_m3/nvidia/indexer_nvfp4_decode_cp.py
_page_keys(row_ptr, j, n, CP, RANK)
¶
Key 1 << 48 | ordered(score) << 16 | (0xFFFF - page) for j < n,
else 0. Unique; orders by score desc, then page asc.
Source code in vllm/models/minimax_m3/nvidia/indexer_nvfp4_decode_cp.py
_q_exchange_kernel(q_ptr, tile_ptrs, H, CP, RANK, HEAD, QLEN)
¶
Store this rank's index-q head into every rank's tile, then wait for the other heads. The PDL wait is for index-q's producer.
Source code in vllm/models/minimax_m3/nvidia/indexer_nvfp4_decode_cp.py
get_nvfp4_indexer_decode_cp(*, num_index_heads, max_decode_query_len)
¶
The process's decode-CP state, or None (stock decode). Built once.