Files
xserv/csrc/gemm/gemv.cu
Gahow Wang ae08896f46 xserv-chat: support gpt-oss-20b with TP; fix GEMV precision bug
- Add ChatModel enum dispatching between Qwen3 and GptOss based on
  config.is_moe(), following the TP engine pattern.
- Add --tp N flag for tensor-parallel inference (required for 39GB
  gpt-oss-20b which doesn't fit on a single 32GB GPU).
- Add gpt-oss harmony chat template with channel/message format.
- Replace hardcoded is_stop_token() with tokenizer.is_eos() for
  multi-model EOS support.
- Restore gpt-oss hardcoded prompt template in server api.rs, lost
  during the Jinja template refactor.
- Fix GEMV race condition: the K-split kernel zeroed the FP32
  accumulator inside the kernel (block k=0) while other blocks
  atomicAdd'd concurrently. Pre-zero with cudaMemsetAsync instead.
- Update benchmark docs with post-fix results.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-06-02 00:58:10 +08:00

104 lines
2.9 KiB
Plaintext

#include <cuda_bf16.h>
#include <cuda_runtime.h>
#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;
if (col >= N) return;
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;
__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();
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<<<grid, GEMV_BLOCK, 0, s>>>(
(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<<<conv_grid, conv_block, 0, s>>>(
(const float*)y_fp32_buf,
(__nv_bfloat16*)y_bf16,
N
);
CUDA_CHECK_LAST_ERROR();
}
} // extern "C"