makepad/libs/ai/vulkan/kernels/llm.splash

2239 lines
87 KiB
Text

// The LLM graph kernels (makepad-ai-llm's Vulkan executor): one kernel per
// graph op the qwen35 decode and prefill graphs run. Every stride is in
// ELEMENTS of the buffer's type; every tensor is passed by the address of
// its first element. Quantised weights are read as raw u32 words in their
// GGUF block encoding (q4_K/q5_K: 144/176 bytes, q6_K: 210, q8_0: 34 per 32).
// Quantised weight formats as the kernels read them: `value_at(bo, l)`
// decodes value `l` (0..256) of the 256-value unit at byte `bo` (q6_K and
// q8_0 in their repacked layouts, see vulkan_exec.rs `repack`).
let deq = {
w: shader.buffer(u32)
byte: fn(o) {
return (self.w[o >> 2] >> ((o & 3) * 8)) & 255
}
// The 4 bytes at byte `o` (any alignment) as one little-endian word.
word: fn(o) {
let i = o >> 2
let sh = (o & 3) * 8
let lo = self.w[i]
if sh == 0 {
return lo
}
return (lo >> sh) | (self.w[i + 1] << (32 - sh))
}
half: fn(o) {
let word = self.w[o >> 2]
return unpack_half2((word >> ((o & 2) * 8)) & 65535).x
}
scale_k4: fn(bo, si) {
if si < 4 {
return self.byte(bo + 4 + si) & 63
}
return (self.byte(bo + 4 + si + 4) & 15) | ((self.byte(bo + 4 + si - 4) >> 6) << 4)
}
min_k4: fn(bo, si) {
if si < 4 {
return self.byte(bo + 4 + si + 4) & 63
}
return (self.byte(bo + 4 + si + 4) >> 4) | ((self.byte(bo + 4 + si) >> 6) << 4)
}
signed: fn(b) {
if b > 127 {
return float(b) - 256.0
}
return float(b)
}
}
let deq_q6k = deq{
unit_bytes: 224
value_at: fn(bo, l) {
let d = self.half(bo + 208)
let n = l >> 7
let r = l & 127
let group = r >> 5
let lo = r & 31
let ql = self.byte(bo + n * 64 + (group & 1) * 32 + lo)
let qh = self.byte(bo + 128 + n * 32 + lo)
let q = ((ql >> (((group >> 1) & 1) * 4)) & 15) | (((qh >> (group * 2)) & 3) << 4)
let sc = self.signed(self.byte(bo + 192 + n * 8 + lo / 16 + 2 * group))
return d * sc * (float(q) - 32.0)
}
code_word: fn(bo, j, w) {
let n = j >> 2
let grp = j & 3
let ql = (self.word(bo + n * 64 + (grp & 1) * 32 + w * 4) >> ((grp >> 1) * 4)) & u32(252645135)
let qh = (self.word(bo + 128 + n * 32 + w * 4) >> (grp * 2)) & u32(50529027)
return ql | (qh << 4)
}
meta: fn(bo, j) {
let n = j >> 2
let grp = j & 3
let d = self.half(bo + 208)
let sc0 = self.signed(self.byte(bo + 192 + n * 8 + 2 * grp))
let sc1 = self.signed(self.byte(bo + 192 + n * 8 + 1 + 2 * grp))
return vec4(d * sc0, d * sc1, 0.0, 32.0)
}
}
let deq_q5k = deq{
unit_bytes: 176
value_at: fn(bo, l) {
let j64 = l >> 6
let rem = l & 63
let si = 2 * j64 + (rem >> 5)
let q = self.byte(bo + 48 + 32 * j64 + (rem & 31))
var nib = float(q & 15)
if rem >= 32 {
nib = float(q >> 4)
}
let hi = float((self.byte(bo + 16 + (rem & 31)) >> si) & 1) * 16.0
return self.half(bo) * float(self.scale_k4(bo, si)) * (nib + hi) - self.half(bo + 2) * float(self.min_k4(bo, si))
}
// Codes of int8-block j (0..8) of the unit, word w (0..8): 4 values
// as unsigned bytes; meta: (scale words 0-3, scale words 4-7, min
// coefficient, code offset).
code_word: fn(bo, j, w) {
let q = self.word(bo + 48 + (j >> 1) * 32 + w * 4)
let h = self.word(bo + 16 + w * 4)
return ((q >> ((j & 1) * 4)) & u32(252645135)) | (((h >> j) & u32(16843009)) << 4)
}
meta: fn(bo, j) {
let dm = unpack_half2(self.word(bo))
let sc = float(self.scale_k4(bo, j))
let mn = float(self.min_k4(bo, j))
return vec4(dm.x * sc, dm.x * sc, dm.y * mn, 0.0)
}
}
let deq_q4k = deq{
unit_bytes: 144
value_at: fn(bo, l) {
let j64 = l >> 6
let rem = l & 63
let si = 2 * j64 + (rem >> 5)
let q = self.byte(bo + 16 + 32 * j64 + (rem & 31))
var nib = float(q & 15)
if rem >= 32 {
nib = float(q >> 4)
}
return self.half(bo) * float(self.scale_k4(bo, si)) * nib - self.half(bo + 2) * float(self.min_k4(bo, si))
}
code_word: fn(bo, j, w) {
let q = self.word(bo + 16 + (j >> 1) * 32 + w * 4)
return (q >> ((j & 1) * 4)) & u32(252645135)
}
meta: fn(bo, j) {
let dm = unpack_half2(self.word(bo))
let sc = float(self.scale_k4(bo, j))
let mn = float(self.min_k4(bo, j))
return vec4(dm.x * sc, dm.x * sc, dm.y * mn, 0.0)
}
}
let deq_q80 = deq{
unit_bytes: 272
value_at: fn(bo, l) {
let blk = l >> 5
let d = unpack_half2(self.w[(bo >> 2) + (blk >> 1)])
var dv = d.x
if (blk & 1) == 1 {
dv = d.y
}
return dv * self.signed(self.byte(bo + 16 + l))
}
code_word: fn(bo, j, w) {
return self.word(bo + 16 + 32 * j + w * 4) ^ u32(2155905152)
}
meta: fn(bo, j) {
let d = self.half(bo + 2 * j)
return vec4(d, d, 0.0, 128.0)
}
}
// Quantised rows: dst[r, :] = dequant(src[rows[r], :]) — the token
// embedding lookup. One invocation per value.
let rows_main = fn() {
let i = global_id().x
let r = group_id().y
if i < self.ne0 {
let row = u32(self.rows[r])
let bo = row * self.w_row + (i / 256) * self.unit_bytes
self.dst[r * self.dst_row + i] = self.value_at(bo, i % 256)
}
}
// Quantised matrix x matrix for prefill (many activation columns) on
// int8 activations (`quantize_q8`): a 32-row by 32-column tile per
// workgroup, k in steps of one 32-value block. The loaders put the block's
// weight codes (format `code_word`) and per-row scales (`meta`) and the
// columns' biased int8 activations in shared memory; each invocation then
// accumulates 4 rows x 2 columns with packed 4x8-bit dot products.
let gemm_main = fn() {
let t = local_id().x
let tx = t % 16
let ty = t / 16
let row0 = group_id().x * 32
let col0 = group_id().y * 32
var acc0 = vec4(0.0, 0.0, 0.0, 0.0)
var acc1 = vec4(0.0, 0.0, 0.0, 0.0)
let ones = u32(16843009)
for kb in 0..self.k / 32 {
let unit = kb / 8
let j = kb % 8
// Weights: 32 rows x 8 words, two words per invocation.
let lr = t / 4
let lw = (t % 4) * 2
let row = min(row0 + lr, self.n - 1)
let bo = row * self.w_row + unit * self.unit_bytes
self.wq[lr * 8 + lw] = self.code_word(bo, j, lw)
self.wq[lr * 8 + lw + 1] = self.code_word(bo, j, lw + 1)
if (t % 4) == 0 {
self.wm[lr] = self.meta(bo, j)
}
// Activations: 32 columns x 8 words, two per invocation.
let col = min(col0 + lr, self.m - 1)
let xw = (col * self.k + kb * 32) / 4
self.xq_s[lr * 8 + lw] = self.xq[xw + lw]
self.xq_s[lr * 8 + lw + 1] = self.xq[xw + lw + 1]
if (t % 4) == 0 {
self.xd_s[lr] = self.xs[(col * self.k + kb * 32) / 32].x
}
barrier()
for c in 0..2 {
let cc = tx * 2 + c
// sum q over each half of the column's block.
var sq0 = 0
var sq1 = 0
for w in 0..4 {
sq0 += i32(dot4_u8(self.xq_s[cc * 8 + w], ones)) - 512
sq1 += i32(dot4_u8(self.xq_s[cc * 8 + 4 + w], ones)) - 512
}
let xd = self.xd_s[cc]
var part = vec4(0.0, 0.0, 0.0, 0.0)
for r in 0..4 {
let rr = ty * 4 + r
var d0 = 0
var d1 = 0
var c0 = 0
var c1 = 0
for w in 0..4 {
let a0 = self.wq[rr * 8 + w]
let a1 = self.wq[rr * 8 + 4 + w]
d0 += i32(dot4_u8(a0, self.xq_s[cc * 8 + w]))
d1 += i32(dot4_u8(a1, self.xq_s[cc * 8 + 4 + w]))
c0 += i32(dot4_u8(a0, ones))
c1 += i32(dot4_u8(a1, ones))
}
let mt = self.wm[rr]
// sum(code * q) per half, less the code offset, less the min.
let v0 = float(d0 - 128 * c0) - mt.w * float(sq0)
let v1 = float(d1 - 128 * c1) - mt.w * float(sq1)
part[r] = xd * (mt.x * v0 + mt.y * v1 - mt.z * float(sq0 + sq1))
}
if c == 0 {
acc0 += part
} else {
acc1 += part
}
}
barrier()
}
for c in 0..2 {
let col = col0 + tx * 2 + c
var acc = acc0
if c == 1 {
acc = acc1
}
if col < self.m {
for r in 0..4 {
let row = row0 + ty * 4 + r
if row < self.n {
self.dst[col * self.d_col + row] = acc[r]
}
}
}
}
}
// Prefill register tiles. Each invocation owns four rows by four columns;
// named vectors keep the hot loop out of dynamically indexed local arrays.
// Codes are decoded only into workgroup memory, never a second weight buffer.
// Stage two (or four) 32-value blocks per barrier pair. Weight/activation
// sums are computed by the loaders once, then reused by the whole tile.
let gemm_register = deq{
workgroup_size: 256
tile_n: 64
tile_k: 64
format: 5
unit_bytes: 176
w4: shader.buffer(vec4u)
xq4: shader.buffer(vec4u)
xs: shader.buffer(vec2f)
dst: shader.buffer_rw(f32)
k: shader.uniform(u32)
n: shader.uniform(u32)
m: shader.uniform(u32)
w_row: shader.uniform(u32)
d_col: shader.uniform(u32)
code16: fn(bo, j, h) {
let at = bo / 16
let mask = vec4u(u32(252645135))
if self.format == 6 {
let half = j >> 2
let g = j & 3
let lo = self.w4[at + half * 4 + (g & 1) * 2 + h]
let hi = self.w4[at + 8 + half * 2 + h]
return ((lo >> vec4u((g >> 1) * 4)) & mask)
| (((hi >> vec4u(g * 2)) & vec4u(u32(50529027))) << vec4u(4))
}
if self.format == 8 {
return self.w4[at + 1 + j * 2 + h] ^ vec4u(u32(2155905152))
}
var qs = u32(1)
if self.format == 5 { qs = u32(3) }
let lo = self.w4[at + qs + (j >> 1) * 2 + h]
var codes = (lo >> vec4u((j & 1) * 4)) & mask
if self.format == 5 {
let hi = self.w4[at + 1 + h]
codes = codes | (((hi >> vec4u(j)) & vec4u(u32(16843009))) << vec4u(4))
}
return codes
}
dot16: fn(a, b) {
return i32(dot4_u8(a.x, b.x) + dot4_u8(a.y, b.y)
+ dot4_u8(a.z, b.z) + dot4_u8(a.w, b.w))
}
sum16: fn(a) {
return self.dot16(a, vec4u(u32(16843009)))
}
dots: fn(a, b0, b1, b2, b3) {
return vec4i(self.dot16(a, b0), self.dot16(a, b1), self.dot16(a, b2), self.dot16(a, b3))
}
store: fn(r, c, v) {
if r < self.n {
if c < self.m { self.dst[c * self.d_col + r] = v.x }
if c + 1 < self.m { self.dst[(c + 1) * self.d_col + r] = v.y }
if c + 2 < self.m { self.dst[(c + 2) * self.d_col + r] = v.z }
if c + 3 < self.m { self.dst[(c + 3) * self.d_col + r] = v.w }
}
}
}
// Four rows by eight columns per invocation, 128 invocations per 64x64
// tile, staging 16 values of k per step. Q4/Q5/Q8 combine
// a whole 32-value integer dot before applying its scale and minimum.
// Metadata and bias corrections are prepared once per row/column block.
let gemm_wide = gemm_register{
workgroup_size: 128
tile_k: 32
wide_meta: fn(bo, j) {
if self.format == 6 {
let s = self.w[bo / 4 + 48 + j / 2] >> ((j & 1) * 16)
return vec4(self.signed(s & 255), self.signed((s >> 8) & 255), self.half(bo + 208), 32.0)
}
if self.format == 8 {
return vec4(self.half(bo + j * 2), 0.0, 0.0, 128.0)
}
// One aligned header load supplies both half scales and all eight
// packed sub-block scale/min pairs; no byte-at-a-time loads.
let header = self.w4[bo / 16]
let dm = unpack_half2(header.x)
let shift = (j & 3) * 8
var sc = (header.y >> shift) & 63
var mn = (header.z >> shift) & 63
if j >= 4 {
let extra = header.w >> shift
sc = (extra & 15) | (((header.y >> (shift + 6)) & 3) << 4)
mn = ((extra >> 4) & 15) | (((header.z >> (shift + 6)) & 3) << 4)
}
return vec4(dm.x * float(sc), dm.y * float(mn), 0.0, 0.0)
}
load_wide: fn(base, rn, cm) {
let lane = local_id().x
let blocks = u32(max(self.tile_k / 32, 1))
let entries = 64 * blocks
var i = lane
while i < entries * 2 {
let index = i % entries
let kb = base / 32 + index % blocks
if i < entries {
let row = min(rn + index / blocks, self.n - 1)
let bo = row * self.w_row + (kb / 8) * self.unit_bytes
var mt = self.wide_meta(bo, kb % 8)
if self.tile_k == 16 {
let h = (base / 16) & 1
let a = self.code16(bo, kb % 8, h)
self.wq[index] = a
if self.format == 6 {
var sc = mt.x
if h == 1 { sc = mt.y }
mt = vec4(mt.z * sc, 0.0, 0.0, 32.0)
}
mt.z = float(128 * self.sum16(a))
} else {
let a = self.code16(bo, kb % 8, u32(0))
let b = self.code16(bo, kb % 8, u32(1))
self.wq[index * 2] = a
self.wq[index * 2 + 1] = b
let ca = 128 * self.sum16(a)
let cb = 128 * self.sum16(b)
if self.format == 6 { self.wc[index] = vec2i(ca, cb) }
else { mt.z = float(ca + cb) }
}
self.wm[index] = mt
} else {
let col = min(cm + index / blocks, self.m - 1)
let at = col * (self.k / 32) + kb
let xs = self.xs[at]
if self.tile_k == 16 {
let a = self.xq4[at * 2 + ((base / 16) & 1)]
self.xq_s[index] = a
self.xm[index] = vec4(xs.x, float(self.sum16(a) - 2048), 0.0, 0.0)
} else {
let a = self.xq4[at * 2]
let b = self.xq4[at * 2 + 1]
self.xq_s[index * 2] = a
self.xq_s[index * 2 + 1] = b
if self.format == 6 {
let sa = float(self.sum16(a) - 2048)
self.xm[index] = vec4(xs.x, sa, xs.y - sa, 0.0)
} else {
// The quantiser has already computed the signed sum.
self.xm[index] = vec4(xs.x, xs.y, 0.0, 0.0)
}
}
}
i += self.workgroup_size
}
}
half_block: fn(at, dot, xd, sx) {
let mt = self.wm[at]
var v = vec4(float(dot.x), float(dot.y), float(dot.z), float(dot.w)) - mt.z
if self.format == 6 || self.format == 8 { v -= mt.w * sx }
return xd * (mt.x * v - mt.y * sx)
}
whole_block: fn(at, lo, hi, xd, sx0, sx1) {
let mt = self.wm[at]
if self.format == 6 {
let correction = self.wc[at]
let a = lo - vec4i(correction.x)
let b = hi - vec4i(correction.y)
let v0 = vec4(float(a.x), float(a.y), float(a.z), float(a.w)) - 32.0 * sx0
let v1 = vec4(float(b.x), float(b.y), float(b.z), float(b.w)) - 32.0 * sx1
// Both signed-scale products and their sum are exact in f32.
return xd * (mt.z * (mt.x * v0 + mt.y * v1))
}
return self.half_block(at, lo + hi, xd, sx0)
}
main: fn() {
let lane = local_id().x
let rn = group_id().x * 64
let cm = group_id().y * 64
let r = (lane / 8) * 4
let c = (lane % 8) * 8
let blocks = u32(max(self.tile_k / 32, 1))
let halves = u32(min(self.tile_k / 16, 2))
var c00 = vec4f(0)
var c01 = vec4f(0)
var c10 = vec4f(0)
var c11 = vec4f(0)
var c20 = vec4f(0)
var c21 = vec4f(0)
var c30 = vec4f(0)
var c31 = vec4f(0)
var err00 = vec4f(0)
var err01 = vec4f(0)
var err10 = vec4f(0)
var err11 = vec4f(0)
var err20 = vec4f(0)
var err21 = vec4f(0)
var err30 = vec4f(0)
var err31 = vec4f(0)
for base in 0..self.k / self.tile_k {
self.load_wide(base * self.tile_k, rn, cm)
barrier()
for d in 0..blocks {
let at = c * blocks + d
let x0 = self.xm[at]
let x1 = self.xm[at + blocks]
let x2 = self.xm[at + 2 * blocks]
let x3 = self.xm[at + 3 * blocks]
let x4 = self.xm[at + 4 * blocks]
let x5 = self.xm[at + 5 * blocks]
let x6 = self.xm[at + 6 * blocks]
let x7 = self.xm[at + 7 * blocks]
let xd0 = vec4(x0.x, x1.x, x2.x, x3.x)
let xd1 = vec4(x4.x, x5.x, x6.x, x7.x)
let sx00 = vec4(x0.y, x1.y, x2.y, x3.y)
let sx01 = vec4(x4.y, x5.y, x6.y, x7.y)
let sx10 = vec4(x0.z, x1.z, x2.z, x3.z)
let sx11 = vec4(x4.z, x5.z, x6.z, x7.z)
let b0 = self.xq_s[at * halves]
let b1 = self.xq_s[(at + blocks) * halves]
let b2 = self.xq_s[(at + 2 * blocks) * halves]
let b3 = self.xq_s[(at + 3 * blocks) * halves]
let b4 = self.xq_s[(at + 4 * blocks) * halves]
let b5 = self.xq_s[(at + 5 * blocks) * halves]
let b6 = self.xq_s[(at + 6 * blocks) * halves]
let b7 = self.xq_s[(at + 7 * blocks) * halves]
let wr = r * blocks + d
let a0 = self.wq[wr * halves]
let a1 = self.wq[(wr + blocks) * halves]
let a2 = self.wq[(wr + 2 * blocks) * halves]
let a3 = self.wq[(wr + 3 * blocks) * halves]
let lo00 = self.dots(a0, b0, b1, b2, b3)
let lo01 = self.dots(a0, b4, b5, b6, b7)
let lo10 = self.dots(a1, b0, b1, b2, b3)
let lo11 = self.dots(a1, b4, b5, b6, b7)
let lo20 = self.dots(a2, b0, b1, b2, b3)
let lo21 = self.dots(a2, b4, b5, b6, b7)
let lo30 = self.dots(a3, b0, b1, b2, b3)
let lo31 = self.dots(a3, b4, b5, b6, b7)
var p00 = vec4f(0)
var p01 = vec4f(0)
var p10 = vec4f(0)
var p11 = vec4f(0)
var p20 = vec4f(0)
var p21 = vec4f(0)
var p30 = vec4f(0)
var p31 = vec4f(0)
if self.tile_k == 16 {
p00 = self.half_block(wr, lo00, xd0, sx00)
p01 = self.half_block(wr, lo01, xd1, sx01)
p10 = self.half_block(wr + blocks, lo10, xd0, sx00)
p11 = self.half_block(wr + blocks, lo11, xd1, sx01)
p20 = self.half_block(wr + 2 * blocks, lo20, xd0, sx00)
p21 = self.half_block(wr + 2 * blocks, lo21, xd1, sx01)
p30 = self.half_block(wr + 3 * blocks, lo30, xd0, sx00)
p31 = self.half_block(wr + 3 * blocks, lo31, xd1, sx01)
} else {
let e0 = self.xq_s[at * 2 + 1]
let e1 = self.xq_s[(at + blocks) * 2 + 1]
let e2 = self.xq_s[(at + 2 * blocks) * 2 + 1]
let e3 = self.xq_s[(at + 3 * blocks) * 2 + 1]
let e4 = self.xq_s[(at + 4 * blocks) * 2 + 1]
let e5 = self.xq_s[(at + 5 * blocks) * 2 + 1]
let e6 = self.xq_s[(at + 6 * blocks) * 2 + 1]
let e7 = self.xq_s[(at + 7 * blocks) * 2 + 1]
let h0 = self.wq[wr * 2 + 1]
p00 = self.whole_block(wr, lo00, self.dots(h0, e0, e1, e2, e3), xd0, sx00, sx10)
p01 = self.whole_block(wr, lo01, self.dots(h0, e4, e5, e6, e7), xd1, sx01, sx11)
let h1 = self.wq[(wr + blocks) * 2 + 1]
p10 = self.whole_block(wr + blocks, lo10, self.dots(h1, e0, e1, e2, e3), xd0, sx00, sx10)
p11 = self.whole_block(wr + blocks, lo11, self.dots(h1, e4, e5, e6, e7), xd1, sx01, sx11)
let h2 = self.wq[(wr + 2 * blocks) * 2 + 1]
p20 = self.whole_block(wr + 2 * blocks, lo20, self.dots(h2, e0, e1, e2, e3), xd0, sx00, sx10)
p21 = self.whole_block(wr + 2 * blocks, lo21, self.dots(h2, e4, e5, e6, e7), xd1, sx01, sx11)
let h3 = self.wq[(wr + 3 * blocks) * 2 + 1]
p30 = self.whole_block(wr + 3 * blocks, lo30, self.dots(h3, e0, e1, e2, e3), xd0, sx00, sx10)
p31 = self.whole_block(wr + 3 * blocks, lo31, self.dots(h3, e4, e5, e6, e7), xd1, sx01, sx11)
}
if self.format == 6 {
// Retain the Q6 accumulation residual across blocks.
let y00 = p00 + err00
let y01 = p01 + err01
let y10 = p10 + err10
let y11 = p11 + err11
let y20 = p20 + err20
let y21 = p21 + err21
let y30 = p30 + err30
let y31 = p31 + err31
let t00 = c00 + y00
let t01 = c01 + y01
let t10 = c10 + y10
let t11 = c11 + y11
let t20 = c20 + y20
let t21 = c21 + y21
let t30 = c30 + y30
let t31 = c31 + y31
err00 = fma(vec4f(-1), t00, c00) + y00
err01 = fma(vec4f(-1), t01, c01) + y01
err10 = fma(vec4f(-1), t10, c10) + y10
err11 = fma(vec4f(-1), t11, c11) + y11
err20 = fma(vec4f(-1), t20, c20) + y20
err21 = fma(vec4f(-1), t21, c21) + y21
err30 = fma(vec4f(-1), t30, c30) + y30
err31 = fma(vec4f(-1), t31, c31) + y31
c00 = t00
c01 = t01
c10 = t10
c11 = t11
c20 = t20
c21 = t21
c30 = t30
c31 = t31
} else {
c00 += p00
c01 += p01
c10 += p10
c11 += p11
c20 += p20
c21 += p21
c30 += p30
c31 += p31
}
}
barrier()
}
self.store(rn + r, cm + c, c00)
self.store(rn + r, cm + c + 4, c01)
self.store(rn + r + 1, cm + c, c10)
self.store(rn + r + 1, cm + c + 4, c11)
self.store(rn + r + 2, cm + c, c20)
self.store(rn + r + 2, cm + c + 4, c21)
self.store(rn + r + 3, cm + c, c30)
self.store(rn + r + 3, cm + c + 4, c31)
}
}
let gemm_wide_16 = gemm_wide{
tile_k: 16
wq: shader.workgroup(vec4u, 64)
wm: shader.workgroup(vec4f, 64)
wc: shader.workgroup(vec2i, 64)
xq_s: shader.workgroup(vec4u, 64)
xm: shader.workgroup(vec4f, 64)
}
// Quantised matrix x vector on 8-bit activations. `quantize_q8` turns
// each 32 activations into int8 q = round(x / d), d = max|x| / 127, stored
// biased (q + 128, four per word) with (d, sum q) per block; the gemv then
// runs packed 4x8-bit unsigned dot products (dot4_u8) on the weights' own
// integer codes and applies the scales once per block. Integer work is
// what phone GPUs are slow at; this keeps it to a few ops per 4 weights.
//
// dst[c, row] = sum_k w[row, k] * x[c, k] for the m activation columns. A
// 32-lane group computes 4 consecutive rows; each lane takes 8 values of
// every 256-value unit (positions per format) and a butterfly sums lanes.
let gemv = {
workgroup_size: 128
w: shader.buffer(u32)
w4: shader.buffer(vec4u)
xq: shader.buffer(u32)
xs: shader.buffer(vec2f)
dst: shader.buffer_rw(f32)
k: shader.uniform(u32)
n: shader.uniform(u32)
m: shader.uniform(u32)
w_row: shader.uniform(u32)
d_col: shader.uniform(u32)
main: fn() {
let lane = local_id().x % 32
let row0 = (group_id().x * (self.workgroup_size / 32) + local_id().x / 32) * 4
if row0 < self.n {
let units = self.k / 256
for col in 0..self.m {
var acc = array(0.0, 0.0, 0.0, 0.0)
for u in 0..units {
// This unit's activations: word and block indices.
let xw = (col * self.k + u * 256) / 4
let xb = (col * self.k + u * 256) / 32
for r in 0..4 {
let row = min(row0 + r, self.n - 1)
acc[r] += self.unit_dot(row * self.w_row + u * self.unit_bytes, xw, xb, lane)
}
}
for r in 0..4 {
let total = self.lane_sum(acc[r])
if lane == 0 && row0 + r < self.n {
self.dst[col * self.d_col + row0 + r] = total
}
}
}
}
}
// Byte `o` of the weight tensor.
byte: fn(o) {
return (self.w[o >> 2] >> ((o & 3) * 8)) & 255
}
// The 4 bytes at byte `o` (any alignment) as one little-endian word.
word: fn(o) {
let i = o >> 2
let sh = (o & 3) * 8
let lo = self.w[i]
if sh == 0 {
return lo
}
return (lo >> sh) | (self.w[i + 1] << (32 - sh))
}
// sum(a_i * q_i) for 4 unsigned codes `a` against 4 biased
// activations `x` (q + 128 each), as an integer.
dot_codes: fn(a, x) {
return i32(dot4_u8(a, x)) - 128 * i32(dot4_u8(a, u32(16843009)))
}
// sum(q_i) of 4 biased activations.
sum_x: fn(x) {
return i32(dot4_u8(x, u32(16843009))) - 512
}
// Byte `j` (0..12) of the three words s0 s1 s2.
pick: fn(s0, s1, s2, j) {
var v = s2
if j < 4 {
v = s0
} else if j < 8 {
v = s1
}
return (v >> ((j & 3) * 8)) & 255
}
sc_k4: fn(s0, s1, s2, sb) {
if sb < 4 {
return self.pick(s0, s1, s2, sb) & 63
}
return (self.pick(s0, s1, s2, sb + 4) & 15) | ((self.pick(s0, s1, s2, sb - 4) >> 6) << 4)
}
mn_k4: fn(s0, s1, s2, sb) {
if sb < 4 {
return self.pick(s0, s1, s2, sb + 4) & 63
}
return (self.pick(s0, s1, s2, sb + 4) >> 4) | ((self.pick(s0, s1, s2, sb) >> 6) << 4)
}
lane_sum: fn(v) {
var s = v
s += subgroup_shuffle_xor(s, 16)
s += subgroup_shuffle_xor(s, 8)
s += subgroup_shuffle_xor(s, 4)
s += subgroup_shuffle_xor(s, 2)
s += subgroup_shuffle_xor(s, 1)
return s
}
}
// q4_K / q5_K: f16 d and dmin, 12 bytes of 6-bit scales and mins, (q5_K:
// 32 bytes of fifth bits), 128 bytes of nibbles; every part 16-aligned.
// Eight lanes take one 256-value unit, a lane 16 nibble bytes (one 16-byte
// load): 16 low-nibble values of scale group 2j and 16 high-nibble values
// of group 2j + 1, j = lane / 2; four units run side by side per row.
let gemv_k = {
workgroup_size: 256
w4: shader.buffer(vec4u)
xq4: shader.buffer(vec4u)
xs: shader.buffer(vec2f)
xs4: shader.buffer(vec4f)
dst: shader.buffer_rw(f32)
k: shader.uniform(u32)
n: shader.uniform(u32)
m: shader.uniform(u32)
w_row: shader.uniform(u32)
d_col: shader.uniform(u32)
// Rows per 32-lane group, 1 or 4 (4 share the activation loads; the
// accumulators are a vec4, never a dynamically indexed array, which
// phone compilers put in memory).
rpg: 1
main: fn() {
let lane = local_id().x % 32
let row0 = (group_id().x * (self.workgroup_size / 32) + local_id().x / 32) * self.rpg
if row0 < self.n {
let units = self.k / 256
let slot = lane >> 3
let part = lane & 7
let r1 = min(row0 + 1, self.n - 1)
let r2 = min(row0 + 2, self.n - 1)
let r3 = min(row0 + 3, self.n - 1)
for col in 0..self.m {
var acc = vec4(0.0, 0.0, 0.0, 0.0)
var u = slot
while u < units {
let xbase = col * self.k + u * 256 + (part >> 1) * 64 + (part & 1) * 16
let x_lo = self.xq4[xbase >> 4]
let x_hi = self.xq4[(xbase + 32) >> 4]
let sc = self.xs4[(col * self.k + u * 256) / 64 + (part >> 1)]
let ub = u * self.unit_bytes
acc.x += self.unit_part(row0 * self.w_row + ub, part, x_lo, x_hi, sc)
if self.rpg == 4 {
acc.y += self.unit_part(r1 * self.w_row + ub, part, x_lo, x_hi, sc)
acc.z += self.unit_part(r2 * self.w_row + ub, part, x_lo, x_hi, sc)
acc.w += self.unit_part(r3 * self.w_row + ub, part, x_lo, x_hi, sc)
}
u += 4
}
let t0 = self.lane_sum(acc.x)
if lane == 0 {
self.dst[col * self.d_col + row0] = t0
}
if self.rpg == 4 {
let t1 = self.lane_sum(acc.y)
let t2 = self.lane_sum(acc.z)
let t3 = self.lane_sum(acc.w)
if lane == 0 {
if row0 + 1 < self.n {
self.dst[col * self.d_col + row0 + 1] = t1
}
if row0 + 2 < self.n {
self.dst[col * self.d_col + row0 + 2] = t2
}
if row0 + 3 < self.n {
self.dst[col * self.d_col + row0 + 3] = t3
}
}
}
}
}
}
unit_part: fn(bo, part, x_lo, x_hi, sc) {
let head = self.w4[bo >> 4]
let dm = unpack_half2(head.x)
let q = self.w4[(bo + self.qs_at) / 16 + part]
let sb = (part >> 1) * 2
var lo = vec4u(q.x & u32(252645135), q.y & u32(252645135), q.z & u32(252645135), q.w & u32(252645135))
var hi = vec4u((q.x >> 4) & u32(252645135), (q.y >> 4) & u32(252645135), (q.z >> 4) & u32(252645135), (q.w >> 4) & u32(252645135))
if self.high_bit == 1 {
let h = self.w4[(bo + 16) / 16 + (part & 1)]
lo = lo | (((h >> vec4u(sb, sb, sb, sb)) & vec4u(u32(16843009), u32(16843009), u32(16843009), u32(16843009))) << vec4u(4, 4, 4, 4))
hi = hi | (((h >> vec4u(sb + 1, sb + 1, sb + 1, sb + 1)) & vec4u(u32(16843009), u32(16843009), u32(16843009), u32(16843009))) << vec4u(4, 4, 4, 4))
}
let sc_lo = float(self.sc_k4(head.y, head.z, head.w, sb))
let m_lo = float(self.mn_k4(head.y, head.z, head.w, sb))
let sc_hi = float(self.sc_k4(head.y, head.z, head.w, sb + 1))
let m_hi = float(self.mn_k4(head.y, head.z, head.w, sb + 1))
let d_lo = self.dot16(lo, x_lo)
let d_hi = self.dot16(hi, x_hi)
let s_lo = self.sum16(x_lo)
let s_hi = self.sum16(x_hi)
return sc.x * (dm.x * sc_lo * float(d_lo) - dm.y * m_lo * float(s_lo))
+ sc.z * (dm.x * sc_hi * float(d_hi) - dm.y * m_hi * float(s_hi))
}
// sum(a_i * q_i) over 16 unsigned codes against 16 biased activations.
dot16: fn(a, x) {
let ones = u32(16843009)
let raw = dot4_u8(a.x, x.x) + dot4_u8(a.y, x.y) + dot4_u8(a.z, x.z) + dot4_u8(a.w, x.w)
let asum = dot4_u8(a.x, ones) + dot4_u8(a.y, ones) + dot4_u8(a.z, ones) + dot4_u8(a.w, ones)
return i32(raw) - 128 * i32(asum)
}
// sum(q_i) of 16 biased activations.
sum16: fn(x) {
let ones = u32(16843009)
return i32(dot4_u8(x.x, ones) + dot4_u8(x.y, ones) + dot4_u8(x.z, ones) + dot4_u8(x.w, ones)) - 2048
}
pick: fn(s0, s1, s2, j) {
var v = s2
if j < 4 {
v = s0
} else if j < 8 {
v = s1
}
return (v >> ((j & 3) * 8)) & 255
}
sc_k4: fn(s0, s1, s2, sb) {
if sb < 4 {
return self.pick(s0, s1, s2, sb) & 63
}
return (self.pick(s0, s1, s2, sb + 4) & 15) | ((self.pick(s0, s1, s2, sb - 4) >> 6) << 4)
}
mn_k4: fn(s0, s1, s2, sb) {
if sb < 4 {
return self.pick(s0, s1, s2, sb + 4) & 63
}
return (self.pick(s0, s1, s2, sb + 4) >> 4) | ((self.pick(s0, s1, s2, sb) >> 6) << 4)
}
lane_sum: fn(v) {
var s = v
s += subgroup_shuffle_xor(s, 16)
s += subgroup_shuffle_xor(s, 8)
s += subgroup_shuffle_xor(s, 4)
s += subgroup_shuffle_xor(s, 2)
s += subgroup_shuffle_xor(s, 1)
return s
}
}
// Opt-in decode GEMVs. Each lane still loads 16-byte weight payloads;
// activation loads and corrections are outside the explicit row calls.
// All accumulators are registers, never dynamically indexed local arrays.
// Source-level counts per 16 nibble bytes (32 codes), excluding addresses,
// scale extraction and float accumulation: Q4 = 28 integer ops + 10
// dot4, Q5 = 56 integer ops + 10 dot4, Q6 = 56 integer ops + 10 dot4.
// Q4/Q5/Q6 additionally need 8 dot4 + 8 integer ops for activation sums
// shared across the rows. Q8 per 32 code bytes = 25 integer ops + 16 dot4, with
// its activation sum read from quantize_q8's metadata. Conversions omitted.
// These are algorithmic counts, not claims about a driver's instruction ISA.
let gemv_decode = gemv_k{
format: 5
unit_bytes: 176
row_lanes: 32
rpg: 1
groups_x: shader.uniform(u32)
partial: shader.workgroup(vec4f, 16)
main: fn() {
let t = local_id().x
let lane = t % self.row_lanes
let group = group_id().x + group_id().y * self.groups_x
let row0 = (group * (self.workgroup_size / self.row_lanes) + t / self.row_lanes) * self.rpg
let row1 = min(row0 + 1, self.n - 1)
let row2 = min(row0 + 2, self.n - 1)
let row3 = min(row0 + 3, self.n - 1)
let slot = lane >> 3
let part = lane & 7
for col in 0..self.m {
var acc = vec4(0.0, 0.0, 0.0, 0.0)
if row0 < self.n {
var u = slot
while u < self.k / 256 {
let xb = col * self.k + u * 256
var lo_at = xb + (part >> 1) * 64 + (part & 1) * 16
var hi_at = lo_at + 32
if self.format == 6 {
lo_at = xb + (part >> 2) * 128 + ((part >> 1) & 1) * 32 + (part & 1) * 16
hi_at = lo_at + 64
}
if self.format == 8 {
lo_at = xb + part * 32
hi_at = lo_at + 16
}
let xlo = self.xq4[lo_at >> 4]
let xhi = self.xq4[hi_at >> 4]
// Keep the original per-lane correction order. Splitting
// a full-block sum between lanes changes decode rounding.
var sc = vec4(0.0, 0.0, 0.0, 0.0)
if self.format == 4 || self.format == 5 {
let meta = self.xs4[xb / 64 + (part >> 1)]
sc = vec4(meta.x, float(self.sum16(xlo)), meta.z, float(self.sum16(xhi)))
} else if self.format == 6 {
sc = vec4(self.xs[lo_at / 32].x, float(self.sum16(xlo)), self.xs[hi_at / 32].x, float(self.sum16(xhi)))
} else {
let slo = self.xs[lo_at / 32]
sc = vec4(slo.x, slo.y, 0.0, 0.0)
}
let ub = u * self.unit_bytes
acc.x += self.decode_part(row0 * self.w_row + ub, part, xlo, xhi, sc)
if self.rpg >= 2 {
acc.y += self.decode_part(row1 * self.w_row + ub, part, xlo, xhi, sc)
}
if self.rpg == 4 {
acc.z += self.decode_part(row2 * self.w_row + ub, part, xlo, xhi, sc)
acc.w += self.decode_part(row3 * self.w_row + ub, part, xlo, xhi, sc)
}
u += self.row_lanes / 8
}
}
// Wide row groups use one subgroup reduction when the hardware
// width matches. Smaller subgroups use shared partials;
// every invocation reaches both barriers, including tail rows.
var total = acc
let sg = subgroup_size()
if self.row_lanes == 32 {
// Preserve the current kernel's tree, independently of
// the hardware subgroup width; this path needs no barrier.
total += subgroup_shuffle_xor(total, 16)
total += subgroup_shuffle_xor(total, 8)
total += subgroup_shuffle_xor(total, 4)
total += subgroup_shuffle_xor(total, 2)
total += subgroup_shuffle_xor(total, 1)
} else if sg == self.row_lanes {
total = subgroup_add(acc)
} else if sg < self.row_lanes {
let sub = subgroup_add(acc)
if subgroup_lane() == 0 {
self.partial[t / sg] = sub
}
barrier()
if lane == 0 {
let first = t / sg
total = self.partial[first]
for i in 1..self.row_lanes / sg {
total += self.partial[first + i]
}
}
barrier()
} else {
if self.row_lanes == 64 {
total += subgroup_shuffle_xor(total, 32)
}
total += subgroup_shuffle_xor(total, 16)
total += subgroup_shuffle_xor(total, 8)
total += subgroup_shuffle_xor(total, 4)
total += subgroup_shuffle_xor(total, 2)
total += subgroup_shuffle_xor(total, 1)
}
if lane == 0 && row0 < self.n {
self.dst[col * self.d_col + row0] = total.x
if self.rpg >= 2 && row0 + 1 < self.n {
self.dst[col * self.d_col + row0 + 1] = total.y
}
if self.rpg == 4 {
if row0 + 2 < self.n {
self.dst[col * self.d_col + row0 + 2] = total.z
}
if row0 + 3 < self.n {
self.dst[col * self.d_col + row0 + 3] = total.w
}
}
}
}
}
// Four codes sum to <=252 in each byte: no inter-byte carry. Bias
// correction uses one dot4 instead of four (5 dot4 per 16 codes).
dot16small: fn(a, x) {
let raw = self.dot16raw(a, x)
let sum = dot4_u8(a.x + a.y + a.z + a.w, u32(16843009))
return i32(raw) - (i32(sum) << 7)
}
dot16raw: fn(a, x) {
return dot4_u8(a.x, x.x) + dot4_u8(a.y, x.y) + dot4_u8(a.z, x.z) + dot4_u8(a.w, x.w)
}
decode_part: fn(bo, part, xlo, xhi, sc) {
let b16 = bo >> 4
let m4 = vec4u(u32(252645135), u32(252645135), u32(252645135), u32(252645135))
if self.format == 8 {
let ds = self.w4[b16]
var dw = ds.w
if part < 2 {
dw = ds.x
} else if part < 4 {
dw = ds.y
} else if part < 6 {
dw = ds.z
}
let d = unpack_half2(dw >> ((part & 1) * 16)).x
let flip = vec4u(u32(2155905152), u32(2155905152), u32(2155905152), u32(2155905152))
let a = self.w4[b16 + 1 + part * 2] ^ flip
let b = self.w4[b16 + 2 + part * 2] ^ flip
let ones = vec4u(u32(16843009), u32(16843009), u32(16843009), u32(16843009))
let raw = self.dot16raw(a, xlo) + self.dot16raw(b, xhi)
let wsum = self.dot16raw(a, ones) + self.dot16raw(b, ones)
let v = i32(raw) - ((i32(wsum) + i32(sc.y)) << 7)
return d * sc.x * float(v)
}
if self.format == 6 {
let half = part >> 2
let g = (part >> 1) & 1
let q = self.w4[b16 + half * 4 + (part & 3)]
let high = self.w4[b16 + 8 + half * 2 + (part & 1)]
let sh = g * 2
let h = high >> vec4u(sh, sh, sh, sh)
let m6 = vec4u(u32(808464432), u32(808464432), u32(808464432), u32(808464432))
let lo = (q & m4) | ((h << vec4u(4, 4, 4, 4)) & m6)
let hi = ((q >> vec4u(4, 4, 4, 4)) & m4) | (h & m6)
let scales = self.w4[b16 + 12]
var sl = scales.x
var shigh = scales.y
if half == 1 {
sl = scales.z
shigh = scales.w
}
let shift = 24 - (part & 3) * 8
let dl = float(i32(sl << shift) >> 24)
let dh = float(i32(shigh << shift) >> 24)
let d = unpack_half2(self.w4[b16 + 13].x & u32(65535)).x
let vl = self.dot16small(lo, xlo) - 32 * i32(sc.y)
let vh = self.dot16small(hi, xhi) - 32 * i32(sc.w)
return d * (dl * sc.x * float(vl) + dh * sc.z * float(vh))
}
let head = self.w4[b16]
let dm = unpack_half2(head.x)
var at = b16 + 1 + part
if self.format == 5 {
at += 2
}
let q = self.w4[at]
var lo = q & m4
var hi = (q >> vec4u(4, 4, 4, 4)) & m4
if self.format == 5 {
let high = self.w4[b16 + 1 + (part & 1)]
let sb = part & 6
let h = high >> vec4u(sb, sb, sb, sb)
let m5 = vec4u(u32(269488144), u32(269488144), u32(269488144), u32(269488144))
lo = lo | ((h << vec4u(4, 4, 4, 4)) & m5)
hi = hi | ((h << vec4u(3, 3, 3, 3)) & m5)
}
// Decode the adjacent sub-block scales/mins together as packed
// bytes, before extracting the pair and applying each scale once.
var scales = head.y & u32(1061109567)
var mins = head.z & u32(1061109567)
if part >= 4 {
scales = (head.w & u32(252645135)) | ((head.y >> 2) & u32(808464432))
mins = ((head.w >> 4) & u32(252645135)) | ((head.z >> 2) & u32(808464432))
}
let shift = (part & 2) * 8
let sp = scales >> shift
let mp = mins >> shift
let sl = float(sp & 255)
let sh = float((sp >> 8) & 255)
let ml = float(mp & 255)
let mh = float((mp >> 8) & 255)
let vl = self.dot16small(lo, xlo)
let vh = self.dot16small(hi, xhi)
return sc.x * (dm.x * sl * float(vl) - dm.y * ml * sc.y)
+ sc.z * (dm.x * sh * float(vh) - dm.y * mh * sc.w)
}
}
// Gated delta rule over the tokens of one sequence. 32 lanes own one
// state column (sv/32 elements each); the state is stored transposed
// (a column's sv values contiguous), q/k heads map to value heads by
// modulo, the gate decays the state before the k dot, beta/gate are
// indexed flat [G, H, T, S]. Writes the attention output then the
// final state behind it, or one state plane per token with checkpoints.
let gdn = {
workgroup_size: 128
// A pipeline constant: ordinary decode keeps only the final store.
checkpoints: 0
q: shader.buffer(f32)
k: shader.buffer(f32)
v: shader.buffer(f32)
g: shader.buffer(f32)
beta: shader.buffer(f32)
state: shader.buffer_rw(f32)
dst: shader.buffer_rw(f32)
sv: shader.uniform(u32)
h: shader.uniform(u32)
n_tokens: shader.uniform(u32)
n_seqs: shader.uniform(u32)
gate_g: shader.uniform(u32)
q_heads: shader.uniform(u32)
k_heads: shader.uniform(u32)
q_h: shader.uniform(u32)
q_t: shader.uniform(u32)
q_s: shader.uniform(u32)
k_h: shader.uniform(u32)
k_t: shader.uniform(u32)
k_s: shader.uniform(u32)
v_h: shader.uniform(u32)
v_t: shader.uniform(u32)
v_s: shader.uniform(u32)
main: fn() {
let lane = local_id().x % 32
let col = group_id().x * 4 + local_id().x / 32
let head = group_id().y
let seq = group_id().z
if col < self.sv {
let per = self.sv / 32
let iq = head % self.q_heads
let ik = head % self.k_heads
let scale = inverseSqrt(float(self.sv))
let sbase = self.state_base(seq, head, col, lane, per)
var ls = array(0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0)
for j in 0..per {
ls[j] = self.state[sbase + j]
}
var qo = self.q_start(seq, iq)
var ko = self.k_start(seq, ik)
var vo = self.v_start(seq, head)
var bo = seq * self.n_tokens * self.h + head
var go = (seq * self.n_tokens * self.h + head) * self.gate_g
for t in 0..self.n_tokens {
var s_k = 0.0
for j in 0..per {
let si = lane * per + j
var decay = 0.0
if self.gate_g == 1 {
decay = exp(self.g[go])
} else {
decay = exp(self.g[go + si])
}
ls[j] = ls[j] * decay
s_k += ls[j] * self.k[ko + si]
}
s_k = self.lane_sum(s_k)
let d = (self.v[vo + col] - s_k) * self.beta[bo]
var y = 0.0
for j in 0..per {
let si = lane * per + j
ls[j] = ls[j] + self.k[ko + si] * d
y += ls[j] * self.q[qo + si]
}
y = self.lane_sum(y)
if lane == 0 {
self.dst[((seq * self.n_tokens + t) * self.h + head) * self.sv + col] = y * scale
}
if self.checkpoints == 1 {
let out = self.n_seqs * self.n_tokens * self.h * self.sv
+ t * self.n_seqs * self.h * self.sv * self.sv + sbase
for j in 0..per {
self.dst[out + j] = ls[j]
}
}
qo += self.q_t
ko += self.k_t
vo += self.v_t
bo += self.h
go += self.h * self.gate_g
}
if self.checkpoints == 0 {
for j in 0..per {
self.write_state(sbase, j, ls[j])
}
}
}
}
q_start: fn(seq, head) { return seq * self.q_s + head * self.q_h }
k_start: fn(seq, head) { return seq * self.k_s + head * self.k_h }
v_start: fn(seq, head) { return seq * self.v_s + head * self.v_h }
state_base: fn(seq, head, col, lane, per) {
return ((seq * self.h + head) * self.sv + col) * self.sv + lane * per
}
write_state: fn(base, j, v) {
let out = self.n_seqs * self.n_tokens * self.h * self.sv + base
self.dst[out + j] = v
}
// The sum over the 32 lanes of this lane's group (any subgroup size
// >= 32: the butterfly stays inside the 32-aligned group).
lane_sum: fn(v) {
var s = v
s += subgroup_shuffle_xor(s, 16)
s += subgroup_shuffle_xor(s, 8)
s += subgroup_shuffle_xor(s, 4)
s += subgroup_shuffle_xor(s, 2)
s += subgroup_shuffle_xor(s, 1)
return s
}
}
// The same for sv == 128 (four state values per lane) with every q/k
// offset and stride a multiple of 4: each lane's state slice lives in
// one vec4 register and q/k/state move as 16-byte loads (a dynamically
// indexed local array goes to memory on some GPUs).
let gdn_v4 = gdn{
q4: shader.buffer(vec4f)
k4: shader.buffer(vec4f)
state4: shader.buffer_rw(vec4f)
dst4: shader.buffer_rw(vec4f)
main: fn() {
let lane = local_id().x % 32
let col = group_id().x * 4 + local_id().x / 32
let head = group_id().y
let seq = group_id().z
let iq = head % self.q_heads
let ik = head % self.k_heads
let scale = inverseSqrt(float(self.sv))
let si = self.state_base(seq, head, col, lane, u32(4)) / 4
var ls = self.state4[si]
var qo = self.q_start(seq, iq) / 4 + lane
var ko = self.k_start(seq, ik) / 4 + lane
var vo = self.v_start(seq, head)
var bo = seq * self.n_tokens * self.h + head
var go = (seq * self.n_tokens * self.h + head) * self.gate_g
for t in 0..self.n_tokens {
if self.gate_g == 1 {
ls = ls * exp(self.g[go])
} else {
let gi = go + lane * 4
ls = ls * vec4(exp(self.g[gi]), exp(self.g[gi + 1]), exp(self.g[gi + 2]), exp(self.g[gi + 3]))
}
let kv = self.k4[ko]
let s_k = self.lane_sum(dot(ls, kv))
let d = (self.v[vo + col] - s_k) * self.beta[bo]
ls = ls + kv * d
let y = self.lane_sum(dot(ls, self.q4[qo]))
if lane == 0 {
self.dst[((seq * self.n_tokens + t) * self.h + head) * self.sv + col] = y * scale
}
if self.checkpoints == 1 {
let out = (self.n_seqs * self.n_tokens * self.h * self.sv) / 4
+ t * self.n_seqs * self.h * self.sv * 32
+ ((seq * self.h + head) * self.sv + col) * 32 + lane
self.dst4[out] = ls
}
qo += self.q_t / 4
ko += self.k_t / 4
vo += self.v_t
bo += self.h
go += self.h * self.gate_g
}
if self.checkpoints == 0 {
self.write_state4(si, ls)
}
}
write_state4: fn(base, v) {
let out = (self.n_seqs * self.n_tokens * self.h * self.sv) / 4 + base
self.dst4[out] = v
}
}
// Every invocation owns its state slice, so the selected row can be read
// and written in place. Single-sequence address functions also keep the
// push constants within the 128-byte limit without extra parameter buffers.
let gdn_state = gdn{
rows: shader.buffer(i32)
state_row: shader.uniform(u32)
q_start: fn(seq, head) { return head * self.q_h }
k_start: fn(seq, head) { return head * self.k_h }
v_start: fn(seq, head) { return head * self.v_h }
state_base: fn(seq, head, col, lane, per) {
return u32(self.rows[0]) * self.state_row + (head * self.sv + col) * self.sv + lane * per
}
write_state: fn(base, j, v) { self.state[base + j] = v }
}
let gdn_v4_state = gdn_v4{
rows: shader.buffer(i32)
state_row: shader.uniform(u32)
q_start: gdn_state.q_start
k_start: gdn_state.k_start
v_start: gdn_state.v_start
state_base: gdn_state.state_base
write_state4: fn(base, v) { self.state4[base] = v }
}
{
// ---- copies --------------------------------------------------------
// dst = src, element by element in src order (ggml cpy/cont/dup): the
// flat index walks src's shape and lands at the same flat index of
// dst's shape. 4D, strided on both sides.
copy_f32: {
workgroup_size: 256
src: shader.buffer(f32)
dst: shader.buffer_rw(f32)
n: shader.uniform(u32)
groups_x: shader.uniform(u32)
s_ne0: shader.uniform(u32)
s_ne1: shader.uniform(u32)
s_ne2: shader.uniform(u32)
s_nb0: shader.uniform(u32)
s_nb1: shader.uniform(u32)
s_nb2: shader.uniform(u32)
s_nb3: shader.uniform(u32)
d_ne0: shader.uniform(u32)
d_ne1: shader.uniform(u32)
d_ne2: shader.uniform(u32)
d_nb0: shader.uniform(u32)
d_nb1: shader.uniform(u32)
d_nb2: shader.uniform(u32)
d_nb3: shader.uniform(u32)
main: fn() {
let g = group_id()
let i = (g.y * self.groups_x + g.x) * self.workgroup_size + local_id().x
if i < self.n {
let s0 = i % self.s_ne0
let s1 = (i / self.s_ne0) % self.s_ne1
let s2 = (i / (self.s_ne0 * self.s_ne1)) % self.s_ne2
let s3 = i / (self.s_ne0 * self.s_ne1 * self.s_ne2)
let d0 = i % self.d_ne0
let d1 = (i / self.d_ne0) % self.d_ne1
let d2 = (i / (self.d_ne0 * self.d_ne1)) % self.d_ne2
let d3 = i / (self.d_ne0 * self.d_ne1 * self.d_ne2)
self.dst[d0 * self.d_nb0 + d1 * self.d_nb1 + d2 * self.d_nb2 + d3 * self.d_nb3] =
self.src[s0 * self.s_nb0 + s1 * self.s_nb1 + s2 * self.s_nb2 + s3 * self.s_nb3]
}
}
}
// ---- rows ----------------------------------------------------------
// dst[r, :] = src[rows[r], :] (f32 rows).
get_rows_f32: {
workgroup_size: 256
src: shader.buffer(f32)
rows: shader.buffer(i32)
dst: shader.buffer_rw(f32)
ne0: shader.uniform(u32)
src_row: shader.uniform(u32)
dst_row: shader.uniform(u32)
main: fn() {
let i = global_id().x
let r = group_id().y
if i < self.ne0 {
let row = u32(self.rows[r])
self.dst[r * self.dst_row + i] = self.src[row * self.src_row + i]
}
}
}
// set_rows: dst[rows[r], :] = src[r, :], f32 rows into f32 (or f16 in
// the variant below).
set_rows_f32: {
workgroup_size: 256
src: shader.buffer(f32)
rows: shader.buffer(i32)
dst: shader.buffer_rw(f32)
ne0: shader.uniform(u32)
src_row: shader.uniform(u32)
dst_row: shader.uniform(u32)
main: fn() {
let i = global_id().x
let r = group_id().y
if i < self.ne0 {
let row = u32(self.rows[r])
self.dst[row * self.dst_row + i] = self.src[r * self.src_row + i]
}
}
}
set_rows_f16: {
workgroup_size: 256
src: shader.buffer(f32)
rows: shader.buffer(i32)
dst: shader.buffer_rw(f16)
ne0: shader.uniform(u32)
src_row: shader.uniform(u32)
dst_row: shader.uniform(u32)
main: fn() {
let i = global_id().x
let r = group_id().y
if i < self.ne0 {
let row = u32(self.rows[r])
self.dst[row * self.dst_row + i] = f16(self.src[r * self.src_row + i])
}
}
}
// ---- norms ---------------------------------------------------------
// RMS norm (l2 == 0): x / sqrt(mean(x^2) + eps); L2 norm (l2 == 1):
// x / sqrt(max(sum(x^2), eps)). One workgroup per row; rows along
// dim 0, the higher dims strided.
norm: {
workgroup_size: 256
x: shader.buffer(f32)
dst: shader.buffer_rw(f32)
// With w_mode 1 the result is also multiplied by the row weight w
// (a fused RmsNorm + Mul).
w: shader.buffer(f32)
w_mode: shader.uniform(u32)
ne0: shader.uniform(u32)
ne1: shader.uniform(u32)
ne2: shader.uniform(u32)
x_nb1: shader.uniform(u32)
x_nb2: shader.uniform(u32)
x_nb3: shader.uniform(u32)
d_nb1: shader.uniform(u32)
d_nb2: shader.uniform(u32)
d_nb3: shader.uniform(u32)
eps: shader.uniform(f32)
l2: shader.uniform(u32)
partial: shader.workgroup(f32, 32)
main: fn() {
let b = group_id().x
let i1 = b % self.ne1
let i2 = (b / self.ne1) % self.ne2
let i3 = b / (self.ne1 * self.ne2)
let xo = i1 * self.x_nb1 + i2 * self.x_nb2 + i3 * self.x_nb3
let dout = i1 * self.d_nb1 + i2 * self.d_nb2 + i3 * self.d_nb3
let t = local_id().x
var sum = 0.0
var c = t
while c < self.ne0 {
let v = self.x[xo + c]
sum += v * v
c += self.workgroup_size
}
sum = self.block_sum(sum)
var scale = 0.0
if self.l2 == 1 {
scale = 1.0 / sqrt(max(sum, self.eps))
} else {
scale = inverseSqrt(sum / float(self.ne0) + self.eps)
}
c = t
while c < self.ne0 {
var v = self.x[xo + c] * scale
if self.w_mode == 1 {
v = v * self.w[c]
}
self.dst[dout + c] = v
c += self.workgroup_size
}
}
// The workgroup's total of `v`, in every invocation.
block_sum: fn(v) {
let s = subgroup_add(v)
if subgroup_lane() == 0 {
self.partial[subgroup_id()] = s
}
barrier()
var total = 0.0
for k in 0..self.workgroup_size / subgroup_size() {
total += self.partial[k]
}
barrier()
return total
}
}
// ---- rope ----------------------------------------------------------
// Multi-section rope (MROPE / IMROPE): split-half pairs (x[ic],
// x[ic + n_dims/2]) rotated by a theta whose position comes from one of
// four position streams by section. One workgroup per (i1, i2, i3).
rope_multi: {
workgroup_size: 64
src: shader.buffer(f32)
pos: shader.buffer(i32)
dst: shader.buffer_rw(f32)
ne0: shader.uniform(u32)
ne2: shader.uniform(u32)
imrope: shader.uniform(u32)
n_dims: shader.uniform(u32)
sect_0: shader.uniform(u32)
sect_1: shader.uniform(u32)
sect_2: shader.uniform(u32)
sect_3: shader.uniform(u32)
freq_base: shader.uniform(f32)
freq_scale: shader.uniform(f32)
ext_factor: shader.uniform(f32)
attn_factor: shader.uniform(f32)
corr0: shader.uniform(f32)
corr1: shader.uniform(f32)
s_nb0: shader.uniform(u32)
s_nb1: shader.uniform(u32)
s_nb2: shader.uniform(u32)
s_nb3: shader.uniform(u32)
d_nb0: shader.uniform(u32)
d_nb1: shader.uniform(u32)
d_nb2: shader.uniform(u32)
d_nb3: shader.uniform(u32)
main: fn() {
let g = group_id()
let sbase = g.x * self.s_nb1 + g.y * self.s_nb2 + g.z * self.s_nb3
let dbase = g.x * self.d_nb1 + g.y * self.d_nb2 + g.z * self.d_nb3
let i2 = g.y
let half = self.n_dims / 2
var i0 = 2 * local_id().x
while i0 < self.ne0 {
if i0 < self.n_dims {
let ic = i0 / 2
let sect_dims = self.sect_0 + self.sect_1 + self.sect_2 + self.sect_3
let sector = ic % sect_dims
var stream = 3
if self.imrope == 1 {
if sector % 3 == 1 && sector < 3 * self.sect_1 {
stream = 1
} else if sector % 3 == 2 && sector < 3 * self.sect_2 {
stream = 2
} else if sector % 3 == 0 && sector < 3 * self.sect_0 {
stream = 0
}
} else {
if sector < self.sect_0 {
stream = 0
} else if sector < self.sect_0 + self.sect_1 {
stream = 1
} else if sector < self.sect_0 + self.sect_1 + self.sect_2 {
stream = 2
}
}
let theta_base = float(self.pos[i2 + self.ne2 * u32(stream)])
let theta_extrap = theta_base * pow(self.freq_base, -float(i0) / float(self.n_dims))
var mscale = self.attn_factor
var theta = self.freq_scale * theta_extrap
if self.ext_factor != 0.0 {
let y = (float(i0 / 2) - self.corr0) / max(0.001, self.corr1 - self.corr0)
let ramp = (1.0 - min(1.0, max(0.0, y))) * self.ext_factor
theta = theta * (1.0 - ramp) + theta_extrap * ramp
mscale *= 1.0 + 0.1 * log(1.0 / self.freq_scale)
}
let cos_t = cos(theta) * mscale
let sin_t = sin(theta) * mscale
let x0 = self.src[sbase + ic * self.s_nb0]
let x1 = self.src[sbase + (ic + half) * self.s_nb0]
self.dst[dbase + ic * self.d_nb0] = x0 * cos_t - x1 * sin_t
self.dst[dbase + (ic + half) * self.d_nb0] = x0 * sin_t + x1 * cos_t
} else {
self.dst[dbase + i0 * self.d_nb0] = self.src[sbase + i0 * self.s_nb0]
self.dst[dbase + (i0 + 1) * self.d_nb0] = self.src[sbase + (i0 + 1) * self.s_nb0]
}
i0 += 2 * self.workgroup_size
}
}
}
// ---- elementwise ---------------------------------------------------
// ggml unary ops by op code (abs 0 ... softplus 15), contiguous.
unary: {
workgroup_size: 256
x: shader.buffer(f32)
dst: shader.buffer_rw(f32)
// With gated 1 the result is multiplied by g (a fused Unary + Mul).
g: shader.buffer(f32)
n: shader.uniform(u32)
groups_x: shader.uniform(u32)
op: shader.uniform(u32)
gated: shader.uniform(u32)
g_width: shader.uniform(u32)
// Optional row-broadcast add before the activation.
bias: shader.buffer(f32)
add_width: shader.uniform(u32)
main: fn() {
let gr = group_id()
let i = (gr.y * self.groups_x + gr.x) * self.workgroup_size + local_id().x
if i < self.n {
var x = self.x[i]
if self.add_width > 0 {
x = x + self.bias[i % self.add_width]
}
var v = self.apply(self.op, x)
if self.gated == 1 {
var gi = i
if self.g_width > 0 {
gi = i % self.g_width
}
v = v * self.g[gi]
}
self.dst[i] = v
}
}
apply: fn(op, x) {
if op == 0 { return abs(x) }
if op == 1 { return sign(x) }
if op == 2 { return -x }
if op == 3 { return step(0.0, x) * min(1.0, abs(sign(x))) }
if op == 4 { return tanh(x) }
if op == 5 {
if x > 0.0 { return x }
return exp(x) - 1.0
}
if op == 6 { return max(x, 0.0) }
if op == 7 { return 1.0 / (1.0 + exp(-x)) }
if op == 8 { return 0.5 * x * (1.0 + tanh(0.7978845608 * (x + 0.044715 * x * x * x))) }
if op == 9 { return x / (1.0 + exp(-1.702 * x)) }
if op == 10 { return x / (1.0 + exp(-x)) }
if op == 11 { return x * min(1.0, max(0.0, (x + 3.0) / 6.0)) }
if op == 12 { return min(1.0, max(0.0, (x + 3.0) / 6.0)) }
if op == 13 { return exp(x) }
if op == 14 { return exp(x) - 1.0 }
if op == 15 {
if x > 20.0 { return x }
return log(1.0 + exp(x))
}
return x
}
}
// dst = act(a) * b (ggml glu with a separate gate tensor), contiguous.
glu: {
workgroup_size: 256
a: shader.buffer(f32)
b: shader.buffer(f32)
dst: shader.buffer_rw(f32)
n: shader.uniform(u32)
groups_x: shader.uniform(u32)
op: shader.uniform(u32)
main: fn() {
let g = group_id()
let i = (g.y * self.groups_x + g.x) * self.workgroup_size + local_id().x
if i < self.n {
let x = self.a[i]
var act = x / (1.0 + exp(-x))
if self.op == 0 {
act = max(x, 0.0)
} else if self.op == 1 {
act = 0.5 * x * (1.0 + tanh(0.7978845608 * (x + 0.044715 * x * x * x)))
} else if self.op == 5 {
act = x / (1.0 + exp(-1.702 * x))
}
self.dst[i] = act * self.b[i]
}
}
}
// dst = a op b with ggml's modulo broadcast of b (add 0, sub 1, mul 2,
// div 3). One invocation per dst element of dim 0, rows on y, the
// higher dims flattened on z.
binary: {
workgroup_size: 256
a: shader.buffer(f32)
b: shader.buffer(f32)
dst: shader.buffer_rw(f32)
op: shader.uniform(u32)
ne0: shader.uniform(u32)
ne1: shader.uniform(u32)
ne2: shader.uniform(u32)
b_ne0: shader.uniform(u32)
b_ne1: shader.uniform(u32)
b_ne2: shader.uniform(u32)
b_ne3: shader.uniform(u32)
a_nb0: shader.uniform(u32)
a_nb1: shader.uniform(u32)
a_nb2: shader.uniform(u32)
a_nb3: shader.uniform(u32)
b_nb0: shader.uniform(u32)
b_nb1: shader.uniform(u32)
b_nb2: shader.uniform(u32)
b_nb3: shader.uniform(u32)
d_nb0: shader.uniform(u32)
d_nb1: shader.uniform(u32)
d_nb2: shader.uniform(u32)
d_nb3: shader.uniform(u32)
main: fn() {
let i0 = global_id().x
let i1 = group_id().y
let i23 = group_id().z
if i0 < self.ne0 {
let i2 = i23 % self.ne2
let i3 = i23 / self.ne2
let av = self.a[i0 * self.a_nb0 + i1 * self.a_nb1 + i2 * self.a_nb2 + i3 * self.a_nb3]
let bv = self.b[(i0 % self.b_ne0) * self.b_nb0 + (i1 % self.b_ne1) * self.b_nb1
+ (i2 % self.b_ne2) * self.b_nb2 + (i3 % self.b_ne3) * self.b_nb3]
var r = av + bv
if self.op == 1 {
r = av - bv
} else if self.op == 2 {
r = av * bv
} else if self.op == 3 {
r = av / bv
}
self.dst[i0 * self.d_nb0 + i1 * self.d_nb1 + i2 * self.d_nb2 + i3 * self.d_nb3] = r
}
}
}
// ---- recurrent (qwen35 Gated DeltaNet layers) ------------------------
// Gather + concat + the final state window + scatter. One invocation
// owns a channel; it reads the old prefix in order before overwriting
// earlier entries of that same channel. The full convolution input is
// still materialized for the convolution, for decode and prefill alike.
conv_state: {
workgroup_size: 128
state: shader.buffer_rw(f32)
rows: shader.buffer(i32)
x: shader.buffer(f32)
dst: shader.buffer_rw(f32)
prefix: shader.uniform(u32)
channels: shader.uniform(u32)
tokens: shader.uniform(u32)
state_row: shader.uniform(u32)
x_t: shader.uniform(u32)
x_c: shader.uniform(u32)
main: fn() {
let c = global_id().x
if c < self.channels {
let sb = u32(self.rows[0]) * self.state_row + c * self.prefix
let db = c * (self.prefix + self.tokens)
for k in 0..self.prefix {
let v = self.state[sb + k]
self.dst[db + k] = v
if k >= self.tokens {
self.state[sb + k - self.tokens] = v
}
}
for t in 0..self.tokens {
let v = self.x[c * self.x_c + t * self.x_t]
let k = self.prefix + t
self.dst[db + k] = v
if k >= self.tokens {
self.state[sb + k - self.tokens] = v
}
}
}
}
}
// Short causal convolution (ggml ssm_conv): src [d_conv - 1 + T, C, S]
// with time along dim 0, w [d_conv, C], dst [C, T, S];
// dst[c, t, s] = sum_k src[t + k, c, s] * w[k, c]. One invocation per
// (channel, token, seq).
ssm_conv: {
workgroup_size: 128
src: shader.buffer(f32)
w: shader.buffer(f32)
dst: shader.buffer_rw(f32)
d_conv: shader.uniform(u32)
d_inner: shader.uniform(u32)
silu: shader.uniform(u32)
s_nb0: shader.uniform(u32)
s_nb1: shader.uniform(u32)
s_nb2: shader.uniform(u32)
w_nb1: shader.uniform(u32)
d_nb0: shader.uniform(u32)
d_nb1: shader.uniform(u32)
d_nb2: shader.uniform(u32)
main: fn() {
let c = global_id().x
let t = group_id().y
let s = group_id().z
if c < self.d_inner {
let base = s * self.s_nb2 + c * self.s_nb1
var sum = 0.0
for k in 0..self.d_conv {
sum += self.src[base + (t + k) * self.s_nb0] * self.w[c * self.w_nb1 + k]
}
if self.silu == 1 {
sum = sum / (1.0 + exp(-sum))
}
self.dst[s * self.d_nb2 + t * self.d_nb1 + c * self.d_nb0] = sum
}
}
}
// Gated delta rule (see `gdn`).
gated_delta_net: gdn{}
gated_delta_net_checkpoints: gdn{checkpoints: 1}
gated_delta_net_v4: gdn_v4{}
gated_delta_net_v4_checkpoints: gdn_v4{checkpoints: 1}
gated_delta_net_state: gdn_state{}
gated_delta_net_v4_state: gdn_v4_state{}
// ---- attention -----------------------------------------------------
// Attention for a few queries over the f16 KV cache: one workgroup per
// (head, query). Scores for every key in parallel into shared memory
// (in chunks of `chunk` keys), an online softmax across the chunks, and
// the output accumulated one dimension per invocation.
// q f32 [D, nq, H], k/v f16 [D, kc, Hkv], mask f16 [kc, nq], dst f32
// [Dv, H, nq].
attn_decode: {
workgroup_size: 128
chunk: 2048
q: shader.buffer(f32)
k: shader.buffer(f16)
v: shader.buffer(f16)
mask: shader.buffer(f16)
dst: shader.buffer_rw(f32)
d: shader.uniform(u32)
dv: shader.uniform(u32)
kc: shader.uniform(u32)
h: shader.uniform(u32)
hkv: shader.uniform(u32)
scale: shader.uniform(f32)
q_nb1: shader.uniform(u32)
q_nb2: shader.uniform(u32)
k_nb1: shader.uniform(u32)
k_nb2: shader.uniform(u32)
v_nb1: shader.uniform(u32)
v_nb2: shader.uniform(u32)
m_nb1: shader.uniform(u32)
d_nb1: shader.uniform(u32)
d_nb2: shader.uniform(u32)
qs: shader.workgroup(f32, 256)
scores: shader.workgroup(f32, 2048)
partial: shader.workgroup(f32, 32)
main: fn() {
let head = group_id().x
let iq = group_id().y
let t = local_id().x
let kvh = head / (self.h / self.hkv)
let qo = head * self.q_nb2 + iq * self.q_nb1
var i = t
while i < self.d {
self.qs[i] = self.q[qo + i]
i += self.workgroup_size
}
barrier()
// Each invocation keeps two output dimensions (Dv <= 256).
var acc0 = 0.0
var acc1 = 0.0
var m_run = -3.0e38
var l_run = 0.0
var base = u32(0)
while base < self.kc {
let n = min(self.chunk, self.kc - base)
var mx = -3.0e38
var j = t
while j < n {
let key = base + j
let mval = float(self.mask[iq * self.m_nb1 + key])
var s = -3.0e38
if mval > -65500.0 {
let ko = kvh * self.k_nb2 + key * self.k_nb1
var dot = 0.0
for c in 0..self.d {
dot += self.qs[c] * float(self.k[ko + c])
}
s = dot * self.scale + mval
}
self.scores[j] = s
mx = max(mx, s)
j += self.workgroup_size
}
mx = self.block_max(mx)
let m_new = max(m_run, mx)
let corr = exp(m_run - m_new)
var sum = 0.0
j = t
while j < n {
let p = exp(self.scores[j] - m_new)
self.scores[j] = p
sum += p
j += self.workgroup_size
}
sum = self.block_sum(sum)
barrier()
acc0 = acc0 * corr
acc1 = acc1 * corr
let c0 = t
let c1 = t + self.workgroup_size
for jj in 0..n {
let p = self.scores[jj]
let vo = kvh * self.v_nb2 + (base + jj) * self.v_nb1
if c0 < self.dv {
acc0 += p * float(self.v[vo + c0])
}
if c1 < self.dv {
acc1 += p * float(self.v[vo + c1])
}
}
l_run = l_run * corr + sum
m_run = m_new
barrier()
base += self.chunk
}
var inv = 0.0
if l_run > 0.0 {
inv = 1.0 / l_run
}
let out = iq * self.d_nb2 + head * self.d_nb1
if t < self.dv {
self.dst[out + t] = acc0 * inv
}
if t + self.workgroup_size < self.dv {
self.dst[out + t + self.workgroup_size] = acc1 * inv
}
}
block_sum: fn(v) {
let s = subgroup_add(v)
if subgroup_lane() == 0 {
self.partial[subgroup_id()] = s
}
barrier()
var total = 0.0
for k in 0..self.workgroup_size / subgroup_size() {
total += self.partial[k]
}
barrier()
return total
}
block_max: fn(v) {
let s = subgroup_max(v)
if subgroup_lane() == 0 {
self.partial[subgroup_id()] = s
}
barrier()
var total = -3.0e38
for k in 0..self.workgroup_size / subgroup_size() {
total = max(total, self.partial[k])
}
barrier()
return total
}
}
// ---- quantised matrix x vector (see `gemv` above) -------------------
// Activations to biased int8 per 32 (+ d and sum q per block): one
// invocation per block.
quantize_q8: {
workgroup_size: 64
x: shader.buffer(vec4f)
xq: shader.buffer_rw(u32)
xs: shader.buffer_rw(vec2f)
blocks: shader.uniform(u32)
k: shader.uniform(u32)
x_col: shader.uniform(u32)
main: fn() {
let b = global_id().x
if b < self.blocks {
let per_col = self.k / 32
let col = b / per_col
let at = (col * self.x_col + (b % per_col) * 32) / 4
var amax = 0.0
for i in 0..8 {
let v = abs(self.x[at + i])
amax = max(amax, max(max(v.x, v.y), max(v.z, v.w)))
}
let d = amax / 127.0
var inv = 0.0
if amax > 0.0 {
inv = 1.0 / d
}
var sum = 0
for i in 0..8 {
let v = self.x[at + i] * inv
let q = sign(v) * floor(abs(v) + 0.5)
sum += i32(q.x + q.y + q.z + q.w)
let u = q + 128.0
self.xq[b * 8 + i] = u32(u.x) | (u32(u.y) << 8) | (u32(u.z) << 16) | (u32(u.w) << 24)
}
self.xs[b] = vec2(d, float(sum))
}
}
}
gemv_q5k: gemv_k{
unit_bytes: 176
qs_at: 48
high_bit: 1
}
gemv_q4k: gemv_k{
unit_bytes: 144
qs_at: 16
high_bit: 0
}
// q6_K, repacked at upload into 224-byte blocks (the 210 bytes, then
// padding) so every part is 16-aligned: ql at 0, qh at 128, the int8
// scales at 192, f16 d at 208. Eight lanes per unit; lane p takes 16
// consecutive l of half n = p / 4: from one 16-byte ql load the low
// nibbles (group g = (p / 2) % 2) and high nibbles (group g + 2), the
// two high bits each from one 16-byte qh load. Codes are q + 32.
gemv_q6k: gemv_k{
unit_bytes: 224
main: fn() {
let lane = local_id().x % 32
let row = group_id().x * (self.workgroup_size / 32) + local_id().x / 32
if row < self.n {
let units = self.k / 256
let slot = lane >> 3
let part = lane & 7
let half = part >> 2
let g = (part >> 1) & 1
let l = (part & 1) * 16
for col in 0..self.m {
var acc = 0.0
var u = slot
while u < units {
let ub = col * self.k + u * 256 + half * 128
let x_lo = self.xq4[(ub + g * 32 + l) >> 4]
let x_hi = self.xq4[(ub + (g + 2) * 32 + l) >> 4]
let xd_lo = self.xs[(ub + g * 32) / 32].x
let xd_hi = self.xs[(ub + (g + 2) * 32) / 32].x
acc += self.unit_part(row * self.w_row + u * 224, part, x_lo, x_hi, vec4(xd_lo, 0.0, xd_hi, 0.0))
u += 4
}
let total = self.lane_sum(acc)
if lane == 0 {
self.dst[col * self.d_col + row] = total
}
}
}
}
unit_part: fn(bo, part, x_lo, x_hi, xd) {
let b16 = bo >> 4
let half = part >> 2
let g = (part >> 1) & 1
let ql = self.w4[b16 + half * 4 + ((part >> 1) & 1) * 2 + (part & 1)]
let qh = self.w4[b16 + 8 + half * 2 + (part & 1)]
let tail = self.w4[b16 + 12]
let d = unpack_half2(self.w4[b16 + 13].x & u32(65535)).x
let m4 = vec4u(u32(252645135), u32(252645135), u32(252645135), u32(252645135))
let m2 = vec4u(u32(50529027), u32(50529027), u32(50529027), u32(50529027))
let sh = g * 2
let lo = (ql & m4) | (((qh >> vec4u(sh, sh, sh, sh)) & m2) << vec4u(4, 4, 4, 4))
let hi = ((ql >> vec4u(4, 4, 4, 4)) & m4) | (((qh >> vec4u(sh + 4, sh + 4, sh + 4, sh + 4)) & m2) << vec4u(4, 4, 4, 4))
let sub = part & 1
let sc_lo = self.sbyte(tail, half * 8 + sub + 2 * g)
let sc_hi = self.sbyte(tail, half * 8 + sub + 2 * (g + 2))
let v_lo = self.dot16(lo, x_lo) - 32 * self.sum16(x_lo)
let v_hi = self.dot16(hi, x_hi) - 32 * self.sum16(x_hi)
return d * (sc_lo * xd.x * float(v_lo) + sc_hi * xd.z * float(v_hi))
}
// Signed byte `j` of a 16-byte value, as a float.
sbyte: fn(v, j) {
var w = v.w
if j < 4 {
w = v.x
} else if j < 8 {
w = v.y
} else if j < 12 {
w = v.z
}
let b = (w >> ((j & 3) * 8)) & 255
if b > 127 {
return float(b) - 256.0
}
return float(b)
}
}
// q8_0, repacked at upload: per 8 blocks (one 256-value unit) the eight
// f16 d (16 bytes), then the eight blocks' 32 int8 each. Lane p of the
// unit's eight takes block p: two 16-byte loads; sign bits flipped make
// the weights biased unsigned codes too.
gemv_q80: gemv_k{
unit_bytes: 272
main: fn() {
let lane = local_id().x % 32
let row = group_id().x * (self.workgroup_size / 32) + local_id().x / 32
if row < self.n {
let units = self.k / 256
let slot = lane >> 3
let part = lane & 7
for col in 0..self.m {
var acc = 0.0
var u = slot
while u < units {
let xb = col * self.k + u * 256 + part * 32
let x_a = self.xq4[xb >> 4]
let x_b = self.xq4[(xb >> 4) + 1]
let xd = self.xs[xb / 32].x
acc += self.unit_part(row * self.w_row + u * 272, part, x_a, x_b, vec4(xd, 0.0, 0.0, 0.0))
u += 4
}
let total = self.lane_sum(acc)
if lane == 0 {
self.dst[col * self.d_col + row] = total
}
}
}
}
unit_part: fn(bo, part, x_a, x_b, xd) {
let b16 = bo >> 4
let ds = self.w4[b16]
var dw = ds.w
if part < 2 {
dw = ds.x
} else if part < 4 {
dw = ds.y
} else if part < 6 {
dw = ds.z
}
let d = unpack_half2(dw).x
var d2 = d
if (part & 1) == 1 {
d2 = unpack_half2(dw).y
}
let flip = vec4u(u32(2155905152), u32(2155905152), u32(2155905152), u32(2155905152))
let wa = self.w4[b16 + 1 + part * 2] ^ flip
let wb = self.w4[b16 + 2 + part * 2] ^ flip
// sum (W - 128) q = sum W q' - 128 sum W - 128 sum q (q' = q + 128)
let ones = u32(16843009)
let raw = self.dot16raw(wa, x_a) + self.dot16raw(wb, x_b)
let wsum = self.dot16raw(wa, vec4u(ones, ones, ones, ones)) + self.dot16raw(wb, vec4u(ones, ones, ones, ones))
let v = i32(raw) - 128 * i32(wsum) - 128 * (self.sum16(x_a) + self.sum16(x_b))
return d2 * xd.x * float(v)
}
dot16raw: fn(a, x) {
return dot4_u8(a.x, x.x) + dot4_u8(a.y, x.y) + dot4_u8(a.z, x.z) + dot4_u8(a.w, x.w)
}
}
// word: 1 row/32 lanes, 256 threads. Per 16 payload bytes: Q4/Q5
// 18 dot4 including x sums, Q6 18, Q8 8; integer counts above.
gemv_q4k_word: gemv_decode{format: 4 unit_bytes: 144}
gemv_q5k_word: gemv_decode{}
gemv_q6k_word: gemv_decode{format: 6 unit_bytes: 224}
gemv_q80_word: gemv_decode{format: 8 unit_bytes: 272}
// rows2: 2 rows/32 lanes. Same weight ops; x loads halved and Q4/Q5/Q6
// amortises to 14 dot4/16 payload bytes (10 weight + 8/2 activation).
gemv_q4k_rows2: gemv_decode{format: 4 unit_bytes: 144 rpg: 2}
gemv_q5k_rows2: gemv_decode{rpg: 2}
gemv_q6k_rows2: gemv_decode{format: 6 unit_bytes: 224 rpg: 2}
gemv_q80_rows2: gemv_decode{format: 8 unit_bytes: 272 rpg: 2}
// rows4: 4 rows/32 lanes. Same weight ops; x loads quartered and Q4/Q5/Q6
// amortises to 12 dot4/16 payload bytes (10 weight + 8/4 activation).
gemv_q4k_rows4: gemv_decode{format: 4 unit_bytes: 144 rpg: 4}
gemv_q5k_rows4: gemv_decode{rpg: 4}
gemv_q6k_rows4: gemv_decode{format: 6 unit_bytes: 224 rpg: 4}
gemv_q80_rows4: gemv_decode{format: 8 unit_bytes: 272 rpg: 4}
// wide: 1 row/32 lanes, 512 threads; word's 18/18/18/8 dot4 counts
// per 16 payload bytes, twice as many independent rows per workgroup.
gemv_q4k_wide: gemv_decode{format: 4 unit_bytes: 144 workgroup_size: 512}
gemv_q5k_wide: gemv_decode{workgroup_size: 512}
gemv_q6k_wide: gemv_decode{format: 6 unit_bytes: 224 workgroup_size: 512}
gemv_q80_wide: gemv_decode{format: 8 unit_bytes: 272 workgroup_size: 512}
// lanes64: word's 18/18/18/8 dot4 per 16 payload bytes, eight units
// in flight per row; one subgroup_add per row at subgroup size 64.
gemv_q4k_lanes64: gemv_decode{format: 4 unit_bytes: 144 row_lanes: 64}
gemv_q5k_lanes64: gemv_decode{row_lanes: 64}
gemv_q6k_lanes64: gemv_decode{format: 6 unit_bytes: 224 row_lanes: 64}
gemv_q80_lanes64: gemv_decode{format: 8 unit_bytes: 272 row_lanes: 64}
// lanes128: word's 18/18/18/8 dot4 per 16 payload bytes, sixteen
// units in flight; one subgroup_add per row at subgroup size 128.
gemv_q4k_lanes128: gemv_decode{format: 4 unit_bytes: 144 row_lanes: 128}
gemv_q5k_lanes128: gemv_decode{row_lanes: 128}
gemv_q6k_lanes128: gemv_decode{format: 6 unit_bytes: 224 row_lanes: 128}
gemv_q80_lanes128: gemv_decode{format: 8 unit_bytes: 272 row_lanes: 128}
// ---- quantised row lookups and prefill matmuls (see `deq`) -------
// 64x64 output tile, 4x8 results per invocation, k steps of 16 (on the
// phone faster than 4x4 results, k 32 or 64).
gemm_q4k_r8k16: gemm_wide_16{format: 4 unit_bytes: 144}
gemm_q5k_r8k16: gemm_wide_16{}
gemm_q6k_r8k16: gemm_wide_16{format: 6 unit_bytes: 224}
gemm_q80_r8k16: gemm_wide_16{format: 8 unit_bytes: 272}
get_rows_q6k: deq_q6k{
workgroup_size: 256
rows: shader.buffer(i32)
dst: shader.buffer_rw(f32)
ne0: shader.uniform(u32)
w_row: shader.uniform(u32)
dst_row: shader.uniform(u32)
main: rows_main
}
gemm_q6k: deq_q6k{
workgroup_size: 128
xq: shader.buffer(u32)
xs: shader.buffer(vec2f)
dst: shader.buffer_rw(f32)
k: shader.uniform(u32)
n: shader.uniform(u32)
m: shader.uniform(u32)
w_row: shader.uniform(u32)
d_col: shader.uniform(u32)
wq: shader.workgroup(u32, 256)
wm: shader.workgroup(vec4f, 32)
xq_s: shader.workgroup(u32, 256)
xd_s: shader.workgroup(f32, 32)
main: gemm_main
}
get_rows_q5k: deq_q5k{
workgroup_size: 256
rows: shader.buffer(i32)
dst: shader.buffer_rw(f32)
ne0: shader.uniform(u32)
w_row: shader.uniform(u32)
dst_row: shader.uniform(u32)
main: rows_main
}
gemm_q5k: deq_q5k{
workgroup_size: 128
xq: shader.buffer(u32)
xs: shader.buffer(vec2f)
dst: shader.buffer_rw(f32)
k: shader.uniform(u32)
n: shader.uniform(u32)
m: shader.uniform(u32)
w_row: shader.uniform(u32)
d_col: shader.uniform(u32)
wq: shader.workgroup(u32, 256)
wm: shader.workgroup(vec4f, 32)
xq_s: shader.workgroup(u32, 256)
xd_s: shader.workgroup(f32, 32)
main: gemm_main
}
get_rows_q4k: deq_q4k{
workgroup_size: 256
rows: shader.buffer(i32)
dst: shader.buffer_rw(f32)
ne0: shader.uniform(u32)
w_row: shader.uniform(u32)
dst_row: shader.uniform(u32)
main: rows_main
}
gemm_q4k: deq_q4k{
workgroup_size: 128
xq: shader.buffer(u32)
xs: shader.buffer(vec2f)
dst: shader.buffer_rw(f32)
k: shader.uniform(u32)
n: shader.uniform(u32)
m: shader.uniform(u32)
w_row: shader.uniform(u32)
d_col: shader.uniform(u32)
wq: shader.workgroup(u32, 256)
wm: shader.workgroup(vec4f, 32)
xq_s: shader.workgroup(u32, 256)
xd_s: shader.workgroup(f32, 32)
main: gemm_main
}
get_rows_q80: deq_q80{
workgroup_size: 256
rows: shader.buffer(i32)
dst: shader.buffer_rw(f32)
ne0: shader.uniform(u32)
w_row: shader.uniform(u32)
dst_row: shader.uniform(u32)
main: rows_main
}
gemm_q80: deq_q80{
workgroup_size: 128
xq: shader.buffer(u32)
xs: shader.buffer(vec2f)
dst: shader.buffer_rw(f32)
k: shader.uniform(u32)
n: shader.uniform(u32)
m: shader.uniform(u32)
w_row: shader.uniform(u32)
d_col: shader.uniform(u32)
wq: shader.workgroup(u32, 256)
wm: shader.workgroup(vec4f, 32)
xq_s: shader.workgroup(u32, 256)
xd_s: shader.workgroup(f32, 32)
main: gemm_main
}
}