1use anyhow::Result;
9
10use crate::GpuCtx;
11use crate::encoder::{
12 GEMM2_TILES, GEMM3_TILES, act_code, enc_gemm2_src, enc_gemm3_src, gemm2_tier, gemm2_tile,
13 gemm3_tier, gemm3_tile,
14};
15use crate::encoder_weights::Act;
16use crate::forward::{make_bg, pipeline, uni};
17use crate::whisper::{Whisper, WhisperEncoder};
18
19pub(crate) const VANILLA_ATTN: &str = r#"
26struct Meta { t: u32, heads: u32, hd: u32, p0: u32 }
27@group(0) @binding(0) var<storage, read> q: array<f32>;
28@group(0) @binding(1) var<storage, read> k: array<f32>;
29@group(0) @binding(2) var<storage, read> v: array<f32>;
30@group(0) @binding(3) var<storage, read_write> outp: array<f32>;
31@group(0) @binding(4) var<uniform> mt: Meta;
32const TILE: u32 = 128u;
33var<workgroup> q_sh: array<f32, 128>; // this row's q (hd ≤ 128)
34var<workgroup> sc: array<f32, 128>; // tile scores, then tile probs
35var<workgroup> red: array<f32, 128>; // reduction scratch (max, then sum)
36@compute @workgroup_size(128)
37fn main(@builtin(workgroup_id) wg: vec3<u32>, @builtin(local_invocation_index) tid: u32) {
38 let t = mt.t;
39 let hd = mt.hd;
40 let d = mt.heads * hd;
41 let i = wg.x;
42 let head = wg.y;
43 if (i >= t || head >= mt.heads) { return; }
44 let scale = 1.0 / sqrt(f32(hd));
45 let qbase = i * d + head * hd;
46 if (tid < hd) { q_sh[tid] = q[qbase + tid]; }
47 workgroupBarrier();
48
49 var acc = 0.0; // output for dim `tid` (tid < hd), across all tiles
50 var run_max = -1e30;
51 var run_den = 0.0;
52 var tile0 = 0u;
53 loop {
54 if (tile0 >= t) { break; }
55 let j = tile0 + tid;
56 // 1) score for this thread's key (thread ↔ key), dot over hd with no barrier
57 var s = -1e30;
58 if (j < t) {
59 let kbase = j * d + head * hd;
60 var dot = 0.0;
61 for (var c = 0u; c < hd; c = c + 1u) { dot = dot + q_sh[c] * k[kbase + c]; }
62 s = dot * scale;
63 }
64 sc[tid] = s;
65 red[tid] = s;
66 workgroupBarrier();
67 // 2) tile max
68 for (var st = 64u; st > 0u; st = st >> 1u) {
69 if (tid < st) { red[tid] = max(red[tid], red[tid + st]); }
70 workgroupBarrier();
71 }
72 let nm = max(run_max, red[0]);
73 let corr = exp(run_max - nm);
74 workgroupBarrier();
75 // 3) probs relative to the new running max
76 var p = 0.0;
77 if (j < t) { p = exp(sc[tid] - nm); }
78 sc[tid] = p;
79 red[tid] = p;
80 workgroupBarrier();
81 // 4) tile denom
82 for (var st = 64u; st > 0u; st = st >> 1u) {
83 if (tid < st) { red[tid] = red[tid] + red[tid + st]; }
84 workgroupBarrier();
85 }
86 run_den = run_den * corr + red[0];
87 // 5) accumulate output (thread ↔ head-dim); v read once per key
88 if (tid < hd) {
89 var a = acc * corr;
90 let n = min(TILE, t - tile0);
91 for (var r = 0u; r < n; r = r + 1u) {
92 a = a + sc[r] * v[(tile0 + r) * d + head * hd + tid];
93 }
94 acc = a;
95 }
96 run_max = nm;
97 workgroupBarrier(); // sc/red consumed before the next tile overwrites them
98 tile0 = tile0 + TILE;
99 }
100 if (tid < hd) { outp[qbase + tid] = acc / run_den; }
101}
102"#;
103
104pub(crate) const LN_SRC: &str = r#"
105struct Meta { h: u32, p0: u32, p1: u32, p2: u32 }
106@group(0) @binding(0) var<storage, read> x: array<f32>;
107@group(0) @binding(1) var<storage, read> w: array<f32>;
108@group(0) @binding(2) var<storage, read> b: array<f32>;
109@group(0) @binding(3) var<storage, read_write> outp: array<f32>;
110@group(0) @binding(4) var<uniform> mt: Meta;
111var<workgroup> sh: array<f32, 256>;
112@compute @workgroup_size(256)
113fn main(@builtin(workgroup_id) wg: vec3<u32>, @builtin(local_invocation_index) t: u32) {
114 let base = wg.x * mt.h;
115 var sum = 0.0;
116 for (var i = t; i < mt.h; i = i + 256u) { sum = sum + x[base + i]; }
117 sh[t] = sum;
118 workgroupBarrier();
119 for (var s = 128u; s > 0u; s = s >> 1u) { if (t < s) { sh[t] = sh[t] + sh[t + s]; } workgroupBarrier(); }
120 let mean = sh[0] / f32(mt.h);
121 workgroupBarrier();
122 var sq = 0.0;
123 for (var i = t; i < mt.h; i = i + 256u) { let d = x[base + i] - mean; sq = sq + d * d; }
124 sh[t] = sq;
125 workgroupBarrier();
126 for (var s = 128u; s > 0u; s = s >> 1u) { if (t < s) { sh[t] = sh[t] + sh[t + s]; } workgroupBarrier(); }
127 let inv = 1.0 / sqrt(sh[0] / f32(mt.h) + 1e-5);
128 for (var i = t; i < mt.h; i = i + 256u) { outp[base + i] = (x[base + i] - mean) * inv * w[i] + b[i]; }
129}
130"#;
131
132pub(crate) const ADD_SRC: &str = r#"
133struct Meta { n: u32, p0: u32, p1: u32, p2: u32 }
134@group(0) @binding(0) var<storage, read_write> dst: array<f32>;
135@group(0) @binding(1) var<storage, read> src: array<f32>;
136@group(0) @binding(2) var<uniform> mt: Meta;
137@compute @workgroup_size(256)
138fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
139 if (gid.x < mt.n) { dst[gid.x] = dst[gid.x] + src[gid.x]; }
140}
141"#;
142
143pub(crate) struct GpuLinear {
144 pub(crate) w: wgpu::Buffer,
145 pub(crate) b: wgpu::Buffer,
146 pub(crate) n: u32,
147 pub(crate) k: u32,
148 pub(crate) v3: bool,
149}
150
151impl GpuLinear {
152 pub(crate) fn new_v3(ctx: &GpuCtx, w: &[f32], b: &[f32], n: usize, k: usize) -> Self {
153 let mut wt = vec![0f32; n * k];
154 for nn in 0..n {
155 for kk in 0..k {
156 wt[kk * n + nn] = w[nn * k + kk];
157 }
158 }
159 Self {
160 w: ctx.storage(&wt),
161 b: ctx.storage(b),
162 n: n as u32,
163 k: k as u32,
164 v3: true,
165 }
166 }
167}
168
169pub(crate) struct GpuNorm {
170 pub(crate) w: wgpu::Buffer,
171 pub(crate) b: wgpu::Buffer,
172}
173
174struct GpuBlock {
175 n_attn: GpuNorm,
176 q: GpuLinear,
177 k: GpuLinear,
178 v: GpuLinear,
179 out: GpuLinear,
180 n_ff: GpuNorm,
181 fc1: GpuLinear,
182 fc2: GpuLinear,
183}
184
185pub struct WhisperEncoderGpu {
186 cpu: WhisperEncoder,
187 gemm2: Vec<wgpu::ComputePipeline>,
188 gemm3: Vec<wgpu::ComputePipeline>,
189 ln: wgpu::ComputePipeline,
190 add: wgpu::ComputePipeline,
191 attn: wgpu::ComputePipeline,
192 ln_post: Option<GpuNorm>,
194 blocks: Vec<GpuBlock>,
195 d: usize,
196 heads: usize,
197 hd: usize,
198 ff: usize,
199 max_t: usize,
200}
201
202impl WhisperEncoderGpu {
203 pub fn new(ctx: &GpuCtx, cpu: WhisperEncoder, max_t: usize) -> Result<Self> {
204 let d = cpu.d;
205 let heads = cpu.blocks[0].attn.heads;
206 let hd = cpu.blocks[0].attn.hd;
207 anyhow::ensure!(hd <= 128, "attention supports head_dim ≤ 128");
208 let ff = cpu.blocks[0].fc1.n;
209 let norm = |g: &crate::whisper::LayerNorm| GpuNorm {
210 w: ctx.storage(&g.w),
211 b: ctx.storage(&g.b),
212 };
213 let lin = |l: &crate::whisper::Linear| GpuLinear::new_v3(ctx, &l.w, &l.b, l.n, l.k);
214 let blocks = cpu
215 .blocks
216 .iter()
217 .map(|b| GpuBlock {
218 n_attn: norm(&b.norm_attn),
219 q: lin(&b.attn.q),
220 k: lin(&b.attn.k),
221 v: lin(&b.attn.v),
222 out: lin(&b.attn.out),
223 n_ff: norm(&b.norm_ff),
224 fc1: lin(&b.fc1),
225 fc2: lin(&b.fc2),
226 })
227 .collect();
228 let ln_post = cpu.ln_post.as_ref().map(norm);
231 Ok(Self {
232 gemm2: GEMM2_TILES
233 .iter()
234 .map(|&(bm, bn)| pipeline(ctx, "w_gemm2", &enc_gemm2_src(false, bm, bn)))
235 .collect(),
236 gemm3: GEMM3_TILES
237 .iter()
238 .map(|&(bm, bn, bk)| pipeline(ctx, "w_gemm3", &enc_gemm3_src(false, bm, bn, bk)))
239 .collect(),
240 ln: pipeline(ctx, "w_ln", LN_SRC),
241 add: pipeline(ctx, "w_add", ADD_SRC),
242 attn: pipeline(ctx, "w_attn", VANILLA_ATTN),
243 cpu,
244 ln_post,
245 blocks,
246 d,
247 heads,
248 hd,
249 ff,
250 max_t,
251 })
252 }
253
254 pub fn cpu(&self) -> &WhisperEncoder {
255 &self.cpu
256 }
257
258 pub fn forward(&self, ctx: &GpuCtx, mel: &[f32]) -> Result<Vec<f32>> {
260 let (stem, t) = self.cpu.stem(mel);
261 anyhow::ensure!(t <= self.max_t, "t {t} exceeds max_t {}", self.max_t);
262 let (d, ff) = (self.d, self.ff);
263
264 let xb = ctx.storage(&stem);
265 let nb = ctx.empty(t * d);
266 let qb = ctx.empty(t * d);
267 let kb = ctx.empty(t * d);
268 let vb = ctx.empty(t * d);
269 let sb = ctx.empty(t * d);
270 let hb = ctx.empty(t * ff);
271
272 let mut passes: Vec<(&wgpu::ComputePipeline, wgpu::BindGroup, u32, u32)> = Vec::new();
273 let mut keep: Vec<wgpu::Buffer> = Vec::new();
274
275 macro_rules! gemm {
276 ($x:expr, $lw:expr, $y:expr, $act:expr) => {{
277 let lw: &GpuLinear = $lw;
278 let flags = 1u32 | (act_code($act) << 8);
279 let meta = uni(ctx, bytemuck::cast_slice(&[t as u32, lw.n, lw.k, flags]));
280 let (pl, bm, bn) = if lw.v3 {
281 let tile = gemm3_tile(t, lw.n as usize);
282 (&self.gemm3[gemm3_tier(tile)], tile.0, tile.1)
283 } else {
284 let tile = gemm2_tile(t, lw.n as usize);
285 (&self.gemm2[gemm2_tier(tile)], tile.0, tile.1)
286 };
287 let bg = make_bg(ctx, pl, &[$x, &lw.w, &lw.b, $y], &meta);
288 passes.push((
289 pl,
290 bg,
291 lw.n.div_ceil(bn as u32),
292 (t as u32).div_ceil(bm as u32),
293 ));
294 keep.push(meta);
295 }};
296 }
297 macro_rules! ln {
298 ($x:expr, $n:expr, $y:expr) => {{
299 let meta = uni(ctx, bytemuck::cast_slice(&[d as u32, 0u32, 0u32, 0u32]));
300 let bg = make_bg(ctx, &self.ln, &[$x, &$n.w, &$n.b, $y], &meta);
301 passes.push((&self.ln, bg, t as u32, 1));
302 keep.push(meta);
303 }};
304 }
305 macro_rules! add {
306 ($dst:expr, $src:expr) => {{
307 let meta = uni(
308 ctx,
309 bytemuck::cast_slice(&[(t * d) as u32, 0u32, 0u32, 0u32]),
310 );
311 let bg = make_bg(ctx, &self.add, &[$dst, $src], &meta);
312 passes.push((&self.add, bg, ((t * d) as u32).div_ceil(256), 1));
313 keep.push(meta);
314 }};
315 }
316
317 for b in &self.blocks {
318 ln!(&xb, b.n_attn, &nb);
320 gemm!(&nb, &b.q, &qb, None);
321 gemm!(&nb, &b.k, &kb, None);
322 gemm!(&nb, &b.v, &vb, None);
323 {
324 let meta = uni(
325 ctx,
326 bytemuck::cast_slice(&[t as u32, self.heads as u32, self.hd as u32, 0u32]),
327 );
328 let bg = make_bg(ctx, &self.attn, &[&qb, &kb, &vb, &nb], &meta);
329 passes.push((&self.attn, bg, t as u32, self.heads as u32));
330 keep.push(meta);
331 }
332 gemm!(&nb, &b.out, &sb, None);
333 add!(&xb, &sb);
334 ln!(&xb, b.n_ff, &nb);
336 gemm!(&nb, &b.fc1, &hb, Some(Act::GeluErf));
337 gemm!(&hb, &b.fc2, &sb, None);
338 add!(&xb, &sb);
339 }
340 let out_buf = match &self.ln_post {
343 Some(ln) => {
344 ln!(&xb, ln, &nb);
345 &nb
346 }
347 None => &xb,
348 };
349
350 let mut enc = ctx
351 .device
352 .create_command_encoder(&wgpu::CommandEncoderDescriptor {
353 label: Some("whisper_enc"),
354 });
355 {
356 let mut cpass = enc.begin_compute_pass(&wgpu::ComputePassDescriptor {
357 label: Some("whisper_enc"),
358 timestamp_writes: None,
359 });
360 for (pl, bg, gx, gy) in &passes {
361 cpass.set_pipeline(pl);
362 cpass.set_bind_group(0, bg, &[]);
363 cpass.dispatch_workgroups(*gx, *gy, 1);
364 }
365 }
366 ctx.queue.submit(Some(enc.finish()));
367 let out = ctx.read(out_buf, t * d)?;
368 drop(keep);
369 Ok(out)
370 }
371}
372
373const DEC_EMBED: &str = r#"
387struct Meta { id: u32, p: u32, d: u32, pad: u32 }
388@group(0) @binding(0) var<storage, read> et: array<f32>;
389@group(0) @binding(1) var<storage, read> ep: array<f32>;
390@group(0) @binding(2) var<storage, read_write> x: array<f32>;
391@group(0) @binding(3) var<uniform> mt: Meta;
392@compute @workgroup_size(256)
393fn main(@builtin(global_invocation_id) g: vec3<u32>) {
394 if (g.x >= mt.d) { return; }
395 x[g.x] = et[mt.id * mt.d + g.x] + ep[mt.p * mt.d + g.x];
396}
397"#;
398
399const DEC_WRITE: &str = r#"
401struct Meta { p: u32, d: u32, p0: u32, p1: u32 }
402@group(0) @binding(0) var<storage, read> src: array<f32>;
403@group(0) @binding(1) var<storage, read_write> dst: array<f32>;
404@group(0) @binding(2) var<uniform> mt: Meta;
405@compute @workgroup_size(256)
406fn main(@builtin(global_invocation_id) g: vec3<u32>) {
407 if (g.x >= mt.d) { return; }
408 dst[mt.p * mt.d + g.x] = src[g.x];
409}
410"#;
411
412const DEC_QATTN: &str = r#"
416struct Meta { heads: u32, hd: u32, t: u32, pad: u32 }
417@group(0) @binding(0) var<storage, read> q: array<f32>; // [hid]
418@group(0) @binding(1) var<storage, read> k: array<f32>; // [t, hid]
419@group(0) @binding(2) var<storage, read> v: array<f32>; // [t, hid]
420@group(0) @binding(3) var<storage, read_write> o: array<f32>; // [hid]
421@group(0) @binding(4) var<uniform> mt: Meta;
422var<workgroup> q_sh: array<f32, 128>;
423var<workgroup> sc: array<f32, 1600>; // scores/probs (>= 1500 encoder frames)
424var<workgroup> red: array<f32, 256>;
425@compute @workgroup_size(256)
426fn main(@builtin(workgroup_id) wg: vec3<u32>, @builtin(local_invocation_index) t: u32) {
427 let head = wg.x;
428 let hd = mt.hd;
429 let hid = mt.heads * hd;
430 let tk = mt.t;
431 let scale = 1.0 / sqrt(f32(hd));
432 if (t < hd) { q_sh[t] = q[head * hd + t]; }
433 workgroupBarrier();
434 for (var j = t; j < tk; j += 256u) {
435 let kb = j * hid + head * hd;
436 var d = 0.0;
437 for (var c = 0u; c < hd; c += 1u) { d += q_sh[c] * k[kb + c]; }
438 sc[j] = d * scale;
439 }
440 workgroupBarrier();
441 var lm = -3.0e38;
442 for (var j = t; j < tk; j += 256u) { lm = max(lm, sc[j]); }
443 red[t] = lm;
444 workgroupBarrier();
445 for (var s = 128u; s > 0u; s >>= 1u) { if (t < s) { red[t] = max(red[t], red[t + s]); } workgroupBarrier(); }
446 let m = red[0];
447 workgroupBarrier();
448 var ls = 0.0;
449 for (var j = t; j < tk; j += 256u) { let e = exp(sc[j] - m); sc[j] = e; ls += e; }
450 red[t] = ls;
451 workgroupBarrier();
452 for (var s = 128u; s > 0u; s >>= 1u) { if (t < s) { red[t] += red[t + s]; } workgroupBarrier(); }
453 let sm = red[0];
454 workgroupBarrier();
455 if (t < hd) {
456 var acc = 0.0;
457 for (var j = 0u; j < tk; j += 1u) { acc += sc[j] * v[j * hid + head * hd + t]; }
458 o[head * hd + t] = acc / sm;
459 }
460}
461"#;
462
463const DEC_ARGMAX: &str = r#"
467struct Meta { vocab: u32, p0: u32, p1: u32, p2: u32 }
468@group(0) @binding(0) var<storage, read> logits: array<f32>;
469@group(0) @binding(1) var<storage, read> mask: array<u32>;
470@group(0) @binding(2) var<storage, read_write> tid: array<u32>;
471@group(0) @binding(3) var<uniform> mt: Meta;
472var<workgroup> vb: array<f32, 256>;
473var<workgroup> ib: array<u32, 256>;
474@compute @workgroup_size(256)
475fn main(@builtin(local_invocation_index) t: u32) {
476 var lv = -3.0e38;
477 var li = 0u;
478 for (var j = t; j < mt.vocab; j += 256u) {
479 if (mask[j] == 0u && logits[j] > lv) { lv = logits[j]; li = j; }
480 }
481 vb[t] = lv;
482 ib[t] = li;
483 workgroupBarrier();
484 for (var s = 128u; s > 0u; s >>= 1u) {
485 if (t < s) {
486 if (vb[t + s] > vb[t] || (vb[t + s] == vb[t] && ib[t + s] < ib[t])) {
487 vb[t] = vb[t + s];
488 ib[t] = ib[t + s];
489 }
490 }
491 workgroupBarrier();
492 }
493 if (t == 0u) { tid[0] = ib[0]; }
494}
495"#;
496
497struct DecGpuBlock {
498 n_self: GpuNorm,
499 sq: GpuLinear,
500 sk: GpuLinear,
501 sv: GpuLinear,
502 so: GpuLinear,
503 n_cross: GpuNorm,
504 cq: GpuLinear,
505 ck: GpuLinear,
506 cv: GpuLinear,
507 co: GpuLinear,
508 n_ff: GpuNorm,
509 fc1: GpuLinear,
510 fc2: GpuLinear,
511}
512
513pub struct WhisperDecoderGpu {
515 gemm3: Vec<wgpu::ComputePipeline>,
516 ln: wgpu::ComputePipeline,
517 add: wgpu::ComputePipeline,
518 embed_pl: wgpu::ComputePipeline,
519 write_pl: wgpu::ComputePipeline,
520 qattn_pl: wgpu::ComputePipeline,
521 argmax_pl: wgpu::ComputePipeline,
522 embed_tokens: wgpu::Buffer,
523 embed_pos: wgpu::Buffer,
524 blocks: Vec<DecGpuBlock>,
525 ln_post: GpuNorm,
526 lm_head: GpuLinear,
527 mask_gen: wgpu::Buffer,
528 mask_gen0: wgpu::Buffer,
529 mask_lang: wgpu::Buffer,
530 d: usize,
531 heads: usize,
532 hd: usize,
533 ff: usize,
534 vocab: usize,
535 max_target: usize,
536 prompt: Vec<usize>,
537 lang_on: bool,
538 transcribe_token: usize,
539 notimestamps_token: usize,
540 eos: usize,
541}
542
543impl WhisperDecoderGpu {
544 pub fn new(ctx: &GpuCtx, cpu: &Whisper) -> Result<Self> {
545 let d = cpu.d;
546 let heads = cpu.blocks[0].self_attn.heads;
547 let hd = cpu.blocks[0].self_attn.hd;
548 anyhow::ensure!(hd <= 128, "decoder head_dim {hd} > 128");
549 let ff = cpu.blocks[0].fc1.n;
550 let vocab = cpu.vocab;
551 let norm = |g: &crate::whisper::LayerNorm| GpuNorm {
552 w: ctx.storage(&g.w),
553 b: ctx.storage(&g.b),
554 };
555 let lin = |l: &crate::whisper::Linear| GpuLinear::new_v3(ctx, &l.w, &l.b, l.n, l.k);
556 let blocks = cpu
557 .blocks
558 .iter()
559 .map(|b| DecGpuBlock {
560 n_self: norm(&b.norm_self),
561 sq: lin(&b.self_attn.q),
562 sk: lin(&b.self_attn.k),
563 sv: lin(&b.self_attn.v),
564 so: lin(&b.self_attn.out),
565 n_cross: norm(&b.norm_cross),
566 cq: lin(&b.cross_attn.q),
567 ck: lin(&b.cross_attn.k),
568 cv: lin(&b.cross_attn.v),
569 co: lin(&b.cross_attn.out),
570 n_ff: norm(&b.norm_ff),
571 fc1: lin(&b.fc1),
572 fc2: lin(&b.fc2),
573 })
574 .collect();
575 let mask_of = |set: &[usize], base_ones: bool| -> wgpu::Buffer {
576 let mut m = vec![if base_ones { 1u32 } else { 0u32 }; vocab];
577 for &t in set {
578 if t < vocab {
579 m[t] = if base_ones { 0 } else { 1 };
580 }
581 }
582 ctx.storage_bytes(bytemuck::cast_slice(&m))
583 };
584 let mask_gen = mask_of(&cpu.suppress, false);
585 let mut m0 = vec![0u32; vocab];
586 for &t in cpu.suppress.iter().chain(cpu.begin_suppress.iter()) {
587 if t < vocab {
588 m0[t] = 1;
589 }
590 }
591 let mask_gen0 = ctx.storage_bytes(bytemuck::cast_slice(&m0));
592 let mask_lang = mask_of(&cpu.lang_tokens, true); Ok(Self {
595 gemm3: GEMM3_TILES
596 .iter()
597 .map(|&(bm, bn, bk)| pipeline(ctx, "wd_gemm3", &enc_gemm3_src(false, bm, bn, bk)))
598 .collect(),
599 ln: pipeline(ctx, "wd_ln", LN_SRC),
600 add: pipeline(ctx, "wd_add", ADD_SRC),
601 embed_pl: pipeline(ctx, "wd_embed", DEC_EMBED),
602 write_pl: pipeline(ctx, "wd_write", DEC_WRITE),
603 qattn_pl: pipeline(ctx, "wd_qattn", DEC_QATTN),
604 argmax_pl: pipeline(ctx, "wd_argmax", DEC_ARGMAX),
605 embed_tokens: ctx.storage(&cpu.embed_tokens),
606 embed_pos: ctx.storage(&cpu.embed_pos),
607 ln_post: norm(&cpu.ln_post),
608 lm_head: lin(&cpu.lm_head),
609 blocks,
610 mask_gen,
611 mask_gen0,
612 mask_lang,
613 d,
614 heads,
615 hd,
616 ff,
617 vocab,
618 max_target: cpu.max_target,
619 prompt: cpu.prompt.clone(),
620 lang_on: !cpu.lang_tokens.is_empty(),
621 transcribe_token: cpu.transcribe_token,
622 notimestamps_token: cpu.notimestamps_token,
623 eos: cpu.eos,
624 })
625 }
626
627 pub fn generate(&self, ctx: &GpuCtx, enc: &[f32], max_new: usize) -> Result<Vec<usize>> {
630 let d = self.d;
631 let (heads, hd, ff, vocab) = (self.heads, self.hd, self.ff, self.vocab);
632 let n_cross = enc.len() / d;
633
634 let b_enc = ctx.storage(enc);
635 let ckv: Vec<(wgpu::Buffer, wgpu::Buffer)> = self
637 .blocks
638 .iter()
639 .map(|_| (ctx.empty(n_cross * d), ctx.empty(n_cross * d)))
640 .collect();
641 let kc: Vec<wgpu::Buffer> = self
643 .blocks
644 .iter()
645 .map(|_| ctx.empty(self.max_target * d))
646 .collect();
647 let vc: Vec<wgpu::Buffer> = self
648 .blocks
649 .iter()
650 .map(|_| ctx.empty(self.max_target * d))
651 .collect();
652 let x = ctx.empty(d);
653 let normed = ctx.empty(d);
654 let q = ctx.empty(d);
655 let k_new = ctx.empty(d);
656 let v_new = ctx.empty(d);
657 let sa = ctx.empty(d);
658 let ca = ctx.empty(d);
659 let ffb = ctx.empty(ff);
660 let logits = ctx.empty(vocab);
661 let tid = ctx.empty(1);
662
663 type Pass<'a> = (&'a wgpu::ComputePipeline, wgpu::BindGroup, u32, u32);
664 macro_rules! gemm {
665 ($passes:expr, $keep:expr, $x:expr, $l:expr, $y:expr, $m:expr, $act:expr) => {{
666 let l: &GpuLinear = $l;
667 let m: usize = $m;
668 let flags = 1u32 | (act_code($act) << 8);
669 let meta = uni(ctx, bytemuck::cast_slice(&[m as u32, l.n, l.k, flags]));
670 let tile = gemm3_tile(m, l.n as usize);
671 let pl = &self.gemm3[gemm3_tier(tile)];
672 let bg = make_bg(ctx, pl, &[$x, &l.w, &l.b, $y], &meta);
673 $passes.push((
674 pl,
675 bg,
676 l.n.div_ceil(tile.1 as u32),
677 (m as u32).div_ceil(tile.0 as u32),
678 ));
679 $keep.push(meta);
680 }};
681 }
682 macro_rules! ln {
683 ($passes:expr, $keep:expr, $x:expr, $nm:expr, $y:expr) => {{
684 let nm: &GpuNorm = $nm;
685 let meta = uni(ctx, bytemuck::cast_slice(&[d as u32, 0u32, 0u32, 0u32]));
686 let bg = make_bg(ctx, &self.ln, &[$x, &nm.w, &nm.b, $y], &meta);
687 $passes.push((&self.ln, bg, 1u32, 1u32));
688 $keep.push(meta);
689 }};
690 }
691 macro_rules! add {
692 ($passes:expr, $keep:expr, $dst:expr, $src:expr) => {{
693 let meta = uni(ctx, bytemuck::cast_slice(&[d as u32, 0u32, 0u32, 0u32]));
694 let bg = make_bg(ctx, &self.add, &[$dst, $src], &meta);
695 $passes.push((&self.add, bg, (d as u32).div_ceil(256), 1u32));
696 $keep.push(meta);
697 }};
698 }
699 macro_rules! write {
700 ($passes:expr, $keep:expr, $src:expr, $dst:expr, $pos:expr) => {{
701 let meta = uni(
702 ctx,
703 bytemuck::cast_slice(&[$pos as u32, d as u32, 0u32, 0u32]),
704 );
705 let bg = make_bg(ctx, &self.write_pl, &[$src, $dst], &meta);
706 $passes.push((&self.write_pl, bg, (d as u32).div_ceil(256), 1u32));
707 $keep.push(meta);
708 }};
709 }
710 macro_rules! qattn {
711 ($passes:expr, $keep:expr, $q:expr, $k:expr, $v:expr, $o:expr, $tk:expr) => {{
712 let meta = uni(
713 ctx,
714 bytemuck::cast_slice(&[heads as u32, hd as u32, $tk as u32, 0u32]),
715 );
716 let bg = make_bg(ctx, &self.qattn_pl, &[$q, $k, $v, $o], &meta);
717 $passes.push((&self.qattn_pl, bg, heads as u32, 1u32));
718 $keep.push(meta);
719 }};
720 }
721
722 {
724 let mut passes: Vec<Pass> = Vec::new();
725 let mut keep: Vec<wgpu::Buffer> = Vec::new();
726 for (b, (cbk, cbv)) in self.blocks.iter().zip(&ckv) {
727 gemm!(passes, keep, &b_enc, &b.ck, cbk, n_cross, None);
728 gemm!(passes, keep, &b_enc, &b.cv, cbv, n_cross, None);
729 }
730 record_submit(ctx, &passes);
731 drop(keep);
732 }
733
734 let step =
736 |token: usize, pos: usize, emit: Option<&wgpu::Buffer>| -> Result<Option<usize>> {
737 let mut passes: Vec<Pass> = Vec::new();
738 let mut keep: Vec<wgpu::Buffer> = Vec::new();
739 {
740 let meta = uni(
741 ctx,
742 bytemuck::cast_slice(&[token as u32, pos as u32, d as u32, 0u32]),
743 );
744 let bg = make_bg(
745 ctx,
746 &self.embed_pl,
747 &[&self.embed_tokens, &self.embed_pos, &x],
748 &meta,
749 );
750 passes.push((&self.embed_pl, bg, (d as u32).div_ceil(256), 1u32));
751 keep.push(meta);
752 }
753 for (bi, b) in self.blocks.iter().enumerate() {
754 ln!(passes, keep, &x, &b.n_self, &normed);
755 gemm!(passes, keep, &normed, &b.sq, &q, 1, None);
756 gemm!(passes, keep, &normed, &b.sk, &k_new, 1, None);
757 gemm!(passes, keep, &normed, &b.sv, &v_new, 1, None);
758 write!(passes, keep, &k_new, &kc[bi], pos);
759 write!(passes, keep, &v_new, &vc[bi], pos);
760 qattn!(passes, keep, &q, &kc[bi], &vc[bi], &sa, pos + 1);
761 gemm!(passes, keep, &sa, &b.so, &normed, 1, None);
762 add!(passes, keep, &x, &normed);
763 ln!(passes, keep, &x, &b.n_cross, &normed);
764 gemm!(passes, keep, &normed, &b.cq, &q, 1, None);
765 qattn!(passes, keep, &q, &ckv[bi].0, &ckv[bi].1, &ca, n_cross);
766 gemm!(passes, keep, &ca, &b.co, &normed, 1, None);
767 add!(passes, keep, &x, &normed);
768 ln!(passes, keep, &x, &b.n_ff, &normed);
769 gemm!(passes, keep, &normed, &b.fc1, &ffb, 1, Some(Act::GeluErf));
770 gemm!(passes, keep, &ffb, &b.fc2, &normed, 1, None);
771 add!(passes, keep, &x, &normed);
772 }
773 if let Some(mask) = emit {
774 ln!(passes, keep, &x, &self.ln_post, &normed);
775 gemm!(passes, keep, &normed, &self.lm_head, &logits, 1, None);
776 let meta = uni(ctx, bytemuck::cast_slice(&[vocab as u32, 0u32, 0u32, 0u32]));
777 let bg = make_bg(ctx, &self.argmax_pl, &[&logits, mask, &tid], &meta);
778 passes.push((&self.argmax_pl, bg, 1u32, 1u32));
779 keep.push(meta);
780 }
781 record_submit(ctx, &passes);
782 drop(keep);
783 if emit.is_some() {
784 Ok(Some(ctx.read(&tid, 1)?[0].to_bits() as usize))
785 } else {
786 Ok(None)
787 }
788 };
789
790 let sot = self.prompt[0];
792 let lang = if self.lang_on {
793 step(sot, 0, Some(&self.mask_lang))?.expect("emit")
794 } else {
795 self.prompt[1]
796 };
797 let prompt = vec![sot, lang, self.transcribe_token, self.notimestamps_token];
798 let mut ids = prompt.clone();
799 for (i, &t) in prompt.iter().enumerate().take(prompt.len() - 1) {
800 step(t, i, None)?; }
802 let cap = (prompt.len() + max_new).min(self.max_target);
803 let mut pos = prompt.len() - 1;
804 let mut tok = prompt[pos];
805 let mut first = true;
806 loop {
807 let mask = if first {
808 &self.mask_gen0
809 } else {
810 &self.mask_gen
811 };
812 let nxt = step(tok, pos, Some(mask))?.expect("emit");
813 ids.push(nxt);
814 if nxt == self.eos || ids.len() >= cap {
815 break;
816 }
817 pos += 1;
818 tok = nxt;
819 first = false;
820 }
821 Ok(ids)
822 }
823}
824
825fn record_submit(ctx: &GpuCtx, passes: &[(&wgpu::ComputePipeline, wgpu::BindGroup, u32, u32)]) {
827 let mut enc = ctx
828 .device
829 .create_command_encoder(&wgpu::CommandEncoderDescriptor {
830 label: Some("whisper_dec"),
831 });
832 {
833 let mut p = enc.begin_compute_pass(&wgpu::ComputePassDescriptor::default());
834 for (pl, bg, gx, gy) in passes {
835 p.set_pipeline(pl);
836 p.set_bind_group(0, bg, &[]);
837 p.dispatch_workgroups(*gx, *gy, 1);
838 }
839 }
840 ctx.queue.submit(Some(enc.finish()));
841}