| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| """ |
| Pure PyTorch implementation of FP8 block-wise GEMM. |
| |
| This module provides CPU/Windows-compatible implementations that mirror |
| the Triton kernel for FP8 GEMM with block-wise quantization. |
| """ |
|
|
| from typing import Optional |
|
|
| import torch |
|
|
|
|
| def fp8_gemm_torch_block( |
| a: torch.Tensor, |
| a_s: torch.Tensor, |
| b: torch.Tensor, |
| b_s: torch.Tensor, |
| out_dtype: torch.dtype = torch.bfloat16, |
| bias: Optional[torch.Tensor] = None, |
| block_size: int = 128, |
| ) -> torch.Tensor: |
| """ |
| Pure PyTorch implementation of FP8 GEMM with block-wise quantization. |
| |
| Performs a matrix multiplication using FP8 precision with per-block scaling. |
| This implementation dequantizes the inputs and performs standard matmul. |
| |
| C = (A * A_scale) @ (B * B_scale).T + bias |
| |
| Args: |
| a: Input activation tensor in FP8 format, shape [..., K] |
| a_s: Scale tensor for A, shape [..., K // block_size] or [..., num_k_blocks] |
| b: Weight tensor in FP8 format, shape [N, K] |
| b_s: Scale tensor for B, shape [N // block_size, K // block_size] |
| out_dtype: Output data type (default: bfloat16) |
| bias: Optional bias tensor, shape [N] |
| block_size: Block size used for quantization (default: 128) |
| |
| Returns: |
| Output tensor of shape [..., N] |
| """ |
| assert a.is_contiguous() and b.is_contiguous() |
| assert a_s.is_contiguous() and b_s.is_contiguous() |
|
|
| K = a.size(-1) |
| orig_shape = a.shape[:-1] |
| M = a.numel() // K |
| N = b.size(0) |
|
|
| |
| a_2d = a.view(M, K) |
|
|
| |
| |
| a_s_2d = a_s.view(M, -1) |
|
|
| |
| a_dq = _dequantize_per_group(a_2d, a_s_2d, block_size, K) |
|
|
| |
| b_dq = _dequantize_blockwise_2d(b, b_s, block_size) |
|
|
| |
| c = torch.matmul(a_dq.to(out_dtype), b_dq.to(out_dtype).t()) |
|
|
| |
| c = c.view(*orig_shape, N) |
|
|
| if bias is not None: |
| c = c + bias |
|
|
| return c |
|
|
|
|
| def _dequantize_per_group( |
| x: torch.Tensor, |
| s: torch.Tensor, |
| group_size: int, |
| K: int, |
| ) -> torch.Tensor: |
| """ |
| Dequantize tensor with per-group scales. |
| |
| Args: |
| x: Quantized tensor [M, K] |
| s: Scale tensor [M, num_groups] |
| group_size: Size of each group |
| K: Total size of last dimension |
| |
| Returns: |
| Dequantized tensor [M, K] |
| """ |
| M = x.shape[0] |
| num_groups = s.shape[1] |
|
|
| x_float = x.to(torch.float32) |
|
|
| |
| |
| s_expanded = s.unsqueeze(-1).expand(M, num_groups, group_size) |
| s_expanded = s_expanded.reshape(M, num_groups * group_size) |
|
|
| |
| if s_expanded.shape[1] > K: |
| s_expanded = s_expanded[:, :K] |
| elif s_expanded.shape[1] < K: |
| |
| pad_size = K - s_expanded.shape[1] |
| s_expanded = torch.nn.functional.pad(s_expanded, (0, pad_size), mode="replicate") |
|
|
| return x_float * s_expanded |
|
|
|
|
| def _dequantize_blockwise_2d( |
| x: torch.Tensor, |
| s: torch.Tensor, |
| block_size: int, |
| ) -> torch.Tensor: |
| """ |
| Dequantize 2D tensor with block-wise scales. |
| |
| Args: |
| x: Quantized tensor [N, K] |
| s: Scale tensor [n_blocks, k_blocks] |
| block_size: Block size |
| |
| Returns: |
| Dequantized tensor [N, K] |
| """ |
| N, K = x.shape |
| n_blocks, k_blocks = s.shape |
|
|
| x_float = x.to(torch.float32) |
| y = torch.empty_like(x_float) |
|
|
| for nb in range(n_blocks): |
| n_start = nb * block_size |
| n_end = min(n_start + block_size, N) |
| for kb in range(k_blocks): |
| k_start = kb * block_size |
| k_end = min(k_start + block_size, K) |
|
|
| scale = s[nb, kb] |
| y[n_start:n_end, k_start:k_end] = x_float[n_start:n_end, k_start:k_end] * scale |
|
|
| return y |
|
|
|
|
| def fp8_gemm_torch_simple( |
| a: torch.Tensor, |
| a_s: torch.Tensor, |
| b: torch.Tensor, |
| b_s: torch.Tensor, |
| out_dtype: torch.dtype = torch.bfloat16, |
| bias: Optional[torch.Tensor] = None, |
| ) -> torch.Tensor: |
| """ |
| Simplified PyTorch FP8 GEMM using full dequantization. |
| |
| This version is simpler but may use more memory for large tensors. |
| |
| Args: |
| a: Input activation tensor in FP8 format |
| a_s: Scale tensor for A |
| b: Weight tensor in FP8 format |
| b_s: Scale tensor for B |
| out_dtype: Output data type |
| bias: Optional bias tensor |
| |
| Returns: |
| Output tensor |
| """ |
| K = a.size(-1) |
| orig_shape = a.shape[:-1] |
| M = a.numel() // K |
| N = b.size(0) |
|
|
| |
| a_2d = a.view(M, K) |
| a_s_2d = a_s.view(M, -1) |
|
|
| |
| block_size = K // a_s_2d.shape[1] if a_s_2d.shape[1] > 0 else K |
|
|
| |
| a_dq = a_2d.to(torch.float32) |
| if a_s_2d.shape[1] > 1: |
| a_s_expanded = a_s_2d.repeat_interleave(block_size, dim=1)[:, :K] |
| a_dq = a_dq * a_s_expanded |
| else: |
| a_dq = a_dq * a_s_2d |
|
|
| |
| b_dq = _dequantize_blockwise_2d(b, b_s, block_size) |
|
|
| |
| c = torch.matmul(a_dq.to(out_dtype), b_dq.to(out_dtype).t()) |
| c = c.view(*orig_shape, N) |
|
|
| if bias is not None: |
| c = c + bias |
|
|
| return c |
|
|