speculative: batched-GEMV kernel for verify path (Phase 24 step 1)

Add launch_gemv_bf16_batched: runs M m=1 GEMVs in a single 3D grid
launch (z = batch row) with numerically identical output to M sequential
launch_gemv_bf16 calls — same K-block partial accumulation, same
fixed-order reduction. Verified on dash5 with 10 prompts × 32 tokens:
matched=true, verify_decode_mismatches=0.

Expose as matmul_batched_gemv(a: [M,K], b: [K,N]) → [M,N] in
xserv-kernels. Replace the old matmul_rows_gemv helper in qwen3
forward_verify_paged_decode_attention; the per-row loop over matmul_2d +
concat_rows is replaced by a single matmul_batched_gemv call that
allocates the partials buffer in one shot and launches 2 kernels instead
of 2*M.

Current speedup_e2e is 0.47× (same ballpark as Phase 23 0.44×);
the batched launch saves ~3 ms overhead but this is small relative to
the total 28 ms spec cost. The path forward (per docs/24 §4) is
higher acceptance rate or cheaper draft, not further kernel optimization.
This commit is contained in:
2026-07-01 16:13:37 +08:00
parent 42e13f33dd
commit e5734b41fa
4 changed files with 155 additions and 20 deletions

View File

@@ -18,6 +18,17 @@ unsafe extern "C" {
n: i32,
stream: *mut c_void,
);
fn launch_gemv_bf16_batched(
x: *const c_void,
w: *const c_void,
y_bf16: *mut c_void,
y_fp32_buf: *mut c_void,
m: i32,
k: i32,
n: i32,
stream: *mut c_void,
);
}
#[derive(Debug, Clone, Copy)]
@@ -31,6 +42,55 @@ pub fn gemv_scratch_elems(k: usize, n: usize) -> usize {
n * k.div_ceil(GEMV_TILE_K)
}
/// Batched GEMV: [M, K] × [K, N] → [M, N], all BF16.
/// Bit-exact with calling matmul on each row individually (same K-block partial
/// + fixed-order reduction path), but in a single kernel launch per phase.
pub fn matmul_batched_gemv(a: &Tensor, b: &Tensor) -> Tensor {
assert_eq!(a.ndim(), 2);
assert_eq!(b.ndim(), 2);
assert!(a.is_contiguous());
assert!(b.is_contiguous());
assert_eq!(a.dtype(), DType::BF16);
assert_eq!(b.dtype(), DType::BF16);
let m = a.shape()[0];
let k = a.shape()[1];
let n = b.shape()[1];
assert_eq!(b.shape()[0], k);
let out = Tensor::empty(&[m, n], DType::BF16, a.device());
let scratch_elems = m * gemv_scratch_elems(k, n);
let mut fp32_buf = xserv_cuda::allocator::cached_alloc(scratch_elems * 4).unwrap();
let null_stream = xserv_cuda::current_stream_raw();
if m == 1 {
unsafe {
launch_gemv_bf16(
a.data_ptr() as *const c_void,
b.data_ptr() as *const c_void,
out.data_ptr() as *mut c_void,
fp32_buf.as_mut_ptr() as *mut c_void,
k as i32,
n as i32,
null_stream,
);
}
} else {
unsafe {
launch_gemv_bf16_batched(
a.data_ptr() as *const c_void,
b.data_ptr() as *const c_void,
out.data_ptr() as *mut c_void,
fp32_buf.as_mut_ptr() as *mut c_void,
m as i32,
k as i32,
n as i32,
null_stream,
);
}
}
out
}
// --- FFI: custom CUDA kernels ---
unsafe extern "C" {
fn launch_gemm_naive_f32(

View File

@@ -19,7 +19,7 @@ pub use attention::{
paged_decode_attention_sinks, reshape_and_cache_batched_bf16, reshape_and_cache_bf16,
};
pub use embedding::{embedding, embedding_device_ids};
pub use gemm::{GemmBackend, batched_matmul, matmul};
pub use gemm::{GemmBackend, batched_matmul, matmul, matmul_batched_gemv};
pub use layernorm::layernorm;
pub use rmsnorm::{add_rmsnorm, rmsnorm};
pub use rope::{RopeCache, rope_inplace, rope_inplace_device_pos};