A high-throughput and memory-efficient inference and serving engine for LLMs
Mirrored from https://github.com/tduncan-lum/Nvidia-Optimization-Stack---VLLM.git
Synced Nov 10, 02:45 UTC
[ROCM] DSfp4 mla projection gemms weight dynamic quantization (#32238)
Signed-off-by: Aleksandr Malyshev <maleksan@amd.com> Co-authored-by: Aleksandr Malyshev <maleksan@amd.com>
Aleksandr Malyshev committed
Jan 15, 2026 at 12:13 UTC
8c11001ba2110134df3c3aecc63f2559ad1f5996
4 files changed
+97
-80
vllm/_aiter_ops.py
+30
@@ -837,6 +837,7 @@ class rocm_aiter_ops:
837
_TRITON_UNIFIED_ATTN_ENABLED = envs.VLLM_ROCM_USE_AITER_UNIFIED_ATTENTION
838
# TODO: Consolidate under _LINEAR_ENABLED
839
_FP8BMM_ENABLED = envs.VLLM_ROCM_USE_AITER_FP8BMM
840
+ _FP4BMM_ENABLED = envs.VLLM_ROCM_USE_AITER_FP4BMM
841
# TODO: Consolidate under _LINEAR_ENABLED
842
_FP4_GEMM_DYNAMIC_QUANT_ASM = envs.VLLM_ROCM_USE_AITER_FP4_ASM_GEMM
843
# TODO: Consolidate under VLLM_ROCM_USE_AITER_ROPE
@@ -863,6 +864,7 @@ class rocm_aiter_ops:
864
cls._SHUFFLE_KV_CACHE_ENABLED = envs.VLLM_ROCM_SHUFFLE_KV_CACHE_LAYOUT
865
cls._TRITON_UNIFIED_ATTN_ENABLED = envs.VLLM_ROCM_USE_AITER_UNIFIED_ATTENTION
866
cls._FP8BMM_ENABLED = envs.VLLM_ROCM_USE_AITER_FP8BMM
867
+ cls._FP4BMM_ENABLED = envs.VLLM_ROCM_USE_AITER_FP4BMM
868
cls._FP4_GEMM_DYNAMIC_QUANT_ASM = envs.VLLM_ROCM_USE_AITER_FP4_ASM_GEMM
869
cls._TRITON_ROTARY_EMBED = envs.VLLM_ROCM_USE_AITER_TRITON_ROPE
870
cls._MOE_SHARED_EXPERTS_ENABLED = envs.VLLM_ROCM_USE_AITER_FUSION_SHARED_EXPERTS
@@ -923,6 +925,11 @@ class rocm_aiter_ops:
925
def is_fp8bmm_enabled(cls) -> bool:
926
return cls._AITER_ENABLED and cls._FP8BMM_ENABLED
927
928
+ @classmethod
929
+ @if_aiter_supported
930
+ def is_fp4bmm_enabled(cls) -> bool:
931
+ return cls._AITER_ENABLED and cls._FP4BMM_ENABLED
932
+
933
@classmethod
934
@if_aiter_supported
935
def is_asm_fp4_gemm_dynamic_quant_enabled(cls) -> bool:
@@ -1403,6 +1410,29 @@ class rocm_aiter_ops:
1410
query = query.view(query_shape)
1411
key = key.view(key_shape)
1412
1413
+ @staticmethod
1414
+ def batched_gemm_a16wfp4(
1415
+ X: torch.Tensor,
1416
+ W: torch.Tensor,
1417
+ w_scale: torch.Tensor,
1418
+ Y: torch.Tensor,
1419
+ transpose_bm: bool | None = False,
1420
+ prequant: bool | None = False,
1421
+ y_scale: torch.Tensor | None = None,
1422
+ ) -> torch.Tensor:
1423
+ # ruff: noqa: E501 # isort: skip
1424
+ from aiter.ops.triton.batched_gemm_a16wfp4 import batched_gemm_a16wfp4
1425
+
1426
+ return batched_gemm_a16wfp4(
1427
+ X,
1428
+ W,
1429
+ w_scale,
1430
+ y=Y,
1431
+ transpose_bm=transpose_bm,
1432
+ prequant=prequant,
1433
+ y_scale=y_scale,
1434
+ )
1435
+
1436
@staticmethod
1437
def triton_fp8_bmm(
1438
X: torch.Tensor,
vllm/envs.py
+6
@@ -121,6 +121,7 @@ if TYPE_CHECKING:
121
VLLM_ROCM_USE_AITER_FP4_ASM_GEMM: bool = False
122
VLLM_ROCM_USE_AITER_TRITON_ROPE: bool = False
123
VLLM_ROCM_USE_AITER_FP8BMM: bool = True
124
+ VLLM_ROCM_USE_AITER_FP4BMM: bool = True
125
VLLM_ROCM_USE_AITER_UNIFIED_ATTENTION: bool = False
126
VLLM_ROCM_USE_AITER_FUSION_SHARED_EXPERTS: bool = False
127
VLLM_ROCM_USE_AITER_TRITON_GEMM: bool = True
@@ -991,6 +992,11 @@ environment_variables: dict[str, Callable[[], Any]] = {
992
"VLLM_ROCM_USE_AITER_FP8BMM": lambda: (
993
os.getenv("VLLM_ROCM_USE_AITER_FP8BMM", "True").lower() in ("true", "1")
994
),
995
+ # Whether to use aiter triton fp4 bmm kernel
996
+ # By default is enabled.
997
+ "VLLM_ROCM_USE_AITER_FP4BMM": lambda: (
998
+ os.getenv("VLLM_ROCM_USE_AITER_FP4BMM", "True").lower() in ("true", "1")
999
+ ),
1000
# Use AITER triton unified attention for V1 attention
1001
"VLLM_ROCM_USE_AITER_UNIFIED_ATTENTION": lambda: (
1002
os.getenv("VLLM_ROCM_USE_AITER_UNIFIED_ATTENTION", "False").lower()
vllm/model_executor/layers/attention/mla_attention.py
+47
-80
@@ -1183,6 +1183,12 @@ class MLACommonBaseImpl(MLAAttentionImpl[A], Generic[A]):
1183
self.q_pad_num_heads = q_pad_num_heads
1184
self.is_aiter_triton_fp8_bmm_enabled = rocm_aiter_ops.is_fp8bmm_enabled()
1185
1186
+ # If kv_b_proj_weight is unquantized, quantize it to mxfp4 if supported
1187
+ self.is_aiter_triton_fp4_bmm_enabled = (
1188
+ rocm_aiter_ops.is_fp4bmm_enabled()
1189
+ and self.kv_b_proj.weight.dtype == torch.bfloat16
1190
+ )
1191
+
1192
def process_weights_after_loading(self, act_dtype: torch.dtype):
1193
# we currently do not have quantized bmm's which are needed for
1194
# `W_UV` and `W_UK_T`, we just store fp16/bf16 copies and perform
@@ -1211,7 +1217,21 @@ class MLACommonBaseImpl(MLAAttentionImpl[A], Generic[A]):
1217
[self.qk_nope_head_dim, self.v_head_dim], dim=-1
1218
)
1219
1214
- if self.is_aiter_triton_fp8_bmm_enabled:
1220
+ # If kv_b_proj_weight is unquantized, quantize it to mxfp4 if supported
1221
+ if self.is_aiter_triton_fp4_bmm_enabled:
1222
+ from vllm.model_executor.layers.quantization.quark.utils import (
1223
+ quark_quantize_weight_to_mxfp4,
1224
+ )
1225
+
1226
+ self.W_K, self.W_K_scale = quark_quantize_weight_to_mxfp4(W_UK)
1227
+ # Convert from (L, N, P) to (N, L, P)
1228
+ self.W_K = self.W_K.transpose(0, 1)
1229
+ self.W_K_scale = self.W_K_scale.transpose(0, 1)
1230
+
1231
+ self.W_V, self.W_V_scale = quark_quantize_weight_to_mxfp4(
1232
+ W_UV.permute(1, 2, 0)
1233
+ )
1234
+ elif self.is_aiter_triton_fp8_bmm_enabled:
1235
W_K = W_UK.transpose(0, 1) # 16 512 128
1236
W_V = W_UV.permute(1, 2, 0) # 16 128 512
1237
self.W_K, self.W_K_scale = dynamic_per_batched_tensor_quant(
@@ -1261,16 +1281,26 @@ class MLACommonBaseImpl(MLAAttentionImpl[A], Generic[A]):
1281
def _v_up_proj(self, x: torch.Tensor, out: torch.Tensor):
1282
# Convert from (B, N, L) to (N, B, L)
1283
x = x.view(-1, self.num_heads, self.kv_lora_rank).transpose(0, 1)
1264
-
1265
- if self.is_aiter_triton_fp8_bmm_enabled:
1266
- out = out.view(-1, self.num_heads, self.v_head_dim)
1284
+ out = out.view(-1, self.num_heads, self.v_head_dim)
1285
+ if self.is_aiter_triton_fp4_bmm_enabled:
1286
+ out = rocm_aiter_ops.batched_gemm_a16wfp4(
1287
+ x,
1288
+ self.W_V,
1289
+ self.W_V_scale,
1290
+ out,
1291
+ transpose_bm=True,
1292
+ prequant=True,
1293
+ y_scale=None,
1294
+ )
1295
+ x = out.view(-1, self.num_heads * self.v_head_dim)
1296
+ elif self.is_aiter_triton_fp8_bmm_enabled:
1297
# Multiply + Transpose (N, B, L) x (N, L, V)->(N, B, V)->(B, N, V)
1298
x = rocm_aiter_ops.triton_fp8_bmm(
1299
x, self.W_V, self.W_V_scale, group_size=128, transpose_bm=True, YQ=out
1300
)
1301
else:
1302
# Convert from (B, N * V) to (N, B, V)
1273
- out = out.view(-1, self.num_heads, self.v_head_dim).transpose(0, 1)
1303
+ out = out.transpose(0, 1)
1304
1305
# Multiply (N, B, L) x (N, L, V) -> (N, B, V)
1306
torch.bmm(x, self.W_UV, out=out) # Reuse "out" to make it "hot"
@@ -1579,80 +1609,6 @@ class MLACommonImpl(MLACommonBaseImpl[M], Generic[M]):
1609
# Convert from (q_len, num_heads) to (num_heads, q_len)
1610
return attn_out, lse.transpose(0, 1).contiguous()
1611
1582
- def process_weights_after_loading(self, act_dtype: torch.dtype):
1583
- # we currently do not have quantized bmm's which are needed for
1584
- # `W_UV` and `W_UK_T`, we just store fp16/bf16 copies and perform
1585
- # the bmm's in 16-bit, the extra memory overhead of this is fairly low
1586
- kv_b_proj_weight = get_and_maybe_dequant_weights(
1587
- self.kv_b_proj, out_dtype=act_dtype
1588
- ).T
1589
- assert kv_b_proj_weight.shape == (
1590
- self.kv_lora_rank,
1591
- self.num_heads * (self.qk_nope_head_dim + self.v_head_dim),
1592
- ), (
1593
- f"{kv_b_proj_weight.shape=}, "
1594
- f"{self.kv_lora_rank=}, "
1595
- f"{self.num_heads=}, "
1596
- f"{self.qk_nope_head_dim=}, "
1597
- f"{self.v_head_dim=}"
1598
- )
1599
- kv_b_proj_weight = kv_b_proj_weight.view(
1600
- self.kv_lora_rank,
1601
- self.num_heads,
1602
- self.qk_nope_head_dim + self.v_head_dim,
1603
- )
1604
-
1605
- W_UK, W_UV = kv_b_proj_weight.split(
1606
- [self.qk_nope_head_dim, self.v_head_dim], dim=-1
1607
- )
1608
-
1609
- if self.is_aiter_triton_fp8_bmm_enabled:
1610
- W_K = W_UK.transpose(0, 1) # 16 512 128
1611
- W_V = W_UV.permute(1, 2, 0) # 16 128 512
1612
- self.W_K, self.W_K_scale = dynamic_per_batched_tensor_quant(
1613
- W_K, dtype=current_platform.fp8_dtype()
1614
- )
1615
- self.W_V, self.W_V_scale = dynamic_per_batched_tensor_quant(
1616
- W_V, dtype=current_platform.fp8_dtype()
1617
- )
1618
-
1619
- # The kernel operates on non-padded inputs. Hence, pre-compiling
1620
- # triton kernel to avoid runtime compilation for unseen batch sizes
1621
- # Pre-compile for batch sizes 1 to 1024 to cover most use-cases.
1622
- # On DS-R1, this step adds roughly 50s to the model loading time.
1623
- max_batch_size = 1024 # [ToDo] Find the optimal upper limit
1624
- pre_compilation_list = list(range(1, max_batch_size + 1))
1625
- if is_global_first_rank():
1626
- pre_compilation_list = tqdm(
1627
- pre_compilation_list,
1628
- desc="[Aiter Triton] Pre-compiling fp8 BMM kernel",
1629
- total=max_batch_size,
1630
- )
1631
-
1632
- for m in pre_compilation_list:
1633
- x = torch.empty(
1634
- (self.W_K.shape[0], m, self.W_K.shape[2]),
1635
- dtype=torch.bfloat16,
1636
- device=self.W_K.device,
1637
- )
1638
- rocm_aiter_ops.triton_fp8_bmm(
1639
- x, self.W_K, self.W_K_scale, group_size=128, transpose_bm=True
1640
- )
1641
-
1642
- x = torch.empty(
1643
- (self.W_V.shape[0], m, self.W_V.shape[2]),
1644
- dtype=torch.bfloat16,
1645
- device=self.W_V.device,
1646
- )
1647
- rocm_aiter_ops.triton_fp8_bmm(
1648
- x, self.W_V, self.W_V_scale, group_size=128, transpose_bm=True
1649
- )
1650
- else:
1651
- # Convert from (L, N, V) to (N, L, V)
1652
- self.W_UV = W_UV.transpose(0, 1)
1653
- # Convert from (L, N, P) to (N, P, L)
1654
- self.W_UK_T = W_UK.permute(1, 2, 0)
1655
-
1612
def _concat_k_nope_k_pe(
1613
self, k_nope: torch.Tensor, k_pe: torch.Tensor
1614
) -> torch.Tensor:
@@ -2033,7 +1989,18 @@ class MLACommonImpl(MLACommonBaseImpl[M], Generic[M]):
1989
decode_pe_padded.copy_(decode_q_pe)
1990
decode_q_pe = decode_pe_padded
1991
2036
- if self.is_aiter_triton_fp8_bmm_enabled:
1992
+ if self.is_aiter_triton_fp4_bmm_enabled:
1993
+ from aiter.ops.triton.batched_gemm_a16wfp4 import batched_gemm_a16wfp4
1994
+
1995
+ decode_ql_nope = batched_gemm_a16wfp4(
1996
+ decode_q_nope,
1997
+ self.W_K,
1998
+ self.W_K_scale,
1999
+ transpose_bm=True,
2000
+ prequant=True,
2001
+ y_scale=layer._q_scale if fp8_attention else None,
2002
+ )
2003
+ elif self.is_aiter_triton_fp8_bmm_enabled:
2004
# Multiply+Transpose (N, B, P)x(N, P, L)->(N, B, L)->(B, N, L)
2005
decode_ql_nope = rocm_aiter_ops.triton_fp8_bmm(
2006
decode_q_nope,
vllm/model_executor/layers/quantization/quark/utils.py
+14
@@ -6,6 +6,7 @@ from types import MappingProxyType
6
from typing import Any
7
8
import regex as re
9
+import torch
10
11
12
def deep_compare(dict1: Any, dict2: Any) -> bool:
@@ -103,3 +104,16 @@ def _is_equal_or_regex_match(
104
elif target == value:
105
return True
106
return False
107
+
108
+
109
+# utility for tensor dims > 2 cases
110
+def quark_quantize_weight_to_mxfp4(w: torch.Tensor):
111
+ assert w.dtype == torch.bfloat16, (
112
+ "Quark dynamic quantization is supported only for fp16 weights and only to MXF4"
113
+ )
114
+
115
+ from aiter.ops.triton.quant import dynamic_mxfp4_quant
116
+
117
+ *dims, d = w.shape
118
+ w, w_scales = dynamic_mxfp4_quant(w.reshape(-1, d))
119
+ return w.view(*dims, d // 2), w_scales.view(*dims, d // 32)