Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 46 additions & 2 deletions ggml/src/ggml-cuda/convert.cu
Original file line number Diff line number Diff line change
Expand Up @@ -503,6 +503,50 @@ static void dequantize_block_cont_cuda(const void * __restrict__ vx, dst_t * __r
dequantize_block_cuda<qk, qr, dequantize_kernel, dst_t>(vx, y, k, 1, 1, 1, k/qk, k/qk, k/qk, stream);
}

// Fast warp-cooperative TQ4_1S dequant: one warp per 32-element block.
// WHT via __shfl_xor_sync — 16× less compute than the per-element generic template.
template <typename dst_t>
static __global__ void k_dequantize_tq4_1s_warp(
const block_tq4_1s * __restrict__ vx, dst_t * __restrict__ y,
const int64_t n_elements) {
const int64_t block_idx = (int64_t)blockIdx.x * blockDim.y + threadIdx.y;
const int lane = threadIdx.x;
if (block_idx * 32 + lane >= n_elements) return;

const block_tq4_1s * blk = &vx[block_idx];
const float d = (lane < 16) ? __half2float(blk->d0) : __half2float(blk->d1);
const uint8_t idx = (blk->qs[lane / 2] >> ((lane & 1) * 4)) & 0xF;
float val = TQ4_CENTROIDS_WEIGHT[idx] * d;

#pragma unroll
for (int h = 1; h < 32; h <<= 1) {
float o = __shfl_xor_sync(0xffffffff, val, h);
val = (lane & h) ? (o - val) : (val + o);
}
val *= 0.17677669529663688f * TQ_WEIGHT_SIGNS[lane];

y[block_idx * 32 + lane] = (dst_t)val;
}

template <typename dst_t>
static void dequantize_tq4_1s_warp_cuda(const void * vx, dst_t * y, const int64_t k, cudaStream_t stream) {
GGML_ASSERT(k % 32 == 0);
const int64_t n_blocks = k / 32;
const int wpb = 4;
const dim3 block(32, wpb);
const dim3 grid((n_blocks + wpb - 1) / wpb);
k_dequantize_tq4_1s_warp<<<grid, block, 0, stream>>>((const block_tq4_1s *)vx, y, k);
}

// Non-contiguous version for general tensor layouts
template <typename dst_t>
static void dequantize_tq4_1s_warp_nc_cuda(const void * vx, dst_t * y,
const int64_t ne00, const int64_t ne01, const int64_t ne02, const int64_t ne03,
const int64_t s01, const int64_t s02, const int64_t s03, cudaStream_t stream) {
// For non-contiguous, fall back to generic per-element (rare path)
dequantize_block_cuda<QK_TQ4_1S, QR_TQ4_1S, dequantize_tq4_1s, dst_t>(vx, y, ne00, ne01, ne02, ne03, s01, s02, s03, stream);
}

static void dequantize_block_q8_0_f16_cuda(const void * __restrict__ vx, half * __restrict__ y, const int64_t k, cudaStream_t stream) {
const int num_blocks = (k + CUDA_Q8_0_NE_ALIGN - 1) / CUDA_Q8_0_NE_ALIGN;
if (k % CUDA_Q8_0_NE_ALIGN == 0) {
Expand Down Expand Up @@ -764,7 +808,7 @@ to_fp16_cuda_t ggml_get_to_fp16_cuda(ggml_type type) {
case GGML_TYPE_TURBO4_0:
return dequantize_block_cont_cuda<QK_TURBO4, QR_TURBO4, dequantize_turbo4_0>;
case GGML_TYPE_TQ4_1S:
return dequantize_block_cont_cuda<QK_TQ4_1S, QR_TQ4_1S, dequantize_tq4_1s>;
return dequantize_tq4_1s_warp_cuda<half>; // fast warp-cooperative WHT
case GGML_TYPE_TQ3_1S:
return dequantize_block_cont_cuda<QK_TQ3_0, QR_TQ3_1S, dequantize_tq3_1s>;
case GGML_TYPE_F32:
Expand Down Expand Up @@ -827,7 +871,7 @@ to_fp32_cuda_t ggml_get_to_fp32_cuda(ggml_type type) {
case GGML_TYPE_TURBO4_0:
return dequantize_block_cont_cuda<QK_TURBO4, QR_TURBO4, dequantize_turbo4_0>;
case GGML_TYPE_TQ4_1S:
return dequantize_block_cont_cuda<QK_TQ4_1S, QR_TQ4_1S, dequantize_tq4_1s>;
return dequantize_tq4_1s_warp_cuda<float>; // fast warp-cooperative WHT
case GGML_TYPE_TQ3_1S:
return dequantize_block_cont_cuda<QK_TQ3_0, QR_TQ3_1S, dequantize_tq3_1s>;
case GGML_TYPE_F16:
Expand Down
34 changes: 26 additions & 8 deletions ggml/src/ggml-cuda/ggml-cuda.cu
Original file line number Diff line number Diff line change
Expand Up @@ -642,13 +642,26 @@ static void ggml_backend_cuda_buffer_memset_tensor(ggml_backend_buffer_t buffer,
CUDA_CHECK(cudaStreamSynchronize(cudaStreamPerThread));
}

// TQ4_1S load-time q8_0 conversion: ON by default for best prefill speed.
// Native TQ4_1S decode is faster (+29-33%) but prefill is 2× slower because
// cuBLAS dequant-to-f16 requires per-element inverse WHT.
// Opt-out: GGML_TQ_NATIVE=1 for decode-heavy workloads (saves 1.7× VRAM).
static bool ggml_tq_convert_q8() {
static int val = -1;
if (val == -1) {
const char * env = getenv("GGML_TQ_NATIVE");
val = (env && env[0] == '1') ? 0 : 1; // default ON, GGML_TQ_NATIVE=1 disables
}
return val == 1;
}

static void ggml_backend_cuda_buffer_set_tensor(ggml_backend_buffer_t buffer, ggml_tensor * tensor, const void * data, size_t offset, size_t size) {
ggml_backend_cuda_buffer_context * ctx = (ggml_backend_cuda_buffer_context *)buffer->context;

ggml_cuda_set_device(ctx->device);

// TQ4_1S → q8_0 load-time conversion
if (tensor->type == GGML_TYPE_TQ4_1S && offset == 0 && size == ggml_nbytes(tensor)) {
// TQ4_1S → q8_0 load-time conversion (opt-in: GGML_TQ_CONVERT_Q8=1)
if (ggml_tq_convert_q8() && tensor->type == GGML_TYPE_TQ4_1S && offset == 0 && size == ggml_nbytes(tensor)) {
const int64_t n_elements = ggml_nelements(tensor);

// Upload TQ4_1S to a temp GPU buffer
Expand Down Expand Up @@ -771,8 +784,8 @@ static size_t ggml_backend_cuda_buffer_type_get_alloc_size(ggml_backend_buffer_t
size_t size = ggml_nbytes(tensor);
int64_t ne0 = tensor->ne[0];

// TQ4_1S → q8_0 load-time conversion: allocate q8_0-sized space in VRAM
if (tensor->type == GGML_TYPE_TQ4_1S) {
// TQ4_1S → q8_0 load-time conversion: allocate q8_0-sized space if opted in
if (ggml_tq_convert_q8() && tensor->type == GGML_TYPE_TQ4_1S) {
// q8_0 block: 34 bytes per 32 elements. TQ4_1S block: 20 bytes per 32 elements.
const int64_t n_blocks = ggml_nelements(tensor) / QK_TQ4_1S;
size = n_blocks * sizeof(block_q8_0);
Expand Down Expand Up @@ -2291,7 +2304,7 @@ static void ggml_cuda_mul_mat(ggml_backend_cuda_context & ctx, const ggml_tensor
&& src1->type == GGML_TYPE_F32 && dst->type == GGML_TYPE_F32;
bool use_mul_mat_f = !ggml_is_quantized(src0->type)
&& src1->type == GGML_TYPE_F32 && dst->type == GGML_TYPE_F32;
// TQ weight types use dequant-to-f16 cuBLAS path only (no mmvq/mmq kernels)
// TQ weight types use fused dp4a path (all batch sizes), not mmvq/mmq
const bool is_tq_weight = (src0->type == GGML_TYPE_TQ4_1S || src0->type == GGML_TYPE_TQ3_1S);
bool use_mul_mat_vec_q = ggml_is_quantized(src0->type) && !bad_padding_clear && !is_tq_weight
&& src1->type == GGML_TYPE_F32 && dst->type == GGML_TYPE_F32
Expand Down Expand Up @@ -2360,9 +2373,14 @@ static void ggml_cuda_mul_mat(ggml_backend_cuda_context & ctx, const ggml_tensor
ggml_cuda_op_mul_mat(ctx, src0, src1, dst, ggml_cuda_op_mul_mat_vec_q, quantize_row_q8_1_cuda);
} else if (use_mul_mat_q) {
ggml_cuda_op_mul_mat(ctx, src0, src1, dst, ggml_cuda_op_mul_mat_q, quantize_mmq_q8_1_cuda);
} else if (!split && is_tq_weight && src1->ne[1] == 1) {
// Fused TQ weight mul_mat_vec with pre-rotated activations via warp shuffle WHT
ggml_cuda_mul_mat_vec_tq(ctx, src0, src1, dst);
} else if (!split && is_tq_weight && src1->ne[1] <= MMVQ_MAX_BATCH_SIZE) {
// Fused TQ weight mul_mat with pre-rotated activations via warp shuffle WHT
// Handles ne[1]=1 (decode) and ne[1]≤8 (multi-token / speculative decoding)
ggml_cuda_mul_mat_tq(ctx, src0, src1, dst);
} else if (!split && is_tq_weight && src0->type == GGML_TYPE_TQ4_1S) {
// Large prefill: runtime TQ4_1S → q8_0 scratch conversion + cuBLAS
// Gets tensor core throughput without permanent 1.7× VRAM cost
ggml_cuda_mul_mat_tq4_1s_cublas(ctx, src0, src1, dst);
} else {
ggml_cuda_op_mul_mat(ctx, src0, src1, dst, ggml_cuda_op_mul_mat_cublas, nullptr);
}
Expand Down
Loading
Loading