@tryhamster/gerbil 1.5.0 → 1.6.1

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.
Files changed (49) hide show
  1. package/dist/{architectures-Ol7yUmKf.mjs → architectures-CpmH4Gha.mjs} +86 -82
  2. package/dist/architectures-CpmH4Gha.mjs.map +1 -0
  3. package/dist/browser/index.js.map +1 -1
  4. package/dist/cli.mjs +8 -8
  5. package/dist/cli.mjs.map +1 -1
  6. package/dist/frameworks/express.mjs +1 -1
  7. package/dist/frameworks/fastify.mjs +1 -1
  8. package/dist/frameworks/hono.mjs +1 -1
  9. package/dist/frameworks/next.d.mts +2 -2
  10. package/dist/frameworks/next.mjs +1 -1
  11. package/dist/frameworks/trpc.mjs +1 -1
  12. package/dist/{gerbil-_68_JSGV.d.mts → gerbil--YDNklsY.d.mts} +2 -2
  13. package/dist/{gerbil-_68_JSGV.d.mts.map → gerbil--YDNklsY.d.mts.map} +1 -1
  14. package/dist/gerbil-BPpEvMYU.mjs +4 -0
  15. package/dist/{gerbil-DVrdjEjm.mjs → gerbil-D13QAlk2.mjs} +2 -2
  16. package/dist/{gerbil-DVrdjEjm.mjs.map → gerbil-D13QAlk2.mjs.map} +1 -1
  17. package/dist/gpu/hooks.d.mts +1 -1
  18. package/dist/gpu/index.d.mts +1 -1
  19. package/dist/gpu/index.mjs +3 -3
  20. package/dist/{gpu-B3nsz-U7.mjs → gpu-DtA28rES.mjs} +12 -8
  21. package/dist/{gpu-B3nsz-U7.mjs.map → gpu-DtA28rES.mjs.map} +1 -1
  22. package/dist/{index-BdbeBDTz.d.mts → index-CZVIHkaH.d.mts} +23 -2
  23. package/dist/index-CZVIHkaH.d.mts.map +1 -0
  24. package/dist/index.d.mts +2 -2
  25. package/dist/index.mjs +5 -5
  26. package/dist/integrations/ai-sdk.mjs +1 -1
  27. package/dist/integrations/langchain.mjs +1 -1
  28. package/dist/integrations/llamaindex.mjs +1 -1
  29. package/dist/integrations/mcp.d.mts +2 -2
  30. package/dist/integrations/mcp.mjs +4 -4
  31. package/dist/{mcp-BmAo2Ydf.mjs → mcp-DuaxaIwt.mjs} +3 -3
  32. package/dist/{mcp-BmAo2Ydf.mjs.map → mcp-DuaxaIwt.mjs.map} +1 -1
  33. package/dist/moonshine-stt-BkU9cx2d.mjs +4 -0
  34. package/dist/{moonshine-stt-BeXNc7o2.mjs → moonshine-stt-BwX7NQ0O.mjs} +910 -46
  35. package/dist/moonshine-stt-BwX7NQ0O.mjs.map +1 -0
  36. package/dist/{one-liner-D-iu4TES.mjs → one-liner-CMk_GZgS.mjs} +2 -2
  37. package/dist/{one-liner-D-iu4TES.mjs.map → one-liner-CMk_GZgS.mjs.map} +1 -1
  38. package/dist/repl-CD4NDrku.mjs +9 -0
  39. package/dist/skills/index.d.mts +2 -2
  40. package/dist/skills/index.mjs +3 -3
  41. package/dist/{skills-CVbIAHBv.mjs → skills-CJHhljKM.mjs} +2 -2
  42. package/dist/{skills-CVbIAHBv.mjs.map → skills-CJHhljKM.mjs.map} +1 -1
  43. package/package.json +1 -1
  44. package/dist/architectures-Ol7yUmKf.mjs.map +0 -1
  45. package/dist/gerbil-Za8EdZxM.mjs +0 -4
  46. package/dist/index-BdbeBDTz.d.mts.map +0 -1
  47. package/dist/moonshine-stt-BeXNc7o2.mjs.map +0 -1
  48. package/dist/moonshine-stt-hjR_SKhe.mjs +0 -4
  49. package/dist/repl-jbL3czwC.mjs +0 -9
@@ -1,4 +1,4 @@
1
- import { C as quantizeInt4, D as createDefaultHFKeyMapper, S as DEFAULT_GROUP_SIZE, T as DTYPE_BYTES, a as generateMoonshineEncoderGraph, b as nanoCodecWeightMap, i as generateMoonshineDecoderGraph, o as moonshineEncoderFrames, p as foldNanoCodecWeightNorm, s as parseMoonshineConfig, t as generateGraph, w as CANONICAL_KEYS } from "./architectures-Ol7yUmKf.mjs";
1
+ import { C as quantizeInt4, D as createDefaultHFKeyMapper, S as DEFAULT_GROUP_SIZE, T as DTYPE_BYTES, a as generateMoonshineEncoderGraph, b as nanoCodecWeightMap, i as generateMoonshineDecoderGraph, o as moonshineEncoderFrames, p as foldNanoCodecWeightNorm, s as parseMoonshineConfig, t as generateGraph, w as CANONICAL_KEYS } from "./architectures-CpmH4Gha.mjs";
2
2
 
3
3
  //#region src/gpu/device.ts
4
4
  const COPY_SRC = 4;
@@ -27,7 +27,7 @@ async function initGPU(options) {
27
27
  if (!adapter) throw new Error("No WebGPU adapter found. Your GPU may not support WebGPU, or another tab may be using it. Try closing other tabs.");
28
28
  const hasF16 = adapter.features.has("shader-f16");
29
29
  const hasSubgroups = adapter.features.has("subgroups");
30
- const hasTimestamp = typeof process !== "undefined" && process.env?.GERBIL_PROFILE != null && adapter.features.has("timestamp-query");
30
+ const hasTimestamp = typeof process !== "undefined" && (process.env?.GERBIL_PROFILE != null || process.env?.GERBIL_TS_TOTAL != null) && adapter.features.has("timestamp-query");
31
31
  const requiredFeatures = [];
32
32
  if (hasF16) requiredFeatures.push("shader-f16");
33
33
  if (hasSubgroups) requiredFeatures.push("subgroups");
@@ -2341,6 +2341,98 @@ fn main(
2341
2341
  }
2342
2342
  }
2343
2343
  `;
2344
+ const WGSL_DUAL_RMSNORM_ROPE = `\
2345
+ struct Params {
2346
+ rows0: u32, // T * num_q_heads
2347
+ rows1: u32, // T * num_kv_heads
2348
+ head_dim: u32,
2349
+ eps_bits: u32,
2350
+ num_q_heads: u32,
2351
+ num_kv_heads: u32,
2352
+ rope_base_bits: u32,
2353
+ position_offset: u32,
2354
+ rope_half: u32,
2355
+ rope_denom: u32,
2356
+ rope_active_pairs: u32,
2357
+ _pad: u32,
2358
+ }
2359
+
2360
+ @group(0) @binding(0) var<storage, read> input0: array<f32>;
2361
+ @group(0) @binding(1) var<storage, read> weight0: array<f32>;
2362
+ @group(0) @binding(2) var<storage, read> input1: array<f32>;
2363
+ @group(0) @binding(3) var<storage, read> weight1: array<f32>;
2364
+ @group(0) @binding(4) var<storage, read_write> output0: array<f32>;
2365
+ @group(0) @binding(5) var<storage, read_write> output1: array<f32>;
2366
+ @group(0) @binding(6) var<storage, read> params: Params;
2367
+
2368
+ var<workgroup> shared_sum: array<f32, 256>;
2369
+ var<workgroup> shared_row: array<f32, 256>;
2370
+
2371
+ @compute @workgroup_size(256)
2372
+ fn main(
2373
+ @builtin(local_invocation_id) lid: vec3u,
2374
+ @builtin(workgroup_id) wid: vec3u,
2375
+ ) {
2376
+ let wg = wid.x;
2377
+ if (wg >= params.rows0 + params.rows1) { return; }
2378
+
2379
+ let tid = lid.x;
2380
+ let hd = params.head_dim;
2381
+ let eps = bitcast<f32>(params.eps_bits);
2382
+
2383
+ let is1 = wg >= params.rows0;
2384
+ let row = select(wg, wg - params.rows0, is1);
2385
+ let row_offset = row * hd;
2386
+ let heads = select(params.num_q_heads, params.num_kv_heads, is1);
2387
+ let pos = row / heads;
2388
+
2389
+ // ── Phase 1: row sum of squares ──
2390
+ var val: f32 = 0.0;
2391
+ if (tid < hd) {
2392
+ if (is1) { val = input1[row_offset + tid]; } else { val = input0[row_offset + tid]; }
2393
+ }
2394
+ shared_sum[tid] = val * val;
2395
+ workgroupBarrier();
2396
+ var stride: u32 = 128u;
2397
+ while (stride > 0u) {
2398
+ if (tid < stride) { shared_sum[tid] += shared_sum[tid + stride]; }
2399
+ workgroupBarrier();
2400
+ stride = stride / 2u;
2401
+ }
2402
+ let rms = sqrt(shared_sum[0] / f32(hd) + eps);
2403
+
2404
+ // ── Phase 2: normalized row into shared memory ──
2405
+ if (tid < hd) {
2406
+ var w: f32;
2407
+ if (is1) { w = weight1[tid]; } else { w = weight0[tid]; }
2408
+ shared_row[tid] = (val / rms) * w;
2409
+ }
2410
+ workgroupBarrier();
2411
+
2412
+ // ── Phase 3: rotate_half rotation from shared pairs, write out ──
2413
+ if (tid < hd) {
2414
+ let rope_half = params.rope_half;
2415
+ var out_val = shared_row[tid];
2416
+ if (tid < 2u * rope_half) {
2417
+ let is_hi = tid >= rope_half;
2418
+ let pair_idx = select(tid, tid - rope_half, is_hi);
2419
+ var cos_val = 1.0;
2420
+ var sin_val = 0.0;
2421
+ if (pair_idx < params.rope_active_pairs) {
2422
+ let rope_base = bitcast<f32>(params.rope_base_bits);
2423
+ let position = f32(pos + params.position_offset);
2424
+ let freq = position / pow(rope_base, f32(2u * pair_idx) / f32(params.rope_denom));
2425
+ cos_val = cos(freq);
2426
+ sin_val = sin(freq);
2427
+ }
2428
+ let lo = shared_row[pair_idx];
2429
+ let hi = shared_row[pair_idx + rope_half];
2430
+ out_val = select(lo * cos_val - hi * sin_val, lo * sin_val + hi * cos_val, is_hi);
2431
+ }
2432
+ if (is1) { output1[row_offset + tid] = out_val; } else { output0[row_offset + tid] = out_val; }
2433
+ }
2434
+ }
2435
+ `;
2344
2436
  const WGSL_LAYERNORM = `\
2345
2437
  // Layer Normalization: output = ((x - mean) / sqrt(var + eps)) * weight + bias
2346
2438
 
@@ -3864,8 +3956,10 @@ fn main(
3864
3956
  `;
3865
3957
  const WGSL_SWIGLU_MATVEC_INT4 = `\
3866
3958
  // CONSTRAINT: N_TILE must equal workgroup_size / K_THREADS
3867
- const N_TILE: u32 = 8u;
3868
- const K_THREADS: u32 = 32u;
3959
+ // 16/16 matches the plain-matvec sweep winner (8/32 measured slower at every
3960
+ // production shape; this fused kernel had never been re-swept).
3961
+ const N_TILE: u32 = 16u;
3962
+ const K_THREADS: u32 = 16u;
3869
3963
 
3870
3964
  struct Params {
3871
3965
  K: u32,
@@ -3969,8 +4063,8 @@ fn main(
3969
4063
  }
3970
4064
  `;
3971
4065
  const WGSL_DUAL_MATVEC_INT4 = `\
3972
- const N_TILE: u32 = 8u;
3973
- const K_THREADS: u32 = 32u;
4066
+ const N_TILE: u32 = 16u;
4067
+ const K_THREADS: u32 = 16u;
3974
4068
 
3975
4069
  struct Params {
3976
4070
  K: u32,
@@ -4233,6 +4327,235 @@ fn main(@builtin(global_invocation_id) gid: vec3u) {
4233
4327
  output[t * params.channels + c] = sum / (1.0 + exp(max(-sum, -80.0)));
4234
4328
  }
4235
4329
  `;
4330
+ const WGSL_CAUSAL_CONV1D_SILU_UPDATE = `\
4331
+ struct Params {
4332
+ seq_len: u32,
4333
+ channels: u32,
4334
+ kernel_size: u32,
4335
+ _pad: u32,
4336
+ }
4337
+
4338
+ @group(0) @binding(0) var<storage, read> input: array<f32>;
4339
+ @group(0) @binding(1) var<storage, read> weight: array<f32>;
4340
+ @group(0) @binding(2) var<storage, read_write> conv_state: array<f32>;
4341
+ @group(0) @binding(3) var<storage, read_write> output: array<f32>;
4342
+ @group(0) @binding(4) var<storage, read> params: Params;
4343
+
4344
+ @compute @workgroup_size(256)
4345
+ fn main(@builtin(global_invocation_id) gid: vec3u) {
4346
+ let idx = gid.x;
4347
+ let total = params.seq_len * params.channels;
4348
+ if (idx >= total) { return; }
4349
+
4350
+ let t = idx / params.channels;
4351
+ let c = idx % params.channels;
4352
+ let state_size = params.kernel_size - 1u;
4353
+
4354
+ // Snapshot this channel's rolling state (kernel_size <= 8) before any write.
4355
+ var olds: array<f32, 8>;
4356
+ for (var s: u32 = 0u; s < state_size; s = s + 1u) {
4357
+ olds[s] = conv_state[s * params.channels + c];
4358
+ }
4359
+
4360
+ var sum: f32 = 0.0;
4361
+ for (var k: u32 = 0u; k < params.kernel_size; k = k + 1u) {
4362
+ let src_t = i32(t) - i32(k);
4363
+ var in_val: f32 = 0.0;
4364
+ if (src_t >= 0) {
4365
+ in_val = input[u32(src_t) * params.channels + c];
4366
+ } else {
4367
+ let state_t = i32(state_size) + src_t;
4368
+ if (state_t >= 0) {
4369
+ in_val = olds[u32(state_t)];
4370
+ }
4371
+ }
4372
+ let w_val = weight[c * params.kernel_size + (params.kernel_size - 1u - k)];
4373
+ sum += in_val * w_val;
4374
+ }
4375
+
4376
+ // Fused SiLU: silu(x) = x / (1 + exp(-x))
4377
+ output[t * params.channels + c] = sum / (1.0 + exp(max(-sum, -80.0)));
4378
+
4379
+ // Rolling-state shift (decode T=1): new state = [olds[1..], input[0,c]].
4380
+ if (params.seq_len == 1u && t == 0u) {
4381
+ for (var s: u32 = 0u; s + 1u < state_size; s = s + 1u) {
4382
+ conv_state[s * params.channels + c] = olds[s + 1u];
4383
+ }
4384
+ if (state_size > 0u) {
4385
+ conv_state[(state_size - 1u) * params.channels + c] = input[c];
4386
+ }
4387
+ }
4388
+ }
4389
+ `;
4390
+ const WGSL_CAUSAL_CONV1D_GATED_UPDATE = `\
4391
+ struct Params {
4392
+ seq_len: u32,
4393
+ channels: u32,
4394
+ kernel_size: u32,
4395
+ _pad: u32,
4396
+ }
4397
+
4398
+ @group(0) @binding(0) var<storage, read> input: array<f32>;
4399
+ @group(0) @binding(1) var<storage, read> weight: array<f32>;
4400
+ @group(0) @binding(2) var<storage, read_write> conv_state: array<f32>;
4401
+ @group(0) @binding(3) var<storage, read> gate: array<f32>;
4402
+ @group(0) @binding(4) var<storage, read_write> output: array<f32>;
4403
+ @group(0) @binding(5) var<storage, read> params: Params;
4404
+
4405
+ @compute @workgroup_size(256)
4406
+ fn main(@builtin(global_invocation_id) gid: vec3u) {
4407
+ let idx = gid.x;
4408
+ let total = params.seq_len * params.channels;
4409
+ if (idx >= total) { return; }
4410
+
4411
+ let t = idx / params.channels;
4412
+ let c = idx % params.channels;
4413
+ let state_size = params.kernel_size - 1u;
4414
+
4415
+ var olds: array<f32, 8>;
4416
+ for (var s: u32 = 0u; s < state_size; s = s + 1u) {
4417
+ olds[s] = conv_state[s * params.channels + c];
4418
+ }
4419
+
4420
+ var sum: f32 = 0.0;
4421
+ for (var k: u32 = 0u; k < params.kernel_size; k = k + 1u) {
4422
+ let src_t = i32(t) - i32(k);
4423
+ var in_val: f32 = 0.0;
4424
+ if (src_t >= 0) {
4425
+ in_val = input[u32(src_t) * params.channels + c];
4426
+ } else {
4427
+ let state_t = i32(state_size) + src_t;
4428
+ if (state_t >= 0) {
4429
+ in_val = olds[u32(state_t)];
4430
+ }
4431
+ }
4432
+ let w_val = weight[c * params.kernel_size + (params.kernel_size - 1u - k)];
4433
+ sum += in_val * w_val;
4434
+ }
4435
+
4436
+ output[t * params.channels + c] = gate[idx] * sum;
4437
+
4438
+ if (params.seq_len == 1u && t == 0u) {
4439
+ for (var s: u32 = 0u; s + 1u < state_size; s = s + 1u) {
4440
+ conv_state[s * params.channels + c] = olds[s + 1u];
4441
+ }
4442
+ if (state_size > 0u) {
4443
+ conv_state[(state_size - 1u) * params.channels + c] = input[c];
4444
+ }
4445
+ }
4446
+ }
4447
+ `;
4448
+ const WGSL_CAUSAL_CONV1D_GATED_BCX = `\
4449
+ struct Params {
4450
+ seq_len: u32,
4451
+ channels: u32,
4452
+ kernel_size: u32,
4453
+ in_width: u32, // bcx row stride = 3 * channels
4454
+ }
4455
+
4456
+ @group(0) @binding(0) var<storage, read> bcx: array<f32>;
4457
+ @group(0) @binding(1) var<storage, read> weight: array<f32>;
4458
+ @group(0) @binding(2) var<storage, read_write> conv_state: array<f32>;
4459
+ @group(0) @binding(3) var<storage, read_write> output: array<f32>;
4460
+ @group(0) @binding(4) var<storage, read> params: Params;
4461
+
4462
+ fn bx_at(t: u32, c: u32) -> f32 {
4463
+ let row = t * params.in_width;
4464
+ return bcx[row + c] * bcx[row + 2u * params.channels + c];
4465
+ }
4466
+
4467
+ @compute @workgroup_size(256)
4468
+ fn main(@builtin(global_invocation_id) gid: vec3u) {
4469
+ let idx = gid.x;
4470
+ let total = params.seq_len * params.channels;
4471
+ if (idx >= total) { return; }
4472
+
4473
+ let t = idx / params.channels;
4474
+ let c = idx % params.channels;
4475
+ let state_size = params.kernel_size - 1u;
4476
+
4477
+ // Snapshot this channel's rolling state (kernel_size <= 8) before any write.
4478
+ var olds: array<f32, 8>;
4479
+ for (var s: u32 = 0u; s < state_size; s = s + 1u) {
4480
+ olds[s] = conv_state[s * params.channels + c];
4481
+ }
4482
+
4483
+ var sum: f32 = 0.0;
4484
+ for (var k: u32 = 0u; k < params.kernel_size; k = k + 1u) {
4485
+ let src_t = i32(t) - i32(k);
4486
+ var in_val: f32 = 0.0;
4487
+ if (src_t >= 0) {
4488
+ in_val = bx_at(u32(src_t), c);
4489
+ } else {
4490
+ let state_t = i32(state_size) + src_t;
4491
+ if (state_t >= 0) {
4492
+ in_val = olds[u32(state_t)];
4493
+ }
4494
+ }
4495
+ let w_val = weight[c * params.kernel_size + (params.kernel_size - 1u - k)];
4496
+ sum += in_val * w_val;
4497
+ }
4498
+
4499
+ // Fused multiplicative gate from the C block.
4500
+ output[t * params.channels + c] = bcx[t * params.in_width + params.channels + c] * sum;
4501
+
4502
+ // Rolling-state shift (decode T=1 only; prefill uses ConvStateUpdateBcx).
4503
+ if (params.seq_len == 1u && t == 0u) {
4504
+ for (var s: u32 = 0u; s + 1u < state_size; s = s + 1u) {
4505
+ conv_state[s * params.channels + c] = olds[s + 1u];
4506
+ }
4507
+ if (state_size > 0u) {
4508
+ conv_state[(state_size - 1u) * params.channels + c] = bx_at(0u, c);
4509
+ }
4510
+ }
4511
+ }
4512
+ `;
4513
+ const WGSL_CONV_STATE_UPDATE_BCX = `\
4514
+ // ConvStateUpdate reading raw bcx (computes B*x for the copied timesteps).
4515
+ struct Params {
4516
+ seq_len: u32,
4517
+ channels: u32,
4518
+ state_size: u32,
4519
+ in_width: u32,
4520
+ }
4521
+
4522
+ @group(0) @binding(0) var<storage, read> bcx: array<f32>;
4523
+ @group(0) @binding(1) var<storage, read_write> conv_state: array<f32>;
4524
+ @group(0) @binding(2) var<storage, read> params: Params;
4525
+
4526
+ fn bx_at(t: u32, c: u32) -> f32 {
4527
+ let row = t * params.in_width;
4528
+ return bcx[row + c] * bcx[row + 2u * params.channels + c];
4529
+ }
4530
+
4531
+ @compute @workgroup_size(256)
4532
+ fn main(@builtin(global_invocation_id) gid: vec3u) {
4533
+ let c = gid.x;
4534
+ if (c >= params.channels) { return; }
4535
+ let ss = params.state_size;
4536
+
4537
+ if (params.seq_len >= ss) {
4538
+ for (var s: u32 = 0u; s < ss; s = s + 1u) {
4539
+ conv_state[s * params.channels + c] = bx_at(params.seq_len - ss + s, c);
4540
+ }
4541
+ } else {
4542
+ var olds: array<f32, 8>;
4543
+ for (var s: u32 = 0u; s < ss; s = s + 1u) {
4544
+ olds[s] = conv_state[s * params.channels + c];
4545
+ }
4546
+ let shift = ss - params.seq_len;
4547
+ for (var s: u32 = 0u; s < ss; s = s + 1u) {
4548
+ var val: f32 = 0.0;
4549
+ if (s < shift) {
4550
+ val = olds[s + params.seq_len];
4551
+ } else {
4552
+ val = bx_at(s - shift, c);
4553
+ }
4554
+ conv_state[s * params.channels + c] = val;
4555
+ }
4556
+ }
4557
+ }
4558
+ `;
4236
4559
  const WGSL_CAUSAL_CONV1D_GATED = `\
4237
4560
  // Causal 1D depthwise convolution with a fused multiplicative output gate:
4238
4561
  // output = gate * conv(input)
@@ -4470,20 +4793,45 @@ struct Params {
4470
4793
  @group(0) @binding(6) var<storage, read_write> output: array<f32>;
4471
4794
  @group(0) @binding(7) var<storage, read> params: Params;
4472
4795
 
4473
- // Shared memory for Q and K vectors (L2-normalized) and reduction
4796
+ // OCCUPANCY FAN-OUT: one workgroup per (head, d-chunk) instead of per head.
4797
+ // Each head's state column d is touched ONLY by the workgroup owning d's chunk
4798
+ // (kv_dot for lane d reads state[:, d]; the rank-1 update writes state[:, d]),
4799
+ // so chunks are fully independent across timesteps — no cross-workgroup sync.
4800
+ // 16 heads → 128 workgroups (vs 16), each 128 threads = D_LANES(16) × K_SPLITS(8).
4801
+ // Requires val_dim % (D_CHUNKS*... ) handled via bounds guards; key_dim <= 128.
4802
+
4803
+ const D_CHUNKS: u32 = 8u; // workgroups per head (must match JS dispatch)
4804
+ const D_LANES: u32 = 16u; // d lanes per workgroup
4805
+ const K_SPLITS: u32 = 8u; // threads cooperating on each d lane's k loop
4806
+
4807
+ // Shared memory for Q and K vectors (L2-normalized), reduction, partials
4474
4808
  var<workgroup> shared_q: array<f32, 128>;
4475
4809
  var<workgroup> shared_k: array<f32, 128>;
4476
4810
  var<workgroup> shared_norm: array<f32, 128>;
4811
+ var<workgroup> shared_partial: array<f32, 128>; // [D_LANES][K_SPLITS]
4812
+ var<workgroup> shared_kvmem: array<f32, D_LANES>;
4477
4813
 
4478
4814
  @compute @workgroup_size(128)
4479
4815
  fn main(
4480
4816
  @builtin(local_invocation_id) lid: vec3u,
4481
4817
  @builtin(workgroup_id) wid: vec3u,
4482
4818
  ) {
4483
- let h = wid.x; // uniform within workgroup
4819
+ let h = wid.x / D_CHUNKS;
4820
+ let chunk = wid.x % D_CHUNKS;
4484
4821
  if (h >= params.num_heads) { return; }
4485
4822
 
4486
- let d = lid.x; // 0..127, all threads participate in barriers
4823
+ let tid = lid.x; // 0..127
4824
+ let d_local = tid % D_LANES; // lane within chunk
4825
+ let k_split = tid / D_LANES; // 0..K_SPLITS-1
4826
+ let d_per_chunk = (params.val_dim + D_CHUNKS - 1u) / D_CHUNKS;
4827
+ // This lane's output dim (may exceed val_dim for ragged sizes — guarded below).
4828
+ // d_per_chunk > D_LANES is unsupported (val_dim > 128 with D_CHUNKS=8).
4829
+ let d = chunk * d_per_chunk + d_local;
4830
+ let d_ok = d < params.val_dim && d_local < d_per_chunk;
4831
+
4832
+ let k_slice = (params.key_dim + K_SPLITS - 1u) / K_SPLITS;
4833
+ let k_begin = k_split * k_slice;
4834
+ let k_end = min(k_begin + k_slice, params.key_dim);
4487
4835
 
4488
4836
  let A_val = -exp(A_log[h]);
4489
4837
  let state_base = h * params.key_dim * params.val_dim;
@@ -4497,37 +4845,48 @@ fn main(
4497
4845
  for (var t: u32 = 0u; t < params.T; t = t + 1u) {
4498
4846
  let qkv_base = t * params.qkv_dim;
4499
4847
 
4500
- // ── Load Q and K into shared memory ──
4501
- shared_q[d] = qkv_conv[qkv_base + q_off + d];
4502
- shared_k[d] = qkv_conv[qkv_base + k_off + d];
4848
+ // ── Load Q and K into shared memory (lane tid covers key_dim <= 128) ──
4849
+ if (tid < params.key_dim) {
4850
+ shared_q[tid] = qkv_conv[qkv_base + q_off + tid];
4851
+ shared_k[tid] = qkv_conv[qkv_base + k_off + tid];
4852
+ shared_norm[tid] = shared_q[tid] * shared_q[tid];
4853
+ } else {
4854
+ shared_norm[tid] = 0.0;
4855
+ }
4503
4856
  workgroupBarrier();
4504
4857
 
4505
4858
  // ── L2-normalize Q (parallel reduction) ──
4506
- shared_norm[d] = shared_q[d] * shared_q[d];
4507
- workgroupBarrier();
4508
4859
  var stride: u32 = 64u;
4509
4860
  while (stride > 0u) {
4510
- if (d < stride) { shared_norm[d] += shared_norm[d + stride]; }
4861
+ if (tid < stride) { shared_norm[tid] += shared_norm[tid + stride]; }
4511
4862
  workgroupBarrier();
4512
4863
  stride /= 2u;
4513
4864
  }
4514
4865
  let q_inv_norm = 1.0 / sqrt(shared_norm[0] + 1e-12);
4515
- shared_q[d] *= q_inv_norm;
4866
+ workgroupBarrier();
4516
4867
 
4517
4868
  // ── L2-normalize K (parallel reduction) ──
4518
- shared_norm[d] = shared_k[d] * shared_k[d];
4869
+ if (tid < params.key_dim) {
4870
+ shared_q[tid] *= q_inv_norm;
4871
+ shared_norm[tid] = shared_k[tid] * shared_k[tid];
4872
+ } else {
4873
+ shared_norm[tid] = 0.0;
4874
+ }
4519
4875
  workgroupBarrier();
4520
4876
  stride = 64u;
4521
4877
  while (stride > 0u) {
4522
- if (d < stride) { shared_norm[d] += shared_norm[d + stride]; }
4878
+ if (tid < stride) { shared_norm[tid] += shared_norm[tid + stride]; }
4523
4879
  workgroupBarrier();
4524
4880
  stride /= 2u;
4525
4881
  }
4526
4882
  let k_inv_norm = 1.0 / sqrt(shared_norm[0] + 1e-12);
4527
- shared_k[d] *= k_inv_norm;
4883
+ workgroupBarrier();
4884
+ if (tid < params.key_dim) {
4885
+ shared_k[tid] *= k_inv_norm;
4886
+ }
4528
4887
  workgroupBarrier();
4529
4888
 
4530
- // ── Compute decay and beta ──
4889
+ // ── Compute decay and beta (redundantly per thread — trivial scalars) ──
4531
4890
  let a_val = a_input[t * params.num_heads + h];
4532
4891
  let b_val = b_input[t * params.num_heads + h];
4533
4892
  let dt_in = a_val + dt_bias[h];
@@ -4536,32 +4895,52 @@ fn main(
4536
4895
  let decay = exp(max(g, -80.0));
4537
4896
  let beta = 1.0 / (1.0 + exp(max(-b_val, -80.0)));
4538
4897
 
4539
- // ── 1+2. Retrieve from memory (decay is a per-step scalar, so it factors
4540
- // out of the dot product): kv_mem[d] = decay * Σ_k state[k,d] * k_norm[k] ──
4541
- let v_val = qkv_conv[qkv_base + v_off + d];
4898
+ // ── 1+2. kv_mem[d] = decay * Σ_k state[k,d] * k_norm[k], k split 8 ways ──
4542
4899
  var kv_dot: f32 = 0.0;
4543
- for (var k: u32 = 0u; k < params.key_dim; k = k + 1u) {
4544
- kv_dot += f32(ssm_state[state_base + k * params.val_dim + d]) * shared_k[k];
4900
+ if (d_ok) {
4901
+ for (var k: u32 = k_begin; k < k_end; k = k + 1u) {
4902
+ kv_dot += f32(ssm_state[state_base + k * params.val_dim + d]) * shared_k[k];
4903
+ }
4545
4904
  }
4546
- let kv_mem = kv_dot * decay;
4547
-
4548
- // ── 3. Delta rule: delta[d] = (v[d] - kv_mem[d]) * beta ──
4549
- let delta_val = (v_val - kv_mem) * beta;
4905
+ shared_partial[d_local * K_SPLITS + k_split] = kv_dot;
4906
+ workgroupBarrier();
4907
+ if (k_split == 0u) {
4908
+ var acc: f32 = 0.0;
4909
+ for (var j: u32 = 0u; j < K_SPLITS; j = j + 1u) {
4910
+ acc += shared_partial[d_local * K_SPLITS + j];
4911
+ }
4912
+ shared_kvmem[d_local] = acc;
4913
+ }
4914
+ workgroupBarrier();
4550
4915
 
4551
- // ── 4+5. Fused decay + rank-1 update + output in ONE pass over state
4552
- // (one read + one write instead of three reads + two writes):
4553
- // state'[k,d] = state[k,d] * decay + k_norm[k] * delta[d]
4554
- // out[d] = Σ_k state'[k,d] * q_norm[k] * scale ──
4555
- var out_val: f32 = 0.0;
4556
- for (var k: u32 = 0u; k < params.key_dim; k = k + 1u) {
4557
- let idx = state_base + k * params.val_dim + d;
4558
- let s = f32(ssm_state[idx]) * decay + shared_k[k] * delta_val;
4559
- ssm_state[idx] = f16(s);
4560
- out_val += s * shared_q[k];
4916
+ // ── 3. Delta rule (each of the d's 8 threads recomputes cheap) ──
4917
+ var delta_val: f32 = 0.0;
4918
+ if (d_ok) {
4919
+ let v_val = qkv_conv[qkv_base + v_off + d];
4920
+ let kv_mem = shared_kvmem[d_local] * decay;
4921
+ delta_val = (v_val - kv_mem) * beta;
4561
4922
  }
4562
- output[t * out_stride + h * params.val_dim + d] = out_val * scale;
4563
4923
 
4564
- workgroupBarrier(); // sync before next timestep loads shared_q/k
4924
+ // ── 4+5. Fused decay + rank-1 update + output, k split 8 ways ──
4925
+ var out_dot: f32 = 0.0;
4926
+ if (d_ok) {
4927
+ for (var k: u32 = k_begin; k < k_end; k = k + 1u) {
4928
+ let idx = state_base + k * params.val_dim + d;
4929
+ let s = f32(ssm_state[idx]) * decay + shared_k[k] * delta_val;
4930
+ ssm_state[idx] = f16(s);
4931
+ out_dot += s * shared_q[k];
4932
+ }
4933
+ }
4934
+ shared_partial[d_local * K_SPLITS + k_split] = out_dot;
4935
+ workgroupBarrier();
4936
+ if (k_split == 0u && d_ok) {
4937
+ var acc: f32 = 0.0;
4938
+ for (var j: u32 = 0u; j < K_SPLITS; j = j + 1u) {
4939
+ acc += shared_partial[d_local * K_SPLITS + j];
4940
+ }
4941
+ output[t * out_stride + h * params.val_dim + d] = acc * scale;
4942
+ }
4943
+ workgroupBarrier(); // sync before next timestep reuses shared arrays
4565
4944
  }
4566
4945
  }
4567
4946
  `;
@@ -6165,7 +6544,7 @@ const SWIGLU_MATVEC_INT4_SPEC = {
6165
6544
  getDispatchSize(op) {
6166
6545
  const N = op.attributes.N;
6167
6546
  return [
6168
- cdiv(N, 8),
6547
+ cdiv(N, 16),
6169
6548
  1,
6170
6549
  1
6171
6550
  ];
@@ -6200,7 +6579,7 @@ const DUAL_MATVEC_INT4_SPEC = {
6200
6579
  getDispatchSize(op) {
6201
6580
  const N = op.attributes.N;
6202
6581
  return [
6203
- cdiv(N, 8),
6582
+ cdiv(N, 16),
6204
6583
  1,
6205
6584
  1
6206
6585
  ];
@@ -6403,6 +6782,62 @@ const embeddingInt4Spec = {
6403
6782
  ]);
6404
6783
  }
6405
6784
  };
6785
+ const DUAL_RMSNORM_ROPE_SPEC = {
6786
+ shaderCode: WGSL_DUAL_RMSNORM_ROPE,
6787
+ entryPoint: "main",
6788
+ bindings: [
6789
+ { type: "storage-read" },
6790
+ { type: "storage-read" },
6791
+ { type: "storage-read" },
6792
+ { type: "storage-read" },
6793
+ { type: "storage-read-write" },
6794
+ { type: "storage-read-write" },
6795
+ { type: "uniform" }
6796
+ ],
6797
+ getDispatchSize(op, resolvedShapes) {
6798
+ const num_q_heads = op.attributes.num_q_heads;
6799
+ const num_kv_heads = op.attributes.num_kv_heads;
6800
+ const seqRef = op.attributes.seq_len_tensor;
6801
+ let T;
6802
+ if (seqRef && resolvedShapes[seqRef]) T = resolvedShapes[seqRef][0];
6803
+ else T = op.attributes.seq_len ?? 1;
6804
+ return [
6805
+ T * (num_q_heads + num_kv_heads),
6806
+ 1,
6807
+ 1
6808
+ ];
6809
+ },
6810
+ buildParams(op, resolvedShapes, context) {
6811
+ const head_dim = op.attributes.hidden_size;
6812
+ const eps = op.attributes.eps ?? 1e-6;
6813
+ const num_q_heads = op.attributes.num_q_heads;
6814
+ const num_kv_heads = op.attributes.num_kv_heads;
6815
+ const rope_base = op.attributes.rope_base;
6816
+ const rope_dim = op.attributes.rope_dim ?? head_dim;
6817
+ const rope_half = op.attributes.rope_half ?? rope_dim / 2;
6818
+ const rope_denom = op.attributes.rope_denom ?? rope_dim;
6819
+ const rope_active_pairs = op.attributes.rope_active_pairs ?? rope_dim / 2;
6820
+ const position_offset = context?.seqPos ?? op.attributes.position_offset ?? 0;
6821
+ const seqRef = op.attributes.seq_len_tensor;
6822
+ let T;
6823
+ if (seqRef && resolvedShapes[seqRef]) T = resolvedShapes[seqRef][0];
6824
+ else T = op.attributes.seq_len ?? 1;
6825
+ return buildUniformBuffer([
6826
+ T * num_q_heads,
6827
+ T * num_kv_heads,
6828
+ head_dim,
6829
+ f32BitsToU32(eps),
6830
+ num_q_heads,
6831
+ num_kv_heads,
6832
+ f32BitsToU32(rope_base),
6833
+ position_offset,
6834
+ rope_half,
6835
+ rope_denom,
6836
+ rope_active_pairs,
6837
+ 0
6838
+ ]);
6839
+ }
6840
+ };
6406
6841
  const ropeSpec = {
6407
6842
  shaderCode: WGSL_ROPE,
6408
6843
  entryPoint: "main",
@@ -6907,6 +7342,134 @@ const causalConv1dSiluSpec = {
6907
7342
  ]);
6908
7343
  }
6909
7344
  };
7345
+ const convUpdateDispatch = {
7346
+ getDispatchSize(op, resolvedShapes) {
7347
+ const channels = op.attributes.channels;
7348
+ const ref = op.attributes.seq_len_tensor;
7349
+ let seq_len;
7350
+ if (ref && resolvedShapes[ref]) seq_len = resolvedShapes[ref][0];
7351
+ else seq_len = op.attributes.seq_len;
7352
+ return [
7353
+ cdiv(seq_len * channels, 256),
7354
+ 1,
7355
+ 1
7356
+ ];
7357
+ },
7358
+ buildParams(op, resolvedShapes) {
7359
+ const channels = op.attributes.channels;
7360
+ const kernel_size = op.attributes.kernel_size;
7361
+ const ref = op.attributes.seq_len_tensor;
7362
+ let seq_len;
7363
+ if (ref && resolvedShapes[ref]) seq_len = resolvedShapes[ref][0];
7364
+ else seq_len = op.attributes.seq_len;
7365
+ return buildUniformBuffer([
7366
+ seq_len,
7367
+ channels,
7368
+ kernel_size,
7369
+ 0
7370
+ ]);
7371
+ }
7372
+ };
7373
+ const CAUSAL_CONV1D_SILU_UPDATE_SPEC = {
7374
+ shaderCode: WGSL_CAUSAL_CONV1D_SILU_UPDATE,
7375
+ entryPoint: "main",
7376
+ bindings: [
7377
+ { type: "storage-read" },
7378
+ { type: "storage-read" },
7379
+ { type: "storage-read-write" },
7380
+ { type: "storage-read-write" },
7381
+ { type: "uniform" }
7382
+ ],
7383
+ getDispatchSize: convUpdateDispatch.getDispatchSize,
7384
+ buildParams: convUpdateDispatch.buildParams
7385
+ };
7386
+ const CAUSAL_CONV1D_GATED_UPDATE_SPEC = {
7387
+ shaderCode: WGSL_CAUSAL_CONV1D_GATED_UPDATE,
7388
+ entryPoint: "main",
7389
+ bindings: [
7390
+ { type: "storage-read" },
7391
+ { type: "storage-read" },
7392
+ { type: "storage-read-write" },
7393
+ { type: "storage-read" },
7394
+ { type: "storage-read-write" },
7395
+ { type: "uniform" }
7396
+ ],
7397
+ getDispatchSize: convUpdateDispatch.getDispatchSize,
7398
+ buildParams: convUpdateDispatch.buildParams
7399
+ };
7400
+ const bcxDispatch = (op, resolvedShapes) => {
7401
+ const channels = op.attributes.channels;
7402
+ const ref = op.attributes.seq_len_tensor;
7403
+ let seq_len;
7404
+ if (ref && resolvedShapes[ref]) seq_len = resolvedShapes[ref][0];
7405
+ else seq_len = op.attributes.seq_len ?? 1;
7406
+ return {
7407
+ channels,
7408
+ seq_len
7409
+ };
7410
+ };
7411
+ const CAUSAL_CONV1D_GATED_BCX_SPEC = {
7412
+ shaderCode: WGSL_CAUSAL_CONV1D_GATED_BCX,
7413
+ entryPoint: "main",
7414
+ bindings: [
7415
+ { type: "storage-read" },
7416
+ { type: "storage-read" },
7417
+ { type: "storage-read-write" },
7418
+ { type: "storage-read-write" },
7419
+ { type: "uniform" }
7420
+ ],
7421
+ getDispatchSize(op, resolvedShapes) {
7422
+ const { channels, seq_len } = bcxDispatch(op, resolvedShapes);
7423
+ return [
7424
+ cdiv(seq_len * channels, 256),
7425
+ 1,
7426
+ 1
7427
+ ];
7428
+ },
7429
+ buildParams(op, resolvedShapes) {
7430
+ const { channels, seq_len } = bcxDispatch(op, resolvedShapes);
7431
+ const kernel_size = op.attributes.kernel_size;
7432
+ const in_width = op.attributes.in_width;
7433
+ return buildUniformBuffer([
7434
+ seq_len,
7435
+ channels,
7436
+ kernel_size,
7437
+ in_width
7438
+ ]);
7439
+ }
7440
+ };
7441
+ const CONV_STATE_UPDATE_BCX_SPEC = {
7442
+ shaderCode: WGSL_CONV_STATE_UPDATE_BCX,
7443
+ entryPoint: "main",
7444
+ bindings: [
7445
+ { type: "storage-read" },
7446
+ { type: "storage-read-write" },
7447
+ { type: "uniform" }
7448
+ ],
7449
+ getDispatchSize(op) {
7450
+ const channels = op.attributes.channels;
7451
+ return [
7452
+ cdiv(channels, 256),
7453
+ 1,
7454
+ 1
7455
+ ];
7456
+ },
7457
+ buildParams(op, resolvedShapes) {
7458
+ const channels = op.attributes.channels;
7459
+ const state_size = op.attributes.state_size;
7460
+ const in_width = op.attributes.in_width;
7461
+ const ref = op.attributes.T_tensor;
7462
+ let T;
7463
+ if (ref && resolvedShapes[ref]) T = resolvedShapes[ref][0];
7464
+ else T = op.attributes.T ?? 1;
7465
+ return buildUniformBuffer([
7466
+ T,
7467
+ channels,
7468
+ state_size,
7469
+ in_width
7470
+ ]);
7471
+ }
7472
+ };
6910
7473
  const causalConv1dGatedSpec = {
6911
7474
  shaderCode: WGSL_CAUSAL_CONV1D_GATED,
6912
7475
  entryPoint: "main",
@@ -7008,7 +7571,14 @@ const mambaSSMSpec = {
7008
7571
  };
7009
7572
  const MAMBA_SSM_F16_SPEC = {
7010
7573
  ...mambaSSMSpec,
7011
- shaderCode: WGSL_MAMBA_SSM_F16
7574
+ shaderCode: WGSL_MAMBA_SSM_F16,
7575
+ getDispatchSize(op) {
7576
+ return [
7577
+ op.attributes.num_heads * 8,
7578
+ 1,
7579
+ 1
7580
+ ];
7581
+ }
7012
7582
  };
7013
7583
  const kvCacheAppendSpec = {
7014
7584
  shaderCode: WGSL_KV_CACHE_APPEND,
@@ -8059,6 +8629,8 @@ const KERNEL_REGISTRY = {
8059
8629
  CausalConv1d: causalConv1dSpec,
8060
8630
  CausalConv1dSiLU: causalConv1dSiluSpec,
8061
8631
  CausalConv1dGated: causalConv1dGatedSpec,
8632
+ CausalConv1dGatedBcx: CAUSAL_CONV1D_GATED_BCX_SPEC,
8633
+ ConvStateUpdateBcx: CONV_STATE_UPDATE_BCX_SPEC,
8062
8634
  SigmoidGate: sigmoidGateSpec,
8063
8635
  MambaSSM: mambaSSMSpec,
8064
8636
  KVCacheAppend: kvCacheAppendSpec,
@@ -8341,7 +8913,10 @@ var Executor = class Executor {
8341
8913
  this.fuseDualKVCacheAppendEntries();
8342
8914
  this.fuseGatedOProjDecodeEntries();
8343
8915
  this.fuseSwiGLUGatedProjDecodeEntries();
8916
+ this.fuseDualRMSNormRoPEDecodeEntries();
8344
8917
  this.fuseDualRMSNormDecodeEntries();
8918
+ this.fuseConvStateUpdateDecodeEntries();
8919
+ this.fuseAddRMSNormDecodeEntries();
8345
8920
  console.log(`[executor] decode: ${this.decodeEntries.length} dispatches/token`);
8346
8921
  const logitsBuffer = this.getBuffer("logits");
8347
8922
  if (logitsBuffer) {
@@ -8908,7 +9483,39 @@ var Executor = class Executor {
8908
9483
  }
8909
9484
  const encoder = this.ctx.device.createCommandEncoder({ label: "decode_step" });
8910
9485
  if (tokenId === null) encoder.copyBufferToBuffer(this.argmaxResultBuffer, 0, this.inputIdsBuffer, 0, 4);
8911
- const pass = encoder.beginComputePass({ label: "decode_pass" });
9486
+ const g = globalThis;
9487
+ if (process.env?.GERBIL_TS_TOTAL != null) {
9488
+ g.__tsTotal ??= {
9489
+ ns: 0,
9490
+ steps: 0,
9491
+ seq: 0,
9492
+ inFlight: false
9493
+ };
9494
+ g.__tsTotal.seq++;
9495
+ }
9496
+ const tsTotal = this.ctx.hasTimestamp && typeof process !== "undefined" && process.env?.GERBIL_TS_TOTAL != null && g.__tsTotal.seq % 8 === 0 && !g.__tsTotal.inFlight;
9497
+ if (tsTotal && !this.querySet) {
9498
+ this.querySet = this.ctx.device.createQuerySet({
9499
+ type: "timestamp",
9500
+ count: 2
9501
+ });
9502
+ this.queryResolveBuf = this.ctx.device.createBuffer({
9503
+ size: 16,
9504
+ usage: 516
9505
+ });
9506
+ this.queryReadbackBuf = this.ctx.device.createBuffer({
9507
+ size: 16,
9508
+ usage: 9
9509
+ });
9510
+ }
9511
+ const pass = encoder.beginComputePass(tsTotal ? {
9512
+ label: "decode_pass",
9513
+ timestampWrites: {
9514
+ querySet: this.querySet,
9515
+ beginningOfPassWriteIndex: 0,
9516
+ endOfPassWriteIndex: 1
9517
+ }
9518
+ } : { label: "decode_pass" });
8912
9519
  for (let i = 0; i < this.decodeEntries.length; i++) {
8913
9520
  const entry = this.decodeEntries[i];
8914
9521
  pass.setPipeline(entry.pipeline);
@@ -8922,7 +9529,27 @@ var Executor = class Executor {
8922
9529
  }
8923
9530
  pass.end();
8924
9531
  encoder.copyBufferToBuffer(this.argmaxResultBuffer, 0, this.decodeReadbacks[slot], 0, 4);
9532
+ if (tsTotal) {
9533
+ encoder.resolveQuerySet(this.querySet, 0, 2, this.queryResolveBuf, 0);
9534
+ encoder.copyBufferToBuffer(this.queryResolveBuf, 0, this.queryReadbackBuf, 0, 16);
9535
+ }
8925
9536
  this.ctx.device.queue.submit([encoder.finish()]);
9537
+ if (tsTotal) {
9538
+ const rb = this.queryReadbackBuf;
9539
+ const acc = g.__tsTotal;
9540
+ acc.inFlight = true;
9541
+ (async () => {
9542
+ try {
9543
+ await rb.mapAsync(MAP_MODE_READ$1, 0, 16);
9544
+ const ts = new BigUint64Array(rb.getMappedRange(0, 16).slice(0));
9545
+ rb.unmap();
9546
+ acc.ns += Number(ts[1] - ts[0]);
9547
+ acc.steps += 1;
9548
+ } catch {} finally {
9549
+ acc.inFlight = false;
9550
+ }
9551
+ })();
9552
+ }
8926
9553
  this.seqPos += 1;
8927
9554
  }
8928
9555
  /** Read back the token produced by the pipelined step that used `slot`. */
@@ -9631,6 +10258,243 @@ var Executor = class Executor {
9631
10258
  * WebKit risk: low — same single-workgroup reduction as the proven RMSNorm
9632
10259
  * kernel, only the row→input routing is added.
9633
10260
  */
10261
+ /**
10262
+ * Fuse [CausalConv1dSiLU|CausalConv1dGated → ConvStateUpdate] into one
10263
+ * dispatch (decode entries only). At T=1 the conv read and the rolling-state
10264
+ * write for a channel live in the same thread, so the fused kernel is
10265
+ * race-free; prefill (T>1) keeps the two-dispatch path.
10266
+ */
10267
+ fuseConvStateUpdateDecodeEntries() {
10268
+ let fusionCount = 0;
10269
+ for (let i = 0; i < this.decodeEntries.length - 1; i++) {
10270
+ const e1 = this.decodeEntries[i];
10271
+ const e2 = this.decodeEntries[i + 1];
10272
+ if (e1.node.opType === "CausalConv1dGatedBcx" && e2.node.opType === "ConvStateUpdateBcx" && e2.node.inputs[0] === e1.node.inputs[0] && e2.node.outputs[0] === e1.node.inputs[2] && e1.node.attributes.kernel_size <= 8) {
10273
+ this.decodeEntries.splice(i + 1, 1);
10274
+ fusionCount++;
10275
+ continue;
10276
+ }
10277
+ const isSilu = e1.node.opType === "CausalConv1dSiLU";
10278
+ const isGated = e1.node.opType === "CausalConv1dGated";
10279
+ if (!isSilu && !isGated) continue;
10280
+ if (e2.node.opType !== "ConvStateUpdate") continue;
10281
+ if (e2.node.inputs[0] !== e1.node.inputs[0]) continue;
10282
+ if (e2.node.outputs[0] !== e1.node.inputs[2]) continue;
10283
+ const kernelSize = e1.node.attributes.kernel_size;
10284
+ if (e2.node.attributes.state_size !== kernelSize - 1) continue;
10285
+ if (kernelSize > 8) continue;
10286
+ const inputBuf = this.getBuffer(e1.node.inputs[0]);
10287
+ const weightBuf = this.getBuffer(e1.node.inputs[1]);
10288
+ const stateBuf = this.getBuffer(e1.node.inputs[2]);
10289
+ const gateBuf = isGated ? this.getBuffer(e1.node.inputs[3]) : null;
10290
+ const outBuf = this.getBuffer(e1.node.outputs[0]);
10291
+ if (!inputBuf || !weightBuf || !stateBuf || !outBuf || isGated && !gateBuf) continue;
10292
+ if (outBuf === inputBuf || outBuf === stateBuf || stateBuf === inputBuf) continue;
10293
+ if (gateBuf && (gateBuf === outBuf || gateBuf === stateBuf)) continue;
10294
+ const fusedSpec = isSilu ? CAUSAL_CONV1D_SILU_UPDATE_SPEC : CAUSAL_CONV1D_GATED_UPDATE_SPEC;
10295
+ const fusedPipeline = getOrCreatePipeline(this.ctx, `conv_update_${e1.nodeId}`, fusedSpec.shaderCode, fusedSpec.entryPoint);
10296
+ const fusedUniformData = fusedSpec.buildParams(e1.node, {}, { seqPos: 0 });
10297
+ const fusedUniform = createUniformBuffer(this.ctx, `uniform_conv_update_${e1.nodeId}`, fusedUniformData);
10298
+ const bufferList = isSilu ? [
10299
+ { buffer: inputBuf },
10300
+ { buffer: weightBuf },
10301
+ { buffer: stateBuf },
10302
+ { buffer: outBuf },
10303
+ { buffer: fusedUniform }
10304
+ ] : [
10305
+ { buffer: inputBuf },
10306
+ { buffer: weightBuf },
10307
+ { buffer: stateBuf },
10308
+ { buffer: gateBuf },
10309
+ { buffer: outBuf },
10310
+ { buffer: fusedUniform }
10311
+ ];
10312
+ const fusedBindGroup = createBindGroup(this.ctx, fusedPipeline, bufferList, `bg_conv_update_${e1.nodeId}`);
10313
+ const fusedEntry = {
10314
+ nodeId: `fused_conv_update_${e1.nodeId}`,
10315
+ node: e1.node,
10316
+ spec: fusedSpec,
10317
+ pipeline: fusedPipeline,
10318
+ bindGroup: fusedBindGroup,
10319
+ uniformBuffer: fusedUniform,
10320
+ lastParamsBytes: null,
10321
+ lastDispatchSize: null,
10322
+ buffers: bufferList
10323
+ };
10324
+ this.decodeEntries.splice(i, 2, fusedEntry);
10325
+ fusionCount++;
10326
+ }
10327
+ if (fusionCount > 0) console.log(`[executor] Fused ${fusionCount} conv+state-update pairs (saved ${fusionCount} dispatches)`);
10328
+ }
10329
+ /**
10330
+ * Fuse [per-head q_norm RMSNorm, k_norm RMSNorm, in-place RoPE] into ONE
10331
+ * DualRMSNormRoPE dispatch. Numerically identical (norm then rotate, same
10332
+ * math); requires head_dim <= 256 and RoPE operating in-place on exactly the
10333
+ * two norm outputs. Saves 2 dispatches per full-attention layer — a mobile
10334
+ * (WebKit submit-per-dispatch) win, desktop-neutral.
10335
+ */
10336
+ /**
10337
+ * Fuse [Add → RMSNorm] into a single ResidualRMSNorm dispatch: the add's sum
10338
+ * is still materialized (sum_output — downstream residual chain reads it) and
10339
+ * the norm result is written in the same dispatch. Numerically identical.
10340
+ */
10341
+ fuseAddRMSNormDecodeEntries() {
10342
+ let fusionCount = 0;
10343
+ for (let i = 0; i < this.decodeEntries.length - 1; i++) {
10344
+ const e0 = this.decodeEntries[i];
10345
+ const e1 = this.decodeEntries[i + 1];
10346
+ if (e0.node.opType !== "Add" || e1.node.opType !== "RMSNorm") continue;
10347
+ if (e1.node.inputs[0] !== e0.node.outputs[0]) continue;
10348
+ const a = e0.node.inputs[0];
10349
+ const b = e0.node.inputs[1];
10350
+ const sumOut = e0.node.outputs[0];
10351
+ const normW = e1.node.inputs[1];
10352
+ const normOut = e1.node.outputs[0];
10353
+ const aBuf = this.getBuffer(a);
10354
+ const bBuf = this.getBuffer(b);
10355
+ const sumBuf = this.getBuffer(sumOut);
10356
+ const wBuf = this.getBuffer(normW);
10357
+ const outBuf = this.getBuffer(normOut);
10358
+ if (!aBuf || !bBuf || !sumBuf || !wBuf || !outBuf) continue;
10359
+ const bufs = [
10360
+ aBuf,
10361
+ bBuf,
10362
+ wBuf,
10363
+ sumBuf,
10364
+ outBuf
10365
+ ];
10366
+ if (new Set(bufs).size !== bufs.length) continue;
10367
+ const fusedNode = {
10368
+ id: `fused_addnorm_${e0.nodeId}`,
10369
+ opType: "ResidualRMSNorm",
10370
+ inputs: [
10371
+ a,
10372
+ b,
10373
+ normW
10374
+ ],
10375
+ outputs: [sumOut, normOut],
10376
+ attributes: {
10377
+ hidden_size: e1.node.attributes.hidden_size,
10378
+ eps: e1.node.attributes.eps,
10379
+ seq_len_tensor: e1.node.attributes.seq_len_tensor ?? e0.node.attributes.count_tensor
10380
+ }
10381
+ };
10382
+ const fusedSpec = KERNEL_REGISTRY.ResidualRMSNorm;
10383
+ if (!fusedSpec) continue;
10384
+ const fusedPipeline = getOrCreatePipeline(this.ctx, `add_norm_${e0.nodeId}`, fusedSpec.shaderCode, fusedSpec.entryPoint);
10385
+ const dummyShapes = this.resolveShapes(1);
10386
+ const fusedUniformData = fusedSpec.buildParams(fusedNode, dummyShapes, { seqPos: 0 });
10387
+ const fusedUniform = createUniformBuffer(this.ctx, `uniform_add_norm_${e0.nodeId}`, fusedUniformData);
10388
+ const bufferEntries = [
10389
+ { buffer: aBuf },
10390
+ { buffer: bBuf },
10391
+ { buffer: wBuf },
10392
+ { buffer: sumBuf },
10393
+ { buffer: outBuf },
10394
+ { buffer: fusedUniform }
10395
+ ];
10396
+ const fusedBindGroup = createBindGroup(this.ctx, fusedPipeline, bufferEntries, `bg_add_norm_${e0.nodeId}`);
10397
+ this.decodeEntries.splice(i, 2, {
10398
+ nodeId: fusedNode.id,
10399
+ node: fusedNode,
10400
+ spec: fusedSpec,
10401
+ pipeline: fusedPipeline,
10402
+ bindGroup: fusedBindGroup,
10403
+ uniformBuffer: fusedUniform,
10404
+ lastParamsBytes: null,
10405
+ lastDispatchSize: null,
10406
+ buffers: bufferEntries
10407
+ });
10408
+ fusionCount++;
10409
+ }
10410
+ if (fusionCount > 0) console.log(`[executor] Fused ${fusionCount} add+RMSNorm pairs (saved ${fusionCount} dispatches)`);
10411
+ }
10412
+ fuseDualRMSNormRoPEDecodeEntries() {
10413
+ let fusionCount = 0;
10414
+ for (let i = 0; i < this.decodeEntries.length - 2; i++) {
10415
+ const e0 = this.decodeEntries[i];
10416
+ const e1 = this.decodeEntries[i + 1];
10417
+ const e2 = this.decodeEntries[i + 2];
10418
+ if (e0.node.opType !== "RMSNorm" || e1.node.opType !== "RMSNorm") continue;
10419
+ if (e2.node.opType !== "RoPE") continue;
10420
+ if (e2.node.attributes.interleaved === true) continue;
10421
+ if (e0.node.attributes.hidden_size !== e1.node.attributes.hidden_size) continue;
10422
+ if (e0.node.attributes.eps !== e1.node.attributes.eps) continue;
10423
+ const headDim = e0.node.attributes.hidden_size;
10424
+ if (headDim > 256) continue;
10425
+ if (e2.node.attributes.head_dim !== headDim) continue;
10426
+ const out0 = e0.node.outputs[0];
10427
+ const out1 = e1.node.outputs[0];
10428
+ if (e2.node.inputs[0] !== out0 || e2.node.inputs[1] !== out1) continue;
10429
+ if (e2.node.outputs[0] !== out0 || e2.node.outputs[1] !== out1) continue;
10430
+ const in0 = e0.node.inputs[0];
10431
+ const w0 = e0.node.inputs[1];
10432
+ const in1 = e1.node.inputs[0];
10433
+ const w1 = e1.node.inputs[1];
10434
+ const in0Buf = this.getBuffer(in0);
10435
+ const w0Buf = this.getBuffer(w0);
10436
+ const out0Buf = this.getBuffer(out0);
10437
+ const in1Buf = this.getBuffer(in1);
10438
+ const w1Buf = this.getBuffer(w1);
10439
+ const out1Buf = this.getBuffer(out1);
10440
+ if (!in0Buf || !w0Buf || !out0Buf || !in1Buf || !w1Buf || !out1Buf) continue;
10441
+ if (out0Buf === out1Buf) continue;
10442
+ if (in0Buf === out1Buf || in1Buf === out0Buf) continue;
10443
+ const fusedNode = {
10444
+ id: `fused_dualnorm_rope_${e0.nodeId}`,
10445
+ opType: "RMSNorm",
10446
+ inputs: [
10447
+ in0,
10448
+ w0,
10449
+ in1,
10450
+ w1
10451
+ ],
10452
+ outputs: [out0, out1],
10453
+ attributes: {
10454
+ hidden_size: headDim,
10455
+ eps: e0.node.attributes.eps,
10456
+ seq_len_tensor: e0.node.attributes.seq_len_tensor,
10457
+ seq_len: e0.node.attributes.seq_len,
10458
+ num_q_heads: e2.node.attributes.num_q_heads,
10459
+ num_kv_heads: e2.node.attributes.num_kv_heads,
10460
+ rope_base: e2.node.attributes.rope_base,
10461
+ rope_dim: e2.node.attributes.rope_dim,
10462
+ rope_half: e2.node.attributes.rope_half,
10463
+ rope_denom: e2.node.attributes.rope_denom,
10464
+ rope_active_pairs: e2.node.attributes.rope_active_pairs
10465
+ }
10466
+ };
10467
+ const fusedSpec = DUAL_RMSNORM_ROPE_SPEC;
10468
+ const fusedPipeline = getOrCreatePipeline(this.ctx, `dual_norm_rope_${e0.nodeId}`, fusedSpec.shaderCode, fusedSpec.entryPoint);
10469
+ const dummyShapes = this.resolveShapes(1);
10470
+ const fusedUniformData = fusedSpec.buildParams(fusedNode, dummyShapes, { seqPos: 0 });
10471
+ const fusedUniform = createUniformBuffer(this.ctx, `uniform_dual_norm_rope_${e0.nodeId}`, fusedUniformData);
10472
+ const bufferEntries = [
10473
+ { buffer: in0Buf },
10474
+ { buffer: w0Buf },
10475
+ { buffer: in1Buf },
10476
+ { buffer: w1Buf },
10477
+ { buffer: out0Buf },
10478
+ { buffer: out1Buf },
10479
+ { buffer: fusedUniform }
10480
+ ];
10481
+ const fusedBindGroup = createBindGroup(this.ctx, fusedPipeline, bufferEntries, `bg_dual_norm_rope_${e0.nodeId}`);
10482
+ const fusedEntry = {
10483
+ nodeId: fusedNode.id,
10484
+ node: fusedNode,
10485
+ spec: fusedSpec,
10486
+ pipeline: fusedPipeline,
10487
+ bindGroup: fusedBindGroup,
10488
+ uniformBuffer: fusedUniform,
10489
+ lastParamsBytes: null,
10490
+ lastDispatchSize: null,
10491
+ buffers: bufferEntries
10492
+ };
10493
+ this.decodeEntries.splice(i, 3, fusedEntry);
10494
+ fusionCount++;
10495
+ }
10496
+ if (fusionCount > 0) console.log(`[executor] Fused ${fusionCount} norm+norm+RoPE triples (saved ${fusionCount * 2} dispatches)`);
10497
+ }
9634
10498
  fuseDualRMSNormDecodeEntries() {
9635
10499
  let fusionCount = 0;
9636
10500
  for (let i = 0; i < this.decodeEntries.length - 1; i++) {
@@ -13308,4 +14172,4 @@ function selectGraphWeights(graph, weights) {
13308
14172
 
13309
14173
  //#endregion
13310
14174
  export { verifyGPU as S, createStorageBuffer as _, loadMoonshine as a, getOrCreatePipeline as b, quantizeBackboneInt4 as c, Tokenizer as d, Executor as f, createBindGroup as g, clearPipelineCache as h, loadModel as i, quantizeKaniBackbone as l, MATMUL_BIAS_F16C_SPEC as m, MoonshineEncoderExecutor as n, loadOuteTTS as o, KERNEL_REGISTRY as p, loadKaniTTS as r, loadParlerTTS as s, MoonshineSTT as t, remapPrunedToken as u, createUniformBuffer as v, initGPU as x, destroyBuffers as y };
13311
- //# sourceMappingURL=moonshine-stt-BeXNc7o2.mjs.map
14175
+ //# sourceMappingURL=moonshine-stt-BwX7NQ0O.mjs.map