@tduncan / Nvidia-Optimization-Stack-VLLM Mirror · read-only / commits / a88b3be7c4
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 8, 03:45 UTC

[Bugfix] Fix quant RMS norm fusion for quantization with TMA-aligned scales (#33255)

Signed-off-by: ElizaWszola <ewszola@redhat.com> Co-authored-by: Luka Govedič <ProExpertProg@users.noreply.github.com>

ElizaWszola committed Feb 18, 2026 at 08:35 UTC a88b3be7c4c494c896f4a88ca8e6bfc1083625e0
12 files changed +235 -76
csrc/ops.h
+3 -1
@@ -315,7 +315,9 @@ void silu_and_mul_scaled_fp4_experts_quant(
315 void per_token_group_quant_fp8(const torch::Tensor& input,
316 torch::Tensor& output_q, torch::Tensor& output_s,
317 int64_t group_size, double eps, double fp8_min,
318 - double fp8_max, bool scale_ue8m0);
318 + double fp8_max, bool scale_ue8m0,
319 + bool dummy_is_scale_transposed,
320 + bool dummy_is_tma_aligned);
321
322 void per_token_group_quant_int8(const torch::Tensor& input,
323 torch::Tensor& output_q,
csrc/quantization/fused_kernels/fused_layernorm_dynamic_per_token_quant.cu
+12 -4
@@ -97,7 +97,7 @@ __global__ void rms_norm_per_block_quant_kernel(
97 scalar_t const* __restrict__ input, // [..., hidden_size]
98 scalar_t const* __restrict__ weight, // [hidden_size]
99 float const* scale_ub, float const var_epsilon, int32_t const hidden_size,
100 - scalar_t* __restrict__ residual = nullptr) {
100 + scalar_t* __restrict__ residual = nullptr, int64_t outer_scale_stride = 1) {
101 float rms;
102 // Compute RMS
103 // Always able to vectorize due to constraints on hidden_size
@@ -108,7 +108,8 @@ __global__ void rms_norm_per_block_quant_kernel(
108 // Always able to vectorize due to constraints on hidden_size and group_size
109 vllm::vectorized::compute_dynamic_per_token_scales<
110 scalar_t, scalar_out_t, has_residual, is_scale_transposed, group_size>(
111 - nullptr, scales, input, weight, rms, scale_ub, hidden_size, residual);
111 + nullptr, scales, input, weight, rms, scale_ub, hidden_size, residual,
112 + outer_scale_stride);
113
114 // RMS Norm + Quant
115 // Always able to vectorize due to constraints on hidden_size
@@ -119,7 +120,8 @@ __global__ void rms_norm_per_block_quant_kernel(
120 vllm::vectorized::norm_and_quant<
121 scalar_t, scalar_out_t, std::is_same_v<scalar_out_t, int8_t>,
122 has_residual, is_scale_transposed, group_size>(
122 - out, input, weight, rms, scales, hidden_size, residual);
123 + out, input, weight, rms, scales, hidden_size, residual,
124 + outer_scale_stride);
125 }
126
127 } // namespace vllm
@@ -225,7 +227,8 @@ void rms_norm_per_block_quant_dispatch(
227 : nullptr,
228 var_epsilon, hidden_size,
229 has_residual ? residual->data_ptr<scalar_in_t>()
228 - : nullptr);
230 + : nullptr,
231 + scales.stride(1));
232 });
233 });
234 });
@@ -257,6 +260,11 @@ void rms_norm_per_block_quant(torch::Tensor& out, torch::Tensor const& input,
260 TORCH_CHECK(group_size == 128 || group_size == 64,
261 "Unsupported group size: ", group_size);
262
263 + if (scales.stride(1) > 1) {
264 + TORCH_CHECK(is_scale_transposed,
265 + "Outer scale stride must be 1 when scales are not transposed");
266 + }
267 +
268 rms_norm_per_block_quant_dispatch(out, input, weight, scales, group_size,
269 var_epsilon, scale_ub, residual,
270 is_scale_transposed);
csrc/quantization/fused_kernels/layernorm_utils.cuh
+22 -15
@@ -74,7 +74,7 @@ __device__ void compute_dynamic_per_token_scales(
74 scalar_t const* __restrict__ input, scalar_t const* __restrict__ weight,
75 float const rms, float const* __restrict__ scale_ub,
76 int32_t const hidden_size, scalar_t const* __restrict__ residual = nullptr,
77 - int32_t const group_size = 0) {
77 + int32_t const group_size = 0, int64_t outer_scale_stride = 1) {
78 float block_absmax_val_maybe = 0.0f;
79 constexpr scalar_out_t qmax{quant_type_max_v<scalar_out_t>};
80 __syncthreads();
@@ -133,7 +133,9 @@ __device__ void compute_dynamic_per_token_scales(
133 scale = max(scale / qmax, min_scaling_factor<scalar_out_t>::val());
134 // Global output store
135 if constexpr (is_scale_transposed) {
136 - all_token_scales[(threadIdx.x / threads_per_group) * gridDim.x +
136 + int64_t const scale_rows = (gridDim.x + outer_scale_stride - 1) /
137 + outer_scale_stride * outer_scale_stride;
138 + all_token_scales[(threadIdx.x / threads_per_group) * scale_rows +
139 blockIdx.x] = scale;
140 } else {
141 all_token_scales[blockIdx.x * num_groups +
@@ -180,13 +182,11 @@ __device__ void compute_dynamic_per_token_scales(
182
183 template <typename scalar_t, typename scalar_out_t, bool is_scale_inverted,
184 bool has_residual = false, bool is_scale_transposed = false>
183 -__device__ void norm_and_quant(scalar_out_t* __restrict__ output,
184 - scalar_t const* __restrict__ input,
185 - scalar_t const* __restrict__ weight,
186 - float const rms, float* const scale,
187 - int32_t const hidden_size,
188 - scalar_t* __restrict__ residual = nullptr,
189 - int32_t const group_size = 0) {
185 +__device__ void norm_and_quant(
186 + scalar_out_t* __restrict__ output, scalar_t const* __restrict__ input,
187 + scalar_t const* __restrict__ weight, float const rms, float* const scale,
188 + int32_t const hidden_size, scalar_t* __restrict__ residual = nullptr,
189 + int32_t const group_size = 0, int64_t outer_scale_stride = 1) {
190 int64_t const token_offset = blockIdx.x * static_cast<int64_t>(hidden_size);
191
192 for (auto i = threadIdx.x; i < hidden_size; i += blockDim.x) {
@@ -202,7 +202,9 @@ __device__ void norm_and_quant(scalar_out_t* __restrict__ output,
202 int64_t scale_idx = 0;
203 if (group_size > 0) {
204 if constexpr (is_scale_transposed) {
205 - scale_idx = (i / group_size) * gridDim.x + blockIdx.x;
205 + int64_t const scale_rows = (gridDim.x + outer_scale_stride - 1) /
206 + outer_scale_stride * outer_scale_stride;
207 + scale_idx = (i / group_size) * scale_rows + blockIdx.x;
208 } else {
209 scale_idx = blockIdx.x * (hidden_size / group_size) + i / group_size;
210 }
@@ -286,8 +288,8 @@ __device__ void compute_dynamic_per_token_scales(
288 float* __restrict__ token_scale, float* __restrict__ all_token_scales,
289 scalar_t const* __restrict__ input, scalar_t const* __restrict__ weight,
290 float const rms, float const* __restrict__ scale_ub,
289 - int32_t const hidden_size,
290 - scalar_t const* __restrict__ residual = nullptr) {
291 + int32_t const hidden_size, scalar_t const* __restrict__ residual = nullptr,
292 + int64_t outer_scale_stride = 1) {
293 constexpr scalar_out_t qmax{quant_type_max_v<scalar_out_t>};
294
295 const int VEC_SIZE = 4;
@@ -382,7 +384,9 @@ __device__ void compute_dynamic_per_token_scales(
384 scale = max(scale / qmax, min_scaling_factor<scalar_out_t>::val());
385 // Global output store
386 if constexpr (is_scale_transposed) {
385 - all_token_scales[(threadIdx.x / threads_per_group) * gridDim.x +
387 + int64_t const scale_rows = (gridDim.x + outer_scale_stride - 1) /
388 + outer_scale_stride * outer_scale_stride;
389 + all_token_scales[(threadIdx.x / threads_per_group) * scale_rows +
390 blockIdx.x] = scale;
391 } else {
392 all_token_scales[blockIdx.x * num_groups +
@@ -463,7 +467,8 @@ __device__ void norm_and_quant(scalar_out_t* __restrict__ output,
467 scalar_t const* __restrict__ weight,
468 float const rms, float* const scale,
469 int32_t const hidden_size,
466 - scalar_t* __restrict__ residual = nullptr) {
470 + scalar_t* __restrict__ residual = nullptr,
471 + int64_t outer_scale_stride = 1) {
472 int64_t const token_offset = blockIdx.x * static_cast<int64_t>(hidden_size);
473
474 // Vectorized input/output/weight/residual to better utilize memory bandwidth.
@@ -516,7 +521,9 @@ __device__ void norm_and_quant(scalar_out_t* __restrict__ output,
521 int64_t const num_groups = hidden_size / group_size;
522 int64_t scale_idx = 0;
523 if constexpr (is_scale_transposed) {
519 - scale_idx = (i * VEC_SIZE / group_size) * gridDim.x + blockIdx.x;
524 + int64_t const scale_rows = (gridDim.x + outer_scale_stride - 1) /
525 + outer_scale_stride * outer_scale_stride;
526 + scale_idx = (i * VEC_SIZE / group_size) * scale_rows + blockIdx.x;
527 } else {
528 scale_idx = blockIdx.x * num_groups + i * VEC_SIZE / group_size;
529 }
csrc/quantization/w8a8/fp8/per_token_group_quant.cu
+3 -1
@@ -379,7 +379,9 @@ void per_token_group_quant_8bit_packed(const torch::Tensor& input,
379 void per_token_group_quant_fp8(const torch::Tensor& input,
380 torch::Tensor& output_q, torch::Tensor& output_s,
381 int64_t group_size, double eps, double fp8_min,
382 - double fp8_max, bool scale_ue8m0) {
382 + double fp8_max, bool scale_ue8m0,
383 + bool dummy_is_scale_transposed = false,
384 + bool dummy_is_tma_aligned = false) {
385 per_token_group_quant_8bit(input, output_q, output_s, group_size, eps,
386 fp8_min, fp8_max, scale_ue8m0);
387 }
\ No newline at end of file
csrc/torch_bindings.cpp
+3 -1
@@ -643,11 +643,13 @@ TORCH_LIBRARY_EXPAND(TORCH_EXTENSION_NAME, ops) {
643
644 #ifndef USE_ROCM
645 // Compute per-token-group FP8 quantized tensor and scaling factor.
646 + // The dummy arguments are here so we can correctly fuse with RMSNorm.
647 ops.def(
648 "per_token_group_fp8_quant(Tensor input, Tensor! output_q, Tensor! "
649 "output_s, "
650 "int group_size, float eps, float fp8_min, float fp8_max, bool "
650 - "scale_ue8m0) -> ()");
651 + "scale_ue8m0, bool dummy_is_scale_transposed, bool dummy_is_tma_aligned "
652 + ") -> ()");
653 ops.impl("per_token_group_fp8_quant", torch::kCUDA,
654 &per_token_group_quant_fp8);
655
tests/compile/fusions_e2e/test_tp1_quant.py
+2 -4
@@ -50,10 +50,9 @@ def test_tp1_fp8_fusions(
50 run_e2e_fusion_test,
51 monkeypatch,
52 ):
53 - if use_deepgemm:
54 - # TODO(luka/eliza) DeepGEMM uses different quants, matching not supported
53 + if use_deepgemm and is_blackwell():
54 + # TODO(luka) DeepGEMM uses different quants, matching not supported
55 # - on Blackwell, uses a special quant fp8, currently not supported
56 - # - on Hopper, tma-aligned scales inhibit matching (fix WIP)
56 pytest.skip("DeepGEMM & quant matching not currently supported")
57
58 matches = matches_fn(n_layers)
@@ -66,7 +65,6 @@ def test_tp1_fp8_fusions(
65 model_kwargs["hf_overrides"] = hf_overrides(n_layers)
66 model_kwargs["load_format"] = "dummy"
67 model_kwargs["max_model_len"] = 1024
69 -
68 compilation_config = dict(
69 use_inductor_graph_partition=inductor_graph_partition,
70 custom_ops=custom_ops.split(","),
tests/kernels/core/test_fused_quant_layernorm.py
+36 -5
@@ -2,6 +2,8 @@
2 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
4
5 +import itertools
6 +
7 import pytest
8 import torch
9
@@ -21,7 +23,7 @@ QUANT_DTYPES = [torch.int8, current_platform.fp8_dtype()]
23 VEC_HIDDEN_SIZES = [1024, 1025, 1027, 1029]
24 # Avoid combinatorial explosion with full Cartesian product
25 NUM_TOKENS_HIDDEN_SIZES = [
24 - *[(1, i) for i in [1, 64, *VEC_HIDDEN_SIZES, 5120, 5137]],
26 + *[(1, i) for i in [1, 64, 128, *VEC_HIDDEN_SIZES, 5120, 5137]],
27 *[(2048, i) for i in [1, 64, *VEC_HIDDEN_SIZES, 5137]],
28 *[(4096, i) for i in [1, 64, 5137]],
29 ]
@@ -29,6 +31,7 @@ NUM_TOKENS_HIDDEN_SIZES = [
31 ADD_RESIDUAL = [False, True]
32 SCALE_UBS = [True, False]
33 GROUP_SIZES = [None, [1, 64], [1, 128]]
34 +TMA_ALIGNMENTS = [0, 4]
35 SEEDS = [0]
36 CUDA_DEVICES = [f"cuda:{i}" for i in range(1 if torch.cuda.device_count() == 1 else 2)]
37
@@ -110,12 +113,21 @@ def ops_dynamic_per_token_or_block_quant(
113 residual: torch.Tensor | None,
114 scale_ub: torch.Tensor | None,
115 group_size: list[int] | None,
116 + tma_alignment: int,
117 ) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor | None]:
118 if residual is not None:
119 residual = residual.clone()
120 if group_size is not None:
121 out, scales = ops.rms_norm_per_block_quant(
118 - x, weight, EPS, quant_dtype, group_size, scale_ub, residual, True
122 + x,
123 + weight,
124 + EPS,
125 + quant_dtype,
126 + group_size,
127 + scale_ub,
128 + residual,
129 + True,
130 + tma_alignment,
131 )
132 scales = scales.contiguous()
133 else:
@@ -132,9 +144,10 @@ def ops_impl(
144 residual: torch.Tensor | None,
145 scale_ub: torch.Tensor | None,
146 group_size: list[int] | None,
147 + tma_alignment: int,
148 ) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor | None]:
149 return ops_dynamic_per_token_or_block_quant(
137 - weight, x, quant_dtype, residual, scale_ub, group_size
150 + weight, x, quant_dtype, residual, scale_ub, group_size, tma_alignment
151 )
152
153
@@ -143,7 +156,10 @@ def ops_impl(
156 @pytest.mark.parametrize("has_scale_ub", SCALE_UBS)
157 @pytest.mark.parametrize("dtype", DTYPES)
158 @pytest.mark.parametrize("quant_dtype", QUANT_DTYPES)
146 -@pytest.mark.parametrize("group_size", GROUP_SIZES)
159 +@pytest.mark.parametrize(
160 + "group_size, tma_alignment",
161 + [(None, 0), *itertools.product(GROUP_SIZES, TMA_ALIGNMENTS)],
162 +)
163 @pytest.mark.parametrize("seed", SEEDS)
164 @pytest.mark.parametrize("device", CUDA_DEVICES)
165 @torch.inference_mode()
@@ -156,6 +172,7 @@ def test_rms_norm(
172 dtype: torch.dtype,
173 quant_dtype: torch.dtype,
174 group_size: list[int] | None,
175 + tma_alignment: int,
176 seed: int,
177 device: str,
178 ) -> None:
@@ -173,6 +190,20 @@ def test_rms_norm(
190 # blockwise baseline doesn't support scale_ub
191 return
192
193 + if (
194 + group_size is None or quant_dtype != current_platform.fp8_dtype()
195 + ) and tma_alignment != 0:
196 + # TMA alignment is only supported for groupwise fp8 kernels
197 + return
198 +
199 + if (
200 + group_size is not None
201 + and tma_alignment != 0
202 + and hidden_size // group_size[1] % tma_alignment == 0
203 + ):
204 + # Skip tests where TMA alignment doesn't create extra padding to save time
205 + return
206 +
207 if has_scale_ub and quant_dtype != current_platform.fp8_dtype():
208 # skip
209 return
@@ -196,7 +227,7 @@ def test_rms_norm(
227 layer, x, quant_dtype, residual, scale_ub, group_size
228 )
229 ops_out, ops_scales, ops_residual = ops_impl(
199 - layer.weight, x, quant_dtype, residual, scale_ub, group_size
230 + layer.weight, x, quant_dtype, residual, scale_ub, group_size, tma_alignment
231 )
232
233 assert ref_out.dtype == quant_dtype
vllm/_custom_ops.py
+24 -5
@@ -450,15 +450,30 @@ def rms_norm_per_block_quant(
450 scale_ub: torch.Tensor | None = None,
451 residual: torch.Tensor | None = None,
452 is_scale_transposed: bool = False,
453 + tma_alignment: int = 0,
454 ) -> tuple[torch.Tensor, torch.Tensor]:
455 assert len(group_size) == 2
456 output = torch.empty_like(input, dtype=quant_dtype)
457 if is_scale_transposed:
457 - scales = torch.empty(
458 - (input.shape[-1] // group_size[1], input.numel() // input.shape[-1]),
459 - device=input.device,
460 - dtype=torch.float32,
461 - ).transpose(0, 1)
458 + if tma_alignment == 0:
459 + scales = torch.empty(
460 + (input.shape[-1] // group_size[1], input.numel() // input.shape[-1]),
461 + device=input.device,
462 + dtype=torch.float32,
463 + ).transpose(0, 1)
464 + else:
465 + m = input.shape[-2]
466 + sf_k = input.shape[-1] // group_size[1]
467 + tma_aligned_m = (m + tma_alignment - 1) // tma_alignment * tma_alignment
468 + shape = input.shape[:-2] + (m, sf_k)
469 + stride = (
470 + (1, tma_aligned_m)
471 + if input.dim() == 2
472 + else (tma_aligned_m * sf_k, 1, tma_aligned_m)
473 + )
474 + scales = torch.empty_strided(
475 + shape, stride, device=input.device, dtype=torch.float32
476 + )
477 else:
478 scales = torch.empty(
479 (input.numel() // input.shape[-1], input.shape[-1] // group_size[1]),
@@ -466,6 +481,10 @@ def rms_norm_per_block_quant(
481 dtype=torch.float32,
482 )
483
484 + assert tma_alignment in [0, 4], "Expected TMA alignment 0 or 4, but got " + str(
485 + tma_alignment
486 + )
487 +
488 torch.ops._C.rms_norm_per_block_quant(
489 output,
490 input,
vllm/compilation/passes/fusion/matcher_utils.py
+10 -2
@@ -292,6 +292,7 @@ class MatcherQuantFP8(MatcherCustomOp):
292 has_col_major_scales: bool = False,
293 is_e8m0: bool = False,
294 match_rocm_aiter: bool = False,
295 + is_tma_aligned: bool = False,
296 ) -> None:
297 if enabled is None:
298 enabled = QuantFP8.enabled()
@@ -301,6 +302,7 @@ class MatcherQuantFP8(MatcherCustomOp):
302 self.has_col_major_scales = has_col_major_scales
303 self.is_e8m0 = is_e8m0
304 self.match_rocm_aiter = match_rocm_aiter
305 + self.is_tma_aligned = is_tma_aligned
306
307 if match_rocm_aiter:
308 assert not quant_key.scale.group_shape.is_per_tensor(), (
@@ -336,6 +338,7 @@ class MatcherQuantFP8(MatcherCustomOp):
338 quant_key.scale.group_shape,
339 column_major_scales=has_col_major_scales,
340 use_ue8m0=is_e8m0,
341 + tma_aligned_scales=self.is_tma_aligned,
342 compile_native=False,
343 )
344
@@ -367,8 +370,11 @@ class MatcherQuantFP8(MatcherCustomOp):
370 )
371
372 if self.quant_key.scale.group_shape.is_per_group():
370 - assert scale is None
371 - scale = self.make_scale(input, transposed=self.has_col_major_scales)
373 + # for tma_aligned, the scale must be passed to forward_custom
374 + # tma_aligned fusion then matches by custom op arguments
375 + if not self.is_tma_aligned:
376 + assert scale is None
377 + scale = self.make_scale(input, transposed=self.has_col_major_scales)
378
379 finfo = torch.finfo(self.quant_key.dtype)
380 fp8_min = finfo.min
@@ -384,6 +390,8 @@ class MatcherQuantFP8(MatcherCustomOp):
390 fp8_min=fp8_min,
391 fp8_max=fp8_max,
392 scale_ue8m0=self.is_e8m0,
393 + dummy_is_scale_transposed=self.has_col_major_scales,
394 + dummy_is_tma_aligned=self.is_tma_aligned,
395 )
396 return result, scale
397
vllm/compilation/passes/fusion/rms_quant_fusion.py
+109 -36
@@ -121,6 +121,7 @@ class RMSNormQuantPattern:
121 key: FusedRMSQuantKey,
122 has_col_major_scales: bool = False,
123 is_e8m0: bool = False,
124 + is_tma_aligned: bool = False,
125 ) -> None:
126 self.epsilon = epsilon
127 self.quant_dtype = key.quant.dtype
@@ -136,7 +137,10 @@ class RMSNormQuantPattern:
137 else MatcherFusedAddRMSNorm(epsilon)
138 )
139 self.quant_matcher = MatcherQuantFP8(
139 - key.quant, has_col_major_scales=has_col_major_scales, is_e8m0=is_e8m0
140 + key.quant,
141 + has_col_major_scales=has_col_major_scales,
142 + is_e8m0=is_e8m0,
143 + is_tma_aligned=is_tma_aligned,
144 )
145
146
@@ -262,8 +266,9 @@ class FusedAddRMSNormGroupQuantPattern(RMSNormQuantPattern):
266 quant_dtype: torch.dtype,
267 group_shape: GroupShape,
268 symmetric: bool = True,
265 - has_col_major_scales: bool = False,
269 is_e8m0: bool = False,
270 + has_col_major_scales: bool = True,
271 + is_tma_aligned: bool = True,
272 ) -> None:
273 scale = ScaleDesc(torch.float32, False, group_shape)
274 key = FusedRMSQuantKey(
@@ -271,29 +276,63 @@ class FusedAddRMSNormGroupQuantPattern(RMSNormQuantPattern):
276 quant=QuantKey(dtype=quant_dtype, scale=scale, symmetric=symmetric),
277 )
278 self.group_shape = group_shape
274 - self.has_col_major_scales = has_col_major_scales
279 self.is_e8m0 = is_e8m0
280 + self.has_col_major_scales = has_col_major_scales
281 + self.is_tma_aligned = is_tma_aligned
282 super().__init__(
277 - epsilon, key, has_col_major_scales=has_col_major_scales, is_e8m0=is_e8m0
283 + epsilon,
284 + key,
285 + has_col_major_scales=has_col_major_scales,
286 + is_e8m0=is_e8m0,
287 + is_tma_aligned=is_tma_aligned,
288 )
289
290 def register(self, pm_pass: PatternMatcherPass) -> None:
291 def pattern(
282 - input: torch.Tensor, weight: torch.Tensor, residual: torch.Tensor
292 + input: torch.Tensor,
293 + weight: torch.Tensor,
294 + residual: torch.Tensor,
295 + scale: torch.Tensor,
296 ) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
297 result_rms, residual = self.rmsnorm_matcher(input, weight, residual)
285 - result, scale = self.quant_matcher(result_rms)
298 + result = torch.empty(
299 + result_rms.shape,
300 + device=result_rms.device,
301 + dtype=self.quant_matcher.quant_key.dtype,
302 + )
303 + assert scale is not None
304 + finfo = torch.finfo(self.quant_matcher.quant_key.dtype)
305 + fp8_min = finfo.min
306 + fp8_max = finfo.max
307 +
308 + _, result, scale = auto_functionalized(
309 + self.quant_matcher.QUANT_OP,
310 + input=result_rms,
311 + output_q=result,
312 + output_s=scale,
313 + group_size=self.quant_matcher.quant_key.scale.group_shape[1],
314 + eps=1e-10,
315 + fp8_min=fp8_min,
316 + fp8_max=fp8_max,
317 + scale_ue8m0=self.quant_matcher.is_e8m0,
318 + dummy_is_scale_transposed=self.has_col_major_scales,
319 + dummy_is_tma_aligned=self.is_tma_aligned,
320 + )
321 +
322 return result, residual, scale
323
324 def replacement(
289 - input: torch.Tensor, weight: torch.Tensor, residual: torch.Tensor
325 + input: torch.Tensor,
326 + weight: torch.Tensor,
327 + residual: torch.Tensor,
328 + scale: torch.Tensor,
329 ) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
330 # In case we're matching native rms-norm, conversions might be
331 # optimized out. We convert here just to be safe.
332 input = input.to(dtype=self.model_dtype)
333
334 result = torch.empty_like(input, dtype=self.quant_dtype)
296 - scale = self.quant_matcher.make_scale(input, self.has_col_major_scales)
335 +
336 at = auto_functionalized(
337 self.FUSED_OP,
338 result=result,
@@ -310,10 +349,12 @@ class FusedAddRMSNormGroupQuantPattern(RMSNormQuantPattern):
349 # result, residual, scale
350 return at[1], at[3], at[2]
351
352 + scale = self.quant_matcher.empty_f32(1, 1)
353 +
354 pm.register_replacement(
355 pattern,
356 replacement,
316 - self.rmsnorm_matcher.inputs(),
357 + self.rmsnorm_matcher.inputs() + [scale],
358 pm.fwd_only,
359 pm_pass,
360 )
@@ -326,8 +367,9 @@ class RMSNormGroupQuantPattern(RMSNormQuantPattern):
367 quant_dtype: torch.dtype,
368 group_shape: GroupShape,
369 symmetric: bool = True,
329 - has_col_major_scales: bool = False,
370 is_e8m0: bool = False,
371 + has_col_major_scales: bool = True,
372 + is_tma_aligned: bool = True,
373 ) -> None:
374 scale = ScaleDesc(torch.float32, False, group_shape)
375 key = FusedRMSQuantKey(
@@ -335,29 +377,55 @@ class RMSNormGroupQuantPattern(RMSNormQuantPattern):
377 quant=QuantKey(dtype=quant_dtype, scale=scale, symmetric=symmetric),
378 )
379 self.group_shape = group_shape
380 + self.has_col_major_scales = has_col_major_scales
381 + self.is_tma_aligned = is_tma_aligned
382 super().__init__(
339 - epsilon, key, has_col_major_scales=has_col_major_scales, is_e8m0=is_e8m0
383 + epsilon,
384 + key,
385 + has_col_major_scales=self.has_col_major_scales,
386 + is_e8m0=is_e8m0,
387 + is_tma_aligned=is_tma_aligned,
388 )
389
390 def register(self, pm_pass: PatternMatcherPass) -> None:
391 def pattern(
344 - input: torch.Tensor, weight: torch.Tensor
392 + input: torch.Tensor, weight: torch.Tensor, scale: torch.Tensor
393 ) -> tuple[torch.Tensor, torch.Tensor]:
394 result_rms = self.rmsnorm_matcher(input, weight)
347 - result, scale = self.quant_matcher(result_rms)
395 + result = torch.empty(
396 + result_rms.shape,
397 + device=result_rms.device,
398 + dtype=self.quant_matcher.quant_key.dtype,
399 + )
400 + assert scale is not None
401 + finfo = torch.finfo(self.quant_matcher.quant_key.dtype)
402 + fp8_min = finfo.min
403 + fp8_max = finfo.max
404 +
405 + _, result, scale = auto_functionalized(
406 + self.quant_matcher.QUANT_OP,
407 + input=result_rms,
408 + output_q=result,
409 + output_s=scale,
410 + group_size=self.quant_matcher.quant_key.scale.group_shape[1],
411 + eps=1e-10,
412 + fp8_min=fp8_min,
413 + fp8_max=fp8_max,
414 + scale_ue8m0=self.quant_matcher.is_e8m0,
415 + dummy_is_scale_transposed=self.has_col_major_scales,
416 + dummy_is_tma_aligned=self.is_tma_aligned,
417 + )
418 +
419 return result, scale
420
421 def replacement(
351 - input: torch.Tensor, weight: torch.Tensor
422 + input: torch.Tensor, weight: torch.Tensor, scale: torch.Tensor
423 ) -> tuple[torch.Tensor, torch.Tensor]:
424 # In case we're matching native rms-norm, conversions might be
425 # optimized out. We convert here just to be safe.
426 input = input.to(dtype=self.model_dtype)
427
428 result = torch.empty_like(input, dtype=self.quant_dtype)
358 - scale = self.quant_matcher.make_scale(
359 - input, transposed=self.quant_matcher.has_col_major_scales
360 - )
429 at = auto_functionalized(
430 self.FUSED_OP,
431 result=result,
@@ -368,16 +436,18 @@ class RMSNormGroupQuantPattern(RMSNormQuantPattern):
436 scale_ub=None,
437 residual=None,
438 group_size=self.group_shape[1],
371 - is_scale_transposed=self.quant_matcher.has_col_major_scales,
439 + is_scale_transposed=self.has_col_major_scales,
440 )
441
442 # result, scale
443 return at[1], at[2]
444
445 + scale = self.quant_matcher.empty_f32(1, 1)
446 +
447 pm.register_replacement(
448 pattern,
449 replacement,
380 - self.rmsnorm_matcher.inputs(),
450 + self.rmsnorm_matcher.inputs() + [scale],
451 pm.fwd_only,
452 pm_pass,
453 )
@@ -532,23 +602,26 @@ class RMSNormQuantFusionPass(VllmPatternMatcherPass):
602 for group_shape in [GroupShape(1, 128), GroupShape(1, 64)]:
603 for has_col_major_scales in [True, False]:
604 for is_e8m0 in [True, False]:
535 - # Fuse fused_add_rms_norm + fp8 group quant
536 - FusedAddRMSNormGroupQuantPattern(
537 - epsilon,
538 - FP8_DTYPE,
539 - group_shape=group_shape,
540 - has_col_major_scales=has_col_major_scales,
541 - is_e8m0=is_e8m0,
542 - ).register(self.patterns)
543 -
544 - # Fuse rms_norm + fp8 group quant
545 - RMSNormGroupQuantPattern(
546 - epsilon,
547 - FP8_DTYPE,
548 - group_shape=group_shape,
549 - has_col_major_scales=has_col_major_scales,
550 - is_e8m0=is_e8m0,
551 - ).register(self.patterns)
605 + for is_tma_aligned in [False, True]:
606 + # Fuse fused_add_rms_norm + fp8 group quant
607 + FusedAddRMSNormGroupQuantPattern(
608 + epsilon,
609 + FP8_DTYPE,
610 + group_shape=group_shape,
611 + is_e8m0=is_e8m0,
612 + has_col_major_scales=has_col_major_scales,
613 + is_tma_aligned=is_tma_aligned,
614 + ).register(self.patterns)
615 +
616 + # Fuse rms_norm + fp8 group quant
617 + RMSNormGroupQuantPattern(
618 + epsilon,
619 + FP8_DTYPE,
620 + group_shape=group_shape,
621 + is_e8m0=is_e8m0,
622 + has_col_major_scales=has_col_major_scales,
623 + is_tma_aligned=is_tma_aligned,
624 + ).register(self.patterns)
625
626 self.dump_patterns(config, self.patterns)
627
vllm/model_executor/layers/quantization/utils/fp8_utils.py
+10 -1
@@ -924,7 +924,16 @@ def per_token_group_quant_fp8(
924 # TODO(bnell): this causes some fp8 moe test to fail.
925 if current_platform.is_cuda() and x.is_contiguous():
926 torch.ops._C.per_token_group_fp8_quant(
927 - x, x_q, x_s, group_size, eps, fp8_min, fp8_max, use_ue8m0
927 + x,
928 + x_q,
929 + x_s,
930 + group_size,
931 + eps,
932 + fp8_min,
933 + fp8_max,
934 + use_ue8m0,
935 + column_major_scales,
936 + tma_aligned_scales,
937 )
938 return x_q, x_s
939
vllm/utils/deep_gemm.py
+1 -1
@@ -349,7 +349,7 @@ def _align(x: int, y: int) -> int:
349
350
351 # Taken from https://github.com/deepseek-ai/DeepGEMM/blob/v2.1.1/csrc/utils/math.hpp#L19
352 -def get_tma_aligned_size(x: int, element_size: int):
352 +def get_tma_aligned_size(x: int, element_size: int) -> int:
353 return _align(x, 16 // element_size)
354
355