|
| 1 | +# SPDX-License-Identifier: Apache-2.0 |
| 2 | +"""MXFP8 block quantization and linear operations for Blackwell inference.""" |
| 3 | + |
| 4 | +from __future__ import annotations |
| 5 | + |
| 6 | +import torch |
| 7 | +import torch.nn.functional as F |
| 8 | +import triton |
| 9 | +import triton.language as tl |
| 10 | +from quack.mx_utils import to_blocked, to_mx |
| 11 | + |
| 12 | +MXFP8_BLOCK_SIZE = 32 |
| 13 | +_BLOCKS_PER_PROGRAM = 16 |
| 14 | + |
| 15 | + |
| 16 | +def _validate_mxfp8_matrix(matrix: torch.Tensor) -> None: |
| 17 | + """Validate one row-major matrix before MXFP8 quantization.""" |
| 18 | + if matrix.ndim != 2: |
| 19 | + raise ValueError(f"MXFP8 quantization requires a 2D tensor, got shape {tuple(matrix.shape)}.") |
| 20 | + if matrix.dtype not in (torch.bfloat16, torch.float32): |
| 21 | + raise TypeError(f"MXFP8 quantization requires BF16 or FP32 input, got {matrix.dtype}.") |
| 22 | + if matrix.shape[1] % MXFP8_BLOCK_SIZE: |
| 23 | + raise ValueError( |
| 24 | + f"MXFP8 reduction dimension must be divisible by {MXFP8_BLOCK_SIZE}, got {matrix.shape[1]}.") |
| 25 | + |
| 26 | + |
| 27 | +@torch.compile(dynamic=True, fullgraph=True) |
| 28 | +def _quantize_mxfp8_weight_blockwise(matrix: torch.Tensor) -> tuple[torch.Tensor, torch.Tensor]: |
| 29 | + values, natural_scales = to_mx(matrix.contiguous(), MXFP8_BLOCK_SIZE) |
| 30 | + return values, to_blocked(natural_scales) |
| 31 | + |
| 32 | + |
| 33 | +def quantize_mxfp8_weight_blockwise(matrix: torch.Tensor) -> tuple[torch.Tensor, torch.Tensor]: |
| 34 | + """Prequantize a weight with Quack and return hardware-blocked scales.""" |
| 35 | + _validate_mxfp8_matrix(matrix) |
| 36 | + return _quantize_mxfp8_weight_blockwise(matrix) |
| 37 | + |
| 38 | + |
| 39 | +@triton.jit |
| 40 | +def _store_mxfp8_blocks( |
| 41 | + matrix, |
| 42 | + quantized_ptr, |
| 43 | + scale_ptr, |
| 44 | + row, |
| 45 | + first_block, |
| 46 | + row_count, |
| 47 | + column_count, |
| 48 | + scale_column_count, |
| 49 | + BLOCKS_PER_PROGRAM: tl.constexpr, |
| 50 | +): |
| 51 | + """Quantize row blocks and store scales in cuBLAS's 32x4x4 layout.""" |
| 52 | + element_offsets = tl.arange(0, BLOCKS_PER_PROGRAM * 32) |
| 53 | + columns = first_block * 32 + element_offsets |
| 54 | + valid_elements = (row < row_count) & (columns < column_count) |
| 55 | + matrix = tl.reshape(matrix, (BLOCKS_PER_PROGRAM, 32)) |
| 56 | + |
| 57 | + # FLOOR scaling encodes exponent(max_abs) - 8 as one E8M0 byte per 32 values. |
| 58 | + max_abs = tl.max(tl.abs(matrix), axis=1) |
| 59 | + exponent = (max_abs.to(tl.int32, bitcast=True) >> 23) & 0xFF |
| 60 | + scale_biased = tl.maximum(tl.minimum(exponent - 8, 255), 0) |
| 61 | + scale_biased = tl.where(max_abs != max_abs, 255, scale_biased) |
| 62 | + scale_bits = scale_biased.to(tl.int32) << 23 |
| 63 | + scale = tl.maximum(scale_bits.to(tl.float32, bitcast=True), 1.1754943508222875e-38) |
| 64 | + |
| 65 | + scaled_matrix = tl.maximum(tl.minimum(matrix / scale[:, None], 448.0), -448.0) |
| 66 | + tl.store( |
| 67 | + quantized_ptr + row * column_count + columns, |
| 68 | + tl.reshape(scaled_matrix.to(tl.float8e4nv), (BLOCKS_PER_PROGRAM * 32, )), |
| 69 | + mask=valid_elements, |
| 70 | + ) |
| 71 | + |
| 72 | + blocks = first_block + tl.arange(0, BLOCKS_PER_PROGRAM) |
| 73 | + # Map natural [row, K / 32] coordinates into cuBLAS's padded 128x4 tiles. |
| 74 | + row_block = row // 128 |
| 75 | + row_in_block = row % 128 |
| 76 | + column_block = blocks // 4 |
| 77 | + column_in_block = blocks % 4 |
| 78 | + scale_offset = ( |
| 79 | + (row_block * (scale_column_count // 4) + column_block) * 512 |
| 80 | + + (row_in_block % 32) * 16 |
| 81 | + + (row_in_block // 32) * 4 |
| 82 | + + column_in_block |
| 83 | + ) |
| 84 | + tl.store(scale_ptr + scale_offset, scale_biased.to(tl.uint8), mask=blocks < scale_column_count) |
| 85 | + |
| 86 | + |
| 87 | +@triton.jit |
| 88 | +def _quantize_mxfp8_kernel( |
| 89 | + matrix_ptr, |
| 90 | + quantized_ptr, |
| 91 | + scale_ptr, |
| 92 | + row_count, |
| 93 | + column_count, |
| 94 | + scale_column_count, |
| 95 | + BLOCKS_PER_PROGRAM: tl.constexpr, |
| 96 | +): |
| 97 | + """Load BF16 activation blocks for direct MXFP8 quantization.""" |
| 98 | + row = tl.program_id(0) |
| 99 | + first_block = tl.program_id(1) * BLOCKS_PER_PROGRAM |
| 100 | + element_offsets = tl.arange(0, BLOCKS_PER_PROGRAM * 32) |
| 101 | + columns = first_block * 32 + element_offsets |
| 102 | + matrix = tl.load( |
| 103 | + matrix_ptr + row * column_count + columns, |
| 104 | + mask=(row < row_count) & (columns < column_count), |
| 105 | + other=0.0, |
| 106 | + ).to(tl.float32) |
| 107 | + _store_mxfp8_blocks( |
| 108 | + matrix, |
| 109 | + quantized_ptr, |
| 110 | + scale_ptr, |
| 111 | + row, |
| 112 | + first_block, |
| 113 | + row_count, |
| 114 | + column_count, |
| 115 | + scale_column_count, |
| 116 | + BLOCKS_PER_PROGRAM, |
| 117 | + ) |
| 118 | + |
| 119 | + |
| 120 | +@triton.jit |
| 121 | +def _swiglu_quantize_mxfp8_kernel( |
| 122 | + preactivation_ptr, |
| 123 | + quantized_ptr, |
| 124 | + scale_ptr, |
| 125 | + row_count, |
| 126 | + column_count, |
| 127 | + scale_column_count, |
| 128 | + BLOCKS_PER_PROGRAM: tl.constexpr, |
| 129 | +): |
| 130 | + """Apply value-first SwiGLU and directly quantize its BF16 result.""" |
| 131 | + row = tl.program_id(0) |
| 132 | + first_block = tl.program_id(1) * BLOCKS_PER_PROGRAM |
| 133 | + element_offsets = tl.arange(0, BLOCKS_PER_PROGRAM * 32) |
| 134 | + columns = first_block * 32 + element_offsets |
| 135 | + valid_elements = (row < row_count) & (columns < column_count) |
| 136 | + packed_row_offset = row * (2 * column_count) |
| 137 | + values = tl.load( |
| 138 | + preactivation_ptr + packed_row_offset + columns, |
| 139 | + mask=valid_elements, |
| 140 | + other=0.0, |
| 141 | + ).to(tl.float32) |
| 142 | + gates = tl.load( |
| 143 | + preactivation_ptr + packed_row_offset + column_count + columns, |
| 144 | + mask=valid_elements, |
| 145 | + other=0.0, |
| 146 | + ).to(tl.float32) |
| 147 | + postactivation = (values * gates * tl.sigmoid(gates)).to(tl.bfloat16).to(tl.float32) |
| 148 | + _store_mxfp8_blocks( |
| 149 | + postactivation, |
| 150 | + quantized_ptr, |
| 151 | + scale_ptr, |
| 152 | + row, |
| 153 | + first_block, |
| 154 | + row_count, |
| 155 | + column_count, |
| 156 | + scale_column_count, |
| 157 | + BLOCKS_PER_PROGRAM, |
| 158 | + ) |
| 159 | + |
| 160 | + |
| 161 | +def _allocate_mxfp8_outputs(matrix: torch.Tensor, column_count: int) -> tuple[torch.Tensor, torch.Tensor, int]: |
| 162 | + """Allocate contiguous values and a complete padded blocked-scale buffer.""" |
| 163 | + row_count = matrix.shape[0] |
| 164 | + scale_column_count = triton.cdiv(column_count // MXFP8_BLOCK_SIZE, 4) * 4 |
| 165 | + blocked_scale_size = triton.cdiv(row_count, 128) * 128 * scale_column_count |
| 166 | + quantized = torch.empty((row_count, column_count), dtype=torch.float8_e4m3fn, device=matrix.device) |
| 167 | + scale_storage = torch.empty(blocked_scale_size, dtype=torch.uint8, device=matrix.device) |
| 168 | + return quantized, scale_storage.view(torch.float8_e8m0fnu), scale_column_count |
| 169 | + |
| 170 | + |
| 171 | +def quantize_mxfp8_blockwise(matrix: torch.Tensor) -> tuple[torch.Tensor, torch.Tensor]: |
| 172 | + """Quantize activation rows and write hardware-blocked scales directly.""" |
| 173 | + _validate_mxfp8_matrix(matrix) |
| 174 | + matrix = matrix.contiguous() |
| 175 | + row_count, column_count = matrix.shape |
| 176 | + quantized, blocked_scales, scale_column_count = _allocate_mxfp8_outputs(matrix, column_count) |
| 177 | + grid = (triton.cdiv(row_count, 128) * 128, triton.cdiv(scale_column_count, _BLOCKS_PER_PROGRAM)) |
| 178 | + _quantize_mxfp8_kernel[grid]( |
| 179 | + matrix, |
| 180 | + quantized, |
| 181 | + blocked_scales.view(torch.uint8), |
| 182 | + row_count, |
| 183 | + column_count, |
| 184 | + scale_column_count, |
| 185 | + BLOCKS_PER_PROGRAM=_BLOCKS_PER_PROGRAM, |
| 186 | + num_warps=8, |
| 187 | + ) |
| 188 | + return quantized, blocked_scales |
| 189 | + |
| 190 | + |
| 191 | +def swiglu_quantize_mxfp8_blockwise(preactivation: torch.Tensor) -> tuple[torch.Tensor, torch.Tensor]: |
| 192 | + """Apply H3 value-first SwiGLU and quantize the BF16 result to MXFP8.""" |
| 193 | + if preactivation.ndim != 2: |
| 194 | + raise ValueError(f"MXFP8 SwiGLU requires a 2D tensor, got shape {tuple(preactivation.shape)}.") |
| 195 | + if preactivation.dtype != torch.bfloat16: |
| 196 | + raise TypeError(f"MXFP8 SwiGLU requires BF16 input, got {preactivation.dtype}.") |
| 197 | + if preactivation.shape[1] % (2 * MXFP8_BLOCK_SIZE): |
| 198 | + raise ValueError( |
| 199 | + "MXFP8 SwiGLU requires each packed half to be divisible by " |
| 200 | + f"{MXFP8_BLOCK_SIZE}, got packed width {preactivation.shape[1]}.") |
| 201 | + preactivation = preactivation.contiguous() |
| 202 | + row_count = preactivation.shape[0] |
| 203 | + column_count = preactivation.shape[1] // 2 |
| 204 | + quantized, blocked_scales, scale_column_count = _allocate_mxfp8_outputs(preactivation, column_count) |
| 205 | + grid = (triton.cdiv(row_count, 128) * 128, triton.cdiv(scale_column_count, _BLOCKS_PER_PROGRAM)) |
| 206 | + _swiglu_quantize_mxfp8_kernel[grid]( |
| 207 | + preactivation, |
| 208 | + quantized, |
| 209 | + blocked_scales.view(torch.uint8), |
| 210 | + row_count, |
| 211 | + column_count, |
| 212 | + scale_column_count, |
| 213 | + BLOCKS_PER_PROGRAM=_BLOCKS_PER_PROGRAM, |
| 214 | + num_warps=8, |
| 215 | + ) |
| 216 | + return quantized, blocked_scales |
| 217 | + |
| 218 | + |
| 219 | +def mxfp8_scaled_mm( |
| 220 | + activation_values: torch.Tensor, |
| 221 | + activation_scales: torch.Tensor, |
| 222 | + weight_values: torch.Tensor, |
| 223 | + weight_scales: torch.Tensor, |
| 224 | + bias: torch.Tensor | None, |
| 225 | +) -> torch.Tensor: |
| 226 | + """Multiply two MXFP8 matrices and return a BF16 matrix.""" |
| 227 | + return F.scaled_mm( |
| 228 | + mat_a=activation_values, |
| 229 | + mat_b=weight_values.mT, |
| 230 | + scale_a=activation_scales, |
| 231 | + scale_recipe_a=F.ScalingType.BlockWise1x32, |
| 232 | + scale_b=weight_scales, |
| 233 | + scale_recipe_b=F.ScalingType.BlockWise1x32, |
| 234 | + swizzle_a=F.SwizzleType.SWIZZLE_32_4_4, |
| 235 | + swizzle_b=F.SwizzleType.SWIZZLE_32_4_4, |
| 236 | + bias=bias, |
| 237 | + output_dtype=torch.bfloat16, |
| 238 | + ) |
| 239 | + |
| 240 | + |
| 241 | +def _resolve_merged_linear(linear: torch.nn.Module) -> torch.nn.Module: |
| 242 | + """Return a linear whose weight includes every active inference adapter.""" |
| 243 | + base_layer = getattr(linear, "base_layer", linear) |
| 244 | + if base_layer is linear: |
| 245 | + return base_layer |
| 246 | + if not getattr(linear, "merged", False) and not getattr(linear, "disable_lora", False): |
| 247 | + raise RuntimeError("MXFP8 feed-forward requires active LoRA weights to be merged before inference.") |
| 248 | + return base_layer |
| 249 | + |
| 250 | + |
| 251 | +def mxfp8_swiglu_feed_forward( |
| 252 | + hidden_states: torch.Tensor, |
| 253 | + fc_in: torch.nn.Module, |
| 254 | + fc_out: torch.nn.Module, |
| 255 | +) -> torch.Tensor: |
| 256 | + """Run the MiniMax-H3 feed-forward network with MXFP8 GEMMs.""" |
| 257 | + fc_in_base = _resolve_merged_linear(fc_in) |
| 258 | + fc_out_base = _resolve_merged_linear(fc_out) |
| 259 | + fc_in_method = fc_in_base.quant_method |
| 260 | + fc_out_method = fc_out_base.quant_method |
| 261 | + |
| 262 | + preactivation = fc_in_method.apply(fc_in_base, hidden_states, fc_in_base.bias) |
| 263 | + output_shape = (*hidden_states.shape[:-1], fc_out_base.output_size) |
| 264 | + preactivation_2d = preactivation.reshape(-1, preactivation.shape[-1]) |
| 265 | + activation_values, activation_scales = swiglu_quantize_mxfp8_blockwise(preactivation_2d) |
| 266 | + output_2d = fc_out_method.apply_quantized( |
| 267 | + fc_out_base, |
| 268 | + activation_values, |
| 269 | + activation_scales, |
| 270 | + fc_out_base.bias, |
| 271 | + ) |
| 272 | + return output_2d.reshape(output_shape) |
| 273 | + |
| 274 | + |
| 275 | +__all__ = [ |
| 276 | + "MXFP8_BLOCK_SIZE", |
| 277 | + "mxfp8_scaled_mm", |
| 278 | + "mxfp8_swiglu_feed_forward", |
| 279 | + "quantize_mxfp8_blockwise", |
| 280 | + "quantize_mxfp8_weight_blockwise", |
| 281 | + "swiglu_quantize_mxfp8_blockwise", |
| 282 | +] |
0 commit comments