Files
2026-07-13 12:40:42 +08:00

365 lines
15 KiB
Plaintext

// Copyright (c) 2025 PaddlePaddle Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <cublasLt.h>
#include <cublas_v2.h>
#include <cuda.h>
#include <cuda_fp16.h>
#include <cuda_fp8.h>
#include <cuda_runtime.h>
#include <cuda_runtime_api.h>
#include <stdlib.h>
#include <cassert>
#include <cstdint>
#include "paddle/phi/backends/dynload/cublasLt.h"
#include "paddle/phi/backends/gpu/gpu_info.h"
#include "paddle/phi/common/memory_utils.h"
#include "paddle/phi/api/include/context_pool.h"
#include "paddle/phi/common/data_type.h"
#include "paddle/phi/common/place.h"
#include "paddle/phi/core/allocator.h"
#include "paddle/phi/kernels/funcs/blas/blaslt_gemm_search.h"
#include "paddle/phi/core/dense_tensor.h"
#include "paddle/phi/core/kernel_registry.h"
#include "paddle/phi/kernels/funcs/blas/blas.h"
namespace phi {
namespace {
// Helper function to check if dtype is FP8
bool IsFp8Dtype(phi::DataType dtype) {
return dtype == phi::DataType::FLOAT8_E4M3FN ||
dtype == phi::DataType::FLOAT8_E5M2;
}
// Convert phi::DataType to cudaDataType_t
cudaDataType_t ScalarTypeToCudaDataType(phi::DataType dtype) {
switch (dtype) {
case phi::DataType::FLOAT8_E4M3FN:
return CUDA_R_8F_E4M3;
case phi::DataType::FLOAT8_E5M2:
return CUDA_R_8F_E5M2;
case phi::DataType::BFLOAT16:
return CUDA_R_16BF;
case phi::DataType::FLOAT32:
return CUDA_R_32F;
case phi::DataType::FLOAT16:
return CUDA_R_16F;
default:
PADDLE_THROW(common::errors::InvalidArgument("Unsupported data type"));
}
}
// cuBLAS error checking macro
#define PADDLE_CUDABLAS_CHECK(func) \
do { \
cublasStatus_t status = func; \
if (status != CUBLAS_STATUS_SUCCESS) { \
PADDLE_THROW(common::errors::External("cuBLAS error: %d", status)); \
} \
} while (0)
template <typename Context>
void cublas_gemm_blockwise_impl(const Context& dev_ctx,
const DenseTensor& A,
const DenseTensor& A_decode_scale,
const DenseTensor& B,
const DenseTensor& B_decode_scale,
DenseTensor* D,
const DenseTensor& bias,
DenseTensor* pre_gelu_out,
bool transa,
bool transb,
bool grad,
DenseTensor* workspace,
bool accumulate,
bool use_split_accumulator,
int math_sm_count,
bool is_A_1d_scaled,
bool is_B_1d_scaled,
cudaStream_t stream) {
// Sanity checks
PADDLE_ENFORCE_EQ(
transa,
true,
common::errors::InvalidArgument("Only transa == true is supported"));
PADDLE_ENFORCE_EQ(
transb,
false,
common::errors::InvalidArgument("Only transb == false is supported"));
PADDLE_ENFORCE_EQ(A.place().GetType(),
AllocationType::GPU,
common::errors::InvalidArgument(
"Input tensor A must be on CUDA device."));
PADDLE_ENFORCE_EQ(B.place().GetType(),
AllocationType::GPU,
common::errors::InvalidArgument(
"Input tensor B must be on CUDA device."));
PADDLE_ENFORCE_EQ(D->place().GetType(),
AllocationType::GPU,
common::errors::InvalidArgument(
"Output tensor D must be on CUDA device."));
PADDLE_ENFORCE_EQ(IsFp8Dtype(A.dtype()),
true,
common::errors::InvalidArgument("A must be FP8"));
PADDLE_ENFORCE_EQ(IsFp8Dtype(B.dtype()),
true,
common::errors::InvalidArgument("B must be FP8"));
PADDLE_ENFORCE_EQ(
D->dtype() == phi::DataType::BFLOAT16 ||
D->dtype() == phi::DataType::FLOAT32,
true,
common::errors::InvalidArgument("D must be BFloat16 or float"));
PADDLE_ENFORCE_EQ(
A_decode_scale.dtype() == phi::DataType::FLOAT32,
true,
common::errors::InvalidArgument("A_decode_scale must be float"));
PADDLE_ENFORCE_EQ(
B_decode_scale.dtype() == phi::DataType::FLOAT32,
true,
common::errors::InvalidArgument("B_decode_scale must be float"));
PADDLE_ENFORCE_EQ(A.dims().size() == 2,
true,
common::errors::InvalidArgument("A must be 2D"));
PADDLE_ENFORCE_EQ(B.dims().size() == 2,
true,
common::errors::InvalidArgument("B must be 2D"));
PADDLE_ENFORCE_EQ(D->dims().size() == 2,
true,
common::errors::InvalidArgument("D must be 2D"));
const int m = transa ? A.dims()[0] : A.dims()[1];
const int k = transa ? A.dims()[1] : A.dims()[0];
const int n = transb ? B.dims()[1] : B.dims()[0];
int lda = k, ldb = k, ldc = m, ldd = m;
float alpha = 1.0, beta = accumulate ? 1.0 : 0.0;
cublasLtHandle_t ltHandle = dev_ctx.cublaslt_handle();
// Create operation descriptor
cublasLtMatmulDesc_t operationDesc = nullptr;
PADDLE_CUDABLAS_CHECK(phi::dynload::cublasLtMatmulDescCreate(
&operationDesc, CUBLAS_COMPUTE_32F, CUDA_R_32F));
#if CUBLAS_VERSION >= 120805 && CUDA_VERSION >= 12080
// Setup scaling for A and B
cublasLtMatmulMatrixScale_t A_scale_mode, B_scale_mode;
// Note: in cuBLAS term, tensor name A and B are swapped.
if (is_B_1d_scaled && is_A_1d_scaled) {
A_scale_mode = CUBLASLT_MATMUL_MATRIX_SCALE_VEC128_32F;
B_scale_mode = CUBLASLT_MATMUL_MATRIX_SCALE_VEC128_32F;
} else if (!is_B_1d_scaled && is_A_1d_scaled) {
// So this corresponds to 2Dx1D GEMM.
A_scale_mode = CUBLASLT_MATMUL_MATRIX_SCALE_VEC128_32F;
B_scale_mode = CUBLASLT_MATMUL_MATRIX_SCALE_BLK128x128_32F;
} else if (is_B_1d_scaled && !is_A_1d_scaled) {
// So this corresponds to 1Dx2D GEMM.
A_scale_mode = CUBLASLT_MATMUL_MATRIX_SCALE_BLK128x128_32F;
B_scale_mode = CUBLASLT_MATMUL_MATRIX_SCALE_VEC128_32F;
} else {
PADDLE_THROW(
common::errors::InvalidArgument("2Dx2D scaling is not supported"));
}
PADDLE_CUDABLAS_CHECK(phi::dynload::cublasLtMatmulDescSetAttribute(
operationDesc,
CUBLASLT_MATMUL_DESC_A_SCALE_MODE,
&A_scale_mode,
sizeof(A_scale_mode)));
PADDLE_CUDABLAS_CHECK(phi::dynload::cublasLtMatmulDescSetAttribute(
operationDesc,
CUBLASLT_MATMUL_DESC_B_SCALE_MODE,
&B_scale_mode,
sizeof(B_scale_mode)));
#else
PADDLE_THROW(common::errors::InvalidArgument(
"Sub-channel FP8 GEMM requires CUDA 12.8 and cuBLAS 12.8.5 or later."));
#endif
// setup transa and transb
const cublasOperation_t transa_type = transa ? CUBLAS_OP_T : CUBLAS_OP_N;
const cublasOperation_t transb_type = transb ? CUBLAS_OP_T : CUBLAS_OP_N;
PADDLE_CUDABLAS_CHECK(
phi::dynload::cublasLtMatmulDescSetAttribute(operationDesc,
CUBLASLT_MATMUL_DESC_TRANSA,
&transa_type,
sizeof(transa_type)));
PADDLE_CUDABLAS_CHECK(
phi::dynload::cublasLtMatmulDescSetAttribute(operationDesc,
CUBLASLT_MATMUL_DESC_TRANSB,
&transb_type,
sizeof(transb_type)));
const void* A_decode_scale_ptr = A_decode_scale.data();
const void* B_decode_scale_ptr = B_decode_scale.data();
const cudaDataType_t Atype = ScalarTypeToCudaDataType(A.dtype());
const cudaDataType_t Btype = ScalarTypeToCudaDataType(B.dtype());
const cudaDataType_t Dtype = ScalarTypeToCudaDataType(D->dtype());
// split_accumulator is always true
const int8_t fast_accum_mode = 0;
PADDLE_CUDABLAS_CHECK(phi::dynload::cublasLtMatmulDescSetAttribute(
operationDesc,
CUBLASLT_MATMUL_DESC_FAST_ACCUM,
&fast_accum_mode,
sizeof(fast_accum_mode)));
PADDLE_CUDABLAS_CHECK(phi::dynload::cublasLtMatmulDescSetAttribute(
operationDesc,
CUBLASLT_MATMUL_DESC_A_SCALE_POINTER,
&A_decode_scale_ptr,
sizeof(A_decode_scale_ptr)));
PADDLE_CUDABLAS_CHECK(phi::dynload::cublasLtMatmulDescSetAttribute(
operationDesc,
CUBLASLT_MATMUL_DESC_B_SCALE_POINTER,
&B_decode_scale_ptr,
sizeof(B_decode_scale_ptr)));
PADDLE_CUDABLAS_CHECK(phi::dynload::cublasLtMatmulDescSetAttribute(
operationDesc,
CUBLASLT_MATMUL_DESC_SM_COUNT_TARGET,
&math_sm_count,
sizeof(math_sm_count)));
// Setup mat layout descriptors
cublasLtMatrixLayout_t Adesc = nullptr, Bdesc = nullptr, Cdesc = nullptr,
Ddesc = nullptr;
PADDLE_CUDABLAS_CHECK(phi::dynload::cublasLtMatrixLayoutCreate(
&Adesc,
Atype,
transa_type == CUBLAS_OP_N ? m : k,
transa_type == CUBLAS_OP_N ? k : m,
lda));
PADDLE_CUDABLAS_CHECK(phi::dynload::cublasLtMatrixLayoutCreate(
&Bdesc,
Btype,
transb_type == CUBLAS_OP_N ? k : n,
transb_type == CUBLAS_OP_N ? n : k,
ldb));
PADDLE_CUDABLAS_CHECK(
phi::dynload::cublasLtMatrixLayoutCreate(&Cdesc, Dtype, m, n, ldc));
PADDLE_CUDABLAS_CHECK(
phi::dynload::cublasLtMatrixLayoutCreate(&Ddesc, Dtype, m, n, ldd));
// setup epilogue attributes
cublasLtEpilogue_t epilogue = CUBLASLT_EPILOGUE_DEFAULT;
PADDLE_CUDABLAS_CHECK(phi::dynload::cublasLtMatmulDescSetAttribute(
operationDesc,
CUBLASLT_MATMUL_DESC_EPILOGUE,
&epilogue,
sizeof(epilogue)));
// setup preference attributes
cublasLtMatmulPreference_t preference = nullptr;
PADDLE_CUDABLAS_CHECK(
phi::dynload::cublasLtMatmulPreferenceCreate(&preference));
size_t workspace_size = workspace->dims()[0];
PADDLE_CUDABLAS_CHECK(phi::dynload::cublasLtMatmulPreferenceSetAttribute(
preference,
CUBLASLT_MATMUL_PREF_MAX_WORKSPACE_BYTES,
&workspace_size,
sizeof(workspace_size)));
PADDLE_CUDABLAS_CHECK(phi::dynload::cublasLtMatmul(ltHandle,
operationDesc,
&alpha,
A.data(),
Adesc,
B.data(),
Bdesc,
&beta,
D->data(),
Cdesc,
D->data(),
Ddesc,
/*algo*/ nullptr,
workspace->data(),
workspace_size,
stream));
// Cleanup
if (preference)
PADDLE_CUDABLAS_CHECK(
phi::dynload::cublasLtMatmulPreferenceDestroy(preference));
if (Ddesc)
PADDLE_CUDABLAS_CHECK(phi::dynload::cublasLtMatrixLayoutDestroy(Ddesc));
if (Cdesc)
PADDLE_CUDABLAS_CHECK(phi::dynload::cublasLtMatrixLayoutDestroy(Cdesc));
if (Bdesc)
PADDLE_CUDABLAS_CHECK(phi::dynload::cublasLtMatrixLayoutDestroy(Bdesc));
if (Adesc)
PADDLE_CUDABLAS_CHECK(phi::dynload::cublasLtMatrixLayoutDestroy(Adesc));
if (operationDesc)
PADDLE_CUDABLAS_CHECK(
phi::dynload::cublasLtMatmulDescDestroy(operationDesc));
}
} // anonymous namespace
template <typename T, typename Context>
void Fp8GemmBlockwiseKernel(const Context& dev_ctx,
const DenseTensor& A,
const DenseTensor& A_scale,
const DenseTensor& B,
const DenseTensor& B_scale,
const DenseTensor& input_result,
const DenseTensor& bias,
const DenseTensor& pre_gelu,
const DenseTensor& workspace,
bool transa,
bool transb,
bool grad,
bool accumulate,
bool use_split_accumulator,
int math_sm_count,
bool is_A_1d_scaled,
bool is_B_1d_scaled,
DenseTensor* output,
DenseTensor* pre_gelu_out,
DenseTensor* workspace_out) {
cublas_gemm_blockwise_impl<Context>(dev_ctx,
A,
A_scale,
B,
B_scale,
output,
bias,
pre_gelu_out,
transa,
transb,
grad,
workspace_out,
accumulate,
use_split_accumulator,
math_sm_count,
is_A_1d_scaled,
is_B_1d_scaled,
dev_ctx.stream());
}
} // namespace phi
// Register the kernel
PD_REGISTER_KERNEL(fp8_gemm_blockwise,
GPU,
ALL_LAYOUT,
phi::Fp8GemmBlockwiseKernel,
phi::bfloat16,
phi::float8_e4m3fn,
uint8_t,
float,
double) {}