Skip to content

[ROCm][Quant] Requantize serialized MXFP8 linears to FP8 PTPC - #48427

Draft
tanpinsiang wants to merge 1 commit into
vllm-project:mainfrom
tanpinsiang:rocm-minimax-m3-mxfp8-source-aware-ptpc
Draft

tanpinsiang wants to merge 1 commit into
vllm-project:mainfrom
tanpinsiang:rocm-minimax-m3-mxfp8-source-aware-ptpc

Conversation

@tanpinsiang

@tanpinsiang tanpinsiang commented Jul 12, 2026

Copy link
Copy Markdown
Contributor

Summary

This PR lets ROCm users requantize serialized MXFP8 linear weights to FP8 per-token/per-channel (PTPC) at model startup:

MXFP8 values + E8M0 block scales -> BF16 -> FP8 PTPC

The supported activation flag is:

--quantization-config.linear fp8_per_channel

The implementation is source-format-aware and model-agnostic. It adds no model-specific quantization class or model-name routing.

ModelOpt names vLLM's existing checkpoint-format handler: ModelOptMxFp8Config and ModelOptMxFp8LinearMethod. MiniMax-M3 checkpoints tagged with quant_method: mxfp8 use the same serialized E4M3-value plus E8M0 block-scale format, so vLLM routes them through this handler. This does not mean the checkpoint was created by NVIDIA ModelOpt.

The PTPC target is generic, but this PR currently provides source reconstruction for the ModelOpt MXFP8 loader.

Implementation

  • LinearRequantizationSource is the small generic interface between a serialized source method and an online quantizer.
  • ModelOptMxFp8LinearMethod.dequantize_weight() reconstructs BF16 from the checkpoint's MXFP8 values and E8M0 scales.
  • Fp8PtpcOnlineLinearMethod performs PTPC quantization and AITER preshuffling from the reconstructed weight.
  • Routing uses checkpoint quantization and QuantizationConfigArgs; there is no model_type check or hard-coded layer-name list.
  • Excluded and unspecified linears keep checkpoint quantization. MoE remains MXFP8.
  • Tensor-parallel row weights synchronize their per-channel scale before final quantization.

Usage

vllm serve MiniMaxAI/MiniMax-M3-MXFP8 \
  --tensor-parallel-size 4 \
  --attention-backend TRITON_ATTN \
  --moe-backend aiter \
  --linear-backend auto \
  --kv-cache-dtype fp8 \
  --no-enable-prefix-caching \
  --language-model-only \
  --quantization-config.linear fp8_per_channel

Without the final flag, the checkpoint keeps its native MXFP8 linear path.

Validation

environment

Item Value
Current-main base b6754f536e9cb40a1e117705a44d74218873b437
PR head 85199f11c15b8860429c2eb8f37ff2b8dec143cf
Hardware AMD Instinct MI355X

Startup and backend checks

Test Configuration Result
Current Main Base, auto, no PTPC flag RocmDotScaledMxfp8LinearKernel
PR with PTPC Head, auto, fp8_per_channel AiterPreshuffledPerTokenFp8ScaledMMLinearKernel
PR without Flag Head, auto, no PTPC flag Unchanged native MXFP8
Invalid Backend Head, emulation, fp8_per_channel Expected early actionable failure
Forced Emulation Base, emulation, no PTPC flag EmulationMxfp8LinearKernel

Quality: does PTPC preserve native MXFP8 accuracy?

The required paired GSM8K run used lm-eval[api]==0.4.12, 25-shot, 200 samples, chat template, temperature 0, max 4096 tokens, concurrency 200, and the same dataset revision.

Metric Current Main PR with PTPC Difference
Strict exact match 0.965 0.955 -0.010
Flexible exact match 0.965 0.955 -0.010

Because this two-sample difference lacked per-sample records, a separate full-dataset run repeated the protocol on all 1,319 samples with sample logging:

Metric Current Main PR with PTPC Difference
Strict exact match 0.948446 0.951478 +0.003033
Flexible exact match 0.947688 0.950720 +0.003033

C64: does PTPC improve over current native MXFP8?

Metric Current Main PR with PTPC Change
Output throughput / GPU (tok/s) 315.378 328.463 +4.149%
Total output throughput (tok/s) 1261.512 1313.853 +4.149%
Total token throughput (tok/s) 22259.993 23183.570 +4.149%
Mean TTFT (ms) 1229.384 1143.733 -6.967%
p99 TTFT (ms) 12933.988 11926.503 -7.789%
Mean TPOT (ms) 47.398 45.379 -4.260%
p99 TPOT (ms) 73.013 72.471 -0.742%
p99 ITL (ms) 405.151 378.142 -6.666%
Server startup (s) 237.961 257.985 +8.415%
Ready TP4 VRAM (GB) 1165.783 1166.422 +0.055%

PTPC improved throughput by 4.149%; every reported request-latency metric also
improved in this run.

C256: does PTPC improve over current native MXFP8?

Metric Current Main PR with PTPC Change
Output throughput / GPU (tok/s) 662.991 694.401 +4.738%
Total output throughput (tok/s) 2651.963 2777.604 +4.738%
Total token throughput (tok/s) 24997.365 26181.652 +4.738%
Mean TTFT (ms) 3250.910 2971.010 -8.610%
p99 TTFT (ms) 48113.349 44232.797 -8.065%
Mean TPOT (ms) 92.812 88.881 -4.235%
p99 TPOT (ms) 155.739 164.762 +5.793%
p99 ITL (ms) 616.272 583.070 -5.388%
Server startup (s) 233.559 258.725 +10.775%
Ready TP4 VRAM (GB) 1165.783 1166.422 +0.055%

PTPC improved throughput by 4.738%.

Migration: is PTPC better than forced emulation?

This was a direct concurrent C256 comparison, not a percentage inferred from
other runs. Both arms completed 2,560/2,560 with zero failures and no
measured-phase JIT/autotuning.

Metric Forced Emulation PR with PTPC Change
Output throughput / GPU (tok/s) 676.806 696.070 +2.846%
Total output throughput (tok/s) 2707.224 2784.281 +2.846%
Total token throughput (tok/s) 25518.253 26244.596 +2.846%
Mean TTFT (ms) 3032.546 2927.926 -3.450%
p99 TTFT (ms) 48231.056 44180.714 -8.398%
Mean TPOT (ms) 91.190 88.622 -2.816%
p99 TPOT (ms) 163.426 156.802 -4.053%
p99 ITL (ms) 603.063 565.714 -6.193%
Server startup (s) 237.764 258.186 +8.590%
Ready TP4 VRAM (GB) 1174.890 1166.422 -0.721%

PTPC improved throughput by 2.846%

@mergify

mergify Bot commented Jul 12, 2026

Copy link
Copy Markdown
Contributor

Documentation preview: https://vllm--48427.org.readthedocs.build/en/48427/

@mergify mergify Bot added documentation Improvements or additions to documentation rocm Related to AMD ROCm labels Jul 12, 2026
@github-project-automation github-project-automation Bot moved this to Todo in AMD Jul 12, 2026
@tanpinsiang
tanpinsiang force-pushed the rocm-minimax-m3-mxfp8-source-aware-ptpc branch 3 times, most recently from 6860540 to 170aa20 Compare July 13, 2026 06:02
@tanpinsiang
tanpinsiang marked this pull request as ready for review July 13, 2026 08:19

@claude claude 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.

Claude Code Review

This pull request is from a fork — automated review is disabled. A repository maintainer can comment @claude review to run a one-time review.

@tanpinsiang

Copy link
Copy Markdown
Contributor Author

@tjtanaa @hongxiayang

Additional validation: PTPC with AITER sparse PA and shuffled KV

export VLLM_ROCM_SHUFFLE_KV_CACHE_LAYOUT=1

Frozen comparison: base 36484e464a6cf763c5b4c8af7be8e19df324997a,
head 170aa201577f11bcce22a1cedeb7a393ba988600

selected MiniMax AITER_SPARSE_PA, retained AITER MXFP8 MoE, and used FP8 KV cache with index cache, prefix cache, and MTP/spec decode disabled.

TRITON_ATTN remained the common ordinary-attention backend;
AITER_SPARSE_PA handled MiniMax's sparse paged-attention operation.

Both comparison arms use the same sparse-attention path. These results measure PTPC against its control with AITER sparse PA and shuffled KV enabled; they do not isolate the standalone uplift of AITER sparse PA over Triton.

Quality

Paired GSM8K used lm-eval[api]==0.4.12, 25-shot, 200 samples, chat template, temperature 0, max 4096 tokens, concurrency 200, and the same dataset revision.

Metric Current Main PR with PTPC Difference
Strict exact match 0.950 0.955 +0.005
Flexible exact match 0.950 0.955 +0.005

C64: does PTPC improve over native MXFP8

8192 input, 512 output, concurrency 64.

Metric Current Main PR with PTPC Change
Output throughput / GPU (tok/s) 325.743 340.712 +4.595%
Total output throughput (tok/s) 1302.972 1362.847 +4.595%
Total token throughput (tok/s) 22991.565 24048.090 +4.595%
Mean TTFT (ms) 1117.531 1044.502 -6.535%
p99 TTFT (ms) 11718.960 10688.341 -8.794%
Mean TPOT (ms) 45.814 43.691 -4.635%
p99 TPOT (ms) 70.387 70.149 -0.338%
p99 ITL (ms) 373.851 343.836 -8.029%
Cold startup (s) 328.784 404.503 +23.030%
Ready TP4 VRAM (GB) 1165.802 1166.440 +0.055%

PTPC improved throughput by 4.595%; every reported latency metric also
improved in this run.

C256: does PTPC improve over native MXFP8

8192 input, 1024 output, concurrency 256.

Metric Current Main PR with PTPC Change
Output throughput / GPU (tok/s) 697.750 728.640 +4.427%
Total output throughput (tok/s) 2791.002 2914.562 +4.427%
Total token throughput (tok/s) 26307.940 27472.615 +4.427%
Mean TTFT (ms) 2958.994 2662.932 -10.006%
p99 TTFT (ms) 43871.482 39809.819 -9.258%
Mean TPOT (ms) 88.137 84.756 -3.836%
p99 TPOT (ms) 145.855 158.932 +8.966%
p99 ITL (ms) 579.659 531.155 -8.368%
Cold startup (s) 328.863 349.074 +6.146%
Ready TP4 VRAM (GB) 1165.801 1166.441 +0.055%

PTPC improved throughput by 4.427%. TTFT, mean TPOT, and p99 ITL improved;

is PTPC better than forced emulation?

Metric Forced Emulation PR with PTPC Change
Output throughput / GPU (tok/s) 716.621 736.055 +2.712%
Total output throughput (tok/s) 2866.483 2944.219 +2.712%
Total token throughput (tok/s) 27019.430 27752.161 +2.712%
Mean TTFT (ms) 2727.014 2645.017 -3.007%
p99 TTFT (ms) 40577.455 39738.065 -2.069%
Mean TPOT (ms) 85.907 83.763 -2.495%
p99 TPOT (ms) 142.660 154.655 +8.408%
p99 ITL (ms) 534.852 528.946 -1.104%
Cold startup (s) 323.803 344.001 +6.238%
Ready TP4 VRAM (GB) 1174.901 1166.441 -0.720%

PTPC improved throughput by 2.712%

@tanpinsiang
tanpinsiang force-pushed the rocm-minimax-m3-mxfp8-source-aware-ptpc branch from 170aa20 to 0197453 Compare July 14, 2026 12:33
@tanpinsiang tanpinsiang changed the title [ROCm][Quant] MiniMax-M3: Enable fp8_per_channel for MXFP8 checkpoints [ROCm][Quant] Enable FP8 per-channel linear kernels for ModelOpt MXFP8 checkpoints. Jul 14, 2026
@tanpinsiang
tanpinsiang force-pushed the rocm-minimax-m3-mxfp8-source-aware-ptpc branch from 0197453 to 85199f1 Compare July 14, 2026 12:47
@tanpinsiang tanpinsiang changed the title [ROCm][Quant] Enable FP8 per-channel linear kernels for ModelOpt MXFP8 checkpoints. [ROCm][Quant] Requantize serialized MXFP8 linears to FP8 PTPC Jul 14, 2026
@tanpinsiang

Copy link
Copy Markdown
Contributor Author

@tjtanaa @hongxiayang I addressed the request to make this generic and rebased onto current main.
Body updated.
Results on MI355X TP4:

  • C64 throughput: +4.149%
  • C256 throughput: +4.738%

@tanpinsiang

tanpinsiang commented Jul 15, 2026

Copy link
Copy Markdown
Contributor Author

@hongxiayang @tjtanaa

I tested mgoin/Qwen3-0.6B-MXFP8 as an independent ModelOpt MXFP8 checkpoint.
This is a useful cross-model case because it exercises the generic ModelOpt source format and dense attention/MLP linears without using MiniMax-M3 model code.

environment

Item Value
Base b6754f536e9cb40a1e117705a44d74218873b437
Head 85199f11c15b8860429c2eb8f37ff2b8dec143cf

PTPC startup: blocked by AITER shape coverage

Configuration TP1 TP4
Current Main, native MXFP8 Passed Passed
PR, flag absent Passed Passed
PR, fp8_per_channel Failed before ready Failed before ready

The PTPC flag reaches the generic source-aware path, but kernel selection
falls through to RowWiseTorchFP8ScaledMMLinearKernel. The PR then raises the intended guard because source-aware requantization requires AiterPreshuffledPerTokenFp8ScaledMMLinearKernel:

RuntimeError: ROCm source-aware FP8 PTPC requantization requires the AITER preshuffled per-token FP8 kernel, selected RowWiseTorchFP8ScaledMMLinearKernel.

The packaged AITER tuning probe found no gfx950 preshuffled configuration for any of this model's dense shapes:

Layer N K Tuned
Fused QKV 4096 1024 No
Output projection 1024 1024 No
Fused gate/up 6144 1024 No
Down projection 1024 3072 No

Both TP1 and TP4 failed the mandatory ready gate.

New code regression check

the code change does not hurt performance. It also shows that the PTPC activation path is operationally limited by AITER's tuned shape coverage: arbitrary ModelOpt MXFP8 checkpoints cannot use it yet.

C64 throughput

8192 input, 512 output, concurrency 64, 128 warmup, 64 verification, 512 measured.

Metric Current Main PR, flag absent Change
Output throughput (tok/s) 1812.952 1818.588 +0.311%
Mean TTFT (ms) 549.428 520.204 -5.319%
p99 TTFT (ms) 5384.459 5402.203 +0.330%
Mean TPOT (ms) 33.276 33.209 -0.199%
p99 TPOT (ms) 42.932 43.188 +0.598%
p99 ITL (ms) 214.679 212.819 -0.866%

Flag-absent quality: exact parity

Paired GSM8K used lm-eval[api]==0.4.12, 5-shot, all 1,319 samples, chat template, temperature 0, maximum 4096 tokens, concurrency 200, and the same dataset revision.

Metric Current Main PR, flag absent Difference
Strict exact match 0.043215 0.043215 0
Flexible exact match 0.622441 0.622441 0

@@ -6,6 +6,7 @@
"""

import os
from types import SimpleNamespace

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Maybe these tests should be in test_online.py?

Can you also add a test that ensures that the MXFP8 -> BF16 -> FP8 PTPC conversion does not double memory requirement during requantization (due to BF16 dequant)? Is it indeed the case?

Maybe similar to

def test_online_quant_peak_mem(

Comment on lines +377 to +390
if self.requantization_source is not None and current_platform.is_rocm():
from vllm.model_executor.kernels.linear import (
AiterPreshuffledPerTokenFp8ScaledMMLinearKernel,
)

if not isinstance(
self.fp8_linear,
AiterPreshuffledPerTokenFp8ScaledMMLinearKernel,
):
raise RuntimeError(
"ROCm source-aware FP8 PTPC requantization requires the "
"AITER preshuffled per-token FP8 kernel, selected "
f"{type(self.fp8_linear).__name__}."
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Why is this done here? Shouldn't choose_scaled_mm_linear_kernel be the source of truth and already handle this:

# Apply --linear-backend filtering when set.
linear_backend = _get_linear_backend()
if linear_backend != "auto":
filtered = _filter_kernels_by_backend(linear_backend, platform_kernels)
if not filtered:
raise ValueError(
f"--linear-backend={linear_backend} was requested but no "
f"'{linear_backend}' kernel exists for this layer type."
)
platform_kernels = filtered
for kernel in platform_kernels:
is_supported_and_can_implement, failure_reason = (
is_supported_and_can_implement_kernel(kernel, config, compute_capability)
)
if is_supported_and_can_implement:
return kernel
failure_reason_list.append(failure_reason)
raise ValueError(
"Failed to find a kernel that can implement the "
"ScaledMM linear layer. Reasons: \n" + "\n".join(failure_reason_list)
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Same question for the above isinstance(self.fp8_linear, MarlinFP8ScaledMMLinearKernel actually..

Comment on lines +427 to +432
if self.requantization_source is not None:
logger.info_once(
"Requantized serialized weights to FP8 PTPC via %s",
type(self.fp8_linear).__name__,
scope="global",
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Same comment as #49313 (comment), for an other PR

Comment on lines +1743 to +1751
get_current_vllm_config().kernel_config.linear_backend
)
if linear_backend not in {"auto", "aiter"}:
raise ValueError(
"ModelOpt MXFP8 to FP8 PTPC requantization requires "
"the AITER linear kernel; use --linear-backend=auto "
"or --linear-backend=aiter, got "
f"--linear-backend={linear_backend}."
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I don't think this should be done here.

def init_fp8_linear_kernel(
should handle this already

def get_quant_method(
self, layer: torch.nn.Module, prefix: str
) -> "QuantizeMethodBase | None":
if current_platform.is_rocm() and isinstance(layer, LinearBase):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Why the check on current_platform.is_rocm()? IMO such re-quantization logic should be accelerator-agnostic, and fail gracefully in case no requantization implementation / backend implementation is available for a given accelerator.

target_method = OnlineQuantizationConfig(args).get_quant_method(
layer, prefix
)
if isinstance(target_method, Fp8PtpcOnlineLinearMethod):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

else? not supported error?

@@ -1717,6 +1726,40 @@ def get_name(self) -> QuantizationMethods:
def get_supported_act_dtypes(self) -> list[torch.dtype]:
return [torch.bfloat16]

def get_quant_method(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I am not sure about most of the logic here, seems it could belong elsewhere

@mergify

mergify Bot commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

This pull request has merge conflicts that must be resolved before it can be
merged. Please rebase the PR, @tanpinsiang.

https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/syncing-a-fork

@mergify mergify Bot added the needs-rebase label Jul 27, 2026
Co-authored-by: vllmellm <vllm.ellm@embeddedllm.com>
Signed-off-by: Tan Pin Siang <tanpinsiang@gmail.com>
@tanpinsiang
tanpinsiang force-pushed the rocm-minimax-m3-mxfp8-source-aware-ptpc branch from 85199f1 to 32f6260 Compare July 29, 2026 23:44
@mergify mergify Bot removed the needs-rebase label Jul 30, 2026
@mergify

mergify Bot commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

This pull request has merge conflicts that must be resolved before it can be
merged. Please rebase the PR, @tanpinsiang.

https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/syncing-a-fork

@mergify mergify Bot added the needs-rebase label Jul 30, 2026
@fxmarty-amd

Copy link
Copy Markdown
Contributor

Related: #51392 -> once this is merged, we could relax the constraint of online quantization not colliding with the base quant method, and allow online quantization to override the base quant method (if implemented, otherwise error) as done in this PR.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation needs-rebase quantization rocm Related to AMD ROCm

Projects

Status: Todo

Development

Successfully merging this pull request may close these issues.

2 participants