← Back to C-Kernel-Engine Docs Doxygen Source Documentation
 
Loading...
Searching...
No Matches
gemm_head_major_output.c File Reference

Output projection from head-major attention (NO LAYOUT CONVERSION) More...

#include <stdint.h>
#include <stddef.h>
#include <string.h>
#include "ckernel_quant.h"
#include "ckernel_dtype.h"

Go to the source code of this file.

Functions

void ck_gemm_nt_head_major_q5_0 (const float *attn_out, const void *wo, const float *bias, float *output, int tokens, int embed_dim, int num_heads, int head_dim)
 Output projection from head-major attention (auto-dispatch)
 
void ck_gemm_nt_head_major_q8_0 (const float *attn_out, const void *wo, const float *bias, float *output, int tokens, int embed_dim, int num_heads, int head_dim)
 Output projection from head-major attention (Q8_0 weights)
 
void dequant_q5_0_block (const block_q5_0 *block, float *output)
 Dequantize a single Q5_0 block to FP32.
 
void dequant_q5_0_row (const void *src, float *dst, size_t n_elements)
 Dequantize Q5_0 row (multiple blocks)
 
void gemv_nt_q5_0_head_major_output (float *output, const float *attn_out, const void *wo, const float *bias, int tokens, int embed_dim, int num_heads, int head_dim)
 Output projection reading head-major attention output (Q5_0 weights)
 

Detailed Description

Output projection from head-major attention (NO LAYOUT CONVERSION)

CK-ENGINE KERNEL RULES:

  1. NO malloc/free - memory via bump allocator, pointers passed in
  2. NO OpenMP - parallelization at orchestrator/codegen layer
  3. NO memcpy for layout - use strided access, not copies
  4. API must define: inputs, outputs, workspace, and memory layouts
  5. Pure computation - deterministic, no side effects

After changes: make test && make llamacpp-parity-full

PROBLEM THIS SOLVES:

The standard mega_fused_attention_prefill has a bottleneck: attn_out [num_heads, tokens, head_dim] (head-major) → flatten_head_major() - 448 memcpy calls for 32 tokens × 14 heads! → token-major buffer → GEMM output projection

This kernel eliminates the flatten by reading head-major data directly with strided access. The output projection computes:

output[t, n] = bias[n] + sum_h wo[n, h*head_dim:(h+1)*head_dim] @ attn_out[h, t, :]

where wo is Q5_0 quantized [embed_dim, embed_dim] and attn_out is head-major.

Expected speedup: 1.5-2x by eliminating 448 small memcpy calls.

Definition in file gemm_head_major_output.c.

Function Documentation

◆ ck_gemm_nt_head_major_q5_0()

void ck_gemm_nt_head_major_q5_0 ( const float *  attn_out,
const void *  wo,
const float *  bias,
float *  output,
int  tokens,
int  embed_dim,
int  num_heads,
int  head_dim 
)

Output projection from head-major attention (auto-dispatch)

This replaces flatten_head_major() + ck_gemm_nt_quant() with a single strided-access kernel that reads head-major attention output directly.

Definition at line 290 of file gemm_head_major_output.c.

298{
299#if defined(__AVX__)
300 gemv_nt_q5_0_head_major_output_avx(output, attn_out, wo, bias,
301 tokens, embed_dim, num_heads, head_dim);
302#else
303 gemv_nt_q5_0_head_major_output(output, attn_out, wo, bias,
304 tokens, embed_dim, num_heads, head_dim);
305#endif
306}
void gemv_nt_q5_0_head_major_output(float *output, const float *attn_out, const void *wo, const float *bias, int tokens, int embed_dim, int num_heads, int head_dim)
Output projection reading head-major attention output (Q5_0 weights)

References gemv_nt_q5_0_head_major_output().

Referenced by mega_fused_attention_prefill().

◆ ck_gemm_nt_head_major_q8_0()

void ck_gemm_nt_head_major_q8_0 ( const float *  attn_out,
const void *  wo,
const float *  bias,
float *  output,
int  tokens,
int  embed_dim,
int  num_heads,
int  head_dim 
)

Output projection from head-major attention (Q8_0 weights)

Definition at line 315 of file gemm_head_major_output.c.

323{
324 if (!output || !attn_out || !wo) return;
325 if (tokens <= 0 || embed_dim <= 0 || num_heads <= 0 || head_dim <= 0) return;
326
327 const int blocks_per_head = head_dim / QK8_0;
328 const int blocks_per_row = embed_dim / QK8_0;
329 const block_q8_0 *weights = (const block_q8_0 *)wo;
330
331 const size_t token_stride = head_dim;
332 const size_t head_stride = (size_t)tokens * token_stride;
333
334 /* Initialize output */
335 if (bias) {
336 for (int t = 0; t < tokens; t++) {
337 float *out_row = output + (size_t)t * embed_dim;
338 for (int n = 0; n < embed_dim; n++) {
339 out_row[n] = bias[n];
340 }
341 }
342 } else {
343 memset(output, 0, (size_t)tokens * embed_dim * sizeof(float));
344 }
345
346 /* Accumulate from each head */
347 for (int h = 0; h < num_heads; h++) {
348 const float *head_data = attn_out + (size_t)h * head_stride;
349 const int head_offset = h * blocks_per_head;
350
351 for (int n_block = 0; n_block < blocks_per_head; n_block++) {
352 for (int n = 0; n < embed_dim; n++) {
353 const block_q8_0 *w_row = weights + (size_t)n * blocks_per_row + head_offset + n_block;
354 const float d = CK_FP16_TO_FP32(w_row->d);
355
356 for (int t = 0; t < tokens; t++) {
357 const float *token_vec = head_data + (size_t)t * token_stride + (size_t)n_block * QK8_0;
358 float sum = 0.0f;
359
360 for (int j = 0; j < QK8_0; j++) {
361 sum += d * (float)w_row->qs[j] * token_vec[j];
362 }
363
364 output[(size_t)t * embed_dim + n] += sum;
365 }
366 }
367 }
368 }
369}
#define CK_FP16_TO_FP32(x)
#define QK8_0
int8_t qs[32]

References CK_FP16_TO_FP32, block_q8_0::d, QK8_0, and block_q8_0::qs.

Referenced by mega_fused_attention_prefill().

◆ dequant_q5_0_block()

void dequant_q5_0_block ( const block_q5_0 block,
float *  output 
)

Dequantize a single Q5_0 block to FP32.

Parameters
blockPointer to Q5_0 block (22 bytes)
outputOutput FP32 array (32 floats)

Definition at line 163 of file dequant_kernels.c.

164{
165 const float d = GGML_FP16_TO_FP32(block->d);
166
167 /* Get high bits as a 32-bit integer */
168 uint32_t qh;
169 memcpy(&qh, block->qh, sizeof(qh));
170
171 /* llama.cpp Q5_0 layout:
172 * - Weight j uses: low nibble of qs[j], high bit from qh bit j
173 * - Weight j+16 uses: high nibble of qs[j], high bit from qh bit (j+12)
174 */
175 for (int j = 0; j < QK5_0 / 2; j++) {
176 const uint8_t packed = block->qs[j];
177
178 /* Extract low 4 bits for two weights */
179 const int lo = (packed & 0x0F);
180 const int hi = (packed >> 4);
181
182 /* Extract high bits from qh - matches llama.cpp exactly */
183 const int xh_0 = ((qh >> (j + 0)) << 4) & 0x10;
184 const int xh_1 = ((qh >> (j + 12))) & 0x10;
185
186 /* Combine: 5-bit value, range 0-31, then subtract 16 */
187 const int q0 = (lo | xh_0) - 16;
188 const int q1 = (hi | xh_1) - 16;
189
190 output[j] = d * (float)q0;
191 output[j + 16] = d * (float)q1;
192 }
193}
#define QK5_0
#define GGML_FP16_TO_FP32
uint8_t qh[4]
uint8_t qs[32/2]

◆ dequant_q5_0_row()

void dequant_q5_0_row ( const void *  src,
float *  dst,
size_t  n_elements 
)

Dequantize Q5_0 row (multiple blocks)

Definition at line 198 of file dequant_kernels.c.

199{
200 const block_q5_0 *blocks = (const block_q5_0 *)src;
201 const size_t n_blocks = n_elements / QK5_0;
202
203 for (size_t b = 0; b < n_blocks; b++) {
204 dequant_q5_0_block(&blocks[b], &dst[b * QK5_0]);
205 }
206}
void dequant_q5_0_block(const block_q5_0 *block, float *output)
Dequantize a single Q5_0 block to FP32.

◆ gemv_nt_q5_0_head_major_output()

void gemv_nt_q5_0_head_major_output ( float *  output,
const float *  attn_out,
const void *  wo,
const float *  bias,
int  tokens,
int  embed_dim,
int  num_heads,
int  head_dim 
)

Output projection reading head-major attention output (Q5_0 weights)

Parameters
outputOutput [tokens, embed_dim] (token-major, written contiguously)
attn_outAttention output [num_heads, tokens, head_dim] (head-major, strided)
woOutput weights in Q5_0 format [embed_dim, embed_dim]
biasOptional bias [embed_dim]
tokensNumber of tokens
embed_dimOutput embedding dimension
num_headsNumber of attention heads
head_dimHead dimension (must be multiple of 32 for Q5_0)

Definition at line 63 of file gemm_head_major_output.c.

71{
72 if (!output || !attn_out || !wo) return;
73 if (tokens <= 0 || embed_dim <= 0 || num_heads <= 0 || head_dim <= 0) return;
74
75 const int blocks_per_head = head_dim / QK5_0;
76 const int blocks_per_row = embed_dim / QK5_0;
77 const block_q5_0 *weights = (const block_q5_0 *)wo;
78
79 /* Strides for head-major layout */
80 const size_t token_stride = head_dim; /* attn_out[h][t] offset */
81 const size_t head_stride = (size_t)tokens * token_stride; /* attn_out[h] offset */
82
83 /* Initialize output with bias (if provided) */
84 if (bias) {
85 for (int t = 0; t < tokens; t++) {
86 float *out_row = output + (size_t)t * embed_dim;
87 for (int n = 0; n < embed_dim; n++) {
88 out_row[n] = bias[n];
89 }
90 }
91 } else {
92 memset(output, 0, (size_t)tokens * embed_dim * sizeof(float));
93 }
94
95 /* Accumulate contributions from each head */
96 for (int h = 0; h < num_heads; h++) {
97 const float *head_data = attn_out + (size_t)h * head_stride;
98
99 /* For each output row (n) corresponding to this head's slice */
100 const int head_offset = h * blocks_per_head;
101
102 for (int n_block = 0; n_block < blocks_per_head; n_block++) {
103 for (int n = 0; n < embed_dim; n++) {
104 const block_q5_0 *w_row = weights + (size_t)n * blocks_per_row + head_offset + n_block;
105 const float d = CK_FP16_TO_FP32(w_row->d);
106
107 /* Get high bits */
108 uint32_t qh;
109 memcpy(&qh, w_row->qh, sizeof(qh));
110
111 /* Accumulate for all tokens at once (better cache reuse) */
112 for (int t = 0; t < tokens; t++) {
113 const float *token_vec = head_data + (size_t)t * token_stride + (size_t)n_block * QK5_0;
114 float sum = 0.0f;
115
116 /* Q5_0 dot product for this block */
117 for (int j = 0; j < QK5_0 / 2; j++) {
118 const uint8_t packed = w_row->qs[j];
119 const int lo = (packed & 0x0F);
120 const int hi = (packed >> 4);
121 const int xh_0 = ((qh >> (j + 0)) << 4) & 0x10;
122 const int xh_1 = ((qh >> (j + 12))) & 0x10;
123 const int q0 = (lo | xh_0) - 16;
124 const int q1 = (hi | xh_1) - 16;
125
126 sum += d * (float)q0 * token_vec[j];
127 sum += d * (float)q1 * token_vec[j + 16];
128 }
129
130 output[(size_t)t * embed_dim + n] += sum;
131 }
132 }
133 }
134 }
135}

References CK_FP16_TO_FP32, block_q5_0::d, block_q5_0::qh, QK5_0, and block_q5_0::qs.

Referenced by ck_gemm_nt_head_major_q5_0().