@tduncan / Nvidia-Optimization-Stack-VLLM Mirror · read-only / commits / e2de455c34
Fork 0

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 15, 02:15 UTC

[Feature] Integrate SM100 DeepGEMM support (#20087)

[Feature] Integrate SM100 DeepGEMM support (#20087)

Wentao Ye committed Jul 10, 2025 at 23:18 UTC e2de455c349df8385b18fe447beb6325dcb6af9c
16 files changed +397 -114
benchmarks/kernels/benchmark_moe.py
+3
@@ -86,6 +86,9 @@ def benchmark_config(
86 (num_experts, 2 * shard_intermediate_size), dtype=torch.float32
87 )
88 w2_scale = torch.randn((hidden_size, num_experts), dtype=torch.float32)
89 + if use_deep_gemm:
90 + # we use the default block shape for deepgemm
91 + block_quant_shape = [128, 128]
92 if use_fp8_w8a8:
93 if block_quant_shape:
94 block_n, block_k = block_quant_shape[0], block_quant_shape[1]
tests/kernels/moe/test_block_fp8.py
+8 -8
@@ -15,13 +15,13 @@ from vllm.model_executor.layers.fused_moe.deep_gemm_moe import (
15 from vllm.model_executor.layers.fused_moe.fused_moe import (
16 fused_topk, modular_triton_fused_moe)
17 from vllm.platforms import current_platform
18 +from vllm.utils import has_deep_gemm
19 +from vllm.utils.deep_gemm import is_blackwell_deep_gemm_used
20
19 -dg_available = False
20 -try:
21 - import deep_gemm
22 - dg_available = True
23 -except ImportError:
24 - pass
21 +dg_available = has_deep_gemm()
22 +
23 +if dg_available:
24 + from deep_gemm import get_m_alignment_for_contiguous_layout
25
26 if current_platform.get_device_capability() < (9, 0):
27 pytest.skip("FP8 Triton requires CUDA 9.0 or higher",
@@ -224,6 +224,7 @@ def test_w8a8_block_fp8_fused_moe(M, N, K, E, topk, block_size, dtype, seed,
224 @pytest.mark.parametrize("topk", TOP_KS)
225 @pytest.mark.parametrize("seed", SEEDS)
226 @pytest.mark.skipif(not dg_available, reason="DeepGemm kernels not available.")
227 +@pytest.mark.skipif(is_blackwell_deep_gemm_used(), reason="Not E8M0 scale MOE")
228 @torch.inference_mode()
229 def test_w8a8_block_fp8_deep_gemm_fused_moe(M, N, K, E, topk, seed,
230 monkeypatch):
@@ -238,8 +239,7 @@ def test_w8a8_block_fp8_deep_gemm_fused_moe(M, N, K, E, topk, seed,
239 torch.manual_seed(seed)
240
241 monkeypatch.setenv("VLLM_FUSED_MOE_CHUNK_SIZE", str(chunk_size))
241 -
242 - block_m = deep_gemm.get_m_alignment_for_contiguous_layout()
242 + block_m = get_m_alignment_for_contiguous_layout()
243 block_size = [block_m, block_m]
244 dtype = torch.bfloat16
245
tests/kernels/moe/test_deepep_deepgemm_moe.py
+5
@@ -20,6 +20,7 @@ from vllm.model_executor.layers.fused_moe.modular_kernel import (
20 FusedMoEModularKernel)
21 from vllm.platforms import current_platform
22 from vllm.utils import has_deep_ep, has_deep_gemm
23 +from vllm.utils.deep_gemm import is_blackwell_deep_gemm_used
24
25 from .parallel_utils import ProcessGroupInfo, parallel_launch
26 from .utils import make_test_weights
@@ -368,6 +369,8 @@ NUM_EXPERTS = [32]
369 @pytest.mark.parametrize("world_dp_size", [(2, 1)])
370 @requires_deep_ep
371 @requires_deep_gemm
372 +@pytest.mark.skipif(is_blackwell_deep_gemm_used(),
373 + reason="Skipping test for Blackwell DeepGEMM")
374 def test_ht_deepep_deepgemm_moe(mnk: tuple[int, int, int], num_experts: int,
375 topk: int, world_dp_size: tuple[int, int]):
376 """
@@ -423,6 +426,8 @@ USE_FP8_DISPATCH = [False]
426 @pytest.mark.parametrize("world_dp_size", [(2, 1)])
427 @requires_deep_ep
428 @requires_deep_gemm
429 +@pytest.mark.skipif(is_blackwell_deep_gemm_used(),
430 + reason="Skipping test for Blackwell DeepGEMM")
431 def test_ll_deepep_deepgemm_moe(
432 mnk: tuple[int, int, int],
433 num_experts: int,
tests/kernels/moe/test_deepgemm.py
+8 -47
@@ -13,48 +13,18 @@ import torch
13
14 # vLLM fused-expert reference (Triton fallback + DeepGEMM option)
15 from vllm.model_executor.layers.fused_moe.fused_moe import fused_experts
16 -from vllm.model_executor.layers.quantization.utils.fp8_utils import (
17 - per_token_group_quant_fp8)
18 -from vllm.utils import cdiv
16 +from vllm.utils import has_deep_gemm
17 +from vllm.utils.deep_gemm import (calc_diff, per_block_cast_to_fp8,
18 + per_token_group_cast_to_fp8)
19
20 -has_deep_gemm = importlib.util.find_spec("deep_gemm") is not None
21 -
22 -if has_deep_gemm:
23 - import deep_gemm
24 - BLOCK_M = deep_gemm.get_m_alignment_for_contiguous_layout()
25 - BLOCK_SIZE = [BLOCK_M, BLOCK_M]
20 +BLOCK_SIZE = [128, 128]
21
22 requires_deep_gemm = pytest.mark.skipif(
28 - not has_deep_gemm,
23 + not has_deep_gemm(),
24 reason="Requires deep_gemm kernels",
25 )
26
27
33 -def calc_diff(x: torch.Tensor, y: torch.Tensor):
34 - x, y = x.double(), y.double()
35 - denominator = (x * x + y * y).sum()
36 - sim = 2 * (x * y).sum() / denominator
37 - return 1 - sim
38 -
39 -
40 -def per_block_cast_to_fp8(
41 - x: torch.Tensor,
42 - block_size_n: int = 128) -> tuple[torch.Tensor, torch.Tensor]:
43 - assert x.dim() == 2
44 - m, n = x.shape
45 - x_padded = torch.zeros(
46 - (cdiv(m, 128) * 128, cdiv(n, block_size_n) * block_size_n),
47 - dtype=x.dtype,
48 - device=x.device)
49 - x_padded[:m, :n] = x
50 - x_view = x_padded.view(-1, 128, x_padded.size(1) // 128, block_size_n)
51 - x_amax = x_view.abs().float().amax(dim=(1, 3), keepdim=True).clamp(1e-4)
52 - x_scaled = (x_view * (448.0 / x_amax)).to(torch.float8_e4m3fn)
53 - x_scaled_sub = x_scaled.view_as(x_padded)[:m, :n].contiguous()
54 - scales = (x_amax / 448.0).view(x_view.size(0), x_view.size(2))
55 - return x_scaled_sub, scales
56 -
57 -
28 def make_block_quant_fp8_weights(
29 e: int,
30 n: int,
@@ -111,7 +81,7 @@ def run_single_case(m, n, k, topk, num_experts, block_size):
81 """
82 tokens_bf16 = torch.randn(
83 m, k, device="cuda", dtype=torch.bfloat16).clamp_min_(-1).clamp_max_(1)
114 - _, a1_scale = per_token_group_quant_fp8(tokens_bf16, block_size[1])
84 + _, a1_scale = per_token_group_cast_to_fp8(tokens_bf16, block_size[1])
85
86 # expert weight tensors
87 w1, w2, w1_s, w2_s = make_block_quant_fp8_weights(num_experts, n, k,
@@ -155,17 +125,8 @@ def run_single_case(m, n, k, topk, num_experts, block_size):
125 block_shape=block_size,
126 allow_deep_gemm=True,
127 )
158 -
159 - base = out_triton.abs().mean()
160 - atol = 0.1 * base.clamp(min=1e-2) # 10% of mean, but not lower than 1e-3
161 - rtol = 0.05
162 - # ----- Compare -----
163 - torch.testing.assert_close(
164 - out_deepgemm.to(torch.float32),
165 - out_triton.to(torch.float32),
166 - rtol=rtol,
167 - atol=float(atol),
168 - )
128 + diff = calc_diff(out_deepgemm, out_triton)
129 + assert diff < 0.001, f"Diff exceeded 1%: {diff}"
130
131
132 # Note: W1 has shape (E, 2N, K), so N = 512
tests/kernels/quantization/test_block_fp8.py
+11 -16
@@ -8,19 +8,15 @@ import pytest
8 import torch
9
10 from tests.kernels.quant_utils import (native_per_token_group_quant_fp8,
11 - native_w8a8_block_matmul,
12 - per_block_cast_to_fp8)
11 + native_w8a8_block_matmul)
12 from vllm.config import VllmConfig
13 from vllm.model_executor.layers.quantization.utils.fp8_utils import (
15 - per_token_group_quant_fp8, w8a8_block_fp8_matmul)
14 + get_col_major_tma_aligned_tensor, per_token_group_quant_fp8,
15 + w8a8_block_fp8_matmul)
16 from vllm.platforms import current_platform
17 -
18 -dg_available = False
19 -try:
20 - import deep_gemm
21 - dg_available = True
22 -except ImportError:
23 - pass
17 +from vllm.utils import has_deep_gemm
18 +from vllm.utils.deep_gemm import (fp8_gemm_nt, per_block_cast_to_fp8,
19 + per_token_group_cast_to_fp8)
20
21 if current_platform.get_device_capability() < (9, 0):
22 pytest.skip("FP8 Triton requires CUDA 9.0 or higher",
@@ -106,7 +102,8 @@ def test_w8a8_block_fp8_matmul(M, N, K, block_size, out_dtype, seed):
102 @pytest.mark.parametrize(
103 "M,N,K,block_size,out_dtype,seed",
104 itertools.product(M, N, K, BLOCK_SIZE, OUT_DTYPES, SEEDS))
109 -@pytest.mark.skipif(not dg_available, reason="DeepGemm kernels not available.")
105 +@pytest.mark.skipif(not has_deep_gemm(),
106 + reason="DeepGemm kernels not available.")
107 @torch.inference_mode()
108 def test_w8a8_block_fp8_deep_gemm_matmul(M, N, K, block_size, out_dtype, seed):
109 # only aligned sizes
@@ -120,9 +117,7 @@ def test_w8a8_block_fp8_deep_gemm_matmul(M, N, K, block_size, out_dtype, seed):
117 A_fp32 = (torch.rand(M, K, dtype=torch.float32) - 0.5) * 2 * fp8_max
118 B_fp32 = (torch.rand(N, K, dtype=torch.float32) - 0.5) * 2 * fp8_max
119
123 - _, block_k = block_size[0], block_size[1]
124 -
125 - A_fp8, As_fp8 = per_token_group_quant_fp8(A_fp32, block_k)
120 + A_fp8, As_fp8 = per_token_group_cast_to_fp8(A_fp32, block_size[1])
121 B_fp8, Bs_fp8 = per_block_cast_to_fp8(B_fp32)
122
123 As = As_fp8.to(torch.float32)
@@ -132,14 +127,14 @@ def test_w8a8_block_fp8_deep_gemm_matmul(M, N, K, block_size, out_dtype, seed):
127 out_dtype)
128
129 # Transpose earlier so that the testing will not trigger transposing kernels
135 - As_fp8 = deep_gemm.get_col_major_tma_aligned_tensor(As_fp8)
130 + As_fp8 = get_col_major_tma_aligned_tensor(As_fp8)
131
132 out = torch.zeros((M, N), device='cuda', dtype=out_dtype)
133
134 assert As_fp8.shape == (M, (K + 127) //
135 128), f"{As_fp8.shape} != {(M, (K + 127) // 128)}"
136
142 - deep_gemm.gemm_fp8_fp8_bf16_nt((A_fp8, As_fp8), (B_fp8, Bs_fp8), out)
137 + fp8_gemm_nt((A_fp8, As_fp8), (B_fp8, Bs_fp8), out)
138
139 rel_diff = (torch.mean(
140 torch.abs(out.to(torch.float32) - ref_out.to(torch.float32))) /
vllm/model_executor/layers/fused_moe/batched_deep_gemm_moe.py
+9 -12
@@ -11,6 +11,7 @@ from vllm.model_executor.layers.fused_moe.topk_weight_and_reduce import (
11 TopKWeightAndReduceDelegate)
12 from vllm.model_executor.layers.fused_moe.utils import _resize_cache
13 from vllm.triton_utils import tl, triton
14 +from vllm.utils.deep_gemm import fp8_m_grouped_gemm_nt_masked
15
16 logger = init_logger(__name__)
17
@@ -271,7 +272,6 @@ class BatchedDeepGemmExperts(mk.FusedMoEPermuteExpertsUnpermute):
272 assert expert_tokens_meta is not None
273 expert_num_tokens = expert_tokens_meta.expert_num_tokens
274
274 - import deep_gemm as dg
275 assert hidden_states.ndim == 3
276 assert self.block_shape is not None
277
@@ -289,18 +289,15 @@ class BatchedDeepGemmExperts(mk.FusedMoEPermuteExpertsUnpermute):
289 # for the M expectation of each batch, correctly setting this value
290 # may lead to better performance.
291 expected_m = max_num_tokens
292 -
293 - dg.m_grouped_gemm_fp8_fp8_bf16_nt_masked((a1q, a1q_scale),
294 - (w1, w1_scale),
295 - out=workspace1,
296 - masked_m=expert_num_tokens,
297 - expected_m=expected_m)
292 + fp8_m_grouped_gemm_nt_masked((a1q, a1q_scale), (w1, w1_scale),
293 + out=workspace1,
294 + masked_m=expert_num_tokens,
295 + expected_m=expected_m)
296
297 a2q, a2q_scale = silu_mul_fp8_quant_deep_gemm(workspace1,
298 expert_num_tokens)
299
302 - dg.m_grouped_gemm_fp8_fp8_bf16_nt_masked((a2q, a2q_scale),
303 - (w2, w2_scale),
304 - out=output,
305 - masked_m=expert_num_tokens,
306 - expected_m=expected_m)
300 + fp8_m_grouped_gemm_nt_masked((a2q, a2q_scale), (w2, w2_scale),
301 + out=output,
302 + masked_m=expert_num_tokens,
303 + expected_m=expected_m)
vllm/model_executor/layers/fused_moe/deep_gemm_moe.py
+11 -11
@@ -14,9 +14,10 @@ from vllm.model_executor.layers.fused_moe.prepare_finalize import (
14 MoEPrepareAndFinalizeNoEP)
15 from vllm.model_executor.layers.fused_moe.topk_weight_and_reduce import (
16 TopKWeightAndReduceDelegate)
17 -from vllm.model_executor.layers.fused_moe.utils import (
18 - _resize_cache, per_token_group_quant_fp8)
17 +from vllm.model_executor.layers.fused_moe.utils import _resize_cache
18 from vllm.utils import has_deep_gemm, round_up
19 +from vllm.utils.deep_gemm import (m_grouped_fp8_gemm_nt_contiguous,
20 + per_token_group_cast_to_fp8)
21
22 logger = init_logger(__name__)
23
@@ -127,7 +128,6 @@ class DeepGemmExperts(mk.FusedMoEPermuteExpertsUnpermute):
128 workspace2: torch.Tensor,
129 expert_tokens_meta: Optional[mk.ExpertTokensMetadata],
130 ):
130 - import deep_gemm as dg
131 assert self.block_shape is not None
132
133 a1q = hidden_states
@@ -164,19 +164,19 @@ class DeepGemmExperts(mk.FusedMoEPermuteExpertsUnpermute):
164 (M_sum, N // 2))
165 mm2_out = _resize_cache(workspace2, (M_sum, K))
166
167 - dg.m_grouped_gemm_fp8_fp8_bf16_nt_contiguous(
168 - (a1q, a1q_scale), (w1, w1_scale), mm1_out, expert_ids)
167 + m_grouped_fp8_gemm_nt_contiguous((a1q, a1q_scale), (w1, w1_scale),
168 + mm1_out, expert_ids)
169
170 self.activation(activation, act_out, mm1_out.view(-1, N))
171
172 a2q_scale: Optional[torch.Tensor] = None
173 - a2q, a2q_scale = per_token_group_quant_fp8(act_out,
174 - self.block_shape[1],
175 - column_major_scales=True,
176 - out_q=quant_out)
173 + a2q, a2q_scale = per_token_group_cast_to_fp8(act_out,
174 + self.block_shape[1],
175 + column_major_scales=True,
176 + out_q=quant_out)
177
178 - dg.m_grouped_gemm_fp8_fp8_bf16_nt_contiguous(
179 - (a2q, a2q_scale), (w2, w2_scale), mm2_out, expert_ids)
178 + m_grouped_fp8_gemm_nt_contiguous((a2q, a2q_scale), (w2, w2_scale),
179 + mm2_out, expert_ids)
180
181 torch.index_select(mm2_out, 0, inv_perm, out=output.view((-1, K)))
182
vllm/model_executor/layers/fused_moe/fused_moe.py
+9 -3
@@ -34,6 +34,7 @@ from vllm.model_executor.layers.quantization.utils.mxfp4_utils import (
34 from vllm.platforms import current_platform
35 from vllm.triton_utils import tl, triton
36 from vllm.utils import direct_register_custom_op
37 +from vllm.utils.deep_gemm import is_blackwell_deep_gemm_used
38
39 from .rocm_aiter_fused_moe import is_rocm_aiter_moe_enabled
40
@@ -1171,9 +1172,15 @@ def fused_experts(
1172 allow_cutlass_block_scaled_grouped_gemm: bool = False) -> torch.Tensor:
1173 # For now, disable DeepGemm for small N (<= 512) until better
1174 # permute/unpermute ops are available.
1175 + # However, on B200, we use DeepGemm for all cases becuase they only support
1176 + # E8M0 scale, which means we requantize the weight and input to the specific
1177 + # scale. Fallen back to cutlass or triton for some cases would cause
1178 + # accuracy issue.
1179 N = w1.size(1)
1175 - if (allow_deep_gemm and use_fp8_w8a8 and N > 512
1176 - and _valid_deep_gemm(hidden_states, w1, w2)):
1180 + should_use_deep_gemm = ((N > 512
1181 + and _valid_deep_gemm(hidden_states, w1, w2))
1182 + or is_blackwell_deep_gemm_used())
1183 + if (allow_deep_gemm and use_fp8_w8a8 and should_use_deep_gemm):
1184 assert apply_router_weight_on_input is False
1185 return deep_gemm_moe_fp8(
1186 hidden_states=hidden_states,
@@ -1363,7 +1370,6 @@ def fused_experts_impl(
1370
1371 curr_topk_ids = topk_ids[begin_chunk_idx:end_chunk_idx]
1372 curr_topk_weights = topk_weights[begin_chunk_idx:end_chunk_idx]
1366 -
1373 qcurr_hidden_states, a1q_scale = moe_kernel_quantize_input(
1374 A=curr_hidden_states,
1375 A_scale=a1_scale,
vllm/model_executor/layers/fused_moe/prepare_finalize.py
-1
@@ -48,7 +48,6 @@ class MoEPrepareAndFinalizeNoEP(mk.FusedMoEPrepareAndFinalize):
48 assert topk == 1, \
49 "apply_router_weight_on_input is only implemented for topk=1"
50 a1.mul_(topk_weights.to(a1.dtype))
51 -
51 a1q, a1q_scale = moe_kernel_quantize_input(
52 a1, a1_scale, quant_config.quant_dtype,
53 quant_config.per_act_token_quant, quant_config.block_shape)
vllm/model_executor/layers/fused_moe/triton_deep_gemm_moe.py
+5 -2
@@ -9,6 +9,7 @@ from vllm.model_executor.layers.fused_moe.config import FusedMoEQuantConfig
9 from vllm.model_executor.layers.fused_moe.deep_gemm_moe import (
10 DeepGemmExperts, _valid_deep_gemm, _valid_deep_gemm_shape)
11 from vllm.model_executor.layers.fused_moe.fused_moe import TritonExperts
12 +from vllm.utils.deep_gemm import is_blackwell_deep_gemm_used
13
14
15 class TritonOrDeepGemmExperts(mk.FusedMoEPermuteExpertsUnpermute):
@@ -102,7 +103,8 @@ class TritonOrDeepGemmExperts(mk.FusedMoEPermuteExpertsUnpermute):
103 # Note: the deep gemm workspaces are strictly larger than the triton
104 # workspaces so we can be pessimistic here and allocate for DeepGemm
105 # even if we fall back to triton later, e.g. if expert maps are set.
105 - if self.allow_deep_gemm and _valid_deep_gemm_shape(M, N, K):
106 + if self.allow_deep_gemm and (_valid_deep_gemm_shape(M, N, K)
107 + or is_blackwell_deep_gemm_used()):
108 assert self.deep_gemm_expert is not None
109 return self.deep_gemm_expert.workspace_shapes(
110 a, aq, M, N, K, topk, global_num_experts, local_num_experts)
@@ -132,7 +134,8 @@ class TritonOrDeepGemmExperts(mk.FusedMoEPermuteExpertsUnpermute):
134 expert_tokens_meta: Optional[mk.ExpertTokensMetadata],
135 ):
136 use_deep_gemm = (self.allow_deep_gemm
135 - and _valid_deep_gemm(hidden_states, w1, w2))
137 + and (_valid_deep_gemm(hidden_states, w1, w2)
138 + or is_blackwell_deep_gemm_used()))
139
140 experts = self.deep_gemm_expert if use_deep_gemm else self.triton_expert
141 assert experts is not None
vllm/model_executor/layers/fused_moe/utils.py
+6 -1
@@ -15,6 +15,8 @@ from vllm.model_executor.layers.quantization.utils.mxfp4_utils import (
15 from vllm.platforms import current_platform
16 from vllm.triton_utils import tl, triton
17 from vllm.utils import cdiv
18 +from vllm.utils.deep_gemm import (is_blackwell_deep_gemm_used,
19 + per_token_group_cast_to_fp8)
20
21
22 @triton.jit
@@ -115,7 +117,10 @@ def _fp8_quantize(
117 assert not per_act_token
118 assert len(block_shape) == 2
119 _, block_k = block_shape[0], block_shape[1]
118 - A, A_scale = per_token_group_quant_fp8(A, block_k)
120 + if is_blackwell_deep_gemm_used():
121 + A, A_scale = per_token_group_cast_to_fp8(A, block_k)
122 + else:
123 + A, A_scale = per_token_group_quant_fp8(A, block_k)
124 assert cdiv(A.size(-1), block_k) == A_scale.size(-1)
125
126 return A, A_scale
vllm/model_executor/layers/mamba/ops/causal_conv1d.py
+1 -2
@@ -8,10 +8,9 @@ from typing import Optional, Union
8
9 import numpy as np
10 import torch
11 -import triton
12 -import triton.language as tl
11
12 from vllm.attention.backends.utils import PAD_SLOT_ID
13 +from vllm.triton_utils import tl, triton
14
15
16 @triton.jit()
vllm/model_executor/layers/quantization/deepgemm.py
+3 -5
@@ -6,10 +6,8 @@ import torch
6
7 from vllm.platforms import current_platform
8 from vllm.triton_utils import triton
9 -from vllm.utils import direct_register_custom_op, has_deep_gemm
10 -
11 -if has_deep_gemm():
12 - import deep_gemm
9 +from vllm.utils import direct_register_custom_op
10 +from vllm.utils.deep_gemm import fp8_gemm_nt
11
12 logger = logging.getLogger(__name__)
13
@@ -57,7 +55,7 @@ def w8a8_block_fp8_matmul_deepgemm(
55 output_dtype)
56 # Deepgemm only supports output tensor type as bfloat16
57 assert C.dtype == torch.bfloat16
60 - deep_gemm.gemm_fp8_fp8_bf16_nt((A, As), (B, Bs), C)
58 + fp8_gemm_nt((A, As), (B, Bs), C)
59 return C
60
61
vllm/model_executor/layers/quantization/fp8.py
+42 -4
@@ -23,6 +23,8 @@ from vllm.model_executor.layers.quantization import QuantizationMethods
23 from vllm.model_executor.layers.quantization.base_config import (
24 QuantizationConfig, QuantizeMethodBase)
25 from vllm.model_executor.layers.quantization.kv_cache import BaseKVCacheMethod
26 +from vllm.model_executor.layers.quantization.utils.fp8_utils import (
27 + get_col_major_tma_aligned_tensor, requant_weight_ue8m0_inplace)
28 from vllm.model_executor.layers.quantization.utils.marlin_utils_fp8 import (
29 apply_fp8_marlin_linear, prepare_fp8_layer_for_marlin,
30 prepare_moe_fp8_layer_for_marlin)
@@ -40,6 +42,7 @@ from vllm.model_executor.utils import set_weight_attrs
42 from vllm.platforms import current_platform
43 from vllm.scalar_type import scalar_types
44 from vllm.utils import has_deep_gemm
45 +from vllm.utils.deep_gemm import is_blackwell_deep_gemm_used
46
47 if TYPE_CHECKING:
48 from vllm.model_executor.models.utils import WeightsMapper
@@ -393,6 +396,19 @@ class Fp8LinearMethod(LinearMethodBase):
396 # Activations not quantized for marlin.
397 del layer.input_scale
398
399 + # On B200, DeepGemm only support E8M0 scale, which means we need to
400 + # requantize the weight and input to the specific scale
401 + # at the same time.
402 + if is_blackwell_deep_gemm_used():
403 + assert layer.weight_block_size is not None
404 + block_sz = tuple(layer.weight_block_size)
405 + requant_weight_ue8m0_inplace(
406 + layer.weight.data,
407 + layer.weight_scale_inv.data if hasattr(
408 + layer, "weight_scale_inv") else layer.weight_scale.data,
409 + block_sz,
410 + )
411 +
412 def apply(self,
413 layer: torch.nn.Module,
414 x: torch.Tensor,
@@ -670,15 +686,14 @@ class Fp8MoEMethod(FusedMoEMethodBase):
686
687 # DeepGemm scales need to be transposed and aligned. We try to do
688 # it ahead of time for performance reasons.
673 - if self.allow_deep_gemm:
689 + if self.allow_deep_gemm and not is_blackwell_deep_gemm_used():
690 # Lazy import to avoid CUDA initialization problems.
675 - import deep_gemm as dg
691 if _is_col_major(layer.w13_weight_scale_inv):
692 layer.w13_weight_scale_inv = \
678 - dg.get_col_major_tma_aligned_tensor(layer.w13_weight_scale_inv).contiguous()
693 + get_col_major_tma_aligned_tensor(layer.w13_weight_scale_inv).contiguous()
694 if _is_col_major(layer.w2_weight_scale_inv):
695 layer.w2_weight_scale_inv = \
681 - dg.get_col_major_tma_aligned_tensor(layer.w2_weight_scale_inv).contiguous()
696 + get_col_major_tma_aligned_tensor(layer.w2_weight_scale_inv).contiguous()
697
698 # If checkpoint is fp16, quantize in place.
699 elif not self.quant_config.is_checkpoint_fp8_serialized:
@@ -797,6 +812,29 @@ class Fp8MoEMethod(FusedMoEMethodBase):
812 del layer.w13_input_scale
813 del layer.w2_input_scale
814
815 + if is_blackwell_deep_gemm_used():
816 + assert layer.weight_block_size is not None
817 + # Re-quantise the expert weights so their scales are UE8M0.
818 + block_sz = tuple(layer.weight_block_size)
819 + requant_weight_ue8m0_inplace(
820 + layer.w13_weight.data,
821 + layer.w13_weight_scale_inv.data,
822 + block_sz,
823 + )
824 + requant_weight_ue8m0_inplace(
825 + layer.w2_weight.data,
826 + layer.w2_weight_scale_inv.data,
827 + block_sz,
828 + )
829 +
830 + # Ensure column-major TMA alignment expected by DeepGEMM.
831 + if _is_col_major(layer.w13_weight_scale_inv):
832 + layer.w13_weight_scale_inv = get_col_major_tma_aligned_tensor(
833 + layer.w13_weight_scale_inv).contiguous()
834 + if _is_col_major(layer.w2_weight_scale_inv):
835 + layer.w2_weight_scale_inv = get_col_major_tma_aligned_tensor(
836 + layer.w2_weight_scale_inv).contiguous()
837 +
838 def select_gemm_impl(
839 self,
840 prepare_finalize: FusedMoEPrepareAndFinalize,
vllm/model_executor/layers/quantization/utils/fp8_utils.py
+124 -2
@@ -5,6 +5,7 @@
5 import functools
6 import json
7 import os
8 +from collections.abc import Sequence
9 from typing import Any, Callable, Optional, Union
10
11 import torch
@@ -13,7 +14,7 @@ import vllm.envs as envs
14 from vllm import _custom_ops as ops
15 from vllm.logger import init_logger
16 from vllm.model_executor.layers.quantization.utils.quant_utils import (
16 - scaled_dequantize)
17 + group_broadcast)
18 from vllm.model_executor.layers.quantization.utils.w8a8_utils import (
19 CUTLASS_BLOCK_FP8_SUPPORTED)
20 from vllm.platforms import current_platform
@@ -235,7 +236,7 @@ def block_quant_to_tensor_quant(
236 The outputs are tensor-wise quantization tensor and tensor-wise
237 quantization scale. Note only float8 is supported for now.
238 """
238 - x_dq_block = scaled_dequantize(x_q_block, x_s)
239 + x_dq_block = group_broadcast(x_q_block, x_s)
240 x_q_tensor, scale = input_to_float8(x_dq_block, dtype=x_q_block.dtype)
241 return x_q_tensor, scale
242
@@ -651,3 +652,124 @@ def w8a8_block_fp8_matmul(
652 )
653
654 return C
655 +
656 +
657 +# Taken from https://github.com/deepseek-ai/DeepGEMM/blob/0c88cd01392c1073c7049a97d6328c7bba9b3947
658 +# TODO(wentao): remove this function when DeepGEMM exposes this function
659 +def get_tma_aligned_size(x: int, element_size: int) -> int:
660 + """
661 + Global memory address of TMA must be 16-byte aligned.
662 + Since we use column-major layout for the LHS scaling tensor,
663 + the M-axis of the LHS scaling tensor needs to be padded to a multiple of
664 + 16 bytes.
665 +
666 + Arguments:
667 + x: original M-axis shape of the LHS scaling tensor.
668 + element_size: element size of the LHS scaling tensor.
669 +
670 + Returns:
671 + M-axis shape of the LHS scaling tensor after padding.
672 + """
673 + tma_alignment_bytes = 16
674 + assert tma_alignment_bytes % element_size == 0
675 + alignment = tma_alignment_bytes // element_size
676 + return cdiv(x, alignment) * alignment
677 +
678 +
679 +# Taken from https://github.com/deepseek-ai/DeepGEMM/blob/0c88cd01392c1073c7049a97d6328c7bba9b3947
680 +# TODO(wentao): remove this function when DeepGEMM exposes this function
681 +def get_col_major_tma_aligned_tensor(x: torch.Tensor) -> torch.Tensor:
682 + """
683 + Returns TMA-aligned transposed format of the input tensor. `torch.transpose`
684 + will be called if necessary.
685 + If the input tensor is already column-major layout and 16-byte aligned along
686 + the M axis (thus meets the requirement of LHS scaling tensor in
687 + DeepGEMM), this function will do nothing.
688 +
689 + Arguments:
690 + x: usually the LHS scaling tensor in GEMM.
691 +
692 + Returns:
693 + The LHS scaling tensor of TMA-aligned transposed format.
694 + """
695 + # NOTES: for the extreme performance, you may rewrite/fuse this function in
696 + # CUDA
697 + assert x.dim() in (2, 3)
698 + remove_dim = False
699 + m, n = x.shape[-2], x.shape[-1]
700 + aligned_m = get_tma_aligned_size(m, x.element_size())
701 + if x.dim() == 2:
702 + if x.stride(0) == 1 and x.stride(1) == aligned_m:
703 + return x
704 + x, remove_dim = x.unsqueeze(0), True
705 +
706 + b = x.shape[0]
707 +
708 + # The last kernel gives a column-major TMA aligned layout
709 + if x.stride(0) == aligned_m * n and x.stride(1) == 1 and x.stride(
710 + 2) == aligned_m:
711 + return x.squeeze(0) if remove_dim else x
712 +
713 + # Normal layout requires transposing
714 + aligned_x = torch.transpose(
715 + torch.empty((b, n, aligned_m), device=x.device, dtype=x.dtype), 1, 2)
716 + aligned_x[:, :m, :] = x
717 + aligned_x = aligned_x[:, :m, :]
718 + return aligned_x.squeeze(0) if remove_dim else aligned_x
719 +
720 +
721 +def requant_weight_ue8m0_inplace(
722 + weight: torch.Tensor,
723 + weight_scale: torch.Tensor,
724 + block_size: Sequence[int] = (128, 128),
725 +) -> None:
726 + """Re-quantise *weight* so that its per-block scaling factors are in the
727 + UE8M0 (power-of-two) format expected by the new DeepGEMM kernels inplace.
728 +
729 + Args:
730 + weight: Block-quantised weight tensor stored in ``torch.float8_e4m3fn``.
731 + Expected shape ``(..., M, K)``.
732 + weight_scale: Corresponding per-block scale tensor (``torch.float32``)
733 + with shape ``(..., M // block_size[0], K // block_size[1])``.
734 + block_size: 2-element iterable ``[block_m, block_k]`` describing the
735 + block quantisation granularity.
736 + """
737 + if weight.numel() == 0:
738 + return
739 +
740 + if weight.dtype != torch.float8_e4m3fn:
741 + raise ValueError("Expected *weight* to be torch.float8_e4m3fn, got "
742 + f"{weight.dtype} instead.")
743 +
744 + from vllm.utils.deep_gemm import per_block_cast_to_fp8
745 +
746 + block_m, block_k = int(block_size[0]), int(block_size[1])
747 +
748 + # Flatten leading dimensions so we can iterate over the last two dims.
749 + leading_shape = weight.shape[:-2]
750 + if len(leading_shape) == 0:
751 + w_view = weight.unsqueeze(0)
752 + s_view = weight_scale.unsqueeze(0)
753 + else:
754 + w_view = weight.reshape(-1, weight.shape[-2], weight.shape[-1])
755 + s_view = weight_scale.reshape(-1, *weight_scale.shape[-2:])
756 +
757 + num_mats = w_view.size(0)
758 + for idx in range(num_mats):
759 + w_q = w_view[idx]
760 + s_old = s_view[idx]
761 +
762 + # De-quantise with the *old* scaling factors (float32).
763 + m_cur, k_cur = w_q.shape
764 + s_float = s_old.to(torch.float32)
765 + # Expand scales along rows and cols by block size, then crop.
766 + s_exp_r = torch.repeat_interleave(s_float, block_m, dim=0)
767 + s_exp = torch.repeat_interleave(s_exp_r, block_k, dim=1)
768 + s_exp = s_exp[:m_cur, :k_cur]
769 + w_dq = w_q.to(torch.float32) * s_exp
770 + # Re-quantise using power-of-two scaling (UE8M0).
771 + w_requant, s_requant = per_block_cast_to_fp8(w_dq, [block_m, block_k])
772 +
773 + # Write back the results in-place.
774 + w_q.copy_(w_requant)
775 + s_old.copy_(s_requant)
vllm/utils/deep_gemm.py new
+152
@@ -0,0 +1,152 @@
1 +# SPDX-License-Identifier: Apache-2.0
2 +# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3 +"""Compatibility wrapper for DeepGEMM API changes.
4 +
5 +Users of vLLM should always import **only** these wrappers.
6 +"""
7 +from __future__ import annotations
8 +
9 +import functools
10 +import importlib
11 +from typing import Any, Callable, NoReturn
12 +
13 +import torch
14 +
15 +import vllm.envs as envs
16 +from vllm.utils import cuda_get_device_properties, has_deep_gemm
17 +
18 +
19 +@functools.cache
20 +def is_blackwell_deep_gemm_used() -> bool:
21 + """Return ``True`` if vLLM is configured to use DeepGEMM on a
22 + Blackwell-class GPU.
23 + """
24 +
25 + if not (envs.VLLM_USE_DEEP_GEMM and has_deep_gemm()
26 + and _per_block_cast_impl is not None):
27 + return False
28 +
29 + return cuda_get_device_properties(0, ("major", ))[0] == 10
30 +
31 +
32 +def _missing(*_: Any, **__: Any) -> NoReturn:
33 + """Placeholder for unavailable DeepGEMM backend."""
34 + raise RuntimeError(
35 + "DeepGEMM backend is not available. Please install the `deep_gemm` "
36 + "package to enable FP8 kernels.")
37 +
38 +
39 +def _resolve_symbol(module, new: str, old: str) -> Callable[..., Any] | None:
40 + """Return the *new* symbol if it exists, otherwise the *old* one."""
41 + if hasattr(module, new):
42 + return getattr(module, new)
43 + if hasattr(module, old):
44 + return getattr(module, old)
45 + return None
46 +
47 +
48 +if not has_deep_gemm():
49 + _fp8_gemm_nt_impl: Callable[..., Any] | None = None
50 + _grouped_impl: Callable[..., Any] | None = None
51 + _grouped_masked_impl: Callable[..., Any] | None = None
52 + _per_token_cast_impl: Callable[..., Any] | None = None
53 + _per_block_cast_impl: Callable[..., Any] | None = None
54 +else:
55 + _dg = importlib.import_module("deep_gemm") # type: ignore
56 +
57 + _fp8_gemm_nt_impl = _resolve_symbol(
58 + _dg,
59 + "fp8_gemm_nt",
60 + "gemm_fp8_fp8_bf16_nt",
61 + )
62 + _grouped_impl = _resolve_symbol(
63 + _dg,
64 + "m_grouped_fp8_gemm_nt_contiguous",
65 + "m_grouped_gemm_fp8_fp8_bf16_nt_contiguous",
66 + )
67 + _grouped_masked_impl = _resolve_symbol(
68 + _dg,
69 + "fp8_m_grouped_gemm_nt_masked",
70 + "m_grouped_gemm_fp8_fp8_bf16_nt_masked",
71 + )
72 +
73 + # Try to get per_token_cast_to_fp8 from DeepGEMM math utils.
74 + try:
75 + _math_mod = importlib.import_module(
76 + "deep_gemm.utils.math") # type: ignore
77 + _per_token_cast_impl = getattr(_math_mod, "per_token_cast_to_fp8",
78 + None)
79 + _per_block_cast_impl = getattr(_math_mod, "per_block_cast_to_fp8",
80 + None)
81 + except ModuleNotFoundError:
82 + _per_token_cast_impl = None
83 + _per_block_cast_impl = None
84 +
85 +
86 +def fp8_gemm_nt(*args, **kwargs):
87 + if _fp8_gemm_nt_impl is None:
88 + return _missing(*args, **kwargs)
89 + return _fp8_gemm_nt_impl(*args, **kwargs)
90 +
91 +
92 +def m_grouped_fp8_gemm_nt_contiguous(*args, **kwargs):
93 + if _grouped_impl is None:
94 + return _missing(*args, **kwargs)
95 + return _grouped_impl(*args, **kwargs)
96 +
97 +
98 +def fp8_m_grouped_gemm_nt_masked(*args, **kwargs):
99 + if _grouped_masked_impl is None:
100 + return _missing(*args, **kwargs)
101 + return _grouped_masked_impl(*args, **kwargs)
102 +
103 +
104 +def per_token_group_cast_to_fp8(x, group_size, *args, **kwargs):
105 + """Wrapper for token-wise FP8 quantisation.
106 +
107 + • If DeepGEMM provides ``per_token_cast_to_fp8`` (new API), use it.
108 + • Otherwise, fall back to vLLM's ``per_token_group_quant_fp8``
109 + """
110 +
111 + if _per_token_cast_impl is not None and is_blackwell_deep_gemm_used():
112 + assert group_size == 128, "group_size must be 128 for deepgemm"
113 + return _per_token_cast_impl(x)
114 +
115 + from vllm.model_executor.layers.quantization.utils.fp8_utils import (
116 + per_token_group_quant_fp8 as _ptg)
117 + return _ptg(x, group_size, *args, **kwargs)
118 +
119 +
120 +def per_block_cast_to_fp8(x, *args, **kwargs):
121 + if _per_block_cast_impl is not None and is_blackwell_deep_gemm_used():
122 + return _per_block_cast_impl(x)
123 + # TODO: refactor the `per_block_cast_to_fp8` from tests to vllm utils
124 + from tests.kernels.quant_utils import per_block_cast_to_fp8 as _pbcf
125 + return _pbcf(x, *args, **kwargs)
126 +
127 +
128 +def calc_diff(x: torch.Tensor, y: torch.Tensor):
129 + """Return a global difference metric for unit tests.
130 +
131 + DeepGEMM kernels on Blackwell/B200 currently exhibit noticeable per-element
132 + error, causing ``torch.testing.assert_close`` to fail. Instead of checking
133 + every element, we compute a cosine-style similarity over the whole tensor
134 + and report ``1 - sim``. Once kernel accuracy improves this helper can be
135 + removed.
136 + """
137 +
138 + x, y = x.double(), y.double()
139 + denominator = (x * x + y * y).sum()
140 + sim = 2 * (x * y).sum() / denominator
141 + return 1 - sim
142 +
143 +
144 +__all__ = [
145 + "calc_diff",
146 + "fp8_gemm_nt",
147 + "m_grouped_fp8_gemm_nt_contiguous",
148 + "fp8_m_grouped_gemm_nt_masked",
149 + "per_token_group_cast_to_fp8",
150 + "per_block_cast_to_fp8",
151 + "is_blackwell_deep_gemm_used",
152 +]