fp8-gemm / torch-ext /torch_binding.cpp
liangsu9988's picture
Publish PI0.5 Thor kernels from 503100c (torch213-cxx11-cu130-aarch64-linux)
9022070 verified
Raw
History Blame
24.4 kB
// SPDX-License-Identifier: Apache-2.0
#include <torch/all.h>
#include <torch/library.h>
#include <limits>
#include <sstream>
#include <string>
#if defined(CUDA_KERNEL)
#include <ATen/cuda/CUDAContext.h>
#include <c10/cuda/CUDAGuard.h>
#endif
#if !defined(FLASHRT_FP8_GEMM_SOURCE_SM89_ONLY) && \
!defined(FLASHRT_FP8_GEMM_SOURCE_SM110_ONLY)
#include "fp8_gemv_m1_sm120.cuh"
#include "fp8_smallM_handtuned_ldmatrix_sm120.cuh"
#include "fp8_smallM_handtuned_sm120.cuh"
#include "cutlass_sm120_block128_fp8_gemm.cuh"
#endif
#if !defined(FLASHRT_FP8_GEMM_SOURCE_SM120_ONLY) && \
!defined(FLASHRT_FP8_GEMM_SOURCE_SM110_ONLY)
#include "fp8_block128_gemm_mma_sm89.cuh"
#include "fp8_gemv_m1_sm89.cuh"
#endif
#if !defined(FLASHRT_FP8_GEMM_SOURCE_SM89_ONLY) && \
!defined(FLASHRT_FP8_GEMM_SOURCE_SM120_ONLY)
#include "cutlass_sm110_fp8_gemm.cuh"
#endif
#include "cublaslt_fp8_bias_sm110.cuh"
#include "registration.h"
#include "torch_binding.h"
namespace {
using KernelFn = int (*)(const void*, const void*, void*, int, int, int, float, cudaStream_t);
using Sm110KernelFn = int (*)(void*, void*, void*, int, int, int, float, float,
cudaStream_t);
void check_cuda_contiguous(torch::Tensor const& tensor, const char* name) {
TORCH_CHECK(tensor.is_cuda(), name, " must be a CUDA tensor");
TORCH_CHECK(tensor.is_contiguous(), name, " must be contiguous");
}
void check_fp8_matrix(torch::Tensor const& tensor, const char* name) {
check_cuda_contiguous(tensor, name);
TORCH_CHECK(tensor.scalar_type() == c10::ScalarType::Float8_e4m3fn,
name, " must have dtype torch.float8_e4m3fn");
TORCH_CHECK(tensor.dim() == 2, name, " must have shape (rows, cols)");
TORCH_CHECK(tensor.size(0) > 0 && tensor.size(1) > 0,
name, " dimensions must be positive");
}
void check_bf16_matrix(torch::Tensor const& tensor, const char* name) {
check_cuda_contiguous(tensor, name);
TORCH_CHECK(tensor.scalar_type() == torch::kBFloat16,
name, " must have dtype torch.bfloat16");
TORCH_CHECK(tensor.dim() == 2, name, " must have shape (rows, cols)");
}
void check_fp32_matrix(torch::Tensor const& tensor, const char* name) {
check_cuda_contiguous(tensor, name);
TORCH_CHECK(tensor.scalar_type() == torch::kFloat32,
name, " must have dtype torch.float32");
TORCH_CHECK(tensor.dim() == 2, name, " must be rank 2");
}
void check_bf16_vector(torch::Tensor const& tensor, const char* name) {
check_cuda_contiguous(tensor, name);
TORCH_CHECK(tensor.scalar_type() == torch::kBFloat16,
name, " must have dtype torch.bfloat16");
TORCH_CHECK(tensor.dim() == 1, name, " must be rank 1");
}
int checked_positive_int(int64_t value, const char* name) {
TORCH_CHECK(value > 0 && value <= std::numeric_limits<int>::max(),
name, " must fit in positive int");
return static_cast<int>(value);
}
void check_common(
torch::Tensor const& input,
torch::Tensor const& weight,
torch::Tensor const& out) {
check_fp8_matrix(input, "input");
check_fp8_matrix(weight, "weight");
check_bf16_matrix(out, "out");
TORCH_CHECK(input.get_device() == weight.get_device(),
"input and weight must be on the same CUDA device");
TORCH_CHECK(input.get_device() == out.get_device(),
"input and out must be on the same CUDA device");
TORCH_CHECK(input.size(1) == weight.size(1),
"input.shape[1] must equal weight.shape[1]");
TORCH_CHECK(out.sizes() == torch::IntArrayRef({input.size(0), weight.size(0)}),
"out must have shape (input.shape[0], weight.shape[0])");
TORCH_CHECK(input.size(1) % 16 == 0,
"K must be divisible by 16 for FP8 tensor-core kernels");
}
std::string tile_name_for_shape(int M, int N, int K, int variant) {
if (M == 1) {
if (variant == 4) return "gemv_fp8_m1_w4";
if (variant == 8) return "gemv_fp8_m1_w8";
if (variant == 16) return "gemv_fp8_m1_w16";
TORCH_CHECK(variant == 0, "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";
}
TORCH_CHECK(variant == 0,
"small-M public dispatcher currently supports variant=0 only; "
"use benchmark scripts for tile sweeps before promoting a forced variant");
if (M <= 16) {
if (K % 256 == 0) {
if (N % 128 == 0) return "ld_fp8_gemm_16x128x256_w4";
return "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) {
if (N % 128 == 0) return "ld_fp8_gemm_32x128x256_w4";
return "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) {
if (N % 128 == 0) return "ld_fp8_gemm_64x128x256_w4";
return "ld_fp8_gemm_64x64x256_w4";
}
if (N % 128 == 0) return "ld_fp8_gemm_64x128x128_w4";
return "ld_fp8_gemm_64x64x128_w4";
}
TORCH_CHECK(false, "M > 64 is not exposed in fp8-gemm v1; pending tile tuning");
TORCH_CHECK(false, "unsupported M");
}
KernelFn kernel_for_tile(std::string const& tile, bool residual) {
#if defined(CUDA_KERNEL) && !defined(FLASHRT_FP8_GEMM_SOURCE_SM89_ONLY) && \
!defined(FLASHRT_FP8_GEMM_SOURCE_SM110_ONLY)
namespace gemv = flash_rt::gemm::gemv_m1;
namespace hand = flash_rt::gemm::smallM_hand;
namespace ld = flash_rt::gemm::smallM_ld;
if (tile == "gemv_fp8_m1_w4") return residual ? gemv::gemv_fp8_m1_resadd_w4 : gemv::gemv_fp8_m1_w4;
if (tile == "gemv_fp8_m1_w8") return residual ? gemv::gemv_fp8_m1_resadd_w8 : gemv::gemv_fp8_m1_w8;
if (tile == "gemv_fp8_m1_w16") {
TORCH_CHECK(!residual, "residual path supports only GEMV w4/w8 variants");
return gemv::gemv_fp8_m1_w16;
}
TORCH_CHECK(!residual, "residual path supports M=1 GEMV only");
if (tile == "ld_fp8_gemm_16x64x128_w4") return ld::ld_fp8_gemm_16x64x128_w4;
if (tile == "ld_fp8_gemm_16x128x128_w4") return ld::ld_fp8_gemm_16x128x128_w4;
if (tile == "ld_fp8_gemm_16x256x128_w8") return ld::ld_fp8_gemm_16x256x128_w8;
if (tile == "ld_fp8_gemm_16x192x128_w4") return ld::ld_fp8_gemm_16x192x128_w4;
if (tile == "ld_fp8_gemm_16x64x256_w4") return ld::ld_fp8_gemm_16x64x256_w4;
if (tile == "ld_fp8_gemm_16x128x256_w4") return ld::ld_fp8_gemm_16x128x256_w4;
if (tile == "ld_fp8_gemm_32x64x128_w4") return ld::ld_fp8_gemm_32x64x128_w4;
if (tile == "ld_fp8_gemm_32x128x128_w4") return ld::ld_fp8_gemm_32x128x128_w4;
if (tile == "ld_fp8_gemm_32x192x128_w4") return ld::ld_fp8_gemm_32x192x128_w4;
if (tile == "ld_fp8_gemm_32x64x256_w4") return ld::ld_fp8_gemm_32x64x256_w4;
if (tile == "ld_fp8_gemm_32x128x256_w4") return ld::ld_fp8_gemm_32x128x256_w4;
if (tile == "ld_fp8_gemm_64x64x128_w4") return ld::ld_fp8_gemm_64x64x128_w4;
if (tile == "ld_fp8_gemm_64x128x128_w4") return ld::ld_fp8_gemm_64x128x128_w4;
if (tile == "ld_fp8_gemm_64x64x256_w4") return ld::ld_fp8_gemm_64x64x256_w4;
if (tile == "ld_fp8_gemm_64x128x256_w4") return ld::ld_fp8_gemm_64x128x256_w4;
#else
(void)tile;
(void)residual;
#endif
TORCH_CHECK(false, "unsupported FP8 GEMM tile: ", tile);
}
const char* sm110_tile_name_for_shape(int M, int N, int K, int variant) {
if (variant == 1) return "sm110_sq_bf16";
if (variant == 2) return "sm110_t1_bf16";
if (variant == 3) return "sm110_wide_bf16";
// Thor sweep envelope (PI0.5/GROOT/Cosmos Edge/LingBot): Wide wins
// N>=8K expansions and larger square projections. Sq wins smaller square
// vision projections and larger-row K>=4N contractions; T1 wins the
// remaining projection/down paths.
// The forced variants remain available for diagnostic tile sweeps.
if (M >= 512 && K == 2048 && N >= 2048 && N <= 2560) {
return "sm110_sq_bf16";
}
if (M >= 512 && N >= 16 * K) {
return "sm110_t1_bf16";
}
if (M >= 512 && K >= 4 * N) {
return "sm110_wide_bf16";
}
if (N >= 8 * K) return "sm110_wide_bf16";
if (M >= 128 && K >= 4 * N) return "sm110_sq_bf16";
if (N == K && M >= 512) {
return K <= 1024 ? "sm110_sq_bf16" : "sm110_wide_bf16";
}
if (N == K && M >= 128) {
return "sm110_wide_bf16";
}
return "sm110_t1_bf16";
}
Sm110KernelFn sm110_kernel_for_shape(int M, int N, int K, int variant) {
#if defined(CUDA_KERNEL) && !defined(FLASHRT_FP8_GEMM_SOURCE_SM89_ONLY) && \
!defined(FLASHRT_FP8_GEMM_SOURCE_SM120_ONLY)
const char* tile = sm110_tile_name_for_shape(M, N, K, variant);
if (std::string(tile) == "sm110_wide_bf16") return &cutlass_fp8_wide_bf16out;
if (std::string(tile) == "sm110_t1_bf16") return &cutlass_fp8_t1_bf16out;
return &cutlass_fp8_sq_bf16out;
#else
(void)M;
(void)N;
(void)K;
(void)variant;
TORCH_CHECK(false, "SM110 FP8 GEMM source is not present in this build");
#endif
}
void launch(
torch::Tensor const& input,
torch::Tensor const& weight,
double alpha,
int64_t variant64,
torch::Tensor& out,
bool residual) {
check_common(input, weight, out);
const int M = checked_positive_int(input.size(0), "M");
const int K = checked_positive_int(input.size(1), "K");
const int N = checked_positive_int(weight.size(0), "N");
const int variant = static_cast<int>(variant64);
if (residual) {
TORCH_CHECK(M == 1, "fp8_linear_residual_bf16 supports only M=1");
}
#if defined(CUDA_KERNEL)
at::cuda::CUDAGuard device_guard(input.device());
auto* props = at::cuda::getDeviceProperties(input.get_device());
TORCH_CHECK((props->major == 11 && props->minor == 0) ||
(props->major == 12 && props->minor == 0),
"fp8_linear_bf16 requires SM110 or SM120; got SM",
props->major, props->minor);
auto stream = at::cuda::getCurrentCUDAStream(input.get_device()).stream();
if (props->major == 11) {
TORCH_CHECK(variant >= 0 && variant <= 3,
"SM110 variant must be 0 (auto), 1 (Sq), 2 (T1), or 3 (Wide)");
TORCH_CHECK(N % 16 == 0 && K % 16 == 0,
"SM110 CUTLASS FP8 GEMM requires N and K divisible by 16");
#if defined(FLASHRT_FP8_GEMM_SOURCE_SM89_ONLY) || \
defined(FLASHRT_FP8_GEMM_SOURCE_SM120_ONLY)
TORCH_CHECK(false, "SM110 FP8 GEMM source is not present in this build");
#else
Sm110KernelFn fn = sm110_kernel_for_shape(M, N, K, variant);
const int rc = fn(input.data_ptr(), weight.data_ptr(), out.data_ptr(),
M, N, K, static_cast<float>(alpha),
residual ? 1.0f : 0.0f, stream);
TORCH_CHECK(rc == 0, sm110_tile_name_for_shape(M, N, K, variant),
" failed with rc=", rc);
#endif
} else {
TORCH_CHECK(K % 32 == 0,
"SM120 FP8 GEMM requires K divisible by 32");
TORCH_CHECK(M <= 64,
"SM120 per-tensor FP8 path supports only M <= 64; got M=", M);
if (residual) {
TORCH_CHECK(M == 1, "SM120 residual path supports only M=1");
}
const std::string tile = tile_name_for_shape(M, N, K, variant);
#if defined(FLASHRT_FP8_GEMM_SOURCE_SM89_ONLY) || \
defined(FLASHRT_FP8_GEMM_SOURCE_SM110_ONLY)
TORCH_CHECK(false, "SM120 per-tensor FP8 source is not present in this build");
#else
KernelFn fn = kernel_for_tile(tile, residual);
const int rc = fn(input.data_ptr(), weight.data_ptr(), out.data_ptr(),
M, N, K, static_cast<float>(alpha), stream);
TORCH_CHECK(rc == 0, tile, " failed with rc=", rc);
#endif
}
#else
TORCH_CHECK(false, "fp8-gemm was not built with CUDA support");
#endif
}
void launch_bias(
torch::Tensor const& input,
torch::Tensor const& weight,
torch::Tensor const& bias,
double alpha,
torch::Tensor& out,
double beta,
FlashRtFp8BiasEpilogue epilogue,
const char* op_name) {
check_common(input, weight, out);
check_bf16_vector(bias, "bias");
TORCH_CHECK(bias.size(0) == weight.size(0),
"bias must have shape (weight.shape[0],)");
TORCH_CHECK(input.get_device() == bias.get_device(),
"input and bias must be on the same CUDA device");
#if defined(CUDA_KERNEL)
at::cuda::CUDAGuard device_guard(input.device());
auto* props = at::cuda::getDeviceProperties(input.get_device());
TORCH_CHECK(props->major == 11 && props->minor == 0,
op_name, " requires SM110; got SM", props->major, props->minor);
#if defined(FLASHRT_FP8_GEMM_SOURCE_SM89_ONLY) || \
defined(FLASHRT_FP8_GEMM_SOURCE_SM120_ONLY)
TORCH_CHECK(false, "SM110 FP8 bias GEMM source is not present in this build");
#else
auto stream = at::cuda::getCurrentCUDAStream(input.get_device()).stream();
const int M = checked_positive_int(input.size(0), "M");
const int N = checked_positive_int(weight.size(0), "N");
const int K = checked_positive_int(input.size(1), "K");
int rc;
if (M >= 512 && K >= 3 * N) {
rc = epilogue == FlashRtFp8BiasEpilogue::kBiasGelu
? cutlass_fp8_wide_bias_gelu_bf16out(
input.data_ptr(), weight.data_ptr(), bias.data_ptr(),
out.data_ptr(), M, N, K, static_cast<float>(alpha), stream)
: cutlass_fp8_wide_bias_bf16out(
input.data_ptr(), weight.data_ptr(), bias.data_ptr(),
out.data_ptr(), M, N, K, static_cast<float>(alpha),
static_cast<float>(beta), stream);
} else {
rc = fp8_linear_bias_sm110_bf16(
input.data_ptr(), weight.data_ptr(), bias.data_ptr(), out.data_ptr(),
M, N, K, static_cast<float>(alpha), static_cast<float>(beta),
epilogue, stream);
}
TORCH_CHECK(rc == 0, op_name, " failed with rc=", rc);
#endif
#else
TORCH_CHECK(false, "fp8-gemm was not built with CUDA support");
#endif
}
} // namespace
void fp8_linear_bf16(
torch::Tensor const& input,
torch::Tensor const& weight,
double alpha,
int64_t variant,
torch::Tensor& out) {
launch(input, weight, alpha, variant, out, false);
}
void fp8_linear_residual_bf16(
torch::Tensor const& input,
torch::Tensor const& weight,
double alpha,
int64_t variant,
torch::Tensor& residual) {
launch(input, weight, alpha, variant, residual, true);
}
void fp8_linear_bias_bf16(
torch::Tensor const& input,
torch::Tensor const& weight,
torch::Tensor const& bias,
double alpha,
torch::Tensor& out) {
launch_bias(input, weight, bias, alpha, out, 0.0,
FlashRtFp8BiasEpilogue::kBias, "fp8_linear_bias_bf16");
}
void fp8_linear_bias_residual_bf16(
torch::Tensor const& input,
torch::Tensor const& weight,
torch::Tensor const& bias,
double alpha,
torch::Tensor& residual) {
launch_bias(input, weight, bias, alpha, residual, 1.0,
FlashRtFp8BiasEpilogue::kBias,
"fp8_linear_bias_residual_bf16");
}
void fp8_linear_bias_gelu_bf16(
torch::Tensor const& input,
torch::Tensor const& weight,
torch::Tensor const& bias,
double alpha,
torch::Tensor& out) {
launch_bias(input, weight, bias, alpha, out, 0.0,
FlashRtFp8BiasEpilogue::kBiasGelu,
"fp8_linear_bias_gelu_bf16");
}
void fp8_blockwise_linear_bf16(
torch::Tensor const& input,
torch::Tensor const& weight,
torch::Tensor const& input_scale,
torch::Tensor const& weight_scale,
torch::Tensor& out) {
check_fp8_matrix(input, "input");
check_fp8_matrix(weight, "weight");
check_fp32_matrix(input_scale, "input_scale");
check_fp32_matrix(weight_scale, "weight_scale");
check_bf16_matrix(out, "out");
const int64_t M = input.size(0);
const int64_t K = input.size(1);
const int64_t N = weight.size(0);
TORCH_CHECK(weight.size(1) == K,
"weight must have shape (N, input.shape[1])");
TORCH_CHECK(K % 128 == 0 && N % 128 == 0,
"N and K must be divisible by 128");
TORCH_CHECK(input_scale.sizes() ==
torch::IntArrayRef({M, K / 128}),
"input_scale must have shape (M, K / 128)");
TORCH_CHECK(weight_scale.sizes() ==
torch::IntArrayRef({N / 128, K / 128}),
"weight_scale must have shape (N / 128, K / 128)");
TORCH_CHECK(out.sizes() == torch::IntArrayRef({M, N}),
"out must have shape (M, N)");
TORCH_CHECK(input.get_device() == weight.get_device() &&
input.get_device() == input_scale.get_device() &&
input.get_device() == weight_scale.get_device() &&
input.get_device() == out.get_device(),
"all tensors must be on the same CUDA device");
#if defined(CUDA_KERNEL)
at::cuda::CUDAGuard device_guard(input.device());
auto* props = at::cuda::getDeviceProperties(input.get_device());
TORCH_CHECK((props->major == 8 && props->minor == 9) ||
(props->major == 12 && props->minor == 0),
"fp8_blockwise_linear_bf16 requires SM89 or SM120; got SM",
props->major, props->minor);
auto stream = at::cuda::getCurrentCUDAStream(input.get_device()).stream();
if (props->major == 8) {
#if defined(FLASHRT_FP8_GEMM_SOURCE_SM120_ONLY) || \
defined(FLASHRT_FP8_GEMM_SOURCE_SM110_ONLY)
TORCH_CHECK(false, "SM89 blockwise kernels are not present in this source-test build");
#else
int rc;
if (M == 1) {
namespace gemv89 = flash_rt::gemm::gemv_m1_sm89;
if (N <= 2048) {
rc = gemv89::gemv_fp8_block128_m1_w4(
input.data_ptr(), weight.data_ptr(), out.data_ptr(), 1,
checked_positive_int(N, "N"), checked_positive_int(K, "K"),
input_scale.data_ptr<float>(), weight_scale.data_ptr<float>(),
1.0f, stream);
} else if (N <= 8192) {
rc = gemv89::gemv_fp8_block128_m1_w8(
input.data_ptr(), weight.data_ptr(), out.data_ptr(), 1,
checked_positive_int(N, "N"), checked_positive_int(K, "K"),
input_scale.data_ptr<float>(), weight_scale.data_ptr<float>(),
1.0f, stream);
} else {
rc = gemv89::gemv_fp8_block128_m1_w16(
input.data_ptr(), weight.data_ptr(), out.data_ptr(), 1,
checked_positive_int(N, "N"), checked_positive_int(K, "K"),
input_scale.data_ptr<float>(), weight_scale.data_ptr<float>(),
1.0f, stream);
}
} else {
rc = flash_rt::gemm::block128_sm89::
fp8_block128_gemm_blockscaled_sm89_bf16out(
input.data_ptr(), weight.data_ptr(), out.data_ptr(),
checked_positive_int(M, "M"), checked_positive_int(N, "N"),
checked_positive_int(K, "K"), input_scale.data_ptr<float>(),
weight_scale.data_ptr<float>(), stream);
}
TORCH_CHECK(rc == 0, "SM89 blockwise FP8 linear failed with rc=", rc);
#endif
} else {
#if defined(FLASHRT_FP8_GEMM_SOURCE_SM89_ONLY) || \
defined(FLASHRT_FP8_GEMM_SOURCE_SM110_ONLY)
TORCH_CHECK(false, "SM120 blockwise kernel is not present in this source-test build");
#else
flash_rt::gemm::fp8_block128_gemm_cutlass_sm120_bf16out(
input.data_ptr(), weight.data_ptr(), out.data_ptr(),
checked_positive_int(M, "M"), checked_positive_int(N, "N"),
checked_positive_int(K, "K"), input_scale.data_ptr<float>(),
weight_scale.data_ptr<float>(), stream);
#endif
}
#else
TORCH_CHECK(false, "fp8-gemm was not built with CUDA support");
#endif
}
void fp8_blockwise_swiglu_quantize_fp8(
torch::Tensor const& input,
torch::Tensor const& gate_up_weight,
torch::Tensor const& input_scale,
torch::Tensor const& gate_up_weight_scale,
torch::Tensor& output,
torch::Tensor& output_scale) {
check_fp8_matrix(input, "input");
check_fp8_matrix(gate_up_weight, "gate_up_weight");
check_fp32_matrix(input_scale, "input_scale");
check_fp32_matrix(gate_up_weight_scale, "gate_up_weight_scale");
check_fp8_matrix(output, "output");
check_fp32_matrix(output_scale, "output_scale");
const int64_t M = input.size(0);
const int64_t K = input.size(1);
TORCH_CHECK(gate_up_weight.size(0) % 2 == 0 &&
gate_up_weight.size(1) == K,
"gate_up_weight must have shape (2*N, K)");
const int64_t N = gate_up_weight.size(0) / 2;
TORCH_CHECK(M > 0 && M <= 256,
"SM89 fused SwiGLU producer supports 1 <= M <= 256");
TORCH_CHECK(N % 128 == 0 && K % 128 == 0,
"N and K must be divisible by 128");
TORCH_CHECK(input_scale.sizes() == torch::IntArrayRef({M, K / 128}),
"input_scale must have shape (M, K / 128)");
TORCH_CHECK(gate_up_weight_scale.sizes() ==
torch::IntArrayRef({2 * N / 128, K / 128}),
"gate_up_weight_scale must have shape (2*N/128, K/128)");
TORCH_CHECK(output.sizes() == torch::IntArrayRef({M, N}),
"output must have shape (M, N)");
TORCH_CHECK(output_scale.sizes() == torch::IntArrayRef({M, N / 128}),
"output_scale must have shape (M, N/128)");
TORCH_CHECK(input.get_device() == gate_up_weight.get_device() &&
input.get_device() == input_scale.get_device() &&
input.get_device() == gate_up_weight_scale.get_device() &&
input.get_device() == output.get_device() &&
input.get_device() == output_scale.get_device(),
"all tensors must be on the same CUDA device");
#if defined(CUDA_KERNEL)
at::cuda::CUDAGuard device_guard(input.device());
auto* props = at::cuda::getDeviceProperties(input.get_device());
TORCH_CHECK(props->major == 8 && props->minor == 9,
"fp8_blockwise_swiglu_quantize_fp8 requires SM89; got SM",
props->major, props->minor);
#if defined(FLASHRT_FP8_GEMM_SOURCE_SM120_ONLY) || \
defined(FLASHRT_FP8_GEMM_SOURCE_SM110_ONLY)
TORCH_CHECK(false, "SM89 fused producer is not present in this source-test build");
#else
auto stream = at::cuda::getCurrentCUDAStream(input.get_device()).stream();
int rc = flash_rt::gemm::block128_sm89::
fp8_bs_geglu_silu_fold_sm89_32x128_w4_s1(
input.data_ptr(), gate_up_weight.data_ptr(),
checked_positive_int(M, "M"), checked_positive_int(N, "N"),
checked_positive_int(K, "K"), input_scale.data_ptr<float>(),
gate_up_weight_scale.data_ptr<float>(), output.data_ptr(),
output_scale.data_ptr<float>(), stream);
TORCH_CHECK(rc == 0, "SM89 fused SwiGLU FP8 producer failed with rc=", rc);
#endif
#else
TORCH_CHECK(false, "fp8-gemm was not built with CUDA support");
#endif
}
TORCH_LIBRARY_EXPAND(TORCH_EXTENSION_NAME, ops) {
ops.def("fp8_linear_bf16(Tensor input, Tensor weight, float alpha, int variant, Tensor! out) -> ()");
ops.def("fp8_linear_residual_bf16(Tensor input, Tensor weight, float alpha, int variant, Tensor! residual) -> ()");
ops.def("fp8_linear_bias_bf16(Tensor input, Tensor weight, Tensor bias, float alpha, Tensor! out) -> ()");
ops.def("fp8_linear_bias_residual_bf16(Tensor input, Tensor weight, Tensor bias, float alpha, Tensor! residual) -> ()");
ops.def("fp8_linear_bias_gelu_bf16(Tensor input, Tensor weight, Tensor bias, float alpha, Tensor! out) -> ()");
ops.def("fp8_blockwise_linear_bf16("
"Tensor input, Tensor weight, Tensor input_scale, "
"Tensor weight_scale, Tensor! out) -> ()");
ops.def("fp8_blockwise_swiglu_quantize_fp8("
"Tensor input, Tensor gate_up_weight, Tensor input_scale, "
"Tensor gate_up_weight_scale, Tensor! output, Tensor! output_scale) -> ()");
#if defined(CUDA_KERNEL)
ops.impl("fp8_linear_bf16", torch::kCUDA, &fp8_linear_bf16);
ops.impl("fp8_linear_residual_bf16", torch::kCUDA, &fp8_linear_residual_bf16);
ops.impl("fp8_linear_bias_bf16", torch::kCUDA, &fp8_linear_bias_bf16);
ops.impl("fp8_linear_bias_residual_bf16", torch::kCUDA,
&fp8_linear_bias_residual_bf16);
ops.impl("fp8_linear_bias_gelu_bf16", torch::kCUDA,
&fp8_linear_bias_gelu_bf16);
ops.impl("fp8_blockwise_linear_bf16",
torch::kCUDA,
&fp8_blockwise_linear_bf16);
ops.impl("fp8_blockwise_swiglu_quantize_fp8",
torch::kCUDA,
&fp8_blockwise_swiglu_quantize_fp8);
#endif
}
REGISTER_EXTENSION(TORCH_EXTENSION_NAME)