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 14, 05:30 UTC
[ROCm][Aiter] Add triton fp8 bmm kernel for mla (#23264)
Signed-off-by: Divakar Verma <divakar.verma@amd.com> Co-authored-by: ShaoChunLee <Shao-Chun.Lee@amd.com>
Divakar Verma committed
Aug 28, 2025 at 13:18 UTC
04d1dd7f4a444a61ae4b01ea0271490082dbd605
2 files changed
+104
-12
vllm/envs.py
+8
@@ -99,6 +99,7 @@ if TYPE_CHECKING:
99
VLLM_ROCM_USE_AITER_RMSNORM: bool = True
100
VLLM_ROCM_USE_AITER_MLA: bool = True
101
VLLM_ROCM_USE_AITER_MHA: bool = True
102
+ VLLM_ROCM_USE_AITER_FP8BMM: bool = True
103
VLLM_ROCM_USE_SKINNY_GEMM: bool = True
104
VLLM_ROCM_FP8_PADDING: bool = True
105
VLLM_ROCM_MOE_PADDING: bool = True
@@ -774,6 +775,12 @@ environment_variables: dict[str, Callable[[], Any]] = {
775
lambda: (os.getenv("VLLM_ROCM_USE_AITER_MHA", "True").lower() in
776
("true", "1")),
777
778
+ # Whether to use aiter triton fp8 bmm kernel
779
+ # By default is enabled.
780
+ "VLLM_ROCM_USE_AITER_FP8BMM":
781
+ lambda: (os.getenv("VLLM_ROCM_USE_AITER_FP8BMM", "True").lower() in
782
+ ("true", "1")),
783
+
784
# use rocm skinny gemms
785
"VLLM_ROCM_USE_SKINNY_GEMM":
786
lambda: (os.getenv("VLLM_ROCM_USE_SKINNY_GEMM", "True").lower() in
@@ -1272,6 +1279,7 @@ def compute_hash() -> str:
1279
"VLLM_ROCM_USE_AITER_RMSNORM",
1280
"VLLM_ROCM_USE_AITER_MLA",
1281
"VLLM_ROCM_USE_AITER_MHA",
1282
+ "VLLM_ROCM_USE_AITER_FP8BMM",
1283
"VLLM_ROCM_USE_SKINNY_GEMM",
1284
"VLLM_ROCM_FP8_PADDING",
1285
"VLLM_ROCM_MOE_PADDING",
vllm/v1/attention/backends/mla/common.py
+96
-12
@@ -193,6 +193,7 @@ from dataclasses import dataclass, field
193
from typing import ClassVar, Generic, Optional, TypeVar, Union
194
195
import torch
196
+from tqdm import tqdm
197
198
import vllm.envs as envs
199
from vllm import _custom_ops as ops
@@ -203,6 +204,7 @@ from vllm.attention.backends.utils import get_mla_dims
204
from vllm.attention.ops.merge_attn_states import merge_attn_states
205
from vllm.attention.utils.fa_utils import get_flash_attn_version
206
from vllm.config import VllmConfig
207
+from vllm.distributed.parallel_state import is_global_first_rank
208
from vllm.logger import init_logger
209
from vllm.model_executor.layers.linear import (ColumnParallelLinear,
210
LinearBase,
@@ -234,6 +236,28 @@ try:
236
except ImportError:
237
flashinfer_available = False
238
239
+
240
+def is_rocm_aiter_fp8bmm_enabled() -> bool:
241
+ return current_platform.is_rocm() \
242
+ and envs.VLLM_ROCM_USE_AITER_FP8BMM \
243
+ and envs.VLLM_ROCM_USE_AITER
244
+
245
+
246
+if is_rocm_aiter_fp8bmm_enabled():
247
+ from aiter.ops.triton.batched_gemm_a8w8_a_per_token_group_prequant_w_per_batched_tensor_quant import ( # noqa: E501 # isort: skip
248
+ batched_gemm_a8w8_a_per_token_group_prequant_w_per_batched_tensor_quant
249
+ as aiter_triton_fp8_bmm)
250
+
251
+ def dynamic_per_batched_tensor_quant(
252
+ x: torch.Tensor, dtype: torch.dtype = torch.float8_e4m3fn):
253
+ DTYPE_MAX = torch.finfo(dtype).max
254
+ min_val, max_val = x.aminmax()
255
+ amax = torch.maximum(min_val.abs(), max_val.abs()).clamp(min=1e-10)
256
+ scale = DTYPE_MAX / amax
257
+ x_scl_sat = (x * scale).clamp(min=-DTYPE_MAX, max=DTYPE_MAX)
258
+ return x_scl_sat.to(dtype).contiguous(), scale.float().reciprocal()
259
+
260
+
261
logger = init_logger(__name__)
262
263
CUDNN_WORKSPACE_SIZE = 12800
@@ -945,10 +969,21 @@ class MLACommonImpl(MLAAttentionImpl[M], Generic[M]):
969
def _v_up_proj(self, x):
970
# Convert from (B, N, L) to (N, B, L)
971
x = x.view(-1, self.num_heads, self.kv_lora_rank).transpose(0, 1)
948
- # Multiply (N, B, L) x (N, L, V) -> (N, B, V)
949
- x = torch.bmm(x, self.W_UV)
950
- # Convert from (N, B, V) to (B, N * V)
951
- return x.transpose(0, 1).reshape(-1, self.num_heads * self.v_head_dim)
972
+ if is_rocm_aiter_fp8bmm_enabled():
973
+ # Multiply + Transpose (N, B, L) x (N, L, V)->(N, B, V)->(B, N, V)
974
+ x = aiter_triton_fp8_bmm(x,
975
+ self.W_V,
976
+ self.W_V_scale,
977
+ group_size=128,
978
+ transpose_bm=True)
979
+ # Convert from (B, N, V) to (B, N * V)
980
+ x = x.reshape(-1, self.num_heads * self.v_head_dim)
981
+ else:
982
+ # Multiply (N, B, L) x (N, L, V) -> (N, B, V)
983
+ x = torch.bmm(x, self.W_UV)
984
+ # Convert from (N, B, V) to (B, N * V)
985
+ x = x.transpose(0, 1).reshape(-1, self.num_heads * self.v_head_dim)
986
+ return x
987
988
def process_weights_after_loading(self, act_dtype: torch.dtype):
989
@@ -996,10 +1031,50 @@ class MLACommonImpl(MLAAttentionImpl[M], Generic[M]):
1031
W_UK, W_UV = kv_b_proj_weight.split(
1032
[self.qk_nope_head_dim, self.v_head_dim], dim=-1)
1033
999
- # Convert from (L, N, V) to (N, L, V)
1000
- self.W_UV = W_UV.transpose(0, 1)
1001
- # Convert from (L, N, P) to (N, P, L)
1002
- self.W_UK_T = W_UK.permute(1, 2, 0)
1034
+ if is_rocm_aiter_fp8bmm_enabled():
1035
+ W_K = W_UK.transpose(0, 1) # 16 512 128
1036
+ W_V = W_UV.permute(1, 2, 0) # 16 128 512
1037
+ self.W_K, self.W_K_scale = dynamic_per_batched_tensor_quant(
1038
+ W_K, dtype=current_platform.fp8_dtype())
1039
+ self.W_V, self.W_V_scale = dynamic_per_batched_tensor_quant(
1040
+ W_V, dtype=current_platform.fp8_dtype())
1041
+
1042
+ # The kernel operates on non-padded inputs. Hence, pre-compiling
1043
+ # triton kernel to avoid runtime compilation for unseen batch sizes
1044
+ # Pre-compile for batch sizes 1 to 1024 to cover most use-cases.
1045
+ # On DS-R1, this step adds roughly 50s to the model loading time.
1046
+ max_batch_size = 1024 # [ToDo] Find the optimal upper limit
1047
+ pre_compilation_list = list(range(1, max_batch_size + 1))
1048
+ if is_global_first_rank():
1049
+ pre_compilation_list = tqdm(
1050
+ pre_compilation_list,
1051
+ desc="[Aiter Triton] Pre-compiling fp8 BMM kernel",
1052
+ total=max_batch_size,
1053
+ )
1054
+
1055
+ for m in pre_compilation_list:
1056
+ x = torch.empty((self.W_K.shape[0], m, self.W_K.shape[2]),
1057
+ dtype=torch.bfloat16,
1058
+ device=self.W_K.device)
1059
+ aiter_triton_fp8_bmm(x,
1060
+ self.W_K,
1061
+ self.W_K_scale,
1062
+ group_size=128,
1063
+ transpose_bm=True)
1064
+
1065
+ x = torch.empty((self.W_V.shape[0], m, self.W_V.shape[2]),
1066
+ dtype=torch.bfloat16,
1067
+ device=self.W_V.device)
1068
+ aiter_triton_fp8_bmm(x,
1069
+ self.W_V,
1070
+ self.W_V_scale,
1071
+ group_size=128,
1072
+ transpose_bm=True)
1073
+ else:
1074
+ # Convert from (L, N, V) to (N, L, V)
1075
+ self.W_UV = W_UV.transpose(0, 1)
1076
+ # Convert from (L, N, P) to (N, P, L)
1077
+ self.W_UK_T = W_UK.permute(1, 2, 0)
1078
1079
def _compute_prefill_context(
1080
self,
@@ -1203,10 +1278,19 @@ class MLACommonImpl(MLAAttentionImpl[M], Generic[M]):
1278
[self.qk_nope_head_dim, self.qk_rope_head_dim], dim=-1)
1279
# Convert from (B, N, P) to (N, B, P)
1280
decode_q_nope = decode_q_nope.transpose(0, 1)
1206
- # Multiply (N, B, P) x (N, P, L) -> (N, B, L)
1207
- decode_ql_nope = torch.bmm(decode_q_nope, self.W_UK_T)
1208
- # Convert from (N, B, L) to (B, N, L)
1209
- decode_ql_nope = decode_ql_nope.transpose(0, 1)
1281
+
1282
+ if is_rocm_aiter_fp8bmm_enabled():
1283
+ # Multiply+Transpose (N, B, P)x(N, P, L)->(N, B, L)->(B, N, L)
1284
+ decode_ql_nope = aiter_triton_fp8_bmm(decode_q_nope,
1285
+ self.W_K,
1286
+ self.W_K_scale,
1287
+ group_size=128,
1288
+ transpose_bm=True)
1289
+ else:
1290
+ # Multiply (N, B, P) x (N, P, L) -> (N, B, L)
1291
+ decode_ql_nope = torch.bmm(decode_q_nope, self.W_UK_T)
1292
+ # Convert from (N, B, L) to (B, N, L)
1293
+ decode_ql_nope = decode_ql_nope.transpose(0, 1)
1294
1295
if fp8_attention:
1296
ql_nope_shape = decode_ql_nope.shape