/* Matrix Multiplication, with help from cuBLASLt */ #include #include // std::bool_constant // llmc internal imports #include "cuda_common.h" #include "cuda_utils.cuh" #include "cublas_common.h" // ---------------------------------------------------------------------------- // CUDA kernels template __global__ void matmul_backward_bias_kernel9(OutFloat* dbias, const floatX* dout, int B, int T, int OC, std::bool_constant) { 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<<>>(dbias, dout, B, T, OC, False); cudaCheck(cudaGetLastError()); } else { // kernel 9 overwrites temp buffer, so no need to memset matmul_backward_bias_kernel9<<>>(dbias_buffer, dout, B, T, OC, True); cudaCheck(cudaGetLastError()); reduce_add_sum_kernel<<>>(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()); }