@audio/neural-separate 0.3.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/NOTICE +5 -3
- package/README.md +94 -10
- package/index.d.ts +11 -2
- package/package.json +6 -2
- package/roformer.js +396 -0
- package/scripts/compact.py +351 -0
- package/scripts/engine.mjs +58 -0
- package/scripts/export-roformer.py +481 -0
- package/scripts/export-scnet.py +6 -6
- package/scripts/reference.py +5 -2
- package/separate.js +144 -31
package/roformer.js
ADDED
|
@@ -0,0 +1,396 @@
|
|
|
1
|
+
// Mel-Band RoFormer on WebGPU, kernels of its own: the GPU form scripts/export-roformer.py writes (--engine), one 8 s
|
|
2
|
+
// segment a run, what its ONNX graph computes (47.8 dB from onnxruntime's CPU on the float32 graph: the float16 weights'
|
|
3
|
+
// rounding, as the float16 ONNX file's). Matrix products and attention on the GPU's matrix units through
|
|
4
|
+
// chromium_experimental_subgroup_matrix (Apple's simdgroup matrices: Chromium on Metal), float32 throughout: under the same
|
|
5
|
+
// load, 1.6 s a segment where onnxruntime-web's WebGPU takes 2.6 to 3.3 (an M4 Max). Attention in two passes over the
|
|
6
|
+
// keys (the rows' maxima, then the output in registers), never past 128 MiB a dispatch needs no binding beyond it.
|
|
7
|
+
// let rf = await engine(meta, bytes) // null where the GPU lacks subgroup matrices
|
|
8
|
+
// let { stems_spec } = await rf.run({ mix_spec }) // as an onnxruntime session: [1, 4, F, T] in and out
|
|
9
|
+
const TILE = 32
|
|
10
|
+
|
|
11
|
+
/** A device with what the kernels need, or null */
|
|
12
|
+
export async function gpu() {
|
|
13
|
+
let adapter = await globalThis.navigator?.gpu?.requestAdapter({ powerPreference: 'high-performance' }).catch(() => null)
|
|
14
|
+
if (!adapter || adapter.info?.isFallbackAdapter || !['subgroups', 'chromium-experimental-subgroup-matrix'].every(f => adapter.features.has(f))) return null
|
|
15
|
+
let cfg = [...(adapter.info.subgroupMatrixConfigs ?? [])]
|
|
16
|
+
if (!cfg.some(c => c.componentType === 'f32' && c.M === 8 && c.N === 8 && c.K === 8)) return null
|
|
17
|
+
return adapter.requestDevice({
|
|
18
|
+
requiredFeatures: ['subgroups', 'chromium-experimental-subgroup-matrix'],
|
|
19
|
+
requiredLimits: { maxComputeWorkgroupStorageSize: 32768, maxStorageBufferBindingSize: adapter.limits.maxStorageBufferBindingSize, maxBufferSize: adapter.limits.maxBufferSize },
|
|
20
|
+
}).catch(() => null)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** The engine as an onnxruntime-shaped session ({ inputs, outputs, run(feeds), free() }), or null without such a GPU */
|
|
24
|
+
export async function engine(meta, bytes, device) {
|
|
25
|
+
device ??= await gpu()
|
|
26
|
+
if (!device) return null
|
|
27
|
+
let rf = await roformer(device, meta, bytes), { freqs: F, frames: T } = meta
|
|
28
|
+
return {
|
|
29
|
+
inputs: [{ name: 'mix_spec', dims: [1, 4, F, T], type: 'float32' }], outputs: [{ name: 'stems_spec', dims: [1, 4, F, T], type: 'float32' }],
|
|
30
|
+
backend: 'roformer',
|
|
31
|
+
async run(feeds) { return { stems_spec: { data: await rf.run(feeds.mix_spec.data), dims: [1, 4, F, T], type: 'float32' } } },
|
|
32
|
+
free() { device.destroy() },
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export default async function roformer(device, meta, bytes) {
|
|
37
|
+
const { frames: T, freqs: F, channels: S, dim: D, heads: H, depth, bands: B, groups } = meta
|
|
38
|
+
const DH = 64, FF = 1536, MH = 1536, Tp = Math.ceil(T / TILE) * TILE, QKV = 3 * H * DH, QKVG = Math.ceil((QKV + H) / TILE) * TILE
|
|
39
|
+
const COLS = F * S * 2, LT = Math.ceil(T / 16) * 16, LF = Math.ceil(B / 16) * 16
|
|
40
|
+
const pad16 = w => Math.ceil(w / 16) * 16
|
|
41
|
+
const G = groups.map(([b0, b1, W]) => ({ b0, b1, n: b1 - b0, W, Wp: pad16(W) }))
|
|
42
|
+
|
|
43
|
+
// ---------------------------------------------------------------- tensors
|
|
44
|
+
const view = n => {
|
|
45
|
+
const t = meta.tensors[n], len = t.shape.reduce((a, b) => a * b, 1)
|
|
46
|
+
return t.type === 'float16' ? new Float16Array(bytes.buffer, bytes.byteOffset + t.offset, len)
|
|
47
|
+
: t.type === 'int32' ? new Int32Array(bytes.buffer, bytes.byteOffset + t.offset, len)
|
|
48
|
+
: new Float32Array(bytes.buffer, bytes.byteOffset + t.offset, len)
|
|
49
|
+
}
|
|
50
|
+
const usage = GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST | GPUBufferUsage.COPY_SRC
|
|
51
|
+
const buf = (n, type = 'f32') => device.createBuffer({ size: Math.max(16, n * 4), usage })
|
|
52
|
+
const upload = data => { const b = device.createBuffer({ size: Math.max(16, data.byteLength), usage, mappedAtCreation: true }); new data.constructor(b.getMappedRange()).set(data); b.unmap(); return b }
|
|
53
|
+
const f32 = a => a instanceof Float32Array ? a : Float32Array.from(a)
|
|
54
|
+
// a weight [batch, K, N] to [batch, Kp, Np], zeros past (rows, cols)
|
|
55
|
+
const padded = (a, batch, K, N, Kp, Np, place = (k, n) => k * Np + n) => {
|
|
56
|
+
const o = new Float32Array(batch * Kp * Np)
|
|
57
|
+
for (let b = 0; b < batch; b++) for (let k = 0; k < K; k++) for (let n = 0; n < N; n++) o[b * Kp * Np + place(k, n)] = a[(b * K + k) * N + n]
|
|
58
|
+
return o
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const W = { split: [], splitB: [], splitIdx: [], m2: [], m2B: [] }
|
|
62
|
+
for (const [g, gr] of G.entries()) {
|
|
63
|
+
W.split.push(upload(padded(view(`split_w.${g}`), gr.n, gr.W, D, gr.Wp, D)))
|
|
64
|
+
W.splitB.push(upload(f32(view(`split_b.${g}`))))
|
|
65
|
+
const idx = view(`si${g}`), pi = new Int32Array(gr.n * gr.Wp).fill(COLS)
|
|
66
|
+
for (let j = 0; j < gr.n; j++) for (let e = 0; e < gr.W; e++) pi[j * gr.Wp + e] = idx[j * gr.W + e]
|
|
67
|
+
W.splitIdx.push(upload(pi))
|
|
68
|
+
// the last mask layer: its value half to [0, W), its gate half to [Wp, Wp + W)
|
|
69
|
+
const w = view(`m2_w.${g}`), b = view(`m2_b.${g}`), Wp = gr.Wp, wo = new Float32Array(gr.n * MH * 2 * Wp), bo = new Float32Array(gr.n * 2 * Wp)
|
|
70
|
+
for (let j = 0; j < gr.n; j++) {
|
|
71
|
+
for (let k = 0; k < MH; k++) for (let e = 0; e < gr.W; e++) {
|
|
72
|
+
wo[(j * MH + k) * 2 * Wp + e] = w[(j * MH + k) * 2 * gr.W + e]
|
|
73
|
+
wo[(j * MH + k) * 2 * Wp + Wp + e] = w[(j * MH + k) * 2 * gr.W + gr.W + e]
|
|
74
|
+
}
|
|
75
|
+
for (let e = 0; e < gr.W; e++) { bo[j * 2 * Wp + e] = b[j * 2 * gr.W + e]; bo[j * 2 * Wp + Wp + e] = b[j * 2 * gr.W + gr.W + e] }
|
|
76
|
+
}
|
|
77
|
+
W.m2.push(upload(wo)); W.m2B.push(upload(bo))
|
|
78
|
+
}
|
|
79
|
+
W.mw = [0, 1].map(i => upload(f32(view(`mw.${i}`)))); W.mb = [0, 1].map(i => upload(f32(view(`mb.${i}`))))
|
|
80
|
+
W.blocks = []
|
|
81
|
+
for (let d = 0; d < depth; d++) for (let k = 0; k < 2; k++) {
|
|
82
|
+
const p = `blocks.${d}.${k}.`, a = p + 'layers.0.0.', f = p + 'layers.0.1.'
|
|
83
|
+
W.blocks.push({
|
|
84
|
+
out: upload(f32(view(p + 'out'))),
|
|
85
|
+
qkvg: upload(padded(view(a + 'qkvg'), 1, D, QKV + H, D, QKVG)), gb: upload(f32(view(a + 'gb'))), wo: upload(f32(view(a + 'wo'))),
|
|
86
|
+
w1: upload(f32(view(f + 'w1'))), b1: upload(f32(view(f + 'b1'))), w2: upload(f32(view(f + 'w2'))), b2: upload(f32(view(f + 'b2'))),
|
|
87
|
+
})
|
|
88
|
+
}
|
|
89
|
+
const rope = k => { const c = view(`blocks.0.${k}.layers.0.0.cos`), s = view(`blocks.0.${k}.layers.0.0.sin`); return [upload(f32(c)), upload(f32(s))] }
|
|
90
|
+
const [cosT, sinT] = rope(0), [cosF, sinF] = rope(1)
|
|
91
|
+
// the masks' sources: per output column, two (base, stride: the group's Wp) into the GLU outputs, and its weight
|
|
92
|
+
const i1 = view('i1'), i2 = view('i2'), wt = view('wt'), gofs = [], cols = []
|
|
93
|
+
let mOff = 0, cOff = 0
|
|
94
|
+
for (const gr of G) { gofs.push(mOff); cols.push(cOff); mOff += gr.n * Tp * gr.Wp; cOff += gr.n * gr.W }
|
|
95
|
+
const src = c => {
|
|
96
|
+
if (c >= cOff) return [0, 0, 0]
|
|
97
|
+
let g = cols.findLastIndex(o => o <= c), gr = G[g], r = c - cols[g], j = Math.floor(r / gr.W), e = r % gr.W
|
|
98
|
+
return [gofs[g] + j * Tp * gr.Wp + e, gr.Wp, 1]
|
|
99
|
+
}
|
|
100
|
+
const map = new Uint32Array(COLS * 8) // per column: base1, stride1, on1, base2, stride2, on2, -, -
|
|
101
|
+
for (let o = 0; o < COLS; o++) map.set([...src(i1[o]), ...src(i2[o])], o * 8)
|
|
102
|
+
const maskMap = upload(map)
|
|
103
|
+
const maskWt = upload(Float32Array.from({ length: COLS }, (_, o) => wt[o]))
|
|
104
|
+
|
|
105
|
+
// ---------------------------------------------------------------- activations
|
|
106
|
+
const E = buf(Tp * (COLS + 1)), X = buf(B * Tp * D), XN = buf(B * Tp * D), Y = buf(B * Tp * QKVG)
|
|
107
|
+
const att = Math.max(B * H * LT, T * H * LF) * DH
|
|
108
|
+
const Qb = buf(att), Kb = buf(att), Vb = buf(att), Ob = buf(att), Gt = buf(B * Tp * H), Z = buf(B * Tp * H * DH)
|
|
109
|
+
const FFH = buf(B * Tp * FF), H2 = buf(B * Tp * MH), Mg = buf(mOff), OUT = buf(4 * F * T)
|
|
110
|
+
const splitA = G.map(gr => buf(gr.n * Tp * gr.Wp)), glu = G.map(gr => buf(gr.n * Tp * 2 * gr.Wp)), scratch = buf(4)
|
|
111
|
+
|
|
112
|
+
// ---------------------------------------------------------------- kernels
|
|
113
|
+
const pipes = new Map()
|
|
114
|
+
const pipe = (key, code) => pipes.get(key) ?? pipes.set(key, device.createComputePipeline({ layout: 'auto', compute: { module: device.createShaderModule({ code }), entryPoint: 'main' } })).get(key)
|
|
115
|
+
const uni = (...v) => { const b = device.createBuffer({ size: Math.ceil(v.length / 4) * 16, usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST }); device.queue.writeBuffer(b, 0, new Uint32Array(v.map(x => typeof x === 'number' && !Number.isInteger(x) ? new Uint32Array(new Float32Array([x]).buffer)[0] : x))); return b }
|
|
116
|
+
const steps = [] // [pipeline, bindings, [x, y, z]]
|
|
117
|
+
const step = (p, bindings, grid) => steps.push([p, device.createBindGroup({ layout: p.getBindGroupLayout(0), entries: bindings.map((b, i) => ({ binding: i, resource: { buffer: b } })) }), grid])
|
|
118
|
+
|
|
119
|
+
const HEAD = `enable subgroups;\nenable chromium_experimental_subgroup_matrix;\n`
|
|
120
|
+
// C[b] (M×N, row stride ldc, at c0 + b·sc) = A[b] (M×K, at a0 + b·sa) · Bm[b] (K×N, at b0 + b·sb), then the epilogue:
|
|
121
|
+
// ep: '' as is; 'b' + bias (at bias0 + b·sbias); then 'g' gelu or 't' tanh; 'r' C += it (a residual)
|
|
122
|
+
const MM = ep => pipe('mm' + ep, HEAD + `
|
|
123
|
+
@group(0) @binding(0) var<storage, read> A: array<f32>;
|
|
124
|
+
@group(0) @binding(1) var<storage, read> Bm: array<f32>;
|
|
125
|
+
@group(0) @binding(2) var<storage, read_write> C: array<f32>;
|
|
126
|
+
@group(0) @binding(3) var<storage, read> bias: array<f32>;
|
|
127
|
+
struct P { M: u32, N: u32, K: u32, a0: u32, sa: u32, b0: u32, sb: u32, c0: u32, sc: u32, ldc: u32, bias0: u32, sbias: u32 }
|
|
128
|
+
@group(0) @binding(4) var<uniform> p: P;
|
|
129
|
+
var<workgroup> tile: array<f32, 1024>;
|
|
130
|
+
@compute @workgroup_size(32)
|
|
131
|
+
fn main(@builtin(workgroup_id) wg: vec3u, @builtin(subgroup_invocation_id) lane: u32) {
|
|
132
|
+
_ = bias[0]; // bound whether read or not: the layout is the module's own
|
|
133
|
+
let r0 = wg.y * 32u; let n0 = wg.x * 32u; let b = wg.z;
|
|
134
|
+
let ab = p.a0 + b * p.sa + r0 * p.K; let bb = p.b0 + b * p.sb + n0;
|
|
135
|
+
${[0, 1, 2, 3].map(i => [0, 1, 2, 3].map(j => `var c${i}${j}: subgroup_matrix_result<f32, 8, 8>;`).join(' ')).join('\n ')}
|
|
136
|
+
for (var k = 0u; k < p.K; k += 8u) {
|
|
137
|
+
${[0, 1, 2, 3].map(i => `let a${i} = subgroupMatrixLoad<subgroup_matrix_left<f32, 8, 8>>(&A, ab + ${i * 8}u * p.K + k, false, p.K);`).join('\n ')}
|
|
138
|
+
${[0, 1, 2, 3].map(j => `let b${j} = subgroupMatrixLoad<subgroup_matrix_right<f32, 8, 8>>(&Bm, bb + k * p.N + ${j * 8}u, false, p.N);`).join('\n ')}
|
|
139
|
+
${[0, 1, 2, 3].map(i => [0, 1, 2, 3].map(j => `c${i}${j} = subgroupMatrixMultiplyAccumulate(a${i}, b${j}, c${i}${j});`).join(' ')).join('\n ')}
|
|
140
|
+
}
|
|
141
|
+
${[0, 1, 2, 3].map(i => [0, 1, 2, 3].map(j => `subgroupMatrixStore(&tile, ${i * 8 * 32 + j * 8}u, c${i}${j}, false, 32u);`).join(' ')).join('\n ')}
|
|
142
|
+
workgroupBarrier();
|
|
143
|
+
for (var e = lane; e < 1024u; e += 32u) {
|
|
144
|
+
let r = r0 + e / 32u; let n = n0 + e % 32u;
|
|
145
|
+
if (r < p.M && n < p.N) {
|
|
146
|
+
var v = tile[e];
|
|
147
|
+
${ep.includes('b') ? 'v += bias[p.bias0 + b * p.sbias + n];' : ''}
|
|
148
|
+
${ep.includes('g') ? 'v = 0.5 * v * (1.0 + erf_(v * 0.7071067811865476));' : ep.includes('t') ? 'v = tanh(v);' : ''}
|
|
149
|
+
let o = p.c0 + b * p.sc + r * p.ldc + n;
|
|
150
|
+
${ep.includes('r') ? 'C[o] += v;' : 'C[o] = v;'}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
fn erf_(x: f32) -> f32 { // Abramowitz & Stegun 7.1.26 is 1.5e-7; GELU's own float32 erf is as close
|
|
155
|
+
let t = 1.0 / (1.0 + 0.3275911 * abs(x));
|
|
156
|
+
let y = 1.0 - (((((1.061405429 * t - 1.453152027) * t) + 1.421413741) * t - 0.284496736) * t + 0.254829592) * t * exp(-x * x);
|
|
157
|
+
return select(-y, y, x >= 0.0);
|
|
158
|
+
}`)
|
|
159
|
+
const mm = (ep, A, Bm, C, bias, { M, N, K, batch = 1, a0 = 0, sa = 0, b0 = 0, sb = 0, c0 = 0, sc = 0, ldc = N, bias0 = 0, sbias = 0 }) =>
|
|
160
|
+
step(MM(ep), [A, Bm, C, bias ?? maskWt, uni(M, N, K, a0, sa, b0, sb, c0, sc, ldc, bias0, sbias)], [N / 32, M / 32, batch])
|
|
161
|
+
|
|
162
|
+
// rows of n: y = x / max(|x|, 1e-12), or in place, scaled by g (a vector): x = x / max(|x|, 1e-12) · g
|
|
163
|
+
const NORM = scaled => pipe('norm' + scaled, HEAD + `
|
|
164
|
+
@group(0) @binding(0) var<storage, read_write> x: array<f32>;
|
|
165
|
+
@group(0) @binding(1) var<storage, read_write> y: array<f32>;
|
|
166
|
+
@group(0) @binding(2) var<storage, read> g: array<f32>;
|
|
167
|
+
struct P { rows: u32, n: u32 }
|
|
168
|
+
@group(0) @binding(3) var<uniform> p: P;
|
|
169
|
+
@compute @workgroup_size(32)
|
|
170
|
+
fn main(@builtin(workgroup_id) wg: vec3u, @builtin(subgroup_invocation_id) lane: u32) {
|
|
171
|
+
_ = g[0]; _ = y[0];
|
|
172
|
+
let r = wg.x + wg.y * 65535u;
|
|
173
|
+
if (r >= p.rows) { return; }
|
|
174
|
+
var s = 0.0;
|
|
175
|
+
for (var i = lane; i < p.n; i += 32u) { let v = x[r * p.n + i]; s += v * v; }
|
|
176
|
+
let k = 1.0 / max(sqrt(subgroupAdd(s)), 1e-12);
|
|
177
|
+
for (var i = lane; i < p.n; i += 32u) { ${scaled ? 'x[r * p.n + i] = x[r * p.n + i] * k * g[i];' : 'y[r * p.n + i] = x[r * p.n + i] * k;'} }
|
|
178
|
+
}`)
|
|
179
|
+
const grid1 = n => [Math.min(n, 65535), Math.ceil(n / 65535), 1]
|
|
180
|
+
const norm = (x, y, rows, n, g) => step(NORM(!!g), [x, g ? scratch : y, g ?? maskWt, uni(rows, n)], grid1(rows))
|
|
181
|
+
|
|
182
|
+
// a group's band inputs: band j's columns of E at frame t (none past T), normalized: A[j, t, e]
|
|
183
|
+
const SPLIT = pipe('split', HEAD + `
|
|
184
|
+
@group(0) @binding(0) var<storage, read> E: array<f32>;
|
|
185
|
+
@group(0) @binding(1) var<storage, read> idx: array<i32>;
|
|
186
|
+
@group(0) @binding(2) var<storage, read_write> A: array<f32>;
|
|
187
|
+
struct P { Wp: u32, Tp: u32, T: u32, cols: u32 }
|
|
188
|
+
@group(0) @binding(3) var<uniform> p: P;
|
|
189
|
+
@compute @workgroup_size(32)
|
|
190
|
+
fn main(@builtin(workgroup_id) wg: vec3u, @builtin(subgroup_invocation_id) lane: u32) {
|
|
191
|
+
let t = wg.x; let j = wg.y;
|
|
192
|
+
var s = 0.0;
|
|
193
|
+
for (var e = lane; e < p.Wp; e += 32u) { let v = select(0.0, E[t * (p.cols + 1u) + u32(idx[j * p.Wp + e])], t < p.T); s += v * v; }
|
|
194
|
+
let k = 1.0 / max(sqrt(subgroupAdd(s)), 1e-12);
|
|
195
|
+
for (var e = lane; e < p.Wp; e += 32u) {
|
|
196
|
+
let v = select(0.0, E[t * (p.cols + 1u) + u32(idx[j * p.Wp + e])], t < p.T);
|
|
197
|
+
A[(j * p.Tp + t) * p.Wp + e] = v * k;
|
|
198
|
+
}
|
|
199
|
+
}`)
|
|
200
|
+
|
|
201
|
+
// q, k, v of each head to sequences, the rotation on q and k, the gates: token r = band·Tp + t; over time a sequence is
|
|
202
|
+
// a band, its positions frames (n T, padded to L); over frequency a frame, its positions bands (n B, padded to L)
|
|
203
|
+
const ROPE = pipe('rope', HEAD + `
|
|
204
|
+
@group(0) @binding(0) var<storage, read> Yq: array<f32>;
|
|
205
|
+
@group(0) @binding(1) var<storage, read_write> Q: array<f32>;
|
|
206
|
+
@group(0) @binding(2) var<storage, read_write> K: array<f32>;
|
|
207
|
+
@group(0) @binding(3) var<storage, read_write> V: array<f32>;
|
|
208
|
+
@group(0) @binding(4) var<storage, read_write> Gt: array<f32>;
|
|
209
|
+
@group(0) @binding(5) var<storage, read> cs: array<f32>;
|
|
210
|
+
@group(0) @binding(6) var<storage, read> sn: array<f32>;
|
|
211
|
+
@group(0) @binding(7) var<storage, read> gb: array<f32>;
|
|
212
|
+
struct P { time: u32, Tp: u32, T: u32, L: u32, ld: u32 }
|
|
213
|
+
@group(0) @binding(8) var<uniform> p: P;
|
|
214
|
+
@compute @workgroup_size(64)
|
|
215
|
+
fn main(@builtin(workgroup_id) wg: vec3u, @builtin(local_invocation_id) li: vec3u) {
|
|
216
|
+
let t = wg.x; let band = wg.y; let h = wg.z; let d = li.x;
|
|
217
|
+
if (t >= p.T) { return; }
|
|
218
|
+
let r = band * p.Tp + t;
|
|
219
|
+
var seq = band; var pos = t;
|
|
220
|
+
if (p.time == 0u) { seq = t; pos = band; }
|
|
221
|
+
let o = ((seq * ${H}u + h) * p.L + pos) * 64u + d;
|
|
222
|
+
let y = r * p.ld + h * 64u;
|
|
223
|
+
let rot = select(Yq[y + d - 32u], -Yq[y + d + 32u], d < 32u);
|
|
224
|
+
let rk = select(Yq[y + ${H * DH}u + d - 32u], -Yq[y + ${H * DH}u + d + 32u], d < 32u);
|
|
225
|
+
Q[o] = Yq[y + d] * cs[pos * 64u + d] + rot * sn[pos * 64u + d];
|
|
226
|
+
K[o] = Yq[y + ${H * DH}u + d] * cs[pos * 64u + d] + rk * sn[pos * 64u + d];
|
|
227
|
+
V[o] = Yq[y + ${2 * H * DH}u + d];
|
|
228
|
+
if (d == 0u) { Gt[r * ${H}u + h] = 1.0 / (1.0 + exp(-(Yq[r * p.ld + ${QKV}u + h] + gb[h]))); }
|
|
229
|
+
}`)
|
|
230
|
+
// and back: Z[r, h·64 + d] = O · gate
|
|
231
|
+
const MERGE = pipe('merge', HEAD + `
|
|
232
|
+
@group(0) @binding(0) var<storage, read> O: array<f32>;
|
|
233
|
+
@group(0) @binding(1) var<storage, read> Gt: array<f32>;
|
|
234
|
+
@group(0) @binding(2) var<storage, read_write> Zb: array<f32>;
|
|
235
|
+
struct P { time: u32, Tp: u32, T: u32, L: u32 }
|
|
236
|
+
@group(0) @binding(3) var<uniform> p: P;
|
|
237
|
+
@compute @workgroup_size(64)
|
|
238
|
+
fn main(@builtin(workgroup_id) wg: vec3u, @builtin(local_invocation_id) li: vec3u) {
|
|
239
|
+
let t = wg.x; let band = wg.y; let h = wg.z; let d = li.x;
|
|
240
|
+
let r = band * p.Tp + t;
|
|
241
|
+
if (t >= p.T) { Zb[r * ${H * DH}u + h * 64u + d] = 0.0; return; }
|
|
242
|
+
var seq = band; var pos = t;
|
|
243
|
+
if (p.time == 0u) { seq = t; pos = band; }
|
|
244
|
+
Zb[r * ${H * DH}u + h * 64u + d] = O[((seq * ${H}u + h) * p.L + pos) * 64u + d] * Gt[r * ${H}u + h];
|
|
245
|
+
}`)
|
|
246
|
+
// attention in two passes over the keys, 32 a block, 16 query rows a subgroup: the rows' maxima first (Q Kᵀ alone),
|
|
247
|
+
// then exp(S − max) and the output accumulated in registers, no rescaling; keys past n masked
|
|
248
|
+
const BR = 16, BC = 32, SG = 4, I2 = [0, 1], J4 = [0, 1, 2, 3], D8 = [...Array(8).keys()]
|
|
249
|
+
const scores = `${I2.map(i => J4.map(j => `var s${i}_${j}: subgroup_matrix_result<f32, 8, 8>;`).join(' ')).join('\n ')}
|
|
250
|
+
for (var k = 0u; k < 8u; k++) {
|
|
251
|
+
${I2.map(i => `let q${i} = subgroupMatrixLoad<subgroup_matrix_left<f32, 8, 8>>(&Q, base + (r0 + ${i * 8}u) * 64u + k * 8u, false, 64u);`).join('\n ')}
|
|
252
|
+
${J4.map(j => `let k${j} = subgroupMatrixLoad<subgroup_matrix_right<f32, 8, 8>>(&K, base + (c0 + ${j * 8}u) * 64u + k * 8u, true, 64u);`).join('\n ')}
|
|
253
|
+
${I2.map(i => J4.map(j => `s${i}_${j} = subgroupMatrixMultiplyAccumulate(q${i}, k${j}, s${i}_${j});`).join(' ')).join('\n ')}
|
|
254
|
+
}
|
|
255
|
+
${I2.map(i => J4.map(j => `subgroupMatrixStore(&S[sg], ${i * 8 * BC + j * 8}u, s${i}_${j}, false, ${BC}u);`).join(' ')).join('\n ')}
|
|
256
|
+
workgroupBarrier();`
|
|
257
|
+
const ATT = pipe('att', HEAD + `
|
|
258
|
+
@group(0) @binding(0) var<storage, read> Q: array<f32>;
|
|
259
|
+
@group(0) @binding(1) var<storage, read> K: array<f32>;
|
|
260
|
+
@group(0) @binding(2) var<storage, read> V: array<f32>;
|
|
261
|
+
@group(0) @binding(3) var<storage, read_write> O: array<f32>;
|
|
262
|
+
struct P { n: u32, np: u32 }
|
|
263
|
+
@group(0) @binding(4) var<uniform> p: P;
|
|
264
|
+
var<workgroup> S: array<array<f32, ${BR * BC}>, ${SG}>;
|
|
265
|
+
var<workgroup> Ow: array<array<f32, ${BR * 64}>, ${SG}>;
|
|
266
|
+
@compute @workgroup_size(${32 * SG})
|
|
267
|
+
fn main(@builtin(workgroup_id) wg: vec3u, @builtin(subgroup_id) sg: u32, @builtin(subgroup_invocation_id) lane: u32) {
|
|
268
|
+
let b = wg.y;
|
|
269
|
+
let r0 = (wg.x * ${SG}u + sg) * ${BR}u;
|
|
270
|
+
let base = b * p.np * 64u;
|
|
271
|
+
let row = lane / 2u; let half = (lane % 2u) * ${BC / 2}u; // two lanes a row, 16 columns each
|
|
272
|
+
var mx = -1e30;
|
|
273
|
+
for (var c0 = 0u; c0 < p.n; c0 += ${BC}u) {
|
|
274
|
+
${scores}
|
|
275
|
+
for (var j = 0u; j < ${BC / 2}u; j++) { if (c0 + half + j < p.n) { mx = max(mx, S[sg][row * ${BC}u + half + j]); } }
|
|
276
|
+
workgroupBarrier();
|
|
277
|
+
}
|
|
278
|
+
mx = max(mx, subgroupShuffleXor(mx, 1u));
|
|
279
|
+
var l = 0.0;
|
|
280
|
+
${I2.map(i => D8.map(t => `var o${i}_${t}: subgroup_matrix_result<f32, 8, 8>;`).join(' ')).join('\n ')}
|
|
281
|
+
for (var c0 = 0u; c0 < p.n; c0 += ${BC}u) {
|
|
282
|
+
${scores}
|
|
283
|
+
for (var j = 0u; j < ${BC / 2}u; j++) {
|
|
284
|
+
let at = row * ${BC}u + half + j;
|
|
285
|
+
let e = select(0.0, exp(S[sg][at] - mx), c0 + half + j < p.n);
|
|
286
|
+
S[sg][at] = e; l += e;
|
|
287
|
+
}
|
|
288
|
+
workgroupBarrier();
|
|
289
|
+
${I2.map(i => J4.map(j => `let p${i}_${j} = subgroupMatrixLoad<subgroup_matrix_left<f32, 8, 8>>(&S[sg], ${i * 8 * BC + j * 8}u, false, ${BC}u);`).join(' ')).join('\n ')}
|
|
290
|
+
${D8.map(t => J4.map(j => `let v${j}_${t} = subgroupMatrixLoad<subgroup_matrix_right<f32, 8, 8>>(&V, base + (c0 + ${j * 8}u) * 64u + ${t * 8}u, false, 64u);`).join(' ') + '\n ' + I2.map(i => J4.map(j => `o${i}_${t} = subgroupMatrixMultiplyAccumulate(p${i}_${j}, v${j}_${t}, o${i}_${t});`).join(' ')).join(' ')).join('\n ')}
|
|
291
|
+
workgroupBarrier();
|
|
292
|
+
}
|
|
293
|
+
l += subgroupShuffleXor(l, 1u);
|
|
294
|
+
${I2.map(i => D8.map(t => `subgroupMatrixStore(&Ow[sg], ${i * 8 * 64 + t * 8}u, o${i}_${t}, false, 64u);`).join(' ')).join('\n ')}
|
|
295
|
+
workgroupBarrier();
|
|
296
|
+
// lane pairs hold their row's sum; each lane writes half its row
|
|
297
|
+
for (var d = 0u; d < 32u; d++) {
|
|
298
|
+
let r = r0 + row;
|
|
299
|
+
if (r < p.n) { O[base + r * 64u + half * 2u + d] = Ow[sg][row * 64u + half * 2u + d] / l; }
|
|
300
|
+
}
|
|
301
|
+
}`)
|
|
302
|
+
// GLU: value half by the sigmoid of the gate half
|
|
303
|
+
const GLU = pipe('glu', HEAD + `
|
|
304
|
+
@group(0) @binding(0) var<storage, read> Yg: array<f32>;
|
|
305
|
+
@group(0) @binding(1) var<storage, read_write> Mo: array<f32>;
|
|
306
|
+
struct P { rows: u32, Wp: u32, at: u32 }
|
|
307
|
+
@group(0) @binding(2) var<uniform> p: P;
|
|
308
|
+
@compute @workgroup_size(64)
|
|
309
|
+
fn main(@builtin(global_invocation_id) gi: vec3u) {
|
|
310
|
+
let i = gi.x + gi.y * 65535u * 64u;
|
|
311
|
+
if (i >= p.rows * p.Wp) { return; }
|
|
312
|
+
let r = i / p.Wp; let e = i % p.Wp;
|
|
313
|
+
Mo[p.at + i] = Yg[r * 2u * p.Wp + e] / (1.0 + exp(-Yg[r * 2u * p.Wp + p.Wp + e]));
|
|
314
|
+
}`)
|
|
315
|
+
// the masks averaged over the bands that cover a column, times the spectrum: out planes [s·2 + c][f][t]
|
|
316
|
+
const MASK = pipe('mask', HEAD + `
|
|
317
|
+
@group(0) @binding(0) var<storage, read> Mo: array<f32>;
|
|
318
|
+
@group(0) @binding(1) var<storage, read> map: array<u32>;
|
|
319
|
+
@group(0) @binding(2) var<storage, read> wt: array<f32>;
|
|
320
|
+
@group(0) @binding(3) var<storage, read> E: array<f32>;
|
|
321
|
+
@group(0) @binding(4) var<storage, read_write> Out: array<f32>;
|
|
322
|
+
struct P { T: u32, F: u32, cols: u32 }
|
|
323
|
+
@group(0) @binding(5) var<uniform> p: P;
|
|
324
|
+
fn mask(t: u32, o: u32) -> f32 {
|
|
325
|
+
let a = map[o * 8u]; let sa = map[o * 8u + 1u]; let on1 = map[o * 8u + 2u];
|
|
326
|
+
let b = map[o * 8u + 3u]; let sb = map[o * 8u + 4u]; let on2 = map[o * 8u + 5u];
|
|
327
|
+
var v = 0.0;
|
|
328
|
+
if (on1 == 1u) { v += Mo[a + t * sa]; }
|
|
329
|
+
if (on2 == 1u) { v += Mo[b + t * sb]; }
|
|
330
|
+
return v * wt[o];
|
|
331
|
+
}
|
|
332
|
+
@compute @workgroup_size(64)
|
|
333
|
+
fn main(@builtin(global_invocation_id) gi: vec3u) {
|
|
334
|
+
let t = gi.x; let fs = gi.y; // fs = f·S + s
|
|
335
|
+
if (t >= p.T) { return; }
|
|
336
|
+
let f = fs / ${S}u; let s = fs % ${S}u;
|
|
337
|
+
let o = fs * 2u;
|
|
338
|
+
let er = E[t * (p.cols + 1u) + o]; let ei = E[t * (p.cols + 1u) + o + 1u];
|
|
339
|
+
let mr = mask(t, o); let mi = mask(t, o + 1u);
|
|
340
|
+
Out[((s * 2u) * p.F + f) * p.T + t] = er * mr - ei * mi;
|
|
341
|
+
Out[((s * 2u + 1u) * p.F + f) * p.T + t] = er * mi + ei * mr;
|
|
342
|
+
}`)
|
|
343
|
+
|
|
344
|
+
// ---------------------------------------------------------------- the run, recorded once
|
|
345
|
+
for (const [g, gr] of G.entries()) {
|
|
346
|
+
step(SPLIT, [E, W.splitIdx[g], splitA[g], uni(gr.Wp, Tp, T, COLS)], [Tp, gr.n, 1])
|
|
347
|
+
mm('b', splitA[g], W.split[g], X, W.splitB[g], { M: Tp, N: D, K: gr.Wp, batch: gr.n, sa: Tp * gr.Wp, sb: gr.Wp * D, c0: gr.b0 * Tp * D, sc: Tp * D, sbias: D })
|
|
348
|
+
}
|
|
349
|
+
for (const [i, w] of W.blocks.entries()) {
|
|
350
|
+
const time = i % 2 === 0, L = time ? LT : LF, n = time ? T : B, seqs = time ? B : T
|
|
351
|
+
norm(X, XN, B * Tp, D)
|
|
352
|
+
mm('', XN, w.qkvg, Y, null, { M: B * Tp, N: QKVG, K: D })
|
|
353
|
+
step(ROPE, [Y, Qb, Kb, Vb, Gt, time ? cosT : cosF, time ? sinT : sinF, w.gb, uni(time ? 1 : 0, Tp, T, L, QKVG)], [Tp, B, H])
|
|
354
|
+
step(ATT, [Qb, Kb, Vb, Ob, uni(n, L)], [Math.ceil(L / (BR * SG)), seqs * H, 1])
|
|
355
|
+
step(MERGE, [Ob, Gt, Z, uni(time ? 1 : 0, Tp, T, L)], [Tp, B, H])
|
|
356
|
+
mm('r', Z, w.wo, X, null, { M: B * Tp, N: D, K: H * DH })
|
|
357
|
+
norm(X, XN, B * Tp, D)
|
|
358
|
+
mm('bg', XN, w.w1, FFH, w.b1, { M: B * Tp, N: FF, K: D })
|
|
359
|
+
mm('br', FFH, w.w2, X, w.b2, { M: B * Tp, N: D, K: FF })
|
|
360
|
+
norm(X, X, B * Tp, D, w.out)
|
|
361
|
+
}
|
|
362
|
+
mm('bt', X, W.mw[0], FFH, W.mb[0], { M: Tp, N: MH, K: D, batch: B, sa: Tp * D, sb: D * MH, sc: Tp * MH, sbias: MH })
|
|
363
|
+
mm('bt', FFH, W.mw[1], H2, W.mb[1], { M: Tp, N: MH, K: MH, batch: B, sa: Tp * MH, sb: MH * MH, sc: Tp * MH, sbias: MH })
|
|
364
|
+
for (const [g, gr] of G.entries()) {
|
|
365
|
+
mm('b', H2, W.m2[g], glu[g], W.m2B[g], { M: Tp, N: 2 * gr.Wp, K: MH, batch: gr.n, a0: gr.b0 * Tp * MH, sa: Tp * MH, sb: MH * 2 * gr.Wp, sc: Tp * 2 * gr.Wp, sbias: 2 * gr.Wp })
|
|
366
|
+
const n = gr.n * Tp * gr.Wp
|
|
367
|
+
step(GLU, [glu[g], Mg, uni(gr.n * Tp, gr.Wp, gofs[g])], [Math.min(Math.ceil(n / 64), 65535), Math.ceil(n / 64 / 65535), 1])
|
|
368
|
+
}
|
|
369
|
+
step(MASK, [Mg, maskMap, maskWt, E, OUT, uni(T, F, COLS)], [Math.ceil(T / 64), F * S, 1])
|
|
370
|
+
const zero = device.createCommandEncoder()
|
|
371
|
+
for (const b of [Qb, Kb, Vb]) zero.clearBuffer(b)
|
|
372
|
+
device.queue.submit([zero.finish()])
|
|
373
|
+
|
|
374
|
+
const stage = new Float32Array(Tp * (COLS + 1)), read = device.createBuffer({ size: 4 * F * T * 4, usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ })
|
|
375
|
+
return {
|
|
376
|
+
steps: steps.length,
|
|
377
|
+
async run(spec) {
|
|
378
|
+
// planes [s·2 + c][f][t] → E[t][(f·S + s)·2 + c], a zero column past them, rows past T zero
|
|
379
|
+
stage.fill(0)
|
|
380
|
+
for (let s = 0; s < S; s++) for (let c = 0; c < 2; c++) for (let f = 0; f < F; f++) {
|
|
381
|
+
const plane = ((s * 2 + c) * F + f) * T, col = (f * S + s) * 2 + c
|
|
382
|
+
for (let t = 0; t < T; t++) stage[t * (COLS + 1) + col] = spec[plane + t]
|
|
383
|
+
}
|
|
384
|
+
device.queue.writeBuffer(E, 0, stage)
|
|
385
|
+
const e = device.createCommandEncoder(), pass = e.beginComputePass()
|
|
386
|
+
for (const [p, bg, grid] of steps) { pass.setPipeline(p); pass.setBindGroup(0, bg); pass.dispatchWorkgroups(...grid) }
|
|
387
|
+
pass.end()
|
|
388
|
+
e.copyBufferToBuffer(OUT, 0, read, 0, 4 * F * T * 4)
|
|
389
|
+
device.queue.submit([e.finish()])
|
|
390
|
+
await read.mapAsync(GPUMapMode.READ)
|
|
391
|
+
const out = new Float32Array(read.getMappedRange().slice(0))
|
|
392
|
+
read.unmap()
|
|
393
|
+
return out
|
|
394
|
+
},
|
|
395
|
+
}
|
|
396
|
+
}
|