Files
llama.cpp-mtp-turboquant/ggml/src/ggml-cuda/turbo-wht.cu
T

175 lines
7.3 KiB
Plaintext

#include "turbo-quant.cuh"
#include "turbo-wht.cuh"
// ─── CUDA kernel ──────────────────────────────────────────────────────────────
//
// Templated on direction and group_size (128 or 64).
// One block per group, group_size threads per block.
// direction: 0 = forward (signs1 → WHT → signs2), 1 = inverse (signs2 → WHT → signs1)
//
// When head_dim is not a multiple of group_size, only the full groups
// within each head are processed. Tail elements are left unchanged (identity).
//
// Algorithm mirrors the CPU implementation in ggml-cpu/ops.cpp:
// 1. Apply s_first elementwise
// 2. Radix-2 Hadamard butterfly (log2(group_size) stages, in-place)
// 3. Normalize by 1/sqrt(group_size) and apply s_second elementwise
//
// InnerQ scale_inv: when non-null, applies per-channel inverse scaling for
// Q/V equalization. For forward (Q rotation): multiply BEFORE signs+WHT.
// For inverse (V un-rotation): multiply AFTER WHT+signs.
template <int direction, int group_size>
static __global__ void k_turbo_wht_f32(const float * __restrict__ src,
float * __restrict__ dst,
const float * __restrict__ scale_inv,
int64_t n_groups,
int64_t head_dim,
int64_t groups_per_head) {
static_assert(group_size == 128 || group_size == 64, "group_size must be 128 or 64");
const int64_t g = blockIdx.x;
if (g >= n_groups) return;
const int t = threadIdx.x; // 0 .. group_size-1
// Map group index to position in the tensor:
// each head has groups_per_head full groups, then a gap of tail elements.
const int64_t head_idx = g / groups_per_head;
const int64_t grp_in_head = g % groups_per_head;
const int64_t base = head_idx * head_dim + grp_in_head * group_size;
__shared__ float x[group_size];
// Load from global memory
x[t] = src[base + t];
__syncthreads();
// InnerQ forward: apply scale_inv BEFORE signs+WHT (for Q pre-rotation)
if (direction == 0 && scale_inv != nullptr) {
x[t] *= scale_inv[t % group_size];
__syncthreads();
}
// Apply first sign array
if (group_size == 128) {
x[t] *= (direction == 0) ? TURBO_WHT_SIGNS1[t] : TURBO_WHT_SIGNS2[t];
} else {
x[t] *= (direction == 0) ? TURBO_WHT_SIGNS1_64[t] : TURBO_WHT_SIGNS2_64[t];
}
__syncthreads();
// WHT butterfly — log2(group_size) stages.
// In stage h, threads where (t % (2h)) < h read x[t] and x[t+h],
// then write x[t] = a+b and x[t+h] = a-b. Each active thread
// owns a disjoint pair, so no intra-stage conflicts exist.
#define WHT_STAGE(h) \
if (t % (2*(h)) < (h)) { float a = x[t], b = x[t+(h)]; x[t] = a+b; x[t+(h)] = a-b; } \
__syncthreads();
WHT_STAGE(1)
WHT_STAGE(2)
WHT_STAGE(4)
WHT_STAGE(8)
WHT_STAGE(16)
WHT_STAGE(32)
if (group_size == 128) { WHT_STAGE(64) }
#undef WHT_STAGE
// Normalize and apply second sign array, write to output
constexpr float inv_sqrt = (group_size == 128) ? 0.08838834764831845f : 0.125f;
float result;
if (group_size == 128) {
result = x[t] * inv_sqrt *
((direction == 0) ? TURBO_WHT_SIGNS2[t] : TURBO_WHT_SIGNS1[t]);
} else {
result = x[t] * inv_sqrt *
((direction == 0) ? TURBO_WHT_SIGNS2_64[t] : TURBO_WHT_SIGNS1_64[t]);
}
// InnerQ inverse: apply scale_inv AFTER WHT+signs (for V un-rotation)
if (direction == 1 && scale_inv != nullptr) {
result *= scale_inv[t % group_size];
}
dst[base + t] = result;
}
// ─── Simple copy kernel for tail elements (identity pass-through) ────────────
static __global__ void k_turbo_wht_copy_tail(const float * __restrict__ src,
float * __restrict__ dst,
int64_t n_heads,
int64_t head_dim,
int64_t tail_offset,
int tail_size) {
const int64_t i = (int64_t)blockIdx.x * blockDim.x + threadIdx.x;
if (i >= n_heads * tail_size) return;
const int64_t head_idx = i / tail_size;
const int64_t tail_elem = i % tail_size;
const int64_t offset = head_idx * head_dim + tail_offset + tail_elem;
dst[offset] = src[offset];
}
// ─── Dispatch ─────────────────────────────────────────────────────────────────
void ggml_cuda_turbo_wht(ggml_backend_cuda_context & ctx, ggml_tensor * dst) {
const ggml_tensor * src = dst->src[0];
const ggml_tensor * scale_tensor = dst->src[1]; // InnerQ scale_inv (may be NULL)
GGML_ASSERT(src->type == GGML_TYPE_F32);
GGML_ASSERT(dst->type == GGML_TYPE_F32);
GGML_ASSERT(ggml_is_contiguous(src));
GGML_ASSERT(ggml_is_contiguous(dst));
int direction;
int group_size;
memcpy(&direction, dst->op_params + 0, sizeof(int));
memcpy(&group_size, dst->op_params + sizeof(int), sizeof(int));
const int64_t head_dim = src->ne[0];
const int64_t n_heads = ggml_nelements(src) / head_dim;
GGML_ASSERT(group_size == 64 || group_size == 128);
const int64_t groups_per_head = head_dim / group_size;
const int tail_size = (int)(head_dim % group_size);
const int64_t n_groups = groups_per_head * n_heads;
const float * src_ptr = (const float *) src->data;
float * dst_ptr = (float *) dst->data;
const float * scale_inv_ptr = scale_tensor ? (const float *) scale_tensor->data : nullptr;
cudaStream_t stream = ctx.stream();
// Process full groups
if (n_groups > 0) {
dim3 blocks(n_groups);
if (group_size == 128) {
dim3 threads(128);
if (direction == 0) {
k_turbo_wht_f32<0, 128><<<blocks, threads, 0, stream>>>(src_ptr, dst_ptr, scale_inv_ptr, n_groups, head_dim, groups_per_head);
} else {
k_turbo_wht_f32<1, 128><<<blocks, threads, 0, stream>>>(src_ptr, dst_ptr, scale_inv_ptr, n_groups, head_dim, groups_per_head);
}
} else {
dim3 threads(64);
if (direction == 0) {
k_turbo_wht_f32<0, 64><<<blocks, threads, 0, stream>>>(src_ptr, dst_ptr, scale_inv_ptr, n_groups, head_dim, groups_per_head);
} else {
k_turbo_wht_f32<1, 64><<<blocks, threads, 0, stream>>>(src_ptr, dst_ptr, scale_inv_ptr, n_groups, head_dim, groups_per_head);
}
}
}
// Pass through tail elements unchanged (no rotation)
// Not needed for 64-aligned dims but kept for completeness
if (tail_size > 0) {
const int64_t total_tail = n_heads * tail_size;
const int block_sz = 256;
const int n_blocks = (int)((total_tail + block_sz - 1) / block_sz);
k_turbo_wht_copy_tail<<<n_blocks, block_sz, 0, stream>>>(
src_ptr, dst_ptr, n_heads, head_dim, groups_per_head * group_size, tail_size);
}
}