#include #include #include "../common.cuh" // K-split GEMV for M=1 BF16 decode. // // y[n] = sum_k x[k] * W[k * N + n] // // Grid: (N / TILE_N, K / TILE_K). // All blocks atomicAdd their partial sums into a pre-zeroed FP32 buffer. // A separate conversion kernel writes the final BF16 output. // Launch sequence: cudaMemsetAsync(fp32) → accumulation kernel → convert kernel. #define GEMV_TILE_N 128 #define GEMV_TILE_K 256 #define GEMV_BLOCK 128 __global__ void gemv_bf16_fused_kernel( const __nv_bfloat16* __restrict__ x, const __nv_bfloat16* __restrict__ W, __nv_bfloat16* __restrict__ y_bf16, float* __restrict__ y_fp32, int K, int N, int num_k_blocks ) { const int block_n = blockIdx.x; const int block_k = blockIdx.y; const int t = threadIdx.x; const int col = block_n * GEMV_TILE_N + t; const int k_start = block_k * GEMV_TILE_K; const int k_end = min(k_start + GEMV_TILE_K, K); const int k_len = k_end - k_start; // Cooperative load of x into shared memory uses ALL threads in the block // (indexed by t, independent of col). Threads whose column is out of range // must still help load and reach the barrier — returning early here would // leave part of x_shared uninitialized AND make __syncthreads divergent // (UB). So the col>=N check happens only AFTER the load + barrier. This bug // produced intermittent huge/garbage outputs whenever N % GEMV_TILE_N != 0 // (e.g. gpt-oss decode o_proj with N=2880), collapsing the forward pass. __shared__ float x_shared[GEMV_TILE_K]; for (int i = t; i < k_len; i += GEMV_BLOCK) { x_shared[i] = __bfloat162float(x[k_start + i]); } __syncthreads(); if (col >= N) return; float sum = 0.0f; for (int ki = 0; ki < k_len; ki++) { sum += x_shared[ki] * __bfloat162float(W[(long long)(k_start + ki) * N + col]); } atomicAdd(&y_fp32[col], sum); } // Conversion kernel: FP32 accumulator -> BF16 output __global__ void gemv_fp32_to_bf16_kernel( const float* __restrict__ src, __nv_bfloat16* __restrict__ dst, int n ) { int idx = blockIdx.x * blockDim.x + threadIdx.x; if (idx < n) { dst[idx] = __float2bfloat16(src[idx]); } } extern "C" { void launch_gemv_bf16( const void* x, const void* W, void* y_bf16, void* y_fp32_buf, int K, int N, void* stream ) { cudaStream_t s = (cudaStream_t)stream; // Zero the FP32 accumulator BEFORE the kernel — the kernel uses atomicAdd // across K-blocks with no inter-block ordering, so the buffer must be // pre-zeroed to avoid accumulating on stale data. cudaMemsetAsync(y_fp32_buf, 0, (size_t)N * sizeof(float), s); int num_k_blocks = (K + GEMV_TILE_K - 1) / GEMV_TILE_K; dim3 grid((N + GEMV_TILE_N - 1) / GEMV_TILE_N, num_k_blocks); gemv_bf16_fused_kernel<<>>( (const __nv_bfloat16*)x, (const __nv_bfloat16*)W, (__nv_bfloat16*)y_bf16, (float*)y_fp32_buf, K, N, num_k_blocks ); CUDA_CHECK_LAST_ERROR(); // FP32 → BF16 conversion (must wait for all K-blocks to finish) int conv_block = 256; int conv_grid = (N + conv_block - 1) / conv_block; gemv_fp32_to_bf16_kernel<<>>( (const float*)y_fp32_buf, (__nv_bfloat16*)y_bf16, N ); CUDA_CHECK_LAST_ERROR(); } } // extern "C"