| 1 | # SPDX-License-Identifier: Apache-2.0 |
| 2 | # SPDX-FileCopyrightText: Copyright contributors to the vLLM project |
| 3 | """Tests for the AWQ Triton kernel. |
| 4 | |
| 5 | Run `pytest tests/kernels/quantization/test_awq_triton.py`. |
| 6 | """ |
| 7 | |
| 8 | import pytest |
| 9 | import torch |
| 10 | |
| 11 | from vllm.model_executor.layers.quantization.awq_triton import ( |
| 12 | AWQ_TRITON_SUPPORTED_GROUP_SIZES, |
| 13 | awq_dequantize_triton, |
| 14 | awq_gemm_triton, |
| 15 | ) |
| 16 | from vllm.platforms import current_platform |
| 17 | from vllm.utils.torch_utils import set_random_seed |
| 18 | |
| 19 | pytestmark = pytest.mark.skipif( |
| 20 | not (current_platform.is_cuda_alike() or current_platform.is_xpu()), |
| 21 | reason="AWQ Triton kernels require CUDA/ROCm or XPU.", |
| 22 | ) |
| 23 | |
| 24 | device = current_platform.device_type |
| 25 | |
| 26 | |
| 27 | def reverse_awq_order(t: torch.Tensor): |
| 28 | bits = 4 |
| 29 | AWQ_REVERSE_ORDER = [0, 4, 1, 5, 2, 6, 3, 7] |
| 30 | reverse_order_tensor = torch.arange( |
| 31 | t.shape[-1], |
| 32 | dtype=torch.int32, |
| 33 | device=t.device, |
| 34 | ) |
| 35 | reverse_order_tensor = reverse_order_tensor.view(-1, 32 // bits) |
| 36 | reverse_order_tensor = reverse_order_tensor[:, AWQ_REVERSE_ORDER] |
| 37 | reverse_order_tensor = reverse_order_tensor.view(-1) |
| 38 | |
| 39 | t = t[:, reverse_order_tensor] & 0xF |
| 40 | return t |
| 41 | |
| 42 | |
| 43 | # qweights - [R , C // 8], int32 |
| 44 | # scales - [R // G, C ], float16 |
| 45 | # zeros - [R // G, C // 8], int32 |
| 46 | def awq_dequantize_torch( |
| 47 | qweight: torch.Tensor, scales: torch.Tensor, qzeros: torch.Tensor, group_size: int |
| 48 | ) -> torch.Tensor: |
| 49 | if group_size == -1: |
| 50 | group_size = qweight.shape[0] |
| 51 | |
| 52 | bits = 4 |
| 53 | shifts = torch.arange(0, 32, bits, device=qzeros.device) |
| 54 | |
| 55 | iweights = torch.bitwise_right_shift(qweight[:, :, None], shifts[None, None, :]).to( |
| 56 | torch.int8 |
| 57 | ) |
| 58 | |
| 59 | iweights = iweights.view(iweights.shape[0], -1) |
| 60 | |
| 61 | zeros = torch.bitwise_right_shift(qzeros[:, :, None], shifts[None, None, :]).to( |
| 62 | torch.int8 |
| 63 | ) |
| 64 | zeros = zeros.view(qzeros.shape[0], -1) |
| 65 | zeros = reverse_awq_order(zeros) |
| 66 | |
| 67 | iweights = reverse_awq_order(iweights) |
| 68 | |
| 69 | iweights = torch.bitwise_and(iweights, (2**bits) - 1) |
| 70 | zeros = torch.bitwise_and(zeros, (2**bits) - 1) |
| 71 | |
| 72 | scales = scales.repeat_interleave(group_size, dim=0) |
| 73 | zeros = zeros.repeat_interleave(group_size, dim=0) |
| 74 | return (iweights - zeros) * scales |
| 75 | |
| 76 | |
| 77 | # qweights - [R , C // 8], int32 |
| 78 | # scales - [R // G, C ], float16 |
| 79 | # zeros - [R // G, C // 8], int32 |
| 80 | @pytest.mark.parametrize("qweight_rows", [3584, 18944, 128, 256, 512, 1024]) |
| 81 | @pytest.mark.parametrize("qweight_cols", [448, 576, 4736, 16, 32, 64, 128]) |
| 82 | @pytest.mark.parametrize("group_size", AWQ_TRITON_SUPPORTED_GROUP_SIZES) |
| 83 | def test_dequantize(qweight_rows, qweight_cols, group_size): |
| 84 | if group_size == -1: |
| 85 | group_size = qweight_rows |
| 86 | |
| 87 | qweight_dtype = torch.int32 |
| 88 | scales_rows = qweight_rows // group_size |
| 89 | scales_cols = qweight_cols * 8 |
| 90 | scales_dtype = torch.float16 |
| 91 | zeros_rows = scales_rows |
| 92 | zeros_cols = qweight_cols |
| 93 | zeros_dtype = torch.int32 |
| 94 | |
| 95 | set_random_seed(0) |
| 96 | |
| 97 | qweight = torch.randint( |
| 98 | 0, |
| 99 | torch.iinfo(torch.int32).max, |
| 100 | (qweight_rows, qweight_cols), |
| 101 | dtype=qweight_dtype, |
| 102 | device=device, |
| 103 | ) |
| 104 | scales = torch.rand(scales_rows, scales_cols, dtype=scales_dtype, device=device) |
| 105 | zeros = torch.randint( |
| 106 | 0, |
| 107 | torch.iinfo(torch.int32).max, |
| 108 | (zeros_rows, zeros_cols), |
| 109 | dtype=zeros_dtype, |
| 110 | device=device, |
| 111 | ) |
| 112 | |
| 113 | iweights_triton = awq_dequantize_triton(qweight, scales, zeros) |
| 114 | |
| 115 | assert not torch.any(torch.isinf(iweights_triton)) and not torch.any( |
| 116 | torch.isnan(iweights_triton) |
| 117 | ) |
| 118 | |
| 119 | iweights_torch = awq_dequantize_torch(qweight, scales, zeros, group_size) |
| 120 | |
| 121 | torch.testing.assert_close(iweights_triton, iweights_torch) |
| 122 | |
| 123 | |
| 124 | # input - [N, K] |
| 125 | # qweight - [K, M // 8] |
| 126 | # qzeros - [K // G, M // 8] |
| 127 | # scales - [K // G, M] |
| 128 | @pytest.mark.parametrize("N", [1, 2, 4, 8, 14, 17, 23, 32]) |
| 129 | @pytest.mark.parametrize("K", [128]) |
| 130 | @pytest.mark.parametrize("M", [16, 24, 32]) |
| 131 | @pytest.mark.parametrize("group_size", AWQ_TRITON_SUPPORTED_GROUP_SIZES) |
| 132 | @pytest.mark.parametrize("splitK", [1, 8]) |
| 133 | def test_gemm(N, K, M, splitK, group_size): |
| 134 | if group_size == -1: |
| 135 | group_size = K |
| 136 | |
| 137 | split_k_iters = splitK |
| 138 | |
| 139 | input_rows = N |
| 140 | input_cols = K |
| 141 | input_dtype = torch.float32 |
| 142 | qweight_rows = input_cols |
| 143 | qweight_cols = M // 8 |
| 144 | scales_rows = qweight_rows // group_size |
| 145 | scales_cols = M |
| 146 | scales_dtype = torch.float32 |
| 147 | qzeros_rows = scales_rows |
| 148 | qzeros_cols = qweight_cols |
| 149 | |
| 150 | set_random_seed(0) |
| 151 | |
| 152 | input = torch.rand((input_rows, input_cols), dtype=input_dtype, device=device) |
| 153 | qweight = torch.randint( |
| 154 | 0, torch.iinfo(torch.int32).max, (qweight_rows, qweight_cols), device=device |
| 155 | ) |
| 156 | qzeros = torch.randint( |
| 157 | 0, torch.iinfo(torch.int32).max, (qzeros_rows, qzeros_cols), device=device |
| 158 | ) |
| 159 | scales = torch.rand((scales_rows, scales_cols), dtype=scales_dtype, device=device) |
| 160 | |
| 161 | output_triton = awq_gemm_triton(input, qweight, scales, qzeros, split_k_iters) |
| 162 | |
| 163 | assert not torch.any(torch.isinf(output_triton)) and not torch.any( |
| 164 | torch.isnan(output_triton) |
| 165 | ) |
| 166 | |
| 167 | dequantized_weights = awq_dequantize_triton(qweight, scales, qzeros) |
| 168 | |
| 169 | output_torch = torch.matmul(input, dequantized_weights) |
| 170 | |
| 171 | assert not torch.any(torch.isinf(output_torch)) and not torch.any( |
| 172 | torch.isnan(output_torch) |
| 173 | ) |
| 174 | |
| 175 | torch.testing.assert_close( |
| 176 | output_triton.cpu(), output_torch.cpu(), atol=1e-1, rtol=1e-1 |
| 177 | ) |