Skip to content

[ROCm][Kimi-K3] Add opt-in gfx942 MXFP4-to-int4 conversion - #51274

Draft
maeehart wants to merge 7 commits into
vllm-project:mainfrom
maeehart:k3-gfx942-int4-lossy
Draft

[ROCm][Kimi-K3] Add opt-in gfx942 MXFP4-to-int4 conversion#51274
maeehart wants to merge 7 commits into
vllm-project:mainfrom
maeehart:k3-gfx942-int4-lossy

Conversation

@maeehart

@maeehart maeehart commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Summary

Add an explicit int4_per_group_32 MoE override for Kimi-K3 on gfx942. When selected, the MXFP4 expert weights are requantized once at load time and served through AITER's BF16 x packed-int4 FlyDSL path.

The conversion is lossy and never happens by architecture alone. The default MXFP4 path is unchanged.

Why

gfx942 does not have the scaled MXFP4 MFMA used by the native CDNA4 path. The fallback can serve the model, but it dequantizes the expert weights during every forward. This opt-in path pays the conversion once during model loading and then uses the existing packed-int4 kernels.

The original implementation was part of the broader closed #50319. This draft keeps only the conversion path, its memory bounds, and the configuration needed to select it.

Changes

  • Register int4_per_group_32 as an online MoE weight quantization key.
  • Require the explicit --quantization-config.moe.weight int4_per_group_32 override on gfx942.
  • Refuse to load unless AITER includes [FlyDSL] Support SiTUv2 in the packed-int4 MoE stage1 epilogue ROCm/aiter#4471, because older packed-int4 kernels silently compute SiLU instead of Kimi-K3's SiTUv2 activation.
  • Convert eight experts at a time and release each source tensor before converting the next one, bounding transient memory during load.
  • Log a warning that the selected conversion is lossy.

Validation

Unit tests:

tests/quantization/test_quantization_config_args.py: 15 passed
tests/models/kimi_k3/test_gfx942_int4.py: 5 passed

End-to-end integration on 8x MI325X, Kimi-K3 TP8 with expert parallelism, DSpark with seven speculative tokens, prefix caching, and a 262,144-token model length:

  • Requests completed at 32K, 64K, 131K, and 250K input lengths.
  • Needle retrieval passed at 65K, 131K, and 250K.
  • A repeated 68K prompt reached a 98.26% prefix-cache hit rate and returned the same correct answer twice.
  • Full five-shot GSM8K scored 95.6% strict and flexible on all 1,319 questions.
  • No GPU memory fault, segmentation fault, NaN, OOM, or metadata OOB warning occurred.

The end-to-end image also included vLLM #50578 and #51065 plus the merged AITER large-KV offset fix. These numbers are integration evidence for the complete serving stack, not an isolated performance delta for this PR.

Dependency and scope

This remains a draft until ROCm/aiter#4471 lands in an AITER release used by vLLM.

The preferred long-term path is a numerically correct native A16W4/MXFP4 kernel such as ROCm/aiter#4502. This PR is an explicit stopgap for deployments that accept a lossy one-time conversion. It does not replace that work.

Test plan

@maeehart

maeehart commented Aug 6, 2026

Copy link
Copy Markdown
Contributor Author

I have read the DCO document and hereby sign off past commits made by me.

maeehart and others added 7 commits August 6, 2026 08:24
gfx942 (MI325X, MI300X) has no native MXFP4 matmul, so the day-0 Kimi-K3
expert path falls through to code that dies in LLVM codegen. Convert the
MXFP4 expert weights to int4 with groupwise bf16 scales at load time and
let the existing FlyDSL SiTU stage1 kernel consume them. The gfx950
native MXFP4 path is left untouched.

Two supporting changes are needed for the same reason. The AITER MoE
expert backend refuses kMxfp4Static outside gfx950 and does not list the
SiTU activation as supported. The AITER top-k/top-p sampler is a
gfx950-only prebuilt that segfaults on gfx942, so sampling falls back to
the native torch implementation there.

Co-authored-by: Cursor <cursoragent@cursor.com>
(cherry picked from commit 2253680)
Signed-off-by: Markus Hartikainen <markus.hartikainen@amd.com>
The gfx942 path requantized Kimi-K3's MXFP4 experts to groupwise int4
whenever the hardware matched, without the user asking. The conversion is
lossy, so it is now opt-in through --quantization-config.moe.weight int4
and gfx942 keeps the native MXFP4 path otherwise.

Also refuse to load when the installed AITER predates ROCm/aiter#4471.
Before that fix the packed-int4 stage1 dropped the requested activation
and hardcoded SiLU, so Kimi-K3 served fluent text while computing SiLU
instead of the SiTUv2 its config asks for.

Signed-off-by: Markus Hartikainen <markus.hartikainen@amd.com>
The gfx942 Kimi-K3 path asks for groupwise int4 MoE weights through
--quantization-config.moe.weight, but QUANT_KEY_NAMES had no name for that
scheme, so the flag was rejected before the path could be selected. Register
the existing kInt4Static32 key under int4_per_group_32 and match on the parsed
QuantKey rather than a raw string.

Signed-off-by: Markus Hartikainen <markus.hartikainen@amd.com>
mxfp4_to_f32 splits packed nibbles with repeat_interleave and then gathers
through an f32 lookup table, so the working tensor is 8x the packed weight
before per_1x32_i4_quant shrinks it again. Materializing that for a whole
expert tensor peaks above 20 GiB per rank and fails once the weights are
resident, which is what pure tensor parallel hits since it holds all experts
per rank. Convert 8 experts at a time and free each slice, bounding the
transient without changing the result.

Signed-off-by: Markus Hartikainen <markus.hartikainen@amd.com>
The ROCm branch of mxfp4_round_up_hidden_size_and_intermediate_size()
rounds the per-partition intermediate size up to 256. Kimi-K3 has
moe_intermediate_size 3072, so a TP8 shard is 384 and gets rounded to
512. That inflates every w13 tensor from (896, 768, 1792) to
(896, 1024, 1792), a 33 percent increase on all 92 MoE layers, which is
about 38 GiB per rank. Pure TP8 then sits at 248.69 GiB resident with
2.12 GiB free and dies during the int4 conversion.

The round-up is not needed here. AITER's resolve_flydsl_stage1_tile_n()
already downgrades tile_n from 256 to 128 for a non-256-aligned
inter_dim, and 128 divides 384 exactly. Mxfp4MoEMethod already skipped
the round-up for the gfx950 SiTU path, so extend the same condition to
the gfx942 int4 path.

Measured on 8 MI325X at TP8 without expert parallel, max-model-len
16384, gpu-memory-utilization 0.97: resident drops from 248.69 GiB to
192.51 GiB, which matches the expert-parallel figure exactly, free rises
from 2.12 GiB to 58.61 GiB, all 96 shards load with zero out-of-memory
workers, and the KV cache is 948,305 tokens.

Signed-off-by: Markus Hartikainen <markus.hartikainen@amd.com>
Keep the draft focused by dropping the unrelated sampler fallback. Add unit
coverage for the groupwise-int4 key, explicit gfx942 selection, and native
Kimi-K3 intermediate shape. Make the lossy conversion visible in startup logs
and use the accelerator cache API while bounding conversion memory.

Signed-off-by: Markus Hartikainen <markus.hartikainen@amd.com>
Current main already preserves Kimi-K3's native AITER intermediate shape.
Drop the duplicate early return and its redundant test. Also remove a stale
unreachable setup tail left by porting the original broader branch.

Signed-off-by: Markus Hartikainen <markus.hartikainen@amd.com>
@maeehart
maeehart force-pushed the k3-gfx942-int4-lossy branch from 410a582 to be8095e Compare August 6, 2026 15:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

Status: Todo

Development

Successfully merging this pull request may close these issues.

1 participant