Files
2026-07-13 12:37:59 +08:00

203 lines
6.5 KiB
Plaintext

/*
Kernels for the positional encoder forward pass in GPT-2.
Compile example:
nvcc -O3 --use_fast_math -lcublas -lcublasLt encoder_backward.cu -o encoder_backward
version 1 is naive port from CPU code to kernel
parallelizes over B,T,C, uses atomics to add to dwte, dwpe
./encoder_backward 1
version 2 is another naive port
parallelizes over C, loops over B,T; much slower than version 1
./encoder_backward 2
*/
#include <stdio.h>
#include <stdlib.h>
#include <cuda_runtime.h>
#include "common.h"
// ----------------------------------------------------------------------------
// CPU code reference
// GPT-2 positional encoder forward pass
void encoder_backward_cpu(float* dwte, float* dwpe,
float* dout, int* inp,
int B, int T, int C) {
for (int b = 0; b < B; b++) {
for (int t = 0; t < T; t++) {
float* dout_bt = dout + b * T * C + t * C;
int ix = inp[b * T + t];
float* dwte_ix = dwte + ix * C;
float* dwpe_t = dwpe + t * C;
for (int i = 0; i < C; i++) {
float d = dout_bt[i];
dwte_ix[i] += d;
dwpe_t[i] += d;
}
}
}
}
// ----------------------------------------------------------------------------
// GPU kernels
// naive implementation with atomics
__global__ void encoder_backward_kernel1(float* dwte, float* dwpe,
const float* dout, const int* inp,
int B, int T, int C) {
int idx = blockIdx.x * blockDim.x + threadIdx.x;
int N = B * T * C;
if (idx < N) {
int bt = idx / C;
int b = bt / T;
int t = bt % T;
int c = idx % C;
int ix = inp[b * T + t];
const float* dout_btc = dout + b * T * C + t * C + c;
float* dwte_ix = dwte + ix * C + c;
float* dwpe_tc = dwpe + t * C + c;
atomicAdd(dwte_ix, *dout_btc);
atomicAdd(dwpe_tc, *dout_btc);
}
}
// naive implementation that parallelizes over C and loops over B,T
// but it gets rid of atomics
__global__ void encoder_backward_kernel2(float* dwte, float* dwpe,
const float* dout, const int* inp,
int B, int T, int C) {
int c = blockIdx.x * blockDim.x + threadIdx.x;
if (c >= C) { return; } // guard
int BT = B * T;
for (int i = 0; i < BT; i++) {
int t = i % T;
int ix = inp[i];
float dout_btc = dout[i * C + c];
dwte[ix * C + c] += dout_btc;
dwpe[t * C + c] += dout_btc;
}
}
// ----------------------------------------------------------------------------
// kernel launcher
void encoder_backward1(float* dwte, float* dwpe,
const float* dout, const int* inp,
int B, int T, int C,
const int block_size) {
const int N = B * T * C;
const int grid_size = ceil_div(N, block_size);
encoder_backward_kernel1<<<grid_size, block_size>>>(dwte, dwpe, dout, inp, B, T, C);
cudaCheck(cudaGetLastError());
}
void encoder_backward2(float* dwte, float* dwpe,
const float* dout, const int* inp,
int B, int T, int C,
const int block_size) {
const int grid_size = ceil_div(C, block_size);
encoder_backward_kernel2<<<grid_size, block_size>>>(dwte, dwpe, dout, inp, B, T, C);
cudaCheck(cudaGetLastError());
}
// kernel version dispatch
void encoder_backward(int kernel_num,
float* dwte, float* dwpe,
const float* dout, const int* inp,
int B, int T, int C,
const int block_size) {
switch (kernel_num) {
case 1:
encoder_backward1(dwte, dwpe, dout, inp, B, T, C, block_size);
break;
case 2:
encoder_backward2(dwte, dwpe, dout, inp, B, T, C, block_size);
break;
default:
printf("Invalid kernel number\n");
exit(1);
}
}
// ----------------------------------------------------------------------------
int main(int argc, char **argv) {
srand(0);
int B = 8;
int T = 1024;
int C = 768;
int V = 50257;
int deviceIdx = 0;
cudaCheck(cudaSetDevice(deviceIdx));
// create host memory of random numbers
float* dout = make_random_float(B * T * C);
int* inp = make_random_int(B * T, V);
float* dwte = make_zeros_float(V * C);
float* dwpe = make_zeros_float(T * C);
// move to GPU
float* d_dout;
int* d_inp;
float* d_dwte;
float* d_dwpe;
cudaCheck(cudaMalloc(&d_dout, B * T * C * sizeof(float)));
cudaCheck(cudaMalloc(&d_inp, B * T * sizeof(int)));
cudaCheck(cudaMalloc(&d_dwte, V * C * sizeof(float)));
cudaCheck(cudaMalloc(&d_dwpe, T * C * sizeof(float)));
cudaCheck(cudaMemcpy(d_dout, dout, B * T * C * sizeof(float), cudaMemcpyHostToDevice));
cudaCheck(cudaMemcpy(d_inp, inp, B * T * sizeof(int), cudaMemcpyHostToDevice));
// read kernel_num from command line
int kernel_num = 1;
if (argc > 1) {
kernel_num = atoi(argv[1]);
}
printf("Using kernel %d\n", kernel_num);
// first check the correctness of the kernel
encoder_backward_cpu(dwte, dwpe, dout, inp, B, T, C);
// time the kernel at different block sizes
int block_sizes[] = {32, 64, 128, 256, 512, 1024};
for (int j = 0; j < sizeof(block_sizes) / sizeof(int); j++) {
int block_size = block_sizes[j];
cudaCheck(cudaMemset(d_dwte, 0, V * C * sizeof(float)));
cudaCheck(cudaMemset(d_dwpe, 0, T * C * sizeof(float)));
printf("Checking block size %d.\n", block_size);
encoder_backward(kernel_num, d_dwte, d_dwpe, d_dout, d_inp, B, T, C, block_size);
validate_result(d_dwte, dwte, "dwte", V * C, 1e-5f);
validate_result(d_dwpe, dwpe, "dwpe", T * C, 1e-5f);
}
printf("All results match. Starting benchmarks.\n\n");
for (int j = 0; j < sizeof(block_sizes) / sizeof(int); j++) {
int block_size = block_sizes[j];
int repeat_times = 1000;
float elapsed_time = benchmark_kernel(repeat_times, encoder_backward,
kernel_num, d_dwte, d_dwpe, d_dout, d_inp, B, T, C, block_size);
printf("block_size %4d | time %.4f ms\n", block_size, elapsed_time);
}
// free memory
free(dout);
free(inp);
free(dwte);
free(dwpe);
cudaFree(d_dout);
cudaFree(d_inp);
cudaFree(d_dwte);
cudaFree(d_dwpe);
return 0;
}