|
1 | 1 | # SPDX-License-Identifier: Apache-2.0 |
2 | 2 | # SPDX-FileCopyrightText: Copyright contributors to the vLLM project |
3 | 3 |
|
| 4 | +import os |
4 | 5 | import torch |
5 | 6 | import torch.nn.functional as F |
6 | 7 |
|
|
13 | 14 | group_broadcast, |
14 | 15 | ) |
15 | 16 | from vllm.platforms import current_platform |
| 17 | +from vllm.triton_utils import HAS_TRITON, tl, triton |
16 | 18 |
|
17 | 19 | _FP8_DTYPE = current_platform.fp8_dtype() |
18 | 20 | _FP8_MIN, _FP8_MAX = get_fp8_min_max() |
19 | 21 | _FP8_MIN_SCALING_FACTOR = 1.0 / (_FP8_MAX * 512.0) |
20 | 22 |
|
21 | 23 |
|
| 24 | +@triton.jit |
| 25 | +def _quantize_pad_fp8_kernel( |
| 26 | + x_ptr, |
| 27 | + y_ptr, |
| 28 | + scale_ptr, |
| 29 | + stride_xs, # input stride along token (seq) dim — may be non-contiguous |
| 30 | + stride_xh, # input stride along head dim |
| 31 | + stride_xd, # input stride along head_dim dim (usually 1) |
| 32 | + stride_ys, # output stride along token dim (contiguous) |
| 33 | + stride_yh, # output stride along head dim |
| 34 | + stride_yd, # output stride along head_dim dim (usually 1) |
| 35 | + num_heads, |
| 36 | + n_rows, # total rows = S * H |
| 37 | + n_cols, |
| 38 | + n_cols_padded, |
| 39 | + fp8_min, |
| 40 | + fp8_max, |
| 41 | + SKIP_SCALE: tl.constexpr, |
| 42 | + BLOCK_M: tl.constexpr, |
| 43 | + BLOCK_N: tl.constexpr, |
| 44 | +): |
| 45 | + pid_m = tl.program_id(0) |
| 46 | + pid_n = tl.program_id(1) |
| 47 | + |
| 48 | + offs_m = pid_m * BLOCK_M + tl.arange(0, BLOCK_M) |
| 49 | + offs_n = pid_n * BLOCK_N + tl.arange(0, BLOCK_N) |
| 50 | + mask_m = offs_m < n_rows |
| 51 | + mask_out = mask_m[:, None] & (offs_n[None, :] < n_cols_padded) |
| 52 | + mask_in = mask_m[:, None] & (offs_n[None, :] < n_cols) |
| 53 | + |
| 54 | + # Decompose flattened row into (token, head) for 3D stride indexing. |
| 55 | + # This lets the kernel read directly from non-contiguous QKV views. |
| 56 | + s = offs_m // num_heads |
| 57 | + h = offs_m % num_heads |
| 58 | + |
| 59 | + x_ptrs = (x_ptr |
| 60 | + + s[:, None] * stride_xs |
| 61 | + + h[:, None] * stride_xh |
| 62 | + + offs_n[None, :] * stride_xd) |
| 63 | + x = tl.load(x_ptrs, mask=mask_in, other=0.0).to(tl.float32) |
| 64 | + if SKIP_SCALE: |
| 65 | + x_q = x |
| 66 | + else: |
| 67 | + scale = tl.load(scale_ptr) |
| 68 | + x_q = x / scale |
| 69 | + x_q = tl.where(mask_in, x_q, 0.0) |
| 70 | + x_q = tl.clamp(x_q, fp8_min, fp8_max).to(y_ptr.dtype.element_ty) |
| 71 | + |
| 72 | + y_ptrs = (y_ptr |
| 73 | + + s[:, None] * stride_ys |
| 74 | + + h[:, None] * stride_yh |
| 75 | + + offs_n[None, :] * stride_yd) |
| 76 | + tl.store(y_ptrs, x_q, mask=mask_out) |
| 77 | + |
| 78 | + |
| 79 | +def _get_fp8_pad_quant_config(padded_head_dim: int) -> tuple[int, int, int]: |
| 80 | + # Blackwell: use a single static config to avoid recompiles. |
| 81 | + if current_platform.is_device_capability_family(100): |
| 82 | + block_n, num_warps, block_m = 128, 4, 16 |
| 83 | + else: |
| 84 | + block_n = triton.next_power_of_2(padded_head_dim) |
| 85 | + block_n = max(16, min(block_n, 256)) |
| 86 | + num_warps = 4 if block_n >= 128 else 2 |
| 87 | + block_m = 16 |
| 88 | + |
| 89 | + env_block_n = os.getenv("VLLM_FP8_PAD_QUANT_BLOCK_N") |
| 90 | + env_num_warps = os.getenv("VLLM_FP8_PAD_QUANT_NUM_WARPS") |
| 91 | + env_block_m = os.getenv("VLLM_FP8_PAD_QUANT_BLOCK_M") |
| 92 | + if env_block_n is not None: |
| 93 | + block_n = max(16, min(int(env_block_n), 256)) |
| 94 | + if env_num_warps is not None: |
| 95 | + num_warps = int(env_num_warps) |
| 96 | + if env_block_m is not None: |
| 97 | + block_m = max(1, int(env_block_m)) |
| 98 | + |
| 99 | + return block_n, num_warps, block_m |
| 100 | + |
| 101 | + |
| 102 | +def quantize_fp8_pad_head_dim_triton( |
| 103 | + tensor: torch.Tensor, |
| 104 | + scale: torch.Tensor, |
| 105 | + skip_scale: bool = False, |
| 106 | + block_n: int | None = None, |
| 107 | + num_warps: int | None = None, |
| 108 | + block_m: int | None = None, |
| 109 | +) -> torch.Tensor: |
| 110 | + """Quantize a 4D (B, S, H, D) or 3D (S, H, D) tensor to FP8 while padding D to a multiple of 16. |
| 111 | +
|
| 112 | + Reads directly from the input using its 3D strides, so non-contiguous |
| 113 | + views (e.g. Q/K/V slices from an interleaved QKV buffer) are handled |
| 114 | + without an extra copy. Output is always a fresh contiguous tensor |
| 115 | + with shape (S, H, padded_D). |
| 116 | + """ |
| 117 | + if not HAS_TRITON: |
| 118 | + raise RuntimeError( |
| 119 | + "Triton is required to quantize with head_dim padding." |
| 120 | + ) |
| 121 | + |
| 122 | + original_shape = tensor.shape |
| 123 | + if tensor.dim() == 4: |
| 124 | + tensor = tensor.view(-1, tensor.shape[-2], tensor.shape[-1]) |
| 125 | + assert tensor.dim() == 3, ( |
| 126 | + f"Expected 3D input (S, H, D), got {tensor.dim()}D" |
| 127 | + ) |
| 128 | + S, H, D = tensor.shape |
| 129 | + padded_head_dim = (D + 15) // 16 * 16 |
| 130 | + out_dtype = current_platform.fp8_dtype() |
| 131 | + output = torch.empty( |
| 132 | + (S, H, padded_head_dim), |
| 133 | + device=tensor.device, |
| 134 | + dtype=out_dtype, |
| 135 | + ) |
| 136 | + |
| 137 | + scale_1d = scale.reshape(-1) |
| 138 | + fp8_min, fp8_max = get_fp8_min_max() |
| 139 | + n_rows = S * H |
| 140 | + |
| 141 | + if block_n is None or num_warps is None or block_m is None: |
| 142 | + block_n, num_warps, block_m = _get_fp8_pad_quant_config(padded_head_dim) |
| 143 | + |
| 144 | + grid = (triton.cdiv(n_rows, block_m), |
| 145 | + triton.cdiv(padded_head_dim, block_n)) |
| 146 | + |
| 147 | + _quantize_pad_fp8_kernel[grid]( |
| 148 | + tensor, |
| 149 | + output, |
| 150 | + scale_1d, |
| 151 | + tensor.stride(0), tensor.stride(1), tensor.stride(2), |
| 152 | + output.stride(0), output.stride(1), output.stride(2), |
| 153 | + H, |
| 154 | + n_rows, |
| 155 | + D, |
| 156 | + padded_head_dim, |
| 157 | + fp8_min, |
| 158 | + fp8_max, |
| 159 | + SKIP_SCALE=skip_scale, |
| 160 | + BLOCK_M=block_m, |
| 161 | + BLOCK_N=block_n, |
| 162 | + num_warps=num_warps, |
| 163 | + ) |
| 164 | + |
| 165 | + return output.view((*original_shape[:-1], padded_head_dim)) |
| 166 | + |
| 167 | + |
22 | 168 | # --8<-- [start:quant_fp8] |
23 | 169 | @CustomOp.register("quant_fp8") |
24 | 170 | class QuantFP8(CustomOp): |
|
0 commit comments