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
[Kernel] Add nvfp4 gemm flashinfer backends (#22346)
Signed-off-by: Julien Lin <jullin@nvidia.com> Signed-off-by: mgoin <mgoin64@gmail.com> Co-authored-by: mgoin <mgoin64@gmail.com>
nvjullin committed
Aug 15, 2025 at 04:03 UTC
279a5f31b3faa6f40759516efa5c742f637ab8b7
9 files changed
+371
-41
.buildkite/test-pipeline.yaml
+1
@@ -669,6 +669,7 @@ steps:
669
- pytest -v -s tests/kernels/quantization/test_cutlass_scaled_mm.py -k 'fp8'
670
- pytest -v -s tests/kernels/quantization/test_nvfp4_quant.py
671
- pytest -v -s tests/kernels/quantization/test_nvfp4_scaled_mm.py
672
+ - pytest -v -s tests/kernels/quantization/test_flashinfer_nvfp4_scaled_mm.py
673
- pytest -v -s tests/kernels/moe/test_nvfp4_moe.py
674
# Fusion
675
- pytest -v -s tests/compile/test_fusion_all_reduce.py
tests/kernels/quantization/test_flashinfer_nvfp4_scaled_mm.py
new
+139
@@ -0,0 +1,139 @@
1
+# SPDX-License-Identifier: Apache-2.0
2
+# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
+import pytest
4
+import torch
5
+from nvfp4_utils import (FLOAT4_E2M1_MAX, FLOAT8_E4M3_MAX,
6
+ convert_swizzled_to_linear, dequantize_nvfp4_to_dtype)
7
+
8
+from vllm import _custom_ops as ops
9
+from vllm.platforms import current_platform
10
+from vllm.utils.flashinfer import flashinfer_scaled_fp4_mm
11
+
12
+if not current_platform.has_device_capability(100):
13
+ pytest.skip(
14
+ reason="Nvfp4 Requires compute capability of 10 or above.",
15
+ allow_module_level=True,
16
+ )
17
+
18
+DTYPES = [torch.float16, torch.bfloat16]
19
+# m, n, k
20
+SHAPES = [(128, 128, 64), (128, 128, 128), (256, 128, 64), (128, 256, 128)]
21
+PAD_SHAPES = [(150, 128, 64), (128, 128, 96)]
22
+SHAPES.extend(PAD_SHAPES)
23
+
24
+SEEDS = [42]
25
+CUDA_DEVICES = ["cuda:0"]
26
+
27
+
28
+def get_ref_results(
29
+ a_fp4,
30
+ b_fp4,
31
+ a_sf,
32
+ b_sf,
33
+ a_global_scale,
34
+ b_global_scale,
35
+ m,
36
+ n,
37
+ dtype,
38
+ block_size,
39
+ device,
40
+):
41
+ _, m_k = a_fp4.shape
42
+ _, n_k = b_fp4.shape
43
+ assert m_k == n_k
44
+ a_in_dtype = dequantize_nvfp4_to_dtype(a_fp4,
45
+ a_sf,
46
+ a_global_scale,
47
+ dtype=dtype,
48
+ device=device,
49
+ block_size=block_size)
50
+ b_in_dtype = dequantize_nvfp4_to_dtype(b_fp4,
51
+ b_sf,
52
+ b_global_scale,
53
+ dtype=dtype,
54
+ device=device,
55
+ block_size=block_size)
56
+ return torch.matmul(a_in_dtype, b_in_dtype.t())
57
+
58
+
59
+@pytest.mark.parametrize("dtype", DTYPES)
60
+@pytest.mark.parametrize("shape", SHAPES)
61
+@pytest.mark.parametrize("seed", SEEDS)
62
+@pytest.mark.parametrize("device", CUDA_DEVICES)
63
+@pytest.mark.parametrize("backend", ["cutlass", "trtllm"])
64
+@pytest.mark.parametrize("autotune", [False, True])
65
+@torch.inference_mode()
66
+def test_flashinfer_nvfp4_gemm(
67
+ dtype: torch.dtype,
68
+ shape: tuple[int, int, int],
69
+ seed: int,
70
+ device: str,
71
+ backend: str,
72
+ autotune: bool,
73
+) -> None:
74
+ if backend == "trtllm" and dtype == torch.float16:
75
+ pytest.skip(
76
+ "Only torch.bfloat16 is supported for TRTLLM FP4 GEMM operations")
77
+
78
+ current_platform.seed_everything(seed)
79
+ m, n, packed_k = shape
80
+ k = packed_k * 2
81
+ block_size = 16
82
+ a_dtype = torch.randn((m, k), dtype=dtype, device=device)
83
+ b_dtype = torch.randn((n, k), dtype=dtype, device=device)
84
+
85
+ a_global_scale = ((FLOAT8_E4M3_MAX * FLOAT4_E2M1_MAX) /
86
+ torch.amax(a_dtype.flatten(), dim=-1)).to(torch.float32)
87
+ b_global_scale = ((FLOAT8_E4M3_MAX * FLOAT4_E2M1_MAX) /
88
+ torch.amax(b_dtype.flatten(), dim=-1)).to(torch.float32)
89
+ alpha = 1.0 / (a_global_scale * b_global_scale)
90
+ # ops.scaled_fp4_quant returns swizzled scales, while weights
91
+ # from checkpoints are in linear scales.
92
+ # So instead of needing to swizzle for cutlass as in modelopt.py,
93
+ # we need to unswizzle for trtllm here.
94
+ a_fp4, a_scale_interleaved = ops.scaled_fp4_quant(a_dtype, a_global_scale)
95
+ b_fp4, b_scale_interleaved = ops.scaled_fp4_quant(b_dtype, b_global_scale)
96
+
97
+ # get_ref_results unswizzles the scales internally.
98
+ expected_out = get_ref_results(
99
+ a_fp4,
100
+ b_fp4,
101
+ a_scale_interleaved,
102
+ b_scale_interleaved,
103
+ a_global_scale,
104
+ b_global_scale,
105
+ m,
106
+ n,
107
+ dtype,
108
+ block_size,
109
+ device,
110
+ )
111
+
112
+ import flashinfer
113
+
114
+ if backend == "trtllm":
115
+ epilogue_tile_m = 128
116
+ b_fp4 = flashinfer.shuffle_matrix_a(b_fp4.view(torch.uint8),
117
+ epilogue_tile_m)
118
+
119
+ b_scale_interleaved = convert_swizzled_to_linear(
120
+ b_scale_interleaved, n, k, block_size)
121
+ b_scale_interleaved = (flashinfer.shuffle_matrix_sf_a(
122
+ b_scale_interleaved.view(torch.uint8), epilogue_tile_m).reshape(
123
+ b_scale_interleaved.shape).view(torch.float8_e4m3fn))
124
+
125
+ with flashinfer.autotune(autotune):
126
+ out = flashinfer_scaled_fp4_mm(
127
+ a_fp4,
128
+ b_fp4,
129
+ a_scale_interleaved,
130
+ b_scale_interleaved,
131
+ alpha,
132
+ dtype,
133
+ backend=backend,
134
+ )
135
+
136
+ torch.testing.assert_close(out,
137
+ expected_out.to(dtype=dtype),
138
+ atol=1e-1,
139
+ rtol=1e-1)
tests/kernels/quantization/test_nvfp4_scaled_mm.py
+3
@@ -65,9 +65,12 @@ def test_nvfp4_gemm(
65
b_global_scale = ((FLOAT8_E4M3_MAX * FLOAT4_E2M1_MAX) /
66
torch.amax(b_dtype.flatten(), dim=-1)).to(torch.float32)
67
alpha = 1. / (a_global_scale * b_global_scale)
68
+ # ops.scaled_fp4_quant returns swizzled scales, while weights
69
+ # from checkpoints are in linear scales.
70
a_fp4, a_scale_interleaved = ops.scaled_fp4_quant(a_dtype, a_global_scale)
71
b_fp4, b_scale_interleaved = ops.scaled_fp4_quant(b_dtype, b_global_scale)
72
73
+ # get_ref_results unswizzles the scales internally.
74
expected_out = get_ref_results(a_fp4, b_fp4, a_scale_interleaved,
75
b_scale_interleaved, a_global_scale,
76
b_global_scale, m, n, dtype, block_size,
vllm/envs.py
+7
@@ -1101,6 +1101,12 @@ environment_variables: dict[str, Callable[[], Any]] = {
1101
"VLLM_USE_TRTLLM_ATTENTION":
1102
lambda: os.getenv("VLLM_USE_TRTLLM_ATTENTION", None),
1103
1104
+ # If set to 1, force the use of TRTLLM FP4 GEMM backend in flashinfer.
1105
+ # Otherwise, uses the first available of: flashinfer cutlass GEMM,
1106
+ # vllm cutlass GEMM, marlin GEMM.
1107
+ "VLLM_USE_TRTLLM_FP4_GEMM":
1108
+ lambda: bool(int(os.getenv("VLLM_USE_TRTLLM_FP4_GEMM", "0"))),
1109
+
1110
# Controls garbage collection during CUDA graph capture.
1111
# If set to 0 (default), enables GC freezing to speed up capture time.
1112
# If set to 1, allows GC to run during capture.
@@ -1208,6 +1214,7 @@ def compute_hash() -> str:
1214
"VLLM_DP_SIZE",
1215
"VLLM_USE_STANDALONE_COMPILE",
1216
"VLLM_FUSED_MOE_CHUNK_SIZE",
1217
+ "VLLM_USE_TRTLLM_FP4_GEMM",
1218
]
1219
for key in environment_variables_to_hash:
1220
if key in environment_variables:
vllm/model_executor/layers/quantization/compressed_tensors/schemes/compressed_tensors_w4a4_nvfp4.py
+49
-15
@@ -15,6 +15,7 @@ from vllm.model_executor.layers.quantization.utils.nvfp4_emulation_utils import
15
from vllm.model_executor.parameter import (GroupQuantScaleParameter,
16
ModelWeightParameter,
17
PerTensorScaleParameter)
18
+from vllm.utils.flashinfer import flashinfer_scaled_fp4_mm, has_flashinfer
19
20
logger = init_logger(__name__)
21
@@ -24,6 +25,13 @@ __all__ = ["CompressedTensorsW4A4Fp4"]
25
class CompressedTensorsW4A4Fp4(CompressedTensorsScheme):
26
27
def __init__(self):
28
+ if envs.VLLM_USE_TRTLLM_FP4_GEMM:
29
+ assert has_flashinfer(), "TRTLLM FP4 GEMM requires FlashInfer"
30
+ self.backend = "flashinfer-trtllm"
31
+ elif has_flashinfer():
32
+ self.backend = "flashinfer-cutlass"
33
+ else:
34
+ self.backend = "cutlass"
35
self.group_size = 16
36
37
@classmethod
@@ -108,16 +116,36 @@ class CompressedTensorsW4A4Fp4(CompressedTensorsScheme):
116
layer.weight_global_scale.max().to(torch.float32),
117
requires_grad=False)
118
111
- swizzled_weight_scale = self.swizzle_blockscale(layer.weight_scale)
112
- layer.weight_scale_swizzled = Parameter(swizzled_weight_scale,
113
- requires_grad=False)
114
-
115
- # required by cutlass kernel; need Parameter, not ModelWeightParameter
116
- layer.weight = Parameter(layer.weight_packed.data, requires_grad=False)
117
-
118
- layer.alpha = Parameter(layer.input_global_scale *
119
- layer.weight_global_scale,
120
- requires_grad=False)
119
+ if self.backend == "flashinfer-trtllm":
120
+ # FlashInfer TRTLLM FP4 GEMM requires a different weight layout.
121
+ # FlashInfer provides nvfp4_quantize to quantize + shuffle the
122
+ # layout but we use our own quantization so we have to call
123
+ # shuffles ourselves.
124
+ from flashinfer import shuffle_matrix_a, shuffle_matrix_sf_a
125
+
126
+ weight = layer.weight_packed.data
127
+ weight_scale = layer.weight_scale.data
128
+
129
+ epilogue_tile_m = 128
130
+ weight = shuffle_matrix_a(weight.view(torch.uint8),
131
+ epilogue_tile_m)
132
+ weight_scale = (shuffle_matrix_sf_a(weight_scale.view(
133
+ torch.uint8), epilogue_tile_m).reshape(
134
+ weight_scale.shape).view(torch.float8_e4m3fn))
135
+
136
+ layer.weight_scale_swizzled = Parameter(weight_scale,
137
+ requires_grad=False)
138
+ layer.weight_packed = Parameter(weight, requires_grad=False)
139
+ else:
140
+ swizzled_weight_scale = self.swizzle_blockscale(layer.weight_scale)
141
+ layer.weight_scale_swizzled = Parameter(swizzled_weight_scale,
142
+ requires_grad=False)
143
+ layer.weight_packed = Parameter(layer.weight_packed.data,
144
+ requires_grad=False)
145
+
146
+ layer.alpha = Parameter(
147
+ 1 / (layer.input_global_scale * layer.weight_global_scale),
148
+ requires_grad=False)
149
150
def apply_weights(self,
151
layer: torch.nn.Module,
@@ -128,7 +156,7 @@ class CompressedTensorsW4A4Fp4(CompressedTensorsScheme):
156
out = run_nvfp4_emulations(
157
x=x,
158
input_global_scale=layer.input_global_scale,
131
- weight=layer.weight,
159
+ weight=layer.weight_packed,
160
weight_scale_swizzled=layer.weight_scale_swizzled,
161
weight_global_scale=layer.weight_global_scale)
162
if bias is not None:
@@ -136,14 +164,20 @@ class CompressedTensorsW4A4Fp4(CompressedTensorsScheme):
164
return out
165
166
output_dtype = x.dtype
139
- output_shape = [x.shape[0], layer.weight.shape[0]]
167
+ output_shape = [x.shape[0], layer.weight_packed.shape[0]]
168
169
# quantize BF16 or FP16 to (FP4 and interleaved block scale)
170
x_fp4, x_blockscale = scaled_fp4_quant(x, layer.input_global_scale)
171
144
- out = cutlass_scaled_fp4_mm(x_fp4, layer.weight, x_blockscale,
145
- layer.weight_scale_swizzled,
146
- 1 / layer.alpha, output_dtype)
172
+ mm_args = (x_fp4, layer.weight_packed, x_blockscale,
173
+ layer.weight_scale_swizzled, layer.alpha, output_dtype)
174
+ if self.backend == "flashinfer-trtllm":
175
+ out = flashinfer_scaled_fp4_mm(*mm_args, backend="trtllm")
176
+ elif self.backend == "flashinfer-cutlass":
177
+ out = flashinfer_scaled_fp4_mm(*mm_args, backend="cutlass")
178
+ else:
179
+ out = cutlass_scaled_fp4_mm(*mm_args)
180
+
181
if bias is not None:
182
out = out + bias
183
return out.view(*output_shape)
vllm/model_executor/layers/quantization/modelopt.py
+61
-23
@@ -38,7 +38,8 @@ from vllm.model_executor.parameter import (ModelWeightParameter,
38
PerTensorScaleParameter)
39
from vllm.scalar_type import scalar_types
40
from vllm.utils import next_power_of_2
41
-from vllm.utils.flashinfer import has_flashinfer_moe
41
+from vllm.utils.flashinfer import (flashinfer_scaled_fp4_mm, has_flashinfer,
42
+ has_flashinfer_moe)
43
44
logger = init_logger(__name__)
45
@@ -724,16 +725,20 @@ class ModelOptNvFp4LinearMethod(LinearMethodBase):
725
726
def __init__(self, quant_config: ModelOptNvFp4Config) -> None:
727
self.quant_config = quant_config
727
- self.cutlass_nvfp4_supported = cutlass_fp4_supported()
728
- self.use_marlin = False
728
730
- if not self.cutlass_nvfp4_supported:
731
- if is_fp4_marlin_supported():
732
- self.use_marlin = True
733
- else:
734
- raise ValueError("Current platform does not support NVFP4"
735
- " quantization. Please use Blackwell and"
736
- " above.")
729
+ if envs.VLLM_USE_TRTLLM_FP4_GEMM:
730
+ assert has_flashinfer(), "TRTLLM FP4 GEMM requires FlashInfer"
731
+ self.backend = "flashinfer-trtllm"
732
+ elif has_flashinfer():
733
+ self.backend = "flashinfer-cutlass"
734
+ elif cutlass_fp4_supported():
735
+ self.backend = "cutlass"
736
+ elif is_fp4_marlin_supported():
737
+ self.backend = "marlin"
738
+ else:
739
+ raise ValueError("Current platform does not support NVFP4"
740
+ " quantization. Please use Blackwell and"
741
+ " above.")
742
743
def create_weights(
744
self,
@@ -815,17 +820,38 @@ class ModelOptNvFp4LinearMethod(LinearMethodBase):
820
# block_size = 16;
821
assert (layer.weight_scale.dtype == torch.float8_e4m3fn), (
822
"Weight Block scale must be represented as FP8-E4M3")
818
- swizzled_weight_scale = swizzle_blockscale(layer.weight_scale)
823
820
- layer.weight_scale_swizzled = Parameter(swizzled_weight_scale,
821
- requires_grad=False)
822
- layer.weight = Parameter(layer.weight.data, requires_grad=False)
824
+ if self.backend == "flashinfer-trtllm":
825
+ # FlashInfer TRTLLM FP4 GEMM requires a different weight layout.
826
+ # FlashInfer provides nvfp4_quantize to quantize + shuffle the
827
+ # layout but we use our own quantization so we have to call
828
+ # shuffles ourselves.
829
+ from flashinfer import shuffle_matrix_a, shuffle_matrix_sf_a
830
+
831
+ weight = layer.weight.data
832
+ weight_scale = layer.weight_scale.data
833
+
834
+ epilogue_tile_m = 128
835
+ weight = shuffle_matrix_a(weight.view(torch.uint8),
836
+ epilogue_tile_m)
837
+ weight_scale = (shuffle_matrix_sf_a(weight_scale.view(
838
+ torch.uint8), epilogue_tile_m).reshape(
839
+ weight_scale.shape).view(torch.float8_e4m3fn))
840
+
841
+ layer.weight_scale_swizzled = Parameter(weight_scale,
842
+ requires_grad=False)
843
+ layer.weight = Parameter(weight, requires_grad=False)
844
+ else:
845
+ swizzled_weight_scale = swizzle_blockscale(layer.weight_scale)
846
+ layer.weight_scale_swizzled = Parameter(swizzled_weight_scale,
847
+ requires_grad=False)
848
+ layer.weight = Parameter(layer.weight.data, requires_grad=False)
849
824
- if self.use_marlin:
825
- prepare_fp4_layer_for_marlin(layer)
826
- del layer.alpha
827
- del layer.input_scale
828
- del layer.weight_scale_swizzled
850
+ if self.backend == "marlin":
851
+ prepare_fp4_layer_for_marlin(layer)
852
+ del layer.alpha
853
+ del layer.input_scale
854
+ del layer.weight_scale_swizzled
855
856
def apply(
857
self,
@@ -833,7 +859,7 @@ class ModelOptNvFp4LinearMethod(LinearMethodBase):
859
x: torch.Tensor,
860
bias: Optional[torch.Tensor] = None,
861
) -> torch.Tensor:
836
- if self.use_marlin:
862
+ if self.backend == "marlin":
863
return apply_fp4_marlin_linear(
864
input=x,
865
weight=layer.weight,
@@ -859,9 +885,21 @@ class ModelOptNvFp4LinearMethod(LinearMethodBase):
885
assert (layer.weight_scale_swizzled.dtype == torch.float8_e4m3fn)
886
assert (layer.alpha.dtype == torch.float32)
887
862
- out = cutlass_scaled_fp4_mm(x_fp4, layer.weight, x_blockscale,
863
- layer.weight_scale_swizzled, layer.alpha,
864
- output_dtype)
888
+ mm_args = (
889
+ x_fp4,
890
+ layer.weight,
891
+ x_blockscale,
892
+ layer.weight_scale_swizzled,
893
+ layer.alpha,
894
+ output_dtype,
895
+ )
896
+ if self.backend == "flashinfer-trtllm":
897
+ out = flashinfer_scaled_fp4_mm(*mm_args, backend="trtllm")
898
+ elif self.backend == "flashinfer-cutlass":
899
+ out = flashinfer_scaled_fp4_mm(*mm_args, backend="cutlass")
900
+ else:
901
+ out = cutlass_scaled_fp4_mm(*mm_args)
902
+
903
if bias is not None:
904
out = out + bias
905
return out.view(*output_shape)
vllm/model_executor/warmup/kernel_warmup.py
+38
-1
@@ -5,16 +5,53 @@ Warmup kernels used during model execution.
5
This is useful specifically for JIT'ed kernels as we don't want JIT'ing to
6
happen during model execution.
7
"""
8
+from typing import TYPE_CHECKING
9
+
10
import torch
11
12
import vllm.envs as envs
13
from vllm.model_executor.warmup.deep_gemm_warmup import deep_gemm_warmup
14
+from vllm.platforms import current_platform
15
from vllm.utils.deep_gemm import is_deep_gemm_supported
16
+from vllm.utils.flashinfer import has_flashinfer
17
+
18
+if TYPE_CHECKING:
19
+ from vllm.v1.worker.gpu_model_runner import GPUModelRunner
20
+ from vllm.v1.worker.gpu_worker import Worker
21
22
15
-def kernel_warmup(model: torch.nn.Module, max_tokens: int):
23
+def kernel_warmup(worker: "Worker"):
24
+ # Deep GEMM warmup
25
do_deep_gemm_warmup = (envs.VLLM_USE_DEEP_GEMM
26
and is_deep_gemm_supported()
27
and not envs.VLLM_SKIP_DEEP_GEMM_WARMUP)
28
if do_deep_gemm_warmup:
29
+ model = worker.get_model()
30
+ max_tokens = worker.scheduler_config.max_num_batched_tokens
31
deep_gemm_warmup(model, max_tokens)
32
+
33
+ # FlashInfer autotune for Blackwell (SM 10.0) GPUs
34
+ if has_flashinfer() and current_platform.is_device_capability(100):
35
+ flashinfer_autotune(worker.model_runner)
36
+
37
+
38
+def flashinfer_autotune(runner: "GPUModelRunner") -> None:
39
+ """
40
+ Autotune FlashInfer operations.
41
+ FlashInfer have many implementations for the same operation,
42
+ autotuning runs benchmarks for each implementation and stores
43
+ the results. The results are cached transparently and
44
+ future calls to FlashInfer will use the best implementation.
45
+ Without autotuning, FlashInfer will rely on heuristics, which may
46
+ be significantly slower.
47
+ """
48
+ from vllm.utils.flashinfer import autotune
49
+
50
+ with torch.inference_mode(), autotune():
51
+ # We skip EPLB here since we don't want to record dummy metrics
52
+ # When autotuning with number of tokens m, flashinfer will autotune
53
+ # operations for all number of tokens up to m.
54
+ # So we only need to run with the max number of tokens.
55
+ runner._dummy_run(runner.scheduler_config.max_num_batched_tokens,
56
+ skip_eplb=True,
57
+ is_profile=True)
vllm/utils/flashinfer.py
+71
@@ -14,6 +14,7 @@ import os
14
from typing import Any, Callable, NoReturn, Optional
15
16
import requests
17
+import torch
18
19
import vllm.envs as envs
20
from vllm.logger import init_logger
@@ -193,6 +194,75 @@ def use_trtllm_attention(
194
return use_trtllm
195
196
197
+if has_flashinfer():
198
+
199
+ @torch.library.custom_op(
200
+ "vllm::flashinfer_mm_fp4",
201
+ mutates_args=[],
202
+ device_types="cuda",
203
+ )
204
+ def flashinfer_mm_fp4(
205
+ A: torch.Tensor,
206
+ B: torch.Tensor,
207
+ A_scale: torch.Tensor,
208
+ B_scale: torch.Tensor,
209
+ g_scale: torch.Tensor,
210
+ dtype: torch.dtype,
211
+ backend: str,
212
+ ) -> torch.Tensor:
213
+ from flashinfer import mm_fp4 as flashinfer_mm_fp4_
214
+ return flashinfer_mm_fp4_(A,
215
+ B,
216
+ A_scale,
217
+ B_scale,
218
+ g_scale,
219
+ dtype,
220
+ block_size=16,
221
+ backend=backend)
222
+
223
+ @torch.library.register_fake("vllm::flashinfer_mm_fp4", )
224
+ def flashinfer_mm_fp4_fake(
225
+ A: torch.Tensor,
226
+ B: torch.Tensor,
227
+ A_scale: torch.Tensor,
228
+ B_scale: torch.Tensor,
229
+ g_scale: torch.Tensor,
230
+ dtype: torch.dtype,
231
+ backend: str,
232
+ ) -> torch.Tensor:
233
+ return torch.empty(A.shape[0],
234
+ B.shape[1],
235
+ dtype=dtype,
236
+ device=A.device)
237
+
238
+
239
+def flashinfer_scaled_fp4_mm(a: torch.Tensor, b: torch.Tensor,
240
+ block_scale_a: torch.Tensor,
241
+ block_scale_b: torch.Tensor, alpha: torch.Tensor,
242
+ out_dtype: torch.dtype,
243
+ backend: str) -> torch.Tensor:
244
+ assert a.ndim == 2 and b.ndim == 2
245
+ assert block_scale_a.ndim == 2 and block_scale_b.ndim == 2
246
+ assert a.stride(-1) == 1 and b.stride(-1) == 1
247
+ assert a.shape[1] == b.shape[1]
248
+ assert block_scale_a.shape[1] == a.shape[1] // 8
249
+ assert block_scale_b.shape[1] == b.shape[1] // 8
250
+
251
+ if backend == "cutlass":
252
+ block_scale_a = block_scale_a.view(torch.uint8)
253
+ block_scale_b = block_scale_b.view(torch.uint8)
254
+
255
+ return flashinfer_mm_fp4(
256
+ a,
257
+ b.t(),
258
+ block_scale_a,
259
+ block_scale_b.t(),
260
+ alpha,
261
+ out_dtype,
262
+ backend=backend,
263
+ )
264
+
265
+
266
__all__ = [
267
"has_flashinfer",
268
"flashinfer_trtllm_fp8_block_scale_moe",
@@ -205,4 +275,5 @@ __all__ = [
275
"has_flashinfer_cutlass_fused_moe",
276
"has_nvidia_artifactory",
277
"use_trtllm_attention",
278
+ "flashinfer_scaled_fp4_mm",
279
]
vllm/v1/worker/gpu_worker.py
+2
-2
@@ -310,6 +310,7 @@ class Worker(WorkerBase):
310
for size in sorted(warmup_sizes, reverse=True):
311
logger.info("Compile and warming up model for size %d", size)
312
self.model_runner._dummy_run(size, skip_eplb=True)
313
+
314
if not self.model_config.enforce_eager:
315
self.model_runner.capture_model()
316
@@ -340,8 +341,7 @@ class Worker(WorkerBase):
341
hidden_states=last_hidden_states)
342
343
# Warmup kernels used during model execution
343
- kernel_warmup(self.get_model(),
344
- max_tokens=self.scheduler_config.max_num_batched_tokens)
344
+ kernel_warmup(self)
345
346
# Reset the seed to ensure that the random state is not affected by
347
# the model initialization and profiling.