/* CUDA kernels for RoPE. Compile and run as: make rope ./rope The fastest block size is 128 on H100. */ #include #include #include #include #include "common.h" void precompute_freqs_cis(float *freqs_cis, int dim, int end, float theta, int use_scaled) { // same as precompute_freqs_cis_real in rope.py for (int i = 0; i < dim / 2; i++) { // calculate the frequency for the (i, i+1)th dimension float freq = 1.0f / powf(theta, (float)(2 * i) / dim); if (use_scaled) { const int scale_factor = 8; const int low_freq_factor = 1; const int high_freq_factor = 4; const int old_context_len = 8192; // original llama3 length const float low_freq_wavelen = (float)old_context_len / low_freq_factor; const float high_freq_wavelen = (float)old_context_len / high_freq_factor; float wavelen = 2.0f * M_PI / freq; if (wavelen < high_freq_wavelen) { // skip; keep freq as is } else if (wavelen > low_freq_wavelen) { // scale down by scale_factor freq /= scale_factor; } else { // smooth transition between scaled and unscaled float smooth = ((float)old_context_len / wavelen - low_freq_factor) / (high_freq_factor - low_freq_factor); freq = (1.0f - smooth) * freq / scale_factor + smooth * freq; } } // iterate over all time steps, calculate the angle, and store the cos/sin for (int t = 0; t < end; t++) { float angle = (float)t * freq; freqs_cis[t * dim + 2 * i] = cosf(angle); // real part freqs_cis[t * dim + 2 * i + 1] = sinf(angle); // imaginary part } } } void apply_rotary_emb_forward(float *out, const float *inp, const float *freqs_cis, int B, int T, int n_head, int head_dim) { // same as apply_rotary_emb_real in rope.py for (int b = 0; b < B; b++) { for (int t = 0; t < T; t++) { int idx_bt = b * (T * n_head * head_dim) + t * (n_head * head_dim); for (int h = 0; h < n_head; h++) { int idx_bth = idx_bt + h * head_dim; for (int d = 0; d < head_dim / 2; d++) { // fetch a tuple of activations, which we imagine as a complex number int idx = idx_bth + 2 * d; float x_real = inp[idx]; float x_imag = inp[idx + 1]; // fetch the angle from freqs_cis int freqs_idx = t * head_dim + 2 * d; float freqs_cos = freqs_cis[freqs_idx]; float freqs_sin = freqs_cis[freqs_idx + 1]; // apply the rotation out[idx] = x_real * freqs_cos - x_imag * freqs_sin; out[idx + 1] = x_real * freqs_sin + x_imag * freqs_cos; } } } } } // kernel __global__ void rope_forward_kernel1(floatX *out, const floatX *inp, const floatX *freqs_cis, int B, int T, int n_head, int head_dim) { int idx = blockIdx.x * blockDim.x + threadIdx.x; int head_dim_half = head_dim / 2; if (idx >= B * T * n_head * head_dim_half) return; // decode the individual indices int b = idx / (T * n_head * head_dim_half); int t = (idx / (n_head * head_dim_half)) % T; int h = (idx / head_dim_half) % n_head; int d = idx % head_dim_half; // calculate the index in the input int idx_bt = b * (T * n_head * head_dim) + t * (n_head * head_dim); int idx_bth = idx_bt + h * head_dim; int idxi = idx_bth + 2 * d; // index in the input // fetch the input float x_real = inp[idxi]; float x_imag = inp[idxi + 1]; // fetch the freqs_cis int freqs_idx = t * head_dim + 2 * d; float freqs_cos = freqs_cis[freqs_idx]; float freqs_sin = freqs_cis[freqs_idx + 1]; // apply the rotation out[idxi] = x_real * freqs_cos - x_imag * freqs_sin; out[idxi + 1] = x_real * freqs_sin + x_imag * freqs_cos; } // launchers void rope_forward1(floatX *out, const floatX *inp, const floatX *freqs_cis, int B, int T, int n_head, int head_dim, int block_size) { // let's launch one thread per element of the output (but divide two!) because the work is in "tuples" int total_threads = B * T * n_head * head_dim / 2; int num_blocks = ceil_div(total_threads, block_size); rope_forward_kernel1<<>>(out, inp, freqs_cis, B, T, n_head, head_dim); cudaCheck(cudaGetLastError()); } void rope_forward(int kernel_num, floatX *out, const floatX *inp, const floatX *freqs_cis, int B, int T, int n_head, int head_dim, int block_size) { switch (kernel_num) { case 1: rope_forward1(out, inp, freqs_cis, B, T, n_head, head_dim, block_size); break; default: printf("Invalid kernel number\n"); exit(1); } } // ---------------------------------------------------------------------------- // while we're at it, let's also briefly validate our backward kernel here void apply_rotary_emb_backward(float *dinp, const float *dout, const float *inp, const float *freqs_cis, int B, int T, int n_head, int head_dim) { // backward pass of the RoPE embedding for (int b = 0; b < B; b++) { for (int t = 0; t < T; t++) { int idx_bt = b * (T * n_head * head_dim) + t * (n_head * head_dim); for (int h = 0; h < n_head; h++) { int idx_bth = idx_bt + h * head_dim; for (int d = 0; d < head_dim / 2; d++) { // fetch the angle from freqs_cis int freqs_idx = t * head_dim + 2 * d; float freqs_cos = freqs_cis[freqs_idx]; float freqs_sin = freqs_cis[freqs_idx + 1]; // and the input index we'll be updating int idx = idx_bth + 2 * d; // backward pass is simple because freqs_cis is just scaling by a constant dinp[idx] += dout[idx] * freqs_cos + dout[idx + 1] * freqs_sin; dinp[idx + 1] += -dout[idx] * freqs_sin + dout[idx + 1] * freqs_cos; } } } } } __global__ void rope_backward_inplace_kernel1(floatX *dinp, const floatX *dout, const floatX *freqs_cis, int B, int T, int n_head, int head_dim) { int idx = blockIdx.x * blockDim.x + threadIdx.x; int head_dim_half = head_dim / 2; if (idx >= B * T * n_head * head_dim_half) return; // decode the individual indices int b = idx / (T * n_head * head_dim_half); int t = (idx / (n_head * head_dim_half)) % T; int h = (idx / head_dim_half) % n_head; int d = idx % head_dim_half; // calculate the index in the input int idx_bt = b * (T * n_head * head_dim) + t * (n_head * head_dim); int idx_bth = idx_bt + h * head_dim; int idxi = idx_bth + 2 * d; // index in the input // fetch the freqs_cis int freqs_idx = t * head_dim + 2 * d; float freqs_cos = freqs_cis[freqs_idx]; float freqs_sin = freqs_cis[freqs_idx + 1]; // apply the rotation float dout_real = (float)dout[idxi]; float dout_imag = (float)dout[idxi + 1]; dinp[idxi] = dout_real * freqs_cos + dout_imag * freqs_sin; dinp[idxi + 1] = -dout_real * freqs_sin + dout_imag * freqs_cos; } void rope_backward_inplace(floatX *dinp, const floatX *dout, const floatX *freqs_cis, int B, int T, int n_head, int head_dim, cudaStream_t stream) { // backward pass of forward, mirrors the forward kernel in setup and indexing const int block_size = 128; int total_threads = B * T * 3 * n_head * head_dim / 2; int num_blocks = ceil_div(total_threads, block_size); rope_backward_inplace_kernel1<<>>(dinp, dout, freqs_cis, B, T, n_head, head_dim); cudaCheck(cudaGetLastError()); } // ---------------------------------------------------------------------------- // tester int main(int argc, char **argv) { srand(0); int B = 8; int T = 1024; int n_head = 32; int head_dim = 128; int deviceIdx = 0; cudaCheck(cudaSetDevice(deviceIdx)); // do the CPU reference calculation float *inp = make_random_float(B * T * n_head * head_dim); float *freqs_cis = (float *)malloc(T * head_dim * sizeof(float)); precompute_freqs_cis(freqs_cis, head_dim, T, 10000, 1); float *out = (float *)malloc(B * T * n_head * head_dim * sizeof(float)); apply_rotary_emb_forward(out, inp, freqs_cis, B, T, n_head, head_dim); // allocate GPU memory float *d_inp; float *d_freqs_cis; float *d_out; cudaCheck(cudaMalloc(&d_inp, B * T * n_head * head_dim * sizeof(float))); cudaCheck(cudaMalloc(&d_freqs_cis, T * head_dim * sizeof(float))); cudaCheck(cudaMalloc(&d_out, B * T * n_head * head_dim * sizeof(float))); // copy data to GPU cudaCheck(cudaMemcpy(d_inp, inp, B * T * n_head * head_dim * sizeof(float), cudaMemcpyHostToDevice)); cudaCheck(cudaMemcpy(d_freqs_cis, freqs_cis, T * head_dim * sizeof(float), 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); // check the correctness of the kernel at all 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]; printf("Checking block size %d.\n", block_size); rope_forward(kernel_num, d_out, d_inp, d_freqs_cis, B, T, n_head, head_dim, block_size); validate_result(d_out, out, "out", B * T * n_head * head_dim, 1e-5f); } printf("All results match. Starting benchmarks.\n\n"); // now benchmark 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, rope_forward, kernel_num, d_out, d_inp, d_freqs_cis, B, T, n_head, head_dim, block_size); printf("block_size %4d time %.4f ms\n", block_size, elapsed_time); } // now also briefly validate the backward pass // first, the reference CPU calculation float *dinp = (float *)malloc(B * T * n_head * head_dim * sizeof(float)); memset(dinp, 0, B * T * n_head * head_dim * sizeof(float)); // init at zero apply_rotary_emb_backward(dinp, out, inp, freqs_cis, B, T, n_head, head_dim); // now the GPU calculation (note it is done in-place, as we wish it to be to save space) rope_backward_inplace(d_out, d_out, d_freqs_cis, B, T, n_head, head_dim, 0); validate_result(d_out, dinp, "dinp", B * T * n_head * head_dim, 1e-5f); printf("Backward pass result matches.\n"); // free memory free(inp); free(freqs_cis); free(out); cudaCheck(cudaFree(d_inp)); cudaCheck(cudaFree(d_freqs_cis)); cudaCheck(cudaFree(d_out)); return 0; }