karpathy--llm.c
221 行
9.5 KiB
Plaintext
221 行
9.5 KiB
Plaintext
/*
|
|
Matrix Multiplication, with help from cuBLASLt
|
|
*/
|
|
#include <assert.h>
|
|
#include <type_traits> // std::bool_constant
|
|
// llmc internal imports
|
|
#include "cuda_common.h"
|
|
#include "cuda_utils.cuh"
|
|
#include "cublas_common.h"
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// CUDA kernels
|
|
|
|
template<typename OutFloat, bool UseAuxBuffer>
|
|
__global__ void matmul_backward_bias_kernel9(OutFloat* dbias, const floatX* dout, int B, int T, int OC,
|
|
std::bool_constant<UseAuxBuffer>) {
|
|
constexpr const int bdx = 4;
|
|
constexpr const int bdy = WARP_SIZE / bdx;
|
|
assert(blockDim.x == bdx);
|
|
assert(blockDim.y == bdy);
|
|
|
|
int warp_d = (int)threadIdx.x;
|
|
int warp_c = (int)threadIdx.y;
|
|
int block_d = (int)threadIdx.z;
|
|
|
|
const int OC_per_warp = bdy * x128::size; // 64 at BF16
|
|
|
|
int local_oc = warp_c * x128::size;
|
|
int global_oc = blockIdx.x * OC_per_warp + local_oc;
|
|
|
|
int local_bt = warp_d + bdx * block_d;
|
|
int bt_per_block = bdx * blockDim.z;
|
|
|
|
float accumulators[x128::size];
|
|
for (int k = 0; k < x128::size; k++) {
|
|
accumulators[k] = 0.0f;
|
|
}
|
|
|
|
if(global_oc < OC) {
|
|
// sum up over all bt within registers
|
|
for (int idx = blockIdx.y * bt_per_block + local_bt; idx < B * T; idx += gridDim.y * bt_per_block) {
|
|
x128 packed_dout = load128(dout + global_oc + idx*OC);
|
|
for (int k = 0; k < x128::size; k++) {
|
|
accumulators[k] += (float)packed_dout[k];
|
|
}
|
|
}
|
|
}
|
|
|
|
__shared__ float sub_results[x128::size][WARP_SIZE][bdy];
|
|
|
|
// reduce within-warp results
|
|
for (int k = 0; k < x128::size; k++) {
|
|
float v = accumulators[k];
|
|
v += __shfl_down_sync(0xffffffff, v, 1, 4);
|
|
v += __shfl_down_sync(0xffffffff, v, 2, 4);
|
|
if(warp_d == 0) {
|
|
sub_results[k][block_d][warp_c] = v;
|
|
}
|
|
}
|
|
__syncthreads();
|
|
|
|
// block-wide reductions
|
|
for (int k = block_d; k < x128::size; k += blockDim.z) {
|
|
float a = 0.f;
|
|
for (int r = warp_d; r < blockDim.z; r += bdx) {
|
|
float v = sub_results[k][r][warp_c];
|
|
v += __shfl_down_sync(0xffffffff, v, 1, 4);
|
|
v += __shfl_down_sync(0xffffffff, v, 2, 4);
|
|
a += v;
|
|
}
|
|
if(warp_d == 0 && global_oc < OC) {
|
|
if constexpr (!UseAuxBuffer) {
|
|
dbias[global_oc + k] = (OutFloat)(a + (float)dbias[global_oc + k]);
|
|
} else {
|
|
dbias[global_oc + k + blockIdx.y * OC] = a;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
__global__ void reduce_add_sum_kernel(floatX* dst, const float* src, size_t n, size_t m) {
|
|
const size_t idx = (blockIdx.x * blockDim.x + threadIdx.x) * f128::size;
|
|
assert(n % x128::size == 0);
|
|
if (idx < n) {
|
|
f128 acc;
|
|
for(int k = 0; k < f128::size; ++k) {
|
|
acc[k] = 0.f;
|
|
}
|
|
|
|
for(int l = 0; l < m; ++l) {
|
|
f128 s = load128(src + idx + n * l);
|
|
for(int k = 0; k < f128::size; ++k) {
|
|
acc[k] += s[k];
|
|
}
|
|
}
|
|
for(int k = 0; k < f128::size; ++k) {
|
|
dst[idx + k] = (floatX) ((float)dst[idx + k] + acc[k]);
|
|
}
|
|
}
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// kernel launchers
|
|
|
|
// https://docs.nvidia.com/cuda/cublas/#cublasltmatmul
|
|
void matmul_forward_cublaslt(floatX* out,
|
|
floatX* inp, floatX* weight, floatX* bias,
|
|
int B, int T, int C, int OC, cudaStream_t stream) {
|
|
NVTX_RANGE_FN();
|
|
int has_bias = (bias != NULL);
|
|
|
|
// check bias alignment
|
|
if(((uintptr_t)bias % 16) != 0) {
|
|
printf("Bias pointer is not aligned (cuBLASLt requirement)!\n");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
// these need to be in FP16 if and only if alpha/beta are CUBLAS_COMPUTE_16F
|
|
const float alpha = 1.0f, beta = 0.0f;
|
|
|
|
int returnedResults = 0;
|
|
cublasLtMatmulDesc_t operationDesc;
|
|
cublasLtMatmulPreference_t preference;
|
|
cublasLtMatrixLayout_t weightLayout;
|
|
cublasLtMatrixLayout_t inputLayout;
|
|
cublasLtMatrixLayout_t outputLayout;
|
|
cublasLtMatrixLayout_t biasLayout;
|
|
cublasLtMatmulHeuristicResult_t heuristic;
|
|
|
|
// create the operation descriptor
|
|
cublasOperation_t opNoTranspose = CUBLAS_OP_N;
|
|
cublasOperation_t opTranspose = CUBLAS_OP_T;
|
|
cublasLtEpilogue_t epilogueBias = has_bias ? CUBLASLT_EPILOGUE_BIAS : CUBLASLT_EPILOGUE_DEFAULT;
|
|
|
|
cublasCheck(cublasLtMatmulDescCreate(&operationDesc, cublas_compute, CUDA_R_32F)); // FP16 if CUBLAS_COMPUTE_16F
|
|
cublasCheck(cublasLtMatmulDescSetAttribute(operationDesc, CUBLASLT_MATMUL_DESC_TRANSA, &opTranspose, sizeof(opTranspose)));
|
|
cublasCheck(cublasLtMatmulDescSetAttribute(operationDesc, CUBLASLT_MATMUL_DESC_TRANSB, &opNoTranspose, sizeof(opNoTranspose)));
|
|
cublasCheck(cublasLtMatmulDescSetAttribute(operationDesc, CUBLASLT_MATMUL_DESC_EPILOGUE, &epilogueBias, sizeof(epilogueBias)));
|
|
cublasCheck(cublasLtMatmulDescSetAttribute(operationDesc, CUBLASLT_MATMUL_DESC_BIAS_POINTER, &bias, sizeof(bias)));
|
|
|
|
// define matrix layouts
|
|
cublasCheck(cublasLtMatrixLayoutCreate(&weightLayout, CUBLAS_LOWP, C, OC, C));
|
|
cublasCheck(cublasLtMatrixLayoutCreate(&inputLayout, CUBLAS_LOWP, C, B*T, C));
|
|
cublasCheck(cublasLtMatrixLayoutCreate(&outputLayout, CUBLAS_LOWP, OC, B*T, OC));
|
|
cublasCheck(cublasLtMatrixLayoutCreate(&biasLayout, CUBLAS_LOWP, OC, 1, OC));
|
|
|
|
// create a preference handle with specified max workspace
|
|
cublasCheck(cublasLtMatmulPreferenceCreate(&preference));
|
|
cublasCheck(cublasLtMatmulPreferenceSetAttribute(preference,
|
|
CUBLASLT_MATMUL_PREF_MAX_WORKSPACE_BYTES, &cublaslt_workspace_size, sizeof(cublaslt_workspace_size)));
|
|
|
|
// find a suitable algorithm
|
|
cublasCheck(cublasLtMatmulAlgoGetHeuristic(cublaslt_handle, operationDesc,
|
|
weightLayout, inputLayout, outputLayout, outputLayout,
|
|
preference, 1, &heuristic, &returnedResults));
|
|
if (returnedResults == 0) {
|
|
printf("No cuBLASLt algorithm: B: %d, T: %d, C: %d, OC: %d, bias: %d\n", B, T, C, OC, has_bias);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
// call the matmul
|
|
cublasCheck(cublasLtMatmul(cublaslt_handle, operationDesc,
|
|
&alpha, weight, weightLayout, inp, inputLayout, &beta,
|
|
out, outputLayout, out, outputLayout, &heuristic.algo,
|
|
cublaslt_workspace, cublaslt_workspace_size, stream));
|
|
|
|
// cleanups
|
|
cublasCheck(cublasLtMatmulPreferenceDestroy(preference));
|
|
cublasCheck(cublasLtMatmulDescDestroy(operationDesc));
|
|
cublasCheck(cublasLtMatrixLayoutDestroy(weightLayout));
|
|
cublasCheck(cublasLtMatrixLayoutDestroy(inputLayout));
|
|
cublasCheck(cublasLtMatrixLayoutDestroy(outputLayout));
|
|
cublasCheck(cublasLtMatrixLayoutDestroy(biasLayout));
|
|
}
|
|
|
|
void matmul_backward(floatX* dinp, floatX* dweight, floatX* dbias,
|
|
floatX* dout, floatX* inp, floatX* weight,
|
|
float* dbias_buffer,
|
|
int B, int T, int C, int OC, cudaStream_t stream) {
|
|
NVTX_RANGE_FN();
|
|
float one = 1.0f, zero = 0.0f;
|
|
|
|
// backward to bias, if given, does a +=
|
|
if (dbias != NULL) {
|
|
// Each warp is responsible for 8 * "x128::size" = 64 OCs at BF16 (OC must be a multiple of 64!)
|
|
// Block size is 1024 | 768 threads (32|24 warps) and we reduce those values into 1 at the end
|
|
|
|
const int block_size = deviceProp.maxThreadsPerMultiProcessor == 1536 ? 768 : 1024;
|
|
|
|
dim3 block_dim = {4, 8, (unsigned)block_size/WARP_SIZE};
|
|
const int OC_per_warp = block_dim.y * x128::size; // 64 at BF16
|
|
const int grid_size_x = CEIL_DIV(OC, OC_per_warp); // e.g. 12 horizontal blocks for 768 OCs at BF16
|
|
const int grid_size_y = max(1, deviceProp.maxThreadsPerMultiProcessor * deviceProp.multiProcessorCount / (block_size * grid_size_x)); // full GPU!
|
|
|
|
// If we have enough OC that we don't need cross-block reductions, we can skip the bias_buffer accumulation
|
|
// and write results directly to the output.
|
|
if(grid_size_y == 1) {
|
|
matmul_backward_bias_kernel9<<<dim3(grid_size_x, grid_size_y), block_dim, 0, stream>>>(dbias, dout, B, T, OC, std::bool_constant<false>{});
|
|
cudaCheck(cudaGetLastError());
|
|
} else {
|
|
// kernel 9 overwrites temp buffer, so no need to memset
|
|
matmul_backward_bias_kernel9<<<dim3(grid_size_x, grid_size_y), block_dim, 0, stream>>>(dbias_buffer, dout, B, T, OC, std::bool_constant<true>{});
|
|
cudaCheck(cudaGetLastError());
|
|
reduce_add_sum_kernel<<<CEIL_DIV(OC, 256 * f128::size), 256, 0, stream>>>(dbias, dbias_buffer, OC, grid_size_y);
|
|
cudaCheck(cudaGetLastError());
|
|
}
|
|
}
|
|
|
|
// backward to input, uses = in the backward pass (set the gradient)
|
|
cublasCheck(cublasSetStream(cublas_handle, stream));
|
|
cublasCheck(cublasSetWorkspace(cublas_handle, cublaslt_workspace, cublaslt_workspace_size));
|
|
cublasCheck(cublasGemmEx(cublas_handle, CUBLAS_OP_N, CUBLAS_OP_N, C, B*T, OC, &one,
|
|
weight, CUBLAS_LOWP, C, dout, CUBLAS_LOWP, OC, &zero,
|
|
dinp, CUBLAS_LOWP, C, cublas_compute, CUBLAS_GEMM_DEFAULT_TENSOR_OP));
|
|
// backward to weight, uses += in the backward pass (accumulate the gradient) by setting alpha=one
|
|
cublasCheck(cublasGemmEx(cublas_handle, CUBLAS_OP_N, CUBLAS_OP_T, C, OC, B*T, &one,
|
|
inp, CUBLAS_LOWP, C, dout, CUBLAS_LOWP, OC, &one,
|
|
dweight, CUBLAS_LOWP, C, cublas_compute, CUBLAS_GEMM_DEFAULT_TENSOR_OP));
|
|
cudaCheck(cudaGetLastError());
|
|
}
|