From 9a7d43df1e8587763e8a57aaa98dd1f965043590 Mon Sep 17 00:00:00 2001 From: "Piotr Wilkin (ilintar)" Date: Mon, 21 Sep 2026 18:00:51 +0200 Subject: [PATCH] ggml-cuda : convert contiguous tensors four elements at a time (llama/29155) convert_unary handles the contiguous case through the general strided kernel, one element per thread: each lane reads 4 bytes and writes 2. Converting the activations for a bf16 matrix multiplication that way moves 126 MB in 1021 us on gfx1151, about 65% of what the memory system can do. Give the contiguous path its own kernel that takes four elements per thread through a vector type, so a warp loads 512 bytes at a time instead of 128. It is used only when the element count is a multiple of four and both pointers carry the alignment the vector type needs, and falls back to the strided kernel otherwise. Model level, Qwen3.8-Next-Flash IQ3_XXS on gfx1151, llama-bench -ub 2048 -r 6, mean of the last 3 reps, ABBA counterbalanced: pp2048 688.0 680.0 -> 694.3 691.1 +1.26% tg128 24.8 24.8 -> 24.8 24.8 +0.14% Every conversion in a prefill takes the new kernel (kernel trace: 1146 convert_unary_cont_vec4, no convert_unary). Output is bit identical; MUL_MAT, MUL_MAT_ID, CPY, CONT, GET_ROWS and SET_ROWS pass. Assisted-by: Claude Opus 5 --- ggml/src/ggml-cuda/convert.cu | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/ggml/src/ggml-cuda/convert.cu b/ggml/src/ggml-cuda/convert.cu index 360c614a4..0619f4760 100644 --- a/ggml/src/ggml-cuda/convert.cu +++ b/ggml/src/ggml-cuda/convert.cu @@ -439,6 +439,29 @@ static __global__ void convert_unary( } } +template struct alignas(sizeof(T)*4) cvt_vec4 { T v[4]; }; + +// four elements per thread, so a warp moves 512B (RDNA) / 1k (CDNA) per load +template +static __global__ void convert_unary_cont_vec4( + const void * __restrict__ vx, dst_t * __restrict__ y, const int64_t k4) { + const int64_t i = (int64_t)blockDim.x*blockIdx.x + threadIdx.x; + + if (i >= k4) { + return; + } + + const cvt_vec4 xv = ((const cvt_vec4 *) vx)[i]; + + cvt_vec4 yv; +#pragma unroll + for (int j = 0; j < 4; ++j) { + yv.v[j] = ggml_cuda_cast(xv.v[j]); + } + + ((cvt_vec4 *) y)[i] = yv; +} + template static void convert_unary_cuda(const void * vx, dst_t * y, const int64_t ne00, const int64_t ne01, const int64_t ne02, const int64_t ne03, @@ -452,6 +475,15 @@ static void convert_unary_cuda(const void * vx, dst_t * y, template static void convert_unary_cont_cuda(const void * vx, dst_t * y, const int64_t k, cudaStream_t stream) { + if (k % 4 == 0 && + (uintptr_t) vx % alignof(cvt_vec4) == 0 && + (uintptr_t) y % alignof(cvt_vec4) == 0) { + const int64_t k4 = k/4; + const int64_t num_blocks = (k4 + CUDA_DEQUANTIZE_BLOCK_SIZE - 1) / CUDA_DEQUANTIZE_BLOCK_SIZE; + convert_unary_cont_vec4<<>>(vx, y, k4); + return; + } + convert_unary_cuda(vx, y, k, 1, 1, 1, k, k, k, stream); }