mirror of
https://github.com/ggml-org/llama.cpp.git
synced 2026-09-04 02:37:27 +02:00
opencl: quant lm_head / decode GEMV and medium-batch GEMM optimizations (speculative decoding/MTP) (#26477)
* opencl: quant lm_head / decode GEMV and medium-batch GEMM optimizations * opencl: guard q4_K/q6_K tiled_ns convert-kernel registration for non-Adreno build * opencl: gate q4_K MUL_MAT+GLU fusion dispatch to Adreno * opencl: require the noshuffle weight layout in the q4_K GLU fusion gate * opencl: do not take the vectorized f16 mrow GEMV path on an unaligned row stride * opencl: pass the new get_scale_min_k4 stride argument at the row-major call sites * opencl: enable the q4_K split-K decode GEMV only where it is measured to win * opencl: record the X1-85 split-K datapoint (neutral, exclusion confirmed) * opencl: restrict the tiled lm_head/embed GEMV default to X2E/A8X * opencl: fix q4_K variant kernels to read the transposed scales layout * opencl: keep the flat-GEMV large-m escape opt-in * opencl: guard the o4 GEMV store against the rounded-up dispatch tail * opencl: restore the tiled q4_K/q6_K layout on tensor read-back * opencl: split-K for the q8_0 decode GEMV at small M * opencl: keep the q6_K noshuffle correctness escape ahead of the opt-in gate
This commit is contained in:
@@ -85,6 +85,7 @@ set(GGML_OPENCL_KERNELS
|
||||
mul_mv_f16_f32_1row
|
||||
mul_mv_f16_f32_l4
|
||||
mul_mv_f16_f32
|
||||
mul_mv_f16_f32_mrow
|
||||
mul_mv_f32_f32
|
||||
mul_mv_q1_0_f32
|
||||
mul_mv_q1_0_f32_flat
|
||||
@@ -180,9 +181,14 @@ set(GGML_OPENCL_KERNELS
|
||||
gemv_noshuffle_q8_0_f32
|
||||
gemm_noshuffle_q8_0_f32
|
||||
gemv_noshuffle_q4_k_f32
|
||||
gemv_noshuffle_q4_k_f32_o4
|
||||
gemv_noshuffle_q4_k_f32_tiled
|
||||
gemm_noshuffle_q4_k_f32
|
||||
gemv_noshuffle_q6_k_f32
|
||||
gemv_noshuffle_q6_k_f32_o4
|
||||
gemv_noshuffle_q6_k_f32_tiled
|
||||
gemm_noshuffle_q6_k_f32
|
||||
gemm_noshuffle_q6_k_f32_tiled
|
||||
gemv_noshuffle_q5_k_f32
|
||||
gemm_noshuffle_q5_k_f32
|
||||
mul
|
||||
|
||||
+1510
-101
File diff suppressed because it is too large
Load Diff
@@ -1110,6 +1110,78 @@ kernel void kernel_restore_block_q4_k_trans4_ns(
|
||||
}
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// kernel_convert_block_q4_k_tiled_ns
|
||||
//
|
||||
// Tiled-wide layout for the long-vocab q4_K lm_head/embed GEMV (decode path).
|
||||
// Mirror of kernel_convert_block_q6_k_tiled_ns: recovers each weight's 4-bit
|
||||
// code in CANONICAL ggml element order (e in [0,256)) and re-packs into 32 uints
|
||||
// (8 codes/uint), stored TILED by 64 output rows so the matching GEMV
|
||||
// (gemv_noshuffle_q4_k_f32_tiled) coalesces every weight load. The 12-byte
|
||||
// packed scale block `s` and d/dm are stored per (row, K-block) tiled; the GEMV
|
||||
// re-derives the 8 (scale,min) pairs via get_scale_min_k4, exactly like the o4
|
||||
// kernel. Both ends owned here -> correct by construction vs the reference q4_K
|
||||
// dequant. Requires ne01 % 64 == 0 (gated host-side). Buffer sizes identical to
|
||||
// the trans4_ns layout.
|
||||
//
|
||||
// q uint4 granule g of (row r, K-block sb): idx = ((rt*ne00_blk+sb)*8 + g)*64 + rit
|
||||
// s (12 bytes) of (r, sb): idx = (rt*ne00_blk+sb)*64 + rit, *12
|
||||
// d/dm (half) of (r, sb): idx = (rt*ne00_blk+sb)*64 + rit
|
||||
// where rt = r/64, rit = r%64.
|
||||
//------------------------------------------------------------------------------
|
||||
kernel void kernel_convert_block_q4_k_tiled_ns(
|
||||
__global struct block_q4_K * src0,
|
||||
__global uint * dst_q, // 32 uints / superblock (4-bit codes, 8 codes/uint)
|
||||
__global half * dst_d, // 1 half / superblock
|
||||
__global half * dst_dm, // 1 half / superblock
|
||||
__global uchar * dst_s, // K_SCALE_SIZE (12) bytes / superblock
|
||||
uint ne00,
|
||||
uint ne01
|
||||
) {
|
||||
uint i00 = get_global_id(1); // K-block index (superblock along ne00)
|
||||
uint i01 = get_global_id(0); // output row index (along ne01)
|
||||
uint i02 = get_global_id(2); // batch
|
||||
|
||||
uint ne00_blk = ne00 / QK_K;
|
||||
|
||||
uint src_blk_offset = i00 + i01 * ne00_blk + i02 * ne00_blk * ne01;
|
||||
__global struct block_q4_K * b = src0 + src_blk_offset;
|
||||
|
||||
uint rt = i01 / 64;
|
||||
uint rit = i01 % 64;
|
||||
uint tile_blk = (i02 * (ne01 / 64) + rt) * ne00_blk + i00;
|
||||
|
||||
// --- recover canonical 4-bit codes in e-order, pack 8 codes/uint ---
|
||||
uint qw[32] = {0};
|
||||
for (uint e = 0; e < 256; ++e) {
|
||||
uint g = e >> 6; // group 0..3 (q advances 32 bytes/group)
|
||||
uint within = e & 63u;
|
||||
uint hlf = within >> 5; // 0 = low nibble, 1 = high nibble
|
||||
uint l = within & 31u; // 0..31
|
||||
uchar byte = b->q[g * 32u + l];
|
||||
uint code = (hlf == 0u) ? (uint)(byte & 0x0F) : (uint)(byte >> 4);
|
||||
qw[e >> 3] |= code << ((e & 7u) * 4u);
|
||||
}
|
||||
|
||||
for (uint gr = 0; gr < 8; ++gr) {
|
||||
uint base = (tile_blk * 8u + gr) * 64u + rit; // uint4 index
|
||||
dst_q[base * 4u + 0u] = qw[gr * 4u + 0u];
|
||||
dst_q[base * 4u + 1u] = qw[gr * 4u + 1u];
|
||||
dst_q[base * 4u + 2u] = qw[gr * 4u + 2u];
|
||||
dst_q[base * 4u + 3u] = qw[gr * 4u + 3u];
|
||||
}
|
||||
|
||||
// packed scales (12 bytes), tiled per (row, block)
|
||||
__global uchar * s_dst = dst_s + (tile_blk * 64u + rit) * K_SCALE_SIZE;
|
||||
#pragma unroll
|
||||
for (int i = 0; i < K_SCALE_SIZE; ++i) {
|
||||
s_dst[i] = b->s[i];
|
||||
}
|
||||
|
||||
dst_d [tile_blk * 64u + rit] = b->d;
|
||||
dst_dm[tile_blk * 64u + rit] = b->dm;
|
||||
}
|
||||
|
||||
kernel void kernel_convert_block_q5_k_trans4_ns(
|
||||
__global struct block_q5_K * src0,
|
||||
__global uint * dst_qs,
|
||||
@@ -1494,6 +1566,105 @@ kernel void kernel_restore_block_mxfp4_trans(
|
||||
b->e = src_e[src_blk_offset];
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// kernel_convert_block_q6_k_tiled_ns
|
||||
//
|
||||
// Tiled-wide layout for the long-vocab q6_K lm_head/embed GEMV (decode path).
|
||||
// Unlike *_trans4_ns (which mirrors the bit-interleave the legacy 2-output GEMV
|
||||
// consumes), this kernel is correct-by-construction against the CANONICAL ggml
|
||||
// q6_K dequant: it recovers each weight's 6-bit code in element order e in
|
||||
// [0,256), then re-packs low-4-bits into 32 uints (8 codes/uint) and high-2-bits
|
||||
// into 16 uints (16 codes/uint). The matching GEMV (gemv_noshuffle_q6_k_f32_tiled)
|
||||
// unpacks the same order, so both ends are owned here.
|
||||
//
|
||||
// Storage is TILED by 64 output rows so the GEMV's 64-thread tile coalesces:
|
||||
// ql uint4 granule g of (row r, K-block sb): idx = ((rt*ne00_blk + sb)*8 + g)*64 + rit
|
||||
// qh uint4 granule g: idx = ((rt*ne00_blk + sb)*4 + g)*64 + rit
|
||||
// scales (char16) of (r, sb): idx = (rt*ne00_blk + sb)*64 + rit
|
||||
// d (half) of (r, sb): idx = (rt*ne00_blk + sb)*64 + rit
|
||||
// where rt = r/64, rit = r%64. Requires ne01 % 64 == 0 (gated host-side).
|
||||
// Buffer sizes are byte-identical to the trans4_ns layout.
|
||||
//------------------------------------------------------------------------------
|
||||
kernel void kernel_convert_block_q6_k_tiled_ns(
|
||||
__global struct block_q6_K * src0,
|
||||
__global uint * dst_ql, // 32 uints / superblock (low 4 bits, 8 codes/uint)
|
||||
__global uint * dst_qh, // 16 uints / superblock (high 2 bits, 16 codes/uint)
|
||||
__global half * dst_d, // 1 half / superblock
|
||||
__global char * dst_s, // 16 chars/ superblock
|
||||
uint ne00,
|
||||
uint ne01
|
||||
) {
|
||||
uint i00 = get_global_id(1); // K-block index (superblock along ne00)
|
||||
uint i01 = get_global_id(0); // output row index (along ne01)
|
||||
uint i02 = get_global_id(2); // batch
|
||||
|
||||
uint ne00_blk = ne00 / QK_K;
|
||||
|
||||
// Source block: row-major over (i02, i01, i00).
|
||||
uint src_blk_offset = i00 + i01 * ne00_blk + i02 * ne00_blk * ne01;
|
||||
__global struct block_q6_K * b = src0 + src_blk_offset;
|
||||
|
||||
uint rt = i01 / 64;
|
||||
uint rit = i01 % 64;
|
||||
uint tile_blk = (i02 * (ne01 / 64) + rt) * ne00_blk + i00; // tile-major (row-tile, K-block)
|
||||
|
||||
// --- recover canonical 6-bit codes, pack into ql (4b) + qh (2b) in e-order ---
|
||||
// 32 ql-uints (8 low-nibbles each) + 16 qh-uints (16 2-bit slots each).
|
||||
uint qlw[32] = {0};
|
||||
uint qhw[16] = {0};
|
||||
|
||||
for (uint e = 0; e < 256; ++e) {
|
||||
uint n = (e >= 128) ? 1u : 0u; // which 128-half
|
||||
uint within = e - n * 128u;
|
||||
uint q = within / 32u; // quadrant 0..3
|
||||
uint l = within % 32u; // 0..31
|
||||
|
||||
uint off_ql = n * 64u; // raw ql byte base for this half
|
||||
uint off_qh = n * 32u; // raw qh byte base for this half
|
||||
|
||||
uchar low4;
|
||||
uchar qlb0 = b->ql[off_ql + l];
|
||||
uchar qlb1 = b->ql[off_ql + l + 32];
|
||||
if (q == 0) low4 = qlb0 & 0x0F;
|
||||
else if (q == 1) low4 = qlb1 & 0x0F;
|
||||
else if (q == 2) low4 = (qlb0 >> 4) & 0x0F;
|
||||
else low4 = (qlb1 >> 4) & 0x0F;
|
||||
|
||||
uchar hi2 = (b->qh[off_qh + l] >> (q * 2u)) & 0x03;
|
||||
|
||||
// pack low4 (e-order): uint e/8, nibble (e%8)
|
||||
qlw[e >> 3] |= ((uint)low4) << ((e & 7u) * 4u);
|
||||
// pack hi2 (e-order): uint e/16, 2-bit slot (e%16)
|
||||
qhw[e >> 4] |= ((uint)hi2) << ((e & 15u) * 2u);
|
||||
}
|
||||
|
||||
// --- write tiled ---
|
||||
for (uint g = 0; g < 8; ++g) {
|
||||
uint base = (tile_blk * 8u + g) * 64u + rit; // uint4 index
|
||||
dst_ql[base * 4u + 0u] = qlw[g * 4u + 0u];
|
||||
dst_ql[base * 4u + 1u] = qlw[g * 4u + 1u];
|
||||
dst_ql[base * 4u + 2u] = qlw[g * 4u + 2u];
|
||||
dst_ql[base * 4u + 3u] = qlw[g * 4u + 3u];
|
||||
}
|
||||
for (uint g = 0; g < 4; ++g) {
|
||||
uint base = (tile_blk * 4u + g) * 64u + rit; // uint4 index
|
||||
dst_qh[base * 4u + 0u] = qhw[g * 4u + 0u];
|
||||
dst_qh[base * 4u + 1u] = qhw[g * 4u + 1u];
|
||||
dst_qh[base * 4u + 2u] = qhw[g * 4u + 2u];
|
||||
dst_qh[base * 4u + 3u] = qhw[g * 4u + 3u];
|
||||
}
|
||||
|
||||
// scales: 16 chars contiguous per (row, block), tiled
|
||||
__global char * s_dst = dst_s + (tile_blk * 64u + rit) * 16u;
|
||||
#pragma unroll
|
||||
for (int i = 0; i < 16; ++i) {
|
||||
s_dst[i] = b->scales[i];
|
||||
}
|
||||
|
||||
// super-block scale
|
||||
dst_d[tile_blk * 64u + rit] = b->d;
|
||||
}
|
||||
|
||||
kernel void kernel_convert_block_mxfp4_trans4_ns(
|
||||
global struct block_mxfp4 * src0,
|
||||
__global uint * dst_q,
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#pragma OPENCL EXTENSION cl_qcom_reqd_sub_group_size : enable
|
||||
#define ADRENO_GPU 1
|
||||
#define REQD_SUBGROUP_SIZE_128 __attribute__((qcom_reqd_sub_group_size("full")))
|
||||
#define REQD_SUBGROUP_SIZE_64 __attribute__((qcom_reqd_sub_group_size("half")))
|
||||
#endif
|
||||
#define QK_K 256
|
||||
#define K_SCALE_SIZE 12
|
||||
@@ -171,3 +172,319 @@ kernel void kernel_gemm_noshuffle_q4_k_f32(
|
||||
vstore4((float4)(c0.s7, c1.s7, c2.s7, c3.s7), 0, dst + idx);
|
||||
}
|
||||
}
|
||||
|
||||
// 1x8 per-WI tile (1 output row x 8 output cols). For the small-batch
|
||||
// (medium n_q, e.g. MTP/spec verify) path where the 2x8 kernel is starved:
|
||||
// at ne1<=8 the grid is (1, ceil(M/2)) -> only ~M/256 workgroups, leaving
|
||||
// the SP under-occupied. 1 row per WI doubles the M-axis workgroup count
|
||||
// (ceil(M/1)/128 vs ceil(M/2)/128) AND collapses the accumulators to a
|
||||
// single half8 (16 regs, no spill), so more waves co-reside. Same weight
|
||||
// traffic as 2x8 (rows never share weights); the win is pure occupancy.
|
||||
#ifdef ADRENO_GPU
|
||||
REQD_SUBGROUP_SIZE_128
|
||||
#endif
|
||||
kernel void kernel_gemm_noshuffle_q4_k_f32_r1(
|
||||
global const ushort * src0_q,
|
||||
global const uchar * src0_s,
|
||||
global const half * src0_d,
|
||||
global const half * src0_dm,
|
||||
read_only image1d_buffer_t src1,
|
||||
global float * dst,
|
||||
ulong offsetd,
|
||||
int m,
|
||||
int n,
|
||||
int k,
|
||||
int n_no_padding,
|
||||
uchar mask_d6,
|
||||
uchar mask_d4,
|
||||
uchar mask_hi2
|
||||
) {
|
||||
dst = (global float *)((global char *)dst + offsetd);
|
||||
int n_4 = n >> 2;
|
||||
int gy = get_global_id(0);
|
||||
int gx = get_global_id(1); // 1 row per WI
|
||||
|
||||
half8 c0 = 0;
|
||||
half8 B;
|
||||
half dq;
|
||||
|
||||
int num_blocks_K = k / QK_K;
|
||||
|
||||
global const ushort * weight_ptr = src0_q + gx;
|
||||
global const half * d_ptr = src0_d + gx;
|
||||
global const half * dm_ptr = src0_dm + gx;
|
||||
|
||||
for (int i = 0; i < k; i += 32) {
|
||||
int sb_idx = i / QK_K;
|
||||
int sub_idx = (i / 32) % 8;
|
||||
|
||||
half dd = d_ptr [sb_idx * m];
|
||||
half dmm = dm_ptr[sb_idx * m];
|
||||
|
||||
global const uchar * sc0 = src0_s + sb_idx * K_SCALE_SIZE * m + gx;
|
||||
|
||||
uchar sv0, mn0;
|
||||
get_scale_min_k4(sub_idx, sc0, m, &sv0, &mn0, mask_d6, mask_d4, mask_hi2);
|
||||
|
||||
half scale = convert_half(convert_float(dd) * (float)sv0);
|
||||
half mval = convert_half(convert_float(dmm) * (float)mn0);
|
||||
|
||||
for (int l = 0; l < 32; l += 4) {
|
||||
int ki = i + l;
|
||||
ushort bits = weight_ptr[(ki/4) * m];
|
||||
|
||||
B.s0123 = read_imageh(src1, gy*2 + (ki+0) * n_4);
|
||||
B.s4567 = read_imageh(src1, gy*2+1 + (ki+0) * n_4);
|
||||
dq = (bits & 0x000F) * scale - mval;
|
||||
c0 += B * dq;
|
||||
|
||||
B.s0123 = read_imageh(src1, gy*2 + (ki+1) * n_4);
|
||||
B.s4567 = read_imageh(src1, gy*2+1 + (ki+1) * n_4);
|
||||
dq = ((bits & 0x00F0) >> 4) * scale - mval;
|
||||
c0 += B * dq;
|
||||
|
||||
B.s0123 = read_imageh(src1, gy*2 + (ki+2) * n_4);
|
||||
B.s4567 = read_imageh(src1, gy*2+1 + (ki+2) * n_4);
|
||||
dq = ((bits & 0x0F00) >> 8) * scale - mval;
|
||||
c0 += B * dq;
|
||||
|
||||
B.s0123 = read_imageh(src1, gy*2 + (ki+3) * n_4);
|
||||
B.s4567 = read_imageh(src1, gy*2+1 + (ki+3) * n_4);
|
||||
dq = ((bits & 0xF000) >> 12) * scale - mval;
|
||||
c0 += B * dq;
|
||||
}
|
||||
}
|
||||
|
||||
// Output: 8 cols, 1 row per col-step. Scalar store, coalesced across
|
||||
// neighbouring WIs (consecutive gx -> consecutive dst addresses).
|
||||
int idx = (gy<<3)*m + gx;
|
||||
if (idx < m*n_no_padding) { dst[idx] = c0.s0; idx += m; }
|
||||
if (idx < m*n_no_padding) { dst[idx] = c0.s1; idx += m; }
|
||||
if (idx < m*n_no_padding) { dst[idx] = c0.s2; idx += m; }
|
||||
if (idx < m*n_no_padding) { dst[idx] = c0.s3; idx += m; }
|
||||
if (idx < m*n_no_padding) { dst[idx] = c0.s4; idx += m; }
|
||||
if (idx < m*n_no_padding) { dst[idx] = c0.s5; idx += m; }
|
||||
if (idx < m*n_no_padding) { dst[idx] = c0.s6; idx += m; }
|
||||
if (idx < m*n_no_padding) { dst[idx] = c0.s7; }
|
||||
}
|
||||
|
||||
// 2x8 tile, but weights read through an image1d_buffer (CL_R/UINT32 over the
|
||||
// same packed-q buffer) instead of a plain global buffer. The ne1==1 GEMV
|
||||
// already does this and is much faster per weight byte than this GEMM at
|
||||
// small n_q; the structural difference is the image path hits the dedicated
|
||||
// TPL1 weight cache (L1) while the global path only reaches L2. At small n_q
|
||||
// the forward is weight-read-bound, so L1-cached weights is the lever.
|
||||
// The 2 adjacent rows the 2x8 tile reads as a ushort2 are exactly one uint32,
|
||||
// so the vload2 becomes a single read_imageui at index gx + (ki/4)*(m/2).
|
||||
#ifdef ADRENO_GPU
|
||||
REQD_SUBGROUP_SIZE_128
|
||||
#endif
|
||||
kernel void kernel_gemm_noshuffle_q4_k_f32_kimg(
|
||||
read_only image1d_buffer_t src0_q_img,
|
||||
global const uchar * src0_s,
|
||||
global const half * src0_d,
|
||||
global const half * src0_dm,
|
||||
read_only image1d_buffer_t src1,
|
||||
global float * dst,
|
||||
ulong offsetd,
|
||||
int m,
|
||||
int n,
|
||||
int k,
|
||||
int n_no_padding,
|
||||
uchar mask_d6,
|
||||
uchar mask_d4,
|
||||
uchar mask_hi2
|
||||
) {
|
||||
dst = (global float *)((global char *)dst + offsetd);
|
||||
int n_4 = n >> 2;
|
||||
int m_2 = m >> 1;
|
||||
int gy = get_global_id(0);
|
||||
int gx = get_global_id(1);
|
||||
int gx_2 = gx << 1;
|
||||
|
||||
half8 c0 = 0, c1 = 0;
|
||||
half8 B;
|
||||
half2 dequantized_weights;
|
||||
|
||||
int num_blocks_K = k / QK_K;
|
||||
|
||||
global const half * d_ptr = src0_d + gx_2;
|
||||
global const half * dm_ptr = src0_dm + gx_2;
|
||||
|
||||
for (int i = 0; i < k; i += 32) {
|
||||
int sb_idx = i / QK_K;
|
||||
int sub_idx = (i / 32) % 8;
|
||||
|
||||
half2 d = vload2(0, d_ptr + sb_idx * m);
|
||||
half2 dm = vload2(0, dm_ptr + sb_idx * m);
|
||||
|
||||
global const uchar * sc0 = src0_s + sb_idx * K_SCALE_SIZE * m + (gx_2+0);
|
||||
global const uchar * sc1 = sc0 + 1;
|
||||
|
||||
uchar sv0, mn0, sv1, mn1;
|
||||
get_scale_min_k4(sub_idx, sc0, m, &sv0, &mn0, mask_d6, mask_d4, mask_hi2);
|
||||
get_scale_min_k4(sub_idx, sc1, m, &sv1, &mn1, mask_d6, mask_d4, mask_hi2);
|
||||
|
||||
half2 scale = convert_half2(convert_float2(d) * convert_float2((uchar2)(sv0, sv1)));
|
||||
half2 mval = convert_half2(convert_float2(dm) * convert_float2((uchar2)(mn0, mn1)));
|
||||
|
||||
for (int l = 0; l < 32; l += 4) {
|
||||
int ki = i + l;
|
||||
uint wpacked = read_imageui(src0_q_img, gx + (ki/4) * m_2).x;
|
||||
ushort2 bits2 = (ushort2)((ushort)(wpacked & 0xFFFFu), (ushort)(wpacked >> 16));
|
||||
|
||||
// j=0
|
||||
B.s0123 = read_imageh(src1, gy*2 + (ki+0) * n_4);
|
||||
B.s4567 = read_imageh(src1, gy*2+1 + (ki+0) * n_4);
|
||||
dequantized_weights.s0 = (bits2.s0 & 0x000F) * scale.s0 - mval.s0;
|
||||
dequantized_weights.s1 = (bits2.s1 & 0x000F) * scale.s1 - mval.s1;
|
||||
c0 += B * dequantized_weights.s0;
|
||||
c1 += B * dequantized_weights.s1;
|
||||
|
||||
// j=1
|
||||
B.s0123 = read_imageh(src1, gy*2 + (ki+1) * n_4);
|
||||
B.s4567 = read_imageh(src1, gy*2+1 + (ki+1) * n_4);
|
||||
dequantized_weights.s0 = ((bits2.s0 & 0x00F0) >> 4) * scale.s0 - mval.s0;
|
||||
dequantized_weights.s1 = ((bits2.s1 & 0x00F0) >> 4) * scale.s1 - mval.s1;
|
||||
c0 += B * dequantized_weights.s0;
|
||||
c1 += B * dequantized_weights.s1;
|
||||
|
||||
// j=2
|
||||
B.s0123 = read_imageh(src1, gy*2 + (ki+2) * n_4);
|
||||
B.s4567 = read_imageh(src1, gy*2+1 + (ki+2) * n_4);
|
||||
dequantized_weights.s0 = ((bits2.s0 & 0x0F00) >> 8) * scale.s0 - mval.s0;
|
||||
dequantized_weights.s1 = ((bits2.s1 & 0x0F00) >> 8) * scale.s1 - mval.s1;
|
||||
c0 += B * dequantized_weights.s0;
|
||||
c1 += B * dequantized_weights.s1;
|
||||
|
||||
// j=3
|
||||
B.s0123 = read_imageh(src1, gy*2 + (ki+3) * n_4);
|
||||
B.s4567 = read_imageh(src1, gy*2+1 + (ki+3) * n_4);
|
||||
dequantized_weights.s0 = ((bits2.s0 & 0xF000) >> 12) * scale.s0 - mval.s0;
|
||||
dequantized_weights.s1 = ((bits2.s1 & 0xF000) >> 12) * scale.s1 - mval.s1;
|
||||
c0 += B * dequantized_weights.s0;
|
||||
c1 += B * dequantized_weights.s1;
|
||||
}
|
||||
}
|
||||
|
||||
int idx = (gy<<3)*m + (gx<<1);
|
||||
if (idx+1 < m*n_no_padding) { vstore2((float2)(c0.s0, c1.s0), 0, dst + idx); idx += m; }
|
||||
if (idx+1 < m*n_no_padding) { vstore2((float2)(c0.s1, c1.s1), 0, dst + idx); idx += m; }
|
||||
if (idx+1 < m*n_no_padding) { vstore2((float2)(c0.s2, c1.s2), 0, dst + idx); idx += m; }
|
||||
if (idx+1 < m*n_no_padding) { vstore2((float2)(c0.s3, c1.s3), 0, dst + idx); idx += m; }
|
||||
if (idx+1 < m*n_no_padding) { vstore2((float2)(c0.s4, c1.s4), 0, dst + idx); idx += m; }
|
||||
if (idx+1 < m*n_no_padding) { vstore2((float2)(c0.s5, c1.s5), 0, dst + idx); idx += m; }
|
||||
if (idx+1 < m*n_no_padding) { vstore2((float2)(c0.s6, c1.s6), 0, dst + idx); idx += m; }
|
||||
if (idx+1 < m*n_no_padding) { vstore2((float2)(c0.s7, c1.s7), 0, dst + idx); }
|
||||
}
|
||||
|
||||
// Cooperative-K GEMM for the small-batch (n_q in [2..8]) path. Mirrors the
|
||||
// ne1==1 GEMV's structure: a WG is (COK_SG lanes x COK_NSG subgroups); each
|
||||
// lane owns ONE output row and computes its 8 (padded) columns, and the
|
||||
// COK_NSG subgroups SPLIT the K reduction round-robin, combining via a
|
||||
// __local reduction. This is the thing the per-WI GEMM lacked — at small n_q
|
||||
// the old kernel had ~M/256 workgroups each walking all of K serially; this
|
||||
// has M/64 workgroups AND COK_NSG-way K parallelism. Uses REQD_SUBGROUP_SIZE_64
|
||||
// + barrier (same safe reduction pattern as the GEMV; never sub_group_reduce
|
||||
// at full width on X2 per the GDN miscompile note).
|
||||
#define COK_NSG 8
|
||||
#define COK_SG 64
|
||||
#ifdef ADRENO_GPU
|
||||
REQD_SUBGROUP_SIZE_64
|
||||
#endif
|
||||
kernel void kernel_gemm_noshuffle_q4_k_f32_cok(
|
||||
global const ushort * src0_q,
|
||||
global const uchar * src0_s,
|
||||
global const half * src0_d,
|
||||
global const half * src0_dm,
|
||||
read_only image1d_buffer_t src1,
|
||||
global float * dst,
|
||||
ulong offsetd,
|
||||
int m,
|
||||
int n,
|
||||
int k,
|
||||
int n_no_padding,
|
||||
uchar mask_d6,
|
||||
uchar mask_d4,
|
||||
uchar mask_hi2
|
||||
) {
|
||||
dst = (global float *)((global char *)dst + offsetd);
|
||||
int n_4 = n >> 2;
|
||||
int gx = get_global_id(0); // output row
|
||||
int sg = get_local_id(1); // subgroup index (K-split lane)
|
||||
int lane = get_local_id(0); // lane within subgroup (0..COK_SG-1)
|
||||
|
||||
int num_blocks_K = k / QK_K;
|
||||
int num_32blk = k / 32;
|
||||
|
||||
global const ushort * weight_ptr = src0_q + gx;
|
||||
global const half * d_ptr = src0_d + gx;
|
||||
global const half * dm_ptr = src0_dm + gx;
|
||||
|
||||
half8 acc = 0;
|
||||
half8 B;
|
||||
half dq;
|
||||
|
||||
for (int blk = sg; blk < num_32blk; blk += COK_NSG) {
|
||||
int i = blk << 5; // blk * 32
|
||||
int sb_idx = blk >> 3; // (blk*32) / QK_K (QK_K = 256 = 32*8)
|
||||
int sub_idx = blk & 7; // (i/32) % 8
|
||||
|
||||
half dd = d_ptr [sb_idx * m];
|
||||
half dmm = dm_ptr[sb_idx * m];
|
||||
|
||||
global const uchar * sc0 = src0_s + sb_idx * K_SCALE_SIZE * m + gx;
|
||||
uchar sv0, mn0;
|
||||
get_scale_min_k4(sub_idx, sc0, m, &sv0, &mn0, mask_d6, mask_d4, mask_hi2);
|
||||
half scale = convert_half(convert_float(dd) * (float)sv0);
|
||||
half mval = convert_half(convert_float(dmm) * (float)mn0);
|
||||
|
||||
for (int l = 0; l < 32; l += 4) {
|
||||
int ki = i + l;
|
||||
ushort bits = weight_ptr[(ki>>2) * m];
|
||||
|
||||
B.s0123 = read_imageh(src1, (ki+0) * n_4);
|
||||
B.s4567 = read_imageh(src1, 1 + (ki+0) * n_4);
|
||||
dq = (bits & 0x000F) * scale - mval;
|
||||
acc += B * dq;
|
||||
|
||||
B.s0123 = read_imageh(src1, (ki+1) * n_4);
|
||||
B.s4567 = read_imageh(src1, 1 + (ki+1) * n_4);
|
||||
dq = ((bits & 0x00F0) >> 4) * scale - mval;
|
||||
acc += B * dq;
|
||||
|
||||
B.s0123 = read_imageh(src1, (ki+2) * n_4);
|
||||
B.s4567 = read_imageh(src1, 1 + (ki+2) * n_4);
|
||||
dq = ((bits & 0x0F00) >> 8) * scale - mval;
|
||||
acc += B * dq;
|
||||
|
||||
B.s0123 = read_imageh(src1, (ki+3) * n_4);
|
||||
B.s4567 = read_imageh(src1, 1 + (ki+3) * n_4);
|
||||
dq = ((bits & 0xF000) >> 12) * scale - mval;
|
||||
acc += B * dq;
|
||||
}
|
||||
}
|
||||
|
||||
// cross-subgroup reduction over the K-split (float for accuracy)
|
||||
local float8 reduceLM[COK_SG * (COK_NSG - 1)];
|
||||
if (sg > 0) {
|
||||
reduceLM[(sg - 1) * COK_SG + lane] = convert_float8(acc);
|
||||
}
|
||||
barrier(CLK_LOCAL_MEM_FENCE);
|
||||
|
||||
if (sg == 0) {
|
||||
float8 sum = convert_float8(acc);
|
||||
for (int s = 0; s < COK_NSG - 1; s++) {
|
||||
sum += reduceLM[s * COK_SG + lane];
|
||||
}
|
||||
int idx = gx;
|
||||
if (idx < m*n_no_padding) { dst[idx] = sum.s0; idx += m; }
|
||||
if (idx < m*n_no_padding) { dst[idx] = sum.s1; idx += m; }
|
||||
if (idx < m*n_no_padding) { dst[idx] = sum.s2; idx += m; }
|
||||
if (idx < m*n_no_padding) { dst[idx] = sum.s3; idx += m; }
|
||||
if (idx < m*n_no_padding) { dst[idx] = sum.s4; idx += m; }
|
||||
if (idx < m*n_no_padding) { dst[idx] = sum.s5; idx += m; }
|
||||
if (idx < m*n_no_padding) { dst[idx] = sum.s6; idx += m; }
|
||||
if (idx < m*n_no_padding) { dst[idx] = sum.s7; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#pragma OPENCL EXTENSION cl_qcom_reqd_sub_group_size : enable
|
||||
#define ADRENO_GPU 1
|
||||
#define REQD_SUBGROUP_SIZE_128 __attribute__((qcom_reqd_sub_group_size("full")))
|
||||
#define REQD_SUBGROUP_SIZE_64 __attribute__((qcom_reqd_sub_group_size("half")))
|
||||
#endif
|
||||
|
||||
#ifdef ADRENO_GPU
|
||||
@@ -138,3 +139,107 @@ kernel void kernel_gemm_noshuffle_q6_K_f32(
|
||||
vstore4((float4)(c0.s7, c1.s7, c2.s7, c3.s7), 0, dst + idx);
|
||||
}
|
||||
}
|
||||
|
||||
// Cooperative-K q6_K GEMM for the small-batch (n_q in [2..8]) path. Same idea
|
||||
// as the q4_K _cok kernel: WG = (COK_SG lanes x COK_NSG subgroups), each lane
|
||||
// owns ONE output row (half8 over the 8 padded cols), and the COK_NSG
|
||||
// subgroups split the K iterations round-robin and combine via a __local
|
||||
// reduction. Replaces the default 4-row-per-WI tile that walked all of K alone
|
||||
// (~M/512 WGs + serial reduction) at small n_q. REQD_SUBGROUP_SIZE_64 +
|
||||
// barrier (never sub_group_reduce at full width on X2).
|
||||
#define COK_NSG 8
|
||||
#define COK_SG 64
|
||||
#ifdef ADRENO_GPU
|
||||
REQD_SUBGROUP_SIZE_64
|
||||
#endif
|
||||
kernel void kernel_gemm_noshuffle_q6_K_f32_cok(
|
||||
global const ushort * src0_ql,
|
||||
global const uchar * src0_qh,
|
||||
global const ushort * src0_s,
|
||||
global const half * src0_d,
|
||||
read_only image1d_buffer_t src1,
|
||||
global float * dst,
|
||||
ulong offsetd,
|
||||
int m,
|
||||
int n,
|
||||
int k,
|
||||
int n_no_padding,
|
||||
ushort mask_f000,
|
||||
uchar mask_c0
|
||||
) {
|
||||
dst = (global float *)( (global char *)dst + offsetd );
|
||||
|
||||
int n_4 = n >> 2;
|
||||
int gx = get_global_id(0); // output row
|
||||
int sg = get_local_id(1); // subgroup index (K-split)
|
||||
int lane = get_local_id(0); // lane within subgroup
|
||||
|
||||
global const ushort * ptr_ql = src0_ql + gx;
|
||||
global const uchar * ptr_qh = src0_qh + gx;
|
||||
global const ushort * ptr_s = src0_s + gx;
|
||||
global const half * ptr_d = src0_d + gx;
|
||||
|
||||
half8 acc = 0;
|
||||
half8 B;
|
||||
half dq;
|
||||
|
||||
int num_iter = k >> 2; // k/4 iterations, 4 k-values each
|
||||
|
||||
for (int ib = sg; ib < num_iter; ib += COK_NSG) {
|
||||
int i = ib << 2; // ib * 4
|
||||
|
||||
ushort bits4 = ptr_ql[ib * m]; // ql for row gx at this 4-block
|
||||
uchar bits2 = ptr_qh[ib * m]; // qh
|
||||
|
||||
ushort s_packed = ptr_s[(i >> 5) * m]; // (i/16/2) = i/32
|
||||
char2 sc2 = as_char2(s_packed);
|
||||
char scale_s = (((i >> 4) & 1) == 0) ? sc2.s0 : sc2.s1; // (i/16)%2
|
||||
half scale_d = ptr_d[(i >> 8) * m]; // i/256
|
||||
|
||||
// j=0
|
||||
B.s0123 = read_imageh(src1, (i + 0)*n_4 + 0);
|
||||
B.s4567 = read_imageh(src1, (i + 0)*n_4 + 1);
|
||||
dq = (convert_half((bits4 & 0x000F) | ((bits2 & 0x03) << 4)) - 32.f) * scale_s * scale_d;
|
||||
acc += B * dq;
|
||||
|
||||
// j=1
|
||||
B.s0123 = read_imageh(src1, (i + 1)*n_4 + 0);
|
||||
B.s4567 = read_imageh(src1, (i + 1)*n_4 + 1);
|
||||
dq = (convert_half(((bits4 & 0x00F0) >> 4) | ((bits2 & 0x0C) << 2)) - 32.f) * scale_s * scale_d;
|
||||
acc += B * dq;
|
||||
|
||||
// j=2
|
||||
B.s0123 = read_imageh(src1, (i + 2)*n_4 + 0);
|
||||
B.s4567 = read_imageh(src1, (i + 2)*n_4 + 1);
|
||||
dq = (convert_half(((bits4 & 0x0F00) >> 8) | (bits2 & 0x30)) - 32.f) * scale_s * scale_d;
|
||||
acc += B * dq;
|
||||
|
||||
// j=3
|
||||
B.s0123 = read_imageh(src1, (i + 3)*n_4 + 0);
|
||||
B.s4567 = read_imageh(src1, (i + 3)*n_4 + 1);
|
||||
dq = (convert_half(((bits4 & mask_f000) >> 12) | ((bits2 & mask_c0) >> 2)) - 32.f) * scale_s * scale_d;
|
||||
acc += B * dq;
|
||||
}
|
||||
|
||||
local float8 reduceLM[COK_SG * (COK_NSG - 1)];
|
||||
if (sg > 0) {
|
||||
reduceLM[(sg - 1) * COK_SG + lane] = convert_float8(acc);
|
||||
}
|
||||
barrier(CLK_LOCAL_MEM_FENCE);
|
||||
|
||||
if (sg == 0) {
|
||||
float8 sum = convert_float8(acc);
|
||||
for (int s = 0; s < COK_NSG - 1; s++) {
|
||||
sum += reduceLM[s * COK_SG + lane];
|
||||
}
|
||||
int idx = gx;
|
||||
if (idx < m*n_no_padding) { dst[idx] = sum.s0; idx += m; }
|
||||
if (idx < m*n_no_padding) { dst[idx] = sum.s1; idx += m; }
|
||||
if (idx < m*n_no_padding) { dst[idx] = sum.s2; idx += m; }
|
||||
if (idx < m*n_no_padding) { dst[idx] = sum.s3; idx += m; }
|
||||
if (idx < m*n_no_padding) { dst[idx] = sum.s4; idx += m; }
|
||||
if (idx < m*n_no_padding) { dst[idx] = sum.s5; idx += m; }
|
||||
if (idx < m*n_no_padding) { dst[idx] = sum.s6; idx += m; }
|
||||
if (idx < m*n_no_padding) { dst[idx] = sum.s7; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,136 @@
|
||||
// Batched (N>1) q6_K GEMM over the 64-row-TILED canonical layout produced by
|
||||
// kernel_convert_block_q6_k_tiled_ns (cvt.cl). Companion to the decode kernel
|
||||
// kernel_gemv_noshuffle_q6_K_f32_tiled: SAME pack, SAME canonical e-order
|
||||
// dequant (correct by construction vs reference ggml q6_K), extended to N output
|
||||
// columns. Makes the batched lm_head/embed (perplexity, spec-decode verify,
|
||||
// batched serving) correct on GPU while keeping the tiled convert the fast decode
|
||||
// GEMV depends on.
|
||||
//
|
||||
// One work-item owns one output ROW for a block of BN columns. A work-group is
|
||||
// {64 lanes, NTILES subgroups} = NTILES*64 rows; the global z dimension tiles the
|
||||
// N columns by BN. Each work-item computes its row's FULL K (no K-split, so no
|
||||
// cross-subgroup reduction), which lets the whole work-group share one staged
|
||||
// activation block:
|
||||
//
|
||||
// __local activation staging — the BN columns of the current superblock (BN*256
|
||||
// floats) are loaded into __local once per superblock, cooperatively by all
|
||||
// NTILES*64 work-items, then every row reads its activation from __local. This
|
||||
// removes the ~Nrows-fold redundant image reads of the first version (each lane
|
||||
// re-read the activation), which made the batched GEMM ~2x slower than the plain
|
||||
// noshuffle GEMM.
|
||||
//
|
||||
// Weights are read from __global (coalesced) — matching the decode kernel; the
|
||||
// lm_head weight is streamed with little reuse where coalesced global beats the
|
||||
// Adreno texture cache.
|
||||
|
||||
#pragma OPENCL EXTENSION cl_khr_fp16 : enable
|
||||
|
||||
#ifdef cl_qcom_reqd_sub_group_size
|
||||
#pragma OPENCL EXTENSION cl_qcom_reqd_sub_group_size : enable
|
||||
#define ADRENO_GPU 1
|
||||
#define REQD_SUBGROUP_SIZE_64 __attribute__((qcom_reqd_sub_group_size("half")))
|
||||
#endif
|
||||
|
||||
#define NTILES 4 // 64-row tiles per work-group (NTILES*64 = 256 rows)
|
||||
#define TILE_ROWS 64
|
||||
#define BN 16 // output columns handled per work-group (global z step)
|
||||
#define WG_THREADS (NTILES * TILE_ROWS)
|
||||
|
||||
#if defined(ADRENO_GPU)
|
||||
REQD_SUBGROUP_SIZE_64
|
||||
#endif
|
||||
kernel void kernel_gemm_noshuffle_q6_K_f32_tiled(
|
||||
__global uint4 * src0_ql, // tiled: 8 uint4 granules / superblock
|
||||
__global uint4 * src0_qh, // tiled: 4 uint4 granules / superblock
|
||||
__global char * src0_s, // tiled: 16 chars / superblock
|
||||
__global half * src0_d, // tiled: 1 half / superblock
|
||||
read_only image1d_buffer_t src1, // activation [ne00, ne11] f32 (RGBA), column-major
|
||||
global float * dst,
|
||||
ulong offsetd,
|
||||
int ne00,
|
||||
int ne01,
|
||||
int ne11
|
||||
) {
|
||||
int rit = get_local_id(0); // 0..63 (lane within a tile; coalesces weight loads)
|
||||
int sg = get_local_id(1); // 0..NTILES-1
|
||||
int lid = sg * TILE_ROWS + rit; // 0..WG_THREADS-1 (flat local id)
|
||||
int row = get_group_id(0) * WG_THREADS + lid;
|
||||
int rt = row / TILE_ROWS; // global 64-row tile index
|
||||
int col0 = get_global_id(2) * BN; // first output column of this block
|
||||
|
||||
int nb = ne00 / 256; // superblocks per row
|
||||
int act_col_stride = ne00 / 4; // activation float4 pixels per column
|
||||
|
||||
const bool row_ok = row < ne01;
|
||||
|
||||
// staged activation: BN columns x 256 elements for the current superblock
|
||||
__local float lact[BN * 256];
|
||||
|
||||
float acc[BN];
|
||||
#pragma unroll
|
||||
for (int j = 0; j < BN; ++j) acc[j] = 0.0f;
|
||||
|
||||
for (int sb = 0; sb < nb; ++sb) {
|
||||
// cooperatively stage BN columns' 256 activation elements (= BN*64 float4)
|
||||
for (int p = lid; p < BN * 64; p += WG_THREADS) {
|
||||
int j = p >> 6; // column within the BN block (p / 64)
|
||||
int e4 = p & 63; // element-quad within the column (p % 64)
|
||||
int c = col0 + j;
|
||||
float4 v = (c < ne11)
|
||||
? read_imagef(src1, c * act_col_stride + sb * 64 + e4)
|
||||
: (float4)(0.0f);
|
||||
lact[p * 4 + 0] = v.x;
|
||||
lact[p * 4 + 1] = v.y;
|
||||
lact[p * 4 + 2] = v.z;
|
||||
lact[p * 4 + 3] = v.w; // lact[j*256 + e], e = e4*4 + t
|
||||
}
|
||||
barrier(CLK_LOCAL_MEM_FENCE);
|
||||
|
||||
if (row_ok) {
|
||||
int tile_blk = rt * nb + sb; // ne02 == 1 for lm_head/embed
|
||||
|
||||
float dval = (float)src0_d[tile_blk * TILE_ROWS + rit];
|
||||
__global char * sc = src0_s + (tile_blk * TILE_ROWS + rit) * 16;
|
||||
|
||||
uint ql[32];
|
||||
uint qh[16];
|
||||
#pragma unroll
|
||||
for (int g = 0; g < 8; ++g) {
|
||||
uint4 v = src0_ql[(tile_blk * 8 + g) * TILE_ROWS + rit];
|
||||
ql[g*4+0] = v.x; ql[g*4+1] = v.y; ql[g*4+2] = v.z; ql[g*4+3] = v.w;
|
||||
}
|
||||
#pragma unroll
|
||||
for (int g = 0; g < 4; ++g) {
|
||||
uint4 v = src0_qh[(tile_blk * 4 + g) * TILE_ROWS + rit];
|
||||
qh[g*4+0] = v.x; qh[g*4+1] = v.y; qh[g*4+2] = v.z; qh[g*4+3] = v.w;
|
||||
}
|
||||
|
||||
// NOTE: the e loop (256) is deliberately NOT unrolled. Fully unrolling
|
||||
// 256*BN MACs overflows the in-process Adreno compiler (host stack
|
||||
// overflow at clBuildProgram, same class as the FA DK=512 OOM).
|
||||
for (int e = 0; e < 256; ++e) {
|
||||
uint low4 = (ql[e >> 3] >> ((e & 7) * 4)) & 0xF;
|
||||
uint hi2 = (qh[e >> 4] >> ((e & 15) * 2)) & 0x3;
|
||||
int code = (int)(low4 | (hi2 << 4)) - 32;
|
||||
int sidx = ((e >> 7) << 3) + (((e >> 5) & 3) << 1) + ((e >> 4) & 1);
|
||||
float cs = (float)code * (float)sc[sidx] * dval;
|
||||
#pragma unroll
|
||||
for (int j = 0; j < BN; ++j) {
|
||||
acc[j] += cs * lact[j * 256 + e];
|
||||
}
|
||||
}
|
||||
}
|
||||
barrier(CLK_LOCAL_MEM_FENCE);
|
||||
}
|
||||
|
||||
if (row_ok) {
|
||||
dst = (global float*)((global char*)dst + offsetd);
|
||||
#pragma unroll
|
||||
for (int j = 0; j < BN; ++j) {
|
||||
int c = col0 + j;
|
||||
if (c < ne11) {
|
||||
dst[(ulong)c * ne01 + row] = acc[j];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -277,3 +277,107 @@ __kernel void kernel_gemv_noshuffle_q4_0_f32(
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Multi-column (N in [2..4]) variant of the q4_0 decode GEMV, for the speculative
|
||||
// / MTP verify batch (n_cols = 2..4 = drafted + bonus positions). Routes the small-
|
||||
// batch verify OFF the transposed-GEMM dead-zone (gemm_noshuffle_q4_0) onto the
|
||||
// efficient GEMV path. Each K-block's weights (regA hi+lo) are loaded ONCE and
|
||||
// reused across the n_cols activation columns. Per-column accumulation is
|
||||
// independent and identical to n_cols standalone GEMVs. n_cols==3 is byte-identical
|
||||
// to the original mc3 (col3 disabled, slots 6/7 stay zero). Kept the _mc3 name.
|
||||
#ifdef VECTOR_SUB_GROUP_BROADCAST
|
||||
#define MC_DQ_HI dequantizeBlockAccum_ns_sgbroadcast_8_hi
|
||||
#define MC_DQ_LO dequantizeBlockAccum_ns_sgbroadcast_8_lo
|
||||
#else
|
||||
#define MC_DQ_HI dequantizeBlockAccum_ns_sgbroadcast_1_hi
|
||||
#define MC_DQ_LO dequantizeBlockAccum_ns_sgbroadcast_1_lo
|
||||
#endif
|
||||
// One column c: load this column's activation (own brace scope so the macros'
|
||||
// `shared_y` decl is re-scoped), then dequant (hi+lo) against the shared weights.
|
||||
#define MC_COL_Q40(ts, c) \
|
||||
{ if (slid < 4) { regB.s0123 = read_imagef(src1, (c)*COL_STRIDE + slid*2 + k*8); \
|
||||
regB.s4567 = read_imagef(src1, (c)*COL_STRIDE + 1 + slid*2 + k*8); } \
|
||||
MC_DQ_HI(ts, as_ushort8(regA_hi), regS, regB); \
|
||||
MC_DQ_LO(ts, as_ushort8(regA_lo), regS, regB); }
|
||||
|
||||
#ifdef ADRENO_GPU
|
||||
REQD_SUBGROUP_SIZE_64
|
||||
#endif
|
||||
__kernel void kernel_gemv_noshuffle_q4_0_f32_mc3(
|
||||
__read_only image1d_buffer_t src0_q, // quantized A
|
||||
global half2 * src0_d, // A scales
|
||||
__read_only image1d_buffer_t src1, // B (n_cols columns, col-major image)
|
||||
global float * dst, // C (column-major [M x n_cols])
|
||||
ulong offsetd,
|
||||
int ne00, // K
|
||||
int ne01, // M
|
||||
int n_cols) // N (2..4)
|
||||
{
|
||||
uint groupId = get_local_id(1);
|
||||
uint gid = get_global_id(0);
|
||||
ushort slid = get_sub_group_local_id();
|
||||
|
||||
uint K = ne00;
|
||||
uint M = ne01;
|
||||
|
||||
uint LINE_STRIDE_A = M / 2;
|
||||
// BLOCK_STRIDE_A is the LAYOUT stride between consecutive K-blocks = 4 uints
|
||||
// per q4_0 block * M (set by the trans4_ns convert). The "4" is uints/block, NOT
|
||||
// the subgroup count — keep it fixed so the K-split count (nsg) can vary.
|
||||
uint BLOCK_STRIDE_A = N_SIMDGROUP * M; // = 4 * M (N_SIMDGROUP is the #define 4)
|
||||
uint COL_STRIDE = K / 4; // float4 pixels per activation column
|
||||
uint nsg = get_local_size(1); // runtime K-split (4 default, 8 small-M)
|
||||
|
||||
__private uint4 regA_hi, regA_lo;
|
||||
__private half2 regS;
|
||||
__private float8 regB;
|
||||
|
||||
__private float2 ts0 = (float2)(0.0f);
|
||||
__private float2 ts1 = (float2)(0.0f);
|
||||
__private float2 ts2 = (float2)(0.0f);
|
||||
__private float2 ts3 = (float2)(0.0f);
|
||||
|
||||
for (uint k = groupId; k < (K / QK4_0); k += nsg) {
|
||||
regS = src0_d[gid + k * LINE_STRIDE_A];
|
||||
|
||||
// weights loaded ONCE, reused across the columns
|
||||
regA_hi.s0 = read_imageui(src0_q, (gid + k * BLOCK_STRIDE_A + LINE_STRIDE_A * 0)).x;
|
||||
regA_hi.s1 = read_imageui(src0_q, (gid + k * BLOCK_STRIDE_A + LINE_STRIDE_A * 1)).x;
|
||||
regA_hi.s2 = read_imageui(src0_q, (gid + k * BLOCK_STRIDE_A + LINE_STRIDE_A * 2)).x;
|
||||
regA_hi.s3 = read_imageui(src0_q, (gid + k * BLOCK_STRIDE_A + LINE_STRIDE_A * 3)).x;
|
||||
regA_lo.s0 = read_imageui(src0_q, (gid + k * BLOCK_STRIDE_A + LINE_STRIDE_A * 4)).x;
|
||||
regA_lo.s1 = read_imageui(src0_q, (gid + k * BLOCK_STRIDE_A + LINE_STRIDE_A * 5)).x;
|
||||
regA_lo.s2 = read_imageui(src0_q, (gid + k * BLOCK_STRIDE_A + LINE_STRIDE_A * 6)).x;
|
||||
regA_lo.s3 = read_imageui(src0_q, (gid + k * BLOCK_STRIDE_A + LINE_STRIDE_A * 7)).x;
|
||||
|
||||
MC_COL_Q40(ts0, 0);
|
||||
MC_COL_Q40(ts1, 1);
|
||||
if (n_cols > 2) MC_COL_Q40(ts2, 2);
|
||||
if (n_cols > 3) MC_COL_Q40(ts3, 3);
|
||||
}
|
||||
|
||||
// cross-subgroup reduce over nsg subgroups: pack the (up to 4) columns' float2
|
||||
// into a float8. Generalized to runtime nsg (4 default, 8 for small-M). Each
|
||||
// subgroup writes its partial; subgroup 0 sums the rest into its own acc. At
|
||||
// nsg==4 this is byte-identical to the original (sums subgroups 1,2,3 in order).
|
||||
__local float8 reduceLM[SIMDGROUP_WIDTH * 8];
|
||||
float8 acc = (float8)(ts0.s0, ts0.s1, ts1.s0, ts1.s1, ts2.s0, ts2.s1, ts3.s0, ts3.s1);
|
||||
reduceLM[groupId * SIMDGROUP_WIDTH + slid] = acc;
|
||||
|
||||
barrier(CLK_LOCAL_MEM_FENCE);
|
||||
|
||||
if (groupId == 0) {
|
||||
for (uint g = 1; g < nsg; g++) {
|
||||
acc += reduceLM[g * SIMDGROUP_WIDTH + slid];
|
||||
}
|
||||
dst = (global float*)((global char*)dst + offsetd);
|
||||
// dst is column-major [M rows x n_cols cols]: (row, col) at col*M + row
|
||||
vstore2((float2)(acc.s0, acc.s1), 0, &(dst[0 * M + gid * 2]));
|
||||
vstore2((float2)(acc.s2, acc.s3), 0, &(dst[1 * M + gid * 2]));
|
||||
if (n_cols > 2) vstore2((float2)(acc.s4, acc.s5), 0, &(dst[2 * M + gid * 2]));
|
||||
if (n_cols > 3) vstore2((float2)(acc.s6, acc.s7), 0, &(dst[3 * M + gid * 2]));
|
||||
}
|
||||
}
|
||||
#undef MC_COL_Q40
|
||||
#undef MC_DQ_HI
|
||||
#undef MC_DQ_LO
|
||||
|
||||
@@ -286,3 +286,99 @@ kernel void kernel_gemv_noshuffle_q4_1_f32(
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Multi-column (N in [2..4]) variant of the q4_1 decode GEMV (spec/MTP verify) =
|
||||
// q4_0 mc3 + the q4_1 per-block min (regM; dequant = q*scale + minv). n_cols=2..4;
|
||||
// routes the small-batch verify OFF the gemm_noshuffle_q4_1 dead-zone. n_cols==3 is
|
||||
// byte-identical to the original mc3. NB: this file spells the vec-broadcast define
|
||||
// BROADCAT (no S) — match it so the fast _8 path compiles.
|
||||
#ifdef VECTOR_SUB_GROUP_BROADCAT
|
||||
#define MC_DQ1_HI dequantizeBlockAccum_ns_sgbroadcast_8_hi
|
||||
#define MC_DQ1_LO dequantizeBlockAccum_ns_sgbroadcast_8_lo
|
||||
#else
|
||||
#define MC_DQ1_HI dequantizeBlockAccum_ns_sgbroadcast_1_hi
|
||||
#define MC_DQ1_LO dequantizeBlockAccum_ns_sgbroadcast_1_lo
|
||||
#endif
|
||||
#define MC_COL_Q41(ts, c) \
|
||||
{ if (slid < 4) { regB.s0123 = read_imagef(src1, (c)*COL_STRIDE + slid*2 + k*8); \
|
||||
regB.s4567 = read_imagef(src1, (c)*COL_STRIDE + 1 + slid*2 + k*8); } \
|
||||
MC_DQ1_HI(ts, as_ushort8(regA_hi), regS, regM, regB); \
|
||||
MC_DQ1_LO(ts, as_ushort8(regA_lo), regS, regM, regB); }
|
||||
#ifdef ADRENO_GPU
|
||||
REQD_SUBGROUP_SIZE_64
|
||||
#endif
|
||||
kernel void kernel_gemv_noshuffle_q4_1_f32_mc3(
|
||||
read_only image1d_buffer_t src0_q,
|
||||
global half2 * src0_d,
|
||||
global half2 * src0_m,
|
||||
read_only image1d_buffer_t src1,
|
||||
global float * dst,
|
||||
ulong offsetd,
|
||||
int ne00,
|
||||
int ne01,
|
||||
int n_cols)
|
||||
{
|
||||
uint groupId = get_local_id(1);
|
||||
uint gid = get_global_id(0);
|
||||
ushort slid = get_sub_group_local_id();
|
||||
|
||||
uint K = ne00;
|
||||
uint M = ne01;
|
||||
|
||||
uint LINE_STRIDE_A = M / 2;
|
||||
uint BLOCK_STRIDE_A = NSUBGROUPS * M;
|
||||
uint COL_STRIDE = K / 4; // float4 pixels per activation column
|
||||
|
||||
private uint4 regA_hi, regA_lo;
|
||||
private half2 regS, regM;
|
||||
private float8 regB;
|
||||
|
||||
private float2 ts0 = (float2)(0.0f);
|
||||
private float2 ts1 = (float2)(0.0f);
|
||||
private float2 ts2 = (float2)(0.0f);
|
||||
private float2 ts3 = (float2)(0.0f);
|
||||
|
||||
for (uint k = groupId; k < (K / QK4_0); k += NSUBGROUPS) {
|
||||
regS = src0_d[gid + k * LINE_STRIDE_A];
|
||||
regM = src0_m[gid + k * LINE_STRIDE_A];
|
||||
|
||||
// weights loaded ONCE, reused across the columns
|
||||
regA_hi.s0 = read_imageui(src0_q, (gid + k * BLOCK_STRIDE_A + LINE_STRIDE_A * 0)).x;
|
||||
regA_hi.s1 = read_imageui(src0_q, (gid + k * BLOCK_STRIDE_A + LINE_STRIDE_A * 1)).x;
|
||||
regA_hi.s2 = read_imageui(src0_q, (gid + k * BLOCK_STRIDE_A + LINE_STRIDE_A * 2)).x;
|
||||
regA_hi.s3 = read_imageui(src0_q, (gid + k * BLOCK_STRIDE_A + LINE_STRIDE_A * 3)).x;
|
||||
regA_lo.s0 = read_imageui(src0_q, (gid + k * BLOCK_STRIDE_A + LINE_STRIDE_A * 4)).x;
|
||||
regA_lo.s1 = read_imageui(src0_q, (gid + k * BLOCK_STRIDE_A + LINE_STRIDE_A * 5)).x;
|
||||
regA_lo.s2 = read_imageui(src0_q, (gid + k * BLOCK_STRIDE_A + LINE_STRIDE_A * 6)).x;
|
||||
regA_lo.s3 = read_imageui(src0_q, (gid + k * BLOCK_STRIDE_A + LINE_STRIDE_A * 7)).x;
|
||||
|
||||
MC_COL_Q41(ts0, 0);
|
||||
MC_COL_Q41(ts1, 1);
|
||||
if (n_cols > 2) MC_COL_Q41(ts2, 2);
|
||||
if (n_cols > 3) MC_COL_Q41(ts3, 3);
|
||||
}
|
||||
|
||||
// cross-subgroup reduce: pack the (up to 4) columns' float2 into a float8.
|
||||
local float8 reduceLM[SUBGROUP_SIZE * 3];
|
||||
float8 acc = (float8)(ts0.s0, ts0.s1, ts1.s0, ts1.s1, ts2.s0, ts2.s1, ts3.s0, ts3.s1);
|
||||
if (groupId == 1) { reduceLM[SUBGROUP_SIZE * 0 + slid] = acc; }
|
||||
if (groupId == 2) { reduceLM[SUBGROUP_SIZE * 1 + slid] = acc; }
|
||||
if (groupId == 3) { reduceLM[SUBGROUP_SIZE * 2 + slid] = acc; }
|
||||
|
||||
barrier(CLK_LOCAL_MEM_FENCE);
|
||||
|
||||
if (groupId == 0) {
|
||||
acc += reduceLM[SUBGROUP_SIZE * 0 + slid];
|
||||
acc += reduceLM[SUBGROUP_SIZE * 1 + slid];
|
||||
acc += reduceLM[SUBGROUP_SIZE * 2 + slid];
|
||||
dst = (global float*)((global char*)dst + offsetd);
|
||||
// dst is column-major [M rows x n_cols cols]: (row, col) at col*M + row
|
||||
vstore2((float2)(acc.s0, acc.s1), 0, &(dst[0 * M + gid * 2]));
|
||||
vstore2((float2)(acc.s2, acc.s3), 0, &(dst[1 * M + gid * 2]));
|
||||
if (n_cols > 2) vstore2((float2)(acc.s4, acc.s5), 0, &(dst[2 * M + gid * 2]));
|
||||
if (n_cols > 3) vstore2((float2)(acc.s6, acc.s7), 0, &(dst[3 * M + gid * 2]));
|
||||
}
|
||||
}
|
||||
#undef MC_COL_Q41
|
||||
#undef MC_DQ1_HI
|
||||
#undef MC_DQ1_LO
|
||||
|
||||
@@ -228,12 +228,20 @@ kernel void kernel_gemv_noshuffle_q4_k_f32(
|
||||
uint groupId = get_local_id(1);
|
||||
uint gid = get_global_id(0);
|
||||
ushort slid = get_sub_group_local_id();
|
||||
// K-split factor = #subgroups in the WG. Read from the launch (NOT a compile
|
||||
// constant) so small-M projections (Kcur/Vcur/Qcur) can dispatch a wider
|
||||
// K-split (more waves/SP -> latency hiding) while large-M keeps 4. The
|
||||
// physical weight layout stride below is INDEPENDENT of this (see BLOCK_STRIDE_A).
|
||||
uint nsg = get_local_size(1);
|
||||
|
||||
uint K = ne00;
|
||||
uint M = ne01;
|
||||
|
||||
uint LINE_STRIDE_A = M / 2;
|
||||
uint BLOCK_STRIDE_A = NSUBGROUPS * M;
|
||||
// Physical per-K-block stride in the packed image: 8 uints/block-row-pair *
|
||||
// (M/2) row-pairs = 4*M uints. This is a layout constant, not tied to nsg.
|
||||
uint BLOCK_STRIDE_A = 4 * M;
|
||||
uint scales_per_row = (K / QK_K) * 12;
|
||||
|
||||
// The x-grid is padded to CEIL_DIV(ne01/2,64)*64, so when ne01 % 128 != 0 the
|
||||
// tail lanes hold gid >= ne01/2. The output stores below are guarded, but the
|
||||
@@ -259,7 +267,7 @@ kernel void kernel_gemv_noshuffle_q4_k_f32(
|
||||
|
||||
private float2 totalSum = (float2)(0.0f);
|
||||
|
||||
for (uint k = groupId; k < (K / 32); k += NSUBGROUPS) {
|
||||
for (uint k = groupId; k < (K / 32); k += nsg) {
|
||||
uint sb = k / 8;
|
||||
uint j = k % 8;
|
||||
|
||||
@@ -303,28 +311,21 @@ kernel void kernel_gemv_noshuffle_q4_k_f32(
|
||||
#endif // VECTOR_SUB_GROUP_BROADCAST
|
||||
}
|
||||
|
||||
// reduction in local memory, assumes #wave=4
|
||||
local float2 reduceLM[SUBGROUP_SIZE * 3];
|
||||
if (groupId == 1) {
|
||||
reduceLM[SUBGROUP_SIZE * 0 + slid] = totalSum;
|
||||
}
|
||||
if (groupId == 2) {
|
||||
reduceLM[SUBGROUP_SIZE * 1 + slid] = totalSum;
|
||||
}
|
||||
if (groupId == 3) {
|
||||
reduceLM[SUBGROUP_SIZE * 2 + slid] = totalSum;
|
||||
// Cross-subgroup reduction in local memory. Generalized to nsg subgroups
|
||||
// (was a hard-coded 4-wave unroll). Sized for up to 16 subgroups (the widest
|
||||
// K-split we dispatch for small M). At nsg==4 the accumulation order is
|
||||
// identical to the original unroll -> byte-identical for the large-M path.
|
||||
local float2 reduceLM[SUBGROUP_SIZE * 15];
|
||||
if (groupId > 0) {
|
||||
reduceLM[SUBGROUP_SIZE * (groupId - 1) + slid] = totalSum;
|
||||
}
|
||||
|
||||
barrier(CLK_LOCAL_MEM_FENCE);
|
||||
|
||||
if (groupId == 0) {
|
||||
totalSum += reduceLM[SUBGROUP_SIZE * 0 + slid];
|
||||
}
|
||||
if (groupId == 0) {
|
||||
totalSum += reduceLM[SUBGROUP_SIZE * 1 + slid];
|
||||
}
|
||||
if (groupId == 0) {
|
||||
totalSum += reduceLM[SUBGROUP_SIZE * 2 + slid];
|
||||
for (uint i = 0; i < nsg - 1; ++i) {
|
||||
totalSum += reduceLM[SUBGROUP_SIZE * i + slid];
|
||||
}
|
||||
}
|
||||
|
||||
// 2 outputs per fiber in wave 0
|
||||
@@ -339,3 +340,484 @@ kernel void kernel_gemv_noshuffle_q4_k_f32(
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// --- Fused gate+up GEMV + GLU epilogue (FFN) ------------------------------------
|
||||
// Folds the FFN's two decode GEMVs (ffn_gate, ffn_up) and the following GLU into a
|
||||
// SINGLE dispatch: {MUL_MAT(Wg,x), MUL_MAT(Wu,x), GLU}. Both matmuls share the same
|
||||
// activation x (ffn_norm), so the activation image read is issued ONCE per K-block
|
||||
// and reused for the gate and up dot products (the per-op path re-reads it twice and
|
||||
// also materializes the two full ffn-wide intermediates to global, which the GLU
|
||||
// then re-reads). The gate/up partial sums are accumulated in the SAME per-fiber
|
||||
// order and reduced in the SAME cross-subgroup order as the standalone GEMV, and the
|
||||
// GLU formula is the exact scalar expression from kernels/glu.cl, so the output is
|
||||
// BYTE-IDENTICAL to the per-op matmul+matmul+glu path -> safe to default on.
|
||||
// glu_op: REGLU=0, GEGLU=1, SWIGLU=2, GEGLU_ERF=4, GEGLU_QUICK=5 (ggml_glu_op).
|
||||
// Weights: src0g_* = gate (= GLU src[0]); src0u_* = up (= GLU src[1]).
|
||||
#define GLU_GEGLU_COEF_A 0.044715f
|
||||
#define GLU_SQRT_2_OVER_PI 0.79788456080286535587989211986876f
|
||||
#define GLU_SQRT_2_INV 0.70710678118654752440084436210484f
|
||||
#define GLU_QUICK_COEF -1.702f
|
||||
|
||||
inline float glu_apply(int glu_op, float g, float u) {
|
||||
float act;
|
||||
if (glu_op == 1) { // GEGLU (tanh-approx gelu)
|
||||
act = 0.5f*g*(1.0f + tanh(GLU_SQRT_2_OVER_PI*g*(1.0f + GLU_GEGLU_COEF_A*g*g)));
|
||||
} else if (glu_op == 2) { // SWIGLU (silu)
|
||||
act = g / (1.0f + exp(-g));
|
||||
} else if (glu_op == 0) { // REGLU
|
||||
return g*u*(g > 0.0f);
|
||||
} else if (glu_op == 4) { // GEGLU_ERF
|
||||
act = 0.5f*g*(1.0f + erf(g*GLU_SQRT_2_INV));
|
||||
} else { // GEGLU_QUICK (glu_op == 5)
|
||||
act = g*(1.0f/(1.0f + exp(GLU_QUICK_COEF*g)));
|
||||
}
|
||||
return act*u;
|
||||
}
|
||||
|
||||
#ifdef ADRENO_GPU
|
||||
REQD_SUBGROUP_SIZE_64
|
||||
#endif
|
||||
kernel void kernel_gemv_noshuffle_q4_k_f32_glu(
|
||||
read_only image1d_buffer_t src0g_q,
|
||||
global half2 * src0g_d,
|
||||
global half2 * src0g_m,
|
||||
global uchar * src0g_s,
|
||||
read_only image1d_buffer_t src0u_q,
|
||||
global half2 * src0u_d,
|
||||
global half2 * src0u_m,
|
||||
global uchar * src0u_s,
|
||||
read_only image1d_buffer_t src1,
|
||||
global float * dst,
|
||||
ulong offsetd,
|
||||
int ne00,
|
||||
int ne01,
|
||||
int glu_op,
|
||||
uchar mask_d6,
|
||||
uchar mask_d4,
|
||||
uchar mask_hi2)
|
||||
{
|
||||
uint groupId = get_local_id(1);
|
||||
uint gid = get_global_id(0);
|
||||
ushort slid = get_sub_group_local_id();
|
||||
uint nsg = get_local_size(1);
|
||||
|
||||
uint K = ne00;
|
||||
uint M = ne01;
|
||||
|
||||
uint LINE_STRIDE_A = M / 2;
|
||||
uint BLOCK_STRIDE_A = 4 * M;
|
||||
|
||||
private uint4 regA;
|
||||
private half2 regS, regM;
|
||||
private float8 regB;
|
||||
|
||||
private float2 gateSum = (float2)(0.0f);
|
||||
private float2 upSum = (float2)(0.0f);
|
||||
|
||||
// Two SEQUENTIAL K-loops (gate fully, then up). Keeping only one weight's
|
||||
// working set live at a time holds the kernel's register footprint at ~the
|
||||
// base single-weight GEMV's, so its max WG stays 1024 (16 subgroups) and the
|
||||
// per-subgroup K-split matches the standalone wide GEMV exactly -> the gate
|
||||
// and up partial sums are BYTE-IDENTICAL to the per-op path. The macro body
|
||||
// is the base kernel's inner loop verbatim, parameterized by weight source.
|
||||
#define Q4K_GLU_LOOP(SUM, Q, DD, MM, SS) \
|
||||
for (uint k = groupId; k < (K / 32); k += nsg) { \
|
||||
uint sb = k / 8; \
|
||||
uint j = k % 8; \
|
||||
half2 d = DD[gid + sb * LINE_STRIDE_A]; \
|
||||
half2 dm = MM[gid + sb * LINE_STRIDE_A]; \
|
||||
global const uchar * sc0 = SS + sb * 12 * M + 2 * gid; \
|
||||
global const uchar * sc1 = sc0 + 1; \
|
||||
uchar sv0, mn0, sv1, mn1; \
|
||||
get_scale_min_k4(j, sc0, M, &sv0, &mn0, mask_d6, mask_d4, mask_hi2); \
|
||||
get_scale_min_k4(j, sc1, M, &sv1, &mn1, mask_d6, mask_d4, mask_hi2); \
|
||||
regS = convert_half2(convert_float2(d) * convert_float2((uchar2)(sv0, sv1))); \
|
||||
regM = convert_half2(convert_float2(dm) * convert_float2((uchar2)(mn0, mn1))); \
|
||||
if (slid < 4) { \
|
||||
regB.s0123 = read_imagef(src1, (slid * 2 + k * 8)); \
|
||||
regB.s4567 = read_imagef(src1, (1 + slid * 2 + k * 8)); \
|
||||
} \
|
||||
regA.s0 = read_imageui(Q, (gid + k * BLOCK_STRIDE_A + LINE_STRIDE_A * 0)).x; \
|
||||
regA.s1 = read_imageui(Q, (gid + k * BLOCK_STRIDE_A + LINE_STRIDE_A * 1)).x; \
|
||||
regA.s2 = read_imageui(Q, (gid + k * BLOCK_STRIDE_A + LINE_STRIDE_A * 2)).x; \
|
||||
regA.s3 = read_imageui(Q, (gid + k * BLOCK_STRIDE_A + LINE_STRIDE_A * 3)).x; \
|
||||
DEQ_HI(SUM, as_ushort8(regA), regS, regM, regB); \
|
||||
regA.s0 = read_imageui(Q, (gid + k * BLOCK_STRIDE_A + LINE_STRIDE_A * 4)).x; \
|
||||
regA.s1 = read_imageui(Q, (gid + k * BLOCK_STRIDE_A + LINE_STRIDE_A * 5)).x; \
|
||||
regA.s2 = read_imageui(Q, (gid + k * BLOCK_STRIDE_A + LINE_STRIDE_A * 6)).x; \
|
||||
regA.s3 = read_imageui(Q, (gid + k * BLOCK_STRIDE_A + LINE_STRIDE_A * 7)).x; \
|
||||
DEQ_LO(SUM, as_ushort8(regA), regS, regM, regB); \
|
||||
}
|
||||
|
||||
#ifdef VECTOR_SUB_GROUP_BROADCAST
|
||||
#define DEQ_HI dequantizeBlockAccum_ns_sgbroadcast_8_hi
|
||||
#define DEQ_LO dequantizeBlockAccum_ns_sgbroadcast_8_lo
|
||||
#else
|
||||
#define DEQ_HI dequantizeBlockAccum_ns_sgbroadcast_1_hi
|
||||
#define DEQ_LO dequantizeBlockAccum_ns_sgbroadcast_1_lo
|
||||
#endif
|
||||
|
||||
Q4K_GLU_LOOP(gateSum, src0g_q, src0g_d, src0g_m, src0g_s)
|
||||
Q4K_GLU_LOOP(upSum, src0u_q, src0u_d, src0u_m, src0u_s)
|
||||
|
||||
#undef DEQ_HI
|
||||
#undef DEQ_LO
|
||||
#undef Q4K_GLU_LOOP
|
||||
|
||||
// Cross-subgroup reduction in local memory. Packs gate (xy) + up (zw) into a
|
||||
// float4 so both reduce in one pass; summation order matches the base GEMV's
|
||||
// per-channel loop -> byte-identical partial sums.
|
||||
local float4 reduceLM[SUBGROUP_SIZE * 15];
|
||||
if (groupId > 0) {
|
||||
reduceLM[SUBGROUP_SIZE * (groupId - 1) + slid] = (float4)(gateSum, upSum);
|
||||
}
|
||||
barrier(CLK_LOCAL_MEM_FENCE);
|
||||
if (groupId == 0) {
|
||||
for (uint i = 0; i < nsg - 1; ++i) {
|
||||
float4 p = reduceLM[SUBGROUP_SIZE * i + slid];
|
||||
gateSum += p.xy;
|
||||
upSum += p.zw;
|
||||
}
|
||||
dst = (global float*)((global char*)dst + offsetd);
|
||||
dst[gid * 2 + 0] = glu_apply(glu_op, gateSum.s0, upSum.s0);
|
||||
dst[gid * 2 + 1] = glu_apply(glu_op, gateSum.s1, upSum.s1);
|
||||
}
|
||||
}
|
||||
|
||||
// --- Split-K-across-workgroups decode GEMV (small-M projections) ----------------
|
||||
// A single-token GEMV makes only ceil(M/2/64) workgroups; a WG runs on one Adreno
|
||||
// compute unit, so for small M (Kcur/Vcur, M=512 -> 4 WGs) most of the 16 CUs sit
|
||||
// idle and the matmul is bandwidth-starved even with a wide intra-WG K-split. This
|
||||
// variant adds a SECOND grid dimension of `ksplit` workgroups that each reduce a
|
||||
// disjoint slice of K and write a per-slice partial; kernel_gemv_splitk_reduce_f32
|
||||
// then sums the partials into dst. Identical math/layout to the base kernel
|
||||
// (physical block stride 4*M, get_scale_min_k4) -> coherent. Gated host-side to
|
||||
// M<=1024 (M>=2048
|
||||
// already fills the CUs and the extra reduce dispatch only hurts).
|
||||
#ifdef ADRENO_GPU
|
||||
REQD_SUBGROUP_SIZE_64
|
||||
#endif
|
||||
kernel void kernel_gemv_noshuffle_q4_k_f32_splitk(
|
||||
read_only image1d_buffer_t src0_q,
|
||||
global half2 * src0_d,
|
||||
global half2 * src0_m,
|
||||
global uchar * src0_s,
|
||||
read_only image1d_buffer_t src1,
|
||||
global float * partial, // [ksplit * M], slice-major
|
||||
int ne00,
|
||||
int ne01,
|
||||
uchar mask_d6,
|
||||
uchar mask_d4,
|
||||
uchar mask_hi2)
|
||||
{
|
||||
uint groupId = get_local_id(1);
|
||||
uint gid = get_global_id(0);
|
||||
ushort slid = get_sub_group_local_id();
|
||||
uint nsg = get_local_size(1);
|
||||
uint ksplit = get_num_groups(1);
|
||||
uint kslice = get_group_id(1);
|
||||
|
||||
uint K = ne00;
|
||||
uint M = ne01;
|
||||
uint LINE_STRIDE_A = M / 2;
|
||||
uint BLOCK_STRIDE_A = 4 * M; // physical, independent of the K-split
|
||||
|
||||
private uint4 regA;
|
||||
private half2 regS, regM;
|
||||
private float8 regB;
|
||||
private float2 totalSum = (float2)(0.0f);
|
||||
|
||||
// each (kslice, subgroup) pair owns a disjoint set of K-blocks
|
||||
for (uint k = kslice * nsg + groupId; k < (K / 32); k += ksplit * nsg) {
|
||||
uint sb = k / 8;
|
||||
uint j = k % 8;
|
||||
half2 d = src0_d[gid + sb * LINE_STRIDE_A];
|
||||
half2 dm = src0_m[gid + sb * LINE_STRIDE_A];
|
||||
global const uchar * sc0 = src0_s + sb * 12 * M + 2 * gid;
|
||||
global const uchar * sc1 = sc0 + 1;
|
||||
uchar sv0, mn0, sv1, mn1;
|
||||
get_scale_min_k4(j, sc0, M, &sv0, &mn0, mask_d6, mask_d4, mask_hi2);
|
||||
get_scale_min_k4(j, sc1, M, &sv1, &mn1, mask_d6, mask_d4, mask_hi2);
|
||||
regS = convert_half2(convert_float2(d) * convert_float2((uchar2)(sv0, sv1)));
|
||||
regM = convert_half2(convert_float2(dm) * convert_float2((uchar2)(mn0, mn1)));
|
||||
if (slid < 4) {
|
||||
regB.s0123 = read_imagef(src1, (slid * 2 + k * 8));
|
||||
regB.s4567 = read_imagef(src1, (1 + slid * 2 + k * 8));
|
||||
}
|
||||
regA.s0 = read_imageui(src0_q, (gid + k * BLOCK_STRIDE_A + LINE_STRIDE_A * 0)).x;
|
||||
regA.s1 = read_imageui(src0_q, (gid + k * BLOCK_STRIDE_A + LINE_STRIDE_A * 1)).x;
|
||||
regA.s2 = read_imageui(src0_q, (gid + k * BLOCK_STRIDE_A + LINE_STRIDE_A * 2)).x;
|
||||
regA.s3 = read_imageui(src0_q, (gid + k * BLOCK_STRIDE_A + LINE_STRIDE_A * 3)).x;
|
||||
#ifdef VECTOR_SUB_GROUP_BROADCAST
|
||||
dequantizeBlockAccum_ns_sgbroadcast_8_hi(totalSum, as_ushort8(regA), regS, regM, regB);
|
||||
#else
|
||||
dequantizeBlockAccum_ns_sgbroadcast_1_hi(totalSum, as_ushort8(regA), regS, regM, regB);
|
||||
#endif
|
||||
regA.s0 = read_imageui(src0_q, (gid + k * BLOCK_STRIDE_A + LINE_STRIDE_A * 4)).x;
|
||||
regA.s1 = read_imageui(src0_q, (gid + k * BLOCK_STRIDE_A + LINE_STRIDE_A * 5)).x;
|
||||
regA.s2 = read_imageui(src0_q, (gid + k * BLOCK_STRIDE_A + LINE_STRIDE_A * 6)).x;
|
||||
regA.s3 = read_imageui(src0_q, (gid + k * BLOCK_STRIDE_A + LINE_STRIDE_A * 7)).x;
|
||||
#ifdef VECTOR_SUB_GROUP_BROADCAST
|
||||
dequantizeBlockAccum_ns_sgbroadcast_8_lo(totalSum, as_ushort8(regA), regS, regM, regB);
|
||||
#else
|
||||
dequantizeBlockAccum_ns_sgbroadcast_1_lo(totalSum, as_ushort8(regA), regS, regM, regB);
|
||||
#endif
|
||||
}
|
||||
|
||||
local float2 reduceLM[SUBGROUP_SIZE * 15];
|
||||
if (groupId > 0) {
|
||||
reduceLM[SUBGROUP_SIZE * (groupId - 1) + slid] = totalSum;
|
||||
}
|
||||
barrier(CLK_LOCAL_MEM_FENCE);
|
||||
if (groupId == 0) {
|
||||
for (uint i = 0; i < nsg - 1; ++i) {
|
||||
totalSum += reduceLM[SUBGROUP_SIZE * i + slid];
|
||||
}
|
||||
vstore2(totalSum, 0, &(partial[kslice * M + gid * 2]));
|
||||
}
|
||||
}
|
||||
|
||||
// Sum the per-slice partials [ksplit * M] into dst[M]; applies the dst byte offset.
|
||||
kernel void kernel_gemv_splitk_reduce_f32(
|
||||
global float * partial,
|
||||
global float * dst,
|
||||
ulong offsetd,
|
||||
int ne01, // M
|
||||
int ksplit)
|
||||
{
|
||||
uint r = get_global_id(0);
|
||||
if (r >= (uint)ne01) return;
|
||||
float acc = 0.0f;
|
||||
for (uint s = 0; s < (uint)ksplit; ++s) {
|
||||
acc += partial[s * (uint)ne01 + r];
|
||||
}
|
||||
dst = (global float*)((global char*)dst + offsetd);
|
||||
dst[r] = acc;
|
||||
}
|
||||
|
||||
|
||||
// --- Dequant-once macros for the mc3 verify GEMV (Q4K_MC3_DEQUANT_ONCE) ---
|
||||
// The inline dequantizeBlockAccum_* macros recompute the dequantized weight
|
||||
// ((code & mask)>>shift)*scale - minv ONCE PER COLUMN (3x), and the flat
|
||||
// 32-FMA unroll spills ~430 B of temporaries. These macros split the work:
|
||||
// DEQUANT_Q4K_BLOCK computes the 16 weights/row of one 32-block ONCE into a
|
||||
// half2[] (row0 in .s0, row1 in .s1) — stored as half, the exact type the
|
||||
// inline expression yields (int*half-half), so no extra rounding. MAC_Q4K_BLOCK
|
||||
// then accumulates them against a column's broadcast activation in the SAME
|
||||
// per-accumulator order as the inline macro. Each weight value and each
|
||||
// accumulator's add-chain is bit-for-bit identical => byte-identical output,
|
||||
// while the dequant ALU drops 3x->1x and the live set shrinks. Requires the
|
||||
// Qualcomm vector sub_group_broadcast (float8); enabled opt-in on Adreno.
|
||||
#define DEQ_Q4K_HALF2(b0, b1, msk, sh, scale, minv) \
|
||||
(half2)( ((b0 & msk) >> sh) * scale.s0 - minv.s0, \
|
||||
((b1 & msk) >> sh) * scale.s1 - minv.s1 )
|
||||
|
||||
#define DEQUANT_Q4K_BLOCK(wq, bits, scale, minv) \
|
||||
wq[0] = DEQ_Q4K_HALF2(bits.s0, bits.s1, 0x000F, 0, scale, minv); \
|
||||
wq[1] = DEQ_Q4K_HALF2(bits.s0, bits.s1, 0x00F0, 4, scale, minv); \
|
||||
wq[2] = DEQ_Q4K_HALF2(bits.s0, bits.s1, 0x0F00, 8, scale, minv); \
|
||||
wq[3] = DEQ_Q4K_HALF2(bits.s0, bits.s1, 0xF000, 12, scale, minv); \
|
||||
wq[4] = DEQ_Q4K_HALF2(bits.s2, bits.s3, 0x000F, 0, scale, minv); \
|
||||
wq[5] = DEQ_Q4K_HALF2(bits.s2, bits.s3, 0x00F0, 4, scale, minv); \
|
||||
wq[6] = DEQ_Q4K_HALF2(bits.s2, bits.s3, 0x0F00, 8, scale, minv); \
|
||||
wq[7] = DEQ_Q4K_HALF2(bits.s2, bits.s3, 0xF000, 12, scale, minv); \
|
||||
wq[8] = DEQ_Q4K_HALF2(bits.s4, bits.s5, 0x000F, 0, scale, minv); \
|
||||
wq[9] = DEQ_Q4K_HALF2(bits.s4, bits.s5, 0x00F0, 4, scale, minv); \
|
||||
wq[10] = DEQ_Q4K_HALF2(bits.s4, bits.s5, 0x0F00, 8, scale, minv); \
|
||||
wq[11] = DEQ_Q4K_HALF2(bits.s4, bits.s5, 0xF000, 12, scale, minv); \
|
||||
wq[12] = DEQ_Q4K_HALF2(bits.s6, bits.s7, 0x000F, 0, scale, minv); \
|
||||
wq[13] = DEQ_Q4K_HALF2(bits.s6, bits.s7, 0x00F0, 4, scale, minv); \
|
||||
wq[14] = DEQ_Q4K_HALF2(bits.s6, bits.s7, 0x0F00, 8, scale, minv); \
|
||||
wq[15] = DEQ_Q4K_HALF2(bits.s6, bits.s7, 0xF000, 12, scale, minv);
|
||||
|
||||
// ln0/ln1 = the two source lanes whose activation float8 this block consumes
|
||||
// (0,1 for the hi block, 2,3 for the lo block — matching the inline _hi/_lo).
|
||||
#define MAC_Q4K_BLOCK(ts, wq, y, ln0, ln1) { \
|
||||
float8 sy = sub_group_broadcast(y, ln0); \
|
||||
ts.s0 += wq[0].s0*sy.s0; ts.s0 += wq[1].s0*sy.s1; ts.s0 += wq[2].s0*sy.s2; ts.s0 += wq[3].s0*sy.s3; \
|
||||
ts.s0 += wq[4].s0*sy.s4; ts.s0 += wq[5].s0*sy.s5; ts.s0 += wq[6].s0*sy.s6; ts.s0 += wq[7].s0*sy.s7; \
|
||||
ts.s1 += wq[0].s1*sy.s0; ts.s1 += wq[1].s1*sy.s1; ts.s1 += wq[2].s1*sy.s2; ts.s1 += wq[3].s1*sy.s3; \
|
||||
ts.s1 += wq[4].s1*sy.s4; ts.s1 += wq[5].s1*sy.s5; ts.s1 += wq[6].s1*sy.s6; ts.s1 += wq[7].s1*sy.s7; \
|
||||
sy = sub_group_broadcast(y, ln1); \
|
||||
ts.s0 += wq[8].s0*sy.s0; ts.s0 += wq[9].s0*sy.s1; ts.s0 += wq[10].s0*sy.s2; ts.s0 += wq[11].s0*sy.s3; \
|
||||
ts.s0 += wq[12].s0*sy.s4; ts.s0 += wq[13].s0*sy.s5; ts.s0 += wq[14].s0*sy.s6; ts.s0 += wq[15].s0*sy.s7; \
|
||||
ts.s1 += wq[8].s1*sy.s0; ts.s1 += wq[9].s1*sy.s1; ts.s1 += wq[10].s1*sy.s2; ts.s1 += wq[11].s1*sy.s3; \
|
||||
ts.s1 += wq[12].s1*sy.s4; ts.s1 += wq[13].s1*sy.s5; ts.s1 += wq[14].s1*sy.s6; ts.s1 += wq[15].s1*sy.s7; \
|
||||
}
|
||||
|
||||
// Multi-column (N=3) variant of the q4_K decode GEMV, for the speculative /
|
||||
// MTP verify batch (ne1=3 = 2 drafts + 1 bonus). Stays on the efficient GEMV
|
||||
// path (subgroup-broadcast activation, NSUBGROUPS K-split) instead of the
|
||||
// transposed-GEMM dead-zone path. Each K-block's weights (regA_hi/regA_lo) are
|
||||
// loaded ONCE and reused across all 3 activation columns — same weight traffic
|
||||
// as one decode, ~3x the (cheap) dequant ALU. Per-column accumulation is
|
||||
// independent and identical to 3 standalone GEMVs => byte-identical, so it does
|
||||
// NOT perturb the lm_head logits / spec accept rate.
|
||||
#ifdef ADRENO_GPU
|
||||
REQD_SUBGROUP_SIZE_64
|
||||
#endif
|
||||
kernel void kernel_gemv_noshuffle_q4_k_f32_mc3(
|
||||
read_only image1d_buffer_t src0_q,
|
||||
global half2 * src0_d,
|
||||
global half2 * src0_m,
|
||||
global uchar * src0_s,
|
||||
read_only image1d_buffer_t src1,
|
||||
global float * dst,
|
||||
ulong offsetd,
|
||||
int ne00,
|
||||
int ne01,
|
||||
uchar mask_d6,
|
||||
uchar mask_d4,
|
||||
uchar mask_hi2)
|
||||
{
|
||||
uint groupId = get_local_id(1);
|
||||
uint gid = get_global_id(0);
|
||||
ushort slid = get_sub_group_local_id();
|
||||
|
||||
uint K = ne00;
|
||||
uint M = ne01;
|
||||
|
||||
uint LINE_STRIDE_A = M / 2;
|
||||
uint BLOCK_STRIDE_A = NSUBGROUPS * M;
|
||||
uint COL_STRIDE = K / 4; // float4 pixels per activation column
|
||||
|
||||
private uint4 regA_hi, regA_lo;
|
||||
private half2 regS, regM;
|
||||
private float8 regB;
|
||||
|
||||
private float2 ts0 = (float2)(0.0f);
|
||||
private float2 ts1 = (float2)(0.0f);
|
||||
private float2 ts2 = (float2)(0.0f);
|
||||
|
||||
#ifdef Q4K_MC3_DEQUANT_LDS
|
||||
// One 16-half2 block buffer per WI (reused hi->lo): forces the dequantized
|
||||
// weights into LDS instead of private arrays (which spill to slow global on
|
||||
// Adreno). 64*NSUBGROUPS WIs * 16 half2 = 16 KB; each WI owns its own slot
|
||||
// range (flat*16) -> no cross-lane sharing, no barrier needed.
|
||||
local half2 wstage[SUBGROUP_SIZE * NSUBGROUPS * 16];
|
||||
local half2 * ws = wstage + (groupId * SUBGROUP_SIZE + slid) * 16;
|
||||
#endif
|
||||
|
||||
for (uint k = groupId; k < (K / 32); k += NSUBGROUPS) {
|
||||
uint sb = k / 8;
|
||||
uint j = k % 8;
|
||||
|
||||
half2 d = src0_d[gid + sb * LINE_STRIDE_A];
|
||||
half2 dm = src0_m[gid + sb * LINE_STRIDE_A];
|
||||
|
||||
global const uchar * sc0 = src0_s + sb * 12 * M + 2 * gid;
|
||||
global const uchar * sc1 = sc0 + 1;
|
||||
|
||||
uchar sv0, mn0, sv1, mn1;
|
||||
get_scale_min_k4(j, sc0, M, &sv0, &mn0, mask_d6, mask_d4, mask_hi2);
|
||||
get_scale_min_k4(j, sc1, M, &sv1, &mn1, mask_d6, mask_d4, mask_hi2);
|
||||
|
||||
regS = convert_half2(convert_float2(d) * convert_float2((uchar2)(sv0, sv1)));
|
||||
regM = convert_half2(convert_float2(dm) * convert_float2((uchar2)(mn0, mn1)));
|
||||
|
||||
// weights loaded ONCE, reused across the 3 columns
|
||||
regA_hi.s0 = read_imageui(src0_q, (gid + k * BLOCK_STRIDE_A + LINE_STRIDE_A * 0)).x;
|
||||
regA_hi.s1 = read_imageui(src0_q, (gid + k * BLOCK_STRIDE_A + LINE_STRIDE_A * 1)).x;
|
||||
regA_hi.s2 = read_imageui(src0_q, (gid + k * BLOCK_STRIDE_A + LINE_STRIDE_A * 2)).x;
|
||||
regA_hi.s3 = read_imageui(src0_q, (gid + k * BLOCK_STRIDE_A + LINE_STRIDE_A * 3)).x;
|
||||
regA_lo.s0 = read_imageui(src0_q, (gid + k * BLOCK_STRIDE_A + LINE_STRIDE_A * 4)).x;
|
||||
regA_lo.s1 = read_imageui(src0_q, (gid + k * BLOCK_STRIDE_A + LINE_STRIDE_A * 5)).x;
|
||||
regA_lo.s2 = read_imageui(src0_q, (gid + k * BLOCK_STRIDE_A + LINE_STRIDE_A * 6)).x;
|
||||
regA_lo.s3 = read_imageui(src0_q, (gid + k * BLOCK_STRIDE_A + LINE_STRIDE_A * 7)).x;
|
||||
|
||||
#ifdef Q4K_MC3_DEQUANT_ONCE
|
||||
// Dequant the 32 weights/row (16 hi + 16 lo) ONCE into half2[] (byte-
|
||||
// identical to the inline intermediate), then MAC against each column's
|
||||
// activation. Drops the dequant ALU 3x->1x and the macro-temp spill.
|
||||
half2 wq_hi[16], wq_lo[16];
|
||||
DEQUANT_Q4K_BLOCK(wq_hi, as_ushort8(regA_hi), regS, regM);
|
||||
DEQUANT_Q4K_BLOCK(wq_lo, as_ushort8(regA_lo), regS, regM);
|
||||
{ if (slid < 4) { regB.s0123 = read_imagef(src1, 0*COL_STRIDE + slid*2 + k*8);
|
||||
regB.s4567 = read_imagef(src1, 0*COL_STRIDE + 1 + slid*2 + k*8); }
|
||||
MAC_Q4K_BLOCK(ts0, wq_hi, regB, 0, 1); MAC_Q4K_BLOCK(ts0, wq_lo, regB, 2, 3); }
|
||||
{ if (slid < 4) { regB.s0123 = read_imagef(src1, 1*COL_STRIDE + slid*2 + k*8);
|
||||
regB.s4567 = read_imagef(src1, 1*COL_STRIDE + 1 + slid*2 + k*8); }
|
||||
MAC_Q4K_BLOCK(ts1, wq_hi, regB, 0, 1); MAC_Q4K_BLOCK(ts1, wq_lo, regB, 2, 3); }
|
||||
{ if (slid < 4) { regB.s0123 = read_imagef(src1, 2*COL_STRIDE + slid*2 + k*8);
|
||||
regB.s4567 = read_imagef(src1, 2*COL_STRIDE + 1 + slid*2 + k*8); }
|
||||
MAC_Q4K_BLOCK(ts2, wq_hi, regB, 0, 1); MAC_Q4K_BLOCK(ts2, wq_lo, regB, 2, 3); }
|
||||
#elif defined(Q4K_MC3_DEQUANT_LDS)
|
||||
// LDS-staged dequant: dequant a 32-block ONCE into the per-WI LDS slot
|
||||
// (hi pass then lo pass, overwriting), MAC each column from LDS. ts*
|
||||
// receive hi-then-lo in the same order as DEQUANT_ONCE -> byte-identical.
|
||||
// Activations reloaded per pass (cheap, imaged); only one regB + 0 weight
|
||||
// regs live -> the weight working set lives in LDS, not spilled private.
|
||||
DEQUANT_Q4K_BLOCK(ws, as_ushort8(regA_hi), regS, regM);
|
||||
{ if (slid < 4) { regB.s0123 = read_imagef(src1, 0*COL_STRIDE + slid*2 + k*8);
|
||||
regB.s4567 = read_imagef(src1, 0*COL_STRIDE + 1 + slid*2 + k*8); }
|
||||
MAC_Q4K_BLOCK(ts0, ws, regB, 0, 1); }
|
||||
{ if (slid < 4) { regB.s0123 = read_imagef(src1, 1*COL_STRIDE + slid*2 + k*8);
|
||||
regB.s4567 = read_imagef(src1, 1*COL_STRIDE + 1 + slid*2 + k*8); }
|
||||
MAC_Q4K_BLOCK(ts1, ws, regB, 0, 1); }
|
||||
{ if (slid < 4) { regB.s0123 = read_imagef(src1, 2*COL_STRIDE + slid*2 + k*8);
|
||||
regB.s4567 = read_imagef(src1, 2*COL_STRIDE + 1 + slid*2 + k*8); }
|
||||
MAC_Q4K_BLOCK(ts2, ws, regB, 0, 1); }
|
||||
DEQUANT_Q4K_BLOCK(ws, as_ushort8(regA_lo), regS, regM);
|
||||
{ if (slid < 4) { regB.s0123 = read_imagef(src1, 0*COL_STRIDE + slid*2 + k*8);
|
||||
regB.s4567 = read_imagef(src1, 0*COL_STRIDE + 1 + slid*2 + k*8); }
|
||||
MAC_Q4K_BLOCK(ts0, ws, regB, 2, 3); }
|
||||
{ if (slid < 4) { regB.s0123 = read_imagef(src1, 1*COL_STRIDE + slid*2 + k*8);
|
||||
regB.s4567 = read_imagef(src1, 1*COL_STRIDE + 1 + slid*2 + k*8); }
|
||||
MAC_Q4K_BLOCK(ts1, ws, regB, 2, 3); }
|
||||
{ if (slid < 4) { regB.s0123 = read_imagef(src1, 2*COL_STRIDE + slid*2 + k*8);
|
||||
regB.s4567 = read_imagef(src1, 2*COL_STRIDE + 1 + slid*2 + k*8); }
|
||||
MAC_Q4K_BLOCK(ts2, ws, regB, 2, 3); }
|
||||
#else
|
||||
// Per-column: load only this column's activation (single regB live at a
|
||||
// time -> 1/3 the activation register pressure vs holding all 3) then
|
||||
// dequant against the shared weights. Cuts the private-mem spill.
|
||||
#ifdef VECTOR_SUB_GROUP_BROADCAST
|
||||
{ if (slid < 4) { regB.s0123 = read_imagef(src1, 0*COL_STRIDE + slid*2 + k*8);
|
||||
regB.s4567 = read_imagef(src1, 0*COL_STRIDE + 1 + slid*2 + k*8); }
|
||||
dequantizeBlockAccum_ns_sgbroadcast_8_hi(ts0, as_ushort8(regA_hi), regS, regM, regB);
|
||||
dequantizeBlockAccum_ns_sgbroadcast_8_lo(ts0, as_ushort8(regA_lo), regS, regM, regB); }
|
||||
{ if (slid < 4) { regB.s0123 = read_imagef(src1, 1*COL_STRIDE + slid*2 + k*8);
|
||||
regB.s4567 = read_imagef(src1, 1*COL_STRIDE + 1 + slid*2 + k*8); }
|
||||
dequantizeBlockAccum_ns_sgbroadcast_8_hi(ts1, as_ushort8(regA_hi), regS, regM, regB);
|
||||
dequantizeBlockAccum_ns_sgbroadcast_8_lo(ts1, as_ushort8(regA_lo), regS, regM, regB); }
|
||||
{ if (slid < 4) { regB.s0123 = read_imagef(src1, 2*COL_STRIDE + slid*2 + k*8);
|
||||
regB.s4567 = read_imagef(src1, 2*COL_STRIDE + 1 + slid*2 + k*8); }
|
||||
dequantizeBlockAccum_ns_sgbroadcast_8_hi(ts2, as_ushort8(regA_hi), regS, regM, regB);
|
||||
dequantizeBlockAccum_ns_sgbroadcast_8_lo(ts2, as_ushort8(regA_lo), regS, regM, regB); }
|
||||
#else
|
||||
{ if (slid < 4) { regB.s0123 = read_imagef(src1, 0*COL_STRIDE + slid*2 + k*8);
|
||||
regB.s4567 = read_imagef(src1, 0*COL_STRIDE + 1 + slid*2 + k*8); }
|
||||
dequantizeBlockAccum_ns_sgbroadcast_1_hi(ts0, as_ushort8(regA_hi), regS, regM, regB);
|
||||
dequantizeBlockAccum_ns_sgbroadcast_1_lo(ts0, as_ushort8(regA_lo), regS, regM, regB); }
|
||||
{ if (slid < 4) { regB.s0123 = read_imagef(src1, 1*COL_STRIDE + slid*2 + k*8);
|
||||
regB.s4567 = read_imagef(src1, 1*COL_STRIDE + 1 + slid*2 + k*8); }
|
||||
dequantizeBlockAccum_ns_sgbroadcast_1_hi(ts1, as_ushort8(regA_hi), regS, regM, regB);
|
||||
dequantizeBlockAccum_ns_sgbroadcast_1_lo(ts1, as_ushort8(regA_lo), regS, regM, regB); }
|
||||
{ if (slid < 4) { regB.s0123 = read_imagef(src1, 2*COL_STRIDE + slid*2 + k*8);
|
||||
regB.s4567 = read_imagef(src1, 2*COL_STRIDE + 1 + slid*2 + k*8); }
|
||||
dequantizeBlockAccum_ns_sgbroadcast_1_hi(ts2, as_ushort8(regA_hi), regS, regM, regB);
|
||||
dequantizeBlockAccum_ns_sgbroadcast_1_lo(ts2, as_ushort8(regA_lo), regS, regM, regB); }
|
||||
#endif
|
||||
#endif // Q4K_MC3_DEQUANT_ONCE
|
||||
}
|
||||
|
||||
// cross-subgroup reduce: pack the 3 columns' float2 into a float8 (6 used).
|
||||
local float8 reduceLM[SUBGROUP_SIZE * 3];
|
||||
float8 acc = (float8)(ts0.s0, ts0.s1, ts1.s0, ts1.s1, ts2.s0, ts2.s1, 0.0f, 0.0f);
|
||||
if (groupId == 1) { reduceLM[SUBGROUP_SIZE * 0 + slid] = acc; }
|
||||
if (groupId == 2) { reduceLM[SUBGROUP_SIZE * 1 + slid] = acc; }
|
||||
if (groupId == 3) { reduceLM[SUBGROUP_SIZE * 2 + slid] = acc; }
|
||||
|
||||
barrier(CLK_LOCAL_MEM_FENCE);
|
||||
|
||||
if (groupId == 0) {
|
||||
acc += reduceLM[SUBGROUP_SIZE * 0 + slid];
|
||||
acc += reduceLM[SUBGROUP_SIZE * 1 + slid];
|
||||
acc += reduceLM[SUBGROUP_SIZE * 2 + slid];
|
||||
dst = (global float*)((global char*)dst + offsetd);
|
||||
// dst is column-major [M rows x 3 cols]: (row, col) at col*M + row
|
||||
vstore2((float2)(acc.s0, acc.s1), 0, &(dst[0 * M + gid * 2]));
|
||||
vstore2((float2)(acc.s2, acc.s3), 0, &(dst[1 * M + gid * 2]));
|
||||
vstore2((float2)(acc.s4, acc.s5), 0, &(dst[2 * M + gid * 2]));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,349 @@
|
||||
#pragma OPENCL EXTENSION cl_khr_fp16 : enable
|
||||
#pragma OPENCL EXTENSION cl_khr_subgroups : enable
|
||||
|
||||
#ifdef cl_qcom_reqd_sub_group_size
|
||||
#pragma OPENCL EXTENSION cl_qcom_reqd_sub_group_size : enable
|
||||
#define ADRENO_GPU 1
|
||||
#define REQD_SUBGROUP_SIZE_64 __attribute__((qcom_reqd_sub_group_size("half")))
|
||||
#endif
|
||||
|
||||
#define QK_K 256
|
||||
#define NSUBGROUPS 4
|
||||
#define SUBGROUP_SIZE 64
|
||||
|
||||
// scales are transposed: consecutive codes of a row are `stride` apart
|
||||
inline void get_scale_min_k4(
|
||||
int j,
|
||||
global const uchar * q,
|
||||
uint stride,
|
||||
uchar * d,
|
||||
uchar * m,
|
||||
uchar mask_d6,
|
||||
uchar mask_d4,
|
||||
uchar mask_hi2
|
||||
) {
|
||||
if (j < 4) {
|
||||
*d = q[j*stride] & mask_d6;
|
||||
*m = q[(j+4)*stride] & mask_d6;
|
||||
} else {
|
||||
*d = (q[(j+4)*stride] & mask_d4) | ((q[(j-4)*stride] & mask_hi2) >> 2);
|
||||
*m = ((q[(j+4)*stride] >> 4) & mask_d4) | ((q[j*stride] & mask_hi2) >> 2);
|
||||
}
|
||||
}
|
||||
|
||||
#define dequantizeBlockAccum_ns_sgbroadcast_1_hi(total_sums, bits4, scale, minv, y) \
|
||||
float shared_y; \
|
||||
shared_y = sub_group_broadcast(y.s0, 0); \
|
||||
total_sums.s0 += ((bits4.s0 & 0x000F) * scale.s0 - minv.s0) * shared_y; \
|
||||
total_sums.s1 += ((bits4.s1 & 0x000F) * scale.s1 - minv.s1) * shared_y; \
|
||||
shared_y = sub_group_broadcast(y.s1, 0); \
|
||||
total_sums.s0 += (((bits4.s0 & 0x00F0) >> 4) * scale.s0 - minv.s0) * shared_y; \
|
||||
total_sums.s1 += (((bits4.s1 & 0x00F0) >> 4) * scale.s1 - minv.s1) * shared_y; \
|
||||
shared_y = sub_group_broadcast(y.s2, 0); \
|
||||
total_sums.s0 += (((bits4.s0 & 0x0F00) >> 8) * scale.s0 - minv.s0) * shared_y; \
|
||||
total_sums.s1 += (((bits4.s1 & 0x0F00) >> 8) * scale.s1 - minv.s1) * shared_y; \
|
||||
shared_y = sub_group_broadcast(y.s3, 0); \
|
||||
total_sums.s0 += (((bits4.s0 & 0xF000) >> 12) * scale.s0 - minv.s0) * shared_y; \
|
||||
total_sums.s1 += (((bits4.s1 & 0xF000) >> 12) * scale.s1 - minv.s1) * shared_y; \
|
||||
shared_y = sub_group_broadcast(y.s4, 0); \
|
||||
total_sums.s0 += ((bits4.s2 & 0x000F) * scale.s0 - minv.s0) * shared_y; \
|
||||
total_sums.s1 += ((bits4.s3 & 0x000F) * scale.s1 - minv.s1) * shared_y; \
|
||||
shared_y = sub_group_broadcast(y.s5, 0); \
|
||||
total_sums.s0 += (((bits4.s2 & 0x00F0) >> 4) * scale.s0 - minv.s0) * shared_y; \
|
||||
total_sums.s1 += (((bits4.s3 & 0x00F0) >> 4) * scale.s1 - minv.s1) * shared_y; \
|
||||
shared_y = sub_group_broadcast(y.s6, 0); \
|
||||
total_sums.s0 += (((bits4.s2 & 0x0F00) >> 8) * scale.s0 - minv.s0) * shared_y; \
|
||||
total_sums.s1 += (((bits4.s3 & 0x0F00) >> 8) * scale.s1 - minv.s1) * shared_y; \
|
||||
shared_y = sub_group_broadcast(y.s7, 0); \
|
||||
total_sums.s0 += (((bits4.s2 & 0xF000) >> 12) * scale.s0 - minv.s0) * shared_y; \
|
||||
total_sums.s1 += (((bits4.s3 & 0xF000) >> 12) * scale.s1 - minv.s1) * shared_y; \
|
||||
shared_y = sub_group_broadcast(y.s0, 1); \
|
||||
total_sums.s0 += ((bits4.s4 & 0x000F) * scale.s0 - minv.s0) * shared_y; \
|
||||
total_sums.s1 += ((bits4.s5 & 0x000F) * scale.s1 - minv.s1) * shared_y; \
|
||||
shared_y = sub_group_broadcast(y.s1, 1); \
|
||||
total_sums.s0 += (((bits4.s4 & 0x00F0) >> 4) * scale.s0 - minv.s0) * shared_y; \
|
||||
total_sums.s1 += (((bits4.s5 & 0x00F0) >> 4) * scale.s1 - minv.s1) * shared_y; \
|
||||
shared_y = sub_group_broadcast(y.s2, 1); \
|
||||
total_sums.s0 += (((bits4.s4 & 0x0F00) >> 8) * scale.s0 - minv.s0) * shared_y; \
|
||||
total_sums.s1 += (((bits4.s5 & 0x0F00) >> 8) * scale.s1 - minv.s1) * shared_y; \
|
||||
shared_y = sub_group_broadcast(y.s3, 1); \
|
||||
total_sums.s0 += (((bits4.s4 & 0xF000) >> 12) * scale.s0 - minv.s0) * shared_y; \
|
||||
total_sums.s1 += (((bits4.s5 & 0xF000) >> 12) * scale.s1 - minv.s1) * shared_y; \
|
||||
shared_y = sub_group_broadcast(y.s4, 1); \
|
||||
total_sums.s0 += ((bits4.s6 & 0x000F) * scale.s0 - minv.s0) * shared_y; \
|
||||
total_sums.s1 += ((bits4.s7 & 0x000F) * scale.s1 - minv.s1) * shared_y; \
|
||||
shared_y = sub_group_broadcast(y.s5, 1); \
|
||||
total_sums.s0 += (((bits4.s6 & 0x00F0) >> 4) * scale.s0 - minv.s0) * shared_y; \
|
||||
total_sums.s1 += (((bits4.s7 & 0x00F0) >> 4) * scale.s1 - minv.s1) * shared_y; \
|
||||
shared_y = sub_group_broadcast(y.s6, 1); \
|
||||
total_sums.s0 += (((bits4.s6 & 0x0F00) >> 8) * scale.s0 - minv.s0) * shared_y; \
|
||||
total_sums.s1 += (((bits4.s7 & 0x0F00) >> 8) * scale.s1 - minv.s1) * shared_y; \
|
||||
shared_y = sub_group_broadcast(y.s7, 1); \
|
||||
total_sums.s0 += (((bits4.s6 & 0xF000) >> 12) * scale.s0 - minv.s0) * shared_y; \
|
||||
total_sums.s1 += (((bits4.s7 & 0xF000) >> 12) * scale.s1 - minv.s1) * shared_y; \
|
||||
|
||||
|
||||
#define dequantizeBlockAccum_ns_sgbroadcast_1_lo(total_sums, bits4, scale, minv, y) \
|
||||
shared_y = sub_group_broadcast(y.s0, 2); \
|
||||
total_sums.s0 += ((bits4.s0 & 0x000F) * scale.s0 - minv.s0) * shared_y; \
|
||||
total_sums.s1 += ((bits4.s1 & 0x000F) * scale.s1 - minv.s1) * shared_y; \
|
||||
shared_y = sub_group_broadcast(y.s1, 2); \
|
||||
total_sums.s0 += (((bits4.s0 & 0x00F0) >> 4) * scale.s0 - minv.s0) * shared_y; \
|
||||
total_sums.s1 += (((bits4.s1 & 0x00F0) >> 4) * scale.s1 - minv.s1) * shared_y; \
|
||||
shared_y = sub_group_broadcast(y.s2, 2); \
|
||||
total_sums.s0 += (((bits4.s0 & 0x0F00) >> 8) * scale.s0 - minv.s0) * shared_y; \
|
||||
total_sums.s1 += (((bits4.s1 & 0x0F00) >> 8) * scale.s1 - minv.s1) * shared_y; \
|
||||
shared_y = sub_group_broadcast(y.s3, 2); \
|
||||
total_sums.s0 += (((bits4.s0 & 0xF000) >> 12) * scale.s0 - minv.s0) * shared_y; \
|
||||
total_sums.s1 += (((bits4.s1 & 0xF000) >> 12) * scale.s1 - minv.s1) * shared_y; \
|
||||
shared_y = sub_group_broadcast(y.s4, 2); \
|
||||
total_sums.s0 += ((bits4.s2 & 0x000F) * scale.s0 - minv.s0) * shared_y; \
|
||||
total_sums.s1 += ((bits4.s3 & 0x000F) * scale.s1 - minv.s1) * shared_y; \
|
||||
shared_y = sub_group_broadcast(y.s5, 2); \
|
||||
total_sums.s0 += (((bits4.s2 & 0x00F0) >> 4) * scale.s0 - minv.s0) * shared_y; \
|
||||
total_sums.s1 += (((bits4.s3 & 0x00F0) >> 4) * scale.s1 - minv.s1) * shared_y; \
|
||||
shared_y = sub_group_broadcast(y.s6, 2); \
|
||||
total_sums.s0 += (((bits4.s2 & 0x0F00) >> 8) * scale.s0 - minv.s0) * shared_y; \
|
||||
total_sums.s1 += (((bits4.s3 & 0x0F00) >> 8) * scale.s1 - minv.s1) * shared_y; \
|
||||
shared_y = sub_group_broadcast(y.s7, 2); \
|
||||
total_sums.s0 += (((bits4.s2 & 0xF000) >> 12) * scale.s0 - minv.s0) * shared_y; \
|
||||
total_sums.s1 += (((bits4.s3 & 0xF000) >> 12) * scale.s1 - minv.s1) * shared_y; \
|
||||
shared_y = sub_group_broadcast(y.s0, 3); \
|
||||
total_sums.s0 += ((bits4.s4 & 0x000F) * scale.s0 - minv.s0) * shared_y; \
|
||||
total_sums.s1 += ((bits4.s5 & 0x000F) * scale.s1 - minv.s1) * shared_y; \
|
||||
shared_y = sub_group_broadcast(y.s1, 3); \
|
||||
total_sums.s0 += (((bits4.s4 & 0x00F0) >> 4) * scale.s0 - minv.s0) * shared_y; \
|
||||
total_sums.s1 += (((bits4.s5 & 0x00F0) >> 4) * scale.s1 - minv.s1) * shared_y; \
|
||||
shared_y = sub_group_broadcast(y.s2, 3); \
|
||||
total_sums.s0 += (((bits4.s4 & 0x0F00) >> 8) * scale.s0 - minv.s0) * shared_y; \
|
||||
total_sums.s1 += (((bits4.s5 & 0x0F00) >> 8) * scale.s1 - minv.s1) * shared_y; \
|
||||
shared_y = sub_group_broadcast(y.s3, 3); \
|
||||
total_sums.s0 += (((bits4.s4 & 0xF000) >> 12) * scale.s0 - minv.s0) * shared_y; \
|
||||
total_sums.s1 += (((bits4.s5 & 0xF000) >> 12) * scale.s1 - minv.s1) * shared_y; \
|
||||
shared_y = sub_group_broadcast(y.s4, 3); \
|
||||
total_sums.s0 += ((bits4.s6 & 0x000F) * scale.s0 - minv.s0) * shared_y; \
|
||||
total_sums.s1 += ((bits4.s7 & 0x000F) * scale.s1 - minv.s1) * shared_y; \
|
||||
shared_y = sub_group_broadcast(y.s5, 3); \
|
||||
total_sums.s0 += (((bits4.s6 & 0x00F0) >> 4) * scale.s0 - minv.s0) * shared_y; \
|
||||
total_sums.s1 += (((bits4.s7 & 0x00F0) >> 4) * scale.s1 - minv.s1) * shared_y; \
|
||||
shared_y = sub_group_broadcast(y.s6, 3); \
|
||||
total_sums.s0 += (((bits4.s6 & 0x0F00) >> 8) * scale.s0 - minv.s0) * shared_y; \
|
||||
total_sums.s1 += (((bits4.s7 & 0x0F00) >> 8) * scale.s1 - minv.s1) * shared_y; \
|
||||
shared_y = sub_group_broadcast(y.s7, 3); \
|
||||
total_sums.s0 += (((bits4.s6 & 0xF000) >> 12) * scale.s0 - minv.s0) * shared_y; \
|
||||
total_sums.s1 += (((bits4.s7 & 0xF000) >> 12) * scale.s1 - minv.s1) * shared_y; \
|
||||
|
||||
|
||||
#define dequantizeBlockAccum_ns_sgbroadcast_8_hi(total_sums, bits4, scale, minv, y) \
|
||||
float8 shared_y; \
|
||||
shared_y = sub_group_broadcast(y, 0); \
|
||||
total_sums.s0 += ((bits4.s0 & 0x000F) * scale.s0 - minv.s0) * shared_y.s0; \
|
||||
total_sums.s0 += (((bits4.s0 & 0x00F0) >> 4) * scale.s0 - minv.s0) * shared_y.s1; \
|
||||
total_sums.s0 += (((bits4.s0 & 0x0F00) >> 8) * scale.s0 - minv.s0) * shared_y.s2; \
|
||||
total_sums.s0 += (((bits4.s0 & 0xF000) >> 12) * scale.s0 - minv.s0) * shared_y.s3; \
|
||||
total_sums.s0 += ((bits4.s2 & 0x000F) * scale.s0 - minv.s0) * shared_y.s4; \
|
||||
total_sums.s0 += (((bits4.s2 & 0x00F0) >> 4) * scale.s0 - minv.s0) * shared_y.s5; \
|
||||
total_sums.s0 += (((bits4.s2 & 0x0F00) >> 8) * scale.s0 - minv.s0) * shared_y.s6; \
|
||||
total_sums.s0 += (((bits4.s2 & 0xF000) >> 12) * scale.s0 - minv.s0) * shared_y.s7; \
|
||||
total_sums.s1 += ((bits4.s1 & 0x000F) * scale.s1 - minv.s1) * shared_y.s0; \
|
||||
total_sums.s1 += (((bits4.s1 & 0x00F0) >> 4) * scale.s1 - minv.s1) * shared_y.s1; \
|
||||
total_sums.s1 += (((bits4.s1 & 0x0F00) >> 8) * scale.s1 - minv.s1) * shared_y.s2; \
|
||||
total_sums.s1 += (((bits4.s1 & 0xF000) >> 12) * scale.s1 - minv.s1) * shared_y.s3; \
|
||||
total_sums.s1 += ((bits4.s3 & 0x000F) * scale.s1 - minv.s1) * shared_y.s4; \
|
||||
total_sums.s1 += (((bits4.s3 & 0x00F0) >> 4) * scale.s1 - minv.s1) * shared_y.s5; \
|
||||
total_sums.s1 += (((bits4.s3 & 0x0F00) >> 8) * scale.s1 - minv.s1) * shared_y.s6; \
|
||||
total_sums.s1 += (((bits4.s3 & 0xF000) >> 12) * scale.s1 - minv.s1) * shared_y.s7; \
|
||||
shared_y = sub_group_broadcast(y, 1); \
|
||||
total_sums.s0 += ((bits4.s4 & 0x000F) * scale.s0 - minv.s0) * shared_y.s0; \
|
||||
total_sums.s0 += (((bits4.s4 & 0x00F0) >> 4) * scale.s0 - minv.s0) * shared_y.s1; \
|
||||
total_sums.s0 += (((bits4.s4 & 0x0F00) >> 8) * scale.s0 - minv.s0) * shared_y.s2; \
|
||||
total_sums.s0 += (((bits4.s4 & 0xF000) >> 12) * scale.s0 - minv.s0) * shared_y.s3; \
|
||||
total_sums.s0 += ((bits4.s6 & 0x000F) * scale.s0 - minv.s0) * shared_y.s4; \
|
||||
total_sums.s0 += (((bits4.s6 & 0x00F0) >> 4) * scale.s0 - minv.s0) * shared_y.s5; \
|
||||
total_sums.s0 += (((bits4.s6 & 0x0F00) >> 8) * scale.s0 - minv.s0) * shared_y.s6; \
|
||||
total_sums.s0 += (((bits4.s6 & 0xF000) >> 12) * scale.s0 - minv.s0) * shared_y.s7; \
|
||||
total_sums.s1 += ((bits4.s5 & 0x000F) * scale.s1 - minv.s1) * shared_y.s0; \
|
||||
total_sums.s1 += (((bits4.s5 & 0x00F0) >> 4) * scale.s1 - minv.s1) * shared_y.s1; \
|
||||
total_sums.s1 += (((bits4.s5 & 0x0F00) >> 8) * scale.s1 - minv.s1) * shared_y.s2; \
|
||||
total_sums.s1 += (((bits4.s5 & 0xF000) >> 12) * scale.s1 - minv.s1) * shared_y.s3; \
|
||||
total_sums.s1 += ((bits4.s7 & 0x000F) * scale.s1 - minv.s1) * shared_y.s4; \
|
||||
total_sums.s1 += (((bits4.s7 & 0x00F0) >> 4) * scale.s1 - minv.s1) * shared_y.s5; \
|
||||
total_sums.s1 += (((bits4.s7 & 0x0F00) >> 8) * scale.s1 - minv.s1) * shared_y.s6; \
|
||||
total_sums.s1 += (((bits4.s7 & 0xF000) >> 12) * scale.s1 - minv.s1) * shared_y.s7; \
|
||||
|
||||
|
||||
#define dequantizeBlockAccum_ns_sgbroadcast_8_lo(total_sums, bits4, scale, minv, y) \
|
||||
shared_y = sub_group_broadcast(y, 2); \
|
||||
total_sums.s0 += ((bits4.s0 & 0x000F) * scale.s0 - minv.s0) * shared_y.s0; \
|
||||
total_sums.s0 += (((bits4.s0 & 0x00F0) >> 4) * scale.s0 - minv.s0) * shared_y.s1; \
|
||||
total_sums.s0 += (((bits4.s0 & 0x0F00) >> 8) * scale.s0 - minv.s0) * shared_y.s2; \
|
||||
total_sums.s0 += (((bits4.s0 & 0xF000) >> 12) * scale.s0 - minv.s0) * shared_y.s3; \
|
||||
total_sums.s0 += ((bits4.s2 & 0x000F) * scale.s0 - minv.s0) * shared_y.s4; \
|
||||
total_sums.s0 += (((bits4.s2 & 0x00F0) >> 4) * scale.s0 - minv.s0) * shared_y.s5; \
|
||||
total_sums.s0 += (((bits4.s2 & 0x0F00) >> 8) * scale.s0 - minv.s0) * shared_y.s6; \
|
||||
total_sums.s0 += (((bits4.s2 & 0xF000) >> 12) * scale.s0 - minv.s0) * shared_y.s7; \
|
||||
total_sums.s1 += ((bits4.s1 & 0x000F) * scale.s1 - minv.s1) * shared_y.s0; \
|
||||
total_sums.s1 += (((bits4.s1 & 0x00F0) >> 4) * scale.s1 - minv.s1) * shared_y.s1; \
|
||||
total_sums.s1 += (((bits4.s1 & 0x0F00) >> 8) * scale.s1 - minv.s1) * shared_y.s2; \
|
||||
total_sums.s1 += (((bits4.s1 & 0xF000) >> 12) * scale.s1 - minv.s1) * shared_y.s3; \
|
||||
total_sums.s1 += ((bits4.s3 & 0x000F) * scale.s1 - minv.s1) * shared_y.s4; \
|
||||
total_sums.s1 += (((bits4.s3 & 0x00F0) >> 4) * scale.s1 - minv.s1) * shared_y.s5; \
|
||||
total_sums.s1 += (((bits4.s3 & 0x0F00) >> 8) * scale.s1 - minv.s1) * shared_y.s6; \
|
||||
total_sums.s1 += (((bits4.s3 & 0xF000) >> 12) * scale.s1 - minv.s1) * shared_y.s7; \
|
||||
shared_y = sub_group_broadcast(y, 3); \
|
||||
total_sums.s0 += ((bits4.s4 & 0x000F) * scale.s0 - minv.s0) * shared_y.s0; \
|
||||
total_sums.s0 += (((bits4.s4 & 0x00F0) >> 4) * scale.s0 - minv.s0) * shared_y.s1; \
|
||||
total_sums.s0 += (((bits4.s4 & 0x0F00) >> 8) * scale.s0 - minv.s0) * shared_y.s2; \
|
||||
total_sums.s0 += (((bits4.s4 & 0xF000) >> 12) * scale.s0 - minv.s0) * shared_y.s3; \
|
||||
total_sums.s0 += ((bits4.s6 & 0x000F) * scale.s0 - minv.s0) * shared_y.s4; \
|
||||
total_sums.s0 += (((bits4.s6 & 0x00F0) >> 4) * scale.s0 - minv.s0) * shared_y.s5; \
|
||||
total_sums.s0 += (((bits4.s6 & 0x0F00) >> 8) * scale.s0 - minv.s0) * shared_y.s6; \
|
||||
total_sums.s0 += (((bits4.s6 & 0xF000) >> 12) * scale.s0 - minv.s0) * shared_y.s7; \
|
||||
total_sums.s1 += ((bits4.s5 & 0x000F) * scale.s1 - minv.s1) * shared_y.s0; \
|
||||
total_sums.s1 += (((bits4.s5 & 0x00F0) >> 4) * scale.s1 - minv.s1) * shared_y.s1; \
|
||||
total_sums.s1 += (((bits4.s5 & 0x0F00) >> 8) * scale.s1 - minv.s1) * shared_y.s2; \
|
||||
total_sums.s1 += (((bits4.s5 & 0xF000) >> 12) * scale.s1 - minv.s1) * shared_y.s3; \
|
||||
total_sums.s1 += ((bits4.s7 & 0x000F) * scale.s1 - minv.s1) * shared_y.s4; \
|
||||
total_sums.s1 += (((bits4.s7 & 0x00F0) >> 4) * scale.s1 - minv.s1) * shared_y.s5; \
|
||||
total_sums.s1 += (((bits4.s7 & 0x0F00) >> 8) * scale.s1 - minv.s1) * shared_y.s6; \
|
||||
total_sums.s1 += (((bits4.s7 & 0xF000) >> 12) * scale.s1 - minv.s1) * shared_y.s7; \
|
||||
|
||||
#ifdef ADRENO_GPU
|
||||
REQD_SUBGROUP_SIZE_64
|
||||
#endif
|
||||
kernel void kernel_gemv_noshuffle_q4_k_f32_o4(
|
||||
read_only image1d_buffer_t src0_q,
|
||||
global half2 * src0_d,
|
||||
global half2 * src0_m,
|
||||
global uchar * src0_s,
|
||||
read_only image1d_buffer_t src1,
|
||||
global float * dst,
|
||||
ulong offsetd,
|
||||
int ne00,
|
||||
int ne01,
|
||||
uchar mask_d6,
|
||||
uchar mask_d4,
|
||||
uchar mask_hi2)
|
||||
{
|
||||
uint groupId = get_local_id(1);
|
||||
uint gid = get_global_id(0); // 4-output quad index
|
||||
ushort slid = get_sub_group_local_id();
|
||||
|
||||
// Two consecutive pair-indices (each the same access pattern the 2-output
|
||||
// kernel uses); together they cover 4 consecutive output rows.
|
||||
uint gid_a = gid * 2;
|
||||
uint gid_b = gid * 2 + 1;
|
||||
|
||||
uint K = ne00;
|
||||
uint M = ne01;
|
||||
|
||||
uint LINE_STRIDE_A = M / 2;
|
||||
uint BLOCK_STRIDE_A = NSUBGROUPS * M;
|
||||
|
||||
private uint4 regA;
|
||||
private half2 regS_a, regS_b;
|
||||
private half2 regM_a, regM_b;
|
||||
private float8 regB;
|
||||
|
||||
private float2 totalSum_a = (float2)(0.0f);
|
||||
private float2 totalSum_b = (float2)(0.0f);
|
||||
|
||||
for (uint k = groupId; k < (K / 32); k += NSUBGROUPS) {
|
||||
uint sb = k / 8;
|
||||
uint j = k % 8;
|
||||
|
||||
// pair a scales/mins
|
||||
half2 d_a = src0_d[gid_a + sb * LINE_STRIDE_A];
|
||||
half2 dm_a = src0_m[gid_a + sb * LINE_STRIDE_A];
|
||||
global const uchar * sc0a = src0_s + sb * 12 * M + 2 * gid_a;
|
||||
global const uchar * sc1a = sc0a + 1;
|
||||
uchar sv0a, mn0a, sv1a, mn1a;
|
||||
get_scale_min_k4(j, sc0a, M, &sv0a, &mn0a, mask_d6, mask_d4, mask_hi2);
|
||||
get_scale_min_k4(j, sc1a, M, &sv1a, &mn1a, mask_d6, mask_d4, mask_hi2);
|
||||
regS_a = convert_half2(convert_float2(d_a) * convert_float2((uchar2)(sv0a, sv1a)));
|
||||
regM_a = convert_half2(convert_float2(dm_a) * convert_float2((uchar2)(mn0a, mn1a)));
|
||||
|
||||
// pair b scales/mins
|
||||
half2 d_b = src0_d[gid_b + sb * LINE_STRIDE_A];
|
||||
half2 dm_b = src0_m[gid_b + sb * LINE_STRIDE_A];
|
||||
global const uchar * sc0b = src0_s + sb * 12 * M + 2 * gid_b;
|
||||
global const uchar * sc1b = sc0b + 1;
|
||||
uchar sv0b, mn0b, sv1b, mn1b;
|
||||
get_scale_min_k4(j, sc0b, M, &sv0b, &mn0b, mask_d6, mask_d4, mask_hi2);
|
||||
get_scale_min_k4(j, sc1b, M, &sv1b, &mn1b, mask_d6, mask_d4, mask_hi2);
|
||||
regS_b = convert_half2(convert_float2(d_b) * convert_float2((uchar2)(sv0b, sv1b)));
|
||||
regM_b = convert_half2(convert_float2(dm_b) * convert_float2((uchar2)(mn0b, mn1b)));
|
||||
|
||||
// activation: load once, reuse for both pairs
|
||||
if (slid < 4) {
|
||||
regB.s0123 = read_imagef(src1, (slid * 2 + k * 8));
|
||||
regB.s4567 = read_imagef(src1, (1 + slid * 2 + k * 8));
|
||||
}
|
||||
|
||||
// pair a (own block so _lo sees the shared_y declared by _hi)
|
||||
{
|
||||
regA.s0 = read_imageui(src0_q, (gid_a + k * BLOCK_STRIDE_A + LINE_STRIDE_A * 0)).x;
|
||||
regA.s1 = read_imageui(src0_q, (gid_a + k * BLOCK_STRIDE_A + LINE_STRIDE_A * 1)).x;
|
||||
regA.s2 = read_imageui(src0_q, (gid_a + k * BLOCK_STRIDE_A + LINE_STRIDE_A * 2)).x;
|
||||
regA.s3 = read_imageui(src0_q, (gid_a + k * BLOCK_STRIDE_A + LINE_STRIDE_A * 3)).x;
|
||||
#ifdef VECTOR_SUB_GROUP_BROADCAST
|
||||
dequantizeBlockAccum_ns_sgbroadcast_8_hi(totalSum_a, as_ushort8(regA), regS_a, regM_a, regB);
|
||||
#else
|
||||
dequantizeBlockAccum_ns_sgbroadcast_1_hi(totalSum_a, as_ushort8(regA), regS_a, regM_a, regB);
|
||||
#endif
|
||||
regA.s0 = read_imageui(src0_q, (gid_a + k * BLOCK_STRIDE_A + LINE_STRIDE_A * 4)).x;
|
||||
regA.s1 = read_imageui(src0_q, (gid_a + k * BLOCK_STRIDE_A + LINE_STRIDE_A * 5)).x;
|
||||
regA.s2 = read_imageui(src0_q, (gid_a + k * BLOCK_STRIDE_A + LINE_STRIDE_A * 6)).x;
|
||||
regA.s3 = read_imageui(src0_q, (gid_a + k * BLOCK_STRIDE_A + LINE_STRIDE_A * 7)).x;
|
||||
#ifdef VECTOR_SUB_GROUP_BROADCAST
|
||||
dequantizeBlockAccum_ns_sgbroadcast_8_lo(totalSum_a, as_ushort8(regA), regS_a, regM_a, regB);
|
||||
#else
|
||||
dequantizeBlockAccum_ns_sgbroadcast_1_lo(totalSum_a, as_ushort8(regA), regS_a, regM_a, regB);
|
||||
#endif
|
||||
}
|
||||
|
||||
// pair b
|
||||
{
|
||||
regA.s0 = read_imageui(src0_q, (gid_b + k * BLOCK_STRIDE_A + LINE_STRIDE_A * 0)).x;
|
||||
regA.s1 = read_imageui(src0_q, (gid_b + k * BLOCK_STRIDE_A + LINE_STRIDE_A * 1)).x;
|
||||
regA.s2 = read_imageui(src0_q, (gid_b + k * BLOCK_STRIDE_A + LINE_STRIDE_A * 2)).x;
|
||||
regA.s3 = read_imageui(src0_q, (gid_b + k * BLOCK_STRIDE_A + LINE_STRIDE_A * 3)).x;
|
||||
#ifdef VECTOR_SUB_GROUP_BROADCAST
|
||||
dequantizeBlockAccum_ns_sgbroadcast_8_hi(totalSum_b, as_ushort8(regA), regS_b, regM_b, regB);
|
||||
#else
|
||||
dequantizeBlockAccum_ns_sgbroadcast_1_hi(totalSum_b, as_ushort8(regA), regS_b, regM_b, regB);
|
||||
#endif
|
||||
regA.s0 = read_imageui(src0_q, (gid_b + k * BLOCK_STRIDE_A + LINE_STRIDE_A * 4)).x;
|
||||
regA.s1 = read_imageui(src0_q, (gid_b + k * BLOCK_STRIDE_A + LINE_STRIDE_A * 5)).x;
|
||||
regA.s2 = read_imageui(src0_q, (gid_b + k * BLOCK_STRIDE_A + LINE_STRIDE_A * 6)).x;
|
||||
regA.s3 = read_imageui(src0_q, (gid_b + k * BLOCK_STRIDE_A + LINE_STRIDE_A * 7)).x;
|
||||
#ifdef VECTOR_SUB_GROUP_BROADCAST
|
||||
dequantizeBlockAccum_ns_sgbroadcast_8_lo(totalSum_b, as_ushort8(regA), regS_b, regM_b, regB);
|
||||
#else
|
||||
dequantizeBlockAccum_ns_sgbroadcast_1_lo(totalSum_b, as_ushort8(regA), regS_b, regM_b, regB);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
// reduce 4 outputs (a.s0, a.s1, b.s0, b.s1) across the 4 subgroups
|
||||
local float4 reduceLM[SUBGROUP_SIZE * 3];
|
||||
float4 acc = (float4)(totalSum_a.s0, totalSum_a.s1, totalSum_b.s0, totalSum_b.s1);
|
||||
if (groupId == 1) { reduceLM[SUBGROUP_SIZE * 0 + slid] = acc; }
|
||||
if (groupId == 2) { reduceLM[SUBGROUP_SIZE * 1 + slid] = acc; }
|
||||
if (groupId == 3) { reduceLM[SUBGROUP_SIZE * 2 + slid] = acc; }
|
||||
|
||||
barrier(CLK_LOCAL_MEM_FENCE);
|
||||
|
||||
if (groupId == 0) {
|
||||
acc += reduceLM[SUBGROUP_SIZE * 0 + slid];
|
||||
acc += reduceLM[SUBGROUP_SIZE * 1 + slid];
|
||||
acc += reduceLM[SUBGROUP_SIZE * 2 + slid];
|
||||
dst = (global float*)((global char*)dst + offsetd);
|
||||
// The dispatch rounds ne01/4 up to the subgroup width, so the tail
|
||||
// quads past the last row must not store (they wrote 128 rows past
|
||||
// dst on every ne01 % 256 == 128 vocab, e.g. 151936).
|
||||
if (gid * 4 + 3 < (uint)ne01) {
|
||||
vstore4(acc, 0, &(dst[gid * 4]));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
// Tiled-wide q4_K GEMV for the long-vocab lm_head/embed (decode path).
|
||||
//
|
||||
// Pairs with kernel_convert_block_q4_k_tiled_ns (cvt.cl): the weights are laid
|
||||
// out CANONICALLY (4-bit code in element order e in [0,256)) and TILED by 64
|
||||
// output rows so the 64-thread lane group coalesces every weight load. Both the
|
||||
// pack (convert) and the unpack (here) are owned by us -> correct by
|
||||
// construction vs the reference ggml q4_K dequant. Same structure as the q6_K
|
||||
// tiled GEMV; the only differences are the 4-bit dequant and the q4_K
|
||||
// scale/min decode (get_scale_min_k4 from the packed 12-byte block).
|
||||
//
|
||||
// One work-item produces one output row. WG = {64 lanes, 4 subgroups}: the 64
|
||||
// lanes cover the 64 rows of one tile (coalesced uint4 reads), the 4 subgroups
|
||||
// split the K-blocks and reduce through __local at the end. Weights read from
|
||||
// __global (lm_head is streamed once per token; texture cache caps it below the
|
||||
// coalesced-global rate).
|
||||
|
||||
#pragma OPENCL EXTENSION cl_khr_fp16 : enable
|
||||
|
||||
#ifdef cl_qcom_reqd_sub_group_size
|
||||
#pragma OPENCL EXTENSION cl_qcom_reqd_sub_group_size : enable
|
||||
#define ADRENO_GPU 1
|
||||
#define REQD_SUBGROUP_SIZE_64 __attribute__((qcom_reqd_sub_group_size("half")))
|
||||
#endif
|
||||
|
||||
#define QK_K 256
|
||||
#define NSUBGROUPS 4
|
||||
#define TILE_ROWS 64
|
||||
|
||||
// Decode one q4_K sub-block scale + min from the packed 12-byte block.
|
||||
// Identical to the o4 kernel's helper (masks hard-coded: d6=0x3F, d4=0x0F, hi2=0xC0).
|
||||
inline void q4k_scale_min(int j, __global const uchar * q, uchar * d, uchar * m) {
|
||||
if (j < 4) {
|
||||
*d = q[j] & 0x3F;
|
||||
*m = q[j+4] & 0x3F;
|
||||
} else {
|
||||
*d = (q[j+4] & 0x0F) | ((q[j-4] & 0xC0) >> 2);
|
||||
*m = ((q[j+4] >> 4) & 0x0F) | ((q[j] & 0xC0) >> 2);
|
||||
}
|
||||
}
|
||||
|
||||
#if defined(ADRENO_GPU)
|
||||
REQD_SUBGROUP_SIZE_64
|
||||
#endif
|
||||
kernel void kernel_gemv_noshuffle_q4_k_f32_tiled(
|
||||
__global uint4 * src0_q, // tiled: 8 uint4 granules / superblock (4-bit codes)
|
||||
__global half * src0_d, // tiled: 1 half / superblock
|
||||
__global half * src0_dm, // tiled: 1 half / superblock
|
||||
__global uchar * src0_s, // tiled: 12 bytes / superblock (packed scales)
|
||||
read_only image1d_buffer_t src1, // activation (RGBA f32)
|
||||
global float * dst,
|
||||
ulong offsetd,
|
||||
int ne00,
|
||||
int ne01
|
||||
) {
|
||||
int grp = get_local_id(1); // subgroup index 0..3 (splits K)
|
||||
int row = get_global_id(0); // output row along ne01
|
||||
int rt = row / TILE_ROWS;
|
||||
int rit = row % TILE_ROWS;
|
||||
|
||||
int nb = ne00 / QK_K; // superblocks per row
|
||||
|
||||
float acc = 0.0f;
|
||||
|
||||
for (int sb = grp; sb < nb; sb += NSUBGROUPS) {
|
||||
int tile_blk = rt * nb + sb; // ne02 == 1 for lm_head/embed
|
||||
|
||||
float dval = (float)src0_d [tile_blk * TILE_ROWS + rit];
|
||||
float dmval = (float)src0_dm[tile_blk * TILE_ROWS + rit];
|
||||
|
||||
// decode the 8 sub-block (scale, min) pairs
|
||||
__global uchar * sc = src0_s + (tile_blk * TILE_ROWS + rit) * 12;
|
||||
float scale[8], minv[8];
|
||||
#pragma unroll
|
||||
for (int is = 0; is < 8; ++is) {
|
||||
uchar sd, sm;
|
||||
q4k_scale_min(is, sc, &sd, &sm);
|
||||
scale[is] = dval * (float)sd;
|
||||
minv[is] = dmval * (float)sm;
|
||||
}
|
||||
|
||||
// 32 uints of 4-bit codes (8 codes/uint), e-order
|
||||
uint q[32];
|
||||
#pragma unroll
|
||||
for (int g = 0; g < 8; ++g) {
|
||||
uint4 v = src0_q[(tile_blk * 8 + g) * TILE_ROWS + rit];
|
||||
q[g*4+0] = v.x; q[g*4+1] = v.y; q[g*4+2] = v.z; q[g*4+3] = v.w;
|
||||
}
|
||||
|
||||
// dequant 256 codes in canonical e-order, MAC with activation.
|
||||
int act_base = sb * 64; // activation float4 pixel base (256/4)
|
||||
#pragma unroll
|
||||
for (int e4 = 0; e4 < 64; ++e4) {
|
||||
float4 a = read_imagef(src1, act_base + e4);
|
||||
#pragma unroll
|
||||
for (int t = 0; t < 4; ++t) {
|
||||
int e = e4 * 4 + t;
|
||||
uint code = (q[e >> 3] >> ((e & 7) * 4)) & 0xF;
|
||||
int is = e >> 5; // sub-block index = e/32
|
||||
float av = (t == 0) ? a.x : (t == 1) ? a.y : (t == 2) ? a.z : a.w;
|
||||
acc += ((float)code * scale[is] - minv[is]) * av;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// reduce across the NSUBGROUPS subgroups (same rit, different K-subset)
|
||||
local float reduce_lm[NSUBGROUPS * TILE_ROWS];
|
||||
reduce_lm[grp * TILE_ROWS + rit] = acc;
|
||||
barrier(CLK_LOCAL_MEM_FENCE);
|
||||
|
||||
if (grp == 0) {
|
||||
float total = reduce_lm[0 * TILE_ROWS + rit]
|
||||
+ reduce_lm[1 * TILE_ROWS + rit]
|
||||
+ reduce_lm[2 * TILE_ROWS + rit]
|
||||
+ reduce_lm[3 * TILE_ROWS + rit];
|
||||
dst = (global float*)((global char*)dst + offsetd);
|
||||
dst[row] = total;
|
||||
}
|
||||
}
|
||||
@@ -329,3 +329,125 @@ kernel void kernel_gemv_noshuffle_q5_k_f32(
|
||||
if (gid * 2 + 1 < M) dst[gid * 2 + 1] = totalSum.s1;
|
||||
}
|
||||
}
|
||||
|
||||
// Multi-column (N in [2..4]) variant of the q5_K decode GEMV (spec/MTP verify) =
|
||||
// q4_K mc3 + the high-bit qh plane (regH). n_cols = 2..4 (drafted + bonus); routes
|
||||
// the small-batch verify OFF the gemm_noshuffle_q5_k dead-zone. n_cols==3 is byte-
|
||||
// identical to the original mc3 (col3 disabled, float8 slots 6/7 stay zero).
|
||||
#ifdef VECTOR_SUB_GROUP_BROADCAST
|
||||
#define MC_DQ5_HI dequantizeBlockAccum_ns_sgbroadcast_8_hi
|
||||
#define MC_DQ5_LO dequantizeBlockAccum_ns_sgbroadcast_8_lo
|
||||
#else
|
||||
#define MC_DQ5_HI dequantizeBlockAccum_ns_sgbroadcast_1_hi
|
||||
#define MC_DQ5_LO dequantizeBlockAccum_ns_sgbroadcast_1_lo
|
||||
#endif
|
||||
#define MC_COL_Q5K(ts, c) \
|
||||
{ if (slid < 4) { regB.s0123 = read_imagef(src1, (c)*COL_STRIDE + slid*2 + k*8); \
|
||||
regB.s4567 = read_imagef(src1, (c)*COL_STRIDE + 1 + slid*2 + k*8); } \
|
||||
MC_DQ5_HI(ts, as_ushort8(regA_hi), as_uchar8(regH), regS, regM, regB); \
|
||||
MC_DQ5_LO(ts, as_ushort8(regA_lo), as_uchar8(regH), regS, regM, regB); }
|
||||
#ifdef ADRENO_GPU
|
||||
REQD_SUBGROUP_SIZE_64
|
||||
#endif
|
||||
kernel void kernel_gemv_noshuffle_q5_k_f32_mc3(
|
||||
read_only image1d_buffer_t src0_q,
|
||||
read_only image1d_buffer_t src0_qh,
|
||||
global half2 * src0_d,
|
||||
global half2 * src0_m,
|
||||
global uchar * src0_s,
|
||||
read_only image1d_buffer_t src1,
|
||||
global float * dst,
|
||||
ulong offsetd,
|
||||
int ne00,
|
||||
int ne01,
|
||||
uchar mask_d6,
|
||||
uchar mask_d4,
|
||||
uchar mask_hi2,
|
||||
int n_cols)
|
||||
{
|
||||
uint groupId = get_local_id(1);
|
||||
uint gid = get_global_id(0);
|
||||
ushort slid = get_sub_group_local_id();
|
||||
|
||||
uint K = ne00;
|
||||
uint M = ne01;
|
||||
|
||||
uint LINE_STRIDE_A = M / 2;
|
||||
uint BLOCK_STRIDE_A = NSUBGROUPS * M;
|
||||
uint LINE_STRIDE_A_QH = M / 2;
|
||||
uint BLOCK_STRIDE_A_QH = NSUBGROUPS * M / 2;
|
||||
uint scales_per_row = (K / QK_K) * 12;
|
||||
uint COL_STRIDE = K / 4; // float4 pixels per activation column
|
||||
|
||||
private uint4 regA_hi, regA_lo;
|
||||
private ushort4 regH;
|
||||
private half2 regS, regM;
|
||||
private float8 regB;
|
||||
|
||||
private float2 ts0 = (float2)(0.0f);
|
||||
private float2 ts1 = (float2)(0.0f);
|
||||
private float2 ts2 = (float2)(0.0f);
|
||||
private float2 ts3 = (float2)(0.0f);
|
||||
|
||||
for (uint k = groupId; k < (K / 32); k += NSUBGROUPS) {
|
||||
uint sb = k / 8;
|
||||
uint j = k % 8;
|
||||
|
||||
half2 d = src0_d[gid + sb * LINE_STRIDE_A];
|
||||
half2 dm = src0_m[gid + sb * LINE_STRIDE_A];
|
||||
|
||||
global const uchar * sc0 = src0_s + 2 * gid * scales_per_row + sb * 12;
|
||||
global const uchar * sc1 = src0_s + (2 * gid + 1) * scales_per_row + sb * 12;
|
||||
|
||||
uchar sv0, mn0, sv1, mn1;
|
||||
get_scale_min_k4(j, sc0, &sv0, &mn0, mask_d6, mask_d4, mask_hi2);
|
||||
get_scale_min_k4(j, sc1, &sv1, &mn1, mask_d6, mask_d4, mask_hi2);
|
||||
|
||||
regS = convert_half2(convert_float2(d) * convert_float2((uchar2)(sv0, sv1)));
|
||||
regM = convert_half2(convert_float2(dm) * convert_float2((uchar2)(mn0, mn1)));
|
||||
|
||||
// high-bit plane + weights loaded ONCE, reused across the columns
|
||||
regH.s0 = as_ushort(read_imageh(src0_qh, (gid + k * BLOCK_STRIDE_A_QH + LINE_STRIDE_A_QH * 0)).x);
|
||||
regH.s1 = as_ushort(read_imageh(src0_qh, (gid + k * BLOCK_STRIDE_A_QH + LINE_STRIDE_A_QH * 1)).x);
|
||||
regH.s2 = as_ushort(read_imageh(src0_qh, (gid + k * BLOCK_STRIDE_A_QH + LINE_STRIDE_A_QH * 2)).x);
|
||||
regH.s3 = as_ushort(read_imageh(src0_qh, (gid + k * BLOCK_STRIDE_A_QH + LINE_STRIDE_A_QH * 3)).x);
|
||||
|
||||
regA_hi.s0 = read_imageui(src0_q, (gid + k * BLOCK_STRIDE_A + LINE_STRIDE_A * 0)).x;
|
||||
regA_hi.s1 = read_imageui(src0_q, (gid + k * BLOCK_STRIDE_A + LINE_STRIDE_A * 1)).x;
|
||||
regA_hi.s2 = read_imageui(src0_q, (gid + k * BLOCK_STRIDE_A + LINE_STRIDE_A * 2)).x;
|
||||
regA_hi.s3 = read_imageui(src0_q, (gid + k * BLOCK_STRIDE_A + LINE_STRIDE_A * 3)).x;
|
||||
regA_lo.s0 = read_imageui(src0_q, (gid + k * BLOCK_STRIDE_A + LINE_STRIDE_A * 4)).x;
|
||||
regA_lo.s1 = read_imageui(src0_q, (gid + k * BLOCK_STRIDE_A + LINE_STRIDE_A * 5)).x;
|
||||
regA_lo.s2 = read_imageui(src0_q, (gid + k * BLOCK_STRIDE_A + LINE_STRIDE_A * 6)).x;
|
||||
regA_lo.s3 = read_imageui(src0_q, (gid + k * BLOCK_STRIDE_A + LINE_STRIDE_A * 7)).x;
|
||||
|
||||
MC_COL_Q5K(ts0, 0);
|
||||
MC_COL_Q5K(ts1, 1);
|
||||
if (n_cols > 2) MC_COL_Q5K(ts2, 2);
|
||||
if (n_cols > 3) MC_COL_Q5K(ts3, 3);
|
||||
}
|
||||
|
||||
// cross-subgroup reduce: pack the (up to 4) columns' float2 into a float8.
|
||||
local float8 reduceLM[SUBGROUP_SIZE * 3];
|
||||
float8 acc = (float8)(ts0.s0, ts0.s1, ts1.s0, ts1.s1, ts2.s0, ts2.s1, ts3.s0, ts3.s1);
|
||||
if (groupId == 1) { reduceLM[SUBGROUP_SIZE * 0 + slid] = acc; }
|
||||
if (groupId == 2) { reduceLM[SUBGROUP_SIZE * 1 + slid] = acc; }
|
||||
if (groupId == 3) { reduceLM[SUBGROUP_SIZE * 2 + slid] = acc; }
|
||||
|
||||
barrier(CLK_LOCAL_MEM_FENCE);
|
||||
|
||||
if (groupId == 0) {
|
||||
acc += reduceLM[SUBGROUP_SIZE * 0 + slid];
|
||||
acc += reduceLM[SUBGROUP_SIZE * 1 + slid];
|
||||
acc += reduceLM[SUBGROUP_SIZE * 2 + slid];
|
||||
dst = (global float*)((global char*)dst + offsetd);
|
||||
// dst is column-major [M rows x n_cols cols]: (row, col) at col*M + row
|
||||
vstore2((float2)(acc.s0, acc.s1), 0, &(dst[0 * M + gid * 2]));
|
||||
vstore2((float2)(acc.s2, acc.s3), 0, &(dst[1 * M + gid * 2]));
|
||||
if (n_cols > 2) vstore2((float2)(acc.s4, acc.s5), 0, &(dst[2 * M + gid * 2]));
|
||||
if (n_cols > 3) vstore2((float2)(acc.s6, acc.s7), 0, &(dst[3 * M + gid * 2]));
|
||||
}
|
||||
}
|
||||
#undef MC_COL_Q5K
|
||||
#undef MC_DQ5_HI
|
||||
#undef MC_DQ5_LO
|
||||
|
||||
@@ -296,3 +296,114 @@ kernel void kernel_gemv_noshuffle_q6_K_f32(
|
||||
if (gid * 2 + 1 < ne01) dst[gid * 2 + 1] = total_sum.s1;
|
||||
}
|
||||
}
|
||||
|
||||
// Multi-column (N=3) q6_K decode GEMV for the spec/MTP verify batch. Same idea
|
||||
// as the q4_K mc3: stay on the efficient GEMV path (subgroup broadcast, no
|
||||
// transpose) instead of the transposed-GEMM dead-zone. Each K-block's weights
|
||||
// (ql/qh, hi+lo) are loaded ONCE and reused across all 3 activation columns.
|
||||
// Per-column accumulation is independent and identical to 3 standalone GEMVs
|
||||
// => byte-identical; does NOT perturb the lm_head logits / spec accept rate.
|
||||
#if defined(ADRENO_GPU)
|
||||
REQD_SUBGROUP_SIZE_64
|
||||
#endif
|
||||
kernel void kernel_gemv_noshuffle_q6_K_f32_mc3(
|
||||
read_only image1d_buffer_t src0_ql,
|
||||
read_only image1d_buffer_t src0_qh,
|
||||
global half2 * src0_s,
|
||||
global half2 * src0_d,
|
||||
read_only image1d_buffer_t src1,
|
||||
global float * dst,
|
||||
ulong offsetd,
|
||||
int ne00,
|
||||
int ne01
|
||||
) {
|
||||
int grp = get_local_id(1);
|
||||
int gid = get_global_id(0);
|
||||
ushort slid = get_sub_group_local_id();
|
||||
|
||||
int nb = ne00 / 32;
|
||||
int line_stride_a = ne01 / 2;
|
||||
int block_stride_a = NSUBGROUPS * ne01;
|
||||
int COL_STRIDE = ne00 / 4; // float4 pixels per activation column
|
||||
|
||||
uint4 ql_hi, ql_lo;
|
||||
ushort4 qh_hi, qh_lo;
|
||||
half2 reg_d;
|
||||
char4 reg_s;
|
||||
float8 reg_b;
|
||||
|
||||
float2 ts0 = 0.0f, ts1 = 0.0f, ts2 = 0.0f;
|
||||
|
||||
for (int k = grp; k < nb; k += NSUBGROUPS) {
|
||||
reg_d = src0_d[gid + k/8 * line_stride_a];
|
||||
reg_s = as_char4(src0_s[gid + k * line_stride_a]);
|
||||
|
||||
// weights loaded ONCE (hi: blocks 0-3, lo: blocks 4-7), reused x3 cols
|
||||
ql_hi.s0 = read_imageui(src0_ql, gid + k*block_stride_a + line_stride_a*0).x;
|
||||
ql_hi.s1 = read_imageui(src0_ql, gid + k*block_stride_a + line_stride_a*1).x;
|
||||
ql_hi.s2 = read_imageui(src0_ql, gid + k*block_stride_a + line_stride_a*2).x;
|
||||
ql_hi.s3 = read_imageui(src0_ql, gid + k*block_stride_a + line_stride_a*3).x;
|
||||
qh_hi.s0 = as_ushort(read_imageh(src0_qh, gid + k*block_stride_a + line_stride_a*0).x);
|
||||
qh_hi.s1 = as_ushort(read_imageh(src0_qh, gid + k*block_stride_a + line_stride_a*1).x);
|
||||
qh_hi.s2 = as_ushort(read_imageh(src0_qh, gid + k*block_stride_a + line_stride_a*2).x);
|
||||
qh_hi.s3 = as_ushort(read_imageh(src0_qh, gid + k*block_stride_a + line_stride_a*3).x);
|
||||
|
||||
ql_lo.s0 = read_imageui(src0_ql, gid + k*block_stride_a + line_stride_a*4).x;
|
||||
ql_lo.s1 = read_imageui(src0_ql, gid + k*block_stride_a + line_stride_a*5).x;
|
||||
ql_lo.s2 = read_imageui(src0_ql, gid + k*block_stride_a + line_stride_a*6).x;
|
||||
ql_lo.s3 = read_imageui(src0_ql, gid + k*block_stride_a + line_stride_a*7).x;
|
||||
qh_lo.s0 = as_ushort(read_imageh(src0_qh, gid + k*block_stride_a + line_stride_a*4).x);
|
||||
qh_lo.s1 = as_ushort(read_imageh(src0_qh, gid + k*block_stride_a + line_stride_a*5).x);
|
||||
qh_lo.s2 = as_ushort(read_imageh(src0_qh, gid + k*block_stride_a + line_stride_a*6).x);
|
||||
qh_lo.s3 = as_ushort(read_imageh(src0_qh, gid + k*block_stride_a + line_stride_a*7).x);
|
||||
|
||||
// Per-column: load only this column's activation (single reg_b live) ->
|
||||
// 1/3 the activation register pressure, cutting the private-mem spill.
|
||||
#ifdef VECTOR_SUB_GROUP_BROADCAT
|
||||
{ if (slid < 4) { reg_b.s0123 = read_imagef(src1, 0*COL_STRIDE + 0 + slid*2 + k*8);
|
||||
reg_b.s4567 = read_imagef(src1, 0*COL_STRIDE + 1 + slid*2 + k*8); }
|
||||
dequantize_block_acc_bcast_8_hi(ts0, as_ushort8(ql_hi), as_uchar8(qh_hi), reg_d, reg_s, reg_b);
|
||||
dequantize_block_acc_bcast_8_lo(ts0, as_ushort8(ql_lo), as_uchar8(qh_lo), reg_d, reg_s, reg_b); }
|
||||
{ if (slid < 4) { reg_b.s0123 = read_imagef(src1, 1*COL_STRIDE + 0 + slid*2 + k*8);
|
||||
reg_b.s4567 = read_imagef(src1, 1*COL_STRIDE + 1 + slid*2 + k*8); }
|
||||
dequantize_block_acc_bcast_8_hi(ts1, as_ushort8(ql_hi), as_uchar8(qh_hi), reg_d, reg_s, reg_b);
|
||||
dequantize_block_acc_bcast_8_lo(ts1, as_ushort8(ql_lo), as_uchar8(qh_lo), reg_d, reg_s, reg_b); }
|
||||
{ if (slid < 4) { reg_b.s0123 = read_imagef(src1, 2*COL_STRIDE + 0 + slid*2 + k*8);
|
||||
reg_b.s4567 = read_imagef(src1, 2*COL_STRIDE + 1 + slid*2 + k*8); }
|
||||
dequantize_block_acc_bcast_8_hi(ts2, as_ushort8(ql_hi), as_uchar8(qh_hi), reg_d, reg_s, reg_b);
|
||||
dequantize_block_acc_bcast_8_lo(ts2, as_ushort8(ql_lo), as_uchar8(qh_lo), reg_d, reg_s, reg_b); }
|
||||
#else
|
||||
{ if (slid < 4) { reg_b.s0123 = read_imagef(src1, 0*COL_STRIDE + 0 + slid*2 + k*8);
|
||||
reg_b.s4567 = read_imagef(src1, 0*COL_STRIDE + 1 + slid*2 + k*8); }
|
||||
dequantize_block_acc_bcast_1_hi(ts0, as_ushort8(ql_hi), as_uchar8(qh_hi), reg_d, reg_s, reg_b);
|
||||
dequantize_block_acc_bcast_1_lo(ts0, as_ushort8(ql_lo), as_uchar8(qh_lo), reg_d, reg_s, reg_b); }
|
||||
{ if (slid < 4) { reg_b.s0123 = read_imagef(src1, 1*COL_STRIDE + 0 + slid*2 + k*8);
|
||||
reg_b.s4567 = read_imagef(src1, 1*COL_STRIDE + 1 + slid*2 + k*8); }
|
||||
dequantize_block_acc_bcast_1_hi(ts1, as_ushort8(ql_hi), as_uchar8(qh_hi), reg_d, reg_s, reg_b);
|
||||
dequantize_block_acc_bcast_1_lo(ts1, as_ushort8(ql_lo), as_uchar8(qh_lo), reg_d, reg_s, reg_b); }
|
||||
{ if (slid < 4) { reg_b.s0123 = read_imagef(src1, 2*COL_STRIDE + 0 + slid*2 + k*8);
|
||||
reg_b.s4567 = read_imagef(src1, 2*COL_STRIDE + 1 + slid*2 + k*8); }
|
||||
dequantize_block_acc_bcast_1_hi(ts2, as_ushort8(ql_hi), as_uchar8(qh_hi), reg_d, reg_s, reg_b);
|
||||
dequantize_block_acc_bcast_1_lo(ts2, as_ushort8(ql_lo), as_uchar8(qh_lo), reg_d, reg_s, reg_b); }
|
||||
#endif
|
||||
}
|
||||
|
||||
local float8 reduce_lm[SUBGROUP_SIZE * 3];
|
||||
float8 acc = (float8)(ts0.s0, ts0.s1, ts1.s0, ts1.s1, ts2.s0, ts2.s1, 0.0f, 0.0f);
|
||||
if (grp == 1) { reduce_lm[SUBGROUP_SIZE*0 + slid] = acc; }
|
||||
if (grp == 2) { reduce_lm[SUBGROUP_SIZE*1 + slid] = acc; }
|
||||
if (grp == 3) { reduce_lm[SUBGROUP_SIZE*2 + slid] = acc; }
|
||||
|
||||
barrier(CLK_LOCAL_MEM_FENCE);
|
||||
|
||||
if (grp == 0) {
|
||||
acc += reduce_lm[SUBGROUP_SIZE*0 + slid];
|
||||
acc += reduce_lm[SUBGROUP_SIZE*1 + slid];
|
||||
acc += reduce_lm[SUBGROUP_SIZE*2 + slid];
|
||||
dst = (global float*)((global char*)dst + offsetd);
|
||||
// dst column-major [ne01 rows x 3 cols]: (row, col) at col*ne01 + row
|
||||
vstore2((float2)(acc.s0, acc.s1), 0, &(dst[0*ne01 + gid*2]));
|
||||
vstore2((float2)(acc.s2, acc.s3), 0, &(dst[1*ne01 + gid*2]));
|
||||
vstore2((float2)(acc.s4, acc.s5), 0, &(dst[2*ne01 + gid*2]));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,372 @@
|
||||
// 4-output-per-WI variant of kernel_gemv_noshuffle_q6_K_f32.
|
||||
// Each WI now produces 4 consecutive outputs (output quad). The activation
|
||||
// fetch (reg_b) is shared across all 4 outputs, doubling per-WI ALU per
|
||||
// activation broadcast and halving the WG count vs the 2-output kernel.
|
||||
//
|
||||
// Implementation: each K-block we fetch TWO sets of (scales + ql + qh)
|
||||
// — one for the low pair (rows 0,1 of the quad) and one for the high pair
|
||||
// (rows 2,3) — and invoke the existing 2-output dequant macros twice
|
||||
// against the *same* reg_b. Identical data layout to the 2-output kernel,
|
||||
// so the host only needs to halve the grid and double the gid-to-output
|
||||
// mapping.
|
||||
//
|
||||
// Opt-in via the host dispatch when GGML_OPENCL_Q6K_GEMV_O4=1.
|
||||
|
||||
#pragma OPENCL EXTENSION cl_khr_fp16 : enable
|
||||
#pragma OPENCL EXTENSION cl_khr_subgroups : enable
|
||||
|
||||
#ifdef cl_intel_required_subgroup_size
|
||||
#pragma OPENCL EXTENSION cl_intel_required_subgroup_size : enable
|
||||
#define INTEL_GPU 1
|
||||
#define REQD_SUBGROUP_SIZE_16 __attribute__((intel_reqd_sub_group_size(16)))
|
||||
#define REQD_SUBGROUP_SIZE_32 __attribute__((intel_reqd_sub_group_size(32)))
|
||||
#elif defined(cl_qcom_reqd_sub_group_size)
|
||||
#pragma OPENCL EXTENSION cl_qcom_reqd_sub_group_size : enable
|
||||
#define ADRENO_GPU 1
|
||||
#define REQD_SUBGROUP_SIZE_64 __attribute__((qcom_reqd_sub_group_size("half")))
|
||||
#define REQD_SUBGROUP_SIZE_128 __attribute__((qcom_reqd_sub_group_size("full")))
|
||||
#endif
|
||||
|
||||
#define NSUBGROUPS 4
|
||||
#define SUBGROUP_SIZE 64
|
||||
|
||||
// Macros are identical to the 2-output kernel — they accept `total_sum` as
|
||||
// a parameter so we can call them twice (once per pair) against different
|
||||
// accumulators against the same reg_b.
|
||||
#define dequantize_block_acc_bcast_8_hi(total_sum, bits4, bits2, cs, y) \
|
||||
float8 shared_y; \
|
||||
shared_y = sub_group_broadcast(y, 0); \
|
||||
total_sum.s0 += ((float)(((bits4.s0 & 0x000F) ) | ((bits2.s0 & 0x03) << 4)) - 32.f) * cs.s0 * shared_y.s0; \
|
||||
total_sum.s0 += ((float)(((bits4.s0 & 0x00F0) >> 4) | ((bits2.s0 & 0x0C) << 2)) - 32.f) * cs.s0 * shared_y.s1; \
|
||||
total_sum.s0 += ((float)(((bits4.s0 & 0x0F00) >> 8) | ((bits2.s0 & 0x30) )) - 32.f) * cs.s0 * shared_y.s2; \
|
||||
total_sum.s0 += ((float)(((bits4.s0 & 0xF000) >> 12) | ((bits2.s0 & 0xC0) >> 2)) - 32.f) * cs.s0 * shared_y.s3; \
|
||||
total_sum.s0 += ((float)(((bits4.s2 & 0x000F) ) | ((bits2.s2 & 0x03) << 4)) - 32.f) * cs.s0 * shared_y.s4; \
|
||||
total_sum.s0 += ((float)(((bits4.s2 & 0x00F0) >> 4) | ((bits2.s2 & 0x0C) << 2)) - 32.f) * cs.s0 * shared_y.s5; \
|
||||
total_sum.s0 += ((float)(((bits4.s2 & 0x0F00) >> 8) | ((bits2.s2 & 0x30) )) - 32.f) * cs.s0 * shared_y.s6; \
|
||||
total_sum.s0 += ((float)(((bits4.s2 & 0xF000) >> 12) | ((bits2.s2 & 0xC0) >> 2)) - 32.f) * cs.s0 * shared_y.s7; \
|
||||
total_sum.s1 += ((float)(((bits4.s1 & 0x000F) ) | ((bits2.s1 & 0x03) << 4)) - 32.f) * cs.s2 * shared_y.s0; \
|
||||
total_sum.s1 += ((float)(((bits4.s1 & 0x00F0) >> 4) | ((bits2.s1 & 0x0C) << 2)) - 32.f) * cs.s2 * shared_y.s1; \
|
||||
total_sum.s1 += ((float)(((bits4.s1 & 0x0F00) >> 8) | ((bits2.s1 & 0x30) )) - 32.f) * cs.s2 * shared_y.s2; \
|
||||
total_sum.s1 += ((float)(((bits4.s1 & 0xF000) >> 12) | ((bits2.s1 & 0xC0) >> 2)) - 32.f) * cs.s2 * shared_y.s3; \
|
||||
total_sum.s1 += ((float)(((bits4.s3 & 0x000F) ) | ((bits2.s3 & 0x03) << 4)) - 32.f) * cs.s2 * shared_y.s4; \
|
||||
total_sum.s1 += ((float)(((bits4.s3 & 0x00F0) >> 4) | ((bits2.s3 & 0x0C) << 2)) - 32.f) * cs.s2 * shared_y.s5; \
|
||||
total_sum.s1 += ((float)(((bits4.s3 & 0x0F00) >> 8) | ((bits2.s3 & 0x30) )) - 32.f) * cs.s2 * shared_y.s6; \
|
||||
total_sum.s1 += ((float)(((bits4.s3 & 0xF000) >> 12) | ((bits2.s3 & 0xC0) >> 2)) - 32.f) * cs.s2 * shared_y.s7; \
|
||||
shared_y = sub_group_broadcast(y, 1); \
|
||||
total_sum.s0 += ((float)(((bits4.s4 & 0x000F) ) | ((bits2.s4 & 0x03) << 4)) - 32.f) * cs.s0 * shared_y.s0; \
|
||||
total_sum.s0 += ((float)(((bits4.s4 & 0x00F0) >> 4) | ((bits2.s4 & 0x0C) << 2)) - 32.f) * cs.s0 * shared_y.s1; \
|
||||
total_sum.s0 += ((float)(((bits4.s4 & 0x0F00) >> 8) | ((bits2.s4 & 0x30) )) - 32.f) * cs.s0 * shared_y.s2; \
|
||||
total_sum.s0 += ((float)(((bits4.s4 & 0xF000) >> 12) | ((bits2.s4 & 0xC0) >> 2)) - 32.f) * cs.s0 * shared_y.s3; \
|
||||
total_sum.s0 += ((float)(((bits4.s6 & 0x000F) ) | ((bits2.s6 & 0x03) << 4)) - 32.f) * cs.s0 * shared_y.s4; \
|
||||
total_sum.s0 += ((float)(((bits4.s6 & 0x00F0) >> 4) | ((bits2.s6 & 0x0C) << 2)) - 32.f) * cs.s0 * shared_y.s5; \
|
||||
total_sum.s0 += ((float)(((bits4.s6 & 0x0F00) >> 8) | ((bits2.s6 & 0x30) )) - 32.f) * cs.s0 * shared_y.s6; \
|
||||
total_sum.s0 += ((float)(((bits4.s6 & 0xF000) >> 12) | ((bits2.s6 & 0xC0) >> 2)) - 32.f) * cs.s0 * shared_y.s7; \
|
||||
total_sum.s1 += ((float)(((bits4.s5 & 0x000F) ) | ((bits2.s5 & 0x03) << 4)) - 32.f) * cs.s2 * shared_y.s0; \
|
||||
total_sum.s1 += ((float)(((bits4.s5 & 0x00F0) >> 4) | ((bits2.s5 & 0x0C) << 2)) - 32.f) * cs.s2 * shared_y.s1; \
|
||||
total_sum.s1 += ((float)(((bits4.s5 & 0x0F00) >> 8) | ((bits2.s5 & 0x30) )) - 32.f) * cs.s2 * shared_y.s2; \
|
||||
total_sum.s1 += ((float)(((bits4.s5 & 0xF000) >> 12) | ((bits2.s5 & 0xC0) >> 2)) - 32.f) * cs.s2 * shared_y.s3; \
|
||||
total_sum.s1 += ((float)(((bits4.s7 & 0x000F) ) | ((bits2.s7 & 0x03) << 4)) - 32.f) * cs.s2 * shared_y.s4; \
|
||||
total_sum.s1 += ((float)(((bits4.s7 & 0x00F0) >> 4) | ((bits2.s7 & 0x0C) << 2)) - 32.f) * cs.s2 * shared_y.s5; \
|
||||
total_sum.s1 += ((float)(((bits4.s7 & 0x0F00) >> 8) | ((bits2.s7 & 0x30) )) - 32.f) * cs.s2 * shared_y.s6; \
|
||||
total_sum.s1 += ((float)(((bits4.s7 & 0xF000) >> 12) | ((bits2.s7 & 0xC0) >> 2)) - 32.f) * cs.s2 * shared_y.s7; \
|
||||
|
||||
#define dequantize_block_acc_bcast_8_lo(total_sum, bits4, bits2, cs, y) \
|
||||
shared_y = sub_group_broadcast(y, 2); \
|
||||
total_sum.s0 += ((float)(((bits4.s0 & 0x000F) ) | ((bits2.s0 & 0x03) << 4)) - 32.f) * cs.s1 * shared_y.s0; \
|
||||
total_sum.s0 += ((float)(((bits4.s0 & 0x00F0) >> 4) | ((bits2.s0 & 0x0C) << 2)) - 32.f) * cs.s1 * shared_y.s1; \
|
||||
total_sum.s0 += ((float)(((bits4.s0 & 0x0F00) >> 8) | ((bits2.s0 & 0x30) )) - 32.f) * cs.s1 * shared_y.s2; \
|
||||
total_sum.s0 += ((float)(((bits4.s0 & 0xF000) >> 12) | ((bits2.s0 & 0xC0) >> 2)) - 32.f) * cs.s1 * shared_y.s3; \
|
||||
total_sum.s0 += ((float)(((bits4.s2 & 0x000F) ) | ((bits2.s2 & 0x03) << 4)) - 32.f) * cs.s1 * shared_y.s4; \
|
||||
total_sum.s0 += ((float)(((bits4.s2 & 0x00F0) >> 4) | ((bits2.s2 & 0x0C) << 2)) - 32.f) * cs.s1 * shared_y.s5; \
|
||||
total_sum.s0 += ((float)(((bits4.s2 & 0x0F00) >> 8) | ((bits2.s2 & 0x30) )) - 32.f) * cs.s1 * shared_y.s6; \
|
||||
total_sum.s0 += ((float)(((bits4.s2 & 0xF000) >> 12) | ((bits2.s2 & 0xC0) >> 2)) - 32.f) * cs.s1 * shared_y.s7; \
|
||||
total_sum.s1 += ((float)(((bits4.s1 & 0x000F) ) | ((bits2.s1 & 0x03) << 4)) - 32.f) * cs.s3 * shared_y.s0; \
|
||||
total_sum.s1 += ((float)(((bits4.s1 & 0x00F0) >> 4) | ((bits2.s1 & 0x0C) << 2)) - 32.f) * cs.s3 * shared_y.s1; \
|
||||
total_sum.s1 += ((float)(((bits4.s1 & 0x0F00) >> 8) | ((bits2.s1 & 0x30) )) - 32.f) * cs.s3 * shared_y.s2; \
|
||||
total_sum.s1 += ((float)(((bits4.s1 & 0xF000) >> 12) | ((bits2.s1 & 0xC0) >> 2)) - 32.f) * cs.s3 * shared_y.s3; \
|
||||
total_sum.s1 += ((float)(((bits4.s3 & 0x000F) ) | ((bits2.s3 & 0x03) << 4)) - 32.f) * cs.s3 * shared_y.s4; \
|
||||
total_sum.s1 += ((float)(((bits4.s3 & 0x00F0) >> 4) | ((bits2.s3 & 0x0C) << 2)) - 32.f) * cs.s3 * shared_y.s5; \
|
||||
total_sum.s1 += ((float)(((bits4.s3 & 0x0F00) >> 8) | ((bits2.s3 & 0x30) )) - 32.f) * cs.s3 * shared_y.s6; \
|
||||
total_sum.s1 += ((float)(((bits4.s3 & 0xF000) >> 12) | ((bits2.s3 & 0xC0) >> 2)) - 32.f) * cs.s3 * shared_y.s7; \
|
||||
shared_y = sub_group_broadcast(y, 3); \
|
||||
total_sum.s0 += ((float)(((bits4.s4 & 0x000F) ) | ((bits2.s4 & 0x03) << 4)) - 32.f) * cs.s1 * shared_y.s0; \
|
||||
total_sum.s0 += ((float)(((bits4.s4 & 0x00F0) >> 4) | ((bits2.s4 & 0x0C) << 2)) - 32.f) * cs.s1 * shared_y.s1; \
|
||||
total_sum.s0 += ((float)(((bits4.s4 & 0x0F00) >> 8) | ((bits2.s4 & 0x30) )) - 32.f) * cs.s1 * shared_y.s2; \
|
||||
total_sum.s0 += ((float)(((bits4.s4 & 0xF000) >> 12) | ((bits2.s4 & 0xC0) >> 2)) - 32.f) * cs.s1 * shared_y.s3; \
|
||||
total_sum.s0 += ((float)(((bits4.s6 & 0x000F) ) | ((bits2.s6 & 0x03) << 4)) - 32.f) * cs.s1 * shared_y.s4; \
|
||||
total_sum.s0 += ((float)(((bits4.s6 & 0x00F0) >> 4) | ((bits2.s6 & 0x0C) << 2)) - 32.f) * cs.s1 * shared_y.s5; \
|
||||
total_sum.s0 += ((float)(((bits4.s6 & 0x0F00) >> 8) | ((bits2.s6 & 0x30) )) - 32.f) * cs.s1 * shared_y.s6; \
|
||||
total_sum.s0 += ((float)(((bits4.s6 & 0xF000) >> 12) | ((bits2.s6 & 0xC0) >> 2)) - 32.f) * cs.s1 * shared_y.s7; \
|
||||
total_sum.s1 += ((float)(((bits4.s5 & 0x000F) ) | ((bits2.s5 & 0x03) << 4)) - 32.f) * cs.s3 * shared_y.s0; \
|
||||
total_sum.s1 += ((float)(((bits4.s5 & 0x00F0) >> 4) | ((bits2.s5 & 0x0C) << 2)) - 32.f) * cs.s3 * shared_y.s1; \
|
||||
total_sum.s1 += ((float)(((bits4.s5 & 0x0F00) >> 8) | ((bits2.s5 & 0x30) )) - 32.f) * cs.s3 * shared_y.s2; \
|
||||
total_sum.s1 += ((float)(((bits4.s5 & 0xF000) >> 12) | ((bits2.s5 & 0xC0) >> 2)) - 32.f) * cs.s3 * shared_y.s3; \
|
||||
total_sum.s1 += ((float)(((bits4.s7 & 0x000F) ) | ((bits2.s7 & 0x03) << 4)) - 32.f) * cs.s3 * shared_y.s4; \
|
||||
total_sum.s1 += ((float)(((bits4.s7 & 0x00F0) >> 4) | ((bits2.s7 & 0x0C) << 2)) - 32.f) * cs.s3 * shared_y.s5; \
|
||||
total_sum.s1 += ((float)(((bits4.s7 & 0x0F00) >> 8) | ((bits2.s7 & 0x30) )) - 32.f) * cs.s3 * shared_y.s6; \
|
||||
total_sum.s1 += ((float)(((bits4.s7 & 0xF000) >> 12) | ((bits2.s7 & 0xC0) >> 2)) - 32.f) * cs.s3 * shared_y.s7; \
|
||||
|
||||
#define dequantize_block_acc_bcast_1_hi(total_sum, bits4, bits2, cs, y) \
|
||||
float shared_y; \
|
||||
shared_y = sub_group_broadcast(y.s0, 0); \
|
||||
total_sum.s0 += ((float)(((bits4.s0 & 0x000F) ) | ((bits2.s0 & 0x03) << 4)) - 32.f) * cs.s0 * shared_y; \
|
||||
total_sum.s1 += ((float)(((bits4.s1 & 0x000F) ) | ((bits2.s1 & 0x03) << 4)) - 32.f) * cs.s2 * shared_y; \
|
||||
shared_y = sub_group_broadcast(y.s1, 0); \
|
||||
total_sum.s0 += ((float)(((bits4.s0 & 0x00F0) >> 4) | ((bits2.s0 & 0x0C) << 2)) - 32.f) * cs.s0 * shared_y; \
|
||||
total_sum.s1 += ((float)(((bits4.s1 & 0x00F0) >> 4) | ((bits2.s1 & 0x0C) << 2)) - 32.f) * cs.s2 * shared_y; \
|
||||
shared_y = sub_group_broadcast(y.s2, 0); \
|
||||
total_sum.s0 += ((float)(((bits4.s0 & 0x0F00) >> 8) | ((bits2.s0 & 0x30) )) - 32.f) * cs.s0 * shared_y; \
|
||||
total_sum.s1 += ((float)(((bits4.s1 & 0x0F00) >> 8) | ((bits2.s1 & 0x30) )) - 32.f) * cs.s2 * shared_y; \
|
||||
shared_y = sub_group_broadcast(y.s3, 0); \
|
||||
total_sum.s0 += ((float)(((bits4.s0 & 0xF000) >> 12) | ((bits2.s0 & 0xC0) >> 2)) - 32.f) * cs.s0 * shared_y; \
|
||||
total_sum.s1 += ((float)(((bits4.s1 & 0xF000) >> 12) | ((bits2.s1 & 0xC0) >> 2)) - 32.f) * cs.s2 * shared_y; \
|
||||
shared_y = sub_group_broadcast(y.s4, 0); \
|
||||
total_sum.s0 += ((float)(((bits4.s2 & 0x000F) ) | ((bits2.s2 & 0x03) << 4)) - 32.f) * cs.s0 * shared_y; \
|
||||
total_sum.s1 += ((float)(((bits4.s3 & 0x000F) ) | ((bits2.s3 & 0x03) << 4)) - 32.f) * cs.s2 * shared_y; \
|
||||
shared_y = sub_group_broadcast(y.s5, 0); \
|
||||
total_sum.s0 += ((float)(((bits4.s2 & 0x00F0) >> 4) | ((bits2.s2 & 0x0C) << 2)) - 32.f) * cs.s0 * shared_y; \
|
||||
total_sum.s1 += ((float)(((bits4.s3 & 0x00F0) >> 4) | ((bits2.s3 & 0x0C) << 2)) - 32.f) * cs.s2 * shared_y; \
|
||||
shared_y = sub_group_broadcast(y.s6, 0); \
|
||||
total_sum.s0 += ((float)(((bits4.s2 & 0x0F00) >> 8) | ((bits2.s2 & 0x30) )) - 32.f) * cs.s0 * shared_y; \
|
||||
total_sum.s1 += ((float)(((bits4.s3 & 0x0F00) >> 8) | ((bits2.s3 & 0x30) )) - 32.f) * cs.s2 * shared_y; \
|
||||
shared_y = sub_group_broadcast(y.s7, 0); \
|
||||
total_sum.s0 += ((float)(((bits4.s2 & 0xF000) >> 12) | ((bits2.s2 & 0xC0) >> 2)) - 32.f) * cs.s0 * shared_y; \
|
||||
total_sum.s1 += ((float)(((bits4.s3 & 0xF000) >> 12) | ((bits2.s3 & 0xC0) >> 2)) - 32.f) * cs.s2 * shared_y; \
|
||||
shared_y = sub_group_broadcast(y.s0, 1); \
|
||||
total_sum.s0 += ((float)(((bits4.s4 & 0x000F) ) | ((bits2.s4 & 0x03) << 4)) - 32.f) * cs.s0 * shared_y; \
|
||||
total_sum.s1 += ((float)(((bits4.s5 & 0x000F) ) | ((bits2.s5 & 0x03) << 4)) - 32.f) * cs.s2 * shared_y; \
|
||||
shared_y = sub_group_broadcast(y.s1, 1); \
|
||||
total_sum.s0 += ((float)(((bits4.s4 & 0x00F0) >> 4) | ((bits2.s4 & 0x0C) << 2)) - 32.f) * cs.s0 * shared_y; \
|
||||
total_sum.s1 += ((float)(((bits4.s5 & 0x00F0) >> 4) | ((bits2.s5 & 0x0C) << 2)) - 32.f) * cs.s2 * shared_y; \
|
||||
shared_y = sub_group_broadcast(y.s2, 1); \
|
||||
total_sum.s0 += ((float)(((bits4.s4 & 0x0F00) >> 8) | ((bits2.s4 & 0x30) )) - 32.f) * cs.s0 * shared_y; \
|
||||
total_sum.s1 += ((float)(((bits4.s5 & 0x0F00) >> 8) | ((bits2.s5 & 0x30) )) - 32.f) * cs.s2 * shared_y; \
|
||||
shared_y = sub_group_broadcast(y.s3, 1); \
|
||||
total_sum.s0 += ((float)(((bits4.s4 & 0xF000) >> 12) | ((bits2.s4 & 0xC0) >> 2)) - 32.f) * cs.s0 * shared_y; \
|
||||
total_sum.s1 += ((float)(((bits4.s5 & 0xF000) >> 12) | ((bits2.s5 & 0xC0) >> 2)) - 32.f) * cs.s2 * shared_y; \
|
||||
shared_y = sub_group_broadcast(y.s4, 1); \
|
||||
total_sum.s0 += ((float)(((bits4.s6 & 0x000F) ) | ((bits2.s6 & 0x03) << 4)) - 32.f) * cs.s0 * shared_y; \
|
||||
total_sum.s1 += ((float)(((bits4.s7 & 0x000F) ) | ((bits2.s7 & 0x03) << 4)) - 32.f) * cs.s2 * shared_y; \
|
||||
shared_y = sub_group_broadcast(y.s5, 1); \
|
||||
total_sum.s0 += ((float)(((bits4.s6 & 0x00F0) >> 4) | ((bits2.s6 & 0x0C) << 2)) - 32.f) * cs.s0 * shared_y; \
|
||||
total_sum.s1 += ((float)(((bits4.s7 & 0x00F0) >> 4) | ((bits2.s7 & 0x0C) << 2)) - 32.f) * cs.s2 * shared_y; \
|
||||
shared_y = sub_group_broadcast(y.s6, 1); \
|
||||
total_sum.s0 += ((float)(((bits4.s6 & 0x0F00) >> 8) | ((bits2.s6 & 0x30) )) - 32.f) * cs.s0 * shared_y; \
|
||||
total_sum.s1 += ((float)(((bits4.s7 & 0x0F00) >> 8) | ((bits2.s7 & 0x30) )) - 32.f) * cs.s2 * shared_y; \
|
||||
shared_y = sub_group_broadcast(y.s7, 1); \
|
||||
total_sum.s0 += ((float)(((bits4.s6 & 0xF000) >> 12) | ((bits2.s6 & 0xC0) >> 2)) - 32.f) * cs.s0 * shared_y; \
|
||||
total_sum.s1 += ((float)(((bits4.s7 & 0xF000) >> 12) | ((bits2.s7 & 0xC0) >> 2)) - 32.f) * cs.s2 * shared_y; \
|
||||
|
||||
#define dequantize_block_acc_bcast_1_lo(total_sum, bits4, bits2, cs, y) \
|
||||
shared_y = sub_group_broadcast(y.s0, 2); \
|
||||
total_sum.s0 += ((float)(((bits4.s0 & 0x000F) ) | ((bits2.s0 & 0x03) << 4)) - 32.f) * cs.s1 * shared_y; \
|
||||
total_sum.s1 += ((float)(((bits4.s1 & 0x000F) ) | ((bits2.s1 & 0x03) << 4)) - 32.f) * cs.s3 * shared_y; \
|
||||
shared_y = sub_group_broadcast(y.s1, 2); \
|
||||
total_sum.s0 += ((float)(((bits4.s0 & 0x00F0) >> 4) | ((bits2.s0 & 0x0C) << 2)) - 32.f) * cs.s1 * shared_y; \
|
||||
total_sum.s1 += ((float)(((bits4.s1 & 0x00F0) >> 4) | ((bits2.s1 & 0x0C) << 2)) - 32.f) * cs.s3 * shared_y; \
|
||||
shared_y = sub_group_broadcast(y.s2, 2); \
|
||||
total_sum.s0 += ((float)(((bits4.s0 & 0x0F00) >> 8) | ((bits2.s0 & 0x30) )) - 32.f) * cs.s1 * shared_y; \
|
||||
total_sum.s1 += ((float)(((bits4.s1 & 0x0F00) >> 8) | ((bits2.s1 & 0x30) )) - 32.f) * cs.s3 * shared_y; \
|
||||
shared_y = sub_group_broadcast(y.s3, 2); \
|
||||
total_sum.s0 += ((float)(((bits4.s0 & 0xF000) >> 12) | ((bits2.s0 & 0xC0) >> 2)) - 32.f) * cs.s1 * shared_y; \
|
||||
total_sum.s1 += ((float)(((bits4.s1 & 0xF000) >> 12) | ((bits2.s1 & 0xC0) >> 2)) - 32.f) * cs.s3 * shared_y; \
|
||||
shared_y = sub_group_broadcast(y.s4, 2); \
|
||||
total_sum.s0 += ((float)(((bits4.s2 & 0x000F) ) | ((bits2.s2 & 0x03) << 4)) - 32.f) * cs.s1 * shared_y; \
|
||||
total_sum.s1 += ((float)(((bits4.s3 & 0x000F) ) | ((bits2.s3 & 0x03) << 4)) - 32.f) * cs.s3 * shared_y; \
|
||||
shared_y = sub_group_broadcast(y.s5, 2); \
|
||||
total_sum.s0 += ((float)(((bits4.s2 & 0x00F0) >> 4) | ((bits2.s2 & 0x0C) << 2)) - 32.f) * cs.s1 * shared_y; \
|
||||
total_sum.s1 += ((float)(((bits4.s3 & 0x00F0) >> 4) | ((bits2.s3 & 0x0C) << 2)) - 32.f) * cs.s3 * shared_y; \
|
||||
shared_y = sub_group_broadcast(y.s6, 2); \
|
||||
total_sum.s0 += ((float)(((bits4.s2 & 0x0F00) >> 8) | ((bits2.s2 & 0x30) )) - 32.f) * cs.s1 * shared_y; \
|
||||
total_sum.s1 += ((float)(((bits4.s3 & 0x0F00) >> 8) | ((bits2.s3 & 0x30) )) - 32.f) * cs.s3 * shared_y; \
|
||||
shared_y = sub_group_broadcast(y.s7, 2); \
|
||||
total_sum.s0 += ((float)(((bits4.s2 & 0xF000) >> 12) | ((bits2.s2 & 0xC0) >> 2)) - 32.f) * cs.s1 * shared_y; \
|
||||
total_sum.s1 += ((float)(((bits4.s3 & 0xF000) >> 12) | ((bits2.s3 & 0xC0) >> 2)) - 32.f) * cs.s3 * shared_y; \
|
||||
shared_y = sub_group_broadcast(y.s0, 3); \
|
||||
total_sum.s0 += ((float)(((bits4.s4 & 0x000F) ) | ((bits2.s4 & 0x03) << 4)) - 32.f) * cs.s1 * shared_y; \
|
||||
total_sum.s1 += ((float)(((bits4.s5 & 0x000F) ) | ((bits2.s5 & 0x03) << 4)) - 32.f) * cs.s3 * shared_y; \
|
||||
shared_y = sub_group_broadcast(y.s1, 3); \
|
||||
total_sum.s0 += ((float)(((bits4.s4 & 0x00F0) >> 4) | ((bits2.s4 & 0x0C) << 2)) - 32.f) * cs.s1 * shared_y; \
|
||||
total_sum.s1 += ((float)(((bits4.s5 & 0x00F0) >> 4) | ((bits2.s5 & 0x0C) << 2)) - 32.f) * cs.s3 * shared_y; \
|
||||
shared_y = sub_group_broadcast(y.s2, 3); \
|
||||
total_sum.s0 += ((float)(((bits4.s4 & 0x0F00) >> 8) | ((bits2.s4 & 0x30) )) - 32.f) * cs.s1 * shared_y; \
|
||||
total_sum.s1 += ((float)(((bits4.s5 & 0x0F00) >> 8) | ((bits2.s5 & 0x30) )) - 32.f) * cs.s3 * shared_y; \
|
||||
shared_y = sub_group_broadcast(y.s3, 3); \
|
||||
total_sum.s0 += ((float)(((bits4.s4 & 0xF000) >> 12) | ((bits2.s4 & 0xC0) >> 2)) - 32.f) * cs.s1 * shared_y; \
|
||||
total_sum.s1 += ((float)(((bits4.s5 & 0xF000) >> 12) | ((bits2.s5 & 0xC0) >> 2)) - 32.f) * cs.s3 * shared_y; \
|
||||
shared_y = sub_group_broadcast(y.s4, 3); \
|
||||
total_sum.s0 += ((float)(((bits4.s6 & 0x000F) ) | ((bits2.s6 & 0x03) << 4)) - 32.f) * cs.s1 * shared_y; \
|
||||
total_sum.s1 += ((float)(((bits4.s7 & 0x000F) ) | ((bits2.s7 & 0x03) << 4)) - 32.f) * cs.s3 * shared_y; \
|
||||
shared_y = sub_group_broadcast(y.s5, 3); \
|
||||
total_sum.s0 += ((float)(((bits4.s6 & 0x00F0) >> 4) | ((bits2.s6 & 0x0C) << 2)) - 32.f) * cs.s1 * shared_y; \
|
||||
total_sum.s1 += ((float)(((bits4.s7 & 0x00F0) >> 4) | ((bits2.s7 & 0x0C) << 2)) - 32.f) * cs.s3 * shared_y; \
|
||||
shared_y = sub_group_broadcast(y.s6, 3); \
|
||||
total_sum.s0 += ((float)(((bits4.s6 & 0x0F00) >> 8) | ((bits2.s6 & 0x30) )) - 32.f) * cs.s1 * shared_y; \
|
||||
total_sum.s1 += ((float)(((bits4.s7 & 0x0F00) >> 8) | ((bits2.s7 & 0x30) )) - 32.f) * cs.s3 * shared_y; \
|
||||
shared_y = sub_group_broadcast(y.s7, 3); \
|
||||
total_sum.s0 += ((float)(((bits4.s6 & 0xF000) >> 12) | ((bits2.s6 & 0xC0) >> 2)) - 32.f) * cs.s1 * shared_y; \
|
||||
total_sum.s1 += ((float)(((bits4.s7 & 0xF000) >> 12) | ((bits2.s7 & 0xC0) >> 2)) - 32.f) * cs.s3 * shared_y; \
|
||||
|
||||
#if defined(ADRENO_GPU)
|
||||
REQD_SUBGROUP_SIZE_64
|
||||
#endif
|
||||
// Q6K_O4_GLOBAL: read the (read-once-per-token, no-reuse) lm_head/embed weights
|
||||
// from __global coalesced instead of image1d_buffer. The texture cache caps the
|
||||
// streaming (no-reuse) lm_head read bandwidth; global coalesced reaches the
|
||||
// higher rate the rest of the model gets. src1 (activation) stays an image (it IS reused via
|
||||
// the cross-subgroup broadcast).
|
||||
#ifdef Q6K_O4_GLOBAL
|
||||
#define Q6K_O4_NAME kernel_gemv_noshuffle_q6_K_f32_o4_global
|
||||
#define QL_ARG __global uint * src0_ql
|
||||
#define QH_ARG __global half * src0_qh
|
||||
#define RD_QL(b,i) (b[i])
|
||||
#define RD_QH(b,i) as_ushort(b[i])
|
||||
#else
|
||||
#define Q6K_O4_NAME kernel_gemv_noshuffle_q6_K_f32_o4
|
||||
#define QL_ARG read_only image1d_buffer_t src0_ql
|
||||
#define QH_ARG read_only image1d_buffer_t src0_qh
|
||||
#define RD_QL(b,i) (read_imageui(b,i).x)
|
||||
#define RD_QH(b,i) as_ushort(read_imageh(b,i).x)
|
||||
#endif
|
||||
kernel void Q6K_O4_NAME(
|
||||
QL_ARG,
|
||||
QH_ARG,
|
||||
global half2 * src0_s,
|
||||
global half2 * src0_d,
|
||||
read_only image1d_buffer_t src1,
|
||||
global float * dst,
|
||||
ulong offsetd,
|
||||
int ne00,
|
||||
int ne01
|
||||
) {
|
||||
int grp = get_local_id(1);
|
||||
int gid = get_global_id(0); // 4-output-quad index
|
||||
ushort slid = get_sub_group_local_id();
|
||||
|
||||
// Map quad index to the two pair-indices the existing 2-output access
|
||||
// pattern uses (consecutive output pairs along ne01). NB: the two pairs are
|
||||
// kept ADJACENT (gid*2, gid*2+1) on purpose -- a "stride-1" split (pairs
|
||||
// ne01/4 apart) is slower because two distant cache-line streams have worse
|
||||
// locality than the adjacent pair whose reads interleave into the same lines
|
||||
// each iteration.
|
||||
int gid_a = gid * 2;
|
||||
int gid_b = gid * 2 + 1;
|
||||
|
||||
int nb = ne00 / 32;
|
||||
|
||||
uint4 reg_a_l_a, reg_a_l_b;
|
||||
ushort4 reg_a_h_a, reg_a_h_b;
|
||||
half2 reg_d_a, reg_d_b;
|
||||
char4 reg_s_a, reg_s_b;
|
||||
float8 reg_b;
|
||||
|
||||
float2 total_sum_a = 0.0f;
|
||||
float2 total_sum_b = 0.0f;
|
||||
|
||||
int line_stride_a = ne01 / 2;
|
||||
int block_stride_a = NSUBGROUPS * ne01;
|
||||
|
||||
for (int k = grp; k < nb; k += NSUBGROUPS) {
|
||||
reg_d_a = src0_d[gid_a + k/8 * line_stride_a];
|
||||
reg_d_b = src0_d[gid_b + k/8 * line_stride_a];
|
||||
reg_s_a = as_char4(src0_s[gid_a + k * line_stride_a]);
|
||||
reg_s_b = as_char4(src0_s[gid_b + k * line_stride_a]);
|
||||
// Precompute the loop-invariant combined scale (sub-block scale * super-block d)
|
||||
// once per pair instead of re-multiplying it for every one of the 256 elements.
|
||||
float4 cs_a = (float4)((float)reg_s_a.s0*(float)reg_d_a.s0, (float)reg_s_a.s1*(float)reg_d_a.s0,
|
||||
(float)reg_s_a.s2*(float)reg_d_a.s1, (float)reg_s_a.s3*(float)reg_d_a.s1);
|
||||
float4 cs_b = (float4)((float)reg_s_b.s0*(float)reg_d_b.s0, (float)reg_s_b.s1*(float)reg_d_b.s0,
|
||||
(float)reg_s_b.s2*(float)reg_d_b.s1, (float)reg_s_b.s3*(float)reg_d_b.s1);
|
||||
|
||||
if (slid < 4) {
|
||||
reg_b.s0123 = read_imagef(src1, 0 + slid*2 + k*8);
|
||||
reg_b.s4567 = read_imagef(src1, 1 + slid*2 + k*8);
|
||||
}
|
||||
|
||||
// Pair a (output rows gid_a*2, gid_a*2+1): read hi+lo then dequant
|
||||
// both in one block so the `_lo` macro can see the `shared_y` that
|
||||
// `_hi` declared. Pair b follows in its own block — fresh shared_y.
|
||||
{
|
||||
reg_a_l_a.s0 = RD_QL(src0_ql, gid_a + k*block_stride_a + line_stride_a*0);
|
||||
reg_a_l_a.s1 = RD_QL(src0_ql, gid_a + k*block_stride_a + line_stride_a*1);
|
||||
reg_a_l_a.s2 = RD_QL(src0_ql, gid_a + k*block_stride_a + line_stride_a*2);
|
||||
reg_a_l_a.s3 = RD_QL(src0_ql, gid_a + k*block_stride_a + line_stride_a*3);
|
||||
reg_a_h_a.s0 = RD_QH(src0_qh, gid_a + k*block_stride_a + line_stride_a*0);
|
||||
reg_a_h_a.s1 = RD_QH(src0_qh, gid_a + k*block_stride_a + line_stride_a*1);
|
||||
reg_a_h_a.s2 = RD_QH(src0_qh, gid_a + k*block_stride_a + line_stride_a*2);
|
||||
reg_a_h_a.s3 = RD_QH(src0_qh, gid_a + k*block_stride_a + line_stride_a*3);
|
||||
#ifdef VECTOR_SUB_GROUP_BROADCAT
|
||||
dequantize_block_acc_bcast_8_hi(total_sum_a, as_ushort8(reg_a_l_a), as_uchar8(reg_a_h_a), cs_a, reg_b);
|
||||
#else
|
||||
dequantize_block_acc_bcast_1_hi(total_sum_a, as_ushort8(reg_a_l_a), as_uchar8(reg_a_h_a), cs_a, reg_b);
|
||||
#endif
|
||||
|
||||
reg_a_l_a.s0 = RD_QL(src0_ql, gid_a + k*block_stride_a + line_stride_a*4);
|
||||
reg_a_l_a.s1 = RD_QL(src0_ql, gid_a + k*block_stride_a + line_stride_a*5);
|
||||
reg_a_l_a.s2 = RD_QL(src0_ql, gid_a + k*block_stride_a + line_stride_a*6);
|
||||
reg_a_l_a.s3 = RD_QL(src0_ql, gid_a + k*block_stride_a + line_stride_a*7);
|
||||
reg_a_h_a.s0 = RD_QH(src0_qh, gid_a + k*block_stride_a + line_stride_a*4);
|
||||
reg_a_h_a.s1 = RD_QH(src0_qh, gid_a + k*block_stride_a + line_stride_a*5);
|
||||
reg_a_h_a.s2 = RD_QH(src0_qh, gid_a + k*block_stride_a + line_stride_a*6);
|
||||
reg_a_h_a.s3 = RD_QH(src0_qh, gid_a + k*block_stride_a + line_stride_a*7);
|
||||
#ifdef VECTOR_SUB_GROUP_BROADCAT
|
||||
dequantize_block_acc_bcast_8_lo(total_sum_a, as_ushort8(reg_a_l_a), as_uchar8(reg_a_h_a), cs_a, reg_b);
|
||||
#else
|
||||
dequantize_block_acc_bcast_1_lo(total_sum_a, as_ushort8(reg_a_l_a), as_uchar8(reg_a_h_a), cs_a, reg_b);
|
||||
#endif
|
||||
}
|
||||
|
||||
{
|
||||
reg_a_l_b.s0 = RD_QL(src0_ql, gid_b + k*block_stride_a + line_stride_a*0);
|
||||
reg_a_l_b.s1 = RD_QL(src0_ql, gid_b + k*block_stride_a + line_stride_a*1);
|
||||
reg_a_l_b.s2 = RD_QL(src0_ql, gid_b + k*block_stride_a + line_stride_a*2);
|
||||
reg_a_l_b.s3 = RD_QL(src0_ql, gid_b + k*block_stride_a + line_stride_a*3);
|
||||
reg_a_h_b.s0 = RD_QH(src0_qh, gid_b + k*block_stride_a + line_stride_a*0);
|
||||
reg_a_h_b.s1 = RD_QH(src0_qh, gid_b + k*block_stride_a + line_stride_a*1);
|
||||
reg_a_h_b.s2 = RD_QH(src0_qh, gid_b + k*block_stride_a + line_stride_a*2);
|
||||
reg_a_h_b.s3 = RD_QH(src0_qh, gid_b + k*block_stride_a + line_stride_a*3);
|
||||
#ifdef VECTOR_SUB_GROUP_BROADCAT
|
||||
dequantize_block_acc_bcast_8_hi(total_sum_b, as_ushort8(reg_a_l_b), as_uchar8(reg_a_h_b), cs_b, reg_b);
|
||||
#else
|
||||
dequantize_block_acc_bcast_1_hi(total_sum_b, as_ushort8(reg_a_l_b), as_uchar8(reg_a_h_b), cs_b, reg_b);
|
||||
#endif
|
||||
|
||||
reg_a_l_b.s0 = RD_QL(src0_ql, gid_b + k*block_stride_a + line_stride_a*4);
|
||||
reg_a_l_b.s1 = RD_QL(src0_ql, gid_b + k*block_stride_a + line_stride_a*5);
|
||||
reg_a_l_b.s2 = RD_QL(src0_ql, gid_b + k*block_stride_a + line_stride_a*6);
|
||||
reg_a_l_b.s3 = RD_QL(src0_ql, gid_b + k*block_stride_a + line_stride_a*7);
|
||||
reg_a_h_b.s0 = RD_QH(src0_qh, gid_b + k*block_stride_a + line_stride_a*4);
|
||||
reg_a_h_b.s1 = RD_QH(src0_qh, gid_b + k*block_stride_a + line_stride_a*5);
|
||||
reg_a_h_b.s2 = RD_QH(src0_qh, gid_b + k*block_stride_a + line_stride_a*6);
|
||||
reg_a_h_b.s3 = RD_QH(src0_qh, gid_b + k*block_stride_a + line_stride_a*7);
|
||||
#ifdef VECTOR_SUB_GROUP_BROADCAT
|
||||
dequantize_block_acc_bcast_8_lo(total_sum_b, as_ushort8(reg_a_l_b), as_uchar8(reg_a_h_b), cs_b, reg_b);
|
||||
#else
|
||||
dequantize_block_acc_bcast_1_lo(total_sum_b, as_ushort8(reg_a_l_b), as_uchar8(reg_a_h_b), cs_b, reg_b);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
// Cross-subgroup reduce. Same shape as the 2-output kernel but with the
|
||||
// pair-a and pair-b accumulators concatenated into a single float4.
|
||||
local float4 reduce_lm[SUBGROUP_SIZE * 3];
|
||||
float4 acc = (float4)(total_sum_a.s0, total_sum_a.s1, total_sum_b.s0, total_sum_b.s1);
|
||||
if (grp == 1) { reduce_lm[SUBGROUP_SIZE*0 + slid] = acc; }
|
||||
if (grp == 2) { reduce_lm[SUBGROUP_SIZE*1 + slid] = acc; }
|
||||
if (grp == 3) { reduce_lm[SUBGROUP_SIZE*2 + slid] = acc; }
|
||||
|
||||
barrier(CLK_LOCAL_MEM_FENCE);
|
||||
|
||||
if (grp == 0) {
|
||||
acc += reduce_lm[SUBGROUP_SIZE*0 + slid];
|
||||
acc += reduce_lm[SUBGROUP_SIZE*1 + slid];
|
||||
acc += reduce_lm[SUBGROUP_SIZE*2 + slid];
|
||||
dst = (global float*)((global char*)dst + offsetd);
|
||||
// The dispatch rounds ne01/4 up to the subgroup width, so the tail
|
||||
// quads past the last row must not store (they wrote 128 rows past
|
||||
// dst on every ne01 % 256 == 128 vocab, e.g. 151936).
|
||||
if (gid * 4 + 3 < (uint)ne01) {
|
||||
vstore4(acc, 0, &(dst[gid * 4]));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,196 @@
|
||||
// Tiled-wide q6_K GEMV for the long-vocab lm_head/embed (decode path).
|
||||
//
|
||||
// Pairs with kernel_convert_block_q6_k_tiled_ns (cvt.cl): the weights are laid
|
||||
// out CANONICALLY (6-bit code in element order e in [0,256)) and TILED by 64
|
||||
// output rows so the 64-thread lane group coalesces every weight load. Both the
|
||||
// pack (convert) and the unpack (here) are owned by us — correct by construction
|
||||
// against the reference ggml q6_K dequant, no bit-interleave reverse-engineering.
|
||||
//
|
||||
// One work-item produces one output row. A work-group is {64 lanes, 4 subgroups}:
|
||||
// the 64 lanes cover the 64 rows of one tile (coalesced reads), the 4 subgroups
|
||||
// split the K-blocks and reduce through __local at the end.
|
||||
//
|
||||
// Weights are read from __global (coalesced) rather than image1d_buffer: the
|
||||
// lm_head is read once per token with no reuse, and the Adreno texture cache
|
||||
// caps such a streaming read well below the coalesced-global rate
|
||||
// (see opencl_q6k_gemv_o4_shipped / x2-90 roofline notes).
|
||||
|
||||
#pragma OPENCL EXTENSION cl_khr_fp16 : enable
|
||||
|
||||
#ifdef cl_qcom_reqd_sub_group_size
|
||||
#pragma OPENCL EXTENSION cl_qcom_reqd_sub_group_size : enable
|
||||
#define ADRENO_GPU 1
|
||||
#define REQD_SUBGROUP_SIZE_64 __attribute__((qcom_reqd_sub_group_size("half")))
|
||||
#endif
|
||||
|
||||
#define NSUBGROUPS 4
|
||||
#define TILE_ROWS 64
|
||||
|
||||
#if defined(ADRENO_GPU)
|
||||
REQD_SUBGROUP_SIZE_64
|
||||
#endif
|
||||
kernel void kernel_gemv_noshuffle_q6_K_f32_tiled(
|
||||
__global uint4 * src0_ql, // tiled: 8 uint4 granules / superblock
|
||||
__global uint4 * src0_qh, // tiled: 4 uint4 granules / superblock
|
||||
__global char * src0_s, // tiled: 16 chars / superblock
|
||||
__global half * src0_d, // tiled: 1 half / superblock
|
||||
read_only image1d_buffer_t src1, // activation (RGBA f32)
|
||||
global float * dst,
|
||||
ulong offsetd,
|
||||
int ne00,
|
||||
int ne01
|
||||
) {
|
||||
int grp = get_local_id(1); // subgroup index 0..3 (splits K)
|
||||
int row = get_global_id(0); // output row along ne01
|
||||
int rt = row / TILE_ROWS;
|
||||
int rit = row % TILE_ROWS;
|
||||
|
||||
int nb = ne00 / 256; // superblocks per row
|
||||
|
||||
float acc = 0.0f;
|
||||
|
||||
for (int sb = grp; sb < nb; sb += NSUBGROUPS) {
|
||||
int tile_blk = rt * nb + sb; // ne02 == 1 for lm_head/embed
|
||||
|
||||
// d + 16 scales for this (row, superblock)
|
||||
float dval = (float)src0_d[tile_blk * TILE_ROWS + rit];
|
||||
__global char * sc = src0_s + (tile_blk * TILE_ROWS + rit) * 16;
|
||||
|
||||
// 32 ql-uints (8 codes/uint) + 16 qh-uints (16 codes/uint)
|
||||
uint ql[32];
|
||||
uint qh[16];
|
||||
#pragma unroll
|
||||
for (int g = 0; g < 8; ++g) {
|
||||
uint4 v = src0_ql[(tile_blk * 8 + g) * TILE_ROWS + rit];
|
||||
ql[g*4+0] = v.x; ql[g*4+1] = v.y; ql[g*4+2] = v.z; ql[g*4+3] = v.w;
|
||||
}
|
||||
#pragma unroll
|
||||
for (int g = 0; g < 4; ++g) {
|
||||
uint4 v = src0_qh[(tile_blk * 4 + g) * TILE_ROWS + rit];
|
||||
qh[g*4+0] = v.x; qh[g*4+1] = v.y; qh[g*4+2] = v.z; qh[g*4+3] = v.w;
|
||||
}
|
||||
|
||||
// dequant 256 codes in canonical e-order, MAC with activation.
|
||||
int act_base = sb * 64; // activation float4 pixel base (256/4)
|
||||
#pragma unroll
|
||||
for (int e4 = 0; e4 < 64; ++e4) {
|
||||
float4 a = read_imagef(src1, act_base + e4);
|
||||
#pragma unroll
|
||||
for (int t = 0; t < 4; ++t) {
|
||||
int e = e4 * 4 + t;
|
||||
uint low4 = (ql[e >> 3] >> ((e & 7) * 4)) & 0xF;
|
||||
uint hi2 = (qh[e >> 4] >> ((e & 15) * 2)) & 0x3;
|
||||
int code = (int)(low4 | (hi2 << 4)) - 32;
|
||||
int sidx = ((e >> 7) << 3) + (((e >> 5) & 3) << 1) + ((e >> 4) & 1);
|
||||
float scale = (float)sc[sidx] * dval;
|
||||
float av = (t == 0) ? a.x : (t == 1) ? a.y : (t == 2) ? a.z : a.w;
|
||||
acc += (float)code * scale * av;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// reduce across the NSUBGROUPS subgroups (same rit, different K-subset)
|
||||
local float reduce_lm[NSUBGROUPS * TILE_ROWS];
|
||||
reduce_lm[grp * TILE_ROWS + rit] = acc;
|
||||
barrier(CLK_LOCAL_MEM_FENCE);
|
||||
|
||||
if (grp == 0) {
|
||||
float total = reduce_lm[0 * TILE_ROWS + rit]
|
||||
+ reduce_lm[1 * TILE_ROWS + rit]
|
||||
+ reduce_lm[2 * TILE_ROWS + rit]
|
||||
+ reduce_lm[3 * TILE_ROWS + rit];
|
||||
dst = (global float*)((global char*)dst + offsetd);
|
||||
dst[row] = total;
|
||||
}
|
||||
}
|
||||
|
||||
// Multi-column (N=3) variant of the tiled q6_K decode GEMV, for the speculative/
|
||||
// MTP VERIFY lm_head/embed (ne1=3 = 2 drafts + 1 bonus). Identical tiled weight
|
||||
// layout + unpack as the ne1=1 kernel above; each WI computes 3 output columns,
|
||||
// streaming the (large) lm_head weight ONCE per superblock and reusing it across
|
||||
// the 3 verify activation columns (dequant once per code, MAC into 3 accs). This
|
||||
// is the lm_head analogue of the per-layer mc3 GEMV; the multiply order matches
|
||||
// the ne1=1 kernel, so each column is byte-identical to a standalone tiled GEMV.
|
||||
#if defined(ADRENO_GPU)
|
||||
REQD_SUBGROUP_SIZE_64
|
||||
#endif
|
||||
kernel void kernel_gemv_noshuffle_q6_K_f32_tiled_mc3(
|
||||
__global uint4 * src0_ql,
|
||||
__global uint4 * src0_qh,
|
||||
__global char * src0_s,
|
||||
__global half * src0_d,
|
||||
read_only image1d_buffer_t src1,
|
||||
global float * dst,
|
||||
ulong offsetd,
|
||||
int ne00,
|
||||
int ne01
|
||||
) {
|
||||
int grp = get_local_id(1);
|
||||
int row = get_global_id(0);
|
||||
int rt = row / TILE_ROWS;
|
||||
int rit = row % TILE_ROWS;
|
||||
|
||||
int nb = ne00 / 256;
|
||||
int col_stride = ne00 / 4; // activation float4 pixels per column
|
||||
|
||||
float acc0 = 0.0f, acc1 = 0.0f, acc2 = 0.0f;
|
||||
|
||||
for (int sb = grp; sb < nb; sb += NSUBGROUPS) {
|
||||
int tile_blk = rt * nb + sb;
|
||||
|
||||
float dval = (float)src0_d[tile_blk * TILE_ROWS + rit];
|
||||
__global char * sc = src0_s + (tile_blk * TILE_ROWS + rit) * 16;
|
||||
|
||||
uint ql[32];
|
||||
uint qh[16];
|
||||
#pragma unroll
|
||||
for (int g = 0; g < 8; ++g) {
|
||||
uint4 v = src0_ql[(tile_blk * 8 + g) * TILE_ROWS + rit];
|
||||
ql[g*4+0] = v.x; ql[g*4+1] = v.y; ql[g*4+2] = v.z; ql[g*4+3] = v.w;
|
||||
}
|
||||
#pragma unroll
|
||||
for (int g = 0; g < 4; ++g) {
|
||||
uint4 v = src0_qh[(tile_blk * 4 + g) * TILE_ROWS + rit];
|
||||
qh[g*4+0] = v.x; qh[g*4+1] = v.y; qh[g*4+2] = v.z; qh[g*4+3] = v.w;
|
||||
}
|
||||
|
||||
int act_base = sb * 64;
|
||||
#pragma unroll
|
||||
for (int e4 = 0; e4 < 64; ++e4) {
|
||||
float4 a0 = read_imagef(src1, 0*col_stride + act_base + e4);
|
||||
float4 a1 = read_imagef(src1, 1*col_stride + act_base + e4);
|
||||
float4 a2 = read_imagef(src1, 2*col_stride + act_base + e4);
|
||||
#pragma unroll
|
||||
for (int t = 0; t < 4; ++t) {
|
||||
int e = e4 * 4 + t;
|
||||
uint low4 = (ql[e >> 3] >> ((e & 7) * 4)) & 0xF;
|
||||
uint hi2 = (qh[e >> 4] >> ((e & 15) * 2)) & 0x3;
|
||||
int code = (int)(low4 | (hi2 << 4)) - 32;
|
||||
int sidx = ((e >> 7) << 3) + (((e >> 5) & 3) << 1) + ((e >> 4) & 1);
|
||||
float w = (float)code * ((float)sc[sidx] * dval); // dequant+scale once
|
||||
float av0 = (t == 0) ? a0.x : (t == 1) ? a0.y : (t == 2) ? a0.z : a0.w;
|
||||
float av1 = (t == 0) ? a1.x : (t == 1) ? a1.y : (t == 2) ? a1.z : a1.w;
|
||||
float av2 = (t == 0) ? a2.x : (t == 1) ? a2.y : (t == 2) ? a2.z : a2.w;
|
||||
acc0 += w * av0;
|
||||
acc1 += w * av1;
|
||||
acc2 += w * av2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
local float4 reduce_lm[NSUBGROUPS * TILE_ROWS];
|
||||
reduce_lm[grp * TILE_ROWS + rit] = (float4)(acc0, acc1, acc2, 0.0f);
|
||||
barrier(CLK_LOCAL_MEM_FENCE);
|
||||
|
||||
if (grp == 0) {
|
||||
float4 total = reduce_lm[0 * TILE_ROWS + rit]
|
||||
+ reduce_lm[1 * TILE_ROWS + rit]
|
||||
+ reduce_lm[2 * TILE_ROWS + rit]
|
||||
+ reduce_lm[3 * TILE_ROWS + rit];
|
||||
dst = (global float*)((global char*)dst + offsetd);
|
||||
// dst column-major [ne01 rows x 3 cols]: (row, col) at col*ne01 + row
|
||||
dst[0*ne01 + row] = total.x;
|
||||
dst[1*ne01 + row] = total.y;
|
||||
dst[2*ne01 + row] = total.z;
|
||||
}
|
||||
}
|
||||
@@ -118,6 +118,87 @@
|
||||
elem = (char)((bits8.s7 & 0xFF000000) >> 24); \
|
||||
total_sums += convert_int(elem) * scale * shared_y; \
|
||||
|
||||
// ============================================================================
|
||||
// Split-K variant for small-M decode GEMVs.
|
||||
// ----------------------------------------------------------------------------
|
||||
// The base kernel below puts one output row per lane and splits K only across
|
||||
// the N_SIMDGROUP subgroups of a single workgroup, so M=512 yields M/64 = 8
|
||||
// workgroups -- half the compute units on a 16-CU X2 sit idle, and the kernel
|
||||
// measures ~48 GB/s against the ~122 GB/s the larger projections reach in the
|
||||
// same graph. Here each (kslice, subgroup) pair reduces a disjoint set of
|
||||
// K-blocks into partial[kslice * M + row]; kernel_gemv_splitk_reduce_f32 (in
|
||||
// gemv_noshuffle_q4_k_f32.cl) sums the slices. Same operand order within a
|
||||
// slice as the base kernel; only the cross-slice grouping differs.
|
||||
//
|
||||
// Placed BEFORE the base kernel deliberately: on A6X no kernel may be defined
|
||||
// after one that uses a subgroup builtin, or it silently miscompiles.
|
||||
// ============================================================================
|
||||
#ifdef ADRENO_GPU
|
||||
REQD_SUBGROUP_SIZE_64
|
||||
#endif
|
||||
__kernel void kernel_gemv_noshuffle_q8_0_f32_splitk(
|
||||
__read_only image1d_buffer_t src0_q, // quantized A (weights)
|
||||
global half * src0_d, // A scales
|
||||
__read_only image1d_buffer_t src1, // B (activations)
|
||||
global float * partial, // [ksplit * M], slice-major
|
||||
int ne00, // K
|
||||
int ne01) // M
|
||||
{
|
||||
uint groupId = get_local_id(1);
|
||||
uint gid = get_global_id(0);
|
||||
ushort slid = get_sub_group_local_id();
|
||||
uint nsg = get_local_size(1);
|
||||
uint ksplit = get_num_groups(1);
|
||||
uint kslice = get_group_id(1);
|
||||
|
||||
uint K = ne00;
|
||||
uint M = ne01;
|
||||
|
||||
uint LINE_STRIDE_A = M;
|
||||
uint BLOCK_STRIDE_A = 8 * M; // physical, independent of the K-split
|
||||
|
||||
__private uint8 regA;
|
||||
__private half regS;
|
||||
__private float8 regB;
|
||||
__private float totalSum = (float)(0.0f);
|
||||
|
||||
#pragma unroll 1
|
||||
for (uint k = kslice * nsg + groupId; k < (K / QK8_0); k += ksplit * nsg) {
|
||||
regS = src0_d[gid + k * LINE_STRIDE_A];
|
||||
if (slid < 4) {
|
||||
regB.s0123 = read_imagef(src1, (slid * 2 + k * 8));
|
||||
regB.s4567 = read_imagef(src1, (1 + slid * 2 + k * 8));
|
||||
}
|
||||
regA.s0 = read_imageui(src0_q, (gid + k * BLOCK_STRIDE_A + LINE_STRIDE_A * 0)).x;
|
||||
regA.s1 = read_imageui(src0_q, (gid + k * BLOCK_STRIDE_A + LINE_STRIDE_A * 1)).x;
|
||||
regA.s2 = read_imageui(src0_q, (gid + k * BLOCK_STRIDE_A + LINE_STRIDE_A * 2)).x;
|
||||
regA.s3 = read_imageui(src0_q, (gid + k * BLOCK_STRIDE_A + LINE_STRIDE_A * 3)).x;
|
||||
regA.s4 = read_imageui(src0_q, (gid + k * BLOCK_STRIDE_A + LINE_STRIDE_A * 4)).x;
|
||||
regA.s5 = read_imageui(src0_q, (gid + k * BLOCK_STRIDE_A + LINE_STRIDE_A * 5)).x;
|
||||
regA.s6 = read_imageui(src0_q, (gid + k * BLOCK_STRIDE_A + LINE_STRIDE_A * 6)).x;
|
||||
regA.s7 = read_imageui(src0_q, (gid + k * BLOCK_STRIDE_A + LINE_STRIDE_A * 7)).x;
|
||||
|
||||
dequantizeBlockAccum_ns_sgbroadcast_1(totalSum, regA, convert_float(regS), regB);
|
||||
}
|
||||
|
||||
// Intra-workgroup reduce across this K-slice's subgroups. Sized for
|
||||
// nsg <= 8; the host never dispatches more.
|
||||
__local float reduceLM[SIMDGROUP_WIDTH * 7];
|
||||
if (groupId > 0) {
|
||||
reduceLM[SIMDGROUP_WIDTH * (groupId - 1) + slid] = totalSum;
|
||||
}
|
||||
barrier(CLK_LOCAL_MEM_FENCE);
|
||||
if (groupId == 0) {
|
||||
for (uint i = 0; i < nsg - 1; ++i) {
|
||||
totalSum += reduceLM[SIMDGROUP_WIDTH * i + slid];
|
||||
}
|
||||
// x-grid is padded to CEIL_DIV(M,wave)*wave; guard the tail rows.
|
||||
if (gid < M) {
|
||||
partial[kslice * M + gid] = totalSum;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef ADRENO_GPU
|
||||
REQD_SUBGROUP_SIZE_64
|
||||
#endif
|
||||
|
||||
@@ -145,3 +145,52 @@ kernel void kernel_mul_mm_f32_f32_l4_lm(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Multi-column f32 GEMV for the small-N (spec/MTP verify) batch. The tiled GEMM
|
||||
// above always computes a full BM x BN = 64 x 64 output tile, so at ne11=3 with a
|
||||
// skinny weight (e.g. GDN ssm_alpha/ssm_beta, M=32) it launches ONE under-occupied
|
||||
// workgroup at ~2.3% tile utilization. This kernel assigns one 64-thread workgroup
|
||||
// per output element (m,n): the 64 threads split the K reduction (float4) and
|
||||
// tree-reduce in __local (no subgroup ops -> portable). ne01*ne11 workgroups.
|
||||
// Weight row is re-read per column (N small -> negligible). Summation order differs
|
||||
// from the tiled GEMM (lane-strided + tree) -> f32-exact-ish, not bit-identical.
|
||||
kernel void kernel_gemv_f32_f32_mc(
|
||||
global float * src0, ulong offset0, // weight: row m at m*stride_a (elements)
|
||||
global float * src1, ulong offset1, // activations: col n at n*stride_b
|
||||
global float * dst, ulong offsetd, // dst [M x N] col-major: (m,n) at n*stride_d+m
|
||||
int ne00, // K
|
||||
int ne01, // M
|
||||
int ne11, // N
|
||||
int stride_a, // weight row stride (elements) = K
|
||||
int stride_b, // activation col stride (elements) = K
|
||||
int stride_d) // dst column stride (elements) = M
|
||||
{
|
||||
src0 = (global float*)((global char*)src0 + offset0);
|
||||
src1 = (global float*)((global char*)src1 + offset1);
|
||||
dst = (global float*)((global char*)dst + offsetd);
|
||||
|
||||
uint lane = get_local_id(0); // 0..63
|
||||
uint out = get_global_id(1); // 0 .. ne01*ne11 - 1
|
||||
uint m = out % (uint)ne01;
|
||||
uint n = out / (uint)ne01;
|
||||
|
||||
global float4 * wrow = (global float4*)(src0 + (ulong)m * (uint)stride_a);
|
||||
global float4 * xcol = (global float4*)(src1 + (ulong)n * (uint)stride_b);
|
||||
uint k4 = (uint)ne00 >> 2;
|
||||
|
||||
float acc = 0.0f;
|
||||
for (uint k = lane; k < k4; k += 64) {
|
||||
float4 w = wrow[k];
|
||||
float4 x = xcol[k];
|
||||
acc += w.s0*x.s0 + w.s1*x.s1 + w.s2*x.s2 + w.s3*x.s3;
|
||||
}
|
||||
|
||||
local float red[64];
|
||||
red[lane] = acc;
|
||||
barrier(CLK_LOCAL_MEM_FENCE);
|
||||
for (uint s = 32; s > 0; s >>= 1) {
|
||||
if (lane < s) red[lane] += red[lane + s];
|
||||
barrier(CLK_LOCAL_MEM_FENCE);
|
||||
}
|
||||
if (lane == 0) dst[(ulong)n * (uint)stride_d + m] = red[0];
|
||||
}
|
||||
|
||||
@@ -0,0 +1,306 @@
|
||||
#pragma OPENCL EXTENSION cl_khr_fp16 : enable
|
||||
|
||||
#ifdef cl_intel_subgroups
|
||||
#pragma OPENCL EXTENSION cl_intel_subgroups : enable
|
||||
#else
|
||||
#pragma OPENCL EXTENSION cl_khr_subgroups : enable
|
||||
#endif
|
||||
|
||||
#ifdef cl_intel_required_subgroup_size
|
||||
#pragma OPENCL EXTENSION cl_intel_required_subgroup_size : enable
|
||||
#define INTEL_GPU 1
|
||||
#define REQD_SUBGROUP_SIZE_16 __attribute__((intel_reqd_sub_group_size(16)))
|
||||
#define REQD_SUBGROUP_SIZE_32 __attribute__((intel_reqd_sub_group_size(32)))
|
||||
#elif defined(cl_qcom_reqd_sub_group_size)
|
||||
#pragma OPENCL EXTENSION cl_qcom_reqd_sub_group_size : enable
|
||||
#define ADRENO_GPU 1
|
||||
#define REQD_SUBGROUP_SIZE_64 __attribute__((qcom_reqd_sub_group_size("half")))
|
||||
#define REQD_SUBGROUP_SIZE_128 __attribute__((qcom_reqd_sub_group_size("full")))
|
||||
#endif
|
||||
|
||||
// Multi-row f16xf32 GEMV for the DECODE path (single token, ne11*ne12 small).
|
||||
// The legacy kernel_mul_mat_f16_f32_1row runs ONE 64-lane subgroup per workgroup =
|
||||
// one output row per WG, which caps memory-level parallelism at roughly half of
|
||||
// LPDDR5x peak. This variant packs MROW subgroups per workgroup, each
|
||||
// computing a distinct output row, so a WG keeps 64*MROW loads in flight. The
|
||||
// activation column y (shared by every output row) is staged into __local ONCE per
|
||||
// WG and reused across the MROW rows, cutting redundant activation reads. Used for
|
||||
// the f16 attention projections (Q/K/V/O) and lm_head, which dominate decode.
|
||||
// Numerically equivalent to _1row (same f16->f32 widening, same float4 partial sums,
|
||||
// same subgroup-reduce order), so byte-identical to the per-op path.
|
||||
|
||||
#define MROW 16
|
||||
|
||||
#ifdef ADRENO_GPU
|
||||
REQD_SUBGROUP_SIZE_64
|
||||
#endif
|
||||
kernel void kernel_mul_mat_f16_f32_mrow(
|
||||
global char * src0,
|
||||
ulong offset0,
|
||||
global char * src1,
|
||||
ulong offset1,
|
||||
global float * dst,
|
||||
ulong offsetd,
|
||||
int ne00,
|
||||
int ne01,
|
||||
int ne02,
|
||||
ulong nb00,
|
||||
ulong nb01,
|
||||
ulong nb02,
|
||||
ulong nb03,
|
||||
int ne10,
|
||||
int ne11,
|
||||
int ne12,
|
||||
ulong nb10,
|
||||
ulong nb11,
|
||||
ulong nb12,
|
||||
ulong nb13,
|
||||
int ne0,
|
||||
int ne1,
|
||||
int r2,
|
||||
int r3,
|
||||
__local float * ysh
|
||||
) {
|
||||
src0 = (global char*)((global char*)src0 + offset0);
|
||||
src1 = (global char*)((global char*)src1 + offset1);
|
||||
dst = (global float*)((global char*)dst + offsetd);
|
||||
|
||||
int r0 = get_group_id(0) * MROW + get_local_id(1); // output row
|
||||
int r1 = get_group_id(1); // token (ne11)
|
||||
int im = get_group_id(2);
|
||||
int lid = get_sub_group_local_id(); // 0..63
|
||||
int nsg = get_local_size(1); // == MROW
|
||||
|
||||
int i12 = im % ne12;
|
||||
int i13 = im / ne12;
|
||||
|
||||
ulong offset_src1 = r1*nb11 + (i12)*nb12 + (i13)*nb13;
|
||||
global float * y = (global float *) (src1 + offset_src1);
|
||||
|
||||
// Cooperatively stage the activation column (ne00 floats) into __local once per
|
||||
// WG and reuse across the MROW rows. Staging is the actual win here: dropping it
|
||||
// (each subgroup re-reading y from global) regresses below the 1-row kernel.
|
||||
for (int i = get_local_id(1)*get_sub_group_size() + lid; i < ne00; i += nsg*get_sub_group_size()) {
|
||||
ysh[i] = y[i];
|
||||
}
|
||||
barrier(CLK_LOCAL_MEM_FENCE);
|
||||
|
||||
if (r0 >= ne01) {
|
||||
return;
|
||||
}
|
||||
|
||||
ulong offset_src0 = r0*nb01 + (i12/r2)*nb02 + (i13/r3)*nb03;
|
||||
global half * x = (global half *) (src0 + offset_src0);
|
||||
|
||||
// The vector path below casts the row pointer to half4, which must be 8-byte aligned.
|
||||
// A row address is r0*nb01 + ..., and a permuted or strided src0 leaves nb01/nb02/nb03
|
||||
// unconstrained -- ne00 % 4 == 0 bounds the element count per row, not the byte stride
|
||||
// between rows. Take the vector path only when this work-item's row is actually
|
||||
// aligned; the scalar loop below has no such requirement.
|
||||
const bool row_aligned = (((ulong) x) & 7) == 0;
|
||||
|
||||
float sumf = 0.0f;
|
||||
if (ne00 < 128 || !row_aligned) {
|
||||
for (int i = lid; i < ne00; i += get_sub_group_size()) {
|
||||
sumf += (float) x[i] * ysh[i];
|
||||
}
|
||||
float all_sum = sub_group_reduce_add(sumf);
|
||||
if (lid == 0) {
|
||||
dst[im*ne1*ne0 + r1*ne0 + r0] = all_sum;
|
||||
}
|
||||
} else {
|
||||
global half4 * x4 = (global half4 *) x;
|
||||
__local float4 * ysh4 = (__local float4 *) ysh;
|
||||
for (int i = lid; i < ne00/4; i += get_sub_group_size()) {
|
||||
float4 yv = ysh4[i];
|
||||
sumf += (float) x4[i].s0 * yv.s0;
|
||||
sumf += (float) x4[i].s1 * yv.s1;
|
||||
sumf += (float) x4[i].s2 * yv.s2;
|
||||
sumf += (float) x4[i].s3 * yv.s3;
|
||||
}
|
||||
float all_sum = sub_group_reduce_add(sumf);
|
||||
if (lid == 0) {
|
||||
for (int i = 4*(ne00/4); i < ne00; ++i) {
|
||||
all_sum += (float) x[i] * ysh[i];
|
||||
}
|
||||
dst[im*ne1*ne0 + r1*ne0 + r0] = all_sum;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Register-blocked variant: each 64-lane subgroup accumulates RPT consecutive
|
||||
// output rows instead of one. The staged activation is reused across all RPT rows,
|
||||
// and each lane keeps RPT independent weight loads in flight per column step ->
|
||||
// more memory-level parallelism on the streaming f16 weight read (the BW limiter),
|
||||
// plus RPT fewer staging barriers per output row. Per-row reduction order is
|
||||
// identical to _mrow, so byte-identical to the per-op path. Dispatch guarantees
|
||||
// ne00 >= 128 and ne00 % 4 == 0, so only the half4 path is needed (no tail).
|
||||
#define MROW_RB_BODY(RPT) \
|
||||
src0 = (global char*)((global char*)src0 + offset0); \
|
||||
src1 = (global char*)((global char*)src1 + offset1); \
|
||||
dst = (global float*)((global char*)dst + offsetd); \
|
||||
int r0b = (get_group_id(0) * get_local_size(1) + get_local_id(1)) * (RPT); \
|
||||
int r1 = get_group_id(1); \
|
||||
int im = get_group_id(2); \
|
||||
int lid = get_sub_group_local_id(); \
|
||||
int nsg = get_local_size(1); \
|
||||
int i12 = im % ne12; \
|
||||
int i13 = im / ne12; \
|
||||
ulong off_y = r1*nb11 + i12*nb12 + i13*nb13; \
|
||||
global float * y = (global float *) (src1 + off_y); \
|
||||
for (int i = get_local_id(1)*get_sub_group_size() + lid; i < ne00; \
|
||||
i += nsg*get_sub_group_size()) { \
|
||||
ysh[i] = y[i]; \
|
||||
} \
|
||||
barrier(CLK_LOCAL_MEM_FENCE); \
|
||||
__local float4 * ysh4 = (__local float4 *) ysh; \
|
||||
global half4 * xr[RPT]; \
|
||||
_Pragma("unroll") \
|
||||
for (int rr = 0; rr < (RPT); ++rr) { \
|
||||
int row = r0b + rr; \
|
||||
if (row > ne01 - 1) row = ne01 - 1; \
|
||||
xr[rr] = (global half4 *) (src0 + (ulong)row*nb01 + (i12/r2)*nb02 + (i13/r3)*nb03); \
|
||||
} \
|
||||
float sumf[RPT]; \
|
||||
_Pragma("unroll") \
|
||||
for (int rr = 0; rr < (RPT); ++rr) sumf[rr] = 0.0f; \
|
||||
for (int i = lid; i < ne00/4; i += get_sub_group_size()) { \
|
||||
float4 yv = ysh4[i]; \
|
||||
_Pragma("unroll") \
|
||||
for (int rr = 0; rr < (RPT); ++rr) { \
|
||||
half4 xv = xr[rr][i]; \
|
||||
sumf[rr] += (float) xv.s0 * yv.s0 + (float) xv.s1 * yv.s1 \
|
||||
+ (float) xv.s2 * yv.s2 + (float) xv.s3 * yv.s3; \
|
||||
} \
|
||||
} \
|
||||
_Pragma("unroll") \
|
||||
for (int rr = 0; rr < (RPT); ++rr) { \
|
||||
float s = sub_group_reduce_add(sumf[rr]); \
|
||||
int row = r0b + rr; \
|
||||
if (lid == 0 && row < ne01) { \
|
||||
dst[im*ne1*ne0 + r1*ne0 + row] = s; \
|
||||
} \
|
||||
}
|
||||
|
||||
// half8 (128-bit) load variant: Adreno's load/store unit issues 128-bit
|
||||
// transactions, so half4 (64-bit) loads may leave the load path half-idle. This
|
||||
// processes 8 weight elements per lane per step via half8. Accumulation groups
|
||||
// elements in 8s rather than 4s, so it is NOT bit-identical to _1row (float add is
|
||||
// non-associative) -- experimental BW probe, gate on ne00 % 8 == 0.
|
||||
#define MROW_H8_BODY(RPT) \
|
||||
src0 = (global char*)((global char*)src0 + offset0); \
|
||||
src1 = (global char*)((global char*)src1 + offset1); \
|
||||
dst = (global float*)((global char*)dst + offsetd); \
|
||||
int r0b = (get_group_id(0) * get_local_size(1) + get_local_id(1)) * (RPT); \
|
||||
int r1 = get_group_id(1); \
|
||||
int im = get_group_id(2); \
|
||||
int lid = get_sub_group_local_id(); \
|
||||
int nsg = get_local_size(1); \
|
||||
int i12 = im % ne12; \
|
||||
int i13 = im / ne12; \
|
||||
ulong off_y = r1*nb11 + i12*nb12 + i13*nb13; \
|
||||
global float * y = (global float *) (src1 + off_y); \
|
||||
for (int i = get_local_id(1)*get_sub_group_size() + lid; i < ne00; \
|
||||
i += nsg*get_sub_group_size()) { \
|
||||
ysh[i] = y[i]; \
|
||||
} \
|
||||
barrier(CLK_LOCAL_MEM_FENCE); \
|
||||
__local float4 * ysh4 = (__local float4 *) ysh; \
|
||||
global half8 * xr[RPT]; \
|
||||
_Pragma("unroll") \
|
||||
for (int rr = 0; rr < (RPT); ++rr) { \
|
||||
int row = r0b + rr; \
|
||||
if (row > ne01 - 1) row = ne01 - 1; \
|
||||
xr[rr] = (global half8 *) (src0 + (ulong)row*nb01 + (i12/r2)*nb02 + (i13/r3)*nb03); \
|
||||
} \
|
||||
float sumf[RPT]; \
|
||||
_Pragma("unroll") \
|
||||
for (int rr = 0; rr < (RPT); ++rr) sumf[rr] = 0.0f; \
|
||||
for (int i = lid; i < ne00/8; i += get_sub_group_size()) { \
|
||||
float4 y0 = ysh4[2*i]; \
|
||||
float4 y1 = ysh4[2*i + 1]; \
|
||||
_Pragma("unroll") \
|
||||
for (int rr = 0; rr < (RPT); ++rr) { \
|
||||
half8 xv = xr[rr][i]; \
|
||||
sumf[rr] += (float) xv.s0 * y0.s0 + (float) xv.s1 * y0.s1 \
|
||||
+ (float) xv.s2 * y0.s2 + (float) xv.s3 * y0.s3 \
|
||||
+ (float) xv.s4 * y1.s0 + (float) xv.s5 * y1.s1 \
|
||||
+ (float) xv.s6 * y1.s2 + (float) xv.s7 * y1.s3; \
|
||||
} \
|
||||
} \
|
||||
_Pragma("unroll") \
|
||||
for (int rr = 0; rr < (RPT); ++rr) { \
|
||||
float s = sub_group_reduce_add(sumf[rr]); \
|
||||
int row = r0b + rr; \
|
||||
if (lid == 0 && row < ne01) { \
|
||||
dst[im*ne1*ne0 + r1*ne0 + row] = s; \
|
||||
} \
|
||||
}
|
||||
|
||||
#ifdef ADRENO_GPU
|
||||
REQD_SUBGROUP_SIZE_64
|
||||
#endif
|
||||
kernel void kernel_mul_mat_f16_f32_mrow_h8(
|
||||
global char * src0, ulong offset0,
|
||||
global char * src1, ulong offset1,
|
||||
global float * dst, ulong offsetd,
|
||||
int ne00, int ne01, int ne02,
|
||||
ulong nb00, ulong nb01, ulong nb02, ulong nb03,
|
||||
int ne10, int ne11, int ne12,
|
||||
ulong nb10, ulong nb11, ulong nb12, ulong nb13,
|
||||
int ne0, int ne1, int r2, int r3,
|
||||
__local float * ysh
|
||||
) {
|
||||
MROW_H8_BODY(1)
|
||||
}
|
||||
|
||||
#ifdef ADRENO_GPU
|
||||
REQD_SUBGROUP_SIZE_64
|
||||
#endif
|
||||
kernel void kernel_mul_mat_f16_f32_mrow_h8r2(
|
||||
global char * src0, ulong offset0,
|
||||
global char * src1, ulong offset1,
|
||||
global float * dst, ulong offsetd,
|
||||
int ne00, int ne01, int ne02,
|
||||
ulong nb00, ulong nb01, ulong nb02, ulong nb03,
|
||||
int ne10, int ne11, int ne12,
|
||||
ulong nb10, ulong nb11, ulong nb12, ulong nb13,
|
||||
int ne0, int ne1, int r2, int r3,
|
||||
__local float * ysh
|
||||
) {
|
||||
MROW_H8_BODY(2)
|
||||
}
|
||||
|
||||
#ifdef ADRENO_GPU
|
||||
REQD_SUBGROUP_SIZE_64
|
||||
#endif
|
||||
kernel void kernel_mul_mat_f16_f32_mrow_r2(
|
||||
global char * src0, ulong offset0,
|
||||
global char * src1, ulong offset1,
|
||||
global float * dst, ulong offsetd,
|
||||
int ne00, int ne01, int ne02,
|
||||
ulong nb00, ulong nb01, ulong nb02, ulong nb03,
|
||||
int ne10, int ne11, int ne12,
|
||||
ulong nb10, ulong nb11, ulong nb12, ulong nb13,
|
||||
int ne0, int ne1, int r2, int r3,
|
||||
__local float * ysh
|
||||
) {
|
||||
MROW_RB_BODY(2)
|
||||
}
|
||||
|
||||
#ifdef ADRENO_GPU
|
||||
REQD_SUBGROUP_SIZE_64
|
||||
#endif
|
||||
kernel void kernel_mul_mat_f16_f32_mrow_r4(
|
||||
global char * src0, ulong offset0,
|
||||
global char * src1, ulong offset1,
|
||||
global float * dst, ulong offsetd,
|
||||
int ne00, int ne01, int ne02,
|
||||
ulong nb00, ulong nb01, ulong nb02, ulong nb03,
|
||||
int ne10, int ne11, int ne12,
|
||||
ulong nb10, ulong nb11, ulong nb12, ulong nb13,
|
||||
int ne0, int ne1, int r2, int r3,
|
||||
__local float * ysh
|
||||
) {
|
||||
MROW_RB_BODY(4)
|
||||
}
|
||||
@@ -188,3 +188,182 @@ kernel void kernel_rms_norm_mul(
|
||||
y[i00] = (x[i00] * scale) * f[i00%(ne10/4)];
|
||||
}
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// rms_norm + mul (norm weight) + add (residual), fused. Mirrors
|
||||
// kernel_rms_norm_mul with an extra residual operand src2: computes
|
||||
// y = (rmsnorm(x) * w) + g
|
||||
// in one dispatch, removing one kernel launch + one global round-trip per
|
||||
// residual block (the dominant per-layer adjacency on Gemma matformers).
|
||||
//------------------------------------------------------------------------------
|
||||
kernel void kernel_rms_norm_mul_add(
|
||||
global char * src0,
|
||||
ulong offset0,
|
||||
global char * src1,
|
||||
ulong offset1,
|
||||
global char * src2,
|
||||
ulong offset2,
|
||||
global char * dst,
|
||||
ulong offsetd,
|
||||
int ne00,
|
||||
int ne01,
|
||||
int ne02,
|
||||
int ne03,
|
||||
ulong nb01,
|
||||
ulong nb02,
|
||||
ulong nb03,
|
||||
int ne10,
|
||||
int ne11,
|
||||
int ne12,
|
||||
int ne13,
|
||||
ulong nb11,
|
||||
ulong nb12,
|
||||
ulong nb13,
|
||||
int ne20,
|
||||
int ne21,
|
||||
int ne22,
|
||||
int ne23,
|
||||
ulong nb21,
|
||||
ulong nb22,
|
||||
ulong nb23,
|
||||
ulong nb1,
|
||||
ulong nb2,
|
||||
ulong nb3,
|
||||
float eps,
|
||||
local float * sum
|
||||
) {
|
||||
src0 = src0 + offset0;
|
||||
src1 = src1 + offset1;
|
||||
src2 = src2 + offset2;
|
||||
dst = dst + offsetd;
|
||||
|
||||
if (get_sub_group_id() == 0) {
|
||||
sum[get_sub_group_local_id()] = 0.0f;
|
||||
}
|
||||
|
||||
int i03 = get_group_id(2);
|
||||
int i02 = get_group_id(1);
|
||||
int i01 = get_group_id(0);
|
||||
|
||||
global float4 * x = (global float4 *) (src0 + i03*nb03 + i02*nb02 + i01*nb01);
|
||||
global float4 * f = (global float4 *) (src1 + (i03%ne13)*nb13 + (i02%ne12)*nb12 + (i01%ne11)*nb11);
|
||||
global float4 * g = (global float4 *) (src2 + (i03%ne23)*nb23 + (i02%ne22)*nb22 + (i01%ne21)*nb21);
|
||||
|
||||
float sumf = 0;
|
||||
|
||||
for (int i00 = get_local_id(0); i00 < ne00/4; i00 += get_local_size(0)) {
|
||||
sumf += dot(x[i00], x[i00]);
|
||||
}
|
||||
sumf = sub_group_reduce_add(sumf);
|
||||
|
||||
barrier(CLK_LOCAL_MEM_FENCE);
|
||||
|
||||
if (get_sub_group_local_id() == 0) {
|
||||
sum[get_sub_group_id()] = sumf;
|
||||
}
|
||||
|
||||
barrier(CLK_LOCAL_MEM_FENCE);
|
||||
|
||||
sumf = sum[get_sub_group_local_id()];
|
||||
sumf = sub_group_reduce_add(sumf);
|
||||
|
||||
float mean = sumf / ne00;
|
||||
float scale = 1.0f/sqrt(mean + eps);
|
||||
|
||||
global float4 * y = (global float4 *) (dst + i03*nb3 + i02*nb2 + i01*nb1);
|
||||
for (int i00 = get_local_id(0); i00 < ne00/4; i00 += get_local_size(0)) {
|
||||
y[i00] = (x[i00] * scale) * f[i00%(ne10/4)] + g[i00%(ne20/4)];
|
||||
}
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// rms_norm + mul(norm weight) + add(residual) + mul(scalar scale), fused.
|
||||
// Computes y = ((rmsnorm(x) * w) + g) * s, where s is a broadcast SCALAR (e.g.
|
||||
// Gemma-4 layer_output_scale). Folds the trailing per-layer l_out scale-mul into
|
||||
// the residual-norm kernel: one extra dispatch + global round-trip saved per
|
||||
// layer. src3 points at the single scale value.
|
||||
//------------------------------------------------------------------------------
|
||||
kernel void kernel_rms_norm_mul_add_scale(
|
||||
global char * src0,
|
||||
ulong offset0,
|
||||
global char * src1,
|
||||
ulong offset1,
|
||||
global char * src2,
|
||||
ulong offset2,
|
||||
global char * src3,
|
||||
ulong offset3,
|
||||
global char * dst,
|
||||
ulong offsetd,
|
||||
int ne00,
|
||||
int ne01,
|
||||
int ne02,
|
||||
int ne03,
|
||||
ulong nb01,
|
||||
ulong nb02,
|
||||
ulong nb03,
|
||||
int ne10,
|
||||
int ne11,
|
||||
int ne12,
|
||||
int ne13,
|
||||
ulong nb11,
|
||||
ulong nb12,
|
||||
ulong nb13,
|
||||
int ne20,
|
||||
int ne21,
|
||||
int ne22,
|
||||
int ne23,
|
||||
ulong nb21,
|
||||
ulong nb22,
|
||||
ulong nb23,
|
||||
ulong nb1,
|
||||
ulong nb2,
|
||||
ulong nb3,
|
||||
float eps,
|
||||
local float * sum
|
||||
) {
|
||||
src0 = src0 + offset0;
|
||||
src1 = src1 + offset1;
|
||||
src2 = src2 + offset2;
|
||||
src3 = src3 + offset3;
|
||||
dst = dst + offsetd;
|
||||
|
||||
const float sc = *((global float *) src3);
|
||||
|
||||
if (get_sub_group_id() == 0) {
|
||||
sum[get_sub_group_local_id()] = 0.0f;
|
||||
}
|
||||
|
||||
int i03 = get_group_id(2);
|
||||
int i02 = get_group_id(1);
|
||||
int i01 = get_group_id(0);
|
||||
|
||||
global float4 * x = (global float4 *) (src0 + i03*nb03 + i02*nb02 + i01*nb01);
|
||||
global float4 * f = (global float4 *) (src1 + (i03%ne13)*nb13 + (i02%ne12)*nb12 + (i01%ne11)*nb11);
|
||||
global float4 * g = (global float4 *) (src2 + (i03%ne23)*nb23 + (i02%ne22)*nb22 + (i01%ne21)*nb21);
|
||||
|
||||
float sumf = 0;
|
||||
|
||||
for (int i00 = get_local_id(0); i00 < ne00/4; i00 += get_local_size(0)) {
|
||||
sumf += dot(x[i00], x[i00]);
|
||||
}
|
||||
sumf = sub_group_reduce_add(sumf);
|
||||
|
||||
barrier(CLK_LOCAL_MEM_FENCE);
|
||||
|
||||
if (get_sub_group_local_id() == 0) {
|
||||
sum[get_sub_group_id()] = sumf;
|
||||
}
|
||||
|
||||
barrier(CLK_LOCAL_MEM_FENCE);
|
||||
|
||||
sumf = sum[get_sub_group_local_id()];
|
||||
sumf = sub_group_reduce_add(sumf);
|
||||
|
||||
float mean = sumf / ne00;
|
||||
float scale = 1.0f/sqrt(mean + eps);
|
||||
|
||||
global float4 * y = (global float4 *) (dst + i03*nb3 + i02*nb2 + i01*nb1);
|
||||
for (int i00 = get_local_id(0); i00 < ne00/4; i00 += get_local_size(0)) {
|
||||
y[i00] = ((x[i00] * scale) * f[i00%(ne10/4)] + g[i00%(ne20/4)]) * sc;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user