@tduncan / Nvidia-Optimization-Stack-VLLM Mirror · read-only / commits / 71df063c49
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 2, 02:15 UTC

Enable perf_token_group_quant/_C_stable_libtorch for ROCm (#42758)

Signed-off-by: charlifu <charlifu@amd.com>

Charlie Fu committed Jun 3, 2026 at 01:23 UTC 71df063c494c111ab60f6a33c54aafe7b9ae1d02
12 files changed +146 -110
CMakeLists.txt
+2 -2
@@ -633,6 +633,8 @@ if(VLLM_GPU_LANG STREQUAL "CUDA" OR VLLM_GPU_LANG STREQUAL "HIP")
633 "csrc/libtorch_stable/activation_kernels.cu"
634 "csrc/libtorch_stable/quantization/w8a8/int8/scaled_quant.cu"
635 "csrc/libtorch_stable/quantization/w8a8/fp8/common.cu"
636 + "csrc/libtorch_stable/quantization/w8a8/fp8/per_token_group_quant.cu"
637 + "csrc/libtorch_stable/quantization/w8a8/int8/per_token_group_quant.cu"
638 "csrc/libtorch_stable/quantization/gptq/q_gemm.cu"
639 "csrc/libtorch_stable/quantization/gguf/gguf_kernel.cu"
640 "csrc/libtorch_stable/pos_encoding_kernels.cu"
@@ -657,8 +659,6 @@ if(VLLM_GPU_LANG STREQUAL "CUDA" OR VLLM_GPU_LANG STREQUAL "HIP")
659 "csrc/libtorch_stable/quantization/fp4/nvfp4_quant_entry.cu"
660 "csrc/libtorch_stable/quantization/fp4/nvfp4_scaled_mm_entry.cu"
661 "csrc/libtorch_stable/permute_cols.cu"
660 - "csrc/libtorch_stable/quantization/w8a8/fp8/per_token_group_quant.cu"
661 - "csrc/libtorch_stable/quantization/w8a8/int8/per_token_group_quant.cu"
662 "csrc/libtorch_stable/quantization/awq/gemm_kernels.cu")
663
664 set_gencode_flags_for_srcs(
cmake/hipify.py
+31 -10
@@ -14,7 +14,18 @@ import argparse
14 import os
15 import shutil
16
17 -from torch.utils.hipify.hipify_python import hipify
17 +from torch.utils.hipify.hipify_python import get_hip_file_path, hipify
18 +
19 +
20 +def _expected_hip_build_path(source_abs: str, output_directory: str) -> str:
21 + """Match torch.utils.hipify.hipify_python.preprocessor fout_path naming."""
22 + rel = os.path.relpath(source_abs, output_directory)
23 + return os.path.abspath(
24 + os.path.join(
25 + output_directory, get_hip_file_path(rel, is_pytorch_extension=True)
26 + )
27 + )
28 +
29
30 if __name__ == "__main__":
31 parser = argparse.ArgumentParser()
@@ -53,7 +64,11 @@ if __name__ == "__main__":
64 hipify_result = hipify(
65 project_directory=args.project_dir,
66 output_directory=args.output_dir,
56 - header_include_dirs=[],
67 + # Hipify resolves quoted includes next to the including file first; vLLM
68 + # uses paths relative to csrc/ (e.g. "libtorch_stable/torch_utils.h"
69 + # from quantization/w8a8/fp8/*.cu). Without an include root here, those
70 + # headers are never found and are not hipified or rewritten in dependents.
71 + header_include_dirs=["."],
72 includes=includes,
73 extra_files=extra_files,
74 show_detailed=True,
@@ -64,14 +79,20 @@ if __name__ == "__main__":
79 hipified_sources = []
80 for source in args.sources:
81 s_abs = os.path.abspath(source)
67 - hipified_s_abs = (
68 - hipify_result[s_abs].hipified_path
69 - if (
70 - s_abs in hipify_result
71 - and hipify_result[s_abs].hipified_path is not None
72 - )
73 - else s_abs
74 - )
82 + if s_abs in hipify_result and hipify_result[s_abs].hipified_path is not None:
83 + path = hipify_result[s_abs].hipified_path
84 + # PyTorch skips writing when is_pytorch_extension and text unchanged;
85 + # hipified_path then stays *.cu. CMake expects *.hip under output_dir.
86 + if s_abs.endswith(".cu") and path.endswith(".cu"):
87 + dest = _expected_hip_build_path(s_abs, args.output_dir)
88 + if os.path.normpath(path) != os.path.normpath(dest):
89 + os.makedirs(os.path.dirname(dest), exist_ok=True)
90 + shutil.copy2(path, dest)
91 + hipified_s_abs = dest
92 + else:
93 + hipified_s_abs = path
94 + else:
95 + hipified_s_abs = s_abs
96 hipified_sources.append(hipified_s_abs)
97
98 assert len(hipified_sources) == len(args.sources)
csrc/libtorch_stable/ops.h
+4 -4
@@ -3,10 +3,6 @@
3 #include <torch/csrc/stable/library.h>
4 #include <torch/csrc/stable/tensor.h>
5
6 -#ifndef USE_ROCM
7 -torch::stable::Tensor permute_cols(torch::stable::Tensor const& A,
8 - torch::stable::Tensor const& perm);
9 -
6 void per_token_group_quant_fp8(const torch::stable::Tensor& input,
7 torch::stable::Tensor& output_q,
8 torch::stable::Tensor& output_s,
@@ -28,6 +24,10 @@ void per_token_group_quant_int8(const torch::stable::Tensor& input,
24 int64_t group_size, double eps, double int8_min,
25 double int8_max);
26
27 +#ifndef USE_ROCM
28 +torch::stable::Tensor permute_cols(torch::stable::Tensor const& A,
29 + torch::stable::Tensor const& perm);
30 +
31 bool cutlass_scaled_mm_supports_fp8(int64_t cuda_device_capability);
32 bool cutlass_scaled_mm_supports_block_fp8(int64_t cuda_device_capability);
33 bool cutlass_group_gemm_supported(int64_t cuda_device_capability);
csrc/libtorch_stable/quantization/w8a8/fp8/per_token_group_quant.cu
+33 -11
@@ -7,7 +7,11 @@
7
8 #include <cmath>
9
10 -#include <cuda_fp8.h>
10 +#ifdef USE_ROCM
11 + #include <hip/hip_fp8.h>
12 +#else
13 + #include <cuda_fp8.h>
14 +#endif
15
16 #include "libtorch_stable/quantization/vectorization.cuh"
17 #include "libtorch_stable/quantization/vectorization_utils.cuh"
@@ -15,12 +19,23 @@
19 #include "libtorch_stable/torch_utils.h"
20
21 __device__ __forceinline__ float GroupReduceMax(float val) {
22 +#ifdef USE_ROCM
23 + // 16-thread logical groups may pack up to four per 64-lane wavefront; use a
24 + // 64-bit mask and explicit width so shuffles stay within each group.
25 + const int lane_in_wave = threadIdx.x % warpSize;
26 + const unsigned long long mask = 0xFFFFull << ((lane_in_wave / 16) * 16);
27 + val = fmaxf(val, __shfl_xor_sync(mask, val, 8, 16));
28 + val = fmaxf(val, __shfl_xor_sync(mask, val, 4, 16));
29 + val = fmaxf(val, __shfl_xor_sync(mask, val, 2, 16));
30 + val = fmaxf(val, __shfl_xor_sync(mask, val, 1, 16));
31 +#else
32 unsigned mask = threadIdx.x % 32 >= 16 ? 0xffff0000 : 0x0000ffff;
33
34 val = fmaxf(val, __shfl_xor_sync(mask, val, 8));
35 val = fmaxf(val, __shfl_xor_sync(mask, val, 4));
36 val = fmaxf(val, __shfl_xor_sync(mask, val, 2));
37 val = fmaxf(val, __shfl_xor_sync(mask, val, 1));
38 +#endif
39 return val;
40 }
41
@@ -237,10 +252,12 @@ void per_token_group_quant_8bit(const torch::stable::Tensor& input,
252
253 VLLM_STABLE_DISPATCH_FLOATING_TYPES(
254 input.scalar_type(), "per_token_group_quant_8bit", ([&] {
240 - if (dst_type == torch::headeronly::ScalarType::Float8_e4m3fn) {
241 - LAUNCH_KERNEL(scalar_t, __nv_fp8_e4m3);
242 - } else if (dst_type == torch::headeronly::ScalarType::Char) {
255 + if (dst_type == torch::headeronly::ScalarType::Char) {
256 LAUNCH_KERNEL(scalar_t, int8_t);
257 + } else {
258 + VLLM_STABLE_DISPATCH_FP8_TYPES(
259 + dst_type, "per_token_group_quant_8bit_fp8",
260 + ([&] { LAUNCH_KERNEL(scalar_t, fp8_t); }));
261 }
262 }));
263
@@ -317,10 +334,18 @@ __global__ void per_token_group_quant_8bit_packed_register_kernel(
334
335 // 8-lane subgroup shuffle reduce (octet of the warp). The mask selects the
336 // 8 lanes within the warp that share a group.
337 +#ifdef USE_ROCM
338 + const int lane_in_wave = threadIdx.x % warpSize;
339 + const unsigned long long mask = 0xFFull << (lane_in_wave & ~7);
340 + local_absmax = fmaxf(local_absmax, __shfl_xor_sync(mask, local_absmax, 4, 8));
341 + local_absmax = fmaxf(local_absmax, __shfl_xor_sync(mask, local_absmax, 2, 8));
342 + local_absmax = fmaxf(local_absmax, __shfl_xor_sync(mask, local_absmax, 1, 8));
343 +#else
344 unsigned mask = 0xffu << (threadIdx.x & 24u);
345 local_absmax = fmaxf(local_absmax, __shfl_xor_sync(mask, local_absmax, 4));
346 local_absmax = fmaxf(local_absmax, __shfl_xor_sync(mask, local_absmax, 2));
347 local_absmax = fmaxf(local_absmax, __shfl_xor_sync(mask, local_absmax, 1));
348 +#endif
349
350 float y_s = local_absmax / max_8bit;
351 y_s = fmaxf(y_s, 1e-10f);
@@ -503,15 +528,12 @@ void per_token_group_quant_8bit_packed(const torch::stable::Tensor& input,
528
529 VLLM_STABLE_DISPATCH_HALF_TYPES(
530 input.scalar_type(), "per_token_group_quant_8bit_packed_register", ([&] {
506 - if (dst_type == torch::headeronly::ScalarType::Float8_e4m3fn) {
507 - LAUNCH_REG_KERNEL(scalar_t, __nv_fp8_e4m3);
508 - } else if (dst_type == torch::headeronly::ScalarType::Char) {
531 + if (dst_type == torch::headeronly::ScalarType::Char) {
532 LAUNCH_REG_KERNEL(scalar_t, int8_t);
533 } else {
511 - STD_TORCH_CHECK(
512 - false,
513 - "per_token_group_quant_8bit_packed only supports FP8/INT8 "
514 - "outputs.");
534 + VLLM_STABLE_DISPATCH_FP8_TYPES(
535 + dst_type, "per_token_group_quant_8bit_packed_fp8",
536 + ([&] { LAUNCH_REG_KERNEL(scalar_t, fp8_t); }));
537 }
538 }));
539
csrc/libtorch_stable/torch_bindings.cpp
+10 -10
@@ -7,11 +7,6 @@
7 // Note: We register under namespace "_C" so ops are accessible as
8 // torch.ops._C.<op_name> for compatibility with existing code.
9 STABLE_TORCH_LIBRARY_FRAGMENT(_C, ops) {
10 -#ifndef USE_ROCM
11 - ops.def("permute_cols(Tensor A, Tensor perm) -> Tensor");
12 -#endif
13 -
14 -#ifndef USE_ROCM
10 // Compute per-token-group FP8 quantized tensor and scaling factor.
11 // The dummy arguments are here so we can correctly fuse with RMSNorm.
12 ops.def(
@@ -32,6 +27,11 @@ STABLE_TORCH_LIBRARY_FRAGMENT(_C, ops) {
27 "output_s, int group_size, float eps, float int8_min, float int8_max) -> "
28 "()");
29
30 +#ifndef USE_ROCM
31 + ops.def("permute_cols(Tensor A, Tensor perm) -> Tensor");
32 +#endif
33 +
34 +#ifndef USE_ROCM
35 // CUTLASS w8a8 GEMM, supporting symmetric per-tensor or per-row/column
36 // quantization, as well as bias
37 ops.def(
@@ -508,11 +508,6 @@ STABLE_TORCH_LIBRARY_FRAGMENT(_C, ops) {
508 }
509
510 STABLE_TORCH_LIBRARY_IMPL(_C, CUDA, ops) {
511 -#ifndef USE_ROCM
512 - ops.impl("permute_cols", TORCH_BOX(&permute_cols));
513 -#endif
514 -
515 -#ifndef USE_ROCM
511 // Per-token group quantization
512 ops.impl("per_token_group_fp8_quant", TORCH_BOX(&per_token_group_quant_fp8));
513 ops.impl("per_token_group_fp8_quant_packed",
@@ -520,6 +515,11 @@ STABLE_TORCH_LIBRARY_IMPL(_C, CUDA, ops) {
515 ops.impl("per_token_group_quant_int8",
516 TORCH_BOX(&per_token_group_quant_int8));
517
518 +#ifndef USE_ROCM
519 + ops.impl("permute_cols", TORCH_BOX(&permute_cols));
520 +#endif
521 +
522 +#ifndef USE_ROCM
523 // CUTLASS scaled_mm ops
524 ops.impl("cutlass_scaled_mm", TORCH_BOX(&cutlass_scaled_mm));
525 ops.impl("cutlass_scaled_mm_azp", TORCH_BOX(&cutlass_scaled_mm_azp));
csrc/libtorch_stable/torch_utils.h
+1 -5
@@ -6,11 +6,7 @@
6 #include <torch/csrc/stable/tensor.h>
7 #include <torch/headeronly/util/shim_utils.h>
8
9 -#ifndef USE_ROCM
10 - #include <cuda_runtime.h>
11 -#else
12 - #include <hip/hip_runtime.h>
13 -#endif
9 +#include <cuda_runtime.h>
10 #include <cublas_v2.h>
11
12 #include <deque>
tests/kernels/quantization/test_per_token_group_quant.py
+24 -17
@@ -6,6 +6,7 @@ import pytest
6 import torch
7
8 from vllm.model_executor.layers.quantization.utils import fp8_utils, int8_utils
9 +from vllm.model_executor.layers.quantization.utils.quant_utils import get_fp8_min_max
10 from vllm.platforms import current_platform
11
12
@@ -16,7 +17,9 @@ from vllm.platforms import current_platform
17 @pytest.mark.parametrize("tma_aligned", [False, True])
18 @pytest.mark.parametrize("scale_ue8m0", [False, True])
19 @pytest.mark.parametrize("group_size", [64, 128])
19 -@pytest.mark.skipif(not torch.cuda.is_available(), reason="CUDA not available")
20 +@pytest.mark.skipif(
21 + not current_platform.is_cuda_alike(), reason="Only test on CUDA/ROCm."
22 +)
23 def test_per_token_group_quant_fp8(
24 shape, column_major: bool, tma_aligned: bool, scale_ue8m0: bool, group_size: int
25 ):
@@ -37,7 +40,7 @@ def test_per_token_group_quant_fp8(
40 )
41
42 # triton ref
40 - with patch("vllm.platforms.current_platform.is_cuda", return_value=False):
43 + with patch("vllm.platforms.current_platform.is_cuda_alike", return_value=False):
44 ref_q, ref_s = fp8_utils.per_token_group_quant_fp8(
45 x,
46 group_size,
@@ -77,7 +80,8 @@ def test_per_token_group_quant_fp8(
80 )
81 @pytest.mark.parametrize("poisoned_scales", [False, True])
82 @pytest.mark.skipif(
80 - not current_platform.is_cuda(), reason="DeepGEMM not available on this platform"
83 + not current_platform.is_cuda_alike(),
84 + reason="DeepGEMM not available on this platform",
85 )
86 def test_per_token_group_quant_fp8_packed(
87 num_tokens, hidden_dim, group_size, poisoned_scales
@@ -99,8 +103,8 @@ def test_per_token_group_quant_fp8_packed(
103 if poisoned_scales:
104 # Call the kernel with poisoned scale buffer to
105 # ensure padded indices are correctly zeroed.
102 - fp8_dtype = torch.float8_e4m3fn
103 - finfo = torch.finfo(fp8_dtype)
106 + fp8_dtype = current_platform.fp8_dtype()
107 + fp8_min, fp8_max = get_fp8_min_max()
108 out_q = torch.empty_like(x, dtype=fp8_dtype)
109 out_s_packed = torch.empty_strided(
110 (mn, k_num_packed),
@@ -115,8 +119,8 @@ def test_per_token_group_quant_fp8_packed(
119 out_s_packed,
120 group_size,
121 1e-10,
118 - finfo.min,
119 - finfo.max,
122 + fp8_min,
123 + fp8_max,
124 )
125 else:
126 out_q, out_s_packed = fp8_utils.per_token_group_quant_fp8_packed_for_deepgemm(
@@ -126,7 +130,7 @@ def test_per_token_group_quant_fp8_packed(
130 )
131
132 # Triton reference (row-major float32 scales, UE8M0)
129 - with patch("vllm.platforms.current_platform.is_cuda", return_value=False):
133 + with patch("vllm.platforms.current_platform.is_cuda_alike", return_value=False):
134 ref_q, ref_s = fp8_utils.per_token_group_quant_fp8(
135 x,
136 group_size,
@@ -157,7 +161,8 @@ def test_per_token_group_quant_fp8_packed(
161
162
163 @pytest.mark.skipif(
160 - not current_platform.is_cuda(), reason="DeepGEMM not available on this platform"
164 + not current_platform.is_cuda_alike(),
165 + reason="DeepGEMM not available on this platform",
166 )
167 def test_per_token_group_quant_fp8_packed_all_zero():
168 """All-zero input must produce well-defined UE8M0 scale bytes via the eps
@@ -216,7 +221,8 @@ def test_per_token_group_quant_fp8_packed_all_zero():
221
222
223 @pytest.mark.skipif(
219 - not current_platform.is_cuda(), reason="DeepGEMM not available on this platform"
224 + not current_platform.is_cuda_alike(),
225 + reason="DeepGEMM not available on this platform",
226 )
227 def test_per_token_group_quant_fp8_packed_mantissa_rounds_up():
228 """Inputs whose absmax/max_8bit produces a non-power-of-2 force the
@@ -244,7 +250,7 @@ def test_per_token_group_quant_fp8_packed_mantissa_rounds_up():
250 use_ue8m0=True,
251 )
252
247 - with patch("vllm.platforms.current_platform.is_cuda", return_value=False):
253 + with patch("vllm.platforms.current_platform.is_cuda_alike", return_value=False):
254 ref_q, ref_s = fp8_utils.per_token_group_quant_fp8(
255 x,
256 group_size,
@@ -286,7 +292,8 @@ def test_per_token_group_quant_fp8_packed_mantissa_rounds_up():
292 ],
293 )
294 @pytest.mark.skipif(
289 - not current_platform.is_cuda(), reason="DeepGEMM not available on this platform"
295 + not current_platform.is_cuda_alike(),
296 + reason="DeepGEMM not available on this platform",
297 )
298 def test_per_token_group_quant_fp8_packed_zero_fills_padded_output_q(
299 num_tokens, hidden_dim
@@ -305,8 +312,8 @@ def test_per_token_group_quant_fp8_packed_zero_fills_padded_output_q(
312 k_num_packed = (groups_per_row + 3) // 4
313 tma_aligned_mn = ((mn + 3) // 4) * 4
314
308 - fp8_dtype = torch.float8_e4m3fn
309 - finfo = torch.finfo(fp8_dtype)
315 + fp8_dtype = current_platform.fp8_dtype()
316 + fp8_min, fp8_max = get_fp8_min_max()
317 # Allocate output_q with the padded mn extent and pre-fill with 0xFF
318 # so the kernel cannot rely on a clean buffer.
319 out_q = torch.empty((tma_aligned_mn, hidden_dim), device=device, dtype=fp8_dtype)
@@ -320,11 +327,11 @@ def test_per_token_group_quant_fp8_packed_zero_fills_padded_output_q(
327 )
328
329 torch.ops._C.per_token_group_fp8_quant_packed(
323 - x, out_q, out_s_packed, group_size, 1e-10, finfo.min, finfo.max
330 + x, out_q, out_s_packed, group_size, 1e-10, fp8_min, fp8_max
331 )
332
333 # Live rows must match the Triton reference.
327 - with patch("vllm.platforms.current_platform.is_cuda", return_value=False):
334 + with patch("vllm.platforms.current_platform.is_cuda_alike", return_value=False):
335 ref_q, _ = fp8_utils.per_token_group_quant_fp8(x, group_size, use_ue8m0=True)
336 assert torch.equal(out_q[:mn], ref_q), "Live region mismatch"
337
@@ -356,7 +363,7 @@ def test_per_token_group_quant_int8(shape, group_size: int):
363 )
364
365 # triton ref
359 - with patch("vllm.platforms.current_platform.is_cuda", return_value=False):
366 + with patch("vllm.platforms.current_platform.is_cuda_alike", return_value=False):
367 ref_q, ref_s = int8_utils.per_token_group_quant_int8(
368 x,
369 group_size,
vllm/compilation/passes/fusion/matcher_utils.py
+2 -3
@@ -36,14 +36,13 @@ QUANT_OPS: dict[QuantKey, OpOverload] = {
36 kFp8StaticTensorSym: torch.ops._C.static_scaled_fp8_quant.default, # noqa: E501
37 kFp8DynamicTensorSym: torch.ops._C.dynamic_scaled_fp8_quant.default, # noqa: E501
38 kFp8DynamicTokenSym: torch.ops._C.dynamic_per_token_scaled_fp8_quant.default, # noqa: E501
39 + kFp8Dynamic128Sym: torch.ops._C.per_token_group_fp8_quant.default, # noqa: E501
40 + kFp8Dynamic64Sym: torch.ops._C.per_token_group_fp8_quant.default, # noqa: E501
41 }
42
43 if current_platform.is_cuda() and hasattr(torch.ops._C, "scaled_fp4_quant"):
44 QUANT_OPS[kNvfp4Dynamic] = torch.ops._C.scaled_fp4_quant.out # noqa: E501
45
44 -if current_platform.is_cuda():
45 - QUANT_OPS[kFp8Dynamic128Sym] = torch.ops._C.per_token_group_fp8_quant.default # noqa: E501
46 - QUANT_OPS[kFp8Dynamic64Sym] = torch.ops._C.per_token_group_fp8_quant.default # noqa: E501
46
47 SILU_MUL_OP = torch.ops._C.silu_and_mul.default
48
vllm/compilation/passes/fusion/rms_quant_fusion.py
+29 -34
@@ -16,6 +16,7 @@ from vllm.model_executor.layers.quantization.utils.quant_utils import (
16 GroupShape,
17 QuantKey,
18 ScaleDesc,
19 + get_fp8_min_max,
20 kFp8Dynamic64Sym,
21 kFp8Dynamic128Sym,
22 kFp8DynamicTensorSym,
@@ -83,12 +84,11 @@ QUANT_OPS: dict[QuantKey, OpOverload] = {
84 kFp8StaticTensorSym: torch.ops._C.static_scaled_fp8_quant.default, # noqa: E501
85 kFp8DynamicTensorSym: torch.ops._C.dynamic_scaled_fp8_quant.default, # noqa: E501
86 kFp8DynamicTokenSym: torch.ops._C.dynamic_per_token_scaled_fp8_quant.default, # noqa: E501
87 + kFp8Dynamic128Sym: torch.ops._C.per_token_group_fp8_quant.default, # noqa: E501
88 + kFp8Dynamic64Sym: torch.ops._C.per_token_group_fp8_quant.default, # noqa: E501
89 }
90 if current_platform.is_cuda() and hasattr(torch.ops._C, "scaled_fp4_quant"):
91 QUANT_OPS[kNvfp4Dynamic] = torch.ops._C.scaled_fp4_quant.out
89 -if current_platform.is_cuda():
90 - QUANT_OPS[kFp8Dynamic128Sym] = torch.ops._C.per_token_group_fp8_quant.default # noqa: E501
91 - QUANT_OPS[kFp8Dynamic64Sym] = torch.ops._C.per_token_group_fp8_quant.default # noqa: E501
92
93
94 class FusedRMSQuantKey(NamedTuple):
@@ -327,9 +327,7 @@ class FusedAddRMSNormGroupQuantPattern(RMSNormQuantPattern):
327 dtype=self.quant_matcher.quant_key.dtype,
328 )
329 assert scale is not None
330 - finfo = torch.finfo(self.quant_matcher.quant_key.dtype)
331 - fp8_min = finfo.min
332 - fp8_max = finfo.max
330 + fp8_min, fp8_max = get_fp8_min_max()
331
332 _, result, scale = auto_functionalized(
333 self.quant_matcher.QUANT_OP,
@@ -430,9 +428,7 @@ class RMSNormGroupQuantPattern(RMSNormQuantPattern):
428 dtype=self.quant_matcher.quant_key.dtype,
429 )
430 assert scale is not None
433 - finfo = torch.finfo(self.quant_matcher.quant_key.dtype)
434 - fp8_min = finfo.min
435 - fp8_max = finfo.max
431 + fp8_min, fp8_max = get_fp8_min_max()
432
433 _, result, scale = auto_functionalized(
434 self.quant_matcher.QUANT_OP,
@@ -645,31 +641,30 @@ class RMSNormQuantFusionPass(VllmPatternMatcherPass):
641 # Fuse rms_norm + dynamic per-token fp8 quant
642 RMSNormDynamicQuantPattern(epsilon, FP8_DTYPE).register(self.patterns)
643
648 - # Only register group quant patterns on CUDA where the C++ op exists
649 - if current_platform.is_cuda():
650 - for group_shape in [GroupShape(1, 128), GroupShape(1, 64)]:
651 - for has_col_major_scales in [True, False]:
652 - for is_e8m0 in [True, False]:
653 - for is_tma_aligned in [False, True]:
654 - # Fuse fused_add_rms_norm + fp8 group quant
655 - FusedAddRMSNormGroupQuantPattern(
656 - epsilon,
657 - FP8_DTYPE,
658 - group_shape=group_shape,
659 - is_e8m0=is_e8m0,
660 - has_col_major_scales=has_col_major_scales,
661 - is_tma_aligned=is_tma_aligned,
662 - ).register(self.patterns)
663 -
664 - # Fuse rms_norm + fp8 group quant
665 - RMSNormGroupQuantPattern(
666 - epsilon,
667 - FP8_DTYPE,
668 - group_shape=group_shape,
669 - is_e8m0=is_e8m0,
670 - has_col_major_scales=has_col_major_scales,
671 - is_tma_aligned=is_tma_aligned,
672 - ).register(self.patterns)
644 + # Only register group quant patterns on CUDA/ROCm where the C++ op exists
645 + for group_shape in [GroupShape(1, 128), GroupShape(1, 64)]:
646 + for has_col_major_scales in [True, False]:
647 + for is_e8m0 in [True, False]:
648 + for is_tma_aligned in [False, True]:
649 + # Fuse fused_add_rms_norm + fp8 group quant
650 + FusedAddRMSNormGroupQuantPattern(
651 + epsilon,
652 + FP8_DTYPE,
653 + group_shape=group_shape,
654 + is_e8m0=is_e8m0,
655 + has_col_major_scales=has_col_major_scales,
656 + is_tma_aligned=is_tma_aligned,
657 + ).register(self.patterns)
658 +
659 + # Fuse rms_norm + fp8 group quant
660 + RMSNormGroupQuantPattern(
661 + epsilon,
662 + FP8_DTYPE,
663 + group_shape=group_shape,
664 + is_e8m0=is_e8m0,
665 + has_col_major_scales=has_col_major_scales,
666 + is_tma_aligned=is_tma_aligned,
667 + ).register(self.patterns)
668
669 self.dump_patterns(config, self.patterns)
670
vllm/model_executor/layers/quantization/input_quant_fp8.py
-5
@@ -158,11 +158,6 @@ class QuantFP8(CustomOp):
158 if use_aiter_per_token_quant:
159 return rocm_aiter_ops.per_token_quant(x, _FP8_DTYPE, scale)
160
161 - # Fallback to native implementation for group quantization.
162 - if self.is_group_quant:
163 - assert scale is None, "Dynamic group quantization does not use scale"
164 - return self._quantize_group_native(x)
165 -
161 # Fallback to CUDA implementation
162 return self.forward_cuda(x, scale, scale_ub)
163
vllm/model_executor/layers/quantization/utils/fp8_utils.py
+8 -7
@@ -575,7 +575,9 @@ def per_token_group_quant_fp8(
575
576 # prefer CUDA/XPU kernel if available
577 # TODO(bnell): this causes some fp8 moe test to fail.
578 - if (current_platform.is_cuda() or current_platform.is_xpu()) and x.is_contiguous():
578 + if (
579 + current_platform.is_cuda_alike() or current_platform.is_xpu()
580 + ) and x.is_contiguous():
581 torch.ops._C.per_token_group_fp8_quant(
582 x,
583 x_q,
@@ -664,8 +666,7 @@ def per_token_group_quant_fp8_packed_for_deepgemm(
666 )
667 assert x.stride(-1) == 1, "`x` groups must be contiguous"
668
667 - finfo = torch.finfo(dtype)
668 - fp8_min, fp8_max = finfo.min, finfo.max
669 + fp8_min, fp8_max = get_fp8_min_max()
670
671 # compute DeepGEMM-style packed scale tensor shape.
672 hidden_dim = x.shape[-1]
@@ -681,10 +682,10 @@ def per_token_group_quant_fp8_packed_for_deepgemm(
682 dtype=torch.int32,
683 )
684
684 - # CUDA kernel path only (DeepGEMM + E8M0 is CUDA-specific).
685 - assert current_platform.is_cuda(), (
686 - "per_token_group_quant_fp8_packed_for_deepgemm is only valid on CUDA "
687 - "platforms using DeepGEMM."
685 + # Native kernel (libtorch stable); used with DeepGEMM on CUDA and
686 + # available on ROCm for the same packed UE8M0 scale layout.
687 + assert current_platform.is_cuda_alike(), (
688 + "per_token_group_quant_fp8_packed_for_deepgemm requires a CUDA or ROCm GPU."
689 )
690
691 x_contiguous = x.contiguous()
vllm/model_executor/layers/quantization/utils/int8_utils.py
+2 -2
@@ -235,8 +235,8 @@ def per_token_group_quant_int8(
235 device=x.device,
236 dtype=torch.float32,
237 )
238 - # prefer CUDA kernel if available
239 - if current_platform.is_cuda():
238 + # Prefer native stable kernel on CUDA/ROCm when available.
239 + if current_platform.is_cuda_alike():
240 torch.ops._C.per_token_group_quant_int8(
241 x, x_q, x_s, group_size, eps, float(int8_min), float(int8_max)
242 )