fp8-gemm / torch-ext /fp8_gemm /__init__.py
liangsu9988's picture
Publish PI0.5 Thor kernels from 503100c (torch213-cxx11-cu130-aarch64-linux)
9022070 verified
Raw
History Blame
11.9 kB
"""FlashRT FP8 GEMM kernels."""
from __future__ import annotations
import torch
from ._ops import add_op_namespace_prefix, ops
@torch.library.register_fake(add_op_namespace_prefix("fp8_linear_bf16"))
def _fp8_linear_bf16_fake(
input: torch.Tensor,
weight: torch.Tensor,
alpha: float,
variant: int,
out: torch.Tensor,
) -> None:
if input.dim() != 2 or weight.dim() != 2:
raise RuntimeError("input and weight must be rank-2 tensors")
if out.shape != (input.shape[0], weight.shape[0]):
raise RuntimeError("out must have shape (input.shape[0], weight.shape[0])")
return None
@torch.library.register_fake(add_op_namespace_prefix("fp8_linear_residual_bf16"))
def _fp8_linear_residual_bf16_fake(
input: torch.Tensor,
weight: torch.Tensor,
alpha: float,
variant: int,
residual: torch.Tensor,
) -> None:
if input.shape[0] != 1:
raise RuntimeError("residual path supports only M=1")
if residual.shape != (1, weight.shape[0]):
raise RuntimeError("residual must have shape (1, weight.shape[0])")
return None
def _check_bias_linear_shapes(input, weight, bias, out) -> None:
if input.dim() != 2 or weight.dim() != 2:
raise RuntimeError("input and weight must be rank-2 tensors")
if input.shape[1] != weight.shape[1]:
raise RuntimeError("input and weight K dimensions must match")
if bias.shape != (weight.shape[0],):
raise RuntimeError("bias must have shape (weight.shape[0],)")
if out.shape != (input.shape[0], weight.shape[0]):
raise RuntimeError("out must have shape (input.shape[0], weight.shape[0])")
@torch.library.register_fake(add_op_namespace_prefix("fp8_linear_bias_bf16"))
def _fp8_linear_bias_bf16_fake(input, weight, bias, alpha: float, out) -> None:
_check_bias_linear_shapes(input, weight, bias, out)
@torch.library.register_fake(add_op_namespace_prefix("fp8_linear_bias_residual_bf16"))
def _fp8_linear_bias_residual_bf16_fake(
input, weight, bias, alpha: float, residual
) -> None:
_check_bias_linear_shapes(input, weight, bias, residual)
@torch.library.register_fake(add_op_namespace_prefix("fp8_linear_bias_gelu_bf16"))
def _fp8_linear_bias_gelu_bf16_fake(input, weight, bias, alpha: float, out) -> None:
_check_bias_linear_shapes(input, weight, bias, out)
@torch.library.register_fake(add_op_namespace_prefix("fp8_blockwise_linear_bf16"))
def _fp8_blockwise_linear_bf16_fake(
input: torch.Tensor,
weight: torch.Tensor,
input_scale: torch.Tensor,
weight_scale: torch.Tensor,
out: torch.Tensor,
) -> None:
if input.dim() != 2 or weight.dim() != 2:
raise RuntimeError("input and weight must be rank-2 tensors")
m, k = input.shape
n = weight.shape[0]
if weight.shape[1] != k or n % 128 or k % 128:
raise RuntimeError("weight shape is invalid or N/K are not divisible by 128")
if input_scale.shape != (m, k // 128):
raise RuntimeError("input_scale must have shape (M, K / 128)")
if weight_scale.shape != (n // 128, k // 128):
raise RuntimeError("weight_scale must have shape (N / 128, K / 128)")
if out.shape != (m, n):
raise RuntimeError("out must have shape (M, N)")
return None
@torch.library.register_fake(
add_op_namespace_prefix("fp8_blockwise_swiglu_quantize_fp8")
)
def _fp8_blockwise_swiglu_quantize_fp8_fake(
input: torch.Tensor,
gate_up_weight: torch.Tensor,
input_scale: torch.Tensor,
gate_up_weight_scale: torch.Tensor,
output: torch.Tensor,
output_scale: torch.Tensor,
) -> None:
m, k = input.shape
if gate_up_weight.dim() != 2 or gate_up_weight.shape[0] % 2:
raise RuntimeError("gate_up_weight must have shape (2*N, K)")
n = gate_up_weight.shape[0] // 2
if gate_up_weight.shape[1] != k or n % 128 or k % 128:
raise RuntimeError("gate_up_weight shape is invalid or N/K are not divisible by 128")
if input_scale.shape != (m, k // 128):
raise RuntimeError("input_scale must have shape (M, K/128)")
if gate_up_weight_scale.shape != (2 * n // 128, k // 128):
raise RuntimeError("gate_up_weight_scale must have shape (2*N/128, K/128)")
if output.shape != (m, n) or output_scale.shape != (m, n // 128):
raise RuntimeError("output buffers have invalid shapes")
return None
def select_fp8_linear_tile(m: int, n: int, k: int, variant: int = 0) -> str:
"""Return the FlashRT tile selected by the public dispatcher."""
m = int(m)
n = int(n)
k = int(k)
variant = int(variant)
if m <= 0 or n <= 0 or k <= 0:
raise RuntimeError("m, n, and k must be positive")
if k % 16 != 0:
raise RuntimeError("k must be divisible by 16")
capability = torch.cuda.get_device_capability() if torch.cuda.is_available() else None
if capability == (11, 0):
forced = {1: "sm110_sq_bf16", 2: "sm110_t1_bf16", 3: "sm110_wide_bf16"}
if variant not in {0, *forced}:
raise RuntimeError("SM110 variant must be 0 (auto), 1 (Sq), 2 (T1), or 3 (Wide)")
if n % 16 or k % 16:
raise RuntimeError("SM110 requires n and k divisible by 16")
if variant:
return forced[variant]
if m >= 512 and k == 2048 and 2048 <= n <= 2560:
return "sm110_sq_bf16"
if m >= 512 and n >= 16 * k:
return "sm110_t1_bf16"
if m >= 512 and k >= 4 * n:
return "sm110_wide_bf16"
if n >= 8 * k:
return "sm110_wide_bf16"
if m >= 128 and k >= 4 * n:
return "sm110_sq_bf16"
if n == k and m >= 512:
return "sm110_sq_bf16" if k <= 1024 else "sm110_wide_bf16"
if n == k and m >= 128:
return "sm110_wide_bf16"
return "sm110_t1_bf16"
if m == 1:
if k % 32:
raise RuntimeError("SM120 requires k divisible by 32")
if variant == 4:
return "gemv_fp8_m1_w4"
if variant == 8:
return "gemv_fp8_m1_w8"
if variant == 16:
return "gemv_fp8_m1_w16"
if variant != 0:
raise RuntimeError("M=1 variant must be 0, 4, 8, or 16")
if n <= 2048:
return "gemv_fp8_m1_w4"
if n <= 8192:
return "gemv_fp8_m1_w8"
return "gemv_fp8_m1_w16"
if variant != 0:
raise RuntimeError("small-M dispatcher currently supports variant=0 only")
if k % 32:
raise RuntimeError("SM120 requires k divisible by 32")
if m <= 16:
if k % 256 == 0:
return "ld_fp8_gemm_16x128x256_w4" if n % 128 == 0 else "ld_fp8_gemm_16x64x256_w4"
if n % 256 == 0:
return "ld_fp8_gemm_16x256x128_w8"
if n % 192 == 0:
return "ld_fp8_gemm_16x192x128_w4"
if n % 128 == 0:
return "ld_fp8_gemm_16x128x128_w4"
return "ld_fp8_gemm_16x64x128_w4"
if m <= 32:
if k % 256 == 0:
return "ld_fp8_gemm_32x128x256_w4" if n % 128 == 0 else "ld_fp8_gemm_32x64x256_w4"
if n % 192 == 0:
return "ld_fp8_gemm_32x192x128_w4"
if n % 128 == 0:
return "ld_fp8_gemm_32x128x128_w4"
return "ld_fp8_gemm_32x64x128_w4"
if m <= 64:
if k % 256 == 0:
return "ld_fp8_gemm_64x128x256_w4" if n % 128 == 0 else "ld_fp8_gemm_64x64x256_w4"
if n % 128 == 0:
return "ld_fp8_gemm_64x128x128_w4"
return "ld_fp8_gemm_64x64x128_w4"
raise RuntimeError("only M=1 decode or 2 <= M <= 64 small-M rows are supported")
def fp8_linear_bf16(
input: torch.Tensor,
weight: torch.Tensor,
alpha: float = 1.0,
out: torch.Tensor | None = None,
variant: int = 0,
) -> torch.Tensor:
"""Compute ``(input @ weight.T) * alpha`` with BF16 output.
``input`` and ``weight`` must be FP8 E4M3 CUDA tensors with shapes
``(M, K)`` and ``(N, K)``. ``alpha`` is a host float, normally the product
of static per-tensor input and weight scales. SM110 uses the production
CUTLASS Sq/T1/Wide dispatcher over full model row counts; SM120 uses the
hand-tuned M<=64 path.
"""
if out is None:
out = torch.empty(
(input.shape[0], weight.shape[0]),
device=input.device,
dtype=torch.bfloat16,
)
ops.fp8_linear_bf16(input, weight, float(alpha), int(variant), out)
return out
def fp8_linear_residual_bf16(
input: torch.Tensor,
weight: torch.Tensor,
residual: torch.Tensor,
alpha: float = 1.0,
variant: int = 0,
) -> torch.Tensor:
"""In-place ``residual += (input @ weight.T) * alpha`` for M=1 decode."""
ops.fp8_linear_residual_bf16(input, weight, float(alpha), int(variant), residual)
return residual
def fp8_linear_bias_bf16(
input: torch.Tensor,
weight: torch.Tensor,
bias: torch.Tensor,
alpha: float = 1.0,
out: torch.Tensor | None = None,
) -> torch.Tensor:
"""SM110 FP8 linear with fused BF16 bias and BF16 output."""
if out is None:
out = torch.empty(
(input.shape[0], weight.shape[0]),
device=input.device,
dtype=torch.bfloat16,
)
ops.fp8_linear_bias_bf16(input, weight, bias, float(alpha), out)
return out
def fp8_linear_bias_residual_bf16(
input: torch.Tensor,
weight: torch.Tensor,
bias: torch.Tensor,
residual: torch.Tensor,
alpha: float = 1.0,
) -> torch.Tensor:
"""SM110 fused ``residual += alpha * input @ weight.T + bias``."""
ops.fp8_linear_bias_residual_bf16(
input, weight, bias, float(alpha), residual
)
return residual
def fp8_linear_bias_gelu_bf16(
input: torch.Tensor,
weight: torch.Tensor,
bias: torch.Tensor,
alpha: float = 1.0,
out: torch.Tensor | None = None,
) -> torch.Tensor:
"""SM110 FP8 linear with fused BF16 bias and GELU epilogue."""
if out is None:
out = torch.empty(
(input.shape[0], weight.shape[0]),
device=input.device,
dtype=torch.bfloat16,
)
ops.fp8_linear_bias_gelu_bf16(input, weight, bias, float(alpha), out)
return out
def fp8_blockwise_linear_bf16(
input: torch.Tensor,
weight: torch.Tensor,
input_scale: torch.Tensor,
weight_scale: torch.Tensor,
out: torch.Tensor | None = None,
) -> torch.Tensor:
"""Block-128 scaled FP8 linear with BF16 output on SM89/SM120."""
if out is None:
out = torch.empty(
(input.shape[0], weight.shape[0]),
device=input.device,
dtype=torch.bfloat16,
)
ops.fp8_blockwise_linear_bf16(
input, weight, input_scale, weight_scale, out
)
return out
def fp8_blockwise_swiglu_quantize_fp8(
input: torch.Tensor,
gate_up_weight: torch.Tensor,
input_scale: torch.Tensor,
gate_up_weight_scale: torch.Tensor,
*,
output: torch.Tensor | None = None,
output_scale: torch.Tensor | None = None,
) -> tuple[torch.Tensor, torch.Tensor]:
"""SM89 block-128 FP8 gate/up GEMM + SiLU + FP8 requant producer."""
n = gate_up_weight.shape[0] // 2
if output is None:
output = torch.empty(
(input.shape[0], n), device=input.device, dtype=torch.float8_e4m3fn
)
if output_scale is None:
output_scale = torch.empty(
(input.shape[0], n // 128), device=input.device, dtype=torch.float32
)
ops.fp8_blockwise_swiglu_quantize_fp8(
input, gate_up_weight, input_scale, gate_up_weight_scale,
output, output_scale
)
return output, output_scale
__all__ = [
"fp8_linear_bf16",
"fp8_linear_residual_bf16",
"fp8_linear_bias_bf16",
"fp8_linear_bias_residual_bf16",
"fp8_linear_bias_gelu_bf16",
"fp8_blockwise_linear_bf16",
"fp8_blockwise_swiglu_quantize_fp8",
"select_fp8_linear_tile",
]