@@ -424,6 +424,69 @@ def test_mxfp8_linear_emulation_bf16_at_load(
424424 assert _relerr (out .float (), out_ref .float ()) < 2e-2
425425
426426
427+ # --------------------------------------------------------------------------- #
428+ # Native MXFP8 linear: the K % 128 != 0 weight is dequantized once at load
429+ # --------------------------------------------------------------------------- #
430+ @requires_gfx950
431+ # (5120, 576) and (5120, 288): DSv4.1 shared-expert down_proj at TP4 and TP8.
432+ @pytest .mark .parametrize ("shape" , [(5120 , 576 ), (5120 , 288 ), (256 , 2048 )])
433+ @torch .inference_mode ()
434+ def test_mxfp8_rocm_native_unaligned_k_dequantizes_at_load (shape ):
435+ """``dot_scaled`` tiles K by 128, so an unaligned weight must not reach it.
436+
437+ ``process_weights_after_loading`` converts those to BF16 once; a weight that
438+ never went through it has to fall back per step rather than take the
439+ invalid path, which is what keying the dispatch on the weight dtype alone
440+ would do.
441+ """
442+ from vllm .model_executor .kernels .linear .mxfp8 import rocm_native
443+ from vllm .model_executor .kernels .linear .mxfp8 .Mxfp8LinearKernel import (
444+ Mxfp8LinearLayerConfig ,
445+ )
446+
447+ N , K = shape
448+ aligned = K % 128 == 0
449+ torch .manual_seed (0 )
450+ w_bf16 = torch .randn (N , K , device = DEVICE , dtype = torch .bfloat16 ) * 0.1
451+ w_fp8 , w_scale = _mxfp8_e4m3_quantize_torch (w_bf16 , is_sf_swizzled_layout = False )
452+ x = torch .randn (7 , K , device = DEVICE , dtype = torch .bfloat16 ) * 0.5
453+ out_ref = torch .nn .functional .linear (x , dequant_mxfp8_to_bf16 (w_fp8 , w_scale ))
454+ # dot_scaled re-quantizes the activation, the dequant paths do not.
455+ tol = 5e-2 if aligned else 2e-3
456+
457+ def make_layer ():
458+ layer = torch .nn .Module ()
459+ layer .weight = torch .nn .Parameter (w_fp8 .clone (), requires_grad = False )
460+ layer .weight_scale = torch .nn .Parameter (w_scale .clone (), requires_grad = False )
461+ return layer
462+
463+ kernel = rocm_native .RocmDotScaledMxfp8LinearKernel (Mxfp8LinearLayerConfig ())
464+ layer = make_layer ()
465+ kernel .process_weights_after_loading (layer )
466+ assert (layer .weight .element_size () >= 2 ) is not aligned
467+ assert _relerr (kernel .apply_weights (layer , x ).float (), out_ref .float ()) < tol
468+
469+ # An unaligned weight that is still FP8 must not reach dot_scaled.
470+ unprocessed = make_layer ()
471+ unprocessed .weight_scale = torch .nn .Parameter (
472+ w_scale [:N , : K // 32 ].contiguous (), requires_grad = False
473+ )
474+ calls = []
475+ real = rocm_native ._mxfp8_dot_scaled_linear
476+
477+ def _spy (* a , ** k ):
478+ calls .append (1 )
479+ return real (* a , ** k )
480+
481+ rocm_native ._mxfp8_dot_scaled_linear = _spy
482+ try :
483+ out = kernel .apply_weights (unprocessed , x )
484+ finally :
485+ rocm_native ._mxfp8_dot_scaled_linear = real
486+ assert bool (calls ) is aligned
487+ assert _relerr (out .float (), out_ref .float ()) < tol
488+
489+
427490# ── EP expert_mask handling for the FlyDSL (AITER_MXFP8) MoE ────────────────
428491# The map/mask choice lives in ``RoutedExperts.expert_map``: it hands AITER
429492# experts (``consumes_expert_mask``) the precomputed 0/1 ``expert_mask`` and
0 commit comments