makepad/libs/ai/vulkan/kernels/kokoro.splash
Admin 74faf62ef1 ai: the AI hub gives way to the UI, smaller models, vision and speech
The AI hub serves local models on the LAN and now yields GPU memory to the
UI (shrinks the chat context, then unloads; grows back after; never loads
into the UI reserve), groups models by type, and reports each model's
memory split. LLM decode on Vulkan with Splash kernels, one shared GPU
device per process, vision on chat turns, an on-device text detector,
music models and stems.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-13 12:26:21 +02:00

372 lines
14 KiB
Text

// Channel-major activations, with each row padded to four floats. The
// convolution GEMM consumes patches [input * taps, time] and resident
// weights [output, input * taps]. A 32x32x32 tile uses 8 KiB shared memory.
// Vulkan guarantees at least 65535 groups per dimension. Split large flat
// dispatches over two dimensions instead of imposing an activation limit.
let flat = {
workgroup_size: 128
index: fn() { return (group_id().y * 65535 + group_id().x) * 128 + local_id().x }
}
{
kokoro_gemm: {
workgroup_size: 128
a: shader.buffer(vec4f)
b: shader.buffer(vec4f)
bias: shader.buffer(f32)
dst: shader.buffer_rw(vec4f)
m: shader.uniform(u32)
n: shader.uniform(u32)
k: shader.uniform(u32)
astride: shader.uniform(u32)
bstride: shader.uniform(u32)
has_bias: shader.uniform(u32)
aa: shader.workgroup(f32, 1024)
bb: shader.workgroup(vec4f, 256)
main: fn() {
let t = local_id().x
let row = t / 8
let col = t % 8
let mr = group_id().y * 32
let nc = group_id().x * 8
var acc0 = vec4f(0.0)
var acc1 = vec4f(0.0)
var base = u32(0)
while base < self.k {
for half in 0..2 {
let r = row + half * 16
var av = vec4f(0.0)
var bv = vec4f(0.0)
if mr + r < self.m && base + col * 4 < self.k {
av = self.a[(mr + r) * self.astride / 4 + base / 4 + col]
}
if base + r < self.k && (nc + col) * 4 < self.n {
bv = self.b[(base + r) * self.bstride / 4 + nc + col]
}
for j in 0..4 { self.aa[r * 32 + col * 4 + j] = av[j] }
self.bb[r * 8 + col] = bv
}
barrier()
for j in 0..32 {
let bv = self.bb[j * 8 + col]
acc0 += self.aa[row * 32 + j] * bv
acc1 += self.aa[(row + 16) * 32 + j] * bv
}
barrier()
base += 32
}
if (nc + col) * 4 < self.n {
if mr + row < self.m {
var bv = 0.0
if self.has_bias != 0 { bv = self.bias[mr + row] }
self.dst[(mr + row) * self.bstride / 4 + nc + col] = acc0 + bv
}
if mr + row + 16 < self.m {
var bv = 0.0
if self.has_bias != 0 { bv = self.bias[mr + row + 16] }
self.dst[(mr + row + 16) * self.bstride / 4 + nc + col] = acc1 + bv
}
}
}
}
// Four adjacent time positions per invocation. Stride-one interiors use
// one aligned vector load when possible, including dilated convolutions.
kokoro_im2col: flat{
x: shader.buffer(f32)
x4: shader.buffer(vec4f)
dst: shader.buffer_rw(vec4f)
ic: shader.uniform(u32)
iw: shader.uniform(u32)
xs: shader.uniform(u32)
ow: shader.uniform(u32)
os: shader.uniform(u32)
kw: shader.uniform(u32)
stride: shader.uniform(u32)
pad: shader.uniform(u32)
dilation: shader.uniform(u32)
main: fn() {
let i = self.index()
let time = (i % (self.os / 4)) * 4
let taprow = i / (self.os / 4)
if taprow < self.ic * self.kw {
let channel = taprow / self.kw
let tap = taprow % self.kw
var v = vec4f(0.0)
let start = i32(time * self.stride + tap * self.dilation) - i32(self.pad)
if self.stride == 1 && start >= 0 && start + 3 < i32(self.iw) && start % 4 == 0 && time + 3 < self.ow {
v = self.x4[(channel * self.xs + u32(start)) / 4]
} else {
for j in 0..4 {
let pos = start + i32(j * self.stride)
if time + j < self.ow && pos >= 0 && pos < i32(self.iw) {
v[j] = self.x[channel * self.xs + u32(pos)]
}
}
}
self.dst[i] = v
}
}
}
// Fold GEMM's [output * tap, input_time] into the upsampled sequence.
// Each output has one writer and visits only contributing taps.
kokoro_overlap: flat{
x: shader.buffer(f32)
bias: shader.buffer(f32)
dst: shader.buffer_rw(vec4f)
channels: shader.uniform(u32)
iw: shader.uniform(u32)
xs: shader.uniform(u32)
ow: shader.uniform(u32)
os: shader.uniform(u32)
kw: shader.uniform(u32)
stride: shader.uniform(u32)
pad: shader.uniform(u32)
has_bias: shader.uniform(u32)
main: fn() {
let i = self.index()
let channel = i / (self.os / 4)
let time = (i % (self.os / 4)) * 4
if channel < self.channels {
var value = vec4f(0.0)
if self.has_bias != 0 { value = vec4f(self.bias[channel]) }
for j in 0..4 {
let pos = time + j + self.pad
var tap = pos % self.stride
while tap < self.kw {
if time + j < self.ow && pos >= tap && (pos - tap) / self.stride < self.iw {
value[j] += self.x[(channel * self.kw + tap) * self.xs + (pos - tap) / self.stride]
}
tap += self.stride
}
}
self.dst[i] = value
}
}
}
// op: copy/scale, scaled add, leaky rectifier, Snake, or GELU.
kokoro_element: flat{
x: shader.buffer(vec4f)
b: shader.buffer(vec4f)
alpha: shader.buffer(f32)
dst: shader.buffer_rw(vec4f)
count: shader.uniform(u32)
stride: shader.uniform(u32)
op: shader.uniform(u32)
scale: shader.uniform(f32)
main: fn() {
let i = self.index()
if i < self.count / 4 {
let v = self.x[i]
var out = v * self.scale
if self.op == 1 { out = (v + self.b[i]) * self.scale }
if self.op == 2 { out = max(v, vec4f(0.0)) + min(v, vec4f(0.0)) * self.scale }
if self.op == 3 {
let a = self.alpha[i * 4 / self.stride]
let s = sin(v * a)
out = v + (1.0 / a) * s * s
}
if self.op == 4 {
out = 0.5 * v * (1.0 + tanh(0.7978845608 * (v + 0.044715 * v * v * v)))
}
self.dst[i] = out
}
}
}
// Two-pass variance over the logical row, excluding its padding. Affine
// mode 1 is per column; mode 2 is AdaIN's [gamma | beta] per channel.
// Fuse the following activation so each conditioned norm writes once.
kokoro_norm: {
workgroup_size: 128
x: shader.buffer(vec4f)
affine: shader.buffer(f32)
beta: shader.buffer(f32)
alpha: shader.buffer(f32)
dst: shader.buffer_rw(vec4f)
cols: shader.uniform(u32)
stride: shader.uniform(u32)
rows: shader.uniform(u32)
mode: shader.uniform(u32)
activation: shader.uniform(u32)
eps: shader.uniform(f32)
slope: shader.uniform(f32)
partial: shader.workgroup(f32, 128)
total: fn(v) {
let sum = subgroup_add(v)
if subgroup_lane() == 0 { self.partial[subgroup_id()] = sum }
barrier()
var result = 0.0
for j in 0..(self.workgroup_size + subgroup_size() - 1) / subgroup_size() {
result += self.partial[j]
}
barrier()
return result
}
main: fn() {
let row = group_id().x
let lane = local_id().x
let offset = row * self.stride / 4
var col = lane * 4
var sum = 0.0
while col < self.cols {
let v = self.x[offset + col / 4]
for j in 0..4 { if col + j < self.cols { sum += v[j] } }
col += self.workgroup_size * 4
}
let mean = self.total(sum) / float(self.cols)
col = lane * 4
var variance = 0.0
while col < self.cols {
let delta = self.x[offset + col / 4] - mean
for j in 0..4 { if col + j < self.cols { variance += delta[j] * delta[j] } }
col += self.workgroup_size * 4
}
let inv = 1.0 / sqrt(self.total(variance) / float(self.cols) + self.eps)
col = lane * 4
while col < self.cols {
var value = (self.x[offset + col / 4] - mean) * inv
if self.mode == 1 {
for j in 0..4 {
if col + j < self.cols { value[j] = value[j] * self.affine[col + j] + self.beta[col + j] }
}
}
if self.mode == 2 { value = value * (1.0 + self.affine[row]) + self.affine[self.rows + row] }
if self.activation == 1 { value = max(value, vec4f(0.0)) + min(value, vec4f(0.0)) * self.slope }
if self.activation == 2 {
let a = self.alpha[row]
let s = sin(value * a)
value += (1.0 / a) * s * s
}
for j in 0..4 { if col + j >= self.cols { value[j] = 0.0 } }
self.dst[offset + col / 4] = value
col += self.workgroup_size * 4
}
}
}
// op 0 transpose, 1 nearest repeat, 2 left reflection padding.
kokoro_layout: flat{
x: shader.buffer(f32)
dst: shader.buffer_rw(vec4f)
rows: shader.uniform(u32)
cols: shader.uniform(u32)
xs: shader.uniform(u32)
os: shader.uniform(u32)
ow: shader.uniform(u32)
op: shader.uniform(u32)
factor: shader.uniform(u32)
main: fn() {
let i = self.index()
let row = i / (self.os / 4)
let col = (i % (self.os / 4)) * 4
if row < self.rows {
var value = vec4f(0.0)
for j in 0..4 {
if col + j < self.ow {
if self.op == 0 { value[j] = self.x[(col + j) * self.xs + row] }
if self.op == 1 { value[j] = self.x[row * self.xs + (col + j) / self.factor] }
if self.op == 2 {
var src = i32(col + j) - i32(self.factor)
if src < 0 { src = -src }
value[j] = self.x[row * self.xs + u32(src)]
}
}
}
self.dst[i] = value
}
}
}
kokoro_depthwise: flat{
x: shader.buffer(f32)
w: shader.buffer(f32)
bias: shader.buffer(f32)
dst: shader.buffer_rw(vec4f)
channels: shader.uniform(u32)
iw: shader.uniform(u32)
xs: shader.uniform(u32)
ow: shader.uniform(u32)
os: shader.uniform(u32)
kw: shader.uniform(u32)
stride: shader.uniform(u32)
pad: shader.uniform(u32)
main: fn() {
let i = self.index()
let channel = i / (self.os / 4)
let time = (i % (self.os / 4)) * 4
if channel < self.channels {
var value = vec4f(0.0)
for tap in 0..self.kw {
for j in 0..4 {
let src = i32(time + j + self.pad) - i32(tap)
if time + j < self.ow && src >= 0 && src % i32(self.stride) == 0 && src / i32(self.stride) < i32(self.iw) {
value[j] += self.x[channel * self.xs + u32(src) / self.stride] * self.w[channel * self.kw + tap]
}
}
}
self.dst[i] = value + self.bias[channel]
}
}
}
// One workgroup owns one recurrent direction for the entire sequence.
// Input projections are a preceding GEMM. Hidden state stays in shared
// memory and cell state in registers; there is no dispatch per frame.
// Kokoro's recurrent directions have 256 hidden units.
kokoro_lstm: {
workgroup_size: 256
projected: shader.buffer(f32)
w: shader.buffer(vec4f)
bias: shader.buffer(f32)
dst: shader.buffer_rw(f32)
hidden: shader.uniform(u32)
time: shader.uniform(u32)
stride: shader.uniform(u32)
ws: shader.uniform(u32)
reverse: shader.uniform(u32)
state: shader.workgroup(f32, 256)
main: fn() {
let lane = local_id().x
self.state[lane] = 0.0
var cell = 0.0
barrier()
for step in 0..self.time {
var time = step
if self.reverse != 0 { time = self.time - 1 - step }
var gates = vec4f(0.0)
if lane < self.hidden {
for gate in 0..4 {
let row = gate * self.hidden + lane
gates[gate] = self.projected[row * self.stride + time] + self.bias[row]
}
var c = u32(0)
while c < self.hidden {
let h = vec4f(self.state[c], self.state[c + 1], self.state[c + 2], self.state[c + 3])
for gate in 0..4 {
let row = gate * self.hidden + lane
gates[gate] += dot(self.w[(row * self.ws + c) / 4], h)
}
c += 4
}
}
// No lane may overwrite the previous state until every
// lane has completed the recurrent matrix product.
barrier()
if lane < self.hidden {
let input = 1.0 / (1.0 + exp(-gates.x))
let forget = 1.0 / (1.0 + exp(-gates.y))
let update = tanh(gates.z)
let output = 1.0 / (1.0 + exp(-gates.w))
cell = forget * cell + input * update
let h = output * tanh(cell)
self.state[lane] = h
self.dst[lane * self.stride + time] = h
}
barrier()
}
}
}
}