@tryhamster/gerbil 1.0.1 → 1.0.2
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/README.md +14 -0
- package/dist/cli.mjs +7 -7
- package/dist/cli.mjs.map +1 -1
- package/dist/frameworks/express.mjs +1 -1
- package/dist/frameworks/fastify.mjs +1 -1
- package/dist/frameworks/hono.mjs +1 -1
- package/dist/frameworks/next.mjs +1 -1
- package/dist/frameworks/trpc.mjs +1 -1
- package/dist/gerbil-B-jMOrnE.mjs +4 -0
- package/dist/{gerbil-DNniplr4.mjs → gerbil-CV4VpF4_.mjs} +2 -2
- package/dist/{gerbil-DNniplr4.mjs.map → gerbil-CV4VpF4_.mjs.map} +1 -1
- package/dist/gpu/hooks.d.mts +131 -86
- package/dist/gpu/hooks.d.mts.map +1 -1
- package/dist/gpu/hooks.mjs +28 -1
- package/dist/gpu/hooks.mjs.map +1 -1
- package/dist/gpu/index.mjs +2 -2
- package/dist/{gpu-DFuglcEx.mjs → gpu-836grvrv.mjs} +2 -2
- package/dist/{gpu-DFuglcEx.mjs.map → gpu-836grvrv.mjs.map} +1 -1
- package/dist/index-Dgmb2kE3.d.mts.map +1 -1
- package/dist/index-DukkJRMj.d.mts.map +1 -1
- package/dist/index.mjs +4 -4
- package/dist/integrations/ai-sdk.mjs +1 -1
- package/dist/integrations/langchain.mjs +1 -1
- package/dist/integrations/llamaindex.mjs +1 -1
- package/dist/integrations/mcp.mjs +4 -4
- package/dist/{mcp-D2vvH1Xc.mjs → mcp-BVHI5vzD.mjs} +3 -3
- package/dist/{mcp-D2vvH1Xc.mjs.map → mcp-BVHI5vzD.mjs.map} +1 -1
- package/dist/{moonshine-stt-4ojLtMq7.mjs → moonshine-stt-BzQRl-BO.mjs} +22 -16
- package/dist/{moonshine-stt-4ojLtMq7.mjs.map → moonshine-stt-BzQRl-BO.mjs.map} +1 -1
- package/dist/moonshine-stt-CIolM_SX.mjs +4 -0
- package/dist/{one-liner-JhdIPxzF.mjs → one-liner-Cn7IEg1G.mjs} +2 -2
- package/dist/{one-liner-JhdIPxzF.mjs.map → one-liner-Cn7IEg1G.mjs.map} +1 -1
- package/dist/{repl-BDRkwPGX.mjs → repl-CKAf2M7H.mjs} +3 -3
- package/dist/skills/index.d.mts +2 -2
- package/dist/skills/index.mjs +3 -3
- package/dist/{skills-CU694Dc8.mjs → skills-uuU5GONV.mjs} +2 -2
- package/dist/{skills-CU694Dc8.mjs.map → skills-uuU5GONV.mjs.map} +1 -1
- package/package.json +1 -1
- package/dist/gerbil-CTZUa8EZ.mjs +0 -4
- package/dist/moonshine-stt-17dpP1kr.mjs +0 -4
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"moonshine-stt-4ojLtMq7.mjs","names":["MAP_MODE_READ","requiredFeatures: GPUFeatureName[]","requestLimits: Record<string, number>","device: GPUDevice","layout: GPUPipelineLayout | \"auto\"","details: string[]","mismatches: string[]","addSpec: KernelSpec","mulSpec: KernelSpec","sliceLastRowSpec: KernelSpec","meanPoolSpec: KernelSpec","scaleSpec: KernelSpec","softcapSpec: KernelSpec","l2NormSpec: KernelSpec","swigluSpec: KernelSpec","residualRmsnormSpec: KernelSpec","siluSpec: KernelSpec","geluSpec: KernelSpec","geluErfSpec: KernelSpec","addBiasSpec: KernelSpec","sliceColsSpec: KernelSpec","mulColsSpec: KernelSpec","applyRotarySpec: KernelSpec","matmulSpec: KernelSpec","M: number","matmulBiasSpec: KernelSpec","poolMatMulSpec: KernelSpec","clippedMatMulSpec: KernelSpec","MATMUL_BIAS_F16C_SPEC: KernelSpec","matmulInt4Spec: KernelSpec","MATVEC_SPEC: KernelSpec","MATVEC_INT4_SPEC: KernelSpec","GATED_MATVEC_INT4_SPEC: KernelSpec","SWIGLU_GATED_MATVEC_INT4_SPEC: KernelSpec","MATVEC_SUBGROUPS_SPEC: KernelSpec","MATVEC_INT4_SUBGROUPS_SPEC: KernelSpec","SWIGLU_MATVEC_SPEC: KernelSpec","SWIGLU_MATVEC_INT4_SPEC: KernelSpec","DUAL_MATVEC_INT4_SPEC: KernelSpec","ARGMAX_SPEC: KernelSpec","rmsnormSpec: KernelSpec","seq_len: number","DUAL_RMSNORM_SPEC: KernelSpec","layernormSpec: KernelSpec","embeddingSpec: KernelSpec","embeddingInt4Spec: KernelSpec","ropeSpec: KernelSpec","ROPE_INTERLEAVED_SPEC: KernelSpec","mropeSpec: KernelSpec","embedSpliceSpec: KernelSpec","attentionSpec: KernelSpec","crossAttentionSpec: KernelSpec","KV_CACHE_APPEND_PACKED_F16_SPEC: KernelSpec","T: number","ATTENTION_PACKED_F16_SPEC: KernelSpec","softmaxSpec: KernelSpec","causalConv1dSpec: KernelSpec","causalConv1dSiluSpec: KernelSpec","causalConv1dGatedSpec: KernelSpec","sigmoidGateSpec: KernelSpec","mambaSSMSpec: KernelSpec","MAMBA_SSM_F16_SPEC: KernelSpec","kvCacheAppendSpec: KernelSpec","DUAL_KV_CACHE_APPEND_SPEC: KernelSpec","DUAL_KV_CACHE_APPEND_F16_SPEC: KernelSpec","DUAL_KV_CACHE_APPEND_PACKED_F16_SPEC: KernelSpec","KV_CACHE_APPEND_F16_SPEC: KernelSpec","ATTENTION_F16_SPEC: KernelSpec","convStateUpdateSpec: KernelSpec","LAYERNORM_NO_BIAS_SPEC: KernelSpec","tanhSpec: KernelSpec","transposeSpec: KernelSpec","groupnormSpec: KernelSpec","conv1dFullSpec: KernelSpec","convTranspose1dSpec: KernelSpec","snake1dSpec: KernelSpec","fsqDequantSpec: KernelSpec","halfSnake1dSpec: KernelSpec","convTranspose1dDepthwiseSpec: KernelSpec","KERNEL_REGISTRY: Partial<Record<OpType, KernelSpec>>","MAP_MODE_READ","prefillEntry: DispatchEntry","runtimeContext: RuntimeContext","dispatchSizes: Array<[number, number, number]>","vocabSize","dispatchSizes: number[][]","argmaxDispatchSizes: Array<[number, number, number]>","results: Array<{\n idx: number;\n nodeId: string;\n opType: string;\n tensor: string;\n sum: number;\n first4: number[];\n uniformParams?: number[];\n }>","uniformParams: number[] | undefined","resolved: Record<string, number[]>","bufferEntries: Array<{ buffer: GPUBuffer }>","fusedEntry: DispatchEntry","fusedSpec: KernelSpec","fusedNode: OpNode","entries: Array<{ buffer: GPUBuffer }>","buffer: GPUBuffer | undefined","_dbPromise: Promise<IDBDatabase | null> | null","_client: OpfsClient | null","stale: string[]","header: Record<string, unknown>","entries: SafetensorEntry[]","metadata: Record<string, string> | null","bs: number[]","ids: number[]","pieces: string[]","byteRun: number[]","chars: number[]","parts: string[]","parts: Array<{ text: string; special: boolean }>","match: RegExpExecArray | null","chunks: string[]","buf: ArrayBuffer | null","_fs: typeof import(\"node:fs\") | null","_path: typeof import(\"node:path\") | null","buf: Buffer","headers: Record<string, string>","chunks: Uint8Array[]","resp: Response","ranges: ByteRange[]","current: ByteRange","uncached: SafetensorEntry[]","resolvedDtype: GraphDType | undefined","store: MutableWeightStore","cachedBuffer: ArrayBuffer | null","data: ArrayBufferView","isFullAttn: boolean","normKeys: string[]","gemmaNormKeys: string[]","N: number","K: number","pleSource: PleSource | undefined","missingTensors: string[]","sizes: Array<[number, number, number]>","enc","p","encK: Float32Array[]","encV: Float32Array[]","resolved: Record<string, number[]>","entries: Array<{ buffer: GPUBuffer }>","encK: Float32Array[]","encV: Float32Array[]","frames: number","tokens: number[]"],"sources":["../src/gpu/device.ts","../src/gpu/kernels/registry.ts","../src/gpu/executor.ts","../src/gpu/gptq-adapter.ts","../src/gpu/idb-cache.ts","../src/gpu/mlx-adapter.ts","../src/gpu/opfs-cache.ts","../src/gpu/safetensors.ts","../src/gpu/tokenizer.ts","../src/gpu/weight-source.ts","../src/gpu/model-loader.ts","../src/gpu/moonshine-executor.ts","../src/gpu/moonshine-stt.ts"],"sourcesContent":["/**\n * WebGPU device abstraction layer.\n *\n * Wraps GPUDevice with helpers for buffer allocation, pipeline compilation,\n * compute dispatch, and readback. All GPU interaction flows through here.\n */\n\n// WebGPU buffer usage flags (numeric for Node.js Dawn polyfill compatibility)\nconst STORAGE = 0x0080;\nconst COPY_SRC = 0x0004;\nconst COPY_DST = 0x0008;\nconst UNIFORM = 0x0040;\nconst MAP_READ = 0x0001;\nconst MAP_MODE_READ = 0x0001;\n\nexport interface GPUContext {\n /** The underlying WebGPU device. */\n device: GPUDevice;\n /** Device limits (max buffer size, workgroup size, etc.). */\n limits: GPUSupportedLimits;\n /** Whether f16 is supported as a shader type. */\n hasF16: boolean;\n /**\n * Whether the WebGPU `subgroups` feature is available (Chrome 134+, Safari 26+).\n * When true, kernels may use `subgroupAdd`/`subgroupBroadcast` etc. (requires\n * `enable subgroups;` in the shader). Absence falls back to the portable\n * shared-memory reductions — never assume this is present.\n */\n hasSubgroups: boolean;\n /** True when the WebGPU implementation is WebKit's (Safari, all iOS/iPadOS browsers). */\n isWebKitWebGPU: boolean;\n /** Whether the `timestamp-query` feature is available (per-pass GPU timing). Used\n * only by the env-gated decode profiler; never on the normal inference path. */\n hasTimestamp: boolean;\n /** Raw adapter info string for diagnostics. */\n adapterDescription: string;\n}\n\nexport interface InitGPUOptions {\n /** Called when the GPU device is lost (e.g. tab backgrounded on iOS). */\n onDeviceLost?: (reason: string, message: string) => void;\n}\n\n/**\n * Initialize WebGPU and request a device with the features we need.\n *\n * In Node.js, initializes Dawn's WebGPU polyfill if navigator.gpu is absent.\n * In the browser, uses the native WebGPU API directly.\n *\n * Throws a clear error if WebGPU is unavailable.\n */\nexport async function initGPU(options?: InitGPUOptions): Promise<GPUContext> {\n // Node.js: initialize Dawn polyfill if needed\n if ((typeof navigator === \"undefined\" || !navigator.gpu) && typeof process !== \"undefined\") {\n try {\n const dynamicImport = new Function(\"specifier\", \"return import(specifier)\");\n const webgpuModule = await dynamicImport(\"webgpu\");\n const { create } = webgpuModule;\n if (!(globalThis as any).navigator) {\n (globalThis as any).navigator = {} as Navigator;\n }\n (globalThis as any).navigator.gpu = create([]);\n } catch {\n // Dawn not available\n }\n }\n\n if (typeof navigator === \"undefined\" || !navigator.gpu) {\n throw new Error(\n \"WebGPU is not available in this environment. \" +\n \"Requires Chrome 113+, Safari 26+ (iOS 26+), or Firefox 141+.\",\n );\n }\n\n // Detect mobile for power preference\n const isMobile =\n typeof navigator !== \"undefined\" && /iPhone|iPad|iPod|Android/i.test(navigator.userAgent);\n\n const adapter = await navigator.gpu.requestAdapter({\n powerPreference: isMobile ? \"low-power\" : \"high-performance\",\n });\n\n if (!adapter) {\n throw new Error(\n \"No WebGPU adapter found. Your GPU may not support WebGPU, \" +\n \"or another tab may be using it. Try closing other tabs.\",\n );\n }\n\n // Check for f16 support\n const hasF16 = adapter.features.has(\"shader-f16\");\n\n // Check for subgroups support (Chrome 134+, Safari 26+). Optional — kernels\n // fall back to portable shared-memory reductions when absent.\n const hasSubgroups = adapter.features.has(\"subgroups\" as GPUFeatureName);\n\n // timestamp-query: per-pass GPU timing for the env-gated decode profiler. Only\n // requested when GERBIL_PROFILE is set so the normal path is untouched.\n const wantTimestamp = typeof process !== \"undefined\" && process.env?.GERBIL_PROFILE != null;\n const hasTimestamp = wantTimestamp && adapter.features.has(\"timestamp-query\");\n\n // Request device with f16 if available\n const requiredFeatures: GPUFeatureName[] = [];\n if (hasF16) {\n requiredFeatures.push(\"shader-f16\");\n }\n if (hasSubgroups) {\n requiredFeatures.push(\"subgroups\" as GPUFeatureName);\n }\n if (hasTimestamp) {\n requiredFeatures.push(\"timestamp-query\");\n }\n\n // Request limits conservatively — don't exceed adapter limits\n const requestLimits: Record<string, number> = {\n maxBufferSize: adapter.limits.maxBufferSize,\n maxStorageBufferBindingSize: adapter.limits.maxStorageBufferBindingSize,\n maxComputeWorkgroupSizeX: Math.min(256, adapter.limits.maxComputeWorkgroupSizeX),\n maxComputeWorkgroupSizeY: Math.min(256, adapter.limits.maxComputeWorkgroupSizeY),\n maxComputeWorkgroupStorageSize: adapter.limits.maxComputeWorkgroupStorageSize,\n maxStorageBuffersPerShaderStage: adapter.limits.maxStorageBuffersPerShaderStage,\n };\n\n let device: GPUDevice;\n try {\n device = await adapter.requestDevice({\n requiredFeatures,\n requiredLimits: requestLimits,\n });\n } catch (e) {\n // Retry with minimal limits if the first attempt fails (e.g. on low-end iPad)\n try {\n device = await adapter.requestDevice({ requiredFeatures });\n } catch {\n throw new Error(\n `Failed to create WebGPU device: ${e instanceof Error ? e.message : String(e)}. ` +\n \"Your GPU may not meet the minimum requirements.\",\n );\n }\n }\n\n // Handle device loss\n device.lost.then((info) => {\n console.error(`[Gerbil] WebGPU device lost: ${info.reason}`, info.message);\n options?.onDeviceLost?.(info.reason, info.message);\n });\n\n // Surface errors that no error scope caught — without this, failed buffer\n // allocations and invalid bind groups on Safari are completely silent and\n // the affected dispatches just no-op (reads as zero logits).\n try {\n device.onuncapturederror = (ev: GPUUncapturedErrorEvent) => {\n console.error(`[Gerbil] Uncaptured GPU error: ${ev.error?.message ?? ev.error}`);\n };\n } catch {\n // not supported by this implementation (e.g. older Dawn polyfill)\n }\n\n // Detect WebKit's WebGPU implementation, NOT Apple GPU hardware: Dawn on Apple\n // Silicon (node-dawn, Chrome on macOS) also reports vendor \"apple\"/arch \"common-*\",\n // so adapter info cannot distinguish implementations. Only WebKit's implementation\n // needs the Safari coherence workarounds; hardware-based detection routed desktop\n // Dawn through them (161 -> 19.7 tok/s regression).\n let adapterDescription = \"\";\n try {\n const info = (adapter as any).info ?? (adapter as any).getInfo?.();\n if (info) {\n adapterDescription = `vendor=${info.vendor ?? \"\"} arch=${info.architecture ?? \"\"} device=${info.device ?? \"\"} desc=${info.description ?? \"\"}`;\n }\n } catch {\n // adapter.info not available\n }\n\n // All iOS/iPadOS browsers are WebKit; on macOS only Safari is (Chrome/Chromium UAs\n // contain \"Chrome/\"). Node (Dawn) has no AppleWebKit UA and resolves to false.\n const ua = typeof navigator !== \"undefined\" && navigator.userAgent ? navigator.userAgent : \"\";\n const isAppleWebKit = ua.includes(\"AppleWebKit\");\n const isIOSOrIPadOS =\n /iPhone|iPad|iPod/.test(ua) ||\n (isAppleWebKit && typeof navigator !== \"undefined\" && (navigator.maxTouchPoints ?? 0) > 1);\n const isSafariMac = isAppleWebKit && !ua.includes(\"Chrome/\") && !isIOSOrIPadOS;\n const isWebKitWebGPU = isIOSOrIPadOS || isSafariMac;\n if (!adapterDescription) {\n adapterDescription = `UA-fallback: ${ua.slice(0, 120)}`;\n }\n\n return {\n device,\n limits: device.limits,\n hasF16,\n hasSubgroups,\n isWebKitWebGPU,\n hasTimestamp,\n adapterDescription,\n };\n}\n\n// ── Buffer helpers ────────────────────────────────────────────────────\n\n/**\n * Create a GPU storage buffer and optionally upload data to it.\n */\nexport function createStorageBuffer(\n ctx: GPUContext,\n label: string,\n sizeBytes: number,\n data?: ArrayBuffer | ArrayBufferView,\n): GPUBuffer {\n const usage = STORAGE | COPY_SRC | COPY_DST;\n\n // Align size to 4 bytes (WebGPU requirement)\n const alignedSize = Math.ceil(sizeBytes / 4) * 4;\n\n const buffer = ctx.device.createBuffer({\n label,\n size: alignedSize,\n usage,\n });\n\n // Use writeBuffer instead of mappedAtCreation — Safari/WebKit's mappedAtCreation\n // may not properly persist data after unmap(), causing all-zero weight buffers.\n if (data) {\n if (data instanceof ArrayBuffer) {\n ctx.device.queue.writeBuffer(buffer, 0, data);\n } else {\n ctx.device.queue.writeBuffer(buffer, 0, data.buffer, data.byteOffset, data.byteLength);\n }\n }\n\n return buffer;\n}\n\n/**\n * Create a uniform buffer for passing parameters to shaders.\n */\nexport function createUniformBuffer(\n ctx: GPUContext,\n label: string,\n data: ArrayBuffer | ArrayBufferView,\n): GPUBuffer {\n const byteLength = data instanceof ArrayBuffer ? data.byteLength : data.byteLength;\n\n // Use STORAGE instead of UNIFORM to bypass Safari/Metal bugs with uniform buffer\n // visibility across many compute passes. WGSL kernels use var<storage, read> for params.\n // Keep 16-byte alignment for compatibility.\n const alignedSize = Math.ceil(byteLength / 16) * 16;\n\n const buffer = ctx.device.createBuffer({\n label,\n size: alignedSize,\n usage: STORAGE | COPY_SRC | COPY_DST,\n });\n\n if (data instanceof ArrayBuffer) {\n ctx.device.queue.writeBuffer(buffer, 0, data);\n } else {\n ctx.device.queue.writeBuffer(buffer, 0, data.buffer, data.byteOffset, data.byteLength);\n }\n\n return buffer;\n}\n\n/**\n * Create a staging buffer for reading GPU results back to CPU.\n */\nexport function createReadbackBuffer(ctx: GPUContext, label: string, sizeBytes: number): GPUBuffer {\n const alignedSize = Math.ceil(sizeBytes / 4) * 4;\n return ctx.device.createBuffer({\n label,\n size: alignedSize,\n usage: MAP_READ | COPY_DST,\n });\n}\n\n/**\n * Update a uniform buffer's contents.\n */\nexport function writeUniformBuffer(\n ctx: GPUContext,\n buffer: GPUBuffer,\n data: ArrayBuffer | ArrayBufferView,\n): void {\n if (data instanceof ArrayBuffer) {\n ctx.device.queue.writeBuffer(buffer, 0, data);\n } else {\n ctx.device.queue.writeBuffer(buffer, 0, data.buffer, data.byteOffset, data.byteLength);\n }\n}\n\n// ── Pipeline helpers ──────────────────────────────────────────────────\n\n/**\n * Per-device pipeline cache. Pipelines are bound to the GPUDevice that created\n * them — using a pipeline from device1 with device2 causes WebGPU validation\n * errors. WeakMap ensures the cache is GC'd when the device is destroyed.\n */\nconst perDeviceCache = new WeakMap<GPUDevice, Map<string, GPUComputePipeline>>();\n\n/** Get or initialize the pipeline cache for a specific device. */\nfunction getPipelineCache(device: GPUDevice): Map<string, GPUComputePipeline> {\n let cache = perDeviceCache.get(device);\n if (!cache) {\n cache = new Map();\n perDeviceCache.set(device, cache);\n }\n return cache;\n}\n\n/**\n * Get or create a compute pipeline from WGSL source code.\n */\nexport function getOrCreatePipeline(\n ctx: GPUContext,\n label: string,\n wgslCode: string,\n entryPoint: string = \"main\",\n bindGroupLayoutEntries?: GPUBindGroupLayoutEntry[],\n): GPUComputePipeline {\n const pipelineCache = getPipelineCache(ctx.device);\n const cacheKey = `${wgslCode}::${entryPoint}`;\n const cached = pipelineCache.get(cacheKey);\n if (cached) return cached;\n\n let code = wgslCode;\n\n // Only prepend f16 enable if the shader actually uses f16 types.\n // Enabling it unconditionally can trigger buggy codepaths in Safari/WebKit's\n // WGSL→Metal compiler even when no f16 types are used.\n const usesF16 = code.includes(\": f16\") || code.includes(\"<f16>\");\n if (ctx.hasF16 && usesF16 && !code.includes(\"enable f16\")) {\n code = `enable f16;\\n\\n${code}`;\n }\n\n const shaderModule = ctx.device.createShaderModule({\n label: `${label}_shader`,\n code,\n });\n\n let layout: GPUPipelineLayout | \"auto\" = \"auto\";\n if (bindGroupLayoutEntries) {\n const bindGroupLayout = ctx.device.createBindGroupLayout({\n label: `${label}_layout`,\n entries: bindGroupLayoutEntries,\n });\n layout = ctx.device.createPipelineLayout({\n label: `${label}_pipeline_layout`,\n bindGroupLayouts: [bindGroupLayout],\n });\n }\n\n const pipeline = ctx.device.createComputePipeline({\n label,\n layout,\n compute: {\n module: shaderModule,\n entryPoint,\n },\n });\n\n pipelineCache.set(cacheKey, pipeline);\n return pipeline;\n}\n\n/**\n * Clear the pipeline cache for a specific device, or no-op if called without\n * a device (WeakMap handles cleanup automatically when the device is GC'd).\n */\nexport function clearPipelineCache(device?: GPUDevice): void {\n if (device) {\n perDeviceCache.delete(device);\n }\n}\n\n// ── Bind group helpers ────────────────────────────────────────────────\n\nexport interface BindGroupEntry {\n buffer: GPUBuffer;\n offset?: number;\n size?: number;\n}\n\n/**\n * Create a bind group from a list of buffers.\n */\nexport function createBindGroup(\n ctx: GPUContext,\n pipeline: GPUComputePipeline,\n entries: BindGroupEntry[],\n label?: string,\n): GPUBindGroup {\n return ctx.device.createBindGroup({\n label,\n layout: pipeline.getBindGroupLayout(0),\n entries: entries.map((entry, i) => ({\n binding: i,\n resource: {\n buffer: entry.buffer,\n offset: entry.offset ?? 0,\n size: entry.size ?? entry.buffer.size,\n },\n })),\n });\n}\n\n// ── Dispatch helpers ──────────────────────────────────────────────────\n\n/**\n * Encode and submit a single compute dispatch.\n * Useful for testing; the executor batches dispatches in practice.\n */\nexport function dispatchOnce(\n ctx: GPUContext,\n pipeline: GPUComputePipeline,\n bindGroup: GPUBindGroup,\n workgroupCountX: number,\n workgroupCountY: number = 1,\n workgroupCountZ: number = 1,\n): void {\n const encoder = ctx.device.createCommandEncoder();\n const pass = encoder.beginComputePass();\n pass.setPipeline(pipeline);\n pass.setBindGroup(0, bindGroup);\n pass.dispatchWorkgroups(workgroupCountX, workgroupCountY, workgroupCountZ);\n pass.end();\n ctx.device.queue.submit([encoder.finish()]);\n}\n\n// ── Readback helpers ──────────────────────────────────────────────────\n\n/**\n * Copy data from a storage buffer to a readback buffer, then map and return\n * the contents as a Float32Array.\n *\n * This is the primary way to get results (logits) back to CPU for sampling.\n */\nexport async function readbackFloats(\n ctx: GPUContext,\n source: GPUBuffer,\n readback: GPUBuffer,\n byteOffset: number = 0,\n byteLength?: number,\n): Promise<Float32Array> {\n const length = byteLength ?? source.size - byteOffset;\n\n const encoder = ctx.device.createCommandEncoder();\n encoder.copyBufferToBuffer(source, byteOffset, readback, 0, length);\n ctx.device.queue.submit([encoder.finish()]);\n\n await readback.mapAsync(MAP_MODE_READ, 0, length);\n const mapped = readback.getMappedRange(0, length);\n const result = new Float32Array(mapped.slice(0));\n readback.unmap();\n\n return result;\n}\n\n/**\n * Read back raw bytes from a GPU buffer.\n */\nexport async function readbackBytes(\n ctx: GPUContext,\n source: GPUBuffer,\n readback: GPUBuffer,\n byteOffset: number = 0,\n byteLength?: number,\n): Promise<ArrayBuffer> {\n const length = byteLength ?? source.size - byteOffset;\n\n const encoder = ctx.device.createCommandEncoder();\n encoder.copyBufferToBuffer(source, byteOffset, readback, 0, length);\n ctx.device.queue.submit([encoder.finish()]);\n\n await readback.mapAsync(MAP_MODE_READ, 0, length);\n const mapped = readback.getMappedRange(0, length);\n const result = mapped.slice(0);\n readback.unmap();\n\n return result;\n}\n\n// ── Memory helpers ────────────────────────────────────────────────────\n\n/**\n * Destroy a list of GPU buffers and release their memory.\n */\nexport function destroyBuffers(buffers: GPUBuffer[]): void {\n for (const buf of buffers) {\n buf.destroy();\n }\n}\n\n// ── GPU diagnostics ──────────────────────────────────────────────────\n\nexport interface GPUDiagnosticResult {\n /** Whether the basic buffer upload → readback round-trip works. */\n bufferIntegrity: boolean;\n /** Whether a trivial compute shader executes correctly. */\n computeWorks: boolean;\n /** Whether shared memory + workgroupBarrier() works. */\n sharedMemoryWorks: boolean;\n /** Detailed messages for each test. */\n details: string[];\n}\n\n/**\n * Run GPU diagnostics to verify basic WebGPU operations work correctly.\n * Useful for isolating Safari/WebKit-specific issues.\n */\nexport async function verifyGPU(ctx: GPUContext): Promise<GPUDiagnosticResult> {\n const details: string[] = [];\n let bufferIntegrity = false;\n let computeWorks = false;\n let sharedMemoryWorks = false;\n\n // Report device limits\n try {\n details.push(\n `Limits: wgSize=${ctx.limits.maxComputeWorkgroupSizeX}, ` +\n `wgStorage=${ctx.limits.maxComputeWorkgroupStorageSize}, ` +\n `maxBuf=${(ctx.limits.maxBufferSize / 1024 / 1024).toFixed(0)}MB, ` +\n `f16=${ctx.hasF16}, subgroups=${ctx.hasSubgroups}`,\n );\n } catch {}\n\n // Test 1: Buffer upload via writeBuffer → readback\n try {\n const testData = new Float32Array([1.0, 2.0, 3.0, 4.0, -1.5, 0.0, 100.0, 0.001]);\n const srcBuf = createStorageBuffer(ctx, \"diag_src\", testData.byteLength, testData);\n const readBuf = createReadbackBuffer(ctx, \"diag_read\", testData.byteLength);\n const result = await readbackFloats(ctx, srcBuf, readBuf, 0, testData.byteLength);\n\n let match = true;\n for (let i = 0; i < testData.length; i++) {\n if (Math.abs(result[i] - testData[i]) > 1e-6) {\n details.push(`Buffer mismatch at [${i}]: expected ${testData[i]}, got ${result[i]}`);\n match = false;\n }\n }\n bufferIntegrity = match;\n details.push(\n bufferIntegrity ? \"✓ Buffer upload/readback OK\" : \"✗ Buffer upload/readback FAILED\",\n );\n srcBuf.destroy();\n readBuf.destroy();\n } catch (e) {\n details.push(`✗ Buffer test error: ${e instanceof Error ? e.message : String(e)}`);\n }\n\n // ── Isolation tests ──────────────────────────────────────────────\n // Test 2a PASSED (single encoder, wg=1, no input, onSubmittedWorkDone).\n // Test 2b FAILED (separate encoders, wg=4, has input, no wait).\n // Need to isolate: is it (A) separate encoders, (B) input binding, or (C) workgroup size?\n //\n // Matrix of tests — each changes ONE variable from the passing baseline:\n // Baseline: single encoder, wg=1, no input, with onSubmittedWorkDone → PASSES\n // Test A: SEPARATE encoders, wg=1, no input, no wait → tests queue ordering\n // Test B: single encoder, wg=1, HAS input, with wait → tests input binding\n // Test C: single encoder, wg=4, no input, with wait → tests workgroup size\n // Test D: single encoder, wg=256 + shared mem, no input, with wait → tests shared memory\n\n /** Helper: run a compute shader and read back result using a SINGLE command encoder + onSubmittedWorkDone. */\n const runSingleEncoder = async (\n label: string,\n code: string,\n bindings: GPUBuffer[],\n readBuf: GPUBuffer,\n outBuf: GPUBuffer,\n readBytes: number,\n workgroups: number = 1,\n ): Promise<Float32Array> => {\n const pipeline = getOrCreatePipeline(ctx, label, code, \"main\");\n const bg = createBindGroup(\n ctx,\n pipeline,\n bindings.map((b) => ({ buffer: b })),\n `${label}_bg`,\n );\n const encoder = ctx.device.createCommandEncoder();\n const pass = encoder.beginComputePass();\n pass.setPipeline(pipeline);\n pass.setBindGroup(0, bg);\n pass.dispatchWorkgroups(workgroups);\n pass.end();\n encoder.copyBufferToBuffer(outBuf, 0, readBuf, 0, readBytes);\n ctx.device.queue.submit([encoder.finish()]);\n if (ctx.device.queue.onSubmittedWorkDone) {\n await ctx.device.queue.onSubmittedWorkDone();\n }\n await readBuf.mapAsync(MAP_MODE_READ, 0, readBytes);\n const mapped = readBuf.getMappedRange(0, readBytes);\n const result = new Float32Array(mapped.slice(0));\n readBuf.unmap();\n return result;\n };\n\n /** Helper: run compute with SEPARATE encoders (like dispatchOnce + readbackFloats). */\n const runSeparateEncoders = async (\n label: string,\n code: string,\n bindings: GPUBuffer[],\n readBuf: GPUBuffer,\n outBuf: GPUBuffer,\n readBytes: number,\n workgroups: number = 1,\n ): Promise<Float32Array> => {\n const pipeline = getOrCreatePipeline(ctx, label, code, \"main\");\n const bg = createBindGroup(\n ctx,\n pipeline,\n bindings.map((b) => ({ buffer: b })),\n `${label}_bg`,\n );\n // Encoder 1: compute dispatch\n dispatchOnce(ctx, pipeline, bg, workgroups);\n // Encoder 2: copy + readback\n return readbackFloats(ctx, outBuf, readBuf, 0, readBytes);\n };\n\n // Test A: Separate encoders, wg=1, no input (isolates queue ordering)\n try {\n const shader = `\n @group(0) @binding(0) var<storage, read_write> out: array<f32>;\n @compute @workgroup_size(1)\n fn main() { out[0] = 42.0; }\n `;\n const outBuf = createStorageBuffer(ctx, \"dA_out\", 4);\n const readBuf = createReadbackBuffer(ctx, \"dA_read\", 4);\n const result = await runSeparateEncoders(\"dA\", shader, [outBuf], readBuf, outBuf, 4);\n const ok = Math.abs(result[0] - 42.0) < 1e-3;\n details.push(\n ok\n ? `✓ A: separate encoders OK (${result[0]})`\n : `✗ A: separate encoders FAILED (got ${result[0]})`,\n );\n outBuf.destroy();\n readBuf.destroy();\n } catch (e) {\n details.push(`✗ A error: ${e instanceof Error ? e.message : String(e)}`);\n }\n\n // Test B: Single encoder, wg=1, HAS input (isolates input binding)\n try {\n const shader = `\n @group(0) @binding(0) var<storage, read> src: array<f32>;\n @group(0) @binding(1) var<storage, read_write> dst: array<f32>;\n @compute @workgroup_size(1)\n fn main() { dst[0] = src[0] * 2.0 + 1.0; }\n `;\n const input = new Float32Array([3.0]);\n const inBuf = createStorageBuffer(ctx, \"dB_in\", 4, input);\n const outBuf = createStorageBuffer(ctx, \"dB_out\", 4);\n const readBuf = createReadbackBuffer(ctx, \"dB_read\", 4);\n const result = await runSingleEncoder(\"dB\", shader, [inBuf, outBuf], readBuf, outBuf, 4);\n const ok = Math.abs(result[0] - 7.0) < 1e-3;\n details.push(\n ok ? `✓ B: input binding OK (${result[0]})` : `✗ B: input binding FAILED (got ${result[0]})`,\n );\n computeWorks = ok;\n inBuf.destroy();\n outBuf.destroy();\n readBuf.destroy();\n } catch (e) {\n details.push(`✗ B error: ${e instanceof Error ? e.message : String(e)}`);\n }\n\n // Test C: Single encoder, wg=4, no input (isolates workgroup size)\n try {\n const shader = `\n @group(0) @binding(0) var<storage, read_write> out: array<f32>;\n @compute @workgroup_size(4)\n fn main(@builtin(local_invocation_id) lid: vec3u) {\n out[lid.x] = f32(lid.x) + 1.0;\n }\n `;\n const outBuf = createStorageBuffer(ctx, \"dC_out\", 16);\n const readBuf = createReadbackBuffer(ctx, \"dC_read\", 16);\n const result = await runSingleEncoder(\"dC\", shader, [outBuf], readBuf, outBuf, 16);\n const ok = Math.abs(result[0] - 1.0) < 1e-3 && Math.abs(result[3] - 4.0) < 1e-3;\n details.push(\n ok\n ? `✓ C: wg=4 OK (${Array.from(result).join(\",\")})`\n : `✗ C: wg=4 FAILED (${Array.from(result).join(\",\")})`,\n );\n outBuf.destroy();\n readBuf.destroy();\n } catch (e) {\n details.push(`✗ C error: ${e instanceof Error ? e.message : String(e)}`);\n }\n\n // Test D: Single encoder, wg=4 + shared memory (isolates shared mem)\n // Uses a small workgroup (4 threads) to avoid 256-thread driver issues in diagnostics.\n // Each thread writes to shared memory, barrier, thread 0 sums and writes to output.\n try {\n const shader = `\n var<workgroup> smem: array<f32, 4>;\n @group(0) @binding(0) var<storage, read_write> dst: array<f32>;\n @compute @workgroup_size(4)\n fn main(@builtin(local_invocation_id) lid: vec3u) {\n smem[lid.x] = f32(lid.x) + 1.0;\n workgroupBarrier();\n if (lid.x == 0u) {\n dst[0] = smem[0] + smem[1] + smem[2] + smem[3];\n }\n }\n `;\n const outBuf = createStorageBuffer(ctx, \"dD_out\", 4);\n const readBuf = createReadbackBuffer(ctx, \"dD_read\", 4);\n const result = await runSingleEncoder(\"dD\", shader, [outBuf], readBuf, outBuf, 4);\n sharedMemoryWorks = Math.abs(result[0] - 10.0) < 1e-3;\n details.push(\n sharedMemoryWorks\n ? `✓ D: shared mem OK (${result[0]})`\n : `✗ D: shared mem FAILED (got ${result[0]})`,\n );\n outBuf.destroy();\n readBuf.destroy();\n } catch (e) {\n details.push(`✗ D error: ${e instanceof Error ? e.message : String(e)}`);\n }\n\n // Test E: Separate encoders + input + wg=4 (the full failing combo, to confirm)\n try {\n const shader = `\n @group(0) @binding(0) var<storage, read> src: array<f32>;\n @group(0) @binding(1) var<storage, read_write> dst: array<f32>;\n @compute @workgroup_size(4)\n fn main(@builtin(global_invocation_id) gid: vec3u) {\n if (gid.x < 4u) { dst[gid.x] = src[gid.x] * 2.0 + 1.0; }\n }\n `;\n const input = new Float32Array([3.0, 7.0, -2.0, 0.5]);\n const inBuf = createStorageBuffer(ctx, \"dE_in\", 16, input);\n const outBuf = createStorageBuffer(ctx, \"dE_out\", 16);\n const readBuf = createReadbackBuffer(ctx, \"dE_read\", 16);\n const result = await runSeparateEncoders(\"dE\", shader, [inBuf, outBuf], readBuf, outBuf, 16);\n const ok = Math.abs(result[0] - 7.0) < 1e-3 && Math.abs(result[1] - 15.0) < 1e-3;\n details.push(\n ok ? `✓ E: full combo OK` : `✗ E: full combo FAILED (${Array.from(result).join(\",\")})`,\n );\n inBuf.destroy();\n outBuf.destroy();\n readBuf.destroy();\n } catch (e) {\n details.push(`✗ E error: ${e instanceof Error ? e.message : String(e)}`);\n }\n\n // Test F: pack2x16float / unpack2x16float round-trip (tests the packed-f16 KV path)\n // Packs pairs of f32 → u32 via pack2x16float, then unpacks back via unpack2x16float.\n // If this fails on Safari/Metal, the packed KV cache will produce gibberish.\n try {\n const shader = `\n @group(0) @binding(0) var<storage, read> src: array<f32>;\n @group(0) @binding(1) var<storage, read_write> dst: array<f32>;\n @compute @workgroup_size(1)\n fn main() {\n // Pack pairs of f32 into u32, then unpack back\n let packed0 = pack2x16float(vec2f(src[0], src[1]));\n let packed1 = pack2x16float(vec2f(src[2], src[3]));\n let packed2 = pack2x16float(vec2f(src[4], src[5]));\n let packed3 = pack2x16float(vec2f(src[6], src[7]));\n\n let u0 = unpack2x16float(packed0);\n let u1 = unpack2x16float(packed1);\n let u2 = unpack2x16float(packed2);\n let u3 = unpack2x16float(packed3);\n\n dst[0] = u0.x; dst[1] = u0.y;\n dst[2] = u1.x; dst[3] = u1.y;\n dst[4] = u2.x; dst[5] = u2.y;\n dst[6] = u3.x; dst[7] = u3.y;\n }\n `;\n // Values chosen to cover: positive, negative, zero, small, >1\n const input = new Float32Array([1.0, -0.5, 0.0, 3.14, 0.001, -2.0, 100.0, 0.25]);\n const inBuf = createStorageBuffer(ctx, \"dF_in\", input.byteLength, input);\n const outBuf = createStorageBuffer(ctx, \"dF_out\", input.byteLength);\n const readBuf = createReadbackBuffer(ctx, \"dF_read\", input.byteLength);\n const result = await runSingleEncoder(\n \"dF\",\n shader,\n [inBuf, outBuf],\n readBuf,\n outBuf,\n input.byteLength,\n );\n // f16 has limited precision, so use larger tolerance\n let ok = true;\n const mismatches: string[] = [];\n for (let i = 0; i < input.length; i++) {\n const tol = Math.max(Math.abs(input[i]) * 0.01, 0.001); // 1% or 0.001\n if (Math.abs(result[i] - input[i]) > tol) {\n ok = false;\n mismatches.push(`[${i}]:${input[i]}→${result[i]}`);\n }\n }\n details.push(\n ok\n ? `✓ F: pack/unpack2x16float OK`\n : `✗ F: pack/unpack2x16float FAILED (${mismatches.join(\", \")})`,\n );\n inBuf.destroy();\n outBuf.destroy();\n readBuf.destroy();\n } catch (e) {\n details.push(`✗ F error: ${e instanceof Error ? e.message : String(e)}`);\n }\n\n // Test G: Packed KV-like pattern — pack into array<u32>, read back with elem indexing\n // Mimics the actual KV cache append + attention load_k/load_v pattern.\n try {\n const shader = `\n @group(0) @binding(0) var<storage, read> src: array<f32>;\n @group(0) @binding(1) var<storage, read_write> packed: array<u32>;\n @group(0) @binding(2) var<storage, read_write> dst: array<f32>;\n @compute @workgroup_size(1)\n fn main() {\n // Step 1: Pack f32 pairs into u32 array (like KVCacheAppend)\n packed[0] = pack2x16float(vec2f(src[0], src[1]));\n packed[1] = pack2x16float(vec2f(src[2], src[3]));\n packed[2] = pack2x16float(vec2f(src[4], src[5]));\n packed[3] = pack2x16float(vec2f(src[6], src[7]));\n\n // Step 2: Read back using element-level indexing (like load_k/load_v)\n for (var i: u32 = 0u; i < 8u; i += 1u) {\n let pair = unpack2x16float(packed[i >> 1u]);\n if ((i & 1u) == 0u) {\n dst[i] = pair.x;\n } else {\n dst[i] = pair.y;\n }\n }\n }\n `;\n const input = new Float32Array([1.5, -0.75, 2.0, 0.0, -1.0, 0.5, 3.0, -3.0]);\n const inBuf = createStorageBuffer(ctx, \"dG_in\", input.byteLength, input);\n const packedBuf = createStorageBuffer(ctx, \"dG_packed\", 16); // 4 u32 = 16 bytes\n const outBuf = createStorageBuffer(ctx, \"dG_out\", input.byteLength);\n const readBuf = createReadbackBuffer(ctx, \"dG_read\", input.byteLength);\n const result = await runSingleEncoder(\n \"dG\",\n shader,\n [inBuf, packedBuf, outBuf],\n readBuf,\n outBuf,\n input.byteLength,\n );\n let ok = true;\n const mismatches: string[] = [];\n for (let i = 0; i < input.length; i++) {\n const tol = Math.max(Math.abs(input[i]) * 0.01, 0.001);\n if (Math.abs(result[i] - input[i]) > tol) {\n ok = false;\n mismatches.push(`[${i}]:${input[i]}→${result[i]}`);\n }\n }\n details.push(\n ok\n ? `✓ G: packed KV round-trip OK`\n : `✗ G: packed KV round-trip FAILED (${mismatches.join(\", \")})`,\n );\n inBuf.destroy();\n packedBuf.destroy();\n outBuf.destroy();\n readBuf.destroy();\n } catch (e) {\n details.push(`✗ G error: ${e instanceof Error ? e.message : String(e)}`);\n }\n\n // Test H: Tree reduction with 256 threads + shared memory (RMSNorm/softmax pattern)\n // 256 threads each write a value, then tree-reduce to sum. Tests the exact\n // shared memory reduction pattern used in RMSNorm, softmax, and dot products.\n try {\n const shader = `\n var<workgroup> smem: array<f32, 256>;\n @group(0) @binding(0) var<storage, read> src: array<f32>;\n @group(0) @binding(1) var<storage, read_write> dst: array<f32>;\n @compute @workgroup_size(256)\n fn main(@builtin(local_invocation_id) lid: vec3u) {\n let tid = lid.x;\n smem[tid] = src[tid] * src[tid];\n workgroupBarrier();\n var stride: u32 = 128u;\n while (stride > 0u) {\n if (tid < stride) {\n smem[tid] += smem[tid + stride];\n }\n workgroupBarrier();\n stride /= 2u;\n }\n // Thread 0 writes sum, all threads write their squared input for verification\n dst[tid] = src[tid] * src[tid];\n if (tid == 0u) { dst[256] = smem[0]; }\n }\n `;\n // Input: 1..256, expected sum of squares = n(n+1)(2n+1)/6 = 5559680\n const input = new Float32Array(256);\n for (let i = 0; i < 256; i++) input[i] = i + 1;\n const expectedSum = (256 * 257 * 513) / 6; // 5559680\n const inBuf = createStorageBuffer(ctx, \"dH_in\", input.byteLength, input);\n const outBuf = createStorageBuffer(ctx, \"dH_out\", 257 * 4);\n const readBuf = createReadbackBuffer(ctx, \"dH_read\", 257 * 4);\n const result = await runSingleEncoder(\"dH\", shader, [inBuf, outBuf], readBuf, outBuf, 257 * 4);\n const sum = result[256];\n const ok = Math.abs(sum - expectedSum) / expectedSum < 0.001;\n details.push(\n ok\n ? `✓ H: 256-thread tree reduce OK (${sum})`\n : `✗ H: 256-thread tree reduce FAILED (got ${sum}, expected ${expectedSum})`,\n );\n inBuf.destroy();\n outBuf.destroy();\n readBuf.destroy();\n } catch (e) {\n details.push(`✗ H error: ${e instanceof Error ? e.message : String(e)}`);\n }\n\n // Test I: INT4 dequantize pattern (bit manipulation + multiply-add)\n // Tests the exact u32→nibble extraction used in MatMulInt4 and SwiGLU INT4 kernels.\n try {\n const shader = `\n @group(0) @binding(0) var<storage, read> packed: array<u32>;\n @group(0) @binding(1) var<storage, read> scales: array<f32>;\n @group(0) @binding(2) var<storage, read> zeros: array<f32>;\n @group(0) @binding(3) var<storage, read_write> dst: array<f32>;\n @compute @workgroup_size(1)\n fn main() {\n // Unpack 8 nibbles from one u32, dequantize: (nibble - zero) * scale\n let word = packed[0];\n let scale = scales[0];\n let zero = zeros[0];\n for (var i: u32 = 0u; i < 8u; i += 1u) {\n let nibble = (word >> (i * 4u)) & 0xFu;\n dst[i] = (f32(nibble) - zero) * scale;\n }\n }\n `;\n // Pack nibbles 0,1,2,...,7 into one u32: 0x76543210\n const packedData = new Uint32Array([0x76543210]);\n const scaleData = new Float32Array([0.5]);\n const zeroData = new Float32Array([2.0]);\n // Expected: (0-2)*0.5=-1, (1-2)*0.5=-0.5, (2-2)*0.5=0, (3-2)*0.5=0.5, ...\n const expected = [0, 1, 2, 3, 4, 5, 6, 7].map((n) => (n - 2.0) * 0.5);\n\n const packedBuf = createStorageBuffer(ctx, \"dI_packed\", 4, packedData);\n const scaleBuf = createStorageBuffer(ctx, \"dI_scale\", 4, scaleData);\n const zeroBuf = createStorageBuffer(ctx, \"dI_zero\", 4, zeroData);\n const outBuf = createStorageBuffer(ctx, \"dI_out\", 32);\n const readBuf = createReadbackBuffer(ctx, \"dI_read\", 32);\n const result = await runSingleEncoder(\n \"dI\",\n shader,\n [packedBuf, scaleBuf, zeroBuf, outBuf],\n readBuf,\n outBuf,\n 32,\n );\n let ok = true;\n const mismatches: string[] = [];\n for (let i = 0; i < 8; i++) {\n if (Math.abs(result[i] - expected[i]) > 0.001) {\n ok = false;\n mismatches.push(`[${i}]:${expected[i]}→${result[i]}`);\n }\n }\n details.push(\n ok ? `✓ I: INT4 dequant OK` : `✗ I: INT4 dequant FAILED (${mismatches.join(\", \")})`,\n );\n packedBuf.destroy();\n scaleBuf.destroy();\n zeroBuf.destroy();\n outBuf.destroy();\n readBuf.destroy();\n } catch (e) {\n details.push(`✗ I error: ${e instanceof Error ? e.message : String(e)}`);\n }\n\n // Test J: Multi-dispatch chain (tests sequential dispatch ordering)\n // Runs 3 dispatches in sequence within one command encoder: A→B→C.\n // If dispatch ordering is broken, intermediate results will be wrong.\n try {\n const shaderAdd = `\n @group(0) @binding(0) var<storage, read> a: array<f32>;\n @group(0) @binding(1) var<storage, read_write> b: array<f32>;\n @compute @workgroup_size(4)\n fn main(@builtin(global_invocation_id) gid: vec3u) {\n if (gid.x < 4u) { b[gid.x] = a[gid.x] + b[gid.x]; }\n }\n `;\n const shaderMul = `\n @group(0) @binding(0) var<storage, read_write> x: array<f32>;\n @compute @workgroup_size(4)\n fn main(@builtin(global_invocation_id) gid: vec3u) {\n if (gid.x < 4u) { x[gid.x] = x[gid.x] * 2.0; }\n }\n `;\n // Chain: buf = [1,2,3,4], add [10,20,30,40] → [11,22,33,44], mul ×2 → [22,44,66,88], add again → [32,64,96,128]... actually simpler:\n // Start: buf = [0,0,0,0]\n // Dispatch 1: buf += src ([1,2,3,4])\n // Dispatch 2: buf *= 2\n // Dispatch 3: buf += src again\n // Expected: ([1,2,3,4] * 2) + [1,2,3,4] = [3,6,9,12]\n const srcData = new Float32Array([1, 2, 3, 4]);\n const srcBuf = createStorageBuffer(ctx, \"dJ_src\", 16, srcData);\n const bufBuf = createStorageBuffer(ctx, \"dJ_buf\", 16, new Float32Array(4)); // zeros\n const readBuf = createReadbackBuffer(ctx, \"dJ_read\", 16);\n\n const addPipeline = getOrCreatePipeline(ctx, \"dJ_add\", shaderAdd, \"main\");\n const mulPipeline = getOrCreatePipeline(ctx, \"dJ_mul\", shaderMul, \"main\");\n\n const addBg = createBindGroup(\n ctx,\n addPipeline,\n [{ buffer: srcBuf }, { buffer: bufBuf }],\n \"dJ_add_bg\",\n );\n const mulBg = createBindGroup(ctx, mulPipeline, [{ buffer: bufBuf }], \"dJ_mul_bg\");\n\n const encoder = ctx.device.createCommandEncoder();\n const pass = encoder.beginComputePass();\n pass.setPipeline(addPipeline);\n pass.setBindGroup(0, addBg);\n pass.dispatchWorkgroups(1); // buf = [1,2,3,4]\n pass.setPipeline(mulPipeline);\n pass.setBindGroup(0, mulBg);\n pass.dispatchWorkgroups(1); // buf = [2,4,6,8]\n pass.setPipeline(addPipeline);\n pass.setBindGroup(0, addBg);\n pass.dispatchWorkgroups(1); // buf = [3,6,9,12]\n pass.end();\n encoder.copyBufferToBuffer(bufBuf, 0, readBuf, 0, 16);\n ctx.device.queue.submit([encoder.finish()]);\n if (ctx.device.queue.onSubmittedWorkDone) {\n await ctx.device.queue.onSubmittedWorkDone();\n }\n await readBuf.mapAsync(MAP_MODE_READ, 0, 16);\n const mapped = readBuf.getMappedRange(0, 16);\n const result = new Float32Array(mapped.slice(0));\n readBuf.unmap();\n\n const expected = [3, 6, 9, 12];\n let ok = true;\n for (let i = 0; i < 4; i++) {\n if (Math.abs(result[i] - expected[i]) > 0.01) ok = false;\n }\n details.push(\n ok\n ? `✓ J: multi-dispatch chain OK (${Array.from(result).join(\",\")})`\n : `✗ J: multi-dispatch chain FAILED (got ${Array.from(result).join(\",\")}, expected ${expected.join(\",\")})`,\n );\n srcBuf.destroy();\n bufBuf.destroy();\n readBuf.destroy();\n } catch (e) {\n details.push(`✗ J error: ${e instanceof Error ? e.message : String(e)}`);\n }\n\n // Test K: exp() clamp safety (Metal fast-math NaN check)\n // Tests that exp(large_negative) returns ~0 and not NaN.\n try {\n const shader = `\n @group(0) @binding(0) var<storage, read_write> dst: array<f32>;\n @compute @workgroup_size(1)\n fn main() {\n dst[0] = exp(max(-1000.0, -80.0)); // should be exp(-80) ≈ 1.8e-35\n dst[1] = exp(max(-1e30, -80.0)); // should be exp(-80) ≈ 1.8e-35\n dst[2] = exp(-10.0); // should be ~4.54e-5\n dst[3] = 1.0 / (1.0 + exp(max(-50.0, -80.0))); // sigmoid(50) ≈ 1.0\n // Direct exp without clamp — what does Metal actually do?\n dst[4] = exp(-100.0); // should be ~3.7e-44 or 0\n dst[5] = exp(-1000.0); // might be NaN on Metal!\n dst[6] = exp(-1e10); // might be NaN on Metal!\n // Check for NaN: NaN != NaN\n let v5 = exp(-1000.0);\n if (v5 != v5) { dst[7] = -999.0; } else { dst[7] = v5; }\n }\n `;\n const outBuf = createStorageBuffer(ctx, \"dK_out\", 32);\n const readBuf = createReadbackBuffer(ctx, \"dK_read\", 32);\n const result = await runSingleEncoder(\"dK\", shader, [outBuf], readBuf, outBuf, 32);\n const clampedOk = result[0] > 0 && result[0] < 1e-30 && result[1] > 0 && result[1] < 1e-30;\n const normalOk = Math.abs(result[2] - Math.exp(-10)) < 1e-8;\n const sigmoidOk = Math.abs(result[3] - 1.0) < 0.01;\n const nanDetected =\n result[7] === -999 || Number.isNaN(result[5]) || Number.isNaN(result[6]) || result[5] === 0;\n\n const detail =\n `clamped=${result[0].toExponential(2)},${result[1].toExponential(2)} ` +\n `exp(-10)=${result[2].toExponential(2)} sig=${result[3].toFixed(4)} ` +\n `raw: exp(-100)=${result[4]} exp(-1000)=${result[5]} exp(-1e10)=${result[6]} ` +\n `NaN?=${result[7]}`;\n\n details.push(\n clampedOk && normalOk\n ? `✓ K: exp() clamp OK (${detail})`\n : `✗ K: exp() clamp FAILED (${detail})`,\n );\n outBuf.destroy();\n readBuf.destroy();\n } catch (e) {\n details.push(`✗ K error: ${e instanceof Error ? e.message : String(e)}`);\n }\n\n // Test L: Large buffer integrity (1 MB upload + readback)\n // Weights are uploaded as large storage buffers. If Safari's writeBuffer silently\n // corrupts data for large buffers or views with non-zero byteOffset, this will catch it.\n try {\n const MB = 256 * 1024; // 256K floats = 1 MB\n const testData = new Float32Array(MB);\n // Fill with a deterministic non-trivial pattern (not all-zeros or sequential)\n for (let i = 0; i < MB; i++) {\n testData[i] = Math.sin(i * 0.001) * (i % 1000) * 0.01;\n }\n const srcBuf = createStorageBuffer(ctx, \"dL_src\", testData.byteLength, testData);\n const readBuf = createReadbackBuffer(ctx, \"dL_read\", testData.byteLength);\n const result = await readbackFloats(ctx, srcBuf, readBuf, 0, testData.byteLength);\n\n let mismatches = 0;\n let firstMismatchIdx = -1;\n for (let i = 0; i < MB; i++) {\n if (Math.abs(result[i] - testData[i]) > 1e-6) {\n if (firstMismatchIdx === -1) firstMismatchIdx = i;\n mismatches++;\n }\n }\n const ok = mismatches === 0;\n details.push(\n ok\n ? `✓ L: 1MB buffer integrity OK (${MB} floats)`\n : `✗ L: 1MB buffer FAILED (${mismatches} mismatches, first at [${firstMismatchIdx}]: expected ${testData[firstMismatchIdx]}, got ${result[firstMismatchIdx]})`,\n );\n srcBuf.destroy();\n readBuf.destroy();\n } catch (e) {\n details.push(`✗ L error: ${e instanceof Error ? e.message : String(e)}`);\n }\n\n // Test M: Large buffer with byteOffset (simulates weight tensor views)\n // Weight tensors from safetensors are ArrayBufferViews with non-zero byteOffset\n // into a larger buffer. Safari might handle the offset incorrectly in writeBuffer.\n try {\n // Create a 2 MB backing buffer, write a known pattern at offset 1 MB\n const backingSize = 512 * 1024; // 512K floats = 2 MB\n const backing = new Float32Array(backingSize);\n const sliceStart = 256 * 1024; // Start at 1 MB offset\n const sliceLen = 128 * 1024; // 128K floats = 512 KB\n for (let i = 0; i < sliceLen; i++) {\n backing[sliceStart + i] = Math.cos(i * 0.002) * (i % 500) * 0.005;\n }\n // Create a view with non-zero byteOffset (like getTensorData returns)\n const view = new Float32Array(backing.buffer, sliceStart * 4, sliceLen);\n\n const srcBuf = createStorageBuffer(ctx, \"dM_src\", view.byteLength, view);\n const readBuf = createReadbackBuffer(ctx, \"dM_read\", view.byteLength);\n const result = await readbackFloats(ctx, srcBuf, readBuf, 0, view.byteLength);\n\n let mismatches = 0;\n let firstMismatchIdx = -1;\n for (let i = 0; i < sliceLen; i++) {\n if (Math.abs(result[i] - view[i]) > 1e-6) {\n if (firstMismatchIdx === -1) firstMismatchIdx = i;\n mismatches++;\n }\n }\n const ok = mismatches === 0;\n details.push(\n ok\n ? `✓ M: offset buffer integrity OK (512KB at 1MB offset)`\n : `✗ M: offset buffer FAILED (${mismatches} mismatches, first at [${firstMismatchIdx}]: expected ${view[firstMismatchIdx]}, got ${result[firstMismatchIdx]})`,\n );\n srcBuf.destroy();\n readBuf.destroy();\n } catch (e) {\n details.push(`✗ M error: ${e instanceof Error ? e.message : String(e)}`);\n }\n\n // Test N: Real RMSNorm kernel (actual WGSL from registry, not simplified)\n // Tests the exact shader code used in inference. If Safari's WGSL→MSL compiler\n // miscompiles the real kernel, this will catch it.\n try {\n const rmsnormShader = `\nstruct Params {\n seq_len: u32,\n hidden_size: u32,\n eps_bits: u32,\n _pad: u32,\n}\n\n@group(0) @binding(0) var<storage, read> input: array<f32>;\n@group(0) @binding(1) var<storage, read> weight: array<f32>;\n@group(0) @binding(2) var<storage, read_write> output: array<f32>;\n@group(0) @binding(3) var<storage, read> params: Params;\n\nvar<workgroup> shared_sum: array<f32, 256>;\n\n@compute @workgroup_size(256)\nfn main(\n @builtin(local_invocation_id) lid: vec3u,\n @builtin(workgroup_id) wid: vec3u,\n) {\n let row = wid.x;\n if (row >= params.seq_len) { return; }\n\n let tid = lid.x;\n let row_offset = row * params.hidden_size;\n let eps = bitcast<f32>(params.eps_bits);\n\n var local_sum: f32 = 0.0;\n var i = tid;\n while (i < params.hidden_size) {\n let val = input[row_offset + i];\n local_sum += val * val;\n i += 256u;\n }\n shared_sum[tid] = local_sum;\n workgroupBarrier();\n\n var stride: u32 = 128u;\n while (stride > 0u) {\n if (tid < stride) {\n shared_sum[tid] += shared_sum[tid + stride];\n }\n workgroupBarrier();\n stride = stride / 2u;\n }\n\n let rms = sqrt(shared_sum[0] / f32(params.hidden_size) + eps);\n\n i = tid;\n while (i < params.hidden_size) {\n let val = input[row_offset + i];\n output[row_offset + i] = (val / rms) * weight[i];\n i += 256u;\n }\n}\n `;\n\n // Set up test data: hidden_size=1024, seq_len=1, eps=1e-6\n const hiddenSize = 1024;\n const inputData = new Float32Array(hiddenSize);\n const weightData = new Float32Array(hiddenSize);\n for (let i = 0; i < hiddenSize; i++) {\n inputData[i] = Math.sin(i * 0.01) * 2.0; // range [-2, 2]\n weightData[i] = 1.0 + Math.cos(i * 0.005) * 0.1; // range [0.9, 1.1]\n }\n\n // Compute expected output in JS\n let sumSq = 0;\n for (let i = 0; i < hiddenSize; i++) sumSq += inputData[i] * inputData[i];\n const rms = Math.sqrt(sumSq / hiddenSize + 1e-6);\n const expectedOutput = new Float32Array(hiddenSize);\n for (let i = 0; i < hiddenSize; i++) {\n expectedOutput[i] = (inputData[i] / rms) * weightData[i];\n }\n\n // Encode eps as u32 bits\n const epsF32 = new Float32Array(1);\n epsF32[0] = 1e-6;\n const epsBits = new Uint32Array(epsF32.buffer)[0];\n\n // Build uniform buffer: {seq_len=1, hidden_size=1024, eps_bits, _pad=0}\n const paramsBuf = new ArrayBuffer(16);\n const paramsView = new DataView(paramsBuf);\n paramsView.setUint32(0, 1, true);\n paramsView.setUint32(4, hiddenSize, true);\n paramsView.setUint32(8, epsBits, true);\n paramsView.setUint32(12, 0, true);\n\n const inBuf = createStorageBuffer(ctx, \"dN_in\", inputData.byteLength, inputData);\n const wBuf = createStorageBuffer(ctx, \"dN_w\", weightData.byteLength, weightData);\n const outBuf = createStorageBuffer(ctx, \"dN_out\", hiddenSize * 4);\n const uniformBuf = createUniformBuffer(ctx, \"dN_params\", paramsBuf);\n const readBuf = createReadbackBuffer(ctx, \"dN_read\", hiddenSize * 4);\n\n const pipeline = getOrCreatePipeline(ctx, \"dN_rmsnorm\", rmsnormShader, \"main\");\n const bg = createBindGroup(\n ctx,\n pipeline,\n [{ buffer: inBuf }, { buffer: wBuf }, { buffer: outBuf }, { buffer: uniformBuf }],\n \"dN_bg\",\n );\n\n const encoder = ctx.device.createCommandEncoder();\n const pass = encoder.beginComputePass();\n pass.setPipeline(pipeline);\n pass.setBindGroup(0, bg);\n pass.dispatchWorkgroups(1); // 1 row\n pass.end();\n encoder.copyBufferToBuffer(outBuf, 0, readBuf, 0, hiddenSize * 4);\n ctx.device.queue.submit([encoder.finish()]);\n if (ctx.device.queue.onSubmittedWorkDone) {\n await ctx.device.queue.onSubmittedWorkDone();\n }\n await readBuf.mapAsync(MAP_MODE_READ, 0, hiddenSize * 4);\n const mapped = readBuf.getMappedRange(0, hiddenSize * 4);\n const result = new Float32Array(mapped.slice(0));\n readBuf.unmap();\n\n let maxErr = 0;\n let maxErrIdx = 0;\n for (let i = 0; i < hiddenSize; i++) {\n const err = Math.abs(result[i] - expectedOutput[i]);\n if (err > maxErr) {\n maxErr = err;\n maxErrIdx = i;\n }\n }\n // Allow small floating-point tolerance (tree reduce accumulation order differs)\n const ok = maxErr < 0.01;\n details.push(\n ok\n ? `✓ N: real RMSNorm kernel OK (maxErr=${maxErr.toExponential(2)} at [${maxErrIdx}])`\n : `✗ N: real RMSNorm kernel FAILED (maxErr=${maxErr.toExponential(2)} at [${maxErrIdx}]: expected ${expectedOutput[maxErrIdx].toFixed(4)}, got ${result[maxErrIdx].toFixed(4)})`,\n );\n inBuf.destroy();\n wBuf.destroy();\n outBuf.destroy();\n uniformBuf.destroy();\n readBuf.destroy();\n } catch (e) {\n details.push(`✗ N error: ${e instanceof Error ? e.message : String(e)}`);\n }\n\n // Test O: Real MatVec kernel (actual WGSL from registry)\n // Tests K-parallel matrix-vector multiply with 256 threads = 8×32 layout.\n try {\n const matvecShader = `\nconst N_TILE: u32 = 8u;\nconst K_THREADS: u32 = 32u;\n\nstruct Params {\n K: u32,\n N: u32,\n}\n\n@group(0) @binding(0) var<storage, read> A: array<f32>;\n@group(0) @binding(1) var<storage, read> B: array<f32>;\n@group(0) @binding(2) var<storage, read_write> C: array<f32>;\n@group(0) @binding(3) var<storage, read> params: Params;\n\nvar<workgroup> shared_sums: array<f32, 256>;\n\n@compute @workgroup_size(256)\nfn main(\n @builtin(local_invocation_id) lid: vec3u,\n @builtin(workgroup_id) wid: vec3u,\n) {\n let tid = lid.x;\n let n_idx = tid / K_THREADS;\n let k_tid = tid % K_THREADS;\n let col = wid.x * N_TILE + n_idx;\n let K = params.K;\n\n var sum: f32 = 0.0;\n if (col < params.N) {\n let b_off = col * K;\n var k = k_tid * 4u;\n while (k + 3u < K) {\n let a_v = vec4f(A[k], A[k+1u], A[k+2u], A[k+3u]);\n let b_v = vec4f(B[b_off+k], B[b_off+k+1u], B[b_off+k+2u], B[b_off+k+3u]);\n sum += dot(a_v, b_v);\n k += K_THREADS * 4u;\n }\n }\n\n shared_sums[tid] = sum;\n workgroupBarrier();\n\n if (k_tid == 0u && col < params.N) {\n let base = n_idx * K_THREADS;\n var total: f32 = shared_sums[base];\n for (var i: u32 = 1u; i < K_THREADS; i++) {\n total += shared_sums[base + i];\n }\n C[col] = total;\n }\n}\n `;\n\n // Test: A[256] * B[64, 256] → C[64] (small but realistic dimensions)\n const K_dim = 256;\n const N_dim = 64;\n const aData = new Float32Array(K_dim);\n const bData = new Float32Array(N_dim * K_dim);\n for (let i = 0; i < K_dim; i++) aData[i] = Math.sin(i * 0.05) * 0.5;\n for (let i = 0; i < N_dim * K_dim; i++) bData[i] = Math.cos(i * 0.003) * 0.1;\n\n // Expected: C[n] = sum_k A[k] * B[n*K + k]\n const expectedC = new Float32Array(N_dim);\n for (let n = 0; n < N_dim; n++) {\n let s = 0;\n for (let k = 0; k < K_dim; k++) s += aData[k] * bData[n * K_dim + k];\n expectedC[n] = s;\n }\n\n const paramBuf2 = new ArrayBuffer(8);\n const pv2 = new DataView(paramBuf2);\n pv2.setUint32(0, K_dim, true);\n pv2.setUint32(4, N_dim, true);\n\n const aBuf = createStorageBuffer(ctx, \"dO_a\", aData.byteLength, aData);\n const bBuf = createStorageBuffer(ctx, \"dO_b\", bData.byteLength, bData);\n const cBuf = createStorageBuffer(ctx, \"dO_c\", N_dim * 4);\n const uBuf = createUniformBuffer(ctx, \"dO_params\", paramBuf2);\n const rBuf = createReadbackBuffer(ctx, \"dO_read\", N_dim * 4);\n\n const mvPipeline = getOrCreatePipeline(ctx, \"dO_matvec\", matvecShader, \"main\");\n const mvBg = createBindGroup(\n ctx,\n mvPipeline,\n [{ buffer: aBuf }, { buffer: bBuf }, { buffer: cBuf }, { buffer: uBuf }],\n \"dO_bg\",\n );\n\n // Dispatch: N_dim / 8 = 8 workgroups\n const encoder2 = ctx.device.createCommandEncoder();\n const pass2 = encoder2.beginComputePass();\n pass2.setPipeline(mvPipeline);\n pass2.setBindGroup(0, mvBg);\n pass2.dispatchWorkgroups(Math.ceil(N_dim / 8));\n pass2.end();\n encoder2.copyBufferToBuffer(cBuf, 0, rBuf, 0, N_dim * 4);\n ctx.device.queue.submit([encoder2.finish()]);\n if (ctx.device.queue.onSubmittedWorkDone) {\n await ctx.device.queue.onSubmittedWorkDone();\n }\n await rBuf.mapAsync(MAP_MODE_READ, 0, N_dim * 4);\n const mapped2 = rBuf.getMappedRange(0, N_dim * 4);\n const resultC = new Float32Array(mapped2.slice(0));\n rBuf.unmap();\n\n let maxErr2 = 0;\n let maxErrIdx2 = 0;\n for (let i = 0; i < N_dim; i++) {\n const err = Math.abs(resultC[i] - expectedC[i]);\n if (err > maxErr2) {\n maxErr2 = err;\n maxErrIdx2 = i;\n }\n }\n const ok2 = maxErr2 < 0.01;\n details.push(\n ok2\n ? `✓ O: real MatVec kernel OK (maxErr=${maxErr2.toExponential(2)} at [${maxErrIdx2}])`\n : `✗ O: real MatVec kernel FAILED (maxErr=${maxErr2.toExponential(2)} at [${maxErrIdx2}]: expected ${expectedC[maxErrIdx2].toFixed(4)}, got ${resultC[maxErrIdx2].toFixed(4)})`,\n );\n aBuf.destroy();\n bBuf.destroy();\n cBuf.destroy();\n uBuf.destroy();\n rBuf.destroy();\n } catch (e) {\n details.push(`✗ O error: ${e instanceof Error ? e.message : String(e)}`);\n }\n\n // Test P: Many dispatches in one compute pass (300 sequential dispatches)\n // The model runs ~300 dispatches per forward pass in a single compute pass.\n // If Safari silently drops or corrupts dispatches beyond a limit, only this test catches it.\n // Uses a simple chain: buf[i] += 1.0 for 300 dispatches → should equal 300.0.\n try {\n const manyShader = `\n @group(0) @binding(0) var<storage, read_write> data: array<f32>;\n @compute @workgroup_size(64)\n fn main(@builtin(global_invocation_id) gid: vec3u) {\n if (gid.x < 4u) { data[gid.x] += 1.0; }\n }\n `;\n const DISPATCH_COUNT = 300;\n const manyBuf = createStorageBuffer(ctx, \"dP_data\", 16, new Float32Array(4));\n const manyReadBuf = createReadbackBuffer(ctx, \"dP_read\", 16);\n const manyPipeline = getOrCreatePipeline(ctx, \"dP\", manyShader, \"main\");\n const manyBg = createBindGroup(ctx, manyPipeline, [{ buffer: manyBuf }], \"dP_bg\");\n\n const enc = ctx.device.createCommandEncoder();\n const cpass = enc.beginComputePass();\n for (let d = 0; d < DISPATCH_COUNT; d++) {\n cpass.setPipeline(manyPipeline);\n cpass.setBindGroup(0, manyBg);\n cpass.dispatchWorkgroups(1);\n }\n cpass.end();\n enc.copyBufferToBuffer(manyBuf, 0, manyReadBuf, 0, 16);\n ctx.device.queue.submit([enc.finish()]);\n if (ctx.device.queue.onSubmittedWorkDone) {\n await ctx.device.queue.onSubmittedWorkDone();\n }\n await manyReadBuf.mapAsync(MAP_MODE_READ, 0, 16);\n const manyMapped = manyReadBuf.getMappedRange(0, 16);\n const manyResult = new Float32Array(manyMapped.slice(0));\n manyReadBuf.unmap();\n\n const allCorrect = manyResult.every((v) => Math.abs(v - DISPATCH_COUNT) < 0.1);\n details.push(\n allCorrect\n ? `✓ P: ${DISPATCH_COUNT} dispatches OK (${manyResult[0]})`\n : `✗ P: ${DISPATCH_COUNT} dispatches FAILED (got [${Array.from(manyResult).join(\",\")}], expected ${DISPATCH_COUNT})`,\n );\n manyBuf.destroy();\n manyReadBuf.destroy();\n } catch (e) {\n details.push(`✗ P error: ${e instanceof Error ? e.message : String(e)}`);\n }\n\n // Test Q: Same shader, different bind groups (Metal argument buffer caching bug)\n // This is the EXACT pattern that triggers the Safari/Metal bug:\n // Two dispatches use the same shader code but different bind groups pointing\n // to different output buffers. If Metal caches argument buffers per compiled\n // function, the second dispatch will write to the wrong buffer.\n try {\n const qShader = `\n @group(0) @binding(0) var<storage, read> src: array<f32>;\n @group(0) @binding(1) var<storage, read_write> dst: array<f32>;\n @compute @workgroup_size(4)\n fn main(@builtin(global_invocation_id) gid: vec3u) {\n if (gid.x < 4u) { dst[gid.x] = src[gid.x] * 2.0; }\n }\n `;\n const qSrc = createStorageBuffer(ctx, \"dQ_src\", 16, new Float32Array([1, 2, 3, 4]));\n const qDst1 = createStorageBuffer(ctx, \"dQ_dst1\", 16, new Float32Array(4)); // zeros\n const qDst2 = createStorageBuffer(ctx, \"dQ_dst2\", 16, new Float32Array(4)); // zeros\n const qRead1 = createReadbackBuffer(ctx, \"dQ_read1\", 16);\n const qRead2 = createReadbackBuffer(ctx, \"dQ_read2\", 16);\n\n // Same shader, same pipeline — different bind groups with different output buffers\n const qPipeline = getOrCreatePipeline(ctx, \"dQ\", qShader, \"main\");\n const qBg1 = createBindGroup(ctx, qPipeline, [{ buffer: qSrc }, { buffer: qDst1 }], \"dQ_bg1\");\n const qBg2 = createBindGroup(ctx, qPipeline, [{ buffer: qSrc }, { buffer: qDst2 }], \"dQ_bg2\");\n\n const qEnc = ctx.device.createCommandEncoder();\n const qPass = qEnc.beginComputePass();\n qPass.setPipeline(qPipeline);\n qPass.setBindGroup(0, qBg1);\n qPass.dispatchWorkgroups(1); // dst1 should be [2,4,6,8]\n qPass.setPipeline(qPipeline); // SAME pipeline\n qPass.setBindGroup(0, qBg2); // DIFFERENT bind group → different output buffer\n qPass.dispatchWorkgroups(1); // dst2 should be [2,4,6,8]\n qPass.end();\n qEnc.copyBufferToBuffer(qDst1, 0, qRead1, 0, 16);\n qEnc.copyBufferToBuffer(qDst2, 0, qRead2, 0, 16);\n ctx.device.queue.submit([qEnc.finish()]);\n if (ctx.device.queue.onSubmittedWorkDone) {\n await ctx.device.queue.onSubmittedWorkDone();\n }\n await qRead1.mapAsync(MAP_MODE_READ, 0, 16);\n const qR1 = new Float32Array(qRead1.getMappedRange(0, 16).slice(0));\n qRead1.unmap();\n await qRead2.mapAsync(MAP_MODE_READ, 0, 16);\n const qR2 = new Float32Array(qRead2.getMappedRange(0, 16).slice(0));\n qRead2.unmap();\n\n const qExpected = [2, 4, 6, 8];\n const dst1Ok = qExpected.every((v, i) => Math.abs(qR1[i] - v) < 0.01);\n const dst2Ok = qExpected.every((v, i) => Math.abs(qR2[i] - v) < 0.01);\n details.push(\n dst1Ok && dst2Ok\n ? `✓ Q: same-shader diff-bindgroup OK (dst1=[${Array.from(qR1)}] dst2=[${Array.from(qR2)}])`\n : `✗ Q: same-shader diff-bindgroup FAILED (dst1=[${Array.from(qR1)}] dst2=[${Array.from(qR2)}], expected [${qExpected}])`,\n );\n qSrc.destroy();\n qDst1.destroy();\n qDst2.destroy();\n qRead1.destroy();\n qRead2.destroy();\n } catch (e) {\n details.push(`✗ Q error: ${e instanceof Error ? e.message : String(e)}`);\n }\n\n return { bufferIntegrity, computeWorks, sharedMemoryWorks, details };\n}\n","/**\n * Kernel registry — maps IR op types to WGSL shader code and dispatch logic.\n *\n * Each entry defines:\n * - The WGSL source (embedded as string constants for browser compatibility)\n * - The compute entry point name\n * - How to calculate workgroup dispatch dimensions\n * - How to build the uniform buffer matching the WGSL Params struct\n * - The binding layout (storage-read, storage-read-write, uniform) in order\n *\n * Only ops with WGSL implementations are included. Stubbed ops (MoERouter,\n * ExpertMatMul, Conv2d, AvgPool2d, CrossAttention, Gather, Reshape,\n * Transpose, Concat) are omitted.\n */\n\nimport type { OpNode, OpType } from \"../ir.js\";\n\n// ── KernelSpec interface ────────────────────────────────────────────────\n\n/** Runtime context passed from the executor to kernels at dispatch time. */\nexport interface RuntimeContext {\n /** Current sequence position (for autoregressive decode). */\n seqPos: number;\n /**\n * Query-grid base offset for windowed Attention dispatches. When set, the\n * Attention kernel treats workgroup row r as query position r + qOffset, so a\n * caller can process a sub-range of query rows in one dispatch. Only the WebKit\n * vision encoder sets it (to chunk the O(N²) ViT attention); defaults to 0.\n */\n qOffset?: number;\n}\n\nexport interface KernelSpec {\n /** WGSL shader source code */\n shaderCode: string;\n /** Compute entry point name */\n entryPoint: string;\n /** Calculate dispatch workgroup counts from op attributes and resolved shapes */\n getDispatchSize: (\n op: OpNode,\n resolvedShapes: Record<string, number[]>,\n context?: RuntimeContext,\n ) => [number, number, number];\n /** Build the uniform buffer data for this op */\n buildParams: (\n op: OpNode,\n resolvedShapes: Record<string, number[]>,\n context?: RuntimeContext,\n ) => ArrayBuffer;\n /** List of binding entries (storage vs uniform) in order */\n bindings: Array<{\n type: \"storage-read\" | \"storage-read-write\" | \"uniform\";\n }>;\n}\n\n// ── Helpers ─────────────────────────────────────────────────────────────\n\n/** Ceiling division: ceil(a / b) */\nfunction cdiv(a: number, b: number): number {\n return Math.ceil(a / b);\n}\n\n/** Reinterpret an f32 value as its u32 bit pattern. */\nfunction f32BitsToU32(value: number): number {\n const f = new Float32Array(1);\n f[0] = value;\n return new Uint32Array(f.buffer)[0];\n}\n\n/**\n * Create an ArrayBuffer from an array of u32 values.\n * Each value is written at 4-byte intervals using DataView.\n */\nfunction buildUniformBuffer(values: number[]): ArrayBuffer {\n const buf = new ArrayBuffer(values.length * 4);\n const view = new DataView(buf);\n for (let i = 0; i < values.length; i++) {\n view.setUint32(i * 4, values[i], true /* littleEndian */);\n }\n return buf;\n}\n\n/**\n * Resolve the first dimension of a tensor referenced by an attribute.\n * Falls back to the direct attribute value if no tensor reference is set.\n */\nfunction _resolveFirstDim(\n op: OpNode,\n attrName: string,\n resolvedShapes: Record<string, number[]>,\n): number {\n const tensorRef = op.attributes[`${attrName}_tensor`] as string | undefined;\n if (tensorRef && resolvedShapes[tensorRef]) {\n return resolvedShapes[tensorRef][0];\n }\n return op.attributes[attrName] as number;\n}\n\n/**\n * Resolve total element count of a tensor referenced by an attribute.\n */\nfunction _resolveTotalElements(\n _op: OpNode,\n tensorRef: string,\n resolvedShapes: Record<string, number[]>,\n): number {\n const shape = resolvedShapes[tensorRef];\n if (!shape) return 0;\n return shape.reduce((a, b) => a * b, 1);\n}\n\n/**\n * Resolve a dynamic seq_len from tensor references, output shape, or static attribute.\n *\n * Tries (in order):\n * 1. Explicit tensor reference via `seq_len_tensor` attribute — computes total / hidden\n * 2. First output tensor's first dimension (for 2D+ outputs)\n * 3. Static `seq_len` attribute\n */\nfunction resolveSeqLen(\n op: OpNode,\n resolvedShapes: Record<string, number[]>,\n hidden: number,\n): number {\n // Try explicit tensor reference first\n const ref = op.attributes.seq_len_tensor as string | undefined;\n if (ref && resolvedShapes[ref]) {\n const shape = resolvedShapes[ref];\n const total = shape.reduce((a: number, b: number) => a * b, 1);\n return total / hidden;\n }\n // Fall back to output tensor shape\n const outShape = resolvedShapes[op.outputs[0]];\n if (outShape && outShape.length >= 2) {\n return outShape[0];\n }\n return op.attributes.seq_len as number;\n}\n\n// ── Embedded WGSL shader sources ────────────────────────────────────────\n\nconst WGSL_EMBEDDING = `\\\n// Embedding lookup: gather rows from weight matrix by token ID.\n\nstruct Params {\n seq_len: u32,\n hidden_size: u32,\n}\n\n@group(0) @binding(0) var<storage, read> input_ids: array<u32>;\n@group(0) @binding(1) var<storage, read> weight: array<f32>;\n@group(0) @binding(2) var<storage, read_write> output: array<f32>;\n@group(0) @binding(3) var<storage, read> params: Params;\n\n@compute @workgroup_size(256)\nfn main(@builtin(global_invocation_id) gid: vec3u) {\n let idx = gid.x;\n let total = params.seq_len * params.hidden_size;\n if (idx >= total) { return; }\n\n let token_pos = idx / params.hidden_size;\n let dim = idx % params.hidden_size;\n\n let token_id = input_ids[token_pos];\n let weight_idx = token_id * params.hidden_size + dim;\n\n output[idx] = weight[weight_idx];\n}\n`;\n\nconst WGSL_EMBEDDING_INT4 = `\\\n// INT4 embedding lookup: dequantize rows from packed weight matrix by token ID.\n// Weight is packed as 8 nibbles per u32, with per-group scales and zeros.\n// Dequant formula: output = (nibble - zero) * scale\n//\n// Bindings:\n// @group(0) @binding(0) input_ids: array<u32> — token IDs, length T\n// @group(0) @binding(1) weight_q: array<u32> — packed INT4 [vocab_size * hidden_size / 8]\n// @group(0) @binding(2) scales: array<f32> — per-group scales\n// @group(0) @binding(3) zeros: array<f32> — per-group zeros\n// @group(0) @binding(4) output: array<f32> — output [T, hidden_size]\n// @group(0) @binding(5) params: { seq_len, hidden_size, group_size }\n\nstruct Params {\n seq_len: u32,\n hidden_size: u32,\n group_size: u32,\n _pad: u32,\n}\n\n@group(0) @binding(0) var<storage, read> input_ids: array<u32>;\n@group(0) @binding(1) var<storage, read> weight_q: array<u32>;\n@group(0) @binding(2) var<storage, read> scales: array<f32>;\n@group(0) @binding(3) var<storage, read> zeros: array<f32>;\n@group(0) @binding(4) var<storage, read_write> output: array<f32>;\n@group(0) @binding(5) var<storage, read> params: Params;\n\n@compute @workgroup_size(256)\nfn main(@builtin(global_invocation_id) gid: vec3u) {\n let idx = gid.x;\n let total = params.seq_len * params.hidden_size;\n if (idx >= total) { return; }\n\n let token_pos = idx / params.hidden_size;\n let dim = idx % params.hidden_size;\n\n let token_id = input_ids[token_pos];\n\n // Flat index into the [vocab_size, hidden_size] embedding table\n let flat_idx = token_id * params.hidden_size + dim;\n let packed_idx = flat_idx / 8u;\n let nibble_pos = flat_idx % 8u;\n let packed = weight_q[packed_idx];\n let shift = nibble_pos * 4u;\n let raw_val = f32((packed >> shift) & 0xFu);\n\n let group_idx = flat_idx / params.group_size;\n let scale = scales[group_idx];\n let zero = zeros[group_idx];\n\n output[idx] = (raw_val - zero) * scale;\n}\n`;\n\nconst WGSL_MATMUL = `\\\n// Tiled matrix multiply with 4x2 register blocking.\n// C[M,N] = A[M,K] * B^T[N,K]; B stored in [N,K] layout (HF [out,in]).\n//\n// A 16x16 workgroup computes a 64-row x 32-col output tile: each thread owns a\n// 4x2 block of outputs (rows {r,r+16,r+32,r+48} x cols {c,c+16}). Each B column\n// loaded into shared memory is reused for 4 output rows, cutting B (weight)\n// global traffic 4x vs the scalar version — the dominant cost of the wide f32\n// ViT matmuls. A/B tile fills use vec4 loads (K is always %4==0).\n\nconst MT: u32 = 64u; // output rows per workgroup\nconst NT: u32 = 32u; // output cols per workgroup\nconst KT: u32 = 16u; // K-dimension tile depth\nconst KT4: u32 = 4u; // KT / 4 (vec4 columns along K)\n\nstruct Params {\n M: u32,\n K: u32,\n N: u32,\n}\n\n@group(0) @binding(0) var<storage, read> A: array<vec4f>;\n@group(0) @binding(1) var<storage, read> B: array<vec4f>;\n@group(0) @binding(2) var<storage, read_write> C: array<f32>;\n@group(0) @binding(3) var<storage, read> params: Params;\n\n// tileA: [MT rows x KT] = 1024, tileB: [KT x NT cols] = 512\nvar<workgroup> tileA: array<f32, 1024>;\nvar<workgroup> tileB: array<f32, 512>;\n\n@compute @workgroup_size(16, 16)\nfn main(\n @builtin(local_invocation_id) lid: vec3u,\n @builtin(workgroup_id) wid: vec3u,\n) {\n let lr = lid.y;\n let lc = lid.x;\n let mBase = wid.y * MT;\n let nBase = wid.x * NT;\n // This thread's 4x2 output block: rows {lr, lr+16, lr+32, lr+48}, cols {lc, lc+16}.\n let row0 = mBase + lr;\n let col0 = nBase + lc;\n\n var acc: array<f32, 8>; // [4 rows x 2 cols]\n for (var i = 0u; i < 8u; i = i + 1u) { acc[i] = 0.0; }\n\n let numTiles = (params.K + KT - 1u) / KT;\n let tid = lr * 16u + lc; // 0..255 linear thread id\n let K4 = params.K / 4u; // K in vec4 units\n\n for (var t: u32 = 0u; t < numTiles; t = t + 1u) {\n let kv0 = t * KT4; // first vec4 column of this K-tile\n // Load A tile [MT x KT] = 64 rows * 4 vec4 = 256 vec4 loads (1 per thread).\n {\n let ar = tid / KT4; // 0..63 row within tile\n let av4 = tid % KT4; // 0..3 vec4-col within tile (along K)\n let gRow = mBase + ar;\n var v = vec4f(0.0, 0.0, 0.0, 0.0);\n if (gRow < params.M && kv0 + av4 < K4) {\n v = A[gRow * K4 + kv0 + av4];\n }\n let base = ar * KT + av4 * 4u;\n tileA[base] = v.x;\n tileA[base + 1u] = v.y;\n tileA[base + 2u] = v.z;\n tileA[base + 3u] = v.w;\n }\n // Load B tile [KT x NT cols]: 32 cols * 4 vec4 = 128 vec4 loads (threads 0..127).\n if (tid < 128u) {\n let bc = tid / KT4; // 0..31 col within tile\n let bv4 = tid % KT4; // 0..3 vec4-col within tile (along K)\n let gCol = nBase + bc;\n var v = vec4f(0.0, 0.0, 0.0, 0.0);\n if (gCol < params.N && kv0 + bv4 < K4) {\n v = B[gCol * K4 + kv0 + bv4];\n }\n let krow = bv4 * 4u;\n tileB[(krow + 0u) * NT + bc] = v.x;\n tileB[(krow + 1u) * NT + bc] = v.y;\n tileB[(krow + 2u) * NT + bc] = v.z;\n tileB[(krow + 3u) * NT + bc] = v.w;\n }\n\n workgroupBarrier();\n\n for (var k: u32 = 0u; k < KT; k = k + 1u) {\n let b0 = tileB[k * NT + lc];\n let b1 = tileB[k * NT + lc + 16u];\n let a0 = tileA[lr * KT + k];\n let a1 = tileA[(lr + 16u) * KT + k];\n let a2 = tileA[(lr + 32u) * KT + k];\n let a3 = tileA[(lr + 48u) * KT + k];\n acc[0] += a0 * b0;\n acc[1] += a0 * b1;\n acc[2] += a1 * b0;\n acc[3] += a1 * b1;\n acc[4] += a2 * b0;\n acc[5] += a2 * b1;\n acc[6] += a3 * b0;\n acc[7] += a3 * b1;\n }\n\n workgroupBarrier();\n }\n\n for (var rr = 0u; rr < 4u; rr = rr + 1u) {\n let gRow = row0 + rr * 16u;\n if (gRow < params.M) {\n if (col0 < params.N) {\n C[gRow * params.N + col0] = acc[rr * 2u];\n }\n if (col0 + 16u < params.N) {\n C[gRow * params.N + col0 + 16u] = acc[rr * 2u + 1u];\n }\n }\n }\n}\n`;\n\n// Fused MatMul + row-broadcast bias: C[r,c] = A[r,:]·B[c,:] + bias[c].\n// Identical tiling to WGSL_MATMUL (4x2 register blocking, vec4 loads) but adds\n// the bias at store time, eliminating a full wide read+write of the matmul\n// output that a separate AddBias dispatch would incur — one fewer round-trip per\n// ViT linear layer (qkv/proj/fc1/fc2/patch_embed/merger fc1/fc2).\nconst WGSL_MATMUL_BIAS = `\\\nconst MT: u32 = 64u;\nconst NT: u32 = 32u;\nconst KT: u32 = 16u;\nconst KT4: u32 = 4u;\n\nstruct Params {\n M: u32,\n K: u32,\n N: u32,\n}\n\n@group(0) @binding(0) var<storage, read> A: array<vec4f>;\n@group(0) @binding(1) var<storage, read> B: array<vec4f>;\n@group(0) @binding(2) var<storage, read> bias: array<f32>;\n@group(0) @binding(3) var<storage, read_write> C: array<f32>;\n@group(0) @binding(4) var<storage, read> params: Params;\n\nvar<workgroup> tileA: array<f32, 1024>;\nvar<workgroup> tileB: array<f32, 512>;\n\n@compute @workgroup_size(16, 16)\nfn main(\n @builtin(local_invocation_id) lid: vec3u,\n @builtin(workgroup_id) wid: vec3u,\n) {\n let lr = lid.y;\n let lc = lid.x;\n let mBase = wid.y * MT;\n let nBase = wid.x * NT;\n let row0 = mBase + lr;\n let col0 = nBase + lc;\n\n var acc: array<f32, 8>;\n for (var i = 0u; i < 8u; i = i + 1u) { acc[i] = 0.0; }\n\n let numTiles = (params.K + KT - 1u) / KT;\n let tid = lr * 16u + lc;\n let K4 = params.K / 4u;\n\n for (var t: u32 = 0u; t < numTiles; t = t + 1u) {\n let kv0 = t * KT4;\n {\n let ar = tid / KT4;\n let av4 = tid % KT4;\n let gRow = mBase + ar;\n var v = vec4f(0.0, 0.0, 0.0, 0.0);\n if (gRow < params.M && kv0 + av4 < K4) {\n v = A[gRow * K4 + kv0 + av4];\n }\n let base = ar * KT + av4 * 4u;\n tileA[base] = v.x;\n tileA[base + 1u] = v.y;\n tileA[base + 2u] = v.z;\n tileA[base + 3u] = v.w;\n }\n if (tid < 128u) {\n let bc = tid / KT4;\n let bv4 = tid % KT4;\n let gCol = nBase + bc;\n var v = vec4f(0.0, 0.0, 0.0, 0.0);\n if (gCol < params.N && kv0 + bv4 < K4) {\n v = B[gCol * K4 + kv0 + bv4];\n }\n let krow = bv4 * 4u;\n tileB[(krow + 0u) * NT + bc] = v.x;\n tileB[(krow + 1u) * NT + bc] = v.y;\n tileB[(krow + 2u) * NT + bc] = v.z;\n tileB[(krow + 3u) * NT + bc] = v.w;\n }\n\n workgroupBarrier();\n\n for (var k: u32 = 0u; k < KT; k = k + 1u) {\n let b0 = tileB[k * NT + lc];\n let b1 = tileB[k * NT + lc + 16u];\n let a0 = tileA[lr * KT + k];\n let a1 = tileA[(lr + 16u) * KT + k];\n let a2 = tileA[(lr + 32u) * KT + k];\n let a3 = tileA[(lr + 48u) * KT + k];\n acc[0] += a0 * b0;\n acc[1] += a0 * b1;\n acc[2] += a1 * b0;\n acc[3] += a1 * b1;\n acc[4] += a2 * b0;\n acc[5] += a2 * b1;\n acc[6] += a3 * b0;\n acc[7] += a3 * b1;\n }\n\n workgroupBarrier();\n }\n\n var bias0 = 0.0;\n if (col0 < params.N) { bias0 = bias[col0]; }\n var bias1 = 0.0;\n if (col0 + 16u < params.N) { bias1 = bias[col0 + 16u]; }\n for (var rr = 0u; rr < 4u; rr = rr + 1u) {\n let gRow = row0 + rr * 16u;\n if (gRow < params.M) {\n if (col0 < params.N) {\n C[gRow * params.N + col0] = acc[rr * 2u] + bias0;\n }\n if (col0 + 16u < params.N) {\n C[gRow * params.N + col0 + 16u] = acc[rr * 2u + 1u] + bias1;\n }\n }\n }\n}\n`;\n\n// MIXED-precision MatMulBias: f16 weight + f16 shared tiles, but products are\n// summed in an f32 accumulator (acc += f32(a*b), with a*b done in f16). Keeps the\n// fast f16 MULTIPLY (the ~2% win measured in b4-r5) while accumulating in f32 to\n// stay inside the cos gate that full-f16 accumulation (r5) broke.\nconst WGSL_MATMUL_BIAS_F16MIX = `\\\nenable f16;\n\nconst MT: u32 = 64u;\nconst NT: u32 = 32u;\nconst KT: u32 = 16u;\nconst KT4: u32 = 4u;\n\nstruct Params {\n M: u32,\n K: u32,\n N: u32,\n}\n\n@group(0) @binding(0) var<storage, read> A: array<vec4f>;\n@group(0) @binding(1) var<storage, read> B: array<vec4<f16>>;\n@group(0) @binding(2) var<storage, read> bias: array<f32>;\n@group(0) @binding(3) var<storage, read_write> C: array<f32>;\n@group(0) @binding(4) var<storage, read> params: Params;\n\nvar<workgroup> tileA: array<f16, 1024>;\nvar<workgroup> tileB: array<f16, 512>;\n\n@compute @workgroup_size(16, 16)\nfn main(\n @builtin(local_invocation_id) lid: vec3u,\n @builtin(workgroup_id) wid: vec3u,\n) {\n let lr = lid.y;\n let lc = lid.x;\n let mBase = wid.y * MT;\n let nBase = wid.x * NT;\n let row0 = mBase + lr;\n let col0 = nBase + lc;\n\n var acc: array<f32, 8>;\n for (var i = 0u; i < 8u; i = i + 1u) { acc[i] = 0.0; }\n\n let numTiles = (params.K + KT - 1u) / KT;\n let tid = lr * 16u + lc;\n let K4 = params.K / 4u;\n\n for (var t: u32 = 0u; t < numTiles; t = t + 1u) {\n let kv0 = t * KT4;\n {\n let ar = tid / KT4;\n let av4 = tid % KT4;\n let gRow = mBase + ar;\n var v = vec4f(0.0, 0.0, 0.0, 0.0);\n if (gRow < params.M && kv0 + av4 < K4) {\n v = A[gRow * K4 + kv0 + av4];\n }\n let hv = vec4<f16>(v);\n let base = ar * KT + av4 * 4u;\n tileA[base] = hv.x;\n tileA[base + 1u] = hv.y;\n tileA[base + 2u] = hv.z;\n tileA[base + 3u] = hv.w;\n }\n if (tid < 128u) {\n let bc = tid / KT4;\n let bv4 = tid % KT4;\n let gCol = nBase + bc;\n var v = vec4<f16>(f16(0.0), f16(0.0), f16(0.0), f16(0.0));\n if (gCol < params.N && kv0 + bv4 < K4) {\n v = B[gCol * K4 + kv0 + bv4];\n }\n let krow = bv4 * 4u;\n tileB[(krow + 0u) * NT + bc] = v.x;\n tileB[(krow + 1u) * NT + bc] = v.y;\n tileB[(krow + 2u) * NT + bc] = v.z;\n tileB[(krow + 3u) * NT + bc] = v.w;\n }\n\n workgroupBarrier();\n\n for (var k: u32 = 0u; k < KT; k = k + 1u) {\n let b0 = tileB[k * NT + lc];\n let b1 = tileB[k * NT + lc + 16u];\n let a0 = tileA[lr * KT + k];\n let a1 = tileA[(lr + 16u) * KT + k];\n let a2 = tileA[(lr + 32u) * KT + k];\n let a3 = tileA[(lr + 48u) * KT + k];\n acc[0] += f32(a0 * b0);\n acc[1] += f32(a0 * b1);\n acc[2] += f32(a1 * b0);\n acc[3] += f32(a1 * b1);\n acc[4] += f32(a2 * b0);\n acc[5] += f32(a2 * b1);\n acc[6] += f32(a3 * b0);\n acc[7] += f32(a3 * b1);\n }\n\n workgroupBarrier();\n }\n\n var bias0 = 0.0;\n if (col0 < params.N) { bias0 = bias[col0]; }\n var bias1 = 0.0;\n if (col0 + 16u < params.N) { bias1 = bias[col0 + 16u]; }\n for (var rr = 0u; rr < 4u; rr = rr + 1u) {\n let gRow = row0 + rr * 16u;\n if (gRow < params.M) {\n if (col0 < params.N) {\n C[gRow * params.N + col0] = acc[rr * 2u] + bias0;\n }\n if (col0 + 16u < params.N) {\n C[gRow * params.N + col0 + 16u] = acc[rr * 2u + 1u] + bias1;\n }\n }\n }\n}\n`;\n\nconst WGSL_MATMUL_INT4 = `\\\n// Tiled INT4 dequantize + matrix multiply.\n// C[M,N] = A[M,K] * dequant(B_q[N,K])\n// B_q is packed nibbles in [N,K] layout (same as F32 MatMul convention).\n// 8 nibbles per u32, little-nibble-first.\n\nconst TILE: u32 = 16u;\n\nstruct Params {\n M: u32,\n K: u32,\n N: u32,\n group_size: u32,\n}\n\n@group(0) @binding(0) var<storage, read> A: array<f32>;\n@group(0) @binding(1) var<storage, read> B_q: array<u32>;\n@group(0) @binding(2) var<storage, read> scales: array<f32>;\n@group(0) @binding(3) var<storage, read> zeros: array<f32>;\n@group(0) @binding(4) var<storage, read_write> C: array<f32>;\n@group(0) @binding(5) var<storage, read> params: Params;\n\nvar<workgroup> tileA: array<f32, 256>;\nvar<workgroup> tileB: array<f32, 256>;\n\n@compute @workgroup_size(16, 16)\nfn main(\n @builtin(global_invocation_id) gid: vec3u,\n @builtin(local_invocation_id) lid: vec3u,\n) {\n let row = gid.y;\n let col = gid.x;\n let lr = lid.y;\n let lc = lid.x;\n\n var sum: f32 = 0.0;\n let numTiles = (params.K + TILE - 1u) / TILE;\n\n for (var t: u32 = 0u; t < numTiles; t = t + 1u) {\n // Load tile of A into shared memory\n let a_col = t * TILE + lc;\n if (row < params.M && a_col < params.K) {\n tileA[lr * TILE + lc] = A[row * params.K + a_col];\n } else {\n tileA[lr * TILE + lc] = 0.0;\n }\n\n // Load tile of B (INT4 dequantized) into shared memory\n // B is stored as [N,K] packed nibbles. We need B[col, t*TILE+lr].\n let b_k = t * TILE + lr;\n if (b_k < params.K && col < params.N) {\n let flat_idx = col * params.K + b_k;\n let packed_idx = flat_idx / 8u;\n let nibble_pos = flat_idx % 8u;\n let packed = B_q[packed_idx];\n let shift = nibble_pos * 4u;\n let raw_val = f32((packed >> shift) & 0xFu);\n\n let group_idx = flat_idx / params.group_size;\n let scale = scales[group_idx];\n let zero = zeros[group_idx];\n tileB[lr * TILE + lc] = (raw_val - zero) * scale;\n } else {\n tileB[lr * TILE + lc] = 0.0;\n }\n\n workgroupBarrier();\n\n for (var k: u32 = 0u; k < TILE; k = k + 1u) {\n sum += tileA[lr * TILE + k] * tileB[k * TILE + lc];\n }\n\n workgroupBarrier();\n }\n\n if (row < params.M && col < params.N) {\n C[row * params.N + col] = sum;\n }\n}\n`;\n\nconst WGSL_MATVEC = `\\\n// K-parallel matrix-vector multiply: C[N] = A[K] * B^T[N,K]\n// Optimized for M=1 decode on Apple Silicon (SIMD width 32).\n//\n// Design: 256 threads = 8 output columns × 32 K-threads per column.\n// Each group of 32 threads cooperates along K for one output element.\n// A reads go through L1 cache (no shared memory — maximizes occupancy).\n// B reads are coalesced: 32 adjacent threads read 32 adjacent f32 values\n// = 128 bytes = one full cache line per iteration.\n// Single-barrier reduction (thread 0 of each group sums 32 partials).\n\nconst N_TILE: u32 = 8u;\nconst K_THREADS: u32 = 32u;\n\nstruct Params {\n K: u32,\n N: u32,\n}\n\n@group(0) @binding(0) var<storage, read> A: array<f32>;\n@group(0) @binding(1) var<storage, read> B: array<f32>;\n@group(0) @binding(2) var<storage, read_write> C: array<f32>;\n@group(0) @binding(3) var<storage, read> params: Params;\n\nvar<workgroup> shared_sums: array<f32, 256>;\n\n@compute @workgroup_size(256)\nfn main(\n @builtin(local_invocation_id) lid: vec3u,\n @builtin(workgroup_id) wid: vec3u,\n) {\n let tid = lid.x;\n let n_idx = tid / K_THREADS;\n let k_tid = tid % K_THREADS;\n let col = wid.x * N_TILE + n_idx;\n let K = params.K;\n\n // K-parallel accumulation with vec4 dot products\n // A reads hit L1 cache (same addresses across all 16 output columns)\n var sum: f32 = 0.0;\n if (col < params.N) {\n let b_off = col * K;\n var k = k_tid * 4u;\n while (k + 3u < K) {\n let a_v = vec4f(A[k], A[k+1u], A[k+2u], A[k+3u]);\n let b_v = vec4f(B[b_off+k], B[b_off+k+1u], B[b_off+k+2u], B[b_off+k+3u]);\n sum += dot(a_v, b_v);\n k += K_THREADS * 4u;\n }\n }\n\n // Store partials and reduce\n shared_sums[tid] = sum;\n workgroupBarrier();\n\n // Thread 0 of each K-group sequentially sums 16 partials\n if (k_tid == 0u && col < params.N) {\n let base = n_idx * K_THREADS;\n var total: f32 = shared_sums[base];\n for (var i: u32 = 1u; i < K_THREADS; i++) {\n total += shared_sums[base + i];\n }\n C[col] = total;\n }\n}\n`;\n\n// ── Subgroups variant of WGSL_MATVEC (desktop-only fast path) ──\n//\n// Identical math and memory-access pattern to WGSL_MATVEC, but the per-column\n// partial-sum reduction is done with subgroupAdd() instead of a shared-memory\n// write + barrier + thread-0 sequential sum. Requires the \"subgroups\" device\n// feature (Chrome 134+, Safari 26+) and \"enable subgroups;\".\n//\n// Layout: 256 threads = 8 output columns × 32 K-threads. The kernel is hand-\n// tuned for SIMD width 32 (Apple Silicon / desktop GPUs), where each group of\n// 32 contiguous K-threads forms exactly one subgroup. We do NOT rely on the\n// (unspecified) lane↔invocation mapping for correctness across arbitrary\n// subgroup sizes: subgroupAdd() reduces over whatever subgroup the lane belongs\n// to, then the subgroup leaders deposit their partials into a tiny shared array\n// (one slot per column), and the column leader combines them. When subgroup\n// size == K_THREADS (the common desktop case) there is exactly one subgroup per\n// column and the shared-memory combine collapses to a single store + load.\n//\n// This variant is selected only on non-WebKit devices that expose subgroups\n// (see executor.ts) — the portable WGSL_MATVEC remains the universal fallback.\nconst WGSL_MATVEC_SUBGROUPS = `\\\nenable subgroups;\n\nconst N_TILE: u32 = 8u;\nconst K_THREADS: u32 = 32u;\n\nstruct Params {\n K: u32,\n N: u32,\n}\n\n@group(0) @binding(0) var<storage, read> A: array<f32>;\n@group(0) @binding(1) var<storage, read> B: array<f32>;\n@group(0) @binding(2) var<storage, read_write> C: array<f32>;\n@group(0) @binding(3) var<storage, read> params: Params;\n\n// One partial slot per column per possible subgroup-within-column. K_THREADS=32\n// so at most 32 subgroups per column in the pathological size-1 case; size\n// 8*32 = 256 covers it. In practice (size 32) only [n_idx*32] is used.\nvar<workgroup> col_partials: array<f32, 256>;\n\n@compute @workgroup_size(256)\nfn main(\n @builtin(local_invocation_id) lid: vec3u,\n @builtin(workgroup_id) wid: vec3u,\n) {\n let tid = lid.x;\n let n_idx = tid / K_THREADS;\n let k_tid = tid % K_THREADS;\n let col = wid.x * N_TILE + n_idx;\n let K = params.K;\n\n var sum: f32 = 0.0;\n if (col < params.N) {\n let b_off = col * K;\n var k = k_tid * 4u;\n while (k + 3u < K) {\n let a_v = vec4f(A[k], A[k+1u], A[k+2u], A[k+3u]);\n let b_v = vec4f(B[b_off+k], B[b_off+k+1u], B[b_off+k+2u], B[b_off+k+3u]);\n sum += dot(a_v, b_v);\n k += K_THREADS * 4u;\n }\n }\n\n // Reduce within the subgroup. Each lane gets the sum over its subgroup.\n let sg_sum = subgroupAdd(sum);\n\n // Subgroup leaders deposit their partial into a per-column slot. Slot index is\n // the leader's k_tid (its offset within the column), guaranteeing distinct\n // slots per column even when a column spans multiple subgroups.\n col_partials[tid] = 0.0;\n workgroupBarrier();\n if (subgroupElect()) {\n col_partials[n_idx * K_THREADS + k_tid] = sg_sum;\n }\n workgroupBarrier();\n\n // Column leader sums the (few) deposited subgroup partials for its column.\n if (k_tid == 0u && col < params.N) {\n let base = n_idx * K_THREADS;\n var total: f32 = 0.0;\n for (var i: u32 = 0u; i < K_THREADS; i++) {\n total += col_partials[base + i];\n }\n C[col] = total;\n }\n}\n`;\n\n// ── Subgroups variant of WGSL_MATVEC_INT4 (desktop-only fast path) ──\n//\n// Same approach as WGSL_MATVEC_SUBGROUPS applied to the INT4 dequant matvec:\n// subgroupAdd() replaces the shared-memory tree reduction. Layout is widened to\n// 256 threads = 8 columns × 32 K-threads (vs the portable kernel's 128/16) so\n// each column maps to exactly one 32-wide subgroup on desktop GPUs. N_TILE stays\n// 8 so getDispatchSize is unchanged. Selected only on non-WebKit + subgroups.\nconst WGSL_MATVEC_INT4_SUBGROUPS = `\\\nenable subgroups;\n\nconst N_TILE: u32 = 8u;\nconst K_THREADS: u32 = 32u;\n\nstruct Params {\n K: u32,\n N: u32,\n group_size: u32,\n _pad: u32,\n}\n\n@group(0) @binding(0) var<storage, read> A: array<vec4f>;\n@group(0) @binding(1) var<storage, read> B_q: array<vec4u>;\n@group(0) @binding(2) var<storage, read> scales: array<f32>;\n@group(0) @binding(3) var<storage, read> zeros: array<f32>;\n@group(0) @binding(4) var<storage, read_write> C: array<f32>;\n@group(0) @binding(5) var<storage, read> params: Params;\n\nvar<workgroup> col_partials: array<f32, 256>;\n\n@compute @workgroup_size(256)\nfn main(\n @builtin(local_invocation_id) lid: vec3u,\n @builtin(workgroup_id) wid: vec3u,\n) {\n let tid = lid.x;\n let n_idx = tid / K_THREADS;\n let k_tid = tid % K_THREADS;\n let col = wid.x * N_TILE + n_idx;\n let K = params.K;\n let k_vec = K / 32u;\n let groups_per_col = K / params.group_size;\n let packed_per_group = params.group_size / 8u;\n\n var sum: f32 = 0.0;\n if (col < params.N) {\n let b_off_v = col * k_vec;\n let col_g_base = col * groups_per_col;\n\n var v = k_tid;\n while (v < k_vec) {\n let bq = B_q[b_off_v + v];\n let g = (v * 4u) / packed_per_group;\n let scale = scales[col_g_base + g];\n let zero = zeros[col_g_base + g];\n let a_base = v * 8u;\n\n for (var j: u32 = 0u; j < 4u; j++) {\n let packed = bq[j];\n let n0 = f32(packed & 0xFu);\n let n1 = f32((packed >> 4u) & 0xFu);\n let n2 = f32((packed >> 8u) & 0xFu);\n let n3 = f32((packed >> 12u) & 0xFu);\n let n4 = f32((packed >> 16u) & 0xFu);\n let n5 = f32((packed >> 20u) & 0xFu);\n let n6 = f32((packed >> 24u) & 0xFu);\n let n7 = f32((packed >> 28u) & 0xFu);\n\n let b0 = vec4f(n0 - zero, n1 - zero, n2 - zero, n3 - zero) * scale;\n let b1 = vec4f(n4 - zero, n5 - zero, n6 - zero, n7 - zero) * scale;\n\n sum += dot(A[a_base + j * 2u], b0) + dot(A[a_base + j * 2u + 1u], b1);\n }\n\n v += K_THREADS;\n }\n }\n\n let sg_sum = subgroupAdd(sum);\n\n col_partials[tid] = 0.0;\n workgroupBarrier();\n if (subgroupElect()) {\n col_partials[n_idx * K_THREADS + k_tid] = sg_sum;\n }\n workgroupBarrier();\n\n if (k_tid == 0u && col < params.N) {\n let base = n_idx * K_THREADS;\n var total: f32 = 0.0;\n for (var i: u32 = 0u; i < K_THREADS; i++) {\n total += col_partials[base + i];\n }\n C[col] = total;\n }\n}\n`;\n\nconst WGSL_MATVEC_INT4 = `\\\n// K-parallel INT4 matrix-vector multiply: C[N] = A[K] * dequant(B_q[N,K])\n// Optimized for M=1 decode on Apple Silicon (SIMD width 32).\n//\n// CONSTRAINT: N_TILE must equal workgroup_size / K_THREADS\n// Design: 256 threads = 8 output columns × 32 K-threads per column.\n\nconst N_TILE: u32 = 16u;\nconst K_THREADS: u32 = 16u;\n\nstruct Params {\n K: u32,\n N: u32,\n group_size: u32,\n _pad: u32,\n}\n\n@group(0) @binding(0) var<storage, read> A: array<vec4f>;\n@group(0) @binding(1) var<storage, read> B_q: array<vec4u>;\n@group(0) @binding(2) var<storage, read> scales: array<f32>;\n@group(0) @binding(3) var<storage, read> zeros: array<f32>;\n@group(0) @binding(4) var<storage, read_write> C: array<f32>;\n@group(0) @binding(5) var<storage, read> params: Params;\n\nvar<workgroup> shared_sums: array<f32, 256>;\n\n@compute @workgroup_size(256)\nfn main(\n @builtin(local_invocation_id) lid: vec3u,\n @builtin(workgroup_id) wid: vec3u,\n) {\n let tid = lid.x;\n let n_idx = tid / K_THREADS;\n let k_tid = tid % K_THREADS;\n let col = wid.x * N_TILE + n_idx;\n let K = params.K;\n let k_vec = K / 32u;\n let groups_per_col = K / params.group_size;\n let packed_per_group = params.group_size / 8u;\n\n // K-parallel accumulation with vec4 loads: each thread reads one vec4u\n // (4 packed u32s = 32 INT4 weights) per iteration. group_size (128) spans\n // 16 packed u32s, so a vec4u never straddles a quantization group — one\n // scale/zero load per iteration instead of four.\n // A reads hit L1 cache (same addresses across all 8 output columns in workgroup)\n var sum: f32 = 0.0;\n if (col < params.N) {\n let b_off_v = col * k_vec;\n let col_g_base = col * groups_per_col;\n\n var v = k_tid;\n while (v < k_vec) {\n let bq = B_q[b_off_v + v];\n let g = (v * 4u) / packed_per_group;\n let scale = scales[col_g_base + g];\n let zero = zeros[col_g_base + g];\n let a_base = v * 8u;\n\n for (var j: u32 = 0u; j < 4u; j++) {\n let packed = bq[j];\n // Manual nibble extraction (avoids unpack4xU8 — broken on Safari/WebKit)\n let n0 = f32(packed & 0xFu);\n let n1 = f32((packed >> 4u) & 0xFu);\n let n2 = f32((packed >> 8u) & 0xFu);\n let n3 = f32((packed >> 12u) & 0xFu);\n let n4 = f32((packed >> 16u) & 0xFu);\n let n5 = f32((packed >> 20u) & 0xFu);\n let n6 = f32((packed >> 24u) & 0xFu);\n let n7 = f32((packed >> 28u) & 0xFu);\n\n let b0 = vec4f(n0 - zero, n1 - zero, n2 - zero, n3 - zero) * scale;\n let b1 = vec4f(n4 - zero, n5 - zero, n6 - zero, n7 - zero) * scale;\n\n sum += dot(A[a_base + j * 2u], b0) + dot(A[a_base + j * 2u + 1u], b1);\n }\n\n v += K_THREADS;\n }\n }\n\n // Store partials and reduce\n shared_sums[tid] = sum;\n workgroupBarrier();\n\n if (k_tid == 0u && col < params.N) {\n let base = n_idx * K_THREADS;\n var total: f32 = shared_sums[base];\n for (var i: u32 = 1u; i < K_THREADS; i++) {\n total += shared_sums[base + i];\n }\n C[col] = total;\n }\n}\n`;\n\n// ── Gated MatVecInt4 (fused attn-gate + INT4 o_proj, M=1 decode) ──\n//\n// out[n] = sum_k (attn[k] * sigmoid(gate[k])) * dequant(W[n,k])\n// Folds the SigmoidGate (attn_out * sigmoid(gate)) into the o_proj input so the\n// gate elementwise + o_proj run in ONE dispatch. Same INT4 dequant and\n// K-parallel reduction as WGSL_MATVEC_INT4 — only the A vector is built from two\n// inputs with an inline sigmoid. Numerically identical to SigmoidGate→MatVecInt4.\nconst WGSL_GATED_MATVEC_INT4 = `\\\nconst N_TILE: u32 = 8u;\nconst K_THREADS: u32 = 16u;\n\nstruct Params {\n K: u32,\n N: u32,\n group_size: u32,\n _pad: u32,\n}\n\n@group(0) @binding(0) var<storage, read> attn: array<vec4f>;\n@group(0) @binding(1) var<storage, read> gate: array<vec4f>;\n@group(0) @binding(2) var<storage, read> B_q: array<vec4u>;\n@group(0) @binding(3) var<storage, read> scales: array<f32>;\n@group(0) @binding(4) var<storage, read> zeros: array<f32>;\n@group(0) @binding(5) var<storage, read_write> C: array<f32>;\n@group(0) @binding(6) var<storage, read> params: Params;\n\nvar<workgroup> shared_sums: array<f32, 128>;\n\nfn gated(i: u32) -> vec4f {\n let a = attn[i];\n let g = gate[i];\n let s = vec4f(1.0) / (vec4f(1.0) + exp(max(-g, vec4f(-80.0))));\n return a * s;\n}\n\n@compute @workgroup_size(128)\nfn main(\n @builtin(local_invocation_id) lid: vec3u,\n @builtin(workgroup_id) wid: vec3u,\n) {\n let tid = lid.x;\n let n_idx = tid / K_THREADS;\n let k_tid = tid % K_THREADS;\n let col = wid.x * N_TILE + n_idx;\n let K = params.K;\n let k_vec = K / 32u;\n let groups_per_col = K / params.group_size;\n let packed_per_group = params.group_size / 8u;\n\n var sum: f32 = 0.0;\n if (col < params.N) {\n let b_off_v = col * k_vec;\n let col_g_base = col * groups_per_col;\n\n var v = k_tid;\n while (v < k_vec) {\n let bq = B_q[b_off_v + v];\n let g = (v * 4u) / packed_per_group;\n let scale = scales[col_g_base + g];\n let zero = zeros[col_g_base + g];\n let a_base = v * 8u;\n\n for (var j: u32 = 0u; j < 4u; j++) {\n let packed = bq[j];\n let n0 = f32(packed & 0xFu);\n let n1 = f32((packed >> 4u) & 0xFu);\n let n2 = f32((packed >> 8u) & 0xFu);\n let n3 = f32((packed >> 12u) & 0xFu);\n let n4 = f32((packed >> 16u) & 0xFu);\n let n5 = f32((packed >> 20u) & 0xFu);\n let n6 = f32((packed >> 24u) & 0xFu);\n let n7 = f32((packed >> 28u) & 0xFu);\n\n let b0 = vec4f(n0 - zero, n1 - zero, n2 - zero, n3 - zero) * scale;\n let b1 = vec4f(n4 - zero, n5 - zero, n6 - zero, n7 - zero) * scale;\n\n sum += dot(gated(a_base + j * 2u), b0) + dot(gated(a_base + j * 2u + 1u), b1);\n }\n\n v += K_THREADS;\n }\n }\n\n shared_sums[tid] = sum;\n workgroupBarrier();\n\n if (k_tid == 0u && col < params.N) {\n let base = n_idx * K_THREADS;\n var total: f32 = shared_sums[base];\n for (var i: u32 = 1u; i < K_THREADS; i++) {\n total += shared_sums[base + i];\n }\n C[col] = total;\n }\n}\n`;\n\n// ── SwiGLU-gated MatVecInt4 (fused SwiGLU + INT4 projection, M=1 decode) ──\n//\n// out[n] = sum_k (silu(gate[k]) * up[k]) * dequant(W[n,k])\n// Folds a SwiGLU (silu(gate) * up) into the INT4 projection that consumes it —\n// used for the Mamba block's mamba_swiglu (silu(z) * norm_out) feeding out_proj.\n// Same INT4 dequant/reduction as WGSL_MATVEC_INT4; only the A vector is built\n// from two inputs with an inline SiLU. Identical to SwiGLU→MatVecInt4.\nconst WGSL_SWIGLU_GATED_MATVEC_INT4 = `\\\nconst N_TILE: u32 = 8u;\nconst K_THREADS: u32 = 16u;\n\nstruct Params {\n K: u32,\n N: u32,\n group_size: u32,\n _pad: u32,\n}\n\n@group(0) @binding(0) var<storage, read> gate: array<vec4f>;\n@group(0) @binding(1) var<storage, read> up: array<vec4f>;\n@group(0) @binding(2) var<storage, read> B_q: array<vec4u>;\n@group(0) @binding(3) var<storage, read> scales: array<f32>;\n@group(0) @binding(4) var<storage, read> zeros: array<f32>;\n@group(0) @binding(5) var<storage, read_write> C: array<f32>;\n@group(0) @binding(6) var<storage, read> params: Params;\n\nvar<workgroup> shared_sums: array<f32, 128>;\n\nfn swiglu_a(i: u32) -> vec4f {\n let g = gate[i];\n let u = up[i];\n let s = g / (vec4f(1.0) + exp(max(-g, vec4f(-80.0))));\n return s * u;\n}\n\n@compute @workgroup_size(128)\nfn main(\n @builtin(local_invocation_id) lid: vec3u,\n @builtin(workgroup_id) wid: vec3u,\n) {\n let tid = lid.x;\n let n_idx = tid / K_THREADS;\n let k_tid = tid % K_THREADS;\n let col = wid.x * N_TILE + n_idx;\n let K = params.K;\n let k_vec = K / 32u;\n let groups_per_col = K / params.group_size;\n let packed_per_group = params.group_size / 8u;\n\n var sum: f32 = 0.0;\n if (col < params.N) {\n let b_off_v = col * k_vec;\n let col_g_base = col * groups_per_col;\n\n var v = k_tid;\n while (v < k_vec) {\n let bq = B_q[b_off_v + v];\n let g = (v * 4u) / packed_per_group;\n let scale = scales[col_g_base + g];\n let zero = zeros[col_g_base + g];\n let a_base = v * 8u;\n\n for (var j: u32 = 0u; j < 4u; j++) {\n let packed = bq[j];\n let n0 = f32(packed & 0xFu);\n let n1 = f32((packed >> 4u) & 0xFu);\n let n2 = f32((packed >> 8u) & 0xFu);\n let n3 = f32((packed >> 12u) & 0xFu);\n let n4 = f32((packed >> 16u) & 0xFu);\n let n5 = f32((packed >> 20u) & 0xFu);\n let n6 = f32((packed >> 24u) & 0xFu);\n let n7 = f32((packed >> 28u) & 0xFu);\n\n let b0 = vec4f(n0 - zero, n1 - zero, n2 - zero, n3 - zero) * scale;\n let b1 = vec4f(n4 - zero, n5 - zero, n6 - zero, n7 - zero) * scale;\n\n sum += dot(swiglu_a(a_base + j * 2u), b0) + dot(swiglu_a(a_base + j * 2u + 1u), b1);\n }\n\n v += K_THREADS;\n }\n }\n\n shared_sums[tid] = sum;\n workgroupBarrier();\n\n if (k_tid == 0u && col < params.N) {\n let base = n_idx * K_THREADS;\n var total: f32 = shared_sums[base];\n for (var i: u32 = 1u; i < K_THREADS; i++) {\n total += shared_sums[base + i];\n }\n C[col] = total;\n }\n}\n`;\n\nconst WGSL_ARGMAX = `\\\n// GPU-side argmax: finds the index of the maximum value.\n// Single workgroup, 256 threads, parallel reduction.\n\nstruct Params {\n count: u32,\n offset: u32,\n}\n\n@group(0) @binding(0) var<storage, read> input: array<vec4f>;\n@group(0) @binding(1) var<storage, read_write> result: array<u32>;\n@group(0) @binding(2) var<storage, read> params: Params;\n\nvar<workgroup> shared_max: array<f32, 256>;\nvar<workgroup> shared_idx: array<u32, 256>;\n\n@compute @workgroup_size(256)\nfn main(@builtin(local_invocation_id) lid: vec3u) {\n let tid = lid.x;\n // base/count are in elements; argmax always runs offset=0 with count a\n // multiple of 4 (vocab_size), so we scan the logits as vec4f (128-bit loads,\n // 4 logits per fetch) to better saturate memory bandwidth.\n let count4 = params.count >> 2u;\n\n // Each thread scans its chunk of vec4 groups\n var local_max: f32 = -3.402823e+38;\n var local_idx: u32 = 0u;\n\n var i = tid;\n while (i < count4) {\n let v = input[i];\n let g = i << 2u;\n if (v.x > local_max) { local_max = v.x; local_idx = g; }\n if (v.y > local_max) { local_max = v.y; local_idx = g + 1u; }\n if (v.z > local_max) { local_max = v.z; local_idx = g + 2u; }\n if (v.w > local_max) { local_max = v.w; local_idx = g + 3u; }\n i += 256u;\n }\n\n shared_max[tid] = local_max;\n shared_idx[tid] = local_idx;\n workgroupBarrier();\n\n // Tree reduction\n if (tid < 128u) { if (shared_max[tid + 128u] > shared_max[tid]) { shared_max[tid] = shared_max[tid + 128u]; shared_idx[tid] = shared_idx[tid + 128u]; } }\n workgroupBarrier();\n if (tid < 64u) { if (shared_max[tid + 64u] > shared_max[tid]) { shared_max[tid] = shared_max[tid + 64u]; shared_idx[tid] = shared_idx[tid + 64u]; } }\n workgroupBarrier();\n if (tid < 32u) { if (shared_max[tid + 32u] > shared_max[tid]) { shared_max[tid] = shared_max[tid + 32u]; shared_idx[tid] = shared_idx[tid + 32u]; } }\n workgroupBarrier();\n if (tid < 16u) { if (shared_max[tid + 16u] > shared_max[tid]) { shared_max[tid] = shared_max[tid + 16u]; shared_idx[tid] = shared_idx[tid + 16u]; } }\n workgroupBarrier();\n if (tid < 8u) { if (shared_max[tid + 8u] > shared_max[tid]) { shared_max[tid] = shared_max[tid + 8u]; shared_idx[tid] = shared_idx[tid + 8u]; } }\n workgroupBarrier();\n if (tid < 4u) { if (shared_max[tid + 4u] > shared_max[tid]) { shared_max[tid] = shared_max[tid + 4u]; shared_idx[tid] = shared_idx[tid + 4u]; } }\n workgroupBarrier();\n if (tid < 2u) { if (shared_max[tid + 2u] > shared_max[tid]) { shared_max[tid] = shared_max[tid + 2u]; shared_idx[tid] = shared_idx[tid + 2u]; } }\n workgroupBarrier();\n if (tid == 0u) {\n if (shared_max[1u] > shared_max[0u]) {\n result[0] = shared_idx[1u];\n } else {\n result[0] = shared_idx[0u];\n }\n }\n}\n`;\n\nconst WGSL_RMSNORM = `\\\n// RMS Normalization: output = (x / rms(x)) * weight\n\nstruct Params {\n seq_len: u32,\n hidden_size: u32,\n eps_bits: u32,\n _pad: u32,\n}\n\n@group(0) @binding(0) var<storage, read> input: array<f32>;\n@group(0) @binding(1) var<storage, read> weight: array<f32>;\n@group(0) @binding(2) var<storage, read_write> output: array<f32>;\n@group(0) @binding(3) var<storage, read> params: Params;\n\nvar<workgroup> shared_sum: array<f32, 256>;\n\n@compute @workgroup_size(256)\nfn main(\n @builtin(local_invocation_id) lid: vec3u,\n @builtin(workgroup_id) wid: vec3u,\n) {\n let row = wid.x;\n if (row >= params.seq_len) { return; }\n\n let tid = lid.x;\n let row_offset = row * params.hidden_size;\n let eps = bitcast<f32>(params.eps_bits);\n\n var local_sum: f32 = 0.0;\n var i = tid;\n while (i < params.hidden_size) {\n let val = input[row_offset + i];\n local_sum += val * val;\n i += 256u;\n }\n shared_sum[tid] = local_sum;\n workgroupBarrier();\n\n var stride: u32 = 128u;\n while (stride > 0u) {\n if (tid < stride) {\n shared_sum[tid] += shared_sum[tid + stride];\n }\n workgroupBarrier();\n stride = stride / 2u;\n }\n\n let rms = sqrt(shared_sum[0] / f32(params.hidden_size) + eps);\n\n i = tid;\n while (i < params.hidden_size) {\n let val = input[row_offset + i];\n output[row_offset + i] = (val / rms) * weight[i];\n i += 256u;\n }\n}\n`;\n\nconst WGSL_DUAL_RMSNORM = `\\\n// Two independent per-row RMSNorms in one dispatch, sharing hidden_size + eps.\n// Workgroups [0, rows0) normalize input0→output0 with weight0; workgroups\n// [rows0, rows0+rows1) normalize input1→output1 with weight1. Used to fuse the\n// per-head q_norm and k_norm in full-attention decode (same head_dim, same eps;\n// q has num_heads rows, k has num_kv_heads rows). Numerically identical to two\n// separate RMSNorm dispatches.\n\nstruct Params {\n rows0: u32, // workgroups handling input0 (e.g. T*num_heads)\n rows1: u32, // workgroups handling input1 (e.g. T*num_kv_heads)\n hidden_size: u32, // shared per-row width (head_dim)\n eps_bits: u32,\n}\n\n@group(0) @binding(0) var<storage, read> input0: array<f32>;\n@group(0) @binding(1) var<storage, read> weight0: array<f32>;\n@group(0) @binding(2) var<storage, read> input1: array<f32>;\n@group(0) @binding(3) var<storage, read> weight1: array<f32>;\n@group(0) @binding(4) var<storage, read_write> output0: array<f32>;\n@group(0) @binding(5) var<storage, read_write> output1: array<f32>;\n@group(0) @binding(6) var<storage, read> params: Params;\n\nvar<workgroup> shared_sum: array<f32, 256>;\n\n@compute @workgroup_size(256)\nfn main(\n @builtin(local_invocation_id) lid: vec3u,\n @builtin(workgroup_id) wid: vec3u,\n) {\n let wg = wid.x;\n if (wg >= params.rows0 + params.rows1) { return; }\n\n let tid = lid.x;\n let hs = params.hidden_size;\n let eps = bitcast<f32>(params.eps_bits);\n\n // Route this workgroup to input0 (first rows0) or input1 (remaining).\n let is1 = wg >= params.rows0;\n let row = select(wg, wg - params.rows0, is1);\n let row_offset = row * hs;\n\n var local_sum: f32 = 0.0;\n var i = tid;\n while (i < hs) {\n var val: f32;\n if (is1) { val = input1[row_offset + i]; } else { val = input0[row_offset + i]; }\n local_sum += val * val;\n i += 256u;\n }\n shared_sum[tid] = local_sum;\n workgroupBarrier();\n\n var stride: u32 = 128u;\n while (stride > 0u) {\n if (tid < stride) {\n shared_sum[tid] += shared_sum[tid + stride];\n }\n workgroupBarrier();\n stride = stride / 2u;\n }\n\n let rms = sqrt(shared_sum[0] / f32(hs) + eps);\n\n i = tid;\n while (i < hs) {\n if (is1) {\n output1[row_offset + i] = (input1[row_offset + i] / rms) * weight1[i];\n } else {\n output0[row_offset + i] = (input0[row_offset + i] / rms) * weight0[i];\n }\n i += 256u;\n }\n}\n`;\n\nconst WGSL_LAYERNORM = `\\\n// Layer Normalization: output = ((x - mean) / sqrt(var + eps)) * weight + bias\n\nstruct Params {\n seq_len: u32,\n hidden_size: u32,\n eps_bits: u32,\n _pad: u32,\n}\n\n@group(0) @binding(0) var<storage, read> input: array<f32>;\n@group(0) @binding(1) var<storage, read> weight: array<f32>;\n@group(0) @binding(2) var<storage, read> bias: array<f32>;\n@group(0) @binding(3) var<storage, read_write> output: array<f32>;\n@group(0) @binding(4) var<storage, read> params: Params;\n\nvar<workgroup> shared_sum: array<f32, 256>;\nvar<workgroup> shared_sq_sum: array<f32, 256>;\n\n@compute @workgroup_size(256)\nfn main(\n @builtin(local_invocation_id) lid: vec3u,\n @builtin(workgroup_id) wid: vec3u,\n) {\n let row = wid.x;\n if (row >= params.seq_len) { return; }\n\n let tid = lid.x;\n let row_offset = row * params.hidden_size;\n let eps = bitcast<f32>(params.eps_bits);\n\n var local_sum: f32 = 0.0;\n var i = tid;\n while (i < params.hidden_size) {\n local_sum += input[row_offset + i];\n i += 256u;\n }\n shared_sum[tid] = local_sum;\n workgroupBarrier();\n\n var stride: u32 = 128u;\n while (stride > 0u) {\n if (tid < stride) {\n shared_sum[tid] += shared_sum[tid + stride];\n }\n workgroupBarrier();\n stride /= 2u;\n }\n let mean = shared_sum[0] / f32(params.hidden_size);\n\n var local_sq: f32 = 0.0;\n i = tid;\n while (i < params.hidden_size) {\n let diff = input[row_offset + i] - mean;\n local_sq += diff * diff;\n i += 256u;\n }\n shared_sq_sum[tid] = local_sq;\n workgroupBarrier();\n\n stride = 128u;\n while (stride > 0u) {\n if (tid < stride) {\n shared_sq_sum[tid] += shared_sq_sum[tid + stride];\n }\n workgroupBarrier();\n stride /= 2u;\n }\n let inv_std = 1.0 / sqrt(shared_sq_sum[0] / f32(params.hidden_size) + eps);\n\n i = tid;\n while (i < params.hidden_size) {\n let val = (input[row_offset + i] - mean) * inv_std;\n output[row_offset + i] = val * weight[i] + bias[i];\n i += 256u;\n }\n}\n`;\n\nconst WGSL_ROPE = `\\\n// Rotary Position Embeddings (RoPE) with partial rotation and rotate_half convention.\n// Uses HuggingFace \"rotate_half\" pairing: (x[i], x[i + rope_half]) NOT adjacent (x[2i], x[2i+1]).\n//\n// Three decoupled knobs (so we match both Qwen-style partial RoPE and Gemma-style\n// \"proportional\" RoPE exactly):\n// - rope_half : the rotate_half pairing offset AND the number of pairs we\n// iterate per head. For HF this is head_dim/2 (Gemma) or\n// rope_dim/2 (Qwen partial). Pairs dim i with i+rope_half.\n// - rope_denom : denominator in the inv_freq exponent base^(2i/rope_denom).\n// Gemma \"proportional\" uses head_dim; Qwen partial uses rope_dim.\n// - rope_active_pairs : pairs [0, rope_active_pairs) get a real frequency; pairs\n// >= rope_active_pairs have inv_freq=0 (identity, the \"nope\"\n// zero-padded tail of HF _compute_proportional_rope_parameters).\n//\n// Defaults (set in buildParams) reproduce the legacy single-rope_dim behavior:\n// rope_half = rope_dim/2, rope_denom = rope_dim, rope_active_pairs = rope_dim/2.\n\nstruct Params {\n seq_len: u32,\n num_q_heads: u32,\n num_kv_heads: u32,\n head_dim: u32,\n rope_base_bits: u32,\n position_offset: u32,\n rope_dim: u32,\n rope_half: u32,\n rope_denom: u32,\n rope_active_pairs: u32,\n _pad0: u32,\n _pad1: u32,\n}\n\n@group(0) @binding(0) var<storage, read_write> q: array<f32>;\n@group(0) @binding(1) var<storage, read_write> k: array<f32>;\n@group(0) @binding(2) var<storage, read> params: Params;\n\n@compute @workgroup_size(256)\nfn main(@builtin(global_invocation_id) gid: vec3u) {\n let rope_half = params.rope_half;\n let total_q_pairs = params.seq_len * params.num_q_heads * rope_half;\n let total_k_pairs = params.seq_len * params.num_kv_heads * rope_half;\n\n let idx = gid.x;\n\n let rope_base = bitcast<f32>(params.rope_base_bits);\n\n // rotate_half convention:\n // For pair_idx i (0..rope_half-1):\n // x_lo = x[i], x_hi = x[i + rope_half]\n // x_lo' = x_lo * cos - x_hi * sin\n // x_hi' = x_lo * sin + x_hi * cos\n // inv_freq[i] = 1 / base^(2i/rope_denom) for i < rope_active_pairs, else 0.\n\n if (idx < total_q_pairs) {\n let pos = idx / (params.num_q_heads * rope_half);\n let remainder = idx % (params.num_q_heads * rope_half);\n let head = remainder / rope_half;\n let pair_idx = remainder % rope_half;\n\n let position = f32(pos + params.position_offset);\n var cos_val = 1.0;\n var sin_val = 0.0;\n if (pair_idx < params.rope_active_pairs) {\n let freq = position / pow(rope_base, f32(2u * pair_idx) / f32(params.rope_denom));\n cos_val = cos(freq);\n sin_val = sin(freq);\n }\n\n let q_stride = params.num_q_heads * params.head_dim;\n let head_base = pos * q_stride + head * params.head_dim;\n let lo = head_base + pair_idx;\n let hi = head_base + pair_idx + rope_half;\n\n let q_lo = q[lo];\n let q_hi = q[hi];\n q[lo] = q_lo * cos_val - q_hi * sin_val;\n q[hi] = q_lo * sin_val + q_hi * cos_val;\n }\n\n if (idx < total_k_pairs) {\n let pos = idx / (params.num_kv_heads * rope_half);\n let remainder = idx % (params.num_kv_heads * rope_half);\n let head = remainder / rope_half;\n let pair_idx = remainder % rope_half;\n\n let position = f32(pos + params.position_offset);\n var cos_val = 1.0;\n var sin_val = 0.0;\n if (pair_idx < params.rope_active_pairs) {\n let freq = position / pow(rope_base, f32(2u * pair_idx) / f32(params.rope_denom));\n cos_val = cos(freq);\n sin_val = sin(freq);\n }\n\n let k_stride = params.num_kv_heads * params.head_dim;\n let head_base = pos * k_stride + head * params.head_dim;\n let lo = head_base + pair_idx;\n let hi = head_base + pair_idx + rope_half;\n\n let k_lo = k[lo];\n let k_hi = k[hi];\n k[lo] = k_lo * cos_val - k_hi * sin_val;\n k[hi] = k_lo * sin_val + k_hi * cos_val;\n }\n}\n`;\n\nconst WGSL_ROPE_INTERLEAVED = `\\\n// Rotary Position Embeddings — INTERLEAVED (adjacent-pair) convention, used by\n// Moonshine. Unlike the HF-Llama \"rotate_half\" split kernel above (which pairs\n// dim i with dim i+rope_half), Moonshine pairs ADJACENT dims (2p, 2p+1) with a\n// single frequency inv_freq[p] = 1 / base^(2p/rope_denom). Verified against the HF\n// MoonshineRotaryEmbedding (cos[..., :half].repeat_interleave(2) + interleaved\n// rotate_half). Only the first 2*rope_half dims of each head are rotated.\n//\n// Shares the Params layout with WGSL_ROPE (same buildParams). rope_half is the\n// number of adjacent pairs; rope_denom/rope_active_pairs default to the legacy\n// values so Moonshine is byte-identical to before.\n\nstruct Params {\n seq_len: u32,\n num_q_heads: u32,\n num_kv_heads: u32,\n head_dim: u32,\n rope_base_bits: u32,\n position_offset: u32,\n rope_dim: u32,\n rope_half: u32,\n rope_denom: u32,\n rope_active_pairs: u32,\n _pad0: u32,\n _pad1: u32,\n}\n\n@group(0) @binding(0) var<storage, read_write> q: array<f32>;\n@group(0) @binding(1) var<storage, read_write> k: array<f32>;\n@group(0) @binding(2) var<storage, read> params: Params;\n\n@compute @workgroup_size(256)\nfn main(@builtin(global_invocation_id) gid: vec3u) {\n let half_rope = params.rope_half;\n let total_q_pairs = params.seq_len * params.num_q_heads * half_rope;\n let total_k_pairs = params.seq_len * params.num_kv_heads * half_rope;\n let idx = gid.x;\n let rope_base = bitcast<f32>(params.rope_base_bits);\n\n if (idx < total_q_pairs) {\n let pos = idx / (params.num_q_heads * half_rope);\n let remainder = idx % (params.num_q_heads * half_rope);\n let head = remainder / half_rope;\n let pair_idx = remainder % half_rope;\n\n let position = f32(pos + params.position_offset);\n var cos_val = 1.0;\n var sin_val = 0.0;\n if (pair_idx < params.rope_active_pairs) {\n let freq = position / pow(rope_base, f32(2u * pair_idx) / f32(params.rope_denom));\n cos_val = cos(freq);\n sin_val = sin(freq);\n }\n\n let q_stride = params.num_q_heads * params.head_dim;\n let head_base = pos * q_stride + head * params.head_dim;\n // Adjacent-pair (interleaved): dims 2*pair_idx and 2*pair_idx+1.\n let lo = head_base + 2u * pair_idx;\n let hi = lo + 1u;\n\n let q_lo = q[lo];\n let q_hi = q[hi];\n q[lo] = q_lo * cos_val - q_hi * sin_val;\n q[hi] = q_hi * cos_val + q_lo * sin_val;\n }\n\n if (idx < total_k_pairs) {\n let pos = idx / (params.num_kv_heads * half_rope);\n let remainder = idx % (params.num_kv_heads * half_rope);\n let head = remainder / half_rope;\n let pair_idx = remainder % half_rope;\n\n let position = f32(pos + params.position_offset);\n var cos_val = 1.0;\n var sin_val = 0.0;\n if (pair_idx < params.rope_active_pairs) {\n let freq = position / pow(rope_base, f32(2u * pair_idx) / f32(params.rope_denom));\n cos_val = cos(freq);\n sin_val = sin(freq);\n }\n\n let k_stride = params.num_kv_heads * params.head_dim;\n let head_base = pos * k_stride + head * params.head_dim;\n let lo = head_base + 2u * pair_idx;\n let hi = lo + 1u;\n\n let k_lo = k[lo];\n let k_hi = k[hi];\n k[lo] = k_lo * cos_val - k_hi * sin_val;\n k[hi] = k_hi * cos_val + k_lo * sin_val;\n }\n}\n`;\n\nconst WGSL_ATTENTION = `\\\n// Tiled online-softmax attention (Flash-Attention style).\n//\n// Processes KV cache in tiles of TILE_S=32 positions. Per tile:\n// 1. Cooperative K tile load into shared memory (256 threads)\n// 2. Parallel Q·K dot products (8 threads per position, reduce via smem)\n// 3. Online softmax: update running max, rescale, compute tile weights\n// 4. Cooperative V tile load into shared memory (reuse K buffer)\n// 5. Parallel V accumulation: each of 128 threads handles one output dim\n//\n// Uses online softmax to maintain running (max, sum_exp, output) across tiles.\n// No global scratch buffer needed — everything in shared memory + registers.\n//\n// Safari/Metal compatibility:\n// - Uses if/else instead of select() (Safari has select() bugs)\n// - Clamps exp() args to -80 (Metal can return NaN for exp(-1e30))\n//\n// Shared memory: 4096 f32 = 16384 bytes = exactly 16 KB (minimum WebGPU guarantee)\n\nconst TILE_S: u32 = 16u;\nconst WG: u32 = 256u;\n// 16 threads cooperate on each dot product (256 / 16 = 16)\nconst DOT_THREADS: u32 = 16u;\n\nstruct Params {\n T: u32,\n S: u32,\n num_q_heads: u32,\n num_kv_heads: u32,\n head_dim: u32,\n position_offset: u32,\n is_causal: u32,\n // Query-grid base offset. Lets a caller process a window of query positions\n // [q_offset, q_offset + dispatched_workgroups_x) in a single dispatch without a\n // workgroup-id offset (WebGPU has none). Default 0 → full grid from row 0, byte-\n // identical to the un-windowed path. Used only by the WebKit vision encoder to\n // split the O(N²) ViT attention into watchdog-safe chunks.\n q_offset: u32,\n // Softmax QK temperature, supplied by buildParams as an f32. Defaults to\n // 1/sqrt(head_dim) (byte-identical to the legacy hardcoded scale), so every\n // existing caller is unchanged. Gemma 4 sets it to 1.0 because the per-head\n // QK-norm already absorbs the 1/sqrt(head_dim) factor (HF scaling = 1.0).\n attn_scale: f32,\n}\n\n@group(0) @binding(0) var<storage, read> Q: array<f32>;\n@group(0) @binding(1) var<storage, read> K: array<f32>;\n@group(0) @binding(2) var<storage, read> V: array<f32>;\n@group(0) @binding(3) var<storage, read_write> output: array<f32>;\n@group(0) @binding(4) var<storage, read> params: Params;\n\n// Shared memory layout (all phases share a single 4096-element array):\n// K phase: smem[0..TILE_S*hd) = K tile data (16×256 = 4096 f32)\n// Dot phase: smem[0..WG) = partial dot products for reduction\n// smem[0..TILE_S) reused for final scores after reduction\n// Softmax: smem[0..TILE_S) = scores → exp weights\n// V phase: smem[TILE_S..TILE_S+TILE_S*hd) = V tile data\n// But TILE_S+TILE_S*hd = 16+4096 > 4096, so we must save scores\n// to per-thread registers before V load, then use full smem for V.\n// Total: 4096 f32 = 16384 bytes = exactly 16 KB (minimum WebGPU guarantee)\nvar<workgroup> smem: array<f32, 4096>;\n\n@compute @workgroup_size(256)\nfn main(\n @builtin(local_invocation_id) lid: vec3u,\n @builtin(workgroup_id) wid: vec3u,\n) {\n let q_pos = wid.x + params.q_offset;\n let q_head = wid.y;\n\n if (q_pos >= params.T || q_head >= params.num_q_heads) { return; }\n\n let tid = lid.x;\n let heads_per_kv = params.num_q_heads / params.num_kv_heads;\n let kv_head = q_head / heads_per_kv;\n let scale = params.attn_scale;\n\n let q_stride = params.num_q_heads * params.head_dim;\n let kv_stride = params.num_kv_heads * params.head_dim;\n let q_base = q_pos * q_stride + q_head * params.head_dim;\n let kv_head_offset = kv_head * params.head_dim;\n let causal_limit = q_pos + params.position_offset + 1u;\n let hd = params.head_dim;\n // Causal (text) attends to keys up to its own position; bidirectional (ViT)\n // attends to all S keys. Default is_causal=1 keeps text decoding unchanged.\n var S_eff = params.S;\n if (params.is_causal != 0u) { S_eff = min(params.S, causal_limit); }\n\n // Online softmax running state (per-thread for output dims)\n var running_max: f32 = -1e30;\n var running_sum: f32 = 0.0;\n // Per-thread output accumulators. Thread tid handles output dim tid; a second\n // accumulator covers dim tid+WG so head_dim up to 2*WG (512) is supported with\n // WG=256 threads. For hd<=256, tid+WG>=hd is never written → byte-identical.\n var out_acc: f32 = 0.0;\n var out_acc2: f32 = 0.0;\n let d2 = tid + WG;\n\n // Tile size capped so a K/V tile (tile_cap * hd f32) fits the 4096-f32 smem.\n // hd<=256 → tile_cap=16=TILE_S (unchanged). hd=512 → tile_cap=8.\n let tile_cap = min(TILE_S, 4096u / hd);\n\n // Process KV cache in tiles of tile_cap positions\n let num_tiles = (S_eff + tile_cap - 1u) / tile_cap;\n\n for (var tile: u32 = 0u; tile < num_tiles; tile += 1u) {\n let tile_start = tile * tile_cap;\n let tile_end = min(tile_start + tile_cap, S_eff);\n let tile_len = tile_end - tile_start;\n\n // ── Step 1: Cooperative K tile load ──\n // Load tile_len × hd values from K cache into smem\n let total_k_elems = tile_len * hd;\n var load_idx = tid;\n while (load_idx < total_k_elems) {\n let s_local = load_idx / hd;\n let d = load_idx % hd;\n let s_global = tile_start + s_local;\n let k_addr = s_global * kv_stride + kv_head_offset + d;\n smem[s_local * hd + d] = K[k_addr];\n load_idx += WG;\n }\n workgroupBarrier();\n\n // ── Step 2: Parallel Q·K dot products ──\n // 256 threads = TILE_S positions × DOT_THREADS per position\n let pos_in_tile = tid / DOT_THREADS;\n let dot_tid = tid % DOT_THREADS;\n\n var my_partial: f32 = 0.0;\n if (pos_in_tile < tile_len) {\n let dims_per_thread = hd / DOT_THREADS;\n let d_start = dot_tid * dims_per_thread;\n let d_end = d_start + dims_per_thread;\n let kv_row_base = pos_in_tile * hd;\n\n var d = d_start;\n while (d + 3u < d_end) {\n let q_v = vec4f(Q[q_base + d], Q[q_base + d + 1u],\n Q[q_base + d + 2u], Q[q_base + d + 3u]);\n let k_v = vec4f(smem[kv_row_base + d], smem[kv_row_base + d + 1u],\n smem[kv_row_base + d + 2u], smem[kv_row_base + d + 3u]);\n my_partial += dot(q_v, k_v);\n d += 4u;\n }\n while (d < d_end) {\n my_partial += Q[q_base + d] * smem[kv_row_base + d];\n d += 1u;\n }\n }\n\n // K tile no longer needed — reuse smem for dot reduction\n workgroupBarrier();\n smem[tid] = my_partial;\n workgroupBarrier();\n\n // Thread 0 of each dot group reduces and writes score to smem[0..TILE_S).\n // Two-phase: leader 0 reads smem[1..15], which are exactly the slots\n // leaders 1..15 write — reduce into a local first, barrier (in uniform\n // control flow), then write. Read+write in one block is a data race.\n var group_total: f32 = 0.0;\n if (dot_tid == 0u && pos_in_tile < tile_len) {\n group_total = smem[tid];\n for (var i: u32 = 1u; i < DOT_THREADS; i += 1u) {\n group_total += smem[tid + i];\n }\n }\n workgroupBarrier();\n if (dot_tid == 0u && pos_in_tile < tile_len) {\n smem[pos_in_tile] = group_total * scale;\n }\n workgroupBarrier();\n\n // ── Step 3: Online softmax ──\n // Scores are in smem[0..tile_len). Find max via tree reduce in smem[TILE_S..].\n // NOTE: Uses if/else instead of select() — Safari/Metal has bugs with select().\n if (tid < TILE_S) {\n if (tid < tile_len) {\n smem[TILE_S + tid] = smem[tid];\n } else {\n smem[TILE_S + tid] = -1e30;\n }\n }\n workgroupBarrier();\n if (tid < 8u) { smem[TILE_S + tid] = max(smem[TILE_S + tid], smem[TILE_S + tid + 8u]); }\n workgroupBarrier();\n if (tid < 4u) { smem[TILE_S + tid] = max(smem[TILE_S + tid], smem[TILE_S + tid + 4u]); }\n workgroupBarrier();\n if (tid < 2u) { smem[TILE_S + tid] = max(smem[TILE_S + tid], smem[TILE_S + tid + 2u]); }\n workgroupBarrier();\n if (tid < 1u) { smem[TILE_S + tid] = max(smem[TILE_S + tid], smem[TILE_S + tid + 1u]); }\n workgroupBarrier();\n let tile_max = smem[TILE_S];\n\n let new_max = max(running_max, tile_max);\n // Clamp exp() argument to avoid NaN on Metal (exp(-87) ≈ 0 in f32)\n let old_correction = exp(max(running_max - new_max, -80.0));\n\n // Compute exp weights in smem[0..TILE_S)\n if (tid < tile_len) {\n smem[tid] = exp(max(smem[tid] - new_max, -80.0));\n }\n workgroupBarrier();\n\n // Sum tile weights via tree reduce in smem[TILE_S..]\n // NOTE: Uses if/else instead of select() — Safari/Metal has bugs with select().\n if (tid < TILE_S) {\n if (tid < tile_len) {\n smem[TILE_S + tid] = smem[tid];\n } else {\n smem[TILE_S + tid] = 0.0;\n }\n }\n workgroupBarrier();\n if (tid < 8u) { smem[TILE_S + tid] += smem[TILE_S + tid + 8u]; }\n workgroupBarrier();\n if (tid < 4u) { smem[TILE_S + tid] += smem[TILE_S + tid + 4u]; }\n workgroupBarrier();\n if (tid < 2u) { smem[TILE_S + tid] += smem[TILE_S + tid + 2u]; }\n workgroupBarrier();\n if (tid < 1u) { smem[TILE_S + tid] += smem[TILE_S + tid + 1u]; }\n workgroupBarrier();\n let tile_sum = smem[TILE_S];\n\n // ── Save scores to per-thread registers before V overwrites smem ──\n // Each thread saves the scores it needs (all TILE_S values)\n var w0: f32 = 0.0; var w1: f32 = 0.0; var w2: f32 = 0.0; var w3: f32 = 0.0;\n var w4: f32 = 0.0; var w5: f32 = 0.0; var w6: f32 = 0.0; var w7: f32 = 0.0;\n var w8: f32 = 0.0; var w9: f32 = 0.0; var w10: f32 = 0.0; var w11: f32 = 0.0;\n var w12: f32 = 0.0; var w13: f32 = 0.0; var w14: f32 = 0.0; var w15: f32 = 0.0;\n if (tid < hd) {\n if (0u < tile_len) { w0 = smem[0]; }\n if (1u < tile_len) { w1 = smem[1]; }\n if (2u < tile_len) { w2 = smem[2]; }\n if (3u < tile_len) { w3 = smem[3]; }\n if (4u < tile_len) { w4 = smem[4]; }\n if (5u < tile_len) { w5 = smem[5]; }\n if (6u < tile_len) { w6 = smem[6]; }\n if (7u < tile_len) { w7 = smem[7]; }\n if (8u < tile_len) { w8 = smem[8]; }\n if (9u < tile_len) { w9 = smem[9]; }\n if (10u < tile_len) { w10 = smem[10]; }\n if (11u < tile_len) { w11 = smem[11]; }\n if (12u < tile_len) { w12 = smem[12]; }\n if (13u < tile_len) { w13 = smem[13]; }\n if (14u < tile_len) { w14 = smem[14]; }\n if (15u < tile_len) { w15 = smem[15]; }\n }\n workgroupBarrier();\n\n // ── Step 4: Cooperative V tile load (full smem reuse) ──\n let total_v_elems = tile_len * hd;\n load_idx = tid;\n while (load_idx < total_v_elems) {\n let s_local = load_idx / hd;\n let d = load_idx % hd;\n let s_global = tile_start + s_local;\n let v_addr = s_global * kv_stride + kv_head_offset + d;\n smem[s_local * hd + d] = V[v_addr];\n load_idx += WG;\n }\n workgroupBarrier();\n\n // ── Step 5: Parallel V accumulation ──\n // Thread tid handles output dim tid and (for hd>WG) dim tid+WG via out_acc2.\n // Uses saved score registers instead of smem[0..TILE_S).\n if (tid < hd) {\n out_acc = out_acc * old_correction;\n if (0u < tile_len) { out_acc += w0 * smem[0u * hd + tid]; }\n if (1u < tile_len) { out_acc += w1 * smem[1u * hd + tid]; }\n if (2u < tile_len) { out_acc += w2 * smem[2u * hd + tid]; }\n if (3u < tile_len) { out_acc += w3 * smem[3u * hd + tid]; }\n if (4u < tile_len) { out_acc += w4 * smem[4u * hd + tid]; }\n if (5u < tile_len) { out_acc += w5 * smem[5u * hd + tid]; }\n if (6u < tile_len) { out_acc += w6 * smem[6u * hd + tid]; }\n if (7u < tile_len) { out_acc += w7 * smem[7u * hd + tid]; }\n if (8u < tile_len) { out_acc += w8 * smem[8u * hd + tid]; }\n if (9u < tile_len) { out_acc += w9 * smem[9u * hd + tid]; }\n if (10u < tile_len) { out_acc += w10 * smem[10u * hd + tid]; }\n if (11u < tile_len) { out_acc += w11 * smem[11u * hd + tid]; }\n if (12u < tile_len) { out_acc += w12 * smem[12u * hd + tid]; }\n if (13u < tile_len) { out_acc += w13 * smem[13u * hd + tid]; }\n if (14u < tile_len) { out_acc += w14 * smem[14u * hd + tid]; }\n if (15u < tile_len) { out_acc += w15 * smem[15u * hd + tid]; }\n }\n if (d2 < hd) {\n out_acc2 = out_acc2 * old_correction;\n if (0u < tile_len) { out_acc2 += w0 * smem[0u * hd + d2]; }\n if (1u < tile_len) { out_acc2 += w1 * smem[1u * hd + d2]; }\n if (2u < tile_len) { out_acc2 += w2 * smem[2u * hd + d2]; }\n if (3u < tile_len) { out_acc2 += w3 * smem[3u * hd + d2]; }\n if (4u < tile_len) { out_acc2 += w4 * smem[4u * hd + d2]; }\n if (5u < tile_len) { out_acc2 += w5 * smem[5u * hd + d2]; }\n if (6u < tile_len) { out_acc2 += w6 * smem[6u * hd + d2]; }\n if (7u < tile_len) { out_acc2 += w7 * smem[7u * hd + d2]; }\n if (8u < tile_len) { out_acc2 += w8 * smem[8u * hd + d2]; }\n if (9u < tile_len) { out_acc2 += w9 * smem[9u * hd + d2]; }\n if (10u < tile_len) { out_acc2 += w10 * smem[10u * hd + d2]; }\n if (11u < tile_len) { out_acc2 += w11 * smem[11u * hd + d2]; }\n if (12u < tile_len) { out_acc2 += w12 * smem[12u * hd + d2]; }\n if (13u < tile_len) { out_acc2 += w13 * smem[13u * hd + d2]; }\n if (14u < tile_len) { out_acc2 += w14 * smem[14u * hd + d2]; }\n if (15u < tile_len) { out_acc2 += w15 * smem[15u * hd + d2]; }\n }\n\n // Update running state\n running_sum = running_sum * old_correction + tile_sum;\n running_max = new_max;\n workgroupBarrier();\n }\n\n // ── Write normalized output ──\n var inv_sum: f32 = 0.0;\n if (running_sum > 0.0) { inv_sum = 1.0 / running_sum; }\n let out_base = q_pos * q_stride + q_head * hd;\n if (tid < hd) {\n output[out_base + tid] = out_acc * inv_sum;\n }\n if (d2 < hd) {\n output[out_base + d2] = out_acc2 * inv_sum;\n }\n}\n`;\n\n// ── Cross-Attention (encoder-decoder) ──────────────────────────────────\n//\n// Decoder queries attend to a FIXED encoder output. This is the distinct\n// coherence regime from the causal self-attention KVCache path:\n//\n// Q = decoder hidden state [T_dec, num_q_heads * head_dim]\n// K = encoder output (K proj) [S_enc, num_kv_heads * head_dim]\n// V = encoder output (V proj) [S_enc, num_kv_heads * head_dim]\n//\n// The encoder K/V are computed ONCE and frozen for the whole decode — there\n// is no position_offset and no growing cache. Attention is UNMASKED /\n// bidirectional over the full S_enc encoder sequence (every decoder query\n// position sees every encoder position). One workgroup per (q_pos, q_head).\n//\n// Algorithm is the identical online-softmax tiling to WGSL_ATTENTION (the\n// validated flash-attention kernel) with the causal-limit logic removed, so\n// it inherits the same Safari/Metal-safe patterns:\n// - if/else instead of select()\n// - exp() args clamped to -80 (Metal returns NaN for exp(-1e30))\n// - <= 16 KB workgroup memory (single 4096 f32 smem array)\n// - no `enable f16`\nconst WGSL_CROSS_ATTENTION = `\\\nconst TILE_S: u32 = 16u;\nconst WG: u32 = 256u;\nconst DOT_THREADS: u32 = 16u;\n\nstruct Params {\n T: u32, // decoder query length\n S: u32, // encoder (key/value) length\n num_q_heads: u32,\n num_kv_heads: u32,\n head_dim: u32,\n}\n\n@group(0) @binding(0) var<storage, read> Q: array<f32>;\n@group(0) @binding(1) var<storage, read> K: array<f32>;\n@group(0) @binding(2) var<storage, read> V: array<f32>;\n@group(0) @binding(3) var<storage, read_write> output: array<f32>;\n@group(0) @binding(4) var<storage, read> params: Params;\n\nvar<workgroup> smem: array<f32, 4096>;\n\n@compute @workgroup_size(256)\nfn main(\n @builtin(local_invocation_id) lid: vec3u,\n @builtin(workgroup_id) wid: vec3u,\n) {\n let q_pos = wid.x;\n let q_head = wid.y;\n\n if (q_pos >= params.T || q_head >= params.num_q_heads) { return; }\n\n let tid = lid.x;\n let heads_per_kv = params.num_q_heads / params.num_kv_heads;\n let kv_head = q_head / heads_per_kv;\n let scale = 1.0 / sqrt(f32(params.head_dim));\n\n let q_stride = params.num_q_heads * params.head_dim;\n let kv_stride = params.num_kv_heads * params.head_dim;\n let q_base = q_pos * q_stride + q_head * params.head_dim;\n let kv_head_offset = kv_head * params.head_dim;\n let hd = params.head_dim;\n // Unmasked: every query attends to all S_enc encoder positions.\n let S_eff = params.S;\n\n var running_max: f32 = -1e30;\n var running_sum: f32 = 0.0;\n // out_acc2 covers dim tid+WG for head_dim up to 2*WG; unused for hd<=WG.\n var out_acc: f32 = 0.0;\n var out_acc2: f32 = 0.0;\n let d2 = tid + WG;\n\n // Tile capped so a K/V tile (tile_cap*hd f32) fits the 4096-f32 smem.\n let tile_cap = min(TILE_S, 4096u / hd);\n let num_tiles = (S_eff + tile_cap - 1u) / tile_cap;\n\n for (var tile: u32 = 0u; tile < num_tiles; tile += 1u) {\n let tile_start = tile * tile_cap;\n let tile_end = min(tile_start + tile_cap, S_eff);\n let tile_len = tile_end - tile_start;\n\n // ── Step 1: Cooperative K tile load ──\n let total_k_elems = tile_len * hd;\n var load_idx = tid;\n while (load_idx < total_k_elems) {\n let s_local = load_idx / hd;\n let d = load_idx % hd;\n let s_global = tile_start + s_local;\n let k_addr = s_global * kv_stride + kv_head_offset + d;\n smem[s_local * hd + d] = K[k_addr];\n load_idx += WG;\n }\n workgroupBarrier();\n\n // ── Step 2: Parallel Q·K dot products ──\n let pos_in_tile = tid / DOT_THREADS;\n let dot_tid = tid % DOT_THREADS;\n\n var my_partial: f32 = 0.0;\n if (pos_in_tile < tile_len) {\n let kv_row_base = pos_in_tile * hd;\n // Strided over head_dim so any head_dim works (incl. ones not divisible\n // by DOT_THREADS, e.g. Moonshine's 52). Each thread sums its dims.\n var d = dot_tid;\n while (d < hd) {\n my_partial += Q[q_base + d] * smem[kv_row_base + d];\n d += DOT_THREADS;\n }\n }\n\n workgroupBarrier();\n smem[tid] = my_partial;\n workgroupBarrier();\n\n var group_total: f32 = 0.0;\n if (dot_tid == 0u && pos_in_tile < tile_len) {\n group_total = smem[tid];\n for (var i: u32 = 1u; i < DOT_THREADS; i += 1u) {\n group_total += smem[tid + i];\n }\n }\n workgroupBarrier();\n if (dot_tid == 0u && pos_in_tile < tile_len) {\n smem[pos_in_tile] = group_total * scale;\n }\n workgroupBarrier();\n\n // ── Step 3: Online softmax (max reduce, no select()) ──\n if (tid < TILE_S) {\n if (tid < tile_len) {\n smem[TILE_S + tid] = smem[tid];\n } else {\n smem[TILE_S + tid] = -1e30;\n }\n }\n workgroupBarrier();\n if (tid < 8u) { smem[TILE_S + tid] = max(smem[TILE_S + tid], smem[TILE_S + tid + 8u]); }\n workgroupBarrier();\n if (tid < 4u) { smem[TILE_S + tid] = max(smem[TILE_S + tid], smem[TILE_S + tid + 4u]); }\n workgroupBarrier();\n if (tid < 2u) { smem[TILE_S + tid] = max(smem[TILE_S + tid], smem[TILE_S + tid + 2u]); }\n workgroupBarrier();\n if (tid < 1u) { smem[TILE_S + tid] = max(smem[TILE_S + tid], smem[TILE_S + tid + 1u]); }\n workgroupBarrier();\n let tile_max = smem[TILE_S];\n\n let new_max = max(running_max, tile_max);\n let old_correction = exp(max(running_max - new_max, -80.0));\n\n if (tid < tile_len) {\n smem[tid] = exp(max(smem[tid] - new_max, -80.0));\n }\n workgroupBarrier();\n\n if (tid < TILE_S) {\n if (tid < tile_len) {\n smem[TILE_S + tid] = smem[tid];\n } else {\n smem[TILE_S + tid] = 0.0;\n }\n }\n workgroupBarrier();\n if (tid < 8u) { smem[TILE_S + tid] += smem[TILE_S + tid + 8u]; }\n workgroupBarrier();\n if (tid < 4u) { smem[TILE_S + tid] += smem[TILE_S + tid + 4u]; }\n workgroupBarrier();\n if (tid < 2u) { smem[TILE_S + tid] += smem[TILE_S + tid + 2u]; }\n workgroupBarrier();\n if (tid < 1u) { smem[TILE_S + tid] += smem[TILE_S + tid + 1u]; }\n workgroupBarrier();\n let tile_sum = smem[TILE_S];\n\n // ── Save scores to per-thread registers before V overwrites smem ──\n var w0: f32 = 0.0; var w1: f32 = 0.0; var w2: f32 = 0.0; var w3: f32 = 0.0;\n var w4: f32 = 0.0; var w5: f32 = 0.0; var w6: f32 = 0.0; var w7: f32 = 0.0;\n var w8: f32 = 0.0; var w9: f32 = 0.0; var w10: f32 = 0.0; var w11: f32 = 0.0;\n var w12: f32 = 0.0; var w13: f32 = 0.0; var w14: f32 = 0.0; var w15: f32 = 0.0;\n if (tid < hd) {\n if (0u < tile_len) { w0 = smem[0]; }\n if (1u < tile_len) { w1 = smem[1]; }\n if (2u < tile_len) { w2 = smem[2]; }\n if (3u < tile_len) { w3 = smem[3]; }\n if (4u < tile_len) { w4 = smem[4]; }\n if (5u < tile_len) { w5 = smem[5]; }\n if (6u < tile_len) { w6 = smem[6]; }\n if (7u < tile_len) { w7 = smem[7]; }\n if (8u < tile_len) { w8 = smem[8]; }\n if (9u < tile_len) { w9 = smem[9]; }\n if (10u < tile_len) { w10 = smem[10]; }\n if (11u < tile_len) { w11 = smem[11]; }\n if (12u < tile_len) { w12 = smem[12]; }\n if (13u < tile_len) { w13 = smem[13]; }\n if (14u < tile_len) { w14 = smem[14]; }\n if (15u < tile_len) { w15 = smem[15]; }\n }\n workgroupBarrier();\n\n // ── Step 4: Cooperative V tile load ──\n let total_v_elems = tile_len * hd;\n load_idx = tid;\n while (load_idx < total_v_elems) {\n let s_local = load_idx / hd;\n let d = load_idx % hd;\n let s_global = tile_start + s_local;\n let v_addr = s_global * kv_stride + kv_head_offset + d;\n smem[s_local * hd + d] = V[v_addr];\n load_idx += WG;\n }\n workgroupBarrier();\n\n // ── Step 5: Parallel V accumulation ──\n if (tid < hd) {\n out_acc = out_acc * old_correction;\n if (0u < tile_len) { out_acc += w0 * smem[0u * hd + tid]; }\n if (1u < tile_len) { out_acc += w1 * smem[1u * hd + tid]; }\n if (2u < tile_len) { out_acc += w2 * smem[2u * hd + tid]; }\n if (3u < tile_len) { out_acc += w3 * smem[3u * hd + tid]; }\n if (4u < tile_len) { out_acc += w4 * smem[4u * hd + tid]; }\n if (5u < tile_len) { out_acc += w5 * smem[5u * hd + tid]; }\n if (6u < tile_len) { out_acc += w6 * smem[6u * hd + tid]; }\n if (7u < tile_len) { out_acc += w7 * smem[7u * hd + tid]; }\n if (8u < tile_len) { out_acc += w8 * smem[8u * hd + tid]; }\n if (9u < tile_len) { out_acc += w9 * smem[9u * hd + tid]; }\n if (10u < tile_len) { out_acc += w10 * smem[10u * hd + tid]; }\n if (11u < tile_len) { out_acc += w11 * smem[11u * hd + tid]; }\n if (12u < tile_len) { out_acc += w12 * smem[12u * hd + tid]; }\n if (13u < tile_len) { out_acc += w13 * smem[13u * hd + tid]; }\n if (14u < tile_len) { out_acc += w14 * smem[14u * hd + tid]; }\n if (15u < tile_len) { out_acc += w15 * smem[15u * hd + tid]; }\n }\n if (d2 < hd) {\n out_acc2 = out_acc2 * old_correction;\n if (0u < tile_len) { out_acc2 += w0 * smem[0u * hd + d2]; }\n if (1u < tile_len) { out_acc2 += w1 * smem[1u * hd + d2]; }\n if (2u < tile_len) { out_acc2 += w2 * smem[2u * hd + d2]; }\n if (3u < tile_len) { out_acc2 += w3 * smem[3u * hd + d2]; }\n if (4u < tile_len) { out_acc2 += w4 * smem[4u * hd + d2]; }\n if (5u < tile_len) { out_acc2 += w5 * smem[5u * hd + d2]; }\n if (6u < tile_len) { out_acc2 += w6 * smem[6u * hd + d2]; }\n if (7u < tile_len) { out_acc2 += w7 * smem[7u * hd + d2]; }\n if (8u < tile_len) { out_acc2 += w8 * smem[8u * hd + d2]; }\n if (9u < tile_len) { out_acc2 += w9 * smem[9u * hd + d2]; }\n if (10u < tile_len) { out_acc2 += w10 * smem[10u * hd + d2]; }\n if (11u < tile_len) { out_acc2 += w11 * smem[11u * hd + d2]; }\n if (12u < tile_len) { out_acc2 += w12 * smem[12u * hd + d2]; }\n if (13u < tile_len) { out_acc2 += w13 * smem[13u * hd + d2]; }\n if (14u < tile_len) { out_acc2 += w14 * smem[14u * hd + d2]; }\n if (15u < tile_len) { out_acc2 += w15 * smem[15u * hd + d2]; }\n }\n\n running_sum = running_sum * old_correction + tile_sum;\n running_max = new_max;\n workgroupBarrier();\n }\n\n // ── Write normalized output ──\n var inv_sum: f32 = 0.0;\n if (running_sum > 0.0) { inv_sum = 1.0 / running_sum; }\n let out_base = q_pos * q_stride + q_head * hd;\n if (tid < hd) {\n output[out_base + tid] = out_acc * inv_sum;\n }\n if (d2 < hd) {\n output[out_base + d2] = out_acc2 * inv_sum;\n }\n}\n`;\n\nconst WGSL_SOFTMAX = `\\\n// Row-wise softmax: output[i] = exp(input[i] - max) / sum(exp(input - max))\n\nstruct Params {\n num_rows: u32,\n row_size: u32,\n}\n\n@group(0) @binding(0) var<storage, read> input: array<f32>;\n@group(0) @binding(1) var<storage, read_write> output: array<f32>;\n@group(0) @binding(2) var<storage, read> params: Params;\n\nvar<workgroup> shared_data: array<f32, 256>;\n\n@compute @workgroup_size(256)\nfn main(\n @builtin(local_invocation_id) lid: vec3u,\n @builtin(workgroup_id) wid: vec3u,\n) {\n let row = wid.x;\n if (row >= params.num_rows) { return; }\n\n let tid = lid.x;\n let row_offset = row * params.row_size;\n\n var local_max: f32 = -1e30;\n var i = tid;\n while (i < params.row_size) {\n local_max = max(local_max, input[row_offset + i]);\n i += 256u;\n }\n shared_data[tid] = local_max;\n workgroupBarrier();\n\n var stride: u32 = 128u;\n while (stride > 0u) {\n if (tid < stride) {\n shared_data[tid] = max(shared_data[tid], shared_data[tid + stride]);\n }\n workgroupBarrier();\n stride /= 2u;\n }\n let row_max = shared_data[0];\n workgroupBarrier();\n\n var local_sum: f32 = 0.0;\n i = tid;\n while (i < params.row_size) {\n local_sum += exp(max(input[row_offset + i] - row_max, -80.0));\n i += 256u;\n }\n shared_data[tid] = local_sum;\n workgroupBarrier();\n\n stride = 128u;\n while (stride > 0u) {\n if (tid < stride) {\n shared_data[tid] += shared_data[tid + stride];\n }\n workgroupBarrier();\n stride /= 2u;\n }\n let inv_sum = 1.0 / shared_data[0];\n workgroupBarrier();\n\n i = tid;\n while (i < params.row_size) {\n output[row_offset + i] = exp(max(input[row_offset + i] - row_max, -80.0)) * inv_sum;\n i += 256u;\n }\n}\n`;\n\nconst WGSL_SILU = `\\\n// SiLU (Sigmoid Linear Unit): output = x * sigmoid(x) = x / (1 + exp(-x))\n\nstruct Params {\n count: u32,\n}\n\n@group(0) @binding(0) var<storage, read> input: array<f32>;\n@group(0) @binding(1) var<storage, read_write> output: array<f32>;\n@group(0) @binding(2) var<storage, read> params: Params;\n\n@compute @workgroup_size(256)\nfn main(@builtin(global_invocation_id) gid: vec3u) {\n let idx = gid.x;\n if (idx >= params.count) { return; }\n\n let x = input[idx];\n output[idx] = x / (1.0 + exp(max(-x, -80.0)));\n}\n`;\n\nconst WGSL_GELU = `\\\n// GELU (Gaussian Error Linear Unit) - approximate version\n// output = 0.5 * x * (1 + tanh(sqrt(2/pi) * (x + 0.044715 * x^3)))\n\nstruct Params {\n count: u32,\n}\n\n@group(0) @binding(0) var<storage, read> input: array<f32>;\n@group(0) @binding(1) var<storage, read_write> output: array<f32>;\n@group(0) @binding(2) var<storage, read> params: Params;\n\nconst SQRT_2_OVER_PI: f32 = 0.7978845608;\nconst GELU_COEFF: f32 = 0.044715;\n\n@compute @workgroup_size(256)\nfn main(@builtin(global_invocation_id) gid: vec3u) {\n let idx = gid.x;\n if (idx >= params.count) { return; }\n\n let x = input[idx];\n // Clamp the tanh argument: tanh saturates to ±1 by |arg|≈10, but on Metal/Dawn\n // tanh() of a large argument returns NaN (the internal exp overflows). The ViT\n // MLP can produce |x|>30 → x^3 → arg in the thousands, so clamp to a safe ±15.\n let inner = clamp(SQRT_2_OVER_PI * (x + GELU_COEFF * x * x * x), -15.0, 15.0);\n output[idx] = 0.5 * x * (1.0 + tanh(inner));\n}\n`;\n\nconst WGSL_ADD = `\\\n// Element-wise addition: output = a + b\n\nstruct Params {\n count: u32,\n}\n\n@group(0) @binding(0) var<storage, read> a: array<f32>;\n@group(0) @binding(1) var<storage, read> b: array<f32>;\n@group(0) @binding(2) var<storage, read_write> output: array<f32>;\n@group(0) @binding(3) var<storage, read> params: Params;\n\n@compute @workgroup_size(256)\nfn main(@builtin(global_invocation_id) gid: vec3u) {\n let idx = gid.x;\n if (idx >= params.count) { return; }\n output[idx] = a[idx] + b[idx];\n}\n`;\n\nconst WGSL_MUL = `\\\n// Element-wise multiplication: output = a * b\n\nstruct Params {\n count: u32,\n}\n\n@group(0) @binding(0) var<storage, read> a: array<f32>;\n@group(0) @binding(1) var<storage, read> b: array<f32>;\n@group(0) @binding(2) var<storage, read_write> output: array<f32>;\n@group(0) @binding(3) var<storage, read> params: Params;\n\n@compute @workgroup_size(256)\nfn main(@builtin(global_invocation_id) gid: vec3u) {\n let idx = gid.x;\n if (idx >= params.count) { return; }\n output[idx] = a[idx] * b[idx];\n}\n`;\n\nconst WGSL_SLICE_LAST_ROW = `\\\n// Copy the last row of a [T, width] tensor into a [1, width] tensor.\n// Lets lm_head run with M=1, so the logits buffer is [1, vocab] instead\n// of [T, vocab] (485MB at T=512 for a 248k vocab).\n\nstruct Params {\n width: u32,\n last_row_offset: u32,\n}\n\n@group(0) @binding(0) var<storage, read> src: array<f32>;\n@group(0) @binding(1) var<storage, read_write> output: array<f32>;\n@group(0) @binding(2) var<storage, read> params: Params;\n\n@compute @workgroup_size(256)\nfn main(@builtin(global_invocation_id) gid: vec3u) {\n let idx = gid.x;\n if (idx >= params.width) { return; }\n output[idx] = src[params.last_row_offset + idx];\n}\n`;\n\nconst WGSL_MEAN_POOL = `\\\n// Mean-pool a [T, width] tensor over the T tokens into a [1, width] tensor:\n// output[c] = (1/T) * sum_{t=0..T-1} src[t * width + c]\n// Used for bidirectional encoder embeddings (EmbeddingGemma) where the sentence\n// vector is the average of all token hidden states. We process a single unpadded\n// sequence, so every one of the T rows is a valid (non-pad) token — the mean is\n// over the full T with no attention-mask gather needed.\n// One thread per output channel.\n\nstruct Params {\n seq_len: u32,\n width: u32,\n}\n\n@group(0) @binding(0) var<storage, read> src: array<f32>;\n@group(0) @binding(1) var<storage, read_write> output: array<f32>;\n@group(0) @binding(2) var<storage, read> params: Params;\n\n@compute @workgroup_size(256)\nfn main(@builtin(global_invocation_id) gid: vec3u) {\n let c = gid.x;\n if (c >= params.width) { return; }\n var acc = 0.0;\n var t = 0u;\n loop {\n if (t >= params.seq_len) { break; }\n acc += src[t * params.width + c];\n t += 1u;\n }\n let inv_t = 1.0 / max(f32(params.seq_len), 1.0);\n output[c] = acc * inv_t;\n}\n`;\n\nconst WGSL_SCALE = `\\\n// Multiply every element of a tensor by a scalar constant:\n// output[i] = input[i] * scale\n// Used for Gemma's embedding normalizer (× sqrt(hidden_size)), which is applied\n// to the residual stream after the token embedding lookup.\n\nstruct Params {\n count: u32,\n scale_bits: u32,\n}\n\n@group(0) @binding(0) var<storage, read> input: array<f32>;\n@group(0) @binding(1) var<storage, read_write> output: array<f32>;\n@group(0) @binding(2) var<storage, read> params: Params;\n\n@compute @workgroup_size(256)\nfn main(@builtin(global_invocation_id) gid: vec3u) {\n let idx = gid.x;\n if (idx >= params.count) { return; }\n output[idx] = input[idx] * bitcast<f32>(params.scale_bits);\n}\n`;\n\n// ── Softcap (Gemma final logit softcapping) ──\n// output[i] = cap * tanh(input[i] / cap)\n// Squashes logits into (-cap, cap). Mobile-safe: tanh is a WGSL builtin, no\n// select(), no exp (tanh is bounded), no f16. The divide-by-cap argument to tanh\n// is finite for real logits, so no clamping is required.\nconst WGSL_SOFTCAP = `\\\nstruct Params {\n count: u32,\n cap_bits: u32,\n}\n\n@group(0) @binding(0) var<storage, read> input: array<f32>;\n@group(0) @binding(1) var<storage, read_write> output: array<f32>;\n@group(0) @binding(2) var<storage, read> params: Params;\n\n@compute @workgroup_size(256)\nfn main(@builtin(global_invocation_id) gid: vec3u) {\n let idx = gid.x;\n if (idx >= params.count) { return; }\n let cap = bitcast<f32>(params.cap_bits);\n output[idx] = cap * tanh(input[idx] / cap);\n}\n`;\n\n// (SliceCols WGSL + spec already defined above for the ViT qkv split; Gemma 4\n// PLE reuses it for the per-layer-input column slice.)\n\nconst WGSL_L2NORM = `\\\n// L2-normalize each row of a [rows, width] tensor.\n// embedding[r, :] = x[r, :] / max(sqrt(sum(x[r, :]^2)), eps)\n// One workgroup per row; threads cooperatively reduce the sum of squares.\n\nstruct Params {\n rows: u32,\n width: u32,\n}\n\n@group(0) @binding(0) var<storage, read> src: array<f32>;\n@group(0) @binding(1) var<storage, read_write> output: array<f32>;\n@group(0) @binding(2) var<storage, read> params: Params;\n\nvar<workgroup> partial: array<f32, 256>;\n\n@compute @workgroup_size(256)\nfn main(\n @builtin(workgroup_id) wid: vec3u,\n @builtin(local_invocation_id) lid: vec3u,\n) {\n let row = wid.x;\n if (row >= params.rows) { return; }\n let tid = lid.x;\n let base = row * params.width;\n\n // Each thread accumulates a strided slice of the row.\n var acc = 0.0;\n var i = tid;\n loop {\n if (i >= params.width) { break; }\n let v = src[base + i];\n acc += v * v;\n i += 256u;\n }\n partial[tid] = acc;\n workgroupBarrier();\n\n // Tree reduction over the 256 partials.\n var stride = 128u;\n loop {\n if (stride == 0u) { break; }\n if (tid < stride) {\n partial[tid] += partial[tid + stride];\n }\n workgroupBarrier();\n stride = stride >> 1u;\n }\n\n // inv_norm = 1 / max(sqrt(sumsq), eps)\n let inv_norm = 1.0 / max(sqrt(partial[0]), 1e-12);\n workgroupBarrier();\n\n var j = tid;\n loop {\n if (j >= params.width) { break; }\n output[base + j] = src[base + j] * inv_norm;\n j += 256u;\n }\n}\n`;\n\nconst WGSL_ADD_BIAS = `\\\n// Row-broadcast bias add: output[r * width + c] = input[r * width + c] + bias[c].\n// Used by the ViT linear layers (qkv/proj/fc1/fc2/patch_embed) whose MatMul has\n// no bias term — the bias vector (length = width) is added across all rows.\n\nstruct Params {\n count: u32,\n width: u32,\n}\n\n@group(0) @binding(0) var<storage, read> input: array<f32>;\n@group(0) @binding(1) var<storage, read> bias: array<f32>;\n@group(0) @binding(2) var<storage, read_write> output: array<f32>;\n@group(0) @binding(3) var<storage, read> params: Params;\n\n@compute @workgroup_size(256)\nfn main(@builtin(global_invocation_id) gid: vec3u) {\n let idx = gid.x;\n if (idx >= params.count) { return; }\n let c = idx % params.width;\n output[idx] = input[idx] + bias[c];\n}\n`;\n\nconst WGSL_GELU_ERF = `\\\n// Exact GELU (erf form): output = 0.5 * x * (1 + erf(x / sqrt(2))).\n// The ViT merger uses nn.GELU() (exact), unlike the block MLP which uses the\n// tanh approximation. WGSL has no erf(), so we use a high-accuracy rational\n// approximation (Abramowitz & Stegun 7.1.26), giving < 1e-6 abs error.\n\nstruct Params {\n count: u32,\n}\n\n@group(0) @binding(0) var<storage, read> input: array<f32>;\n@group(0) @binding(1) var<storage, read_write> output: array<f32>;\n@group(0) @binding(2) var<storage, read> params: Params;\n\nconst INV_SQRT2: f32 = 0.7071067811865476;\n\nfn erf_approx(x: f32) -> f32 {\n // A&S 7.1.26: erf(x) for x >= 0; odd-extended for x < 0.\n // if/else instead of select() — Safari/Metal has select() bugs.\n var sign = 1.0;\n if (x < 0.0) { sign = -1.0; }\n let ax = abs(x);\n let t = 1.0 / (1.0 + 0.3275911 * ax);\n let y = 1.0 - (((((1.061405429 * t - 1.453152027) * t) + 1.421413741) * t - 0.284496736) * t + 0.254829592) * t * exp(-ax * ax);\n return sign * y;\n}\n\n@compute @workgroup_size(256)\nfn main(@builtin(global_invocation_id) gid: vec3u) {\n let idx = gid.x;\n if (idx >= params.count) { return; }\n let x = input[idx];\n output[idx] = 0.5 * x * (1.0 + erf_approx(x * INV_SQRT2));\n}\n`;\n\nconst WGSL_SLICE_COLS = `\\\n// Extract a contiguous column range from a [rows, in_width] tensor into a\n// [rows, out_width] tensor: output[r, c] = input[r, col_offset + c].\n// Used to split the fused ViT qkv projection [N, 3*768] into Q/K/V [N, 768].\n\nstruct Params {\n rows: u32,\n in_width: u32,\n out_width: u32,\n col_offset: u32,\n}\n\n@group(0) @binding(0) var<storage, read> input: array<f32>;\n@group(0) @binding(1) var<storage, read_write> output: array<f32>;\n@group(0) @binding(2) var<storage, read> params: Params;\n\n@compute @workgroup_size(256)\nfn main(@builtin(global_invocation_id) gid: vec3u) {\n let idx = gid.x;\n let total = params.rows * params.out_width;\n if (idx >= total) { return; }\n let r = idx / params.out_width;\n let c = idx % params.out_width;\n output[idx] = input[r * params.in_width + params.col_offset + c];\n}\n`;\n\nconst WGSL_MUL_COLS = `\\\n// Multiply two contiguous column ranges of a single [rows, in_width] tensor:\n// output[r, c] = src[r, off_a + c] * src[r, off_b + c]\n// LFM2 short-conv pre-gate: Bx = B * x where B and x are column slices of the\n// fused in_proj output — fuses two SliceCols + a Mul into one dispatch.\n\nstruct Params {\n rows: u32,\n in_width: u32,\n out_width: u32,\n off_a: u32,\n off_b: u32,\n _pad0: u32,\n _pad1: u32,\n _pad2: u32,\n}\n\n@group(0) @binding(0) var<storage, read> src: array<f32>;\n@group(0) @binding(1) var<storage, read_write> output: array<f32>;\n@group(0) @binding(2) var<storage, read> params: Params;\n\n@compute @workgroup_size(256)\nfn main(@builtin(global_invocation_id) gid: vec3u) {\n let idx = gid.x;\n let total = params.rows * params.out_width;\n if (idx >= total) { return; }\n let r = idx / params.out_width;\n let c = idx % params.out_width;\n let base = r * params.in_width;\n output[idx] = src[base + params.off_a + c] * src[base + params.off_b + c];\n}\n`;\n\nconst WGSL_APPLY_ROTARY = `\\\n// Apply precomputed rotary embeddings to a [T, num_heads*head_dim] tensor.\n// rotate_half convention (matches HF apply_rotary_pos_emb_vision):\n// out = x * cos + rotate_half(x) * sin\n// rotate_half(x) = concat(-x[half:], x[:half]) over each head's head_dim.\n// cos/sin are [T, head_dim], broadcast across all heads of a row.\n// In-place safe is NOT assumed — writes to a separate output buffer.\n\nstruct Params {\n seq_len: u32,\n num_heads: u32,\n head_dim: u32,\n _pad: u32,\n}\n\n@group(0) @binding(0) var<storage, read> x: array<f32>;\n@group(0) @binding(1) var<storage, read> cos_t: array<f32>;\n@group(0) @binding(2) var<storage, read> sin_t: array<f32>;\n@group(0) @binding(3) var<storage, read_write> output: array<f32>;\n@group(0) @binding(4) var<storage, read> params: Params;\n\n@compute @workgroup_size(256)\nfn main(@builtin(global_invocation_id) gid: vec3u) {\n let idx = gid.x;\n let hd = params.head_dim;\n let total = params.seq_len * params.num_heads * hd;\n if (idx >= total) { return; }\n\n let d = idx % hd; // dim within head\n let row = idx / (params.num_heads * hd); // token position (for cos/sin lookup)\n let half = hd / 2u;\n\n let xv = x[idx];\n // rotate_half: for d < half → pairs with (d+half) as -x2; for d >= half → x1\n var rot: f32;\n if (d < half) {\n rot = -x[idx + half];\n } else {\n rot = x[idx - half];\n }\n\n let cs = cos_t[row * hd + d];\n let sn = sin_t[row * hd + d];\n output[idx] = xv * cs + rot * sn;\n}\n`;\n\nconst WGSL_MROPE = `\\\n// Multimodal RoPE (M-RoPE) with partial rotation, rotate_half convention.\n// Identical math to WGSL_ROPE but the per-token angles come from host-precomputed\n// cos/sin tables [seq, rope_dim] (built from 3D position ids) instead of pos*inv_freq.\n// Only the first rope_dim of each head is rotated; the rest passes through.\n// For text-only this is fed linear-position cos/sin and reduces to standard 1D RoPE.\n//\n// Q: [T, num_q_heads*head_dim] K: [T, num_kv_heads*head_dim]\n// cos/sin: [seq, rope_dim] (rope_dim == 2*half_rope; emb=cat(freqs,freqs))\n\nstruct Params {\n seq_len: u32,\n num_q_heads: u32,\n num_kv_heads: u32,\n head_dim: u32,\n rope_dim: u32,\n pos_offset: u32, // row offset into cos/sin for decode (single-token steps)\n _pad0: u32,\n _pad1: u32,\n}\n\n@group(0) @binding(0) var<storage, read_write> q: array<f32>;\n@group(0) @binding(1) var<storage, read_write> k: array<f32>;\n@group(0) @binding(2) var<storage, read> cos_t: array<f32>;\n@group(0) @binding(3) var<storage, read> sin_t: array<f32>;\n@group(0) @binding(4) var<storage, read> params: Params;\n\n@compute @workgroup_size(256)\nfn main(@builtin(global_invocation_id) gid: vec3u) {\n let half_rope = params.rope_dim / 2u;\n let total_q_pairs = params.seq_len * params.num_q_heads * half_rope;\n let total_k_pairs = params.seq_len * params.num_kv_heads * half_rope;\n let idx = gid.x;\n let rd = params.rope_dim;\n\n if (idx < total_q_pairs) {\n let pos = idx / (params.num_q_heads * half_rope);\n let remainder = idx % (params.num_q_heads * half_rope);\n let head = remainder / half_rope;\n let pair_idx = remainder % half_rope;\n\n let cos_row = (pos + params.pos_offset) * rd;\n let cs = cos_t[cos_row + pair_idx];\n let sn = sin_t[cos_row + pair_idx];\n\n let q_stride = params.num_q_heads * params.head_dim;\n let head_base = pos * q_stride + head * params.head_dim;\n let lo = head_base + pair_idx;\n let hi = head_base + pair_idx + half_rope;\n let q_lo = q[lo];\n let q_hi = q[hi];\n // rotate_half: out_lo = lo*cos - hi*sin ; out_hi = hi*cos + lo*sin\n q[lo] = q_lo * cs - q_hi * sn;\n q[hi] = q_hi * cs + q_lo * sn;\n }\n\n if (idx < total_k_pairs) {\n let pos = idx / (params.num_kv_heads * half_rope);\n let remainder = idx % (params.num_kv_heads * half_rope);\n let head = remainder / half_rope;\n let pair_idx = remainder % half_rope;\n\n let cos_row = (pos + params.pos_offset) * rd;\n let cs = cos_t[cos_row + pair_idx];\n let sn = sin_t[cos_row + pair_idx];\n\n let k_stride = params.num_kv_heads * params.head_dim;\n let head_base = pos * k_stride + head * params.head_dim;\n let lo = head_base + pair_idx;\n let hi = head_base + pair_idx + half_rope;\n let k_lo = k[lo];\n let k_hi = k[hi];\n k[lo] = k_lo * cs - k_hi * sn;\n k[hi] = k_hi * cs + k_lo * sn;\n }\n}\n`;\n\nconst WGSL_EMBED_SPLICE = `\\\n// Overwrite image-token rows of the text embeddings with merged vision tokens.\n// embed_out[row, c] = vision[map[row], c] when map[row] >= 0, else embed_in[row,c].\n// row_map: i32 per token; -1 for text rows, else the index into the vision buffer.\n// Writes to a separate output buffer (pooled-alias-safe).\n\nstruct Params {\n seq_len: u32,\n hidden: u32,\n}\n\n@group(0) @binding(0) var<storage, read> embed_in: array<f32>;\n@group(0) @binding(1) var<storage, read> vision: array<f32>;\n@group(0) @binding(2) var<storage, read> row_map: array<i32>;\n@group(0) @binding(3) var<storage, read_write> embed_out: array<f32>;\n@group(0) @binding(4) var<storage, read> params: Params;\n\n@compute @workgroup_size(256)\nfn main(@builtin(global_invocation_id) gid: vec3u) {\n let idx = gid.x;\n let total = params.seq_len * params.hidden;\n if (idx >= total) { return; }\n let row = idx / params.hidden;\n let col = idx % params.hidden;\n let vrow = row_map[row];\n if (vrow >= 0) {\n embed_out[idx] = vision[u32(vrow) * params.hidden + col];\n } else {\n embed_out[idx] = embed_in[idx];\n }\n}\n`;\n\nconst WGSL_SWIGLU = `\\\n// Fused SwiGLU: output = SiLU(gate) * up = (gate / (1 + exp(-gate))) * up\n// Saves 2 dispatches per MLP block (SiLU + Mul → single kernel).\n\nstruct Params {\n count: u32,\n}\n\n@group(0) @binding(0) var<storage, read> gate: array<f32>;\n@group(0) @binding(1) var<storage, read> up: array<f32>;\n@group(0) @binding(2) var<storage, read_write> output: array<f32>;\n@group(0) @binding(3) var<storage, read> params: Params;\n\n@compute @workgroup_size(256)\nfn main(@builtin(global_invocation_id) gid: vec3u) {\n let idx = gid.x;\n if (idx >= params.count) { return; }\n\n let g = gate[idx];\n output[idx] = (g / (1.0 + exp(max(-g, -80.0)))) * up[idx];\n}\n`;\n\nconst WGSL_SWIGLU_MATVEC = `\\\n// Fused gate+up projection + SwiGLU for M=1 decode.\n// output[d] = SiLU(A · B_gate[d,:]) * (A · B_up[d,:])\n// Reads input vector A once from L1 cache, computes both projections,\n// and applies SiLU gating in a single dispatch.\n// Saves 2 dispatches per MLP block vs separate gate_proj + up_proj + SwiGLU.\n\nconst N_TILE: u32 = 8u;\nconst K_THREADS: u32 = 32u;\n\nstruct Params {\n K: u32,\n N: u32,\n}\n\n@group(0) @binding(0) var<storage, read> A: array<f32>;\n@group(0) @binding(1) var<storage, read> B_gate: array<f32>;\n@group(0) @binding(2) var<storage, read> B_up: array<f32>;\n@group(0) @binding(3) var<storage, read_write> output: array<f32>;\n@group(0) @binding(4) var<storage, read> params: Params;\n\nvar<workgroup> shared_gate: array<f32, 256>;\nvar<workgroup> shared_up: array<f32, 256>;\n\n@compute @workgroup_size(256)\nfn main(\n @builtin(local_invocation_id) lid: vec3u,\n @builtin(workgroup_id) wid: vec3u,\n) {\n let tid = lid.x;\n let n_idx = tid / K_THREADS;\n let k_tid = tid % K_THREADS;\n let col = wid.x * N_TILE + n_idx;\n let K = params.K;\n\n var sum_gate: f32 = 0.0;\n var sum_up: f32 = 0.0;\n if (col < params.N) {\n let b_off = col * K;\n var k = k_tid * 4u;\n while (k + 3u < K) {\n let a_v = vec4f(A[k], A[k+1u], A[k+2u], A[k+3u]);\n sum_gate += dot(a_v, vec4f(B_gate[b_off+k], B_gate[b_off+k+1u], B_gate[b_off+k+2u], B_gate[b_off+k+3u]));\n sum_up += dot(a_v, vec4f(B_up[b_off+k], B_up[b_off+k+1u], B_up[b_off+k+2u], B_up[b_off+k+3u]));\n k += K_THREADS * 4u;\n }\n }\n\n shared_gate[tid] = sum_gate;\n shared_up[tid] = sum_up;\n workgroupBarrier();\n\n if (k_tid == 0u && col < params.N) {\n let base = n_idx * K_THREADS;\n var tg: f32 = shared_gate[base];\n var tu: f32 = shared_up[base];\n for (var i: u32 = 1u; i < K_THREADS; i++) {\n tg += shared_gate[base + i];\n tu += shared_up[base + i];\n }\n let g = tg;\n output[col] = (g / (1.0 + exp(max(-g, -80.0)))) * tu;\n }\n}\n`;\n\nconst WGSL_SWIGLU_MATVEC_INT4 = `\\\n// CONSTRAINT: N_TILE must equal workgroup_size / K_THREADS\nconst N_TILE: u32 = 8u;\nconst K_THREADS: u32 = 32u;\n\nstruct Params {\n K: u32,\n N: u32,\n group_size: u32,\n _pad: u32,\n}\n\n@group(0) @binding(0) var<storage, read> A: array<vec4f>;\n@group(0) @binding(1) var<storage, read> B_gate_q: array<vec4u>;\n@group(0) @binding(2) var<storage, read> B_gate_s: array<f32>;\n@group(0) @binding(3) var<storage, read> B_gate_z: array<f32>;\n@group(0) @binding(4) var<storage, read> B_up_q: array<vec4u>;\n@group(0) @binding(5) var<storage, read> B_up_s: array<f32>;\n@group(0) @binding(6) var<storage, read> B_up_z: array<f32>;\n@group(0) @binding(7) var<storage, read_write> output: array<f32>;\n@group(0) @binding(8) var<storage, read> params: Params;\n\nvar<workgroup> shared_gate: array<f32, 256>;\nvar<workgroup> shared_up: array<f32, 256>;\n\n@compute @workgroup_size(256)\nfn main(\n @builtin(local_invocation_id) lid: vec3u,\n @builtin(workgroup_id) wid: vec3u,\n) {\n let tid = lid.x;\n let n_idx = tid / K_THREADS;\n let k_tid = tid % K_THREADS;\n let col = wid.x * N_TILE + n_idx;\n let K = params.K;\n let k_vec = K / 32u;\n let groups_per_col = K / params.group_size;\n let packed_per_group = params.group_size / 8u;\n\n // vec4 loads: each thread reads one vec4u per matrix per iteration\n // (4 packed u32s = 32 INT4 weights). group_size (128) spans 16 packed u32s,\n // so a vec4u never straddles a quantization group.\n var sum_gate: f32 = 0.0;\n var sum_up: f32 = 0.0;\n if (col < params.N) {\n let b_off_v = col * k_vec;\n let col_g_base = col * groups_per_col;\n\n var v = k_tid;\n while (v < k_vec) {\n let group_idx = col_g_base + (v * 4u) / packed_per_group;\n let gq = B_gate_q[b_off_v + v];\n let g_scale = B_gate_s[group_idx];\n let g_zero = B_gate_z[group_idx];\n let uq = B_up_q[b_off_v + v];\n let u_scale = B_up_s[group_idx];\n let u_zero = B_up_z[group_idx];\n let a_base = v * 8u;\n\n for (var j: u32 = 0u; j < 4u; j++) {\n // Gate weight dequantization\n let g_packed = gq[j];\n let gn0 = f32(g_packed & 0xFu); let gn1 = f32((g_packed >> 4u) & 0xFu);\n let gn2 = f32((g_packed >> 8u) & 0xFu); let gn3 = f32((g_packed >> 12u) & 0xFu);\n let gn4 = f32((g_packed >> 16u) & 0xFu); let gn5 = f32((g_packed >> 20u) & 0xFu);\n let gn6 = f32((g_packed >> 24u) & 0xFu); let gn7 = f32((g_packed >> 28u) & 0xFu);\n let gb0 = vec4f(gn0 - g_zero, gn1 - g_zero, gn2 - g_zero, gn3 - g_zero) * g_scale;\n let gb1 = vec4f(gn4 - g_zero, gn5 - g_zero, gn6 - g_zero, gn7 - g_zero) * g_scale;\n\n // Up weight dequantization\n let u_packed = uq[j];\n let un0 = f32(u_packed & 0xFu); let un1 = f32((u_packed >> 4u) & 0xFu);\n let un2 = f32((u_packed >> 8u) & 0xFu); let un3 = f32((u_packed >> 12u) & 0xFu);\n let un4 = f32((u_packed >> 16u) & 0xFu); let un5 = f32((u_packed >> 20u) & 0xFu);\n let un6 = f32((u_packed >> 24u) & 0xFu); let un7 = f32((u_packed >> 28u) & 0xFu);\n let ub0 = vec4f(un0 - u_zero, un1 - u_zero, un2 - u_zero, un3 - u_zero) * u_scale;\n let ub1 = vec4f(un4 - u_zero, un5 - u_zero, un6 - u_zero, un7 - u_zero) * u_scale;\n\n let a0 = A[a_base + j * 2u];\n let a1 = A[a_base + j * 2u + 1u];\n\n sum_gate += dot(a0, gb0) + dot(a1, gb1);\n sum_up += dot(a0, ub0) + dot(a1, ub1);\n }\n\n v += K_THREADS;\n }\n }\n\n shared_gate[tid] = sum_gate;\n shared_up[tid] = sum_up;\n workgroupBarrier();\n\n if (k_tid == 0u && col < params.N) {\n let base = n_idx * K_THREADS;\n var tg: f32 = shared_gate[base];\n var tu: f32 = shared_up[base];\n for (var i: u32 = 1u; i < K_THREADS; i++) {\n tg += shared_gate[base + i];\n tu += shared_up[base + i];\n }\n let gg = tg;\n output[col] = (gg / (1.0 + exp(max(-gg, -80.0)))) * tu;\n }\n}\n`;\n\n// ── Dual MatVecInt4 (two independent INT4 projections sharing input A, M=1) ──\n//\n// out0[d] = A · dequant(B0[d,:]), out1[d] = A · dequant(B1[d,:])\n// Identical dequant + reduction to WGSL_SWIGLU_MATVEC_INT4, but writes the two\n// projection sums to SEPARATE output buffers instead of SiLU-combining them.\n// Used to fuse q_proj+gate_proj and k_proj+v_proj in full-attention decode:\n// both projections share the same input_layernorm activation and the same K/N,\n// so reading A once and computing both halves removes one full GPU round-trip\n// (one submit+drain on Safari/iOS) per fused pair.\nconst WGSL_DUAL_MATVEC_INT4 = `\\\nconst N_TILE: u32 = 8u;\nconst K_THREADS: u32 = 32u;\n\nstruct Params {\n K: u32,\n N: u32,\n group_size: u32,\n _pad: u32,\n}\n\n@group(0) @binding(0) var<storage, read> A: array<vec4f>;\n@group(0) @binding(1) var<storage, read> B0_q: array<vec4u>;\n@group(0) @binding(2) var<storage, read> B0_s: array<f32>;\n@group(0) @binding(3) var<storage, read> B0_z: array<f32>;\n@group(0) @binding(4) var<storage, read> B1_q: array<vec4u>;\n@group(0) @binding(5) var<storage, read> B1_s: array<f32>;\n@group(0) @binding(6) var<storage, read> B1_z: array<f32>;\n@group(0) @binding(7) var<storage, read_write> out0: array<f32>;\n@group(0) @binding(8) var<storage, read_write> out1: array<f32>;\n@group(0) @binding(9) var<storage, read> params: Params;\n\nvar<workgroup> shared_0: array<f32, 256>;\nvar<workgroup> shared_1: array<f32, 256>;\n\n@compute @workgroup_size(256)\nfn main(\n @builtin(local_invocation_id) lid: vec3u,\n @builtin(workgroup_id) wid: vec3u,\n) {\n let tid = lid.x;\n let n_idx = tid / K_THREADS;\n let k_tid = tid % K_THREADS;\n let col = wid.x * N_TILE + n_idx;\n let K = params.K;\n let k_vec = K / 32u;\n let groups_per_col = K / params.group_size;\n let packed_per_group = params.group_size / 8u;\n\n var sum_0: f32 = 0.0;\n var sum_1: f32 = 0.0;\n if (col < params.N) {\n let b_off_v = col * k_vec;\n let col_g_base = col * groups_per_col;\n\n var v = k_tid;\n while (v < k_vec) {\n let group_idx = col_g_base + (v * 4u) / packed_per_group;\n let q0 = B0_q[b_off_v + v];\n let s0 = B0_s[group_idx];\n let z0 = B0_z[group_idx];\n let q1 = B1_q[b_off_v + v];\n let s1 = B1_s[group_idx];\n let z1 = B1_z[group_idx];\n let a_base = v * 8u;\n\n for (var j: u32 = 0u; j < 4u; j++) {\n let p0 = q0[j];\n let a0n0 = f32(p0 & 0xFu); let a0n1 = f32((p0 >> 4u) & 0xFu);\n let a0n2 = f32((p0 >> 8u) & 0xFu); let a0n3 = f32((p0 >> 12u) & 0xFu);\n let a0n4 = f32((p0 >> 16u) & 0xFu); let a0n5 = f32((p0 >> 20u) & 0xFu);\n let a0n6 = f32((p0 >> 24u) & 0xFu); let a0n7 = f32((p0 >> 28u) & 0xFu);\n let b0a = vec4f(a0n0 - z0, a0n1 - z0, a0n2 - z0, a0n3 - z0) * s0;\n let b0b = vec4f(a0n4 - z0, a0n5 - z0, a0n6 - z0, a0n7 - z0) * s0;\n\n let p1 = q1[j];\n let a1n0 = f32(p1 & 0xFu); let a1n1 = f32((p1 >> 4u) & 0xFu);\n let a1n2 = f32((p1 >> 8u) & 0xFu); let a1n3 = f32((p1 >> 12u) & 0xFu);\n let a1n4 = f32((p1 >> 16u) & 0xFu); let a1n5 = f32((p1 >> 20u) & 0xFu);\n let a1n6 = f32((p1 >> 24u) & 0xFu); let a1n7 = f32((p1 >> 28u) & 0xFu);\n let b1a = vec4f(a1n0 - z1, a1n1 - z1, a1n2 - z1, a1n3 - z1) * s1;\n let b1b = vec4f(a1n4 - z1, a1n5 - z1, a1n6 - z1, a1n7 - z1) * s1;\n\n let av0 = A[a_base + j * 2u];\n let av1 = A[a_base + j * 2u + 1u];\n\n sum_0 += dot(av0, b0a) + dot(av1, b0b);\n sum_1 += dot(av0, b1a) + dot(av1, b1b);\n }\n\n v += K_THREADS;\n }\n }\n\n shared_0[tid] = sum_0;\n shared_1[tid] = sum_1;\n workgroupBarrier();\n\n if (k_tid == 0u && col < params.N) {\n let base = n_idx * K_THREADS;\n var t0: f32 = shared_0[base];\n var t1: f32 = shared_1[base];\n for (var i: u32 = 1u; i < K_THREADS; i++) {\n t0 += shared_0[base + i];\n t1 += shared_1[base + i];\n }\n out0[col] = t0;\n out1[col] = t1;\n }\n}\n`;\n\nconst WGSL_RESIDUAL_RMSNORM = `\\\n// Fused residual add + RMS normalization.\n// sum_output = a + b (residual sum, kept for downstream residual)\n// norm_output = RMSNorm(a + b) * weight\n\nstruct Params {\n seq_len: u32,\n hidden_size: u32,\n eps_bits: u32,\n _pad: u32,\n}\n\n@group(0) @binding(0) var<storage, read> a: array<f32>;\n@group(0) @binding(1) var<storage, read> b: array<f32>;\n@group(0) @binding(2) var<storage, read> weight: array<f32>;\n@group(0) @binding(3) var<storage, read_write> sum_output: array<f32>;\n@group(0) @binding(4) var<storage, read_write> norm_output: array<f32>;\n@group(0) @binding(5) var<storage, read> params: Params;\n\nvar<workgroup> shared_sum: array<f32, 256>;\n\n@compute @workgroup_size(256)\nfn main(\n @builtin(local_invocation_id) lid: vec3u,\n @builtin(workgroup_id) wid: vec3u,\n) {\n let row = wid.x;\n if (row >= params.seq_len) { return; }\n\n let tid = lid.x;\n let row_offset = row * params.hidden_size;\n let eps = bitcast<f32>(params.eps_bits);\n\n // Pass 1: add residual, store sum, accumulate sum-of-squares\n var local_sum: f32 = 0.0;\n var i = tid;\n while (i < params.hidden_size) {\n let val = a[row_offset + i] + b[row_offset + i];\n sum_output[row_offset + i] = val;\n local_sum += val * val;\n i += 256u;\n }\n shared_sum[tid] = local_sum;\n workgroupBarrier();\n\n // Tree reduction\n var stride: u32 = 128u;\n while (stride > 0u) {\n if (tid < stride) {\n shared_sum[tid] += shared_sum[tid + stride];\n }\n workgroupBarrier();\n stride = stride / 2u;\n }\n\n let rms = sqrt(shared_sum[0] / f32(params.hidden_size) + eps);\n\n // Pass 2: normalize and scale\n i = tid;\n while (i < params.hidden_size) {\n let val = sum_output[row_offset + i];\n norm_output[row_offset + i] = (val / rms) * weight[i];\n i += 256u;\n }\n}\n`;\n\nconst WGSL_CAUSAL_CONV1D = `\\\n// Causal 1D depthwise convolution with state buffer for autoregressive decode.\n// conv_state stores the last (kernel_size-1) input timesteps from prior forward passes.\n\nstruct Params {\n seq_len: u32,\n channels: u32,\n kernel_size: u32,\n _pad: u32,\n}\n\n@group(0) @binding(0) var<storage, read> input: array<f32>;\n@group(0) @binding(1) var<storage, read> weight: array<f32>;\n@group(0) @binding(2) var<storage, read> conv_state: array<f32>;\n@group(0) @binding(3) var<storage, read_write> output: array<f32>;\n@group(0) @binding(4) var<storage, read> params: Params;\n\n@compute @workgroup_size(256)\nfn main(@builtin(global_invocation_id) gid: vec3u) {\n let idx = gid.x;\n let total = params.seq_len * params.channels;\n if (idx >= total) { return; }\n\n let t = idx / params.channels;\n let c = idx % params.channels;\n let state_size = params.kernel_size - 1u;\n\n var sum: f32 = 0.0;\n for (var k: u32 = 0u; k < params.kernel_size; k = k + 1u) {\n let src_t = i32(t) - i32(k);\n var in_val: f32 = 0.0;\n if (src_t >= 0) {\n in_val = input[u32(src_t) * params.channels + c];\n } else {\n // Read from state: state[0]=oldest, state[state_size-1]=most recent\n let state_t = i32(state_size) + src_t;\n if (state_t >= 0) {\n in_val = conv_state[u32(state_t) * params.channels + c];\n }\n }\n // PyTorch Conv1d stores weights reversed: w[0]=most distant, w[K-1]=current.\n // Our loop looks back k steps, so we need w[K-1-k] to match PyTorch.\n let w_val = weight[c * params.kernel_size + (params.kernel_size - 1u - k)];\n sum += in_val * w_val;\n }\n\n output[t * params.channels + c] = sum;\n}\n`;\n\nconst WGSL_CAUSAL_CONV1D_SILU = `\\\n// Causal 1D depthwise convolution with fused SiLU activation on the output.\n// Identical to WGSL_CAUSAL_CONV1D but applies output = silu(sum), eliminating a\n// separate SiLU dispatch + a full read/write of the conv output (Qwen3.5 linear\n// attention: F.silu(conv1d(x))).\n\nstruct Params {\n seq_len: u32,\n channels: u32,\n kernel_size: u32,\n _pad: u32,\n}\n\n@group(0) @binding(0) var<storage, read> input: array<f32>;\n@group(0) @binding(1) var<storage, read> weight: array<f32>;\n@group(0) @binding(2) var<storage, read> conv_state: array<f32>;\n@group(0) @binding(3) var<storage, read_write> output: array<f32>;\n@group(0) @binding(4) var<storage, read> params: Params;\n\n@compute @workgroup_size(256)\nfn main(@builtin(global_invocation_id) gid: vec3u) {\n let idx = gid.x;\n let total = params.seq_len * params.channels;\n if (idx >= total) { return; }\n\n let t = idx / params.channels;\n let c = idx % params.channels;\n let state_size = params.kernel_size - 1u;\n\n var sum: f32 = 0.0;\n for (var k: u32 = 0u; k < params.kernel_size; k = k + 1u) {\n let src_t = i32(t) - i32(k);\n var in_val: f32 = 0.0;\n if (src_t >= 0) {\n in_val = input[u32(src_t) * params.channels + c];\n } else {\n let state_t = i32(state_size) + src_t;\n if (state_t >= 0) {\n in_val = conv_state[u32(state_t) * params.channels + c];\n }\n }\n let w_val = weight[c * params.kernel_size + (params.kernel_size - 1u - k)];\n sum += in_val * w_val;\n }\n\n // Fused SiLU: silu(x) = x / (1 + exp(-x))\n output[t * params.channels + c] = sum / (1.0 + exp(max(-sum, -80.0)));\n}\n`;\n\nconst WGSL_CAUSAL_CONV1D_GATED = `\\\n// Causal 1D depthwise convolution with a fused multiplicative output gate:\n// output = gate * conv(input)\n// LFM2 short-conv: y = C * depthwise_conv(Bx). Fusing the C-gate into the conv\n// eliminates a separate Mul dispatch + a conv_dim-wide read/write per layer.\n\nstruct Params {\n seq_len: u32,\n channels: u32,\n kernel_size: u32,\n _pad: u32,\n}\n\n@group(0) @binding(0) var<storage, read> input: array<f32>;\n@group(0) @binding(1) var<storage, read> weight: array<f32>;\n@group(0) @binding(2) var<storage, read> conv_state: array<f32>;\n@group(0) @binding(3) var<storage, read> gate: array<f32>;\n@group(0) @binding(4) var<storage, read_write> output: array<f32>;\n@group(0) @binding(5) var<storage, read> params: Params;\n\n@compute @workgroup_size(256)\nfn main(@builtin(global_invocation_id) gid: vec3u) {\n let idx = gid.x;\n let total = params.seq_len * params.channels;\n if (idx >= total) { return; }\n\n let t = idx / params.channels;\n let c = idx % params.channels;\n let state_size = params.kernel_size - 1u;\n\n var sum: f32 = 0.0;\n for (var k: u32 = 0u; k < params.kernel_size; k = k + 1u) {\n let src_t = i32(t) - i32(k);\n var in_val: f32 = 0.0;\n if (src_t >= 0) {\n in_val = input[u32(src_t) * params.channels + c];\n } else {\n let state_t = i32(state_size) + src_t;\n if (state_t >= 0) {\n in_val = conv_state[u32(state_t) * params.channels + c];\n }\n }\n let w_val = weight[c * params.kernel_size + (params.kernel_size - 1u - k)];\n sum += in_val * w_val;\n }\n\n output[t * params.channels + c] = gate[idx] * sum;\n}\n`;\n\nconst WGSL_SIGMOID_GATE = `\\\n// Sigmoid gate: output = x * sigmoid(gate)\n\nstruct Params {\n count: u32,\n}\n\n@group(0) @binding(0) var<storage, read> x: array<f32>;\n@group(0) @binding(1) var<storage, read> gate: array<f32>;\n@group(0) @binding(2) var<storage, read_write> output: array<f32>;\n@group(0) @binding(3) var<storage, read> params: Params;\n\n@compute @workgroup_size(256)\nfn main(@builtin(global_invocation_id) gid: vec3u) {\n let idx = gid.x;\n if (idx >= params.count) { return; }\n\n let g = gate[idx];\n let sig = 1.0 / (1.0 + exp(max(-g, -80.0)));\n output[idx] = x[idx] * sig;\n}\n`;\n\nconst WGSL_MAMBA_SSM = `\\\n// Gated Delta Net recurrence (Qwen3.5 linear attention).\n//\n// For each head, maintains state [key_dim, val_dim] across timesteps.\n// Recurrence per timestep:\n// g = -exp(A_log) * softplus(a + dt_bias) (log-space decay)\n// decay = exp(g) (actual decay in (0,1))\n// state *= decay (decay state)\n// q_norm, k_norm = L2_normalize(q), L2_normalize(k)\n// kv_mem = state^T @ k_norm (retrieve from memory)\n// delta = (v - kv_mem) * sigmoid(b) (delta rule)\n// state += k_norm @ delta^T (rank-1 update)\n// output = state^T @ q_norm / sqrt(key_dim) (query output)\n\nstruct Params {\n T: u32,\n num_heads: u32,\n key_dim: u32,\n val_dim: u32,\n qkv_dim: u32,\n _pad1: u32,\n _pad2: u32,\n _pad3: u32,\n}\n\n@group(0) @binding(0) var<storage, read> qkv_conv: array<f32>;\n@group(0) @binding(1) var<storage, read> a_input: array<f32>;\n@group(0) @binding(2) var<storage, read> b_input: array<f32>;\n@group(0) @binding(3) var<storage, read> A_log: array<f32>;\n@group(0) @binding(4) var<storage, read> dt_bias: array<f32>;\n@group(0) @binding(5) var<storage, read_write> ssm_state: array<f32>;\n@group(0) @binding(6) var<storage, read_write> output: array<f32>;\n@group(0) @binding(7) var<storage, read> params: Params;\n\n// Shared memory for Q and K vectors (L2-normalized) and reduction\nvar<workgroup> shared_q: array<f32, 128>;\nvar<workgroup> shared_k: array<f32, 128>;\nvar<workgroup> shared_norm: array<f32, 128>;\n\n@compute @workgroup_size(128)\nfn main(\n @builtin(local_invocation_id) lid: vec3u,\n @builtin(workgroup_id) wid: vec3u,\n) {\n let h = wid.x; // uniform within workgroup\n if (h >= params.num_heads) { return; }\n\n let d = lid.x; // 0..127, all threads participate in barriers\n\n let A_val = -exp(A_log[h]);\n let state_base = h * params.key_dim * params.val_dim;\n let scale = 1.0 / sqrt(f32(params.key_dim));\n\n let q_off = h * params.key_dim;\n let k_off = params.num_heads * params.key_dim + h * params.key_dim;\n let v_off = 2u * params.num_heads * params.key_dim + h * params.val_dim;\n let out_stride = params.num_heads * params.val_dim;\n\n for (var t: u32 = 0u; t < params.T; t = t + 1u) {\n let qkv_base = t * params.qkv_dim;\n\n // ── Load Q and K into shared memory ──\n shared_q[d] = qkv_conv[qkv_base + q_off + d];\n shared_k[d] = qkv_conv[qkv_base + k_off + d];\n workgroupBarrier();\n\n // ── L2-normalize Q (parallel reduction) ──\n shared_norm[d] = shared_q[d] * shared_q[d];\n workgroupBarrier();\n var stride: u32 = 64u;\n while (stride > 0u) {\n if (d < stride) { shared_norm[d] += shared_norm[d + stride]; }\n workgroupBarrier();\n stride /= 2u;\n }\n let q_inv_norm = 1.0 / sqrt(shared_norm[0] + 1e-12);\n shared_q[d] *= q_inv_norm;\n\n // ── L2-normalize K (parallel reduction) ──\n shared_norm[d] = shared_k[d] * shared_k[d];\n workgroupBarrier();\n stride = 64u;\n while (stride > 0u) {\n if (d < stride) { shared_norm[d] += shared_norm[d + stride]; }\n workgroupBarrier();\n stride /= 2u;\n }\n let k_inv_norm = 1.0 / sqrt(shared_norm[0] + 1e-12);\n shared_k[d] *= k_inv_norm;\n workgroupBarrier();\n\n // ── Compute decay and beta ──\n let a_val = a_input[t * params.num_heads + h];\n let b_val = b_input[t * params.num_heads + h];\n let dt_in = a_val + dt_bias[h];\n let sp = log(1.0 + exp(max(dt_in, -80.0)));\n let g = A_val * sp;\n let decay = exp(max(g, -80.0));\n let beta = 1.0 / (1.0 + exp(max(-b_val, -80.0)));\n\n // ── 1+2. Retrieve from memory (decay is a per-step scalar, so it factors\n // out of the dot product): kv_mem[d] = decay * Σ_k state[k,d] * k_norm[k] ──\n let v_val = qkv_conv[qkv_base + v_off + d];\n var kv_dot: f32 = 0.0;\n for (var k: u32 = 0u; k < params.key_dim; k = k + 1u) {\n kv_dot += ssm_state[state_base + k * params.val_dim + d] * shared_k[k];\n }\n let kv_mem = kv_dot * decay;\n\n // ── 3. Delta rule: delta[d] = (v[d] - kv_mem[d]) * beta ──\n let delta_val = (v_val - kv_mem) * beta;\n\n // ── 4+5. Fused decay + rank-1 update + output in ONE pass over state\n // (one read + one write instead of three reads + two writes):\n // state'[k,d] = state[k,d] * decay + k_norm[k] * delta[d]\n // out[d] = Σ_k state'[k,d] * q_norm[k] * scale ──\n var out_val: f32 = 0.0;\n for (var k: u32 = 0u; k < params.key_dim; k = k + 1u) {\n let idx = state_base + k * params.val_dim + d;\n let s = ssm_state[idx] * decay + shared_k[k] * delta_val;\n ssm_state[idx] = s;\n out_val += s * shared_q[k];\n }\n output[t * out_stride + h * params.val_dim + d] = out_val * scale;\n\n workgroupBarrier(); // sync before next timestep loads shared_q/k\n }\n}\n`;\n\nconst WGSL_MAMBA_SSM_F16 = `\\\n// Gated Delta Net recurrence with f16 SSM state storage (Dawn only).\n// Compute stays f32; only the persistent state reads/writes are half-precision,\n// halving the dominant state bandwidth. Buffer is allocated as f32-sized and\n// reinterpreted (extra capacity unused).\n//\n// For each head, maintains state [key_dim, val_dim] across timesteps.\n// Recurrence per timestep:\n// g = -exp(A_log) * softplus(a + dt_bias) (log-space decay)\n// decay = exp(g) (actual decay in (0,1))\n// state *= decay (decay state)\n// q_norm, k_norm = L2_normalize(q), L2_normalize(k)\n// kv_mem = state^T @ k_norm (retrieve from memory)\n// delta = (v - kv_mem) * sigmoid(b) (delta rule)\n// state += k_norm @ delta^T (rank-1 update)\n// output = state^T @ q_norm / sqrt(key_dim) (query output)\n\nstruct Params {\n T: u32,\n num_heads: u32,\n key_dim: u32,\n val_dim: u32,\n qkv_dim: u32,\n _pad1: u32,\n _pad2: u32,\n _pad3: u32,\n}\n\n@group(0) @binding(0) var<storage, read> qkv_conv: array<f32>;\n@group(0) @binding(1) var<storage, read> a_input: array<f32>;\n@group(0) @binding(2) var<storage, read> b_input: array<f32>;\n@group(0) @binding(3) var<storage, read> A_log: array<f32>;\n@group(0) @binding(4) var<storage, read> dt_bias: array<f32>;\n@group(0) @binding(5) var<storage, read_write> ssm_state: array<f16>;\n@group(0) @binding(6) var<storage, read_write> output: array<f32>;\n@group(0) @binding(7) var<storage, read> params: Params;\n\n// Shared memory for Q and K vectors (L2-normalized) and reduction\nvar<workgroup> shared_q: array<f32, 128>;\nvar<workgroup> shared_k: array<f32, 128>;\nvar<workgroup> shared_norm: array<f32, 128>;\n\n@compute @workgroup_size(128)\nfn main(\n @builtin(local_invocation_id) lid: vec3u,\n @builtin(workgroup_id) wid: vec3u,\n) {\n let h = wid.x; // uniform within workgroup\n if (h >= params.num_heads) { return; }\n\n let d = lid.x; // 0..127, all threads participate in barriers\n\n let A_val = -exp(A_log[h]);\n let state_base = h * params.key_dim * params.val_dim;\n let scale = 1.0 / sqrt(f32(params.key_dim));\n\n let q_off = h * params.key_dim;\n let k_off = params.num_heads * params.key_dim + h * params.key_dim;\n let v_off = 2u * params.num_heads * params.key_dim + h * params.val_dim;\n let out_stride = params.num_heads * params.val_dim;\n\n for (var t: u32 = 0u; t < params.T; t = t + 1u) {\n let qkv_base = t * params.qkv_dim;\n\n // ── Load Q and K into shared memory ──\n shared_q[d] = qkv_conv[qkv_base + q_off + d];\n shared_k[d] = qkv_conv[qkv_base + k_off + d];\n workgroupBarrier();\n\n // ── L2-normalize Q (parallel reduction) ──\n shared_norm[d] = shared_q[d] * shared_q[d];\n workgroupBarrier();\n var stride: u32 = 64u;\n while (stride > 0u) {\n if (d < stride) { shared_norm[d] += shared_norm[d + stride]; }\n workgroupBarrier();\n stride /= 2u;\n }\n let q_inv_norm = 1.0 / sqrt(shared_norm[0] + 1e-12);\n shared_q[d] *= q_inv_norm;\n\n // ── L2-normalize K (parallel reduction) ──\n shared_norm[d] = shared_k[d] * shared_k[d];\n workgroupBarrier();\n stride = 64u;\n while (stride > 0u) {\n if (d < stride) { shared_norm[d] += shared_norm[d + stride]; }\n workgroupBarrier();\n stride /= 2u;\n }\n let k_inv_norm = 1.0 / sqrt(shared_norm[0] + 1e-12);\n shared_k[d] *= k_inv_norm;\n workgroupBarrier();\n\n // ── Compute decay and beta ──\n let a_val = a_input[t * params.num_heads + h];\n let b_val = b_input[t * params.num_heads + h];\n let dt_in = a_val + dt_bias[h];\n let sp = log(1.0 + exp(max(dt_in, -80.0)));\n let g = A_val * sp;\n let decay = exp(max(g, -80.0));\n let beta = 1.0 / (1.0 + exp(max(-b_val, -80.0)));\n\n // ── 1+2. Retrieve from memory (decay is a per-step scalar, so it factors\n // out of the dot product): kv_mem[d] = decay * Σ_k state[k,d] * k_norm[k] ──\n let v_val = qkv_conv[qkv_base + v_off + d];\n var kv_dot: f32 = 0.0;\n for (var k: u32 = 0u; k < params.key_dim; k = k + 1u) {\n kv_dot += f32(ssm_state[state_base + k * params.val_dim + d]) * shared_k[k];\n }\n let kv_mem = kv_dot * decay;\n\n // ── 3. Delta rule: delta[d] = (v[d] - kv_mem[d]) * beta ──\n let delta_val = (v_val - kv_mem) * beta;\n\n // ── 4+5. Fused decay + rank-1 update + output in ONE pass over state\n // (one read + one write instead of three reads + two writes):\n // state'[k,d] = state[k,d] * decay + k_norm[k] * delta[d]\n // out[d] = Σ_k state'[k,d] * q_norm[k] * scale ──\n var out_val: f32 = 0.0;\n for (var k: u32 = 0u; k < params.key_dim; k = k + 1u) {\n let idx = state_base + k * params.val_dim + d;\n let s = f32(ssm_state[idx]) * decay + shared_k[k] * delta_val;\n ssm_state[idx] = f16(s);\n out_val += s * shared_q[k];\n }\n output[t * out_stride + h * params.val_dim + d] = out_val * scale;\n\n workgroupBarrier(); // sync before next timestep loads shared_q/k\n }\n}\n`;\n\nconst WGSL_KV_CACHE_APPEND = `\\\n// Copy T new K or V vectors into cache at position offset.\n// src: [T, width], dst: [max_seq_len, width]\n\nstruct Params {\n count: u32, // T * width (total elements to copy)\n dst_offset: u32, // seqPos * width (element offset into cache)\n}\n\n@group(0) @binding(0) var<storage, read> src: array<f32>;\n@group(0) @binding(1) var<storage, read_write> dst: array<f32>;\n@group(0) @binding(2) var<storage, read> params: Params;\n\n@compute @workgroup_size(256)\nfn main(@builtin(global_invocation_id) gid: vec3u) {\n let idx = gid.x;\n if (idx >= params.count) { return; }\n dst[params.dst_offset + idx] = src[idx];\n}\n`;\n\nconst WGSL_DUAL_KV_CACHE_APPEND = `\\\n// Append both K and V into their f32 caches in one dispatch.\n// Both share the same width, T, and dst_offset (same kv_dim, same seqPos),\n// so a single param set + a single grid over 'count' elements drives both\n// copies. Pure memcpy — numerically identical to two separate appends.\n\nstruct Params {\n count: u32, // T * width (per-cache element count)\n dst_offset: u32, // seqPos * width (element offset into each cache)\n}\n\n@group(0) @binding(0) var<storage, read> src_k: array<f32>;\n@group(0) @binding(1) var<storage, read> src_v: array<f32>;\n@group(0) @binding(2) var<storage, read_write> dst_k: array<f32>;\n@group(0) @binding(3) var<storage, read_write> dst_v: array<f32>;\n@group(0) @binding(4) var<storage, read> params: Params;\n\n@compute @workgroup_size(256)\nfn main(@builtin(global_invocation_id) gid: vec3u) {\n let idx = gid.x;\n if (idx >= params.count) { return; }\n dst_k[params.dst_offset + idx] = src_k[idx];\n dst_v[params.dst_offset + idx] = src_v[idx];\n}\n`;\n\nconst WGSL_DUAL_KV_CACHE_APPEND_F16 = `\\\n// Append both K and V into their native-f16 caches in one dispatch.\n// Same per-element conversion as the single f16 append, doubled.\n\nstruct Params {\n count: u32,\n dst_offset: u32,\n}\n\n@group(0) @binding(0) var<storage, read> src_k: array<f32>;\n@group(0) @binding(1) var<storage, read> src_v: array<f32>;\n@group(0) @binding(2) var<storage, read_write> dst_k: array<f16>;\n@group(0) @binding(3) var<storage, read_write> dst_v: array<f16>;\n@group(0) @binding(4) var<storage, read> params: Params;\n\n@compute @workgroup_size(256)\nfn main(@builtin(global_invocation_id) gid: vec3u) {\n let idx = gid.x;\n if (idx >= params.count) { return; }\n dst_k[params.dst_offset + idx] = f16(src_k[idx]);\n dst_v[params.dst_offset + idx] = f16(src_v[idx]);\n}\n`;\n\nconst WGSL_DUAL_KV_CACHE_APPEND_PACKED_F16 = `\\\n// Append both K and V into packed-f16 caches in one dispatch (Safari-safe).\n// Each u32 packs 2 f16 via pack2x16float; one thread handles one f16 pair per\n// cache. Same math as the single packed append, doubled — no 'enable f16'.\n\nstruct Params {\n count: u32, // T * width (per-cache f32 element count)\n dst_offset: u32, // seqPos * width (f32-element offset)\n}\n\n@group(0) @binding(0) var<storage, read> src_k: array<f32>;\n@group(0) @binding(1) var<storage, read> src_v: array<f32>;\n@group(0) @binding(2) var<storage, read_write> dst_k: array<u32>;\n@group(0) @binding(3) var<storage, read_write> dst_v: array<u32>;\n@group(0) @binding(4) var<storage, read> params: Params;\n\n@compute @workgroup_size(256)\nfn main(@builtin(global_invocation_id) gid: vec3u) {\n let pair_idx = gid.x;\n let num_pairs = params.count >> 1u;\n if (pair_idx >= num_pairs) { return; }\n\n let src_base = pair_idx * 2u;\n let dst_idx = (params.dst_offset >> 1u) + pair_idx;\n\n dst_k[dst_idx] = pack2x16float(vec2f(src_k[src_base], src_k[src_base + 1u]));\n dst_v[dst_idx] = pack2x16float(vec2f(src_v[src_base], src_v[src_base + 1u]));\n}\n`;\n\nconst WGSL_KV_CACHE_APPEND_F16 = `\\\n// Copy T new K or V vectors into f16 cache at position offset.\n// src: [T, width] (f32), dst: [max_seq_len, width] (f16)\n// Converts f32 activations to f16 on write for reduced memory traffic.\n\nstruct Params {\n count: u32, // T * width (total elements to copy)\n dst_offset: u32, // seqPos * width (element offset into cache)\n}\n\n@group(0) @binding(0) var<storage, read> src: array<f32>;\n@group(0) @binding(1) var<storage, read_write> dst: array<f16>;\n@group(0) @binding(2) var<storage, read> params: Params;\n\n@compute @workgroup_size(256)\nfn main(@builtin(global_invocation_id) gid: vec3u) {\n let idx = gid.x;\n if (idx >= params.count) { return; }\n dst[params.dst_offset + idx] = f16(src[idx]);\n}\n`;\n\nconst WGSL_KV_CACHE_APPEND_PACKED_F16 = `\\\n// Copy T new K or V vectors into packed f16 cache (Safari-safe).\n// src: [T, width] (f32), dst: [max_seq_len, width/2] (u32, packed f16 pairs)\n// Each u32 holds 2 f16 values via pack2x16float. Does NOT require 'enable f16'.\n// Params use f32-element-level addressing; kernel converts to u32 pair indices.\n\nstruct Params {\n count: u32, // T * width (total f32 elements)\n dst_offset: u32, // seqPos * width (f32-element offset into cache)\n}\n\n@group(0) @binding(0) var<storage, read> src: array<f32>;\n@group(0) @binding(1) var<storage, read_write> dst: array<u32>;\n@group(0) @binding(2) var<storage, read> params: Params;\n\n@compute @workgroup_size(256)\nfn main(@builtin(global_invocation_id) gid: vec3u) {\n let pair_idx = gid.x;\n let num_pairs = params.count >> 1u;\n if (pair_idx >= num_pairs) { return; }\n\n let src_base = pair_idx * 2u;\n let dst_idx = (params.dst_offset >> 1u) + pair_idx;\n\n dst[dst_idx] = pack2x16float(vec2f(src[src_base], src[src_base + 1u]));\n}\n`;\n\nconst WGSL_ATTENTION_F16 = `\\\n// Tiled online-softmax attention with f16 KV cache (Flash-Attention style).\n//\n// Same algorithm as WGSL_ATTENTION but K/V are stored as f16 in global memory.\n// Shared memory stays f32 for computation accuracy — only global reads change.\n// Halves KV cache memory traffic for ~1.3-1.8x decode speedup.\n\nconst TILE_S: u32 = 16u;\nconst WG: u32 = 256u;\nconst DOT_THREADS: u32 = 16u;\n\nstruct Params {\n T: u32,\n S: u32,\n num_q_heads: u32,\n num_kv_heads: u32,\n head_dim: u32,\n position_offset: u32,\n // Softmax QK temperature (f32). Default 1/sqrt(head_dim); Gemma 4 sets 1.0.\n attn_scale: f32,\n}\n\n@group(0) @binding(0) var<storage, read> Q: array<f32>;\n@group(0) @binding(1) var<storage, read> K: array<f16>;\n@group(0) @binding(2) var<storage, read> V: array<f16>;\n@group(0) @binding(3) var<storage, read_write> output: array<f32>;\n@group(0) @binding(4) var<storage, read> params: Params;\n\nvar<workgroup> smem: array<f32, 4096>;\n\n@compute @workgroup_size(256)\nfn main(\n @builtin(local_invocation_id) lid: vec3u,\n @builtin(workgroup_id) wid: vec3u,\n) {\n let q_pos = wid.x;\n let q_head = wid.y;\n\n if (q_pos >= params.T || q_head >= params.num_q_heads) { return; }\n\n let tid = lid.x;\n let heads_per_kv = params.num_q_heads / params.num_kv_heads;\n let kv_head = q_head / heads_per_kv;\n let scale = params.attn_scale;\n\n let q_stride = params.num_q_heads * params.head_dim;\n let kv_stride = params.num_kv_heads * params.head_dim;\n let q_base = q_pos * q_stride + q_head * params.head_dim;\n let kv_head_offset = kv_head * params.head_dim;\n let causal_limit = q_pos + params.position_offset + 1u;\n let hd = params.head_dim;\n let S_eff = min(params.S, causal_limit);\n\n var running_max: f32 = -1e30;\n var running_sum: f32 = 0.0;\n // Per-thread output accumulators; out_acc2 covers dim tid+WG so head_dim up to\n // 2*WG (512) works with WG=256 threads. hd<=256 → out_acc2 unused (identical).\n var out_acc: f32 = 0.0;\n var out_acc2: f32 = 0.0;\n let d2 = tid + WG;\n\n // Tile capped so a K/V tile (tile_cap*hd f32) fits the 4096-f32 smem.\n let tile_cap = min(TILE_S, 4096u / hd);\n let num_tiles = (S_eff + tile_cap - 1u) / tile_cap;\n\n for (var tile: u32 = 0u; tile < num_tiles; tile += 1u) {\n let tile_start = tile * tile_cap;\n let tile_end = min(tile_start + tile_cap, S_eff);\n let tile_len = tile_end - tile_start;\n\n // ── Step 1: Cooperative K tile load (f16 → f32 conversion) ──\n let total_k_elems = tile_len * hd;\n var load_idx = tid;\n while (load_idx < total_k_elems) {\n let s_local = load_idx / hd;\n let d = load_idx % hd;\n let s_global = tile_start + s_local;\n let k_addr = s_global * kv_stride + kv_head_offset + d;\n smem[s_local * hd + d] = f32(K[k_addr]);\n load_idx += WG;\n }\n workgroupBarrier();\n\n // ── Step 2: Parallel Q·K dot products ──\n let pos_in_tile = tid / DOT_THREADS;\n let dot_tid = tid % DOT_THREADS;\n\n var my_partial: f32 = 0.0;\n if (pos_in_tile < tile_len) {\n let dims_per_thread = hd / DOT_THREADS;\n let d_start = dot_tid * dims_per_thread;\n let d_end = d_start + dims_per_thread;\n let kv_row_base = pos_in_tile * hd;\n\n var d = d_start;\n while (d + 3u < d_end) {\n let q_v = vec4f(Q[q_base + d], Q[q_base + d + 1u],\n Q[q_base + d + 2u], Q[q_base + d + 3u]);\n let k_v = vec4f(smem[kv_row_base + d], smem[kv_row_base + d + 1u],\n smem[kv_row_base + d + 2u], smem[kv_row_base + d + 3u]);\n my_partial += dot(q_v, k_v);\n d += 4u;\n }\n while (d < d_end) {\n my_partial += Q[q_base + d] * smem[kv_row_base + d];\n d += 1u;\n }\n }\n\n workgroupBarrier();\n smem[tid] = my_partial;\n workgroupBarrier();\n\n // Two-phase: leader 0 reads smem[1..15], which are exactly the slots\n // leaders 1..15 write — reduce into a local first, barrier (in uniform\n // control flow), then write. Read+write in one block is a data race.\n var group_total: f32 = 0.0;\n if (dot_tid == 0u && pos_in_tile < tile_len) {\n group_total = smem[tid];\n for (var i: u32 = 1u; i < DOT_THREADS; i += 1u) {\n group_total += smem[tid + i];\n }\n }\n workgroupBarrier();\n if (dot_tid == 0u && pos_in_tile < tile_len) {\n smem[pos_in_tile] = group_total * scale;\n }\n workgroupBarrier();\n\n // ── Step 3: Online softmax ──\n if (tid < TILE_S) {\n if (tid < tile_len) {\n smem[TILE_S + tid] = smem[tid];\n } else {\n smem[TILE_S + tid] = -1e30;\n }\n }\n workgroupBarrier();\n if (tid < 8u) { smem[TILE_S + tid] = max(smem[TILE_S + tid], smem[TILE_S + tid + 8u]); }\n workgroupBarrier();\n if (tid < 4u) { smem[TILE_S + tid] = max(smem[TILE_S + tid], smem[TILE_S + tid + 4u]); }\n workgroupBarrier();\n if (tid < 2u) { smem[TILE_S + tid] = max(smem[TILE_S + tid], smem[TILE_S + tid + 2u]); }\n workgroupBarrier();\n if (tid < 1u) { smem[TILE_S + tid] = max(smem[TILE_S + tid], smem[TILE_S + tid + 1u]); }\n workgroupBarrier();\n let tile_max = smem[TILE_S];\n\n let new_max = max(running_max, tile_max);\n let old_correction = exp(max(running_max - new_max, -80.0));\n\n if (tid < tile_len) {\n smem[tid] = exp(max(smem[tid] - new_max, -80.0));\n }\n workgroupBarrier();\n\n if (tid < TILE_S) {\n if (tid < tile_len) {\n smem[TILE_S + tid] = smem[tid];\n } else {\n smem[TILE_S + tid] = 0.0;\n }\n }\n workgroupBarrier();\n if (tid < 8u) { smem[TILE_S + tid] += smem[TILE_S + tid + 8u]; }\n workgroupBarrier();\n if (tid < 4u) { smem[TILE_S + tid] += smem[TILE_S + tid + 4u]; }\n workgroupBarrier();\n if (tid < 2u) { smem[TILE_S + tid] += smem[TILE_S + tid + 2u]; }\n workgroupBarrier();\n if (tid < 1u) { smem[TILE_S + tid] += smem[TILE_S + tid + 1u]; }\n workgroupBarrier();\n let tile_sum = smem[TILE_S];\n\n // ── Save scores to registers before V overwrites smem ──\n var w0: f32 = 0.0; var w1: f32 = 0.0; var w2: f32 = 0.0; var w3: f32 = 0.0;\n var w4: f32 = 0.0; var w5: f32 = 0.0; var w6: f32 = 0.0; var w7: f32 = 0.0;\n var w8: f32 = 0.0; var w9: f32 = 0.0; var w10: f32 = 0.0; var w11: f32 = 0.0;\n var w12: f32 = 0.0; var w13: f32 = 0.0; var w14: f32 = 0.0; var w15: f32 = 0.0;\n if (tid < hd) {\n if (0u < tile_len) { w0 = smem[0]; }\n if (1u < tile_len) { w1 = smem[1]; }\n if (2u < tile_len) { w2 = smem[2]; }\n if (3u < tile_len) { w3 = smem[3]; }\n if (4u < tile_len) { w4 = smem[4]; }\n if (5u < tile_len) { w5 = smem[5]; }\n if (6u < tile_len) { w6 = smem[6]; }\n if (7u < tile_len) { w7 = smem[7]; }\n if (8u < tile_len) { w8 = smem[8]; }\n if (9u < tile_len) { w9 = smem[9]; }\n if (10u < tile_len) { w10 = smem[10]; }\n if (11u < tile_len) { w11 = smem[11]; }\n if (12u < tile_len) { w12 = smem[12]; }\n if (13u < tile_len) { w13 = smem[13]; }\n if (14u < tile_len) { w14 = smem[14]; }\n if (15u < tile_len) { w15 = smem[15]; }\n }\n workgroupBarrier();\n\n // ── Step 4: Cooperative V tile load (f16 → f32 conversion, full smem reuse) ──\n let total_v_elems = tile_len * hd;\n load_idx = tid;\n while (load_idx < total_v_elems) {\n let s_local = load_idx / hd;\n let d = load_idx % hd;\n let s_global = tile_start + s_local;\n let v_addr = s_global * kv_stride + kv_head_offset + d;\n smem[s_local * hd + d] = f32(V[v_addr]);\n load_idx += WG;\n }\n workgroupBarrier();\n\n // ── Step 5: Parallel V accumulation ──\n if (tid < hd) {\n out_acc = out_acc * old_correction;\n if (0u < tile_len) { out_acc += w0 * smem[0u * hd + tid]; }\n if (1u < tile_len) { out_acc += w1 * smem[1u * hd + tid]; }\n if (2u < tile_len) { out_acc += w2 * smem[2u * hd + tid]; }\n if (3u < tile_len) { out_acc += w3 * smem[3u * hd + tid]; }\n if (4u < tile_len) { out_acc += w4 * smem[4u * hd + tid]; }\n if (5u < tile_len) { out_acc += w5 * smem[5u * hd + tid]; }\n if (6u < tile_len) { out_acc += w6 * smem[6u * hd + tid]; }\n if (7u < tile_len) { out_acc += w7 * smem[7u * hd + tid]; }\n if (8u < tile_len) { out_acc += w8 * smem[8u * hd + tid]; }\n if (9u < tile_len) { out_acc += w9 * smem[9u * hd + tid]; }\n if (10u < tile_len) { out_acc += w10 * smem[10u * hd + tid]; }\n if (11u < tile_len) { out_acc += w11 * smem[11u * hd + tid]; }\n if (12u < tile_len) { out_acc += w12 * smem[12u * hd + tid]; }\n if (13u < tile_len) { out_acc += w13 * smem[13u * hd + tid]; }\n if (14u < tile_len) { out_acc += w14 * smem[14u * hd + tid]; }\n if (15u < tile_len) { out_acc += w15 * smem[15u * hd + tid]; }\n }\n if (d2 < hd) {\n out_acc2 = out_acc2 * old_correction;\n if (0u < tile_len) { out_acc2 += w0 * smem[0u * hd + d2]; }\n if (1u < tile_len) { out_acc2 += w1 * smem[1u * hd + d2]; }\n if (2u < tile_len) { out_acc2 += w2 * smem[2u * hd + d2]; }\n if (3u < tile_len) { out_acc2 += w3 * smem[3u * hd + d2]; }\n if (4u < tile_len) { out_acc2 += w4 * smem[4u * hd + d2]; }\n if (5u < tile_len) { out_acc2 += w5 * smem[5u * hd + d2]; }\n if (6u < tile_len) { out_acc2 += w6 * smem[6u * hd + d2]; }\n if (7u < tile_len) { out_acc2 += w7 * smem[7u * hd + d2]; }\n if (8u < tile_len) { out_acc2 += w8 * smem[8u * hd + d2]; }\n if (9u < tile_len) { out_acc2 += w9 * smem[9u * hd + d2]; }\n if (10u < tile_len) { out_acc2 += w10 * smem[10u * hd + d2]; }\n if (11u < tile_len) { out_acc2 += w11 * smem[11u * hd + d2]; }\n if (12u < tile_len) { out_acc2 += w12 * smem[12u * hd + d2]; }\n if (13u < tile_len) { out_acc2 += w13 * smem[13u * hd + d2]; }\n if (14u < tile_len) { out_acc2 += w14 * smem[14u * hd + d2]; }\n if (15u < tile_len) { out_acc2 += w15 * smem[15u * hd + d2]; }\n }\n\n running_sum = running_sum * old_correction + tile_sum;\n running_max = new_max;\n workgroupBarrier();\n }\n\n // ── Write normalized output ──\n var inv_sum: f32 = 0.0;\n if (running_sum > 0.0) { inv_sum = 1.0 / running_sum; }\n let out_base = q_pos * q_stride + q_head * hd;\n if (tid < hd) {\n output[out_base + tid] = out_acc * inv_sum;\n }\n if (d2 < hd) {\n output[out_base + d2] = out_acc2 * inv_sum;\n }\n}\n`;\n\nconst WGSL_ATTENTION_PACKED_F16 = `\\\n// Tiled online-softmax attention with packed f16 KV cache (Safari-safe).\n//\n// K/V stored as array<u32> — each u32 holds 2 f16 values via pack2x16float.\n// Does NOT require 'enable f16' — only uses u32 + f32 + unpack2x16float.\n// Same algorithm as native f16 attention, same memory savings.\n//\n// Safari/Metal compatibility:\n// - Uses if/else instead of select() (Safari has select() bugs)\n// - Clamps exp() args to -80 (Metal can return NaN for exp(-1e30))\n// - No f16 types avoids WebKit WGSL compiler miscompilation\n//\n// Shared memory: 4096 f32 = 16384 bytes = exactly 16 KB (minimum WebGPU guarantee)\n\nconst TILE_S: u32 = 16u;\nconst WG: u32 = 256u;\nconst DOT_THREADS: u32 = 16u;\n\nstruct Params {\n T: u32,\n S: u32,\n num_q_heads: u32,\n num_kv_heads: u32,\n head_dim: u32,\n position_offset: u32,\n // Softmax QK temperature (f32). Default 1/sqrt(head_dim); Gemma 4 sets 1.0.\n attn_scale: f32,\n}\n\n@group(0) @binding(0) var<storage, read> Q: array<f32>;\n@group(0) @binding(1) var<storage, read> K: array<u32>;\n@group(0) @binding(2) var<storage, read> V: array<u32>;\n@group(0) @binding(3) var<storage, read_write> output: array<f32>;\n@group(0) @binding(4) var<storage, read> params: Params;\n\nvar<workgroup> smem: array<f32, 4096>;\n\n// Load a single f32 from packed u32 K cache at element-level index\nfn load_k(elem_idx: u32) -> f32 {\n let pair = unpack2x16float(K[elem_idx >> 1u]);\n if ((elem_idx & 1u) == 0u) {\n return pair.x;\n } else {\n return pair.y;\n }\n}\n\n// Load a single f32 from packed u32 V cache at element-level index\nfn load_v(elem_idx: u32) -> f32 {\n let pair = unpack2x16float(V[elem_idx >> 1u]);\n if ((elem_idx & 1u) == 0u) {\n return pair.x;\n } else {\n return pair.y;\n }\n}\n\n@compute @workgroup_size(256)\nfn main(\n @builtin(local_invocation_id) lid: vec3u,\n @builtin(workgroup_id) wid: vec3u,\n) {\n let q_pos = wid.x;\n let q_head = wid.y;\n\n if (q_pos >= params.T || q_head >= params.num_q_heads) { return; }\n\n let tid = lid.x;\n let heads_per_kv = params.num_q_heads / params.num_kv_heads;\n let kv_head = q_head / heads_per_kv;\n let scale = params.attn_scale;\n\n let q_stride = params.num_q_heads * params.head_dim;\n let kv_stride = params.num_kv_heads * params.head_dim;\n let q_base = q_pos * q_stride + q_head * params.head_dim;\n let kv_head_offset = kv_head * params.head_dim;\n let causal_limit = q_pos + params.position_offset + 1u;\n let hd = params.head_dim;\n let S_eff = min(params.S, causal_limit);\n\n var running_max: f32 = -1e30;\n var running_sum: f32 = 0.0;\n // Per-thread output accumulators; out_acc2 covers dim tid+WG so head_dim up to\n // 2*WG (512) works with WG=256 threads. hd<=256 → out_acc2 unused (identical).\n var out_acc: f32 = 0.0;\n var out_acc2: f32 = 0.0;\n let d2 = tid + WG;\n\n // Tile capped so a K/V tile (tile_cap*hd f32) fits the 4096-f32 smem.\n let tile_cap = min(TILE_S, 4096u / hd);\n let num_tiles = (S_eff + tile_cap - 1u) / tile_cap;\n\n for (var tile: u32 = 0u; tile < num_tiles; tile += 1u) {\n let tile_start = tile * tile_cap;\n let tile_end = min(tile_start + tile_cap, S_eff);\n let tile_len = tile_end - tile_start;\n\n // ── Step 1: Cooperative K tile load (packed u32 → f32 via unpack2x16float) ──\n let total_k_elems = tile_len * hd;\n var load_idx = tid;\n while (load_idx < total_k_elems) {\n let s_local = load_idx / hd;\n let d = load_idx % hd;\n let s_global = tile_start + s_local;\n let k_addr = s_global * kv_stride + kv_head_offset + d;\n smem[s_local * hd + d] = load_k(k_addr);\n load_idx += WG;\n }\n workgroupBarrier();\n\n // ── Step 2: Parallel Q·K dot products ──\n let pos_in_tile = tid / DOT_THREADS;\n let dot_tid = tid % DOT_THREADS;\n\n var my_partial: f32 = 0.0;\n if (pos_in_tile < tile_len) {\n let dims_per_thread = hd / DOT_THREADS;\n let d_start = dot_tid * dims_per_thread;\n let d_end = d_start + dims_per_thread;\n let kv_row_base = pos_in_tile * hd;\n\n var d = d_start;\n while (d + 3u < d_end) {\n let q_v = vec4f(Q[q_base + d], Q[q_base + d + 1u],\n Q[q_base + d + 2u], Q[q_base + d + 3u]);\n let k_v = vec4f(smem[kv_row_base + d], smem[kv_row_base + d + 1u],\n smem[kv_row_base + d + 2u], smem[kv_row_base + d + 3u]);\n my_partial += dot(q_v, k_v);\n d += 4u;\n }\n while (d < d_end) {\n my_partial += Q[q_base + d] * smem[kv_row_base + d];\n d += 1u;\n }\n }\n\n workgroupBarrier();\n smem[tid] = my_partial;\n workgroupBarrier();\n\n // Two-phase: leader 0 reads smem[1..15], which are exactly the slots\n // leaders 1..15 write — reduce into a local first, barrier (in uniform\n // control flow), then write. Read+write in one block is a data race.\n var group_total: f32 = 0.0;\n if (dot_tid == 0u && pos_in_tile < tile_len) {\n group_total = smem[tid];\n for (var i: u32 = 1u; i < DOT_THREADS; i += 1u) {\n group_total += smem[tid + i];\n }\n }\n workgroupBarrier();\n if (dot_tid == 0u && pos_in_tile < tile_len) {\n smem[pos_in_tile] = group_total * scale;\n }\n workgroupBarrier();\n\n // ── Step 3: Online softmax ──\n if (tid < TILE_S) {\n if (tid < tile_len) {\n smem[TILE_S + tid] = smem[tid];\n } else {\n smem[TILE_S + tid] = -1e30;\n }\n }\n workgroupBarrier();\n if (tid < 8u) { smem[TILE_S + tid] = max(smem[TILE_S + tid], smem[TILE_S + tid + 8u]); }\n workgroupBarrier();\n if (tid < 4u) { smem[TILE_S + tid] = max(smem[TILE_S + tid], smem[TILE_S + tid + 4u]); }\n workgroupBarrier();\n if (tid < 2u) { smem[TILE_S + tid] = max(smem[TILE_S + tid], smem[TILE_S + tid + 2u]); }\n workgroupBarrier();\n if (tid < 1u) { smem[TILE_S + tid] = max(smem[TILE_S + tid], smem[TILE_S + tid + 1u]); }\n workgroupBarrier();\n let tile_max = smem[TILE_S];\n\n let new_max = max(running_max, tile_max);\n let old_correction = exp(max(running_max - new_max, -80.0));\n\n if (tid < tile_len) {\n smem[tid] = exp(max(smem[tid] - new_max, -80.0));\n }\n workgroupBarrier();\n\n if (tid < TILE_S) {\n if (tid < tile_len) {\n smem[TILE_S + tid] = smem[tid];\n } else {\n smem[TILE_S + tid] = 0.0;\n }\n }\n workgroupBarrier();\n if (tid < 8u) { smem[TILE_S + tid] += smem[TILE_S + tid + 8u]; }\n workgroupBarrier();\n if (tid < 4u) { smem[TILE_S + tid] += smem[TILE_S + tid + 4u]; }\n workgroupBarrier();\n if (tid < 2u) { smem[TILE_S + tid] += smem[TILE_S + tid + 2u]; }\n workgroupBarrier();\n if (tid < 1u) { smem[TILE_S + tid] += smem[TILE_S + tid + 1u]; }\n workgroupBarrier();\n let tile_sum = smem[TILE_S];\n\n // ── Save scores to registers before V overwrites smem ──\n var w0: f32 = 0.0; var w1: f32 = 0.0; var w2: f32 = 0.0; var w3: f32 = 0.0;\n var w4: f32 = 0.0; var w5: f32 = 0.0; var w6: f32 = 0.0; var w7: f32 = 0.0;\n var w8: f32 = 0.0; var w9: f32 = 0.0; var w10: f32 = 0.0; var w11: f32 = 0.0;\n var w12: f32 = 0.0; var w13: f32 = 0.0; var w14: f32 = 0.0; var w15: f32 = 0.0;\n if (tid < hd) {\n if (0u < tile_len) { w0 = smem[0]; }\n if (1u < tile_len) { w1 = smem[1]; }\n if (2u < tile_len) { w2 = smem[2]; }\n if (3u < tile_len) { w3 = smem[3]; }\n if (4u < tile_len) { w4 = smem[4]; }\n if (5u < tile_len) { w5 = smem[5]; }\n if (6u < tile_len) { w6 = smem[6]; }\n if (7u < tile_len) { w7 = smem[7]; }\n if (8u < tile_len) { w8 = smem[8]; }\n if (9u < tile_len) { w9 = smem[9]; }\n if (10u < tile_len) { w10 = smem[10]; }\n if (11u < tile_len) { w11 = smem[11]; }\n if (12u < tile_len) { w12 = smem[12]; }\n if (13u < tile_len) { w13 = smem[13]; }\n if (14u < tile_len) { w14 = smem[14]; }\n if (15u < tile_len) { w15 = smem[15]; }\n }\n workgroupBarrier();\n\n // ── Step 4: Cooperative V tile load (packed u32 → f32 via unpack2x16float) ──\n let total_v_elems = tile_len * hd;\n load_idx = tid;\n while (load_idx < total_v_elems) {\n let s_local = load_idx / hd;\n let d = load_idx % hd;\n let s_global = tile_start + s_local;\n let v_addr = s_global * kv_stride + kv_head_offset + d;\n smem[s_local * hd + d] = load_v(v_addr);\n load_idx += WG;\n }\n workgroupBarrier();\n\n // ── Step 5: Parallel V accumulation ──\n if (tid < hd) {\n out_acc = out_acc * old_correction;\n if (0u < tile_len) { out_acc += w0 * smem[0u * hd + tid]; }\n if (1u < tile_len) { out_acc += w1 * smem[1u * hd + tid]; }\n if (2u < tile_len) { out_acc += w2 * smem[2u * hd + tid]; }\n if (3u < tile_len) { out_acc += w3 * smem[3u * hd + tid]; }\n if (4u < tile_len) { out_acc += w4 * smem[4u * hd + tid]; }\n if (5u < tile_len) { out_acc += w5 * smem[5u * hd + tid]; }\n if (6u < tile_len) { out_acc += w6 * smem[6u * hd + tid]; }\n if (7u < tile_len) { out_acc += w7 * smem[7u * hd + tid]; }\n if (8u < tile_len) { out_acc += w8 * smem[8u * hd + tid]; }\n if (9u < tile_len) { out_acc += w9 * smem[9u * hd + tid]; }\n if (10u < tile_len) { out_acc += w10 * smem[10u * hd + tid]; }\n if (11u < tile_len) { out_acc += w11 * smem[11u * hd + tid]; }\n if (12u < tile_len) { out_acc += w12 * smem[12u * hd + tid]; }\n if (13u < tile_len) { out_acc += w13 * smem[13u * hd + tid]; }\n if (14u < tile_len) { out_acc += w14 * smem[14u * hd + tid]; }\n if (15u < tile_len) { out_acc += w15 * smem[15u * hd + tid]; }\n }\n if (d2 < hd) {\n out_acc2 = out_acc2 * old_correction;\n if (0u < tile_len) { out_acc2 += w0 * smem[0u * hd + d2]; }\n if (1u < tile_len) { out_acc2 += w1 * smem[1u * hd + d2]; }\n if (2u < tile_len) { out_acc2 += w2 * smem[2u * hd + d2]; }\n if (3u < tile_len) { out_acc2 += w3 * smem[3u * hd + d2]; }\n if (4u < tile_len) { out_acc2 += w4 * smem[4u * hd + d2]; }\n if (5u < tile_len) { out_acc2 += w5 * smem[5u * hd + d2]; }\n if (6u < tile_len) { out_acc2 += w6 * smem[6u * hd + d2]; }\n if (7u < tile_len) { out_acc2 += w7 * smem[7u * hd + d2]; }\n if (8u < tile_len) { out_acc2 += w8 * smem[8u * hd + d2]; }\n if (9u < tile_len) { out_acc2 += w9 * smem[9u * hd + d2]; }\n if (10u < tile_len) { out_acc2 += w10 * smem[10u * hd + d2]; }\n if (11u < tile_len) { out_acc2 += w11 * smem[11u * hd + d2]; }\n if (12u < tile_len) { out_acc2 += w12 * smem[12u * hd + d2]; }\n if (13u < tile_len) { out_acc2 += w13 * smem[13u * hd + d2]; }\n if (14u < tile_len) { out_acc2 += w14 * smem[14u * hd + d2]; }\n if (15u < tile_len) { out_acc2 += w15 * smem[15u * hd + d2]; }\n }\n\n running_sum = running_sum * old_correction + tile_sum;\n running_max = new_max;\n workgroupBarrier();\n }\n\n // ── Write normalized output ──\n var inv_sum: f32 = 0.0;\n if (running_sum > 0.0) { inv_sum = 1.0 / running_sum; }\n let out_base = q_pos * q_stride + q_head * hd;\n if (tid < hd) {\n output[out_base + tid] = out_acc * inv_sum;\n }\n if (d2 < hd) {\n output[out_base + d2] = out_acc2 * inv_sum;\n }\n}\n`;\n\nconst WGSL_CONV_STATE_UPDATE = `\\\n// Update conv1d rolling state buffer with latest input timesteps.\n// For T >= state_size: copy last state_size timesteps from input.\n// For T < state_size: shift old state left by T, append T new values.\n\nstruct Params {\n seq_len: u32,\n channels: u32,\n state_size: u32,\n _pad: u32,\n}\n\n@group(0) @binding(0) var<storage, read> input: array<f32>;\n@group(0) @binding(1) var<storage, read_write> conv_state: array<f32>;\n@group(0) @binding(2) var<storage, read> params: Params;\n\n@compute @workgroup_size(256)\nfn main(@builtin(global_invocation_id) gid: vec3u) {\n let c = gid.x;\n if (c >= params.channels) { return; }\n\n let ss = params.state_size;\n\n if (params.seq_len >= ss) {\n // Large batch (prefill): copy last state_size timesteps from input\n for (var s: u32 = 0u; s < ss; s = s + 1u) {\n let src_t = params.seq_len - ss + s;\n conv_state[s * params.channels + c] = input[src_t * params.channels + c];\n }\n } else {\n // Small batch (decode): shift old state left, append new values\n // Read old state for this channel first to avoid read-write conflict\n var old0: f32 = 0.0;\n var old1: f32 = 0.0;\n var old2: f32 = 0.0;\n if (ss > 0u) { old0 = conv_state[0u * params.channels + c]; }\n if (ss > 1u) { old1 = conv_state[1u * params.channels + c]; }\n if (ss > 2u) { old2 = conv_state[2u * params.channels + c]; }\n\n let shift = ss - params.seq_len;\n for (var s: u32 = 0u; s < ss; s = s + 1u) {\n var val: f32 = 0.0;\n if (s < shift) {\n // Read from shifted old state\n let src_s = s + params.seq_len;\n if (src_s == 0u) { val = old0; }\n else if (src_s == 1u) { val = old1; }\n else if (src_s == 2u) { val = old2; }\n } else {\n // Read from new input\n let input_idx = s - shift;\n val = input[input_idx * params.channels + c];\n }\n conv_state[s * params.channels + c] = val;\n }\n }\n}\n`;\n\n// PoolMatMul: pooled[Np, W] = poolW[Np, N] @ hidden[N, W].\n// The Gemma 4 ViT spatial average-pool, expressed as a host-built pooling matrix\n// (poolW[r, k] = 1/k² when patch k falls in pooled-cell r, else 0) times the\n// encoder output. One thread per output element accumulates over N. Deliberately\n// simple (no workgroup memory, no select(), no vec4 alignment assumption) — it\n// runs ONCE per image over tiny matrices (Np≤~512, N≤~4096, W=768), so a scalar\n// kernel is plenty fast and maximally mobile-safe.\nconst WGSL_POOL_MATMUL = `\\\nstruct Params {\n Np: u32, // pooled rows (output rows)\n N: u32, // patch count (contraction dim)\n W: u32, // hidden width (output cols)\n}\n\n@group(0) @binding(0) var<storage, read> poolW: array<f32>; // [Np, N]\n@group(0) @binding(1) var<storage, read> hidden: array<f32>; // [N, W]\n@group(0) @binding(2) var<storage, read_write> outp: array<f32>; // [Np, W]\n@group(0) @binding(3) var<storage, read> params: Params;\n\n@compute @workgroup_size(64)\nfn main(@builtin(global_invocation_id) gid: vec3u) {\n let idx = gid.x;\n let total = params.Np * params.W;\n if (idx >= total) { return; }\n let r = idx / params.W; // pooled row\n let c = idx % params.W; // hidden col\n var acc: f32 = 0.0;\n let prow = r * params.N;\n for (var k: u32 = 0u; k < params.N; k = k + 1u) {\n let w = poolW[prow + k];\n if (w != 0.0) {\n acc = acc + w * hidden[k * params.W + c];\n }\n }\n outp[idx] = acc;\n}\n`;\n\n// ClippedMatMul: out = clamp(clamp(A, imin, imax) @ B^T, omin, omax).\n// Gemma 4 ViT Gemma4ClippableLinear: each projection clamps its input to a\n// calibrated [imin, imax] before the linear and its output to [omin, omax] after.\n// Same tiling as WGSL_MATMUL (4×2 register blocking, vec4 loads), with the input\n// clamp applied at tile load and the output clamp at store. Mobile-safe: clamp =\n// min(max(x, lo), hi), no select(); no `enable f16`. The four clip scalars are\n// passed as f32 bit patterns in the uniform.\nconst WGSL_CLIPPED_MATMUL = `\\\nconst MT: u32 = 64u;\nconst NT: u32 = 32u;\nconst KT: u32 = 16u;\nconst KT4: u32 = 4u;\n\nstruct Params {\n M: u32,\n K: u32,\n N: u32,\n imin: f32,\n imax: f32,\n omin: f32,\n omax: f32,\n}\n\n@group(0) @binding(0) var<storage, read> A: array<vec4f>;\n@group(0) @binding(1) var<storage, read> B: array<vec4f>;\n@group(0) @binding(2) var<storage, read_write> C: array<f32>;\n@group(0) @binding(3) var<storage, read> params: Params;\n\nvar<workgroup> tileA: array<f32, 1024>;\nvar<workgroup> tileB: array<f32, 512>;\n\nfn clip4(v: vec4f, lo: f32, hi: f32) -> vec4f {\n return min(max(v, vec4f(lo)), vec4f(hi));\n}\n\n@compute @workgroup_size(16, 16)\nfn main(\n @builtin(local_invocation_id) lid: vec3u,\n @builtin(workgroup_id) wid: vec3u,\n) {\n let lr = lid.y;\n let lc = lid.x;\n let mBase = wid.y * MT;\n let nBase = wid.x * NT;\n let row0 = mBase + lr;\n let col0 = nBase + lc;\n\n var acc: array<f32, 8>;\n for (var i = 0u; i < 8u; i = i + 1u) { acc[i] = 0.0; }\n\n let numTiles = (params.K + KT - 1u) / KT;\n let tid = lr * 16u + lc;\n let K4 = params.K / 4u;\n\n for (var t: u32 = 0u; t < numTiles; t = t + 1u) {\n let kv0 = t * KT4;\n {\n let ar = tid / KT4;\n let av4 = tid % KT4;\n let gRow = mBase + ar;\n var v = vec4f(0.0, 0.0, 0.0, 0.0);\n if (gRow < params.M && kv0 + av4 < K4) {\n v = clip4(A[gRow * K4 + kv0 + av4], params.imin, params.imax);\n }\n let base = ar * KT + av4 * 4u;\n tileA[base] = v.x;\n tileA[base + 1u] = v.y;\n tileA[base + 2u] = v.z;\n tileA[base + 3u] = v.w;\n }\n if (tid < 128u) {\n let bc = tid / KT4;\n let bv4 = tid % KT4;\n let gCol = nBase + bc;\n var v = vec4f(0.0, 0.0, 0.0, 0.0);\n if (gCol < params.N && kv0 + bv4 < K4) {\n v = B[gCol * K4 + kv0 + bv4];\n }\n let krow = bv4 * 4u;\n tileB[(krow + 0u) * NT + bc] = v.x;\n tileB[(krow + 1u) * NT + bc] = v.y;\n tileB[(krow + 2u) * NT + bc] = v.z;\n tileB[(krow + 3u) * NT + bc] = v.w;\n }\n\n workgroupBarrier();\n\n for (var k: u32 = 0u; k < KT; k = k + 1u) {\n let b0 = tileB[k * NT + lc];\n let b1 = tileB[k * NT + lc + 16u];\n let a0 = tileA[lr * KT + k];\n let a1 = tileA[(lr + 16u) * KT + k];\n let a2 = tileA[(lr + 32u) * KT + k];\n let a3 = tileA[(lr + 48u) * KT + k];\n acc[0] += a0 * b0;\n acc[1] += a0 * b1;\n acc[2] += a1 * b0;\n acc[3] += a1 * b1;\n acc[4] += a2 * b0;\n acc[5] += a2 * b1;\n acc[6] += a3 * b0;\n acc[7] += a3 * b1;\n }\n\n workgroupBarrier();\n }\n\n for (var rr = 0u; rr < 4u; rr = rr + 1u) {\n let gRow = row0 + rr * 16u;\n if (gRow < params.M) {\n if (col0 < params.N) {\n C[gRow * params.N + col0] = min(max(acc[rr * 2u], params.omin), params.omax);\n }\n if (col0 + 16u < params.N) {\n C[gRow * params.N + col0 + 16u] = min(max(acc[rr * 2u + 1u], params.omin), params.omax);\n }\n }\n }\n}\n`;\n\n// ── Kernel specs ────────────────────────────────────────────────────────\n\n/**\n * Resolve the total element count of the first output tensor.\n * Used by elementwise ops whose dispatch is based on output size.\n */\nfunction outputElementCount(op: OpNode, resolvedShapes: Record<string, number[]>): number {\n const outShape = resolvedShapes[op.outputs[0]];\n let count = 1;\n for (const d of outShape) count *= d;\n return count;\n}\n\n// ── Add ──\n\nconst addSpec: KernelSpec = {\n shaderCode: WGSL_ADD,\n entryPoint: \"main\",\n\n // @binding(0) a: storage read\n // @binding(1) b: storage read\n // @binding(2) output: storage read_write\n // @binding(3) params: uniform\n bindings: [\n { type: \"storage-read\" },\n { type: \"storage-read\" },\n { type: \"storage-read-write\" },\n { type: \"uniform\" },\n ],\n\n getDispatchSize(op, resolvedShapes) {\n const count = outputElementCount(op, resolvedShapes);\n return [cdiv(count, 256), 1, 1];\n },\n\n buildParams(op, resolvedShapes) {\n const count = outputElementCount(op, resolvedShapes);\n // Params { count: u32 }\n return buildUniformBuffer([count]);\n },\n};\n\n// ── Mul ──\n\nconst mulSpec: KernelSpec = {\n shaderCode: WGSL_MUL,\n entryPoint: \"main\",\n\n // @binding(0) a: storage read\n // @binding(1) b: storage read\n // @binding(2) output: storage read_write\n // @binding(3) params: uniform\n bindings: [\n { type: \"storage-read\" },\n { type: \"storage-read\" },\n { type: \"storage-read-write\" },\n { type: \"uniform\" },\n ],\n\n getDispatchSize(op, resolvedShapes) {\n const count = outputElementCount(op, resolvedShapes);\n return [cdiv(count, 256), 1, 1];\n },\n\n buildParams(op, resolvedShapes) {\n const count = outputElementCount(op, resolvedShapes);\n // Params { count: u32 }\n return buildUniformBuffer([count]);\n },\n};\n\n// ── SliceLastRow ──\n\nconst sliceLastRowSpec: KernelSpec = {\n shaderCode: WGSL_SLICE_LAST_ROW,\n entryPoint: \"main\",\n\n // @binding(0) src: storage read\n // @binding(1) output: storage read_write\n // @binding(2) params: uniform\n bindings: [{ type: \"storage-read\" }, { type: \"storage-read-write\" }, { type: \"uniform\" }],\n\n getDispatchSize(op) {\n const width = op.attributes.width as number;\n return [cdiv(width, 256), 1, 1];\n },\n\n buildParams(op, resolvedShapes) {\n const width = op.attributes.width as number;\n const T = resolvedShapes[op.inputs[0]]?.[0] ?? 1;\n // Params { width: u32, last_row_offset: u32 }\n return buildUniformBuffer([width, (T - 1) * width]);\n },\n};\n\n// ── MeanPool (mean over T tokens → [1, width], bidirectional embeddings) ──\n\nconst meanPoolSpec: KernelSpec = {\n shaderCode: WGSL_MEAN_POOL,\n entryPoint: \"main\",\n\n // @binding(0) src: storage read\n // @binding(1) output: storage read_write\n // @binding(2) params: uniform\n bindings: [{ type: \"storage-read\" }, { type: \"storage-read-write\" }, { type: \"uniform\" }],\n\n getDispatchSize(op) {\n const width = op.attributes.width as number;\n return [cdiv(width, 256), 1, 1];\n },\n\n buildParams(op, resolvedShapes) {\n const width = op.attributes.width as number;\n // seq_len = rows of the input activation (T).\n const T = resolvedShapes[op.inputs[0]]?.[0] ?? 1;\n // Params { seq_len: u32, width: u32 }\n return buildUniformBuffer([T, width]);\n },\n};\n\n// ── Scale (multiply tensor by a scalar constant) ──\n\nconst scaleSpec: KernelSpec = {\n shaderCode: WGSL_SCALE,\n entryPoint: \"main\",\n\n // @binding(0) input: storage read\n // @binding(1) output: storage read_write\n // @binding(2) params: uniform\n bindings: [{ type: \"storage-read\" }, { type: \"storage-read-write\" }, { type: \"uniform\" }],\n\n getDispatchSize(op, resolvedShapes) {\n const count = outputElementCount(op, resolvedShapes);\n return [cdiv(count, 256), 1, 1];\n },\n\n buildParams(op, resolvedShapes) {\n const count = outputElementCount(op, resolvedShapes);\n const scale = op.attributes.scale as number;\n // Params { count: u32, scale_bits: u32 }\n return buildUniformBuffer([count, f32BitsToU32(scale)]);\n },\n};\n\n// ── Softcap (Gemma final logit softcapping) ──\n\nconst softcapSpec: KernelSpec = {\n shaderCode: WGSL_SOFTCAP,\n entryPoint: \"main\",\n\n // @binding(0) input: storage read\n // @binding(1) output: storage read_write\n // @binding(2) params: uniform\n bindings: [{ type: \"storage-read\" }, { type: \"storage-read-write\" }, { type: \"uniform\" }],\n\n getDispatchSize(op, resolvedShapes) {\n const count = outputElementCount(op, resolvedShapes);\n return [cdiv(count, 256), 1, 1];\n },\n\n buildParams(op, resolvedShapes) {\n const count = outputElementCount(op, resolvedShapes);\n const cap = op.attributes.cap as number;\n // Params { count: u32, cap_bits: u32 }\n return buildUniformBuffer([count, f32BitsToU32(cap)]);\n },\n};\n\n// ── L2Norm (row-wise L2 normalization, used for embeddings) ──\n\nconst l2NormSpec: KernelSpec = {\n shaderCode: WGSL_L2NORM,\n entryPoint: \"main\",\n\n // @binding(0) src: storage read\n // @binding(1) output: storage read_write\n // @binding(2) params: uniform\n bindings: [{ type: \"storage-read\" }, { type: \"storage-read-write\" }, { type: \"uniform\" }],\n\n getDispatchSize(op, resolvedShapes) {\n // One workgroup per row.\n const rows = resolvedShapes[op.outputs[0]]?.[0] ?? 1;\n return [rows, 1, 1];\n },\n\n buildParams(op, resolvedShapes) {\n const width = op.attributes.width as number;\n const rows = resolvedShapes[op.outputs[0]]?.[0] ?? 1;\n // Params { rows: u32, width: u32 }\n return buildUniformBuffer([rows, width]);\n },\n};\n\n// ── SwiGLU (fused SiLU + Mul) ──\n\nconst swigluSpec: KernelSpec = {\n shaderCode: WGSL_SWIGLU,\n entryPoint: \"main\",\n\n // @binding(0) gate: storage read\n // @binding(1) up: storage read\n // @binding(2) output: storage read_write\n // @binding(3) params: uniform\n bindings: [\n { type: \"storage-read\" },\n { type: \"storage-read\" },\n { type: \"storage-read-write\" },\n { type: \"uniform\" },\n ],\n\n getDispatchSize(op, resolvedShapes) {\n const count = outputElementCount(op, resolvedShapes);\n return [cdiv(count, 256), 1, 1];\n },\n\n buildParams(op, resolvedShapes) {\n const count = outputElementCount(op, resolvedShapes);\n return buildUniformBuffer([count]);\n },\n};\n\n// ── ResidualRMSNorm (fused Add + RMSNorm) ──\n\nconst residualRmsnormSpec: KernelSpec = {\n shaderCode: WGSL_RESIDUAL_RMSNORM,\n entryPoint: \"main\",\n\n // @binding(0) a: storage read\n // @binding(1) b: storage read\n // @binding(2) weight: storage read\n // @binding(3) sum_output: storage read_write\n // @binding(4) norm_output: storage read_write\n // @binding(5) params: uniform\n bindings: [\n { type: \"storage-read\" },\n { type: \"storage-read\" },\n { type: \"storage-read\" },\n { type: \"storage-read-write\" },\n { type: \"storage-read-write\" },\n { type: \"uniform\" },\n ],\n\n getDispatchSize(op, resolvedShapes) {\n const hidden = op.attributes.hidden_size as number;\n const seqLen = resolveSeqLen(op, resolvedShapes, hidden);\n return [seqLen, 1, 1];\n },\n\n buildParams(op, resolvedShapes) {\n const hidden = op.attributes.hidden_size as number;\n const seqLen = resolveSeqLen(op, resolvedShapes, hidden);\n const eps = (op.attributes.eps as number) ?? 1e-6;\n return buildUniformBuffer([seqLen, hidden, f32BitsToU32(eps), 0]);\n },\n};\n\n// ── SiLU ──\n\nconst siluSpec: KernelSpec = {\n shaderCode: WGSL_SILU,\n entryPoint: \"main\",\n\n // @binding(0) input: storage read\n // @binding(1) output: storage read_write\n // @binding(2) params: uniform\n bindings: [{ type: \"storage-read\" }, { type: \"storage-read-write\" }, { type: \"uniform\" }],\n\n getDispatchSize(op, resolvedShapes) {\n const count = outputElementCount(op, resolvedShapes);\n return [cdiv(count, 256), 1, 1];\n },\n\n buildParams(op, resolvedShapes) {\n const count = outputElementCount(op, resolvedShapes);\n // Params { count: u32 }\n return buildUniformBuffer([count]);\n },\n};\n\n// ── GELU ──\n\nconst geluSpec: KernelSpec = {\n shaderCode: WGSL_GELU,\n entryPoint: \"main\",\n\n // @binding(0) input: storage read\n // @binding(1) output: storage read_write\n // @binding(2) params: uniform\n bindings: [{ type: \"storage-read\" }, { type: \"storage-read-write\" }, { type: \"uniform\" }],\n\n getDispatchSize(op, resolvedShapes) {\n const count = outputElementCount(op, resolvedShapes);\n return [cdiv(count, 256), 1, 1];\n },\n\n buildParams(op, resolvedShapes) {\n const count = outputElementCount(op, resolvedShapes);\n // Params { count: u32 }\n return buildUniformBuffer([count]);\n },\n};\n\n// ── GELU (exact erf form — ViT merger) ──\n\nconst geluErfSpec: KernelSpec = {\n shaderCode: WGSL_GELU_ERF,\n entryPoint: \"main\",\n bindings: [{ type: \"storage-read\" }, { type: \"storage-read-write\" }, { type: \"uniform\" }],\n getDispatchSize(op, resolvedShapes) {\n const count = outputElementCount(op, resolvedShapes);\n return [cdiv(count, 256), 1, 1];\n },\n buildParams(op, resolvedShapes) {\n const count = outputElementCount(op, resolvedShapes);\n return buildUniformBuffer([count]);\n },\n};\n\n// ── AddBias (row-broadcast bias add) ──\n\nconst addBiasSpec: KernelSpec = {\n shaderCode: WGSL_ADD_BIAS,\n entryPoint: \"main\",\n\n // @binding(0) input, (1) bias, (2) output, (3) params\n bindings: [\n { type: \"storage-read\" },\n { type: \"storage-read\" },\n { type: \"storage-read-write\" },\n { type: \"uniform\" },\n ],\n getDispatchSize(op, resolvedShapes) {\n const count = outputElementCount(op, resolvedShapes);\n return [cdiv(count, 256), 1, 1];\n },\n buildParams(op, resolvedShapes) {\n const count = outputElementCount(op, resolvedShapes);\n const width = op.attributes.width as number;\n return buildUniformBuffer([count, width]);\n },\n};\n\n// ── SliceCols (extract column range) ──\n\nconst sliceColsSpec: KernelSpec = {\n shaderCode: WGSL_SLICE_COLS,\n entryPoint: \"main\",\n\n // @binding(0) input, (1) output, (2) params\n bindings: [{ type: \"storage-read\" }, { type: \"storage-read-write\" }, { type: \"uniform\" }],\n getDispatchSize(op, resolvedShapes) {\n const count = outputElementCount(op, resolvedShapes);\n return [cdiv(count, 256), 1, 1];\n },\n buildParams(op, resolvedShapes) {\n const in_width = op.attributes.in_width as number;\n const out_width = op.attributes.out_width as number;\n const col_offset = op.attributes.col_offset as number;\n const outShape = resolvedShapes[op.outputs[0]];\n const rows = outShape[0];\n return buildUniformBuffer([rows, in_width, out_width, col_offset]);\n },\n};\n\n// ── MulCols (multiply two column ranges of one tensor) ──\n\nconst mulColsSpec: KernelSpec = {\n shaderCode: WGSL_MUL_COLS,\n entryPoint: \"main\",\n\n // @binding(0) src, (1) output, (2) params\n bindings: [{ type: \"storage-read\" }, { type: \"storage-read-write\" }, { type: \"uniform\" }],\n getDispatchSize(op, resolvedShapes) {\n const count = outputElementCount(op, resolvedShapes);\n return [cdiv(count, 256), 1, 1];\n },\n buildParams(op, resolvedShapes) {\n const in_width = op.attributes.in_width as number;\n const out_width = op.attributes.out_width as number;\n const off_a = op.attributes.off_a as number;\n const off_b = op.attributes.off_b as number;\n const outShape = resolvedShapes[op.outputs[0]];\n const rows = outShape[0];\n return buildUniformBuffer([rows, in_width, out_width, off_a, off_b, 0, 0, 0]);\n },\n};\n\n// ── ApplyRotaryEmb (precomputed cos/sin, rotate_half) ──\n\nconst applyRotarySpec: KernelSpec = {\n shaderCode: WGSL_APPLY_ROTARY,\n entryPoint: \"main\",\n\n // @binding(0) x, (1) cos, (2) sin, (3) output, (4) params\n bindings: [\n { type: \"storage-read\" },\n { type: \"storage-read\" },\n { type: \"storage-read\" },\n { type: \"storage-read-write\" },\n { type: \"uniform\" },\n ],\n getDispatchSize(op, resolvedShapes) {\n const count = outputElementCount(op, resolvedShapes);\n return [cdiv(count, 256), 1, 1];\n },\n buildParams(op, resolvedShapes) {\n const num_heads = op.attributes.num_heads as number;\n const head_dim = op.attributes.head_dim as number;\n const outShape = resolvedShapes[op.outputs[0]];\n const total = outShape.reduce((a, b) => a * b, 1);\n const seq_len = total / (num_heads * head_dim);\n return buildUniformBuffer([seq_len, num_heads, head_dim, 0]);\n },\n};\n\n// ── MatMul ──\n\nconst matmulSpec: KernelSpec = {\n shaderCode: WGSL_MATMUL,\n entryPoint: \"main\",\n\n // @binding(0) A: storage read\n // @binding(1) B: storage read\n // @binding(2) C: storage read_write\n // @binding(3) params: uniform\n bindings: [\n { type: \"storage-read\" },\n { type: \"storage-read\" },\n { type: \"storage-read-write\" },\n { type: \"uniform\" },\n ],\n\n getDispatchSize(op, resolvedShapes) {\n // C[M,N] = A[M,K] * B[K,N]\n // 16x16 workgroup, 4x2 register blocking -> each workgroup covers a\n // 64-row x 32-col output tile. Dispatch X covers N, Y covers M.\n const N = op.attributes.N as number;\n const mTensor = op.attributes.M_tensor as string | undefined;\n let M: number;\n if (mTensor && resolvedShapes[mTensor]) {\n M = resolvedShapes[mTensor][0];\n } else {\n M = op.attributes.M as number;\n }\n return [cdiv(N, 32), cdiv(M, 64), 1];\n },\n\n buildParams(op, resolvedShapes) {\n // Params { M: u32, K: u32, N: u32 }\n const K = op.attributes.K as number;\n const N = op.attributes.N as number;\n // M is dynamic — derive from referenced tensor or static attribute\n const mTensor = op.attributes.M_tensor as string | undefined;\n let M: number;\n if (mTensor && resolvedShapes[mTensor]) {\n M = resolvedShapes[mTensor][0];\n } else {\n M = op.attributes.M as number;\n }\n return buildUniformBuffer([M, K, N]);\n },\n};\n\n// ── MatMulBias (fused MatMul + bias) ──\n\nconst matmulBiasSpec: KernelSpec = {\n shaderCode: WGSL_MATMUL_BIAS,\n entryPoint: \"main\",\n\n // @binding(0) A, (1) B, (2) bias, (3) C, (4) params\n bindings: [\n { type: \"storage-read\" },\n { type: \"storage-read\" },\n { type: \"storage-read\" },\n { type: \"storage-read-write\" },\n { type: \"uniform\" },\n ],\n\n getDispatchSize(op, resolvedShapes) {\n const N = op.attributes.N as number;\n const mTensor = op.attributes.M_tensor as string | undefined;\n let M: number;\n if (mTensor && resolvedShapes[mTensor]) {\n M = resolvedShapes[mTensor][0];\n } else {\n M = op.attributes.M as number;\n }\n return [cdiv(N, 32), cdiv(M, 64), 1];\n },\n\n buildParams(op, resolvedShapes) {\n const K = op.attributes.K as number;\n const N = op.attributes.N as number;\n const mTensor = op.attributes.M_tensor as string | undefined;\n let M: number;\n if (mTensor && resolvedShapes[mTensor]) {\n M = resolvedShapes[mTensor][0];\n } else {\n M = op.attributes.M as number;\n }\n return buildUniformBuffer([M, K, N]);\n },\n};\n\n// ── PoolMatMul (Gemma 4 ViT spatial average-pool as host matrix · encoder out) ──\n\nconst poolMatMulSpec: KernelSpec = {\n shaderCode: WGSL_POOL_MATMUL,\n entryPoint: \"main\",\n\n // @binding(0) poolW [Np,N], (1) hidden [N,W], (2) out [Np,W], (3) params\n bindings: [\n { type: \"storage-read\" },\n { type: \"storage-read\" },\n { type: \"storage-read-write\" },\n { type: \"uniform\" },\n ],\n\n getDispatchSize(op, resolvedShapes) {\n const out = resolvedShapes[op.outputs[0]]; // [Np, W]\n const Np = out[0];\n const W = out[1];\n return [cdiv(Np * W, 64), 1, 1];\n },\n\n buildParams(op, resolvedShapes) {\n const out = resolvedShapes[op.outputs[0]]; // [Np, W]\n const Np = out[0];\n const W = out[1];\n // N = contraction dim = rows of the hidden tensor (op.inputs[1]).\n const N = resolvedShapes[op.inputs[1]]?.[0] ?? (op.attributes.hidden_size as number);\n // Params { Np: u32, N: u32, W: u32 }\n return buildUniformBuffer([Np, N, W]);\n },\n};\n\n// ── ClippedMatMul (Gemma 4 ViT Gemma4ClippableLinear) ──\n\nconst clippedMatMulSpec: KernelSpec = {\n shaderCode: WGSL_CLIPPED_MATMUL,\n entryPoint: \"main\",\n\n // @binding(0) A, (1) B, (2) C, (3) params\n bindings: [\n { type: \"storage-read\" },\n { type: \"storage-read\" },\n { type: \"storage-read-write\" },\n { type: \"uniform\" },\n ],\n\n getDispatchSize(op, resolvedShapes) {\n const N = op.attributes.N as number;\n const mTensor = op.attributes.M_tensor as string | undefined;\n const M =\n mTensor && resolvedShapes[mTensor] ? resolvedShapes[mTensor][0] : (op.attributes.M as number);\n return [cdiv(N, 32), cdiv(M, 64), 1];\n },\n\n buildParams(op, resolvedShapes) {\n const K = op.attributes.K as number;\n const N = op.attributes.N as number;\n const mTensor = op.attributes.M_tensor as string | undefined;\n const M =\n mTensor && resolvedShapes[mTensor] ? resolvedShapes[mTensor][0] : (op.attributes.M as number);\n // Clip scalars default to ±inf (identity) until the loader patches them.\n const imin = (op.attributes.imin as number) ?? Number.NEGATIVE_INFINITY;\n const imax = (op.attributes.imax as number) ?? Number.POSITIVE_INFINITY;\n const omin = (op.attributes.omin as number) ?? Number.NEGATIVE_INFINITY;\n const omax = (op.attributes.omax as number) ?? Number.POSITIVE_INFINITY;\n // Params { M, K, N, imin, imax, omin, omax } — first 3 u32, rest f32 bits.\n return buildUniformBuffer([\n M,\n K,\n N,\n f32BitsToU32(imin),\n f32BitsToU32(imax),\n f32BitsToU32(omin),\n f32BitsToU32(omax),\n ]);\n },\n};\n\n// Mixed-precision (f16 multiply, f32 accumulate) MatMulBias for ViT linear layers.\n// Same bindings/dispatch/params as matmulBiasSpec; binding(1) B is an f16 weight.\n// The VisionExecutor swaps this in when the device has shader-f16.\nexport const MATMUL_BIAS_F16C_SPEC: KernelSpec = {\n ...matmulBiasSpec,\n shaderCode: WGSL_MATMUL_BIAS_F16MIX,\n};\n\n// ── MatMulInt4 ──\n\nconst matmulInt4Spec: KernelSpec = {\n shaderCode: WGSL_MATMUL_INT4,\n entryPoint: \"main\",\n\n // @binding(0) A: storage read\n // @binding(1) B_q: storage read\n // @binding(2) scales: storage read\n // @binding(3) zeros: storage read\n // @binding(4) C: storage read_write\n // @binding(5) params: uniform\n bindings: [\n { type: \"storage-read\" },\n { type: \"storage-read\" },\n { type: \"storage-read\" },\n { type: \"storage-read\" },\n { type: \"storage-read-write\" },\n { type: \"uniform\" },\n ],\n\n getDispatchSize(op, resolvedShapes) {\n // Workgroup size is (16, 16), dispatch X covers N, Y covers M\n const N = op.attributes.N as number;\n const mTensor = op.attributes.M_tensor as string | undefined;\n let M: number;\n if (mTensor && resolvedShapes[mTensor]) {\n M = resolvedShapes[mTensor][0];\n } else {\n M = op.attributes.M as number;\n }\n return [cdiv(N, 16), cdiv(M, 16), 1];\n },\n\n buildParams(op, resolvedShapes) {\n // Params { M: u32, K: u32, N: u32, group_size: u32 }\n const K = op.attributes.K as number;\n const N = op.attributes.N as number;\n const group_size = op.attributes.group_size as number;\n const mTensor = op.attributes.M_tensor as string | undefined;\n let M: number;\n if (mTensor && resolvedShapes[mTensor]) {\n M = resolvedShapes[mTensor][0];\n } else {\n M = op.attributes.M as number;\n }\n return buildUniformBuffer([M, K, N, group_size]);\n },\n};\n\n// ── MatVec (F32, M=1 decode) ──\n\nexport const MATVEC_SPEC: KernelSpec = {\n shaderCode: WGSL_MATVEC,\n entryPoint: \"main\",\n\n // Same binding layout as MatMul: A, B, C, params\n bindings: [\n { type: \"storage-read\" },\n { type: \"storage-read\" },\n { type: \"storage-read-write\" },\n { type: \"uniform\" },\n ],\n\n getDispatchSize(op) {\n const N = op.attributes.N as number;\n const N_TILE = 8;\n return [cdiv(N, N_TILE), 1, 1];\n },\n\n buildParams(op) {\n const K = op.attributes.K as number;\n const N = op.attributes.N as number;\n return buildUniformBuffer([K, N]);\n },\n};\n\n// ── MatVecInt4 (K-parallel INT4, M=1 decode) ──\n\nexport const MATVEC_INT4_SPEC: KernelSpec = {\n shaderCode: WGSL_MATVEC_INT4,\n entryPoint: \"main\",\n\n // Same binding layout as MatMulInt4: A, B_q, scales, zeros, C, params\n bindings: [\n { type: \"storage-read\" },\n { type: \"storage-read\" },\n { type: \"storage-read\" },\n { type: \"storage-read\" },\n { type: \"storage-read-write\" },\n { type: \"uniform\" },\n ],\n\n getDispatchSize(op) {\n const N = op.attributes.N as number;\n // Must match WGSL N_TILE = workgroup_size / K_THREADS = 256 / 16 = 16\n const N_TILE = 16;\n return [cdiv(N, N_TILE), 1, 1];\n },\n\n buildParams(op) {\n const K = op.attributes.K as number;\n const N = op.attributes.N as number;\n const group_size = op.attributes.group_size as number;\n return buildUniformBuffer([K, N, group_size, 0]);\n },\n};\n\n// ── Gated MatVecInt4 (fused attn sigmoid-gate + INT4 o_proj, M=1 decode) ──\n\nexport const GATED_MATVEC_INT4_SPEC: KernelSpec = {\n shaderCode: WGSL_GATED_MATVEC_INT4,\n entryPoint: \"main\",\n\n // @binding(0) attn, (1) gate, (2) B_q, (3) scales, (4) zeros, (5) C, (6) params\n bindings: [\n { type: \"storage-read\" },\n { type: \"storage-read\" },\n { type: \"storage-read\" },\n { type: \"storage-read\" },\n { type: \"storage-read\" },\n { type: \"storage-read-write\" },\n { type: \"uniform\" },\n ],\n\n getDispatchSize(op) {\n const N = op.attributes.N as number;\n const N_TILE = 8;\n return [cdiv(N, N_TILE), 1, 1];\n },\n\n buildParams(op) {\n const K = op.attributes.K as number;\n const N = op.attributes.N as number;\n const group_size = op.attributes.group_size as number;\n return buildUniformBuffer([K, N, group_size, 0]);\n },\n};\n\n// ── SwiGLU-gated MatVecInt4 (fused SwiGLU + INT4 projection, M=1 decode) ──\n\nexport const SWIGLU_GATED_MATVEC_INT4_SPEC: KernelSpec = {\n ...GATED_MATVEC_INT4_SPEC,\n shaderCode: WGSL_SWIGLU_GATED_MATVEC_INT4,\n};\n\n// ── MatVec subgroups variants (desktop-only fast path) ──\n//\n// Same binding layout, dispatch size, and params as the portable MatVec specs —\n// only the shader differs (subgroupAdd reduction). The executor selects these\n// when the device exposes the \"subgroups\" feature and is NOT WebKit. Kept as\n// distinct specs so the portable kernels remain the universal fallback.\n\nexport const MATVEC_SUBGROUPS_SPEC: KernelSpec = {\n ...MATVEC_SPEC,\n shaderCode: WGSL_MATVEC_SUBGROUPS,\n};\n\nexport const MATVEC_INT4_SUBGROUPS_SPEC: KernelSpec = {\n ...MATVEC_INT4_SPEC,\n shaderCode: WGSL_MATVEC_INT4_SUBGROUPS,\n};\n\n// ── SwiGLU MatVec (fused gate+up+SwiGLU, M=1 decode) ──\n\nexport const SWIGLU_MATVEC_SPEC: KernelSpec = {\n shaderCode: WGSL_SWIGLU_MATVEC,\n entryPoint: \"main\",\n\n // @binding(0) A: storage read\n // @binding(1) B_gate: storage read\n // @binding(2) B_up: storage read\n // @binding(3) output: storage read_write\n // @binding(4) params: uniform\n bindings: [\n { type: \"storage-read\" },\n { type: \"storage-read\" },\n { type: \"storage-read\" },\n { type: \"storage-read-write\" },\n { type: \"uniform\" },\n ],\n\n getDispatchSize(op) {\n const N = op.attributes.N as number;\n const N_TILE = 8;\n return [cdiv(N, N_TILE), 1, 1];\n },\n\n buildParams(op) {\n const K = op.attributes.K as number;\n const N = op.attributes.N as number;\n return buildUniformBuffer([K, N]);\n },\n};\n\n// ── SwiGLU MatVecInt4 (fused gate+up+SwiGLU INT4, M=1 decode) ──\n\nexport const SWIGLU_MATVEC_INT4_SPEC: KernelSpec = {\n shaderCode: WGSL_SWIGLU_MATVEC_INT4,\n entryPoint: \"main\",\n\n // @binding(0) A: storage read\n // @binding(1) B_gate_q: storage read\n // @binding(2) B_gate_scales: storage read\n // @binding(3) B_gate_zeros: storage read\n // @binding(4) B_up_q: storage read\n // @binding(5) B_up_scales: storage read\n // @binding(6) B_up_zeros: storage read\n // @binding(7) output: storage read_write\n // @binding(8) params: uniform\n bindings: [\n { type: \"storage-read\" },\n { type: \"storage-read\" },\n { type: \"storage-read\" },\n { type: \"storage-read\" },\n { type: \"storage-read\" },\n { type: \"storage-read\" },\n { type: \"storage-read\" },\n { type: \"storage-read-write\" },\n { type: \"uniform\" },\n ],\n\n getDispatchSize(op) {\n const N = op.attributes.N as number;\n const N_TILE = 8;\n return [cdiv(N, N_TILE), 1, 1];\n },\n\n buildParams(op) {\n const K = op.attributes.K as number;\n const N = op.attributes.N as number;\n const group_size = op.attributes.group_size as number;\n return buildUniformBuffer([K, N, group_size, 0]);\n },\n};\n\n// ── Dual MatVecInt4 (two INT4 projections sharing input A, M=1 decode) ──\n\nexport const DUAL_MATVEC_INT4_SPEC: KernelSpec = {\n shaderCode: WGSL_DUAL_MATVEC_INT4,\n entryPoint: \"main\",\n\n // @binding(0) A\n // @binding(1..3) B0_q, B0_scales, B0_zeros\n // @binding(4..6) B1_q, B1_scales, B1_zeros\n // @binding(7) out0, @binding(8) out1, @binding(9) params\n bindings: [\n { type: \"storage-read\" },\n { type: \"storage-read\" },\n { type: \"storage-read\" },\n { type: \"storage-read\" },\n { type: \"storage-read\" },\n { type: \"storage-read\" },\n { type: \"storage-read\" },\n { type: \"storage-read-write\" },\n { type: \"storage-read-write\" },\n { type: \"uniform\" },\n ],\n\n getDispatchSize(op) {\n const N = op.attributes.N as number;\n const N_TILE = 8;\n return [cdiv(N, N_TILE), 1, 1];\n },\n\n buildParams(op) {\n const K = op.attributes.K as number;\n const N = op.attributes.N as number;\n const group_size = op.attributes.group_size as number;\n return buildUniformBuffer([K, N, group_size, 0]);\n },\n};\n\n// ── Argmax (GPU-side, single workgroup) ──\n\nexport const ARGMAX_SPEC: KernelSpec = {\n shaderCode: WGSL_ARGMAX,\n entryPoint: \"main\",\n\n bindings: [{ type: \"storage-read\" }, { type: \"storage-read-write\" }, { type: \"uniform\" }],\n\n getDispatchSize() {\n return [1, 1, 1]; // Single workgroup\n },\n\n buildParams(_op, _resolvedShapes) {\n // Params built externally by executor (count + offset)\n return buildUniformBuffer([0, 0]);\n },\n};\n\n// ── RMSNorm ──\n\nconst rmsnormSpec: KernelSpec = {\n shaderCode: WGSL_RMSNORM,\n entryPoint: \"main\",\n\n // @binding(0) input: storage read\n // @binding(1) weight: storage read\n // @binding(2) output: storage read_write\n // @binding(3) params: uniform\n bindings: [\n { type: \"storage-read\" },\n { type: \"storage-read\" },\n { type: \"storage-read-write\" },\n { type: \"uniform\" },\n ],\n\n getDispatchSize(op, resolvedShapes) {\n // One workgroup per row (token)\n const hidden_size = op.attributes.hidden_size as number;\n const ref = op.attributes.seq_len_tensor as string | undefined;\n let seq_len: number;\n if (ref && resolvedShapes[ref]) {\n const shape = resolvedShapes[ref];\n const total = shape.reduce((a: number, b: number) => a * b, 1);\n seq_len = total / hidden_size;\n } else {\n seq_len = op.attributes.seq_len as number;\n }\n return [seq_len, 1, 1];\n },\n\n buildParams(op, resolvedShapes) {\n // Params { seq_len: u32, hidden_size: u32, eps_bits: u32, _pad: u32 }\n const hidden_size = op.attributes.hidden_size as number;\n const eps = op.attributes.eps as number;\n const ref = op.attributes.seq_len_tensor as string | undefined;\n let seq_len: number;\n if (ref && resolvedShapes[ref]) {\n const shape = resolvedShapes[ref];\n const total = shape.reduce((a: number, b: number) => a * b, 1);\n seq_len = total / hidden_size;\n } else {\n seq_len = op.attributes.seq_len as number;\n }\n return buildUniformBuffer([\n seq_len,\n hidden_size,\n f32BitsToU32(eps),\n 0, // _pad\n ]);\n },\n};\n\n// ── Dual RMSNorm (two per-row RMSNorms, one dispatch) ──\n\nfunction dualRmsnormRows(\n op: OpNode,\n resolvedShapes: Record<string, number[]>,\n refKey: string,\n fallbackKey: string,\n): number {\n const hidden_size = op.attributes.hidden_size as number;\n const ref = op.attributes[refKey] as string | undefined;\n if (ref && resolvedShapes[ref]) {\n const total = resolvedShapes[ref].reduce((a: number, b: number) => a * b, 1);\n return total / hidden_size;\n }\n return op.attributes[fallbackKey] as number;\n}\n\nexport const DUAL_RMSNORM_SPEC: KernelSpec = {\n shaderCode: WGSL_DUAL_RMSNORM,\n entryPoint: \"main\",\n\n // input0, weight0, input1, weight1, output0, output1, params\n bindings: [\n { type: \"storage-read\" },\n { type: \"storage-read\" },\n { type: \"storage-read\" },\n { type: \"storage-read\" },\n { type: \"storage-read-write\" },\n { type: \"storage-read-write\" },\n { type: \"uniform\" },\n ],\n\n getDispatchSize(op, resolvedShapes) {\n const rows0 = dualRmsnormRows(op, resolvedShapes, \"seq_len_tensor0\", \"seq_len0\");\n const rows1 = dualRmsnormRows(op, resolvedShapes, \"seq_len_tensor1\", \"seq_len1\");\n return [rows0 + rows1, 1, 1];\n },\n\n buildParams(op, resolvedShapes) {\n const rows0 = dualRmsnormRows(op, resolvedShapes, \"seq_len_tensor0\", \"seq_len0\");\n const rows1 = dualRmsnormRows(op, resolvedShapes, \"seq_len_tensor1\", \"seq_len1\");\n const hidden_size = op.attributes.hidden_size as number;\n const eps = op.attributes.eps as number;\n return buildUniformBuffer([rows0, rows1, hidden_size, f32BitsToU32(eps)]);\n },\n};\n\n// ── LayerNorm ──\n\nconst layernormSpec: KernelSpec = {\n shaderCode: WGSL_LAYERNORM,\n entryPoint: \"main\",\n\n // @binding(0) input: storage read\n // @binding(1) weight: storage read\n // @binding(2) bias: storage read\n // @binding(3) output: storage read_write\n // @binding(4) params: uniform\n bindings: [\n { type: \"storage-read\" },\n { type: \"storage-read\" },\n { type: \"storage-read\" },\n { type: \"storage-read-write\" },\n { type: \"uniform\" },\n ],\n\n getDispatchSize(op, resolvedShapes) {\n // One workgroup per row (token)\n const hidden_size = op.attributes.hidden_size as number;\n const ref = op.attributes.seq_len_tensor as string | undefined;\n let seq_len: number;\n if (ref && resolvedShapes[ref]) {\n const shape = resolvedShapes[ref];\n const total = shape.reduce((a: number, b: number) => a * b, 1);\n seq_len = total / hidden_size;\n } else {\n seq_len = op.attributes.seq_len as number;\n }\n return [seq_len, 1, 1];\n },\n\n buildParams(op, resolvedShapes) {\n // Params { seq_len: u32, hidden_size: u32, eps_bits: u32, _pad: u32 }\n const hidden_size = op.attributes.hidden_size as number;\n const eps = op.attributes.eps as number;\n const ref = op.attributes.seq_len_tensor as string | undefined;\n let seq_len: number;\n if (ref && resolvedShapes[ref]) {\n const shape = resolvedShapes[ref];\n const total = shape.reduce((a: number, b: number) => a * b, 1);\n seq_len = total / hidden_size;\n } else {\n seq_len = op.attributes.seq_len as number;\n }\n return buildUniformBuffer([\n seq_len,\n hidden_size,\n f32BitsToU32(eps),\n 0, // _pad\n ]);\n },\n};\n\n// ── Embedding ──\n\nconst embeddingSpec: KernelSpec = {\n shaderCode: WGSL_EMBEDDING,\n entryPoint: \"main\",\n\n // @binding(0) input_ids: storage read\n // @binding(1) weight: storage read\n // @binding(2) output: storage read_write\n // @binding(3) params: uniform\n bindings: [\n { type: \"storage-read\" },\n { type: \"storage-read\" },\n { type: \"storage-read-write\" },\n { type: \"uniform\" },\n ],\n\n getDispatchSize(op, resolvedShapes) {\n const hidden_size = op.attributes.hidden_size as number;\n const outShape = resolvedShapes[op.outputs[0]];\n const seq_len = outShape ? outShape[0] : (op.attributes.seq_len as number);\n return [cdiv(seq_len * hidden_size, 256), 1, 1];\n },\n\n buildParams(op, resolvedShapes) {\n // Params { seq_len: u32, hidden_size: u32 }\n const hidden_size = op.attributes.hidden_size as number;\n const outShape = resolvedShapes[op.outputs[0]];\n const seq_len = outShape ? outShape[0] : (op.attributes.seq_len as number);\n return buildUniformBuffer([seq_len, hidden_size]);\n },\n};\n\n// ── EmbeddingInt4 ──\n\nconst embeddingInt4Spec: KernelSpec = {\n shaderCode: WGSL_EMBEDDING_INT4,\n entryPoint: \"main\",\n\n // @binding(0) input_ids: storage read\n // @binding(1) weight_q: storage read\n // @binding(2) scales: storage read\n // @binding(3) zeros: storage read\n // @binding(4) output: storage read_write\n // @binding(5) params: uniform\n bindings: [\n { type: \"storage-read\" },\n { type: \"storage-read\" },\n { type: \"storage-read\" },\n { type: \"storage-read\" },\n { type: \"storage-read-write\" },\n { type: \"uniform\" },\n ],\n\n getDispatchSize(op, resolvedShapes) {\n const hidden_size = op.attributes.hidden_size as number;\n const outShape = resolvedShapes[op.outputs[0]];\n const seq_len = outShape ? outShape[0] : (op.attributes.seq_len as number);\n return [cdiv(seq_len * hidden_size, 256), 1, 1];\n },\n\n buildParams(op, resolvedShapes) {\n // Params { seq_len: u32, hidden_size: u32, group_size: u32, _pad: u32 }\n const hidden_size = op.attributes.hidden_size as number;\n const group_size = op.attributes.group_size as number;\n const outShape = resolvedShapes[op.outputs[0]];\n const seq_len = outShape ? outShape[0] : (op.attributes.seq_len as number);\n return buildUniformBuffer([seq_len, hidden_size, group_size, 0]);\n },\n};\n\n// ── RoPE ──\n\nconst ropeSpec: KernelSpec = {\n shaderCode: WGSL_ROPE,\n entryPoint: \"main\",\n\n // @binding(0) q: storage read_write\n // @binding(1) k: storage read_write\n // @binding(2) params: uniform\n bindings: [{ type: \"storage-read-write\" }, { type: \"storage-read-write\" }, { type: \"uniform\" }],\n\n getDispatchSize(op, resolvedShapes) {\n const num_q_heads = op.attributes.num_q_heads as number;\n const num_kv_heads = op.attributes.num_kv_heads as number;\n const head_dim = op.attributes.head_dim as number;\n const rope_dim = (op.attributes.rope_dim as number) ?? head_dim;\n // rope_half is the rotate_half pairing offset / pair count. Defaults to\n // rope_dim/2 (legacy), but Gemma \"proportional\" overrides it to head_dim/2.\n const rope_half = (op.attributes.rope_half as number) ?? rope_dim / 2;\n\n const seqRef = op.attributes.seq_len_tensor as string | undefined;\n let seq_len: number;\n if (seqRef && resolvedShapes[seqRef]) {\n seq_len = resolvedShapes[seqRef][0];\n } else {\n seq_len = op.attributes.seq_len as number;\n }\n\n const total_q_pairs = seq_len * num_q_heads * rope_half;\n const total_k_pairs = seq_len * num_kv_heads * rope_half;\n return [cdiv(Math.max(total_q_pairs, total_k_pairs), 256), 1, 1];\n },\n\n buildParams(op, resolvedShapes, context) {\n // Params { seq_len, num_q_heads, num_kv_heads, head_dim, rope_base_bits,\n // position_offset, rope_dim, rope_half, rope_denom, rope_active_pairs, _pad0, _pad1 }\n const num_q_heads = op.attributes.num_q_heads as number;\n const num_kv_heads = op.attributes.num_kv_heads as number;\n const head_dim = op.attributes.head_dim as number;\n const rope_base = op.attributes.rope_base as number;\n const rope_dim = (op.attributes.rope_dim as number) ?? head_dim;\n // Decoupled RoPE knobs (default to legacy single-rope_dim behavior):\n // rope_half = rotate_half pairing offset + pair count (legacy rope_dim/2)\n // rope_denom = inv_freq exponent denominator (legacy rope_dim)\n // rope_active_pairs = pairs with a real frequency; rest are identity (legacy rope_dim/2)\n const rope_half = (op.attributes.rope_half as number) ?? rope_dim / 2;\n const rope_denom = (op.attributes.rope_denom as number) ?? rope_dim;\n const rope_active_pairs = (op.attributes.rope_active_pairs as number) ?? rope_dim / 2;\n // Use runtime seqPos for position offset during autoregressive decode\n const position_offset = context?.seqPos ?? (op.attributes.position_offset as number) ?? 0;\n\n const seqRef = op.attributes.seq_len_tensor as string | undefined;\n let seq_len: number;\n if (seqRef && resolvedShapes[seqRef]) {\n seq_len = resolvedShapes[seqRef][0];\n } else {\n seq_len = op.attributes.seq_len as number;\n }\n\n return buildUniformBuffer([\n seq_len,\n num_q_heads,\n num_kv_heads,\n head_dim,\n f32BitsToU32(rope_base),\n position_offset,\n rope_dim,\n rope_half,\n rope_denom,\n rope_active_pairs,\n 0, // _pad0\n 0, // _pad1\n ]);\n },\n};\n\n/**\n * Interleaved (adjacent-pair) RoPE, used by Moonshine. Identical dispatch/params\n * to ropeSpec — only the rotation pairing differs (2p/2p+1 vs p/p+half). Selected\n * by the executors when a RoPE node carries `interleaved: true`.\n */\nexport const ROPE_INTERLEAVED_SPEC: KernelSpec = {\n ...ropeSpec,\n shaderCode: WGSL_ROPE_INTERLEAVED,\n};\n\n// ── M-RoPE (precomputed cos/sin, partial rotate_half) ──\n\nconst mropeSpec: KernelSpec = {\n shaderCode: WGSL_MROPE,\n entryPoint: \"main\",\n // @binding(0) q rw, (1) k rw, (2) cos, (3) sin, (4) params\n bindings: [\n { type: \"storage-read-write\" },\n { type: \"storage-read-write\" },\n { type: \"storage-read\" },\n { type: \"storage-read\" },\n { type: \"uniform\" },\n ],\n getDispatchSize(op, resolvedShapes) {\n const num_q_heads = op.attributes.num_q_heads as number;\n const num_kv_heads = op.attributes.num_kv_heads as number;\n const rope_dim = op.attributes.rope_dim as number;\n const half = rope_dim / 2;\n const seqRef = op.attributes.seq_len_tensor as string | undefined;\n const seq_len =\n seqRef && resolvedShapes[seqRef]\n ? resolvedShapes[seqRef][0]\n : (op.attributes.seq_len as number);\n const total = seq_len * Math.max(num_q_heads, num_kv_heads) * half;\n return [cdiv(total, 256), 1, 1];\n },\n buildParams(op, resolvedShapes, context) {\n const num_q_heads = op.attributes.num_q_heads as number;\n const num_kv_heads = op.attributes.num_kv_heads as number;\n const head_dim = op.attributes.head_dim as number;\n const rope_dim = op.attributes.rope_dim as number;\n const seqRef = op.attributes.seq_len_tensor as string | undefined;\n const seq_len =\n seqRef && resolvedShapes[seqRef]\n ? resolvedShapes[seqRef][0]\n : (op.attributes.seq_len as number);\n // Decode (single-token) reads cos/sin row seqPos; prefill writes all rows 0..T.\n const pos_offset = seq_len === 1 ? (context?.seqPos ?? 0) : 0;\n return buildUniformBuffer([\n seq_len,\n num_q_heads,\n num_kv_heads,\n head_dim,\n rope_dim,\n pos_offset,\n 0,\n 0,\n ]);\n },\n};\n\n// ── EmbedSplice (scatter vision tokens into image-token rows) ──\n\nconst embedSpliceSpec: KernelSpec = {\n shaderCode: WGSL_EMBED_SPLICE,\n entryPoint: \"main\",\n // @binding(0) embed_in, (1) vision, (2) row_map, (3) embed_out, (4) params\n bindings: [\n { type: \"storage-read\" },\n { type: \"storage-read\" },\n { type: \"storage-read\" },\n { type: \"storage-read-write\" },\n { type: \"uniform\" },\n ],\n getDispatchSize(op, resolvedShapes) {\n const hidden = op.attributes.hidden as number;\n const seqRef = op.attributes.seq_len_tensor as string;\n const seq_len = resolvedShapes[seqRef][0];\n return [cdiv(seq_len * hidden, 256), 1, 1];\n },\n buildParams(op, resolvedShapes) {\n const hidden = op.attributes.hidden as number;\n const seqRef = op.attributes.seq_len_tensor as string;\n const seq_len = resolvedShapes[seqRef][0];\n return buildUniformBuffer([seq_len, hidden]);\n },\n};\n\n// ── Attention ──\n\nconst attentionSpec: KernelSpec = {\n shaderCode: WGSL_ATTENTION,\n entryPoint: \"main\",\n\n // @binding(0) Q: storage read\n // @binding(1) K: storage read\n // @binding(2) V: storage read\n // @binding(3) output: storage read_write\n // @binding(4) params: uniform\n bindings: [\n { type: \"storage-read\" },\n { type: \"storage-read\" },\n { type: \"storage-read\" },\n { type: \"storage-read-write\" },\n { type: \"uniform\" },\n ],\n\n getDispatchSize(op, resolvedShapes) {\n // One workgroup per (query_position, q_head) pair\n const num_q_heads = op.attributes.num_q_heads as number;\n // Resolve T from output shape or static attribute\n const outShape = resolvedShapes[op.outputs[0]];\n const T = outShape ? outShape[0] : (op.attributes.T as number);\n return [T, num_q_heads, 1];\n },\n\n buildParams(op, resolvedShapes, context) {\n // Params { T, S, num_q_heads, num_kv_heads, head_dim, position_offset, is_causal }\n const num_q_heads = op.attributes.num_q_heads as number;\n const num_kv_heads = op.attributes.num_kv_heads as number;\n const head_dim = op.attributes.head_dim as number;\n // Use runtime seqPos for position offset during autoregressive decode\n const position_offset = context?.seqPos ?? (op.attributes.position_offset as number) ?? 0;\n\n // Resolve T from output shape or static attribute\n const outShape = resolvedShapes[op.outputs[0]];\n const T = outShape ? outShape[0] : (op.attributes.T as number);\n\n // S = total KV length (seqPos + T from KV cache, or T for prefill-only)\n const kvShape = resolvedShapes[op.inputs[1]]; // K cache shape: [L_max, kv_dim]\n const S = kvShape ? kvShape[0] : T;\n\n // Default causal (text). ViT attention sets causal:false → bidirectional.\n const is_causal = (op.attributes.causal as boolean) === false ? 0 : 1;\n\n // Query-grid base offset (default 0). The WebKit vision encoder sets this\n // per-chunk to split the O(N²) ViT attention into watchdog-safe windows; all\n // other callers leave it 0, keeping their dispatch byte-identical.\n const q_offset = context?.qOffset ?? 0;\n\n // Softmax QK temperature. Defaults to 1/sqrt(head_dim) — byte-identical to\n // the legacy kernel-hardcoded scale, so every existing caller is unchanged.\n // Gemma 4 sets attn_scale:1.0 (its per-head QK-norm absorbs the scale).\n const attn_scale = (op.attributes.attn_scale as number) ?? 1 / Math.sqrt(head_dim);\n\n return buildUniformBuffer([\n T,\n S,\n num_q_heads,\n num_kv_heads,\n head_dim,\n position_offset,\n is_causal,\n q_offset,\n f32BitsToU32(attn_scale),\n ]);\n },\n};\n\n// ── Cross-Attention (encoder-decoder) ──\n//\n// Inputs: Q (decoder hidden) [T, num_q_heads*head_dim],\n// K/V (frozen encoder output) [S, num_kv_heads*head_dim].\n// Output: [T, num_q_heads*head_dim]. One workgroup per (q_pos, q_head).\nconst crossAttentionSpec: KernelSpec = {\n shaderCode: WGSL_CROSS_ATTENTION,\n entryPoint: \"main\",\n\n // @binding(0) Q: storage read\n // @binding(1) K: storage read (encoder output, frozen)\n // @binding(2) V: storage read (encoder output, frozen)\n // @binding(3) output: storage read_write\n // @binding(4) params: uniform\n bindings: [\n { type: \"storage-read\" },\n { type: \"storage-read\" },\n { type: \"storage-read\" },\n { type: \"storage-read-write\" },\n { type: \"uniform\" },\n ],\n\n getDispatchSize(op, resolvedShapes) {\n const num_q_heads = op.attributes.num_q_heads as number;\n // T = decoder query length (rows of the output / Q tensor)\n const outShape = resolvedShapes[op.outputs[0]];\n const T = outShape ? outShape[0] : (op.attributes.T as number);\n return [T, num_q_heads, 1];\n },\n\n buildParams(op, resolvedShapes) {\n // Params { T, S, num_q_heads, num_kv_heads, head_dim }\n const num_q_heads = op.attributes.num_q_heads as number;\n const num_kv_heads = op.attributes.num_kv_heads as number;\n const head_dim = op.attributes.head_dim as number;\n\n // T = decoder query length (rows of Q / output)\n const outShape = resolvedShapes[op.outputs[0]];\n const T = outShape ? outShape[0] : (op.attributes.T as number);\n\n // S = encoder length (rows of the frozen encoder K input)\n const kShape = resolvedShapes[op.inputs[1]];\n const S = kShape ? kShape[0] : (op.attributes.S as number);\n\n return buildUniformBuffer([T, S, num_q_heads, num_kv_heads, head_dim]);\n },\n};\n\n// ── KVCacheAppend Packed F16 (f32 src → packed u32 dst, Safari-safe) ──\n\nexport const KV_CACHE_APPEND_PACKED_F16_SPEC: KernelSpec = {\n shaderCode: WGSL_KV_CACHE_APPEND_PACKED_F16,\n entryPoint: \"main\",\n\n // @binding(0) src: storage read (f32)\n // @binding(1) dst: storage read_write (packed u32)\n // @binding(2) params: uniform\n bindings: [{ type: \"storage-read\" }, { type: \"storage-read-write\" }, { type: \"uniform\" }],\n\n getDispatchSize(op, resolvedShapes) {\n const width = op.attributes.width as number;\n const srcRef = op.attributes.T_tensor as string | undefined;\n let T: number;\n if (srcRef && resolvedShapes[srcRef]) {\n T = resolvedShapes[srcRef][0];\n } else {\n T = op.attributes.T as number;\n }\n // Each thread processes a pair of f32 values → half the dispatch count\n return [cdiv((T * width) / 2, 256), 1, 1];\n },\n\n buildParams(op, resolvedShapes, context) {\n const width = op.attributes.width as number;\n const seqPos = context?.seqPos ?? 0;\n const srcRef = op.attributes.T_tensor as string | undefined;\n let T: number;\n if (srcRef && resolvedShapes[srcRef]) {\n T = resolvedShapes[srcRef][0];\n } else {\n T = op.attributes.T as number;\n }\n // Same params as native f16 — kernel internally divides by 2\n return buildUniformBuffer([T * width, seqPos * width]);\n },\n};\n\n// ── Attention Packed F16 (packed u32 KV cache, Safari-safe) ──\n\nexport const ATTENTION_PACKED_F16_SPEC: KernelSpec = {\n shaderCode: WGSL_ATTENTION_PACKED_F16,\n entryPoint: \"main\",\n\n // @binding(0) Q: storage read (f32)\n // @binding(1) K: storage read (packed u32)\n // @binding(2) V: storage read (packed u32)\n // @binding(3) output: storage read_write (f32)\n // @binding(4) params: uniform\n bindings: [\n { type: \"storage-read\" },\n { type: \"storage-read\" },\n { type: \"storage-read\" },\n { type: \"storage-read-write\" },\n { type: \"uniform\" },\n ],\n\n getDispatchSize(op, resolvedShapes) {\n const num_q_heads = op.attributes.num_q_heads as number;\n const outShape = resolvedShapes[op.outputs[0]];\n const T = outShape ? outShape[0] : (op.attributes.T as number);\n return [T, num_q_heads, 1];\n },\n\n buildParams(op, resolvedShapes, context) {\n const num_q_heads = op.attributes.num_q_heads as number;\n const num_kv_heads = op.attributes.num_kv_heads as number;\n const head_dim = op.attributes.head_dim as number;\n const position_offset = context?.seqPos ?? (op.attributes.position_offset as number) ?? 0;\n\n const outShape = resolvedShapes[op.outputs[0]];\n const T = outShape ? outShape[0] : (op.attributes.T as number);\n\n const kvShape = resolvedShapes[op.inputs[1]];\n const S = kvShape ? kvShape[0] : T;\n\n // Default 1/sqrt(head_dim) → byte-identical to the legacy scale; Gemma4 = 1.0.\n const attn_scale = (op.attributes.attn_scale as number) ?? 1 / Math.sqrt(head_dim);\n\n return buildUniformBuffer([\n T,\n S,\n num_q_heads,\n num_kv_heads,\n head_dim,\n position_offset,\n f32BitsToU32(attn_scale),\n ]);\n },\n};\n\n// ── Softmax ──\n\nconst softmaxSpec: KernelSpec = {\n shaderCode: WGSL_SOFTMAX,\n entryPoint: \"main\",\n\n // @binding(0) input: storage read\n // @binding(1) output: storage read_write\n // @binding(2) params: uniform\n bindings: [{ type: \"storage-read\" }, { type: \"storage-read-write\" }, { type: \"uniform\" }],\n\n getDispatchSize(op) {\n // One workgroup per row\n const num_rows = op.attributes.num_rows as number;\n return [num_rows, 1, 1];\n },\n\n buildParams(op) {\n // Params { num_rows: u32, row_size: u32 }\n const num_rows = op.attributes.num_rows as number;\n const row_size = op.attributes.row_size as number;\n return buildUniformBuffer([num_rows, row_size]);\n },\n};\n\n// ── CausalConv1d ──\n\nconst causalConv1dSpec: KernelSpec = {\n shaderCode: WGSL_CAUSAL_CONV1D,\n entryPoint: \"main\",\n\n // @binding(0) input: storage read\n // @binding(1) weight: storage read\n // @binding(2) conv_state: storage read\n // @binding(3) output: storage read_write\n // @binding(4) params: uniform\n bindings: [\n { type: \"storage-read\" },\n { type: \"storage-read\" },\n { type: \"storage-read\" },\n { type: \"storage-read-write\" },\n { type: \"uniform\" },\n ],\n\n getDispatchSize(op, resolvedShapes) {\n const channels = op.attributes.channels as number;\n const ref = op.attributes.seq_len_tensor as string | undefined;\n let seq_len: number;\n if (ref && resolvedShapes[ref]) {\n seq_len = resolvedShapes[ref][0];\n } else {\n seq_len = op.attributes.seq_len as number;\n }\n return [cdiv(seq_len * channels, 256), 1, 1];\n },\n\n buildParams(op, resolvedShapes) {\n // Params { seq_len: u32, channels: u32, kernel_size: u32, _pad: u32 }\n const channels = op.attributes.channels as number;\n const kernel_size = op.attributes.kernel_size as number;\n const ref = op.attributes.seq_len_tensor as string | undefined;\n let seq_len: number;\n if (ref && resolvedShapes[ref]) {\n seq_len = resolvedShapes[ref][0];\n } else {\n seq_len = op.attributes.seq_len as number;\n }\n return buildUniformBuffer([seq_len, channels, kernel_size, 0]);\n },\n};\n\n// ── CausalConv1dSiLU (conv + fused SiLU) ──\n\nconst causalConv1dSiluSpec: KernelSpec = {\n shaderCode: WGSL_CAUSAL_CONV1D_SILU,\n entryPoint: \"main\",\n\n bindings: [\n { type: \"storage-read\" },\n { type: \"storage-read\" },\n { type: \"storage-read\" },\n { type: \"storage-read-write\" },\n { type: \"uniform\" },\n ],\n\n getDispatchSize(op, resolvedShapes) {\n const channels = op.attributes.channels as number;\n const ref = op.attributes.seq_len_tensor as string | undefined;\n let seq_len: number;\n if (ref && resolvedShapes[ref]) {\n seq_len = resolvedShapes[ref][0];\n } else {\n seq_len = op.attributes.seq_len as number;\n }\n return [cdiv(seq_len * channels, 256), 1, 1];\n },\n\n buildParams(op, resolvedShapes) {\n const channels = op.attributes.channels as number;\n const kernel_size = op.attributes.kernel_size as number;\n const ref = op.attributes.seq_len_tensor as string | undefined;\n let seq_len: number;\n if (ref && resolvedShapes[ref]) {\n seq_len = resolvedShapes[ref][0];\n } else {\n seq_len = op.attributes.seq_len as number;\n }\n return buildUniformBuffer([seq_len, channels, kernel_size, 0]);\n },\n};\n\n// ── CausalConv1dGated (conv + fused multiplicative output gate) ──\n\nconst causalConv1dGatedSpec: KernelSpec = {\n shaderCode: WGSL_CAUSAL_CONV1D_GATED,\n entryPoint: \"main\",\n\n // @binding(0) input @binding(1) weight @binding(2) conv_state\n // @binding(3) gate @binding(4) output @binding(5) params\n bindings: [\n { type: \"storage-read\" },\n { type: \"storage-read\" },\n { type: \"storage-read\" },\n { type: \"storage-read\" },\n { type: \"storage-read-write\" },\n { type: \"uniform\" },\n ],\n\n getDispatchSize(op, resolvedShapes) {\n const channels = op.attributes.channels as number;\n const ref = op.attributes.seq_len_tensor as string | undefined;\n let seq_len: number;\n if (ref && resolvedShapes[ref]) {\n seq_len = resolvedShapes[ref][0];\n } else {\n seq_len = op.attributes.seq_len as number;\n }\n return [cdiv(seq_len * channels, 256), 1, 1];\n },\n\n buildParams(op, resolvedShapes) {\n const channels = op.attributes.channels as number;\n const kernel_size = op.attributes.kernel_size as number;\n const ref = op.attributes.seq_len_tensor as string | undefined;\n let seq_len: number;\n if (ref && resolvedShapes[ref]) {\n seq_len = resolvedShapes[ref][0];\n } else {\n seq_len = op.attributes.seq_len as number;\n }\n return buildUniformBuffer([seq_len, channels, kernel_size, 0]);\n },\n};\n\n// ── SigmoidGate ──\n\nconst sigmoidGateSpec: KernelSpec = {\n shaderCode: WGSL_SIGMOID_GATE,\n entryPoint: \"main\",\n\n // @binding(0) x: storage read\n // @binding(1) gate: storage read\n // @binding(2) output: storage read_write\n // @binding(3) params: uniform\n bindings: [\n { type: \"storage-read\" },\n { type: \"storage-read\" },\n { type: \"storage-read-write\" },\n { type: \"uniform\" },\n ],\n\n getDispatchSize(op, resolvedShapes) {\n const count = outputElementCount(op, resolvedShapes);\n return [cdiv(count, 256), 1, 1];\n },\n\n buildParams(op, resolvedShapes) {\n const count = outputElementCount(op, resolvedShapes);\n // Params { count: u32 }\n return buildUniformBuffer([count]);\n },\n};\n\n// ── MambaSSM ──\n\nconst mambaSSMSpec: KernelSpec = {\n shaderCode: WGSL_MAMBA_SSM,\n entryPoint: \"main\",\n\n // @binding(0) qkv_conv: storage read\n // @binding(1) a_input: storage read\n // @binding(2) b_input: storage read\n // @binding(3) A_log: storage read\n // @binding(4) dt_bias: storage read\n // @binding(5) ssm_state: storage read_write\n // @binding(6) output: storage read_write\n // @binding(7) params: uniform\n bindings: [\n { type: \"storage-read\" }, // qkv_conv\n { type: \"storage-read\" }, // a_input\n { type: \"storage-read\" }, // b_input\n { type: \"storage-read\" }, // A_log\n { type: \"storage-read\" }, // dt_bias\n { type: \"storage-read-write\" }, // ssm_state\n { type: \"storage-read-write\" }, // output\n { type: \"uniform\" }, // params\n ],\n\n getDispatchSize(op) {\n const num_heads = op.attributes.num_heads as number;\n return [num_heads, 1, 1];\n },\n\n buildParams(op, resolvedShapes) {\n const num_heads = op.attributes.num_heads as number;\n const key_dim = op.attributes.key_dim as number;\n const val_dim = op.attributes.val_dim as number;\n const qkv_dim = op.attributes.qkv_dim as number;\n // Resolve T from qkv_conv tensor reference\n const qkvRef = op.attributes.T_tensor as string | undefined;\n let T: number;\n if (qkvRef && resolvedShapes[qkvRef]) {\n T = resolvedShapes[qkvRef][0];\n } else {\n T = op.attributes.T as number;\n }\n return buildUniformBuffer([T, num_heads, key_dim, val_dim, qkv_dim, 0, 0, 0]);\n },\n};\n\n// ── MambaSSM with f16 state (Dawn only — selected in executor when hasF16) ──\n\nexport const MAMBA_SSM_F16_SPEC: KernelSpec = {\n ...mambaSSMSpec,\n shaderCode: WGSL_MAMBA_SSM_F16,\n};\n\n// ── KVCacheAppend ──\n\nconst kvCacheAppendSpec: KernelSpec = {\n shaderCode: WGSL_KV_CACHE_APPEND,\n entryPoint: \"main\",\n\n // @binding(0) src: storage read\n // @binding(1) dst: storage read_write\n // @binding(2) params: uniform\n bindings: [{ type: \"storage-read\" }, { type: \"storage-read-write\" }, { type: \"uniform\" }],\n\n getDispatchSize(op, resolvedShapes) {\n const width = op.attributes.width as number;\n const srcRef = op.attributes.T_tensor as string | undefined;\n let T: number;\n if (srcRef && resolvedShapes[srcRef]) {\n T = resolvedShapes[srcRef][0];\n } else {\n T = op.attributes.T as number;\n }\n return [cdiv(T * width, 256), 1, 1];\n },\n\n buildParams(op, resolvedShapes, context) {\n const width = op.attributes.width as number;\n const seqPos = context?.seqPos ?? 0;\n const srcRef = op.attributes.T_tensor as string | undefined;\n let T: number;\n if (srcRef && resolvedShapes[srcRef]) {\n T = resolvedShapes[srcRef][0];\n } else {\n T = op.attributes.T as number;\n }\n return buildUniformBuffer([T * width, seqPos * width]);\n },\n};\n\n// ── Dual KVCacheAppend (f32 K+V into both caches, one dispatch) ──\n\nexport const DUAL_KV_CACHE_APPEND_SPEC: KernelSpec = {\n shaderCode: WGSL_DUAL_KV_CACHE_APPEND,\n entryPoint: \"main\",\n\n // @binding(0) src_k, (1) src_v, (2) dst_k, (3) dst_v, (4) params\n bindings: [\n { type: \"storage-read\" },\n { type: \"storage-read\" },\n { type: \"storage-read-write\" },\n { type: \"storage-read-write\" },\n { type: \"uniform\" },\n ],\n\n getDispatchSize(op, resolvedShapes) {\n const width = op.attributes.width as number;\n const srcRef = op.attributes.T_tensor as string | undefined;\n const T =\n srcRef && resolvedShapes[srcRef] ? resolvedShapes[srcRef][0] : (op.attributes.T as number);\n return [cdiv(T * width, 256), 1, 1];\n },\n\n buildParams(op, resolvedShapes, context) {\n const width = op.attributes.width as number;\n const seqPos = context?.seqPos ?? 0;\n const srcRef = op.attributes.T_tensor as string | undefined;\n const T =\n srcRef && resolvedShapes[srcRef] ? resolvedShapes[srcRef][0] : (op.attributes.T as number);\n return buildUniformBuffer([T * width, seqPos * width]);\n },\n};\n\n// ── Dual KVCacheAppend native-f16 (f32 K+V → f16 caches, one dispatch) ──\n\nexport const DUAL_KV_CACHE_APPEND_F16_SPEC: KernelSpec = {\n ...DUAL_KV_CACHE_APPEND_SPEC,\n shaderCode: WGSL_DUAL_KV_CACHE_APPEND_F16,\n};\n\n// ── Dual KVCacheAppend packed-f16 (Safari-safe, one dispatch) ──\n\nexport const DUAL_KV_CACHE_APPEND_PACKED_F16_SPEC: KernelSpec = {\n ...DUAL_KV_CACHE_APPEND_SPEC,\n shaderCode: WGSL_DUAL_KV_CACHE_APPEND_PACKED_F16,\n // Each thread handles one f16 PAIR per cache → grid over count/2.\n getDispatchSize(op, resolvedShapes) {\n const width = op.attributes.width as number;\n const srcRef = op.attributes.T_tensor as string | undefined;\n const T =\n srcRef && resolvedShapes[srcRef] ? resolvedShapes[srcRef][0] : (op.attributes.T as number);\n return [cdiv((T * width) >> 1, 256), 1, 1];\n },\n};\n\n// ── KVCacheAppend F16 (f32 src → f16 dst) ──\n\nexport const KV_CACHE_APPEND_F16_SPEC: KernelSpec = {\n shaderCode: WGSL_KV_CACHE_APPEND_F16,\n entryPoint: \"main\",\n\n // @binding(0) src: storage read (f32)\n // @binding(1) dst: storage read_write (f16)\n // @binding(2) params: uniform\n bindings: [{ type: \"storage-read\" }, { type: \"storage-read-write\" }, { type: \"uniform\" }],\n\n getDispatchSize(op, resolvedShapes) {\n const width = op.attributes.width as number;\n const srcRef = op.attributes.T_tensor as string | undefined;\n let T: number;\n if (srcRef && resolvedShapes[srcRef]) {\n T = resolvedShapes[srcRef][0];\n } else {\n T = op.attributes.T as number;\n }\n return [cdiv(T * width, 256), 1, 1];\n },\n\n buildParams(op, resolvedShapes, context) {\n const width = op.attributes.width as number;\n const seqPos = context?.seqPos ?? 0;\n const srcRef = op.attributes.T_tensor as string | undefined;\n let T: number;\n if (srcRef && resolvedShapes[srcRef]) {\n T = resolvedShapes[srcRef][0];\n } else {\n T = op.attributes.T as number;\n }\n return buildUniformBuffer([T * width, seqPos * width]);\n },\n};\n\n// ── Attention F16 (f16 KV cache) ──\n\nexport const ATTENTION_F16_SPEC: KernelSpec = {\n shaderCode: WGSL_ATTENTION_F16,\n entryPoint: \"main\",\n\n // @binding(0) Q: storage read (f32)\n // @binding(1) K: storage read (f16)\n // @binding(2) V: storage read (f16)\n // @binding(3) output: storage read_write (f32)\n // @binding(4) params: uniform\n bindings: [\n { type: \"storage-read\" },\n { type: \"storage-read\" },\n { type: \"storage-read\" },\n { type: \"storage-read-write\" },\n { type: \"uniform\" },\n ],\n\n getDispatchSize(op, resolvedShapes) {\n const num_q_heads = op.attributes.num_q_heads as number;\n const outShape = resolvedShapes[op.outputs[0]];\n const T = outShape ? outShape[0] : (op.attributes.T as number);\n return [T, num_q_heads, 1];\n },\n\n buildParams(op, resolvedShapes, context) {\n const num_q_heads = op.attributes.num_q_heads as number;\n const num_kv_heads = op.attributes.num_kv_heads as number;\n const head_dim = op.attributes.head_dim as number;\n const position_offset = context?.seqPos ?? (op.attributes.position_offset as number) ?? 0;\n\n const outShape = resolvedShapes[op.outputs[0]];\n const T = outShape ? outShape[0] : (op.attributes.T as number);\n\n const kvShape = resolvedShapes[op.inputs[1]];\n const S = kvShape ? kvShape[0] : T;\n\n // Default 1/sqrt(head_dim) → byte-identical to the legacy scale; Gemma4 = 1.0.\n const attn_scale = (op.attributes.attn_scale as number) ?? 1 / Math.sqrt(head_dim);\n\n return buildUniformBuffer([\n T,\n S,\n num_q_heads,\n num_kv_heads,\n head_dim,\n position_offset,\n f32BitsToU32(attn_scale),\n ]);\n },\n};\n\n// ── ConvStateUpdate ──\n\nconst convStateUpdateSpec: KernelSpec = {\n shaderCode: WGSL_CONV_STATE_UPDATE,\n entryPoint: \"main\",\n\n // @binding(0) input: storage read\n // @binding(1) conv_state: storage read_write\n // @binding(2) params: uniform\n bindings: [{ type: \"storage-read\" }, { type: \"storage-read-write\" }, { type: \"uniform\" }],\n\n getDispatchSize(op) {\n const channels = op.attributes.channels as number;\n return [cdiv(channels, 256), 1, 1];\n },\n\n buildParams(op, resolvedShapes) {\n const channels = op.attributes.channels as number;\n const state_size = op.attributes.state_size as number;\n const srcRef = op.attributes.T_tensor as string | undefined;\n let seq_len: number;\n if (srcRef && resolvedShapes[srcRef]) {\n seq_len = resolvedShapes[srcRef][0];\n } else {\n seq_len = op.attributes.T as number;\n }\n return buildUniformBuffer([seq_len, channels, state_size, 0]);\n },\n};\n\n// ── Registry ────────────────────────────────────────────────────────────\n\n/**\n * Maps each implemented OpType to its kernel spec.\n *\n * Only ops with WGSL shader implementations are included.\n * Stubbed ops (MoERouter, ExpertMatMul, Conv2d, AvgPool2d,\n * CrossAttention, Gather, Reshape, Transpose, Concat) are not present.\n */\n// ── Audio codec-decoder kernels (OmniVoice / HiggsAudioV2 DAC vocoder) ──\n//\n// New vocoder primitives for native TTS decode (token grid -> 24kHz PCM). The\n// DAC decoder is: Conv1dFull (in) -> 5 upsample blocks (ConvTranspose1d + Snake1d\n// + dilated residual Conv1dFull) -> Snake1d -> Conv1dFull (out, NO tanh). These\n// three kernels cover every op in that path.\n//\n// Validated bit-exact vs NumPy reference (max|err| ~2e-7, f32 rounding only) in\n// scripts/engine/test-codec-kernels.mjs across: kernel-7 dilation-1/3, pointwise\n// kernel-1, transposed stride-4 (even) and stride-3 (odd, output_padding=1), and\n// per-channel Snake. Mobile-safe: flat workgroup, no select(), no exp(), no\n// shared memory, 1 thread per output element.\n\nconst WGSL_CONV1D_FULL = `\\\n// Full cross-channel 1D convolution: x[Cin,L] * w[Cout,Cin,K] + bias[Cout]\n// -> y[Cout,Lout], Lout = (L + 2*padding - dilation*(K-1) - 1)/stride + 1\n// One thread per (oc, ot) output element.\nstruct Params {\n Cin: u32, Cout: u32, L: u32, Lout: u32,\n K: u32, stride: u32, padding: u32, dilation: u32,\n}\n@group(0) @binding(0) var<storage, read> x: array<f32>;\n@group(0) @binding(1) var<storage, read> w: array<f32>;\n@group(0) @binding(2) var<storage, read> bias: array<f32>;\n@group(0) @binding(3) var<storage, read_write> y: array<f32>;\n@group(0) @binding(4) var<storage, read> params: Params;\n\n@compute @workgroup_size(64)\nfn main(@builtin(global_invocation_id) gid: vec3u) {\n let idx = gid.x;\n let total = params.Cout * params.Lout;\n if (idx >= total) { return; }\n let oc = idx / params.Lout;\n let ot = idx % params.Lout;\n let base = i32(ot * params.stride) - i32(params.padding);\n var acc: f32 = 0.0;\n for (var ic: u32 = 0u; ic < params.Cin; ic = ic + 1u) {\n let x_row = ic * params.L;\n let w_row = (oc * params.Cin + ic) * params.K;\n for (var k: u32 = 0u; k < params.K; k = k + 1u) {\n let it = base + i32(k * params.dilation);\n if (it >= 0 && it < i32(params.L)) {\n acc = acc + x[x_row + u32(it)] * w[w_row + k];\n }\n }\n }\n y[oc * params.Lout + ot] = acc + bias[oc];\n}\n`;\n\nconst WGSL_CONV_TRANSPOSE1D = `\\\n// Transposed (strided) 1D convolution: x[Cin,L] * w[Cin,Cout,K] + bias[Cout]\n// -> y[Cout,Lout], Lout = (L-1)*stride - 2*padding + (K-1) + output_padding + 1\n// Gather form (one thread per output): for full-buffer position op = ot+padding,\n// sum over (ic,k) where op-k is a non-negative multiple of stride -> it=(op-k)/stride.\n// HiggsAudioV2 sets output_padding = stride % 2 on every DAC upsample block.\nstruct Params {\n Cin: u32, Cout: u32, L: u32, Lout: u32,\n K: u32, stride: u32, padding: u32, out_pad: u32,\n}\n@group(0) @binding(0) var<storage, read> x: array<f32>;\n@group(0) @binding(1) var<storage, read> w: array<f32>;\n@group(0) @binding(2) var<storage, read> bias: array<f32>;\n@group(0) @binding(3) var<storage, read_write> y: array<f32>;\n@group(0) @binding(4) var<storage, read> params: Params;\n\n@compute @workgroup_size(64)\nfn main(@builtin(global_invocation_id) gid: vec3u) {\n let idx = gid.x;\n let total = params.Cout * params.Lout;\n if (idx >= total) { return; }\n let oc = idx / params.Lout;\n let ot = idx % params.Lout;\n let op = i32(ot + params.padding);\n var acc: f32 = 0.0;\n for (var ic: u32 = 0u; ic < params.Cin; ic = ic + 1u) {\n let x_row = ic * params.L;\n let w_row = (ic * params.Cout + oc) * params.K;\n for (var k: u32 = 0u; k < params.K; k = k + 1u) {\n let num = op - i32(k);\n if (num >= 0 && (num % i32(params.stride)) == 0) {\n let it = num / i32(params.stride);\n if (it >= 0 && it < i32(params.L)) {\n acc = acc + x[x_row + u32(it)] * w[w_row + k];\n }\n }\n }\n }\n y[oc * params.Lout + ot] = acc + bias[oc];\n}\n`;\n\nconst WGSL_SNAKE1D = `\\\n// Snake activation (learned per-channel periodic): y = x + (1/(alpha+1e-9)) * sin(alpha*x)^2\nstruct Params { C: u32, L: u32 }\n@group(0) @binding(0) var<storage, read> x: array<f32>;\n@group(0) @binding(1) var<storage, read> alpha: array<f32>;\n@group(0) @binding(2) var<storage, read_write> y: array<f32>;\n@group(0) @binding(3) var<storage, read> params: Params;\n\n@compute @workgroup_size(64)\nfn main(@builtin(global_invocation_id) gid: vec3u) {\n let idx = gid.x;\n let total = params.C * params.L;\n if (idx >= total) { return; }\n let c = idx / params.L;\n let a = alpha[c];\n let s = sin(a * x[idx]);\n y[idx] = x[idx] + (1.0 / (a + 1e-9)) * s * s;\n}\n`;\n\n// ── Moonshine STT frontend kernels (Tanh, Transpose, GroupNorm) ──\n\nconst WGSL_TANH = `\\\n// Elementwise tanh (Moonshine conv1 activation). Clamp the argument: on Metal/Dawn\n// tanh() of a large magnitude returns NaN (internal exp overflows). |arg|>=10\n// already saturates to +/-1, so clamping to +/-20 is numerically exact and safe.\nstruct Params { count: u32 }\n@group(0) @binding(0) var<storage, read> input: array<f32>;\n@group(0) @binding(1) var<storage, read_write> output: array<f32>;\n@group(0) @binding(2) var<storage, read> params: Params;\n\n@compute @workgroup_size(256)\nfn main(@builtin(global_invocation_id) gid: vec3u) {\n let idx = gid.x;\n if (idx >= params.count) { return; }\n output[idx] = tanh(clamp(input[idx], -20.0, 20.0));\n}\n`;\n\nconst WGSL_TRANSPOSE = `\\\n// Transpose a row-major [rows, cols] tensor into [cols, rows].\n// Moonshine conv output is [C, L] (channels-major); the encoder transformer wants\n// [L, C] (token-major). One thread per output element; bounds-checked.\nstruct Params { rows: u32, cols: u32 }\n@group(0) @binding(0) var<storage, read> input: array<f32>;\n@group(0) @binding(1) var<storage, read_write> output: array<f32>;\n@group(0) @binding(2) var<storage, read> params: Params;\n\n@compute @workgroup_size(256)\nfn main(@builtin(global_invocation_id) gid: vec3u) {\n let idx = gid.x;\n let total = params.rows * params.cols;\n if (idx >= total) { return; }\n // output is [cols, rows]: out[c, r] = in[r, c]\n let c = idx / params.rows;\n let r = idx % params.rows;\n output[idx] = input[r * params.cols + c];\n}\n`;\n\nconst WGSL_GROUPNORM = `\\\n// GroupNorm(num_groups=1) over the channel axis of a [C, L] tensor: normalize each\n// channel-position element by the mean/variance computed across ALL C*L elements\n// (a single group), then scale/shift per-channel: y[c,l] = (x[c,l]-mu)/sqrt(var+eps)\n// * weight[c] + bias[c]. Moonshine's encoder frontend applies this after conv1.\n// One workgroup reduces the whole [C, L] tensor (C*L <= a few k for short clips).\nstruct Params { channels: u32, length: u32, eps_bits: u32, _pad: u32 }\n@group(0) @binding(0) var<storage, read> input: array<f32>;\n@group(0) @binding(1) var<storage, read> weight: array<f32>;\n@group(0) @binding(2) var<storage, read> bias: array<f32>;\n@group(0) @binding(3) var<storage, read_write> output: array<f32>;\n@group(0) @binding(4) var<storage, read> params: Params;\n\nvar<workgroup> shared_sum: array<f32, 256>;\nvar<workgroup> shared_sq: array<f32, 256>;\n\n@compute @workgroup_size(256)\nfn main(@builtin(local_invocation_id) lid: vec3u) {\n let tid = lid.x;\n let total = params.channels * params.length;\n let eps = bitcast<f32>(params.eps_bits);\n\n var local_sum: f32 = 0.0;\n var local_sq: f32 = 0.0;\n var i = tid;\n while (i < total) {\n let v = input[i];\n local_sum += v;\n local_sq += v * v;\n i += 256u;\n }\n shared_sum[tid] = local_sum;\n shared_sq[tid] = local_sq;\n workgroupBarrier();\n\n var stride: u32 = 128u;\n while (stride > 0u) {\n if (tid < stride) {\n shared_sum[tid] += shared_sum[tid + stride];\n shared_sq[tid] += shared_sq[tid + stride];\n }\n workgroupBarrier();\n stride /= 2u;\n }\n\n let n = f32(total);\n let mean = shared_sum[0] / n;\n let variance = shared_sq[0] / n - mean * mean;\n let inv_std = 1.0 / sqrt(variance + eps);\n workgroupBarrier();\n\n i = tid;\n while (i < total) {\n let c = i / params.length; // channel index (row-major [C, L])\n output[i] = (input[i] - mean) * inv_std * weight[c] + bias[c];\n i += 256u;\n }\n}\n`;\n\nconst WGSL_LAYERNORM_NO_BIAS = `\\\n// LayerNorm without a bias term: output = ((x - mean) / sqrt(var + eps)) * weight.\n// Moonshine's encoder/decoder norms are weight-only (no .bias in the checkpoint),\n// so the node binds only [input, weight] — this variant drops the bias binding to\n// keep the executor's positional input->binding mapping correct.\nstruct Params {\n seq_len: u32,\n hidden_size: u32,\n eps_bits: u32,\n _pad: u32,\n}\n@group(0) @binding(0) var<storage, read> input: array<f32>;\n@group(0) @binding(1) var<storage, read> weight: array<f32>;\n@group(0) @binding(2) var<storage, read_write> output: array<f32>;\n@group(0) @binding(3) var<storage, read> params: Params;\n\nvar<workgroup> shared_sum: array<f32, 256>;\nvar<workgroup> shared_sq_sum: array<f32, 256>;\n\n@compute @workgroup_size(256)\nfn main(\n @builtin(local_invocation_id) lid: vec3u,\n @builtin(workgroup_id) wid: vec3u,\n) {\n let row = wid.x;\n if (row >= params.seq_len) { return; }\n\n let tid = lid.x;\n let row_offset = row * params.hidden_size;\n let eps = bitcast<f32>(params.eps_bits);\n\n var local_sum: f32 = 0.0;\n var i = tid;\n while (i < params.hidden_size) {\n local_sum += input[row_offset + i];\n i += 256u;\n }\n shared_sum[tid] = local_sum;\n workgroupBarrier();\n\n var stride: u32 = 128u;\n while (stride > 0u) {\n if (tid < stride) { shared_sum[tid] += shared_sum[tid + stride]; }\n workgroupBarrier();\n stride /= 2u;\n }\n let mean = shared_sum[0] / f32(params.hidden_size);\n\n var local_sq: f32 = 0.0;\n i = tid;\n while (i < params.hidden_size) {\n let diff = input[row_offset + i] - mean;\n local_sq += diff * diff;\n i += 256u;\n }\n shared_sq_sum[tid] = local_sq;\n workgroupBarrier();\n\n stride = 128u;\n while (stride > 0u) {\n if (tid < stride) { shared_sq_sum[tid] += shared_sq_sum[tid + stride]; }\n workgroupBarrier();\n stride /= 2u;\n }\n let inv_std = 1.0 / sqrt(shared_sq_sum[0] / f32(params.hidden_size) + eps);\n\n i = tid;\n while (i < params.hidden_size) {\n output[row_offset + i] = (input[row_offset + i] - mean) * inv_std * weight[i];\n i += 256u;\n }\n}\n`;\n\n/**\n * LayerNorm with no bias term (Moonshine). Binds [input, weight, output, params]\n * — one fewer storage-read than the standard layernormSpec. The executor selects\n * this variant when a LayerNorm node carries only 2 inputs.\n */\nexport const LAYERNORM_NO_BIAS_SPEC: KernelSpec = {\n shaderCode: WGSL_LAYERNORM_NO_BIAS,\n entryPoint: \"main\",\n bindings: [\n { type: \"storage-read\" },\n { type: \"storage-read\" },\n { type: \"storage-read-write\" },\n { type: \"uniform\" },\n ],\n getDispatchSize(op, resolvedShapes) {\n const hidden_size = op.attributes.hidden_size as number;\n const ref = op.attributes.seq_len_tensor as string | undefined;\n let seq_len: number;\n if (ref && resolvedShapes[ref]) {\n const shape = resolvedShapes[ref];\n seq_len = shape.reduce((a, b) => a * b, 1) / hidden_size;\n } else {\n seq_len = op.attributes.seq_len as number;\n }\n return [seq_len, 1, 1];\n },\n buildParams(op, resolvedShapes) {\n const hidden_size = op.attributes.hidden_size as number;\n const eps = op.attributes.eps as number;\n const ref = op.attributes.seq_len_tensor as string | undefined;\n let seq_len: number;\n if (ref && resolvedShapes[ref]) {\n const shape = resolvedShapes[ref];\n seq_len = shape.reduce((a, b) => a * b, 1) / hidden_size;\n } else {\n seq_len = op.attributes.seq_len as number;\n }\n return buildUniformBuffer([seq_len, hidden_size, f32BitsToU32(eps), 0]);\n },\n};\n\nconst tanhSpec: KernelSpec = {\n shaderCode: WGSL_TANH,\n entryPoint: \"main\",\n bindings: [{ type: \"storage-read\" }, { type: \"storage-read-write\" }, { type: \"uniform\" }],\n getDispatchSize(op, resolvedShapes) {\n const count = outputElementCount(op, resolvedShapes);\n return [cdiv(count, 256), 1, 1];\n },\n buildParams(op, resolvedShapes) {\n const count = outputElementCount(op, resolvedShapes);\n return buildUniformBuffer([count]);\n },\n};\n\nconst transposeSpec: KernelSpec = {\n shaderCode: WGSL_TRANSPOSE,\n entryPoint: \"main\",\n bindings: [{ type: \"storage-read\" }, { type: \"storage-read-write\" }, { type: \"uniform\" }],\n getDispatchSize(op, resolvedShapes) {\n const count = outputElementCount(op, resolvedShapes);\n return [cdiv(count, 256), 1, 1];\n },\n buildParams(op, resolvedShapes) {\n // input shape [rows, cols]; output is [cols, rows].\n const inShape = resolvedShapes[op.inputs[0]];\n const rows = inShape ? inShape[0] : (op.attributes.rows as number);\n const cols = inShape ? inShape[1] : (op.attributes.cols as number);\n return buildUniformBuffer([rows, cols]);\n },\n};\n\nconst groupnormSpec: KernelSpec = {\n shaderCode: WGSL_GROUPNORM,\n entryPoint: \"main\",\n bindings: [\n { type: \"storage-read\" },\n { type: \"storage-read\" },\n { type: \"storage-read\" },\n { type: \"storage-read-write\" },\n { type: \"uniform\" },\n ],\n // One workgroup reduces the full [C, L] tensor.\n getDispatchSize() {\n return [1, 1, 1];\n },\n buildParams(op, resolvedShapes) {\n const channels = op.attributes.channels as number;\n const inShape = resolvedShapes[op.inputs[0]];\n const length = inShape ? inShape[1] : (op.attributes.length as number);\n const eps = (op.attributes.eps as number) ?? 1e-5;\n return buildUniformBuffer([channels, length, f32BitsToU32(eps), 0]);\n },\n};\n\nconst conv1dFullSpec: KernelSpec = {\n shaderCode: WGSL_CONV1D_FULL,\n entryPoint: \"main\",\n bindings: [\n { type: \"storage-read\" },\n { type: \"storage-read\" },\n { type: \"storage-read\" },\n { type: \"storage-read-write\" },\n { type: \"uniform\" },\n ],\n getDispatchSize(op) {\n const Cout = op.attributes.Cout as number;\n const Lout = op.attributes.Lout as number;\n return [cdiv(Cout * Lout, 64), 1, 1];\n },\n buildParams(op) {\n return buildUniformBuffer([\n op.attributes.Cin as number,\n op.attributes.Cout as number,\n op.attributes.L as number,\n op.attributes.Lout as number,\n op.attributes.K as number,\n op.attributes.stride as number,\n op.attributes.padding as number,\n op.attributes.dilation as number,\n ]);\n },\n};\n\nconst convTranspose1dSpec: KernelSpec = {\n shaderCode: WGSL_CONV_TRANSPOSE1D,\n entryPoint: \"main\",\n bindings: [\n { type: \"storage-read\" },\n { type: \"storage-read\" },\n { type: \"storage-read\" },\n { type: \"storage-read-write\" },\n { type: \"uniform\" },\n ],\n getDispatchSize(op) {\n const Cout = op.attributes.Cout as number;\n const Lout = op.attributes.Lout as number;\n return [cdiv(Cout * Lout, 64), 1, 1];\n },\n buildParams(op) {\n return buildUniformBuffer([\n op.attributes.Cin as number,\n op.attributes.Cout as number,\n op.attributes.L as number,\n op.attributes.Lout as number,\n op.attributes.K as number,\n op.attributes.stride as number,\n op.attributes.padding as number,\n op.attributes.output_padding as number,\n ]);\n },\n};\n\nconst snake1dSpec: KernelSpec = {\n shaderCode: WGSL_SNAKE1D,\n entryPoint: \"main\",\n bindings: [\n { type: \"storage-read\" },\n { type: \"storage-read\" },\n { type: \"storage-read-write\" },\n { type: \"uniform\" },\n ],\n getDispatchSize(op) {\n const C = op.attributes.C as number;\n const L = op.attributes.L as number;\n return [cdiv(C * L, 64), 1, 1];\n },\n buildParams(op) {\n return buildUniformBuffer([op.attributes.C as number, op.attributes.L as number]);\n },\n};\n\n// ════════════════════════════════════════════════════════════════════════\n// Kani-TTS-2 NanoCodec (NVIDIA NeMo, 22 kHz 0.6 kbps 12.5 fps) decoder kernels.\n// Validated bit-exact vs the real MLX NanoCodec via scripts/engine/test-nanocodec\n// -decode.mjs (max|err| 4.2e-6, f32 rounding only). Three new ops beyond the DAC\n// cluster above:\n// - FSQDequant: code idx -> per-dim continuous latent (finite scalar quantization,\n// 4 groups x 4 dims, levels [9,8,8,7], mixed-radix base [1,9,72,576]).\n// - HalfSnake1d: snake on the first C/2 channels, leaky-relu(0.01) on the rest.\n// - ConvTranspose1dDepthwise: causal depthwise (groups=Cout) transposed conv —\n// insert (stride-1) zeros, cross-correlate the FLIPPED kernel, right-trim.\n// The causal full conv reuses Conv1dFull with padding=(K-1)*dilation, Lout=L.\n// Mobile-safe: flat workgroup_size(64), no select(), no shared memory, no exp(),\n// 1 thread per output element, no `enable f16`.\n// ════════════════════════════════════════════════════════════════════════\n\nconst WGSL_FSQ_DEQUANT = `\\\n// Finite-scalar-quantization decode. codes[groups,T] (u32) -> latent[groups*dims,T]\n// (f32). Channel ch = g*dims + d; per-dim level/base passed as small constants.\nstruct Params {\n groups: u32, dims: u32, T: u32, _pad: u32,\n l0: u32, l1: u32, l2: u32, l3: u32,\n b0: u32, b1: u32, b2: u32, b3: u32,\n}\n@group(0) @binding(0) var<storage, read> codes: array<u32>; // [groups, T]\n@group(0) @binding(1) var<storage, read_write> latent: array<f32>; // [groups*dims, T]\n@group(0) @binding(2) var<storage, read> params: Params;\n\n@compute @workgroup_size(64)\nfn main(@builtin(global_invocation_id) gid: vec3u) {\n let total = params.groups * params.dims * params.T;\n if (gid.x >= total) { return; }\n let ch = gid.x / params.T;\n let t = gid.x % params.T;\n let d = ch % params.dims;\n let g = ch / params.dims;\n var lvl: u32 = params.l0;\n var base: u32 = params.b0;\n if (d == 1u) { lvl = params.l1; base = params.b1; }\n else if (d == 2u) { lvl = params.l2; base = params.b2; }\n else if (d == 3u) { lvl = params.l3; base = params.b3; }\n let idx = codes[g * params.T + t];\n let nonneg = (idx / base) % lvl;\n let scale = lvl / 2u;\n latent[ch * params.T + t] = (f32(nonneg) - f32(scale)) / f32(scale);\n}\n`;\n\nconst WGSL_HALF_SNAKE1D = `\\\n// HalfSnake: first half channels -> snake(x; alpha); rest -> leaky_relu(x, 0.01).\nstruct Params { C: u32, L: u32, half: u32, _pad: u32 }\n@group(0) @binding(0) var<storage, read> x: array<f32>; // [C, L]\n@group(0) @binding(1) var<storage, read> alpha: array<f32>; // [half]\n@group(0) @binding(2) var<storage, read_write> y: array<f32>; // [C, L]\n@group(0) @binding(3) var<storage, read> params: Params;\n\n@compute @workgroup_size(64)\nfn main(@builtin(global_invocation_id) gid: vec3u) {\n let total = params.C * params.L;\n if (gid.x >= total) { return; }\n let c = gid.x / params.L;\n let v = x[gid.x];\n if (c < params.half) {\n let a = alpha[c];\n let s = sin(a * v);\n y[gid.x] = v + (1.0 / (a + 1e-9)) * s * s;\n } else {\n if (v >= 0.0) { y[gid.x] = v; } else { y[gid.x] = 0.01 * v; }\n }\n}\n`;\n\nconst WGSL_CONV_TRANSPOSE1D_DW = `\\\n// Causal depthwise (groups = Cout) transposed conv. Weight in PyTorch\n// ConvTranspose1d layout [Cin, 1, K] (out/groups = 1); each output channel oc is fed\n// by cpg = Cin/Cout consecutive input channels (oc*cpg .. oc*cpg+cpg-1). Matches MLX\n// CausalConvTranspose1d: insert (stride-1) zeros, pad (K-1) both sides, cross-correlate\n// the FLIPPED kernel, then trim (K-stride) from the right. Output length Lout = L*stride.\n// Gather form (one thread per (oc, ot)): contributions where it*stride = ot+k-(K-1),\n// 0<=k<K, with flipped weight w[K-1-k].\nstruct Params { Cin: u32, Cout: u32, L: u32, Lout: u32, K: u32, stride: u32, cpg: u32, _pad: u32 }\n@group(0) @binding(0) var<storage, read> x: array<f32>; // [Cin, L]\n@group(0) @binding(1) var<storage, read> w: array<f32>; // [Cin, 1, K]\n@group(0) @binding(2) var<storage, read> bias: array<f32>; // [Cout]\n@group(0) @binding(3) var<storage, read_write> y: array<f32>; // [Cout, Lout]\n@group(0) @binding(4) var<storage, read> params: Params;\n\n@compute @workgroup_size(64)\nfn main(@builtin(global_invocation_id) gid: vec3u) {\n let total = params.Cout * params.Lout;\n if (gid.x >= total) { return; }\n let oc = gid.x / params.Lout;\n let ot = gid.x % params.Lout;\n var acc: f32 = 0.0;\n for (var j: u32 = 0u; j < params.cpg; j = j + 1u) {\n let ic = oc * params.cpg + j;\n let x_row = ic * params.L;\n let w_row = ic * params.K;\n for (var k: u32 = 0u; k < params.K; k = k + 1u) {\n let num = i32(ot) + i32(k) - (i32(params.K) - 1);\n if (num >= 0 && (num % i32(params.stride)) == 0) {\n let it = num / i32(params.stride);\n if (it >= 0 && it < i32(params.L)) {\n acc = acc + x[x_row + u32(it)] * w[w_row + (params.K - 1u - k)];\n }\n }\n }\n }\n y[oc * params.Lout + ot] = acc + bias[oc];\n}\n`;\n\nconst fsqDequantSpec: KernelSpec = {\n shaderCode: WGSL_FSQ_DEQUANT,\n entryPoint: \"main\",\n bindings: [{ type: \"storage-read\" }, { type: \"storage-read-write\" }, { type: \"uniform\" }],\n getDispatchSize(op) {\n const groups = op.attributes.groups as number;\n const dims = op.attributes.dims as number;\n const T = op.attributes.T as number;\n return [cdiv(groups * dims * T, 64), 1, 1];\n },\n buildParams(op) {\n const levels = op.attributes.levels as number[];\n const base = op.attributes.base as number[];\n return buildUniformBuffer([\n op.attributes.groups as number,\n op.attributes.dims as number,\n op.attributes.T as number,\n 0,\n levels[0],\n levels[1],\n levels[2],\n levels[3],\n base[0],\n base[1],\n base[2],\n base[3],\n ]);\n },\n};\n\nconst halfSnake1dSpec: KernelSpec = {\n shaderCode: WGSL_HALF_SNAKE1D,\n entryPoint: \"main\",\n bindings: [\n { type: \"storage-read\" },\n { type: \"storage-read\" },\n { type: \"storage-read-write\" },\n { type: \"uniform\" },\n ],\n getDispatchSize(op) {\n const C = op.attributes.C as number;\n const L = op.attributes.L as number;\n return [cdiv(C * L, 64), 1, 1];\n },\n buildParams(op) {\n const C = op.attributes.C as number;\n return buildUniformBuffer([C, op.attributes.L as number, Math.floor(C / 2), 0]);\n },\n};\n\nconst convTranspose1dDepthwiseSpec: KernelSpec = {\n shaderCode: WGSL_CONV_TRANSPOSE1D_DW,\n entryPoint: \"main\",\n bindings: [\n { type: \"storage-read\" },\n { type: \"storage-read\" },\n { type: \"storage-read\" },\n { type: \"storage-read-write\" },\n { type: \"uniform\" },\n ],\n getDispatchSize(op) {\n const Cout = op.attributes.Cout as number;\n const Lout = op.attributes.Lout as number;\n return [cdiv(Cout * Lout, 64), 1, 1];\n },\n buildParams(op) {\n const Cin = op.attributes.Cin as number;\n const Cout = op.attributes.Cout as number;\n return buildUniformBuffer([\n Cin,\n Cout,\n op.attributes.L as number,\n op.attributes.Lout as number,\n op.attributes.K as number,\n op.attributes.stride as number,\n Math.floor(Cin / Cout),\n 0,\n ]);\n },\n};\n\nexport const KERNEL_REGISTRY: Partial<Record<OpType, KernelSpec>> = {\n Embedding: embeddingSpec,\n EmbeddingInt4: embeddingInt4Spec,\n MatMul: matmulSpec,\n MatMulBias: matmulBiasSpec,\n MatMulInt4: matmulInt4Spec,\n Add: addSpec,\n Mul: mulSpec,\n SwiGLU: swigluSpec,\n SiLU: siluSpec,\n ResidualRMSNorm: residualRmsnormSpec,\n GELU: geluSpec,\n GeluErf: geluErfSpec,\n AddBias: addBiasSpec,\n ApplyRotaryEmb: applyRotarySpec,\n MRoPE: mropeSpec,\n EmbedSplice: embedSpliceSpec,\n SliceCols: sliceColsSpec,\n MulCols: mulColsSpec,\n PoolMatMul: poolMatMulSpec,\n ClippedMatMul: clippedMatMulSpec,\n RMSNorm: rmsnormSpec,\n LayerNorm: layernormSpec,\n RoPE: ropeSpec,\n Attention: attentionSpec,\n CrossAttention: crossAttentionSpec,\n Softmax: softmaxSpec,\n CausalConv1d: causalConv1dSpec,\n CausalConv1dSiLU: causalConv1dSiluSpec,\n CausalConv1dGated: causalConv1dGatedSpec,\n SigmoidGate: sigmoidGateSpec,\n MambaSSM: mambaSSMSpec,\n KVCacheAppend: kvCacheAppendSpec,\n ConvStateUpdate: convStateUpdateSpec,\n SliceLastRow: sliceLastRowSpec,\n MeanPool: meanPoolSpec,\n Scale: scaleSpec,\n Softcap: softcapSpec,\n L2Norm: l2NormSpec,\n // Audio codec-decoder (OmniVoice native TTS) — validated bit-exact, see above.\n Conv1dFull: conv1dFullSpec,\n ConvTranspose1d: convTranspose1dSpec,\n Snake1d: snake1dSpec,\n // Kani-TTS-2 NanoCodec decoder — validated bit-exact vs MLX, see above.\n FSQDequant: fsqDequantSpec,\n HalfSnake1d: halfSnake1dSpec,\n ConvTranspose1dDepthwise: convTranspose1dDepthwiseSpec,\n // Moonshine STT encoder frontend.\n Tanh: tanhSpec,\n Transpose: transposeSpec,\n GroupNorm: groupnormSpec,\n};\n","/**\n * Graph executor — runs a ModelGraph on WebGPU.\n *\n * Optimized for minimal per-token overhead:\n * - Persistent bind groups (created once after weight upload, reused every forward)\n * - Pre-allocated input buffer (no per-forward buffer creation)\n * - Cached uniform buffers (contents updated via writeBuffer, no recreation)\n * - GPU-side argmax for greedy decode (4 bytes readback vs vocab_size*4)\n * - Dual dispatch paths: tiled matmul for prefill, K-parallel matvec for decode\n */\n\nimport type { KvMode } from \"./architectures/index.js\";\nimport type { GPUContext } from \"./device.js\";\nimport {\n createBindGroup,\n createReadbackBuffer,\n createStorageBuffer,\n createUniformBuffer,\n destroyBuffers,\n getOrCreatePipeline,\n} from \"./device.js\";\nimport type { ModelGraph, OpNode } from \"./ir.js\";\nimport { DTYPE_BYTES } from \"./ir.js\";\nimport {\n ARGMAX_SPEC,\n ATTENTION_F16_SPEC,\n ATTENTION_PACKED_F16_SPEC,\n DUAL_KV_CACHE_APPEND_F16_SPEC,\n DUAL_KV_CACHE_APPEND_PACKED_F16_SPEC,\n DUAL_KV_CACHE_APPEND_SPEC,\n DUAL_MATVEC_INT4_SPEC,\n DUAL_RMSNORM_SPEC,\n GATED_MATVEC_INT4_SPEC,\n KERNEL_REGISTRY,\n type KernelSpec,\n KV_CACHE_APPEND_F16_SPEC,\n KV_CACHE_APPEND_PACKED_F16_SPEC,\n LAYERNORM_NO_BIAS_SPEC,\n MAMBA_SSM_F16_SPEC,\n MATVEC_INT4_SPEC,\n MATVEC_INT4_SUBGROUPS_SPEC,\n MATVEC_SPEC,\n MATVEC_SUBGROUPS_SPEC,\n ROPE_INTERLEAVED_SPEC,\n type RuntimeContext,\n SWIGLU_GATED_MATVEC_INT4_SPEC,\n SWIGLU_MATVEC_INT4_SPEC,\n SWIGLU_MATVEC_SPEC,\n} from \"./kernels/registry.js\";\nimport type { WeightSource } from \"./weight-source.js\";\n\nconst MAP_MODE_READ = 0x0001;\n\n/**\n * Safari/Metal workaround: shader variant alternation.\n * Metal caches argument buffers per compiled function. When consecutive dispatches\n * use the same WGSL code (same Metal function), Metal reuses the previous dispatch's\n * argument buffer, ignoring setBindGroup(). We alternate between variant 0/1 of each\n * shader (prepending `const _MV: u32 = Xu;`) to force different Metal function\n * specializations, preventing argument buffer reuse.\n */\n\nexport interface ExecutorOptions {\n maxSeqLen: number;\n /** KV cache kernel strategy. Defaults to \"native-f16\" when not specified. */\n kvMode?: KvMode;\n /**\n * WebKit only: dispatches per command buffer, with at most one command\n * buffer in flight (awaited). 1 (default) is the proven-correct floor on\n * iPad; larger values are faster if this WebKit version keeps storage\n * writes visible across dispatches within one submission. Sweepable via\n * the ?group=N URL param.\n */\n webkitGroupSize?: number;\n}\n\nexport interface ForwardResult {\n logits: Float32Array;\n}\n\n/**\n * Pre-compiled dispatch entry for a single graph op.\n * Created once during initBindGroups(), reused every forward pass.\n */\ninterface DispatchEntry {\n nodeId: string;\n node: OpNode;\n spec: KernelSpec;\n pipeline: GPUComputePipeline;\n bindGroup: GPUBindGroup;\n uniformBuffer: GPUBuffer;\n /** Cached last-written params bytes. Skip writeBuffer when unchanged. */\n lastParamsBytes: Uint8Array | null;\n /** Cached last dispatch size. */\n lastDispatchSize: [number, number, number] | null;\n}\n\nexport class Executor {\n private ctx: GPUContext;\n private graph: ModelGraph;\n\n private weightBuffers: Map<string, GPUBuffer> = new Map();\n private activationBuffers: Map<string, GPUBuffer> = new Map();\n private ssmStateBuffers: Map<string, GPUBuffer> = new Map();\n private kvCacheBuffers: Map<string, GPUBuffer> = new Map();\n\n /** Pre-allocated input_ids buffer (maxSeqLen * 4 bytes). */\n private inputIdsBuffer: GPUBuffer;\n\n /**\n * CPU-resident Per-Layer-Embeddings (PLE) source for Gemma 4. The PLE table\n * (`embed_tokens_per_layer`, [vocab, L*256]) is ~1.17GB at 4-bit and is kept\n * OFF the GPU. Each forward step we gather + dequantize only the rows for the\n * actual input tokens and upload a tiny [T, L*256] f32 buffer. See setPleSource.\n */\n private pleSource: {\n packed: Uint32Array; // flat row-major INT4 nibbles, 8 per u32\n scales: Float32Array; // per-group scale (Gerbil convention)\n zeros: Float32Array; // per-group zero\n width: number; // L*256 (row width)\n groupSize: number; // dequant group size\n targetTensor: string; // activation tensor the gathered rows are written to\n cache?: {\n cacheName: string;\n packedKey: string;\n scalesKey: string;\n zerosKey: string;\n packedLen: number;\n };\n } | null = null;\n /** Reusable scratch for the dequantized PLE rows (resized on demand). */\n private pleScratch: Float32Array | null = null;\n /**\n * One-time promise that materializes a cache-backed PLE table into heap. The\n * table is read from CacheStorage on the FIRST forward — i.e. AFTER the GPU\n * weight upload has completed, so the ~1.17 GB does not stack on top of the\n * upload's transient allocations at the load-time memory high-water mark.\n */\n private pleReady: Promise<void> | null = null;\n\n /**\n * Dummy GPU buffer bound to an otherwise-aliasing storage-read-write slot.\n * Used by RoPE-Q-only nodes (Gemma 4 KV-shared layers) whose node lists the\n * same tensor as input and output: the RoPE kernel always declares two\n * read_write bindings (q, k), but with num_kv_heads=0 the k slot is never\n * touched. WebGPU still rejects two read_write bindings aliasing one buffer,\n * so we bind this throwaway buffer to the unused k slot. Allocated lazily.\n */\n private bindingScratchBuffer: GPUBuffer | null = null;\n /** Readback buffer for logits. */\n private logitsReadback: GPUBuffer;\n /** GPU buffer for argmax result (1 u32). */\n private argmaxResultBuffer: GPUBuffer;\n /** Readback buffer for argmax result (1 u32). */\n private argmaxReadback: GPUBuffer;\n /** Readback ring for pipelined greedy decode (created lazily). */\n private decodeReadbacks: GPUBuffer[] = [];\n\n /**\n * Staging buffer for uniform param updates.\n * Safari/Metal has weaker visibility guarantees for queue.writeBuffer() to\n * UNIFORM buffers — early writes get dropped when hundreds are queued.\n * Instead, we pack all params into this STORAGE staging buffer (1 writeBuffer),\n * then use encoder.copyBufferToBuffer to distribute to each uniform buffer.\n * Copies are GPU-sequenced and guaranteed to complete before compute passes.\n */\n private uniformStagingBuffer: GPUBuffer | null = null;\n private uniformStagingCapacity: number = 0;\n\n /** Dispatch entries for prefill (M>1): uses tiled matmul kernels. */\n private dispatchEntries: DispatchEntry[] = [];\n /** Dispatch entries for decode (M=1): uses K-parallel matvec kernels. */\n private decodeEntries: DispatchEntry[] = [];\n /** Argmax dispatch entry (created in initBindGroups). */\n private argmaxEntry: {\n pipeline: GPUComputePipeline;\n bindGroup: GPUBindGroup;\n uniformBuffer: GPUBuffer;\n } | null = null;\n\n /** True when running on Safari/WebKit (needs multi-encoder submit). */\n readonly needsMultiEncoder: boolean;\n\n private maxSeqLen: number;\n private kvMode: KvMode;\n private seqPos: number = 0;\n private webkitGroupSize: number;\n\n // ── Env-gated decode profiler (GERBIL_PROFILE) ──\n // Times each dispatch with timestamp-query (one compute pass per dispatch) and\n // accumulates GPU nanoseconds per opType. OFF on the normal inference path.\n private profileEnabled = false;\n private readonly profileData = new Map<string, { ns: number; count: number }>();\n private querySet: GPUQuerySet | null = null;\n private queryResolveBuf: GPUBuffer | null = null;\n private queryReadbackBuf: GPUBuffer | null = null;\n\n constructor(ctx: GPUContext, graph: ModelGraph, options: ExecutorOptions) {\n this.ctx = ctx;\n this.graph = graph;\n this.maxSeqLen = options.maxSeqLen;\n this.kvMode = options.kvMode ?? \"native-f16\";\n this.webkitGroupSize = Math.max(1, options.webkitGroupSize ?? 1);\n this.profileEnabled =\n ctx.hasTimestamp && typeof process !== \"undefined\" && process.env?.GERBIL_PROFILE != null;\n\n // WebKit's WebGPU fails to make storage writes visible across dispatches\n // within one submission at production scale (see docs/mobile-failure-diagnosis.md);\n // Dawn (Chrome, node) does not need the multi-encoder workaround.\n this.needsMultiEncoder = ctx.isWebKitWebGPU;\n\n // Pre-allocate input_ids buffer at max size\n this.inputIdsBuffer = createStorageBuffer(ctx, \"input_ids\", options.maxSeqLen * 4);\n\n this.allocateActivationBuffers();\n this.allocateSSMStateBuffers();\n this.allocateKVCacheBuffers();\n\n // Guard against vocab_size 0 (non-LM graphs, e.g. the NanoCodec decoder run\n // via runGraphOutput): a zero-size buffer is invalid in WebGPU.\n this.logitsReadback = createReadbackBuffer(\n ctx,\n \"logits_readback\",\n Math.max(4, graph.config.vocab_size * 4),\n );\n this.argmaxResultBuffer = createStorageBuffer(ctx, \"argmax_result\", 4);\n this.argmaxReadback = createReadbackBuffer(ctx, \"argmax_readback\", 4);\n }\n\n /**\n * Register the CPU-resident Gemma 4 PLE table. The quantized table is kept in\n * JS memory (NOT uploaded to a GPU buffer), and {@link forward} gathers +\n * dequantizes only the rows for the current input tokens each step, uploading a\n * small [T, width] f32 buffer into `targetTensor`. This is what keeps Gemma 4\n * mobile-viable: resident GPU memory is just the active transformer weights.\n */\n setPleSource(src: {\n packed: Uint32Array;\n scales: Float32Array;\n zeros: Float32Array;\n width: number;\n groupSize: number;\n targetTensor: string;\n cache?: {\n cacheName: string;\n packedKey: string;\n scalesKey: string;\n zerosKey: string;\n packedLen: number;\n };\n }): void {\n this.pleSource = src;\n }\n\n /**\n * Materialize a cache-backed PLE table into heap exactly once. Deferred to the\n * first forward (after GPU upload) so the ~1.17 GB table is not co-resident\n * with the upload's transient allocations during the load-time memory peak.\n */\n private ensurePleLoaded(): Promise<void> {\n const src = this.pleSource;\n if (!src || !src.cache) return Promise.resolve();\n if (this.pleReady) return this.pleReady;\n const { cacheName, packedKey, scalesKey, zerosKey } = src.cache;\n this.pleReady = (async () => {\n const cache = await caches.open(cacheName);\n const read = async (key: string): Promise<ArrayBuffer> => {\n const resp = await cache.match(new Request(key));\n if (!resp) throw new Error(`PLE cache entry missing: ${key}`);\n return resp.arrayBuffer();\n };\n const [pBuf, sBuf, zBuf] = await Promise.all([\n read(packedKey),\n read(scalesKey),\n read(zerosKey),\n ]);\n src.packed = new Uint32Array(pBuf);\n src.scales = new Float32Array(sBuf);\n src.zeros = new Float32Array(zBuf);\n })();\n return this.pleReady;\n }\n\n /**\n * Gather + dequantize the PLE rows for `inputIds` and upload them into the\n * target activation buffer. Touches only T rows (T*width floats) — the full\n * [vocab, width] quantized table never goes to the GPU.\n */\n private async streamPleRows(inputIds: Uint32Array): Promise<void> {\n const src = this.pleSource;\n if (!src) return;\n if (src.cache) await this.ensurePleLoaded();\n const T = inputIds.length;\n const { packed, scales, zeros, width, groupSize } = src;\n const needed = T * width;\n if (!this.pleScratch || this.pleScratch.length < needed) {\n this.pleScratch = new Float32Array(needed);\n }\n const out = this.pleScratch;\n for (let t = 0; t < T; t++) {\n const tokenId = inputIds[t];\n const rowBase = tokenId * width; // flat element offset of this row\n const outBase = t * width;\n for (let d = 0; d < width; d++) {\n const flat = rowBase + d;\n const packedIdx = flat >>> 3; // flat / 8\n const nibblePos = (flat & 7) * 4; // (flat % 8) * 4\n const nibble = (packed[packedIdx] >>> nibblePos) & 0xf;\n const groupIdx = (flat / groupSize) | 0;\n out[outBase + d] = (nibble - zeros[groupIdx]) * scales[groupIdx];\n }\n }\n const buffer = this.getBuffer(src.targetTensor);\n if (!buffer) {\n throw new Error(`PLE target tensor \"${src.targetTensor}\" has no GPU buffer`);\n }\n this.ctx.device.queue.writeBuffer(buffer, 0, out.buffer, out.byteOffset, needed * 4);\n }\n\n /**\n * Stream weights to the GPU one tensor at a time, pulling each from the\n * `WeightSource` only when it is about to be uploaded and dropping the\n * reference immediately afterward.\n *\n * This is the property that bounds peak JS heap: with a cache-backed source\n * (browser/mobile), only ONE tensor's bytes are materialized in heap at a time\n * (read from CacheStorage in `source.get()`), uploaded to its GPU buffer, then\n * released before the next tensor is fetched. The whole model is never co-\n * resident in heap. With a heap-backed source (Node/desktop), behavior matches\n * the old Map path (the source deletes each entry as it is consumed).\n *\n * Accepts either a `WeightSource` (new, streamed/async) or a plain Map\n * (back-compat for callers that build a Map directly, e.g. Kani/Moonshine).\n */\n async uploadWeights(\n source: WeightSource | Map<string, { data: ArrayBufferView; shape: number[] }>,\n ): Promise<void> {\n if (source instanceof Map) {\n this.uploadWeightsMap(source);\n return;\n }\n for (const name of source.keys()) {\n const entry = await source.get(name);\n if (!entry) continue;\n const { data } = entry;\n const buffer = createStorageBuffer(this.ctx, `weight_${name}`, data.byteLength, data);\n this.weightBuffers.set(name, buffer);\n // Each `entry` reference goes out of scope here; with a cache-backed source\n // its bytes can be GC'd before the next tensor is pulled — bounding peak heap.\n }\n }\n\n /**\n * Synchronous Map upload (Node/desktop and the Kani/Moonshine sub-executors,\n * which build small heap Maps). Deletes each entry as it is consumed to free\n * the JS-side bytes once they are GPU-resident. Safe to call from a constructor.\n */\n uploadWeightsMap(weights: Map<string, { data: ArrayBufferView; shape: number[] }>): void {\n const names = [...weights.keys()];\n for (const name of names) {\n const entry = weights.get(name);\n if (!entry) continue;\n const buffer = createStorageBuffer(\n this.ctx,\n `weight_${name}`,\n entry.data.byteLength,\n entry.data,\n );\n this.weightBuffers.set(name, buffer);\n weights.delete(name);\n }\n }\n\n /**\n * Build all pipelines and bind groups. Call ONCE after uploadWeights().\n *\n * Creates two dispatch entry arrays:\n * - dispatchEntries: tiled matmul for prefill (any M)\n * - decodeEntries: K-parallel matvec for decode (M=1)\n */\n initBindGroups(): void {\n for (const nodeId of this.graph.executionOrder) {\n const node = this.graph.nodes.find((n) => n.id === nodeId)!;\n let spec = KERNEL_REGISTRY[node.opType];\n if (!spec) throw new Error(`No kernel for op type: ${node.opType}`);\n\n // Use f16 kernel variants when KV cache tensors are f16.\n // \"packed-f16\" uses pack2x16float (Safari-safe), \"native-f16\" uses enable f16.\n if (node.opType === \"KVCacheAppend\") {\n const hasF16KV = node.outputs.some((out) => this.graph.tensors[out]?.dtype === \"f16\");\n if (hasF16KV) {\n spec =\n this.kvMode === \"packed-f16\"\n ? KV_CACHE_APPEND_PACKED_F16_SPEC\n : KV_CACHE_APPEND_F16_SPEC;\n }\n } else if (node.opType === \"Attention\") {\n const hasF16KV = node.inputs.some((inp) => this.graph.tensors[inp]?.dtype === \"f16\");\n if (hasF16KV) {\n spec = this.kvMode === \"packed-f16\" ? ATTENTION_PACKED_F16_SPEC : ATTENTION_F16_SPEC;\n }\n } else if (node.opType === \"MambaSSM\" && this.ctx.hasF16 && !this.ctx.isWebKitWebGPU) {\n // f16 SSM state storage halves the dominant state bandwidth.\n // Dawn only — WebKit keeps the f32 kernel (unverified there).\n spec = MAMBA_SSM_F16_SPEC;\n } else if (node.opType === \"LayerNorm\" && node.inputs.length === 2) {\n // Weight-only LayerNorm (Moonshine encoder/decoder norms have no bias):\n // the node binds [input, weight] only, so use the no-bias variant whose\n // binding layout matches.\n spec = LAYERNORM_NO_BIAS_SPEC;\n } else if (node.opType === \"RoPE\" && node.attributes.interleaved === true) {\n // Moonshine rotates adjacent dim pairs (2p, 2p+1), not the [p, p+half]\n // split the default kernel uses.\n spec = ROPE_INTERLEAVED_SPEC;\n }\n\n const pipeline = getOrCreatePipeline(\n this.ctx,\n `kernel_${nodeId}`,\n spec.shaderCode,\n spec.entryPoint,\n );\n\n // Create uniform buffer with dummy params (will be overwritten before first dispatch)\n const dummyShapes = this.resolveShapes(1);\n const paramsData = spec.buildParams(node, dummyShapes, { seqPos: 0 });\n const uniformBuffer = createUniformBuffer(this.ctx, `uniform_${nodeId}`, paramsData);\n\n // Gather buffers for bind group\n const bufferEntries = this.gatherBuffers(spec, node, uniformBuffer);\n const bindGroup = createBindGroup(this.ctx, pipeline, bufferEntries, `bg_${nodeId}`);\n\n const prefillEntry: DispatchEntry = {\n nodeId,\n node,\n spec,\n pipeline,\n bindGroup,\n uniformBuffer,\n lastParamsBytes: null,\n lastDispatchSize: null,\n };\n this.dispatchEntries.push(prefillEntry);\n\n // For matmul ops, create a separate decode entry with the matvec kernel\n const isMatmul = node.opType === \"MatMul\" || node.opType === \"MatMulInt4\";\n if (isMatmul) {\n // Subgroups-accelerated matvec reduction. GATED OFF by default: on Dawn\n // (M-series desktop) it measured a large DECODE REGRESSION (~210→47 tok/s\n // Qwen3.5-0.8B, ~593→146 tok/s LFM2.5-350M) — coherent output, but the\n // extra workgroup barriers + per-column subgroup combine outweigh the\n // tiny saved 16/32-element sequential reduction these matvecs do. The\n // variant is kept registered and selectable behind an opt-in flag for\n // future hardware/driver re-evaluation; default path is the portable\n // kernel. WebKit is excluded regardless (mobile-safety rules).\n const subgroupsOptIn =\n (globalThis as { process?: { env?: Record<string, string | undefined> } }).process?.env\n ?.GERBIL_MATVEC_SUBGROUPS === \"1\";\n const useSubgroups = subgroupsOptIn && this.ctx.hasSubgroups && !this.ctx.isWebKitWebGPU;\n const mvSpec =\n node.opType === \"MatMulInt4\"\n ? useSubgroups\n ? MATVEC_INT4_SUBGROUPS_SPEC\n : MATVEC_INT4_SPEC\n : useSubgroups\n ? MATVEC_SUBGROUPS_SPEC\n : MATVEC_SPEC;\n\n const mvPipeline = getOrCreatePipeline(\n this.ctx,\n `matvec_${nodeId}`,\n mvSpec.shaderCode,\n mvSpec.entryPoint,\n );\n const mvUniformData = mvSpec.buildParams(node, dummyShapes, { seqPos: 0 });\n const mvUniform = createUniformBuffer(this.ctx, `mv_uniform_${nodeId}`, mvUniformData);\n const mvBuffers = this.gatherBuffers(mvSpec, node, mvUniform);\n const mvBindGroup = createBindGroup(this.ctx, mvPipeline, mvBuffers, `bg_mv_${nodeId}`);\n\n this.decodeEntries.push({\n nodeId,\n node,\n spec: mvSpec,\n pipeline: mvPipeline,\n bindGroup: mvBindGroup,\n uniformBuffer: mvUniform,\n lastParamsBytes: null,\n lastDispatchSize: null,\n });\n } else {\n // Non-matmul ops share the same entry for decode\n this.decodeEntries.push(prefillEntry);\n }\n }\n\n // ── Fuse gate_proj + up_proj + SwiGLU → single SwiGLUMatVec dispatch ──\n // Scans decode entries for the pattern: MatMul(input→gate) + MatMul(input→up) + SwiGLU(gate,up→out)\n // Replaces 3 dispatches with 1 fused kernel that reads input once.\n this.fuseSwiGLUDecodeEntries();\n\n // ── Fuse adjacent INT4 projections sharing the same input (q+gate, k+v) ──\n // Full-attention decode runs q_proj/gate_proj/k_proj/v_proj all reading the\n // same input_layernorm output. Merge the q+gate pair and the k+v pair into\n // one DualMatVecInt4 dispatch each, removing one GPU round-trip per pair.\n this.fuseDualMatVecDecodeEntries();\n\n // ── Fuse adjacent K+V cache appends (full-attention layers) ──\n this.fuseDualKVCacheAppendEntries();\n\n // ── Fuse attention SigmoidGate into the INT4 o_proj that consumes it ──\n this.fuseGatedOProjDecodeEntries();\n\n // ── Fuse Mamba SwiGLU (silu(z)*norm) into the INT4 out_proj ──\n this.fuseSwiGLUGatedProjDecodeEntries();\n\n // ── Fuse adjacent per-head RMSNorms (q_norm + k_norm) ──\n this.fuseDualRMSNormDecodeEntries();\n\n // Decode dispatch count per token — the mobile-proportional metric. On WebKit\n // each dispatch is a submit+drain round-trip, so decode tok/s ≈ 1/this. Logged\n // so every fusion's dispatch reduction is measurable on desktop/node.\n console.log(`[executor] decode: ${this.decodeEntries.length} dispatches/token`);\n\n // Create argmax bind group\n const logitsBuffer = this.getBuffer(\"logits\");\n if (logitsBuffer) {\n const argmaxPipeline = getOrCreatePipeline(\n this.ctx,\n \"argmax\",\n ARGMAX_SPEC.shaderCode,\n ARGMAX_SPEC.entryPoint,\n );\n const argmaxUniform = createUniformBuffer(\n this.ctx,\n \"uniform_argmax\",\n new ArrayBuffer(16), // {count, offset} — will be overwritten\n );\n const argmaxBindGroup = createBindGroup(\n this.ctx,\n argmaxPipeline,\n [{ buffer: logitsBuffer }, { buffer: this.argmaxResultBuffer }, { buffer: argmaxUniform }],\n \"bg_argmax\",\n );\n this.argmaxEntry = {\n pipeline: argmaxPipeline,\n bindGroup: argmaxBindGroup,\n uniformBuffer: argmaxUniform,\n };\n }\n\n if (this.needsMultiEncoder) {\n console.log(`[executor] WebKit WebGPU detected — using per-dispatch submit`);\n }\n\n // Create staging buffer for uniform param updates.\n // Size = max(total prefill uniform bytes, total decode uniform bytes + argmax).\n let prefillTotal = 0;\n for (const entry of this.dispatchEntries) {\n prefillTotal += Math.ceil(entry.uniformBuffer.size / 4) * 4;\n }\n let decodeTotal = 0;\n for (const entry of this.decodeEntries) {\n decodeTotal += Math.ceil(entry.uniformBuffer.size / 4) * 4;\n }\n decodeTotal += 16; // argmax params (8 bytes, padded)\n // Add space for input_ids (routed through staging too, for Safari safety)\n const inputIdBytes = this.maxSeqLen * 4;\n this.uniformStagingCapacity = Math.max(prefillTotal, decodeTotal) + inputIdBytes;\n this.uniformStagingBuffer = this.ctx.device.createBuffer({\n label: \"uniform_staging\",\n size: this.uniformStagingCapacity,\n usage: 0x0004 | 0x0008, // COPY_SRC | COPY_DST\n });\n }\n\n /**\n * Run a forward pass. Uses matvec kernels for M=1 (decode), tiled for M>1 (prefill).\n */\n async forward(inputIds: Uint32Array): Promise<ForwardResult> {\n const T = inputIds.length;\n\n const resolvedShapes = this.resolveShapes(T);\n const runtimeContext: RuntimeContext = { seqPos: this.seqPos };\n\n // Use matvec entries for single-token decode, tiled for prefill\n const entries = T === 1 ? this.decodeEntries : this.dispatchEntries;\n\n // Write input_ids directly — no staging buffer.\n this.ctx.device.queue.writeBuffer(this.inputIdsBuffer, 0, inputIds as BufferSource);\n\n // Gemma 4: stream the CPU-resident PLE rows for these tokens (no-op otherwise).\n await this.streamPleRows(inputIds);\n\n // Build params and write directly to each uniform buffer.\n // Also compute dispatch sizes up front.\n const dispatchSizes: Array<[number, number, number]> = new Array(entries.length);\n for (let i = 0; i < entries.length; i++) {\n const entry = entries[i];\n const paramsData = entry.spec.buildParams(entry.node, resolvedShapes, runtimeContext);\n const paramsView = new Uint8Array(paramsData);\n\n let changed = !entry.lastParamsBytes || entry.lastParamsBytes.length !== paramsView.length;\n if (!changed) {\n const cached = entry.lastParamsBytes!;\n for (let j = 0; j < paramsView.length; j++) {\n if (paramsView[j] !== cached[j]) {\n changed = true;\n break;\n }\n }\n }\n\n if (changed) {\n this.ctx.device.queue.writeBuffer(entry.uniformBuffer, 0, paramsData);\n entry.lastParamsBytes = new Uint8Array(paramsView.slice(0));\n }\n\n dispatchSizes[i] = entry.spec.getDispatchSize(entry.node, resolvedShapes, runtimeContext);\n }\n\n if (this.profileEnabled) {\n await this.runProfiledDispatches(entries, dispatchSizes);\n } else if (this.needsMultiEncoder) {\n // WebKit: group dispatches into command buffers of webkitGroupSize\n // (one compute pass per dispatch), with at most ONE command buffer in\n // flight (awaited). Fire-and-forget per-dispatch submits queue ~400\n // unbounded in-flight Metal command buffers per token — the documented\n // WebKit resource-exhaustion anti-pattern (WebKit bug 311598). Batching\n // many dispatches into one submission zeroed activations on iPad\n // (docs/metal-safari-intel.md); sweep ?group=N to find this WebKit\n // version's correct/fast granularity (llama.cpp runs ~64/CB on iOS 26.4).\n const group = this.webkitGroupSize;\n for (let start = 0; start < entries.length; start += group) {\n const enc = this.ctx.device.createCommandEncoder();\n const end = Math.min(start + group, entries.length);\n for (let i = start; i < end; i++) {\n const p = enc.beginComputePass();\n p.setPipeline(entries[i].pipeline);\n p.setBindGroup(0, entries[i].bindGroup);\n p.dispatchWorkgroups(...dispatchSizes[i]);\n p.end();\n }\n this.ctx.device.queue.submit([enc.finish()]);\n await this.ctx.device.queue.onSubmittedWorkDone();\n }\n const logitsBuffer = this.getBuffer(\"logits\");\n if (logitsBuffer) {\n const copyEnc = this.ctx.device.createCommandEncoder();\n const vocabSize = this.graph.config.vocab_size;\n // logits is [1, vocab] — SliceLastRow feeds lm_head only the last position\n copyEnc.copyBufferToBuffer(logitsBuffer, 0, this.logitsReadback, 0, vocabSize * 4);\n this.ctx.device.queue.submit([copyEnc.finish()]);\n }\n } else {\n // Desktop/Dawn: single encoder, single compute pass\n const encoder = this.ctx.device.createCommandEncoder({ label: \"forward\" });\n const pass = encoder.beginComputePass({ label: \"fwd_pass\" });\n for (let i = 0; i < entries.length; i++) {\n pass.setPipeline(entries[i].pipeline);\n pass.setBindGroup(0, entries[i].bindGroup);\n pass.dispatchWorkgroups(...dispatchSizes[i]);\n }\n pass.end();\n const logitsBuffer = this.getBuffer(\"logits\");\n if (logitsBuffer) {\n const vocabSize = this.graph.config.vocab_size;\n // logits is [1, vocab] — SliceLastRow feeds lm_head only the last position\n encoder.copyBufferToBuffer(logitsBuffer, 0, this.logitsReadback, 0, vocabSize * 4);\n }\n this.ctx.device.queue.submit([encoder.finish()]);\n }\n\n const vocabSize = this.graph.config.vocab_size;\n await this.logitsReadback.mapAsync(MAP_MODE_READ, 0, vocabSize * 4);\n const mapped = this.logitsReadback.getMappedRange(0, vocabSize * 4);\n const logits = new Float32Array(mapped.slice(0));\n this.logitsReadback.unmap();\n\n this.seqPos += T;\n return { logits };\n }\n\n /**\n * Profiling variant of the desktop dispatch path: one compute pass per dispatch,\n * each bracketed by timestamp queries, so we get per-op GPU time. Accumulates\n * into profileData by opType. Only runs under GERBIL_PROFILE with timestamp-query\n * support — slower than the batched path (it measures relative cost, not tok/s).\n */\n private async runProfiledDispatches(\n entries: DispatchEntry[],\n dispatchSizes: number[][],\n copyLogits = true,\n ): Promise<void> {\n const n = entries.length;\n const queryCount = n * 2;\n if (!this.querySet) {\n this.querySet = this.ctx.device.createQuerySet({ type: \"timestamp\", count: queryCount });\n // QUERY_RESOLVE (0x0200) | COPY_SRC (0x0004)\n this.queryResolveBuf = this.ctx.device.createBuffer({\n size: queryCount * 8,\n usage: 0x0200 | 0x0004,\n });\n // MAP_READ (0x0001) | COPY_DST (0x0008)\n this.queryReadbackBuf = this.ctx.device.createBuffer({\n size: queryCount * 8,\n usage: 0x0001 | 0x0008,\n });\n }\n const querySet = this.querySet;\n const resolveBuf = this.queryResolveBuf as GPUBuffer;\n const readbackBuf = this.queryReadbackBuf as GPUBuffer;\n\n const encoder = this.ctx.device.createCommandEncoder({ label: \"fwd_profiled\" });\n for (let i = 0; i < n; i++) {\n const pass = encoder.beginComputePass({\n timestampWrites: {\n querySet,\n beginningOfPassWriteIndex: i * 2,\n endOfPassWriteIndex: i * 2 + 1,\n },\n });\n pass.setPipeline(entries[i].pipeline);\n pass.setBindGroup(0, entries[i].bindGroup);\n pass.dispatchWorkgroups(dispatchSizes[i][0], dispatchSizes[i][1], dispatchSizes[i][2]);\n pass.end();\n }\n if (copyLogits) {\n const logitsBuffer = this.getBuffer(\"logits\");\n const vocabSize = this.graph.config.vocab_size;\n if (logitsBuffer) {\n encoder.copyBufferToBuffer(logitsBuffer, 0, this.logitsReadback, 0, vocabSize * 4);\n }\n }\n encoder.resolveQuerySet(querySet, 0, queryCount, resolveBuf, 0);\n encoder.copyBufferToBuffer(resolveBuf, 0, readbackBuf, 0, queryCount * 8);\n this.ctx.device.queue.submit([encoder.finish()]);\n\n await readbackBuf.mapAsync(MAP_MODE_READ, 0, queryCount * 8);\n const ts = new BigUint64Array(readbackBuf.getMappedRange(0, queryCount * 8).slice(0));\n readbackBuf.unmap();\n for (let i = 0; i < n; i++) {\n const ns = Number(ts[i * 2 + 1] - ts[i * 2]);\n if (ns <= 0) {\n continue;\n }\n const key = entries[i].node.opType;\n const cur = this.profileData.get(key) ?? { ns: 0, count: 0 };\n cur.ns += ns;\n cur.count += 1;\n this.profileData.set(key, cur);\n }\n }\n\n /** Per-opType GPU time (ns) + dispatch count accumulated by GERBIL_PROFILE, hottest first. */\n getProfile(): Array<{ opType: string; ns: number; count: number }> {\n return [...this.profileData.entries()]\n .map(([opType, v]) => ({ opType, ns: v.ns, count: v.count }))\n .sort((a, b) => b.ns - a.ns);\n }\n\n /** Clear accumulated profiler data (e.g. to drop warm-up tokens). */\n resetProfile(): void {\n this.profileData.clear();\n }\n\n /** GPU dispatches per decode token (post-fusion). On mobile this drives the\n * submit-group count = ceil(dispatchCount / webkitGroupSize). */\n get decodeDispatchCount(): number {\n return this.decodeEntries.length;\n }\n\n /** Device limit that gates the INT4 projection fusions (they need ≥9). If a\n * device caps at 8 the dual/gated/swiglu-gated INT4 fusions silently fall back,\n * inflating the decode dispatch count. */\n get maxStorageBuffers(): number {\n return this.ctx.limits.maxStorageBuffersPerShaderStage;\n }\n\n /**\n * Profile ONE real decode step: times the actual `decodeEntries` (the kernels\n * the pipelined greedy benchmark runs) with per-dispatch timestamps. Timing is\n * token-independent, so pass any valid id; runs un-pipelined with a synchronous\n * timestamp readback (measurement only, not for production decode). Argmax (one\n * tiny dispatch) is intentionally excluded — it is not a hotspot target.\n */\n async profileDecodeStep(tokenId: number): Promise<void> {\n const resolvedShapes = this.resolveShapes(1);\n const runtimeContext: RuntimeContext = { seqPos: this.seqPos };\n this.ctx.device.queue.writeBuffer(this.inputIdsBuffer, 0, new Uint32Array([tokenId]));\n\n const dispatchSizes: number[][] = new Array(this.decodeEntries.length);\n for (let i = 0; i < this.decodeEntries.length; i++) {\n const entry = this.decodeEntries[i];\n const paramsData = entry.spec.buildParams(entry.node, resolvedShapes, runtimeContext);\n this.ctx.device.queue.writeBuffer(entry.uniformBuffer, 0, paramsData);\n dispatchSizes[i] = entry.spec.getDispatchSize(entry.node, resolvedShapes, runtimeContext);\n }\n await this.runProfiledDispatches(this.decodeEntries, dispatchSizes, false);\n this.seqPos += 1;\n }\n\n /**\n * Run a single forward pass over `inputIds` and read back the L2-normalized\n * embedding vector. Requires an embedding graph (one whose output tensor is\n * \"embedding\", produced by the last-token-pool + L2-norm tail).\n *\n * Always runs in a fresh-state single pass (caller should reset() first):\n * embeddings are non-autoregressive, so the whole sequence is one prefill.\n */\n async embed(inputIds: Uint32Array): Promise<Float32Array> {\n const embeddingBuffer = this.getBuffer(\"embedding\");\n if (!embeddingBuffer) {\n throw new Error(\n \"embed() requires an embedding graph (no 'embedding' output tensor found). \" +\n \"Load the model with { embedding: true }.\",\n );\n }\n const hiddenSize = this.graph.config.hidden_size;\n const byteLen = hiddenSize * 4;\n\n const T = inputIds.length;\n const resolvedShapes = this.resolveShapes(T);\n const runtimeContext: RuntimeContext = { seqPos: this.seqPos };\n\n // Embeddings process the full sequence at once → always use prefill entries.\n const entries = T === 1 ? this.decodeEntries : this.dispatchEntries;\n\n this.ctx.device.queue.writeBuffer(this.inputIdsBuffer, 0, inputIds as BufferSource);\n\n const dispatchSizes: Array<[number, number, number]> = new Array(entries.length);\n for (let i = 0; i < entries.length; i++) {\n const entry = entries[i];\n const paramsData = entry.spec.buildParams(entry.node, resolvedShapes, runtimeContext);\n const paramsView = new Uint8Array(paramsData);\n\n let changed = !entry.lastParamsBytes || entry.lastParamsBytes.length !== paramsView.length;\n if (!changed) {\n const cached = entry.lastParamsBytes!;\n for (let j = 0; j < paramsView.length; j++) {\n if (paramsView[j] !== cached[j]) {\n changed = true;\n break;\n }\n }\n }\n if (changed) {\n this.ctx.device.queue.writeBuffer(entry.uniformBuffer, 0, paramsData);\n entry.lastParamsBytes = new Uint8Array(paramsView.slice(0));\n }\n\n dispatchSizes[i] = entry.spec.getDispatchSize(entry.node, resolvedShapes, runtimeContext);\n }\n\n // Dedicated readback buffer (COPY_DST | MAP_READ).\n const readback = this.ctx.device.createBuffer({ size: byteLen, usage: 0x0001 | 0x0008 });\n\n if (this.needsMultiEncoder) {\n const group = this.webkitGroupSize;\n for (let start = 0; start < entries.length; start += group) {\n const enc = this.ctx.device.createCommandEncoder();\n const end = Math.min(start + group, entries.length);\n for (let i = start; i < end; i++) {\n const p = enc.beginComputePass();\n p.setPipeline(entries[i].pipeline);\n p.setBindGroup(0, entries[i].bindGroup);\n p.dispatchWorkgroups(...dispatchSizes[i]);\n p.end();\n }\n this.ctx.device.queue.submit([enc.finish()]);\n await this.ctx.device.queue.onSubmittedWorkDone();\n }\n const copyEnc = this.ctx.device.createCommandEncoder();\n copyEnc.copyBufferToBuffer(embeddingBuffer, 0, readback, 0, byteLen);\n this.ctx.device.queue.submit([copyEnc.finish()]);\n } else {\n const encoder = this.ctx.device.createCommandEncoder({ label: \"embed\" });\n const pass = encoder.beginComputePass({ label: \"embed_pass\" });\n for (let i = 0; i < entries.length; i++) {\n pass.setPipeline(entries[i].pipeline);\n pass.setBindGroup(0, entries[i].bindGroup);\n pass.dispatchWorkgroups(...dispatchSizes[i]);\n }\n pass.end();\n encoder.copyBufferToBuffer(embeddingBuffer, 0, readback, 0, byteLen);\n this.ctx.device.queue.submit([encoder.finish()]);\n }\n\n await readback.mapAsync(MAP_MODE_READ, 0, byteLen);\n const out = new Float32Array(readback.getMappedRange(0, byteLen).slice(0));\n readback.unmap();\n readback.destroy();\n\n this.seqPos += T;\n return out;\n }\n\n /**\n * Generic one-shot dispatch for a non-LM graph: run every entry once over a\n * single forward and read back `elemCount` f32 elements of the named output\n * tensor. Used to execute the NanoCodec decoder graph (codes→PCM) — its ops use\n * concrete lengths and it has no \"logits\" output, so the normal forward()\n * logits-readback path does not apply. Caller writes any inputs (e.g. audio_codes)\n * via writeInput() and reset()s first.\n */\n async runGraphOutput(outputName: string, elemCount: number): Promise<Float32Array> {\n const outBuffer = this.getBuffer(outputName);\n if (!outBuffer) {\n throw new Error(`runGraphOutput: no GPU buffer for output tensor \"${outputName}\".`);\n }\n const byteLen = elemCount * 4;\n // Codec graphs carry concrete lengths; dispatchEntries (M>1 path) builds the\n // full pipeline. Pass T=1 so shape resolution keeps any symbolic dims minimal\n // (the codec ops ignore input_ids entirely).\n const resolvedShapes = this.resolveShapes(1);\n const runtimeContext: RuntimeContext = { seqPos: this.seqPos };\n const entries = this.dispatchEntries;\n\n const dispatchSizes: Array<[number, number, number]> = new Array(entries.length);\n for (let i = 0; i < entries.length; i++) {\n const entry = entries[i];\n const paramsData = entry.spec.buildParams(entry.node, resolvedShapes, runtimeContext);\n this.ctx.device.queue.writeBuffer(entry.uniformBuffer, 0, paramsData);\n entry.lastParamsBytes = new Uint8Array(new Uint8Array(paramsData).slice(0));\n dispatchSizes[i] = entry.spec.getDispatchSize(entry.node, resolvedShapes, runtimeContext);\n }\n\n const readback = this.ctx.device.createBuffer({ size: byteLen, usage: 0x0001 | 0x0008 });\n\n if (this.needsMultiEncoder) {\n const group = this.webkitGroupSize;\n for (let start = 0; start < entries.length; start += group) {\n const enc = this.ctx.device.createCommandEncoder();\n const end = Math.min(start + group, entries.length);\n for (let i = start; i < end; i++) {\n const pass = enc.beginComputePass();\n pass.setPipeline(entries[i].pipeline);\n pass.setBindGroup(0, entries[i].bindGroup);\n pass.dispatchWorkgroups(...dispatchSizes[i]);\n pass.end();\n }\n this.ctx.device.queue.submit([enc.finish()]);\n await this.ctx.device.queue.onSubmittedWorkDone();\n }\n const copyEnc = this.ctx.device.createCommandEncoder();\n copyEnc.copyBufferToBuffer(outBuffer, 0, readback, 0, byteLen);\n this.ctx.device.queue.submit([copyEnc.finish()]);\n } else {\n const encoder = this.ctx.device.createCommandEncoder({ label: \"runGraphOutput\" });\n const pass = encoder.beginComputePass({ label: \"run_pass\" });\n for (let i = 0; i < entries.length; i++) {\n pass.setPipeline(entries[i].pipeline);\n pass.setBindGroup(0, entries[i].bindGroup);\n pass.dispatchWorkgroups(...dispatchSizes[i]);\n }\n pass.end();\n encoder.copyBufferToBuffer(outBuffer, 0, readback, 0, byteLen);\n this.ctx.device.queue.submit([encoder.finish()]);\n }\n\n await readback.mapAsync(MAP_MODE_READ, 0, byteLen);\n const out = new Float32Array(readback.getMappedRange(0, byteLen).slice(0));\n readback.unmap();\n readback.destroy();\n return out;\n }\n\n /**\n * Greedy decode step: forward + GPU argmax. Returns token ID directly.\n * Always uses matvec kernels (M=1). Reads back 4 bytes instead of vocab_size*4.\n */\n async forwardArgmax(inputIds: Uint32Array): Promise<number> {\n const T = inputIds.length;\n\n const resolvedShapes = this.resolveShapes(T);\n const runtimeContext: RuntimeContext = { seqPos: this.seqPos };\n\n // Write input_ids directly\n this.ctx.device.queue.writeBuffer(this.inputIdsBuffer, 0, inputIds as BufferSource);\n\n // Write uniform params directly to each buffer\n const argmaxDispatchSizes: Array<[number, number, number]> = new Array(\n this.decodeEntries.length,\n );\n for (let i = 0; i < this.decodeEntries.length; i++) {\n const entry = this.decodeEntries[i];\n const paramsData = entry.spec.buildParams(entry.node, resolvedShapes, runtimeContext);\n const paramsView = new Uint8Array(paramsData);\n\n let changed = !entry.lastParamsBytes || entry.lastParamsBytes.length !== paramsView.length;\n if (!changed) {\n const cached = entry.lastParamsBytes!;\n for (let j = 0; j < paramsView.length; j++) {\n if (paramsView[j] !== cached[j]) {\n changed = true;\n break;\n }\n }\n }\n\n if (changed) {\n this.ctx.device.queue.writeBuffer(entry.uniformBuffer, 0, paramsData);\n entry.lastParamsBytes = new Uint8Array(paramsView.slice(0));\n }\n\n argmaxDispatchSizes[i] = entry.spec.getDispatchSize(\n entry.node,\n resolvedShapes,\n runtimeContext,\n );\n }\n\n // Write argmax params directly.\n // logits is [1, vocab] (SliceLastRow feeds lm_head only the last position),\n // so the argmax always reads row 0.\n if (this.argmaxEntry) {\n const vocabSize = this.graph.config.vocab_size;\n const argmaxParams = new ArrayBuffer(8);\n const view = new DataView(argmaxParams);\n view.setUint32(0, vocabSize, true);\n view.setUint32(4, 0, true);\n this.ctx.device.queue.writeBuffer(this.argmaxEntry.uniformBuffer, 0, argmaxParams);\n }\n\n if (this.needsMultiEncoder) {\n // WebKit: grouped submits with one command buffer in flight\n // (see forward() comment for rationale)\n const group = this.webkitGroupSize;\n for (let start = 0; start < this.decodeEntries.length; start += group) {\n const enc = this.ctx.device.createCommandEncoder();\n const end = Math.min(start + group, this.decodeEntries.length);\n for (let i = start; i < end; i++) {\n const p = enc.beginComputePass();\n p.setPipeline(this.decodeEntries[i].pipeline);\n p.setBindGroup(0, this.decodeEntries[i].bindGroup);\n p.dispatchWorkgroups(...argmaxDispatchSizes[i]);\n p.end();\n }\n this.ctx.device.queue.submit([enc.finish()]);\n await this.ctx.device.queue.onSubmittedWorkDone();\n }\n // Argmax dispatch and readback copy in SEPARATE submits, mirroring\n // forward(): the copy reads argmaxResultBuffer written by the dispatch,\n // so it must sit behind a command buffer boundary on WebKit.\n if (this.argmaxEntry) {\n const enc = this.ctx.device.createCommandEncoder();\n const p = enc.beginComputePass();\n p.setPipeline(this.argmaxEntry.pipeline);\n p.setBindGroup(0, this.argmaxEntry.bindGroup);\n p.dispatchWorkgroups(1, 1, 1);\n p.end();\n this.ctx.device.queue.submit([enc.finish()]);\n\n const copyEnc = this.ctx.device.createCommandEncoder();\n copyEnc.copyBufferToBuffer(this.argmaxResultBuffer, 0, this.argmaxReadback, 0, 4);\n this.ctx.device.queue.submit([copyEnc.finish()]);\n }\n } else {\n // Desktop/Dawn: single encoder, single compute pass\n const encoder = this.ctx.device.createCommandEncoder({ label: \"argmax\" });\n const pass = encoder.beginComputePass({ label: \"am_pass\" });\n for (let i = 0; i < this.decodeEntries.length; i++) {\n const entry = this.decodeEntries[i];\n pass.setPipeline(entry.pipeline);\n pass.setBindGroup(0, entry.bindGroup);\n pass.dispatchWorkgroups(...argmaxDispatchSizes[i]);\n }\n if (this.argmaxEntry) {\n pass.setPipeline(this.argmaxEntry.pipeline);\n pass.setBindGroup(0, this.argmaxEntry.bindGroup);\n pass.dispatchWorkgroups(1, 1, 1);\n }\n pass.end();\n encoder.copyBufferToBuffer(this.argmaxResultBuffer, 0, this.argmaxReadback, 0, 4);\n this.ctx.device.queue.submit([encoder.finish()]);\n }\n\n await this.argmaxReadback.mapAsync(MAP_MODE_READ, 0, 4);\n const mapped = this.argmaxReadback.getMappedRange(0, 4);\n const tokenId = new Uint32Array(mapped.slice(0))[0];\n this.argmaxReadback.unmap();\n\n this.seqPos += T;\n return tokenId;\n }\n\n /** KV-cache positions still available for decode steps. */\n decodeCapacityRemaining(): number {\n return this.maxSeqLen - this.seqPos;\n }\n\n /** Number of decode steps that may be in flight in the pipelined path. */\n static readonly PIPELINE_DEPTH = 2;\n\n /**\n * Pipelined greedy decode step (Dawn only — WebKit uses forwardArgmax).\n *\n * Encodes one full decode forward + argmax and submits WITHOUT awaiting\n * completion. The input token is taken from `tokenId` for the first step\n * after prefill; for subsequent steps (tokenId === null) the previous step's\n * argmax result is copied into input_ids ON THE GPU, so the decode loop\n * never blocks on a readback before submitting the next step. The argmax\n * result is copied to a per-slot readback buffer, read later (one step\n * behind) via readDecodeToken(slot).\n *\n * queue.writeBuffer is queue-ordered: uniform updates land after the\n * previously submitted step's command buffer and before this one's, so\n * shared uniform buffers are safe with multiple steps in flight.\n */\n submitGreedyDecodeStep(tokenId: number | null, slot: number): void {\n const resolvedShapes = this.resolveShapes(1);\n const runtimeContext: RuntimeContext = { seqPos: this.seqPos };\n\n if (this.decodeReadbacks.length === 0) {\n for (let i = 0; i < Executor.PIPELINE_DEPTH; i++) {\n this.decodeReadbacks.push(createReadbackBuffer(this.ctx, `decode_readback_${i}`, 4));\n }\n }\n\n if (tokenId !== null) {\n this.ctx.device.queue.writeBuffer(this.inputIdsBuffer, 0, new Uint32Array([tokenId]));\n // Argmax params are decode-invariant — write once per generation.\n if (this.argmaxEntry) {\n const argmaxParams = new ArrayBuffer(8);\n const view = new DataView(argmaxParams);\n view.setUint32(0, this.graph.config.vocab_size, true);\n view.setUint32(4, 0, true);\n this.ctx.device.queue.writeBuffer(this.argmaxEntry.uniformBuffer, 0, argmaxParams);\n }\n }\n\n // Update uniforms (same skip-unchanged caching as forwardArgmax)\n const dispatchSizes: Array<[number, number, number]> = new Array(this.decodeEntries.length);\n for (let i = 0; i < this.decodeEntries.length; i++) {\n const entry = this.decodeEntries[i];\n const paramsData = entry.spec.buildParams(entry.node, resolvedShapes, runtimeContext);\n const paramsView = new Uint8Array(paramsData);\n\n let changed = !entry.lastParamsBytes || entry.lastParamsBytes.length !== paramsView.length;\n if (!changed) {\n const cached = entry.lastParamsBytes!;\n for (let j = 0; j < paramsView.length; j++) {\n if (paramsView[j] !== cached[j]) {\n changed = true;\n break;\n }\n }\n }\n\n if (changed) {\n this.ctx.device.queue.writeBuffer(entry.uniformBuffer, 0, paramsData);\n entry.lastParamsBytes = new Uint8Array(paramsView.slice(0));\n }\n\n dispatchSizes[i] = entry.spec.getDispatchSize(entry.node, resolvedShapes, runtimeContext);\n }\n\n const encoder = this.ctx.device.createCommandEncoder({ label: \"decode_step\" });\n if (tokenId === null) {\n // Chain: previous step's argmax result becomes this step's input token.\n encoder.copyBufferToBuffer(this.argmaxResultBuffer, 0, this.inputIdsBuffer, 0, 4);\n }\n const pass = encoder.beginComputePass({ label: \"decode_pass\" });\n for (let i = 0; i < this.decodeEntries.length; i++) {\n const entry = this.decodeEntries[i];\n pass.setPipeline(entry.pipeline);\n pass.setBindGroup(0, entry.bindGroup);\n pass.dispatchWorkgroups(...dispatchSizes[i]);\n }\n if (this.argmaxEntry) {\n pass.setPipeline(this.argmaxEntry.pipeline);\n pass.setBindGroup(0, this.argmaxEntry.bindGroup);\n pass.dispatchWorkgroups(1, 1, 1);\n }\n pass.end();\n encoder.copyBufferToBuffer(this.argmaxResultBuffer, 0, this.decodeReadbacks[slot], 0, 4);\n this.ctx.device.queue.submit([encoder.finish()]);\n\n this.seqPos += 1;\n }\n\n /** Read back the token produced by the pipelined step that used `slot`. */\n async readDecodeToken(slot: number): Promise<number> {\n const buf = this.decodeReadbacks[slot];\n await buf.mapAsync(MAP_MODE_READ, 0, 4);\n const mapped = buf.getMappedRange(0, 4);\n const tokenId = new Uint32Array(mapped.slice(0))[0];\n buf.unmap();\n return tokenId;\n }\n\n reset(): void {\n this.seqPos = 0;\n // Zero SSM state buffers\n for (const buf of this.ssmStateBuffers.values()) {\n this.ctx.device.queue.writeBuffer(buf, 0, new Uint8Array(buf.size));\n }\n // Clear cached params — shapes change between prefill (T=prompt_len) and decode (T=1)\n for (const entry of this.dispatchEntries) {\n entry.lastParamsBytes = null;\n entry.lastDispatchSize = null;\n }\n for (const entry of this.decodeEntries) {\n entry.lastParamsBytes = null;\n entry.lastDispatchSize = null;\n }\n }\n\n /**\n * Diagnostic: dispatch ONLY the first kernel (EmbeddingInt4) using the\n * production bind group, pipeline, and buffers — but in isolation (1 dispatch,\n * no staging, fresh encoder). Compares against full forward pass to isolate\n * whether the issue is the bind group/pipeline or the multi-dispatch context.\n */\n async debugFirstDispatch(inputIds: Uint32Array): Promise<{\n nodeId: string;\n opType: string;\n dispatchSize: [number, number, number];\n output: Float32Array;\n }> {\n const T = inputIds.length;\n const resolvedShapes = this.resolveShapes(T);\n const runtimeContext: RuntimeContext = { seqPos: this.seqPos };\n const entries = T === 1 ? this.decodeEntries : this.dispatchEntries;\n const entry = entries[0];\n\n // Write input_ids directly (no staging)\n this.ctx.device.queue.writeBuffer(this.inputIdsBuffer, 0, inputIds as BufferSource);\n\n // Write params directly to uniform buffer (no staging)\n const paramsData = entry.spec.buildParams(entry.node, resolvedShapes, runtimeContext);\n this.ctx.device.queue.writeBuffer(entry.uniformBuffer, 0, paramsData);\n\n // Single dispatch with fresh encoder\n const dispatchSize = entry.spec.getDispatchSize(entry.node, resolvedShapes, runtimeContext) as [\n number,\n number,\n number,\n ];\n const encoder = this.ctx.device.createCommandEncoder({ label: \"debug_first\" });\n const pass = encoder.beginComputePass({ label: \"debug_first_pass\" });\n pass.setPipeline(entry.pipeline);\n pass.setBindGroup(0, entry.bindGroup);\n pass.dispatchWorkgroups(...dispatchSize);\n pass.end();\n this.ctx.device.queue.submit([encoder.finish()]);\n\n // Read back the output tensor (first 16 elements)\n const outputName = entry.node.outputs[0];\n const output = await this.debugReadBuffer(outputName, 16);\n\n return {\n nodeId: entry.nodeId,\n opType: entry.node.opType,\n dispatchSize,\n output,\n };\n }\n\n /**\n * Diagnostic: run a single dispatch entry by index, in isolation.\n * Call after debugFirstDispatch() to test whether entry[1] (RMSNorm)\n * can read embed_out written by entry[0] (EmbeddingInt4).\n */\n async debugDispatchEntry(\n entryIndex: number,\n T: number,\n ): Promise<{ nodeId: string; opType: string; output: Float32Array }> {\n const resolvedShapes = this.resolveShapes(T);\n const runtimeContext: RuntimeContext = { seqPos: this.seqPos };\n const entries = T === 1 ? this.decodeEntries : this.dispatchEntries;\n const entry = entries[entryIndex];\n\n // Write params directly\n const paramsData = entry.spec.buildParams(entry.node, resolvedShapes, runtimeContext);\n this.ctx.device.queue.writeBuffer(entry.uniformBuffer, 0, paramsData);\n\n // Single dispatch\n const dispatchSize = entry.spec.getDispatchSize(entry.node, resolvedShapes, runtimeContext) as [\n number,\n number,\n number,\n ];\n const encoder = this.ctx.device.createCommandEncoder({ label: `debug_entry_${entryIndex}` });\n const pass = encoder.beginComputePass({ label: `debug_pass_${entryIndex}` });\n pass.setPipeline(entry.pipeline);\n pass.setBindGroup(0, entry.bindGroup);\n pass.dispatchWorkgroups(...dispatchSize);\n pass.end();\n this.ctx.device.queue.submit([encoder.finish()]);\n\n const outputName = entry.node.outputs[0];\n const output = await this.debugReadBuffer(outputName, 16);\n return { nodeId: entry.nodeId, opType: entry.node.opType, output };\n }\n\n /**\n * Diagnostic: compute the JS-side params for the first N decode entries\n * WITHOUT dispatching. Shows what buildParams produces.\n */\n debugComputeParams(\n T: number,\n count = 5,\n ): Array<{\n idx: number;\n nodeId: string;\n opType: string;\n paramsU32: number[];\n dispatchSize: [number, number, number];\n }> {\n const resolvedShapes = this.resolveShapes(T);\n const runtimeContext: RuntimeContext = { seqPos: this.seqPos };\n const entries = T === 1 ? this.decodeEntries : this.dispatchEntries;\n const results = [];\n for (let i = 0; i < Math.min(count, entries.length); i++) {\n const entry = entries[i];\n const paramsData = entry.spec.buildParams(entry.node, resolvedShapes, runtimeContext);\n const u32 = new Uint32Array(paramsData);\n const dispatchSize = entry.spec.getDispatchSize(\n entry.node,\n resolvedShapes,\n runtimeContext,\n ) as [number, number, number];\n results.push({\n idx: i,\n nodeId: entry.nodeId,\n opType: entry.node.opType,\n paramsU32: Array.from(u32),\n dispatchSize,\n });\n }\n return results;\n }\n\n /**\n * Diagnostic: after a forward pass, read back output tensors at several points\n * in the pipeline to find where data drops to zero.\n */\n async debugPipelineProbe(T: number): Promise<\n Array<{\n idx: number;\n nodeId: string;\n opType: string;\n tensor: string;\n sum: number;\n first4: number[];\n uniformParams?: number[];\n }>\n > {\n const entries = T === 1 ? this.decodeEntries : this.dispatchEntries;\n // Focus on the first few entries to find where zeros start\n const totalEntries = entries.length;\n const indices = [\n 0,\n 1,\n 2,\n 3,\n Math.floor(totalEntries / 4),\n Math.floor(totalEntries / 2),\n Math.floor((totalEntries * 3) / 4),\n totalEntries - 1,\n ];\n const unique = [...new Set(indices)].filter((i) => i >= 0 && i < totalEntries);\n\n const results: Array<{\n idx: number;\n nodeId: string;\n opType: string;\n tensor: string;\n sum: number;\n first4: number[];\n uniformParams?: number[];\n }> = [];\n for (const i of unique) {\n const entry = entries[i];\n const outputName = entry.node.outputs[0];\n if (!outputName) continue;\n try {\n const data = await this.debugReadBuffer(outputName, 16);\n let sum = 0;\n for (let j = 0; j < data.length; j++) sum += data[j];\n\n // Also read back the uniform buffer to verify params\n let uniformParams: number[] | undefined;\n try {\n const ubSize = entry.uniformBuffer.size;\n const readback = this.ctx.device.createBuffer({\n size: ubSize,\n usage: 0x0001 | 0x0008, // MAP_READ | COPY_DST\n });\n const enc = this.ctx.device.createCommandEncoder();\n enc.copyBufferToBuffer(entry.uniformBuffer, 0, readback, 0, ubSize);\n this.ctx.device.queue.submit([enc.finish()]);\n await readback.mapAsync(MAP_MODE_READ, 0, ubSize);\n const u32 = new Uint32Array(readback.getMappedRange(0, ubSize).slice(0));\n uniformParams = Array.from(u32);\n readback.unmap();\n readback.destroy();\n } catch {\n /* skip if uniform readback fails */\n }\n\n results.push({\n idx: i,\n nodeId: entry.nodeId,\n opType: entry.node.opType,\n tensor: outputName,\n sum: Math.round(sum * 1e6) / 1e6,\n first4: [data[0], data[1], data[2], data[3]].map((v) => Math.round(v * 1e6) / 1e6),\n uniformParams,\n });\n } catch {\n results.push({\n idx: i,\n nodeId: entry.nodeId,\n opType: entry.node.opType,\n tensor: outputName,\n sum: NaN,\n first4: [NaN, NaN, NaN, NaN],\n });\n }\n }\n return results;\n }\n\n debugWriteBuffer(tensorName: string, data: ArrayBufferView): void {\n const buffer = this.getBuffer(tensorName);\n if (!buffer) throw new Error(`No buffer for \"${tensorName}\"`);\n this.ctx.device.queue.writeBuffer(buffer, 0, data as BufferSource);\n }\n\n /**\n * Write a host-supplied activation input buffer (e.g. multimodal M-RoPE\n * cos/sin, spliced vision embeddings, image row-map). The buffer must be a\n * persistent activation tensor in the graph. Call before forward().\n */\n writeInput(tensorName: string, data: ArrayBufferView): void {\n const buffer = this.getBuffer(tensorName);\n if (!buffer) throw new Error(`No input buffer for \"${tensorName}\"`);\n this.ctx.device.queue.writeBuffer(buffer, 0, data as BufferSource);\n }\n\n /** Write a host activation buffer at a byte offset (for per-row decode updates). */\n writeInputAt(tensorName: string, data: ArrayBufferView, byteOffset: number): void {\n const buffer = this.getBuffer(tensorName);\n if (!buffer) throw new Error(`No input buffer for \"${tensorName}\"`);\n this.ctx.device.queue.writeBuffer(buffer, byteOffset, data as BufferSource);\n }\n\n /** True if the graph has a buffer with this name (multimodal-capability probe). */\n hasBuffer(tensorName: string): boolean {\n return this.getBuffer(tensorName) !== undefined;\n }\n\n /** Current sequence position (number of tokens processed since reset). */\n get currentSeqPos(): number {\n return this.seqPos;\n }\n\n async debugReadBuffer(\n tensorName: string,\n maxElements?: number,\n byteOffset?: number,\n ): Promise<Float32Array> {\n const buffer = this.getBuffer(tensorName);\n if (!buffer) throw new Error(`No buffer for \"${tensorName}\"`);\n const srcOffset = byteOffset ?? 0;\n const remaining = buffer.size - srcOffset;\n const byteLen = maxElements ? Math.min(maxElements * 4, remaining) : remaining;\n const readback = this.ctx.device.createBuffer({\n size: byteLen,\n usage: 0x0001 | 0x0008,\n });\n const encoder = this.ctx.device.createCommandEncoder();\n encoder.copyBufferToBuffer(buffer, srcOffset, readback, 0, byteLen);\n this.ctx.device.queue.submit([encoder.finish()]);\n await readback.mapAsync(MAP_MODE_READ, 0, byteLen);\n const data = new Float32Array(readback.getMappedRange(0, byteLen).slice(0));\n readback.unmap();\n readback.destroy();\n return data;\n }\n\n destroy(): void {\n destroyBuffers([...this.weightBuffers.values()]);\n // Pooled activation buffers are shared by multiple tensor names — dedupe.\n destroyBuffers([...new Set(this.activationBuffers.values())]);\n destroyBuffers([...this.ssmStateBuffers.values()]);\n destroyBuffers([...this.kvCacheBuffers.values()]);\n for (const entry of this.dispatchEntries) {\n entry.uniformBuffer.destroy();\n }\n // Only destroy decode-specific uniform buffers (matmul nodes have their own)\n for (let i = 0; i < this.decodeEntries.length; i++) {\n if (this.decodeEntries[i] !== this.dispatchEntries[i]) {\n this.decodeEntries[i].uniformBuffer.destroy();\n }\n }\n if (this.argmaxEntry) {\n this.argmaxEntry.uniformBuffer.destroy();\n }\n if (this.uniformStagingBuffer) {\n this.uniformStagingBuffer.destroy();\n this.uniformStagingBuffer = null;\n }\n this.inputIdsBuffer.destroy();\n this.logitsReadback.destroy();\n this.argmaxResultBuffer.destroy();\n this.argmaxReadback.destroy();\n destroyBuffers(this.decodeReadbacks);\n this.decodeReadbacks = [];\n this.weightBuffers.clear();\n this.activationBuffers.clear();\n this.ssmStateBuffers.clear();\n this.kvCacheBuffers.clear();\n this.dispatchEntries = [];\n this.decodeEntries = [];\n }\n\n // ── Private ──────────────────────────────────────────────────────\n\n /**\n * Allocate activation buffers with liveness-based reuse.\n *\n * One dedicated buffer per activation tensor at full maxSeqLen is ~2.3GB for\n * Qwen3.5-0.8B at T=512 — over the iOS jetsam budget on its own. Instead,\n * a buffer returns to a size-keyed pool once its tensor's last reader has\n * executed, so concurrently-live tensors share a small working set.\n *\n * Graph outputs and tensors read before they are written (cross-forward\n * state) keep dedicated buffers. Within a forward, dispatches execute in\n * executionOrder on every path (single-pass Dawn, per-dispatch WebKit), and\n * WebGPU synchronizes hazards between dispatches, so reuse is safe.\n *\n * Caveat: debugReadBuffer() on an intermediate tensor is only meaningful\n * before a later op reuses its buffer (probes that stop mid-graph are fine).\n */\n private allocateActivationBuffers(): void {\n const isActivation = (name: string) => this.graph.tensors[name]?.storage === \"activation\";\n // Debug: give every activation its own buffer so post-hoc debugReadBuffer of\n // intermediate tensors is reliable (pooling otherwise recycles buffers).\n const noPool = typeof process !== \"undefined\" && process.env?.GERBIL_NO_ACT_POOL === \"1\";\n if (noPool) {\n for (const name of Object.keys(this.graph.tensors)) {\n if (!isActivation(name)) continue;\n const desc = this.graph.tensors[name];\n const shape = desc.shape.map((d) =>\n d === \"T\" || d === \"L_max\" ? this.maxSeqLen : (d as number),\n );\n const bytes = shape.reduce((a, b) => a * b, 1) * DTYPE_BYTES[desc.dtype];\n this.activationBuffers.set(\n name,\n createStorageBuffer(this.ctx, `act_nopool_${name}`, bytes),\n );\n }\n console.log(\n `[executor] activations: NO-POOL debug mode → ${this.activationBuffers.size} dedicated buffers`,\n );\n return;\n }\n const resolveBytes = (name: string): number => {\n const desc = this.graph.tensors[name];\n const shape = desc.shape.map((d) => {\n if (d === \"T\") return this.maxSeqLen;\n if (d === \"L_max\") return this.maxSeqLen;\n return d as number;\n });\n return shape.reduce((a, b) => a * b, 1) * DTYPE_BYTES[desc.dtype];\n };\n\n const nodeById = new Map(this.graph.nodes.map((n) => [n.id, n]));\n const order = this.graph.executionOrder;\n const persistent = new Set<string>(this.graph.outputs);\n const firstDef = new Map<string, number>();\n const lastUse = new Map<string, number>();\n for (let i = 0; i < order.length; i++) {\n const node = nodeById.get(order[i])!;\n for (const inp of node.inputs) {\n if (!isActivation(inp)) continue;\n // Read before any write → carries state across forwards (or in-place op);\n // never pool it.\n if (!firstDef.has(inp)) persistent.add(inp);\n lastUse.set(inp, i);\n }\n for (const out of node.outputs) {\n if (isActivation(out) && !firstDef.has(out)) firstDef.set(out, i);\n }\n }\n\n const freePool = new Map<number, GPUBuffer[]>();\n let created = 0;\n const released = new Set<string>();\n for (let i = 0; i < order.length; i++) {\n const node = nodeById.get(order[i])!;\n // Acquire outputs first so a node never writes the buffer of a tensor\n // it also reads.\n for (const out of node.outputs) {\n if (!isActivation(out) || persistent.has(out) || this.activationBuffers.has(out)) {\n continue;\n }\n const byteSize = resolveBytes(out);\n const pool = freePool.get(byteSize);\n let buffer = pool?.pop();\n if (!buffer) {\n buffer = createStorageBuffer(this.ctx, `act_pool_${created}_${byteSize}b`, byteSize);\n created++;\n }\n this.activationBuffers.set(out, buffer);\n }\n // Release buffers whose tensor will never be read again.\n for (const name of [...node.inputs, ...node.outputs]) {\n if (!isActivation(name) || persistent.has(name) || released.has(name)) continue;\n const last = lastUse.get(name) ?? firstDef.get(name) ?? i;\n if (last > i) continue;\n const buffer = this.activationBuffers.get(name);\n if (!buffer) continue;\n released.add(name);\n const byteSize = resolveBytes(name);\n const pool = freePool.get(byteSize) ?? [];\n pool.push(buffer);\n freePool.set(byteSize, pool);\n }\n }\n\n // Dedicated buffers for graph outputs, cross-forward state, and any\n // activation tensor not touched by the execution order.\n for (const name of Object.keys(this.graph.tensors)) {\n if (!isActivation(name) || this.activationBuffers.has(name)) continue;\n this.activationBuffers.set(\n name,\n createStorageBuffer(this.ctx, `act_${name}`, resolveBytes(name)),\n );\n }\n\n const unique = new Set(this.activationBuffers.values());\n let totalBytes = 0;\n for (const b of unique) totalBytes += b.size;\n console.log(\n `[executor] activations: ${this.activationBuffers.size} tensors → ${unique.size} buffers (${(totalBytes / 1e6).toFixed(0)} MB)`,\n );\n }\n\n private allocateSSMStateBuffers(): void {\n for (const [name, desc] of Object.entries(this.graph.tensors)) {\n if (desc.storage !== \"ssm_state\") continue;\n const shape = desc.shape as number[];\n const byteSize = shape.reduce((a, b) => a * b, 1) * DTYPE_BYTES[desc.dtype];\n const buffer = createStorageBuffer(this.ctx, `ssm_${name}`, byteSize);\n this.ssmStateBuffers.set(name, buffer);\n }\n }\n\n private allocateKVCacheBuffers(): void {\n for (const [name, desc] of Object.entries(this.graph.tensors)) {\n if (desc.storage !== \"kv_cache\") continue;\n const shape = desc.shape.map((d) => {\n if (d === \"L_max\") return this.maxSeqLen;\n return d as number;\n });\n const byteSize = shape.reduce((a, b) => a * b, 1) * DTYPE_BYTES[desc.dtype];\n const buffer = createStorageBuffer(this.ctx, `kv_${name}`, byteSize);\n this.kvCacheBuffers.set(name, buffer);\n }\n }\n\n private resolveShapes(T: number): Record<string, number[]> {\n const resolved: Record<string, number[]> = {};\n for (const [name, desc] of Object.entries(this.graph.tensors)) {\n resolved[name] = desc.shape.map((d) => {\n if (d === \"T\") return T;\n if (d === \"L_max\") return this.seqPos + T;\n return d as number;\n });\n }\n return resolved;\n }\n\n private getBuffer(tensorName: string): GPUBuffer | undefined {\n return (\n this.weightBuffers.get(tensorName) ??\n this.activationBuffers.get(tensorName) ??\n this.ssmStateBuffers.get(tensorName) ??\n this.kvCacheBuffers.get(tensorName)\n );\n }\n\n /**\n * Detect gate_proj + up_proj + SwiGLU patterns in decode entries and replace\n * with a single fused SwiGLUMatVec dispatch. Saves 2 dispatches per MLP block.\n */\n private fuseSwiGLUDecodeEntries(): void {\n let fusionCount = 0;\n const maxBindings = this.ctx.limits.maxStorageBuffersPerShaderStage;\n\n for (let i = 0; i < this.decodeEntries.length - 2; i++) {\n const e1 = this.decodeEntries[i]; // gate_proj MatVec\n const e2 = this.decodeEntries[i + 1]; // up_proj MatVec\n const e3 = this.decodeEntries[i + 2]; // SwiGLU\n\n // Check op types: two MatMul/MatMulInt4 followed by SwiGLU\n const isE1Mat = e1.node.opType === \"MatMul\" || e1.node.opType === \"MatMulInt4\";\n const isE2Mat = e2.node.opType === \"MatMul\" || e2.node.opType === \"MatMulInt4\";\n if (!isE1Mat || !isE2Mat || e3.node.opType !== \"SwiGLU\") continue;\n\n // Both projections must use the same op type (both F32 or both INT4)\n if (e1.node.opType !== e2.node.opType) continue;\n\n // INT4 fused kernel needs 9 storage bindings — skip if device doesn't support it\n const isInt4Fusion = e1.node.opType === \"MatMulInt4\";\n if (isInt4Fusion && maxBindings < 9) continue;\n\n // Both must share the same input activation (first input tensor)\n if (e1.node.inputs[0] !== e2.node.inputs[0]) continue;\n\n // SwiGLU must consume the outputs of both projections\n const gateOut = e1.node.outputs[0];\n const upOut = e2.node.outputs[0];\n if (e3.node.inputs[0] !== gateOut || e3.node.inputs[1] !== upOut) continue;\n\n // Same K and N dimensions\n if (e1.node.attributes.K !== e2.node.attributes.K) continue;\n if (e1.node.attributes.N !== e2.node.attributes.N) continue;\n\n // Pooled activation aliasing: the fused kernel reads the shared input\n // and writes the SwiGLU output in ONE dispatch — they must not share\n // a pooled buffer (separate dispatches are hazard-synchronized; a\n // single dispatch is not).\n if (this.getBuffer(e1.node.inputs[0]) === this.getBuffer(e3.node.outputs[0])) continue;\n\n // Pattern matched — create fused dispatch entry\n const fusedSpec = isInt4Fusion ? SWIGLU_MATVEC_INT4_SPEC : SWIGLU_MATVEC_SPEC;\n const fusedPipeline = getOrCreatePipeline(\n this.ctx,\n `swiglu_mv_${e1.nodeId}`,\n fusedSpec.shaderCode,\n fusedSpec.entryPoint,\n );\n\n const fusedUniformData = fusedSpec.buildParams(e1.node, {}, { seqPos: 0 });\n const fusedUniform = createUniformBuffer(\n this.ctx,\n `uniform_swiglu_mv_${e1.nodeId}`,\n fusedUniformData,\n );\n\n // Build bind group manually: input, gate weights, up weights, output, uniform\n const inputBuf = this.getBuffer(e1.node.inputs[0])!;\n const outputBuf = this.getBuffer(e3.node.outputs[0])!;\n\n let bufferEntries: Array<{ buffer: GPUBuffer }>;\n if (isInt4Fusion) {\n // INT4: A, gate_q, gate_s, gate_z, up_q, up_s, up_z, output, params\n const gateQ = this.getBuffer(e1.node.inputs[1])!;\n const gateS = this.getBuffer(e1.node.inputs[2])!;\n const gateZ = this.getBuffer(e1.node.inputs[3])!;\n const upQ = this.getBuffer(e2.node.inputs[1])!;\n const upS = this.getBuffer(e2.node.inputs[2])!;\n const upZ = this.getBuffer(e2.node.inputs[3])!;\n bufferEntries = [\n { buffer: inputBuf },\n { buffer: gateQ },\n { buffer: gateS },\n { buffer: gateZ },\n { buffer: upQ },\n { buffer: upS },\n { buffer: upZ },\n { buffer: outputBuf },\n { buffer: fusedUniform },\n ];\n } else {\n // F32: A, B_gate, B_up, output, params\n const gateBuf = this.getBuffer(e1.node.inputs[1])!;\n const upBuf = this.getBuffer(e2.node.inputs[1])!;\n bufferEntries = [\n { buffer: inputBuf },\n { buffer: gateBuf },\n { buffer: upBuf },\n { buffer: outputBuf },\n { buffer: fusedUniform },\n ];\n }\n\n const fusedBindGroup = createBindGroup(\n this.ctx,\n fusedPipeline,\n bufferEntries,\n `bg_swiglu_mv_${e1.nodeId}`,\n );\n\n const fusedEntry: DispatchEntry = {\n nodeId: `fused_swiglu_${e1.nodeId}`,\n node: e1.node, // Use gate node for K/N attributes\n spec: fusedSpec,\n pipeline: fusedPipeline,\n bindGroup: fusedBindGroup,\n uniformBuffer: fusedUniform,\n lastParamsBytes: null,\n lastDispatchSize: null,\n };\n\n // Replace 3 entries with 1 fused entry\n this.decodeEntries.splice(i, 3, fusedEntry);\n fusionCount++;\n // Don't increment i — check if next position is also fusable\n }\n\n if (fusionCount > 0) {\n console.log(\n `[executor] Fused ${fusionCount} SwiGLU MLP blocks (saved ${fusionCount * 2} dispatches)`,\n );\n }\n }\n\n /**\n * Fuse two adjacent INT4 projections that share the same input activation and\n * the same K/N (e.g. q_proj+gate_proj and k_proj+v_proj in full-attention\n * decode) into a single DualMatVecInt4 dispatch. Reads the shared input vector\n * once and writes both projection outputs — removing one GPU round-trip (one\n * submit+drain on Safari/iOS) per fused pair.\n *\n * Numerics are identical to running the two MatVecInt4 kernels separately\n * (same dequant, same K-parallel reduction), so this is WebKit-safe: it reuses\n * the proven INT4 matvec math and only merges two writes into one dispatch.\n *\n * Must run AFTER fuseSwiGLUDecodeEntries so the MLP gate+up pair (which is\n * consumed by a SwiGLU node) is already collapsed and won't be matched here.\n */\n private fuseDualMatVecDecodeEntries(): void {\n let fusionCount = 0;\n // DualMatVecInt4 needs 10 storage/uniform bindings (9 storage + 1 uniform).\n const maxBindings = this.ctx.limits.maxStorageBuffersPerShaderStage;\n if (maxBindings < 9) return;\n\n for (let i = 0; i < this.decodeEntries.length - 1; i++) {\n const e0 = this.decodeEntries[i];\n const e1 = this.decodeEntries[i + 1];\n\n // Both must be INT4 matmuls.\n if (e0.node.opType !== \"MatMulInt4\" || e1.node.opType !== \"MatMulInt4\") continue;\n // Same input activation (first input tensor).\n if (e0.node.inputs[0] !== e1.node.inputs[0]) continue;\n // Same K, N, group_size.\n if (e0.node.attributes.K !== e1.node.attributes.K) continue;\n if (e0.node.attributes.N !== e1.node.attributes.N) continue;\n if (e0.node.attributes.group_size !== e1.node.attributes.group_size) continue;\n\n const out0 = e0.node.outputs[0];\n const out1 = e1.node.outputs[0];\n const inputBuf = this.getBuffer(e0.node.inputs[0]);\n const out0Buf = this.getBuffer(out0);\n const out1Buf = this.getBuffer(out1);\n if (!inputBuf || !out0Buf || !out1Buf) continue;\n\n // Pooled-buffer aliasing guard: the single fused dispatch reads the shared\n // input and writes both outputs. None of these may share a pooled buffer\n // (separate dispatches are hazard-synchronized; one dispatch is not), and\n // the two outputs must be distinct buffers.\n if (inputBuf === out0Buf || inputBuf === out1Buf || out0Buf === out1Buf) continue;\n\n const fusedSpec = DUAL_MATVEC_INT4_SPEC;\n const fusedPipeline = getOrCreatePipeline(\n this.ctx,\n `dual_mv_${e0.nodeId}`,\n fusedSpec.shaderCode,\n fusedSpec.entryPoint,\n );\n\n const fusedUniformData = fusedSpec.buildParams(e0.node, {}, { seqPos: 0 });\n const fusedUniform = createUniformBuffer(\n this.ctx,\n `uniform_dual_mv_${e0.nodeId}`,\n fusedUniformData,\n );\n\n // Bind group: A, B0_q, B0_s, B0_z, B1_q, B1_s, B1_z, out0, out1, params\n const b0q = this.getBuffer(e0.node.inputs[1])!;\n const b0s = this.getBuffer(e0.node.inputs[2])!;\n const b0z = this.getBuffer(e0.node.inputs[3])!;\n const b1q = this.getBuffer(e1.node.inputs[1])!;\n const b1s = this.getBuffer(e1.node.inputs[2])!;\n const b1z = this.getBuffer(e1.node.inputs[3])!;\n\n const bufferEntries = [\n { buffer: inputBuf },\n { buffer: b0q },\n { buffer: b0s },\n { buffer: b0z },\n { buffer: b1q },\n { buffer: b1s },\n { buffer: b1z },\n { buffer: out0Buf },\n { buffer: out1Buf },\n { buffer: fusedUniform },\n ];\n\n const fusedBindGroup = createBindGroup(\n this.ctx,\n fusedPipeline,\n bufferEntries,\n `bg_dual_mv_${e0.nodeId}`,\n );\n\n const fusedEntry: DispatchEntry = {\n nodeId: `fused_dual_${e0.nodeId}`,\n node: e0.node, // K/N/group_size attributes shared\n spec: fusedSpec,\n pipeline: fusedPipeline,\n bindGroup: fusedBindGroup,\n uniformBuffer: fusedUniform,\n lastParamsBytes: null,\n lastDispatchSize: null,\n };\n\n this.decodeEntries.splice(i, 2, fusedEntry);\n fusionCount++;\n // Don't increment i — the next pair may also be fusable.\n }\n\n if (fusionCount > 0) {\n console.log(\n `[executor] Fused ${fusionCount} dual INT4 projections (saved ${fusionCount} dispatches)`,\n );\n }\n }\n\n /**\n * Fuse the adjacent K-cache and V-cache appends in each full-attention layer\n * into a single DualKVCacheAppend dispatch. Both are pure memcpys into f32\n * caches sharing the same width and dst_offset, so one dispatch with two\n * src/dst buffers writes both — removing one GPU round-trip per layer.\n *\n * Supports f32, native-f16, and packed-f16 caches (the dual kernel mirrors the\n * single-append kernel selected for the active kvMode). Numerically identical\n * to the separate appends — WebKit-safe (the packed-f16 variant is the Safari\n * path and uses pack2x16float, no `enable f16`).\n */\n private fuseDualKVCacheAppendEntries(): void {\n let fusionCount = 0;\n\n for (let i = 0; i < this.decodeEntries.length - 1; i++) {\n const e0 = this.decodeEntries[i]; // K append\n const e1 = this.decodeEntries[i + 1]; // V append\n\n if (e0.node.opType !== \"KVCacheAppend\" || e1.node.opType !== \"KVCacheAppend\") continue;\n // Same per-element width (kv_dim) → one param set drives both.\n if (e0.node.attributes.width !== e1.node.attributes.width) continue;\n\n // Select the dual kernel matching the cache dtype/mode. Both destinations\n // must share the same dtype (they always do: K and V caches are symmetric).\n const dstK = e0.node.outputs[0];\n const dstV = e1.node.outputs[0];\n const dtK = this.graph.tensors[dstK]?.dtype;\n const dtV = this.graph.tensors[dstV]?.dtype;\n if (dtK !== dtV) continue;\n let fusedSpec: KernelSpec;\n if (dtK === \"f32\") {\n fusedSpec = DUAL_KV_CACHE_APPEND_SPEC;\n } else if (dtK === \"f16\") {\n fusedSpec =\n this.kvMode === \"packed-f16\"\n ? DUAL_KV_CACHE_APPEND_PACKED_F16_SPEC\n : DUAL_KV_CACHE_APPEND_F16_SPEC;\n } else {\n continue;\n }\n\n const srcK = this.getBuffer(e0.node.inputs[0]);\n const srcV = this.getBuffer(e1.node.inputs[0]);\n const dstKBuf = this.getBuffer(dstK);\n const dstVBuf = this.getBuffer(dstV);\n if (!srcK || !srcV || !dstKBuf || !dstVBuf) continue;\n // Distinct caches; sources distinct from caches.\n if (dstKBuf === dstVBuf) continue;\n\n const fusedPipeline = getOrCreatePipeline(\n this.ctx,\n `dual_kv_${e0.nodeId}`,\n fusedSpec.shaderCode,\n fusedSpec.entryPoint,\n );\n\n const dummyShapes = this.resolveShapes(1);\n const fusedUniformData = fusedSpec.buildParams(e0.node, dummyShapes, { seqPos: 0 });\n const fusedUniform = createUniformBuffer(\n this.ctx,\n `uniform_dual_kv_${e0.nodeId}`,\n fusedUniformData,\n );\n\n const bufferEntries = [\n { buffer: srcK },\n { buffer: srcV },\n { buffer: dstKBuf },\n { buffer: dstVBuf },\n { buffer: fusedUniform },\n ];\n\n const fusedBindGroup = createBindGroup(\n this.ctx,\n fusedPipeline,\n bufferEntries,\n `bg_dual_kv_${e0.nodeId}`,\n );\n\n const fusedEntry: DispatchEntry = {\n nodeId: `fused_kv_${e0.nodeId}`,\n node: e0.node, // width + T_tensor for per-token param rebuild\n spec: fusedSpec,\n pipeline: fusedPipeline,\n bindGroup: fusedBindGroup,\n uniformBuffer: fusedUniform,\n lastParamsBytes: null,\n lastDispatchSize: null,\n };\n\n this.decodeEntries.splice(i, 2, fusedEntry);\n fusionCount++;\n }\n\n if (fusionCount > 0) {\n console.log(\n `[executor] Fused ${fusionCount} dual KV-cache appends (saved ${fusionCount} dispatches)`,\n );\n }\n }\n\n /**\n * Fuse the attention SigmoidGate (attn_out * sigmoid(gate)) into the INT4\n * o_proj that consumes it: a GatedMatVecInt4 reads attn_out and gate directly,\n * applies the sigmoid gate to its input vector, and runs the projection in ONE\n * dispatch — removing the standalone SigmoidGate (one round-trip per\n * full-attention layer).\n *\n * Numerically identical to SigmoidGate→MatVecInt4 (same gate formula, same INT4\n * dequant + reduction). Slight extra ALU: the gated input is recomputed per\n * output column, but A reads hit L1 and the saved submit+drain dominates on\n * mobile. WebKit risk: low — reuses the proven INT4 matvec, only the A vector\n * is built from two reads + a sigmoid (no new reduction/barrier pattern).\n *\n * Runs after the dual fusions so it only sees the post-attention SigmoidGate.\n */\n private fuseGatedOProjDecodeEntries(): void {\n let fusionCount = 0;\n\n for (let i = 0; i < this.decodeEntries.length - 1; i++) {\n const eg = this.decodeEntries[i]; // SigmoidGate\n const eo = this.decodeEntries[i + 1]; // o_proj MatMulInt4\n\n if (eg.node.opType !== \"SigmoidGate\") continue;\n if (eo.node.opType !== \"MatMulInt4\") continue;\n\n // o_proj must consume the gate's output as its activation input.\n const gatedOut = eg.node.outputs[0];\n if (eo.node.inputs[0] !== gatedOut) continue;\n\n // SigmoidGate inputs: [x (attn_out), gate]\n const attnName = eg.node.inputs[0];\n const gateName = eg.node.inputs[1];\n const attnBuf = this.getBuffer(attnName);\n const gateBuf = this.getBuffer(gateName);\n const wQ = this.getBuffer(eo.node.inputs[1]);\n const wS = this.getBuffer(eo.node.inputs[2]);\n const wZ = this.getBuffer(eo.node.inputs[3]);\n const outBuf = this.getBuffer(eo.node.outputs[0]);\n if (!attnBuf || !gateBuf || !wQ || !wS || !wZ || !outBuf) continue;\n\n // Pooled aliasing: the single fused dispatch reads attn + gate and writes\n // the projection output — none may share a pooled buffer.\n if (outBuf === attnBuf || outBuf === gateBuf || attnBuf === gateBuf) continue;\n\n const fusedSpec = GATED_MATVEC_INT4_SPEC;\n const fusedPipeline = getOrCreatePipeline(\n this.ctx,\n `gated_o_${eo.nodeId}`,\n fusedSpec.shaderCode,\n fusedSpec.entryPoint,\n );\n\n const fusedUniformData = fusedSpec.buildParams(eo.node, {}, { seqPos: 0 });\n const fusedUniform = createUniformBuffer(\n this.ctx,\n `uniform_gated_o_${eo.nodeId}`,\n fusedUniformData,\n );\n\n const bufferEntries = [\n { buffer: attnBuf },\n { buffer: gateBuf },\n { buffer: wQ },\n { buffer: wS },\n { buffer: wZ },\n { buffer: outBuf },\n { buffer: fusedUniform },\n ];\n\n const fusedBindGroup = createBindGroup(\n this.ctx,\n fusedPipeline,\n bufferEntries,\n `bg_gated_o_${eo.nodeId}`,\n );\n\n const fusedEntry: DispatchEntry = {\n nodeId: `fused_gated_o_${eo.nodeId}`,\n node: eo.node, // K/N/group_size attributes for per-token param rebuild\n spec: fusedSpec,\n pipeline: fusedPipeline,\n bindGroup: fusedBindGroup,\n uniformBuffer: fusedUniform,\n lastParamsBytes: null,\n lastDispatchSize: null,\n };\n\n this.decodeEntries.splice(i, 2, fusedEntry);\n fusionCount++;\n }\n\n if (fusionCount > 0) {\n console.log(\n `[executor] Fused ${fusionCount} gated o_proj blocks (saved ${fusionCount} dispatches)`,\n );\n }\n }\n\n /**\n * Fuse a standalone SwiGLU (silu(gate) * up) into the INT4 projection that\n * consumes its output: a SwiGLUGatedMatVecInt4 reads gate and up directly,\n * builds the gated input vector, and runs the projection in ONE dispatch.\n * Targets the Mamba block's mamba_swiglu (silu(z) * norm_out) feeding out_proj\n * — one round-trip saved per linear-attention layer.\n *\n * The MLP SwiGLU is already collapsed into a SwiGLUMatVec entry by\n * fuseSwiGLUDecodeEntries (it has no surviving standalone SwiGLU node), so only\n * the Mamba SwiGLU matches here. Numerically identical to SwiGLU→MatVecInt4;\n * WebKit risk low (reuses the proven INT4 matvec, only the A vector changes).\n */\n private fuseSwiGLUGatedProjDecodeEntries(): void {\n let fusionCount = 0;\n\n for (let i = 0; i < this.decodeEntries.length - 1; i++) {\n const es = this.decodeEntries[i]; // SwiGLU\n const ep = this.decodeEntries[i + 1]; // projection MatMulInt4\n\n if (es.node.opType !== \"SwiGLU\") continue;\n if (ep.node.opType !== \"MatMulInt4\") continue;\n\n const swigluOut = es.node.outputs[0];\n if (ep.node.inputs[0] !== swigluOut) continue;\n\n // SwiGLU inputs: [gate, up]\n const gateName = es.node.inputs[0];\n const upName = es.node.inputs[1];\n const gateBuf = this.getBuffer(gateName);\n const upBuf = this.getBuffer(upName);\n const wQ = this.getBuffer(ep.node.inputs[1]);\n const wS = this.getBuffer(ep.node.inputs[2]);\n const wZ = this.getBuffer(ep.node.inputs[3]);\n const outBuf = this.getBuffer(ep.node.outputs[0]);\n if (!gateBuf || !upBuf || !wQ || !wS || !wZ || !outBuf) continue;\n\n // Pooled aliasing: the single fused dispatch reads gate + up and writes the\n // projection output — none may share a pooled buffer.\n if (outBuf === gateBuf || outBuf === upBuf || gateBuf === upBuf) continue;\n\n const fusedSpec = SWIGLU_GATED_MATVEC_INT4_SPEC;\n const fusedPipeline = getOrCreatePipeline(\n this.ctx,\n `swiglu_gated_${ep.nodeId}`,\n fusedSpec.shaderCode,\n fusedSpec.entryPoint,\n );\n\n const fusedUniformData = fusedSpec.buildParams(ep.node, {}, { seqPos: 0 });\n const fusedUniform = createUniformBuffer(\n this.ctx,\n `uniform_swiglu_gated_${ep.nodeId}`,\n fusedUniformData,\n );\n\n const bufferEntries = [\n { buffer: gateBuf },\n { buffer: upBuf },\n { buffer: wQ },\n { buffer: wS },\n { buffer: wZ },\n { buffer: outBuf },\n { buffer: fusedUniform },\n ];\n\n const fusedBindGroup = createBindGroup(\n this.ctx,\n fusedPipeline,\n bufferEntries,\n `bg_swiglu_gated_${ep.nodeId}`,\n );\n\n const fusedEntry: DispatchEntry = {\n nodeId: `fused_swiglu_gated_${ep.nodeId}`,\n node: ep.node,\n spec: fusedSpec,\n pipeline: fusedPipeline,\n bindGroup: fusedBindGroup,\n uniformBuffer: fusedUniform,\n lastParamsBytes: null,\n lastDispatchSize: null,\n };\n\n this.decodeEntries.splice(i, 2, fusedEntry);\n fusionCount++;\n }\n\n if (fusionCount > 0) {\n console.log(\n `[executor] Fused ${fusionCount} SwiGLU-gated projections (saved ${fusionCount} dispatches)`,\n );\n }\n }\n\n /**\n * Fuse two adjacent per-row RMSNorms sharing hidden_size + eps into a single\n * DualRMSNorm dispatch (e.g. the per-head q_norm and k_norm in full-attention\n * decode). One workgroup still handles one row; the fused grid just spans both\n * inputs' rows, so each row's reduction is unchanged — numerically identical to\n * two separate RMSNorm dispatches. One round-trip saved per fused pair.\n *\n * WebKit risk: low — same single-workgroup reduction as the proven RMSNorm\n * kernel, only the row→input routing is added.\n */\n private fuseDualRMSNormDecodeEntries(): void {\n let fusionCount = 0;\n\n for (let i = 0; i < this.decodeEntries.length - 1; i++) {\n const e0 = this.decodeEntries[i];\n const e1 = this.decodeEntries[i + 1];\n\n if (e0.node.opType !== \"RMSNorm\" || e1.node.opType !== \"RMSNorm\") continue;\n // Must share per-row width and eps (the dual kernel uses one of each).\n if (e0.node.attributes.hidden_size !== e1.node.attributes.hidden_size) continue;\n if (e0.node.attributes.eps !== e1.node.attributes.eps) continue;\n\n const in0 = e0.node.inputs[0];\n const w0 = e0.node.inputs[1];\n const out0 = e0.node.outputs[0];\n const in1 = e1.node.inputs[0];\n const w1 = e1.node.inputs[1];\n const out1 = e1.node.outputs[0];\n\n const in0Buf = this.getBuffer(in0);\n const w0Buf = this.getBuffer(w0);\n const out0Buf = this.getBuffer(out0);\n const in1Buf = this.getBuffer(in1);\n const w1Buf = this.getBuffer(w1);\n const out1Buf = this.getBuffer(out1);\n if (!in0Buf || !w0Buf || !out0Buf || !in1Buf || !w1Buf || !out1Buf) continue;\n\n // The two normalizations must write distinct buffers. (In-place RMSNorm\n // where input===output is fine: each row reads then writes its own row,\n // and the fused kernel preserves per-row ordering.) Cross-aliasing — where\n // one norm's input shares a pooled buffer with the OTHER norm's output —\n // would create an unsynchronized read/write hazard within the single\n // dispatch, so reject it.\n if (out0Buf === out1Buf) continue;\n if (in0Buf === out1Buf || in1Buf === out0Buf) continue;\n\n // Build a synthetic node carrying both row sources so the spec can resolve\n // per-token row counts each decode step.\n const fusedNode: OpNode = {\n id: `fused_dualnorm_${e0.nodeId}`,\n opType: \"RMSNorm\",\n inputs: [in0, w0, in1, w1],\n outputs: [out0, out1],\n attributes: {\n hidden_size: e0.node.attributes.hidden_size,\n eps: e0.node.attributes.eps,\n seq_len_tensor0: e0.node.attributes.seq_len_tensor,\n seq_len_tensor1: e1.node.attributes.seq_len_tensor,\n seq_len0: e0.node.attributes.seq_len,\n seq_len1: e1.node.attributes.seq_len,\n },\n };\n\n const fusedSpec = DUAL_RMSNORM_SPEC;\n const fusedPipeline = getOrCreatePipeline(\n this.ctx,\n `dual_norm_${e0.nodeId}`,\n fusedSpec.shaderCode,\n fusedSpec.entryPoint,\n );\n\n const dummyShapes = this.resolveShapes(1);\n const fusedUniformData = fusedSpec.buildParams(fusedNode, dummyShapes, { seqPos: 0 });\n const fusedUniform = createUniformBuffer(\n this.ctx,\n `uniform_dual_norm_${e0.nodeId}`,\n fusedUniformData,\n );\n\n const bufferEntries = [\n { buffer: in0Buf },\n { buffer: w0Buf },\n { buffer: in1Buf },\n { buffer: w1Buf },\n { buffer: out0Buf },\n { buffer: out1Buf },\n { buffer: fusedUniform },\n ];\n\n const fusedBindGroup = createBindGroup(\n this.ctx,\n fusedPipeline,\n bufferEntries,\n `bg_dual_norm_${e0.nodeId}`,\n );\n\n const fusedEntry: DispatchEntry = {\n nodeId: fusedNode.id,\n node: fusedNode,\n spec: fusedSpec,\n pipeline: fusedPipeline,\n bindGroup: fusedBindGroup,\n uniformBuffer: fusedUniform,\n lastParamsBytes: null,\n lastDispatchSize: null,\n };\n\n this.decodeEntries.splice(i, 2, fusedEntry);\n fusionCount++;\n }\n\n if (fusionCount > 0) {\n console.log(\n `[executor] Fused ${fusionCount} dual RMSNorms (saved ${fusionCount} dispatches)`,\n );\n }\n }\n\n /**\n * Gather buffer entries for a bind group, matching the kernel spec's binding layout.\n * Uses the pre-allocated inputIdsBuffer for the \"input_ids\" tensor.\n */\n private gatherBuffers(\n spec: KernelSpec,\n node: OpNode,\n uniformBuffer: GPUBuffer,\n ): Array<{ buffer: GPUBuffer }> {\n const entries: Array<{ buffer: GPUBuffer }> = [];\n const allTensorNames = [...node.inputs, ...node.outputs];\n\n // Track buffers already bound to a writable slot in this group so we can\n // detect (and break) read_write aliasing, which WebGPU rejects.\n const writableBound = new Set<GPUBuffer>();\n\n for (let i = 0; i < spec.bindings.length; i++) {\n const binding = spec.bindings[i];\n\n if (binding.type === \"uniform\") {\n entries.push({ buffer: uniformBuffer });\n } else {\n const tensorIdx = i - spec.bindings.filter((b, j) => j < i && b.type === \"uniform\").length;\n const tensorName = allTensorNames[tensorIdx];\n if (!tensorName) throw new Error(`Missing tensor for binding ${i} in op ${node.id}`);\n\n let buffer: GPUBuffer | undefined;\n if (tensorName === \"input_ids\") {\n buffer = this.inputIdsBuffer;\n } else {\n buffer = this.getBuffer(tensorName);\n }\n if (!buffer) throw new Error(`No GPU buffer for tensor \"${tensorName}\" in op ${node.id}`);\n\n // Break read_write aliasing: if a writable binding would reuse a buffer\n // already bound writable in this group (e.g. RoPE-Q-only nodes that list\n // the same tensor as input+output and leave the k slot unused), bind a\n // throwaway scratch buffer instead. Safe because the aliasing slot is not\n // actually accessed at dispatch time (num_kv_heads=0 → no k work).\n if (binding.type === \"storage-read-write\") {\n if (writableBound.has(buffer)) {\n buffer = this.getBindingScratchBuffer(buffer.size);\n } else {\n writableBound.add(buffer);\n }\n }\n\n entries.push({ buffer });\n }\n }\n\n return entries;\n }\n\n /** Lazily allocate a scratch storage buffer at least `minBytes` large. */\n private getBindingScratchBuffer(minBytes: number): GPUBuffer {\n if (!this.bindingScratchBuffer || this.bindingScratchBuffer.size < minBytes) {\n this.bindingScratchBuffer = createStorageBuffer(\n this.ctx,\n \"binding_scratch\",\n Math.max(minBytes, 256),\n );\n }\n return this.bindingScratchBuffer;\n }\n}\n","/**\n * GPTQ → Gerbil INT4 format adapter.\n *\n * Repacks GPTQ-quantized weights (qweight/qzeros/scales) into the flat\n * row-major packing expected by Gerbil's MatMulInt4 WGSL kernel.\n *\n * GPTQ layout (AutoGPTQ, per linear layer):\n * qweight: Int32Array, shape [K/8, N] — 8 nibbles per int32 along INPUT dim (K)\n * scales: Float32Array, shape [K/group_size, N] — per-group per-output-column\n * qzeros: Int32Array, shape [K/group_size, N/8] — packed 4-bit zero points along N\n *\n * Gerbil layout (per linear layer):\n * packed: Uint32Array, flat [N*K/8] — 8 nibbles per u32 in row-major (N,K) order\n * scales: Float32Array, flat [N * K/group_size] — sequential in (N,K) row-major\n * zeros: Float32Array, flat [N * K/group_size]\n *\n * The kernel indexes: flat_idx = col * K + k, group = flat_idx / group_size\n */\n\nexport interface GPTQTensors {\n qweight: Int32Array; // [K/8, N]\n scales: Float32Array; // [K/group_size, N]\n qzeros: Int32Array; // [K/group_size, N/8]\n K: number;\n N: number;\n groupSize: number;\n}\n\nexport interface GerbilINT4Tensors {\n packed: Uint32Array;\n scales: Float32Array;\n zeros: Float32Array;\n}\n\n/**\n * Repack GPTQ tensors into Gerbil's flat INT4 format.\n *\n * This is pure data reshuffling — no quantization math. The 4-bit values,\n * scales, and zero points are preserved exactly.\n */\nexport function repackGPTQ(gptq: GPTQTensors): GerbilINT4Tensors {\n const { qweight, scales: gptqScales, qzeros, K, N, groupSize } = gptq;\n const totalElements = N * K;\n const groupsPerCol = Math.ceil(K / groupSize);\n const totalGroups = N * groupsPerCol;\n\n const packed = new Uint32Array(Math.ceil(totalElements / 8));\n const scales = new Float32Array(totalGroups);\n const zeros = new Float32Array(totalGroups);\n\n // Repack nibbles: GPTQ [K/8, N] → Gerbil flat [N*K/8]\n // GPTQ: qweight[(k >> 3) * N + col], nibble at (k & 7) * 4\n // Gerbil: packed[(col*K+k)/8], nibble at ((col*K+k) % 8) * 4\n for (let k = 0; k < K; k++) {\n const gptqRow = k >>> 3;\n const gptqNibblePos = k & 7;\n const gptqRowBase = gptqRow * N;\n for (let col = 0; col < N; col++) {\n // Extract nibble from GPTQ\n const gptqWord = qweight[gptqRowBase + col];\n const nibble = (gptqWord >>> (gptqNibblePos * 4)) & 0xf;\n\n // Write nibble into Gerbil flat layout\n const flatIdx = col * K + k;\n const packedIdx = flatIdx >>> 3;\n const nibblePos = flatIdx & 7;\n packed[packedIdx] |= nibble << (nibblePos * 4);\n }\n }\n\n // Repack scales: GPTQ [groups_per_col, N] → Gerbil flat [N * groups_per_col]\n // GPTQ: scales[g * N + col]\n // Gerbil: scales[col * groups_per_col + g]\n for (let g = 0; g < groupsPerCol; g++) {\n const srcRow = g * N;\n for (let col = 0; col < N; col++) {\n scales[col * groupsPerCol + g] = gptqScales[srcRow + col];\n }\n }\n\n // Repack zeros: GPTQ [groups_per_col, N/8] packed int4 → Gerbil flat float32\n // GPTQ: qzeros[g * (N/8) + (col >> 3)], nibble at (col & 7) * 4\n // Gerbil: zeros[col * groups_per_col + g] as float32\n //\n // GPTQ convention: stored qzeros = actual_zero_point - 1.\n // All standard GPTQ kernels (exllama, marlin) add +1 during dequant.\n // We bake the +1 into the stored value so the kernel formula stays simple:\n // dequant = (nibble - zero) * scale\n const zerosNDiv8 = N >>> 3;\n for (let g = 0; g < groupsPerCol; g++) {\n const srcRow = g * zerosNDiv8;\n for (let col = 0; col < N; col++) {\n const zWord = qzeros[srcRow + (col >>> 3)];\n const zNibblePos = col & 7;\n const zeroVal = (zWord >>> (zNibblePos * 4)) & 0xf;\n zeros[col * groupsPerCol + g] = zeroVal + 1; // +1: GPTQ packing convention\n }\n }\n\n return { packed, scales, zeros };\n}\n","/**\n * IndexedDB-backed blob cache for model weights.\n *\n * Why IndexedDB and not the Cache API / OPFS we hand-rolled before:\n * - The Cache API ignores URL fragments in `match()`, which silently collapsed\n * our per-tensor `${url}#t:start-len` keys onto one entry → wrong bytes → the\n * `RRRR` garbage and `wt.data` crashes. IDB keys on the exact string.\n * - OPFS via Worker (`createSyncAccessHandle`) is finicky on iOS Safari (OOM /\n * transient failures) and added a lot of moving parts.\n * IndexedDB is the boring, proven large-binary store every browser supports. It\n * keys on arbitrary strings (no fragment footgun), stores ArrayBuffers directly,\n * and is durable (subject to the same per-origin quota as Cache/OPFS — install to\n * Home Screen lifts that ceiling; IDB doesn't change it, it just makes caching\n * actually CORRECT).\n *\n * Inert on Node (no indexedDB) — every export resolves to a miss/no-op so the\n * heap load path is unchanged off the browser.\n */\n\nconst DB_NAME = \"gerbil-weights-v1\";\nconst STORE = \"tensors\";\n\nfunction idbAvailable(): boolean {\n return typeof indexedDB !== \"undefined\";\n}\n\nlet _dbPromise: Promise<IDBDatabase | null> | null = null;\n\nfunction openDB(): Promise<IDBDatabase | null> {\n if (_dbPromise) return _dbPromise;\n _dbPromise = new Promise<IDBDatabase | null>((resolve) => {\n if (!idbAvailable()) {\n resolve(null);\n return;\n }\n try {\n const req = indexedDB.open(DB_NAME, 1);\n req.onupgradeneeded = () => {\n const db = req.result;\n if (!db.objectStoreNames.contains(STORE)) db.createObjectStore(STORE);\n };\n req.onsuccess = () => resolve(req.result);\n req.onerror = () => resolve(null);\n req.onblocked = () => resolve(null);\n } catch {\n resolve(null);\n }\n });\n return _dbPromise;\n}\n\n/** A single transaction operation, promisified. Resolves the fallback on any error. */\nfunction tx<T>(\n mode: IDBTransactionMode,\n run: (store: IDBObjectStore) => IDBRequest,\n fallback: T,\n): Promise<T> {\n return openDB().then(\n (db) =>\n new Promise<T>((resolve) => {\n if (!db) {\n resolve(fallback);\n return;\n }\n try {\n const t = db.transaction(STORE, mode);\n const req = run(t.objectStore(STORE));\n req.onsuccess = () => resolve((req.result as T) ?? fallback);\n req.onerror = () => resolve(fallback);\n t.onabort = () => resolve(fallback);\n } catch {\n resolve(fallback);\n }\n }),\n );\n}\n\n/** Read a key's bytes, or null on miss / no IDB / any error. */\nexport async function idbGet(key: string): Promise<ArrayBuffer | null> {\n const v = await tx<ArrayBuffer | null>(\"readonly\", (s) => s.get(key), null);\n return v instanceof ArrayBuffer ? v : null;\n}\n\n/** Cheap presence check (reads only the key via getKey — no body materialized). */\nexport async function idbHas(key: string): Promise<boolean> {\n const k = await tx<IDBValidKey | undefined>(\"readonly\", (s) => s.getKey(key), undefined);\n return k !== undefined;\n}\n\n/**\n * All cached keys, as a Set, in ONE transaction. Probing presence per-tensor\n * (hundreds of parallel getKey transactions) storms Safari's IndexedDB and hangs\n * the load; getAllKeys() does it in a single transaction so callers can check\n * membership in memory. Empty set on miss / no IDB / error.\n */\nexport async function idbAllKeys(): Promise<Set<string>> {\n const keys = await tx<IDBValidKey[]>(\"readonly\", (s) => s.getAllKeys(), []);\n return new Set((keys ?? []).map((k) => String(k)));\n}\n\n/** Write a key's bytes. Returns true on success, false on quota/error (caller\n * falls back to network — never throws). */\nexport async function idbPut(key: string, buf: ArrayBuffer): Promise<boolean> {\n // put returns the key on success; map to boolean.\n const r = await tx<IDBValidKey | null>(\"readwrite\", (s) => s.put(buf, key), null);\n return r !== null;\n}\n\n/** Delete a key (best-effort). */\nexport async function idbDelete(key: string): Promise<void> {\n await tx<unknown>(\"readwrite\", (s) => s.delete(key), null);\n}\n\n/** True when IndexedDB is usable in this environment (browser, not Node). */\nexport function idbCacheAvailable(): boolean {\n return idbAvailable();\n}\n","/**\n * MLX 4-bit → Gerbil INT4 format adapter.\n *\n * Converts MLX quantized weights (weight/scales/biases) into the flat\n * row-major packing expected by Gerbil's MatMulInt4 / EmbeddingInt4 kernels.\n *\n * MLX layout (per quantized layer):\n * weight: Uint32Array, shape [N, K/8] — 8 nibbles per u32, packed along K (last axis)\n * scales: Float32Array, shape [N, K/group_size] — per-group scale factors\n * biases: Float32Array, shape [N, K/group_size] — per-group bias values\n *\n * MLX dequant formula (affine mode):\n * w = scale * nibble + bias\n *\n * Gerbil dequant formula:\n * w = (nibble - zero) * scale\n *\n * Conversion: scale_gerbil = scale_mlx, zero_gerbil = -bias_mlx / scale_mlx\n *\n * MLX [N, K/8] is already row-major with nibbles packed along K — same layout\n * as Gerbil flat [N*K/8]. The nibble data is directly usable; only scales/zeros\n * need conversion.\n */\n\nimport type { GerbilINT4Tensors } from \"./gptq-adapter.js\";\n\nexport interface MLXTensors {\n weight: Uint32Array; // [N, K/8] packed 4-bit\n scales: Float32Array; // [N, K/group_size]\n biases: Float32Array; // [N, K/group_size]\n N: number;\n K: number;\n groupSize: number;\n}\n\n/**\n * Convert MLX 4-bit tensors into Gerbil's flat INT4 format.\n *\n * The packed nibbles are already in the correct layout — MLX [N, K/8] row-major\n * matches Gerbil flat [N*K/8]. We just convert the affine scale/bias to\n * Gerbil's (nibble - zero) * scale convention.\n */\nexport function repackMLX(mlx: MLXTensors): GerbilINT4Tensors {\n const { weight, scales: mlxScales, biases: mlxBiases, N, K, groupSize } = mlx;\n const groupsPerRow = Math.ceil(K / groupSize);\n const totalGroups = N * groupsPerRow;\n\n // Nibbles: MLX [N, K/8] is already flat [N*K/8] in row-major order\n // The packing is identical: 8 nibbles per u32, little-nibble-first\n const packed = weight;\n\n // Convert affine scale/bias to Gerbil scale/zero\n // MLX: w = scale * nibble + bias\n // Gerbil: w = (nibble - zero) * scale\n // Therefore: zero = -bias / scale, scale stays the same\n const scales = new Float32Array(totalGroups);\n const zeros = new Float32Array(totalGroups);\n\n // MLX stores [N, groups_per_row] which matches Gerbil flat [N * groups_per_row]\n // Both are row-major with groups sequential per output row — same layout.\n for (let i = 0; i < totalGroups; i++) {\n const s = mlxScales[i];\n const b = mlxBiases[i];\n scales[i] = s;\n // When scale is zero, bias should also be zero (constant group)\n zeros[i] = s !== 0 ? -b / s : 0;\n }\n\n return { packed, scales, zeros };\n}\n","/**\n * OPFS-backed blob cache for durable model-weight persistence.\n *\n * WHY THIS EXISTS\n * ---------------\n * On iOS Safari the CacheStorage API (`caches.open(...)`) used for model weights\n * is evicted under quota pressure in a plain tab — `navigator.storage.persist()`\n * is not granted outside an installed PWA — so the ~404 MB model is re-downloaded\n * on every visit. The Origin Private File System (OPFS) is the durable path, but:\n *\n * • Main-thread OPFS `createWritable` throws \"out of quota\" mid-write on iOS\n * Safari and can leave orphaned, unclearable bytes.\n * • The ONLY iOS-safe OPFS write path is `FileSystemSyncAccessHandle`\n * (`createSyncAccessHandle`), which is available ONLY inside a Worker.\n *\n * So this module runs all OPFS I/O on a dedicated Worker created from an inlined\n * blob URL (no build wiring, zero extra deps). The main thread talks to it over\n * postMessage; payload ArrayBuffers are TRANSFERRED (zero-copy) so a write does\n * not double peak memory. Writes are chunked inside the worker so the sync handle\n * never has to hold the whole tensor at once.\n *\n * KEY → FILENAME\n * --------------\n * Cache keys are URLs with `/`, `#`, `?` etc. (e.g.\n * `https://huggingface.co/...model.safetensors#t:8388608-2359296`). OPFS file\n * names cannot contain those, so each key is mapped to a stable, collision-\n * resistant filename via a 53-bit FNV-style hash plus the key length. Files live\n * in a versioned OPFS subdirectory (see OPFS_DIR_NAME).\n *\n * DESKTOP / NODE\n * --------------\n * Everything here is gated by `opfsAvailable()`, which is false in Node (no\n * `navigator.storage.getDirectory`, no `Worker`, no `Blob`). Callers must short-\n * circuit on that before touching any export here. The Node weight path never\n * reaches this module.\n */\n\n/** Versioned OPFS directory. Bump when the on-disk layout/semantics change. */\nconst OPFS_DIR_NAME = \"gerbil-models-v4\";\n\n/** Per-message write chunk inside the worker (bounded peak memory). */\nconst WRITE_CHUNK_BYTES = 8 * 1024 * 1024;\n\n/**\n * True when the durable OPFS+Worker path is usable. False in Node (no navigator/\n * Worker/Blob) and in browsers without OPFS or Workers. Cheap and synchronous so\n * the seam functions can bail before any async work.\n */\nexport function opfsAvailable(): boolean {\n return (\n typeof navigator !== \"undefined\" &&\n typeof (navigator as { storage?: { getDirectory?: unknown } }).storage?.getDirectory ===\n \"function\" &&\n typeof Worker !== \"undefined\" &&\n typeof Blob !== \"undefined\" &&\n typeof URL !== \"undefined\" &&\n typeof URL.createObjectURL === \"function\"\n );\n}\n\n/**\n * Map an arbitrary cache key to a safe OPFS filename. FNV-1a over UTF-16 code\n * units folded into a 53-bit safe integer, combined with the key length so two\n * different keys are astronomically unlikely to collide. Deterministic across\n * reloads (this is what makes a tensor written last visit re-resolve this visit).\n */\nfunction keyToFileName(key: string): string {\n // FNV-1a (32-bit) mixed twice for a wider hash; emit two hex halves + length.\n let h1 = 0x81_1c_9d_c5;\n let h2 = 0x01_00_01_93;\n for (let i = 0; i < key.length; i++) {\n const c = key.charCodeAt(i);\n h1 ^= c;\n h1 = Math.imul(h1, 0x01_00_01_93);\n h2 ^= c + i;\n h2 = Math.imul(h2, 0x85_eb_ca_6b);\n }\n const a = (h1 >>> 0).toString(16).padStart(8, \"0\");\n const b = (h2 >>> 0).toString(16).padStart(8, \"0\");\n return `${a}${b}-${key.length.toString(16)}.bin`;\n}\n\n// ── Inline worker source ─────────────────────────────────────────────────────\n//\n// Self-contained worker that owns one OPFS directory handle and performs\n// has/read/write/delete via createSyncAccessHandle. Kept as a string so the\n// existing browser tsdown entry emits it inline (no separate worker bundle).\n// The worker has no imports — everything it needs is inlined here.\nconst WORKER_SOURCE = `\nconst DIR_NAME = ${JSON.stringify(OPFS_DIR_NAME)};\nlet dirPromise = null;\n\nfunction getDir() {\n if (!dirPromise) {\n dirPromise = navigator.storage\n .getDirectory()\n .then((root) => root.getDirectoryHandle(DIR_NAME, { create: true }));\n }\n return dirPromise;\n}\n\nasync function handleHas(name) {\n const dir = await getDir();\n try {\n const fh = await dir.getFileHandle(name, { create: false });\n const access = await fh.createSyncAccessHandle();\n const size = access.getSize();\n access.close();\n // A zero-byte file is a half-written/aborted entry — treat as absent.\n return size > 0;\n } catch {\n return false;\n }\n}\n\nasync function handleRead(name) {\n const dir = await getDir();\n const fh = await dir.getFileHandle(name, { create: false });\n const access = await fh.createSyncAccessHandle();\n try {\n const size = access.getSize();\n if (size === 0) {\n access.close();\n return null;\n }\n const buf = new ArrayBuffer(size);\n access.read(new Uint8Array(buf), { at: 0 });\n access.close();\n return buf;\n } catch (e) {\n try { access.close(); } catch {}\n throw e;\n }\n}\n\nasync function handleWrite(name, buf) {\n const dir = await getDir();\n const fh = await dir.getFileHandle(name, { create: true });\n const access = await fh.createSyncAccessHandle();\n try {\n access.truncate(0);\n const total = buf.byteLength;\n const view = new Uint8Array(buf);\n const CHUNK = ${WRITE_CHUNK_BYTES};\n let off = 0;\n while (off < total) {\n const end = Math.min(off + CHUNK, total);\n // Subarray is a zero-copy view; write returns bytes written.\n access.write(view.subarray(off, end), { at: off });\n off = end;\n }\n access.flush();\n const finalSize = access.getSize();\n access.close();\n if (finalSize !== total) {\n // Write came up short (quota) — remove the partial so it never reads back\n // as a valid (wrong-size) entry.\n try { await dir.removeEntry(name); } catch {}\n throw new Error(\"OPFS short write: \" + finalSize + \"/\" + total);\n }\n } catch (e) {\n try { access.close(); } catch {}\n // Best-effort cleanup of any partial file.\n try { await dir.removeEntry(name); } catch {}\n throw e;\n }\n}\n\nasync function handleDelete(name) {\n const dir = await getDir();\n try { await dir.removeEntry(name); } catch {}\n}\n\nself.onmessage = async (ev) => {\n const { id, op, name, buf } = ev.data;\n try {\n if (op === \"has\") {\n const present = await handleHas(name);\n self.postMessage({ id, ok: true, present });\n } else if (op === \"read\") {\n const out = await handleRead(name);\n if (out) self.postMessage({ id, ok: true, buf: out }, [out]);\n else self.postMessage({ id, ok: true, buf: null });\n } else if (op === \"write\") {\n await handleWrite(name, buf);\n self.postMessage({ id, ok: true });\n } else if (op === \"delete\") {\n await handleDelete(name);\n self.postMessage({ id, ok: true });\n } else {\n self.postMessage({ id, ok: false, error: \"unknown op: \" + op });\n }\n } catch (e) {\n self.postMessage({ id, ok: false, error: String((e && e.message) || e) });\n }\n};\n`;\n\ninterface PendingResolver {\n resolve: (value: { present?: boolean; buf?: ArrayBuffer | null }) => void;\n reject: (err: Error) => void;\n}\n\n/**\n * Lazily-created singleton worker client. Created on first use; if creation or\n * any handshake fails the whole OPFS path is marked unavailable so callers fall\n * back to CacheStorage cleanly.\n */\nclass OpfsClient {\n private worker: Worker | null = null;\n private blobUrl: string | null = null;\n private pending = new Map<number, PendingResolver>();\n private nextId = 1;\n private broken = false;\n\n private ensureWorker(): Worker | null {\n if (this.broken) return null;\n if (this.worker) return this.worker;\n try {\n const blob = new Blob([WORKER_SOURCE], { type: \"application/javascript\" });\n this.blobUrl = URL.createObjectURL(blob);\n const worker = new Worker(this.blobUrl);\n worker.onmessage = (ev: MessageEvent) => {\n const { id, ok, present, buf, error } = ev.data as {\n id: number;\n ok: boolean;\n present?: boolean;\n buf?: ArrayBuffer | null;\n error?: string;\n };\n const p = this.pending.get(id);\n if (!p) return;\n this.pending.delete(id);\n if (ok) p.resolve({ present, buf });\n else p.reject(new Error(error || \"OPFS worker error\"));\n };\n worker.onerror = () => {\n // Fatal worker error — reject everything in flight and disable OPFS.\n this.broken = true;\n for (const p of this.pending.values()) {\n p.reject(new Error(\"OPFS worker crashed\"));\n }\n this.pending.clear();\n };\n this.worker = worker;\n return worker;\n } catch {\n this.broken = true;\n return null;\n }\n }\n\n private call(\n op: string,\n name: string,\n buf?: ArrayBuffer,\n ): Promise<{ present?: boolean; buf?: ArrayBuffer | null }> {\n const worker = this.ensureWorker();\n if (!worker) return Promise.reject(new Error(\"OPFS unavailable\"));\n const id = this.nextId++;\n return new Promise((resolve, reject) => {\n this.pending.set(id, { resolve, reject });\n try {\n if (buf) {\n // Transfer the buffer (zero-copy). The caller must not reuse it after.\n worker.postMessage({ id, op, name, buf }, [buf]);\n } else {\n worker.postMessage({ id, op, name });\n }\n } catch (e) {\n this.pending.delete(id);\n reject(e instanceof Error ? e : new Error(String(e)));\n }\n });\n }\n\n async has(key: string): Promise<boolean> {\n const { present } = await this.call(\"has\", keyToFileName(key));\n return Boolean(present);\n }\n\n async read(key: string): Promise<ArrayBuffer | null> {\n const { buf } = await this.call(\"read\", keyToFileName(key));\n return buf ?? null;\n }\n\n async write(key: string, buf: ArrayBuffer): Promise<void> {\n await this.call(\"write\", keyToFileName(key), buf);\n }\n\n async delete(key: string): Promise<void> {\n await this.call(\"delete\", keyToFileName(key));\n }\n}\n\nlet _client: OpfsClient | null = null;\nfunction client(): OpfsClient {\n if (!_client) _client = new OpfsClient();\n return _client;\n}\n\n/**\n * Presence check for a key in the OPFS store. Returns false (never throws) so the\n * caller can fall through to CacheStorage on any error.\n */\nexport async function opfsHas(key: string): Promise<boolean> {\n if (!opfsAvailable()) return false;\n try {\n return await client().has(key);\n } catch {\n return false;\n }\n}\n\n/**\n * Read a key's bytes from OPFS. Returns null on miss or any error (caller falls\n * back to CacheStorage / network).\n */\nexport async function opfsRead(key: string): Promise<ArrayBuffer | null> {\n if (!opfsAvailable()) return null;\n try {\n return await client().read(key);\n } catch {\n return null;\n }\n}\n\n/**\n * Write a key's bytes to OPFS durably. The buffer is TRANSFERRED to the worker\n * (do not reuse `buf` after calling — pass a copy if you still need it). Returns\n * true on success, false on any failure (quota, worker crash, …) so the caller\n * can fall back to CacheStorage without crashing the load.\n */\nexport async function opfsWrite(key: string, buf: ArrayBuffer): Promise<boolean> {\n if (!opfsAvailable()) return false;\n try {\n await client().write(key, buf);\n return true;\n } catch {\n return false;\n }\n}\n\n/** Delete a key from OPFS (best-effort, never throws). */\nexport async function opfsDelete(key: string): Promise<void> {\n if (!opfsAvailable()) return;\n try {\n await client().delete(key);\n } catch {\n /* best-effort */\n }\n}\n\n/**\n * Delete superseded OPFS model directories (old namespaces, e.g. a previous\n * `gerbil-models-v3`) to reclaim quota. On iOS the per-origin quota is ~1 GB and\n * orphaned namespaces can fill it, blocking the current model from caching at all\n * (every write then fails with quota/OOM → redownload every load). Directory\n * removal works on the MAIN thread — only createSyncAccessHandle needs a Worker —\n * so this is cheap and best-effort. Inert on Node (no navigator.storage).\n */\nexport async function opfsEvictStale(): Promise<void> {\n try {\n const root = (await (\n navigator as { storage?: { getDirectory?: () => Promise<unknown> } }\n )?.storage?.getDirectory?.()) as\n | {\n entries?: () => AsyncIterable<[string, { kind: string }]>;\n removeEntry: (n: string, o?: { recursive?: boolean }) => Promise<void>;\n }\n | undefined;\n if (!root) return;\n const stale: string[] = [];\n for await (const [name, handle] of root.entries?.() ?? []) {\n if (\n handle.kind === \"directory\" &&\n name.startsWith(\"gerbil-models-\") &&\n name !== OPFS_DIR_NAME\n ) {\n stale.push(name);\n }\n }\n for (const name of stale) {\n await root.removeEntry(name, { recursive: true }).catch(() => {\n /* best-effort */\n });\n }\n } catch {\n /* best-effort */\n }\n}\n\n/**\n * One-time health probe: actually write a few bytes to OPFS, read them back,\n * verify they round-trip, then clean up. Memoized. iOS Safari can report OPFS as\n * \"available\" (getDirectory/Worker/Blob all present) yet have the Worker's\n * createSyncAccessHandle writes silently fail — in which case we must NOT trust\n * OPFS, because the per-tensor write path transfers (detaches) the buffer and a\n * failed OPFS write would then have no CacheStorage fallback (→ nothing cached →\n * redownload every load). Callers gate OPFS use on THIS, not the cheap synchronous\n * opfsAvailable(). Resolves false if OPFS can't actually persist a round-trip.\n */\nlet _healthy: Promise<boolean> | null = null;\nexport function opfsHealthy(): Promise<boolean> {\n if (_healthy) return _healthy;\n _healthy = (async () => {\n if (!opfsAvailable()) return false;\n try {\n const key = \"__opfs_health_probe__\";\n const ok = await opfsWrite(key, new Uint8Array([7, 13, 42, 99]).buffer);\n if (!ok) return false;\n const back = await opfsRead(key);\n await opfsDelete(key);\n if (!back || back.byteLength !== 4) return false;\n const b = new Uint8Array(back);\n return b[0] === 7 && b[1] === 13 && b[2] === 42 && b[3] === 99;\n } catch {\n return false;\n }\n })();\n return _healthy;\n}\n","/**\n * Safetensors binary format parser.\n *\n * Parses the HuggingFace safetensors format into typed array views\n * over the raw buffer — zero-copy where possible.\n */\n\nexport interface SafetensorEntry {\n /** Tensor name as it appears in the file (e.g. \"model.layers.0.self_attn.q_proj.weight\"). */\n name: string;\n /** Data type string from the header. */\n dtype: SafetensorDType;\n /** Shape dimensions. */\n shape: number[];\n /** Byte offset of this tensor's data relative to the start of the data section. */\n dataOffset: number;\n /** Byte length of this tensor's data. */\n dataLength: number;\n}\n\nexport type SafetensorDType =\n | \"F16\"\n | \"F32\"\n | \"BF16\"\n | \"I32\"\n | \"I8\"\n | \"U8\"\n | \"F64\"\n | \"BOOL\"\n | \"I16\"\n | \"U16\"\n | \"U32\"\n | \"I64\"\n | \"U64\";\n\nexport interface SafetensorsFile {\n /** Header length in bytes. */\n headerLength: number;\n /** Byte offset where tensor data begins (8 + headerLength). */\n dataStart: number;\n /** All tensor entries (excludes __metadata__). */\n entries: SafetensorEntry[];\n /** Optional metadata from __metadata__ key. */\n metadata: Record<string, string> | null;\n}\n\n/**\n * Parse safetensors header from an ArrayBuffer.\n *\n * Can be called with just the header bytes (for streaming — parse header first,\n * then fetch tensor data by offset) or with the entire file.\n */\nexport function parseSafetensorsHeader(buffer: ArrayBuffer): SafetensorsFile {\n if (buffer.byteLength < 8) {\n throw new Error(\n `Buffer too small to contain safetensors header length (need 8 bytes, got ${buffer.byteLength}).`,\n );\n }\n\n const view = new DataView(buffer);\n\n // First 8 bytes: header length as little-endian uint64.\n // JS doesn't have native uint64, but headers are always < 4 GB.\n const headerLength = Number(view.getBigUint64(0, true));\n\n if (headerLength > buffer.byteLength - 8) {\n throw new Error(\n `Safetensors header length (${headerLength}) exceeds buffer size (${buffer.byteLength - 8}). ` +\n `Pass at least the first ${headerLength + 8} bytes.`,\n );\n }\n\n // Decode the JSON header\n const headerBytes = new Uint8Array(buffer, 8, headerLength);\n const headerStr = new TextDecoder().decode(headerBytes);\n const header: Record<string, unknown> = JSON.parse(headerStr);\n\n const dataStart = 8 + headerLength;\n const entries: SafetensorEntry[] = [];\n let metadata: Record<string, string> | null = null;\n\n for (const [name, info] of Object.entries(header)) {\n if (name === \"__metadata__\") {\n metadata = info as Record<string, string>;\n continue;\n }\n\n const { dtype, shape, data_offsets } = info as {\n dtype: SafetensorDType;\n shape: number[];\n data_offsets: [number, number];\n };\n\n entries.push({\n name,\n dtype,\n shape,\n dataOffset: data_offsets[0],\n dataLength: data_offsets[1] - data_offsets[0],\n });\n }\n\n // Sort by offset for sequential access patterns\n entries.sort((a, b) => a.dataOffset - b.dataOffset);\n\n return { headerLength, dataStart, entries, metadata };\n}\n\n/** Byte alignment required for each dtype. */\nfunction dtypeAlignment(dtype: SafetensorDType): number {\n switch (dtype) {\n case \"F64\":\n case \"I64\":\n case \"U64\":\n return 8;\n case \"F32\":\n case \"I32\":\n case \"U32\":\n return 4;\n case \"F16\":\n case \"BF16\":\n case \"I16\":\n case \"U16\":\n return 2;\n case \"I8\":\n case \"U8\":\n case \"BOOL\":\n return 1;\n default:\n return 1;\n }\n}\n\n/**\n * Create a typed array for the given dtype.\n *\n * If `offset` is aligned to the element size, returns a zero-copy view\n * into `buffer`. Otherwise, copies the relevant slice into a new\n * properly-aligned ArrayBuffer.\n */\nfunction makeTypedView(\n buffer: ArrayBuffer,\n offset: number,\n byteLength: number,\n dtype: SafetensorDType,\n): ArrayBufferView {\n const align = dtypeAlignment(dtype);\n const aligned = offset % align === 0;\n\n // For single-byte types, alignment is always satisfied.\n const src = aligned ? buffer : buffer.slice(offset, offset + byteLength);\n const base = aligned ? offset : 0;\n\n switch (dtype) {\n case \"F32\":\n return new Float32Array(src, base, byteLength / 4);\n case \"F16\":\n // F16 has no native TypedArray — return Uint16Array (bitwise).\n return new Uint16Array(src, base, byteLength / 2);\n case \"BF16\":\n // BF16 also has no native TypedArray — return Uint16Array (bitwise).\n return new Uint16Array(src, base, byteLength / 2);\n case \"I32\":\n return new Int32Array(src, base, byteLength / 4);\n case \"U32\":\n return new Uint32Array(src, base, byteLength / 4);\n case \"I8\":\n return new Int8Array(src, base, byteLength);\n case \"U8\":\n return new Uint8Array(src, base, byteLength);\n case \"I16\":\n return new Int16Array(src, base, byteLength / 2);\n case \"U16\":\n return new Uint16Array(src, base, byteLength / 2);\n case \"F64\":\n return new Float64Array(src, base, byteLength / 8);\n case \"I64\":\n return new BigInt64Array(src, base, byteLength / 8);\n case \"U64\":\n return new BigUint64Array(src, base, byteLength / 8);\n case \"BOOL\":\n return new Uint8Array(src, base, byteLength);\n default:\n throw new Error(`Unsupported safetensors dtype: ${dtype}`);\n }\n}\n\n/**\n * Get a typed array view for a tensor entry from a complete safetensors buffer.\n * Zero-copy when the byte offset is properly aligned; copies otherwise.\n */\nexport function getTensorData(\n buffer: ArrayBuffer,\n file: SafetensorsFile,\n entry: SafetensorEntry,\n): ArrayBufferView {\n const offset = file.dataStart + entry.dataOffset;\n return makeTypedView(buffer, offset, entry.dataLength, entry.dtype);\n}\n\n/**\n * Find a tensor entry by name (exact match).\n */\nexport function findTensor(file: SafetensorsFile, name: string): SafetensorEntry | undefined {\n return file.entries.find((e) => e.name === name);\n}\n\n/**\n * Parse just the header from a Response (streaming-friendly).\n *\n * Reads the full response into memory and parses the header.\n * Returns the parsed header and the full buffer for tensor access.\n */\nexport async function parseSafetensorsFromResponse(\n response: Response,\n): Promise<{ file: SafetensorsFile; fullBuffer: ArrayBuffer }> {\n const fullBuffer = await response.arrayBuffer();\n const file = parseSafetensorsHeader(fullBuffer);\n return { file, fullBuffer };\n}\n\n/**\n * Compute the total byte size of all tensor data in a safetensors file.\n */\nexport function totalTensorBytes(file: SafetensorsFile): number {\n let total = 0;\n for (const entry of file.entries) {\n total += entry.dataLength;\n }\n return total;\n}\n\n/**\n * Convert BF16 (bfloat16) data to F32.\n * BF16 is the upper 16 bits of an IEEE 754 float32, so conversion is a simple left-shift.\n */\nexport function bf16ToF32(bf16Bytes: Uint8Array): Float32Array {\n const bf16 = new Uint16Array(bf16Bytes.buffer, bf16Bytes.byteOffset, bf16Bytes.byteLength / 2);\n const f32 = new Float32Array(bf16.length);\n const u32 = new Uint32Array(f32.buffer);\n for (let i = 0; i < bf16.length; i++) {\n u32[i] = bf16[i] << 16;\n }\n return f32;\n}\n\n/**\n * Convert F16 (IEEE 754 half-precision) data to F32.\n */\nexport function f16ToF32(f16View: ArrayBufferView): Float32Array {\n const u16 =\n f16View instanceof Uint16Array\n ? f16View\n : new Uint16Array(f16View.buffer, f16View.byteOffset, f16View.byteLength / 2);\n const f32 = new Float32Array(u16.length);\n for (let i = 0; i < u16.length; i++) {\n const h = u16[i];\n const sign = (h >> 15) & 1;\n const exp = (h >> 10) & 0x1f;\n const frac = h & 0x3ff;\n if (exp === 0) {\n // Subnormal or zero\n f32[i] = (sign ? -1 : 1) * 2 ** -14 * (frac / 1024);\n } else if (exp === 31) {\n // Inf or NaN\n f32[i] = frac === 0 ? (sign ? -Infinity : Infinity) : NaN;\n } else {\n f32[i] = (sign ? -1 : 1) * 2 ** (exp - 15) * (1 + frac / 1024);\n }\n }\n return f32;\n}\n","/**\n * Pure JavaScript BPE tokenizer.\n *\n * Reads HuggingFace tokenizer.json — no WASM, no external dependencies.\n * Supports encoding, decoding, and chat template application.\n */\n\n// Byte-level BPE unicode mapping (same as HuggingFace's bytes_to_unicode).\n// Maps each byte (0-255) to a unique unicode codepoint such that printable\n// ASCII characters map to themselves, and non-printable bytes are shifted\n// into the U+0100+ range.\nconst BYTE_TO_UNICODE = new Map<number, number>();\nconst UNICODE_TO_BYTE = new Map<number, number>();\n\n{\n // Printable bytes that map to themselves: 33-126 (! through ~), 161-172, 174-255\n const bs: number[] = [];\n for (let i = 33; i <= 126; i++) bs.push(i); // ! through ~\n for (let i = 161; i <= 172; i++) bs.push(i); // ¡ through ¬\n for (let i = 174; i <= 255; i++) bs.push(i); // ® through ÿ\n\n const cs = [...bs]; // unicode codepoints (same for these ranges)\n\n // Non-printable bytes get shifted to U+0100+\n let n = 0;\n for (let b = 0; b < 256; b++) {\n if (!bs.includes(b)) {\n bs.push(b);\n cs.push(256 + n);\n n++;\n }\n }\n\n for (let i = 0; i < bs.length; i++) {\n BYTE_TO_UNICODE.set(bs[i], cs[i]);\n UNICODE_TO_BYTE.set(cs[i], bs[i]);\n }\n}\n\nexport interface TokenizerConfig {\n bosToken: string | null;\n eosToken: string | null;\n bosTokenId: number | null;\n eosTokenId: number | null;\n chatTemplate: string | null;\n addBosToken: boolean;\n addEosToken: boolean;\n}\n\nexport interface ChatMessage {\n role: \"system\" | \"user\" | \"assistant\";\n content: string;\n}\n\nexport class Tokenizer {\n private vocab: Map<string, number>; // token string → id\n private vocabReverse: Map<number, string>; // id → token string\n private merges: Map<string, number>; // \"tokenA tokenB\" → priority (lower = merge first)\n private specialTokens: Map<string, number>; // special token string → id\n private addedTokens: Map<string, number>; // ALL added tokens (special + non-special like <think>)\n private byteFallback: Map<number, string>; // byte value → byte-level token\n /**\n * SentencePiece mode (Gemma/Llama-style). When true, vocab uses U+2581 (▁) for\n * spaces and raw UTF-8 tokens (NOT the GPT-2 byte-to-unicode \"Ġ\" mapping), and\n * raw bytes fall back to <0xHH> tokens. When false, GPT-2 byte-level BPE.\n */\n private spmMode: boolean;\n readonly config: TokenizerConfig;\n readonly vocabSize: number;\n\n private constructor(\n vocab: Map<string, number>,\n vocabReverse: Map<number, string>,\n merges: Map<string, number>,\n specialTokens: Map<string, number>,\n addedTokens: Map<string, number>,\n config: TokenizerConfig,\n vocabSize: number,\n spmMode: boolean,\n ) {\n this.vocab = vocab;\n this.vocabReverse = vocabReverse;\n this.merges = merges;\n this.specialTokens = specialTokens;\n this.addedTokens = addedTokens;\n this.byteFallback = new Map();\n this.spmMode = spmMode;\n this.config = config;\n this.vocabSize = vocabSize;\n\n // Build byte fallback map (for handling unknown characters)\n // HF tokenizers use <0xHH> tokens for raw bytes\n for (let b = 0; b < 256; b++) {\n const hex = b.toString(16).toUpperCase().padStart(2, \"0\");\n const key = `<0x${hex}>`;\n if (vocab.has(key)) {\n this.byteFallback.set(b, key);\n }\n }\n }\n\n /**\n * Create a tokenizer from HuggingFace JSON files.\n */\n static fromJSON(tokenizerJSON: any, tokenizerConfigJSON?: any): Tokenizer {\n const model = tokenizerJSON.model;\n if (!model || model.type !== \"BPE\") {\n throw new Error(\n `Unsupported tokenizer type: ${model?.type ?? \"unknown\"}. Only BPE is supported.`,\n );\n }\n\n // Build vocab\n const vocab = new Map<string, number>();\n const vocabReverse = new Map<number, string>();\n for (const [token, id] of Object.entries(model.vocab as Record<string, number>)) {\n vocab.set(token, id);\n vocabReverse.set(id, token);\n }\n\n // Detect SentencePiece mode (Gemma/Llama): the normalizer replaces spaces\n // with U+2581 (▁) and/or the vocab is dominated by ▁-prefixed tokens. In SPM\n // mode tokens are raw UTF-8 with ▁ for spaces and bytes fall back to <0xHH>,\n // rather than the GPT-2 byte-to-unicode (Ġ) mapping.\n const normalizerReplacesSpace = (() => {\n const norm = tokenizerJSON.normalizer;\n const checkOne = (n: any) =>\n n?.type === \"Replace\" && n?.pattern?.String === \" \" && n?.content === \"▁\";\n if (!norm) return false;\n if (checkOne(norm)) return true;\n if (Array.isArray(norm.normalizers)) return norm.normalizers.some(checkOne);\n return false;\n })();\n const byteFallbackFlag = model.byte_fallback === true;\n const spmMode = normalizerReplacesSpace || (byteFallbackFlag && \"▁the\" in model.vocab);\n\n // Build merge priority map. Merges are either \"a b\" strings (GPT-2 style) or\n // [\"a\", \"b\"] arrays (newer/Gemma tokenizer.json). Normalize both to \"a b\".\n const merges = new Map<string, number>();\n const mergeList = (model.merges ?? []) as Array<string | [string, string]>;\n for (let i = 0; i < mergeList.length; i++) {\n const m = mergeList[i];\n const key = Array.isArray(m) ? `${m[0]} ${m[1]}` : m;\n merges.set(key, i);\n }\n\n // Register added tokens — ALL of them go into addedTokensMap for splitting,\n // only special:true ones go into specialTokens for skip-during-decode\n const specialTokens = new Map<string, number>();\n const addedTokensMap = new Map<string, number>();\n const addedTokensList = tokenizerJSON.added_tokens as\n | Array<{ id: number; content: string; special?: boolean }>\n | undefined;\n if (addedTokensList) {\n for (const t of addedTokensList) {\n vocab.set(t.content, t.id);\n vocabReverse.set(t.id, t.content);\n addedTokensMap.set(t.content, t.id);\n if (t.special) {\n specialTokens.set(t.content, t.id);\n }\n }\n }\n\n // Parse config\n const bosToken = tokenizerConfigJSON?.bos_token ?? null;\n const eosToken = tokenizerConfigJSON?.eos_token ?? null;\n const chatTemplate = tokenizerConfigJSON?.chat_template ?? null;\n const addBosToken = tokenizerConfigJSON?.add_bos_token ?? false;\n const addEosToken = tokenizerConfigJSON?.add_eos_token ?? false;\n\n const config: TokenizerConfig = {\n bosToken,\n eosToken,\n bosTokenId: bosToken ? (vocab.get(bosToken) ?? null) : null,\n eosTokenId: eosToken ? (vocab.get(eosToken) ?? null) : null,\n chatTemplate,\n addBosToken,\n addEosToken,\n };\n\n return new Tokenizer(\n vocab,\n vocabReverse,\n merges,\n specialTokens,\n addedTokensMap,\n config,\n vocab.size,\n spmMode,\n );\n }\n\n /**\n * Resolve a literal token string (e.g. \"<|endoftext|>\") to its vocab id,\n * or null if it isn't in the vocabulary.\n */\n tokenToId(token: string): number | null {\n return this.vocab.get(token) ?? null;\n }\n\n /**\n * Encode text into token IDs.\n */\n encode(text: string): number[] {\n if (!text) return [];\n\n const ids: number[] = [];\n\n // Add BOS token if configured\n if (this.config.addBosToken && this.config.bosTokenId !== null) {\n ids.push(this.config.bosTokenId);\n }\n\n // Check for special tokens first — split text around them\n const parts = this.splitOnSpecialTokens(text);\n\n for (const part of parts) {\n if (part.special) {\n const id = this.addedTokens.get(part.text);\n if (id !== undefined) {\n ids.push(id);\n }\n } else {\n // Pre-tokenize: split into words/chunks\n // Use a simple regex-based pre-tokenizer similar to GPT-style\n const chunks = this.preTokenize(part.text);\n for (const chunk of chunks) {\n const chunkIds = this.bpeEncode(chunk);\n ids.push(...chunkIds);\n }\n }\n }\n\n // Add EOS token if configured\n if (this.config.addEosToken && this.config.eosTokenId !== null) {\n ids.push(this.config.eosTokenId);\n }\n\n return ids;\n }\n\n /**\n * Decode token IDs back to text.\n */\n decode(ids: number[], skipSpecialTokens: boolean = true): string {\n const pieces: string[] = [];\n\n for (const id of ids) {\n const token = this.vocabReverse.get(id);\n if (!token) continue;\n\n // Skip special tokens if requested\n if (skipSpecialTokens && this.specialTokens.has(token)) {\n continue;\n }\n\n pieces.push(token);\n }\n\n if (this.spmMode) {\n // SentencePiece (Gemma/Llama): tokens are raw UTF-8 with ▁ for spaces and\n // <0xHH> byte-fallback pieces. Concatenate the <0xHH> bytes into proper\n // UTF-8 runs (Fuse), decode, and turn ▁ back into spaces.\n let out = \"\";\n let byteRun: number[] = [];\n const flushBytes = () => {\n if (byteRun.length === 0) return;\n out += new TextDecoder().decode(new Uint8Array(byteRun));\n byteRun = [];\n };\n for (const piece of pieces) {\n const m = /^<0x([0-9A-Fa-f]{2})>$/.exec(piece);\n if (m) {\n byteRun.push(Number.parseInt(m[1], 16));\n } else {\n flushBytes();\n out += piece;\n }\n }\n flushBytes();\n return out.replace(/▁/g, \" \");\n }\n\n // Join and decode byte-level BPE tokens.\n // GPT/Qwen tokenizers use a byte-to-unicode mapping where non-printable\n // bytes are shifted into the U+0100+ range:\n // byte 0x00-0x20 → U+0100-0x0120 (e.g. 0x0A=\\n → U+010A=Ċ, 0x20=space → U+0120=Ġ)\n // byte 0x7F-0xA0 → U+0121-0x0142\n // We decode by reversing this mapping back to raw bytes.\n let text = pieces.join(\"\");\n\n // Build the reverse byte-level mapping\n const chars: number[] = [];\n for (let i = 0; i < text.length; i++) {\n const cp = text.codePointAt(i)!;\n const byte = UNICODE_TO_BYTE.get(cp);\n if (byte !== undefined) {\n chars.push(byte);\n } else {\n // Multi-byte UTF-8: encode the codepoint as UTF-8 bytes\n const encoded = new TextEncoder().encode(String.fromCodePoint(cp));\n for (const b of encoded) chars.push(b);\n if (cp > 0xffff) i++; // skip surrogate pair\n }\n }\n text = new TextDecoder().decode(new Uint8Array(chars));\n\n // Handle byte-level fallback tokens like <0x0A> → \\n\n text = text.replace(/<0x([0-9A-Fa-f]{2})>/g, (_, hex) => {\n return String.fromCharCode(parseInt(hex, 16));\n });\n\n return text;\n }\n\n /**\n * Apply chat template to messages.\n *\n * For now, implements the common ChatML format used by Qwen models:\n * <|im_start|>system\\n{content}<|im_end|>\\n\n * <|im_start|>user\\n{content}<|im_end|>\\n\n * <|im_start|>assistant\\n\n *\n * TODO: Parse Jinja2 templates from tokenizer_config.json for full generality.\n */\n /**\n * Gemma 4 turn format: `<bos><|turn>user\\n{content}<turn|>\\n<|turn>model\\n`.\n * Gemma has no \"system\" role, so a system message is folded into the next user\n * turn (matching the reference chat template).\n */\n private applyGemmaTurnTemplate(messages: ChatMessage[], addGenPrompt: boolean): string {\n const parts: string[] = [];\n if (this.config.bosToken) parts.push(this.config.bosToken);\n let systemText = \"\";\n for (const msg of messages) {\n if (msg.role === \"system\") {\n systemText += `${msg.content}\\n\\n`;\n continue;\n }\n const role = msg.role === \"assistant\" ? \"model\" : msg.role;\n const content = role === \"user\" && systemText ? systemText + msg.content : msg.content;\n if (role === \"user\") systemText = \"\";\n parts.push(`<|turn>${role}\\n${content}<turn|>\\n`);\n }\n if (addGenPrompt) parts.push(\"<|turn>model\\n\");\n return parts.join(\"\");\n }\n\n applyChatTemplate(messages: ChatMessage[], options?: { addGenerationPrompt?: boolean }): string {\n const addGenPrompt = options?.addGenerationPrompt ?? true;\n const parts: string[] = [];\n\n // ── Gemma 4 turn format ──\n // Detected by the turn-delimiter tokens `<|turn>`/`<turn|>` (unique to the\n // Gemma 4 vocab, absent from the ChatML/Qwen vocabs handled below).\n if (this.addedTokens.has(\"<|turn>\") && this.addedTokens.has(\"<turn|>\")) {\n return this.applyGemmaTurnTemplate(messages, addGenPrompt);\n }\n\n // Models whose chat template prepends bos at the start (e.g. LFM2's\n // `{{- bos_token -}}` ...). The hardcoded ChatML body is otherwise shared.\n const template = this.config.chatTemplate;\n if (template?.includes(\"bos_token\") && this.config.bosToken) {\n parts.push(this.config.bosToken);\n }\n\n for (const msg of messages) {\n parts.push(`<|im_start|>${msg.role}\\n${msg.content}<|im_end|>\\n`);\n }\n\n if (addGenPrompt) {\n parts.push(`<|im_start|>assistant\\n`);\n // Some reasoning models (e.g. Qwen3/3.5) pre-fill an (empty) think block\n // after the generation prompt for non-thinking mode. Only inject it when\n // the model's own chat template actually emits `<think>` at generation\n // time — otherwise models that merely *have* a <think> token but don't\n // pre-fill it (e.g. LFM2.5) would be corrupted into degenerate output.\n const templateWantsThink = template\n ? template.includes(\"<think>\")\n : this.addedTokens.has(\"<think>\");\n if (templateWantsThink) {\n parts.push(`<think>\\n\\n</think>\\n\\n`);\n }\n }\n\n return parts.join(\"\");\n }\n\n /**\n * Encode a chat conversation into token IDs.\n */\n encodeChat(messages: ChatMessage[], options?: { addGenerationPrompt?: boolean }): number[] {\n const text = this.applyChatTemplate(messages, options);\n return this.encode(text);\n }\n\n // ── Internal BPE implementation ──────────────────────────────────\n\n private splitOnSpecialTokens(text: string): Array<{ text: string; special: boolean }> {\n if (this.addedTokens.size === 0) return [{ text, special: false }];\n\n const parts: Array<{ text: string; special: boolean }> = [];\n // Sort added tokens by length (longest first) for greedy matching\n // Uses ALL added tokens (not just special:true) so <think>/<|tool_call|> etc. are split\n const sortedSpecials = [...this.addedTokens.keys()].sort((a, b) => b.length - a.length);\n\n // Build regex that matches any special token\n const escaped = sortedSpecials.map((s) => s.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\"));\n const regex = new RegExp(`(${escaped.join(\"|\")})`, \"g\");\n\n let lastIndex = 0;\n let match: RegExpExecArray | null;\n while ((match = regex.exec(text)) !== null) {\n if (match.index > lastIndex) {\n parts.push({\n text: text.slice(lastIndex, match.index),\n special: false,\n });\n }\n parts.push({ text: match[0], special: true });\n lastIndex = regex.lastIndex;\n }\n if (lastIndex < text.length) {\n parts.push({ text: text.slice(lastIndex), special: false });\n }\n\n return parts;\n }\n\n private preTokenize(text: string): string[] {\n if (!text) return [];\n\n if (this.spmMode) {\n // SentencePiece (Gemma/Llama): normalize spaces → ▁ (U+2581), then split on\n // the ▁ boundary keeping it with the FOLLOWING piece (Split \" \"\n // MergedWithPrevious on the original, equivalent to ▁-prefixed words).\n // BPE then runs on the raw ▁-encoded text — no byte-to-unicode mapping.\n const encoded = text.replace(/ /g, \"▁\");\n const chunks: string[] = [];\n let i = 0;\n while (i < encoded.length) {\n let j = i + 1;\n while (j < encoded.length && encoded[j] !== \"▁\") j++;\n chunks.push(encoded.slice(i, j));\n i = j;\n }\n return chunks;\n }\n\n // GPT-style pre-tokenization regex\n // Splits on whitespace boundaries, keeping the space as a prefix (Ġ convention)\n // Also splits numbers and punctuation\n const pattern = /'s|'t|'re|'ve|'m|'ll|'d| ?\\p{L}+| ?\\p{N}+| ?[^\\s\\p{L}\\p{N}]+|\\s+/gu;\n const matches = text.match(pattern);\n if (!matches) return [];\n\n // Convert to the byte-level representation HF uses:\n // space → Ġ, etc. (the GPT2 byte-to-unicode mapping)\n return matches.map((chunk) => this.textToTokenRepr(chunk));\n }\n\n private textToTokenRepr(text: string): string {\n // Convert text to the representation used in the vocab\n // In HF BPE tokenizers, spaces are typically represented as Ġ (U+0120)\n // and other bytes have specific unicode mappings\n let result = \"\";\n for (let i = 0; i < text.length; i++) {\n const c = text.charCodeAt(i);\n if (c === 32) {\n result += \"\\u0120\"; // space → Ġ\n } else if (c < 33 || (c > 126 && c < 161)) {\n // Control chars and non-printable → byte-level tokens\n // Use the GPT2 byte encoder mapping\n result += String.fromCharCode(c + 256);\n } else {\n result += text[i];\n }\n }\n return result;\n }\n\n private bpeEncode(word: string): number[] {\n if (word.length === 0) return [];\n\n // Check if the whole word is in vocab\n if (this.vocab.has(word)) {\n return [this.vocab.get(word)!];\n }\n\n // Split into individual characters\n let symbols = [...word];\n\n // If any symbol isn't in vocab, try byte fallback\n // Convert to individual characters and check\n if (symbols.length === 1) {\n const id = this.vocab.get(symbols[0]);\n if (id !== undefined) return [id];\n // Byte fallback\n return this.encodeByteFallback(word);\n }\n\n // Iteratively merge the most frequent pair\n while (symbols.length > 1) {\n // Find the pair with the lowest merge rank (highest priority)\n let _bestPair = \"\";\n let bestRank = Infinity;\n let bestIdx = -1;\n\n for (let i = 0; i < symbols.length - 1; i++) {\n const pair = `${symbols[i]} ${symbols[i + 1]}`;\n const rank = this.merges.get(pair);\n if (rank !== undefined && rank < bestRank) {\n bestRank = rank;\n _bestPair = pair;\n bestIdx = i;\n }\n }\n\n // No more merges possible\n if (bestIdx === -1) break;\n\n // Apply the merge\n const merged = symbols[bestIdx] + symbols[bestIdx + 1];\n symbols = [...symbols.slice(0, bestIdx), merged, ...symbols.slice(bestIdx + 2)];\n }\n\n // Convert symbols to IDs\n const ids: number[] = [];\n for (const sym of symbols) {\n const id = this.vocab.get(sym);\n if (id !== undefined) {\n ids.push(id);\n } else {\n // Byte fallback for unknown symbols\n ids.push(...this.encodeByteFallback(sym));\n }\n }\n return ids;\n }\n\n private encodeByteFallback(text: string): number[] {\n const encoder = new TextEncoder();\n const bytes = encoder.encode(text);\n const ids: number[] = [];\n for (const b of bytes) {\n const tok = this.byteFallback.get(b);\n if (tok) {\n const id = this.vocab.get(tok);\n if (id !== undefined) ids.push(id);\n }\n }\n return ids;\n }\n}\n","/**\n * WeightSource — a tensor store the loader/executor can pull from one tensor at a\n * time, so the *whole* model never has to sit in the JS heap simultaneously.\n *\n * Two backends share one async-shaped interface:\n *\n * • HeapWeightStore (Node / desktop) — wraps a plain `Map`. Identical behavior\n * to the original code path: tensors are held in heap, gets/sets are O(1) heap\n * ops (the async methods resolve synchronously). Zero behavior change.\n *\n * • CacheWeightStore (browser / mobile) — backs every tensor's BYTES in the\n * CacheStorage API (already populated per-tensor during download). The heap\n * holds only lightweight descriptors ({ shape, dtype, cacheKey, byteLength })\n * plus a small set of tiny \"override\" tensors. `get()` reads one tensor's\n * bytes back from cache on demand (applying any BF16/F16→F32 conversion) and\n * `set()` writes transform outputs straight back to cache without retaining\n * the bytes. This bounds peak heap to roughly ONE tensor at a time instead of\n * the entire weight set — which is what lets a 2.5 GB model load on iOS Safari\n * without the tab being jetsam-killed.\n *\n * The browser path is selected automatically when `typeof caches !== \"undefined\"`.\n */\n\nimport { opfsAvailable, opfsRead } from \"./opfs-cache.js\";\nimport { bf16ToF32, f16ToF32, type SafetensorDType } from \"./safetensors.js\";\n\n/** A single tensor's data + shape (the unit the executor uploads to a GPU buffer). */\nexport interface WeightEntry {\n data: ArrayBufferView;\n shape: number[];\n}\n\n/**\n * Read-side view consumed by the executor's streaming `uploadWeights`. Async by\n * design so a cache-backed store can fetch one tensor's bytes at a time.\n */\nexport interface WeightSource {\n has(name: string): boolean;\n keys(): string[];\n readonly size: number;\n /** Pull a single tensor (bytes materialized + dtype-converted on demand). */\n get(name: string): Promise<WeightEntry | undefined>;\n /**\n * Release any transient backing storage (e.g. the browser transform-staging\n * cache) once the consumer has finished uploading. No-op for the heap backend.\n */\n dispose?(): Promise<void>;\n}\n\n/**\n * Mutable store used while loading. The loader's post-download transforms\n * (RMSNorm +1 bake, fused-Q split, GPTQ/MLX repack, INT4 quantize, PLE/vision\n * fixups) read tensors, produce new ones, and drop sources through this\n * interface. All ops are async so the cache backend can stream bytes; the heap\n * backend resolves them synchronously.\n */\nexport interface MutableWeightStore extends WeightSource {\n set(name: string, entry: WeightEntry): Promise<void>;\n delete(name: string): Promise<void>;\n}\n\n// ── Heap backend (Node / desktop) — unchanged behavior ──────────────────────\n\nexport class HeapWeightStore implements MutableWeightStore {\n private map: Map<string, WeightEntry>;\n\n constructor(map?: Map<string, WeightEntry>) {\n this.map = map ?? new Map();\n }\n\n has(name: string): boolean {\n return this.map.has(name);\n }\n\n keys(): string[] {\n return [...this.map.keys()];\n }\n\n get size(): number {\n return this.map.size;\n }\n\n get(name: string): Promise<WeightEntry | undefined> {\n return Promise.resolve(this.map.get(name));\n }\n\n set(name: string, entry: WeightEntry): Promise<void> {\n this.map.set(name, entry);\n return Promise.resolve();\n }\n\n delete(name: string): Promise<void> {\n this.map.delete(name);\n return Promise.resolve();\n }\n\n /** Escape hatch for the few Node paths that still want the raw Map. */\n asMap(): Map<string, WeightEntry> {\n return this.map;\n }\n}\n\n// ── Cache backend (browser / mobile) — bounded peak heap ─────────────────────\n\n/**\n * A descriptor of a tensor whose bytes live in CacheStorage rather than the heap.\n * `dtype` is the *source* safetensors dtype so `get()` can reproduce the same\n * BF16/F16→F32 conversion the heap path applies inline during download.\n */\ninterface CacheDescriptor {\n shape: number[];\n /** Source safetensors dtype, or \"RAW\" for already-converted/derived bytes. */\n dtype: SafetensorDType | \"RAW\";\n /** Which CacheStorage bucket holds the bytes (download cache vs staging cache). */\n cacheName: string;\n cacheKey: string;\n /** TypedArray ctor tag for RAW (transform-produced) bytes. */\n view?: \"f32\" | \"u32\" | \"i32\" | \"u16\" | \"u8\";\n}\n\n/**\n * Build a typed-array view over a freshly-read (offset-0) ArrayBuffer. A cache\n * read always returns a fresh, offset-0 buffer, so it is aligned for every dtype\n * — no copy needed (unlike the merged-range download path which can be unaligned).\n */\nfunction viewForSourceDtype(buf: ArrayBuffer, dtype: SafetensorDType): ArrayBufferView {\n switch (dtype) {\n case \"F32\":\n return new Float32Array(buf, 0, buf.byteLength / 4);\n case \"I32\":\n return new Int32Array(buf, 0, buf.byteLength / 4);\n case \"U32\":\n return new Uint32Array(buf, 0, buf.byteLength / 4);\n case \"F16\":\n case \"BF16\":\n return new Uint16Array(buf, 0, buf.byteLength / 2);\n default:\n return new Uint8Array(buf, 0, buf.byteLength);\n }\n}\n\nfunction viewForRaw(buf: ArrayBuffer, tag: CacheDescriptor[\"view\"]): ArrayBufferView {\n switch (tag) {\n case \"u32\":\n return new Uint32Array(buf, 0, buf.byteLength / 4);\n case \"i32\":\n return new Int32Array(buf, 0, buf.byteLength / 4);\n case \"u16\":\n return new Uint16Array(buf, 0, buf.byteLength / 2);\n case \"u8\":\n return new Uint8Array(buf, 0, buf.byteLength);\n default:\n return new Float32Array(buf, 0, buf.byteLength / 4);\n }\n}\n\nfunction rawTag(data: ArrayBufferView): CacheDescriptor[\"view\"] {\n if (data instanceof Float32Array) return \"f32\";\n if (data instanceof Uint32Array) return \"u32\";\n if (data instanceof Int32Array) return \"i32\";\n if (data instanceof Uint16Array) return \"u16\";\n return \"u8\";\n}\n\nconst CACHE_BACKED_NAME = \"gerbil-weights-staging-v2\";\n\nexport class CacheWeightStore implements MutableWeightStore {\n private descriptors = new Map<string, CacheDescriptor>();\n /**\n * Small heap-resident tensors that are NOT worth a cache round-trip: synthetic\n * fill constants, sub-MB norm/scalar tensors, and anything a caller explicitly\n * pins (e.g. the vision pos-embed table that index.ts snapshots before upload).\n * Keeping these in heap is fine — they are individually tiny.\n */\n private overrides = new Map<string, WeightEntry>();\n private cacheName: string;\n\n /** Tensors at or below this size stay in heap rather than round-tripping cache. */\n private static readonly HEAP_THRESHOLD = 512 * 1024; // 512 KB\n\n constructor(cacheName: string = CACHE_BACKED_NAME) {\n this.cacheName = cacheName;\n }\n\n private static stagingKey(name: string): string {\n return `gerbil-staging:${name}`;\n }\n\n /**\n * Register a tensor whose bytes already live in CacheStorage under\n * `downloadCacheName`/`cacheKey` (the per-tensor download cache). No bytes are\n * retained in heap. The descriptor records which cache bucket to read from so\n * `get()` opens the right one (download cache vs this store's staging cache).\n */\n registerCached(\n name: string,\n shape: number[],\n dtype: SafetensorDType,\n cacheKey: string,\n downloadCacheName: string,\n ): void {\n this.overrides.delete(name);\n this.descriptors.set(name, { shape, dtype, cacheName: downloadCacheName, cacheKey });\n }\n\n has(name: string): boolean {\n return this.overrides.has(name) || this.descriptors.has(name);\n }\n\n keys(): string[] {\n const set = new Set<string>(this.descriptors.keys());\n for (const k of this.overrides.keys()) set.add(k);\n return [...set];\n }\n\n get size(): number {\n return this.keys().length;\n }\n\n async get(name: string): Promise<WeightEntry | undefined> {\n const pinned = this.overrides.get(name);\n if (pinned) return pinned;\n const desc = this.descriptors.get(name);\n if (!desc) return undefined;\n\n // Tensors registered from the durable per-tensor download cache may live in\n // OPFS (the iOS-durable backend) rather than CacheStorage — model-loader's\n // browserCacheWrite prefers OPFS. Probe OPFS by the same key first, then fall\n // back to CacheStorage. RAW transform-staging entries (written via cache.put\n // in set() below) are never in OPFS, so this probe simply misses for them.\n let buf: ArrayBuffer | null = null;\n if (desc.dtype !== \"RAW\" && opfsAvailable()) {\n buf = await opfsRead(desc.cacheKey);\n }\n if (!buf) {\n const cache = await caches.open(desc.cacheName);\n const resp = await cache.match(new Request(desc.cacheKey));\n if (!resp) return undefined;\n buf = await resp.arrayBuffer();\n }\n\n if (desc.dtype === \"RAW\") {\n return { data: viewForRaw(buf, desc.view), shape: desc.shape };\n }\n let data = viewForSourceDtype(buf, desc.dtype);\n // Mirror the heap path's inline conversion (model-loader.ts ~860-933).\n if (desc.dtype === \"BF16\") {\n data = bf16ToF32(new Uint8Array(data.buffer, data.byteOffset, data.byteLength));\n } else if (desc.dtype === \"F16\") {\n data = f16ToF32(data);\n }\n return { data, shape: desc.shape };\n }\n\n async set(name: string, entry: WeightEntry): Promise<void> {\n this.descriptors.delete(name);\n // Tiny tensors stay in heap; large ones write through to cache so their bytes\n // do not accumulate in the heap as transforms run layer-by-layer.\n if (entry.data.byteLength <= CacheWeightStore.HEAP_THRESHOLD) {\n this.overrides.set(name, entry);\n return;\n }\n this.overrides.delete(name);\n const cacheKey = CacheWeightStore.stagingKey(name);\n const bytes = entry.data.buffer.slice(\n entry.data.byteOffset,\n entry.data.byteOffset + entry.data.byteLength,\n ) as ArrayBuffer;\n const cache = await caches.open(this.cacheName);\n await cache.put(new Request(cacheKey), new Response(bytes));\n this.descriptors.set(name, {\n shape: entry.shape,\n dtype: \"RAW\",\n cacheName: this.cacheName,\n cacheKey,\n view: rawTag(entry.data),\n });\n }\n\n async delete(name: string): Promise<void> {\n this.overrides.delete(name);\n const desc = this.descriptors.get(name);\n this.descriptors.delete(name);\n // Only evict staging entries we created; never evict the durable per-tensor\n // download cache (a future load reuses those by stable byte-range key).\n if (desc && desc.dtype === \"RAW\" && desc.cacheName === this.cacheName) {\n try {\n const cache = await caches.open(this.cacheName);\n await cache.delete(new Request(desc.cacheKey));\n } catch {\n /* best-effort */\n }\n }\n }\n\n /** Drop all transform-staging cache entries (call after upload completes). */\n async dispose(): Promise<void> {\n try {\n await caches.delete(this.cacheName);\n } catch {\n /* best-effort */\n }\n }\n}\n\n/** True when the browser CacheStorage API is available (i.e. not Node). */\nexport function cacheStorageAvailable(): boolean {\n return typeof caches !== \"undefined\";\n}\n","/**\n * Model loader -- fetches model files from HuggingFace Hub.\n *\n * Handles:\n * - Fetching config.json, tokenizer.json, tokenizer_config.json\n * - Downloading safetensors weight files (with progress callbacks)\n * - HF key -> canonical key mapping\n * - Uploading weight data to GPU buffers via the Executor\n */\n\nimport { type GraphDType, generateGraph, type KVDType } from \"./architectures/index.js\";\nimport { foldNanoCodecWeightNorm, nanoCodecWeightMap } from \"./architectures/kani_tts.js\";\nimport { repackGPTQ } from \"./gptq-adapter.js\";\nimport { idbAllKeys, idbCacheAvailable, idbGet, idbHas, idbPut } from \"./idb-cache.js\";\nimport type { ModelGraph } from \"./ir.js\";\nimport { CANONICAL_KEYS, createDefaultHFKeyMapper, type HFKeyMapper } from \"./ir.js\";\nimport { repackMLX } from \"./mlx-adapter.js\";\nimport { opfsEvictStale } from \"./opfs-cache.js\";\nimport { DEFAULT_GROUP_SIZE, quantizeInt4 } from \"./quantize.js\";\nimport {\n bf16ToF32,\n f16ToF32,\n getTensorData,\n parseSafetensorsHeader,\n type SafetensorEntry,\n type SafetensorsFile,\n} from \"./safetensors.js\";\nimport { Tokenizer } from \"./tokenizer.js\";\nimport {\n CacheWeightStore,\n cacheStorageAvailable,\n HeapWeightStore,\n type MutableWeightStore,\n type WeightSource,\n} from \"./weight-source.js\";\n\nexport interface LoadModelOptions {\n /** HF repo ID (e.g. \"Qwen/Qwen3.5-0.8B\") or full URL. */\n repo: string;\n /** Progress callback: (loaded, total, message) */\n onProgress?: (loaded: number, total: number, message: string) => void;\n /** Custom HF key mapper (defaults to stripping \"model.\" prefix). */\n keyMapper?: HFKeyMapper;\n /** HuggingFace API token for gated models. */\n hfToken?: string;\n /** Revision/branch (default: \"main\"). */\n revision?: string;\n /** Local cache directory for downloaded files (Node.js only). */\n cacheDir?: string;\n /**\n * Weight dtype:\n * - \"f32\" full precision (or the repo's native quantization, e.g. MLX/GPTQ q4)\n * - \"q4\" on-the-fly INT4 quantization (~4× smaller)\n * - \"auto\" (recommended) picks q4 on mobile (iOS/Android) to fit in device\n * memory and f32/native on desktop. Already-quantized repos\n * (MLX/GPTQ 4-bit) stay q4 regardless.\n */\n dtype?: GraphDType | \"auto\";\n /** KV cache dtype: \"f16\" halves memory traffic during attention. Requires GPU f16 support. */\n kvDtype?: KVDType;\n /**\n * Build an embedding graph (last-token pool + L2 norm) instead of an LM head.\n * Only valid for Qwen2/Qwen3 CausalLM architectures (e.g. Qwen3-Embedding).\n */\n embedding?: boolean;\n /**\n * Build the multimodal LM graph variant (M-RoPE + image-embedding splice) so\n * the text model can consume spliced image tokens. Only meaningful for\n * Qwen3_5ForConditionalGeneration. Reserves `maxVisionTokens` rows for the\n * vision-embedding buffer. Text-only generation through this graph is\n * numerically identical to the non-multimodal graph (M-RoPE fed linear\n * positions == standard 1D RoPE).\n */\n multimodal?: { maxVisionTokens: number };\n /**\n * Force-download and key-map the vision tower even without the multimodal LM\n * graph. `enableVision` (via `multimodal`) already implies this; this flag is\n * for callers that load weights directly (e.g. the vision-encoder validation\n * scripts) and build the vision graph/executor themselves. When neither this\n * nor `multimodal` is set, the ~201MB ViT is excluded from the download.\n */\n loadVisionTower?: boolean;\n}\n\n/**\n * Gemma 4 Per-Layer-Embeddings (PLE) source, kept CPU-resident.\n *\n * The PLE table (`embed_tokens_per_layer`, [vocab, num_layers*256]) is ~1.17GB\n * at 4-bit. Uploading it to a GPU buffer would make the model non-mobile-viable\n * and would hit the per-binding size cap. Instead the loader hands the quantized\n * table to the executor in JS memory; the executor gathers + dequantizes only the\n * rows for the current input tokens each forward step (a tiny [T, width] upload).\n */\nexport interface PleSource {\n /**\n * Flat row-major INT4 nibbles (Gerbil packing, 8 per u32). HEAP-RESIDENT path\n * (Node/desktop). In the browser this is empty and `cache` is set instead so\n * the ~1.17 GB table never sits in the JS heap during load.\n */\n packed: Uint32Array;\n /** Per-group scales (Gerbil (nibble - zero) * scale convention). */\n scales: Float32Array;\n /** Per-group zero points. */\n zeros: Float32Array;\n /** Row width = num_layers * hidden_size_per_layer_input (E2B: 35*256 = 8960). */\n width: number;\n /** Dequant group size (MLX: 64). */\n groupSize: number;\n /** Activation tensor the per-step gathered rows are written into. */\n targetTensor: string;\n /**\n * Browser only: when set, the quantized PLE table's bytes live in CacheStorage\n * (not the heap). The executor reads the slice of nibbles/scales/zeros it needs\n * for the current tokens on demand. Keeps peak load heap bounded.\n */\n cache?: {\n cacheName: string;\n packedKey: string;\n scalesKey: string;\n zerosKey: string;\n /** packed.length (u32 count) — for bounds/Range math. */\n packedLen: number;\n };\n}\n\nexport interface LoadedModel {\n /** The generated computation graph (IR). */\n graph: ModelGraph;\n /** The tokenizer. */\n tokenizer: Tokenizer;\n /**\n * Weight tensors mapped to canonical names. A `WeightSource` so the executor\n * can pull one tensor at a time (cache-backed in the browser, heap-backed on\n * Node) instead of requiring the whole model to sit in heap at once. Use\n * `get(name)` (async) to materialize a tensor's bytes on demand.\n */\n weights: WeightSource;\n /** Raw config.json for reference. */\n rawConfig: Record<string, unknown>;\n /**\n * CPU-resident Gemma 4 PLE table (set only for Gemma 4). Pass to\n * `executor.setPleSource()` so the big table never becomes GPU-resident.\n */\n pleSource?: PleSource;\n}\n\n/**\n * Resolve a repo string to a base URL for HuggingFace Hub API.\n */\nfunction resolveHFBaseURL(repo: string, revision: string): string {\n // If it's already a full URL, use it\n if (repo.startsWith(\"http://\") || repo.startsWith(\"https://\")) {\n return repo;\n }\n // Standard HF Hub URL\n return `https://huggingface.co/${repo}/resolve/${revision}`;\n}\n\n// ── File cache helpers (Node.js only) ──────────────────────────────────\n\nlet _fs: typeof import(\"node:fs\") | null = null;\nlet _path: typeof import(\"node:path\") | null = null;\nlet _fsInitialized = false;\n\nasync function initFs(): Promise<void> {\n if (_fsInitialized) return;\n _fsInitialized = true;\n try {\n _fs = await import(\"node:fs\");\n _path = await import(\"node:path\");\n } catch {\n // Not in Node.js (browser) — caching disabled\n }\n}\n\nfunction getCachePath(cacheDir: string, filename: string): string {\n return _path!.join(cacheDir, filename.replace(/\\//g, \"_\"));\n}\n\nfunction readCachedFile(cacheDir: string, filename: string): Buffer | null {\n if (!_fs || !_path) return null;\n const p = getCachePath(cacheDir, filename);\n try {\n return _fs.readFileSync(p);\n } catch {\n return null;\n }\n}\n\nfunction writeCacheFile(\n cacheDir: string,\n filename: string,\n data: ArrayBuffer | Buffer | Uint8Array,\n): void {\n if (!_fs || !_path) return;\n _fs.mkdirSync(cacheDir, { recursive: true });\n const p = getCachePath(cacheDir, filename);\n let buf: Buffer;\n if (Buffer.isBuffer(data)) {\n buf = data;\n } else if (data instanceof Uint8Array) {\n buf = Buffer.from(data);\n } else {\n buf = Buffer.from(data);\n }\n _fs.writeFileSync(p, buf);\n}\n\n// ── Browser cache helpers (OPFS-durable, CacheStorage fallback) ─────────\n//\n// Two-tier durable cache behind a single read/write/has seam:\n//\n// 1. OPFS via a dedicated Worker (`createSyncAccessHandle`, chunked writes) —\n// the ONLY iOS-safe durable path. On iOS Safari the CacheStorage API is\n// evicted under quota pressure in a plain tab (`navigator.storage.persist()`\n// is not granted outside an installed PWA), so weights cached there are\n// re-downloaded every visit. OPFS survives a full tab close + reopen. Main-\n// thread OPFS `createWritable` throws \"out of quota\" mid-write on iOS, so all\n// OPFS I/O runs on a blob-URL worker with sync access handles. See\n// src/gpu/opfs-cache.ts.\n//\n// 2. CacheStorage (`caches.open(...)`) — fallback when OPFS/Worker is\n// unavailable (older browsers, some embedded webviews). Best-effort durable;\n// fine on desktop where quota pressure is rare.\n//\n// Reads prefer OPFS, then fall back to CacheStorage. Writes go to OPFS when\n// available and only to CacheStorage otherwise (no double-write — OPFS is the\n// durable winner and double-storing would waste the ~404 MB twice against quota).\n// All of this is inert on Node: `opfsAvailable()` and `typeof caches` are both\n// false there, so the helpers short-circuit and the Node weight path is\n// unaffected.\n\n// v4: storage layout changed (OPFS subdirectory `gerbil-models-v4` + CacheStorage\n// bucket of the same name). A fresh namespace also abandons any entries poisoned\n// during earlier buggy builds (the reload-loop era wrote right-size-wrong-content\n// tensors that size validation can't detect). The per-tensor cache logic itself\n// is verified correct by scripts/engine/test-cache-roundtrip.mjs (load → cache →\n// reload-from-cache → identical coherent output).\nconst BROWSER_CACHE_NAME = \"gerbil-models-v4\";\n\n// Per-tensor weight caching. ON by default — the round-trip test\n// (scripts/engine/test-cache-roundtrip.mjs) proves the cache read path is\n// correct, and the v3 namespace abandons entries poisoned by earlier buggy\n// builds. Can be force-disabled via GERBIL_TENSOR_CACHE=0 / globalThis flag if a\n// corruption issue ever resurfaces.\nconst TENSOR_CACHE_ENABLED =\n (typeof process === \"undefined\" || process.env?.GERBIL_TENSOR_CACHE !== \"0\") &&\n (globalThis as { GERBIL_TENSOR_CACHE?: boolean }).GERBIL_TENSOR_CACHE !== false;\n\nlet _persistRequested = false;\nasync function requestPersistence(): Promise<void> {\n if (_persistRequested) return;\n _persistRequested = true;\n try {\n // Only granted in an installed PWA on iOS; harmless best-effort elsewhere.\n await (navigator as any)?.storage?.persist?.();\n } catch {\n /* best-effort */\n }\n}\n\n// Model weights are cached in IndexedDB (see idb-cache.ts for why: the Cache API's\n// fragment-stripping collapsed our per-tensor keys → wrong bytes/garbage, and OPFS\n// via Worker was fragile on iOS). IDB keys on the exact string — no footguns. All\n// three helpers are inert on Node (no indexedDB → miss/no-op), so the heap load\n// path is unchanged off the browser.\n\n/** Read a cache key's bytes from IndexedDB, or null on miss. */\nasync function browserCacheRead(cacheKey: string): Promise<ArrayBuffer | null> {\n return idbGet(cacheKey);\n}\n\n/** Cheap presence check — true without materializing the body. */\nasync function browserCacheHas(cacheKey: string): Promise<boolean> {\n return idbHas(cacheKey);\n}\n\n/**\n * Write a cache key's bytes to IndexedDB (best-effort; never throws). The\n * `transferable` flag is accepted for call-site compatibility but unused — IDB\n * structured-clones the value, so the caller's buffer is never detached.\n */\nasync function browserCacheWrite(\n cacheKey: string,\n data: ArrayBuffer,\n _transferable = false,\n): Promise<void> {\n await requestPersistence();\n await idbPut(cacheKey, data);\n}\n\n/**\n * Reclaim quota from the LEGACY caches, ONCE per session. The model cache is now\n * IndexedDB; the old CacheStorage (`gerbil-models-*`, fragment-keyed → the `RRRR`\n * corruption) and OPFS-worker stores are dead. iOS Safari's per-origin quota is\n * ~1 GB and that orphaned junk could consume nearly all of it, so we purge ALL of\n * it to free room for the IDB cache. Best-effort; inert on Node.\n */\nlet _evictedStale = false;\nasync function evictStaleCaches(): Promise<void> {\n if (_evictedStale) return;\n _evictedStale = true;\n try {\n if (typeof caches !== \"undefined\") {\n const keys = await caches.keys();\n await Promise.all(\n keys.filter((k) => k.startsWith(\"gerbil-models-\")).map((k) => caches.delete(k)),\n );\n }\n } catch {\n /* best-effort */\n }\n await opfsEvictStale();\n}\n\n// ── Fetch helpers ──────────────────────────────────────────────────────\n\n/**\n * Fetch a JSON file from HF with optional auth and caching.\n */\nasync function fetchJSON(\n baseURL: string,\n filename: string,\n hfToken?: string,\n cacheDir?: string,\n): Promise<any> {\n // Check Node.js cache first\n if (cacheDir) {\n const cached = readCachedFile(cacheDir, filename);\n if (cached) return JSON.parse(cached.toString(\"utf-8\"));\n }\n\n // Check browser cache\n const browserKey = `${baseURL}/${filename}`;\n const browserCached = await browserCacheRead(browserKey);\n if (browserCached) {\n return JSON.parse(new TextDecoder().decode(browserCached));\n }\n\n const url = `${baseURL}/${filename}`;\n const headers: Record<string, string> = {};\n if (hfToken) {\n headers.Authorization = `Bearer ${hfToken}`;\n }\n const response = await fetch(url, { headers });\n if (!response.ok) {\n throw new Error(`Failed to fetch ${url}: ${response.status} ${response.statusText}`);\n }\n const text = await response.text();\n\n // Write to Node.js cache\n if (cacheDir) {\n writeCacheFile(cacheDir, filename, Buffer.from(text, \"utf-8\"));\n }\n\n // Write to browser cache\n await browserCacheWrite(browserKey, new TextEncoder().encode(text).buffer);\n\n return JSON.parse(text);\n}\n\n/**\n * Fetch a UTF-8 text file from HF (with caching). Returns null on 404 / error.\n * Used for sidecar files like `chat_template.jinja` that some models ship\n * instead of an inline `chat_template` in tokenizer_config.json.\n */\nasync function fetchText(\n baseURL: string,\n filename: string,\n hfToken?: string,\n cacheDir?: string,\n): Promise<string | null> {\n if (cacheDir) {\n const cached = readCachedFile(cacheDir, filename);\n if (cached) return cached.toString(\"utf-8\");\n }\n const browserKey = `${baseURL}/${filename}`;\n const browserCached = await browserCacheRead(browserKey);\n if (browserCached) {\n return new TextDecoder().decode(browserCached);\n }\n const url = `${baseURL}/${filename}`;\n const headers: Record<string, string> = {};\n if (hfToken) {\n headers.Authorization = `Bearer ${hfToken}`;\n }\n const response = await fetch(url, { headers });\n if (!response.ok) return null;\n const text = await response.text();\n if (cacheDir) {\n writeCacheFile(cacheDir, filename, Buffer.from(text, \"utf-8\"));\n }\n await browserCacheWrite(browserKey, new TextEncoder().encode(text).buffer);\n return text;\n}\n\n/**\n * Fetch a binary file from HF with progress tracking and caching.\n */\nasync function fetchBinary(\n baseURL: string,\n filename: string,\n hfToken?: string,\n onProgress?: (loaded: number, total: number) => void,\n cacheDir?: string,\n): Promise<ArrayBuffer> {\n // Check cache first\n if (cacheDir) {\n const cached = readCachedFile(cacheDir, filename);\n if (cached) {\n onProgress?.(cached.byteLength, cached.byteLength);\n return cached.buffer.slice(\n cached.byteOffset,\n cached.byteOffset + cached.byteLength,\n ) as ArrayBuffer;\n }\n }\n\n const url = `${baseURL}/${filename}`;\n const headers: Record<string, string> = {};\n if (hfToken) {\n headers.Authorization = `Bearer ${hfToken}`;\n }\n\n const response = await fetch(url, { headers });\n if (!response.ok) {\n throw new Error(`Failed to fetch ${url}: ${response.status} ${response.statusText}`);\n }\n\n const contentLength = Number(response.headers.get(\"content-length\") || 0);\n\n if (!response.body || !onProgress) {\n const buf = await response.arrayBuffer();\n if (cacheDir) writeCacheFile(cacheDir, filename, buf);\n return buf;\n }\n\n // Stream with progress\n const reader = response.body.getReader();\n const chunks: Uint8Array[] = [];\n let loaded = 0;\n\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n chunks.push(value);\n loaded += value.byteLength;\n onProgress(loaded, contentLength);\n }\n\n // Combine chunks into single ArrayBuffer\n const result = new Uint8Array(loaded);\n let offset = 0;\n for (const chunk of chunks) {\n result.set(chunk, offset);\n offset += chunk.byteLength;\n }\n\n if (cacheDir) writeCacheFile(cacheDir, filename, result);\n return result.buffer;\n}\n\n/**\n * Discover safetensors files in the repo.\n *\n * Models can have a single `model.safetensors` or multiple shards:\n * `model-00001-of-00002.safetensors`, `model-00002-of-00002.safetensors`\n *\n * We check for the index file first, then fall back to single file.\n */\nasync function discoverSafetensorsFiles(\n baseURL: string,\n hfToken?: string,\n cacheDir?: string,\n): Promise<string[]> {\n // Try the index file first (for sharded models)\n try {\n const index = await fetchJSON(baseURL, \"model.safetensors.index.json\", hfToken, cacheDir);\n if (index.weight_map) {\n // Get unique filenames from the weight map\n const files = [...new Set(Object.values(index.weight_map))] as string[];\n return files.sort();\n }\n } catch {\n // No index file -- try single file\n }\n\n return [\"model.safetensors\"];\n}\n\n/**\n * Fetch a byte range from a URL over the network (no caching).\n */\nasync function fetchRangeRaw(\n url: string,\n start: number,\n end: number,\n hfToken?: string,\n): Promise<ArrayBuffer> {\n const headers: Record<string, string> = {\n Range: `bytes=${start}-${end - 1}`,\n };\n if (hfToken) {\n headers.Authorization = `Bearer ${hfToken}`;\n }\n // Retry transient failures with backoff. HuggingFace rate-limits unauthenticated\n // downloads (403/429), and CDN/network hiccups happen; a few backed-off retries\n // turn those into a slightly slower load instead of a hard failure. (Once the\n // model is cached in IDB these requests don't happen again.)\n let lastStatus = 0;\n for (let attempt = 0; attempt < 4; attempt++) {\n let resp: Response;\n try {\n resp = await fetch(url, { headers });\n } catch {\n // Network error — back off and retry.\n if (attempt < 3) {\n await new Promise((r) => setTimeout(r, 600 * 2 ** attempt));\n continue;\n }\n throw new Error(`Range request failed: network error for ${url}`);\n }\n if (resp.ok || resp.status === 206) return resp.arrayBuffer();\n lastStatus = resp.status;\n // 403/429 = rate limit (HF), 5xx = transient server error → retry; else fail.\n const retriable = resp.status === 403 || resp.status === 429 || resp.status >= 500;\n if (retriable && attempt < 3) {\n await new Promise((r) => setTimeout(r, 600 * 2 ** attempt));\n continue;\n }\n break;\n }\n throw new Error(\n `Range request failed: ${lastStatus}${lastStatus === 403 || lastStatus === 429 ? \" (HuggingFace rate limit — wait a bit and retry; once cached this won't recur)\" : \"\"}`,\n );\n}\n\n/**\n * Fetch a byte range from a URL, caching the exact range.\n *\n * Used for small, stable ranges (e.g. safetensors headers) whose boundaries\n * don't depend on which tensors a particular load needs. Tensor *bodies* are\n * NOT fetched through here — they're cached per-tensor by stable byte-range in\n * fetchSelectiveTensors so a vision load can reuse a text load's tensors.\n */\nasync function fetchRange(\n url: string,\n start: number,\n end: number,\n hfToken?: string,\n): Promise<ArrayBuffer> {\n // Check browser cache for this range\n const browserKey = `${url}?_range=${start}-${end}`;\n const browserCached = await browserCacheRead(browserKey);\n if (browserCached) return browserCached;\n\n const buf = await fetchRangeRaw(url, start, end, hfToken);\n\n // Cache for next time\n await browserCacheWrite(browserKey, buf);\n\n return buf;\n}\n\n/**\n * Fetch the safetensors header only (two range requests: 8 bytes + header).\n */\nasync function fetchSafetensorsHeader(url: string, hfToken?: string): Promise<SafetensorsFile> {\n // Fetch first 8 bytes to get header length\n const lengthBuf = await fetchRange(url, 0, 8, hfToken);\n const headerLength = Number(new DataView(lengthBuf).getBigUint64(0, true));\n\n // Fetch full header (8 + headerLength bytes)\n const headerBuf = await fetchRange(url, 0, 8 + headerLength, hfToken);\n return parseSafetensorsHeader(headerBuf);\n}\n\n/** A contiguous byte range to download. */\ninterface ByteRange {\n start: number;\n end: number;\n entries: SafetensorEntry[];\n}\n\n/**\n * Merge close byte ranges to reduce HTTP requests.\n *\n * - `gapThreshold`: merge two ranges if the gap between them is smaller than this\n * (avoids an extra request for a tiny skipped gap).\n * - `maxRangeBytes`: hard cap on a single merged range. Without this, a\n * contiguously-packed safetensors merges into ONE range = the whole model, so\n * the loader buffers the entire file in a single fetch — no streaming progress\n * (the bar sits at 0/N until done) and a transient allocation big enough to\n * crash mobile Safari. Capping splits the download into bounded segments that\n * report progress as they land and keep peak memory low.\n */\nfunction mergeRanges(\n entries: SafetensorEntry[],\n dataStart: number,\n gapThreshold: number = 1024 * 1024,\n maxRangeBytes: number = Number.POSITIVE_INFINITY,\n): ByteRange[] {\n if (entries.length === 0) return [];\n\n // Sort by offset\n const sorted = [...entries].sort((a, b) => a.dataOffset - b.dataOffset);\n\n const ranges: ByteRange[] = [];\n let current: ByteRange = {\n start: dataStart + sorted[0].dataOffset,\n end: dataStart + sorted[0].dataOffset + sorted[0].dataLength,\n entries: [sorted[0]],\n };\n\n for (let i = 1; i < sorted.length; i++) {\n const entryStart = dataStart + sorted[i].dataOffset;\n const entryEnd = entryStart + sorted[i].dataLength;\n\n const withinGap = entryStart - current.end <= gapThreshold;\n const withinCap = entryEnd - current.start <= maxRangeBytes;\n if (withinGap && withinCap) {\n // Merge: extend the range (may include some skipped bytes, but avoids extra request)\n current.end = Math.max(current.end, entryEnd);\n current.entries.push(sorted[i]);\n } else {\n ranges.push(current);\n current = { start: entryStart, end: entryEnd, entries: [sorted[i]] };\n }\n }\n ranges.push(current);\n return ranges;\n}\n\n/**\n * Selectively download needed tensors from a safetensors file using Range requests.\n * Returns a map of tensor name → { data, shape }.\n */\n/** Byte alignment required for a safetensors dtype's typed-array view. */\nfunction dtypeAlign(dtype: string): number {\n if (dtype === \"F64\" || dtype === \"I64\" || dtype === \"U64\") return 8;\n if (dtype === \"F32\" || dtype === \"I32\" || dtype === \"U32\") return 4;\n if (dtype === \"F16\" || dtype === \"BF16\" || dtype === \"I16\" || dtype === \"U16\") return 2;\n return 1;\n}\n\n/** Build the appropriate typed-array view of a tensor at `byteOffset` in `buf`. */\nfunction makeTensorView(\n buf: ArrayBuffer,\n byteOffset: number,\n entry: SafetensorEntry,\n): ArrayBufferView {\n // Aligned views can alias the buffer directly; unaligned ones must be copied\n // into a fresh (offset-0) buffer so the typed-array constructor accepts them.\n const aligned = byteOffset % dtypeAlign(entry.dtype) === 0;\n const src = aligned ? buf : buf.slice(byteOffset, byteOffset + entry.dataLength);\n const off = aligned ? byteOffset : 0;\n switch (entry.dtype) {\n case \"F32\":\n return new Float32Array(src, off, entry.dataLength / 4);\n case \"I32\":\n return new Int32Array(src, off, entry.dataLength / 4);\n case \"U32\":\n return new Uint32Array(src, off, entry.dataLength / 4);\n case \"F16\":\n case \"BF16\":\n return new Uint16Array(src, off, entry.dataLength / 2);\n default:\n return new Uint8Array(src, off, entry.dataLength);\n }\n}\n\n/**\n * Stable per-tensor browser-cache key. Keyed by the tensor's *absolute* byte\n * range in the file (offset + length), which is fixed regardless of which other\n * tensors a given load requests. This is what lets a vision load reuse the\n * tensors a prior text load already downloaded — they hit the same keys —\n * instead of re-downloading because the merged Range boundaries differ.\n */\nfunction tensorCacheKey(url: string, file: SafetensorsFile, entry: SafetensorEntry): string {\n const absStart = file.dataStart + entry.dataOffset;\n return `${url}#t:${absStart}-${entry.dataLength}`;\n}\n\n/**\n * Selectively download needed tensors from a safetensors file using Range\n * requests.\n *\n * Two modes, gated by `cacheStore`:\n *\n * • `cacheStore` supplied (browser): each tensor's bytes are written to the\n * per-tensor download cache (as before) and the tensor is REGISTERED in the\n * store as a cache-backed descriptor under its canonical name — its bytes are\n * NOT retained in heap. The returned Map is empty. This is the change that\n * bounds peak heap during download: instead of accumulating the whole model\n * in the returned Map, the heap only ever holds the current range segment\n * (≤ maxRangeBytes, 24 MB on mobile) which is released each iteration.\n *\n * • `cacheStore` omitted (used by direct-load helpers): returns a Map of\n * name → { data, shape } exactly as before.\n */\nasync function fetchSelectiveTensors(\n url: string,\n file: SafetensorsFile,\n neededEntries: SafetensorEntry[],\n hfToken?: string,\n onProgress?: (loaded: number, total: number, message?: string) => void,\n cacheStore?: { store: CacheWeightStore; keyMapper: HFKeyMapper },\n): Promise<Map<string, { data: ArrayBufferView; shape: number[] }>> {\n const results = new Map<string, { data: ArrayBufferView; shape: number[] }>();\n const totalBytes = neededEntries.reduce((sum, e) => sum + e.dataLength, 0);\n let loadedBytes = 0;\n\n const isMobile =\n typeof navigator !== \"undefined\" && /iPhone|iPad|iPod|Android/i.test(navigator.userAgent);\n // Cap merged-range size so the download streams in bounded segments: keeps\n // peak transient memory low (critical on iOS) and lets the progress bar move.\n const maxRangeBytes = isMobile ? 24 * 1024 * 1024 : 256 * 1024 * 1024;\n\n // Register a downloaded tensor: either retain it in the result Map (Node-ish\n // direct loads) or — in browser cache mode — record only a cache-backed\n // descriptor under the canonical name so no bytes stay in heap.\n const record = (entry: SafetensorEntry, key: string): void => {\n if (cacheStore) {\n const canonical = cacheStore.keyMapper(entry.name);\n if (canonical) {\n // Bytes live in the per-tensor download cache (BROWSER_CACHE_NAME).\n cacheStore.store.registerCached(\n canonical,\n entry.shape,\n entry.dtype,\n key,\n BROWSER_CACHE_NAME,\n );\n }\n return;\n }\n // Fallback (no cache backend): caller still wants bytes in the Map.\n // (This branch is only reachable when CacheStorage is unavailable.)\n };\n\n // Reclaim quota from the dead legacy CacheStorage/OPFS caches before we\n // probe/write IDB — runs on the default (heap) browser path too, since that's\n // where per-tensor IDB caching now happens.\n // Fire-and-forget: legacy-cache cleanup must NEVER block (or hang) the load —\n // OPFS dir iteration can stall on Safari, and it's only reclaiming dead junk.\n if (TENSOR_CACHE_ENABLED) void evictStaleCaches();\n\n // Pass 1: serve any tensor already in the per-tensor browser cache. A tensor\n // cached by an earlier load (e.g. text-only) is reused here even if this load\n // (e.g. enableVision) needs a different overall set — the key is per-tensor.\n // Probe in parallel: hundreds of sequential CacheStorage.match() calls are slow\n // on mobile and would make a fresh load look frozen before any download starts.\n // In cache mode we only need to know WHICH tensors are present (HEAD-style), not\n // their bytes — so probe with `caches.match` but discard bodies immediately.\n const uncached: SafetensorEntry[] = [];\n // Probe presence CHEAPLY (getKey, no bodies) in parallel. Reading the actual\n // bytes for hundreds of tensors at once (the old heap-path Promise.all over\n // browserCacheRead) stormed IndexedDB with ~696 concurrent transactions AND\n // pulled ~404 MB into heap in one burst → Safari hung on a warm cache (the lean\n // harness only ever hit a cold cache, so it never showed). Presence is cheap and\n // parallel-safe; the heap path then reads the HIT bytes one at a time below.\n // ONE getAllKeys() transaction, then membership in memory — NOT hundreds of\n // parallel getKey transactions (that storm hangs Safari on a warm cache).\n const cachedKeys = TENSOR_CACHE_ENABLED ? await idbAllKeys() : new Set<string>();\n const presence = neededEntries.map((entry) => cachedKeys.has(tensorCacheKey(url, file, entry)));\n for (let i = 0; i < neededEntries.length; i++) {\n const entry = neededEntries[i];\n const key = tensorCacheKey(url, file, entry);\n if (!presence[i]) {\n uncached.push(entry);\n continue;\n }\n if (cacheStore) {\n // Presence-only (the store reads bytes lazily later).\n record(entry, key);\n loadedBytes += entry.dataLength;\n onProgress?.(loadedBytes, totalBytes);\n continue;\n }\n // Heap path: read the cached bytes ONE AT A TIME (never all in parallel — that\n // storms IDB and OOMs Safari). VALIDATE size; a partial/corrupt entry is\n // treated as a miss so pass 2 re-downloads and overwrites it (self-healing).\n const buf = await browserCacheRead(key);\n if (buf && buf.byteLength >= entry.dataLength) {\n results.set(entry.name, {\n data: makeTensorView(buf, 0, entry),\n shape: entry.shape,\n });\n loadedBytes += entry.dataLength;\n onProgress?.(loadedBytes, totalBytes);\n } else {\n uncached.push(entry);\n }\n }\n\n // Cache visibility: report how many tensors were reused vs must be downloaded,\n // and which backend served them — so a silent \"redownloads everything\" cache\n // miss is diagnosable on-device (most users have no Safari console). Surfaces\n // through onProgress, which the iPad runner streams back to the dashboard.\n if (TENSOR_CACHE_ENABLED) {\n const hits = neededEntries.length - uncached.length;\n const backend = idbCacheAvailable() ? \"idb\" : \"none\";\n const msg = `cache[${backend}]: ${hits}/${neededEntries.length} tensors reused, ${uncached.length} to download`;\n console.log(`[gerbil] ${msg}`);\n onProgress?.(loadedBytes, totalBytes, msg);\n }\n\n // Pass 2: download only the uncached tensors (merged into bounded contiguous\n // ranges), then cache each one individually by its stable key.\n const ranges = mergeRanges(uncached, file.dataStart, 1024 * 1024, maxRangeBytes);\n for (const range of ranges) {\n const rangeBuf = await fetchRangeRaw(url, range.start, range.end, hfToken);\n // A truncated range response (e.g. an aborted/partial download) would yield\n // short tensor slices — bad views now and a poisoned cache later. Reject it\n // loudly instead of silently caching garbage.\n const expectedRangeBytes = range.end - range.start;\n if (rangeBuf.byteLength < expectedRangeBytes) {\n throw new Error(\n `Incomplete weight download: got ${rangeBuf.byteLength} of ${expectedRangeBytes} bytes for ${url}. Retry.`,\n );\n }\n\n for (const entry of range.entries) {\n const localOffset = file.dataStart + entry.dataOffset - range.start;\n const key = tensorCacheKey(url, file, entry);\n // Cache the exact tensor bytes (a standalone copy) for cross-load reuse.\n const tensorBytes = rangeBuf.slice(localOffset, localOffset + entry.dataLength);\n // Only cache a full-size slice so we never write a short (poisoning) entry.\n if (TENSOR_CACHE_ENABLED && tensorBytes.byteLength === entry.dataLength) {\n // `tensorBytes` is a standalone slice not reused below (the heap path\n // views `rangeBuf`, a different buffer), so it can be transferred to the\n // OPFS worker zero-copy — this is the big-memory path, so avoiding a copy\n // matters most here.\n await browserCacheWrite(key, tensorBytes, true);\n }\n if (cacheStore) {\n // Bytes are now durably in cache; do NOT retain them in heap.\n record(entry, key);\n } else {\n results.set(entry.name, {\n data: makeTensorView(rangeBuf, localOffset, entry),\n shape: entry.shape,\n });\n }\n loadedBytes += entry.dataLength;\n onProgress?.(loadedBytes, totalBytes);\n }\n // `rangeBuf` (and its slices for the non-cache path) go out of scope here;\n // in cache mode nothing references it, so the segment is freed before the\n // next range is fetched — peak heap stays ≈ one range segment.\n }\n\n return results;\n}\n\n/**\n * Create the appropriate HF key mapper for a given architecture.\n *\n * Qwen3.5 conditional generation models use \"model.language_model.\" prefix for\n * text model weights and include visual encoder + MTP weights that we skip.\n * Standard models just use \"model.\" prefix.\n *\n * `wantVision` controls whether the vision tower is kept. When false (text-only\n * loads) the ViT is dropped from the selective download so the ~201MB tower is\n * never fetched. When true (enableVision) the tower is mapped to the canonical\n * \"visual.*\" keys the vision graph expects, regardless of whether the source\n * checkpoint names it \"model.visual.*\" (HF BF16) or \"vision_tower.*\" (MLX).\n */\nfunction createKeyMapperForArch(architectureName: string, wantVision = false): HFKeyMapper {\n // KaniTTS2 ships the LFM2 backbone verbatim — the only structural rename it needs\n // is the same self_attn.out_proj → o_proj as LFM2. Its extra tensors\n // (learnable_rope_layers.*, speaker_emb_projection) pass through after stripping\n // the \"model.\" prefix and are picked up by the codec-LM graph when it lands.\n if (architectureName === \"Lfm2ForCausalLM\" || architectureName === \"KaniTTS2ForCausalLM\") {\n // LFM2 weights live under \"model.\" like the others, but the attention\n // output projection is named \"out_proj\" where the canonical IR (shared with\n // Qwen) expects \"o_proj\". Rename it so the o_proj graph tensors resolve.\n // All other LFM2 keys (operator_norm, ffn_norm, embedding_norm, conv.*,\n // feed_forward.w1/w2/w3, q_layernorm/k_layernorm) are referenced verbatim\n // by the generator via per-tensor safetensorsKey, so they pass through.\n return (hfKey: string): string | null => {\n let key = hfKey;\n if (key.startsWith(\"model.\")) {\n key = key.slice(6);\n }\n key = key.replace(/\\.self_attn\\.out_proj\\./, \".self_attn.o_proj.\");\n return key;\n };\n }\n if (architectureName === \"Qwen3_5ForConditionalGeneration\") {\n return (hfKey: string): string | null => {\n // Vision tower lives under one of two prefixes depending on the converter:\n // • \"model.visual.*\" — HF BF16 checkpoint (Qwen/Qwen3.5-0.8B)\n // • \"vision_tower.*\" — mlx-vlm 4-bit convert (mlx-community/…-4bit).\n // In MLX-4bit repos the text tower is quantized (U32 + scales/biases)\n // but the ViT is left BF16, so the suffix structure is identical to\n // the HF tower — only the prefix differs. Both map to the canonical\n // \"visual.*\" keys the vision graph generator expects.\n // When vision is not requested we drop the tower so the selective\n // downloader never fetches its ~201MB of weights.\n if (hfKey.startsWith(\"model.visual.\")) {\n return wantVision ? hfKey.slice(6) : null; // \"model.visual.*\" → \"visual.*\"\n }\n if (hfKey.startsWith(\"vision_tower.\")) {\n // \"vision_tower.\".length === 13 → replace with \"visual.\"\n return wantVision ? `visual.${hfKey.slice(13)}` : null;\n }\n // Always skip the multi-token-prediction head (an inference-speed\n // optimization, not a modality).\n if (hfKey.startsWith(\"mtp.\")) {\n return null;\n }\n let key = hfKey;\n // HF/GPTQ uses \"model.language_model.\" prefix, MLX uses \"language_model.model.\"\n if (key.startsWith(\"model.language_model.\")) {\n key = key.slice(21); // \"model.language_model.\".length\n } else if (key.startsWith(\"language_model.model.\")) {\n key = key.slice(21); // \"language_model.model.\".length (also 21 chars)\n } else if (key.startsWith(\"model.\")) {\n key = key.slice(6); // \"model.\".length\n }\n return key;\n };\n }\n if (architectureName === \"Gemma4ForConditionalGeneration\") {\n // Gemma 4 nests the text tower under \"language_model.\" and carries vision +\n // audio towers we drop for text-only loads. Prefix shapes observed:\n // HF BF16: \"model.language_model.*\" (+ \"model.vision_tower.*\", \"model.audio_tower.*\",\n // \"model.embed_vision.*\", \"model.embed_audio.*\")\n // MLX-4bit: \"language_model.model.*\" (+ \"vision_tower.*\", \"audio_tower.*\")\n // The text tower keys after stripping match the canonical Gemma layout used\n // by gemma4.ts (embed_tokens, embed_tokens_per_layer, per_layer_*, layers.N.*,\n // norm). PLE/projection tensors pass through verbatim.\n return (hfKey: string): string | null => {\n // Vision tower: keep + canonicalize when wantVision; otherwise drop so the\n // selective downloader never fetches it. Both prefixes (\"model.vision_tower.*\"\n // HF BF16, \"vision_tower.*\" MLX) and the multimodal projector\n // (\"model.embed_vision.*\" / \"embed_vision.*\") map to the canonical\n // \"vision_tower.*\" / \"embed_vision.*\" keys gemma4_vision.ts expects.\n if (hfKey.includes(\"vision_tower.\")) {\n if (!wantVision) return null;\n const idx = hfKey.indexOf(\"vision_tower.\");\n return hfKey.slice(idx); // strip any \"model.\" prefix before vision_tower.\n }\n if (hfKey.includes(\"embed_vision.\")) {\n if (!wantVision) return null;\n const idx = hfKey.indexOf(\"embed_vision.\");\n return hfKey.slice(idx);\n }\n // Always drop the audio modality (not supported in the vision path).\n if (hfKey.includes(\"audio_tower.\") || hfKey.includes(\"embed_audio.\")) {\n return null;\n }\n let key = hfKey;\n if (key.startsWith(\"model.language_model.\")) {\n key = key.slice(21); // \"model.language_model.\".length\n } else if (key.startsWith(\"language_model.model.\")) {\n key = key.slice(21); // \"language_model.model.\".length (also 21)\n } else if (key.startsWith(\"language_model.\")) {\n key = key.slice(15); // \"language_model.\".length (lm_head etc.)\n } else if (key.startsWith(\"model.\")) {\n key = key.slice(6);\n }\n return key;\n };\n }\n if (architectureName === \"MoonshineForConditionalGeneration\") {\n // Moonshine's HF keys already match the canonical Moonshine graph names once\n // the \"model.\" prefix is stripped, e.g.\n // model.encoder.conv1.weight → encoder.conv1.weight\n // model.encoder.layers.0.self_attn.q_proj.weight → encoder.layers.0.self_attn.q_proj.weight\n // model.decoder.layers.0.encoder_attn.q_proj.weight→ decoder.layers.0.encoder_attn.q_proj.weight\n // model.decoder.embed_tokens.weight → decoder.embed_tokens.weight (tied lm_head)\n // The default \"strip model.\" mapper is therefore exactly correct. (Moonshine\n // weight loading runs through the dual-graph STT loader — remaining work —\n // not the single-graph generate() path, which throws by design.)\n return createDefaultHFKeyMapper();\n }\n return createDefaultHFKeyMapper();\n}\n\nconst PLE_CACHE_NAME = \"gerbil-ple-v2\";\n\n/**\n * Build a `PleSource` from the quantized PLE table.\n *\n * Node/desktop (`cacheStore` null): keep the arrays heap-resident — unchanged\n * behavior, the executor streams rows directly from them.\n *\n * Browser (`cacheStore` set): write the (already-compact INT4) packed/scales/\n * zeros to CacheStorage and return EMPTY heap arrays plus a `cache` descriptor.\n * This keeps the ~1.17 GB table out of the JS heap during the load/upload peak;\n * the executor lazily materializes it from cache after the GPU upload has\n * completed (past the memory high-water mark). See Executor.setPleSource.\n */\nasync function buildPleSource(\n packed: Uint32Array,\n scales: Float32Array,\n zeros: Float32Array,\n width: number,\n groupSize: number,\n cacheStore: CacheWeightStore | null,\n): Promise<PleSource> {\n if (!cacheStore) {\n return { packed, scales, zeros, width, groupSize, targetTensor: \"ple_embed_out\" };\n }\n const cache = await caches.open(PLE_CACHE_NAME);\n const packedKey = \"ple:packed\";\n const scalesKey = \"ple:scales\";\n const zerosKey = \"ple:zeros\";\n const copy = (a: ArrayBufferView): ArrayBuffer =>\n a.buffer.slice(a.byteOffset, a.byteOffset + a.byteLength) as ArrayBuffer;\n await cache.put(new Request(packedKey), new Response(copy(packed)));\n await cache.put(new Request(scalesKey), new Response(copy(scales)));\n await cache.put(new Request(zerosKey), new Response(copy(zeros)));\n return {\n // Empty heap arrays — the bytes live in cache now.\n packed: new Uint32Array(0),\n scales: new Float32Array(0),\n zeros: new Float32Array(0),\n width,\n groupSize,\n targetTensor: \"ple_embed_out\",\n cache: {\n cacheName: PLE_CACHE_NAME,\n packedKey,\n scalesKey,\n zerosKey,\n packedLen: packed.length,\n },\n };\n}\n\n/**\n * Load a model from HuggingFace Hub.\n *\n * 1. Fetch config.json -> determine architecture -> generate IR graph\n * 2. Fetch tokenizer.json + tokenizer_config.json -> build tokenizer\n * 3. Download safetensors -> parse headers -> extract weight data\n * 4. Map HF tensor keys -> canonical names\n */\nexport async function loadModel(options: LoadModelOptions): Promise<LoadedModel> {\n const { repo, onProgress, hfToken, revision = \"main\", cacheDir, dtype } = options;\n\n // Initialize Node.js fs for caching (no-op in browser)\n await initFs();\n\n // Resolve cache directory: use provided path or default to ~/.cache/gerbil/<repo>\n let resolvedCacheDir = cacheDir;\n if (!resolvedCacheDir && _fs && _path) {\n const home = process.env.HOME || process.env.USERPROFILE || \"\";\n if (home) {\n resolvedCacheDir = _path.join(home, \".cache\", \"gerbil\", repo.replace(/\\//g, \"_\"), revision);\n }\n }\n\n const baseURL = resolveHFBaseURL(repo, revision);\n\n // -- Step 1: Fetch config.json --\n onProgress?.(0, 100, \"Fetching model config...\");\n const rawConfig = await fetchJSON(baseURL, \"config.json\", hfToken, resolvedCacheDir);\n\n const architectureName = rawConfig.architectures?.[0];\n if (!architectureName) {\n throw new Error(\"config.json missing 'architectures' field\");\n }\n\n // Detect pre-quantized formats — force q4 graph when loading quantized models.\n // MLX-lm writes the quant block under EITHER \"quantization_config\" (newer\n // converts, often with mode:\"affine\") OR \"quantization\" (standard mlx-lm,\n // typically just {bits, group_size} with no mode field). Read whichever exists.\n const quantConfig = (rawConfig.quantization_config ?? rawConfig.quantization) as\n | Record<string, unknown>\n | undefined;\n const isGPTQ = quantConfig?.quant_method === \"gptq\";\n\n // MLX 4-bit detection. Two shapes both indicate a standard affine MLX convert:\n // 1. {bits:4, mode:\"affine\", ...} (newer converts)\n // 2. {bits:4, group_size:N} (no mode field) (standard mlx-lm)\n // CAUTION: DWQ converts are config-INDISTINGUISHABLE from (2) — both write only\n // {bits:4, group_size}. The repack treats the weights as affine MLX, which\n // silently produces garbage for DWQ. So a no-mode MLX config is only accepted\n // for repos we've verified are standard (non-DWQ) affine converts. A verified\n // load must still be sanity-checked at runtime (cosine reference) — see the\n // embedding validation scripts.\n const quantMode = quantConfig?.mode as string | undefined;\n const hasMlxShape =\n !isGPTQ &&\n quantConfig?.bits === 4 &&\n (quantMode === \"affine\" ||\n (quantMode === undefined && typeof quantConfig?.group_size === \"number\"));\n\n // Allowlist of repos verified to be standard (non-DWQ) MLX-4bit converts.\n // Match is substring-insensitive so revision-pinned / mirrored repo strings\n // still resolve. DWQ repos (…-4bit-DWQ) must NOT appear here.\n const VERIFIED_MLX_REPOS = [\"mlx-community/embeddinggemma-300m-4bit\"];\n const repoLower = repo.toLowerCase();\n const isDWQ = repoLower.includes(\"dwq\");\n const isVerifiedMlxRepo =\n !isDWQ && VERIFIED_MLX_REPOS.some((r) => repoLower.includes(r.toLowerCase()));\n\n // Accept MLX when the config explicitly says affine, OR when the no-mode shape\n // is present AND the repo is on the verified allowlist (DWQ-trap guard).\n const isMLX = hasMlxShape && (quantMode === \"affine\" || isVerifiedMlxRepo);\n // Resolve \"auto\": on mobile (iOS/Android) prefer on-the-fly q4 so large f16\n // checkpoints (e.g. LFM2.5-350M ~676 MB f16 → ~190 MB q4) fit in device memory;\n // on desktop keep the repo's native precision. Already-quantized repos (MLX/\n // GPTQ) are always q4.\n const isMobileUA =\n typeof navigator !== \"undefined\" && /iPhone|iPad|iPod|Android/i.test(navigator.userAgent);\n const requestedDtype: GraphDType | undefined =\n dtype === \"auto\" ? (isMobileUA ? \"q4\" : undefined) : dtype;\n const resolvedDtype: GraphDType | undefined = isGPTQ || isMLX ? \"q4\" : requestedDtype;\n\n // MLX uses a different group_size (typically 64 vs Gerbil default 128)\n const quantGroupSize = isMLX ? (quantConfig?.group_size as number) : undefined;\n\n if (isGPTQ) {\n onProgress?.(2, 100, \"Detected GPTQ model — will repack to native INT4...\");\n } else if (isMLX) {\n onProgress?.(\n 2,\n 100,\n `Detected MLX 4-bit model (group_size=${quantGroupSize}) — will repack...`,\n );\n }\n\n // Select key mapper: Qwen3.5 conditional generation models have weights\n // under \"model.language_model.\" prefix instead of the standard \"model.\" prefix.\n // Vision is wanted when the caller requested the multimodal graph variant\n // (enableVision) or explicitly asked for the tower (loadVisionTower — used by\n // the direct-load vision validation scripts). Gate the ViT key-mapping on this\n // so text-only loads stay lean (the ~201MB tower is excluded from download).\n const wantVision = Boolean(options.multimodal || options.loadVisionTower);\n const keyMapper = options.keyMapper ?? createKeyMapperForArch(architectureName, wantVision);\n\n // Generate the computation graph (with INT4 ops if dtype is \"q4\" or GPTQ/MLX detected)\n const graph = generateGraph(\n architectureName,\n rawConfig,\n resolvedDtype,\n quantGroupSize,\n options.kvDtype,\n options.embedding,\n options.multimodal,\n );\n\n // -- Step 2: Fetch tokenizer --\n onProgress?.(5, 100, \"Fetching tokenizer...\");\n const [tokenizerJSON, tokenizerConfigJSON, chatTemplateJinja] = await Promise.all([\n fetchJSON(baseURL, \"tokenizer.json\", hfToken, resolvedCacheDir),\n fetchJSON(baseURL, \"tokenizer_config.json\", hfToken, resolvedCacheDir).catch(() => null),\n // Some models (e.g. LFM2) ship the chat template as a sidecar `.jinja` file\n // instead of inline in tokenizer_config.json. Fetch it as a fallback signal.\n fetchText(baseURL, \"chat_template.jinja\", hfToken, resolvedCacheDir).catch(() => null),\n ]);\n\n // Prefer the inline template; otherwise fall back to the sidecar .jinja.\n const configWithTemplate =\n tokenizerConfigJSON && !tokenizerConfigJSON.chat_template && chatTemplateJinja\n ? { ...tokenizerConfigJSON, chat_template: chatTemplateJinja }\n : (tokenizerConfigJSON ?? (chatTemplateJinja ? { chat_template: chatTemplateJinja } : null));\n\n const tokenizer = Tokenizer.fromJSON(tokenizerJSON, configWithTemplate);\n\n // -- Step 3: Download safetensors --\n onProgress?.(10, 100, \"Discovering weight files...\");\n const safetensorsFiles = await discoverSafetensorsFiles(baseURL, hfToken, resolvedCacheDir);\n\n // Cache-backed streaming store keeps the browser heap bounded (tensor bytes\n // live in CacheStorage; the heap holds only descriptors) so very large models\n // load on mobile without OOM. It is the DEFAULT on mobile — the heap-backed\n // path keeps every tensor resident in the JS heap on top of the GPU buffers\n // (and expands f16/bf16 -> f32 in-heap), doubling peak memory and OOM-crashing\n // iOS WebKit right after the safetensors header read. Desktop/Node keep the\n // proven heap path. Opt in elsewhere with `?stream=1` /\n // `globalThis.GERBIL_STREAM_WEIGHTS = true`; force the heap path anywhere with\n // `globalThis.GERBIL_STREAM_WEIGHTS = false`.\n const streamFlag = (globalThis as { GERBIL_STREAM_WEIGHTS?: boolean }).GERBIL_STREAM_WEIGHTS;\n const streamOptIn =\n streamFlag === true ||\n (typeof location !== \"undefined\" &&\n typeof URLSearchParams !== \"undefined\" &&\n new URLSearchParams(location.search).has(\"stream\"));\n const useCacheBacked =\n streamFlag !== false && (streamOptIn || isMobileUA) && cacheStorageAvailable();\n const cacheStore = useCacheBacked ? new CacheWeightStore() : null;\n const store: MutableWeightStore = cacheStore ?? new HeapWeightStore();\n\n let filesLoaded = 0;\n const totalFiles = safetensorsFiles.length;\n\n for (const filename of safetensorsFiles) {\n const fileUrl = `${baseURL}/${filename}`;\n\n // Check cache first — if cached, use the old full-buffer path\n let cachedBuffer: ArrayBuffer | null = null;\n if (resolvedCacheDir) {\n const cached = readCachedFile(resolvedCacheDir, filename);\n if (cached) {\n cachedBuffer = cached.buffer.slice(\n cached.byteOffset,\n cached.byteOffset + cached.byteLength,\n ) as ArrayBuffer;\n }\n }\n\n if (cachedBuffer) {\n // Cached (Node disk cache) — extract all needed tensors from full buffer.\n const file = parseSafetensorsHeader(cachedBuffer);\n for (const entry of file.entries) {\n const canonicalName = keyMapper(entry.name);\n if (!canonicalName) continue;\n let data: ArrayBufferView = getTensorData(cachedBuffer, file, entry);\n if (entry.dtype === \"BF16\") {\n data = bf16ToF32(new Uint8Array(data.buffer, data.byteOffset, data.byteLength));\n }\n if (entry.dtype === \"F16\") {\n data = f16ToF32(data);\n }\n await store.set(canonicalName, { data, shape: entry.shape });\n }\n } else {\n // Not cached — use selective Range-request downloading\n const fileWeight = 85 / totalFiles;\n const base = 10 + filesLoaded * fileWeight;\n\n // Step 1: Fetch header only. This + the index probe above can take a few\n // seconds with no bytes yet — without this emit the bar looks frozen at 10%\n // (\"stuck at discovering weight files\") until the first chunk lands.\n onProgress?.(base, 100, `Reading ${filename} header (${filesLoaded + 1}/${totalFiles})...`);\n const file = await fetchSafetensorsHeader(fileUrl, hfToken);\n\n // Step 2: Filter to needed entries\n const neededEntries = file.entries.filter((e) => keyMapper(e.name) !== null);\n const skippedBytes = file.entries\n .filter((e) => keyMapper(e.name) === null)\n .reduce((sum, e) => sum + e.dataLength, 0);\n const neededBytes = neededEntries.reduce((sum, e) => sum + e.dataLength, 0);\n\n const totalMB = (neededBytes / 1048576).toFixed(0);\n if (skippedBytes > 0) {\n const savedMB = (skippedBytes / 1048576).toFixed(0);\n onProgress?.(\n base,\n 100,\n `Selective download: ${totalMB} MB needed, skipping ${savedMB} MB (vision/MTP)`,\n );\n }\n // Show the size up front so the first (latency-heavy) chunk doesn't read as a freeze.\n onProgress?.(base, 100, `Downloading ${filename} (0/${totalMB} MB)`);\n\n // Step 3: Download needed tensors via Range requests\n let lastReportedPct = -1;\n\n const rawTensors = await fetchSelectiveTensors(\n fileUrl,\n file,\n neededEntries,\n hfToken,\n (loaded, total) => {\n const filePct = total > 0 ? (loaded / total) * fileWeight : 0;\n const overallPct = Math.round(base + filePct);\n if (overallPct <= lastReportedPct) return;\n lastReportedPct = overallPct;\n const sizeMB =\n total > 0\n ? ` (${(loaded / 1048576).toFixed(0)}/${(total / 1048576).toFixed(0)} MB)`\n : \"\";\n onProgress?.(base + filePct, 100, `Downloading ${filename}${sizeMB}`);\n },\n // Browser: register tensors as cache-backed descriptors under their\n // canonical names — no bytes retained in heap. Node: omitted, so the\n // returned Map carries the bytes (handled below).\n cacheStore ? { store: cacheStore, keyMapper } : undefined,\n );\n\n // Step 4 (Node / non-cache only): map to canonical names with dtype\n // conversion. In cache-backed mode `rawTensors` is empty — tensors were\n // already registered into the store with the canonical name + source dtype,\n // and `store.get()` applies BF16/F16→F32 on demand.\n for (const [hfName, { data: rawData, shape }] of rawTensors) {\n const canonicalName = keyMapper(hfName);\n if (!canonicalName) continue;\n\n let data: ArrayBufferView = rawData;\n // Find the entry to check dtype\n const entry = neededEntries.find((e) => e.name === hfName);\n if (entry) {\n if (entry.dtype === \"BF16\") {\n data = bf16ToF32(new Uint8Array(data.buffer, data.byteOffset, data.byteLength));\n }\n if (entry.dtype === \"F16\") {\n data = f16ToF32(data);\n }\n }\n\n await store.set(canonicalName, { data, shape });\n }\n }\n\n filesLoaded++;\n }\n\n onProgress?.(95, 100, \"Weights loaded.\");\n\n // -- Split fused Q projection for Qwen3.5 full attention layers --\n // HF stores q_proj.weight as [num_heads * 2 * head_dim, hidden_size].\n // The layout is per-head interleaved: for each head h, rows\n // [h*2*head_dim .. h*2*head_dim + head_dim) are Q weights, and\n // [h*2*head_dim + head_dim .. (h+1)*2*head_dim) are gate weights.\n // We deinterleave into q_proj.weight [num_heads*head_dim, hidden_size]\n // and attn_gate.weight [num_heads*head_dim, hidden_size].\n if (architectureName === \"Qwen3_5ForConditionalGeneration\") {\n const textCfg = (rawConfig.text_config as Record<string, unknown>) ?? rawConfig;\n const numLayers = textCfg.num_hidden_layers as number;\n const layerTypesArr = (textCfg.layer_types as string[]) ?? [];\n const fullAttnInterval = (textCfg.full_attention_interval as number) ?? 4;\n const numHeads = textCfg.num_attention_heads as number; // 8\n const headDim =\n (textCfg.head_dim as number) ?? Math.floor((textCfg.hidden_size as number) / numHeads);\n\n for (let i = 0; i < numLayers; i++) {\n // Check if this is a full attention layer\n let isFullAttn: boolean;\n if (layerTypesArr.length > i) {\n isFullAttn = layerTypesArr[i] === \"full_attention\";\n } else {\n isFullAttn = i % fullAttnInterval === fullAttnInterval - 1;\n }\n if (!isFullAttn) continue;\n\n const qProjKey = `layers.${i}.self_attn.q_proj.weight`;\n const attnGateKey = `layers.${i}.self_attn.attn_gate.weight`;\n const fusedWeight = await store.get(qProjKey);\n\n if (fusedWeight && !store.has(attnGateKey) && !isMLX) {\n const fusedData = fusedWeight.data;\n const [fusedRows, cols] = fusedWeight.shape;\n const halfRows = fusedRows / 2; // num_heads * head_dim\n\n const qData = new Float32Array(halfRows * cols);\n const gateData = new Float32Array(halfRows * cols);\n\n const src =\n fusedData instanceof Float32Array\n ? fusedData\n : new Float32Array(fusedData.buffer, fusedData.byteOffset, fusedRows * cols);\n\n // Deinterleave per-head: each head has a block of 2*head_dim rows\n // First head_dim rows = Q, second head_dim rows = gate\n const headBlock = 2 * headDim; // rows per head in fused weight\n for (let h = 0; h < numHeads; h++) {\n const srcBase = h * headBlock * cols;\n const dstBase = h * headDim * cols;\n qData.set(src.subarray(srcBase, srcBase + headDim * cols), dstBase);\n gateData.set(src.subarray(srcBase + headDim * cols, srcBase + headBlock * cols), dstBase);\n }\n\n await store.set(qProjKey, { data: qData, shape: [halfRows, cols] });\n await store.set(attnGateKey, { data: gateData, shape: [halfRows, cols] });\n }\n\n // GPTQ: split fused q_proj GPTQ tensors into q_proj + attn_gate\n // GPTQ q_proj has N=4096 (fused), graph expects N=2048 each.\n // The deinterleave is per-head: each head's block of 2*head_dim output columns\n // has head_dim Q columns followed by head_dim gate columns.\n const qProjQweight = `layers.${i}.self_attn.q_proj.qweight`;\n const attnGateQweight = `layers.${i}.self_attn.attn_gate.qweight`;\n if (store.has(qProjQweight) && !store.has(attnGateQweight)) {\n const fusedN = numHeads * 2 * headDim; // 4096\n const halfN = numHeads * headDim; // 2048\n const K_val = textCfg.hidden_size as number; // 1024\n\n // Split qweight [K/8, fusedN] → two [K/8, halfN] with per-head deinterleave\n const qw = (await store.get(qProjQweight))!;\n const qwSrc =\n qw.data instanceof Int32Array\n ? qw.data\n : new Int32Array(qw.data.buffer, qw.data.byteOffset, qw.data.byteLength / 4);\n const kDiv8 = K_val >>> 3;\n const qwQ = new Int32Array(kDiv8 * halfN);\n const qwG = new Int32Array(kDiv8 * halfN);\n const headBlock2 = 2 * headDim; // output columns per head in fused tensor\n for (let row = 0; row < kDiv8; row++) {\n for (let h = 0; h < numHeads; h++) {\n const srcOff = row * fusedN + h * headBlock2;\n const dstOff = row * halfN + h * headDim;\n for (let d = 0; d < headDim; d++) {\n qwQ[dstOff + d] = qwSrc[srcOff + d];\n qwG[dstOff + d] = qwSrc[srcOff + headDim + d];\n }\n }\n }\n await store.set(qProjQweight, { data: qwQ, shape: [kDiv8, halfN] });\n await store.set(attnGateQweight, { data: qwG, shape: [kDiv8, halfN] });\n\n // Split scales [groups, fusedN] → two [groups, halfN]\n const qProjScales = `layers.${i}.self_attn.q_proj.scales`;\n const attnGateScales = `layers.${i}.self_attn.attn_gate.scales`;\n const sc = (await store.get(qProjScales))!;\n const scSrc =\n sc.data instanceof Float32Array\n ? sc.data\n : new Float32Array(sc.data.buffer, sc.data.byteOffset, sc.data.byteLength / 4);\n const groups = sc.shape[0];\n const scQ = new Float32Array(groups * halfN);\n const scG = new Float32Array(groups * halfN);\n for (let g = 0; g < groups; g++) {\n for (let h = 0; h < numHeads; h++) {\n const srcOff = g * fusedN + h * headBlock2;\n const dstOff = g * halfN + h * headDim;\n for (let d = 0; d < headDim; d++) {\n scQ[dstOff + d] = scSrc[srcOff + d];\n scG[dstOff + d] = scSrc[srcOff + headDim + d];\n }\n }\n }\n await store.set(qProjScales, { data: scQ, shape: [groups, halfN] });\n await store.set(attnGateScales, { data: scG, shape: [groups, halfN] });\n\n // Split qzeros [groups, fusedN/8] → two [groups, halfN/8]\n // qzeros packs 8 output columns per int32. Since headDim (256) is divisible by 8,\n // the deinterleave works on int32 words directly: headDim/8 words per head.\n const qProjQzeros = `layers.${i}.self_attn.q_proj.qzeros`;\n const attnGateQzeros = `layers.${i}.self_attn.attn_gate.qzeros`;\n const qz = (await store.get(qProjQzeros))!;\n const qzSrc =\n qz.data instanceof Int32Array\n ? qz.data\n : new Int32Array(qz.data.buffer, qz.data.byteOffset, qz.data.byteLength / 4);\n const fusedNDiv8 = fusedN >>> 3;\n const halfNDiv8 = halfN >>> 3;\n const headBlockDiv8 = headBlock2 >>> 3;\n const headDimDiv8 = headDim >>> 3;\n const qzQ = new Int32Array(groups * halfNDiv8);\n const qzG = new Int32Array(groups * halfNDiv8);\n for (let g = 0; g < groups; g++) {\n for (let h = 0; h < numHeads; h++) {\n const srcOff = g * fusedNDiv8 + h * headBlockDiv8;\n const dstOff = g * halfNDiv8 + h * headDimDiv8;\n for (let d = 0; d < headDimDiv8; d++) {\n qzQ[dstOff + d] = qzSrc[srcOff + d];\n qzG[dstOff + d] = qzSrc[srcOff + headDimDiv8 + d];\n }\n }\n }\n await store.set(qProjQzeros, { data: qzQ, shape: [groups, halfNDiv8] });\n await store.set(attnGateQzeros, { data: qzG, shape: [groups, halfNDiv8] });\n\n // Clean up g_idx tensors if present (we assume sequential grouping)\n await store.delete(`layers.${i}.self_attn.q_proj.g_idx`);\n }\n\n // MLX: split fused q_proj MLX tensors into q_proj + attn_gate\n // MLX q_proj.weight is packed [fusedN, K/8], scales/biases are [fusedN, K/gs]\n // The split is along the N (output/row) dimension — same deinterleave pattern.\n const mlxQProjWeight = `layers.${i}.self_attn.q_proj.weight`;\n const mlxAttnGateWeight = `layers.${i}.self_attn.attn_gate.weight`;\n if (isMLX && store.has(mlxQProjWeight) && !store.has(mlxAttnGateWeight)) {\n const K_val = textCfg.hidden_size as number; // 1024\n const fusedN = numHeads * 2 * headDim; // 4096\n const halfN = numHeads * headDim; // 2048\n const headBlock2 = 2 * headDim;\n const mlxGS = (quantConfig?.group_size as number) ?? 64;\n\n // Deinterleave [fusedN, cols] → two [halfN, cols] with per-head split\n const deinterleaveMLX = <T extends Float32Array | Uint32Array>(\n src: T,\n fusedRows: number,\n cols: number,\n Ctor: { new (len: number): T },\n ): [T, T] => {\n const halfRows = fusedRows / 2;\n const qArr = new Ctor(halfRows * cols);\n const gArr = new Ctor(halfRows * cols);\n for (let h = 0; h < numHeads; h++) {\n const srcBase = h * headBlock2 * cols;\n const dstBase = h * headDim * cols;\n for (let r = 0; r < headDim; r++) {\n const s0 = srcBase + r * cols;\n const s1 = srcBase + (headDim + r) * cols;\n const d0 = dstBase + r * cols;\n for (let c = 0; c < cols; c++) {\n qArr[d0 + c] = src[s0 + c];\n gArr[d0 + c] = src[s1 + c];\n }\n }\n }\n return [qArr, gArr];\n };\n\n // Split packed weight [fusedN, K/8]\n const wt = (await store.get(mlxQProjWeight))!;\n const wtSrc =\n wt.data instanceof Uint32Array\n ? wt.data\n : new Uint32Array(wt.data.buffer, wt.data.byteOffset, wt.data.byteLength / 4);\n const colsW = K_val >>> 3; // K/8\n const [wtQ, wtG] = deinterleaveMLX(wtSrc, fusedN, colsW, Uint32Array);\n await store.set(mlxQProjWeight, { data: wtQ, shape: [halfN, colsW] });\n await store.set(mlxAttnGateWeight, { data: wtG, shape: [halfN, colsW] });\n\n // Split scales [fusedN, K/gs]\n const mlxQProjScales = `layers.${i}.self_attn.q_proj.scales`;\n const mlxAttnGateScales = `layers.${i}.self_attn.attn_gate.scales`;\n const sc = (await store.get(mlxQProjScales))!;\n const scSrc =\n sc.data instanceof Float32Array\n ? sc.data\n : new Float32Array(sc.data.buffer, sc.data.byteOffset, sc.data.byteLength / 4);\n const colsS = Math.ceil(K_val / mlxGS);\n const [scQ, scG] = deinterleaveMLX(scSrc, fusedN, colsS, Float32Array);\n await store.set(mlxQProjScales, { data: scQ, shape: [halfN, colsS] });\n await store.set(mlxAttnGateScales, { data: scG, shape: [halfN, colsS] });\n\n // Split biases [fusedN, K/gs]\n const mlxQProjBiases = `layers.${i}.self_attn.q_proj.biases`;\n const mlxAttnGateBiases = `layers.${i}.self_attn.attn_gate.biases`;\n const bi = (await store.get(mlxQProjBiases))!;\n const biSrc =\n bi.data instanceof Float32Array\n ? bi.data\n : new Float32Array(bi.data.buffer, bi.data.byteOffset, bi.data.byteLength / 4);\n const [biQ, biG] = deinterleaveMLX(biSrc, fusedN, colsS, Float32Array);\n await store.set(mlxQProjBiases, { data: biQ, shape: [halfN, colsS] });\n await store.set(mlxAttnGateBiases, { data: biG, shape: [halfN, colsS] });\n }\n }\n }\n\n // -- Apply (1 + weight) offset for ALL Qwen3.5 RMSNorm weights --\n // Qwen3_5RMSNorm initializes weight=0 and applies (1 + weight) * normalized.\n // ALL norms in the model use this class: input_layernorm, post_attention_layernorm,\n // final_norm, q_norm, k_norm, and linear_attn.norm. We bake +1 into weights so\n // the standard RMSNorm kernel (which does weight * normalized) produces correct results.\n //\n // EXCEPTION: MLX Community models already absorb the +1 during conversion —\n // their RMSNorm uses `weight * normalized` (no +1), so the stored weights\n // are already `1 + original_weight`. Applying +1 again would double-offset.\n if (architectureName === \"Qwen3_5ForConditionalGeneration\" && !isMLX) {\n const textCfg = (rawConfig.text_config as Record<string, unknown>) ?? rawConfig;\n const numLayers = textCfg.num_hidden_layers as number;\n const layerTypesArr = (textCfg.layer_types as string[]) ?? [];\n const fullAttnInterval = (textCfg.full_attention_interval as number) ?? 4;\n\n // Collect all norm weight keys\n const normKeys: string[] = [];\n for (let i = 0; i < numLayers; i++) {\n // All layers have input/post-attention layernorms\n normKeys.push(`layers.${i}.input_layernorm.weight`);\n normKeys.push(`layers.${i}.post_attention_layernorm.weight`);\n\n let isFullAttn: boolean;\n if (layerTypesArr.length > i) {\n isFullAttn = layerTypesArr[i] === \"full_attention\";\n } else {\n isFullAttn = i % fullAttnInterval === fullAttnInterval - 1;\n }\n\n if (isFullAttn) {\n normKeys.push(`layers.${i}.self_attn.q_norm.weight`);\n normKeys.push(`layers.${i}.self_attn.k_norm.weight`);\n }\n // NOTE: linear_attn.norm.weight uses Qwen3_5RMSNormGated which\n // initializes weight=1 (not 0), so it does NOT need the +1 offset.\n }\n // Final norm\n normKeys.push(\"norm.weight\");\n\n for (const normKey of normKeys) {\n const entry = await store.get(normKey);\n if (entry) {\n const data =\n entry.data instanceof Float32Array\n ? entry.data\n : new Float32Array(entry.data.buffer, entry.data.byteOffset, entry.data.byteLength / 4);\n const adjusted = new Float32Array(data.length);\n for (let j = 0; j < data.length; j++) {\n adjusted[j] = 1.0 + data[j];\n }\n await store.set(normKey, { data: adjusted, shape: entry.shape });\n }\n }\n }\n\n // -- Apply (1 + weight) offset for ALL Gemma3 RMSNorm weights --\n // Gemma3RMSNorm computes `(1.0 + weight) * normalized` (mlx-lm:\n // mx.fast.rms_norm(x, 1.0 + self.weight, eps)). The stored weight is the raw\n // trained value centered near 0; we bake +1 so the standard RMSNorm kernel\n // (weight * normalized) is correct. Unlike Qwen3.5, the MLX convert does NOT\n // absorb the +1 for Gemma, so this runs for MLX too. Every per-layer norm uses\n // this class: input_layernorm, post_attention_layernorm, pre_feedforward_\n // layernorm, post_feedforward_layernorm, q_norm, k_norm, plus the final norm.\n if (architectureName === \"Gemma3TextModel\" || architectureName === \"Gemma3Model\") {\n const gemmaNumLayers = rawConfig.num_hidden_layers as number;\n const gemmaNormKeys: string[] = [];\n for (let i = 0; i < gemmaNumLayers; i++) {\n gemmaNormKeys.push(`layers.${i}.input_layernorm.weight`);\n gemmaNormKeys.push(`layers.${i}.post_attention_layernorm.weight`);\n gemmaNormKeys.push(`layers.${i}.pre_feedforward_layernorm.weight`);\n gemmaNormKeys.push(`layers.${i}.post_feedforward_layernorm.weight`);\n gemmaNormKeys.push(`layers.${i}.self_attn.q_norm.weight`);\n gemmaNormKeys.push(`layers.${i}.self_attn.k_norm.weight`);\n }\n gemmaNormKeys.push(\"norm.weight\");\n\n for (const normKey of gemmaNormKeys) {\n const entry = await store.get(normKey);\n if (entry) {\n const data =\n entry.data instanceof Float32Array\n ? entry.data\n : new Float32Array(entry.data.buffer, entry.data.byteOffset, entry.data.byteLength / 4);\n const adjusted = new Float32Array(data.length);\n for (let j = 0; j < data.length; j++) {\n adjusted[j] = 1.0 + data[j];\n }\n await store.set(normKey, { data: adjusted, shape: entry.shape });\n }\n }\n }\n\n // NOTE on Gemma 4 RMSNorm: unlike Gemma 3 (weights centered near 0, runtime\n // `(1+weight)`), the released Gemma 4 / Gemma3n checkpoints store the FULL norm\n // gain directly (raw input_layernorm values are ~7–68, rms ~12). So NO +1 bake\n // is applied here — the standard `weight * normalized` kernel is already correct,\n // and the large residual-stream magnitudes are expected for this model.\n\n // -- Gemma 4 per-layer output scalar (`layer_scalar`) --\n // Gemma4TextDecoderLayer ends with `hidden_states *= self.layer_scalar` — a single\n // learned scalar per layer (shape [1], values ~0.018–0.5, NOT ones). The graph\n // emits a placeholder Scale node (id `layer{i}_layer_scalar`); we read the scalar\n // value here and patch the node's `scale` attribute, then drop the weight (it is a\n // baked constant, not a GPU tensor). Without this the residual stream is unscaled\n // and generation is incoherent.\n if (architectureName === \"Gemma4ForConditionalGeneration\") {\n for (const node of graph.nodes) {\n const key = node.attributes?.layer_scalar_key as string | undefined;\n if (!key) continue;\n const entry = await store.get(key);\n if (!entry) continue;\n const data =\n entry.data instanceof Float32Array\n ? entry.data\n : new Float32Array(entry.data.buffer, entry.data.byteOffset, entry.data.byteLength / 4);\n node.attributes.scale = data[0];\n await store.delete(key);\n }\n }\n\n // -- Handle tied embeddings --\n // When safetensorsKey differs from the tensor name (e.g. lm_head.weight\n // pointing to embed_tokens.weight for tied embeddings), copy the data.\n for (const [name, desc] of Object.entries(graph.tensors)) {\n if (\n desc.storage === \"constant\" &&\n desc.safetensorsKey &&\n desc.safetensorsKey !== name &&\n !store.has(name)\n ) {\n const sourceData = await store.get(desc.safetensorsKey);\n if (sourceData) {\n await store.set(name, sourceData);\n }\n }\n }\n\n // -- Repack GPTQ weights into Gerbil INT4 format --\n if (isGPTQ) {\n onProgress?.(96, 100, \"Repacking GPTQ weights...\");\n const gptqGroupSize = (quantConfig?.group_size as number) ?? 128;\n\n for (const node of graph.nodes) {\n if (node.opType !== \"MatMulInt4\") continue;\n\n const K = node.attributes.K as number;\n const N = node.attributes.N as number;\n\n const qTensorName = node.inputs[1]; // e.g. layers.0.self_attn.q_proj.weight.q\n const scalesTensorName = node.inputs[2]; // e.g. layers.0.self_attn.q_proj.weight.scales\n const zerosTensorName = node.inputs[3]; // e.g. layers.0.self_attn.q_proj.weight.zeros\n\n // Derive GPTQ tensor names from graph names:\n // Graph: \"layers.0.self_attn.q_proj.weight.q\" → strip \".q\" → strip \".weight\" → base\n const sourceName = qTensorName.slice(0, -2); // \"layers.0.self_attn.q_proj.weight\"\n const gptqBase = sourceName.slice(0, -7); // \"layers.0.self_attn.q_proj\"\n\n const gptqQweight = await store.get(`${gptqBase}.qweight`);\n const gptqScales = await store.get(`${gptqBase}.scales`);\n const gptqQzeros = await store.get(`${gptqBase}.qzeros`);\n\n // Skip layers not quantized in GPTQ — they'll be handled by on-the-fly quantization below\n if (!gptqQweight || !gptqScales || !gptqQzeros) continue;\n\n // Ensure correct typed arrays for the adapter\n const qweightData =\n gptqQweight.data instanceof Int32Array\n ? gptqQweight.data\n : new Int32Array(\n gptqQweight.data.buffer,\n gptqQweight.data.byteOffset,\n gptqQweight.data.byteLength / 4,\n );\n const scalesData =\n gptqScales.data instanceof Float32Array\n ? gptqScales.data\n : new Float32Array(\n gptqScales.data.buffer,\n gptqScales.data.byteOffset,\n gptqScales.data.byteLength / 4,\n );\n const qzerosData =\n gptqQzeros.data instanceof Int32Array\n ? gptqQzeros.data\n : new Int32Array(\n gptqQzeros.data.buffer,\n gptqQzeros.data.byteOffset,\n gptqQzeros.data.byteLength / 4,\n );\n\n const repacked = repackGPTQ({\n qweight: qweightData,\n scales: scalesData,\n qzeros: qzerosData,\n K,\n N,\n groupSize: gptqGroupSize,\n });\n\n await store.set(qTensorName, { data: repacked.packed, shape: [repacked.packed.length] });\n await store.set(scalesTensorName, { data: repacked.scales, shape: [repacked.scales.length] });\n await store.set(zerosTensorName, { data: repacked.zeros, shape: [repacked.zeros.length] });\n\n // Clean up GPTQ source tensors to free memory\n await store.delete(`${gptqBase}.qweight`);\n await store.delete(`${gptqBase}.scales`);\n await store.delete(`${gptqBase}.qzeros`);\n }\n }\n\n // -- Repack MLX 4-bit weights into Gerbil INT4 format --\n if (isMLX) {\n onProgress?.(96, 100, \"Repacking MLX weights...\");\n const mlxGS = (quantConfig?.group_size as number) ?? 64;\n\n for (const node of graph.nodes) {\n if (node.opType !== \"MatMulInt4\" && node.opType !== \"EmbeddingInt4\") continue;\n\n // Derive N and K for this layer\n let N: number, K: number;\n if (node.opType === \"EmbeddingInt4\") {\n N = node.attributes.vocab_size as number;\n K = node.attributes.hidden_size as number;\n } else {\n N = node.attributes.N as number;\n K = node.attributes.K as number;\n }\n\n const qTensorName = node.inputs[1]; // e.g. layers.0.self_attn.q_proj.weight.q\n const scalesTensorName = node.inputs[2]; // e.g. layers.0.self_attn.q_proj.weight.scales\n const zerosTensorName = node.inputs[3]; // e.g. layers.0.self_attn.q_proj.weight.zeros\n\n // Derive MLX source tensor names:\n // Graph: \"base.weight.q\" → strip \".q\" → \"base.weight\" (MLX packed weight)\n // MLX scales: \"base.scales\", MLX biases: \"base.biases\"\n const sourceName = qTensorName.slice(0, -2); // \"base.weight\"\n const baseName = sourceName.slice(0, -7); // \"base\" (strip \".weight\")\n\n const mlxWeight = await store.get(sourceName); // packed U32\n const mlxScales = await store.get(`${baseName}.scales`); // F32 (already converted from BF16)\n const mlxBiases = await store.get(`${baseName}.biases`); // F32 (already converted from BF16)\n\n if (!mlxWeight || !mlxScales || !mlxBiases) continue;\n\n const weightData =\n mlxWeight.data instanceof Uint32Array\n ? mlxWeight.data\n : new Uint32Array(\n mlxWeight.data.buffer,\n mlxWeight.data.byteOffset,\n mlxWeight.data.byteLength / 4,\n );\n const scalesData =\n mlxScales.data instanceof Float32Array\n ? mlxScales.data\n : new Float32Array(\n mlxScales.data.buffer,\n mlxScales.data.byteOffset,\n mlxScales.data.byteLength / 4,\n );\n const biasesData =\n mlxBiases.data instanceof Float32Array\n ? mlxBiases.data\n : new Float32Array(\n mlxBiases.data.buffer,\n mlxBiases.data.byteOffset,\n mlxBiases.data.byteLength / 4,\n );\n\n const repacked = repackMLX({\n weight: weightData,\n scales: scalesData,\n biases: biasesData,\n N,\n K,\n groupSize: mlxGS,\n });\n\n await store.set(qTensorName, { data: repacked.packed, shape: [repacked.packed.length] });\n await store.set(scalesTensorName, { data: repacked.scales, shape: [repacked.scales.length] });\n await store.set(zerosTensorName, { data: repacked.zeros, shape: [repacked.zeros.length] });\n\n // Clean up MLX source tensors to free memory\n await store.delete(sourceName);\n await store.delete(`${baseName}.scales`);\n await store.delete(`${baseName}.biases`);\n }\n }\n\n // -- Quantize weights for INT4 if requested (or for non-GPTQ/MLX layers) --\n if (resolvedDtype === \"q4\") {\n onProgress?.(96, 100, \"Quantizing weights to INT4...\");\n for (const node of graph.nodes) {\n if (node.opType !== \"MatMulInt4\" && node.opType !== \"EmbeddingInt4\") continue;\n\n const groupSz = (node.attributes.group_size as number) ?? DEFAULT_GROUP_SIZE;\n\n const qTensorName = node.inputs[1]; // weight.q\n const scalesTensorName = node.inputs[2]; // weight.scales\n const zerosTensorName = node.inputs[3]; // weight.zeros\n\n // Skip nodes already handled by GPTQ or MLX repacking\n if (store.has(qTensorName)) continue;\n\n // Derive source weight name by stripping \".q\" suffix\n const sourceName = qTensorName.slice(0, -2);\n let sourceWeight = await store.get(sourceName);\n // Tied embeddings fallback: lm_head.weight may not exist in safetensors\n // when tie_word_embeddings=true — use embed_tokens.weight instead.\n if (!sourceWeight && sourceName === \"lm_head.weight\") {\n sourceWeight = await store.get(\"embed_tokens.weight\");\n }\n\n if (sourceWeight) {\n const f32Data =\n sourceWeight.data instanceof Float32Array\n ? sourceWeight.data\n : new Float32Array(\n sourceWeight.data.buffer,\n sourceWeight.data.byteOffset,\n sourceWeight.data.byteLength / 4,\n );\n\n const { packed, scales, zeros } = quantizeInt4(f32Data, groupSz);\n\n await store.set(qTensorName, { data: packed, shape: [packed.length] });\n await store.set(scalesTensorName, { data: scales, shape: [scales.length] });\n await store.set(zerosTensorName, { data: zeros, shape: [zeros.length] });\n\n // Free the original F32 weight to save memory\n await store.delete(sourceName);\n }\n }\n }\n\n // -- MLX vision patch_embed axis fixup --\n // The ViT patch_embed conv weight is a 5D tensor whose flattening must match\n // the host patch vector's [C, T, ph, pw] order. The HF BF16 checkpoint stores\n // it channels-first as [out, C, T, ph, pw] → flattens correctly as-is. The\n // mlx-vlm convert stores it channels-LAST as [out, T, ph, pw, C], so the\n // flat [out, 1536] inner order is permuted and the first matmul would be fed\n // mismatched weights (manifests as low cosine, not NaN). Detect the\n // channels-last 5D shape and permute back to [out, C, T, ph, pw] so the\n // flattened weight matches the host patch layout on both checkpoints.\n if (isMLX) {\n const pe = await store.get(CANONICAL_KEYS.visPatchEmbedWeight);\n const s = pe?.shape;\n // channels-last conv weight: [out, T, ph, pw, C] with C === in_channels (3)\n if (pe && s && s.length === 5 && s[4] === 3) {\n const [outC, T, ph, pw, C] = s;\n const src =\n pe.data instanceof Float32Array\n ? pe.data\n : new Float32Array(pe.data.buffer, pe.data.byteOffset, pe.data.byteLength / 4);\n const dst = new Float32Array(src.length);\n // src index (channels-last): ((((o*T + t)*ph + y)*pw + x)*C + c)\n // dst index (channels-first): ((((o*C + c)*T + t)*ph + y)*pw + x)\n for (let o = 0; o < outC; o++) {\n for (let t = 0; t < T; t++) {\n for (let y = 0; y < ph; y++) {\n for (let x = 0; x < pw; x++) {\n for (let c = 0; c < C; c++) {\n const si = (((o * T + t) * ph + y) * pw + x) * C + c;\n const di = (((o * C + c) * T + t) * ph + y) * pw + x;\n dst[di] = src[si];\n }\n }\n }\n }\n }\n // Store flattened [out, C*T*ph*pw] = [out, 1536] in channels-first order.\n await store.set(CANONICAL_KEYS.visPatchEmbedWeight, {\n data: dst,\n shape: [outC, C * T * ph * pw],\n });\n }\n }\n\n // -- Gemma 4: extract the PLE table to CPU-resident storage (NOT GPU) --\n // The per-layer embedding table `embed_tokens_per_layer` is ~1.17GB at 4-bit.\n // We keep it in JS memory and stream per-token rows at inference, so it never\n // becomes a GPU buffer. Repack MLX 4-bit → Gerbil INT4 here; for an unquantized\n // checkpoint, encode the f32 rows with a unit scale (zero=0) so the executor's\n // single dequant path handles both. The PLE EmbeddingInt4 node was intentionally\n // omitted from the graph (gemma4.ts), so the standard repack loop skips this\n // table — it is the loader's job to divert it out of `weights`.\n let pleSource: PleSource | undefined;\n const isGemma4 = String(architectureName).startsWith(\"Gemma4\");\n if (isGemma4) {\n const pleBase = CANONICAL_KEYS.GEMMA4_PLE_EMBED.slice(0, -7); // \"embed_tokens_per_layer\"\n const textCfg = (rawConfig.text_config as Record<string, unknown>) ?? rawConfig;\n const pleDim = (textCfg.hidden_size_per_layer_input as number) ?? 256;\n const numLayers = textCfg.num_hidden_layers as number;\n const pleWidth = numLayers * pleDim; // L*256\n const pleVocab =\n (textCfg.vocab_size_per_layer_input as number) ?? (textCfg.vocab_size as number);\n\n const packedEntry = await store.get(`${pleBase}.weight`);\n const scalesEntry = await store.get(`${pleBase}.scales`);\n const biasesEntry = await store.get(`${pleBase}.biases`);\n\n if (isMLX && packedEntry && scalesEntry && biasesEntry) {\n const mlxGS = (quantConfig?.group_size as number) ?? 64;\n const packedU32 =\n packedEntry.data instanceof Uint32Array\n ? packedEntry.data\n : new Uint32Array(\n packedEntry.data.buffer,\n packedEntry.data.byteOffset,\n packedEntry.data.byteLength / 4,\n );\n const scalesF32 =\n scalesEntry.data instanceof Float32Array\n ? scalesEntry.data\n : new Float32Array(\n scalesEntry.data.buffer,\n scalesEntry.data.byteOffset,\n scalesEntry.data.byteLength / 4,\n );\n const biasesF32 =\n biasesEntry.data instanceof Float32Array\n ? biasesEntry.data\n : new Float32Array(\n biasesEntry.data.buffer,\n biasesEntry.data.byteOffset,\n biasesEntry.data.byteLength / 4,\n );\n const repacked = repackMLX({\n weight: packedU32,\n scales: scalesF32,\n biases: biasesF32,\n N: pleVocab,\n K: pleWidth,\n groupSize: mlxGS,\n });\n pleSource = await buildPleSource(\n repacked.packed,\n repacked.scales,\n repacked.zeros,\n pleWidth,\n mlxGS,\n cacheStore,\n );\n // Divert out of the store so uploadWeights() never sends it to the GPU.\n await store.delete(`${pleBase}.weight`);\n await store.delete(`${pleBase}.scales`);\n await store.delete(`${pleBase}.biases`);\n console.log(\n `[Gerbil] Gemma4 PLE table kept ${cacheStore ? \"cache-resident\" : \"CPU-resident\"}: ` +\n `[${pleVocab}, ${pleWidth}] int4 ` +\n `(~${((repacked.packed.byteLength + repacked.scales.byteLength + repacked.zeros.byteLength) / 1e6).toFixed(0)} MB, 0 MB GPU)`,\n );\n } else if (packedEntry?.data instanceof Float32Array) {\n // Unquantized f32 table: quantize to INT4 for compact storage and a\n // single shared dequant path. (Quantization-sensitive precision concerns\n // only apply when forced GPU-resident; here we still dequantize per-row.)\n const { packed, scales, zeros } = quantizeInt4(packedEntry.data, DEFAULT_GROUP_SIZE);\n pleSource = await buildPleSource(\n packed,\n scales,\n zeros,\n pleWidth,\n DEFAULT_GROUP_SIZE,\n cacheStore,\n );\n await store.delete(`${pleBase}.weight`);\n } else {\n console.warn(\n \"[Gerbil] Gemma4: PLE table not found in weights — per-layer embeddings \" +\n \"will be zero. Check the checkpoint ships embed_tokens_per_layer.\",\n );\n }\n }\n\n // -- Materialize synthetic fill-value constants (e.g. Gemma 4 v_norm ones) --\n // Tensors declared with `fillValue` carry no checkpoint weight; the loader fills\n // them with a constant at the declared shape. Used for parameter-free norms.\n for (const [name, desc] of Object.entries(graph.tensors)) {\n if (desc.storage === \"constant\" && desc.fillValue !== undefined && !store.has(name)) {\n const length = desc.shape.reduce(\n (acc: number, dim) => acc * (typeof dim === \"number\" ? dim : 1),\n 1,\n );\n const data = new Float32Array(length).fill(desc.fillValue);\n await store.set(name, {\n data,\n shape: desc.shape.map((d) => (typeof d === \"number\" ? d : 1)),\n });\n }\n }\n\n // -- Verify required tensors --\n const missingTensors: string[] = [];\n for (const [name, desc] of Object.entries(graph.tensors)) {\n if (desc.storage === \"constant\" && !store.has(name)) {\n missingTensors.push(name);\n }\n }\n\n if (missingTensors.length > 0) {\n console.warn(\n `[Gerbil] Missing ${missingTensors.length} weight tensors:`,\n missingTensors.slice(0, 10),\n );\n }\n\n onProgress?.(100, 100, \"Model loaded.\");\n\n return { graph, tokenizer, weights: store, rawConfig, pleSource };\n}\n\n// ── Moonshine STT loader ───────────────────────────────────────────────────\n//\n// Moonshine is an encoder-decoder ASR model that cannot be expressed as a single\n// causal-LM graph (the standard loadModel path). It needs two graphs (a conv\n// encoder run once, an AR decoder with cross-attention) sharing one set of f32\n// weights. This loader downloads config + tokenizer + the single model.safetensors\n// and returns the canonical-named f32 weights for both graphs. Keys are mapped by\n// stripping the \"model.\" prefix the checkpoint uses (e.g. \"model.encoder.conv1.\n// weight\" → \"encoder.conv1.weight\"), which is exactly the safetensorsKey the\n// Moonshine graph generators reference.\n\nexport interface LoadedMoonshine {\n /** Canonical-named f32 weights (data + shape), shared by encoder + decoder graphs. */\n weights: Map<string, { data: ArrayBufferView; shape: number[] }>;\n /** The (decode-capable) tokenizer. */\n tokenizer: Tokenizer;\n /** Raw config.json. */\n rawConfig: Record<string, unknown>;\n}\n\nexport async function loadMoonshine(options: {\n repo: string;\n revision?: string;\n hfToken?: string;\n cacheDir?: string;\n onProgress?: (loaded: number, total: number, message: string) => void;\n}): Promise<LoadedMoonshine> {\n const revision = options.revision ?? \"main\";\n const baseURL = resolveHFBaseURL(options.repo, revision);\n await initFs();\n\n options.onProgress?.(0, 100, \"Fetching config + tokenizer…\");\n const [rawConfig, tokenizerJSON] = await Promise.all([\n fetchJSON(baseURL, \"config.json\", options.hfToken, options.cacheDir),\n fetchJSON(baseURL, \"tokenizer.json\", options.hfToken, options.cacheDir),\n ]);\n const tokenizerConfig = await fetchJSON(\n baseURL,\n \"tokenizer_config.json\",\n options.hfToken,\n options.cacheDir,\n ).catch(() => null);\n const tokenizer = Tokenizer.fromJSON(tokenizerJSON, tokenizerConfig);\n\n options.onProgress?.(10, 100, \"Downloading weights…\");\n const buf = await fetchBinary(\n baseURL,\n \"model.safetensors\",\n options.hfToken,\n (loaded, total) => options.onProgress?.(10 + (loaded / (total || 1)) * 85, 100, \"weights\"),\n options.cacheDir,\n );\n\n const file = parseSafetensorsHeader(buf);\n const weights = new Map<string, { data: ArrayBufferView; shape: number[] }>();\n for (const entry of file.entries) {\n // Strip the \"model.\" prefix the Moonshine checkpoint uses on every tensor.\n const canonical = entry.name.startsWith(\"model.\") ? entry.name.slice(6) : entry.name;\n let data: ArrayBufferView = getTensorData(buf, file, entry);\n if (entry.dtype === \"BF16\") {\n data = bf16ToF32(new Uint8Array(data.buffer, data.byteOffset, data.byteLength));\n } else if (entry.dtype === \"F16\") {\n data = f16ToF32(data);\n }\n weights.set(canonical, { data, shape: entry.shape });\n }\n\n options.onProgress?.(100, 100, \"Moonshine loaded.\");\n return { weights, tokenizer, rawConfig };\n}\n\n// ════════════════════════════════════════════════════════════════════════════\n// Kani-TTS-2 dual-checkpoint loader (LFM2-350M codec-LM + NVIDIA NeMo NanoCodec).\n//\n// Kani-TTS-2 is a two-stage TTS model that, like Moonshine, cannot be one graph:\n// 1. the codec-LM backbone (LFM2-350M body) lives in the main repo\n// (nineninesix/kani-tts-2-en, model.safetensors, \"model.\" prefix), and\n// 2. the NanoCodec decoder weights live in a SEPARATE repo\n// (nineninesix/nemo-nano-codec-22khz-0.6kbps-12.5fps-MLX, model.safetensors)\n// stored as weight-norm parametrizations under verbose NeMo key paths.\n//\n// This loader downloads both, folds the codec's weight-norm (W = g·v/‖v‖) and\n// renames its keys to the canonical `nanocodec.*` names the decoder graph expects,\n// and returns one merged f32 weight map (+ tokenizer + config) for the KaniTTS\n// consumer (src/gpu/kani-tts.ts), which builds the backbone + decoder graphs.\n// ════════════════════════════════════════════════════════════════════════════\n\n/** Default NanoCodec checkpoint Kani-TTS-2 uses (NeMo 22 kHz, 0.6 kbps, 12.5 fps). */\nexport const KANI_NANOCODEC_REPO = \"nineninesix/nemo-nano-codec-22khz-0.6kbps-12.5fps-MLX\";\n\nexport interface LoadedKaniTTS {\n /** Canonical-named f32 weights for the codec-LM backbone (LFM2 keys). */\n backboneWeights: Map<string, { data: ArrayBufferView; shape: number[] }>;\n /** Folded NanoCodec decoder weights under canonical `nanocodec.*` names. */\n codecWeights: Map<string, { data: ArrayBufferView; shape: number[] }>;\n /** The text tokenizer. */\n tokenizer: Tokenizer;\n /** Raw backbone config.json (LFM2 dims + KaniTTS2 fields). */\n rawConfig: Record<string, unknown>;\n}\n\n/** Matches the LFM2 attention out_proj key (renamed to o_proj for the canonical map). */\nconst KANI_OUT_PROJ_RE = /\\.self_attn\\.out_proj\\./;\n\n/** Coerce a safetensors tensor view (bf16/f16→f32) to a contiguous Float32Array. */\nfunction toF32(entry: SafetensorEntry, buf: ArrayBuffer, file: SafetensorsFile): Float32Array {\n let data: ArrayBufferView = getTensorData(buf, file, entry);\n if (entry.dtype === \"BF16\") {\n data = bf16ToF32(new Uint8Array(data.buffer, data.byteOffset, data.byteLength));\n } else if (entry.dtype === \"F16\") {\n data = f16ToF32(data);\n }\n return data instanceof Float32Array\n ? data\n : new Float32Array(data.buffer, data.byteOffset, data.byteLength / 4);\n}\n\n/** Fold + rename the raw NanoCodec tensors into the canonical `nanocodec.*` map. */\nfunction buildKaniCodecWeights(\n codecRaw: Map<string, { data: Float32Array; shape: number[] }>,\n): Map<string, { data: ArrayBufferView; shape: number[] }> {\n const out = new Map<string, { data: ArrayBufferView; shape: number[] }>();\n const { convs, plains } = nanoCodecWeightMap();\n for (const c of convs) {\n const g = codecRaw.get(c.gKey);\n const v = codecRaw.get(c.vKey);\n if (!(g && v)) {\n throw new Error(\n `NanoCodec weight-norm pair missing for ${c.graphKey}: ${c.gKey} / ${c.vKey}. ` +\n \"Check the codec checkpoint (expected weight-norm parametrizations).\",\n );\n }\n out.set(c.graphKey, {\n data: foldNanoCodecWeightNorm(g.data, v.data, v.shape),\n shape: v.shape,\n });\n }\n for (const pl of plains) {\n const t = codecRaw.get(pl.mlxKey);\n if (!t) {\n throw new Error(`NanoCodec tensor missing: ${pl.mlxKey} (for ${pl.graphKey}).`);\n }\n out.set(pl.graphKey, { data: t.data, shape: t.shape });\n }\n return out;\n}\n\nexport async function loadKaniTTS(options: {\n /** Backbone repo (default nineninesix/kani-tts-2-en). */\n repo?: string;\n /** NanoCodec repo (default KANI_NANOCODEC_REPO). */\n codecRepo?: string;\n revision?: string;\n hfToken?: string;\n cacheDir?: string;\n onProgress?: (loaded: number, total: number, message: string) => void;\n}): Promise<LoadedKaniTTS> {\n const revision = options.revision ?? \"main\";\n const repo = options.repo ?? \"nineninesix/kani-tts-2-en\";\n const codecRepo = options.codecRepo ?? KANI_NANOCODEC_REPO;\n const baseURL = resolveHFBaseURL(repo, revision);\n const codecURL = resolveHFBaseURL(codecRepo, \"main\");\n await initFs();\n\n options.onProgress?.(0, 100, \"Fetching config + tokenizer…\");\n const [rawConfig, tokenizerJSON] = await Promise.all([\n fetchJSON(baseURL, \"config.json\", options.hfToken, options.cacheDir),\n fetchJSON(baseURL, \"tokenizer.json\", options.hfToken, options.cacheDir),\n ]);\n const tokenizerConfig = await fetchJSON(\n baseURL,\n \"tokenizer_config.json\",\n options.hfToken,\n options.cacheDir,\n ).catch(() => null);\n const tokenizer = Tokenizer.fromJSON(tokenizerJSON, tokenizerConfig);\n\n // ── Backbone weights (LFM2-350M): strip \"model.\" and rename out_proj→o_proj. ──\n options.onProgress?.(8, 100, \"Downloading codec-LM backbone…\");\n const backboneBuf = await fetchBinary(\n baseURL,\n \"model.safetensors\",\n options.hfToken,\n (loaded, total) =>\n options.onProgress?.(8 + (loaded / (total || 1)) * 45, 100, \"backbone weights\"),\n options.cacheDir,\n );\n const backboneFile = parseSafetensorsHeader(backboneBuf);\n const backboneWeights = new Map<string, { data: ArrayBufferView; shape: number[] }>();\n for (const entry of backboneFile.entries) {\n const key = (entry.name.startsWith(\"model.\") ? entry.name.slice(6) : entry.name).replace(\n KANI_OUT_PROJ_RE,\n \".self_attn.o_proj.\",\n );\n backboneWeights.set(key, {\n data: toF32(entry, backboneBuf, backboneFile),\n shape: entry.shape,\n });\n }\n\n // ── NanoCodec weights: fold weight-norm + rename to canonical nanocodec.* ──\n options.onProgress?.(55, 100, \"Downloading NanoCodec decoder…\");\n // The codec's model.safetensors shares its name with the backbone's, and\n // fetchBinary caches by filename only — give the codec its own cache subdir so\n // the two downloads do not collide.\n const codecCacheDir = options.cacheDir ? `${options.cacheDir}/nanocodec` : undefined;\n const codecBuf = await fetchBinary(\n codecURL,\n \"model.safetensors\",\n options.hfToken,\n (loaded, total) =>\n options.onProgress?.(55 + (loaded / (total || 1)) * 40, 100, \"codec weights\"),\n codecCacheDir,\n );\n const codecFile = parseSafetensorsHeader(codecBuf);\n const codecRaw = new Map<string, { data: Float32Array; shape: number[] }>();\n for (const entry of codecFile.entries) {\n codecRaw.set(entry.name, { data: toF32(entry, codecBuf, codecFile), shape: entry.shape });\n }\n const codecWeights = buildKaniCodecWeights(codecRaw);\n\n options.onProgress?.(100, 100, \"Kani-TTS-2 loaded.\");\n return { backboneWeights, codecWeights, tokenizer, rawConfig };\n}\n","/**\n * MoonshineEncoderExecutor — runs the Moonshine STT encoder graph on WebGPU.\n *\n * Modeled on VisionExecutor: a single non-autoregressive prefill (conv frontend +\n * bidirectional transformer) run ONCE per utterance. The graph is length-static\n * (the Conv1dFull ops carry concrete L/Lout), so it is regenerated per utterance\n * from the input sample count (moonshineEncoderFrames) and a fresh executor is\n * built for that length.\n *\n * Beyond the encoder output, the graph also projects the frozen encoder hidden\n * state through each DECODER layer's encoder_attn.k_proj / v_proj, producing\n * enc_k_layer{i} / enc_v_layer{i}. The host reads these back and binds them frozen\n * into the decoder's CrossAttention across every AR decode step.\n *\n * WebKit discipline mirrors VisionExecutor: per-dispatch submit + drain, and the\n * bidirectional Attention op is windowed by query-row so no single dispatch runs\n * long enough to trip Metal's GPU watchdog. Dawn uses one submission.\n */\n\nimport type { GPUContext } from \"./device.js\";\nimport {\n createBindGroup,\n createStorageBuffer,\n createUniformBuffer,\n destroyBuffers,\n getOrCreatePipeline,\n} from \"./device.js\";\nimport { DTYPE_BYTES, type ModelGraph, type OpNode } from \"./ir.js\";\nimport {\n KERNEL_REGISTRY,\n type KernelSpec,\n LAYERNORM_NO_BIAS_SPEC,\n ROPE_INTERLEAVED_SPEC,\n} from \"./kernels/registry.js\";\n\nconst MAP_MODE_READ = 0x0001;\n\n/**\n * WebKit only: window the bidirectional encoder Attention grid into chunks of this\n * many query rows, each its own submit + drain (same watchdog mitigation the vision\n * tower uses). Encoder frames are far fewer than ViT patches, but a long clip can\n * still produce a few hundred frames, so we keep the guard.\n */\nconst WEBKIT_ATTN_CHUNK_ROWS = 64;\n\ninterface MoonshineDispatch {\n node: OpNode;\n spec: KernelSpec;\n pipeline: GPUComputePipeline;\n bindGroup: GPUBindGroup;\n uniformBuffer: GPUBuffer;\n}\n\nexport interface EncoderResult {\n /** encoder_out [S_enc, hidden] (debug / parity). */\n encoderOut: Float32Array;\n /** Per-decoder-layer frozen K, indexed by layer. */\n encK: Float32Array[];\n /** Per-decoder-layer frozen V, indexed by layer. */\n encV: Float32Array[];\n /** Encoder length (frames). */\n sEnc: number;\n}\n\nexport class MoonshineEncoderExecutor {\n private ctx: GPUContext;\n private graph: ModelGraph;\n private decLayers: number;\n private hidden: number;\n\n private weightBuffers = new Map<string, GPUBuffer>();\n private activationBuffers = new Map<string, GPUBuffer>();\n private dispatches: MoonshineDispatch[] = [];\n\n constructor(ctx: GPUContext, graph: ModelGraph, decLayers: number) {\n this.ctx = ctx;\n this.graph = graph;\n this.decLayers = decLayers;\n this.hidden = graph.config.hidden_size;\n this.allocateActivationBuffers();\n }\n\n /**\n * Upload the encoder constants. `weights` holds canonical-named f32 tensors;\n * only those referenced by the graph are uploaded (the decoder weights are\n * uploaded into the decoder Executor separately).\n */\n uploadWeights(weights: Map<string, { data: ArrayBufferView; shape: number[] }>): void {\n for (const [name, desc] of Object.entries(this.graph.tensors)) {\n if (desc.storage !== \"constant\") continue;\n const w = weights.get(name);\n if (w) {\n const buffer = createStorageBuffer(this.ctx, `menc_${name}`, w.data.byteLength, w.data);\n this.weightBuffers.set(name, buffer);\n } else if (!desc.safetensorsKey) {\n // Synthetic constant with no checkpoint source (e.g. conv1's zero bias —\n // Conv1dFull requires a bias binding but Moonshine's conv1 has none).\n // Allocate it zero-filled.\n const count = (desc.shape as number[]).reduce((a, b) => a * (b as number), 1);\n const zeros = new Float32Array(count);\n const buffer = createStorageBuffer(this.ctx, `menc_${name}`, zeros.byteLength, zeros);\n this.weightBuffers.set(name, buffer);\n } else {\n throw new Error(`MoonshineEncoderExecutor: missing weight \"${name}\"`);\n }\n }\n }\n\n initBindGroups(): void {\n const shapes = this.resolveShapes();\n for (const nodeId of this.graph.executionOrder) {\n const node = this.graph.nodes.find((n) => n.id === nodeId)!;\n let spec = KERNEL_REGISTRY[node.opType];\n if (!spec) throw new Error(`MoonshineEncoderExecutor: no kernel for op \"${node.opType}\"`);\n // Moonshine norms are weight-only — pick the no-bias LayerNorm variant.\n if (node.opType === \"LayerNorm\" && node.inputs.length === 2) {\n spec = LAYERNORM_NO_BIAS_SPEC;\n } else if (node.opType === \"RoPE\" && node.attributes.interleaved === true) {\n spec = ROPE_INTERLEAVED_SPEC;\n }\n const pipeline = getOrCreatePipeline(\n this.ctx,\n `mkernel_${nodeId}`,\n spec.shaderCode,\n spec.entryPoint,\n );\n const paramsData = spec.buildParams(node, shapes, { seqPos: 0 });\n const uniformBuffer = createUniformBuffer(this.ctx, `muniform_${nodeId}`, paramsData);\n const bufferEntries = this.gatherBuffers(spec, node, uniformBuffer);\n const bindGroup = createBindGroup(this.ctx, pipeline, bufferEntries, `mbg_${nodeId}`);\n this.dispatches.push({ node, spec, pipeline, bindGroup, uniformBuffer });\n }\n }\n\n /** Run the conv frontend + encoder + K/V projection. `pcm` is raw 16kHz mono. */\n async encode(pcm: Float32Array): Promise<EncoderResult> {\n const q = this.ctx.device.queue;\n q.writeBuffer(this.activationBuffers.get(\"pcm\")!, 0, pcm as BufferSource);\n\n const shapes = this.resolveShapes();\n const ctxRt = { seqPos: 0 };\n const sizes: Array<[number, number, number]> = [];\n for (const d of this.dispatches) {\n const params = d.spec.buildParams(d.node, shapes, ctxRt);\n q.writeBuffer(d.uniformBuffer, 0, params);\n sizes.push(d.spec.getDispatchSize(d.node, shapes, ctxRt));\n }\n\n if (this.ctx.isWebKitWebGPU) {\n for (let i = 0; i < this.dispatches.length; i++) {\n const d = this.dispatches[i];\n if (d.node.opType === \"Attention\") {\n // Window the O(S²) bidirectional attention by query-row (watchdog guard).\n const [gridX, gridY, gridZ] = sizes[i];\n for (let start = 0; start < gridX; start += WEBKIT_ATTN_CHUNK_ROWS) {\n const rows = Math.min(WEBKIT_ATTN_CHUNK_ROWS, gridX - start);\n const params = d.spec.buildParams(d.node, shapes, { seqPos: 0, qOffset: start });\n q.writeBuffer(d.uniformBuffer, 0, params);\n const enc = this.ctx.device.createCommandEncoder();\n const p = enc.beginComputePass();\n p.setPipeline(d.pipeline);\n p.setBindGroup(0, d.bindGroup);\n p.dispatchWorkgroups(rows, gridY, gridZ);\n p.end();\n q.submit([enc.finish()]);\n await q.onSubmittedWorkDone();\n }\n continue;\n }\n const enc = this.ctx.device.createCommandEncoder();\n const p = enc.beginComputePass();\n p.setPipeline(d.pipeline);\n p.setBindGroup(0, d.bindGroup);\n p.dispatchWorkgroups(...sizes[i]);\n p.end();\n q.submit([enc.finish()]);\n await q.onSubmittedWorkDone();\n }\n } else {\n const enc = this.ctx.device.createCommandEncoder({ label: \"moonshine_encode\" });\n const pass = enc.beginComputePass({ label: \"moonshine_enc_pass\" });\n for (let i = 0; i < this.dispatches.length; i++) {\n pass.setPipeline(this.dispatches[i].pipeline);\n pass.setBindGroup(0, this.dispatches[i].bindGroup);\n pass.dispatchWorkgroups(...sizes[i]);\n }\n pass.end();\n q.submit([enc.finish()]);\n }\n\n const sEnc = this.encoderFrames();\n const encoderOut = await this.readBack(\"encoder_out\", sEnc * this.hidden);\n const encK: Float32Array[] = [];\n const encV: Float32Array[] = [];\n for (let i = 0; i < this.decLayers; i++) {\n encK.push(await this.readBack(`enc_k_layer${i}`, sEnc * this.hidden));\n encV.push(await this.readBack(`enc_v_layer${i}`, sEnc * this.hidden));\n }\n return { encoderOut, encK, encV, sEnc };\n }\n\n /** Read back a named activation buffer after encode() (debug / parity checks). */\n async readActivation(name: string, maxElements = 4096): Promise<Float32Array> {\n return this.readBack(name, maxElements);\n }\n\n destroy(): void {\n destroyBuffers([...this.weightBuffers.values()]);\n destroyBuffers([...this.activationBuffers.values()]);\n for (const d of this.dispatches) d.uniformBuffer.destroy();\n this.weightBuffers.clear();\n this.activationBuffers.clear();\n this.dispatches = [];\n }\n\n // ── Private ──\n\n /** Encoder frame count = first dim of encoder_out (resolved, numeric). */\n private encoderFrames(): number {\n return this.graph.tensors.encoder_out.shape[0] as number;\n }\n\n private resolveShapes(): Record<string, number[]> {\n // The Moonshine encoder graph is fully concrete (no symbolic dims) — every\n // tensor shape is numeric for the chosen sample count.\n const resolved: Record<string, number[]> = {};\n for (const [name, desc] of Object.entries(this.graph.tensors)) {\n resolved[name] = desc.shape.map((dim) => dim as number);\n }\n return resolved;\n }\n\n private allocateActivationBuffers(): void {\n for (const [name, desc] of Object.entries(this.graph.tensors)) {\n if (desc.storage !== \"activation\") continue;\n const bytes =\n (desc.shape as number[]).reduce((a, b) => a * (b as number), 1) * DTYPE_BYTES[desc.dtype];\n this.activationBuffers.set(name, createStorageBuffer(this.ctx, `mact_${name}`, bytes));\n }\n }\n\n private async readBack(name: string, elements: number): Promise<Float32Array> {\n const buffer = this.activationBuffers.get(name);\n if (!buffer) throw new Error(`MoonshineEncoderExecutor: no buffer \"${name}\"`);\n const byteLen = Math.min(elements * 4, buffer.size);\n const readback = this.ctx.device.createBuffer({ size: byteLen, usage: 0x0001 | 0x0008 });\n const enc = this.ctx.device.createCommandEncoder();\n enc.copyBufferToBuffer(buffer, 0, readback, 0, byteLen);\n this.ctx.device.queue.submit([enc.finish()]);\n await readback.mapAsync(MAP_MODE_READ, 0, byteLen);\n const data = new Float32Array(readback.getMappedRange(0, byteLen).slice(0));\n readback.unmap();\n readback.destroy();\n return data;\n }\n\n private gatherBuffers(\n spec: KernelSpec,\n node: OpNode,\n uniformBuffer: GPUBuffer,\n ): Array<{ buffer: GPUBuffer }> {\n const entries: Array<{ buffer: GPUBuffer }> = [];\n const tensorNames = [...node.inputs, ...node.outputs];\n let tensorIdx = 0;\n for (const binding of spec.bindings) {\n if (binding.type === \"uniform\") {\n entries.push({ buffer: uniformBuffer });\n } else {\n const tensorName = tensorNames[tensorIdx++];\n const buffer = this.weightBuffers.get(tensorName) ?? this.activationBuffers.get(tensorName);\n if (!buffer) {\n throw new Error(\n `MoonshineEncoderExecutor: no buffer for \"${tensorName}\" in op ${node.id}`,\n );\n }\n entries.push({ buffer });\n }\n }\n return entries;\n }\n}\n","/**\n * MoonshineSTT — native speech-to-text engine for Gerbil's WebGPU backend.\n *\n * Moonshine is an encoder-decoder ASR model with a raw-waveform conv frontend\n * (no FFT / mel spectrogram). Unlike the causal-LM path, it needs two graphs:\n *\n * 1. ENCODER (run once per utterance): conv frontend → bidirectional transformer\n * → encoder hidden state, which is then projected through every decoder\n * layer's cross-attention k_proj/v_proj into FROZEN K/V buffers.\n * 2. DECODER (autoregressive): causal self-attention with a growing KV-cache,\n * plus cross-attention into the frozen encoder K/V at every step.\n *\n * The conv frontend is length-static (Conv1dFull carries concrete L/Lout), so the\n * encoder graph is regenerated per utterance from the input sample count, and the\n * decoder graph is regenerated with the resulting encoder frame count (S_enc).\n * Weights are uploaded once and reused across utterances via per-call executors.\n *\n * Validated on Dawn (desktop) via scripts/engine/test-moonshine-transcribe.mjs.\n * The kernels are mobile-safe (≤16KB workgroup memory, clamped exp/tanh, no\n * select(), no `enable f16`) and the executors use the WebKit submit/drain\n * discipline, so the same path runs on iPad.\n */\n\nimport {\n generateMoonshineDecoderGraph,\n generateMoonshineEncoderGraph,\n moonshineEncoderFrames,\n parseMoonshineConfig,\n} from \"./architectures/moonshine.js\";\nimport type { GPUContext } from \"./device.js\";\nimport { initGPU } from \"./device.js\";\nimport { Executor } from \"./executor.js\";\nimport { loadMoonshine } from \"./model-loader.js\";\nimport { MoonshineEncoderExecutor } from \"./moonshine-executor.js\";\nimport type { Tokenizer } from \"./tokenizer.js\";\n\nconst MOONSHINE_SAMPLE_RATE = 16_000;\n/** Hard cap so a stuck decode can't loop forever (config max_position_embeddings). */\nconst DEFAULT_MAX_NEW_TOKENS = 194;\n\nexport interface MoonshineSTTOptions {\n /** HF repo (default UsefulSensors/moonshine-base). */\n repo?: string;\n revision?: string;\n hfToken?: string;\n cacheDir?: string;\n onProgress?: (loaded: number, total: number, message: string) => void;\n}\n\nexport interface TranscribeOptions {\n /** Stop after this many decoded tokens (default 194). */\n maxNewTokens?: number;\n /**\n * RMS energy below which a clip is treated as silence/noise (`noSpeech`).\n * Default 0.01 (normalized 16 kHz float PCM).\n */\n minRms?: number;\n /**\n * Clips shorter than this are treated as no-speech — a sub-threshold clip is\n * almost always silence, and any transcript on it is a hallucination.\n * Default 0.35 seconds.\n */\n minSpeechSeconds?: number;\n}\n\nexport interface TranscribeResult {\n text: string;\n /** Decoded token ids (excluding the start token, including the trailing EOS). */\n tokens: number[];\n /** Number of encoder frames produced by the conv frontend. */\n encoderFrames: number;\n /** Audio duration in seconds (samples / 16000). */\n audioSeconds: number;\n /** RMS energy of the input PCM (~0..1). */\n speechRms: number;\n /**\n * True when the clip almost certainly contains no speech — too quiet, too\n * short, an empty transcript, or a non-speech marker like \"[BLANK_AUDIO]\".\n * Skip empty/hallucinated transcripts when this is set.\n */\n noSpeech: boolean;\n}\n\n/** Non-speech markers Moonshine can emit on silence, e.g. \"[BLANK_AUDIO]\", \"(silence)\". */\nconst NON_SPEECH_RE = /^\\s*[[(][^)\\]]*[)\\]]\\s*$/;\n\nfunction computeRms(pcm: Float32Array): number {\n if (pcm.length === 0) {\n return 0;\n }\n let sum = 0;\n for (let i = 0; i < pcm.length; i++) {\n sum += pcm[i] * pcm[i];\n }\n return Math.sqrt(sum / pcm.length);\n}\n\nexport class MoonshineSTT {\n private ctx: GPUContext;\n private weights: Map<string, { data: ArrayBufferView; shape: number[] }>;\n private tokenizer: Tokenizer;\n private rawConfig: Record<string, unknown>;\n private bosTokenId: number;\n private eosTokenId: number;\n private decoderStartTokenId: number;\n private _destroyed = false;\n\n /** HF architecture string, for parity with WebGPUEngine. */\n readonly architecture = \"MoonshineForConditionalGeneration\";\n\n private constructor(\n ctx: GPUContext,\n weights: Map<string, { data: ArrayBufferView; shape: number[] }>,\n tokenizer: Tokenizer,\n rawConfig: Record<string, unknown>,\n ) {\n this.ctx = ctx;\n this.weights = weights;\n this.tokenizer = tokenizer;\n this.rawConfig = rawConfig;\n this.bosTokenId = (rawConfig.bos_token_id as number) ?? 1;\n this.eosTokenId = (rawConfig.eos_token_id as number) ?? 2;\n // Moonshine seeds the decoder with decoder_start_token_id (== bos == 1).\n this.decoderStartTokenId = (rawConfig.decoder_start_token_id as number) ?? this.bosTokenId;\n }\n\n /** Download + initialize a Moonshine STT engine. */\n static async create(options: MoonshineSTTOptions = {}): Promise<MoonshineSTT> {\n const ctx = await initGPU();\n const repo = options.repo ?? \"UsefulSensors/moonshine-base\";\n const { weights, tokenizer, rawConfig } = await loadMoonshine({\n repo,\n revision: options.revision,\n hfToken: options.hfToken,\n cacheDir: options.cacheDir,\n onProgress: options.onProgress,\n });\n return new MoonshineSTT(ctx, weights, tokenizer, rawConfig);\n }\n\n /**\n * Transcribe raw 16 kHz mono PCM. Runs the conv frontend + encoder once, then\n * greedily AR-decodes with cross-attention into the frozen encoder K/V, stopping\n * on EOS. Returns the detokenized transcript.\n */\n async transcribe(pcm: Float32Array, opts: TranscribeOptions = {}): Promise<TranscribeResult> {\n if (this._destroyed) throw new Error(\"MoonshineSTT has been destroyed.\");\n if (pcm.length < 127) {\n throw new Error(`PCM too short: ${pcm.length} samples (need >= conv1 kernel size 127).`);\n }\n const d = parseMoonshineConfig(this.rawConfig);\n const sEnc = moonshineEncoderFrames(pcm.length);\n if (sEnc < 1) {\n throw new Error(`Audio too short: produces ${sEnc} encoder frames.`);\n }\n\n const audioSeconds = pcm.length / MOONSHINE_SAMPLE_RATE;\n const speechRms = computeRms(pcm);\n const minRms = opts.minRms ?? 0.01;\n const minSpeechSeconds = opts.minSpeechSeconds ?? 0.35;\n\n // Cheap VAD: skip the encode/decode entirely for clips too quiet or too short\n // to contain speech (a sub-threshold clip is almost always silence; any\n // transcript on it is a hallucination).\n if (speechRms < minRms || audioSeconds < minSpeechSeconds) {\n return {\n text: \"\",\n tokens: [],\n encoderFrames: 0,\n audioSeconds,\n speechRms,\n noSpeech: true,\n };\n }\n\n // ── Encoder (once) ──\n const encoderGraph = generateMoonshineEncoderGraph(this.rawConfig, pcm.length);\n const encExec = new MoonshineEncoderExecutor(this.ctx, encoderGraph, d.dec_layers);\n let encK: Float32Array[];\n let encV: Float32Array[];\n let frames: number;\n try {\n encExec.uploadWeights(this.weights);\n encExec.initBindGroups();\n const enc = await encExec.encode(pcm);\n encK = enc.encK;\n encV = enc.encV;\n frames = enc.sEnc;\n } finally {\n encExec.destroy();\n }\n\n // ── Decoder (autoregressive, greedy) ──\n const decoderGraph = generateMoonshineDecoderGraph(this.rawConfig, frames);\n // maxSeqLen bounds the self-attn KV cache. The decode can never exceed the\n // model's max_position_embeddings, so cap there.\n const maxSeqLen = Math.min(opts.maxNewTokens ?? DEFAULT_MAX_NEW_TOKENS, d.context_length);\n const decExec = new Executor(this.ctx, decoderGraph, { maxSeqLen, kvMode: \"f32\" });\n const tokens: number[] = [];\n try {\n decExec.uploadWeightsMap(selectGraphWeights(decoderGraph, this.weights));\n decExec.initBindGroups();\n // Bind the frozen encoder K/V into each decoder layer's cross-attention.\n for (let i = 0; i < d.dec_layers; i++) {\n decExec.writeInput(`enc_k_layer${i}`, encK[i]);\n decExec.writeInput(`enc_v_layer${i}`, encV[i]);\n }\n decExec.reset();\n\n // Greedy decode: seed with the decoder start token, feed argmax back in.\n let nextToken = this.decoderStartTokenId;\n const limit = maxSeqLen;\n for (let step = 0; step < limit; step++) {\n const tok = await decExec.forwardArgmax(new Uint32Array([nextToken]));\n tokens.push(tok);\n if (tok === this.eosTokenId) break;\n nextToken = tok;\n }\n } finally {\n decExec.destroy();\n }\n\n const text = this.tokenizer.decode(tokens, /* skipSpecialTokens */ true).trim();\n const noSpeech = text.length === 0 || NON_SPEECH_RE.test(text);\n return {\n text,\n tokens,\n encoderFrames: frames,\n audioSeconds,\n speechRms,\n noSpeech,\n };\n }\n\n destroy(): void {\n this._destroyed = true;\n this.weights.clear();\n }\n}\n\n/**\n * Build a fresh map of just the constant weights a graph references.\n *\n * Executor.uploadWeights() DELETES entries from the map it is given (to free JS\n * memory as each weight reaches the GPU). Moonshine reuses the same weights across\n * utterances and across the encoder+decoder, so we hand each executor its OWN map\n * (deletes only touch the copy; the shared ArrayBufferViews are uploaded, never\n * mutated). Scoping to graph-referenced constants also avoids uploading the\n * encoder's weights into the decoder executor.\n */\nfunction selectGraphWeights(\n graph: { tensors: Record<string, { storage: string }> },\n weights: Map<string, { data: ArrayBufferView; shape: number[] }>,\n): Map<string, { data: ArrayBufferView; shape: number[] }> {\n const out = new Map<string, { data: ArrayBufferView; shape: number[] }>();\n for (const [name, desc] of Object.entries(graph.tensors)) {\n if (desc.storage !== \"constant\") continue;\n const w = weights.get(name);\n if (w) out.set(name, w);\n }\n return out;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"moonshine-stt-BzQRl-BO.mjs","names":["MAP_MODE_READ","requiredFeatures: GPUFeatureName[]","requestLimits: Record<string, number>","device: GPUDevice","layout: GPUPipelineLayout | \"auto\"","details: string[]","mismatches: string[]","addSpec: KernelSpec","mulSpec: KernelSpec","sliceLastRowSpec: KernelSpec","meanPoolSpec: KernelSpec","scaleSpec: KernelSpec","softcapSpec: KernelSpec","l2NormSpec: KernelSpec","swigluSpec: KernelSpec","residualRmsnormSpec: KernelSpec","siluSpec: KernelSpec","geluSpec: KernelSpec","geluErfSpec: KernelSpec","addBiasSpec: KernelSpec","sliceColsSpec: KernelSpec","mulColsSpec: KernelSpec","applyRotarySpec: KernelSpec","matmulSpec: KernelSpec","M: number","matmulBiasSpec: KernelSpec","poolMatMulSpec: KernelSpec","clippedMatMulSpec: KernelSpec","MATMUL_BIAS_F16C_SPEC: KernelSpec","matmulInt4Spec: KernelSpec","MATVEC_SPEC: KernelSpec","MATVEC_INT4_SPEC: KernelSpec","GATED_MATVEC_INT4_SPEC: KernelSpec","SWIGLU_GATED_MATVEC_INT4_SPEC: KernelSpec","MATVEC_SUBGROUPS_SPEC: KernelSpec","MATVEC_INT4_SUBGROUPS_SPEC: KernelSpec","SWIGLU_MATVEC_SPEC: KernelSpec","SWIGLU_MATVEC_INT4_SPEC: KernelSpec","DUAL_MATVEC_INT4_SPEC: KernelSpec","ARGMAX_SPEC: KernelSpec","rmsnormSpec: KernelSpec","seq_len: number","DUAL_RMSNORM_SPEC: KernelSpec","layernormSpec: KernelSpec","embeddingSpec: KernelSpec","embeddingInt4Spec: KernelSpec","ropeSpec: KernelSpec","ROPE_INTERLEAVED_SPEC: KernelSpec","mropeSpec: KernelSpec","embedSpliceSpec: KernelSpec","attentionSpec: KernelSpec","crossAttentionSpec: KernelSpec","KV_CACHE_APPEND_PACKED_F16_SPEC: KernelSpec","T: number","ATTENTION_PACKED_F16_SPEC: KernelSpec","softmaxSpec: KernelSpec","causalConv1dSpec: KernelSpec","causalConv1dSiluSpec: KernelSpec","causalConv1dGatedSpec: KernelSpec","sigmoidGateSpec: KernelSpec","mambaSSMSpec: KernelSpec","MAMBA_SSM_F16_SPEC: KernelSpec","kvCacheAppendSpec: KernelSpec","DUAL_KV_CACHE_APPEND_SPEC: KernelSpec","DUAL_KV_CACHE_APPEND_F16_SPEC: KernelSpec","DUAL_KV_CACHE_APPEND_PACKED_F16_SPEC: KernelSpec","KV_CACHE_APPEND_F16_SPEC: KernelSpec","ATTENTION_F16_SPEC: KernelSpec","convStateUpdateSpec: KernelSpec","LAYERNORM_NO_BIAS_SPEC: KernelSpec","tanhSpec: KernelSpec","transposeSpec: KernelSpec","groupnormSpec: KernelSpec","conv1dFullSpec: KernelSpec","convTranspose1dSpec: KernelSpec","snake1dSpec: KernelSpec","fsqDequantSpec: KernelSpec","halfSnake1dSpec: KernelSpec","convTranspose1dDepthwiseSpec: KernelSpec","KERNEL_REGISTRY: Partial<Record<OpType, KernelSpec>>","MAP_MODE_READ","prefillEntry: DispatchEntry","runtimeContext: RuntimeContext","dispatchSizes: Array<[number, number, number]>","vocabSize","dispatchSizes: number[][]","argmaxDispatchSizes: Array<[number, number, number]>","results: Array<{\n idx: number;\n nodeId: string;\n opType: string;\n tensor: string;\n sum: number;\n first4: number[];\n uniformParams?: number[];\n }>","uniformParams: number[] | undefined","resolved: Record<string, number[]>","bufferEntries: Array<{ buffer: GPUBuffer }>","fusedEntry: DispatchEntry","fusedSpec: KernelSpec","fusedNode: OpNode","entries: Array<{ buffer: GPUBuffer }>","buffer: GPUBuffer | undefined","_dbPromise: Promise<IDBDatabase | null> | null","_client: OpfsClient | null","stale: string[]","header: Record<string, unknown>","entries: SafetensorEntry[]","metadata: Record<string, string> | null","bs: number[]","ids: number[]","pieces: string[]","byteRun: number[]","chars: number[]","parts: string[]","parts: Array<{ text: string; special: boolean }>","match: RegExpExecArray | null","chunks: string[]","buf: ArrayBuffer | null","_fs: typeof import(\"node:fs\") | null","_path: typeof import(\"node:path\") | null","buf: Buffer","headers: Record<string, string>","chunks: Uint8Array[]","resp: Response","ranges: ByteRange[]","current: ByteRange","uncached: SafetensorEntry[]","resolvedDtype: GraphDType | undefined","store: MutableWeightStore","cachedBuffer: ArrayBuffer | null","data: ArrayBufferView","isFullAttn: boolean","normKeys: string[]","gemmaNormKeys: string[]","N: number","K: number","pleSource: PleSource | undefined","missingTensors: string[]","sizes: Array<[number, number, number]>","enc","p","encK: Float32Array[]","encV: Float32Array[]","resolved: Record<string, number[]>","entries: Array<{ buffer: GPUBuffer }>","encK: Float32Array[]","encV: Float32Array[]","frames: number","tokens: number[]"],"sources":["../src/gpu/device.ts","../src/gpu/kernels/registry.ts","../src/gpu/executor.ts","../src/gpu/gptq-adapter.ts","../src/gpu/idb-cache.ts","../src/gpu/mlx-adapter.ts","../src/gpu/opfs-cache.ts","../src/gpu/safetensors.ts","../src/gpu/tokenizer.ts","../src/gpu/weight-source.ts","../src/gpu/model-loader.ts","../src/gpu/moonshine-executor.ts","../src/gpu/moonshine-stt.ts"],"sourcesContent":["/**\n * WebGPU device abstraction layer.\n *\n * Wraps GPUDevice with helpers for buffer allocation, pipeline compilation,\n * compute dispatch, and readback. All GPU interaction flows through here.\n */\n\n// WebGPU buffer usage flags (numeric for Node.js Dawn polyfill compatibility)\nconst STORAGE = 0x0080;\nconst COPY_SRC = 0x0004;\nconst COPY_DST = 0x0008;\nconst UNIFORM = 0x0040;\nconst MAP_READ = 0x0001;\nconst MAP_MODE_READ = 0x0001;\n\nexport interface GPUContext {\n /** The underlying WebGPU device. */\n device: GPUDevice;\n /** Device limits (max buffer size, workgroup size, etc.). */\n limits: GPUSupportedLimits;\n /** Whether f16 is supported as a shader type. */\n hasF16: boolean;\n /**\n * Whether the WebGPU `subgroups` feature is available (Chrome 134+, Safari 26+).\n * When true, kernels may use `subgroupAdd`/`subgroupBroadcast` etc. (requires\n * `enable subgroups;` in the shader). Absence falls back to the portable\n * shared-memory reductions — never assume this is present.\n */\n hasSubgroups: boolean;\n /** True when the WebGPU implementation is WebKit's (Safari, all iOS/iPadOS browsers). */\n isWebKitWebGPU: boolean;\n /** Whether the `timestamp-query` feature is available (per-pass GPU timing). Used\n * only by the env-gated decode profiler; never on the normal inference path. */\n hasTimestamp: boolean;\n /** Raw adapter info string for diagnostics. */\n adapterDescription: string;\n}\n\nexport interface InitGPUOptions {\n /** Called when the GPU device is lost (e.g. tab backgrounded on iOS). */\n onDeviceLost?: (reason: string, message: string) => void;\n}\n\n/**\n * Initialize WebGPU and request a device with the features we need.\n *\n * In Node.js, initializes Dawn's WebGPU polyfill if navigator.gpu is absent.\n * In the browser, uses the native WebGPU API directly.\n *\n * Throws a clear error if WebGPU is unavailable.\n */\nexport async function initGPU(options?: InitGPUOptions): Promise<GPUContext> {\n // Node.js: initialize Dawn polyfill if needed\n if ((typeof navigator === \"undefined\" || !navigator.gpu) && typeof process !== \"undefined\") {\n try {\n const dynamicImport = new Function(\"specifier\", \"return import(specifier)\");\n const webgpuModule = await dynamicImport(\"webgpu\");\n const { create } = webgpuModule;\n if (!(globalThis as any).navigator) {\n (globalThis as any).navigator = {} as Navigator;\n }\n (globalThis as any).navigator.gpu = create([]);\n } catch {\n // Dawn not available\n }\n }\n\n if (typeof navigator === \"undefined\" || !navigator.gpu) {\n throw new Error(\n \"WebGPU is not available in this environment. \" +\n \"Requires Chrome 113+, Safari 26+ (iOS 26+), or Firefox 141+.\",\n );\n }\n\n // Detect mobile for power preference\n const isMobile =\n typeof navigator !== \"undefined\" && /iPhone|iPad|iPod|Android/i.test(navigator.userAgent);\n\n const adapter = await navigator.gpu.requestAdapter({\n powerPreference: isMobile ? \"low-power\" : \"high-performance\",\n });\n\n if (!adapter) {\n throw new Error(\n \"No WebGPU adapter found. Your GPU may not support WebGPU, \" +\n \"or another tab may be using it. Try closing other tabs.\",\n );\n }\n\n // Check for f16 support\n const hasF16 = adapter.features.has(\"shader-f16\");\n\n // Check for subgroups support (Chrome 134+, Safari 26+). Optional — kernels\n // fall back to portable shared-memory reductions when absent.\n const hasSubgroups = adapter.features.has(\"subgroups\" as GPUFeatureName);\n\n // timestamp-query: per-pass GPU timing for the env-gated decode profiler. Only\n // requested when GERBIL_PROFILE is set so the normal path is untouched.\n const wantTimestamp = typeof process !== \"undefined\" && process.env?.GERBIL_PROFILE != null;\n const hasTimestamp = wantTimestamp && adapter.features.has(\"timestamp-query\");\n\n // Request device with f16 if available\n const requiredFeatures: GPUFeatureName[] = [];\n if (hasF16) {\n requiredFeatures.push(\"shader-f16\");\n }\n if (hasSubgroups) {\n requiredFeatures.push(\"subgroups\" as GPUFeatureName);\n }\n if (hasTimestamp) {\n requiredFeatures.push(\"timestamp-query\");\n }\n\n // Request limits conservatively — don't exceed adapter limits\n const requestLimits: Record<string, number> = {\n maxBufferSize: adapter.limits.maxBufferSize,\n maxStorageBufferBindingSize: adapter.limits.maxStorageBufferBindingSize,\n maxComputeWorkgroupSizeX: Math.min(256, adapter.limits.maxComputeWorkgroupSizeX),\n maxComputeWorkgroupSizeY: Math.min(256, adapter.limits.maxComputeWorkgroupSizeY),\n maxComputeWorkgroupStorageSize: adapter.limits.maxComputeWorkgroupStorageSize,\n maxStorageBuffersPerShaderStage: adapter.limits.maxStorageBuffersPerShaderStage,\n };\n\n let device: GPUDevice;\n try {\n device = await adapter.requestDevice({\n requiredFeatures,\n requiredLimits: requestLimits,\n });\n } catch (e) {\n // Retry with minimal limits if the first attempt fails (e.g. on low-end iPad)\n try {\n device = await adapter.requestDevice({ requiredFeatures });\n } catch {\n throw new Error(\n `Failed to create WebGPU device: ${e instanceof Error ? e.message : String(e)}. ` +\n \"Your GPU may not meet the minimum requirements.\",\n );\n }\n }\n\n // Handle device loss\n device.lost.then((info) => {\n console.error(`[Gerbil] WebGPU device lost: ${info.reason}`, info.message);\n options?.onDeviceLost?.(info.reason, info.message);\n });\n\n // Surface errors that no error scope caught — without this, failed buffer\n // allocations and invalid bind groups on Safari are completely silent and\n // the affected dispatches just no-op (reads as zero logits).\n try {\n device.onuncapturederror = (ev: GPUUncapturedErrorEvent) => {\n console.error(`[Gerbil] Uncaptured GPU error: ${ev.error?.message ?? ev.error}`);\n };\n } catch {\n // not supported by this implementation (e.g. older Dawn polyfill)\n }\n\n // Detect WebKit's WebGPU implementation, NOT Apple GPU hardware: Dawn on Apple\n // Silicon (node-dawn, Chrome on macOS) also reports vendor \"apple\"/arch \"common-*\",\n // so adapter info cannot distinguish implementations. Only WebKit's implementation\n // needs the Safari coherence workarounds; hardware-based detection routed desktop\n // Dawn through them (161 -> 19.7 tok/s regression).\n let adapterDescription = \"\";\n try {\n const info = (adapter as any).info ?? (adapter as any).getInfo?.();\n if (info) {\n adapterDescription = `vendor=${info.vendor ?? \"\"} arch=${info.architecture ?? \"\"} device=${info.device ?? \"\"} desc=${info.description ?? \"\"}`;\n }\n } catch {\n // adapter.info not available\n }\n\n // All iOS/iPadOS browsers are WebKit; on macOS only Safari is (Chrome/Chromium UAs\n // contain \"Chrome/\"). Node (Dawn) has no AppleWebKit UA and resolves to false.\n const ua = typeof navigator !== \"undefined\" && navigator.userAgent ? navigator.userAgent : \"\";\n const isAppleWebKit = ua.includes(\"AppleWebKit\");\n const isIOSOrIPadOS =\n /iPhone|iPad|iPod/.test(ua) ||\n (isAppleWebKit && typeof navigator !== \"undefined\" && (navigator.maxTouchPoints ?? 0) > 1);\n const isSafariMac = isAppleWebKit && !ua.includes(\"Chrome/\") && !isIOSOrIPadOS;\n const isWebKitWebGPU = isIOSOrIPadOS || isSafariMac;\n if (!adapterDescription) {\n adapterDescription = `UA-fallback: ${ua.slice(0, 120)}`;\n }\n\n return {\n device,\n limits: device.limits,\n hasF16,\n hasSubgroups,\n isWebKitWebGPU,\n hasTimestamp,\n adapterDescription,\n };\n}\n\n// ── Buffer helpers ────────────────────────────────────────────────────\n\n/**\n * Create a GPU storage buffer and optionally upload data to it.\n */\nexport function createStorageBuffer(\n ctx: GPUContext,\n label: string,\n sizeBytes: number,\n data?: ArrayBuffer | ArrayBufferView,\n): GPUBuffer {\n const usage = STORAGE | COPY_SRC | COPY_DST;\n\n // Align size to 4 bytes (WebGPU requirement)\n const alignedSize = Math.ceil(sizeBytes / 4) * 4;\n\n const buffer = ctx.device.createBuffer({\n label,\n size: alignedSize,\n usage,\n });\n\n // Use writeBuffer instead of mappedAtCreation — Safari/WebKit's mappedAtCreation\n // may not properly persist data after unmap(), causing all-zero weight buffers.\n if (data) {\n if (data instanceof ArrayBuffer) {\n ctx.device.queue.writeBuffer(buffer, 0, data);\n } else {\n ctx.device.queue.writeBuffer(buffer, 0, data.buffer, data.byteOffset, data.byteLength);\n }\n }\n\n return buffer;\n}\n\n/**\n * Create a uniform buffer for passing parameters to shaders.\n */\nexport function createUniformBuffer(\n ctx: GPUContext,\n label: string,\n data: ArrayBuffer | ArrayBufferView,\n): GPUBuffer {\n const byteLength = data instanceof ArrayBuffer ? data.byteLength : data.byteLength;\n\n // Use STORAGE instead of UNIFORM to bypass Safari/Metal bugs with uniform buffer\n // visibility across many compute passes. WGSL kernels use var<storage, read> for params.\n // Keep 16-byte alignment for compatibility.\n const alignedSize = Math.ceil(byteLength / 16) * 16;\n\n const buffer = ctx.device.createBuffer({\n label,\n size: alignedSize,\n usage: STORAGE | COPY_SRC | COPY_DST,\n });\n\n if (data instanceof ArrayBuffer) {\n ctx.device.queue.writeBuffer(buffer, 0, data);\n } else {\n ctx.device.queue.writeBuffer(buffer, 0, data.buffer, data.byteOffset, data.byteLength);\n }\n\n return buffer;\n}\n\n/**\n * Create a staging buffer for reading GPU results back to CPU.\n */\nexport function createReadbackBuffer(ctx: GPUContext, label: string, sizeBytes: number): GPUBuffer {\n const alignedSize = Math.ceil(sizeBytes / 4) * 4;\n return ctx.device.createBuffer({\n label,\n size: alignedSize,\n usage: MAP_READ | COPY_DST,\n });\n}\n\n/**\n * Update a uniform buffer's contents.\n */\nexport function writeUniformBuffer(\n ctx: GPUContext,\n buffer: GPUBuffer,\n data: ArrayBuffer | ArrayBufferView,\n): void {\n if (data instanceof ArrayBuffer) {\n ctx.device.queue.writeBuffer(buffer, 0, data);\n } else {\n ctx.device.queue.writeBuffer(buffer, 0, data.buffer, data.byteOffset, data.byteLength);\n }\n}\n\n// ── Pipeline helpers ──────────────────────────────────────────────────\n\n/**\n * Per-device pipeline cache. Pipelines are bound to the GPUDevice that created\n * them — using a pipeline from device1 with device2 causes WebGPU validation\n * errors. WeakMap ensures the cache is GC'd when the device is destroyed.\n */\nconst perDeviceCache = new WeakMap<GPUDevice, Map<string, GPUComputePipeline>>();\n\n/** Get or initialize the pipeline cache for a specific device. */\nfunction getPipelineCache(device: GPUDevice): Map<string, GPUComputePipeline> {\n let cache = perDeviceCache.get(device);\n if (!cache) {\n cache = new Map();\n perDeviceCache.set(device, cache);\n }\n return cache;\n}\n\n/**\n * Get or create a compute pipeline from WGSL source code.\n */\nexport function getOrCreatePipeline(\n ctx: GPUContext,\n label: string,\n wgslCode: string,\n entryPoint: string = \"main\",\n bindGroupLayoutEntries?: GPUBindGroupLayoutEntry[],\n): GPUComputePipeline {\n const pipelineCache = getPipelineCache(ctx.device);\n const cacheKey = `${wgslCode}::${entryPoint}`;\n const cached = pipelineCache.get(cacheKey);\n if (cached) return cached;\n\n let code = wgslCode;\n\n // Only prepend f16 enable if the shader actually uses f16 types.\n // Enabling it unconditionally can trigger buggy codepaths in Safari/WebKit's\n // WGSL→Metal compiler even when no f16 types are used.\n const usesF16 = code.includes(\": f16\") || code.includes(\"<f16>\");\n if (ctx.hasF16 && usesF16 && !code.includes(\"enable f16\")) {\n code = `enable f16;\\n\\n${code}`;\n }\n\n const shaderModule = ctx.device.createShaderModule({\n label: `${label}_shader`,\n code,\n });\n\n let layout: GPUPipelineLayout | \"auto\" = \"auto\";\n if (bindGroupLayoutEntries) {\n const bindGroupLayout = ctx.device.createBindGroupLayout({\n label: `${label}_layout`,\n entries: bindGroupLayoutEntries,\n });\n layout = ctx.device.createPipelineLayout({\n label: `${label}_pipeline_layout`,\n bindGroupLayouts: [bindGroupLayout],\n });\n }\n\n const pipeline = ctx.device.createComputePipeline({\n label,\n layout,\n compute: {\n module: shaderModule,\n entryPoint,\n },\n });\n\n pipelineCache.set(cacheKey, pipeline);\n return pipeline;\n}\n\n/**\n * Clear the pipeline cache for a specific device, or no-op if called without\n * a device (WeakMap handles cleanup automatically when the device is GC'd).\n */\nexport function clearPipelineCache(device?: GPUDevice): void {\n if (device) {\n perDeviceCache.delete(device);\n }\n}\n\n// ── Bind group helpers ────────────────────────────────────────────────\n\nexport interface BindGroupEntry {\n buffer: GPUBuffer;\n offset?: number;\n size?: number;\n}\n\n/**\n * Create a bind group from a list of buffers.\n */\nexport function createBindGroup(\n ctx: GPUContext,\n pipeline: GPUComputePipeline,\n entries: BindGroupEntry[],\n label?: string,\n): GPUBindGroup {\n return ctx.device.createBindGroup({\n label,\n layout: pipeline.getBindGroupLayout(0),\n entries: entries.map((entry, i) => ({\n binding: i,\n resource: {\n buffer: entry.buffer,\n offset: entry.offset ?? 0,\n size: entry.size ?? entry.buffer.size,\n },\n })),\n });\n}\n\n// ── Dispatch helpers ──────────────────────────────────────────────────\n\n/**\n * Encode and submit a single compute dispatch.\n * Useful for testing; the executor batches dispatches in practice.\n */\nexport function dispatchOnce(\n ctx: GPUContext,\n pipeline: GPUComputePipeline,\n bindGroup: GPUBindGroup,\n workgroupCountX: number,\n workgroupCountY: number = 1,\n workgroupCountZ: number = 1,\n): void {\n const encoder = ctx.device.createCommandEncoder();\n const pass = encoder.beginComputePass();\n pass.setPipeline(pipeline);\n pass.setBindGroup(0, bindGroup);\n pass.dispatchWorkgroups(workgroupCountX, workgroupCountY, workgroupCountZ);\n pass.end();\n ctx.device.queue.submit([encoder.finish()]);\n}\n\n// ── Readback helpers ──────────────────────────────────────────────────\n\n/**\n * Copy data from a storage buffer to a readback buffer, then map and return\n * the contents as a Float32Array.\n *\n * This is the primary way to get results (logits) back to CPU for sampling.\n */\nexport async function readbackFloats(\n ctx: GPUContext,\n source: GPUBuffer,\n readback: GPUBuffer,\n byteOffset: number = 0,\n byteLength?: number,\n): Promise<Float32Array> {\n const length = byteLength ?? source.size - byteOffset;\n\n const encoder = ctx.device.createCommandEncoder();\n encoder.copyBufferToBuffer(source, byteOffset, readback, 0, length);\n ctx.device.queue.submit([encoder.finish()]);\n\n await readback.mapAsync(MAP_MODE_READ, 0, length);\n const mapped = readback.getMappedRange(0, length);\n const result = new Float32Array(mapped.slice(0));\n readback.unmap();\n\n return result;\n}\n\n/**\n * Read back raw bytes from a GPU buffer.\n */\nexport async function readbackBytes(\n ctx: GPUContext,\n source: GPUBuffer,\n readback: GPUBuffer,\n byteOffset: number = 0,\n byteLength?: number,\n): Promise<ArrayBuffer> {\n const length = byteLength ?? source.size - byteOffset;\n\n const encoder = ctx.device.createCommandEncoder();\n encoder.copyBufferToBuffer(source, byteOffset, readback, 0, length);\n ctx.device.queue.submit([encoder.finish()]);\n\n await readback.mapAsync(MAP_MODE_READ, 0, length);\n const mapped = readback.getMappedRange(0, length);\n const result = mapped.slice(0);\n readback.unmap();\n\n return result;\n}\n\n// ── Memory helpers ────────────────────────────────────────────────────\n\n/**\n * Destroy a list of GPU buffers and release their memory.\n */\nexport function destroyBuffers(buffers: GPUBuffer[]): void {\n for (const buf of buffers) {\n buf.destroy();\n }\n}\n\n// ── GPU diagnostics ──────────────────────────────────────────────────\n\nexport interface GPUDiagnosticResult {\n /** Whether the basic buffer upload → readback round-trip works. */\n bufferIntegrity: boolean;\n /** Whether a trivial compute shader executes correctly. */\n computeWorks: boolean;\n /** Whether shared memory + workgroupBarrier() works. */\n sharedMemoryWorks: boolean;\n /** Detailed messages for each test. */\n details: string[];\n}\n\n/**\n * Run GPU diagnostics to verify basic WebGPU operations work correctly.\n * Useful for isolating Safari/WebKit-specific issues.\n */\nexport async function verifyGPU(ctx: GPUContext): Promise<GPUDiagnosticResult> {\n const details: string[] = [];\n let bufferIntegrity = false;\n let computeWorks = false;\n let sharedMemoryWorks = false;\n\n // Report device limits\n try {\n details.push(\n `Limits: wgSize=${ctx.limits.maxComputeWorkgroupSizeX}, ` +\n `wgStorage=${ctx.limits.maxComputeWorkgroupStorageSize}, ` +\n `maxBuf=${(ctx.limits.maxBufferSize / 1024 / 1024).toFixed(0)}MB, ` +\n `f16=${ctx.hasF16}, subgroups=${ctx.hasSubgroups}`,\n );\n } catch {}\n\n // Test 1: Buffer upload via writeBuffer → readback\n try {\n const testData = new Float32Array([1.0, 2.0, 3.0, 4.0, -1.5, 0.0, 100.0, 0.001]);\n const srcBuf = createStorageBuffer(ctx, \"diag_src\", testData.byteLength, testData);\n const readBuf = createReadbackBuffer(ctx, \"diag_read\", testData.byteLength);\n const result = await readbackFloats(ctx, srcBuf, readBuf, 0, testData.byteLength);\n\n let match = true;\n for (let i = 0; i < testData.length; i++) {\n if (Math.abs(result[i] - testData[i]) > 1e-6) {\n details.push(`Buffer mismatch at [${i}]: expected ${testData[i]}, got ${result[i]}`);\n match = false;\n }\n }\n bufferIntegrity = match;\n details.push(\n bufferIntegrity ? \"✓ Buffer upload/readback OK\" : \"✗ Buffer upload/readback FAILED\",\n );\n srcBuf.destroy();\n readBuf.destroy();\n } catch (e) {\n details.push(`✗ Buffer test error: ${e instanceof Error ? e.message : String(e)}`);\n }\n\n // ── Isolation tests ──────────────────────────────────────────────\n // Test 2a PASSED (single encoder, wg=1, no input, onSubmittedWorkDone).\n // Test 2b FAILED (separate encoders, wg=4, has input, no wait).\n // Need to isolate: is it (A) separate encoders, (B) input binding, or (C) workgroup size?\n //\n // Matrix of tests — each changes ONE variable from the passing baseline:\n // Baseline: single encoder, wg=1, no input, with onSubmittedWorkDone → PASSES\n // Test A: SEPARATE encoders, wg=1, no input, no wait → tests queue ordering\n // Test B: single encoder, wg=1, HAS input, with wait → tests input binding\n // Test C: single encoder, wg=4, no input, with wait → tests workgroup size\n // Test D: single encoder, wg=256 + shared mem, no input, with wait → tests shared memory\n\n /** Helper: run a compute shader and read back result using a SINGLE command encoder + onSubmittedWorkDone. */\n const runSingleEncoder = async (\n label: string,\n code: string,\n bindings: GPUBuffer[],\n readBuf: GPUBuffer,\n outBuf: GPUBuffer,\n readBytes: number,\n workgroups: number = 1,\n ): Promise<Float32Array> => {\n const pipeline = getOrCreatePipeline(ctx, label, code, \"main\");\n const bg = createBindGroup(\n ctx,\n pipeline,\n bindings.map((b) => ({ buffer: b })),\n `${label}_bg`,\n );\n const encoder = ctx.device.createCommandEncoder();\n const pass = encoder.beginComputePass();\n pass.setPipeline(pipeline);\n pass.setBindGroup(0, bg);\n pass.dispatchWorkgroups(workgroups);\n pass.end();\n encoder.copyBufferToBuffer(outBuf, 0, readBuf, 0, readBytes);\n ctx.device.queue.submit([encoder.finish()]);\n if (ctx.device.queue.onSubmittedWorkDone) {\n await ctx.device.queue.onSubmittedWorkDone();\n }\n await readBuf.mapAsync(MAP_MODE_READ, 0, readBytes);\n const mapped = readBuf.getMappedRange(0, readBytes);\n const result = new Float32Array(mapped.slice(0));\n readBuf.unmap();\n return result;\n };\n\n /** Helper: run compute with SEPARATE encoders (like dispatchOnce + readbackFloats). */\n const runSeparateEncoders = async (\n label: string,\n code: string,\n bindings: GPUBuffer[],\n readBuf: GPUBuffer,\n outBuf: GPUBuffer,\n readBytes: number,\n workgroups: number = 1,\n ): Promise<Float32Array> => {\n const pipeline = getOrCreatePipeline(ctx, label, code, \"main\");\n const bg = createBindGroup(\n ctx,\n pipeline,\n bindings.map((b) => ({ buffer: b })),\n `${label}_bg`,\n );\n // Encoder 1: compute dispatch\n dispatchOnce(ctx, pipeline, bg, workgroups);\n // Encoder 2: copy + readback\n return readbackFloats(ctx, outBuf, readBuf, 0, readBytes);\n };\n\n // Test A: Separate encoders, wg=1, no input (isolates queue ordering)\n try {\n const shader = `\n @group(0) @binding(0) var<storage, read_write> out: array<f32>;\n @compute @workgroup_size(1)\n fn main() { out[0] = 42.0; }\n `;\n const outBuf = createStorageBuffer(ctx, \"dA_out\", 4);\n const readBuf = createReadbackBuffer(ctx, \"dA_read\", 4);\n const result = await runSeparateEncoders(\"dA\", shader, [outBuf], readBuf, outBuf, 4);\n const ok = Math.abs(result[0] - 42.0) < 1e-3;\n details.push(\n ok\n ? `✓ A: separate encoders OK (${result[0]})`\n : `✗ A: separate encoders FAILED (got ${result[0]})`,\n );\n outBuf.destroy();\n readBuf.destroy();\n } catch (e) {\n details.push(`✗ A error: ${e instanceof Error ? e.message : String(e)}`);\n }\n\n // Test B: Single encoder, wg=1, HAS input (isolates input binding)\n try {\n const shader = `\n @group(0) @binding(0) var<storage, read> src: array<f32>;\n @group(0) @binding(1) var<storage, read_write> dst: array<f32>;\n @compute @workgroup_size(1)\n fn main() { dst[0] = src[0] * 2.0 + 1.0; }\n `;\n const input = new Float32Array([3.0]);\n const inBuf = createStorageBuffer(ctx, \"dB_in\", 4, input);\n const outBuf = createStorageBuffer(ctx, \"dB_out\", 4);\n const readBuf = createReadbackBuffer(ctx, \"dB_read\", 4);\n const result = await runSingleEncoder(\"dB\", shader, [inBuf, outBuf], readBuf, outBuf, 4);\n const ok = Math.abs(result[0] - 7.0) < 1e-3;\n details.push(\n ok ? `✓ B: input binding OK (${result[0]})` : `✗ B: input binding FAILED (got ${result[0]})`,\n );\n computeWorks = ok;\n inBuf.destroy();\n outBuf.destroy();\n readBuf.destroy();\n } catch (e) {\n details.push(`✗ B error: ${e instanceof Error ? e.message : String(e)}`);\n }\n\n // Test C: Single encoder, wg=4, no input (isolates workgroup size)\n try {\n const shader = `\n @group(0) @binding(0) var<storage, read_write> out: array<f32>;\n @compute @workgroup_size(4)\n fn main(@builtin(local_invocation_id) lid: vec3u) {\n out[lid.x] = f32(lid.x) + 1.0;\n }\n `;\n const outBuf = createStorageBuffer(ctx, \"dC_out\", 16);\n const readBuf = createReadbackBuffer(ctx, \"dC_read\", 16);\n const result = await runSingleEncoder(\"dC\", shader, [outBuf], readBuf, outBuf, 16);\n const ok = Math.abs(result[0] - 1.0) < 1e-3 && Math.abs(result[3] - 4.0) < 1e-3;\n details.push(\n ok\n ? `✓ C: wg=4 OK (${Array.from(result).join(\",\")})`\n : `✗ C: wg=4 FAILED (${Array.from(result).join(\",\")})`,\n );\n outBuf.destroy();\n readBuf.destroy();\n } catch (e) {\n details.push(`✗ C error: ${e instanceof Error ? e.message : String(e)}`);\n }\n\n // Test D: Single encoder, wg=4 + shared memory (isolates shared mem)\n // Uses a small workgroup (4 threads) to avoid 256-thread driver issues in diagnostics.\n // Each thread writes to shared memory, barrier, thread 0 sums and writes to output.\n try {\n const shader = `\n var<workgroup> smem: array<f32, 4>;\n @group(0) @binding(0) var<storage, read_write> dst: array<f32>;\n @compute @workgroup_size(4)\n fn main(@builtin(local_invocation_id) lid: vec3u) {\n smem[lid.x] = f32(lid.x) + 1.0;\n workgroupBarrier();\n if (lid.x == 0u) {\n dst[0] = smem[0] + smem[1] + smem[2] + smem[3];\n }\n }\n `;\n const outBuf = createStorageBuffer(ctx, \"dD_out\", 4);\n const readBuf = createReadbackBuffer(ctx, \"dD_read\", 4);\n const result = await runSingleEncoder(\"dD\", shader, [outBuf], readBuf, outBuf, 4);\n sharedMemoryWorks = Math.abs(result[0] - 10.0) < 1e-3;\n details.push(\n sharedMemoryWorks\n ? `✓ D: shared mem OK (${result[0]})`\n : `✗ D: shared mem FAILED (got ${result[0]})`,\n );\n outBuf.destroy();\n readBuf.destroy();\n } catch (e) {\n details.push(`✗ D error: ${e instanceof Error ? e.message : String(e)}`);\n }\n\n // Test E: Separate encoders + input + wg=4 (the full failing combo, to confirm)\n try {\n const shader = `\n @group(0) @binding(0) var<storage, read> src: array<f32>;\n @group(0) @binding(1) var<storage, read_write> dst: array<f32>;\n @compute @workgroup_size(4)\n fn main(@builtin(global_invocation_id) gid: vec3u) {\n if (gid.x < 4u) { dst[gid.x] = src[gid.x] * 2.0 + 1.0; }\n }\n `;\n const input = new Float32Array([3.0, 7.0, -2.0, 0.5]);\n const inBuf = createStorageBuffer(ctx, \"dE_in\", 16, input);\n const outBuf = createStorageBuffer(ctx, \"dE_out\", 16);\n const readBuf = createReadbackBuffer(ctx, \"dE_read\", 16);\n const result = await runSeparateEncoders(\"dE\", shader, [inBuf, outBuf], readBuf, outBuf, 16);\n const ok = Math.abs(result[0] - 7.0) < 1e-3 && Math.abs(result[1] - 15.0) < 1e-3;\n details.push(\n ok ? `✓ E: full combo OK` : `✗ E: full combo FAILED (${Array.from(result).join(\",\")})`,\n );\n inBuf.destroy();\n outBuf.destroy();\n readBuf.destroy();\n } catch (e) {\n details.push(`✗ E error: ${e instanceof Error ? e.message : String(e)}`);\n }\n\n // Test F: pack2x16float / unpack2x16float round-trip (tests the packed-f16 KV path)\n // Packs pairs of f32 → u32 via pack2x16float, then unpacks back via unpack2x16float.\n // If this fails on Safari/Metal, the packed KV cache will produce gibberish.\n try {\n const shader = `\n @group(0) @binding(0) var<storage, read> src: array<f32>;\n @group(0) @binding(1) var<storage, read_write> dst: array<f32>;\n @compute @workgroup_size(1)\n fn main() {\n // Pack pairs of f32 into u32, then unpack back\n let packed0 = pack2x16float(vec2f(src[0], src[1]));\n let packed1 = pack2x16float(vec2f(src[2], src[3]));\n let packed2 = pack2x16float(vec2f(src[4], src[5]));\n let packed3 = pack2x16float(vec2f(src[6], src[7]));\n\n let u0 = unpack2x16float(packed0);\n let u1 = unpack2x16float(packed1);\n let u2 = unpack2x16float(packed2);\n let u3 = unpack2x16float(packed3);\n\n dst[0] = u0.x; dst[1] = u0.y;\n dst[2] = u1.x; dst[3] = u1.y;\n dst[4] = u2.x; dst[5] = u2.y;\n dst[6] = u3.x; dst[7] = u3.y;\n }\n `;\n // Values chosen to cover: positive, negative, zero, small, >1\n const input = new Float32Array([1.0, -0.5, 0.0, 3.14, 0.001, -2.0, 100.0, 0.25]);\n const inBuf = createStorageBuffer(ctx, \"dF_in\", input.byteLength, input);\n const outBuf = createStorageBuffer(ctx, \"dF_out\", input.byteLength);\n const readBuf = createReadbackBuffer(ctx, \"dF_read\", input.byteLength);\n const result = await runSingleEncoder(\n \"dF\",\n shader,\n [inBuf, outBuf],\n readBuf,\n outBuf,\n input.byteLength,\n );\n // f16 has limited precision, so use larger tolerance\n let ok = true;\n const mismatches: string[] = [];\n for (let i = 0; i < input.length; i++) {\n const tol = Math.max(Math.abs(input[i]) * 0.01, 0.001); // 1% or 0.001\n if (Math.abs(result[i] - input[i]) > tol) {\n ok = false;\n mismatches.push(`[${i}]:${input[i]}→${result[i]}`);\n }\n }\n details.push(\n ok\n ? `✓ F: pack/unpack2x16float OK`\n : `✗ F: pack/unpack2x16float FAILED (${mismatches.join(\", \")})`,\n );\n inBuf.destroy();\n outBuf.destroy();\n readBuf.destroy();\n } catch (e) {\n details.push(`✗ F error: ${e instanceof Error ? e.message : String(e)}`);\n }\n\n // Test G: Packed KV-like pattern — pack into array<u32>, read back with elem indexing\n // Mimics the actual KV cache append + attention load_k/load_v pattern.\n try {\n const shader = `\n @group(0) @binding(0) var<storage, read> src: array<f32>;\n @group(0) @binding(1) var<storage, read_write> packed: array<u32>;\n @group(0) @binding(2) var<storage, read_write> dst: array<f32>;\n @compute @workgroup_size(1)\n fn main() {\n // Step 1: Pack f32 pairs into u32 array (like KVCacheAppend)\n packed[0] = pack2x16float(vec2f(src[0], src[1]));\n packed[1] = pack2x16float(vec2f(src[2], src[3]));\n packed[2] = pack2x16float(vec2f(src[4], src[5]));\n packed[3] = pack2x16float(vec2f(src[6], src[7]));\n\n // Step 2: Read back using element-level indexing (like load_k/load_v)\n for (var i: u32 = 0u; i < 8u; i += 1u) {\n let pair = unpack2x16float(packed[i >> 1u]);\n if ((i & 1u) == 0u) {\n dst[i] = pair.x;\n } else {\n dst[i] = pair.y;\n }\n }\n }\n `;\n const input = new Float32Array([1.5, -0.75, 2.0, 0.0, -1.0, 0.5, 3.0, -3.0]);\n const inBuf = createStorageBuffer(ctx, \"dG_in\", input.byteLength, input);\n const packedBuf = createStorageBuffer(ctx, \"dG_packed\", 16); // 4 u32 = 16 bytes\n const outBuf = createStorageBuffer(ctx, \"dG_out\", input.byteLength);\n const readBuf = createReadbackBuffer(ctx, \"dG_read\", input.byteLength);\n const result = await runSingleEncoder(\n \"dG\",\n shader,\n [inBuf, packedBuf, outBuf],\n readBuf,\n outBuf,\n input.byteLength,\n );\n let ok = true;\n const mismatches: string[] = [];\n for (let i = 0; i < input.length; i++) {\n const tol = Math.max(Math.abs(input[i]) * 0.01, 0.001);\n if (Math.abs(result[i] - input[i]) > tol) {\n ok = false;\n mismatches.push(`[${i}]:${input[i]}→${result[i]}`);\n }\n }\n details.push(\n ok\n ? `✓ G: packed KV round-trip OK`\n : `✗ G: packed KV round-trip FAILED (${mismatches.join(\", \")})`,\n );\n inBuf.destroy();\n packedBuf.destroy();\n outBuf.destroy();\n readBuf.destroy();\n } catch (e) {\n details.push(`✗ G error: ${e instanceof Error ? e.message : String(e)}`);\n }\n\n // Test H: Tree reduction with 256 threads + shared memory (RMSNorm/softmax pattern)\n // 256 threads each write a value, then tree-reduce to sum. Tests the exact\n // shared memory reduction pattern used in RMSNorm, softmax, and dot products.\n try {\n const shader = `\n var<workgroup> smem: array<f32, 256>;\n @group(0) @binding(0) var<storage, read> src: array<f32>;\n @group(0) @binding(1) var<storage, read_write> dst: array<f32>;\n @compute @workgroup_size(256)\n fn main(@builtin(local_invocation_id) lid: vec3u) {\n let tid = lid.x;\n smem[tid] = src[tid] * src[tid];\n workgroupBarrier();\n var stride: u32 = 128u;\n while (stride > 0u) {\n if (tid < stride) {\n smem[tid] += smem[tid + stride];\n }\n workgroupBarrier();\n stride /= 2u;\n }\n // Thread 0 writes sum, all threads write their squared input for verification\n dst[tid] = src[tid] * src[tid];\n if (tid == 0u) { dst[256] = smem[0]; }\n }\n `;\n // Input: 1..256, expected sum of squares = n(n+1)(2n+1)/6 = 5559680\n const input = new Float32Array(256);\n for (let i = 0; i < 256; i++) input[i] = i + 1;\n const expectedSum = (256 * 257 * 513) / 6; // 5559680\n const inBuf = createStorageBuffer(ctx, \"dH_in\", input.byteLength, input);\n const outBuf = createStorageBuffer(ctx, \"dH_out\", 257 * 4);\n const readBuf = createReadbackBuffer(ctx, \"dH_read\", 257 * 4);\n const result = await runSingleEncoder(\"dH\", shader, [inBuf, outBuf], readBuf, outBuf, 257 * 4);\n const sum = result[256];\n const ok = Math.abs(sum - expectedSum) / expectedSum < 0.001;\n details.push(\n ok\n ? `✓ H: 256-thread tree reduce OK (${sum})`\n : `✗ H: 256-thread tree reduce FAILED (got ${sum}, expected ${expectedSum})`,\n );\n inBuf.destroy();\n outBuf.destroy();\n readBuf.destroy();\n } catch (e) {\n details.push(`✗ H error: ${e instanceof Error ? e.message : String(e)}`);\n }\n\n // Test I: INT4 dequantize pattern (bit manipulation + multiply-add)\n // Tests the exact u32→nibble extraction used in MatMulInt4 and SwiGLU INT4 kernels.\n try {\n const shader = `\n @group(0) @binding(0) var<storage, read> packed: array<u32>;\n @group(0) @binding(1) var<storage, read> scales: array<f32>;\n @group(0) @binding(2) var<storage, read> zeros: array<f32>;\n @group(0) @binding(3) var<storage, read_write> dst: array<f32>;\n @compute @workgroup_size(1)\n fn main() {\n // Unpack 8 nibbles from one u32, dequantize: (nibble - zero) * scale\n let word = packed[0];\n let scale = scales[0];\n let zero = zeros[0];\n for (var i: u32 = 0u; i < 8u; i += 1u) {\n let nibble = (word >> (i * 4u)) & 0xFu;\n dst[i] = (f32(nibble) - zero) * scale;\n }\n }\n `;\n // Pack nibbles 0,1,2,...,7 into one u32: 0x76543210\n const packedData = new Uint32Array([0x76543210]);\n const scaleData = new Float32Array([0.5]);\n const zeroData = new Float32Array([2.0]);\n // Expected: (0-2)*0.5=-1, (1-2)*0.5=-0.5, (2-2)*0.5=0, (3-2)*0.5=0.5, ...\n const expected = [0, 1, 2, 3, 4, 5, 6, 7].map((n) => (n - 2.0) * 0.5);\n\n const packedBuf = createStorageBuffer(ctx, \"dI_packed\", 4, packedData);\n const scaleBuf = createStorageBuffer(ctx, \"dI_scale\", 4, scaleData);\n const zeroBuf = createStorageBuffer(ctx, \"dI_zero\", 4, zeroData);\n const outBuf = createStorageBuffer(ctx, \"dI_out\", 32);\n const readBuf = createReadbackBuffer(ctx, \"dI_read\", 32);\n const result = await runSingleEncoder(\n \"dI\",\n shader,\n [packedBuf, scaleBuf, zeroBuf, outBuf],\n readBuf,\n outBuf,\n 32,\n );\n let ok = true;\n const mismatches: string[] = [];\n for (let i = 0; i < 8; i++) {\n if (Math.abs(result[i] - expected[i]) > 0.001) {\n ok = false;\n mismatches.push(`[${i}]:${expected[i]}→${result[i]}`);\n }\n }\n details.push(\n ok ? `✓ I: INT4 dequant OK` : `✗ I: INT4 dequant FAILED (${mismatches.join(\", \")})`,\n );\n packedBuf.destroy();\n scaleBuf.destroy();\n zeroBuf.destroy();\n outBuf.destroy();\n readBuf.destroy();\n } catch (e) {\n details.push(`✗ I error: ${e instanceof Error ? e.message : String(e)}`);\n }\n\n // Test J: Multi-dispatch chain (tests sequential dispatch ordering)\n // Runs 3 dispatches in sequence within one command encoder: A→B→C.\n // If dispatch ordering is broken, intermediate results will be wrong.\n try {\n const shaderAdd = `\n @group(0) @binding(0) var<storage, read> a: array<f32>;\n @group(0) @binding(1) var<storage, read_write> b: array<f32>;\n @compute @workgroup_size(4)\n fn main(@builtin(global_invocation_id) gid: vec3u) {\n if (gid.x < 4u) { b[gid.x] = a[gid.x] + b[gid.x]; }\n }\n `;\n const shaderMul = `\n @group(0) @binding(0) var<storage, read_write> x: array<f32>;\n @compute @workgroup_size(4)\n fn main(@builtin(global_invocation_id) gid: vec3u) {\n if (gid.x < 4u) { x[gid.x] = x[gid.x] * 2.0; }\n }\n `;\n // Chain: buf = [1,2,3,4], add [10,20,30,40] → [11,22,33,44], mul ×2 → [22,44,66,88], add again → [32,64,96,128]... actually simpler:\n // Start: buf = [0,0,0,0]\n // Dispatch 1: buf += src ([1,2,3,4])\n // Dispatch 2: buf *= 2\n // Dispatch 3: buf += src again\n // Expected: ([1,2,3,4] * 2) + [1,2,3,4] = [3,6,9,12]\n const srcData = new Float32Array([1, 2, 3, 4]);\n const srcBuf = createStorageBuffer(ctx, \"dJ_src\", 16, srcData);\n const bufBuf = createStorageBuffer(ctx, \"dJ_buf\", 16, new Float32Array(4)); // zeros\n const readBuf = createReadbackBuffer(ctx, \"dJ_read\", 16);\n\n const addPipeline = getOrCreatePipeline(ctx, \"dJ_add\", shaderAdd, \"main\");\n const mulPipeline = getOrCreatePipeline(ctx, \"dJ_mul\", shaderMul, \"main\");\n\n const addBg = createBindGroup(\n ctx,\n addPipeline,\n [{ buffer: srcBuf }, { buffer: bufBuf }],\n \"dJ_add_bg\",\n );\n const mulBg = createBindGroup(ctx, mulPipeline, [{ buffer: bufBuf }], \"dJ_mul_bg\");\n\n const encoder = ctx.device.createCommandEncoder();\n const pass = encoder.beginComputePass();\n pass.setPipeline(addPipeline);\n pass.setBindGroup(0, addBg);\n pass.dispatchWorkgroups(1); // buf = [1,2,3,4]\n pass.setPipeline(mulPipeline);\n pass.setBindGroup(0, mulBg);\n pass.dispatchWorkgroups(1); // buf = [2,4,6,8]\n pass.setPipeline(addPipeline);\n pass.setBindGroup(0, addBg);\n pass.dispatchWorkgroups(1); // buf = [3,6,9,12]\n pass.end();\n encoder.copyBufferToBuffer(bufBuf, 0, readBuf, 0, 16);\n ctx.device.queue.submit([encoder.finish()]);\n if (ctx.device.queue.onSubmittedWorkDone) {\n await ctx.device.queue.onSubmittedWorkDone();\n }\n await readBuf.mapAsync(MAP_MODE_READ, 0, 16);\n const mapped = readBuf.getMappedRange(0, 16);\n const result = new Float32Array(mapped.slice(0));\n readBuf.unmap();\n\n const expected = [3, 6, 9, 12];\n let ok = true;\n for (let i = 0; i < 4; i++) {\n if (Math.abs(result[i] - expected[i]) > 0.01) ok = false;\n }\n details.push(\n ok\n ? `✓ J: multi-dispatch chain OK (${Array.from(result).join(\",\")})`\n : `✗ J: multi-dispatch chain FAILED (got ${Array.from(result).join(\",\")}, expected ${expected.join(\",\")})`,\n );\n srcBuf.destroy();\n bufBuf.destroy();\n readBuf.destroy();\n } catch (e) {\n details.push(`✗ J error: ${e instanceof Error ? e.message : String(e)}`);\n }\n\n // Test K: exp() clamp safety (Metal fast-math NaN check)\n // Tests that exp(large_negative) returns ~0 and not NaN.\n try {\n const shader = `\n @group(0) @binding(0) var<storage, read_write> dst: array<f32>;\n @compute @workgroup_size(1)\n fn main() {\n dst[0] = exp(max(-1000.0, -80.0)); // should be exp(-80) ≈ 1.8e-35\n dst[1] = exp(max(-1e30, -80.0)); // should be exp(-80) ≈ 1.8e-35\n dst[2] = exp(-10.0); // should be ~4.54e-5\n dst[3] = 1.0 / (1.0 + exp(max(-50.0, -80.0))); // sigmoid(50) ≈ 1.0\n // Direct exp without clamp — what does Metal actually do?\n dst[4] = exp(-100.0); // should be ~3.7e-44 or 0\n dst[5] = exp(-1000.0); // might be NaN on Metal!\n dst[6] = exp(-1e10); // might be NaN on Metal!\n // Check for NaN: NaN != NaN\n let v5 = exp(-1000.0);\n if (v5 != v5) { dst[7] = -999.0; } else { dst[7] = v5; }\n }\n `;\n const outBuf = createStorageBuffer(ctx, \"dK_out\", 32);\n const readBuf = createReadbackBuffer(ctx, \"dK_read\", 32);\n const result = await runSingleEncoder(\"dK\", shader, [outBuf], readBuf, outBuf, 32);\n const clampedOk = result[0] > 0 && result[0] < 1e-30 && result[1] > 0 && result[1] < 1e-30;\n const normalOk = Math.abs(result[2] - Math.exp(-10)) < 1e-8;\n const sigmoidOk = Math.abs(result[3] - 1.0) < 0.01;\n const nanDetected =\n result[7] === -999 || Number.isNaN(result[5]) || Number.isNaN(result[6]) || result[5] === 0;\n\n const detail =\n `clamped=${result[0].toExponential(2)},${result[1].toExponential(2)} ` +\n `exp(-10)=${result[2].toExponential(2)} sig=${result[3].toFixed(4)} ` +\n `raw: exp(-100)=${result[4]} exp(-1000)=${result[5]} exp(-1e10)=${result[6]} ` +\n `NaN?=${result[7]}`;\n\n details.push(\n clampedOk && normalOk\n ? `✓ K: exp() clamp OK (${detail})`\n : `✗ K: exp() clamp FAILED (${detail})`,\n );\n outBuf.destroy();\n readBuf.destroy();\n } catch (e) {\n details.push(`✗ K error: ${e instanceof Error ? e.message : String(e)}`);\n }\n\n // Test L: Large buffer integrity (1 MB upload + readback)\n // Weights are uploaded as large storage buffers. If Safari's writeBuffer silently\n // corrupts data for large buffers or views with non-zero byteOffset, this will catch it.\n try {\n const MB = 256 * 1024; // 256K floats = 1 MB\n const testData = new Float32Array(MB);\n // Fill with a deterministic non-trivial pattern (not all-zeros or sequential)\n for (let i = 0; i < MB; i++) {\n testData[i] = Math.sin(i * 0.001) * (i % 1000) * 0.01;\n }\n const srcBuf = createStorageBuffer(ctx, \"dL_src\", testData.byteLength, testData);\n const readBuf = createReadbackBuffer(ctx, \"dL_read\", testData.byteLength);\n const result = await readbackFloats(ctx, srcBuf, readBuf, 0, testData.byteLength);\n\n let mismatches = 0;\n let firstMismatchIdx = -1;\n for (let i = 0; i < MB; i++) {\n if (Math.abs(result[i] - testData[i]) > 1e-6) {\n if (firstMismatchIdx === -1) firstMismatchIdx = i;\n mismatches++;\n }\n }\n const ok = mismatches === 0;\n details.push(\n ok\n ? `✓ L: 1MB buffer integrity OK (${MB} floats)`\n : `✗ L: 1MB buffer FAILED (${mismatches} mismatches, first at [${firstMismatchIdx}]: expected ${testData[firstMismatchIdx]}, got ${result[firstMismatchIdx]})`,\n );\n srcBuf.destroy();\n readBuf.destroy();\n } catch (e) {\n details.push(`✗ L error: ${e instanceof Error ? e.message : String(e)}`);\n }\n\n // Test M: Large buffer with byteOffset (simulates weight tensor views)\n // Weight tensors from safetensors are ArrayBufferViews with non-zero byteOffset\n // into a larger buffer. Safari might handle the offset incorrectly in writeBuffer.\n try {\n // Create a 2 MB backing buffer, write a known pattern at offset 1 MB\n const backingSize = 512 * 1024; // 512K floats = 2 MB\n const backing = new Float32Array(backingSize);\n const sliceStart = 256 * 1024; // Start at 1 MB offset\n const sliceLen = 128 * 1024; // 128K floats = 512 KB\n for (let i = 0; i < sliceLen; i++) {\n backing[sliceStart + i] = Math.cos(i * 0.002) * (i % 500) * 0.005;\n }\n // Create a view with non-zero byteOffset (like getTensorData returns)\n const view = new Float32Array(backing.buffer, sliceStart * 4, sliceLen);\n\n const srcBuf = createStorageBuffer(ctx, \"dM_src\", view.byteLength, view);\n const readBuf = createReadbackBuffer(ctx, \"dM_read\", view.byteLength);\n const result = await readbackFloats(ctx, srcBuf, readBuf, 0, view.byteLength);\n\n let mismatches = 0;\n let firstMismatchIdx = -1;\n for (let i = 0; i < sliceLen; i++) {\n if (Math.abs(result[i] - view[i]) > 1e-6) {\n if (firstMismatchIdx === -1) firstMismatchIdx = i;\n mismatches++;\n }\n }\n const ok = mismatches === 0;\n details.push(\n ok\n ? `✓ M: offset buffer integrity OK (512KB at 1MB offset)`\n : `✗ M: offset buffer FAILED (${mismatches} mismatches, first at [${firstMismatchIdx}]: expected ${view[firstMismatchIdx]}, got ${result[firstMismatchIdx]})`,\n );\n srcBuf.destroy();\n readBuf.destroy();\n } catch (e) {\n details.push(`✗ M error: ${e instanceof Error ? e.message : String(e)}`);\n }\n\n // Test N: Real RMSNorm kernel (actual WGSL from registry, not simplified)\n // Tests the exact shader code used in inference. If Safari's WGSL→MSL compiler\n // miscompiles the real kernel, this will catch it.\n try {\n const rmsnormShader = `\nstruct Params {\n seq_len: u32,\n hidden_size: u32,\n eps_bits: u32,\n _pad: u32,\n}\n\n@group(0) @binding(0) var<storage, read> input: array<f32>;\n@group(0) @binding(1) var<storage, read> weight: array<f32>;\n@group(0) @binding(2) var<storage, read_write> output: array<f32>;\n@group(0) @binding(3) var<storage, read> params: Params;\n\nvar<workgroup> shared_sum: array<f32, 256>;\n\n@compute @workgroup_size(256)\nfn main(\n @builtin(local_invocation_id) lid: vec3u,\n @builtin(workgroup_id) wid: vec3u,\n) {\n let row = wid.x;\n if (row >= params.seq_len) { return; }\n\n let tid = lid.x;\n let row_offset = row * params.hidden_size;\n let eps = bitcast<f32>(params.eps_bits);\n\n var local_sum: f32 = 0.0;\n var i = tid;\n while (i < params.hidden_size) {\n let val = input[row_offset + i];\n local_sum += val * val;\n i += 256u;\n }\n shared_sum[tid] = local_sum;\n workgroupBarrier();\n\n var stride: u32 = 128u;\n while (stride > 0u) {\n if (tid < stride) {\n shared_sum[tid] += shared_sum[tid + stride];\n }\n workgroupBarrier();\n stride = stride / 2u;\n }\n\n let rms = sqrt(shared_sum[0] / f32(params.hidden_size) + eps);\n\n i = tid;\n while (i < params.hidden_size) {\n let val = input[row_offset + i];\n output[row_offset + i] = (val / rms) * weight[i];\n i += 256u;\n }\n}\n `;\n\n // Set up test data: hidden_size=1024, seq_len=1, eps=1e-6\n const hiddenSize = 1024;\n const inputData = new Float32Array(hiddenSize);\n const weightData = new Float32Array(hiddenSize);\n for (let i = 0; i < hiddenSize; i++) {\n inputData[i] = Math.sin(i * 0.01) * 2.0; // range [-2, 2]\n weightData[i] = 1.0 + Math.cos(i * 0.005) * 0.1; // range [0.9, 1.1]\n }\n\n // Compute expected output in JS\n let sumSq = 0;\n for (let i = 0; i < hiddenSize; i++) sumSq += inputData[i] * inputData[i];\n const rms = Math.sqrt(sumSq / hiddenSize + 1e-6);\n const expectedOutput = new Float32Array(hiddenSize);\n for (let i = 0; i < hiddenSize; i++) {\n expectedOutput[i] = (inputData[i] / rms) * weightData[i];\n }\n\n // Encode eps as u32 bits\n const epsF32 = new Float32Array(1);\n epsF32[0] = 1e-6;\n const epsBits = new Uint32Array(epsF32.buffer)[0];\n\n // Build uniform buffer: {seq_len=1, hidden_size=1024, eps_bits, _pad=0}\n const paramsBuf = new ArrayBuffer(16);\n const paramsView = new DataView(paramsBuf);\n paramsView.setUint32(0, 1, true);\n paramsView.setUint32(4, hiddenSize, true);\n paramsView.setUint32(8, epsBits, true);\n paramsView.setUint32(12, 0, true);\n\n const inBuf = createStorageBuffer(ctx, \"dN_in\", inputData.byteLength, inputData);\n const wBuf = createStorageBuffer(ctx, \"dN_w\", weightData.byteLength, weightData);\n const outBuf = createStorageBuffer(ctx, \"dN_out\", hiddenSize * 4);\n const uniformBuf = createUniformBuffer(ctx, \"dN_params\", paramsBuf);\n const readBuf = createReadbackBuffer(ctx, \"dN_read\", hiddenSize * 4);\n\n const pipeline = getOrCreatePipeline(ctx, \"dN_rmsnorm\", rmsnormShader, \"main\");\n const bg = createBindGroup(\n ctx,\n pipeline,\n [{ buffer: inBuf }, { buffer: wBuf }, { buffer: outBuf }, { buffer: uniformBuf }],\n \"dN_bg\",\n );\n\n const encoder = ctx.device.createCommandEncoder();\n const pass = encoder.beginComputePass();\n pass.setPipeline(pipeline);\n pass.setBindGroup(0, bg);\n pass.dispatchWorkgroups(1); // 1 row\n pass.end();\n encoder.copyBufferToBuffer(outBuf, 0, readBuf, 0, hiddenSize * 4);\n ctx.device.queue.submit([encoder.finish()]);\n if (ctx.device.queue.onSubmittedWorkDone) {\n await ctx.device.queue.onSubmittedWorkDone();\n }\n await readBuf.mapAsync(MAP_MODE_READ, 0, hiddenSize * 4);\n const mapped = readBuf.getMappedRange(0, hiddenSize * 4);\n const result = new Float32Array(mapped.slice(0));\n readBuf.unmap();\n\n let maxErr = 0;\n let maxErrIdx = 0;\n for (let i = 0; i < hiddenSize; i++) {\n const err = Math.abs(result[i] - expectedOutput[i]);\n if (err > maxErr) {\n maxErr = err;\n maxErrIdx = i;\n }\n }\n // Allow small floating-point tolerance (tree reduce accumulation order differs)\n const ok = maxErr < 0.01;\n details.push(\n ok\n ? `✓ N: real RMSNorm kernel OK (maxErr=${maxErr.toExponential(2)} at [${maxErrIdx}])`\n : `✗ N: real RMSNorm kernel FAILED (maxErr=${maxErr.toExponential(2)} at [${maxErrIdx}]: expected ${expectedOutput[maxErrIdx].toFixed(4)}, got ${result[maxErrIdx].toFixed(4)})`,\n );\n inBuf.destroy();\n wBuf.destroy();\n outBuf.destroy();\n uniformBuf.destroy();\n readBuf.destroy();\n } catch (e) {\n details.push(`✗ N error: ${e instanceof Error ? e.message : String(e)}`);\n }\n\n // Test O: Real MatVec kernel (actual WGSL from registry)\n // Tests K-parallel matrix-vector multiply with 256 threads = 8×32 layout.\n try {\n const matvecShader = `\nconst N_TILE: u32 = 8u;\nconst K_THREADS: u32 = 32u;\n\nstruct Params {\n K: u32,\n N: u32,\n}\n\n@group(0) @binding(0) var<storage, read> A: array<f32>;\n@group(0) @binding(1) var<storage, read> B: array<f32>;\n@group(0) @binding(2) var<storage, read_write> C: array<f32>;\n@group(0) @binding(3) var<storage, read> params: Params;\n\nvar<workgroup> shared_sums: array<f32, 256>;\n\n@compute @workgroup_size(256)\nfn main(\n @builtin(local_invocation_id) lid: vec3u,\n @builtin(workgroup_id) wid: vec3u,\n) {\n let tid = lid.x;\n let n_idx = tid / K_THREADS;\n let k_tid = tid % K_THREADS;\n let col = wid.x * N_TILE + n_idx;\n let K = params.K;\n\n var sum: f32 = 0.0;\n if (col < params.N) {\n let b_off = col * K;\n var k = k_tid * 4u;\n while (k + 3u < K) {\n let a_v = vec4f(A[k], A[k+1u], A[k+2u], A[k+3u]);\n let b_v = vec4f(B[b_off+k], B[b_off+k+1u], B[b_off+k+2u], B[b_off+k+3u]);\n sum += dot(a_v, b_v);\n k += K_THREADS * 4u;\n }\n }\n\n shared_sums[tid] = sum;\n workgroupBarrier();\n\n if (k_tid == 0u && col < params.N) {\n let base = n_idx * K_THREADS;\n var total: f32 = shared_sums[base];\n for (var i: u32 = 1u; i < K_THREADS; i++) {\n total += shared_sums[base + i];\n }\n C[col] = total;\n }\n}\n `;\n\n // Test: A[256] * B[64, 256] → C[64] (small but realistic dimensions)\n const K_dim = 256;\n const N_dim = 64;\n const aData = new Float32Array(K_dim);\n const bData = new Float32Array(N_dim * K_dim);\n for (let i = 0; i < K_dim; i++) aData[i] = Math.sin(i * 0.05) * 0.5;\n for (let i = 0; i < N_dim * K_dim; i++) bData[i] = Math.cos(i * 0.003) * 0.1;\n\n // Expected: C[n] = sum_k A[k] * B[n*K + k]\n const expectedC = new Float32Array(N_dim);\n for (let n = 0; n < N_dim; n++) {\n let s = 0;\n for (let k = 0; k < K_dim; k++) s += aData[k] * bData[n * K_dim + k];\n expectedC[n] = s;\n }\n\n const paramBuf2 = new ArrayBuffer(8);\n const pv2 = new DataView(paramBuf2);\n pv2.setUint32(0, K_dim, true);\n pv2.setUint32(4, N_dim, true);\n\n const aBuf = createStorageBuffer(ctx, \"dO_a\", aData.byteLength, aData);\n const bBuf = createStorageBuffer(ctx, \"dO_b\", bData.byteLength, bData);\n const cBuf = createStorageBuffer(ctx, \"dO_c\", N_dim * 4);\n const uBuf = createUniformBuffer(ctx, \"dO_params\", paramBuf2);\n const rBuf = createReadbackBuffer(ctx, \"dO_read\", N_dim * 4);\n\n const mvPipeline = getOrCreatePipeline(ctx, \"dO_matvec\", matvecShader, \"main\");\n const mvBg = createBindGroup(\n ctx,\n mvPipeline,\n [{ buffer: aBuf }, { buffer: bBuf }, { buffer: cBuf }, { buffer: uBuf }],\n \"dO_bg\",\n );\n\n // Dispatch: N_dim / 8 = 8 workgroups\n const encoder2 = ctx.device.createCommandEncoder();\n const pass2 = encoder2.beginComputePass();\n pass2.setPipeline(mvPipeline);\n pass2.setBindGroup(0, mvBg);\n pass2.dispatchWorkgroups(Math.ceil(N_dim / 8));\n pass2.end();\n encoder2.copyBufferToBuffer(cBuf, 0, rBuf, 0, N_dim * 4);\n ctx.device.queue.submit([encoder2.finish()]);\n if (ctx.device.queue.onSubmittedWorkDone) {\n await ctx.device.queue.onSubmittedWorkDone();\n }\n await rBuf.mapAsync(MAP_MODE_READ, 0, N_dim * 4);\n const mapped2 = rBuf.getMappedRange(0, N_dim * 4);\n const resultC = new Float32Array(mapped2.slice(0));\n rBuf.unmap();\n\n let maxErr2 = 0;\n let maxErrIdx2 = 0;\n for (let i = 0; i < N_dim; i++) {\n const err = Math.abs(resultC[i] - expectedC[i]);\n if (err > maxErr2) {\n maxErr2 = err;\n maxErrIdx2 = i;\n }\n }\n const ok2 = maxErr2 < 0.01;\n details.push(\n ok2\n ? `✓ O: real MatVec kernel OK (maxErr=${maxErr2.toExponential(2)} at [${maxErrIdx2}])`\n : `✗ O: real MatVec kernel FAILED (maxErr=${maxErr2.toExponential(2)} at [${maxErrIdx2}]: expected ${expectedC[maxErrIdx2].toFixed(4)}, got ${resultC[maxErrIdx2].toFixed(4)})`,\n );\n aBuf.destroy();\n bBuf.destroy();\n cBuf.destroy();\n uBuf.destroy();\n rBuf.destroy();\n } catch (e) {\n details.push(`✗ O error: ${e instanceof Error ? e.message : String(e)}`);\n }\n\n // Test P: Many dispatches in one compute pass (300 sequential dispatches)\n // The model runs ~300 dispatches per forward pass in a single compute pass.\n // If Safari silently drops or corrupts dispatches beyond a limit, only this test catches it.\n // Uses a simple chain: buf[i] += 1.0 for 300 dispatches → should equal 300.0.\n try {\n const manyShader = `\n @group(0) @binding(0) var<storage, read_write> data: array<f32>;\n @compute @workgroup_size(64)\n fn main(@builtin(global_invocation_id) gid: vec3u) {\n if (gid.x < 4u) { data[gid.x] += 1.0; }\n }\n `;\n const DISPATCH_COUNT = 300;\n const manyBuf = createStorageBuffer(ctx, \"dP_data\", 16, new Float32Array(4));\n const manyReadBuf = createReadbackBuffer(ctx, \"dP_read\", 16);\n const manyPipeline = getOrCreatePipeline(ctx, \"dP\", manyShader, \"main\");\n const manyBg = createBindGroup(ctx, manyPipeline, [{ buffer: manyBuf }], \"dP_bg\");\n\n const enc = ctx.device.createCommandEncoder();\n const cpass = enc.beginComputePass();\n for (let d = 0; d < DISPATCH_COUNT; d++) {\n cpass.setPipeline(manyPipeline);\n cpass.setBindGroup(0, manyBg);\n cpass.dispatchWorkgroups(1);\n }\n cpass.end();\n enc.copyBufferToBuffer(manyBuf, 0, manyReadBuf, 0, 16);\n ctx.device.queue.submit([enc.finish()]);\n if (ctx.device.queue.onSubmittedWorkDone) {\n await ctx.device.queue.onSubmittedWorkDone();\n }\n await manyReadBuf.mapAsync(MAP_MODE_READ, 0, 16);\n const manyMapped = manyReadBuf.getMappedRange(0, 16);\n const manyResult = new Float32Array(manyMapped.slice(0));\n manyReadBuf.unmap();\n\n const allCorrect = manyResult.every((v) => Math.abs(v - DISPATCH_COUNT) < 0.1);\n details.push(\n allCorrect\n ? `✓ P: ${DISPATCH_COUNT} dispatches OK (${manyResult[0]})`\n : `✗ P: ${DISPATCH_COUNT} dispatches FAILED (got [${Array.from(manyResult).join(\",\")}], expected ${DISPATCH_COUNT})`,\n );\n manyBuf.destroy();\n manyReadBuf.destroy();\n } catch (e) {\n details.push(`✗ P error: ${e instanceof Error ? e.message : String(e)}`);\n }\n\n // Test Q: Same shader, different bind groups (Metal argument buffer caching bug)\n // This is the EXACT pattern that triggers the Safari/Metal bug:\n // Two dispatches use the same shader code but different bind groups pointing\n // to different output buffers. If Metal caches argument buffers per compiled\n // function, the second dispatch will write to the wrong buffer.\n try {\n const qShader = `\n @group(0) @binding(0) var<storage, read> src: array<f32>;\n @group(0) @binding(1) var<storage, read_write> dst: array<f32>;\n @compute @workgroup_size(4)\n fn main(@builtin(global_invocation_id) gid: vec3u) {\n if (gid.x < 4u) { dst[gid.x] = src[gid.x] * 2.0; }\n }\n `;\n const qSrc = createStorageBuffer(ctx, \"dQ_src\", 16, new Float32Array([1, 2, 3, 4]));\n const qDst1 = createStorageBuffer(ctx, \"dQ_dst1\", 16, new Float32Array(4)); // zeros\n const qDst2 = createStorageBuffer(ctx, \"dQ_dst2\", 16, new Float32Array(4)); // zeros\n const qRead1 = createReadbackBuffer(ctx, \"dQ_read1\", 16);\n const qRead2 = createReadbackBuffer(ctx, \"dQ_read2\", 16);\n\n // Same shader, same pipeline — different bind groups with different output buffers\n const qPipeline = getOrCreatePipeline(ctx, \"dQ\", qShader, \"main\");\n const qBg1 = createBindGroup(ctx, qPipeline, [{ buffer: qSrc }, { buffer: qDst1 }], \"dQ_bg1\");\n const qBg2 = createBindGroup(ctx, qPipeline, [{ buffer: qSrc }, { buffer: qDst2 }], \"dQ_bg2\");\n\n const qEnc = ctx.device.createCommandEncoder();\n const qPass = qEnc.beginComputePass();\n qPass.setPipeline(qPipeline);\n qPass.setBindGroup(0, qBg1);\n qPass.dispatchWorkgroups(1); // dst1 should be [2,4,6,8]\n qPass.setPipeline(qPipeline); // SAME pipeline\n qPass.setBindGroup(0, qBg2); // DIFFERENT bind group → different output buffer\n qPass.dispatchWorkgroups(1); // dst2 should be [2,4,6,8]\n qPass.end();\n qEnc.copyBufferToBuffer(qDst1, 0, qRead1, 0, 16);\n qEnc.copyBufferToBuffer(qDst2, 0, qRead2, 0, 16);\n ctx.device.queue.submit([qEnc.finish()]);\n if (ctx.device.queue.onSubmittedWorkDone) {\n await ctx.device.queue.onSubmittedWorkDone();\n }\n await qRead1.mapAsync(MAP_MODE_READ, 0, 16);\n const qR1 = new Float32Array(qRead1.getMappedRange(0, 16).slice(0));\n qRead1.unmap();\n await qRead2.mapAsync(MAP_MODE_READ, 0, 16);\n const qR2 = new Float32Array(qRead2.getMappedRange(0, 16).slice(0));\n qRead2.unmap();\n\n const qExpected = [2, 4, 6, 8];\n const dst1Ok = qExpected.every((v, i) => Math.abs(qR1[i] - v) < 0.01);\n const dst2Ok = qExpected.every((v, i) => Math.abs(qR2[i] - v) < 0.01);\n details.push(\n dst1Ok && dst2Ok\n ? `✓ Q: same-shader diff-bindgroup OK (dst1=[${Array.from(qR1)}] dst2=[${Array.from(qR2)}])`\n : `✗ Q: same-shader diff-bindgroup FAILED (dst1=[${Array.from(qR1)}] dst2=[${Array.from(qR2)}], expected [${qExpected}])`,\n );\n qSrc.destroy();\n qDst1.destroy();\n qDst2.destroy();\n qRead1.destroy();\n qRead2.destroy();\n } catch (e) {\n details.push(`✗ Q error: ${e instanceof Error ? e.message : String(e)}`);\n }\n\n return { bufferIntegrity, computeWorks, sharedMemoryWorks, details };\n}\n","/**\n * Kernel registry — maps IR op types to WGSL shader code and dispatch logic.\n *\n * Each entry defines:\n * - The WGSL source (embedded as string constants for browser compatibility)\n * - The compute entry point name\n * - How to calculate workgroup dispatch dimensions\n * - How to build the uniform buffer matching the WGSL Params struct\n * - The binding layout (storage-read, storage-read-write, uniform) in order\n *\n * Only ops with WGSL implementations are included. Stubbed ops (MoERouter,\n * ExpertMatMul, Conv2d, AvgPool2d, CrossAttention, Gather, Reshape,\n * Transpose, Concat) are omitted.\n */\n\nimport type { OpNode, OpType } from \"../ir.js\";\n\n// ── KernelSpec interface ────────────────────────────────────────────────\n\n/** Runtime context passed from the executor to kernels at dispatch time. */\nexport interface RuntimeContext {\n /** Current sequence position (for autoregressive decode). */\n seqPos: number;\n /**\n * Query-grid base offset for windowed Attention dispatches. When set, the\n * Attention kernel treats workgroup row r as query position r + qOffset, so a\n * caller can process a sub-range of query rows in one dispatch. Only the WebKit\n * vision encoder sets it (to chunk the O(N²) ViT attention); defaults to 0.\n */\n qOffset?: number;\n}\n\nexport interface KernelSpec {\n /** WGSL shader source code */\n shaderCode: string;\n /** Compute entry point name */\n entryPoint: string;\n /** Calculate dispatch workgroup counts from op attributes and resolved shapes */\n getDispatchSize: (\n op: OpNode,\n resolvedShapes: Record<string, number[]>,\n context?: RuntimeContext,\n ) => [number, number, number];\n /** Build the uniform buffer data for this op */\n buildParams: (\n op: OpNode,\n resolvedShapes: Record<string, number[]>,\n context?: RuntimeContext,\n ) => ArrayBuffer;\n /** List of binding entries (storage vs uniform) in order */\n bindings: Array<{\n type: \"storage-read\" | \"storage-read-write\" | \"uniform\";\n }>;\n}\n\n// ── Helpers ─────────────────────────────────────────────────────────────\n\n/** Ceiling division: ceil(a / b) */\nfunction cdiv(a: number, b: number): number {\n return Math.ceil(a / b);\n}\n\n/** Reinterpret an f32 value as its u32 bit pattern. */\nfunction f32BitsToU32(value: number): number {\n const f = new Float32Array(1);\n f[0] = value;\n return new Uint32Array(f.buffer)[0];\n}\n\n/**\n * Create an ArrayBuffer from an array of u32 values.\n * Each value is written at 4-byte intervals using DataView.\n */\nfunction buildUniformBuffer(values: number[]): ArrayBuffer {\n const buf = new ArrayBuffer(values.length * 4);\n const view = new DataView(buf);\n for (let i = 0; i < values.length; i++) {\n view.setUint32(i * 4, values[i], true /* littleEndian */);\n }\n return buf;\n}\n\n/**\n * Resolve the first dimension of a tensor referenced by an attribute.\n * Falls back to the direct attribute value if no tensor reference is set.\n */\nfunction _resolveFirstDim(\n op: OpNode,\n attrName: string,\n resolvedShapes: Record<string, number[]>,\n): number {\n const tensorRef = op.attributes[`${attrName}_tensor`] as string | undefined;\n if (tensorRef && resolvedShapes[tensorRef]) {\n return resolvedShapes[tensorRef][0];\n }\n return op.attributes[attrName] as number;\n}\n\n/**\n * Resolve total element count of a tensor referenced by an attribute.\n */\nfunction _resolveTotalElements(\n _op: OpNode,\n tensorRef: string,\n resolvedShapes: Record<string, number[]>,\n): number {\n const shape = resolvedShapes[tensorRef];\n if (!shape) return 0;\n return shape.reduce((a, b) => a * b, 1);\n}\n\n/**\n * Resolve a dynamic seq_len from tensor references, output shape, or static attribute.\n *\n * Tries (in order):\n * 1. Explicit tensor reference via `seq_len_tensor` attribute — computes total / hidden\n * 2. First output tensor's first dimension (for 2D+ outputs)\n * 3. Static `seq_len` attribute\n */\nfunction resolveSeqLen(\n op: OpNode,\n resolvedShapes: Record<string, number[]>,\n hidden: number,\n): number {\n // Try explicit tensor reference first\n const ref = op.attributes.seq_len_tensor as string | undefined;\n if (ref && resolvedShapes[ref]) {\n const shape = resolvedShapes[ref];\n const total = shape.reduce((a: number, b: number) => a * b, 1);\n return total / hidden;\n }\n // Fall back to output tensor shape\n const outShape = resolvedShapes[op.outputs[0]];\n if (outShape && outShape.length >= 2) {\n return outShape[0];\n }\n return op.attributes.seq_len as number;\n}\n\n// ── Embedded WGSL shader sources ────────────────────────────────────────\n\nconst WGSL_EMBEDDING = `\\\n// Embedding lookup: gather rows from weight matrix by token ID.\n\nstruct Params {\n seq_len: u32,\n hidden_size: u32,\n}\n\n@group(0) @binding(0) var<storage, read> input_ids: array<u32>;\n@group(0) @binding(1) var<storage, read> weight: array<f32>;\n@group(0) @binding(2) var<storage, read_write> output: array<f32>;\n@group(0) @binding(3) var<storage, read> params: Params;\n\n@compute @workgroup_size(256)\nfn main(@builtin(global_invocation_id) gid: vec3u) {\n let idx = gid.x;\n let total = params.seq_len * params.hidden_size;\n if (idx >= total) { return; }\n\n let token_pos = idx / params.hidden_size;\n let dim = idx % params.hidden_size;\n\n let token_id = input_ids[token_pos];\n let weight_idx = token_id * params.hidden_size + dim;\n\n output[idx] = weight[weight_idx];\n}\n`;\n\nconst WGSL_EMBEDDING_INT4 = `\\\n// INT4 embedding lookup: dequantize rows from packed weight matrix by token ID.\n// Weight is packed as 8 nibbles per u32, with per-group scales and zeros.\n// Dequant formula: output = (nibble - zero) * scale\n//\n// Bindings:\n// @group(0) @binding(0) input_ids: array<u32> — token IDs, length T\n// @group(0) @binding(1) weight_q: array<u32> — packed INT4 [vocab_size * hidden_size / 8]\n// @group(0) @binding(2) scales: array<f32> — per-group scales\n// @group(0) @binding(3) zeros: array<f32> — per-group zeros\n// @group(0) @binding(4) output: array<f32> — output [T, hidden_size]\n// @group(0) @binding(5) params: { seq_len, hidden_size, group_size }\n\nstruct Params {\n seq_len: u32,\n hidden_size: u32,\n group_size: u32,\n _pad: u32,\n}\n\n@group(0) @binding(0) var<storage, read> input_ids: array<u32>;\n@group(0) @binding(1) var<storage, read> weight_q: array<u32>;\n@group(0) @binding(2) var<storage, read> scales: array<f32>;\n@group(0) @binding(3) var<storage, read> zeros: array<f32>;\n@group(0) @binding(4) var<storage, read_write> output: array<f32>;\n@group(0) @binding(5) var<storage, read> params: Params;\n\n@compute @workgroup_size(256)\nfn main(@builtin(global_invocation_id) gid: vec3u) {\n let idx = gid.x;\n let total = params.seq_len * params.hidden_size;\n if (idx >= total) { return; }\n\n let token_pos = idx / params.hidden_size;\n let dim = idx % params.hidden_size;\n\n let token_id = input_ids[token_pos];\n\n // Flat index into the [vocab_size, hidden_size] embedding table\n let flat_idx = token_id * params.hidden_size + dim;\n let packed_idx = flat_idx / 8u;\n let nibble_pos = flat_idx % 8u;\n let packed = weight_q[packed_idx];\n let shift = nibble_pos * 4u;\n let raw_val = f32((packed >> shift) & 0xFu);\n\n let group_idx = flat_idx / params.group_size;\n let scale = scales[group_idx];\n let zero = zeros[group_idx];\n\n output[idx] = (raw_val - zero) * scale;\n}\n`;\n\nconst WGSL_MATMUL = `\\\n// Tiled matrix multiply with 4x2 register blocking.\n// C[M,N] = A[M,K] * B^T[N,K]; B stored in [N,K] layout (HF [out,in]).\n//\n// A 16x16 workgroup computes a 64-row x 32-col output tile: each thread owns a\n// 4x2 block of outputs (rows {r,r+16,r+32,r+48} x cols {c,c+16}). Each B column\n// loaded into shared memory is reused for 4 output rows, cutting B (weight)\n// global traffic 4x vs the scalar version — the dominant cost of the wide f32\n// ViT matmuls. A/B tile fills use vec4 loads (K is always %4==0).\n\nconst MT: u32 = 64u; // output rows per workgroup\nconst NT: u32 = 32u; // output cols per workgroup\nconst KT: u32 = 16u; // K-dimension tile depth\nconst KT4: u32 = 4u; // KT / 4 (vec4 columns along K)\n\nstruct Params {\n M: u32,\n K: u32,\n N: u32,\n}\n\n@group(0) @binding(0) var<storage, read> A: array<vec4f>;\n@group(0) @binding(1) var<storage, read> B: array<vec4f>;\n@group(0) @binding(2) var<storage, read_write> C: array<f32>;\n@group(0) @binding(3) var<storage, read> params: Params;\n\n// tileA: [MT rows x KT] = 1024, tileB: [KT x NT cols] = 512\nvar<workgroup> tileA: array<f32, 1024>;\nvar<workgroup> tileB: array<f32, 512>;\n\n@compute @workgroup_size(16, 16)\nfn main(\n @builtin(local_invocation_id) lid: vec3u,\n @builtin(workgroup_id) wid: vec3u,\n) {\n let lr = lid.y;\n let lc = lid.x;\n let mBase = wid.y * MT;\n let nBase = wid.x * NT;\n // This thread's 4x2 output block: rows {lr, lr+16, lr+32, lr+48}, cols {lc, lc+16}.\n let row0 = mBase + lr;\n let col0 = nBase + lc;\n\n var acc: array<f32, 8>; // [4 rows x 2 cols]\n for (var i = 0u; i < 8u; i = i + 1u) { acc[i] = 0.0; }\n\n let numTiles = (params.K + KT - 1u) / KT;\n let tid = lr * 16u + lc; // 0..255 linear thread id\n let K4 = params.K / 4u; // K in vec4 units\n\n for (var t: u32 = 0u; t < numTiles; t = t + 1u) {\n let kv0 = t * KT4; // first vec4 column of this K-tile\n // Load A tile [MT x KT] = 64 rows * 4 vec4 = 256 vec4 loads (1 per thread).\n {\n let ar = tid / KT4; // 0..63 row within tile\n let av4 = tid % KT4; // 0..3 vec4-col within tile (along K)\n let gRow = mBase + ar;\n var v = vec4f(0.0, 0.0, 0.0, 0.0);\n if (gRow < params.M && kv0 + av4 < K4) {\n v = A[gRow * K4 + kv0 + av4];\n }\n let base = ar * KT + av4 * 4u;\n tileA[base] = v.x;\n tileA[base + 1u] = v.y;\n tileA[base + 2u] = v.z;\n tileA[base + 3u] = v.w;\n }\n // Load B tile [KT x NT cols]: 32 cols * 4 vec4 = 128 vec4 loads (threads 0..127).\n if (tid < 128u) {\n let bc = tid / KT4; // 0..31 col within tile\n let bv4 = tid % KT4; // 0..3 vec4-col within tile (along K)\n let gCol = nBase + bc;\n var v = vec4f(0.0, 0.0, 0.0, 0.0);\n if (gCol < params.N && kv0 + bv4 < K4) {\n v = B[gCol * K4 + kv0 + bv4];\n }\n let krow = bv4 * 4u;\n tileB[(krow + 0u) * NT + bc] = v.x;\n tileB[(krow + 1u) * NT + bc] = v.y;\n tileB[(krow + 2u) * NT + bc] = v.z;\n tileB[(krow + 3u) * NT + bc] = v.w;\n }\n\n workgroupBarrier();\n\n for (var k: u32 = 0u; k < KT; k = k + 1u) {\n let b0 = tileB[k * NT + lc];\n let b1 = tileB[k * NT + lc + 16u];\n let a0 = tileA[lr * KT + k];\n let a1 = tileA[(lr + 16u) * KT + k];\n let a2 = tileA[(lr + 32u) * KT + k];\n let a3 = tileA[(lr + 48u) * KT + k];\n acc[0] += a0 * b0;\n acc[1] += a0 * b1;\n acc[2] += a1 * b0;\n acc[3] += a1 * b1;\n acc[4] += a2 * b0;\n acc[5] += a2 * b1;\n acc[6] += a3 * b0;\n acc[7] += a3 * b1;\n }\n\n workgroupBarrier();\n }\n\n for (var rr = 0u; rr < 4u; rr = rr + 1u) {\n let gRow = row0 + rr * 16u;\n if (gRow < params.M) {\n if (col0 < params.N) {\n C[gRow * params.N + col0] = acc[rr * 2u];\n }\n if (col0 + 16u < params.N) {\n C[gRow * params.N + col0 + 16u] = acc[rr * 2u + 1u];\n }\n }\n }\n}\n`;\n\n// Fused MatMul + row-broadcast bias: C[r,c] = A[r,:]·B[c,:] + bias[c].\n// Identical tiling to WGSL_MATMUL (4x2 register blocking, vec4 loads) but adds\n// the bias at store time, eliminating a full wide read+write of the matmul\n// output that a separate AddBias dispatch would incur — one fewer round-trip per\n// ViT linear layer (qkv/proj/fc1/fc2/patch_embed/merger fc1/fc2).\nconst WGSL_MATMUL_BIAS = `\\\nconst MT: u32 = 64u;\nconst NT: u32 = 32u;\nconst KT: u32 = 16u;\nconst KT4: u32 = 4u;\n\nstruct Params {\n M: u32,\n K: u32,\n N: u32,\n}\n\n@group(0) @binding(0) var<storage, read> A: array<vec4f>;\n@group(0) @binding(1) var<storage, read> B: array<vec4f>;\n@group(0) @binding(2) var<storage, read> bias: array<f32>;\n@group(0) @binding(3) var<storage, read_write> C: array<f32>;\n@group(0) @binding(4) var<storage, read> params: Params;\n\nvar<workgroup> tileA: array<f32, 1024>;\nvar<workgroup> tileB: array<f32, 512>;\n\n@compute @workgroup_size(16, 16)\nfn main(\n @builtin(local_invocation_id) lid: vec3u,\n @builtin(workgroup_id) wid: vec3u,\n) {\n let lr = lid.y;\n let lc = lid.x;\n let mBase = wid.y * MT;\n let nBase = wid.x * NT;\n let row0 = mBase + lr;\n let col0 = nBase + lc;\n\n var acc: array<f32, 8>;\n for (var i = 0u; i < 8u; i = i + 1u) { acc[i] = 0.0; }\n\n let numTiles = (params.K + KT - 1u) / KT;\n let tid = lr * 16u + lc;\n let K4 = params.K / 4u;\n\n for (var t: u32 = 0u; t < numTiles; t = t + 1u) {\n let kv0 = t * KT4;\n {\n let ar = tid / KT4;\n let av4 = tid % KT4;\n let gRow = mBase + ar;\n var v = vec4f(0.0, 0.0, 0.0, 0.0);\n if (gRow < params.M && kv0 + av4 < K4) {\n v = A[gRow * K4 + kv0 + av4];\n }\n let base = ar * KT + av4 * 4u;\n tileA[base] = v.x;\n tileA[base + 1u] = v.y;\n tileA[base + 2u] = v.z;\n tileA[base + 3u] = v.w;\n }\n if (tid < 128u) {\n let bc = tid / KT4;\n let bv4 = tid % KT4;\n let gCol = nBase + bc;\n var v = vec4f(0.0, 0.0, 0.0, 0.0);\n if (gCol < params.N && kv0 + bv4 < K4) {\n v = B[gCol * K4 + kv0 + bv4];\n }\n let krow = bv4 * 4u;\n tileB[(krow + 0u) * NT + bc] = v.x;\n tileB[(krow + 1u) * NT + bc] = v.y;\n tileB[(krow + 2u) * NT + bc] = v.z;\n tileB[(krow + 3u) * NT + bc] = v.w;\n }\n\n workgroupBarrier();\n\n for (var k: u32 = 0u; k < KT; k = k + 1u) {\n let b0 = tileB[k * NT + lc];\n let b1 = tileB[k * NT + lc + 16u];\n let a0 = tileA[lr * KT + k];\n let a1 = tileA[(lr + 16u) * KT + k];\n let a2 = tileA[(lr + 32u) * KT + k];\n let a3 = tileA[(lr + 48u) * KT + k];\n acc[0] += a0 * b0;\n acc[1] += a0 * b1;\n acc[2] += a1 * b0;\n acc[3] += a1 * b1;\n acc[4] += a2 * b0;\n acc[5] += a2 * b1;\n acc[6] += a3 * b0;\n acc[7] += a3 * b1;\n }\n\n workgroupBarrier();\n }\n\n var bias0 = 0.0;\n if (col0 < params.N) { bias0 = bias[col0]; }\n var bias1 = 0.0;\n if (col0 + 16u < params.N) { bias1 = bias[col0 + 16u]; }\n for (var rr = 0u; rr < 4u; rr = rr + 1u) {\n let gRow = row0 + rr * 16u;\n if (gRow < params.M) {\n if (col0 < params.N) {\n C[gRow * params.N + col0] = acc[rr * 2u] + bias0;\n }\n if (col0 + 16u < params.N) {\n C[gRow * params.N + col0 + 16u] = acc[rr * 2u + 1u] + bias1;\n }\n }\n }\n}\n`;\n\n// MIXED-precision MatMulBias: f16 weight + f16 shared tiles, but products are\n// summed in an f32 accumulator (acc += f32(a*b), with a*b done in f16). Keeps the\n// fast f16 MULTIPLY (the ~2% win measured in b4-r5) while accumulating in f32 to\n// stay inside the cos gate that full-f16 accumulation (r5) broke.\nconst WGSL_MATMUL_BIAS_F16MIX = `\\\nenable f16;\n\nconst MT: u32 = 64u;\nconst NT: u32 = 32u;\nconst KT: u32 = 16u;\nconst KT4: u32 = 4u;\n\nstruct Params {\n M: u32,\n K: u32,\n N: u32,\n}\n\n@group(0) @binding(0) var<storage, read> A: array<vec4f>;\n@group(0) @binding(1) var<storage, read> B: array<vec4<f16>>;\n@group(0) @binding(2) var<storage, read> bias: array<f32>;\n@group(0) @binding(3) var<storage, read_write> C: array<f32>;\n@group(0) @binding(4) var<storage, read> params: Params;\n\nvar<workgroup> tileA: array<f16, 1024>;\nvar<workgroup> tileB: array<f16, 512>;\n\n@compute @workgroup_size(16, 16)\nfn main(\n @builtin(local_invocation_id) lid: vec3u,\n @builtin(workgroup_id) wid: vec3u,\n) {\n let lr = lid.y;\n let lc = lid.x;\n let mBase = wid.y * MT;\n let nBase = wid.x * NT;\n let row0 = mBase + lr;\n let col0 = nBase + lc;\n\n var acc: array<f32, 8>;\n for (var i = 0u; i < 8u; i = i + 1u) { acc[i] = 0.0; }\n\n let numTiles = (params.K + KT - 1u) / KT;\n let tid = lr * 16u + lc;\n let K4 = params.K / 4u;\n\n for (var t: u32 = 0u; t < numTiles; t = t + 1u) {\n let kv0 = t * KT4;\n {\n let ar = tid / KT4;\n let av4 = tid % KT4;\n let gRow = mBase + ar;\n var v = vec4f(0.0, 0.0, 0.0, 0.0);\n if (gRow < params.M && kv0 + av4 < K4) {\n v = A[gRow * K4 + kv0 + av4];\n }\n let hv = vec4<f16>(v);\n let base = ar * KT + av4 * 4u;\n tileA[base] = hv.x;\n tileA[base + 1u] = hv.y;\n tileA[base + 2u] = hv.z;\n tileA[base + 3u] = hv.w;\n }\n if (tid < 128u) {\n let bc = tid / KT4;\n let bv4 = tid % KT4;\n let gCol = nBase + bc;\n var v = vec4<f16>(f16(0.0), f16(0.0), f16(0.0), f16(0.0));\n if (gCol < params.N && kv0 + bv4 < K4) {\n v = B[gCol * K4 + kv0 + bv4];\n }\n let krow = bv4 * 4u;\n tileB[(krow + 0u) * NT + bc] = v.x;\n tileB[(krow + 1u) * NT + bc] = v.y;\n tileB[(krow + 2u) * NT + bc] = v.z;\n tileB[(krow + 3u) * NT + bc] = v.w;\n }\n\n workgroupBarrier();\n\n for (var k: u32 = 0u; k < KT; k = k + 1u) {\n let b0 = tileB[k * NT + lc];\n let b1 = tileB[k * NT + lc + 16u];\n let a0 = tileA[lr * KT + k];\n let a1 = tileA[(lr + 16u) * KT + k];\n let a2 = tileA[(lr + 32u) * KT + k];\n let a3 = tileA[(lr + 48u) * KT + k];\n acc[0] += f32(a0 * b0);\n acc[1] += f32(a0 * b1);\n acc[2] += f32(a1 * b0);\n acc[3] += f32(a1 * b1);\n acc[4] += f32(a2 * b0);\n acc[5] += f32(a2 * b1);\n acc[6] += f32(a3 * b0);\n acc[7] += f32(a3 * b1);\n }\n\n workgroupBarrier();\n }\n\n var bias0 = 0.0;\n if (col0 < params.N) { bias0 = bias[col0]; }\n var bias1 = 0.0;\n if (col0 + 16u < params.N) { bias1 = bias[col0 + 16u]; }\n for (var rr = 0u; rr < 4u; rr = rr + 1u) {\n let gRow = row0 + rr * 16u;\n if (gRow < params.M) {\n if (col0 < params.N) {\n C[gRow * params.N + col0] = acc[rr * 2u] + bias0;\n }\n if (col0 + 16u < params.N) {\n C[gRow * params.N + col0 + 16u] = acc[rr * 2u + 1u] + bias1;\n }\n }\n }\n}\n`;\n\nconst WGSL_MATMUL_INT4 = `\\\n// Tiled INT4 dequantize + matrix multiply.\n// C[M,N] = A[M,K] * dequant(B_q[N,K])\n// B_q is packed nibbles in [N,K] layout (same as F32 MatMul convention).\n// 8 nibbles per u32, little-nibble-first.\n\nconst TILE: u32 = 16u;\n\nstruct Params {\n M: u32,\n K: u32,\n N: u32,\n group_size: u32,\n}\n\n@group(0) @binding(0) var<storage, read> A: array<f32>;\n@group(0) @binding(1) var<storage, read> B_q: array<u32>;\n@group(0) @binding(2) var<storage, read> scales: array<f32>;\n@group(0) @binding(3) var<storage, read> zeros: array<f32>;\n@group(0) @binding(4) var<storage, read_write> C: array<f32>;\n@group(0) @binding(5) var<storage, read> params: Params;\n\nvar<workgroup> tileA: array<f32, 256>;\nvar<workgroup> tileB: array<f32, 256>;\n\n@compute @workgroup_size(16, 16)\nfn main(\n @builtin(global_invocation_id) gid: vec3u,\n @builtin(local_invocation_id) lid: vec3u,\n) {\n let row = gid.y;\n let col = gid.x;\n let lr = lid.y;\n let lc = lid.x;\n\n var sum: f32 = 0.0;\n let numTiles = (params.K + TILE - 1u) / TILE;\n\n for (var t: u32 = 0u; t < numTiles; t = t + 1u) {\n // Load tile of A into shared memory\n let a_col = t * TILE + lc;\n if (row < params.M && a_col < params.K) {\n tileA[lr * TILE + lc] = A[row * params.K + a_col];\n } else {\n tileA[lr * TILE + lc] = 0.0;\n }\n\n // Load tile of B (INT4 dequantized) into shared memory\n // B is stored as [N,K] packed nibbles. We need B[col, t*TILE+lr].\n let b_k = t * TILE + lr;\n if (b_k < params.K && col < params.N) {\n let flat_idx = col * params.K + b_k;\n let packed_idx = flat_idx / 8u;\n let nibble_pos = flat_idx % 8u;\n let packed = B_q[packed_idx];\n let shift = nibble_pos * 4u;\n let raw_val = f32((packed >> shift) & 0xFu);\n\n let group_idx = flat_idx / params.group_size;\n let scale = scales[group_idx];\n let zero = zeros[group_idx];\n tileB[lr * TILE + lc] = (raw_val - zero) * scale;\n } else {\n tileB[lr * TILE + lc] = 0.0;\n }\n\n workgroupBarrier();\n\n for (var k: u32 = 0u; k < TILE; k = k + 1u) {\n sum += tileA[lr * TILE + k] * tileB[k * TILE + lc];\n }\n\n workgroupBarrier();\n }\n\n if (row < params.M && col < params.N) {\n C[row * params.N + col] = sum;\n }\n}\n`;\n\nconst WGSL_MATVEC = `\\\n// K-parallel matrix-vector multiply: C[N] = A[K] * B^T[N,K]\n// Optimized for M=1 decode on Apple Silicon (SIMD width 32).\n//\n// Design: 256 threads = 8 output columns × 32 K-threads per column.\n// Each group of 32 threads cooperates along K for one output element.\n// A reads go through L1 cache (no shared memory — maximizes occupancy).\n// B reads are coalesced: 32 adjacent threads read 32 adjacent f32 values\n// = 128 bytes = one full cache line per iteration.\n// Single-barrier reduction (thread 0 of each group sums 32 partials).\n\nconst N_TILE: u32 = 8u;\nconst K_THREADS: u32 = 32u;\n\nstruct Params {\n K: u32,\n N: u32,\n}\n\n@group(0) @binding(0) var<storage, read> A: array<f32>;\n@group(0) @binding(1) var<storage, read> B: array<f32>;\n@group(0) @binding(2) var<storage, read_write> C: array<f32>;\n@group(0) @binding(3) var<storage, read> params: Params;\n\nvar<workgroup> shared_sums: array<f32, 256>;\n\n@compute @workgroup_size(256)\nfn main(\n @builtin(local_invocation_id) lid: vec3u,\n @builtin(workgroup_id) wid: vec3u,\n) {\n let tid = lid.x;\n let n_idx = tid / K_THREADS;\n let k_tid = tid % K_THREADS;\n let col = wid.x * N_TILE + n_idx;\n let K = params.K;\n\n // K-parallel accumulation with vec4 dot products\n // A reads hit L1 cache (same addresses across all 16 output columns)\n var sum: f32 = 0.0;\n if (col < params.N) {\n let b_off = col * K;\n var k = k_tid * 4u;\n while (k + 3u < K) {\n let a_v = vec4f(A[k], A[k+1u], A[k+2u], A[k+3u]);\n let b_v = vec4f(B[b_off+k], B[b_off+k+1u], B[b_off+k+2u], B[b_off+k+3u]);\n sum += dot(a_v, b_v);\n k += K_THREADS * 4u;\n }\n }\n\n // Store partials and reduce\n shared_sums[tid] = sum;\n workgroupBarrier();\n\n // Thread 0 of each K-group sequentially sums 16 partials\n if (k_tid == 0u && col < params.N) {\n let base = n_idx * K_THREADS;\n var total: f32 = shared_sums[base];\n for (var i: u32 = 1u; i < K_THREADS; i++) {\n total += shared_sums[base + i];\n }\n C[col] = total;\n }\n}\n`;\n\n// ── Subgroups variant of WGSL_MATVEC (desktop-only fast path) ──\n//\n// Identical math and memory-access pattern to WGSL_MATVEC, but the per-column\n// partial-sum reduction is done with subgroupAdd() instead of a shared-memory\n// write + barrier + thread-0 sequential sum. Requires the \"subgroups\" device\n// feature (Chrome 134+, Safari 26+) and \"enable subgroups;\".\n//\n// Layout: 256 threads = 8 output columns × 32 K-threads. The kernel is hand-\n// tuned for SIMD width 32 (Apple Silicon / desktop GPUs), where each group of\n// 32 contiguous K-threads forms exactly one subgroup. We do NOT rely on the\n// (unspecified) lane↔invocation mapping for correctness across arbitrary\n// subgroup sizes: subgroupAdd() reduces over whatever subgroup the lane belongs\n// to, then the subgroup leaders deposit their partials into a tiny shared array\n// (one slot per column), and the column leader combines them. When subgroup\n// size == K_THREADS (the common desktop case) there is exactly one subgroup per\n// column and the shared-memory combine collapses to a single store + load.\n//\n// This variant is selected only on non-WebKit devices that expose subgroups\n// (see executor.ts) — the portable WGSL_MATVEC remains the universal fallback.\nconst WGSL_MATVEC_SUBGROUPS = `\\\nenable subgroups;\n\nconst N_TILE: u32 = 8u;\nconst K_THREADS: u32 = 32u;\n\nstruct Params {\n K: u32,\n N: u32,\n}\n\n@group(0) @binding(0) var<storage, read> A: array<f32>;\n@group(0) @binding(1) var<storage, read> B: array<f32>;\n@group(0) @binding(2) var<storage, read_write> C: array<f32>;\n@group(0) @binding(3) var<storage, read> params: Params;\n\n// One partial slot per column per possible subgroup-within-column. K_THREADS=32\n// so at most 32 subgroups per column in the pathological size-1 case; size\n// 8*32 = 256 covers it. In practice (size 32) only [n_idx*32] is used.\nvar<workgroup> col_partials: array<f32, 256>;\n\n@compute @workgroup_size(256)\nfn main(\n @builtin(local_invocation_id) lid: vec3u,\n @builtin(workgroup_id) wid: vec3u,\n) {\n let tid = lid.x;\n let n_idx = tid / K_THREADS;\n let k_tid = tid % K_THREADS;\n let col = wid.x * N_TILE + n_idx;\n let K = params.K;\n\n var sum: f32 = 0.0;\n if (col < params.N) {\n let b_off = col * K;\n var k = k_tid * 4u;\n while (k + 3u < K) {\n let a_v = vec4f(A[k], A[k+1u], A[k+2u], A[k+3u]);\n let b_v = vec4f(B[b_off+k], B[b_off+k+1u], B[b_off+k+2u], B[b_off+k+3u]);\n sum += dot(a_v, b_v);\n k += K_THREADS * 4u;\n }\n }\n\n // Reduce within the subgroup. Each lane gets the sum over its subgroup.\n let sg_sum = subgroupAdd(sum);\n\n // Subgroup leaders deposit their partial into a per-column slot. Slot index is\n // the leader's k_tid (its offset within the column), guaranteeing distinct\n // slots per column even when a column spans multiple subgroups.\n col_partials[tid] = 0.0;\n workgroupBarrier();\n if (subgroupElect()) {\n col_partials[n_idx * K_THREADS + k_tid] = sg_sum;\n }\n workgroupBarrier();\n\n // Column leader sums the (few) deposited subgroup partials for its column.\n if (k_tid == 0u && col < params.N) {\n let base = n_idx * K_THREADS;\n var total: f32 = 0.0;\n for (var i: u32 = 0u; i < K_THREADS; i++) {\n total += col_partials[base + i];\n }\n C[col] = total;\n }\n}\n`;\n\n// ── Subgroups variant of WGSL_MATVEC_INT4 (desktop-only fast path) ──\n//\n// Same approach as WGSL_MATVEC_SUBGROUPS applied to the INT4 dequant matvec:\n// subgroupAdd() replaces the shared-memory tree reduction. Layout is widened to\n// 256 threads = 8 columns × 32 K-threads (vs the portable kernel's 128/16) so\n// each column maps to exactly one 32-wide subgroup on desktop GPUs. N_TILE stays\n// 8 so getDispatchSize is unchanged. Selected only on non-WebKit + subgroups.\nconst WGSL_MATVEC_INT4_SUBGROUPS = `\\\nenable subgroups;\n\nconst N_TILE: u32 = 8u;\nconst K_THREADS: u32 = 32u;\n\nstruct Params {\n K: u32,\n N: u32,\n group_size: u32,\n _pad: u32,\n}\n\n@group(0) @binding(0) var<storage, read> A: array<vec4f>;\n@group(0) @binding(1) var<storage, read> B_q: array<vec4u>;\n@group(0) @binding(2) var<storage, read> scales: array<f32>;\n@group(0) @binding(3) var<storage, read> zeros: array<f32>;\n@group(0) @binding(4) var<storage, read_write> C: array<f32>;\n@group(0) @binding(5) var<storage, read> params: Params;\n\nvar<workgroup> col_partials: array<f32, 256>;\n\n@compute @workgroup_size(256)\nfn main(\n @builtin(local_invocation_id) lid: vec3u,\n @builtin(workgroup_id) wid: vec3u,\n) {\n let tid = lid.x;\n let n_idx = tid / K_THREADS;\n let k_tid = tid % K_THREADS;\n let col = wid.x * N_TILE + n_idx;\n let K = params.K;\n let k_vec = K / 32u;\n let groups_per_col = K / params.group_size;\n let packed_per_group = params.group_size / 8u;\n\n var sum: f32 = 0.0;\n if (col < params.N) {\n let b_off_v = col * k_vec;\n let col_g_base = col * groups_per_col;\n\n var v = k_tid;\n while (v < k_vec) {\n let bq = B_q[b_off_v + v];\n let g = (v * 4u) / packed_per_group;\n let scale = scales[col_g_base + g];\n let zero = zeros[col_g_base + g];\n let a_base = v * 8u;\n\n for (var j: u32 = 0u; j < 4u; j++) {\n let packed = bq[j];\n let n0 = f32(packed & 0xFu);\n let n1 = f32((packed >> 4u) & 0xFu);\n let n2 = f32((packed >> 8u) & 0xFu);\n let n3 = f32((packed >> 12u) & 0xFu);\n let n4 = f32((packed >> 16u) & 0xFu);\n let n5 = f32((packed >> 20u) & 0xFu);\n let n6 = f32((packed >> 24u) & 0xFu);\n let n7 = f32((packed >> 28u) & 0xFu);\n\n let b0 = vec4f(n0 - zero, n1 - zero, n2 - zero, n3 - zero) * scale;\n let b1 = vec4f(n4 - zero, n5 - zero, n6 - zero, n7 - zero) * scale;\n\n sum += dot(A[a_base + j * 2u], b0) + dot(A[a_base + j * 2u + 1u], b1);\n }\n\n v += K_THREADS;\n }\n }\n\n let sg_sum = subgroupAdd(sum);\n\n col_partials[tid] = 0.0;\n workgroupBarrier();\n if (subgroupElect()) {\n col_partials[n_idx * K_THREADS + k_tid] = sg_sum;\n }\n workgroupBarrier();\n\n if (k_tid == 0u && col < params.N) {\n let base = n_idx * K_THREADS;\n var total: f32 = 0.0;\n for (var i: u32 = 0u; i < K_THREADS; i++) {\n total += col_partials[base + i];\n }\n C[col] = total;\n }\n}\n`;\n\nconst WGSL_MATVEC_INT4 = `\\\n// K-parallel INT4 matrix-vector multiply: C[N] = A[K] * dequant(B_q[N,K])\n// Optimized for M=1 decode on Apple Silicon (SIMD width 32).\n//\n// CONSTRAINT: N_TILE must equal workgroup_size / K_THREADS\n// Design: 256 threads = 8 output columns × 32 K-threads per column.\n\nconst N_TILE: u32 = 16u;\nconst K_THREADS: u32 = 16u;\n\nstruct Params {\n K: u32,\n N: u32,\n group_size: u32,\n _pad: u32,\n}\n\n@group(0) @binding(0) var<storage, read> A: array<vec4f>;\n@group(0) @binding(1) var<storage, read> B_q: array<vec4u>;\n@group(0) @binding(2) var<storage, read> scales: array<f32>;\n@group(0) @binding(3) var<storage, read> zeros: array<f32>;\n@group(0) @binding(4) var<storage, read_write> C: array<f32>;\n@group(0) @binding(5) var<storage, read> params: Params;\n\nvar<workgroup> shared_sums: array<f32, 256>;\n\n@compute @workgroup_size(256)\nfn main(\n @builtin(local_invocation_id) lid: vec3u,\n @builtin(workgroup_id) wid: vec3u,\n) {\n let tid = lid.x;\n let n_idx = tid / K_THREADS;\n let k_tid = tid % K_THREADS;\n let col = wid.x * N_TILE + n_idx;\n let K = params.K;\n let k_vec = K / 32u;\n let groups_per_col = K / params.group_size;\n let packed_per_group = params.group_size / 8u;\n\n // K-parallel accumulation with vec4 loads: each thread reads one vec4u\n // (4 packed u32s = 32 INT4 weights) per iteration. group_size (128) spans\n // 16 packed u32s, so a vec4u never straddles a quantization group — one\n // scale/zero load per iteration instead of four.\n // A reads hit L1 cache (same addresses across all 8 output columns in workgroup)\n var sum: f32 = 0.0;\n if (col < params.N) {\n let b_off_v = col * k_vec;\n let col_g_base = col * groups_per_col;\n\n var v = k_tid;\n while (v < k_vec) {\n let bq = B_q[b_off_v + v];\n let g = (v * 4u) / packed_per_group;\n let scale = scales[col_g_base + g];\n let zero = zeros[col_g_base + g];\n let a_base = v * 8u;\n\n for (var j: u32 = 0u; j < 4u; j++) {\n let packed = bq[j];\n // Manual nibble extraction (avoids unpack4xU8 — broken on Safari/WebKit)\n let n0 = f32(packed & 0xFu);\n let n1 = f32((packed >> 4u) & 0xFu);\n let n2 = f32((packed >> 8u) & 0xFu);\n let n3 = f32((packed >> 12u) & 0xFu);\n let n4 = f32((packed >> 16u) & 0xFu);\n let n5 = f32((packed >> 20u) & 0xFu);\n let n6 = f32((packed >> 24u) & 0xFu);\n let n7 = f32((packed >> 28u) & 0xFu);\n\n let b0 = vec4f(n0 - zero, n1 - zero, n2 - zero, n3 - zero) * scale;\n let b1 = vec4f(n4 - zero, n5 - zero, n6 - zero, n7 - zero) * scale;\n\n sum += dot(A[a_base + j * 2u], b0) + dot(A[a_base + j * 2u + 1u], b1);\n }\n\n v += K_THREADS;\n }\n }\n\n // Store partials and reduce\n shared_sums[tid] = sum;\n workgroupBarrier();\n\n if (k_tid == 0u && col < params.N) {\n let base = n_idx * K_THREADS;\n var total: f32 = shared_sums[base];\n for (var i: u32 = 1u; i < K_THREADS; i++) {\n total += shared_sums[base + i];\n }\n C[col] = total;\n }\n}\n`;\n\n// ── Gated MatVecInt4 (fused attn-gate + INT4 o_proj, M=1 decode) ──\n//\n// out[n] = sum_k (attn[k] * sigmoid(gate[k])) * dequant(W[n,k])\n// Folds the SigmoidGate (attn_out * sigmoid(gate)) into the o_proj input so the\n// gate elementwise + o_proj run in ONE dispatch. Same INT4 dequant and\n// K-parallel reduction as WGSL_MATVEC_INT4 — only the A vector is built from two\n// inputs with an inline sigmoid. Numerically identical to SigmoidGate→MatVecInt4.\nconst WGSL_GATED_MATVEC_INT4 = `\\\nconst N_TILE: u32 = 8u;\nconst K_THREADS: u32 = 16u;\n\nstruct Params {\n K: u32,\n N: u32,\n group_size: u32,\n _pad: u32,\n}\n\n@group(0) @binding(0) var<storage, read> attn: array<vec4f>;\n@group(0) @binding(1) var<storage, read> gate: array<vec4f>;\n@group(0) @binding(2) var<storage, read> B_q: array<vec4u>;\n@group(0) @binding(3) var<storage, read> scales: array<f32>;\n@group(0) @binding(4) var<storage, read> zeros: array<f32>;\n@group(0) @binding(5) var<storage, read_write> C: array<f32>;\n@group(0) @binding(6) var<storage, read> params: Params;\n\nvar<workgroup> shared_sums: array<f32, 128>;\n\nfn gated(i: u32) -> vec4f {\n let a = attn[i];\n let g = gate[i];\n let s = vec4f(1.0) / (vec4f(1.0) + exp(max(-g, vec4f(-80.0))));\n return a * s;\n}\n\n@compute @workgroup_size(128)\nfn main(\n @builtin(local_invocation_id) lid: vec3u,\n @builtin(workgroup_id) wid: vec3u,\n) {\n let tid = lid.x;\n let n_idx = tid / K_THREADS;\n let k_tid = tid % K_THREADS;\n let col = wid.x * N_TILE + n_idx;\n let K = params.K;\n let k_vec = K / 32u;\n let groups_per_col = K / params.group_size;\n let packed_per_group = params.group_size / 8u;\n\n var sum: f32 = 0.0;\n if (col < params.N) {\n let b_off_v = col * k_vec;\n let col_g_base = col * groups_per_col;\n\n var v = k_tid;\n while (v < k_vec) {\n let bq = B_q[b_off_v + v];\n let g = (v * 4u) / packed_per_group;\n let scale = scales[col_g_base + g];\n let zero = zeros[col_g_base + g];\n let a_base = v * 8u;\n\n for (var j: u32 = 0u; j < 4u; j++) {\n let packed = bq[j];\n let n0 = f32(packed & 0xFu);\n let n1 = f32((packed >> 4u) & 0xFu);\n let n2 = f32((packed >> 8u) & 0xFu);\n let n3 = f32((packed >> 12u) & 0xFu);\n let n4 = f32((packed >> 16u) & 0xFu);\n let n5 = f32((packed >> 20u) & 0xFu);\n let n6 = f32((packed >> 24u) & 0xFu);\n let n7 = f32((packed >> 28u) & 0xFu);\n\n let b0 = vec4f(n0 - zero, n1 - zero, n2 - zero, n3 - zero) * scale;\n let b1 = vec4f(n4 - zero, n5 - zero, n6 - zero, n7 - zero) * scale;\n\n sum += dot(gated(a_base + j * 2u), b0) + dot(gated(a_base + j * 2u + 1u), b1);\n }\n\n v += K_THREADS;\n }\n }\n\n shared_sums[tid] = sum;\n workgroupBarrier();\n\n if (k_tid == 0u && col < params.N) {\n let base = n_idx * K_THREADS;\n var total: f32 = shared_sums[base];\n for (var i: u32 = 1u; i < K_THREADS; i++) {\n total += shared_sums[base + i];\n }\n C[col] = total;\n }\n}\n`;\n\n// ── SwiGLU-gated MatVecInt4 (fused SwiGLU + INT4 projection, M=1 decode) ──\n//\n// out[n] = sum_k (silu(gate[k]) * up[k]) * dequant(W[n,k])\n// Folds a SwiGLU (silu(gate) * up) into the INT4 projection that consumes it —\n// used for the Mamba block's mamba_swiglu (silu(z) * norm_out) feeding out_proj.\n// Same INT4 dequant/reduction as WGSL_MATVEC_INT4; only the A vector is built\n// from two inputs with an inline SiLU. Identical to SwiGLU→MatVecInt4.\nconst WGSL_SWIGLU_GATED_MATVEC_INT4 = `\\\nconst N_TILE: u32 = 8u;\nconst K_THREADS: u32 = 16u;\n\nstruct Params {\n K: u32,\n N: u32,\n group_size: u32,\n _pad: u32,\n}\n\n@group(0) @binding(0) var<storage, read> gate: array<vec4f>;\n@group(0) @binding(1) var<storage, read> up: array<vec4f>;\n@group(0) @binding(2) var<storage, read> B_q: array<vec4u>;\n@group(0) @binding(3) var<storage, read> scales: array<f32>;\n@group(0) @binding(4) var<storage, read> zeros: array<f32>;\n@group(0) @binding(5) var<storage, read_write> C: array<f32>;\n@group(0) @binding(6) var<storage, read> params: Params;\n\nvar<workgroup> shared_sums: array<f32, 128>;\n\nfn swiglu_a(i: u32) -> vec4f {\n let g = gate[i];\n let u = up[i];\n let s = g / (vec4f(1.0) + exp(max(-g, vec4f(-80.0))));\n return s * u;\n}\n\n@compute @workgroup_size(128)\nfn main(\n @builtin(local_invocation_id) lid: vec3u,\n @builtin(workgroup_id) wid: vec3u,\n) {\n let tid = lid.x;\n let n_idx = tid / K_THREADS;\n let k_tid = tid % K_THREADS;\n let col = wid.x * N_TILE + n_idx;\n let K = params.K;\n let k_vec = K / 32u;\n let groups_per_col = K / params.group_size;\n let packed_per_group = params.group_size / 8u;\n\n var sum: f32 = 0.0;\n if (col < params.N) {\n let b_off_v = col * k_vec;\n let col_g_base = col * groups_per_col;\n\n var v = k_tid;\n while (v < k_vec) {\n let bq = B_q[b_off_v + v];\n let g = (v * 4u) / packed_per_group;\n let scale = scales[col_g_base + g];\n let zero = zeros[col_g_base + g];\n let a_base = v * 8u;\n\n for (var j: u32 = 0u; j < 4u; j++) {\n let packed = bq[j];\n let n0 = f32(packed & 0xFu);\n let n1 = f32((packed >> 4u) & 0xFu);\n let n2 = f32((packed >> 8u) & 0xFu);\n let n3 = f32((packed >> 12u) & 0xFu);\n let n4 = f32((packed >> 16u) & 0xFu);\n let n5 = f32((packed >> 20u) & 0xFu);\n let n6 = f32((packed >> 24u) & 0xFu);\n let n7 = f32((packed >> 28u) & 0xFu);\n\n let b0 = vec4f(n0 - zero, n1 - zero, n2 - zero, n3 - zero) * scale;\n let b1 = vec4f(n4 - zero, n5 - zero, n6 - zero, n7 - zero) * scale;\n\n sum += dot(swiglu_a(a_base + j * 2u), b0) + dot(swiglu_a(a_base + j * 2u + 1u), b1);\n }\n\n v += K_THREADS;\n }\n }\n\n shared_sums[tid] = sum;\n workgroupBarrier();\n\n if (k_tid == 0u && col < params.N) {\n let base = n_idx * K_THREADS;\n var total: f32 = shared_sums[base];\n for (var i: u32 = 1u; i < K_THREADS; i++) {\n total += shared_sums[base + i];\n }\n C[col] = total;\n }\n}\n`;\n\nconst WGSL_ARGMAX = `\\\n// GPU-side argmax: finds the index of the maximum value.\n// Single workgroup, 256 threads, parallel reduction.\n\nstruct Params {\n count: u32,\n offset: u32,\n}\n\n@group(0) @binding(0) var<storage, read> input: array<vec4f>;\n@group(0) @binding(1) var<storage, read_write> result: array<u32>;\n@group(0) @binding(2) var<storage, read> params: Params;\n\nvar<workgroup> shared_max: array<f32, 256>;\nvar<workgroup> shared_idx: array<u32, 256>;\n\n@compute @workgroup_size(256)\nfn main(@builtin(local_invocation_id) lid: vec3u) {\n let tid = lid.x;\n // base/count are in elements; argmax always runs offset=0 with count a\n // multiple of 4 (vocab_size), so we scan the logits as vec4f (128-bit loads,\n // 4 logits per fetch) to better saturate memory bandwidth.\n let count4 = params.count >> 2u;\n\n // Each thread scans its chunk of vec4 groups\n var local_max: f32 = -3.402823e+38;\n var local_idx: u32 = 0u;\n\n var i = tid;\n while (i < count4) {\n let v = input[i];\n let g = i << 2u;\n if (v.x > local_max) { local_max = v.x; local_idx = g; }\n if (v.y > local_max) { local_max = v.y; local_idx = g + 1u; }\n if (v.z > local_max) { local_max = v.z; local_idx = g + 2u; }\n if (v.w > local_max) { local_max = v.w; local_idx = g + 3u; }\n i += 256u;\n }\n\n shared_max[tid] = local_max;\n shared_idx[tid] = local_idx;\n workgroupBarrier();\n\n // Tree reduction\n if (tid < 128u) { if (shared_max[tid + 128u] > shared_max[tid]) { shared_max[tid] = shared_max[tid + 128u]; shared_idx[tid] = shared_idx[tid + 128u]; } }\n workgroupBarrier();\n if (tid < 64u) { if (shared_max[tid + 64u] > shared_max[tid]) { shared_max[tid] = shared_max[tid + 64u]; shared_idx[tid] = shared_idx[tid + 64u]; } }\n workgroupBarrier();\n if (tid < 32u) { if (shared_max[tid + 32u] > shared_max[tid]) { shared_max[tid] = shared_max[tid + 32u]; shared_idx[tid] = shared_idx[tid + 32u]; } }\n workgroupBarrier();\n if (tid < 16u) { if (shared_max[tid + 16u] > shared_max[tid]) { shared_max[tid] = shared_max[tid + 16u]; shared_idx[tid] = shared_idx[tid + 16u]; } }\n workgroupBarrier();\n if (tid < 8u) { if (shared_max[tid + 8u] > shared_max[tid]) { shared_max[tid] = shared_max[tid + 8u]; shared_idx[tid] = shared_idx[tid + 8u]; } }\n workgroupBarrier();\n if (tid < 4u) { if (shared_max[tid + 4u] > shared_max[tid]) { shared_max[tid] = shared_max[tid + 4u]; shared_idx[tid] = shared_idx[tid + 4u]; } }\n workgroupBarrier();\n if (tid < 2u) { if (shared_max[tid + 2u] > shared_max[tid]) { shared_max[tid] = shared_max[tid + 2u]; shared_idx[tid] = shared_idx[tid + 2u]; } }\n workgroupBarrier();\n if (tid == 0u) {\n if (shared_max[1u] > shared_max[0u]) {\n result[0] = shared_idx[1u];\n } else {\n result[0] = shared_idx[0u];\n }\n }\n}\n`;\n\nconst WGSL_RMSNORM = `\\\n// RMS Normalization: output = (x / rms(x)) * weight\n\nstruct Params {\n seq_len: u32,\n hidden_size: u32,\n eps_bits: u32,\n _pad: u32,\n}\n\n@group(0) @binding(0) var<storage, read> input: array<f32>;\n@group(0) @binding(1) var<storage, read> weight: array<f32>;\n@group(0) @binding(2) var<storage, read_write> output: array<f32>;\n@group(0) @binding(3) var<storage, read> params: Params;\n\nvar<workgroup> shared_sum: array<f32, 256>;\n\n@compute @workgroup_size(256)\nfn main(\n @builtin(local_invocation_id) lid: vec3u,\n @builtin(workgroup_id) wid: vec3u,\n) {\n let row = wid.x;\n if (row >= params.seq_len) { return; }\n\n let tid = lid.x;\n let row_offset = row * params.hidden_size;\n let eps = bitcast<f32>(params.eps_bits);\n\n var local_sum: f32 = 0.0;\n var i = tid;\n while (i < params.hidden_size) {\n let val = input[row_offset + i];\n local_sum += val * val;\n i += 256u;\n }\n shared_sum[tid] = local_sum;\n workgroupBarrier();\n\n var stride: u32 = 128u;\n while (stride > 0u) {\n if (tid < stride) {\n shared_sum[tid] += shared_sum[tid + stride];\n }\n workgroupBarrier();\n stride = stride / 2u;\n }\n\n let rms = sqrt(shared_sum[0] / f32(params.hidden_size) + eps);\n\n i = tid;\n while (i < params.hidden_size) {\n let val = input[row_offset + i];\n output[row_offset + i] = (val / rms) * weight[i];\n i += 256u;\n }\n}\n`;\n\nconst WGSL_DUAL_RMSNORM = `\\\n// Two independent per-row RMSNorms in one dispatch, sharing hidden_size + eps.\n// Workgroups [0, rows0) normalize input0→output0 with weight0; workgroups\n// [rows0, rows0+rows1) normalize input1→output1 with weight1. Used to fuse the\n// per-head q_norm and k_norm in full-attention decode (same head_dim, same eps;\n// q has num_heads rows, k has num_kv_heads rows). Numerically identical to two\n// separate RMSNorm dispatches.\n\nstruct Params {\n rows0: u32, // workgroups handling input0 (e.g. T*num_heads)\n rows1: u32, // workgroups handling input1 (e.g. T*num_kv_heads)\n hidden_size: u32, // shared per-row width (head_dim)\n eps_bits: u32,\n}\n\n@group(0) @binding(0) var<storage, read> input0: array<f32>;\n@group(0) @binding(1) var<storage, read> weight0: array<f32>;\n@group(0) @binding(2) var<storage, read> input1: array<f32>;\n@group(0) @binding(3) var<storage, read> weight1: array<f32>;\n@group(0) @binding(4) var<storage, read_write> output0: array<f32>;\n@group(0) @binding(5) var<storage, read_write> output1: array<f32>;\n@group(0) @binding(6) var<storage, read> params: Params;\n\nvar<workgroup> shared_sum: array<f32, 256>;\n\n@compute @workgroup_size(256)\nfn main(\n @builtin(local_invocation_id) lid: vec3u,\n @builtin(workgroup_id) wid: vec3u,\n) {\n let wg = wid.x;\n if (wg >= params.rows0 + params.rows1) { return; }\n\n let tid = lid.x;\n let hs = params.hidden_size;\n let eps = bitcast<f32>(params.eps_bits);\n\n // Route this workgroup to input0 (first rows0) or input1 (remaining).\n let is1 = wg >= params.rows0;\n let row = select(wg, wg - params.rows0, is1);\n let row_offset = row * hs;\n\n var local_sum: f32 = 0.0;\n var i = tid;\n while (i < hs) {\n var val: f32;\n if (is1) { val = input1[row_offset + i]; } else { val = input0[row_offset + i]; }\n local_sum += val * val;\n i += 256u;\n }\n shared_sum[tid] = local_sum;\n workgroupBarrier();\n\n var stride: u32 = 128u;\n while (stride > 0u) {\n if (tid < stride) {\n shared_sum[tid] += shared_sum[tid + stride];\n }\n workgroupBarrier();\n stride = stride / 2u;\n }\n\n let rms = sqrt(shared_sum[0] / f32(hs) + eps);\n\n i = tid;\n while (i < hs) {\n if (is1) {\n output1[row_offset + i] = (input1[row_offset + i] / rms) * weight1[i];\n } else {\n output0[row_offset + i] = (input0[row_offset + i] / rms) * weight0[i];\n }\n i += 256u;\n }\n}\n`;\n\nconst WGSL_LAYERNORM = `\\\n// Layer Normalization: output = ((x - mean) / sqrt(var + eps)) * weight + bias\n\nstruct Params {\n seq_len: u32,\n hidden_size: u32,\n eps_bits: u32,\n _pad: u32,\n}\n\n@group(0) @binding(0) var<storage, read> input: array<f32>;\n@group(0) @binding(1) var<storage, read> weight: array<f32>;\n@group(0) @binding(2) var<storage, read> bias: array<f32>;\n@group(0) @binding(3) var<storage, read_write> output: array<f32>;\n@group(0) @binding(4) var<storage, read> params: Params;\n\nvar<workgroup> shared_sum: array<f32, 256>;\nvar<workgroup> shared_sq_sum: array<f32, 256>;\n\n@compute @workgroup_size(256)\nfn main(\n @builtin(local_invocation_id) lid: vec3u,\n @builtin(workgroup_id) wid: vec3u,\n) {\n let row = wid.x;\n if (row >= params.seq_len) { return; }\n\n let tid = lid.x;\n let row_offset = row * params.hidden_size;\n let eps = bitcast<f32>(params.eps_bits);\n\n var local_sum: f32 = 0.0;\n var i = tid;\n while (i < params.hidden_size) {\n local_sum += input[row_offset + i];\n i += 256u;\n }\n shared_sum[tid] = local_sum;\n workgroupBarrier();\n\n var stride: u32 = 128u;\n while (stride > 0u) {\n if (tid < stride) {\n shared_sum[tid] += shared_sum[tid + stride];\n }\n workgroupBarrier();\n stride /= 2u;\n }\n let mean = shared_sum[0] / f32(params.hidden_size);\n\n var local_sq: f32 = 0.0;\n i = tid;\n while (i < params.hidden_size) {\n let diff = input[row_offset + i] - mean;\n local_sq += diff * diff;\n i += 256u;\n }\n shared_sq_sum[tid] = local_sq;\n workgroupBarrier();\n\n stride = 128u;\n while (stride > 0u) {\n if (tid < stride) {\n shared_sq_sum[tid] += shared_sq_sum[tid + stride];\n }\n workgroupBarrier();\n stride /= 2u;\n }\n let inv_std = 1.0 / sqrt(shared_sq_sum[0] / f32(params.hidden_size) + eps);\n\n i = tid;\n while (i < params.hidden_size) {\n let val = (input[row_offset + i] - mean) * inv_std;\n output[row_offset + i] = val * weight[i] + bias[i];\n i += 256u;\n }\n}\n`;\n\nconst WGSL_ROPE = `\\\n// Rotary Position Embeddings (RoPE) with partial rotation and rotate_half convention.\n// Uses HuggingFace \"rotate_half\" pairing: (x[i], x[i + rope_half]) NOT adjacent (x[2i], x[2i+1]).\n//\n// Three decoupled knobs (so we match both Qwen-style partial RoPE and Gemma-style\n// \"proportional\" RoPE exactly):\n// - rope_half : the rotate_half pairing offset AND the number of pairs we\n// iterate per head. For HF this is head_dim/2 (Gemma) or\n// rope_dim/2 (Qwen partial). Pairs dim i with i+rope_half.\n// - rope_denom : denominator in the inv_freq exponent base^(2i/rope_denom).\n// Gemma \"proportional\" uses head_dim; Qwen partial uses rope_dim.\n// - rope_active_pairs : pairs [0, rope_active_pairs) get a real frequency; pairs\n// >= rope_active_pairs have inv_freq=0 (identity, the \"nope\"\n// zero-padded tail of HF _compute_proportional_rope_parameters).\n//\n// Defaults (set in buildParams) reproduce the legacy single-rope_dim behavior:\n// rope_half = rope_dim/2, rope_denom = rope_dim, rope_active_pairs = rope_dim/2.\n\nstruct Params {\n seq_len: u32,\n num_q_heads: u32,\n num_kv_heads: u32,\n head_dim: u32,\n rope_base_bits: u32,\n position_offset: u32,\n rope_dim: u32,\n rope_half: u32,\n rope_denom: u32,\n rope_active_pairs: u32,\n _pad0: u32,\n _pad1: u32,\n}\n\n@group(0) @binding(0) var<storage, read_write> q: array<f32>;\n@group(0) @binding(1) var<storage, read_write> k: array<f32>;\n@group(0) @binding(2) var<storage, read> params: Params;\n\n@compute @workgroup_size(256)\nfn main(@builtin(global_invocation_id) gid: vec3u) {\n let rope_half = params.rope_half;\n let total_q_pairs = params.seq_len * params.num_q_heads * rope_half;\n let total_k_pairs = params.seq_len * params.num_kv_heads * rope_half;\n\n let idx = gid.x;\n\n let rope_base = bitcast<f32>(params.rope_base_bits);\n\n // rotate_half convention:\n // For pair_idx i (0..rope_half-1):\n // x_lo = x[i], x_hi = x[i + rope_half]\n // x_lo' = x_lo * cos - x_hi * sin\n // x_hi' = x_lo * sin + x_hi * cos\n // inv_freq[i] = 1 / base^(2i/rope_denom) for i < rope_active_pairs, else 0.\n\n if (idx < total_q_pairs) {\n let pos = idx / (params.num_q_heads * rope_half);\n let remainder = idx % (params.num_q_heads * rope_half);\n let head = remainder / rope_half;\n let pair_idx = remainder % rope_half;\n\n let position = f32(pos + params.position_offset);\n var cos_val = 1.0;\n var sin_val = 0.0;\n if (pair_idx < params.rope_active_pairs) {\n let freq = position / pow(rope_base, f32(2u * pair_idx) / f32(params.rope_denom));\n cos_val = cos(freq);\n sin_val = sin(freq);\n }\n\n let q_stride = params.num_q_heads * params.head_dim;\n let head_base = pos * q_stride + head * params.head_dim;\n let lo = head_base + pair_idx;\n let hi = head_base + pair_idx + rope_half;\n\n let q_lo = q[lo];\n let q_hi = q[hi];\n q[lo] = q_lo * cos_val - q_hi * sin_val;\n q[hi] = q_lo * sin_val + q_hi * cos_val;\n }\n\n if (idx < total_k_pairs) {\n let pos = idx / (params.num_kv_heads * rope_half);\n let remainder = idx % (params.num_kv_heads * rope_half);\n let head = remainder / rope_half;\n let pair_idx = remainder % rope_half;\n\n let position = f32(pos + params.position_offset);\n var cos_val = 1.0;\n var sin_val = 0.0;\n if (pair_idx < params.rope_active_pairs) {\n let freq = position / pow(rope_base, f32(2u * pair_idx) / f32(params.rope_denom));\n cos_val = cos(freq);\n sin_val = sin(freq);\n }\n\n let k_stride = params.num_kv_heads * params.head_dim;\n let head_base = pos * k_stride + head * params.head_dim;\n let lo = head_base + pair_idx;\n let hi = head_base + pair_idx + rope_half;\n\n let k_lo = k[lo];\n let k_hi = k[hi];\n k[lo] = k_lo * cos_val - k_hi * sin_val;\n k[hi] = k_lo * sin_val + k_hi * cos_val;\n }\n}\n`;\n\nconst WGSL_ROPE_INTERLEAVED = `\\\n// Rotary Position Embeddings — INTERLEAVED (adjacent-pair) convention, used by\n// Moonshine. Unlike the HF-Llama \"rotate_half\" split kernel above (which pairs\n// dim i with dim i+rope_half), Moonshine pairs ADJACENT dims (2p, 2p+1) with a\n// single frequency inv_freq[p] = 1 / base^(2p/rope_denom). Verified against the HF\n// MoonshineRotaryEmbedding (cos[..., :half].repeat_interleave(2) + interleaved\n// rotate_half). Only the first 2*rope_half dims of each head are rotated.\n//\n// Shares the Params layout with WGSL_ROPE (same buildParams). rope_half is the\n// number of adjacent pairs; rope_denom/rope_active_pairs default to the legacy\n// values so Moonshine is byte-identical to before.\n\nstruct Params {\n seq_len: u32,\n num_q_heads: u32,\n num_kv_heads: u32,\n head_dim: u32,\n rope_base_bits: u32,\n position_offset: u32,\n rope_dim: u32,\n rope_half: u32,\n rope_denom: u32,\n rope_active_pairs: u32,\n _pad0: u32,\n _pad1: u32,\n}\n\n@group(0) @binding(0) var<storage, read_write> q: array<f32>;\n@group(0) @binding(1) var<storage, read_write> k: array<f32>;\n@group(0) @binding(2) var<storage, read> params: Params;\n\n@compute @workgroup_size(256)\nfn main(@builtin(global_invocation_id) gid: vec3u) {\n let half_rope = params.rope_half;\n let total_q_pairs = params.seq_len * params.num_q_heads * half_rope;\n let total_k_pairs = params.seq_len * params.num_kv_heads * half_rope;\n let idx = gid.x;\n let rope_base = bitcast<f32>(params.rope_base_bits);\n\n if (idx < total_q_pairs) {\n let pos = idx / (params.num_q_heads * half_rope);\n let remainder = idx % (params.num_q_heads * half_rope);\n let head = remainder / half_rope;\n let pair_idx = remainder % half_rope;\n\n let position = f32(pos + params.position_offset);\n var cos_val = 1.0;\n var sin_val = 0.0;\n if (pair_idx < params.rope_active_pairs) {\n let freq = position / pow(rope_base, f32(2u * pair_idx) / f32(params.rope_denom));\n cos_val = cos(freq);\n sin_val = sin(freq);\n }\n\n let q_stride = params.num_q_heads * params.head_dim;\n let head_base = pos * q_stride + head * params.head_dim;\n // Adjacent-pair (interleaved): dims 2*pair_idx and 2*pair_idx+1.\n let lo = head_base + 2u * pair_idx;\n let hi = lo + 1u;\n\n let q_lo = q[lo];\n let q_hi = q[hi];\n q[lo] = q_lo * cos_val - q_hi * sin_val;\n q[hi] = q_hi * cos_val + q_lo * sin_val;\n }\n\n if (idx < total_k_pairs) {\n let pos = idx / (params.num_kv_heads * half_rope);\n let remainder = idx % (params.num_kv_heads * half_rope);\n let head = remainder / half_rope;\n let pair_idx = remainder % half_rope;\n\n let position = f32(pos + params.position_offset);\n var cos_val = 1.0;\n var sin_val = 0.0;\n if (pair_idx < params.rope_active_pairs) {\n let freq = position / pow(rope_base, f32(2u * pair_idx) / f32(params.rope_denom));\n cos_val = cos(freq);\n sin_val = sin(freq);\n }\n\n let k_stride = params.num_kv_heads * params.head_dim;\n let head_base = pos * k_stride + head * params.head_dim;\n let lo = head_base + 2u * pair_idx;\n let hi = lo + 1u;\n\n let k_lo = k[lo];\n let k_hi = k[hi];\n k[lo] = k_lo * cos_val - k_hi * sin_val;\n k[hi] = k_hi * cos_val + k_lo * sin_val;\n }\n}\n`;\n\nconst WGSL_ATTENTION = `\\\n// Tiled online-softmax attention (Flash-Attention style).\n//\n// Processes KV cache in tiles of TILE_S=32 positions. Per tile:\n// 1. Cooperative K tile load into shared memory (256 threads)\n// 2. Parallel Q·K dot products (8 threads per position, reduce via smem)\n// 3. Online softmax: update running max, rescale, compute tile weights\n// 4. Cooperative V tile load into shared memory (reuse K buffer)\n// 5. Parallel V accumulation: each of 128 threads handles one output dim\n//\n// Uses online softmax to maintain running (max, sum_exp, output) across tiles.\n// No global scratch buffer needed — everything in shared memory + registers.\n//\n// Safari/Metal compatibility:\n// - Uses if/else instead of select() (Safari has select() bugs)\n// - Clamps exp() args to -80 (Metal can return NaN for exp(-1e30))\n//\n// Shared memory: 4096 f32 = 16384 bytes = exactly 16 KB (minimum WebGPU guarantee)\n\nconst TILE_S: u32 = 16u;\nconst WG: u32 = 256u;\n// 16 threads cooperate on each dot product (256 / 16 = 16)\nconst DOT_THREADS: u32 = 16u;\n\nstruct Params {\n T: u32,\n S: u32,\n num_q_heads: u32,\n num_kv_heads: u32,\n head_dim: u32,\n position_offset: u32,\n is_causal: u32,\n // Query-grid base offset. Lets a caller process a window of query positions\n // [q_offset, q_offset + dispatched_workgroups_x) in a single dispatch without a\n // workgroup-id offset (WebGPU has none). Default 0 → full grid from row 0, byte-\n // identical to the un-windowed path. Used only by the WebKit vision encoder to\n // split the O(N²) ViT attention into watchdog-safe chunks.\n q_offset: u32,\n // Softmax QK temperature, supplied by buildParams as an f32. Defaults to\n // 1/sqrt(head_dim) (byte-identical to the legacy hardcoded scale), so every\n // existing caller is unchanged. Gemma 4 sets it to 1.0 because the per-head\n // QK-norm already absorbs the 1/sqrt(head_dim) factor (HF scaling = 1.0).\n attn_scale: f32,\n}\n\n@group(0) @binding(0) var<storage, read> Q: array<f32>;\n@group(0) @binding(1) var<storage, read> K: array<f32>;\n@group(0) @binding(2) var<storage, read> V: array<f32>;\n@group(0) @binding(3) var<storage, read_write> output: array<f32>;\n@group(0) @binding(4) var<storage, read> params: Params;\n\n// Shared memory layout (all phases share a single 4096-element array):\n// K phase: smem[0..TILE_S*hd) = K tile data (16×256 = 4096 f32)\n// Dot phase: smem[0..WG) = partial dot products for reduction\n// smem[0..TILE_S) reused for final scores after reduction\n// Softmax: smem[0..TILE_S) = scores → exp weights\n// V phase: smem[TILE_S..TILE_S+TILE_S*hd) = V tile data\n// But TILE_S+TILE_S*hd = 16+4096 > 4096, so we must save scores\n// to per-thread registers before V load, then use full smem for V.\n// Total: 4096 f32 = 16384 bytes = exactly 16 KB (minimum WebGPU guarantee)\nvar<workgroup> smem: array<f32, 4096>;\n\n@compute @workgroup_size(256)\nfn main(\n @builtin(local_invocation_id) lid: vec3u,\n @builtin(workgroup_id) wid: vec3u,\n) {\n let q_pos = wid.x + params.q_offset;\n let q_head = wid.y;\n\n if (q_pos >= params.T || q_head >= params.num_q_heads) { return; }\n\n let tid = lid.x;\n let heads_per_kv = params.num_q_heads / params.num_kv_heads;\n let kv_head = q_head / heads_per_kv;\n let scale = params.attn_scale;\n\n let q_stride = params.num_q_heads * params.head_dim;\n let kv_stride = params.num_kv_heads * params.head_dim;\n let q_base = q_pos * q_stride + q_head * params.head_dim;\n let kv_head_offset = kv_head * params.head_dim;\n let causal_limit = q_pos + params.position_offset + 1u;\n let hd = params.head_dim;\n // Causal (text) attends to keys up to its own position; bidirectional (ViT)\n // attends to all S keys. Default is_causal=1 keeps text decoding unchanged.\n var S_eff = params.S;\n if (params.is_causal != 0u) { S_eff = min(params.S, causal_limit); }\n\n // Online softmax running state (per-thread for output dims)\n var running_max: f32 = -1e30;\n var running_sum: f32 = 0.0;\n // Per-thread output accumulators. Thread tid handles output dim tid; a second\n // accumulator covers dim tid+WG so head_dim up to 2*WG (512) is supported with\n // WG=256 threads. For hd<=256, tid+WG>=hd is never written → byte-identical.\n var out_acc: f32 = 0.0;\n var out_acc2: f32 = 0.0;\n let d2 = tid + WG;\n\n // Tile size capped so a K/V tile (tile_cap * hd f32) fits the 4096-f32 smem.\n // hd<=256 → tile_cap=16=TILE_S (unchanged). hd=512 → tile_cap=8.\n let tile_cap = min(TILE_S, 4096u / hd);\n\n // Process KV cache in tiles of tile_cap positions\n let num_tiles = (S_eff + tile_cap - 1u) / tile_cap;\n\n for (var tile: u32 = 0u; tile < num_tiles; tile += 1u) {\n let tile_start = tile * tile_cap;\n let tile_end = min(tile_start + tile_cap, S_eff);\n let tile_len = tile_end - tile_start;\n\n // ── Step 1: Cooperative K tile load ──\n // Load tile_len × hd values from K cache into smem\n let total_k_elems = tile_len * hd;\n var load_idx = tid;\n while (load_idx < total_k_elems) {\n let s_local = load_idx / hd;\n let d = load_idx % hd;\n let s_global = tile_start + s_local;\n let k_addr = s_global * kv_stride + kv_head_offset + d;\n smem[s_local * hd + d] = K[k_addr];\n load_idx += WG;\n }\n workgroupBarrier();\n\n // ── Step 2: Parallel Q·K dot products ──\n // 256 threads = TILE_S positions × DOT_THREADS per position\n let pos_in_tile = tid / DOT_THREADS;\n let dot_tid = tid % DOT_THREADS;\n\n var my_partial: f32 = 0.0;\n if (pos_in_tile < tile_len) {\n let dims_per_thread = hd / DOT_THREADS;\n let d_start = dot_tid * dims_per_thread;\n let d_end = d_start + dims_per_thread;\n let kv_row_base = pos_in_tile * hd;\n\n var d = d_start;\n while (d + 3u < d_end) {\n let q_v = vec4f(Q[q_base + d], Q[q_base + d + 1u],\n Q[q_base + d + 2u], Q[q_base + d + 3u]);\n let k_v = vec4f(smem[kv_row_base + d], smem[kv_row_base + d + 1u],\n smem[kv_row_base + d + 2u], smem[kv_row_base + d + 3u]);\n my_partial += dot(q_v, k_v);\n d += 4u;\n }\n while (d < d_end) {\n my_partial += Q[q_base + d] * smem[kv_row_base + d];\n d += 1u;\n }\n }\n\n // K tile no longer needed — reuse smem for dot reduction\n workgroupBarrier();\n smem[tid] = my_partial;\n workgroupBarrier();\n\n // Thread 0 of each dot group reduces and writes score to smem[0..TILE_S).\n // Two-phase: leader 0 reads smem[1..15], which are exactly the slots\n // leaders 1..15 write — reduce into a local first, barrier (in uniform\n // control flow), then write. Read+write in one block is a data race.\n var group_total: f32 = 0.0;\n if (dot_tid == 0u && pos_in_tile < tile_len) {\n group_total = smem[tid];\n for (var i: u32 = 1u; i < DOT_THREADS; i += 1u) {\n group_total += smem[tid + i];\n }\n }\n workgroupBarrier();\n if (dot_tid == 0u && pos_in_tile < tile_len) {\n smem[pos_in_tile] = group_total * scale;\n }\n workgroupBarrier();\n\n // ── Step 3: Online softmax ──\n // Scores are in smem[0..tile_len). Find max via tree reduce in smem[TILE_S..].\n // NOTE: Uses if/else instead of select() — Safari/Metal has bugs with select().\n if (tid < TILE_S) {\n if (tid < tile_len) {\n smem[TILE_S + tid] = smem[tid];\n } else {\n smem[TILE_S + tid] = -1e30;\n }\n }\n workgroupBarrier();\n if (tid < 8u) { smem[TILE_S + tid] = max(smem[TILE_S + tid], smem[TILE_S + tid + 8u]); }\n workgroupBarrier();\n if (tid < 4u) { smem[TILE_S + tid] = max(smem[TILE_S + tid], smem[TILE_S + tid + 4u]); }\n workgroupBarrier();\n if (tid < 2u) { smem[TILE_S + tid] = max(smem[TILE_S + tid], smem[TILE_S + tid + 2u]); }\n workgroupBarrier();\n if (tid < 1u) { smem[TILE_S + tid] = max(smem[TILE_S + tid], smem[TILE_S + tid + 1u]); }\n workgroupBarrier();\n let tile_max = smem[TILE_S];\n\n let new_max = max(running_max, tile_max);\n // Clamp exp() argument to avoid NaN on Metal (exp(-87) ≈ 0 in f32)\n let old_correction = exp(max(running_max - new_max, -80.0));\n\n // Compute exp weights in smem[0..TILE_S)\n if (tid < tile_len) {\n smem[tid] = exp(max(smem[tid] - new_max, -80.0));\n }\n workgroupBarrier();\n\n // Sum tile weights via tree reduce in smem[TILE_S..]\n // NOTE: Uses if/else instead of select() — Safari/Metal has bugs with select().\n if (tid < TILE_S) {\n if (tid < tile_len) {\n smem[TILE_S + tid] = smem[tid];\n } else {\n smem[TILE_S + tid] = 0.0;\n }\n }\n workgroupBarrier();\n if (tid < 8u) { smem[TILE_S + tid] += smem[TILE_S + tid + 8u]; }\n workgroupBarrier();\n if (tid < 4u) { smem[TILE_S + tid] += smem[TILE_S + tid + 4u]; }\n workgroupBarrier();\n if (tid < 2u) { smem[TILE_S + tid] += smem[TILE_S + tid + 2u]; }\n workgroupBarrier();\n if (tid < 1u) { smem[TILE_S + tid] += smem[TILE_S + tid + 1u]; }\n workgroupBarrier();\n let tile_sum = smem[TILE_S];\n\n // ── Save scores to per-thread registers before V overwrites smem ──\n // Each thread saves the scores it needs (all TILE_S values)\n var w0: f32 = 0.0; var w1: f32 = 0.0; var w2: f32 = 0.0; var w3: f32 = 0.0;\n var w4: f32 = 0.0; var w5: f32 = 0.0; var w6: f32 = 0.0; var w7: f32 = 0.0;\n var w8: f32 = 0.0; var w9: f32 = 0.0; var w10: f32 = 0.0; var w11: f32 = 0.0;\n var w12: f32 = 0.0; var w13: f32 = 0.0; var w14: f32 = 0.0; var w15: f32 = 0.0;\n if (tid < hd) {\n if (0u < tile_len) { w0 = smem[0]; }\n if (1u < tile_len) { w1 = smem[1]; }\n if (2u < tile_len) { w2 = smem[2]; }\n if (3u < tile_len) { w3 = smem[3]; }\n if (4u < tile_len) { w4 = smem[4]; }\n if (5u < tile_len) { w5 = smem[5]; }\n if (6u < tile_len) { w6 = smem[6]; }\n if (7u < tile_len) { w7 = smem[7]; }\n if (8u < tile_len) { w8 = smem[8]; }\n if (9u < tile_len) { w9 = smem[9]; }\n if (10u < tile_len) { w10 = smem[10]; }\n if (11u < tile_len) { w11 = smem[11]; }\n if (12u < tile_len) { w12 = smem[12]; }\n if (13u < tile_len) { w13 = smem[13]; }\n if (14u < tile_len) { w14 = smem[14]; }\n if (15u < tile_len) { w15 = smem[15]; }\n }\n workgroupBarrier();\n\n // ── Step 4: Cooperative V tile load (full smem reuse) ──\n let total_v_elems = tile_len * hd;\n load_idx = tid;\n while (load_idx < total_v_elems) {\n let s_local = load_idx / hd;\n let d = load_idx % hd;\n let s_global = tile_start + s_local;\n let v_addr = s_global * kv_stride + kv_head_offset + d;\n smem[s_local * hd + d] = V[v_addr];\n load_idx += WG;\n }\n workgroupBarrier();\n\n // ── Step 5: Parallel V accumulation ──\n // Thread tid handles output dim tid and (for hd>WG) dim tid+WG via out_acc2.\n // Uses saved score registers instead of smem[0..TILE_S).\n if (tid < hd) {\n out_acc = out_acc * old_correction;\n if (0u < tile_len) { out_acc += w0 * smem[0u * hd + tid]; }\n if (1u < tile_len) { out_acc += w1 * smem[1u * hd + tid]; }\n if (2u < tile_len) { out_acc += w2 * smem[2u * hd + tid]; }\n if (3u < tile_len) { out_acc += w3 * smem[3u * hd + tid]; }\n if (4u < tile_len) { out_acc += w4 * smem[4u * hd + tid]; }\n if (5u < tile_len) { out_acc += w5 * smem[5u * hd + tid]; }\n if (6u < tile_len) { out_acc += w6 * smem[6u * hd + tid]; }\n if (7u < tile_len) { out_acc += w7 * smem[7u * hd + tid]; }\n if (8u < tile_len) { out_acc += w8 * smem[8u * hd + tid]; }\n if (9u < tile_len) { out_acc += w9 * smem[9u * hd + tid]; }\n if (10u < tile_len) { out_acc += w10 * smem[10u * hd + tid]; }\n if (11u < tile_len) { out_acc += w11 * smem[11u * hd + tid]; }\n if (12u < tile_len) { out_acc += w12 * smem[12u * hd + tid]; }\n if (13u < tile_len) { out_acc += w13 * smem[13u * hd + tid]; }\n if (14u < tile_len) { out_acc += w14 * smem[14u * hd + tid]; }\n if (15u < tile_len) { out_acc += w15 * smem[15u * hd + tid]; }\n }\n if (d2 < hd) {\n out_acc2 = out_acc2 * old_correction;\n if (0u < tile_len) { out_acc2 += w0 * smem[0u * hd + d2]; }\n if (1u < tile_len) { out_acc2 += w1 * smem[1u * hd + d2]; }\n if (2u < tile_len) { out_acc2 += w2 * smem[2u * hd + d2]; }\n if (3u < tile_len) { out_acc2 += w3 * smem[3u * hd + d2]; }\n if (4u < tile_len) { out_acc2 += w4 * smem[4u * hd + d2]; }\n if (5u < tile_len) { out_acc2 += w5 * smem[5u * hd + d2]; }\n if (6u < tile_len) { out_acc2 += w6 * smem[6u * hd + d2]; }\n if (7u < tile_len) { out_acc2 += w7 * smem[7u * hd + d2]; }\n if (8u < tile_len) { out_acc2 += w8 * smem[8u * hd + d2]; }\n if (9u < tile_len) { out_acc2 += w9 * smem[9u * hd + d2]; }\n if (10u < tile_len) { out_acc2 += w10 * smem[10u * hd + d2]; }\n if (11u < tile_len) { out_acc2 += w11 * smem[11u * hd + d2]; }\n if (12u < tile_len) { out_acc2 += w12 * smem[12u * hd + d2]; }\n if (13u < tile_len) { out_acc2 += w13 * smem[13u * hd + d2]; }\n if (14u < tile_len) { out_acc2 += w14 * smem[14u * hd + d2]; }\n if (15u < tile_len) { out_acc2 += w15 * smem[15u * hd + d2]; }\n }\n\n // Update running state\n running_sum = running_sum * old_correction + tile_sum;\n running_max = new_max;\n workgroupBarrier();\n }\n\n // ── Write normalized output ──\n var inv_sum: f32 = 0.0;\n if (running_sum > 0.0) { inv_sum = 1.0 / running_sum; }\n let out_base = q_pos * q_stride + q_head * hd;\n if (tid < hd) {\n output[out_base + tid] = out_acc * inv_sum;\n }\n if (d2 < hd) {\n output[out_base + d2] = out_acc2 * inv_sum;\n }\n}\n`;\n\n// ── Cross-Attention (encoder-decoder) ──────────────────────────────────\n//\n// Decoder queries attend to a FIXED encoder output. This is the distinct\n// coherence regime from the causal self-attention KVCache path:\n//\n// Q = decoder hidden state [T_dec, num_q_heads * head_dim]\n// K = encoder output (K proj) [S_enc, num_kv_heads * head_dim]\n// V = encoder output (V proj) [S_enc, num_kv_heads * head_dim]\n//\n// The encoder K/V are computed ONCE and frozen for the whole decode — there\n// is no position_offset and no growing cache. Attention is UNMASKED /\n// bidirectional over the full S_enc encoder sequence (every decoder query\n// position sees every encoder position). One workgroup per (q_pos, q_head).\n//\n// Algorithm is the identical online-softmax tiling to WGSL_ATTENTION (the\n// validated flash-attention kernel) with the causal-limit logic removed, so\n// it inherits the same Safari/Metal-safe patterns:\n// - if/else instead of select()\n// - exp() args clamped to -80 (Metal returns NaN for exp(-1e30))\n// - <= 16 KB workgroup memory (single 4096 f32 smem array)\n// - no `enable f16`\nconst WGSL_CROSS_ATTENTION = `\\\nconst TILE_S: u32 = 16u;\nconst WG: u32 = 256u;\nconst DOT_THREADS: u32 = 16u;\n\nstruct Params {\n T: u32, // decoder query length\n S: u32, // encoder (key/value) length\n num_q_heads: u32,\n num_kv_heads: u32,\n head_dim: u32,\n}\n\n@group(0) @binding(0) var<storage, read> Q: array<f32>;\n@group(0) @binding(1) var<storage, read> K: array<f32>;\n@group(0) @binding(2) var<storage, read> V: array<f32>;\n@group(0) @binding(3) var<storage, read_write> output: array<f32>;\n@group(0) @binding(4) var<storage, read> params: Params;\n\nvar<workgroup> smem: array<f32, 4096>;\n\n@compute @workgroup_size(256)\nfn main(\n @builtin(local_invocation_id) lid: vec3u,\n @builtin(workgroup_id) wid: vec3u,\n) {\n let q_pos = wid.x;\n let q_head = wid.y;\n\n if (q_pos >= params.T || q_head >= params.num_q_heads) { return; }\n\n let tid = lid.x;\n let heads_per_kv = params.num_q_heads / params.num_kv_heads;\n let kv_head = q_head / heads_per_kv;\n let scale = 1.0 / sqrt(f32(params.head_dim));\n\n let q_stride = params.num_q_heads * params.head_dim;\n let kv_stride = params.num_kv_heads * params.head_dim;\n let q_base = q_pos * q_stride + q_head * params.head_dim;\n let kv_head_offset = kv_head * params.head_dim;\n let hd = params.head_dim;\n // Unmasked: every query attends to all S_enc encoder positions.\n let S_eff = params.S;\n\n var running_max: f32 = -1e30;\n var running_sum: f32 = 0.0;\n // out_acc2 covers dim tid+WG for head_dim up to 2*WG; unused for hd<=WG.\n var out_acc: f32 = 0.0;\n var out_acc2: f32 = 0.0;\n let d2 = tid + WG;\n\n // Tile capped so a K/V tile (tile_cap*hd f32) fits the 4096-f32 smem.\n let tile_cap = min(TILE_S, 4096u / hd);\n let num_tiles = (S_eff + tile_cap - 1u) / tile_cap;\n\n for (var tile: u32 = 0u; tile < num_tiles; tile += 1u) {\n let tile_start = tile * tile_cap;\n let tile_end = min(tile_start + tile_cap, S_eff);\n let tile_len = tile_end - tile_start;\n\n // ── Step 1: Cooperative K tile load ──\n let total_k_elems = tile_len * hd;\n var load_idx = tid;\n while (load_idx < total_k_elems) {\n let s_local = load_idx / hd;\n let d = load_idx % hd;\n let s_global = tile_start + s_local;\n let k_addr = s_global * kv_stride + kv_head_offset + d;\n smem[s_local * hd + d] = K[k_addr];\n load_idx += WG;\n }\n workgroupBarrier();\n\n // ── Step 2: Parallel Q·K dot products ──\n let pos_in_tile = tid / DOT_THREADS;\n let dot_tid = tid % DOT_THREADS;\n\n var my_partial: f32 = 0.0;\n if (pos_in_tile < tile_len) {\n let kv_row_base = pos_in_tile * hd;\n // Strided over head_dim so any head_dim works (incl. ones not divisible\n // by DOT_THREADS, e.g. Moonshine's 52). Each thread sums its dims.\n var d = dot_tid;\n while (d < hd) {\n my_partial += Q[q_base + d] * smem[kv_row_base + d];\n d += DOT_THREADS;\n }\n }\n\n workgroupBarrier();\n smem[tid] = my_partial;\n workgroupBarrier();\n\n var group_total: f32 = 0.0;\n if (dot_tid == 0u && pos_in_tile < tile_len) {\n group_total = smem[tid];\n for (var i: u32 = 1u; i < DOT_THREADS; i += 1u) {\n group_total += smem[tid + i];\n }\n }\n workgroupBarrier();\n if (dot_tid == 0u && pos_in_tile < tile_len) {\n smem[pos_in_tile] = group_total * scale;\n }\n workgroupBarrier();\n\n // ── Step 3: Online softmax (max reduce, no select()) ──\n if (tid < TILE_S) {\n if (tid < tile_len) {\n smem[TILE_S + tid] = smem[tid];\n } else {\n smem[TILE_S + tid] = -1e30;\n }\n }\n workgroupBarrier();\n if (tid < 8u) { smem[TILE_S + tid] = max(smem[TILE_S + tid], smem[TILE_S + tid + 8u]); }\n workgroupBarrier();\n if (tid < 4u) { smem[TILE_S + tid] = max(smem[TILE_S + tid], smem[TILE_S + tid + 4u]); }\n workgroupBarrier();\n if (tid < 2u) { smem[TILE_S + tid] = max(smem[TILE_S + tid], smem[TILE_S + tid + 2u]); }\n workgroupBarrier();\n if (tid < 1u) { smem[TILE_S + tid] = max(smem[TILE_S + tid], smem[TILE_S + tid + 1u]); }\n workgroupBarrier();\n let tile_max = smem[TILE_S];\n\n let new_max = max(running_max, tile_max);\n let old_correction = exp(max(running_max - new_max, -80.0));\n\n if (tid < tile_len) {\n smem[tid] = exp(max(smem[tid] - new_max, -80.0));\n }\n workgroupBarrier();\n\n if (tid < TILE_S) {\n if (tid < tile_len) {\n smem[TILE_S + tid] = smem[tid];\n } else {\n smem[TILE_S + tid] = 0.0;\n }\n }\n workgroupBarrier();\n if (tid < 8u) { smem[TILE_S + tid] += smem[TILE_S + tid + 8u]; }\n workgroupBarrier();\n if (tid < 4u) { smem[TILE_S + tid] += smem[TILE_S + tid + 4u]; }\n workgroupBarrier();\n if (tid < 2u) { smem[TILE_S + tid] += smem[TILE_S + tid + 2u]; }\n workgroupBarrier();\n if (tid < 1u) { smem[TILE_S + tid] += smem[TILE_S + tid + 1u]; }\n workgroupBarrier();\n let tile_sum = smem[TILE_S];\n\n // ── Save scores to per-thread registers before V overwrites smem ──\n var w0: f32 = 0.0; var w1: f32 = 0.0; var w2: f32 = 0.0; var w3: f32 = 0.0;\n var w4: f32 = 0.0; var w5: f32 = 0.0; var w6: f32 = 0.0; var w7: f32 = 0.0;\n var w8: f32 = 0.0; var w9: f32 = 0.0; var w10: f32 = 0.0; var w11: f32 = 0.0;\n var w12: f32 = 0.0; var w13: f32 = 0.0; var w14: f32 = 0.0; var w15: f32 = 0.0;\n if (tid < hd) {\n if (0u < tile_len) { w0 = smem[0]; }\n if (1u < tile_len) { w1 = smem[1]; }\n if (2u < tile_len) { w2 = smem[2]; }\n if (3u < tile_len) { w3 = smem[3]; }\n if (4u < tile_len) { w4 = smem[4]; }\n if (5u < tile_len) { w5 = smem[5]; }\n if (6u < tile_len) { w6 = smem[6]; }\n if (7u < tile_len) { w7 = smem[7]; }\n if (8u < tile_len) { w8 = smem[8]; }\n if (9u < tile_len) { w9 = smem[9]; }\n if (10u < tile_len) { w10 = smem[10]; }\n if (11u < tile_len) { w11 = smem[11]; }\n if (12u < tile_len) { w12 = smem[12]; }\n if (13u < tile_len) { w13 = smem[13]; }\n if (14u < tile_len) { w14 = smem[14]; }\n if (15u < tile_len) { w15 = smem[15]; }\n }\n workgroupBarrier();\n\n // ── Step 4: Cooperative V tile load ──\n let total_v_elems = tile_len * hd;\n load_idx = tid;\n while (load_idx < total_v_elems) {\n let s_local = load_idx / hd;\n let d = load_idx % hd;\n let s_global = tile_start + s_local;\n let v_addr = s_global * kv_stride + kv_head_offset + d;\n smem[s_local * hd + d] = V[v_addr];\n load_idx += WG;\n }\n workgroupBarrier();\n\n // ── Step 5: Parallel V accumulation ──\n if (tid < hd) {\n out_acc = out_acc * old_correction;\n if (0u < tile_len) { out_acc += w0 * smem[0u * hd + tid]; }\n if (1u < tile_len) { out_acc += w1 * smem[1u * hd + tid]; }\n if (2u < tile_len) { out_acc += w2 * smem[2u * hd + tid]; }\n if (3u < tile_len) { out_acc += w3 * smem[3u * hd + tid]; }\n if (4u < tile_len) { out_acc += w4 * smem[4u * hd + tid]; }\n if (5u < tile_len) { out_acc += w5 * smem[5u * hd + tid]; }\n if (6u < tile_len) { out_acc += w6 * smem[6u * hd + tid]; }\n if (7u < tile_len) { out_acc += w7 * smem[7u * hd + tid]; }\n if (8u < tile_len) { out_acc += w8 * smem[8u * hd + tid]; }\n if (9u < tile_len) { out_acc += w9 * smem[9u * hd + tid]; }\n if (10u < tile_len) { out_acc += w10 * smem[10u * hd + tid]; }\n if (11u < tile_len) { out_acc += w11 * smem[11u * hd + tid]; }\n if (12u < tile_len) { out_acc += w12 * smem[12u * hd + tid]; }\n if (13u < tile_len) { out_acc += w13 * smem[13u * hd + tid]; }\n if (14u < tile_len) { out_acc += w14 * smem[14u * hd + tid]; }\n if (15u < tile_len) { out_acc += w15 * smem[15u * hd + tid]; }\n }\n if (d2 < hd) {\n out_acc2 = out_acc2 * old_correction;\n if (0u < tile_len) { out_acc2 += w0 * smem[0u * hd + d2]; }\n if (1u < tile_len) { out_acc2 += w1 * smem[1u * hd + d2]; }\n if (2u < tile_len) { out_acc2 += w2 * smem[2u * hd + d2]; }\n if (3u < tile_len) { out_acc2 += w3 * smem[3u * hd + d2]; }\n if (4u < tile_len) { out_acc2 += w4 * smem[4u * hd + d2]; }\n if (5u < tile_len) { out_acc2 += w5 * smem[5u * hd + d2]; }\n if (6u < tile_len) { out_acc2 += w6 * smem[6u * hd + d2]; }\n if (7u < tile_len) { out_acc2 += w7 * smem[7u * hd + d2]; }\n if (8u < tile_len) { out_acc2 += w8 * smem[8u * hd + d2]; }\n if (9u < tile_len) { out_acc2 += w9 * smem[9u * hd + d2]; }\n if (10u < tile_len) { out_acc2 += w10 * smem[10u * hd + d2]; }\n if (11u < tile_len) { out_acc2 += w11 * smem[11u * hd + d2]; }\n if (12u < tile_len) { out_acc2 += w12 * smem[12u * hd + d2]; }\n if (13u < tile_len) { out_acc2 += w13 * smem[13u * hd + d2]; }\n if (14u < tile_len) { out_acc2 += w14 * smem[14u * hd + d2]; }\n if (15u < tile_len) { out_acc2 += w15 * smem[15u * hd + d2]; }\n }\n\n running_sum = running_sum * old_correction + tile_sum;\n running_max = new_max;\n workgroupBarrier();\n }\n\n // ── Write normalized output ──\n var inv_sum: f32 = 0.0;\n if (running_sum > 0.0) { inv_sum = 1.0 / running_sum; }\n let out_base = q_pos * q_stride + q_head * hd;\n if (tid < hd) {\n output[out_base + tid] = out_acc * inv_sum;\n }\n if (d2 < hd) {\n output[out_base + d2] = out_acc2 * inv_sum;\n }\n}\n`;\n\nconst WGSL_SOFTMAX = `\\\n// Row-wise softmax: output[i] = exp(input[i] - max) / sum(exp(input - max))\n\nstruct Params {\n num_rows: u32,\n row_size: u32,\n}\n\n@group(0) @binding(0) var<storage, read> input: array<f32>;\n@group(0) @binding(1) var<storage, read_write> output: array<f32>;\n@group(0) @binding(2) var<storage, read> params: Params;\n\nvar<workgroup> shared_data: array<f32, 256>;\n\n@compute @workgroup_size(256)\nfn main(\n @builtin(local_invocation_id) lid: vec3u,\n @builtin(workgroup_id) wid: vec3u,\n) {\n let row = wid.x;\n if (row >= params.num_rows) { return; }\n\n let tid = lid.x;\n let row_offset = row * params.row_size;\n\n var local_max: f32 = -1e30;\n var i = tid;\n while (i < params.row_size) {\n local_max = max(local_max, input[row_offset + i]);\n i += 256u;\n }\n shared_data[tid] = local_max;\n workgroupBarrier();\n\n var stride: u32 = 128u;\n while (stride > 0u) {\n if (tid < stride) {\n shared_data[tid] = max(shared_data[tid], shared_data[tid + stride]);\n }\n workgroupBarrier();\n stride /= 2u;\n }\n let row_max = shared_data[0];\n workgroupBarrier();\n\n var local_sum: f32 = 0.0;\n i = tid;\n while (i < params.row_size) {\n local_sum += exp(max(input[row_offset + i] - row_max, -80.0));\n i += 256u;\n }\n shared_data[tid] = local_sum;\n workgroupBarrier();\n\n stride = 128u;\n while (stride > 0u) {\n if (tid < stride) {\n shared_data[tid] += shared_data[tid + stride];\n }\n workgroupBarrier();\n stride /= 2u;\n }\n let inv_sum = 1.0 / shared_data[0];\n workgroupBarrier();\n\n i = tid;\n while (i < params.row_size) {\n output[row_offset + i] = exp(max(input[row_offset + i] - row_max, -80.0)) * inv_sum;\n i += 256u;\n }\n}\n`;\n\nconst WGSL_SILU = `\\\n// SiLU (Sigmoid Linear Unit): output = x * sigmoid(x) = x / (1 + exp(-x))\n\nstruct Params {\n count: u32,\n}\n\n@group(0) @binding(0) var<storage, read> input: array<f32>;\n@group(0) @binding(1) var<storage, read_write> output: array<f32>;\n@group(0) @binding(2) var<storage, read> params: Params;\n\n@compute @workgroup_size(256)\nfn main(@builtin(global_invocation_id) gid: vec3u) {\n let idx = gid.x;\n if (idx >= params.count) { return; }\n\n let x = input[idx];\n output[idx] = x / (1.0 + exp(max(-x, -80.0)));\n}\n`;\n\nconst WGSL_GELU = `\\\n// GELU (Gaussian Error Linear Unit) - approximate version\n// output = 0.5 * x * (1 + tanh(sqrt(2/pi) * (x + 0.044715 * x^3)))\n\nstruct Params {\n count: u32,\n}\n\n@group(0) @binding(0) var<storage, read> input: array<f32>;\n@group(0) @binding(1) var<storage, read_write> output: array<f32>;\n@group(0) @binding(2) var<storage, read> params: Params;\n\nconst SQRT_2_OVER_PI: f32 = 0.7978845608;\nconst GELU_COEFF: f32 = 0.044715;\n\n@compute @workgroup_size(256)\nfn main(@builtin(global_invocation_id) gid: vec3u) {\n let idx = gid.x;\n if (idx >= params.count) { return; }\n\n let x = input[idx];\n // Clamp the tanh argument: tanh saturates to ±1 by |arg|≈10, but on Metal/Dawn\n // tanh() of a large argument returns NaN (the internal exp overflows). The ViT\n // MLP can produce |x|>30 → x^3 → arg in the thousands, so clamp to a safe ±15.\n let inner = clamp(SQRT_2_OVER_PI * (x + GELU_COEFF * x * x * x), -15.0, 15.0);\n output[idx] = 0.5 * x * (1.0 + tanh(inner));\n}\n`;\n\nconst WGSL_ADD = `\\\n// Element-wise addition: output = a + b\n\nstruct Params {\n count: u32,\n}\n\n@group(0) @binding(0) var<storage, read> a: array<f32>;\n@group(0) @binding(1) var<storage, read> b: array<f32>;\n@group(0) @binding(2) var<storage, read_write> output: array<f32>;\n@group(0) @binding(3) var<storage, read> params: Params;\n\n@compute @workgroup_size(256)\nfn main(@builtin(global_invocation_id) gid: vec3u) {\n let idx = gid.x;\n if (idx >= params.count) { return; }\n output[idx] = a[idx] + b[idx];\n}\n`;\n\nconst WGSL_MUL = `\\\n// Element-wise multiplication: output = a * b\n\nstruct Params {\n count: u32,\n}\n\n@group(0) @binding(0) var<storage, read> a: array<f32>;\n@group(0) @binding(1) var<storage, read> b: array<f32>;\n@group(0) @binding(2) var<storage, read_write> output: array<f32>;\n@group(0) @binding(3) var<storage, read> params: Params;\n\n@compute @workgroup_size(256)\nfn main(@builtin(global_invocation_id) gid: vec3u) {\n let idx = gid.x;\n if (idx >= params.count) { return; }\n output[idx] = a[idx] * b[idx];\n}\n`;\n\nconst WGSL_SLICE_LAST_ROW = `\\\n// Copy the last row of a [T, width] tensor into a [1, width] tensor.\n// Lets lm_head run with M=1, so the logits buffer is [1, vocab] instead\n// of [T, vocab] (485MB at T=512 for a 248k vocab).\n\nstruct Params {\n width: u32,\n last_row_offset: u32,\n}\n\n@group(0) @binding(0) var<storage, read> src: array<f32>;\n@group(0) @binding(1) var<storage, read_write> output: array<f32>;\n@group(0) @binding(2) var<storage, read> params: Params;\n\n@compute @workgroup_size(256)\nfn main(@builtin(global_invocation_id) gid: vec3u) {\n let idx = gid.x;\n if (idx >= params.width) { return; }\n output[idx] = src[params.last_row_offset + idx];\n}\n`;\n\nconst WGSL_MEAN_POOL = `\\\n// Mean-pool a [T, width] tensor over the T tokens into a [1, width] tensor:\n// output[c] = (1/T) * sum_{t=0..T-1} src[t * width + c]\n// Used for bidirectional encoder embeddings (EmbeddingGemma) where the sentence\n// vector is the average of all token hidden states. We process a single unpadded\n// sequence, so every one of the T rows is a valid (non-pad) token — the mean is\n// over the full T with no attention-mask gather needed.\n// One thread per output channel.\n\nstruct Params {\n seq_len: u32,\n width: u32,\n}\n\n@group(0) @binding(0) var<storage, read> src: array<f32>;\n@group(0) @binding(1) var<storage, read_write> output: array<f32>;\n@group(0) @binding(2) var<storage, read> params: Params;\n\n@compute @workgroup_size(256)\nfn main(@builtin(global_invocation_id) gid: vec3u) {\n let c = gid.x;\n if (c >= params.width) { return; }\n var acc = 0.0;\n var t = 0u;\n loop {\n if (t >= params.seq_len) { break; }\n acc += src[t * params.width + c];\n t += 1u;\n }\n let inv_t = 1.0 / max(f32(params.seq_len), 1.0);\n output[c] = acc * inv_t;\n}\n`;\n\nconst WGSL_SCALE = `\\\n// Multiply every element of a tensor by a scalar constant:\n// output[i] = input[i] * scale\n// Used for Gemma's embedding normalizer (× sqrt(hidden_size)), which is applied\n// to the residual stream after the token embedding lookup.\n\nstruct Params {\n count: u32,\n scale_bits: u32,\n}\n\n@group(0) @binding(0) var<storage, read> input: array<f32>;\n@group(0) @binding(1) var<storage, read_write> output: array<f32>;\n@group(0) @binding(2) var<storage, read> params: Params;\n\n@compute @workgroup_size(256)\nfn main(@builtin(global_invocation_id) gid: vec3u) {\n let idx = gid.x;\n if (idx >= params.count) { return; }\n output[idx] = input[idx] * bitcast<f32>(params.scale_bits);\n}\n`;\n\n// ── Softcap (Gemma final logit softcapping) ──\n// output[i] = cap * tanh(input[i] / cap)\n// Squashes logits into (-cap, cap). Mobile-safe: tanh is a WGSL builtin, no\n// select(), no exp (tanh is bounded), no f16. The divide-by-cap argument to tanh\n// is finite for real logits, so no clamping is required.\nconst WGSL_SOFTCAP = `\\\nstruct Params {\n count: u32,\n cap_bits: u32,\n}\n\n@group(0) @binding(0) var<storage, read> input: array<f32>;\n@group(0) @binding(1) var<storage, read_write> output: array<f32>;\n@group(0) @binding(2) var<storage, read> params: Params;\n\n@compute @workgroup_size(256)\nfn main(@builtin(global_invocation_id) gid: vec3u) {\n let idx = gid.x;\n if (idx >= params.count) { return; }\n let cap = bitcast<f32>(params.cap_bits);\n output[idx] = cap * tanh(input[idx] / cap);\n}\n`;\n\n// (SliceCols WGSL + spec already defined above for the ViT qkv split; Gemma 4\n// PLE reuses it for the per-layer-input column slice.)\n\nconst WGSL_L2NORM = `\\\n// L2-normalize each row of a [rows, width] tensor.\n// embedding[r, :] = x[r, :] / max(sqrt(sum(x[r, :]^2)), eps)\n// One workgroup per row; threads cooperatively reduce the sum of squares.\n\nstruct Params {\n rows: u32,\n width: u32,\n}\n\n@group(0) @binding(0) var<storage, read> src: array<f32>;\n@group(0) @binding(1) var<storage, read_write> output: array<f32>;\n@group(0) @binding(2) var<storage, read> params: Params;\n\nvar<workgroup> partial: array<f32, 256>;\n\n@compute @workgroup_size(256)\nfn main(\n @builtin(workgroup_id) wid: vec3u,\n @builtin(local_invocation_id) lid: vec3u,\n) {\n let row = wid.x;\n if (row >= params.rows) { return; }\n let tid = lid.x;\n let base = row * params.width;\n\n // Each thread accumulates a strided slice of the row.\n var acc = 0.0;\n var i = tid;\n loop {\n if (i >= params.width) { break; }\n let v = src[base + i];\n acc += v * v;\n i += 256u;\n }\n partial[tid] = acc;\n workgroupBarrier();\n\n // Tree reduction over the 256 partials.\n var stride = 128u;\n loop {\n if (stride == 0u) { break; }\n if (tid < stride) {\n partial[tid] += partial[tid + stride];\n }\n workgroupBarrier();\n stride = stride >> 1u;\n }\n\n // inv_norm = 1 / max(sqrt(sumsq), eps)\n let inv_norm = 1.0 / max(sqrt(partial[0]), 1e-12);\n workgroupBarrier();\n\n var j = tid;\n loop {\n if (j >= params.width) { break; }\n output[base + j] = src[base + j] * inv_norm;\n j += 256u;\n }\n}\n`;\n\nconst WGSL_ADD_BIAS = `\\\n// Row-broadcast bias add: output[r * width + c] = input[r * width + c] + bias[c].\n// Used by the ViT linear layers (qkv/proj/fc1/fc2/patch_embed) whose MatMul has\n// no bias term — the bias vector (length = width) is added across all rows.\n\nstruct Params {\n count: u32,\n width: u32,\n}\n\n@group(0) @binding(0) var<storage, read> input: array<f32>;\n@group(0) @binding(1) var<storage, read> bias: array<f32>;\n@group(0) @binding(2) var<storage, read_write> output: array<f32>;\n@group(0) @binding(3) var<storage, read> params: Params;\n\n@compute @workgroup_size(256)\nfn main(@builtin(global_invocation_id) gid: vec3u) {\n let idx = gid.x;\n if (idx >= params.count) { return; }\n let c = idx % params.width;\n output[idx] = input[idx] + bias[c];\n}\n`;\n\nconst WGSL_GELU_ERF = `\\\n// Exact GELU (erf form): output = 0.5 * x * (1 + erf(x / sqrt(2))).\n// The ViT merger uses nn.GELU() (exact), unlike the block MLP which uses the\n// tanh approximation. WGSL has no erf(), so we use a high-accuracy rational\n// approximation (Abramowitz & Stegun 7.1.26), giving < 1e-6 abs error.\n\nstruct Params {\n count: u32,\n}\n\n@group(0) @binding(0) var<storage, read> input: array<f32>;\n@group(0) @binding(1) var<storage, read_write> output: array<f32>;\n@group(0) @binding(2) var<storage, read> params: Params;\n\nconst INV_SQRT2: f32 = 0.7071067811865476;\n\nfn erf_approx(x: f32) -> f32 {\n // A&S 7.1.26: erf(x) for x >= 0; odd-extended for x < 0.\n // if/else instead of select() — Safari/Metal has select() bugs.\n var sign = 1.0;\n if (x < 0.0) { sign = -1.0; }\n let ax = abs(x);\n let t = 1.0 / (1.0 + 0.3275911 * ax);\n let y = 1.0 - (((((1.061405429 * t - 1.453152027) * t) + 1.421413741) * t - 0.284496736) * t + 0.254829592) * t * exp(-ax * ax);\n return sign * y;\n}\n\n@compute @workgroup_size(256)\nfn main(@builtin(global_invocation_id) gid: vec3u) {\n let idx = gid.x;\n if (idx >= params.count) { return; }\n let x = input[idx];\n output[idx] = 0.5 * x * (1.0 + erf_approx(x * INV_SQRT2));\n}\n`;\n\nconst WGSL_SLICE_COLS = `\\\n// Extract a contiguous column range from a [rows, in_width] tensor into a\n// [rows, out_width] tensor: output[r, c] = input[r, col_offset + c].\n// Used to split the fused ViT qkv projection [N, 3*768] into Q/K/V [N, 768].\n\nstruct Params {\n rows: u32,\n in_width: u32,\n out_width: u32,\n col_offset: u32,\n}\n\n@group(0) @binding(0) var<storage, read> input: array<f32>;\n@group(0) @binding(1) var<storage, read_write> output: array<f32>;\n@group(0) @binding(2) var<storage, read> params: Params;\n\n@compute @workgroup_size(256)\nfn main(@builtin(global_invocation_id) gid: vec3u) {\n let idx = gid.x;\n let total = params.rows * params.out_width;\n if (idx >= total) { return; }\n let r = idx / params.out_width;\n let c = idx % params.out_width;\n output[idx] = input[r * params.in_width + params.col_offset + c];\n}\n`;\n\nconst WGSL_MUL_COLS = `\\\n// Multiply two contiguous column ranges of a single [rows, in_width] tensor:\n// output[r, c] = src[r, off_a + c] * src[r, off_b + c]\n// LFM2 short-conv pre-gate: Bx = B * x where B and x are column slices of the\n// fused in_proj output — fuses two SliceCols + a Mul into one dispatch.\n\nstruct Params {\n rows: u32,\n in_width: u32,\n out_width: u32,\n off_a: u32,\n off_b: u32,\n _pad0: u32,\n _pad1: u32,\n _pad2: u32,\n}\n\n@group(0) @binding(0) var<storage, read> src: array<f32>;\n@group(0) @binding(1) var<storage, read_write> output: array<f32>;\n@group(0) @binding(2) var<storage, read> params: Params;\n\n@compute @workgroup_size(256)\nfn main(@builtin(global_invocation_id) gid: vec3u) {\n let idx = gid.x;\n let total = params.rows * params.out_width;\n if (idx >= total) { return; }\n let r = idx / params.out_width;\n let c = idx % params.out_width;\n let base = r * params.in_width;\n output[idx] = src[base + params.off_a + c] * src[base + params.off_b + c];\n}\n`;\n\nconst WGSL_APPLY_ROTARY = `\\\n// Apply precomputed rotary embeddings to a [T, num_heads*head_dim] tensor.\n// rotate_half convention (matches HF apply_rotary_pos_emb_vision):\n// out = x * cos + rotate_half(x) * sin\n// rotate_half(x) = concat(-x[half:], x[:half]) over each head's head_dim.\n// cos/sin are [T, head_dim], broadcast across all heads of a row.\n// In-place safe is NOT assumed — writes to a separate output buffer.\n\nstruct Params {\n seq_len: u32,\n num_heads: u32,\n head_dim: u32,\n _pad: u32,\n}\n\n@group(0) @binding(0) var<storage, read> x: array<f32>;\n@group(0) @binding(1) var<storage, read> cos_t: array<f32>;\n@group(0) @binding(2) var<storage, read> sin_t: array<f32>;\n@group(0) @binding(3) var<storage, read_write> output: array<f32>;\n@group(0) @binding(4) var<storage, read> params: Params;\n\n@compute @workgroup_size(256)\nfn main(@builtin(global_invocation_id) gid: vec3u) {\n let idx = gid.x;\n let hd = params.head_dim;\n let total = params.seq_len * params.num_heads * hd;\n if (idx >= total) { return; }\n\n let d = idx % hd; // dim within head\n let row = idx / (params.num_heads * hd); // token position (for cos/sin lookup)\n let half = hd / 2u;\n\n let xv = x[idx];\n // rotate_half: for d < half → pairs with (d+half) as -x2; for d >= half → x1\n var rot: f32;\n if (d < half) {\n rot = -x[idx + half];\n } else {\n rot = x[idx - half];\n }\n\n let cs = cos_t[row * hd + d];\n let sn = sin_t[row * hd + d];\n output[idx] = xv * cs + rot * sn;\n}\n`;\n\nconst WGSL_MROPE = `\\\n// Multimodal RoPE (M-RoPE) with partial rotation, rotate_half convention.\n// Identical math to WGSL_ROPE but the per-token angles come from host-precomputed\n// cos/sin tables [seq, rope_dim] (built from 3D position ids) instead of pos*inv_freq.\n// Only the first rope_dim of each head is rotated; the rest passes through.\n// For text-only this is fed linear-position cos/sin and reduces to standard 1D RoPE.\n//\n// Q: [T, num_q_heads*head_dim] K: [T, num_kv_heads*head_dim]\n// cos/sin: [seq, rope_dim] (rope_dim == 2*half_rope; emb=cat(freqs,freqs))\n\nstruct Params {\n seq_len: u32,\n num_q_heads: u32,\n num_kv_heads: u32,\n head_dim: u32,\n rope_dim: u32,\n pos_offset: u32, // row offset into cos/sin for decode (single-token steps)\n _pad0: u32,\n _pad1: u32,\n}\n\n@group(0) @binding(0) var<storage, read_write> q: array<f32>;\n@group(0) @binding(1) var<storage, read_write> k: array<f32>;\n@group(0) @binding(2) var<storage, read> cos_t: array<f32>;\n@group(0) @binding(3) var<storage, read> sin_t: array<f32>;\n@group(0) @binding(4) var<storage, read> params: Params;\n\n@compute @workgroup_size(256)\nfn main(@builtin(global_invocation_id) gid: vec3u) {\n let half_rope = params.rope_dim / 2u;\n let total_q_pairs = params.seq_len * params.num_q_heads * half_rope;\n let total_k_pairs = params.seq_len * params.num_kv_heads * half_rope;\n let idx = gid.x;\n let rd = params.rope_dim;\n\n if (idx < total_q_pairs) {\n let pos = idx / (params.num_q_heads * half_rope);\n let remainder = idx % (params.num_q_heads * half_rope);\n let head = remainder / half_rope;\n let pair_idx = remainder % half_rope;\n\n let cos_row = (pos + params.pos_offset) * rd;\n let cs = cos_t[cos_row + pair_idx];\n let sn = sin_t[cos_row + pair_idx];\n\n let q_stride = params.num_q_heads * params.head_dim;\n let head_base = pos * q_stride + head * params.head_dim;\n let lo = head_base + pair_idx;\n let hi = head_base + pair_idx + half_rope;\n let q_lo = q[lo];\n let q_hi = q[hi];\n // rotate_half: out_lo = lo*cos - hi*sin ; out_hi = hi*cos + lo*sin\n q[lo] = q_lo * cs - q_hi * sn;\n q[hi] = q_hi * cs + q_lo * sn;\n }\n\n if (idx < total_k_pairs) {\n let pos = idx / (params.num_kv_heads * half_rope);\n let remainder = idx % (params.num_kv_heads * half_rope);\n let head = remainder / half_rope;\n let pair_idx = remainder % half_rope;\n\n let cos_row = (pos + params.pos_offset) * rd;\n let cs = cos_t[cos_row + pair_idx];\n let sn = sin_t[cos_row + pair_idx];\n\n let k_stride = params.num_kv_heads * params.head_dim;\n let head_base = pos * k_stride + head * params.head_dim;\n let lo = head_base + pair_idx;\n let hi = head_base + pair_idx + half_rope;\n let k_lo = k[lo];\n let k_hi = k[hi];\n k[lo] = k_lo * cs - k_hi * sn;\n k[hi] = k_hi * cs + k_lo * sn;\n }\n}\n`;\n\nconst WGSL_EMBED_SPLICE = `\\\n// Overwrite image-token rows of the text embeddings with merged vision tokens.\n// embed_out[row, c] = vision[map[row], c] when map[row] >= 0, else embed_in[row,c].\n// row_map: i32 per token; -1 for text rows, else the index into the vision buffer.\n// Writes to a separate output buffer (pooled-alias-safe).\n\nstruct Params {\n seq_len: u32,\n hidden: u32,\n}\n\n@group(0) @binding(0) var<storage, read> embed_in: array<f32>;\n@group(0) @binding(1) var<storage, read> vision: array<f32>;\n@group(0) @binding(2) var<storage, read> row_map: array<i32>;\n@group(0) @binding(3) var<storage, read_write> embed_out: array<f32>;\n@group(0) @binding(4) var<storage, read> params: Params;\n\n@compute @workgroup_size(256)\nfn main(@builtin(global_invocation_id) gid: vec3u) {\n let idx = gid.x;\n let total = params.seq_len * params.hidden;\n if (idx >= total) { return; }\n let row = idx / params.hidden;\n let col = idx % params.hidden;\n let vrow = row_map[row];\n if (vrow >= 0) {\n embed_out[idx] = vision[u32(vrow) * params.hidden + col];\n } else {\n embed_out[idx] = embed_in[idx];\n }\n}\n`;\n\nconst WGSL_SWIGLU = `\\\n// Fused SwiGLU: output = SiLU(gate) * up = (gate / (1 + exp(-gate))) * up\n// Saves 2 dispatches per MLP block (SiLU + Mul → single kernel).\n\nstruct Params {\n count: u32,\n}\n\n@group(0) @binding(0) var<storage, read> gate: array<f32>;\n@group(0) @binding(1) var<storage, read> up: array<f32>;\n@group(0) @binding(2) var<storage, read_write> output: array<f32>;\n@group(0) @binding(3) var<storage, read> params: Params;\n\n@compute @workgroup_size(256)\nfn main(@builtin(global_invocation_id) gid: vec3u) {\n let idx = gid.x;\n if (idx >= params.count) { return; }\n\n let g = gate[idx];\n output[idx] = (g / (1.0 + exp(max(-g, -80.0)))) * up[idx];\n}\n`;\n\nconst WGSL_SWIGLU_MATVEC = `\\\n// Fused gate+up projection + SwiGLU for M=1 decode.\n// output[d] = SiLU(A · B_gate[d,:]) * (A · B_up[d,:])\n// Reads input vector A once from L1 cache, computes both projections,\n// and applies SiLU gating in a single dispatch.\n// Saves 2 dispatches per MLP block vs separate gate_proj + up_proj + SwiGLU.\n\nconst N_TILE: u32 = 8u;\nconst K_THREADS: u32 = 32u;\n\nstruct Params {\n K: u32,\n N: u32,\n}\n\n@group(0) @binding(0) var<storage, read> A: array<f32>;\n@group(0) @binding(1) var<storage, read> B_gate: array<f32>;\n@group(0) @binding(2) var<storage, read> B_up: array<f32>;\n@group(0) @binding(3) var<storage, read_write> output: array<f32>;\n@group(0) @binding(4) var<storage, read> params: Params;\n\nvar<workgroup> shared_gate: array<f32, 256>;\nvar<workgroup> shared_up: array<f32, 256>;\n\n@compute @workgroup_size(256)\nfn main(\n @builtin(local_invocation_id) lid: vec3u,\n @builtin(workgroup_id) wid: vec3u,\n) {\n let tid = lid.x;\n let n_idx = tid / K_THREADS;\n let k_tid = tid % K_THREADS;\n let col = wid.x * N_TILE + n_idx;\n let K = params.K;\n\n var sum_gate: f32 = 0.0;\n var sum_up: f32 = 0.0;\n if (col < params.N) {\n let b_off = col * K;\n var k = k_tid * 4u;\n while (k + 3u < K) {\n let a_v = vec4f(A[k], A[k+1u], A[k+2u], A[k+3u]);\n sum_gate += dot(a_v, vec4f(B_gate[b_off+k], B_gate[b_off+k+1u], B_gate[b_off+k+2u], B_gate[b_off+k+3u]));\n sum_up += dot(a_v, vec4f(B_up[b_off+k], B_up[b_off+k+1u], B_up[b_off+k+2u], B_up[b_off+k+3u]));\n k += K_THREADS * 4u;\n }\n }\n\n shared_gate[tid] = sum_gate;\n shared_up[tid] = sum_up;\n workgroupBarrier();\n\n if (k_tid == 0u && col < params.N) {\n let base = n_idx * K_THREADS;\n var tg: f32 = shared_gate[base];\n var tu: f32 = shared_up[base];\n for (var i: u32 = 1u; i < K_THREADS; i++) {\n tg += shared_gate[base + i];\n tu += shared_up[base + i];\n }\n let g = tg;\n output[col] = (g / (1.0 + exp(max(-g, -80.0)))) * tu;\n }\n}\n`;\n\nconst WGSL_SWIGLU_MATVEC_INT4 = `\\\n// CONSTRAINT: N_TILE must equal workgroup_size / K_THREADS\nconst N_TILE: u32 = 8u;\nconst K_THREADS: u32 = 32u;\n\nstruct Params {\n K: u32,\n N: u32,\n group_size: u32,\n _pad: u32,\n}\n\n@group(0) @binding(0) var<storage, read> A: array<vec4f>;\n@group(0) @binding(1) var<storage, read> B_gate_q: array<vec4u>;\n@group(0) @binding(2) var<storage, read> B_gate_s: array<f32>;\n@group(0) @binding(3) var<storage, read> B_gate_z: array<f32>;\n@group(0) @binding(4) var<storage, read> B_up_q: array<vec4u>;\n@group(0) @binding(5) var<storage, read> B_up_s: array<f32>;\n@group(0) @binding(6) var<storage, read> B_up_z: array<f32>;\n@group(0) @binding(7) var<storage, read_write> output: array<f32>;\n@group(0) @binding(8) var<storage, read> params: Params;\n\nvar<workgroup> shared_gate: array<f32, 256>;\nvar<workgroup> shared_up: array<f32, 256>;\n\n@compute @workgroup_size(256)\nfn main(\n @builtin(local_invocation_id) lid: vec3u,\n @builtin(workgroup_id) wid: vec3u,\n) {\n let tid = lid.x;\n let n_idx = tid / K_THREADS;\n let k_tid = tid % K_THREADS;\n let col = wid.x * N_TILE + n_idx;\n let K = params.K;\n let k_vec = K / 32u;\n let groups_per_col = K / params.group_size;\n let packed_per_group = params.group_size / 8u;\n\n // vec4 loads: each thread reads one vec4u per matrix per iteration\n // (4 packed u32s = 32 INT4 weights). group_size (128) spans 16 packed u32s,\n // so a vec4u never straddles a quantization group.\n var sum_gate: f32 = 0.0;\n var sum_up: f32 = 0.0;\n if (col < params.N) {\n let b_off_v = col * k_vec;\n let col_g_base = col * groups_per_col;\n\n var v = k_tid;\n while (v < k_vec) {\n let group_idx = col_g_base + (v * 4u) / packed_per_group;\n let gq = B_gate_q[b_off_v + v];\n let g_scale = B_gate_s[group_idx];\n let g_zero = B_gate_z[group_idx];\n let uq = B_up_q[b_off_v + v];\n let u_scale = B_up_s[group_idx];\n let u_zero = B_up_z[group_idx];\n let a_base = v * 8u;\n\n for (var j: u32 = 0u; j < 4u; j++) {\n // Gate weight dequantization\n let g_packed = gq[j];\n let gn0 = f32(g_packed & 0xFu); let gn1 = f32((g_packed >> 4u) & 0xFu);\n let gn2 = f32((g_packed >> 8u) & 0xFu); let gn3 = f32((g_packed >> 12u) & 0xFu);\n let gn4 = f32((g_packed >> 16u) & 0xFu); let gn5 = f32((g_packed >> 20u) & 0xFu);\n let gn6 = f32((g_packed >> 24u) & 0xFu); let gn7 = f32((g_packed >> 28u) & 0xFu);\n let gb0 = vec4f(gn0 - g_zero, gn1 - g_zero, gn2 - g_zero, gn3 - g_zero) * g_scale;\n let gb1 = vec4f(gn4 - g_zero, gn5 - g_zero, gn6 - g_zero, gn7 - g_zero) * g_scale;\n\n // Up weight dequantization\n let u_packed = uq[j];\n let un0 = f32(u_packed & 0xFu); let un1 = f32((u_packed >> 4u) & 0xFu);\n let un2 = f32((u_packed >> 8u) & 0xFu); let un3 = f32((u_packed >> 12u) & 0xFu);\n let un4 = f32((u_packed >> 16u) & 0xFu); let un5 = f32((u_packed >> 20u) & 0xFu);\n let un6 = f32((u_packed >> 24u) & 0xFu); let un7 = f32((u_packed >> 28u) & 0xFu);\n let ub0 = vec4f(un0 - u_zero, un1 - u_zero, un2 - u_zero, un3 - u_zero) * u_scale;\n let ub1 = vec4f(un4 - u_zero, un5 - u_zero, un6 - u_zero, un7 - u_zero) * u_scale;\n\n let a0 = A[a_base + j * 2u];\n let a1 = A[a_base + j * 2u + 1u];\n\n sum_gate += dot(a0, gb0) + dot(a1, gb1);\n sum_up += dot(a0, ub0) + dot(a1, ub1);\n }\n\n v += K_THREADS;\n }\n }\n\n shared_gate[tid] = sum_gate;\n shared_up[tid] = sum_up;\n workgroupBarrier();\n\n if (k_tid == 0u && col < params.N) {\n let base = n_idx * K_THREADS;\n var tg: f32 = shared_gate[base];\n var tu: f32 = shared_up[base];\n for (var i: u32 = 1u; i < K_THREADS; i++) {\n tg += shared_gate[base + i];\n tu += shared_up[base + i];\n }\n let gg = tg;\n output[col] = (gg / (1.0 + exp(max(-gg, -80.0)))) * tu;\n }\n}\n`;\n\n// ── Dual MatVecInt4 (two independent INT4 projections sharing input A, M=1) ──\n//\n// out0[d] = A · dequant(B0[d,:]), out1[d] = A · dequant(B1[d,:])\n// Identical dequant + reduction to WGSL_SWIGLU_MATVEC_INT4, but writes the two\n// projection sums to SEPARATE output buffers instead of SiLU-combining them.\n// Used to fuse q_proj+gate_proj and k_proj+v_proj in full-attention decode:\n// both projections share the same input_layernorm activation and the same K/N,\n// so reading A once and computing both halves removes one full GPU round-trip\n// (one submit+drain on Safari/iOS) per fused pair.\nconst WGSL_DUAL_MATVEC_INT4 = `\\\nconst N_TILE: u32 = 8u;\nconst K_THREADS: u32 = 32u;\n\nstruct Params {\n K: u32,\n N: u32,\n group_size: u32,\n _pad: u32,\n}\n\n@group(0) @binding(0) var<storage, read> A: array<vec4f>;\n@group(0) @binding(1) var<storage, read> B0_q: array<vec4u>;\n@group(0) @binding(2) var<storage, read> B0_s: array<f32>;\n@group(0) @binding(3) var<storage, read> B0_z: array<f32>;\n@group(0) @binding(4) var<storage, read> B1_q: array<vec4u>;\n@group(0) @binding(5) var<storage, read> B1_s: array<f32>;\n@group(0) @binding(6) var<storage, read> B1_z: array<f32>;\n@group(0) @binding(7) var<storage, read_write> out0: array<f32>;\n@group(0) @binding(8) var<storage, read_write> out1: array<f32>;\n@group(0) @binding(9) var<storage, read> params: Params;\n\nvar<workgroup> shared_0: array<f32, 256>;\nvar<workgroup> shared_1: array<f32, 256>;\n\n@compute @workgroup_size(256)\nfn main(\n @builtin(local_invocation_id) lid: vec3u,\n @builtin(workgroup_id) wid: vec3u,\n) {\n let tid = lid.x;\n let n_idx = tid / K_THREADS;\n let k_tid = tid % K_THREADS;\n let col = wid.x * N_TILE + n_idx;\n let K = params.K;\n let k_vec = K / 32u;\n let groups_per_col = K / params.group_size;\n let packed_per_group = params.group_size / 8u;\n\n var sum_0: f32 = 0.0;\n var sum_1: f32 = 0.0;\n if (col < params.N) {\n let b_off_v = col * k_vec;\n let col_g_base = col * groups_per_col;\n\n var v = k_tid;\n while (v < k_vec) {\n let group_idx = col_g_base + (v * 4u) / packed_per_group;\n let q0 = B0_q[b_off_v + v];\n let s0 = B0_s[group_idx];\n let z0 = B0_z[group_idx];\n let q1 = B1_q[b_off_v + v];\n let s1 = B1_s[group_idx];\n let z1 = B1_z[group_idx];\n let a_base = v * 8u;\n\n for (var j: u32 = 0u; j < 4u; j++) {\n let p0 = q0[j];\n let a0n0 = f32(p0 & 0xFu); let a0n1 = f32((p0 >> 4u) & 0xFu);\n let a0n2 = f32((p0 >> 8u) & 0xFu); let a0n3 = f32((p0 >> 12u) & 0xFu);\n let a0n4 = f32((p0 >> 16u) & 0xFu); let a0n5 = f32((p0 >> 20u) & 0xFu);\n let a0n6 = f32((p0 >> 24u) & 0xFu); let a0n7 = f32((p0 >> 28u) & 0xFu);\n let b0a = vec4f(a0n0 - z0, a0n1 - z0, a0n2 - z0, a0n3 - z0) * s0;\n let b0b = vec4f(a0n4 - z0, a0n5 - z0, a0n6 - z0, a0n7 - z0) * s0;\n\n let p1 = q1[j];\n let a1n0 = f32(p1 & 0xFu); let a1n1 = f32((p1 >> 4u) & 0xFu);\n let a1n2 = f32((p1 >> 8u) & 0xFu); let a1n3 = f32((p1 >> 12u) & 0xFu);\n let a1n4 = f32((p1 >> 16u) & 0xFu); let a1n5 = f32((p1 >> 20u) & 0xFu);\n let a1n6 = f32((p1 >> 24u) & 0xFu); let a1n7 = f32((p1 >> 28u) & 0xFu);\n let b1a = vec4f(a1n0 - z1, a1n1 - z1, a1n2 - z1, a1n3 - z1) * s1;\n let b1b = vec4f(a1n4 - z1, a1n5 - z1, a1n6 - z1, a1n7 - z1) * s1;\n\n let av0 = A[a_base + j * 2u];\n let av1 = A[a_base + j * 2u + 1u];\n\n sum_0 += dot(av0, b0a) + dot(av1, b0b);\n sum_1 += dot(av0, b1a) + dot(av1, b1b);\n }\n\n v += K_THREADS;\n }\n }\n\n shared_0[tid] = sum_0;\n shared_1[tid] = sum_1;\n workgroupBarrier();\n\n if (k_tid == 0u && col < params.N) {\n let base = n_idx * K_THREADS;\n var t0: f32 = shared_0[base];\n var t1: f32 = shared_1[base];\n for (var i: u32 = 1u; i < K_THREADS; i++) {\n t0 += shared_0[base + i];\n t1 += shared_1[base + i];\n }\n out0[col] = t0;\n out1[col] = t1;\n }\n}\n`;\n\nconst WGSL_RESIDUAL_RMSNORM = `\\\n// Fused residual add + RMS normalization.\n// sum_output = a + b (residual sum, kept for downstream residual)\n// norm_output = RMSNorm(a + b) * weight\n\nstruct Params {\n seq_len: u32,\n hidden_size: u32,\n eps_bits: u32,\n _pad: u32,\n}\n\n@group(0) @binding(0) var<storage, read> a: array<f32>;\n@group(0) @binding(1) var<storage, read> b: array<f32>;\n@group(0) @binding(2) var<storage, read> weight: array<f32>;\n@group(0) @binding(3) var<storage, read_write> sum_output: array<f32>;\n@group(0) @binding(4) var<storage, read_write> norm_output: array<f32>;\n@group(0) @binding(5) var<storage, read> params: Params;\n\nvar<workgroup> shared_sum: array<f32, 256>;\n\n@compute @workgroup_size(256)\nfn main(\n @builtin(local_invocation_id) lid: vec3u,\n @builtin(workgroup_id) wid: vec3u,\n) {\n let row = wid.x;\n if (row >= params.seq_len) { return; }\n\n let tid = lid.x;\n let row_offset = row * params.hidden_size;\n let eps = bitcast<f32>(params.eps_bits);\n\n // Pass 1: add residual, store sum, accumulate sum-of-squares\n var local_sum: f32 = 0.0;\n var i = tid;\n while (i < params.hidden_size) {\n let val = a[row_offset + i] + b[row_offset + i];\n sum_output[row_offset + i] = val;\n local_sum += val * val;\n i += 256u;\n }\n shared_sum[tid] = local_sum;\n workgroupBarrier();\n\n // Tree reduction\n var stride: u32 = 128u;\n while (stride > 0u) {\n if (tid < stride) {\n shared_sum[tid] += shared_sum[tid + stride];\n }\n workgroupBarrier();\n stride = stride / 2u;\n }\n\n let rms = sqrt(shared_sum[0] / f32(params.hidden_size) + eps);\n\n // Pass 2: normalize and scale\n i = tid;\n while (i < params.hidden_size) {\n let val = sum_output[row_offset + i];\n norm_output[row_offset + i] = (val / rms) * weight[i];\n i += 256u;\n }\n}\n`;\n\nconst WGSL_CAUSAL_CONV1D = `\\\n// Causal 1D depthwise convolution with state buffer for autoregressive decode.\n// conv_state stores the last (kernel_size-1) input timesteps from prior forward passes.\n\nstruct Params {\n seq_len: u32,\n channels: u32,\n kernel_size: u32,\n _pad: u32,\n}\n\n@group(0) @binding(0) var<storage, read> input: array<f32>;\n@group(0) @binding(1) var<storage, read> weight: array<f32>;\n@group(0) @binding(2) var<storage, read> conv_state: array<f32>;\n@group(0) @binding(3) var<storage, read_write> output: array<f32>;\n@group(0) @binding(4) var<storage, read> params: Params;\n\n@compute @workgroup_size(256)\nfn main(@builtin(global_invocation_id) gid: vec3u) {\n let idx = gid.x;\n let total = params.seq_len * params.channels;\n if (idx >= total) { return; }\n\n let t = idx / params.channels;\n let c = idx % params.channels;\n let state_size = params.kernel_size - 1u;\n\n var sum: f32 = 0.0;\n for (var k: u32 = 0u; k < params.kernel_size; k = k + 1u) {\n let src_t = i32(t) - i32(k);\n var in_val: f32 = 0.0;\n if (src_t >= 0) {\n in_val = input[u32(src_t) * params.channels + c];\n } else {\n // Read from state: state[0]=oldest, state[state_size-1]=most recent\n let state_t = i32(state_size) + src_t;\n if (state_t >= 0) {\n in_val = conv_state[u32(state_t) * params.channels + c];\n }\n }\n // PyTorch Conv1d stores weights reversed: w[0]=most distant, w[K-1]=current.\n // Our loop looks back k steps, so we need w[K-1-k] to match PyTorch.\n let w_val = weight[c * params.kernel_size + (params.kernel_size - 1u - k)];\n sum += in_val * w_val;\n }\n\n output[t * params.channels + c] = sum;\n}\n`;\n\nconst WGSL_CAUSAL_CONV1D_SILU = `\\\n// Causal 1D depthwise convolution with fused SiLU activation on the output.\n// Identical to WGSL_CAUSAL_CONV1D but applies output = silu(sum), eliminating a\n// separate SiLU dispatch + a full read/write of the conv output (Qwen3.5 linear\n// attention: F.silu(conv1d(x))).\n\nstruct Params {\n seq_len: u32,\n channels: u32,\n kernel_size: u32,\n _pad: u32,\n}\n\n@group(0) @binding(0) var<storage, read> input: array<f32>;\n@group(0) @binding(1) var<storage, read> weight: array<f32>;\n@group(0) @binding(2) var<storage, read> conv_state: array<f32>;\n@group(0) @binding(3) var<storage, read_write> output: array<f32>;\n@group(0) @binding(4) var<storage, read> params: Params;\n\n@compute @workgroup_size(256)\nfn main(@builtin(global_invocation_id) gid: vec3u) {\n let idx = gid.x;\n let total = params.seq_len * params.channels;\n if (idx >= total) { return; }\n\n let t = idx / params.channels;\n let c = idx % params.channels;\n let state_size = params.kernel_size - 1u;\n\n var sum: f32 = 0.0;\n for (var k: u32 = 0u; k < params.kernel_size; k = k + 1u) {\n let src_t = i32(t) - i32(k);\n var in_val: f32 = 0.0;\n if (src_t >= 0) {\n in_val = input[u32(src_t) * params.channels + c];\n } else {\n let state_t = i32(state_size) + src_t;\n if (state_t >= 0) {\n in_val = conv_state[u32(state_t) * params.channels + c];\n }\n }\n let w_val = weight[c * params.kernel_size + (params.kernel_size - 1u - k)];\n sum += in_val * w_val;\n }\n\n // Fused SiLU: silu(x) = x / (1 + exp(-x))\n output[t * params.channels + c] = sum / (1.0 + exp(max(-sum, -80.0)));\n}\n`;\n\nconst WGSL_CAUSAL_CONV1D_GATED = `\\\n// Causal 1D depthwise convolution with a fused multiplicative output gate:\n// output = gate * conv(input)\n// LFM2 short-conv: y = C * depthwise_conv(Bx). Fusing the C-gate into the conv\n// eliminates a separate Mul dispatch + a conv_dim-wide read/write per layer.\n\nstruct Params {\n seq_len: u32,\n channels: u32,\n kernel_size: u32,\n _pad: u32,\n}\n\n@group(0) @binding(0) var<storage, read> input: array<f32>;\n@group(0) @binding(1) var<storage, read> weight: array<f32>;\n@group(0) @binding(2) var<storage, read> conv_state: array<f32>;\n@group(0) @binding(3) var<storage, read> gate: array<f32>;\n@group(0) @binding(4) var<storage, read_write> output: array<f32>;\n@group(0) @binding(5) var<storage, read> params: Params;\n\n@compute @workgroup_size(256)\nfn main(@builtin(global_invocation_id) gid: vec3u) {\n let idx = gid.x;\n let total = params.seq_len * params.channels;\n if (idx >= total) { return; }\n\n let t = idx / params.channels;\n let c = idx % params.channels;\n let state_size = params.kernel_size - 1u;\n\n var sum: f32 = 0.0;\n for (var k: u32 = 0u; k < params.kernel_size; k = k + 1u) {\n let src_t = i32(t) - i32(k);\n var in_val: f32 = 0.0;\n if (src_t >= 0) {\n in_val = input[u32(src_t) * params.channels + c];\n } else {\n let state_t = i32(state_size) + src_t;\n if (state_t >= 0) {\n in_val = conv_state[u32(state_t) * params.channels + c];\n }\n }\n let w_val = weight[c * params.kernel_size + (params.kernel_size - 1u - k)];\n sum += in_val * w_val;\n }\n\n output[t * params.channels + c] = gate[idx] * sum;\n}\n`;\n\nconst WGSL_SIGMOID_GATE = `\\\n// Sigmoid gate: output = x * sigmoid(gate)\n\nstruct Params {\n count: u32,\n}\n\n@group(0) @binding(0) var<storage, read> x: array<f32>;\n@group(0) @binding(1) var<storage, read> gate: array<f32>;\n@group(0) @binding(2) var<storage, read_write> output: array<f32>;\n@group(0) @binding(3) var<storage, read> params: Params;\n\n@compute @workgroup_size(256)\nfn main(@builtin(global_invocation_id) gid: vec3u) {\n let idx = gid.x;\n if (idx >= params.count) { return; }\n\n let g = gate[idx];\n let sig = 1.0 / (1.0 + exp(max(-g, -80.0)));\n output[idx] = x[idx] * sig;\n}\n`;\n\nconst WGSL_MAMBA_SSM = `\\\n// Gated Delta Net recurrence (Qwen3.5 linear attention).\n//\n// For each head, maintains state [key_dim, val_dim] across timesteps.\n// Recurrence per timestep:\n// g = -exp(A_log) * softplus(a + dt_bias) (log-space decay)\n// decay = exp(g) (actual decay in (0,1))\n// state *= decay (decay state)\n// q_norm, k_norm = L2_normalize(q), L2_normalize(k)\n// kv_mem = state^T @ k_norm (retrieve from memory)\n// delta = (v - kv_mem) * sigmoid(b) (delta rule)\n// state += k_norm @ delta^T (rank-1 update)\n// output = state^T @ q_norm / sqrt(key_dim) (query output)\n\nstruct Params {\n T: u32,\n num_heads: u32,\n key_dim: u32,\n val_dim: u32,\n qkv_dim: u32,\n _pad1: u32,\n _pad2: u32,\n _pad3: u32,\n}\n\n@group(0) @binding(0) var<storage, read> qkv_conv: array<f32>;\n@group(0) @binding(1) var<storage, read> a_input: array<f32>;\n@group(0) @binding(2) var<storage, read> b_input: array<f32>;\n@group(0) @binding(3) var<storage, read> A_log: array<f32>;\n@group(0) @binding(4) var<storage, read> dt_bias: array<f32>;\n@group(0) @binding(5) var<storage, read_write> ssm_state: array<f32>;\n@group(0) @binding(6) var<storage, read_write> output: array<f32>;\n@group(0) @binding(7) var<storage, read> params: Params;\n\n// Shared memory for Q and K vectors (L2-normalized) and reduction\nvar<workgroup> shared_q: array<f32, 128>;\nvar<workgroup> shared_k: array<f32, 128>;\nvar<workgroup> shared_norm: array<f32, 128>;\n\n@compute @workgroup_size(128)\nfn main(\n @builtin(local_invocation_id) lid: vec3u,\n @builtin(workgroup_id) wid: vec3u,\n) {\n let h = wid.x; // uniform within workgroup\n if (h >= params.num_heads) { return; }\n\n let d = lid.x; // 0..127, all threads participate in barriers\n\n let A_val = -exp(A_log[h]);\n let state_base = h * params.key_dim * params.val_dim;\n let scale = 1.0 / sqrt(f32(params.key_dim));\n\n let q_off = h * params.key_dim;\n let k_off = params.num_heads * params.key_dim + h * params.key_dim;\n let v_off = 2u * params.num_heads * params.key_dim + h * params.val_dim;\n let out_stride = params.num_heads * params.val_dim;\n\n for (var t: u32 = 0u; t < params.T; t = t + 1u) {\n let qkv_base = t * params.qkv_dim;\n\n // ── Load Q and K into shared memory ──\n shared_q[d] = qkv_conv[qkv_base + q_off + d];\n shared_k[d] = qkv_conv[qkv_base + k_off + d];\n workgroupBarrier();\n\n // ── L2-normalize Q (parallel reduction) ──\n shared_norm[d] = shared_q[d] * shared_q[d];\n workgroupBarrier();\n var stride: u32 = 64u;\n while (stride > 0u) {\n if (d < stride) { shared_norm[d] += shared_norm[d + stride]; }\n workgroupBarrier();\n stride /= 2u;\n }\n let q_inv_norm = 1.0 / sqrt(shared_norm[0] + 1e-12);\n shared_q[d] *= q_inv_norm;\n\n // ── L2-normalize K (parallel reduction) ──\n shared_norm[d] = shared_k[d] * shared_k[d];\n workgroupBarrier();\n stride = 64u;\n while (stride > 0u) {\n if (d < stride) { shared_norm[d] += shared_norm[d + stride]; }\n workgroupBarrier();\n stride /= 2u;\n }\n let k_inv_norm = 1.0 / sqrt(shared_norm[0] + 1e-12);\n shared_k[d] *= k_inv_norm;\n workgroupBarrier();\n\n // ── Compute decay and beta ──\n let a_val = a_input[t * params.num_heads + h];\n let b_val = b_input[t * params.num_heads + h];\n let dt_in = a_val + dt_bias[h];\n let sp = log(1.0 + exp(max(dt_in, -80.0)));\n let g = A_val * sp;\n let decay = exp(max(g, -80.0));\n let beta = 1.0 / (1.0 + exp(max(-b_val, -80.0)));\n\n // ── 1+2. Retrieve from memory (decay is a per-step scalar, so it factors\n // out of the dot product): kv_mem[d] = decay * Σ_k state[k,d] * k_norm[k] ──\n let v_val = qkv_conv[qkv_base + v_off + d];\n var kv_dot: f32 = 0.0;\n for (var k: u32 = 0u; k < params.key_dim; k = k + 1u) {\n kv_dot += ssm_state[state_base + k * params.val_dim + d] * shared_k[k];\n }\n let kv_mem = kv_dot * decay;\n\n // ── 3. Delta rule: delta[d] = (v[d] - kv_mem[d]) * beta ──\n let delta_val = (v_val - kv_mem) * beta;\n\n // ── 4+5. Fused decay + rank-1 update + output in ONE pass over state\n // (one read + one write instead of three reads + two writes):\n // state'[k,d] = state[k,d] * decay + k_norm[k] * delta[d]\n // out[d] = Σ_k state'[k,d] * q_norm[k] * scale ──\n var out_val: f32 = 0.0;\n for (var k: u32 = 0u; k < params.key_dim; k = k + 1u) {\n let idx = state_base + k * params.val_dim + d;\n let s = ssm_state[idx] * decay + shared_k[k] * delta_val;\n ssm_state[idx] = s;\n out_val += s * shared_q[k];\n }\n output[t * out_stride + h * params.val_dim + d] = out_val * scale;\n\n workgroupBarrier(); // sync before next timestep loads shared_q/k\n }\n}\n`;\n\nconst WGSL_MAMBA_SSM_F16 = `\\\n// Gated Delta Net recurrence with f16 SSM state storage (Dawn only).\n// Compute stays f32; only the persistent state reads/writes are half-precision,\n// halving the dominant state bandwidth. Buffer is allocated as f32-sized and\n// reinterpreted (extra capacity unused).\n//\n// For each head, maintains state [key_dim, val_dim] across timesteps.\n// Recurrence per timestep:\n// g = -exp(A_log) * softplus(a + dt_bias) (log-space decay)\n// decay = exp(g) (actual decay in (0,1))\n// state *= decay (decay state)\n// q_norm, k_norm = L2_normalize(q), L2_normalize(k)\n// kv_mem = state^T @ k_norm (retrieve from memory)\n// delta = (v - kv_mem) * sigmoid(b) (delta rule)\n// state += k_norm @ delta^T (rank-1 update)\n// output = state^T @ q_norm / sqrt(key_dim) (query output)\n\nstruct Params {\n T: u32,\n num_heads: u32,\n key_dim: u32,\n val_dim: u32,\n qkv_dim: u32,\n _pad1: u32,\n _pad2: u32,\n _pad3: u32,\n}\n\n@group(0) @binding(0) var<storage, read> qkv_conv: array<f32>;\n@group(0) @binding(1) var<storage, read> a_input: array<f32>;\n@group(0) @binding(2) var<storage, read> b_input: array<f32>;\n@group(0) @binding(3) var<storage, read> A_log: array<f32>;\n@group(0) @binding(4) var<storage, read> dt_bias: array<f32>;\n@group(0) @binding(5) var<storage, read_write> ssm_state: array<f16>;\n@group(0) @binding(6) var<storage, read_write> output: array<f32>;\n@group(0) @binding(7) var<storage, read> params: Params;\n\n// Shared memory for Q and K vectors (L2-normalized) and reduction\nvar<workgroup> shared_q: array<f32, 128>;\nvar<workgroup> shared_k: array<f32, 128>;\nvar<workgroup> shared_norm: array<f32, 128>;\n\n@compute @workgroup_size(128)\nfn main(\n @builtin(local_invocation_id) lid: vec3u,\n @builtin(workgroup_id) wid: vec3u,\n) {\n let h = wid.x; // uniform within workgroup\n if (h >= params.num_heads) { return; }\n\n let d = lid.x; // 0..127, all threads participate in barriers\n\n let A_val = -exp(A_log[h]);\n let state_base = h * params.key_dim * params.val_dim;\n let scale = 1.0 / sqrt(f32(params.key_dim));\n\n let q_off = h * params.key_dim;\n let k_off = params.num_heads * params.key_dim + h * params.key_dim;\n let v_off = 2u * params.num_heads * params.key_dim + h * params.val_dim;\n let out_stride = params.num_heads * params.val_dim;\n\n for (var t: u32 = 0u; t < params.T; t = t + 1u) {\n let qkv_base = t * params.qkv_dim;\n\n // ── Load Q and K into shared memory ──\n shared_q[d] = qkv_conv[qkv_base + q_off + d];\n shared_k[d] = qkv_conv[qkv_base + k_off + d];\n workgroupBarrier();\n\n // ── L2-normalize Q (parallel reduction) ──\n shared_norm[d] = shared_q[d] * shared_q[d];\n workgroupBarrier();\n var stride: u32 = 64u;\n while (stride > 0u) {\n if (d < stride) { shared_norm[d] += shared_norm[d + stride]; }\n workgroupBarrier();\n stride /= 2u;\n }\n let q_inv_norm = 1.0 / sqrt(shared_norm[0] + 1e-12);\n shared_q[d] *= q_inv_norm;\n\n // ── L2-normalize K (parallel reduction) ──\n shared_norm[d] = shared_k[d] * shared_k[d];\n workgroupBarrier();\n stride = 64u;\n while (stride > 0u) {\n if (d < stride) { shared_norm[d] += shared_norm[d + stride]; }\n workgroupBarrier();\n stride /= 2u;\n }\n let k_inv_norm = 1.0 / sqrt(shared_norm[0] + 1e-12);\n shared_k[d] *= k_inv_norm;\n workgroupBarrier();\n\n // ── Compute decay and beta ──\n let a_val = a_input[t * params.num_heads + h];\n let b_val = b_input[t * params.num_heads + h];\n let dt_in = a_val + dt_bias[h];\n let sp = log(1.0 + exp(max(dt_in, -80.0)));\n let g = A_val * sp;\n let decay = exp(max(g, -80.0));\n let beta = 1.0 / (1.0 + exp(max(-b_val, -80.0)));\n\n // ── 1+2. Retrieve from memory (decay is a per-step scalar, so it factors\n // out of the dot product): kv_mem[d] = decay * Σ_k state[k,d] * k_norm[k] ──\n let v_val = qkv_conv[qkv_base + v_off + d];\n var kv_dot: f32 = 0.0;\n for (var k: u32 = 0u; k < params.key_dim; k = k + 1u) {\n kv_dot += f32(ssm_state[state_base + k * params.val_dim + d]) * shared_k[k];\n }\n let kv_mem = kv_dot * decay;\n\n // ── 3. Delta rule: delta[d] = (v[d] - kv_mem[d]) * beta ──\n let delta_val = (v_val - kv_mem) * beta;\n\n // ── 4+5. Fused decay + rank-1 update + output in ONE pass over state\n // (one read + one write instead of three reads + two writes):\n // state'[k,d] = state[k,d] * decay + k_norm[k] * delta[d]\n // out[d] = Σ_k state'[k,d] * q_norm[k] * scale ──\n var out_val: f32 = 0.0;\n for (var k: u32 = 0u; k < params.key_dim; k = k + 1u) {\n let idx = state_base + k * params.val_dim + d;\n let s = f32(ssm_state[idx]) * decay + shared_k[k] * delta_val;\n ssm_state[idx] = f16(s);\n out_val += s * shared_q[k];\n }\n output[t * out_stride + h * params.val_dim + d] = out_val * scale;\n\n workgroupBarrier(); // sync before next timestep loads shared_q/k\n }\n}\n`;\n\nconst WGSL_KV_CACHE_APPEND = `\\\n// Copy T new K or V vectors into cache at position offset.\n// src: [T, width], dst: [max_seq_len, width]\n\nstruct Params {\n count: u32, // T * width (total elements to copy)\n dst_offset: u32, // seqPos * width (element offset into cache)\n}\n\n@group(0) @binding(0) var<storage, read> src: array<f32>;\n@group(0) @binding(1) var<storage, read_write> dst: array<f32>;\n@group(0) @binding(2) var<storage, read> params: Params;\n\n@compute @workgroup_size(256)\nfn main(@builtin(global_invocation_id) gid: vec3u) {\n let idx = gid.x;\n if (idx >= params.count) { return; }\n dst[params.dst_offset + idx] = src[idx];\n}\n`;\n\nconst WGSL_DUAL_KV_CACHE_APPEND = `\\\n// Append both K and V into their f32 caches in one dispatch.\n// Both share the same width, T, and dst_offset (same kv_dim, same seqPos),\n// so a single param set + a single grid over 'count' elements drives both\n// copies. Pure memcpy — numerically identical to two separate appends.\n\nstruct Params {\n count: u32, // T * width (per-cache element count)\n dst_offset: u32, // seqPos * width (element offset into each cache)\n}\n\n@group(0) @binding(0) var<storage, read> src_k: array<f32>;\n@group(0) @binding(1) var<storage, read> src_v: array<f32>;\n@group(0) @binding(2) var<storage, read_write> dst_k: array<f32>;\n@group(0) @binding(3) var<storage, read_write> dst_v: array<f32>;\n@group(0) @binding(4) var<storage, read> params: Params;\n\n@compute @workgroup_size(256)\nfn main(@builtin(global_invocation_id) gid: vec3u) {\n let idx = gid.x;\n if (idx >= params.count) { return; }\n dst_k[params.dst_offset + idx] = src_k[idx];\n dst_v[params.dst_offset + idx] = src_v[idx];\n}\n`;\n\nconst WGSL_DUAL_KV_CACHE_APPEND_F16 = `\\\n// Append both K and V into their native-f16 caches in one dispatch.\n// Same per-element conversion as the single f16 append, doubled.\n\nstruct Params {\n count: u32,\n dst_offset: u32,\n}\n\n@group(0) @binding(0) var<storage, read> src_k: array<f32>;\n@group(0) @binding(1) var<storage, read> src_v: array<f32>;\n@group(0) @binding(2) var<storage, read_write> dst_k: array<f16>;\n@group(0) @binding(3) var<storage, read_write> dst_v: array<f16>;\n@group(0) @binding(4) var<storage, read> params: Params;\n\n@compute @workgroup_size(256)\nfn main(@builtin(global_invocation_id) gid: vec3u) {\n let idx = gid.x;\n if (idx >= params.count) { return; }\n dst_k[params.dst_offset + idx] = f16(src_k[idx]);\n dst_v[params.dst_offset + idx] = f16(src_v[idx]);\n}\n`;\n\nconst WGSL_DUAL_KV_CACHE_APPEND_PACKED_F16 = `\\\n// Append both K and V into packed-f16 caches in one dispatch (Safari-safe).\n// Each u32 packs 2 f16 via pack2x16float; one thread handles one f16 pair per\n// cache. Same math as the single packed append, doubled — no 'enable f16'.\n\nstruct Params {\n count: u32, // T * width (per-cache f32 element count)\n dst_offset: u32, // seqPos * width (f32-element offset)\n}\n\n@group(0) @binding(0) var<storage, read> src_k: array<f32>;\n@group(0) @binding(1) var<storage, read> src_v: array<f32>;\n@group(0) @binding(2) var<storage, read_write> dst_k: array<u32>;\n@group(0) @binding(3) var<storage, read_write> dst_v: array<u32>;\n@group(0) @binding(4) var<storage, read> params: Params;\n\n@compute @workgroup_size(256)\nfn main(@builtin(global_invocation_id) gid: vec3u) {\n let pair_idx = gid.x;\n let num_pairs = params.count >> 1u;\n if (pair_idx >= num_pairs) { return; }\n\n let src_base = pair_idx * 2u;\n let dst_idx = (params.dst_offset >> 1u) + pair_idx;\n\n dst_k[dst_idx] = pack2x16float(vec2f(src_k[src_base], src_k[src_base + 1u]));\n dst_v[dst_idx] = pack2x16float(vec2f(src_v[src_base], src_v[src_base + 1u]));\n}\n`;\n\nconst WGSL_KV_CACHE_APPEND_F16 = `\\\n// Copy T new K or V vectors into f16 cache at position offset.\n// src: [T, width] (f32), dst: [max_seq_len, width] (f16)\n// Converts f32 activations to f16 on write for reduced memory traffic.\n\nstruct Params {\n count: u32, // T * width (total elements to copy)\n dst_offset: u32, // seqPos * width (element offset into cache)\n}\n\n@group(0) @binding(0) var<storage, read> src: array<f32>;\n@group(0) @binding(1) var<storage, read_write> dst: array<f16>;\n@group(0) @binding(2) var<storage, read> params: Params;\n\n@compute @workgroup_size(256)\nfn main(@builtin(global_invocation_id) gid: vec3u) {\n let idx = gid.x;\n if (idx >= params.count) { return; }\n dst[params.dst_offset + idx] = f16(src[idx]);\n}\n`;\n\nconst WGSL_KV_CACHE_APPEND_PACKED_F16 = `\\\n// Copy T new K or V vectors into packed f16 cache (Safari-safe).\n// src: [T, width] (f32), dst: [max_seq_len, width/2] (u32, packed f16 pairs)\n// Each u32 holds 2 f16 values via pack2x16float. Does NOT require 'enable f16'.\n// Params use f32-element-level addressing; kernel converts to u32 pair indices.\n\nstruct Params {\n count: u32, // T * width (total f32 elements)\n dst_offset: u32, // seqPos * width (f32-element offset into cache)\n}\n\n@group(0) @binding(0) var<storage, read> src: array<f32>;\n@group(0) @binding(1) var<storage, read_write> dst: array<u32>;\n@group(0) @binding(2) var<storage, read> params: Params;\n\n@compute @workgroup_size(256)\nfn main(@builtin(global_invocation_id) gid: vec3u) {\n let pair_idx = gid.x;\n let num_pairs = params.count >> 1u;\n if (pair_idx >= num_pairs) { return; }\n\n let src_base = pair_idx * 2u;\n let dst_idx = (params.dst_offset >> 1u) + pair_idx;\n\n dst[dst_idx] = pack2x16float(vec2f(src[src_base], src[src_base + 1u]));\n}\n`;\n\nconst WGSL_ATTENTION_F16 = `\\\n// Tiled online-softmax attention with f16 KV cache (Flash-Attention style).\n//\n// Same algorithm as WGSL_ATTENTION but K/V are stored as f16 in global memory.\n// Shared memory stays f32 for computation accuracy — only global reads change.\n// Halves KV cache memory traffic for ~1.3-1.8x decode speedup.\n\nconst TILE_S: u32 = 16u;\nconst WG: u32 = 256u;\nconst DOT_THREADS: u32 = 16u;\n\nstruct Params {\n T: u32,\n S: u32,\n num_q_heads: u32,\n num_kv_heads: u32,\n head_dim: u32,\n position_offset: u32,\n // Softmax QK temperature (f32). Default 1/sqrt(head_dim); Gemma 4 sets 1.0.\n attn_scale: f32,\n}\n\n@group(0) @binding(0) var<storage, read> Q: array<f32>;\n@group(0) @binding(1) var<storage, read> K: array<f16>;\n@group(0) @binding(2) var<storage, read> V: array<f16>;\n@group(0) @binding(3) var<storage, read_write> output: array<f32>;\n@group(0) @binding(4) var<storage, read> params: Params;\n\nvar<workgroup> smem: array<f32, 4096>;\n\n@compute @workgroup_size(256)\nfn main(\n @builtin(local_invocation_id) lid: vec3u,\n @builtin(workgroup_id) wid: vec3u,\n) {\n let q_pos = wid.x;\n let q_head = wid.y;\n\n if (q_pos >= params.T || q_head >= params.num_q_heads) { return; }\n\n let tid = lid.x;\n let heads_per_kv = params.num_q_heads / params.num_kv_heads;\n let kv_head = q_head / heads_per_kv;\n let scale = params.attn_scale;\n\n let q_stride = params.num_q_heads * params.head_dim;\n let kv_stride = params.num_kv_heads * params.head_dim;\n let q_base = q_pos * q_stride + q_head * params.head_dim;\n let kv_head_offset = kv_head * params.head_dim;\n let causal_limit = q_pos + params.position_offset + 1u;\n let hd = params.head_dim;\n let S_eff = min(params.S, causal_limit);\n\n var running_max: f32 = -1e30;\n var running_sum: f32 = 0.0;\n // Per-thread output accumulators; out_acc2 covers dim tid+WG so head_dim up to\n // 2*WG (512) works with WG=256 threads. hd<=256 → out_acc2 unused (identical).\n var out_acc: f32 = 0.0;\n var out_acc2: f32 = 0.0;\n let d2 = tid + WG;\n\n // Tile capped so a K/V tile (tile_cap*hd f32) fits the 4096-f32 smem.\n let tile_cap = min(TILE_S, 4096u / hd);\n let num_tiles = (S_eff + tile_cap - 1u) / tile_cap;\n\n for (var tile: u32 = 0u; tile < num_tiles; tile += 1u) {\n let tile_start = tile * tile_cap;\n let tile_end = min(tile_start + tile_cap, S_eff);\n let tile_len = tile_end - tile_start;\n\n // ── Step 1: Cooperative K tile load (f16 → f32 conversion) ──\n let total_k_elems = tile_len * hd;\n var load_idx = tid;\n while (load_idx < total_k_elems) {\n let s_local = load_idx / hd;\n let d = load_idx % hd;\n let s_global = tile_start + s_local;\n let k_addr = s_global * kv_stride + kv_head_offset + d;\n smem[s_local * hd + d] = f32(K[k_addr]);\n load_idx += WG;\n }\n workgroupBarrier();\n\n // ── Step 2: Parallel Q·K dot products ──\n let pos_in_tile = tid / DOT_THREADS;\n let dot_tid = tid % DOT_THREADS;\n\n var my_partial: f32 = 0.0;\n if (pos_in_tile < tile_len) {\n let dims_per_thread = hd / DOT_THREADS;\n let d_start = dot_tid * dims_per_thread;\n let d_end = d_start + dims_per_thread;\n let kv_row_base = pos_in_tile * hd;\n\n var d = d_start;\n while (d + 3u < d_end) {\n let q_v = vec4f(Q[q_base + d], Q[q_base + d + 1u],\n Q[q_base + d + 2u], Q[q_base + d + 3u]);\n let k_v = vec4f(smem[kv_row_base + d], smem[kv_row_base + d + 1u],\n smem[kv_row_base + d + 2u], smem[kv_row_base + d + 3u]);\n my_partial += dot(q_v, k_v);\n d += 4u;\n }\n while (d < d_end) {\n my_partial += Q[q_base + d] * smem[kv_row_base + d];\n d += 1u;\n }\n }\n\n workgroupBarrier();\n smem[tid] = my_partial;\n workgroupBarrier();\n\n // Two-phase: leader 0 reads smem[1..15], which are exactly the slots\n // leaders 1..15 write — reduce into a local first, barrier (in uniform\n // control flow), then write. Read+write in one block is a data race.\n var group_total: f32 = 0.0;\n if (dot_tid == 0u && pos_in_tile < tile_len) {\n group_total = smem[tid];\n for (var i: u32 = 1u; i < DOT_THREADS; i += 1u) {\n group_total += smem[tid + i];\n }\n }\n workgroupBarrier();\n if (dot_tid == 0u && pos_in_tile < tile_len) {\n smem[pos_in_tile] = group_total * scale;\n }\n workgroupBarrier();\n\n // ── Step 3: Online softmax ──\n if (tid < TILE_S) {\n if (tid < tile_len) {\n smem[TILE_S + tid] = smem[tid];\n } else {\n smem[TILE_S + tid] = -1e30;\n }\n }\n workgroupBarrier();\n if (tid < 8u) { smem[TILE_S + tid] = max(smem[TILE_S + tid], smem[TILE_S + tid + 8u]); }\n workgroupBarrier();\n if (tid < 4u) { smem[TILE_S + tid] = max(smem[TILE_S + tid], smem[TILE_S + tid + 4u]); }\n workgroupBarrier();\n if (tid < 2u) { smem[TILE_S + tid] = max(smem[TILE_S + tid], smem[TILE_S + tid + 2u]); }\n workgroupBarrier();\n if (tid < 1u) { smem[TILE_S + tid] = max(smem[TILE_S + tid], smem[TILE_S + tid + 1u]); }\n workgroupBarrier();\n let tile_max = smem[TILE_S];\n\n let new_max = max(running_max, tile_max);\n let old_correction = exp(max(running_max - new_max, -80.0));\n\n if (tid < tile_len) {\n smem[tid] = exp(max(smem[tid] - new_max, -80.0));\n }\n workgroupBarrier();\n\n if (tid < TILE_S) {\n if (tid < tile_len) {\n smem[TILE_S + tid] = smem[tid];\n } else {\n smem[TILE_S + tid] = 0.0;\n }\n }\n workgroupBarrier();\n if (tid < 8u) { smem[TILE_S + tid] += smem[TILE_S + tid + 8u]; }\n workgroupBarrier();\n if (tid < 4u) { smem[TILE_S + tid] += smem[TILE_S + tid + 4u]; }\n workgroupBarrier();\n if (tid < 2u) { smem[TILE_S + tid] += smem[TILE_S + tid + 2u]; }\n workgroupBarrier();\n if (tid < 1u) { smem[TILE_S + tid] += smem[TILE_S + tid + 1u]; }\n workgroupBarrier();\n let tile_sum = smem[TILE_S];\n\n // ── Save scores to registers before V overwrites smem ──\n var w0: f32 = 0.0; var w1: f32 = 0.0; var w2: f32 = 0.0; var w3: f32 = 0.0;\n var w4: f32 = 0.0; var w5: f32 = 0.0; var w6: f32 = 0.0; var w7: f32 = 0.0;\n var w8: f32 = 0.0; var w9: f32 = 0.0; var w10: f32 = 0.0; var w11: f32 = 0.0;\n var w12: f32 = 0.0; var w13: f32 = 0.0; var w14: f32 = 0.0; var w15: f32 = 0.0;\n if (tid < hd) {\n if (0u < tile_len) { w0 = smem[0]; }\n if (1u < tile_len) { w1 = smem[1]; }\n if (2u < tile_len) { w2 = smem[2]; }\n if (3u < tile_len) { w3 = smem[3]; }\n if (4u < tile_len) { w4 = smem[4]; }\n if (5u < tile_len) { w5 = smem[5]; }\n if (6u < tile_len) { w6 = smem[6]; }\n if (7u < tile_len) { w7 = smem[7]; }\n if (8u < tile_len) { w8 = smem[8]; }\n if (9u < tile_len) { w9 = smem[9]; }\n if (10u < tile_len) { w10 = smem[10]; }\n if (11u < tile_len) { w11 = smem[11]; }\n if (12u < tile_len) { w12 = smem[12]; }\n if (13u < tile_len) { w13 = smem[13]; }\n if (14u < tile_len) { w14 = smem[14]; }\n if (15u < tile_len) { w15 = smem[15]; }\n }\n workgroupBarrier();\n\n // ── Step 4: Cooperative V tile load (f16 → f32 conversion, full smem reuse) ──\n let total_v_elems = tile_len * hd;\n load_idx = tid;\n while (load_idx < total_v_elems) {\n let s_local = load_idx / hd;\n let d = load_idx % hd;\n let s_global = tile_start + s_local;\n let v_addr = s_global * kv_stride + kv_head_offset + d;\n smem[s_local * hd + d] = f32(V[v_addr]);\n load_idx += WG;\n }\n workgroupBarrier();\n\n // ── Step 5: Parallel V accumulation ──\n if (tid < hd) {\n out_acc = out_acc * old_correction;\n if (0u < tile_len) { out_acc += w0 * smem[0u * hd + tid]; }\n if (1u < tile_len) { out_acc += w1 * smem[1u * hd + tid]; }\n if (2u < tile_len) { out_acc += w2 * smem[2u * hd + tid]; }\n if (3u < tile_len) { out_acc += w3 * smem[3u * hd + tid]; }\n if (4u < tile_len) { out_acc += w4 * smem[4u * hd + tid]; }\n if (5u < tile_len) { out_acc += w5 * smem[5u * hd + tid]; }\n if (6u < tile_len) { out_acc += w6 * smem[6u * hd + tid]; }\n if (7u < tile_len) { out_acc += w7 * smem[7u * hd + tid]; }\n if (8u < tile_len) { out_acc += w8 * smem[8u * hd + tid]; }\n if (9u < tile_len) { out_acc += w9 * smem[9u * hd + tid]; }\n if (10u < tile_len) { out_acc += w10 * smem[10u * hd + tid]; }\n if (11u < tile_len) { out_acc += w11 * smem[11u * hd + tid]; }\n if (12u < tile_len) { out_acc += w12 * smem[12u * hd + tid]; }\n if (13u < tile_len) { out_acc += w13 * smem[13u * hd + tid]; }\n if (14u < tile_len) { out_acc += w14 * smem[14u * hd + tid]; }\n if (15u < tile_len) { out_acc += w15 * smem[15u * hd + tid]; }\n }\n if (d2 < hd) {\n out_acc2 = out_acc2 * old_correction;\n if (0u < tile_len) { out_acc2 += w0 * smem[0u * hd + d2]; }\n if (1u < tile_len) { out_acc2 += w1 * smem[1u * hd + d2]; }\n if (2u < tile_len) { out_acc2 += w2 * smem[2u * hd + d2]; }\n if (3u < tile_len) { out_acc2 += w3 * smem[3u * hd + d2]; }\n if (4u < tile_len) { out_acc2 += w4 * smem[4u * hd + d2]; }\n if (5u < tile_len) { out_acc2 += w5 * smem[5u * hd + d2]; }\n if (6u < tile_len) { out_acc2 += w6 * smem[6u * hd + d2]; }\n if (7u < tile_len) { out_acc2 += w7 * smem[7u * hd + d2]; }\n if (8u < tile_len) { out_acc2 += w8 * smem[8u * hd + d2]; }\n if (9u < tile_len) { out_acc2 += w9 * smem[9u * hd + d2]; }\n if (10u < tile_len) { out_acc2 += w10 * smem[10u * hd + d2]; }\n if (11u < tile_len) { out_acc2 += w11 * smem[11u * hd + d2]; }\n if (12u < tile_len) { out_acc2 += w12 * smem[12u * hd + d2]; }\n if (13u < tile_len) { out_acc2 += w13 * smem[13u * hd + d2]; }\n if (14u < tile_len) { out_acc2 += w14 * smem[14u * hd + d2]; }\n if (15u < tile_len) { out_acc2 += w15 * smem[15u * hd + d2]; }\n }\n\n running_sum = running_sum * old_correction + tile_sum;\n running_max = new_max;\n workgroupBarrier();\n }\n\n // ── Write normalized output ──\n var inv_sum: f32 = 0.0;\n if (running_sum > 0.0) { inv_sum = 1.0 / running_sum; }\n let out_base = q_pos * q_stride + q_head * hd;\n if (tid < hd) {\n output[out_base + tid] = out_acc * inv_sum;\n }\n if (d2 < hd) {\n output[out_base + d2] = out_acc2 * inv_sum;\n }\n}\n`;\n\nconst WGSL_ATTENTION_PACKED_F16 = `\\\n// Tiled online-softmax attention with packed f16 KV cache (Safari-safe).\n//\n// K/V stored as array<u32> — each u32 holds 2 f16 values via pack2x16float.\n// Does NOT require 'enable f16' — only uses u32 + f32 + unpack2x16float.\n// Same algorithm as native f16 attention, same memory savings.\n//\n// Safari/Metal compatibility:\n// - Uses if/else instead of select() (Safari has select() bugs)\n// - Clamps exp() args to -80 (Metal can return NaN for exp(-1e30))\n// - No f16 types avoids WebKit WGSL compiler miscompilation\n//\n// Shared memory: 4096 f32 = 16384 bytes = exactly 16 KB (minimum WebGPU guarantee)\n\nconst TILE_S: u32 = 16u;\nconst WG: u32 = 256u;\nconst DOT_THREADS: u32 = 16u;\n\nstruct Params {\n T: u32,\n S: u32,\n num_q_heads: u32,\n num_kv_heads: u32,\n head_dim: u32,\n position_offset: u32,\n // Softmax QK temperature (f32). Default 1/sqrt(head_dim); Gemma 4 sets 1.0.\n attn_scale: f32,\n}\n\n@group(0) @binding(0) var<storage, read> Q: array<f32>;\n@group(0) @binding(1) var<storage, read> K: array<u32>;\n@group(0) @binding(2) var<storage, read> V: array<u32>;\n@group(0) @binding(3) var<storage, read_write> output: array<f32>;\n@group(0) @binding(4) var<storage, read> params: Params;\n\nvar<workgroup> smem: array<f32, 4096>;\n\n// Load a single f32 from packed u32 K cache at element-level index\nfn load_k(elem_idx: u32) -> f32 {\n let pair = unpack2x16float(K[elem_idx >> 1u]);\n if ((elem_idx & 1u) == 0u) {\n return pair.x;\n } else {\n return pair.y;\n }\n}\n\n// Load a single f32 from packed u32 V cache at element-level index\nfn load_v(elem_idx: u32) -> f32 {\n let pair = unpack2x16float(V[elem_idx >> 1u]);\n if ((elem_idx & 1u) == 0u) {\n return pair.x;\n } else {\n return pair.y;\n }\n}\n\n@compute @workgroup_size(256)\nfn main(\n @builtin(local_invocation_id) lid: vec3u,\n @builtin(workgroup_id) wid: vec3u,\n) {\n let q_pos = wid.x;\n let q_head = wid.y;\n\n if (q_pos >= params.T || q_head >= params.num_q_heads) { return; }\n\n let tid = lid.x;\n let heads_per_kv = params.num_q_heads / params.num_kv_heads;\n let kv_head = q_head / heads_per_kv;\n let scale = params.attn_scale;\n\n let q_stride = params.num_q_heads * params.head_dim;\n let kv_stride = params.num_kv_heads * params.head_dim;\n let q_base = q_pos * q_stride + q_head * params.head_dim;\n let kv_head_offset = kv_head * params.head_dim;\n let causal_limit = q_pos + params.position_offset + 1u;\n let hd = params.head_dim;\n let S_eff = min(params.S, causal_limit);\n\n var running_max: f32 = -1e30;\n var running_sum: f32 = 0.0;\n // Per-thread output accumulators; out_acc2 covers dim tid+WG so head_dim up to\n // 2*WG (512) works with WG=256 threads. hd<=256 → out_acc2 unused (identical).\n var out_acc: f32 = 0.0;\n var out_acc2: f32 = 0.0;\n let d2 = tid + WG;\n\n // Tile capped so a K/V tile (tile_cap*hd f32) fits the 4096-f32 smem.\n let tile_cap = min(TILE_S, 4096u / hd);\n let num_tiles = (S_eff + tile_cap - 1u) / tile_cap;\n\n for (var tile: u32 = 0u; tile < num_tiles; tile += 1u) {\n let tile_start = tile * tile_cap;\n let tile_end = min(tile_start + tile_cap, S_eff);\n let tile_len = tile_end - tile_start;\n\n // ── Step 1: Cooperative K tile load (packed u32 → f32 via unpack2x16float) ──\n let total_k_elems = tile_len * hd;\n var load_idx = tid;\n while (load_idx < total_k_elems) {\n let s_local = load_idx / hd;\n let d = load_idx % hd;\n let s_global = tile_start + s_local;\n let k_addr = s_global * kv_stride + kv_head_offset + d;\n smem[s_local * hd + d] = load_k(k_addr);\n load_idx += WG;\n }\n workgroupBarrier();\n\n // ── Step 2: Parallel Q·K dot products ──\n let pos_in_tile = tid / DOT_THREADS;\n let dot_tid = tid % DOT_THREADS;\n\n var my_partial: f32 = 0.0;\n if (pos_in_tile < tile_len) {\n let dims_per_thread = hd / DOT_THREADS;\n let d_start = dot_tid * dims_per_thread;\n let d_end = d_start + dims_per_thread;\n let kv_row_base = pos_in_tile * hd;\n\n var d = d_start;\n while (d + 3u < d_end) {\n let q_v = vec4f(Q[q_base + d], Q[q_base + d + 1u],\n Q[q_base + d + 2u], Q[q_base + d + 3u]);\n let k_v = vec4f(smem[kv_row_base + d], smem[kv_row_base + d + 1u],\n smem[kv_row_base + d + 2u], smem[kv_row_base + d + 3u]);\n my_partial += dot(q_v, k_v);\n d += 4u;\n }\n while (d < d_end) {\n my_partial += Q[q_base + d] * smem[kv_row_base + d];\n d += 1u;\n }\n }\n\n workgroupBarrier();\n smem[tid] = my_partial;\n workgroupBarrier();\n\n // Two-phase: leader 0 reads smem[1..15], which are exactly the slots\n // leaders 1..15 write — reduce into a local first, barrier (in uniform\n // control flow), then write. Read+write in one block is a data race.\n var group_total: f32 = 0.0;\n if (dot_tid == 0u && pos_in_tile < tile_len) {\n group_total = smem[tid];\n for (var i: u32 = 1u; i < DOT_THREADS; i += 1u) {\n group_total += smem[tid + i];\n }\n }\n workgroupBarrier();\n if (dot_tid == 0u && pos_in_tile < tile_len) {\n smem[pos_in_tile] = group_total * scale;\n }\n workgroupBarrier();\n\n // ── Step 3: Online softmax ──\n if (tid < TILE_S) {\n if (tid < tile_len) {\n smem[TILE_S + tid] = smem[tid];\n } else {\n smem[TILE_S + tid] = -1e30;\n }\n }\n workgroupBarrier();\n if (tid < 8u) { smem[TILE_S + tid] = max(smem[TILE_S + tid], smem[TILE_S + tid + 8u]); }\n workgroupBarrier();\n if (tid < 4u) { smem[TILE_S + tid] = max(smem[TILE_S + tid], smem[TILE_S + tid + 4u]); }\n workgroupBarrier();\n if (tid < 2u) { smem[TILE_S + tid] = max(smem[TILE_S + tid], smem[TILE_S + tid + 2u]); }\n workgroupBarrier();\n if (tid < 1u) { smem[TILE_S + tid] = max(smem[TILE_S + tid], smem[TILE_S + tid + 1u]); }\n workgroupBarrier();\n let tile_max = smem[TILE_S];\n\n let new_max = max(running_max, tile_max);\n let old_correction = exp(max(running_max - new_max, -80.0));\n\n if (tid < tile_len) {\n smem[tid] = exp(max(smem[tid] - new_max, -80.0));\n }\n workgroupBarrier();\n\n if (tid < TILE_S) {\n if (tid < tile_len) {\n smem[TILE_S + tid] = smem[tid];\n } else {\n smem[TILE_S + tid] = 0.0;\n }\n }\n workgroupBarrier();\n if (tid < 8u) { smem[TILE_S + tid] += smem[TILE_S + tid + 8u]; }\n workgroupBarrier();\n if (tid < 4u) { smem[TILE_S + tid] += smem[TILE_S + tid + 4u]; }\n workgroupBarrier();\n if (tid < 2u) { smem[TILE_S + tid] += smem[TILE_S + tid + 2u]; }\n workgroupBarrier();\n if (tid < 1u) { smem[TILE_S + tid] += smem[TILE_S + tid + 1u]; }\n workgroupBarrier();\n let tile_sum = smem[TILE_S];\n\n // ── Save scores to registers before V overwrites smem ──\n var w0: f32 = 0.0; var w1: f32 = 0.0; var w2: f32 = 0.0; var w3: f32 = 0.0;\n var w4: f32 = 0.0; var w5: f32 = 0.0; var w6: f32 = 0.0; var w7: f32 = 0.0;\n var w8: f32 = 0.0; var w9: f32 = 0.0; var w10: f32 = 0.0; var w11: f32 = 0.0;\n var w12: f32 = 0.0; var w13: f32 = 0.0; var w14: f32 = 0.0; var w15: f32 = 0.0;\n if (tid < hd) {\n if (0u < tile_len) { w0 = smem[0]; }\n if (1u < tile_len) { w1 = smem[1]; }\n if (2u < tile_len) { w2 = smem[2]; }\n if (3u < tile_len) { w3 = smem[3]; }\n if (4u < tile_len) { w4 = smem[4]; }\n if (5u < tile_len) { w5 = smem[5]; }\n if (6u < tile_len) { w6 = smem[6]; }\n if (7u < tile_len) { w7 = smem[7]; }\n if (8u < tile_len) { w8 = smem[8]; }\n if (9u < tile_len) { w9 = smem[9]; }\n if (10u < tile_len) { w10 = smem[10]; }\n if (11u < tile_len) { w11 = smem[11]; }\n if (12u < tile_len) { w12 = smem[12]; }\n if (13u < tile_len) { w13 = smem[13]; }\n if (14u < tile_len) { w14 = smem[14]; }\n if (15u < tile_len) { w15 = smem[15]; }\n }\n workgroupBarrier();\n\n // ── Step 4: Cooperative V tile load (packed u32 → f32 via unpack2x16float) ──\n let total_v_elems = tile_len * hd;\n load_idx = tid;\n while (load_idx < total_v_elems) {\n let s_local = load_idx / hd;\n let d = load_idx % hd;\n let s_global = tile_start + s_local;\n let v_addr = s_global * kv_stride + kv_head_offset + d;\n smem[s_local * hd + d] = load_v(v_addr);\n load_idx += WG;\n }\n workgroupBarrier();\n\n // ── Step 5: Parallel V accumulation ──\n if (tid < hd) {\n out_acc = out_acc * old_correction;\n if (0u < tile_len) { out_acc += w0 * smem[0u * hd + tid]; }\n if (1u < tile_len) { out_acc += w1 * smem[1u * hd + tid]; }\n if (2u < tile_len) { out_acc += w2 * smem[2u * hd + tid]; }\n if (3u < tile_len) { out_acc += w3 * smem[3u * hd + tid]; }\n if (4u < tile_len) { out_acc += w4 * smem[4u * hd + tid]; }\n if (5u < tile_len) { out_acc += w5 * smem[5u * hd + tid]; }\n if (6u < tile_len) { out_acc += w6 * smem[6u * hd + tid]; }\n if (7u < tile_len) { out_acc += w7 * smem[7u * hd + tid]; }\n if (8u < tile_len) { out_acc += w8 * smem[8u * hd + tid]; }\n if (9u < tile_len) { out_acc += w9 * smem[9u * hd + tid]; }\n if (10u < tile_len) { out_acc += w10 * smem[10u * hd + tid]; }\n if (11u < tile_len) { out_acc += w11 * smem[11u * hd + tid]; }\n if (12u < tile_len) { out_acc += w12 * smem[12u * hd + tid]; }\n if (13u < tile_len) { out_acc += w13 * smem[13u * hd + tid]; }\n if (14u < tile_len) { out_acc += w14 * smem[14u * hd + tid]; }\n if (15u < tile_len) { out_acc += w15 * smem[15u * hd + tid]; }\n }\n if (d2 < hd) {\n out_acc2 = out_acc2 * old_correction;\n if (0u < tile_len) { out_acc2 += w0 * smem[0u * hd + d2]; }\n if (1u < tile_len) { out_acc2 += w1 * smem[1u * hd + d2]; }\n if (2u < tile_len) { out_acc2 += w2 * smem[2u * hd + d2]; }\n if (3u < tile_len) { out_acc2 += w3 * smem[3u * hd + d2]; }\n if (4u < tile_len) { out_acc2 += w4 * smem[4u * hd + d2]; }\n if (5u < tile_len) { out_acc2 += w5 * smem[5u * hd + d2]; }\n if (6u < tile_len) { out_acc2 += w6 * smem[6u * hd + d2]; }\n if (7u < tile_len) { out_acc2 += w7 * smem[7u * hd + d2]; }\n if (8u < tile_len) { out_acc2 += w8 * smem[8u * hd + d2]; }\n if (9u < tile_len) { out_acc2 += w9 * smem[9u * hd + d2]; }\n if (10u < tile_len) { out_acc2 += w10 * smem[10u * hd + d2]; }\n if (11u < tile_len) { out_acc2 += w11 * smem[11u * hd + d2]; }\n if (12u < tile_len) { out_acc2 += w12 * smem[12u * hd + d2]; }\n if (13u < tile_len) { out_acc2 += w13 * smem[13u * hd + d2]; }\n if (14u < tile_len) { out_acc2 += w14 * smem[14u * hd + d2]; }\n if (15u < tile_len) { out_acc2 += w15 * smem[15u * hd + d2]; }\n }\n\n running_sum = running_sum * old_correction + tile_sum;\n running_max = new_max;\n workgroupBarrier();\n }\n\n // ── Write normalized output ──\n var inv_sum: f32 = 0.0;\n if (running_sum > 0.0) { inv_sum = 1.0 / running_sum; }\n let out_base = q_pos * q_stride + q_head * hd;\n if (tid < hd) {\n output[out_base + tid] = out_acc * inv_sum;\n }\n if (d2 < hd) {\n output[out_base + d2] = out_acc2 * inv_sum;\n }\n}\n`;\n\nconst WGSL_CONV_STATE_UPDATE = `\\\n// Update conv1d rolling state buffer with latest input timesteps.\n// For T >= state_size: copy last state_size timesteps from input.\n// For T < state_size: shift old state left by T, append T new values.\n\nstruct Params {\n seq_len: u32,\n channels: u32,\n state_size: u32,\n _pad: u32,\n}\n\n@group(0) @binding(0) var<storage, read> input: array<f32>;\n@group(0) @binding(1) var<storage, read_write> conv_state: array<f32>;\n@group(0) @binding(2) var<storage, read> params: Params;\n\n@compute @workgroup_size(256)\nfn main(@builtin(global_invocation_id) gid: vec3u) {\n let c = gid.x;\n if (c >= params.channels) { return; }\n\n let ss = params.state_size;\n\n if (params.seq_len >= ss) {\n // Large batch (prefill): copy last state_size timesteps from input\n for (var s: u32 = 0u; s < ss; s = s + 1u) {\n let src_t = params.seq_len - ss + s;\n conv_state[s * params.channels + c] = input[src_t * params.channels + c];\n }\n } else {\n // Small batch (decode): shift old state left, append new values\n // Read old state for this channel first to avoid read-write conflict\n var old0: f32 = 0.0;\n var old1: f32 = 0.0;\n var old2: f32 = 0.0;\n if (ss > 0u) { old0 = conv_state[0u * params.channels + c]; }\n if (ss > 1u) { old1 = conv_state[1u * params.channels + c]; }\n if (ss > 2u) { old2 = conv_state[2u * params.channels + c]; }\n\n let shift = ss - params.seq_len;\n for (var s: u32 = 0u; s < ss; s = s + 1u) {\n var val: f32 = 0.0;\n if (s < shift) {\n // Read from shifted old state\n let src_s = s + params.seq_len;\n if (src_s == 0u) { val = old0; }\n else if (src_s == 1u) { val = old1; }\n else if (src_s == 2u) { val = old2; }\n } else {\n // Read from new input\n let input_idx = s - shift;\n val = input[input_idx * params.channels + c];\n }\n conv_state[s * params.channels + c] = val;\n }\n }\n}\n`;\n\n// PoolMatMul: pooled[Np, W] = poolW[Np, N] @ hidden[N, W].\n// The Gemma 4 ViT spatial average-pool, expressed as a host-built pooling matrix\n// (poolW[r, k] = 1/k² when patch k falls in pooled-cell r, else 0) times the\n// encoder output. One thread per output element accumulates over N. Deliberately\n// simple (no workgroup memory, no select(), no vec4 alignment assumption) — it\n// runs ONCE per image over tiny matrices (Np≤~512, N≤~4096, W=768), so a scalar\n// kernel is plenty fast and maximally mobile-safe.\nconst WGSL_POOL_MATMUL = `\\\nstruct Params {\n Np: u32, // pooled rows (output rows)\n N: u32, // patch count (contraction dim)\n W: u32, // hidden width (output cols)\n}\n\n@group(0) @binding(0) var<storage, read> poolW: array<f32>; // [Np, N]\n@group(0) @binding(1) var<storage, read> hidden: array<f32>; // [N, W]\n@group(0) @binding(2) var<storage, read_write> outp: array<f32>; // [Np, W]\n@group(0) @binding(3) var<storage, read> params: Params;\n\n@compute @workgroup_size(64)\nfn main(@builtin(global_invocation_id) gid: vec3u) {\n let idx = gid.x;\n let total = params.Np * params.W;\n if (idx >= total) { return; }\n let r = idx / params.W; // pooled row\n let c = idx % params.W; // hidden col\n var acc: f32 = 0.0;\n let prow = r * params.N;\n for (var k: u32 = 0u; k < params.N; k = k + 1u) {\n let w = poolW[prow + k];\n if (w != 0.0) {\n acc = acc + w * hidden[k * params.W + c];\n }\n }\n outp[idx] = acc;\n}\n`;\n\n// ClippedMatMul: out = clamp(clamp(A, imin, imax) @ B^T, omin, omax).\n// Gemma 4 ViT Gemma4ClippableLinear: each projection clamps its input to a\n// calibrated [imin, imax] before the linear and its output to [omin, omax] after.\n// Same tiling as WGSL_MATMUL (4×2 register blocking, vec4 loads), with the input\n// clamp applied at tile load and the output clamp at store. Mobile-safe: clamp =\n// min(max(x, lo), hi), no select(); no `enable f16`. The four clip scalars are\n// passed as f32 bit patterns in the uniform.\nconst WGSL_CLIPPED_MATMUL = `\\\nconst MT: u32 = 64u;\nconst NT: u32 = 32u;\nconst KT: u32 = 16u;\nconst KT4: u32 = 4u;\n\nstruct Params {\n M: u32,\n K: u32,\n N: u32,\n imin: f32,\n imax: f32,\n omin: f32,\n omax: f32,\n}\n\n@group(0) @binding(0) var<storage, read> A: array<vec4f>;\n@group(0) @binding(1) var<storage, read> B: array<vec4f>;\n@group(0) @binding(2) var<storage, read_write> C: array<f32>;\n@group(0) @binding(3) var<storage, read> params: Params;\n\nvar<workgroup> tileA: array<f32, 1024>;\nvar<workgroup> tileB: array<f32, 512>;\n\nfn clip4(v: vec4f, lo: f32, hi: f32) -> vec4f {\n return min(max(v, vec4f(lo)), vec4f(hi));\n}\n\n@compute @workgroup_size(16, 16)\nfn main(\n @builtin(local_invocation_id) lid: vec3u,\n @builtin(workgroup_id) wid: vec3u,\n) {\n let lr = lid.y;\n let lc = lid.x;\n let mBase = wid.y * MT;\n let nBase = wid.x * NT;\n let row0 = mBase + lr;\n let col0 = nBase + lc;\n\n var acc: array<f32, 8>;\n for (var i = 0u; i < 8u; i = i + 1u) { acc[i] = 0.0; }\n\n let numTiles = (params.K + KT - 1u) / KT;\n let tid = lr * 16u + lc;\n let K4 = params.K / 4u;\n\n for (var t: u32 = 0u; t < numTiles; t = t + 1u) {\n let kv0 = t * KT4;\n {\n let ar = tid / KT4;\n let av4 = tid % KT4;\n let gRow = mBase + ar;\n var v = vec4f(0.0, 0.0, 0.0, 0.0);\n if (gRow < params.M && kv0 + av4 < K4) {\n v = clip4(A[gRow * K4 + kv0 + av4], params.imin, params.imax);\n }\n let base = ar * KT + av4 * 4u;\n tileA[base] = v.x;\n tileA[base + 1u] = v.y;\n tileA[base + 2u] = v.z;\n tileA[base + 3u] = v.w;\n }\n if (tid < 128u) {\n let bc = tid / KT4;\n let bv4 = tid % KT4;\n let gCol = nBase + bc;\n var v = vec4f(0.0, 0.0, 0.0, 0.0);\n if (gCol < params.N && kv0 + bv4 < K4) {\n v = B[gCol * K4 + kv0 + bv4];\n }\n let krow = bv4 * 4u;\n tileB[(krow + 0u) * NT + bc] = v.x;\n tileB[(krow + 1u) * NT + bc] = v.y;\n tileB[(krow + 2u) * NT + bc] = v.z;\n tileB[(krow + 3u) * NT + bc] = v.w;\n }\n\n workgroupBarrier();\n\n for (var k: u32 = 0u; k < KT; k = k + 1u) {\n let b0 = tileB[k * NT + lc];\n let b1 = tileB[k * NT + lc + 16u];\n let a0 = tileA[lr * KT + k];\n let a1 = tileA[(lr + 16u) * KT + k];\n let a2 = tileA[(lr + 32u) * KT + k];\n let a3 = tileA[(lr + 48u) * KT + k];\n acc[0] += a0 * b0;\n acc[1] += a0 * b1;\n acc[2] += a1 * b0;\n acc[3] += a1 * b1;\n acc[4] += a2 * b0;\n acc[5] += a2 * b1;\n acc[6] += a3 * b0;\n acc[7] += a3 * b1;\n }\n\n workgroupBarrier();\n }\n\n for (var rr = 0u; rr < 4u; rr = rr + 1u) {\n let gRow = row0 + rr * 16u;\n if (gRow < params.M) {\n if (col0 < params.N) {\n C[gRow * params.N + col0] = min(max(acc[rr * 2u], params.omin), params.omax);\n }\n if (col0 + 16u < params.N) {\n C[gRow * params.N + col0 + 16u] = min(max(acc[rr * 2u + 1u], params.omin), params.omax);\n }\n }\n }\n}\n`;\n\n// ── Kernel specs ────────────────────────────────────────────────────────\n\n/**\n * Resolve the total element count of the first output tensor.\n * Used by elementwise ops whose dispatch is based on output size.\n */\nfunction outputElementCount(op: OpNode, resolvedShapes: Record<string, number[]>): number {\n const outShape = resolvedShapes[op.outputs[0]];\n let count = 1;\n for (const d of outShape) count *= d;\n return count;\n}\n\n// ── Add ──\n\nconst addSpec: KernelSpec = {\n shaderCode: WGSL_ADD,\n entryPoint: \"main\",\n\n // @binding(0) a: storage read\n // @binding(1) b: storage read\n // @binding(2) output: storage read_write\n // @binding(3) params: uniform\n bindings: [\n { type: \"storage-read\" },\n { type: \"storage-read\" },\n { type: \"storage-read-write\" },\n { type: \"uniform\" },\n ],\n\n getDispatchSize(op, resolvedShapes) {\n const count = outputElementCount(op, resolvedShapes);\n return [cdiv(count, 256), 1, 1];\n },\n\n buildParams(op, resolvedShapes) {\n const count = outputElementCount(op, resolvedShapes);\n // Params { count: u32 }\n return buildUniformBuffer([count]);\n },\n};\n\n// ── Mul ──\n\nconst mulSpec: KernelSpec = {\n shaderCode: WGSL_MUL,\n entryPoint: \"main\",\n\n // @binding(0) a: storage read\n // @binding(1) b: storage read\n // @binding(2) output: storage read_write\n // @binding(3) params: uniform\n bindings: [\n { type: \"storage-read\" },\n { type: \"storage-read\" },\n { type: \"storage-read-write\" },\n { type: \"uniform\" },\n ],\n\n getDispatchSize(op, resolvedShapes) {\n const count = outputElementCount(op, resolvedShapes);\n return [cdiv(count, 256), 1, 1];\n },\n\n buildParams(op, resolvedShapes) {\n const count = outputElementCount(op, resolvedShapes);\n // Params { count: u32 }\n return buildUniformBuffer([count]);\n },\n};\n\n// ── SliceLastRow ──\n\nconst sliceLastRowSpec: KernelSpec = {\n shaderCode: WGSL_SLICE_LAST_ROW,\n entryPoint: \"main\",\n\n // @binding(0) src: storage read\n // @binding(1) output: storage read_write\n // @binding(2) params: uniform\n bindings: [{ type: \"storage-read\" }, { type: \"storage-read-write\" }, { type: \"uniform\" }],\n\n getDispatchSize(op) {\n const width = op.attributes.width as number;\n return [cdiv(width, 256), 1, 1];\n },\n\n buildParams(op, resolvedShapes) {\n const width = op.attributes.width as number;\n const T = resolvedShapes[op.inputs[0]]?.[0] ?? 1;\n // Params { width: u32, last_row_offset: u32 }\n return buildUniformBuffer([width, (T - 1) * width]);\n },\n};\n\n// ── MeanPool (mean over T tokens → [1, width], bidirectional embeddings) ──\n\nconst meanPoolSpec: KernelSpec = {\n shaderCode: WGSL_MEAN_POOL,\n entryPoint: \"main\",\n\n // @binding(0) src: storage read\n // @binding(1) output: storage read_write\n // @binding(2) params: uniform\n bindings: [{ type: \"storage-read\" }, { type: \"storage-read-write\" }, { type: \"uniform\" }],\n\n getDispatchSize(op) {\n const width = op.attributes.width as number;\n return [cdiv(width, 256), 1, 1];\n },\n\n buildParams(op, resolvedShapes) {\n const width = op.attributes.width as number;\n // seq_len = rows of the input activation (T).\n const T = resolvedShapes[op.inputs[0]]?.[0] ?? 1;\n // Params { seq_len: u32, width: u32 }\n return buildUniformBuffer([T, width]);\n },\n};\n\n// ── Scale (multiply tensor by a scalar constant) ──\n\nconst scaleSpec: KernelSpec = {\n shaderCode: WGSL_SCALE,\n entryPoint: \"main\",\n\n // @binding(0) input: storage read\n // @binding(1) output: storage read_write\n // @binding(2) params: uniform\n bindings: [{ type: \"storage-read\" }, { type: \"storage-read-write\" }, { type: \"uniform\" }],\n\n getDispatchSize(op, resolvedShapes) {\n const count = outputElementCount(op, resolvedShapes);\n return [cdiv(count, 256), 1, 1];\n },\n\n buildParams(op, resolvedShapes) {\n const count = outputElementCount(op, resolvedShapes);\n const scale = op.attributes.scale as number;\n // Params { count: u32, scale_bits: u32 }\n return buildUniformBuffer([count, f32BitsToU32(scale)]);\n },\n};\n\n// ── Softcap (Gemma final logit softcapping) ──\n\nconst softcapSpec: KernelSpec = {\n shaderCode: WGSL_SOFTCAP,\n entryPoint: \"main\",\n\n // @binding(0) input: storage read\n // @binding(1) output: storage read_write\n // @binding(2) params: uniform\n bindings: [{ type: \"storage-read\" }, { type: \"storage-read-write\" }, { type: \"uniform\" }],\n\n getDispatchSize(op, resolvedShapes) {\n const count = outputElementCount(op, resolvedShapes);\n return [cdiv(count, 256), 1, 1];\n },\n\n buildParams(op, resolvedShapes) {\n const count = outputElementCount(op, resolvedShapes);\n const cap = op.attributes.cap as number;\n // Params { count: u32, cap_bits: u32 }\n return buildUniformBuffer([count, f32BitsToU32(cap)]);\n },\n};\n\n// ── L2Norm (row-wise L2 normalization, used for embeddings) ──\n\nconst l2NormSpec: KernelSpec = {\n shaderCode: WGSL_L2NORM,\n entryPoint: \"main\",\n\n // @binding(0) src: storage read\n // @binding(1) output: storage read_write\n // @binding(2) params: uniform\n bindings: [{ type: \"storage-read\" }, { type: \"storage-read-write\" }, { type: \"uniform\" }],\n\n getDispatchSize(op, resolvedShapes) {\n // One workgroup per row.\n const rows = resolvedShapes[op.outputs[0]]?.[0] ?? 1;\n return [rows, 1, 1];\n },\n\n buildParams(op, resolvedShapes) {\n const width = op.attributes.width as number;\n const rows = resolvedShapes[op.outputs[0]]?.[0] ?? 1;\n // Params { rows: u32, width: u32 }\n return buildUniformBuffer([rows, width]);\n },\n};\n\n// ── SwiGLU (fused SiLU + Mul) ──\n\nconst swigluSpec: KernelSpec = {\n shaderCode: WGSL_SWIGLU,\n entryPoint: \"main\",\n\n // @binding(0) gate: storage read\n // @binding(1) up: storage read\n // @binding(2) output: storage read_write\n // @binding(3) params: uniform\n bindings: [\n { type: \"storage-read\" },\n { type: \"storage-read\" },\n { type: \"storage-read-write\" },\n { type: \"uniform\" },\n ],\n\n getDispatchSize(op, resolvedShapes) {\n const count = outputElementCount(op, resolvedShapes);\n return [cdiv(count, 256), 1, 1];\n },\n\n buildParams(op, resolvedShapes) {\n const count = outputElementCount(op, resolvedShapes);\n return buildUniformBuffer([count]);\n },\n};\n\n// ── ResidualRMSNorm (fused Add + RMSNorm) ──\n\nconst residualRmsnormSpec: KernelSpec = {\n shaderCode: WGSL_RESIDUAL_RMSNORM,\n entryPoint: \"main\",\n\n // @binding(0) a: storage read\n // @binding(1) b: storage read\n // @binding(2) weight: storage read\n // @binding(3) sum_output: storage read_write\n // @binding(4) norm_output: storage read_write\n // @binding(5) params: uniform\n bindings: [\n { type: \"storage-read\" },\n { type: \"storage-read\" },\n { type: \"storage-read\" },\n { type: \"storage-read-write\" },\n { type: \"storage-read-write\" },\n { type: \"uniform\" },\n ],\n\n getDispatchSize(op, resolvedShapes) {\n const hidden = op.attributes.hidden_size as number;\n const seqLen = resolveSeqLen(op, resolvedShapes, hidden);\n return [seqLen, 1, 1];\n },\n\n buildParams(op, resolvedShapes) {\n const hidden = op.attributes.hidden_size as number;\n const seqLen = resolveSeqLen(op, resolvedShapes, hidden);\n const eps = (op.attributes.eps as number) ?? 1e-6;\n return buildUniformBuffer([seqLen, hidden, f32BitsToU32(eps), 0]);\n },\n};\n\n// ── SiLU ──\n\nconst siluSpec: KernelSpec = {\n shaderCode: WGSL_SILU,\n entryPoint: \"main\",\n\n // @binding(0) input: storage read\n // @binding(1) output: storage read_write\n // @binding(2) params: uniform\n bindings: [{ type: \"storage-read\" }, { type: \"storage-read-write\" }, { type: \"uniform\" }],\n\n getDispatchSize(op, resolvedShapes) {\n const count = outputElementCount(op, resolvedShapes);\n return [cdiv(count, 256), 1, 1];\n },\n\n buildParams(op, resolvedShapes) {\n const count = outputElementCount(op, resolvedShapes);\n // Params { count: u32 }\n return buildUniformBuffer([count]);\n },\n};\n\n// ── GELU ──\n\nconst geluSpec: KernelSpec = {\n shaderCode: WGSL_GELU,\n entryPoint: \"main\",\n\n // @binding(0) input: storage read\n // @binding(1) output: storage read_write\n // @binding(2) params: uniform\n bindings: [{ type: \"storage-read\" }, { type: \"storage-read-write\" }, { type: \"uniform\" }],\n\n getDispatchSize(op, resolvedShapes) {\n const count = outputElementCount(op, resolvedShapes);\n return [cdiv(count, 256), 1, 1];\n },\n\n buildParams(op, resolvedShapes) {\n const count = outputElementCount(op, resolvedShapes);\n // Params { count: u32 }\n return buildUniformBuffer([count]);\n },\n};\n\n// ── GELU (exact erf form — ViT merger) ──\n\nconst geluErfSpec: KernelSpec = {\n shaderCode: WGSL_GELU_ERF,\n entryPoint: \"main\",\n bindings: [{ type: \"storage-read\" }, { type: \"storage-read-write\" }, { type: \"uniform\" }],\n getDispatchSize(op, resolvedShapes) {\n const count = outputElementCount(op, resolvedShapes);\n return [cdiv(count, 256), 1, 1];\n },\n buildParams(op, resolvedShapes) {\n const count = outputElementCount(op, resolvedShapes);\n return buildUniformBuffer([count]);\n },\n};\n\n// ── AddBias (row-broadcast bias add) ──\n\nconst addBiasSpec: KernelSpec = {\n shaderCode: WGSL_ADD_BIAS,\n entryPoint: \"main\",\n\n // @binding(0) input, (1) bias, (2) output, (3) params\n bindings: [\n { type: \"storage-read\" },\n { type: \"storage-read\" },\n { type: \"storage-read-write\" },\n { type: \"uniform\" },\n ],\n getDispatchSize(op, resolvedShapes) {\n const count = outputElementCount(op, resolvedShapes);\n return [cdiv(count, 256), 1, 1];\n },\n buildParams(op, resolvedShapes) {\n const count = outputElementCount(op, resolvedShapes);\n const width = op.attributes.width as number;\n return buildUniformBuffer([count, width]);\n },\n};\n\n// ── SliceCols (extract column range) ──\n\nconst sliceColsSpec: KernelSpec = {\n shaderCode: WGSL_SLICE_COLS,\n entryPoint: \"main\",\n\n // @binding(0) input, (1) output, (2) params\n bindings: [{ type: \"storage-read\" }, { type: \"storage-read-write\" }, { type: \"uniform\" }],\n getDispatchSize(op, resolvedShapes) {\n const count = outputElementCount(op, resolvedShapes);\n return [cdiv(count, 256), 1, 1];\n },\n buildParams(op, resolvedShapes) {\n const in_width = op.attributes.in_width as number;\n const out_width = op.attributes.out_width as number;\n const col_offset = op.attributes.col_offset as number;\n const outShape = resolvedShapes[op.outputs[0]];\n const rows = outShape[0];\n return buildUniformBuffer([rows, in_width, out_width, col_offset]);\n },\n};\n\n// ── MulCols (multiply two column ranges of one tensor) ──\n\nconst mulColsSpec: KernelSpec = {\n shaderCode: WGSL_MUL_COLS,\n entryPoint: \"main\",\n\n // @binding(0) src, (1) output, (2) params\n bindings: [{ type: \"storage-read\" }, { type: \"storage-read-write\" }, { type: \"uniform\" }],\n getDispatchSize(op, resolvedShapes) {\n const count = outputElementCount(op, resolvedShapes);\n return [cdiv(count, 256), 1, 1];\n },\n buildParams(op, resolvedShapes) {\n const in_width = op.attributes.in_width as number;\n const out_width = op.attributes.out_width as number;\n const off_a = op.attributes.off_a as number;\n const off_b = op.attributes.off_b as number;\n const outShape = resolvedShapes[op.outputs[0]];\n const rows = outShape[0];\n return buildUniformBuffer([rows, in_width, out_width, off_a, off_b, 0, 0, 0]);\n },\n};\n\n// ── ApplyRotaryEmb (precomputed cos/sin, rotate_half) ──\n\nconst applyRotarySpec: KernelSpec = {\n shaderCode: WGSL_APPLY_ROTARY,\n entryPoint: \"main\",\n\n // @binding(0) x, (1) cos, (2) sin, (3) output, (4) params\n bindings: [\n { type: \"storage-read\" },\n { type: \"storage-read\" },\n { type: \"storage-read\" },\n { type: \"storage-read-write\" },\n { type: \"uniform\" },\n ],\n getDispatchSize(op, resolvedShapes) {\n const count = outputElementCount(op, resolvedShapes);\n return [cdiv(count, 256), 1, 1];\n },\n buildParams(op, resolvedShapes) {\n const num_heads = op.attributes.num_heads as number;\n const head_dim = op.attributes.head_dim as number;\n const outShape = resolvedShapes[op.outputs[0]];\n const total = outShape.reduce((a, b) => a * b, 1);\n const seq_len = total / (num_heads * head_dim);\n return buildUniformBuffer([seq_len, num_heads, head_dim, 0]);\n },\n};\n\n// ── MatMul ──\n\nconst matmulSpec: KernelSpec = {\n shaderCode: WGSL_MATMUL,\n entryPoint: \"main\",\n\n // @binding(0) A: storage read\n // @binding(1) B: storage read\n // @binding(2) C: storage read_write\n // @binding(3) params: uniform\n bindings: [\n { type: \"storage-read\" },\n { type: \"storage-read\" },\n { type: \"storage-read-write\" },\n { type: \"uniform\" },\n ],\n\n getDispatchSize(op, resolvedShapes) {\n // C[M,N] = A[M,K] * B[K,N]\n // 16x16 workgroup, 4x2 register blocking -> each workgroup covers a\n // 64-row x 32-col output tile. Dispatch X covers N, Y covers M.\n const N = op.attributes.N as number;\n const mTensor = op.attributes.M_tensor as string | undefined;\n let M: number;\n if (mTensor && resolvedShapes[mTensor]) {\n M = resolvedShapes[mTensor][0];\n } else {\n M = op.attributes.M as number;\n }\n return [cdiv(N, 32), cdiv(M, 64), 1];\n },\n\n buildParams(op, resolvedShapes) {\n // Params { M: u32, K: u32, N: u32 }\n const K = op.attributes.K as number;\n const N = op.attributes.N as number;\n // M is dynamic — derive from referenced tensor or static attribute\n const mTensor = op.attributes.M_tensor as string | undefined;\n let M: number;\n if (mTensor && resolvedShapes[mTensor]) {\n M = resolvedShapes[mTensor][0];\n } else {\n M = op.attributes.M as number;\n }\n return buildUniformBuffer([M, K, N]);\n },\n};\n\n// ── MatMulBias (fused MatMul + bias) ──\n\nconst matmulBiasSpec: KernelSpec = {\n shaderCode: WGSL_MATMUL_BIAS,\n entryPoint: \"main\",\n\n // @binding(0) A, (1) B, (2) bias, (3) C, (4) params\n bindings: [\n { type: \"storage-read\" },\n { type: \"storage-read\" },\n { type: \"storage-read\" },\n { type: \"storage-read-write\" },\n { type: \"uniform\" },\n ],\n\n getDispatchSize(op, resolvedShapes) {\n const N = op.attributes.N as number;\n const mTensor = op.attributes.M_tensor as string | undefined;\n let M: number;\n if (mTensor && resolvedShapes[mTensor]) {\n M = resolvedShapes[mTensor][0];\n } else {\n M = op.attributes.M as number;\n }\n return [cdiv(N, 32), cdiv(M, 64), 1];\n },\n\n buildParams(op, resolvedShapes) {\n const K = op.attributes.K as number;\n const N = op.attributes.N as number;\n const mTensor = op.attributes.M_tensor as string | undefined;\n let M: number;\n if (mTensor && resolvedShapes[mTensor]) {\n M = resolvedShapes[mTensor][0];\n } else {\n M = op.attributes.M as number;\n }\n return buildUniformBuffer([M, K, N]);\n },\n};\n\n// ── PoolMatMul (Gemma 4 ViT spatial average-pool as host matrix · encoder out) ──\n\nconst poolMatMulSpec: KernelSpec = {\n shaderCode: WGSL_POOL_MATMUL,\n entryPoint: \"main\",\n\n // @binding(0) poolW [Np,N], (1) hidden [N,W], (2) out [Np,W], (3) params\n bindings: [\n { type: \"storage-read\" },\n { type: \"storage-read\" },\n { type: \"storage-read-write\" },\n { type: \"uniform\" },\n ],\n\n getDispatchSize(op, resolvedShapes) {\n const out = resolvedShapes[op.outputs[0]]; // [Np, W]\n const Np = out[0];\n const W = out[1];\n return [cdiv(Np * W, 64), 1, 1];\n },\n\n buildParams(op, resolvedShapes) {\n const out = resolvedShapes[op.outputs[0]]; // [Np, W]\n const Np = out[0];\n const W = out[1];\n // N = contraction dim = rows of the hidden tensor (op.inputs[1]).\n const N = resolvedShapes[op.inputs[1]]?.[0] ?? (op.attributes.hidden_size as number);\n // Params { Np: u32, N: u32, W: u32 }\n return buildUniformBuffer([Np, N, W]);\n },\n};\n\n// ── ClippedMatMul (Gemma 4 ViT Gemma4ClippableLinear) ──\n\nconst clippedMatMulSpec: KernelSpec = {\n shaderCode: WGSL_CLIPPED_MATMUL,\n entryPoint: \"main\",\n\n // @binding(0) A, (1) B, (2) C, (3) params\n bindings: [\n { type: \"storage-read\" },\n { type: \"storage-read\" },\n { type: \"storage-read-write\" },\n { type: \"uniform\" },\n ],\n\n getDispatchSize(op, resolvedShapes) {\n const N = op.attributes.N as number;\n const mTensor = op.attributes.M_tensor as string | undefined;\n const M =\n mTensor && resolvedShapes[mTensor] ? resolvedShapes[mTensor][0] : (op.attributes.M as number);\n return [cdiv(N, 32), cdiv(M, 64), 1];\n },\n\n buildParams(op, resolvedShapes) {\n const K = op.attributes.K as number;\n const N = op.attributes.N as number;\n const mTensor = op.attributes.M_tensor as string | undefined;\n const M =\n mTensor && resolvedShapes[mTensor] ? resolvedShapes[mTensor][0] : (op.attributes.M as number);\n // Clip scalars default to ±inf (identity) until the loader patches them.\n const imin = (op.attributes.imin as number) ?? Number.NEGATIVE_INFINITY;\n const imax = (op.attributes.imax as number) ?? Number.POSITIVE_INFINITY;\n const omin = (op.attributes.omin as number) ?? Number.NEGATIVE_INFINITY;\n const omax = (op.attributes.omax as number) ?? Number.POSITIVE_INFINITY;\n // Params { M, K, N, imin, imax, omin, omax } — first 3 u32, rest f32 bits.\n return buildUniformBuffer([\n M,\n K,\n N,\n f32BitsToU32(imin),\n f32BitsToU32(imax),\n f32BitsToU32(omin),\n f32BitsToU32(omax),\n ]);\n },\n};\n\n// Mixed-precision (f16 multiply, f32 accumulate) MatMulBias for ViT linear layers.\n// Same bindings/dispatch/params as matmulBiasSpec; binding(1) B is an f16 weight.\n// The VisionExecutor swaps this in when the device has shader-f16.\nexport const MATMUL_BIAS_F16C_SPEC: KernelSpec = {\n ...matmulBiasSpec,\n shaderCode: WGSL_MATMUL_BIAS_F16MIX,\n};\n\n// ── MatMulInt4 ──\n\nconst matmulInt4Spec: KernelSpec = {\n shaderCode: WGSL_MATMUL_INT4,\n entryPoint: \"main\",\n\n // @binding(0) A: storage read\n // @binding(1) B_q: storage read\n // @binding(2) scales: storage read\n // @binding(3) zeros: storage read\n // @binding(4) C: storage read_write\n // @binding(5) params: uniform\n bindings: [\n { type: \"storage-read\" },\n { type: \"storage-read\" },\n { type: \"storage-read\" },\n { type: \"storage-read\" },\n { type: \"storage-read-write\" },\n { type: \"uniform\" },\n ],\n\n getDispatchSize(op, resolvedShapes) {\n // Workgroup size is (16, 16), dispatch X covers N, Y covers M\n const N = op.attributes.N as number;\n const mTensor = op.attributes.M_tensor as string | undefined;\n let M: number;\n if (mTensor && resolvedShapes[mTensor]) {\n M = resolvedShapes[mTensor][0];\n } else {\n M = op.attributes.M as number;\n }\n return [cdiv(N, 16), cdiv(M, 16), 1];\n },\n\n buildParams(op, resolvedShapes) {\n // Params { M: u32, K: u32, N: u32, group_size: u32 }\n const K = op.attributes.K as number;\n const N = op.attributes.N as number;\n const group_size = op.attributes.group_size as number;\n const mTensor = op.attributes.M_tensor as string | undefined;\n let M: number;\n if (mTensor && resolvedShapes[mTensor]) {\n M = resolvedShapes[mTensor][0];\n } else {\n M = op.attributes.M as number;\n }\n return buildUniformBuffer([M, K, N, group_size]);\n },\n};\n\n// ── MatVec (F32, M=1 decode) ──\n\nexport const MATVEC_SPEC: KernelSpec = {\n shaderCode: WGSL_MATVEC,\n entryPoint: \"main\",\n\n // Same binding layout as MatMul: A, B, C, params\n bindings: [\n { type: \"storage-read\" },\n { type: \"storage-read\" },\n { type: \"storage-read-write\" },\n { type: \"uniform\" },\n ],\n\n getDispatchSize(op) {\n const N = op.attributes.N as number;\n const N_TILE = 8;\n return [cdiv(N, N_TILE), 1, 1];\n },\n\n buildParams(op) {\n const K = op.attributes.K as number;\n const N = op.attributes.N as number;\n return buildUniformBuffer([K, N]);\n },\n};\n\n// ── MatVecInt4 (K-parallel INT4, M=1 decode) ──\n\nexport const MATVEC_INT4_SPEC: KernelSpec = {\n shaderCode: WGSL_MATVEC_INT4,\n entryPoint: \"main\",\n\n // Same binding layout as MatMulInt4: A, B_q, scales, zeros, C, params\n bindings: [\n { type: \"storage-read\" },\n { type: \"storage-read\" },\n { type: \"storage-read\" },\n { type: \"storage-read\" },\n { type: \"storage-read-write\" },\n { type: \"uniform\" },\n ],\n\n getDispatchSize(op) {\n const N = op.attributes.N as number;\n // Must match WGSL N_TILE = workgroup_size / K_THREADS = 256 / 16 = 16\n const N_TILE = 16;\n return [cdiv(N, N_TILE), 1, 1];\n },\n\n buildParams(op) {\n const K = op.attributes.K as number;\n const N = op.attributes.N as number;\n const group_size = op.attributes.group_size as number;\n return buildUniformBuffer([K, N, group_size, 0]);\n },\n};\n\n// ── Gated MatVecInt4 (fused attn sigmoid-gate + INT4 o_proj, M=1 decode) ──\n\nexport const GATED_MATVEC_INT4_SPEC: KernelSpec = {\n shaderCode: WGSL_GATED_MATVEC_INT4,\n entryPoint: \"main\",\n\n // @binding(0) attn, (1) gate, (2) B_q, (3) scales, (4) zeros, (5) C, (6) params\n bindings: [\n { type: \"storage-read\" },\n { type: \"storage-read\" },\n { type: \"storage-read\" },\n { type: \"storage-read\" },\n { type: \"storage-read\" },\n { type: \"storage-read-write\" },\n { type: \"uniform\" },\n ],\n\n getDispatchSize(op) {\n const N = op.attributes.N as number;\n const N_TILE = 8;\n return [cdiv(N, N_TILE), 1, 1];\n },\n\n buildParams(op) {\n const K = op.attributes.K as number;\n const N = op.attributes.N as number;\n const group_size = op.attributes.group_size as number;\n return buildUniformBuffer([K, N, group_size, 0]);\n },\n};\n\n// ── SwiGLU-gated MatVecInt4 (fused SwiGLU + INT4 projection, M=1 decode) ──\n\nexport const SWIGLU_GATED_MATVEC_INT4_SPEC: KernelSpec = {\n ...GATED_MATVEC_INT4_SPEC,\n shaderCode: WGSL_SWIGLU_GATED_MATVEC_INT4,\n};\n\n// ── MatVec subgroups variants (desktop-only fast path) ──\n//\n// Same binding layout, dispatch size, and params as the portable MatVec specs —\n// only the shader differs (subgroupAdd reduction). The executor selects these\n// when the device exposes the \"subgroups\" feature and is NOT WebKit. Kept as\n// distinct specs so the portable kernels remain the universal fallback.\n\nexport const MATVEC_SUBGROUPS_SPEC: KernelSpec = {\n ...MATVEC_SPEC,\n shaderCode: WGSL_MATVEC_SUBGROUPS,\n};\n\nexport const MATVEC_INT4_SUBGROUPS_SPEC: KernelSpec = {\n ...MATVEC_INT4_SPEC,\n shaderCode: WGSL_MATVEC_INT4_SUBGROUPS,\n};\n\n// ── SwiGLU MatVec (fused gate+up+SwiGLU, M=1 decode) ──\n\nexport const SWIGLU_MATVEC_SPEC: KernelSpec = {\n shaderCode: WGSL_SWIGLU_MATVEC,\n entryPoint: \"main\",\n\n // @binding(0) A: storage read\n // @binding(1) B_gate: storage read\n // @binding(2) B_up: storage read\n // @binding(3) output: storage read_write\n // @binding(4) params: uniform\n bindings: [\n { type: \"storage-read\" },\n { type: \"storage-read\" },\n { type: \"storage-read\" },\n { type: \"storage-read-write\" },\n { type: \"uniform\" },\n ],\n\n getDispatchSize(op) {\n const N = op.attributes.N as number;\n const N_TILE = 8;\n return [cdiv(N, N_TILE), 1, 1];\n },\n\n buildParams(op) {\n const K = op.attributes.K as number;\n const N = op.attributes.N as number;\n return buildUniformBuffer([K, N]);\n },\n};\n\n// ── SwiGLU MatVecInt4 (fused gate+up+SwiGLU INT4, M=1 decode) ──\n\nexport const SWIGLU_MATVEC_INT4_SPEC: KernelSpec = {\n shaderCode: WGSL_SWIGLU_MATVEC_INT4,\n entryPoint: \"main\",\n\n // @binding(0) A: storage read\n // @binding(1) B_gate_q: storage read\n // @binding(2) B_gate_scales: storage read\n // @binding(3) B_gate_zeros: storage read\n // @binding(4) B_up_q: storage read\n // @binding(5) B_up_scales: storage read\n // @binding(6) B_up_zeros: storage read\n // @binding(7) output: storage read_write\n // @binding(8) params: uniform\n bindings: [\n { type: \"storage-read\" },\n { type: \"storage-read\" },\n { type: \"storage-read\" },\n { type: \"storage-read\" },\n { type: \"storage-read\" },\n { type: \"storage-read\" },\n { type: \"storage-read\" },\n { type: \"storage-read-write\" },\n { type: \"uniform\" },\n ],\n\n getDispatchSize(op) {\n const N = op.attributes.N as number;\n const N_TILE = 8;\n return [cdiv(N, N_TILE), 1, 1];\n },\n\n buildParams(op) {\n const K = op.attributes.K as number;\n const N = op.attributes.N as number;\n const group_size = op.attributes.group_size as number;\n return buildUniformBuffer([K, N, group_size, 0]);\n },\n};\n\n// ── Dual MatVecInt4 (two INT4 projections sharing input A, M=1 decode) ──\n\nexport const DUAL_MATVEC_INT4_SPEC: KernelSpec = {\n shaderCode: WGSL_DUAL_MATVEC_INT4,\n entryPoint: \"main\",\n\n // @binding(0) A\n // @binding(1..3) B0_q, B0_scales, B0_zeros\n // @binding(4..6) B1_q, B1_scales, B1_zeros\n // @binding(7) out0, @binding(8) out1, @binding(9) params\n bindings: [\n { type: \"storage-read\" },\n { type: \"storage-read\" },\n { type: \"storage-read\" },\n { type: \"storage-read\" },\n { type: \"storage-read\" },\n { type: \"storage-read\" },\n { type: \"storage-read\" },\n { type: \"storage-read-write\" },\n { type: \"storage-read-write\" },\n { type: \"uniform\" },\n ],\n\n getDispatchSize(op) {\n const N = op.attributes.N as number;\n const N_TILE = 8;\n return [cdiv(N, N_TILE), 1, 1];\n },\n\n buildParams(op) {\n const K = op.attributes.K as number;\n const N = op.attributes.N as number;\n const group_size = op.attributes.group_size as number;\n return buildUniformBuffer([K, N, group_size, 0]);\n },\n};\n\n// ── Argmax (GPU-side, single workgroup) ──\n\nexport const ARGMAX_SPEC: KernelSpec = {\n shaderCode: WGSL_ARGMAX,\n entryPoint: \"main\",\n\n bindings: [{ type: \"storage-read\" }, { type: \"storage-read-write\" }, { type: \"uniform\" }],\n\n getDispatchSize() {\n return [1, 1, 1]; // Single workgroup\n },\n\n buildParams(_op, _resolvedShapes) {\n // Params built externally by executor (count + offset)\n return buildUniformBuffer([0, 0]);\n },\n};\n\n// ── RMSNorm ──\n\nconst rmsnormSpec: KernelSpec = {\n shaderCode: WGSL_RMSNORM,\n entryPoint: \"main\",\n\n // @binding(0) input: storage read\n // @binding(1) weight: storage read\n // @binding(2) output: storage read_write\n // @binding(3) params: uniform\n bindings: [\n { type: \"storage-read\" },\n { type: \"storage-read\" },\n { type: \"storage-read-write\" },\n { type: \"uniform\" },\n ],\n\n getDispatchSize(op, resolvedShapes) {\n // One workgroup per row (token)\n const hidden_size = op.attributes.hidden_size as number;\n const ref = op.attributes.seq_len_tensor as string | undefined;\n let seq_len: number;\n if (ref && resolvedShapes[ref]) {\n const shape = resolvedShapes[ref];\n const total = shape.reduce((a: number, b: number) => a * b, 1);\n seq_len = total / hidden_size;\n } else {\n seq_len = op.attributes.seq_len as number;\n }\n return [seq_len, 1, 1];\n },\n\n buildParams(op, resolvedShapes) {\n // Params { seq_len: u32, hidden_size: u32, eps_bits: u32, _pad: u32 }\n const hidden_size = op.attributes.hidden_size as number;\n const eps = op.attributes.eps as number;\n const ref = op.attributes.seq_len_tensor as string | undefined;\n let seq_len: number;\n if (ref && resolvedShapes[ref]) {\n const shape = resolvedShapes[ref];\n const total = shape.reduce((a: number, b: number) => a * b, 1);\n seq_len = total / hidden_size;\n } else {\n seq_len = op.attributes.seq_len as number;\n }\n return buildUniformBuffer([\n seq_len,\n hidden_size,\n f32BitsToU32(eps),\n 0, // _pad\n ]);\n },\n};\n\n// ── Dual RMSNorm (two per-row RMSNorms, one dispatch) ──\n\nfunction dualRmsnormRows(\n op: OpNode,\n resolvedShapes: Record<string, number[]>,\n refKey: string,\n fallbackKey: string,\n): number {\n const hidden_size = op.attributes.hidden_size as number;\n const ref = op.attributes[refKey] as string | undefined;\n if (ref && resolvedShapes[ref]) {\n const total = resolvedShapes[ref].reduce((a: number, b: number) => a * b, 1);\n return total / hidden_size;\n }\n return op.attributes[fallbackKey] as number;\n}\n\nexport const DUAL_RMSNORM_SPEC: KernelSpec = {\n shaderCode: WGSL_DUAL_RMSNORM,\n entryPoint: \"main\",\n\n // input0, weight0, input1, weight1, output0, output1, params\n bindings: [\n { type: \"storage-read\" },\n { type: \"storage-read\" },\n { type: \"storage-read\" },\n { type: \"storage-read\" },\n { type: \"storage-read-write\" },\n { type: \"storage-read-write\" },\n { type: \"uniform\" },\n ],\n\n getDispatchSize(op, resolvedShapes) {\n const rows0 = dualRmsnormRows(op, resolvedShapes, \"seq_len_tensor0\", \"seq_len0\");\n const rows1 = dualRmsnormRows(op, resolvedShapes, \"seq_len_tensor1\", \"seq_len1\");\n return [rows0 + rows1, 1, 1];\n },\n\n buildParams(op, resolvedShapes) {\n const rows0 = dualRmsnormRows(op, resolvedShapes, \"seq_len_tensor0\", \"seq_len0\");\n const rows1 = dualRmsnormRows(op, resolvedShapes, \"seq_len_tensor1\", \"seq_len1\");\n const hidden_size = op.attributes.hidden_size as number;\n const eps = op.attributes.eps as number;\n return buildUniformBuffer([rows0, rows1, hidden_size, f32BitsToU32(eps)]);\n },\n};\n\n// ── LayerNorm ──\n\nconst layernormSpec: KernelSpec = {\n shaderCode: WGSL_LAYERNORM,\n entryPoint: \"main\",\n\n // @binding(0) input: storage read\n // @binding(1) weight: storage read\n // @binding(2) bias: storage read\n // @binding(3) output: storage read_write\n // @binding(4) params: uniform\n bindings: [\n { type: \"storage-read\" },\n { type: \"storage-read\" },\n { type: \"storage-read\" },\n { type: \"storage-read-write\" },\n { type: \"uniform\" },\n ],\n\n getDispatchSize(op, resolvedShapes) {\n // One workgroup per row (token)\n const hidden_size = op.attributes.hidden_size as number;\n const ref = op.attributes.seq_len_tensor as string | undefined;\n let seq_len: number;\n if (ref && resolvedShapes[ref]) {\n const shape = resolvedShapes[ref];\n const total = shape.reduce((a: number, b: number) => a * b, 1);\n seq_len = total / hidden_size;\n } else {\n seq_len = op.attributes.seq_len as number;\n }\n return [seq_len, 1, 1];\n },\n\n buildParams(op, resolvedShapes) {\n // Params { seq_len: u32, hidden_size: u32, eps_bits: u32, _pad: u32 }\n const hidden_size = op.attributes.hidden_size as number;\n const eps = op.attributes.eps as number;\n const ref = op.attributes.seq_len_tensor as string | undefined;\n let seq_len: number;\n if (ref && resolvedShapes[ref]) {\n const shape = resolvedShapes[ref];\n const total = shape.reduce((a: number, b: number) => a * b, 1);\n seq_len = total / hidden_size;\n } else {\n seq_len = op.attributes.seq_len as number;\n }\n return buildUniformBuffer([\n seq_len,\n hidden_size,\n f32BitsToU32(eps),\n 0, // _pad\n ]);\n },\n};\n\n// ── Embedding ──\n\nconst embeddingSpec: KernelSpec = {\n shaderCode: WGSL_EMBEDDING,\n entryPoint: \"main\",\n\n // @binding(0) input_ids: storage read\n // @binding(1) weight: storage read\n // @binding(2) output: storage read_write\n // @binding(3) params: uniform\n bindings: [\n { type: \"storage-read\" },\n { type: \"storage-read\" },\n { type: \"storage-read-write\" },\n { type: \"uniform\" },\n ],\n\n getDispatchSize(op, resolvedShapes) {\n const hidden_size = op.attributes.hidden_size as number;\n const outShape = resolvedShapes[op.outputs[0]];\n const seq_len = outShape ? outShape[0] : (op.attributes.seq_len as number);\n return [cdiv(seq_len * hidden_size, 256), 1, 1];\n },\n\n buildParams(op, resolvedShapes) {\n // Params { seq_len: u32, hidden_size: u32 }\n const hidden_size = op.attributes.hidden_size as number;\n const outShape = resolvedShapes[op.outputs[0]];\n const seq_len = outShape ? outShape[0] : (op.attributes.seq_len as number);\n return buildUniformBuffer([seq_len, hidden_size]);\n },\n};\n\n// ── EmbeddingInt4 ──\n\nconst embeddingInt4Spec: KernelSpec = {\n shaderCode: WGSL_EMBEDDING_INT4,\n entryPoint: \"main\",\n\n // @binding(0) input_ids: storage read\n // @binding(1) weight_q: storage read\n // @binding(2) scales: storage read\n // @binding(3) zeros: storage read\n // @binding(4) output: storage read_write\n // @binding(5) params: uniform\n bindings: [\n { type: \"storage-read\" },\n { type: \"storage-read\" },\n { type: \"storage-read\" },\n { type: \"storage-read\" },\n { type: \"storage-read-write\" },\n { type: \"uniform\" },\n ],\n\n getDispatchSize(op, resolvedShapes) {\n const hidden_size = op.attributes.hidden_size as number;\n const outShape = resolvedShapes[op.outputs[0]];\n const seq_len = outShape ? outShape[0] : (op.attributes.seq_len as number);\n return [cdiv(seq_len * hidden_size, 256), 1, 1];\n },\n\n buildParams(op, resolvedShapes) {\n // Params { seq_len: u32, hidden_size: u32, group_size: u32, _pad: u32 }\n const hidden_size = op.attributes.hidden_size as number;\n const group_size = op.attributes.group_size as number;\n const outShape = resolvedShapes[op.outputs[0]];\n const seq_len = outShape ? outShape[0] : (op.attributes.seq_len as number);\n return buildUniformBuffer([seq_len, hidden_size, group_size, 0]);\n },\n};\n\n// ── RoPE ──\n\nconst ropeSpec: KernelSpec = {\n shaderCode: WGSL_ROPE,\n entryPoint: \"main\",\n\n // @binding(0) q: storage read_write\n // @binding(1) k: storage read_write\n // @binding(2) params: uniform\n bindings: [{ type: \"storage-read-write\" }, { type: \"storage-read-write\" }, { type: \"uniform\" }],\n\n getDispatchSize(op, resolvedShapes) {\n const num_q_heads = op.attributes.num_q_heads as number;\n const num_kv_heads = op.attributes.num_kv_heads as number;\n const head_dim = op.attributes.head_dim as number;\n const rope_dim = (op.attributes.rope_dim as number) ?? head_dim;\n // rope_half is the rotate_half pairing offset / pair count. Defaults to\n // rope_dim/2 (legacy), but Gemma \"proportional\" overrides it to head_dim/2.\n const rope_half = (op.attributes.rope_half as number) ?? rope_dim / 2;\n\n const seqRef = op.attributes.seq_len_tensor as string | undefined;\n let seq_len: number;\n if (seqRef && resolvedShapes[seqRef]) {\n seq_len = resolvedShapes[seqRef][0];\n } else {\n seq_len = op.attributes.seq_len as number;\n }\n\n const total_q_pairs = seq_len * num_q_heads * rope_half;\n const total_k_pairs = seq_len * num_kv_heads * rope_half;\n return [cdiv(Math.max(total_q_pairs, total_k_pairs), 256), 1, 1];\n },\n\n buildParams(op, resolvedShapes, context) {\n // Params { seq_len, num_q_heads, num_kv_heads, head_dim, rope_base_bits,\n // position_offset, rope_dim, rope_half, rope_denom, rope_active_pairs, _pad0, _pad1 }\n const num_q_heads = op.attributes.num_q_heads as number;\n const num_kv_heads = op.attributes.num_kv_heads as number;\n const head_dim = op.attributes.head_dim as number;\n const rope_base = op.attributes.rope_base as number;\n const rope_dim = (op.attributes.rope_dim as number) ?? head_dim;\n // Decoupled RoPE knobs (default to legacy single-rope_dim behavior):\n // rope_half = rotate_half pairing offset + pair count (legacy rope_dim/2)\n // rope_denom = inv_freq exponent denominator (legacy rope_dim)\n // rope_active_pairs = pairs with a real frequency; rest are identity (legacy rope_dim/2)\n const rope_half = (op.attributes.rope_half as number) ?? rope_dim / 2;\n const rope_denom = (op.attributes.rope_denom as number) ?? rope_dim;\n const rope_active_pairs = (op.attributes.rope_active_pairs as number) ?? rope_dim / 2;\n // Use runtime seqPos for position offset during autoregressive decode\n const position_offset = context?.seqPos ?? (op.attributes.position_offset as number) ?? 0;\n\n const seqRef = op.attributes.seq_len_tensor as string | undefined;\n let seq_len: number;\n if (seqRef && resolvedShapes[seqRef]) {\n seq_len = resolvedShapes[seqRef][0];\n } else {\n seq_len = op.attributes.seq_len as number;\n }\n\n return buildUniformBuffer([\n seq_len,\n num_q_heads,\n num_kv_heads,\n head_dim,\n f32BitsToU32(rope_base),\n position_offset,\n rope_dim,\n rope_half,\n rope_denom,\n rope_active_pairs,\n 0, // _pad0\n 0, // _pad1\n ]);\n },\n};\n\n/**\n * Interleaved (adjacent-pair) RoPE, used by Moonshine. Identical dispatch/params\n * to ropeSpec — only the rotation pairing differs (2p/2p+1 vs p/p+half). Selected\n * by the executors when a RoPE node carries `interleaved: true`.\n */\nexport const ROPE_INTERLEAVED_SPEC: KernelSpec = {\n ...ropeSpec,\n shaderCode: WGSL_ROPE_INTERLEAVED,\n};\n\n// ── M-RoPE (precomputed cos/sin, partial rotate_half) ──\n\nconst mropeSpec: KernelSpec = {\n shaderCode: WGSL_MROPE,\n entryPoint: \"main\",\n // @binding(0) q rw, (1) k rw, (2) cos, (3) sin, (4) params\n bindings: [\n { type: \"storage-read-write\" },\n { type: \"storage-read-write\" },\n { type: \"storage-read\" },\n { type: \"storage-read\" },\n { type: \"uniform\" },\n ],\n getDispatchSize(op, resolvedShapes) {\n const num_q_heads = op.attributes.num_q_heads as number;\n const num_kv_heads = op.attributes.num_kv_heads as number;\n const rope_dim = op.attributes.rope_dim as number;\n const half = rope_dim / 2;\n const seqRef = op.attributes.seq_len_tensor as string | undefined;\n const seq_len =\n seqRef && resolvedShapes[seqRef]\n ? resolvedShapes[seqRef][0]\n : (op.attributes.seq_len as number);\n const total = seq_len * Math.max(num_q_heads, num_kv_heads) * half;\n return [cdiv(total, 256), 1, 1];\n },\n buildParams(op, resolvedShapes, context) {\n const num_q_heads = op.attributes.num_q_heads as number;\n const num_kv_heads = op.attributes.num_kv_heads as number;\n const head_dim = op.attributes.head_dim as number;\n const rope_dim = op.attributes.rope_dim as number;\n const seqRef = op.attributes.seq_len_tensor as string | undefined;\n const seq_len =\n seqRef && resolvedShapes[seqRef]\n ? resolvedShapes[seqRef][0]\n : (op.attributes.seq_len as number);\n // Decode (single-token) reads cos/sin row seqPos; prefill writes all rows 0..T.\n const pos_offset = seq_len === 1 ? (context?.seqPos ?? 0) : 0;\n return buildUniformBuffer([\n seq_len,\n num_q_heads,\n num_kv_heads,\n head_dim,\n rope_dim,\n pos_offset,\n 0,\n 0,\n ]);\n },\n};\n\n// ── EmbedSplice (scatter vision tokens into image-token rows) ──\n\nconst embedSpliceSpec: KernelSpec = {\n shaderCode: WGSL_EMBED_SPLICE,\n entryPoint: \"main\",\n // @binding(0) embed_in, (1) vision, (2) row_map, (3) embed_out, (4) params\n bindings: [\n { type: \"storage-read\" },\n { type: \"storage-read\" },\n { type: \"storage-read\" },\n { type: \"storage-read-write\" },\n { type: \"uniform\" },\n ],\n getDispatchSize(op, resolvedShapes) {\n const hidden = op.attributes.hidden as number;\n const seqRef = op.attributes.seq_len_tensor as string;\n const seq_len = resolvedShapes[seqRef][0];\n return [cdiv(seq_len * hidden, 256), 1, 1];\n },\n buildParams(op, resolvedShapes) {\n const hidden = op.attributes.hidden as number;\n const seqRef = op.attributes.seq_len_tensor as string;\n const seq_len = resolvedShapes[seqRef][0];\n return buildUniformBuffer([seq_len, hidden]);\n },\n};\n\n// ── Attention ──\n\nconst attentionSpec: KernelSpec = {\n shaderCode: WGSL_ATTENTION,\n entryPoint: \"main\",\n\n // @binding(0) Q: storage read\n // @binding(1) K: storage read\n // @binding(2) V: storage read\n // @binding(3) output: storage read_write\n // @binding(4) params: uniform\n bindings: [\n { type: \"storage-read\" },\n { type: \"storage-read\" },\n { type: \"storage-read\" },\n { type: \"storage-read-write\" },\n { type: \"uniform\" },\n ],\n\n getDispatchSize(op, resolvedShapes) {\n // One workgroup per (query_position, q_head) pair\n const num_q_heads = op.attributes.num_q_heads as number;\n // Resolve T from output shape or static attribute\n const outShape = resolvedShapes[op.outputs[0]];\n const T = outShape ? outShape[0] : (op.attributes.T as number);\n return [T, num_q_heads, 1];\n },\n\n buildParams(op, resolvedShapes, context) {\n // Params { T, S, num_q_heads, num_kv_heads, head_dim, position_offset, is_causal }\n const num_q_heads = op.attributes.num_q_heads as number;\n const num_kv_heads = op.attributes.num_kv_heads as number;\n const head_dim = op.attributes.head_dim as number;\n // Use runtime seqPos for position offset during autoregressive decode\n const position_offset = context?.seqPos ?? (op.attributes.position_offset as number) ?? 0;\n\n // Resolve T from output shape or static attribute\n const outShape = resolvedShapes[op.outputs[0]];\n const T = outShape ? outShape[0] : (op.attributes.T as number);\n\n // S = total KV length (seqPos + T from KV cache, or T for prefill-only)\n const kvShape = resolvedShapes[op.inputs[1]]; // K cache shape: [L_max, kv_dim]\n const S = kvShape ? kvShape[0] : T;\n\n // Default causal (text). ViT attention sets causal:false → bidirectional.\n const is_causal = (op.attributes.causal as boolean) === false ? 0 : 1;\n\n // Query-grid base offset (default 0). The WebKit vision encoder sets this\n // per-chunk to split the O(N²) ViT attention into watchdog-safe windows; all\n // other callers leave it 0, keeping their dispatch byte-identical.\n const q_offset = context?.qOffset ?? 0;\n\n // Softmax QK temperature. Defaults to 1/sqrt(head_dim) — byte-identical to\n // the legacy kernel-hardcoded scale, so every existing caller is unchanged.\n // Gemma 4 sets attn_scale:1.0 (its per-head QK-norm absorbs the scale).\n const attn_scale = (op.attributes.attn_scale as number) ?? 1 / Math.sqrt(head_dim);\n\n return buildUniformBuffer([\n T,\n S,\n num_q_heads,\n num_kv_heads,\n head_dim,\n position_offset,\n is_causal,\n q_offset,\n f32BitsToU32(attn_scale),\n ]);\n },\n};\n\n// ── Cross-Attention (encoder-decoder) ──\n//\n// Inputs: Q (decoder hidden) [T, num_q_heads*head_dim],\n// K/V (frozen encoder output) [S, num_kv_heads*head_dim].\n// Output: [T, num_q_heads*head_dim]. One workgroup per (q_pos, q_head).\nconst crossAttentionSpec: KernelSpec = {\n shaderCode: WGSL_CROSS_ATTENTION,\n entryPoint: \"main\",\n\n // @binding(0) Q: storage read\n // @binding(1) K: storage read (encoder output, frozen)\n // @binding(2) V: storage read (encoder output, frozen)\n // @binding(3) output: storage read_write\n // @binding(4) params: uniform\n bindings: [\n { type: \"storage-read\" },\n { type: \"storage-read\" },\n { type: \"storage-read\" },\n { type: \"storage-read-write\" },\n { type: \"uniform\" },\n ],\n\n getDispatchSize(op, resolvedShapes) {\n const num_q_heads = op.attributes.num_q_heads as number;\n // T = decoder query length (rows of the output / Q tensor)\n const outShape = resolvedShapes[op.outputs[0]];\n const T = outShape ? outShape[0] : (op.attributes.T as number);\n return [T, num_q_heads, 1];\n },\n\n buildParams(op, resolvedShapes) {\n // Params { T, S, num_q_heads, num_kv_heads, head_dim }\n const num_q_heads = op.attributes.num_q_heads as number;\n const num_kv_heads = op.attributes.num_kv_heads as number;\n const head_dim = op.attributes.head_dim as number;\n\n // T = decoder query length (rows of Q / output)\n const outShape = resolvedShapes[op.outputs[0]];\n const T = outShape ? outShape[0] : (op.attributes.T as number);\n\n // S = encoder length (rows of the frozen encoder K input)\n const kShape = resolvedShapes[op.inputs[1]];\n const S = kShape ? kShape[0] : (op.attributes.S as number);\n\n return buildUniformBuffer([T, S, num_q_heads, num_kv_heads, head_dim]);\n },\n};\n\n// ── KVCacheAppend Packed F16 (f32 src → packed u32 dst, Safari-safe) ──\n\nexport const KV_CACHE_APPEND_PACKED_F16_SPEC: KernelSpec = {\n shaderCode: WGSL_KV_CACHE_APPEND_PACKED_F16,\n entryPoint: \"main\",\n\n // @binding(0) src: storage read (f32)\n // @binding(1) dst: storage read_write (packed u32)\n // @binding(2) params: uniform\n bindings: [{ type: \"storage-read\" }, { type: \"storage-read-write\" }, { type: \"uniform\" }],\n\n getDispatchSize(op, resolvedShapes) {\n const width = op.attributes.width as number;\n const srcRef = op.attributes.T_tensor as string | undefined;\n let T: number;\n if (srcRef && resolvedShapes[srcRef]) {\n T = resolvedShapes[srcRef][0];\n } else {\n T = op.attributes.T as number;\n }\n // Each thread processes a pair of f32 values → half the dispatch count\n return [cdiv((T * width) / 2, 256), 1, 1];\n },\n\n buildParams(op, resolvedShapes, context) {\n const width = op.attributes.width as number;\n const seqPos = context?.seqPos ?? 0;\n const srcRef = op.attributes.T_tensor as string | undefined;\n let T: number;\n if (srcRef && resolvedShapes[srcRef]) {\n T = resolvedShapes[srcRef][0];\n } else {\n T = op.attributes.T as number;\n }\n // Same params as native f16 — kernel internally divides by 2\n return buildUniformBuffer([T * width, seqPos * width]);\n },\n};\n\n// ── Attention Packed F16 (packed u32 KV cache, Safari-safe) ──\n\nexport const ATTENTION_PACKED_F16_SPEC: KernelSpec = {\n shaderCode: WGSL_ATTENTION_PACKED_F16,\n entryPoint: \"main\",\n\n // @binding(0) Q: storage read (f32)\n // @binding(1) K: storage read (packed u32)\n // @binding(2) V: storage read (packed u32)\n // @binding(3) output: storage read_write (f32)\n // @binding(4) params: uniform\n bindings: [\n { type: \"storage-read\" },\n { type: \"storage-read\" },\n { type: \"storage-read\" },\n { type: \"storage-read-write\" },\n { type: \"uniform\" },\n ],\n\n getDispatchSize(op, resolvedShapes) {\n const num_q_heads = op.attributes.num_q_heads as number;\n const outShape = resolvedShapes[op.outputs[0]];\n const T = outShape ? outShape[0] : (op.attributes.T as number);\n return [T, num_q_heads, 1];\n },\n\n buildParams(op, resolvedShapes, context) {\n const num_q_heads = op.attributes.num_q_heads as number;\n const num_kv_heads = op.attributes.num_kv_heads as number;\n const head_dim = op.attributes.head_dim as number;\n const position_offset = context?.seqPos ?? (op.attributes.position_offset as number) ?? 0;\n\n const outShape = resolvedShapes[op.outputs[0]];\n const T = outShape ? outShape[0] : (op.attributes.T as number);\n\n const kvShape = resolvedShapes[op.inputs[1]];\n const S = kvShape ? kvShape[0] : T;\n\n // Default 1/sqrt(head_dim) → byte-identical to the legacy scale; Gemma4 = 1.0.\n const attn_scale = (op.attributes.attn_scale as number) ?? 1 / Math.sqrt(head_dim);\n\n return buildUniformBuffer([\n T,\n S,\n num_q_heads,\n num_kv_heads,\n head_dim,\n position_offset,\n f32BitsToU32(attn_scale),\n ]);\n },\n};\n\n// ── Softmax ──\n\nconst softmaxSpec: KernelSpec = {\n shaderCode: WGSL_SOFTMAX,\n entryPoint: \"main\",\n\n // @binding(0) input: storage read\n // @binding(1) output: storage read_write\n // @binding(2) params: uniform\n bindings: [{ type: \"storage-read\" }, { type: \"storage-read-write\" }, { type: \"uniform\" }],\n\n getDispatchSize(op) {\n // One workgroup per row\n const num_rows = op.attributes.num_rows as number;\n return [num_rows, 1, 1];\n },\n\n buildParams(op) {\n // Params { num_rows: u32, row_size: u32 }\n const num_rows = op.attributes.num_rows as number;\n const row_size = op.attributes.row_size as number;\n return buildUniformBuffer([num_rows, row_size]);\n },\n};\n\n// ── CausalConv1d ──\n\nconst causalConv1dSpec: KernelSpec = {\n shaderCode: WGSL_CAUSAL_CONV1D,\n entryPoint: \"main\",\n\n // @binding(0) input: storage read\n // @binding(1) weight: storage read\n // @binding(2) conv_state: storage read\n // @binding(3) output: storage read_write\n // @binding(4) params: uniform\n bindings: [\n { type: \"storage-read\" },\n { type: \"storage-read\" },\n { type: \"storage-read\" },\n { type: \"storage-read-write\" },\n { type: \"uniform\" },\n ],\n\n getDispatchSize(op, resolvedShapes) {\n const channels = op.attributes.channels as number;\n const ref = op.attributes.seq_len_tensor as string | undefined;\n let seq_len: number;\n if (ref && resolvedShapes[ref]) {\n seq_len = resolvedShapes[ref][0];\n } else {\n seq_len = op.attributes.seq_len as number;\n }\n return [cdiv(seq_len * channels, 256), 1, 1];\n },\n\n buildParams(op, resolvedShapes) {\n // Params { seq_len: u32, channels: u32, kernel_size: u32, _pad: u32 }\n const channels = op.attributes.channels as number;\n const kernel_size = op.attributes.kernel_size as number;\n const ref = op.attributes.seq_len_tensor as string | undefined;\n let seq_len: number;\n if (ref && resolvedShapes[ref]) {\n seq_len = resolvedShapes[ref][0];\n } else {\n seq_len = op.attributes.seq_len as number;\n }\n return buildUniformBuffer([seq_len, channels, kernel_size, 0]);\n },\n};\n\n// ── CausalConv1dSiLU (conv + fused SiLU) ──\n\nconst causalConv1dSiluSpec: KernelSpec = {\n shaderCode: WGSL_CAUSAL_CONV1D_SILU,\n entryPoint: \"main\",\n\n bindings: [\n { type: \"storage-read\" },\n { type: \"storage-read\" },\n { type: \"storage-read\" },\n { type: \"storage-read-write\" },\n { type: \"uniform\" },\n ],\n\n getDispatchSize(op, resolvedShapes) {\n const channels = op.attributes.channels as number;\n const ref = op.attributes.seq_len_tensor as string | undefined;\n let seq_len: number;\n if (ref && resolvedShapes[ref]) {\n seq_len = resolvedShapes[ref][0];\n } else {\n seq_len = op.attributes.seq_len as number;\n }\n return [cdiv(seq_len * channels, 256), 1, 1];\n },\n\n buildParams(op, resolvedShapes) {\n const channels = op.attributes.channels as number;\n const kernel_size = op.attributes.kernel_size as number;\n const ref = op.attributes.seq_len_tensor as string | undefined;\n let seq_len: number;\n if (ref && resolvedShapes[ref]) {\n seq_len = resolvedShapes[ref][0];\n } else {\n seq_len = op.attributes.seq_len as number;\n }\n return buildUniformBuffer([seq_len, channels, kernel_size, 0]);\n },\n};\n\n// ── CausalConv1dGated (conv + fused multiplicative output gate) ──\n\nconst causalConv1dGatedSpec: KernelSpec = {\n shaderCode: WGSL_CAUSAL_CONV1D_GATED,\n entryPoint: \"main\",\n\n // @binding(0) input @binding(1) weight @binding(2) conv_state\n // @binding(3) gate @binding(4) output @binding(5) params\n bindings: [\n { type: \"storage-read\" },\n { type: \"storage-read\" },\n { type: \"storage-read\" },\n { type: \"storage-read\" },\n { type: \"storage-read-write\" },\n { type: \"uniform\" },\n ],\n\n getDispatchSize(op, resolvedShapes) {\n const channels = op.attributes.channels as number;\n const ref = op.attributes.seq_len_tensor as string | undefined;\n let seq_len: number;\n if (ref && resolvedShapes[ref]) {\n seq_len = resolvedShapes[ref][0];\n } else {\n seq_len = op.attributes.seq_len as number;\n }\n return [cdiv(seq_len * channels, 256), 1, 1];\n },\n\n buildParams(op, resolvedShapes) {\n const channels = op.attributes.channels as number;\n const kernel_size = op.attributes.kernel_size as number;\n const ref = op.attributes.seq_len_tensor as string | undefined;\n let seq_len: number;\n if (ref && resolvedShapes[ref]) {\n seq_len = resolvedShapes[ref][0];\n } else {\n seq_len = op.attributes.seq_len as number;\n }\n return buildUniformBuffer([seq_len, channels, kernel_size, 0]);\n },\n};\n\n// ── SigmoidGate ──\n\nconst sigmoidGateSpec: KernelSpec = {\n shaderCode: WGSL_SIGMOID_GATE,\n entryPoint: \"main\",\n\n // @binding(0) x: storage read\n // @binding(1) gate: storage read\n // @binding(2) output: storage read_write\n // @binding(3) params: uniform\n bindings: [\n { type: \"storage-read\" },\n { type: \"storage-read\" },\n { type: \"storage-read-write\" },\n { type: \"uniform\" },\n ],\n\n getDispatchSize(op, resolvedShapes) {\n const count = outputElementCount(op, resolvedShapes);\n return [cdiv(count, 256), 1, 1];\n },\n\n buildParams(op, resolvedShapes) {\n const count = outputElementCount(op, resolvedShapes);\n // Params { count: u32 }\n return buildUniformBuffer([count]);\n },\n};\n\n// ── MambaSSM ──\n\nconst mambaSSMSpec: KernelSpec = {\n shaderCode: WGSL_MAMBA_SSM,\n entryPoint: \"main\",\n\n // @binding(0) qkv_conv: storage read\n // @binding(1) a_input: storage read\n // @binding(2) b_input: storage read\n // @binding(3) A_log: storage read\n // @binding(4) dt_bias: storage read\n // @binding(5) ssm_state: storage read_write\n // @binding(6) output: storage read_write\n // @binding(7) params: uniform\n bindings: [\n { type: \"storage-read\" }, // qkv_conv\n { type: \"storage-read\" }, // a_input\n { type: \"storage-read\" }, // b_input\n { type: \"storage-read\" }, // A_log\n { type: \"storage-read\" }, // dt_bias\n { type: \"storage-read-write\" }, // ssm_state\n { type: \"storage-read-write\" }, // output\n { type: \"uniform\" }, // params\n ],\n\n getDispatchSize(op) {\n const num_heads = op.attributes.num_heads as number;\n return [num_heads, 1, 1];\n },\n\n buildParams(op, resolvedShapes) {\n const num_heads = op.attributes.num_heads as number;\n const key_dim = op.attributes.key_dim as number;\n const val_dim = op.attributes.val_dim as number;\n const qkv_dim = op.attributes.qkv_dim as number;\n // Resolve T from qkv_conv tensor reference\n const qkvRef = op.attributes.T_tensor as string | undefined;\n let T: number;\n if (qkvRef && resolvedShapes[qkvRef]) {\n T = resolvedShapes[qkvRef][0];\n } else {\n T = op.attributes.T as number;\n }\n return buildUniformBuffer([T, num_heads, key_dim, val_dim, qkv_dim, 0, 0, 0]);\n },\n};\n\n// ── MambaSSM with f16 state (Dawn only — selected in executor when hasF16) ──\n\nexport const MAMBA_SSM_F16_SPEC: KernelSpec = {\n ...mambaSSMSpec,\n shaderCode: WGSL_MAMBA_SSM_F16,\n};\n\n// ── KVCacheAppend ──\n\nconst kvCacheAppendSpec: KernelSpec = {\n shaderCode: WGSL_KV_CACHE_APPEND,\n entryPoint: \"main\",\n\n // @binding(0) src: storage read\n // @binding(1) dst: storage read_write\n // @binding(2) params: uniform\n bindings: [{ type: \"storage-read\" }, { type: \"storage-read-write\" }, { type: \"uniform\" }],\n\n getDispatchSize(op, resolvedShapes) {\n const width = op.attributes.width as number;\n const srcRef = op.attributes.T_tensor as string | undefined;\n let T: number;\n if (srcRef && resolvedShapes[srcRef]) {\n T = resolvedShapes[srcRef][0];\n } else {\n T = op.attributes.T as number;\n }\n return [cdiv(T * width, 256), 1, 1];\n },\n\n buildParams(op, resolvedShapes, context) {\n const width = op.attributes.width as number;\n const seqPos = context?.seqPos ?? 0;\n const srcRef = op.attributes.T_tensor as string | undefined;\n let T: number;\n if (srcRef && resolvedShapes[srcRef]) {\n T = resolvedShapes[srcRef][0];\n } else {\n T = op.attributes.T as number;\n }\n return buildUniformBuffer([T * width, seqPos * width]);\n },\n};\n\n// ── Dual KVCacheAppend (f32 K+V into both caches, one dispatch) ──\n\nexport const DUAL_KV_CACHE_APPEND_SPEC: KernelSpec = {\n shaderCode: WGSL_DUAL_KV_CACHE_APPEND,\n entryPoint: \"main\",\n\n // @binding(0) src_k, (1) src_v, (2) dst_k, (3) dst_v, (4) params\n bindings: [\n { type: \"storage-read\" },\n { type: \"storage-read\" },\n { type: \"storage-read-write\" },\n { type: \"storage-read-write\" },\n { type: \"uniform\" },\n ],\n\n getDispatchSize(op, resolvedShapes) {\n const width = op.attributes.width as number;\n const srcRef = op.attributes.T_tensor as string | undefined;\n const T =\n srcRef && resolvedShapes[srcRef] ? resolvedShapes[srcRef][0] : (op.attributes.T as number);\n return [cdiv(T * width, 256), 1, 1];\n },\n\n buildParams(op, resolvedShapes, context) {\n const width = op.attributes.width as number;\n const seqPos = context?.seqPos ?? 0;\n const srcRef = op.attributes.T_tensor as string | undefined;\n const T =\n srcRef && resolvedShapes[srcRef] ? resolvedShapes[srcRef][0] : (op.attributes.T as number);\n return buildUniformBuffer([T * width, seqPos * width]);\n },\n};\n\n// ── Dual KVCacheAppend native-f16 (f32 K+V → f16 caches, one dispatch) ──\n\nexport const DUAL_KV_CACHE_APPEND_F16_SPEC: KernelSpec = {\n ...DUAL_KV_CACHE_APPEND_SPEC,\n shaderCode: WGSL_DUAL_KV_CACHE_APPEND_F16,\n};\n\n// ── Dual KVCacheAppend packed-f16 (Safari-safe, one dispatch) ──\n\nexport const DUAL_KV_CACHE_APPEND_PACKED_F16_SPEC: KernelSpec = {\n ...DUAL_KV_CACHE_APPEND_SPEC,\n shaderCode: WGSL_DUAL_KV_CACHE_APPEND_PACKED_F16,\n // Each thread handles one f16 PAIR per cache → grid over count/2.\n getDispatchSize(op, resolvedShapes) {\n const width = op.attributes.width as number;\n const srcRef = op.attributes.T_tensor as string | undefined;\n const T =\n srcRef && resolvedShapes[srcRef] ? resolvedShapes[srcRef][0] : (op.attributes.T as number);\n return [cdiv((T * width) >> 1, 256), 1, 1];\n },\n};\n\n// ── KVCacheAppend F16 (f32 src → f16 dst) ──\n\nexport const KV_CACHE_APPEND_F16_SPEC: KernelSpec = {\n shaderCode: WGSL_KV_CACHE_APPEND_F16,\n entryPoint: \"main\",\n\n // @binding(0) src: storage read (f32)\n // @binding(1) dst: storage read_write (f16)\n // @binding(2) params: uniform\n bindings: [{ type: \"storage-read\" }, { type: \"storage-read-write\" }, { type: \"uniform\" }],\n\n getDispatchSize(op, resolvedShapes) {\n const width = op.attributes.width as number;\n const srcRef = op.attributes.T_tensor as string | undefined;\n let T: number;\n if (srcRef && resolvedShapes[srcRef]) {\n T = resolvedShapes[srcRef][0];\n } else {\n T = op.attributes.T as number;\n }\n return [cdiv(T * width, 256), 1, 1];\n },\n\n buildParams(op, resolvedShapes, context) {\n const width = op.attributes.width as number;\n const seqPos = context?.seqPos ?? 0;\n const srcRef = op.attributes.T_tensor as string | undefined;\n let T: number;\n if (srcRef && resolvedShapes[srcRef]) {\n T = resolvedShapes[srcRef][0];\n } else {\n T = op.attributes.T as number;\n }\n return buildUniformBuffer([T * width, seqPos * width]);\n },\n};\n\n// ── Attention F16 (f16 KV cache) ──\n\nexport const ATTENTION_F16_SPEC: KernelSpec = {\n shaderCode: WGSL_ATTENTION_F16,\n entryPoint: \"main\",\n\n // @binding(0) Q: storage read (f32)\n // @binding(1) K: storage read (f16)\n // @binding(2) V: storage read (f16)\n // @binding(3) output: storage read_write (f32)\n // @binding(4) params: uniform\n bindings: [\n { type: \"storage-read\" },\n { type: \"storage-read\" },\n { type: \"storage-read\" },\n { type: \"storage-read-write\" },\n { type: \"uniform\" },\n ],\n\n getDispatchSize(op, resolvedShapes) {\n const num_q_heads = op.attributes.num_q_heads as number;\n const outShape = resolvedShapes[op.outputs[0]];\n const T = outShape ? outShape[0] : (op.attributes.T as number);\n return [T, num_q_heads, 1];\n },\n\n buildParams(op, resolvedShapes, context) {\n const num_q_heads = op.attributes.num_q_heads as number;\n const num_kv_heads = op.attributes.num_kv_heads as number;\n const head_dim = op.attributes.head_dim as number;\n const position_offset = context?.seqPos ?? (op.attributes.position_offset as number) ?? 0;\n\n const outShape = resolvedShapes[op.outputs[0]];\n const T = outShape ? outShape[0] : (op.attributes.T as number);\n\n const kvShape = resolvedShapes[op.inputs[1]];\n const S = kvShape ? kvShape[0] : T;\n\n // Default 1/sqrt(head_dim) → byte-identical to the legacy scale; Gemma4 = 1.0.\n const attn_scale = (op.attributes.attn_scale as number) ?? 1 / Math.sqrt(head_dim);\n\n return buildUniformBuffer([\n T,\n S,\n num_q_heads,\n num_kv_heads,\n head_dim,\n position_offset,\n f32BitsToU32(attn_scale),\n ]);\n },\n};\n\n// ── ConvStateUpdate ──\n\nconst convStateUpdateSpec: KernelSpec = {\n shaderCode: WGSL_CONV_STATE_UPDATE,\n entryPoint: \"main\",\n\n // @binding(0) input: storage read\n // @binding(1) conv_state: storage read_write\n // @binding(2) params: uniform\n bindings: [{ type: \"storage-read\" }, { type: \"storage-read-write\" }, { type: \"uniform\" }],\n\n getDispatchSize(op) {\n const channels = op.attributes.channels as number;\n return [cdiv(channels, 256), 1, 1];\n },\n\n buildParams(op, resolvedShapes) {\n const channels = op.attributes.channels as number;\n const state_size = op.attributes.state_size as number;\n const srcRef = op.attributes.T_tensor as string | undefined;\n let seq_len: number;\n if (srcRef && resolvedShapes[srcRef]) {\n seq_len = resolvedShapes[srcRef][0];\n } else {\n seq_len = op.attributes.T as number;\n }\n return buildUniformBuffer([seq_len, channels, state_size, 0]);\n },\n};\n\n// ── Registry ────────────────────────────────────────────────────────────\n\n/**\n * Maps each implemented OpType to its kernel spec.\n *\n * Only ops with WGSL shader implementations are included.\n * Stubbed ops (MoERouter, ExpertMatMul, Conv2d, AvgPool2d,\n * CrossAttention, Gather, Reshape, Transpose, Concat) are not present.\n */\n// ── Audio codec-decoder kernels (OmniVoice / HiggsAudioV2 DAC vocoder) ──\n//\n// New vocoder primitives for native TTS decode (token grid -> 24kHz PCM). The\n// DAC decoder is: Conv1dFull (in) -> 5 upsample blocks (ConvTranspose1d + Snake1d\n// + dilated residual Conv1dFull) -> Snake1d -> Conv1dFull (out, NO tanh). These\n// three kernels cover every op in that path.\n//\n// Validated bit-exact vs NumPy reference (max|err| ~2e-7, f32 rounding only) in\n// scripts/engine/test-codec-kernels.mjs across: kernel-7 dilation-1/3, pointwise\n// kernel-1, transposed stride-4 (even) and stride-3 (odd, output_padding=1), and\n// per-channel Snake. Mobile-safe: flat workgroup, no select(), no exp(), no\n// shared memory, 1 thread per output element.\n\nconst WGSL_CONV1D_FULL = `\\\n// Full cross-channel 1D convolution: x[Cin,L] * w[Cout,Cin,K] + bias[Cout]\n// -> y[Cout,Lout], Lout = (L + 2*padding - dilation*(K-1) - 1)/stride + 1\n// One thread per (oc, ot) output element.\nstruct Params {\n Cin: u32, Cout: u32, L: u32, Lout: u32,\n K: u32, stride: u32, padding: u32, dilation: u32,\n}\n@group(0) @binding(0) var<storage, read> x: array<f32>;\n@group(0) @binding(1) var<storage, read> w: array<f32>;\n@group(0) @binding(2) var<storage, read> bias: array<f32>;\n@group(0) @binding(3) var<storage, read_write> y: array<f32>;\n@group(0) @binding(4) var<storage, read> params: Params;\n\n@compute @workgroup_size(64)\nfn main(@builtin(global_invocation_id) gid: vec3u) {\n let idx = gid.x;\n let total = params.Cout * params.Lout;\n if (idx >= total) { return; }\n let oc = idx / params.Lout;\n let ot = idx % params.Lout;\n let base = i32(ot * params.stride) - i32(params.padding);\n var acc: f32 = 0.0;\n for (var ic: u32 = 0u; ic < params.Cin; ic = ic + 1u) {\n let x_row = ic * params.L;\n let w_row = (oc * params.Cin + ic) * params.K;\n for (var k: u32 = 0u; k < params.K; k = k + 1u) {\n let it = base + i32(k * params.dilation);\n if (it >= 0 && it < i32(params.L)) {\n acc = acc + x[x_row + u32(it)] * w[w_row + k];\n }\n }\n }\n y[oc * params.Lout + ot] = acc + bias[oc];\n}\n`;\n\nconst WGSL_CONV_TRANSPOSE1D = `\\\n// Transposed (strided) 1D convolution: x[Cin,L] * w[Cin,Cout,K] + bias[Cout]\n// -> y[Cout,Lout], Lout = (L-1)*stride - 2*padding + (K-1) + output_padding + 1\n// Gather form (one thread per output): for full-buffer position op = ot+padding,\n// sum over (ic,k) where op-k is a non-negative multiple of stride -> it=(op-k)/stride.\n// HiggsAudioV2 sets output_padding = stride % 2 on every DAC upsample block.\nstruct Params {\n Cin: u32, Cout: u32, L: u32, Lout: u32,\n K: u32, stride: u32, padding: u32, out_pad: u32,\n}\n@group(0) @binding(0) var<storage, read> x: array<f32>;\n@group(0) @binding(1) var<storage, read> w: array<f32>;\n@group(0) @binding(2) var<storage, read> bias: array<f32>;\n@group(0) @binding(3) var<storage, read_write> y: array<f32>;\n@group(0) @binding(4) var<storage, read> params: Params;\n\n@compute @workgroup_size(64)\nfn main(@builtin(global_invocation_id) gid: vec3u) {\n let idx = gid.x;\n let total = params.Cout * params.Lout;\n if (idx >= total) { return; }\n let oc = idx / params.Lout;\n let ot = idx % params.Lout;\n let op = i32(ot + params.padding);\n var acc: f32 = 0.0;\n for (var ic: u32 = 0u; ic < params.Cin; ic = ic + 1u) {\n let x_row = ic * params.L;\n let w_row = (ic * params.Cout + oc) * params.K;\n for (var k: u32 = 0u; k < params.K; k = k + 1u) {\n let num = op - i32(k);\n if (num >= 0 && (num % i32(params.stride)) == 0) {\n let it = num / i32(params.stride);\n if (it >= 0 && it < i32(params.L)) {\n acc = acc + x[x_row + u32(it)] * w[w_row + k];\n }\n }\n }\n }\n y[oc * params.Lout + ot] = acc + bias[oc];\n}\n`;\n\nconst WGSL_SNAKE1D = `\\\n// Snake activation (learned per-channel periodic): y = x + (1/(alpha+1e-9)) * sin(alpha*x)^2\nstruct Params { C: u32, L: u32 }\n@group(0) @binding(0) var<storage, read> x: array<f32>;\n@group(0) @binding(1) var<storage, read> alpha: array<f32>;\n@group(0) @binding(2) var<storage, read_write> y: array<f32>;\n@group(0) @binding(3) var<storage, read> params: Params;\n\n@compute @workgroup_size(64)\nfn main(@builtin(global_invocation_id) gid: vec3u) {\n let idx = gid.x;\n let total = params.C * params.L;\n if (idx >= total) { return; }\n let c = idx / params.L;\n let a = alpha[c];\n let s = sin(a * x[idx]);\n y[idx] = x[idx] + (1.0 / (a + 1e-9)) * s * s;\n}\n`;\n\n// ── Moonshine STT frontend kernels (Tanh, Transpose, GroupNorm) ──\n\nconst WGSL_TANH = `\\\n// Elementwise tanh (Moonshine conv1 activation). Clamp the argument: on Metal/Dawn\n// tanh() of a large magnitude returns NaN (internal exp overflows). |arg|>=10\n// already saturates to +/-1, so clamping to +/-20 is numerically exact and safe.\nstruct Params { count: u32 }\n@group(0) @binding(0) var<storage, read> input: array<f32>;\n@group(0) @binding(1) var<storage, read_write> output: array<f32>;\n@group(0) @binding(2) var<storage, read> params: Params;\n\n@compute @workgroup_size(256)\nfn main(@builtin(global_invocation_id) gid: vec3u) {\n let idx = gid.x;\n if (idx >= params.count) { return; }\n output[idx] = tanh(clamp(input[idx], -20.0, 20.0));\n}\n`;\n\nconst WGSL_TRANSPOSE = `\\\n// Transpose a row-major [rows, cols] tensor into [cols, rows].\n// Moonshine conv output is [C, L] (channels-major); the encoder transformer wants\n// [L, C] (token-major). One thread per output element; bounds-checked.\nstruct Params { rows: u32, cols: u32 }\n@group(0) @binding(0) var<storage, read> input: array<f32>;\n@group(0) @binding(1) var<storage, read_write> output: array<f32>;\n@group(0) @binding(2) var<storage, read> params: Params;\n\n@compute @workgroup_size(256)\nfn main(@builtin(global_invocation_id) gid: vec3u) {\n let idx = gid.x;\n let total = params.rows * params.cols;\n if (idx >= total) { return; }\n // output is [cols, rows]: out[c, r] = in[r, c]\n let c = idx / params.rows;\n let r = idx % params.rows;\n output[idx] = input[r * params.cols + c];\n}\n`;\n\nconst WGSL_GROUPNORM = `\\\n// GroupNorm(num_groups=1) over the channel axis of a [C, L] tensor: normalize each\n// channel-position element by the mean/variance computed across ALL C*L elements\n// (a single group), then scale/shift per-channel: y[c,l] = (x[c,l]-mu)/sqrt(var+eps)\n// * weight[c] + bias[c]. Moonshine's encoder frontend applies this after conv1.\n// One workgroup reduces the whole [C, L] tensor (C*L <= a few k for short clips).\nstruct Params { channels: u32, length: u32, eps_bits: u32, _pad: u32 }\n@group(0) @binding(0) var<storage, read> input: array<f32>;\n@group(0) @binding(1) var<storage, read> weight: array<f32>;\n@group(0) @binding(2) var<storage, read> bias: array<f32>;\n@group(0) @binding(3) var<storage, read_write> output: array<f32>;\n@group(0) @binding(4) var<storage, read> params: Params;\n\nvar<workgroup> shared_sum: array<f32, 256>;\nvar<workgroup> shared_sq: array<f32, 256>;\n\n@compute @workgroup_size(256)\nfn main(@builtin(local_invocation_id) lid: vec3u) {\n let tid = lid.x;\n let total = params.channels * params.length;\n let eps = bitcast<f32>(params.eps_bits);\n\n var local_sum: f32 = 0.0;\n var local_sq: f32 = 0.0;\n var i = tid;\n while (i < total) {\n let v = input[i];\n local_sum += v;\n local_sq += v * v;\n i += 256u;\n }\n shared_sum[tid] = local_sum;\n shared_sq[tid] = local_sq;\n workgroupBarrier();\n\n var stride: u32 = 128u;\n while (stride > 0u) {\n if (tid < stride) {\n shared_sum[tid] += shared_sum[tid + stride];\n shared_sq[tid] += shared_sq[tid + stride];\n }\n workgroupBarrier();\n stride /= 2u;\n }\n\n let n = f32(total);\n let mean = shared_sum[0] / n;\n let variance = shared_sq[0] / n - mean * mean;\n let inv_std = 1.0 / sqrt(variance + eps);\n workgroupBarrier();\n\n i = tid;\n while (i < total) {\n let c = i / params.length; // channel index (row-major [C, L])\n output[i] = (input[i] - mean) * inv_std * weight[c] + bias[c];\n i += 256u;\n }\n}\n`;\n\nconst WGSL_LAYERNORM_NO_BIAS = `\\\n// LayerNorm without a bias term: output = ((x - mean) / sqrt(var + eps)) * weight.\n// Moonshine's encoder/decoder norms are weight-only (no .bias in the checkpoint),\n// so the node binds only [input, weight] — this variant drops the bias binding to\n// keep the executor's positional input->binding mapping correct.\nstruct Params {\n seq_len: u32,\n hidden_size: u32,\n eps_bits: u32,\n _pad: u32,\n}\n@group(0) @binding(0) var<storage, read> input: array<f32>;\n@group(0) @binding(1) var<storage, read> weight: array<f32>;\n@group(0) @binding(2) var<storage, read_write> output: array<f32>;\n@group(0) @binding(3) var<storage, read> params: Params;\n\nvar<workgroup> shared_sum: array<f32, 256>;\nvar<workgroup> shared_sq_sum: array<f32, 256>;\n\n@compute @workgroup_size(256)\nfn main(\n @builtin(local_invocation_id) lid: vec3u,\n @builtin(workgroup_id) wid: vec3u,\n) {\n let row = wid.x;\n if (row >= params.seq_len) { return; }\n\n let tid = lid.x;\n let row_offset = row * params.hidden_size;\n let eps = bitcast<f32>(params.eps_bits);\n\n var local_sum: f32 = 0.0;\n var i = tid;\n while (i < params.hidden_size) {\n local_sum += input[row_offset + i];\n i += 256u;\n }\n shared_sum[tid] = local_sum;\n workgroupBarrier();\n\n var stride: u32 = 128u;\n while (stride > 0u) {\n if (tid < stride) { shared_sum[tid] += shared_sum[tid + stride]; }\n workgroupBarrier();\n stride /= 2u;\n }\n let mean = shared_sum[0] / f32(params.hidden_size);\n\n var local_sq: f32 = 0.0;\n i = tid;\n while (i < params.hidden_size) {\n let diff = input[row_offset + i] - mean;\n local_sq += diff * diff;\n i += 256u;\n }\n shared_sq_sum[tid] = local_sq;\n workgroupBarrier();\n\n stride = 128u;\n while (stride > 0u) {\n if (tid < stride) { shared_sq_sum[tid] += shared_sq_sum[tid + stride]; }\n workgroupBarrier();\n stride /= 2u;\n }\n let inv_std = 1.0 / sqrt(shared_sq_sum[0] / f32(params.hidden_size) + eps);\n\n i = tid;\n while (i < params.hidden_size) {\n output[row_offset + i] = (input[row_offset + i] - mean) * inv_std * weight[i];\n i += 256u;\n }\n}\n`;\n\n/**\n * LayerNorm with no bias term (Moonshine). Binds [input, weight, output, params]\n * — one fewer storage-read than the standard layernormSpec. The executor selects\n * this variant when a LayerNorm node carries only 2 inputs.\n */\nexport const LAYERNORM_NO_BIAS_SPEC: KernelSpec = {\n shaderCode: WGSL_LAYERNORM_NO_BIAS,\n entryPoint: \"main\",\n bindings: [\n { type: \"storage-read\" },\n { type: \"storage-read\" },\n { type: \"storage-read-write\" },\n { type: \"uniform\" },\n ],\n getDispatchSize(op, resolvedShapes) {\n const hidden_size = op.attributes.hidden_size as number;\n const ref = op.attributes.seq_len_tensor as string | undefined;\n let seq_len: number;\n if (ref && resolvedShapes[ref]) {\n const shape = resolvedShapes[ref];\n seq_len = shape.reduce((a, b) => a * b, 1) / hidden_size;\n } else {\n seq_len = op.attributes.seq_len as number;\n }\n return [seq_len, 1, 1];\n },\n buildParams(op, resolvedShapes) {\n const hidden_size = op.attributes.hidden_size as number;\n const eps = op.attributes.eps as number;\n const ref = op.attributes.seq_len_tensor as string | undefined;\n let seq_len: number;\n if (ref && resolvedShapes[ref]) {\n const shape = resolvedShapes[ref];\n seq_len = shape.reduce((a, b) => a * b, 1) / hidden_size;\n } else {\n seq_len = op.attributes.seq_len as number;\n }\n return buildUniformBuffer([seq_len, hidden_size, f32BitsToU32(eps), 0]);\n },\n};\n\nconst tanhSpec: KernelSpec = {\n shaderCode: WGSL_TANH,\n entryPoint: \"main\",\n bindings: [{ type: \"storage-read\" }, { type: \"storage-read-write\" }, { type: \"uniform\" }],\n getDispatchSize(op, resolvedShapes) {\n const count = outputElementCount(op, resolvedShapes);\n return [cdiv(count, 256), 1, 1];\n },\n buildParams(op, resolvedShapes) {\n const count = outputElementCount(op, resolvedShapes);\n return buildUniformBuffer([count]);\n },\n};\n\nconst transposeSpec: KernelSpec = {\n shaderCode: WGSL_TRANSPOSE,\n entryPoint: \"main\",\n bindings: [{ type: \"storage-read\" }, { type: \"storage-read-write\" }, { type: \"uniform\" }],\n getDispatchSize(op, resolvedShapes) {\n const count = outputElementCount(op, resolvedShapes);\n return [cdiv(count, 256), 1, 1];\n },\n buildParams(op, resolvedShapes) {\n // input shape [rows, cols]; output is [cols, rows].\n const inShape = resolvedShapes[op.inputs[0]];\n const rows = inShape ? inShape[0] : (op.attributes.rows as number);\n const cols = inShape ? inShape[1] : (op.attributes.cols as number);\n return buildUniformBuffer([rows, cols]);\n },\n};\n\nconst groupnormSpec: KernelSpec = {\n shaderCode: WGSL_GROUPNORM,\n entryPoint: \"main\",\n bindings: [\n { type: \"storage-read\" },\n { type: \"storage-read\" },\n { type: \"storage-read\" },\n { type: \"storage-read-write\" },\n { type: \"uniform\" },\n ],\n // One workgroup reduces the full [C, L] tensor.\n getDispatchSize() {\n return [1, 1, 1];\n },\n buildParams(op, resolvedShapes) {\n const channels = op.attributes.channels as number;\n const inShape = resolvedShapes[op.inputs[0]];\n const length = inShape ? inShape[1] : (op.attributes.length as number);\n const eps = (op.attributes.eps as number) ?? 1e-5;\n return buildUniformBuffer([channels, length, f32BitsToU32(eps), 0]);\n },\n};\n\nconst conv1dFullSpec: KernelSpec = {\n shaderCode: WGSL_CONV1D_FULL,\n entryPoint: \"main\",\n bindings: [\n { type: \"storage-read\" },\n { type: \"storage-read\" },\n { type: \"storage-read\" },\n { type: \"storage-read-write\" },\n { type: \"uniform\" },\n ],\n getDispatchSize(op) {\n const Cout = op.attributes.Cout as number;\n const Lout = op.attributes.Lout as number;\n return [cdiv(Cout * Lout, 64), 1, 1];\n },\n buildParams(op) {\n return buildUniformBuffer([\n op.attributes.Cin as number,\n op.attributes.Cout as number,\n op.attributes.L as number,\n op.attributes.Lout as number,\n op.attributes.K as number,\n op.attributes.stride as number,\n op.attributes.padding as number,\n op.attributes.dilation as number,\n ]);\n },\n};\n\nconst convTranspose1dSpec: KernelSpec = {\n shaderCode: WGSL_CONV_TRANSPOSE1D,\n entryPoint: \"main\",\n bindings: [\n { type: \"storage-read\" },\n { type: \"storage-read\" },\n { type: \"storage-read\" },\n { type: \"storage-read-write\" },\n { type: \"uniform\" },\n ],\n getDispatchSize(op) {\n const Cout = op.attributes.Cout as number;\n const Lout = op.attributes.Lout as number;\n return [cdiv(Cout * Lout, 64), 1, 1];\n },\n buildParams(op) {\n return buildUniformBuffer([\n op.attributes.Cin as number,\n op.attributes.Cout as number,\n op.attributes.L as number,\n op.attributes.Lout as number,\n op.attributes.K as number,\n op.attributes.stride as number,\n op.attributes.padding as number,\n op.attributes.output_padding as number,\n ]);\n },\n};\n\nconst snake1dSpec: KernelSpec = {\n shaderCode: WGSL_SNAKE1D,\n entryPoint: \"main\",\n bindings: [\n { type: \"storage-read\" },\n { type: \"storage-read\" },\n { type: \"storage-read-write\" },\n { type: \"uniform\" },\n ],\n getDispatchSize(op) {\n const C = op.attributes.C as number;\n const L = op.attributes.L as number;\n return [cdiv(C * L, 64), 1, 1];\n },\n buildParams(op) {\n return buildUniformBuffer([op.attributes.C as number, op.attributes.L as number]);\n },\n};\n\n// ════════════════════════════════════════════════════════════════════════\n// Kani-TTS-2 NanoCodec (NVIDIA NeMo, 22 kHz 0.6 kbps 12.5 fps) decoder kernels.\n// Validated bit-exact vs the real MLX NanoCodec via scripts/engine/test-nanocodec\n// -decode.mjs (max|err| 4.2e-6, f32 rounding only). Three new ops beyond the DAC\n// cluster above:\n// - FSQDequant: code idx -> per-dim continuous latent (finite scalar quantization,\n// 4 groups x 4 dims, levels [9,8,8,7], mixed-radix base [1,9,72,576]).\n// - HalfSnake1d: snake on the first C/2 channels, leaky-relu(0.01) on the rest.\n// - ConvTranspose1dDepthwise: causal depthwise (groups=Cout) transposed conv —\n// insert (stride-1) zeros, cross-correlate the FLIPPED kernel, right-trim.\n// The causal full conv reuses Conv1dFull with padding=(K-1)*dilation, Lout=L.\n// Mobile-safe: flat workgroup_size(64), no select(), no shared memory, no exp(),\n// 1 thread per output element, no `enable f16`.\n// ════════════════════════════════════════════════════════════════════════\n\nconst WGSL_FSQ_DEQUANT = `\\\n// Finite-scalar-quantization decode. codes[groups,T] (u32) -> latent[groups*dims,T]\n// (f32). Channel ch = g*dims + d; per-dim level/base passed as small constants.\nstruct Params {\n groups: u32, dims: u32, T: u32, _pad: u32,\n l0: u32, l1: u32, l2: u32, l3: u32,\n b0: u32, b1: u32, b2: u32, b3: u32,\n}\n@group(0) @binding(0) var<storage, read> codes: array<u32>; // [groups, T]\n@group(0) @binding(1) var<storage, read_write> latent: array<f32>; // [groups*dims, T]\n@group(0) @binding(2) var<storage, read> params: Params;\n\n@compute @workgroup_size(64)\nfn main(@builtin(global_invocation_id) gid: vec3u) {\n let total = params.groups * params.dims * params.T;\n if (gid.x >= total) { return; }\n let ch = gid.x / params.T;\n let t = gid.x % params.T;\n let d = ch % params.dims;\n let g = ch / params.dims;\n var lvl: u32 = params.l0;\n var base: u32 = params.b0;\n if (d == 1u) { lvl = params.l1; base = params.b1; }\n else if (d == 2u) { lvl = params.l2; base = params.b2; }\n else if (d == 3u) { lvl = params.l3; base = params.b3; }\n let idx = codes[g * params.T + t];\n let nonneg = (idx / base) % lvl;\n let scale = lvl / 2u;\n latent[ch * params.T + t] = (f32(nonneg) - f32(scale)) / f32(scale);\n}\n`;\n\nconst WGSL_HALF_SNAKE1D = `\\\n// HalfSnake: first half channels -> snake(x; alpha); rest -> leaky_relu(x, 0.01).\nstruct Params { C: u32, L: u32, half: u32, _pad: u32 }\n@group(0) @binding(0) var<storage, read> x: array<f32>; // [C, L]\n@group(0) @binding(1) var<storage, read> alpha: array<f32>; // [half]\n@group(0) @binding(2) var<storage, read_write> y: array<f32>; // [C, L]\n@group(0) @binding(3) var<storage, read> params: Params;\n\n@compute @workgroup_size(64)\nfn main(@builtin(global_invocation_id) gid: vec3u) {\n let total = params.C * params.L;\n if (gid.x >= total) { return; }\n let c = gid.x / params.L;\n let v = x[gid.x];\n if (c < params.half) {\n let a = alpha[c];\n let s = sin(a * v);\n y[gid.x] = v + (1.0 / (a + 1e-9)) * s * s;\n } else {\n if (v >= 0.0) { y[gid.x] = v; } else { y[gid.x] = 0.01 * v; }\n }\n}\n`;\n\nconst WGSL_CONV_TRANSPOSE1D_DW = `\\\n// Causal depthwise (groups = Cout) transposed conv. Weight in PyTorch\n// ConvTranspose1d layout [Cin, 1, K] (out/groups = 1); each output channel oc is fed\n// by cpg = Cin/Cout consecutive input channels (oc*cpg .. oc*cpg+cpg-1). Matches MLX\n// CausalConvTranspose1d: insert (stride-1) zeros, pad (K-1) both sides, cross-correlate\n// the FLIPPED kernel, then trim (K-stride) from the right. Output length Lout = L*stride.\n// Gather form (one thread per (oc, ot)): contributions where it*stride = ot+k-(K-1),\n// 0<=k<K, with flipped weight w[K-1-k].\nstruct Params { Cin: u32, Cout: u32, L: u32, Lout: u32, K: u32, stride: u32, cpg: u32, _pad: u32 }\n@group(0) @binding(0) var<storage, read> x: array<f32>; // [Cin, L]\n@group(0) @binding(1) var<storage, read> w: array<f32>; // [Cin, 1, K]\n@group(0) @binding(2) var<storage, read> bias: array<f32>; // [Cout]\n@group(0) @binding(3) var<storage, read_write> y: array<f32>; // [Cout, Lout]\n@group(0) @binding(4) var<storage, read> params: Params;\n\n@compute @workgroup_size(64)\nfn main(@builtin(global_invocation_id) gid: vec3u) {\n let total = params.Cout * params.Lout;\n if (gid.x >= total) { return; }\n let oc = gid.x / params.Lout;\n let ot = gid.x % params.Lout;\n var acc: f32 = 0.0;\n for (var j: u32 = 0u; j < params.cpg; j = j + 1u) {\n let ic = oc * params.cpg + j;\n let x_row = ic * params.L;\n let w_row = ic * params.K;\n for (var k: u32 = 0u; k < params.K; k = k + 1u) {\n let num = i32(ot) + i32(k) - (i32(params.K) - 1);\n if (num >= 0 && (num % i32(params.stride)) == 0) {\n let it = num / i32(params.stride);\n if (it >= 0 && it < i32(params.L)) {\n acc = acc + x[x_row + u32(it)] * w[w_row + (params.K - 1u - k)];\n }\n }\n }\n }\n y[oc * params.Lout + ot] = acc + bias[oc];\n}\n`;\n\nconst fsqDequantSpec: KernelSpec = {\n shaderCode: WGSL_FSQ_DEQUANT,\n entryPoint: \"main\",\n bindings: [{ type: \"storage-read\" }, { type: \"storage-read-write\" }, { type: \"uniform\" }],\n getDispatchSize(op) {\n const groups = op.attributes.groups as number;\n const dims = op.attributes.dims as number;\n const T = op.attributes.T as number;\n return [cdiv(groups * dims * T, 64), 1, 1];\n },\n buildParams(op) {\n const levels = op.attributes.levels as number[];\n const base = op.attributes.base as number[];\n return buildUniformBuffer([\n op.attributes.groups as number,\n op.attributes.dims as number,\n op.attributes.T as number,\n 0,\n levels[0],\n levels[1],\n levels[2],\n levels[3],\n base[0],\n base[1],\n base[2],\n base[3],\n ]);\n },\n};\n\nconst halfSnake1dSpec: KernelSpec = {\n shaderCode: WGSL_HALF_SNAKE1D,\n entryPoint: \"main\",\n bindings: [\n { type: \"storage-read\" },\n { type: \"storage-read\" },\n { type: \"storage-read-write\" },\n { type: \"uniform\" },\n ],\n getDispatchSize(op) {\n const C = op.attributes.C as number;\n const L = op.attributes.L as number;\n return [cdiv(C * L, 64), 1, 1];\n },\n buildParams(op) {\n const C = op.attributes.C as number;\n return buildUniformBuffer([C, op.attributes.L as number, Math.floor(C / 2), 0]);\n },\n};\n\nconst convTranspose1dDepthwiseSpec: KernelSpec = {\n shaderCode: WGSL_CONV_TRANSPOSE1D_DW,\n entryPoint: \"main\",\n bindings: [\n { type: \"storage-read\" },\n { type: \"storage-read\" },\n { type: \"storage-read\" },\n { type: \"storage-read-write\" },\n { type: \"uniform\" },\n ],\n getDispatchSize(op) {\n const Cout = op.attributes.Cout as number;\n const Lout = op.attributes.Lout as number;\n return [cdiv(Cout * Lout, 64), 1, 1];\n },\n buildParams(op) {\n const Cin = op.attributes.Cin as number;\n const Cout = op.attributes.Cout as number;\n return buildUniformBuffer([\n Cin,\n Cout,\n op.attributes.L as number,\n op.attributes.Lout as number,\n op.attributes.K as number,\n op.attributes.stride as number,\n Math.floor(Cin / Cout),\n 0,\n ]);\n },\n};\n\nexport const KERNEL_REGISTRY: Partial<Record<OpType, KernelSpec>> = {\n Embedding: embeddingSpec,\n EmbeddingInt4: embeddingInt4Spec,\n MatMul: matmulSpec,\n MatMulBias: matmulBiasSpec,\n MatMulInt4: matmulInt4Spec,\n Add: addSpec,\n Mul: mulSpec,\n SwiGLU: swigluSpec,\n SiLU: siluSpec,\n ResidualRMSNorm: residualRmsnormSpec,\n GELU: geluSpec,\n GeluErf: geluErfSpec,\n AddBias: addBiasSpec,\n ApplyRotaryEmb: applyRotarySpec,\n MRoPE: mropeSpec,\n EmbedSplice: embedSpliceSpec,\n SliceCols: sliceColsSpec,\n MulCols: mulColsSpec,\n PoolMatMul: poolMatMulSpec,\n ClippedMatMul: clippedMatMulSpec,\n RMSNorm: rmsnormSpec,\n LayerNorm: layernormSpec,\n RoPE: ropeSpec,\n Attention: attentionSpec,\n CrossAttention: crossAttentionSpec,\n Softmax: softmaxSpec,\n CausalConv1d: causalConv1dSpec,\n CausalConv1dSiLU: causalConv1dSiluSpec,\n CausalConv1dGated: causalConv1dGatedSpec,\n SigmoidGate: sigmoidGateSpec,\n MambaSSM: mambaSSMSpec,\n KVCacheAppend: kvCacheAppendSpec,\n ConvStateUpdate: convStateUpdateSpec,\n SliceLastRow: sliceLastRowSpec,\n MeanPool: meanPoolSpec,\n Scale: scaleSpec,\n Softcap: softcapSpec,\n L2Norm: l2NormSpec,\n // Audio codec-decoder (OmniVoice native TTS) — validated bit-exact, see above.\n Conv1dFull: conv1dFullSpec,\n ConvTranspose1d: convTranspose1dSpec,\n Snake1d: snake1dSpec,\n // Kani-TTS-2 NanoCodec decoder — validated bit-exact vs MLX, see above.\n FSQDequant: fsqDequantSpec,\n HalfSnake1d: halfSnake1dSpec,\n ConvTranspose1dDepthwise: convTranspose1dDepthwiseSpec,\n // Moonshine STT encoder frontend.\n Tanh: tanhSpec,\n Transpose: transposeSpec,\n GroupNorm: groupnormSpec,\n};\n","/**\n * Graph executor — runs a ModelGraph on WebGPU.\n *\n * Optimized for minimal per-token overhead:\n * - Persistent bind groups (created once after weight upload, reused every forward)\n * - Pre-allocated input buffer (no per-forward buffer creation)\n * - Cached uniform buffers (contents updated via writeBuffer, no recreation)\n * - GPU-side argmax for greedy decode (4 bytes readback vs vocab_size*4)\n * - Dual dispatch paths: tiled matmul for prefill, K-parallel matvec for decode\n */\n\nimport type { KvMode } from \"./architectures/index.js\";\nimport type { GPUContext } from \"./device.js\";\nimport {\n createBindGroup,\n createReadbackBuffer,\n createStorageBuffer,\n createUniformBuffer,\n destroyBuffers,\n getOrCreatePipeline,\n} from \"./device.js\";\nimport type { ModelGraph, OpNode } from \"./ir.js\";\nimport { DTYPE_BYTES } from \"./ir.js\";\nimport {\n ARGMAX_SPEC,\n ATTENTION_F16_SPEC,\n ATTENTION_PACKED_F16_SPEC,\n DUAL_KV_CACHE_APPEND_F16_SPEC,\n DUAL_KV_CACHE_APPEND_PACKED_F16_SPEC,\n DUAL_KV_CACHE_APPEND_SPEC,\n DUAL_MATVEC_INT4_SPEC,\n DUAL_RMSNORM_SPEC,\n GATED_MATVEC_INT4_SPEC,\n KERNEL_REGISTRY,\n type KernelSpec,\n KV_CACHE_APPEND_F16_SPEC,\n KV_CACHE_APPEND_PACKED_F16_SPEC,\n LAYERNORM_NO_BIAS_SPEC,\n MAMBA_SSM_F16_SPEC,\n MATVEC_INT4_SPEC,\n MATVEC_INT4_SUBGROUPS_SPEC,\n MATVEC_SPEC,\n MATVEC_SUBGROUPS_SPEC,\n ROPE_INTERLEAVED_SPEC,\n type RuntimeContext,\n SWIGLU_GATED_MATVEC_INT4_SPEC,\n SWIGLU_MATVEC_INT4_SPEC,\n SWIGLU_MATVEC_SPEC,\n} from \"./kernels/registry.js\";\nimport type { WeightSource } from \"./weight-source.js\";\n\nconst MAP_MODE_READ = 0x0001;\n\n/**\n * Safari/Metal workaround: shader variant alternation.\n * Metal caches argument buffers per compiled function. When consecutive dispatches\n * use the same WGSL code (same Metal function), Metal reuses the previous dispatch's\n * argument buffer, ignoring setBindGroup(). We alternate between variant 0/1 of each\n * shader (prepending `const _MV: u32 = Xu;`) to force different Metal function\n * specializations, preventing argument buffer reuse.\n */\n\nexport interface ExecutorOptions {\n maxSeqLen: number;\n /** KV cache kernel strategy. Defaults to \"native-f16\" when not specified. */\n kvMode?: KvMode;\n /**\n * WebKit only: dispatches per command buffer, with at most one command\n * buffer in flight (awaited). 1 (default) is the proven-correct floor on\n * iPad; larger values are faster if this WebKit version keeps storage\n * writes visible across dispatches within one submission. Sweepable via\n * the ?group=N URL param.\n */\n webkitGroupSize?: number;\n}\n\nexport interface ForwardResult {\n logits: Float32Array;\n}\n\n/**\n * Pre-compiled dispatch entry for a single graph op.\n * Created once during initBindGroups(), reused every forward pass.\n */\ninterface DispatchEntry {\n nodeId: string;\n node: OpNode;\n spec: KernelSpec;\n pipeline: GPUComputePipeline;\n bindGroup: GPUBindGroup;\n uniformBuffer: GPUBuffer;\n /** Cached last-written params bytes. Skip writeBuffer when unchanged. */\n lastParamsBytes: Uint8Array | null;\n /** Cached last dispatch size. */\n lastDispatchSize: [number, number, number] | null;\n}\n\nexport class Executor {\n private ctx: GPUContext;\n private graph: ModelGraph;\n\n private weightBuffers: Map<string, GPUBuffer> = new Map();\n private activationBuffers: Map<string, GPUBuffer> = new Map();\n private ssmStateBuffers: Map<string, GPUBuffer> = new Map();\n private kvCacheBuffers: Map<string, GPUBuffer> = new Map();\n\n /** Pre-allocated input_ids buffer (maxSeqLen * 4 bytes). */\n private inputIdsBuffer: GPUBuffer;\n\n /**\n * CPU-resident Per-Layer-Embeddings (PLE) source for Gemma 4. The PLE table\n * (`embed_tokens_per_layer`, [vocab, L*256]) is ~1.17GB at 4-bit and is kept\n * OFF the GPU. Each forward step we gather + dequantize only the rows for the\n * actual input tokens and upload a tiny [T, L*256] f32 buffer. See setPleSource.\n */\n private pleSource: {\n packed: Uint32Array; // flat row-major INT4 nibbles, 8 per u32\n scales: Float32Array; // per-group scale (Gerbil convention)\n zeros: Float32Array; // per-group zero\n width: number; // L*256 (row width)\n groupSize: number; // dequant group size\n targetTensor: string; // activation tensor the gathered rows are written to\n cache?: {\n cacheName: string;\n packedKey: string;\n scalesKey: string;\n zerosKey: string;\n packedLen: number;\n };\n } | null = null;\n /** Reusable scratch for the dequantized PLE rows (resized on demand). */\n private pleScratch: Float32Array | null = null;\n /**\n * One-time promise that materializes a cache-backed PLE table into heap. The\n * table is read from CacheStorage on the FIRST forward — i.e. AFTER the GPU\n * weight upload has completed, so the ~1.17 GB does not stack on top of the\n * upload's transient allocations at the load-time memory high-water mark.\n */\n private pleReady: Promise<void> | null = null;\n\n /**\n * Dummy GPU buffer bound to an otherwise-aliasing storage-read-write slot.\n * Used by RoPE-Q-only nodes (Gemma 4 KV-shared layers) whose node lists the\n * same tensor as input and output: the RoPE kernel always declares two\n * read_write bindings (q, k), but with num_kv_heads=0 the k slot is never\n * touched. WebGPU still rejects two read_write bindings aliasing one buffer,\n * so we bind this throwaway buffer to the unused k slot. Allocated lazily.\n */\n private bindingScratchBuffer: GPUBuffer | null = null;\n /** Readback buffer for logits. */\n private logitsReadback: GPUBuffer;\n /** GPU buffer for argmax result (1 u32). */\n private argmaxResultBuffer: GPUBuffer;\n /** Readback buffer for argmax result (1 u32). */\n private argmaxReadback: GPUBuffer;\n /** Readback ring for pipelined greedy decode (created lazily). */\n private decodeReadbacks: GPUBuffer[] = [];\n\n /**\n * Staging buffer for uniform param updates.\n * Safari/Metal has weaker visibility guarantees for queue.writeBuffer() to\n * UNIFORM buffers — early writes get dropped when hundreds are queued.\n * Instead, we pack all params into this STORAGE staging buffer (1 writeBuffer),\n * then use encoder.copyBufferToBuffer to distribute to each uniform buffer.\n * Copies are GPU-sequenced and guaranteed to complete before compute passes.\n */\n private uniformStagingBuffer: GPUBuffer | null = null;\n private uniformStagingCapacity: number = 0;\n\n /** Dispatch entries for prefill (M>1): uses tiled matmul kernels. */\n private dispatchEntries: DispatchEntry[] = [];\n /** Dispatch entries for decode (M=1): uses K-parallel matvec kernels. */\n private decodeEntries: DispatchEntry[] = [];\n /** Argmax dispatch entry (created in initBindGroups). */\n private argmaxEntry: {\n pipeline: GPUComputePipeline;\n bindGroup: GPUBindGroup;\n uniformBuffer: GPUBuffer;\n } | null = null;\n\n /** True when running on Safari/WebKit (needs multi-encoder submit). */\n readonly needsMultiEncoder: boolean;\n\n private maxSeqLen: number;\n private kvMode: KvMode;\n private seqPos: number = 0;\n private webkitGroupSize: number;\n\n // ── Env-gated decode profiler (GERBIL_PROFILE) ──\n // Times each dispatch with timestamp-query (one compute pass per dispatch) and\n // accumulates GPU nanoseconds per opType. OFF on the normal inference path.\n private profileEnabled = false;\n private readonly profileData = new Map<string, { ns: number; count: number }>();\n private querySet: GPUQuerySet | null = null;\n private queryResolveBuf: GPUBuffer | null = null;\n private queryReadbackBuf: GPUBuffer | null = null;\n\n constructor(ctx: GPUContext, graph: ModelGraph, options: ExecutorOptions) {\n this.ctx = ctx;\n this.graph = graph;\n this.maxSeqLen = options.maxSeqLen;\n this.kvMode = options.kvMode ?? \"native-f16\";\n this.webkitGroupSize = Math.max(1, options.webkitGroupSize ?? 1);\n this.profileEnabled =\n ctx.hasTimestamp && typeof process !== \"undefined\" && process.env?.GERBIL_PROFILE != null;\n\n // WebKit's WebGPU fails to make storage writes visible across dispatches\n // within one submission at production scale (see docs/mobile-failure-diagnosis.md);\n // Dawn (Chrome, node) does not need the multi-encoder workaround.\n this.needsMultiEncoder = ctx.isWebKitWebGPU;\n\n // Pre-allocate input_ids buffer at max size\n this.inputIdsBuffer = createStorageBuffer(ctx, \"input_ids\", options.maxSeqLen * 4);\n\n this.allocateActivationBuffers();\n this.allocateSSMStateBuffers();\n this.allocateKVCacheBuffers();\n\n // Guard against vocab_size 0 (non-LM graphs, e.g. the NanoCodec decoder run\n // via runGraphOutput): a zero-size buffer is invalid in WebGPU.\n this.logitsReadback = createReadbackBuffer(\n ctx,\n \"logits_readback\",\n Math.max(4, graph.config.vocab_size * 4),\n );\n this.argmaxResultBuffer = createStorageBuffer(ctx, \"argmax_result\", 4);\n this.argmaxReadback = createReadbackBuffer(ctx, \"argmax_readback\", 4);\n }\n\n /**\n * Register the CPU-resident Gemma 4 PLE table. The quantized table is kept in\n * JS memory (NOT uploaded to a GPU buffer), and {@link forward} gathers +\n * dequantizes only the rows for the current input tokens each step, uploading a\n * small [T, width] f32 buffer into `targetTensor`. This is what keeps Gemma 4\n * mobile-viable: resident GPU memory is just the active transformer weights.\n */\n setPleSource(src: {\n packed: Uint32Array;\n scales: Float32Array;\n zeros: Float32Array;\n width: number;\n groupSize: number;\n targetTensor: string;\n cache?: {\n cacheName: string;\n packedKey: string;\n scalesKey: string;\n zerosKey: string;\n packedLen: number;\n };\n }): void {\n this.pleSource = src;\n }\n\n /**\n * Materialize a cache-backed PLE table into heap exactly once. Deferred to the\n * first forward (after GPU upload) so the ~1.17 GB table is not co-resident\n * with the upload's transient allocations during the load-time memory peak.\n */\n private ensurePleLoaded(): Promise<void> {\n const src = this.pleSource;\n if (!src || !src.cache) return Promise.resolve();\n if (this.pleReady) return this.pleReady;\n const { cacheName, packedKey, scalesKey, zerosKey } = src.cache;\n this.pleReady = (async () => {\n const cache = await caches.open(cacheName);\n const read = async (key: string): Promise<ArrayBuffer> => {\n const resp = await cache.match(new Request(key));\n if (!resp) throw new Error(`PLE cache entry missing: ${key}`);\n return resp.arrayBuffer();\n };\n const [pBuf, sBuf, zBuf] = await Promise.all([\n read(packedKey),\n read(scalesKey),\n read(zerosKey),\n ]);\n src.packed = new Uint32Array(pBuf);\n src.scales = new Float32Array(sBuf);\n src.zeros = new Float32Array(zBuf);\n })();\n return this.pleReady;\n }\n\n /**\n * Gather + dequantize the PLE rows for `inputIds` and upload them into the\n * target activation buffer. Touches only T rows (T*width floats) — the full\n * [vocab, width] quantized table never goes to the GPU.\n */\n private async streamPleRows(inputIds: Uint32Array): Promise<void> {\n const src = this.pleSource;\n if (!src) return;\n if (src.cache) await this.ensurePleLoaded();\n const T = inputIds.length;\n const { packed, scales, zeros, width, groupSize } = src;\n const needed = T * width;\n if (!this.pleScratch || this.pleScratch.length < needed) {\n this.pleScratch = new Float32Array(needed);\n }\n const out = this.pleScratch;\n for (let t = 0; t < T; t++) {\n const tokenId = inputIds[t];\n const rowBase = tokenId * width; // flat element offset of this row\n const outBase = t * width;\n for (let d = 0; d < width; d++) {\n const flat = rowBase + d;\n const packedIdx = flat >>> 3; // flat / 8\n const nibblePos = (flat & 7) * 4; // (flat % 8) * 4\n const nibble = (packed[packedIdx] >>> nibblePos) & 0xf;\n const groupIdx = (flat / groupSize) | 0;\n out[outBase + d] = (nibble - zeros[groupIdx]) * scales[groupIdx];\n }\n }\n const buffer = this.getBuffer(src.targetTensor);\n if (!buffer) {\n throw new Error(`PLE target tensor \"${src.targetTensor}\" has no GPU buffer`);\n }\n this.ctx.device.queue.writeBuffer(buffer, 0, out.buffer, out.byteOffset, needed * 4);\n }\n\n /**\n * Stream weights to the GPU one tensor at a time, pulling each from the\n * `WeightSource` only when it is about to be uploaded and dropping the\n * reference immediately afterward.\n *\n * This is the property that bounds peak JS heap: with a cache-backed source\n * (browser/mobile), only ONE tensor's bytes are materialized in heap at a time\n * (read from CacheStorage in `source.get()`), uploaded to its GPU buffer, then\n * released before the next tensor is fetched. The whole model is never co-\n * resident in heap. With a heap-backed source (Node/desktop), behavior matches\n * the old Map path (the source deletes each entry as it is consumed).\n *\n * Accepts either a `WeightSource` (new, streamed/async) or a plain Map\n * (back-compat for callers that build a Map directly, e.g. Kani/Moonshine).\n */\n async uploadWeights(\n source: WeightSource | Map<string, { data: ArrayBufferView; shape: number[] }>,\n ): Promise<void> {\n if (source instanceof Map) {\n this.uploadWeightsMap(source);\n return;\n }\n for (const name of source.keys()) {\n const entry = await source.get(name);\n if (!entry) continue;\n const { data } = entry;\n const buffer = createStorageBuffer(this.ctx, `weight_${name}`, data.byteLength, data);\n this.weightBuffers.set(name, buffer);\n // Each `entry` reference goes out of scope here; with a cache-backed source\n // its bytes can be GC'd before the next tensor is pulled — bounding peak heap.\n }\n }\n\n /**\n * Synchronous Map upload (Node/desktop and the Kani/Moonshine sub-executors,\n * which build small heap Maps). Deletes each entry as it is consumed to free\n * the JS-side bytes once they are GPU-resident. Safe to call from a constructor.\n */\n uploadWeightsMap(weights: Map<string, { data: ArrayBufferView; shape: number[] }>): void {\n const names = [...weights.keys()];\n for (const name of names) {\n const entry = weights.get(name);\n if (!entry) continue;\n const buffer = createStorageBuffer(\n this.ctx,\n `weight_${name}`,\n entry.data.byteLength,\n entry.data,\n );\n this.weightBuffers.set(name, buffer);\n weights.delete(name);\n }\n }\n\n /**\n * Build all pipelines and bind groups. Call ONCE after uploadWeights().\n *\n * Creates two dispatch entry arrays:\n * - dispatchEntries: tiled matmul for prefill (any M)\n * - decodeEntries: K-parallel matvec for decode (M=1)\n */\n initBindGroups(): void {\n for (const nodeId of this.graph.executionOrder) {\n const node = this.graph.nodes.find((n) => n.id === nodeId)!;\n let spec = KERNEL_REGISTRY[node.opType];\n if (!spec) throw new Error(`No kernel for op type: ${node.opType}`);\n\n // Use f16 kernel variants when KV cache tensors are f16.\n // \"packed-f16\" uses pack2x16float (Safari-safe), \"native-f16\" uses enable f16.\n if (node.opType === \"KVCacheAppend\") {\n const hasF16KV = node.outputs.some((out) => this.graph.tensors[out]?.dtype === \"f16\");\n if (hasF16KV) {\n spec =\n this.kvMode === \"packed-f16\"\n ? KV_CACHE_APPEND_PACKED_F16_SPEC\n : KV_CACHE_APPEND_F16_SPEC;\n }\n } else if (node.opType === \"Attention\") {\n const hasF16KV = node.inputs.some((inp) => this.graph.tensors[inp]?.dtype === \"f16\");\n if (hasF16KV) {\n spec = this.kvMode === \"packed-f16\" ? ATTENTION_PACKED_F16_SPEC : ATTENTION_F16_SPEC;\n }\n } else if (node.opType === \"MambaSSM\" && this.ctx.hasF16 && !this.ctx.isWebKitWebGPU) {\n // f16 SSM state storage halves the dominant state bandwidth.\n // Dawn only — WebKit keeps the f32 kernel (unverified there).\n spec = MAMBA_SSM_F16_SPEC;\n } else if (node.opType === \"LayerNorm\" && node.inputs.length === 2) {\n // Weight-only LayerNorm (Moonshine encoder/decoder norms have no bias):\n // the node binds [input, weight] only, so use the no-bias variant whose\n // binding layout matches.\n spec = LAYERNORM_NO_BIAS_SPEC;\n } else if (node.opType === \"RoPE\" && node.attributes.interleaved === true) {\n // Moonshine rotates adjacent dim pairs (2p, 2p+1), not the [p, p+half]\n // split the default kernel uses.\n spec = ROPE_INTERLEAVED_SPEC;\n }\n\n const pipeline = getOrCreatePipeline(\n this.ctx,\n `kernel_${nodeId}`,\n spec.shaderCode,\n spec.entryPoint,\n );\n\n // Create uniform buffer with dummy params (will be overwritten before first dispatch)\n const dummyShapes = this.resolveShapes(1);\n const paramsData = spec.buildParams(node, dummyShapes, { seqPos: 0 });\n const uniformBuffer = createUniformBuffer(this.ctx, `uniform_${nodeId}`, paramsData);\n\n // Gather buffers for bind group\n const bufferEntries = this.gatherBuffers(spec, node, uniformBuffer);\n const bindGroup = createBindGroup(this.ctx, pipeline, bufferEntries, `bg_${nodeId}`);\n\n const prefillEntry: DispatchEntry = {\n nodeId,\n node,\n spec,\n pipeline,\n bindGroup,\n uniformBuffer,\n lastParamsBytes: null,\n lastDispatchSize: null,\n };\n this.dispatchEntries.push(prefillEntry);\n\n // For matmul ops, create a separate decode entry with the matvec kernel\n const isMatmul = node.opType === \"MatMul\" || node.opType === \"MatMulInt4\";\n if (isMatmul) {\n // Subgroups-accelerated matvec reduction. GATED OFF by default: on Dawn\n // (M-series desktop) it measured a large DECODE REGRESSION (~210→47 tok/s\n // Qwen3.5-0.8B, ~593→146 tok/s LFM2.5-350M) — coherent output, but the\n // extra workgroup barriers + per-column subgroup combine outweigh the\n // tiny saved 16/32-element sequential reduction these matvecs do. The\n // variant is kept registered and selectable behind an opt-in flag for\n // future hardware/driver re-evaluation; default path is the portable\n // kernel. WebKit is excluded regardless (mobile-safety rules).\n const subgroupsOptIn =\n (globalThis as { process?: { env?: Record<string, string | undefined> } }).process?.env\n ?.GERBIL_MATVEC_SUBGROUPS === \"1\";\n const useSubgroups = subgroupsOptIn && this.ctx.hasSubgroups && !this.ctx.isWebKitWebGPU;\n const mvSpec =\n node.opType === \"MatMulInt4\"\n ? useSubgroups\n ? MATVEC_INT4_SUBGROUPS_SPEC\n : MATVEC_INT4_SPEC\n : useSubgroups\n ? MATVEC_SUBGROUPS_SPEC\n : MATVEC_SPEC;\n\n const mvPipeline = getOrCreatePipeline(\n this.ctx,\n `matvec_${nodeId}`,\n mvSpec.shaderCode,\n mvSpec.entryPoint,\n );\n const mvUniformData = mvSpec.buildParams(node, dummyShapes, { seqPos: 0 });\n const mvUniform = createUniformBuffer(this.ctx, `mv_uniform_${nodeId}`, mvUniformData);\n const mvBuffers = this.gatherBuffers(mvSpec, node, mvUniform);\n const mvBindGroup = createBindGroup(this.ctx, mvPipeline, mvBuffers, `bg_mv_${nodeId}`);\n\n this.decodeEntries.push({\n nodeId,\n node,\n spec: mvSpec,\n pipeline: mvPipeline,\n bindGroup: mvBindGroup,\n uniformBuffer: mvUniform,\n lastParamsBytes: null,\n lastDispatchSize: null,\n });\n } else {\n // Non-matmul ops share the same entry for decode\n this.decodeEntries.push(prefillEntry);\n }\n }\n\n // ── Fuse gate_proj + up_proj + SwiGLU → single SwiGLUMatVec dispatch ──\n // Scans decode entries for the pattern: MatMul(input→gate) + MatMul(input→up) + SwiGLU(gate,up→out)\n // Replaces 3 dispatches with 1 fused kernel that reads input once.\n this.fuseSwiGLUDecodeEntries();\n\n // ── Fuse adjacent INT4 projections sharing the same input (q+gate, k+v) ──\n // Full-attention decode runs q_proj/gate_proj/k_proj/v_proj all reading the\n // same input_layernorm output. Merge the q+gate pair and the k+v pair into\n // one DualMatVecInt4 dispatch each, removing one GPU round-trip per pair.\n this.fuseDualMatVecDecodeEntries();\n\n // ── Fuse adjacent K+V cache appends (full-attention layers) ──\n this.fuseDualKVCacheAppendEntries();\n\n // ── Fuse attention SigmoidGate into the INT4 o_proj that consumes it ──\n this.fuseGatedOProjDecodeEntries();\n\n // ── Fuse Mamba SwiGLU (silu(z)*norm) into the INT4 out_proj ──\n this.fuseSwiGLUGatedProjDecodeEntries();\n\n // ── Fuse adjacent per-head RMSNorms (q_norm + k_norm) ──\n this.fuseDualRMSNormDecodeEntries();\n\n // Decode dispatch count per token — the mobile-proportional metric. On WebKit\n // each dispatch is a submit+drain round-trip, so decode tok/s ≈ 1/this. Logged\n // so every fusion's dispatch reduction is measurable on desktop/node.\n console.log(`[executor] decode: ${this.decodeEntries.length} dispatches/token`);\n\n // Create argmax bind group\n const logitsBuffer = this.getBuffer(\"logits\");\n if (logitsBuffer) {\n const argmaxPipeline = getOrCreatePipeline(\n this.ctx,\n \"argmax\",\n ARGMAX_SPEC.shaderCode,\n ARGMAX_SPEC.entryPoint,\n );\n const argmaxUniform = createUniformBuffer(\n this.ctx,\n \"uniform_argmax\",\n new ArrayBuffer(16), // {count, offset} — will be overwritten\n );\n const argmaxBindGroup = createBindGroup(\n this.ctx,\n argmaxPipeline,\n [{ buffer: logitsBuffer }, { buffer: this.argmaxResultBuffer }, { buffer: argmaxUniform }],\n \"bg_argmax\",\n );\n this.argmaxEntry = {\n pipeline: argmaxPipeline,\n bindGroup: argmaxBindGroup,\n uniformBuffer: argmaxUniform,\n };\n }\n\n if (this.needsMultiEncoder) {\n console.log(`[executor] WebKit WebGPU detected — using per-dispatch submit`);\n }\n\n // Create staging buffer for uniform param updates.\n // Size = max(total prefill uniform bytes, total decode uniform bytes + argmax).\n let prefillTotal = 0;\n for (const entry of this.dispatchEntries) {\n prefillTotal += Math.ceil(entry.uniformBuffer.size / 4) * 4;\n }\n let decodeTotal = 0;\n for (const entry of this.decodeEntries) {\n decodeTotal += Math.ceil(entry.uniformBuffer.size / 4) * 4;\n }\n decodeTotal += 16; // argmax params (8 bytes, padded)\n // Add space for input_ids (routed through staging too, for Safari safety)\n const inputIdBytes = this.maxSeqLen * 4;\n this.uniformStagingCapacity = Math.max(prefillTotal, decodeTotal) + inputIdBytes;\n this.uniformStagingBuffer = this.ctx.device.createBuffer({\n label: \"uniform_staging\",\n size: this.uniformStagingCapacity,\n usage: 0x0004 | 0x0008, // COPY_SRC | COPY_DST\n });\n }\n\n /**\n * Run a forward pass. Uses matvec kernels for M=1 (decode), tiled for M>1 (prefill).\n */\n async forward(inputIds: Uint32Array): Promise<ForwardResult> {\n const T = inputIds.length;\n\n const resolvedShapes = this.resolveShapes(T);\n const runtimeContext: RuntimeContext = { seqPos: this.seqPos };\n\n // Use matvec entries for single-token decode, tiled for prefill\n const entries = T === 1 ? this.decodeEntries : this.dispatchEntries;\n\n // Write input_ids directly — no staging buffer.\n this.ctx.device.queue.writeBuffer(this.inputIdsBuffer, 0, inputIds as BufferSource);\n\n // Gemma 4: stream the CPU-resident PLE rows for these tokens (no-op otherwise).\n await this.streamPleRows(inputIds);\n\n // Build params and write directly to each uniform buffer.\n // Also compute dispatch sizes up front.\n const dispatchSizes: Array<[number, number, number]> = new Array(entries.length);\n for (let i = 0; i < entries.length; i++) {\n const entry = entries[i];\n const paramsData = entry.spec.buildParams(entry.node, resolvedShapes, runtimeContext);\n const paramsView = new Uint8Array(paramsData);\n\n let changed = !entry.lastParamsBytes || entry.lastParamsBytes.length !== paramsView.length;\n if (!changed) {\n const cached = entry.lastParamsBytes!;\n for (let j = 0; j < paramsView.length; j++) {\n if (paramsView[j] !== cached[j]) {\n changed = true;\n break;\n }\n }\n }\n\n if (changed) {\n this.ctx.device.queue.writeBuffer(entry.uniformBuffer, 0, paramsData);\n entry.lastParamsBytes = new Uint8Array(paramsView.slice(0));\n }\n\n dispatchSizes[i] = entry.spec.getDispatchSize(entry.node, resolvedShapes, runtimeContext);\n }\n\n if (this.profileEnabled) {\n await this.runProfiledDispatches(entries, dispatchSizes);\n } else if (this.needsMultiEncoder) {\n // WebKit: group dispatches into command buffers of webkitGroupSize\n // (one compute pass per dispatch), with at most ONE command buffer in\n // flight (awaited). Fire-and-forget per-dispatch submits queue ~400\n // unbounded in-flight Metal command buffers per token — the documented\n // WebKit resource-exhaustion anti-pattern (WebKit bug 311598). Batching\n // many dispatches into one submission zeroed activations on iPad\n // (docs/metal-safari-intel.md); sweep ?group=N to find this WebKit\n // version's correct/fast granularity (llama.cpp runs ~64/CB on iOS 26.4).\n const group = this.webkitGroupSize;\n for (let start = 0; start < entries.length; start += group) {\n const enc = this.ctx.device.createCommandEncoder();\n const end = Math.min(start + group, entries.length);\n for (let i = start; i < end; i++) {\n const p = enc.beginComputePass();\n p.setPipeline(entries[i].pipeline);\n p.setBindGroup(0, entries[i].bindGroup);\n p.dispatchWorkgroups(...dispatchSizes[i]);\n p.end();\n }\n this.ctx.device.queue.submit([enc.finish()]);\n await this.ctx.device.queue.onSubmittedWorkDone();\n }\n const logitsBuffer = this.getBuffer(\"logits\");\n if (logitsBuffer) {\n const copyEnc = this.ctx.device.createCommandEncoder();\n const vocabSize = this.graph.config.vocab_size;\n // logits is [1, vocab] — SliceLastRow feeds lm_head only the last position\n copyEnc.copyBufferToBuffer(logitsBuffer, 0, this.logitsReadback, 0, vocabSize * 4);\n this.ctx.device.queue.submit([copyEnc.finish()]);\n }\n } else {\n // Desktop/Dawn: single encoder, single compute pass\n const encoder = this.ctx.device.createCommandEncoder({ label: \"forward\" });\n const pass = encoder.beginComputePass({ label: \"fwd_pass\" });\n for (let i = 0; i < entries.length; i++) {\n pass.setPipeline(entries[i].pipeline);\n pass.setBindGroup(0, entries[i].bindGroup);\n pass.dispatchWorkgroups(...dispatchSizes[i]);\n }\n pass.end();\n const logitsBuffer = this.getBuffer(\"logits\");\n if (logitsBuffer) {\n const vocabSize = this.graph.config.vocab_size;\n // logits is [1, vocab] — SliceLastRow feeds lm_head only the last position\n encoder.copyBufferToBuffer(logitsBuffer, 0, this.logitsReadback, 0, vocabSize * 4);\n }\n this.ctx.device.queue.submit([encoder.finish()]);\n }\n\n const vocabSize = this.graph.config.vocab_size;\n await this.logitsReadback.mapAsync(MAP_MODE_READ, 0, vocabSize * 4);\n const mapped = this.logitsReadback.getMappedRange(0, vocabSize * 4);\n const logits = new Float32Array(mapped.slice(0));\n this.logitsReadback.unmap();\n\n this.seqPos += T;\n return { logits };\n }\n\n /**\n * Profiling variant of the desktop dispatch path: one compute pass per dispatch,\n * each bracketed by timestamp queries, so we get per-op GPU time. Accumulates\n * into profileData by opType. Only runs under GERBIL_PROFILE with timestamp-query\n * support — slower than the batched path (it measures relative cost, not tok/s).\n */\n private async runProfiledDispatches(\n entries: DispatchEntry[],\n dispatchSizes: number[][],\n copyLogits = true,\n ): Promise<void> {\n const n = entries.length;\n const queryCount = n * 2;\n if (!this.querySet) {\n this.querySet = this.ctx.device.createQuerySet({ type: \"timestamp\", count: queryCount });\n // QUERY_RESOLVE (0x0200) | COPY_SRC (0x0004)\n this.queryResolveBuf = this.ctx.device.createBuffer({\n size: queryCount * 8,\n usage: 0x0200 | 0x0004,\n });\n // MAP_READ (0x0001) | COPY_DST (0x0008)\n this.queryReadbackBuf = this.ctx.device.createBuffer({\n size: queryCount * 8,\n usage: 0x0001 | 0x0008,\n });\n }\n const querySet = this.querySet;\n const resolveBuf = this.queryResolveBuf as GPUBuffer;\n const readbackBuf = this.queryReadbackBuf as GPUBuffer;\n\n const encoder = this.ctx.device.createCommandEncoder({ label: \"fwd_profiled\" });\n for (let i = 0; i < n; i++) {\n const pass = encoder.beginComputePass({\n timestampWrites: {\n querySet,\n beginningOfPassWriteIndex: i * 2,\n endOfPassWriteIndex: i * 2 + 1,\n },\n });\n pass.setPipeline(entries[i].pipeline);\n pass.setBindGroup(0, entries[i].bindGroup);\n pass.dispatchWorkgroups(dispatchSizes[i][0], dispatchSizes[i][1], dispatchSizes[i][2]);\n pass.end();\n }\n if (copyLogits) {\n const logitsBuffer = this.getBuffer(\"logits\");\n const vocabSize = this.graph.config.vocab_size;\n if (logitsBuffer) {\n encoder.copyBufferToBuffer(logitsBuffer, 0, this.logitsReadback, 0, vocabSize * 4);\n }\n }\n encoder.resolveQuerySet(querySet, 0, queryCount, resolveBuf, 0);\n encoder.copyBufferToBuffer(resolveBuf, 0, readbackBuf, 0, queryCount * 8);\n this.ctx.device.queue.submit([encoder.finish()]);\n\n await readbackBuf.mapAsync(MAP_MODE_READ, 0, queryCount * 8);\n const ts = new BigUint64Array(readbackBuf.getMappedRange(0, queryCount * 8).slice(0));\n readbackBuf.unmap();\n for (let i = 0; i < n; i++) {\n const ns = Number(ts[i * 2 + 1] - ts[i * 2]);\n if (ns <= 0) {\n continue;\n }\n const key = entries[i].node.opType;\n const cur = this.profileData.get(key) ?? { ns: 0, count: 0 };\n cur.ns += ns;\n cur.count += 1;\n this.profileData.set(key, cur);\n }\n }\n\n /** Per-opType GPU time (ns) + dispatch count accumulated by GERBIL_PROFILE, hottest first. */\n getProfile(): Array<{ opType: string; ns: number; count: number }> {\n return [...this.profileData.entries()]\n .map(([opType, v]) => ({ opType, ns: v.ns, count: v.count }))\n .sort((a, b) => b.ns - a.ns);\n }\n\n /** Clear accumulated profiler data (e.g. to drop warm-up tokens). */\n resetProfile(): void {\n this.profileData.clear();\n }\n\n /** GPU dispatches per decode token (post-fusion). On mobile this drives the\n * submit-group count = ceil(dispatchCount / webkitGroupSize). */\n get decodeDispatchCount(): number {\n return this.decodeEntries.length;\n }\n\n /** Device limit that gates the INT4 projection fusions (they need ≥9). If a\n * device caps at 8 the dual/gated/swiglu-gated INT4 fusions silently fall back,\n * inflating the decode dispatch count. */\n get maxStorageBuffers(): number {\n return this.ctx.limits.maxStorageBuffersPerShaderStage;\n }\n\n /**\n * Profile ONE real decode step: times the actual `decodeEntries` (the kernels\n * the pipelined greedy benchmark runs) with per-dispatch timestamps. Timing is\n * token-independent, so pass any valid id; runs un-pipelined with a synchronous\n * timestamp readback (measurement only, not for production decode). Argmax (one\n * tiny dispatch) is intentionally excluded — it is not a hotspot target.\n */\n async profileDecodeStep(tokenId: number): Promise<void> {\n const resolvedShapes = this.resolveShapes(1);\n const runtimeContext: RuntimeContext = { seqPos: this.seqPos };\n this.ctx.device.queue.writeBuffer(this.inputIdsBuffer, 0, new Uint32Array([tokenId]));\n\n const dispatchSizes: number[][] = new Array(this.decodeEntries.length);\n for (let i = 0; i < this.decodeEntries.length; i++) {\n const entry = this.decodeEntries[i];\n const paramsData = entry.spec.buildParams(entry.node, resolvedShapes, runtimeContext);\n this.ctx.device.queue.writeBuffer(entry.uniformBuffer, 0, paramsData);\n dispatchSizes[i] = entry.spec.getDispatchSize(entry.node, resolvedShapes, runtimeContext);\n }\n await this.runProfiledDispatches(this.decodeEntries, dispatchSizes, false);\n this.seqPos += 1;\n }\n\n /**\n * Run a single forward pass over `inputIds` and read back the L2-normalized\n * embedding vector. Requires an embedding graph (one whose output tensor is\n * \"embedding\", produced by the last-token-pool + L2-norm tail).\n *\n * Always runs in a fresh-state single pass (caller should reset() first):\n * embeddings are non-autoregressive, so the whole sequence is one prefill.\n */\n async embed(inputIds: Uint32Array): Promise<Float32Array> {\n const embeddingBuffer = this.getBuffer(\"embedding\");\n if (!embeddingBuffer) {\n throw new Error(\n \"embed() requires an embedding graph (no 'embedding' output tensor found). \" +\n \"Load the model with { embedding: true }.\",\n );\n }\n const hiddenSize = this.graph.config.hidden_size;\n const byteLen = hiddenSize * 4;\n\n const T = inputIds.length;\n const resolvedShapes = this.resolveShapes(T);\n const runtimeContext: RuntimeContext = { seqPos: this.seqPos };\n\n // Embeddings process the full sequence at once → always use prefill entries.\n const entries = T === 1 ? this.decodeEntries : this.dispatchEntries;\n\n this.ctx.device.queue.writeBuffer(this.inputIdsBuffer, 0, inputIds as BufferSource);\n\n const dispatchSizes: Array<[number, number, number]> = new Array(entries.length);\n for (let i = 0; i < entries.length; i++) {\n const entry = entries[i];\n const paramsData = entry.spec.buildParams(entry.node, resolvedShapes, runtimeContext);\n const paramsView = new Uint8Array(paramsData);\n\n let changed = !entry.lastParamsBytes || entry.lastParamsBytes.length !== paramsView.length;\n if (!changed) {\n const cached = entry.lastParamsBytes!;\n for (let j = 0; j < paramsView.length; j++) {\n if (paramsView[j] !== cached[j]) {\n changed = true;\n break;\n }\n }\n }\n if (changed) {\n this.ctx.device.queue.writeBuffer(entry.uniformBuffer, 0, paramsData);\n entry.lastParamsBytes = new Uint8Array(paramsView.slice(0));\n }\n\n dispatchSizes[i] = entry.spec.getDispatchSize(entry.node, resolvedShapes, runtimeContext);\n }\n\n // Dedicated readback buffer (COPY_DST | MAP_READ).\n const readback = this.ctx.device.createBuffer({ size: byteLen, usage: 0x0001 | 0x0008 });\n\n if (this.needsMultiEncoder) {\n const group = this.webkitGroupSize;\n for (let start = 0; start < entries.length; start += group) {\n const enc = this.ctx.device.createCommandEncoder();\n const end = Math.min(start + group, entries.length);\n for (let i = start; i < end; i++) {\n const p = enc.beginComputePass();\n p.setPipeline(entries[i].pipeline);\n p.setBindGroup(0, entries[i].bindGroup);\n p.dispatchWorkgroups(...dispatchSizes[i]);\n p.end();\n }\n this.ctx.device.queue.submit([enc.finish()]);\n await this.ctx.device.queue.onSubmittedWorkDone();\n }\n const copyEnc = this.ctx.device.createCommandEncoder();\n copyEnc.copyBufferToBuffer(embeddingBuffer, 0, readback, 0, byteLen);\n this.ctx.device.queue.submit([copyEnc.finish()]);\n } else {\n const encoder = this.ctx.device.createCommandEncoder({ label: \"embed\" });\n const pass = encoder.beginComputePass({ label: \"embed_pass\" });\n for (let i = 0; i < entries.length; i++) {\n pass.setPipeline(entries[i].pipeline);\n pass.setBindGroup(0, entries[i].bindGroup);\n pass.dispatchWorkgroups(...dispatchSizes[i]);\n }\n pass.end();\n encoder.copyBufferToBuffer(embeddingBuffer, 0, readback, 0, byteLen);\n this.ctx.device.queue.submit([encoder.finish()]);\n }\n\n await readback.mapAsync(MAP_MODE_READ, 0, byteLen);\n const out = new Float32Array(readback.getMappedRange(0, byteLen).slice(0));\n readback.unmap();\n readback.destroy();\n\n this.seqPos += T;\n return out;\n }\n\n /**\n * Generic one-shot dispatch for a non-LM graph: run every entry once over a\n * single forward and read back `elemCount` f32 elements of the named output\n * tensor. Used to execute the NanoCodec decoder graph (codes→PCM) — its ops use\n * concrete lengths and it has no \"logits\" output, so the normal forward()\n * logits-readback path does not apply. Caller writes any inputs (e.g. audio_codes)\n * via writeInput() and reset()s first.\n */\n async runGraphOutput(outputName: string, elemCount: number): Promise<Float32Array> {\n const outBuffer = this.getBuffer(outputName);\n if (!outBuffer) {\n throw new Error(`runGraphOutput: no GPU buffer for output tensor \"${outputName}\".`);\n }\n const byteLen = elemCount * 4;\n // Codec graphs carry concrete lengths; dispatchEntries (M>1 path) builds the\n // full pipeline. Pass T=1 so shape resolution keeps any symbolic dims minimal\n // (the codec ops ignore input_ids entirely).\n const resolvedShapes = this.resolveShapes(1);\n const runtimeContext: RuntimeContext = { seqPos: this.seqPos };\n const entries = this.dispatchEntries;\n\n const dispatchSizes: Array<[number, number, number]> = new Array(entries.length);\n for (let i = 0; i < entries.length; i++) {\n const entry = entries[i];\n const paramsData = entry.spec.buildParams(entry.node, resolvedShapes, runtimeContext);\n this.ctx.device.queue.writeBuffer(entry.uniformBuffer, 0, paramsData);\n entry.lastParamsBytes = new Uint8Array(new Uint8Array(paramsData).slice(0));\n dispatchSizes[i] = entry.spec.getDispatchSize(entry.node, resolvedShapes, runtimeContext);\n }\n\n const readback = this.ctx.device.createBuffer({ size: byteLen, usage: 0x0001 | 0x0008 });\n\n if (this.needsMultiEncoder) {\n const group = this.webkitGroupSize;\n for (let start = 0; start < entries.length; start += group) {\n const enc = this.ctx.device.createCommandEncoder();\n const end = Math.min(start + group, entries.length);\n for (let i = start; i < end; i++) {\n const pass = enc.beginComputePass();\n pass.setPipeline(entries[i].pipeline);\n pass.setBindGroup(0, entries[i].bindGroup);\n pass.dispatchWorkgroups(...dispatchSizes[i]);\n pass.end();\n }\n this.ctx.device.queue.submit([enc.finish()]);\n await this.ctx.device.queue.onSubmittedWorkDone();\n }\n const copyEnc = this.ctx.device.createCommandEncoder();\n copyEnc.copyBufferToBuffer(outBuffer, 0, readback, 0, byteLen);\n this.ctx.device.queue.submit([copyEnc.finish()]);\n } else {\n const encoder = this.ctx.device.createCommandEncoder({ label: \"runGraphOutput\" });\n const pass = encoder.beginComputePass({ label: \"run_pass\" });\n for (let i = 0; i < entries.length; i++) {\n pass.setPipeline(entries[i].pipeline);\n pass.setBindGroup(0, entries[i].bindGroup);\n pass.dispatchWorkgroups(...dispatchSizes[i]);\n }\n pass.end();\n encoder.copyBufferToBuffer(outBuffer, 0, readback, 0, byteLen);\n this.ctx.device.queue.submit([encoder.finish()]);\n }\n\n await readback.mapAsync(MAP_MODE_READ, 0, byteLen);\n const out = new Float32Array(readback.getMappedRange(0, byteLen).slice(0));\n readback.unmap();\n readback.destroy();\n return out;\n }\n\n /**\n * Greedy decode step: forward + GPU argmax. Returns token ID directly.\n * Always uses matvec kernels (M=1). Reads back 4 bytes instead of vocab_size*4.\n */\n async forwardArgmax(inputIds: Uint32Array): Promise<number> {\n const T = inputIds.length;\n\n const resolvedShapes = this.resolveShapes(T);\n const runtimeContext: RuntimeContext = { seqPos: this.seqPos };\n\n // Write input_ids directly\n this.ctx.device.queue.writeBuffer(this.inputIdsBuffer, 0, inputIds as BufferSource);\n\n // Write uniform params directly to each buffer\n const argmaxDispatchSizes: Array<[number, number, number]> = new Array(\n this.decodeEntries.length,\n );\n for (let i = 0; i < this.decodeEntries.length; i++) {\n const entry = this.decodeEntries[i];\n const paramsData = entry.spec.buildParams(entry.node, resolvedShapes, runtimeContext);\n const paramsView = new Uint8Array(paramsData);\n\n let changed = !entry.lastParamsBytes || entry.lastParamsBytes.length !== paramsView.length;\n if (!changed) {\n const cached = entry.lastParamsBytes!;\n for (let j = 0; j < paramsView.length; j++) {\n if (paramsView[j] !== cached[j]) {\n changed = true;\n break;\n }\n }\n }\n\n if (changed) {\n this.ctx.device.queue.writeBuffer(entry.uniformBuffer, 0, paramsData);\n entry.lastParamsBytes = new Uint8Array(paramsView.slice(0));\n }\n\n argmaxDispatchSizes[i] = entry.spec.getDispatchSize(\n entry.node,\n resolvedShapes,\n runtimeContext,\n );\n }\n\n // Write argmax params directly.\n // logits is [1, vocab] (SliceLastRow feeds lm_head only the last position),\n // so the argmax always reads row 0.\n if (this.argmaxEntry) {\n const vocabSize = this.graph.config.vocab_size;\n const argmaxParams = new ArrayBuffer(8);\n const view = new DataView(argmaxParams);\n view.setUint32(0, vocabSize, true);\n view.setUint32(4, 0, true);\n this.ctx.device.queue.writeBuffer(this.argmaxEntry.uniformBuffer, 0, argmaxParams);\n }\n\n if (this.needsMultiEncoder) {\n // WebKit: grouped submits with one command buffer in flight\n // (see forward() comment for rationale)\n const group = this.webkitGroupSize;\n for (let start = 0; start < this.decodeEntries.length; start += group) {\n const enc = this.ctx.device.createCommandEncoder();\n const end = Math.min(start + group, this.decodeEntries.length);\n for (let i = start; i < end; i++) {\n const p = enc.beginComputePass();\n p.setPipeline(this.decodeEntries[i].pipeline);\n p.setBindGroup(0, this.decodeEntries[i].bindGroup);\n p.dispatchWorkgroups(...argmaxDispatchSizes[i]);\n p.end();\n }\n this.ctx.device.queue.submit([enc.finish()]);\n await this.ctx.device.queue.onSubmittedWorkDone();\n }\n // Argmax dispatch and readback copy in SEPARATE submits, mirroring\n // forward(): the copy reads argmaxResultBuffer written by the dispatch,\n // so it must sit behind a command buffer boundary on WebKit.\n if (this.argmaxEntry) {\n const enc = this.ctx.device.createCommandEncoder();\n const p = enc.beginComputePass();\n p.setPipeline(this.argmaxEntry.pipeline);\n p.setBindGroup(0, this.argmaxEntry.bindGroup);\n p.dispatchWorkgroups(1, 1, 1);\n p.end();\n this.ctx.device.queue.submit([enc.finish()]);\n\n const copyEnc = this.ctx.device.createCommandEncoder();\n copyEnc.copyBufferToBuffer(this.argmaxResultBuffer, 0, this.argmaxReadback, 0, 4);\n this.ctx.device.queue.submit([copyEnc.finish()]);\n }\n } else {\n // Desktop/Dawn: single encoder, single compute pass\n const encoder = this.ctx.device.createCommandEncoder({ label: \"argmax\" });\n const pass = encoder.beginComputePass({ label: \"am_pass\" });\n for (let i = 0; i < this.decodeEntries.length; i++) {\n const entry = this.decodeEntries[i];\n pass.setPipeline(entry.pipeline);\n pass.setBindGroup(0, entry.bindGroup);\n pass.dispatchWorkgroups(...argmaxDispatchSizes[i]);\n }\n if (this.argmaxEntry) {\n pass.setPipeline(this.argmaxEntry.pipeline);\n pass.setBindGroup(0, this.argmaxEntry.bindGroup);\n pass.dispatchWorkgroups(1, 1, 1);\n }\n pass.end();\n encoder.copyBufferToBuffer(this.argmaxResultBuffer, 0, this.argmaxReadback, 0, 4);\n this.ctx.device.queue.submit([encoder.finish()]);\n }\n\n await this.argmaxReadback.mapAsync(MAP_MODE_READ, 0, 4);\n const mapped = this.argmaxReadback.getMappedRange(0, 4);\n const tokenId = new Uint32Array(mapped.slice(0))[0];\n this.argmaxReadback.unmap();\n\n this.seqPos += T;\n return tokenId;\n }\n\n /** KV-cache positions still available for decode steps. */\n decodeCapacityRemaining(): number {\n return this.maxSeqLen - this.seqPos;\n }\n\n /** Number of decode steps that may be in flight in the pipelined path. */\n static readonly PIPELINE_DEPTH = 2;\n\n /**\n * Pipelined greedy decode step (Dawn only — WebKit uses forwardArgmax).\n *\n * Encodes one full decode forward + argmax and submits WITHOUT awaiting\n * completion. The input token is taken from `tokenId` for the first step\n * after prefill; for subsequent steps (tokenId === null) the previous step's\n * argmax result is copied into input_ids ON THE GPU, so the decode loop\n * never blocks on a readback before submitting the next step. The argmax\n * result is copied to a per-slot readback buffer, read later (one step\n * behind) via readDecodeToken(slot).\n *\n * queue.writeBuffer is queue-ordered: uniform updates land after the\n * previously submitted step's command buffer and before this one's, so\n * shared uniform buffers are safe with multiple steps in flight.\n */\n submitGreedyDecodeStep(tokenId: number | null, slot: number): void {\n const resolvedShapes = this.resolveShapes(1);\n const runtimeContext: RuntimeContext = { seqPos: this.seqPos };\n\n if (this.decodeReadbacks.length === 0) {\n for (let i = 0; i < Executor.PIPELINE_DEPTH; i++) {\n this.decodeReadbacks.push(createReadbackBuffer(this.ctx, `decode_readback_${i}`, 4));\n }\n }\n\n if (tokenId !== null) {\n this.ctx.device.queue.writeBuffer(this.inputIdsBuffer, 0, new Uint32Array([tokenId]));\n // Argmax params are decode-invariant — write once per generation.\n if (this.argmaxEntry) {\n const argmaxParams = new ArrayBuffer(8);\n const view = new DataView(argmaxParams);\n view.setUint32(0, this.graph.config.vocab_size, true);\n view.setUint32(4, 0, true);\n this.ctx.device.queue.writeBuffer(this.argmaxEntry.uniformBuffer, 0, argmaxParams);\n }\n }\n\n // Update uniforms (same skip-unchanged caching as forwardArgmax)\n const dispatchSizes: Array<[number, number, number]> = new Array(this.decodeEntries.length);\n for (let i = 0; i < this.decodeEntries.length; i++) {\n const entry = this.decodeEntries[i];\n const paramsData = entry.spec.buildParams(entry.node, resolvedShapes, runtimeContext);\n const paramsView = new Uint8Array(paramsData);\n\n let changed = !entry.lastParamsBytes || entry.lastParamsBytes.length !== paramsView.length;\n if (!changed) {\n const cached = entry.lastParamsBytes!;\n for (let j = 0; j < paramsView.length; j++) {\n if (paramsView[j] !== cached[j]) {\n changed = true;\n break;\n }\n }\n }\n\n if (changed) {\n this.ctx.device.queue.writeBuffer(entry.uniformBuffer, 0, paramsData);\n entry.lastParamsBytes = new Uint8Array(paramsView.slice(0));\n }\n\n dispatchSizes[i] = entry.spec.getDispatchSize(entry.node, resolvedShapes, runtimeContext);\n }\n\n const encoder = this.ctx.device.createCommandEncoder({ label: \"decode_step\" });\n if (tokenId === null) {\n // Chain: previous step's argmax result becomes this step's input token.\n encoder.copyBufferToBuffer(this.argmaxResultBuffer, 0, this.inputIdsBuffer, 0, 4);\n }\n const pass = encoder.beginComputePass({ label: \"decode_pass\" });\n for (let i = 0; i < this.decodeEntries.length; i++) {\n const entry = this.decodeEntries[i];\n pass.setPipeline(entry.pipeline);\n pass.setBindGroup(0, entry.bindGroup);\n pass.dispatchWorkgroups(...dispatchSizes[i]);\n }\n if (this.argmaxEntry) {\n pass.setPipeline(this.argmaxEntry.pipeline);\n pass.setBindGroup(0, this.argmaxEntry.bindGroup);\n pass.dispatchWorkgroups(1, 1, 1);\n }\n pass.end();\n encoder.copyBufferToBuffer(this.argmaxResultBuffer, 0, this.decodeReadbacks[slot], 0, 4);\n this.ctx.device.queue.submit([encoder.finish()]);\n\n this.seqPos += 1;\n }\n\n /** Read back the token produced by the pipelined step that used `slot`. */\n async readDecodeToken(slot: number): Promise<number> {\n const buf = this.decodeReadbacks[slot];\n await buf.mapAsync(MAP_MODE_READ, 0, 4);\n const mapped = buf.getMappedRange(0, 4);\n const tokenId = new Uint32Array(mapped.slice(0))[0];\n buf.unmap();\n return tokenId;\n }\n\n reset(): void {\n this.seqPos = 0;\n // Zero SSM state buffers\n for (const buf of this.ssmStateBuffers.values()) {\n this.ctx.device.queue.writeBuffer(buf, 0, new Uint8Array(buf.size));\n }\n // Clear cached params — shapes change between prefill (T=prompt_len) and decode (T=1)\n for (const entry of this.dispatchEntries) {\n entry.lastParamsBytes = null;\n entry.lastDispatchSize = null;\n }\n for (const entry of this.decodeEntries) {\n entry.lastParamsBytes = null;\n entry.lastDispatchSize = null;\n }\n }\n\n /**\n * Diagnostic: dispatch ONLY the first kernel (EmbeddingInt4) using the\n * production bind group, pipeline, and buffers — but in isolation (1 dispatch,\n * no staging, fresh encoder). Compares against full forward pass to isolate\n * whether the issue is the bind group/pipeline or the multi-dispatch context.\n */\n async debugFirstDispatch(inputIds: Uint32Array): Promise<{\n nodeId: string;\n opType: string;\n dispatchSize: [number, number, number];\n output: Float32Array;\n }> {\n const T = inputIds.length;\n const resolvedShapes = this.resolveShapes(T);\n const runtimeContext: RuntimeContext = { seqPos: this.seqPos };\n const entries = T === 1 ? this.decodeEntries : this.dispatchEntries;\n const entry = entries[0];\n\n // Write input_ids directly (no staging)\n this.ctx.device.queue.writeBuffer(this.inputIdsBuffer, 0, inputIds as BufferSource);\n\n // Write params directly to uniform buffer (no staging)\n const paramsData = entry.spec.buildParams(entry.node, resolvedShapes, runtimeContext);\n this.ctx.device.queue.writeBuffer(entry.uniformBuffer, 0, paramsData);\n\n // Single dispatch with fresh encoder\n const dispatchSize = entry.spec.getDispatchSize(entry.node, resolvedShapes, runtimeContext) as [\n number,\n number,\n number,\n ];\n const encoder = this.ctx.device.createCommandEncoder({ label: \"debug_first\" });\n const pass = encoder.beginComputePass({ label: \"debug_first_pass\" });\n pass.setPipeline(entry.pipeline);\n pass.setBindGroup(0, entry.bindGroup);\n pass.dispatchWorkgroups(...dispatchSize);\n pass.end();\n this.ctx.device.queue.submit([encoder.finish()]);\n\n // Read back the output tensor (first 16 elements)\n const outputName = entry.node.outputs[0];\n const output = await this.debugReadBuffer(outputName, 16);\n\n return {\n nodeId: entry.nodeId,\n opType: entry.node.opType,\n dispatchSize,\n output,\n };\n }\n\n /**\n * Diagnostic: run a single dispatch entry by index, in isolation.\n * Call after debugFirstDispatch() to test whether entry[1] (RMSNorm)\n * can read embed_out written by entry[0] (EmbeddingInt4).\n */\n async debugDispatchEntry(\n entryIndex: number,\n T: number,\n ): Promise<{ nodeId: string; opType: string; output: Float32Array }> {\n const resolvedShapes = this.resolveShapes(T);\n const runtimeContext: RuntimeContext = { seqPos: this.seqPos };\n const entries = T === 1 ? this.decodeEntries : this.dispatchEntries;\n const entry = entries[entryIndex];\n\n // Write params directly\n const paramsData = entry.spec.buildParams(entry.node, resolvedShapes, runtimeContext);\n this.ctx.device.queue.writeBuffer(entry.uniformBuffer, 0, paramsData);\n\n // Single dispatch\n const dispatchSize = entry.spec.getDispatchSize(entry.node, resolvedShapes, runtimeContext) as [\n number,\n number,\n number,\n ];\n const encoder = this.ctx.device.createCommandEncoder({ label: `debug_entry_${entryIndex}` });\n const pass = encoder.beginComputePass({ label: `debug_pass_${entryIndex}` });\n pass.setPipeline(entry.pipeline);\n pass.setBindGroup(0, entry.bindGroup);\n pass.dispatchWorkgroups(...dispatchSize);\n pass.end();\n this.ctx.device.queue.submit([encoder.finish()]);\n\n const outputName = entry.node.outputs[0];\n const output = await this.debugReadBuffer(outputName, 16);\n return { nodeId: entry.nodeId, opType: entry.node.opType, output };\n }\n\n /**\n * Diagnostic: compute the JS-side params for the first N decode entries\n * WITHOUT dispatching. Shows what buildParams produces.\n */\n debugComputeParams(\n T: number,\n count = 5,\n ): Array<{\n idx: number;\n nodeId: string;\n opType: string;\n paramsU32: number[];\n dispatchSize: [number, number, number];\n }> {\n const resolvedShapes = this.resolveShapes(T);\n const runtimeContext: RuntimeContext = { seqPos: this.seqPos };\n const entries = T === 1 ? this.decodeEntries : this.dispatchEntries;\n const results = [];\n for (let i = 0; i < Math.min(count, entries.length); i++) {\n const entry = entries[i];\n const paramsData = entry.spec.buildParams(entry.node, resolvedShapes, runtimeContext);\n const u32 = new Uint32Array(paramsData);\n const dispatchSize = entry.spec.getDispatchSize(\n entry.node,\n resolvedShapes,\n runtimeContext,\n ) as [number, number, number];\n results.push({\n idx: i,\n nodeId: entry.nodeId,\n opType: entry.node.opType,\n paramsU32: Array.from(u32),\n dispatchSize,\n });\n }\n return results;\n }\n\n /**\n * Diagnostic: after a forward pass, read back output tensors at several points\n * in the pipeline to find where data drops to zero.\n */\n async debugPipelineProbe(T: number): Promise<\n Array<{\n idx: number;\n nodeId: string;\n opType: string;\n tensor: string;\n sum: number;\n first4: number[];\n uniformParams?: number[];\n }>\n > {\n const entries = T === 1 ? this.decodeEntries : this.dispatchEntries;\n // Focus on the first few entries to find where zeros start\n const totalEntries = entries.length;\n const indices = [\n 0,\n 1,\n 2,\n 3,\n Math.floor(totalEntries / 4),\n Math.floor(totalEntries / 2),\n Math.floor((totalEntries * 3) / 4),\n totalEntries - 1,\n ];\n const unique = [...new Set(indices)].filter((i) => i >= 0 && i < totalEntries);\n\n const results: Array<{\n idx: number;\n nodeId: string;\n opType: string;\n tensor: string;\n sum: number;\n first4: number[];\n uniformParams?: number[];\n }> = [];\n for (const i of unique) {\n const entry = entries[i];\n const outputName = entry.node.outputs[0];\n if (!outputName) continue;\n try {\n const data = await this.debugReadBuffer(outputName, 16);\n let sum = 0;\n for (let j = 0; j < data.length; j++) sum += data[j];\n\n // Also read back the uniform buffer to verify params\n let uniformParams: number[] | undefined;\n try {\n const ubSize = entry.uniformBuffer.size;\n const readback = this.ctx.device.createBuffer({\n size: ubSize,\n usage: 0x0001 | 0x0008, // MAP_READ | COPY_DST\n });\n const enc = this.ctx.device.createCommandEncoder();\n enc.copyBufferToBuffer(entry.uniformBuffer, 0, readback, 0, ubSize);\n this.ctx.device.queue.submit([enc.finish()]);\n await readback.mapAsync(MAP_MODE_READ, 0, ubSize);\n const u32 = new Uint32Array(readback.getMappedRange(0, ubSize).slice(0));\n uniformParams = Array.from(u32);\n readback.unmap();\n readback.destroy();\n } catch {\n /* skip if uniform readback fails */\n }\n\n results.push({\n idx: i,\n nodeId: entry.nodeId,\n opType: entry.node.opType,\n tensor: outputName,\n sum: Math.round(sum * 1e6) / 1e6,\n first4: [data[0], data[1], data[2], data[3]].map((v) => Math.round(v * 1e6) / 1e6),\n uniformParams,\n });\n } catch {\n results.push({\n idx: i,\n nodeId: entry.nodeId,\n opType: entry.node.opType,\n tensor: outputName,\n sum: NaN,\n first4: [NaN, NaN, NaN, NaN],\n });\n }\n }\n return results;\n }\n\n debugWriteBuffer(tensorName: string, data: ArrayBufferView): void {\n const buffer = this.getBuffer(tensorName);\n if (!buffer) throw new Error(`No buffer for \"${tensorName}\"`);\n this.ctx.device.queue.writeBuffer(buffer, 0, data as BufferSource);\n }\n\n /**\n * Write a host-supplied activation input buffer (e.g. multimodal M-RoPE\n * cos/sin, spliced vision embeddings, image row-map). The buffer must be a\n * persistent activation tensor in the graph. Call before forward().\n */\n writeInput(tensorName: string, data: ArrayBufferView): void {\n const buffer = this.getBuffer(tensorName);\n if (!buffer) throw new Error(`No input buffer for \"${tensorName}\"`);\n this.ctx.device.queue.writeBuffer(buffer, 0, data as BufferSource);\n }\n\n /** Write a host activation buffer at a byte offset (for per-row decode updates). */\n writeInputAt(tensorName: string, data: ArrayBufferView, byteOffset: number): void {\n const buffer = this.getBuffer(tensorName);\n if (!buffer) throw new Error(`No input buffer for \"${tensorName}\"`);\n this.ctx.device.queue.writeBuffer(buffer, byteOffset, data as BufferSource);\n }\n\n /** True if the graph has a buffer with this name (multimodal-capability probe). */\n hasBuffer(tensorName: string): boolean {\n return this.getBuffer(tensorName) !== undefined;\n }\n\n /** Current sequence position (number of tokens processed since reset). */\n get currentSeqPos(): number {\n return this.seqPos;\n }\n\n async debugReadBuffer(\n tensorName: string,\n maxElements?: number,\n byteOffset?: number,\n ): Promise<Float32Array> {\n const buffer = this.getBuffer(tensorName);\n if (!buffer) throw new Error(`No buffer for \"${tensorName}\"`);\n const srcOffset = byteOffset ?? 0;\n const remaining = buffer.size - srcOffset;\n const byteLen = maxElements ? Math.min(maxElements * 4, remaining) : remaining;\n const readback = this.ctx.device.createBuffer({\n size: byteLen,\n usage: 0x0001 | 0x0008,\n });\n const encoder = this.ctx.device.createCommandEncoder();\n encoder.copyBufferToBuffer(buffer, srcOffset, readback, 0, byteLen);\n this.ctx.device.queue.submit([encoder.finish()]);\n await readback.mapAsync(MAP_MODE_READ, 0, byteLen);\n const data = new Float32Array(readback.getMappedRange(0, byteLen).slice(0));\n readback.unmap();\n readback.destroy();\n return data;\n }\n\n destroy(): void {\n destroyBuffers([...this.weightBuffers.values()]);\n // Pooled activation buffers are shared by multiple tensor names — dedupe.\n destroyBuffers([...new Set(this.activationBuffers.values())]);\n destroyBuffers([...this.ssmStateBuffers.values()]);\n destroyBuffers([...this.kvCacheBuffers.values()]);\n for (const entry of this.dispatchEntries) {\n entry.uniformBuffer.destroy();\n }\n // Only destroy decode-specific uniform buffers (matmul nodes have their own)\n for (let i = 0; i < this.decodeEntries.length; i++) {\n if (this.decodeEntries[i] !== this.dispatchEntries[i]) {\n this.decodeEntries[i].uniformBuffer.destroy();\n }\n }\n if (this.argmaxEntry) {\n this.argmaxEntry.uniformBuffer.destroy();\n }\n if (this.uniformStagingBuffer) {\n this.uniformStagingBuffer.destroy();\n this.uniformStagingBuffer = null;\n }\n this.inputIdsBuffer.destroy();\n this.logitsReadback.destroy();\n this.argmaxResultBuffer.destroy();\n this.argmaxReadback.destroy();\n destroyBuffers(this.decodeReadbacks);\n this.decodeReadbacks = [];\n this.weightBuffers.clear();\n this.activationBuffers.clear();\n this.ssmStateBuffers.clear();\n this.kvCacheBuffers.clear();\n this.dispatchEntries = [];\n this.decodeEntries = [];\n }\n\n // ── Private ──────────────────────────────────────────────────────\n\n /**\n * Allocate activation buffers with liveness-based reuse.\n *\n * One dedicated buffer per activation tensor at full maxSeqLen is ~2.3GB for\n * Qwen3.5-0.8B at T=512 — over the iOS jetsam budget on its own. Instead,\n * a buffer returns to a size-keyed pool once its tensor's last reader has\n * executed, so concurrently-live tensors share a small working set.\n *\n * Graph outputs and tensors read before they are written (cross-forward\n * state) keep dedicated buffers. Within a forward, dispatches execute in\n * executionOrder on every path (single-pass Dawn, per-dispatch WebKit), and\n * WebGPU synchronizes hazards between dispatches, so reuse is safe.\n *\n * Caveat: debugReadBuffer() on an intermediate tensor is only meaningful\n * before a later op reuses its buffer (probes that stop mid-graph are fine).\n */\n private allocateActivationBuffers(): void {\n const isActivation = (name: string) => this.graph.tensors[name]?.storage === \"activation\";\n // Debug: give every activation its own buffer so post-hoc debugReadBuffer of\n // intermediate tensors is reliable (pooling otherwise recycles buffers).\n const noPool = typeof process !== \"undefined\" && process.env?.GERBIL_NO_ACT_POOL === \"1\";\n if (noPool) {\n for (const name of Object.keys(this.graph.tensors)) {\n if (!isActivation(name)) continue;\n const desc = this.graph.tensors[name];\n const shape = desc.shape.map((d) =>\n d === \"T\" || d === \"L_max\" ? this.maxSeqLen : (d as number),\n );\n const bytes = shape.reduce((a, b) => a * b, 1) * DTYPE_BYTES[desc.dtype];\n this.activationBuffers.set(\n name,\n createStorageBuffer(this.ctx, `act_nopool_${name}`, bytes),\n );\n }\n console.log(\n `[executor] activations: NO-POOL debug mode → ${this.activationBuffers.size} dedicated buffers`,\n );\n return;\n }\n const resolveBytes = (name: string): number => {\n const desc = this.graph.tensors[name];\n const shape = desc.shape.map((d) => {\n if (d === \"T\") return this.maxSeqLen;\n if (d === \"L_max\") return this.maxSeqLen;\n return d as number;\n });\n return shape.reduce((a, b) => a * b, 1) * DTYPE_BYTES[desc.dtype];\n };\n\n const nodeById = new Map(this.graph.nodes.map((n) => [n.id, n]));\n const order = this.graph.executionOrder;\n const persistent = new Set<string>(this.graph.outputs);\n const firstDef = new Map<string, number>();\n const lastUse = new Map<string, number>();\n for (let i = 0; i < order.length; i++) {\n const node = nodeById.get(order[i])!;\n for (const inp of node.inputs) {\n if (!isActivation(inp)) continue;\n // Read before any write → carries state across forwards (or in-place op);\n // never pool it.\n if (!firstDef.has(inp)) persistent.add(inp);\n lastUse.set(inp, i);\n }\n for (const out of node.outputs) {\n if (isActivation(out) && !firstDef.has(out)) firstDef.set(out, i);\n }\n }\n\n const freePool = new Map<number, GPUBuffer[]>();\n let created = 0;\n const released = new Set<string>();\n for (let i = 0; i < order.length; i++) {\n const node = nodeById.get(order[i])!;\n // Acquire outputs first so a node never writes the buffer of a tensor\n // it also reads.\n for (const out of node.outputs) {\n if (!isActivation(out) || persistent.has(out) || this.activationBuffers.has(out)) {\n continue;\n }\n const byteSize = resolveBytes(out);\n const pool = freePool.get(byteSize);\n let buffer = pool?.pop();\n if (!buffer) {\n buffer = createStorageBuffer(this.ctx, `act_pool_${created}_${byteSize}b`, byteSize);\n created++;\n }\n this.activationBuffers.set(out, buffer);\n }\n // Release buffers whose tensor will never be read again.\n for (const name of [...node.inputs, ...node.outputs]) {\n if (!isActivation(name) || persistent.has(name) || released.has(name)) continue;\n const last = lastUse.get(name) ?? firstDef.get(name) ?? i;\n if (last > i) continue;\n const buffer = this.activationBuffers.get(name);\n if (!buffer) continue;\n released.add(name);\n const byteSize = resolveBytes(name);\n const pool = freePool.get(byteSize) ?? [];\n pool.push(buffer);\n freePool.set(byteSize, pool);\n }\n }\n\n // Dedicated buffers for graph outputs, cross-forward state, and any\n // activation tensor not touched by the execution order.\n for (const name of Object.keys(this.graph.tensors)) {\n if (!isActivation(name) || this.activationBuffers.has(name)) continue;\n this.activationBuffers.set(\n name,\n createStorageBuffer(this.ctx, `act_${name}`, resolveBytes(name)),\n );\n }\n\n const unique = new Set(this.activationBuffers.values());\n let totalBytes = 0;\n for (const b of unique) totalBytes += b.size;\n console.log(\n `[executor] activations: ${this.activationBuffers.size} tensors → ${unique.size} buffers (${(totalBytes / 1e6).toFixed(0)} MB)`,\n );\n }\n\n private allocateSSMStateBuffers(): void {\n for (const [name, desc] of Object.entries(this.graph.tensors)) {\n if (desc.storage !== \"ssm_state\") continue;\n const shape = desc.shape as number[];\n const byteSize = shape.reduce((a, b) => a * b, 1) * DTYPE_BYTES[desc.dtype];\n const buffer = createStorageBuffer(this.ctx, `ssm_${name}`, byteSize);\n this.ssmStateBuffers.set(name, buffer);\n }\n }\n\n private allocateKVCacheBuffers(): void {\n for (const [name, desc] of Object.entries(this.graph.tensors)) {\n if (desc.storage !== \"kv_cache\") continue;\n const shape = desc.shape.map((d) => {\n if (d === \"L_max\") return this.maxSeqLen;\n return d as number;\n });\n const byteSize = shape.reduce((a, b) => a * b, 1) * DTYPE_BYTES[desc.dtype];\n const buffer = createStorageBuffer(this.ctx, `kv_${name}`, byteSize);\n this.kvCacheBuffers.set(name, buffer);\n }\n }\n\n private resolveShapes(T: number): Record<string, number[]> {\n const resolved: Record<string, number[]> = {};\n for (const [name, desc] of Object.entries(this.graph.tensors)) {\n resolved[name] = desc.shape.map((d) => {\n if (d === \"T\") return T;\n if (d === \"L_max\") return this.seqPos + T;\n return d as number;\n });\n }\n return resolved;\n }\n\n private getBuffer(tensorName: string): GPUBuffer | undefined {\n return (\n this.weightBuffers.get(tensorName) ??\n this.activationBuffers.get(tensorName) ??\n this.ssmStateBuffers.get(tensorName) ??\n this.kvCacheBuffers.get(tensorName)\n );\n }\n\n /**\n * Detect gate_proj + up_proj + SwiGLU patterns in decode entries and replace\n * with a single fused SwiGLUMatVec dispatch. Saves 2 dispatches per MLP block.\n */\n private fuseSwiGLUDecodeEntries(): void {\n let fusionCount = 0;\n const maxBindings = this.ctx.limits.maxStorageBuffersPerShaderStage;\n\n for (let i = 0; i < this.decodeEntries.length - 2; i++) {\n const e1 = this.decodeEntries[i]; // gate_proj MatVec\n const e2 = this.decodeEntries[i + 1]; // up_proj MatVec\n const e3 = this.decodeEntries[i + 2]; // SwiGLU\n\n // Check op types: two MatMul/MatMulInt4 followed by SwiGLU\n const isE1Mat = e1.node.opType === \"MatMul\" || e1.node.opType === \"MatMulInt4\";\n const isE2Mat = e2.node.opType === \"MatMul\" || e2.node.opType === \"MatMulInt4\";\n if (!isE1Mat || !isE2Mat || e3.node.opType !== \"SwiGLU\") continue;\n\n // Both projections must use the same op type (both F32 or both INT4)\n if (e1.node.opType !== e2.node.opType) continue;\n\n // INT4 fused kernel needs 9 storage bindings — skip if device doesn't support it\n const isInt4Fusion = e1.node.opType === \"MatMulInt4\";\n if (isInt4Fusion && maxBindings < 9) continue;\n\n // Both must share the same input activation (first input tensor)\n if (e1.node.inputs[0] !== e2.node.inputs[0]) continue;\n\n // SwiGLU must consume the outputs of both projections\n const gateOut = e1.node.outputs[0];\n const upOut = e2.node.outputs[0];\n if (e3.node.inputs[0] !== gateOut || e3.node.inputs[1] !== upOut) continue;\n\n // Same K and N dimensions\n if (e1.node.attributes.K !== e2.node.attributes.K) continue;\n if (e1.node.attributes.N !== e2.node.attributes.N) continue;\n\n // Pooled activation aliasing: the fused kernel reads the shared input\n // and writes the SwiGLU output in ONE dispatch — they must not share\n // a pooled buffer (separate dispatches are hazard-synchronized; a\n // single dispatch is not).\n if (this.getBuffer(e1.node.inputs[0]) === this.getBuffer(e3.node.outputs[0])) continue;\n\n // Pattern matched — create fused dispatch entry\n const fusedSpec = isInt4Fusion ? SWIGLU_MATVEC_INT4_SPEC : SWIGLU_MATVEC_SPEC;\n const fusedPipeline = getOrCreatePipeline(\n this.ctx,\n `swiglu_mv_${e1.nodeId}`,\n fusedSpec.shaderCode,\n fusedSpec.entryPoint,\n );\n\n const fusedUniformData = fusedSpec.buildParams(e1.node, {}, { seqPos: 0 });\n const fusedUniform = createUniformBuffer(\n this.ctx,\n `uniform_swiglu_mv_${e1.nodeId}`,\n fusedUniformData,\n );\n\n // Build bind group manually: input, gate weights, up weights, output, uniform\n const inputBuf = this.getBuffer(e1.node.inputs[0])!;\n const outputBuf = this.getBuffer(e3.node.outputs[0])!;\n\n let bufferEntries: Array<{ buffer: GPUBuffer }>;\n if (isInt4Fusion) {\n // INT4: A, gate_q, gate_s, gate_z, up_q, up_s, up_z, output, params\n const gateQ = this.getBuffer(e1.node.inputs[1])!;\n const gateS = this.getBuffer(e1.node.inputs[2])!;\n const gateZ = this.getBuffer(e1.node.inputs[3])!;\n const upQ = this.getBuffer(e2.node.inputs[1])!;\n const upS = this.getBuffer(e2.node.inputs[2])!;\n const upZ = this.getBuffer(e2.node.inputs[3])!;\n bufferEntries = [\n { buffer: inputBuf },\n { buffer: gateQ },\n { buffer: gateS },\n { buffer: gateZ },\n { buffer: upQ },\n { buffer: upS },\n { buffer: upZ },\n { buffer: outputBuf },\n { buffer: fusedUniform },\n ];\n } else {\n // F32: A, B_gate, B_up, output, params\n const gateBuf = this.getBuffer(e1.node.inputs[1])!;\n const upBuf = this.getBuffer(e2.node.inputs[1])!;\n bufferEntries = [\n { buffer: inputBuf },\n { buffer: gateBuf },\n { buffer: upBuf },\n { buffer: outputBuf },\n { buffer: fusedUniform },\n ];\n }\n\n const fusedBindGroup = createBindGroup(\n this.ctx,\n fusedPipeline,\n bufferEntries,\n `bg_swiglu_mv_${e1.nodeId}`,\n );\n\n const fusedEntry: DispatchEntry = {\n nodeId: `fused_swiglu_${e1.nodeId}`,\n node: e1.node, // Use gate node for K/N attributes\n spec: fusedSpec,\n pipeline: fusedPipeline,\n bindGroup: fusedBindGroup,\n uniformBuffer: fusedUniform,\n lastParamsBytes: null,\n lastDispatchSize: null,\n };\n\n // Replace 3 entries with 1 fused entry\n this.decodeEntries.splice(i, 3, fusedEntry);\n fusionCount++;\n // Don't increment i — check if next position is also fusable\n }\n\n if (fusionCount > 0) {\n console.log(\n `[executor] Fused ${fusionCount} SwiGLU MLP blocks (saved ${fusionCount * 2} dispatches)`,\n );\n }\n }\n\n /**\n * Fuse two adjacent INT4 projections that share the same input activation and\n * the same K/N (e.g. q_proj+gate_proj and k_proj+v_proj in full-attention\n * decode) into a single DualMatVecInt4 dispatch. Reads the shared input vector\n * once and writes both projection outputs — removing one GPU round-trip (one\n * submit+drain on Safari/iOS) per fused pair.\n *\n * Numerics are identical to running the two MatVecInt4 kernels separately\n * (same dequant, same K-parallel reduction), so this is WebKit-safe: it reuses\n * the proven INT4 matvec math and only merges two writes into one dispatch.\n *\n * Must run AFTER fuseSwiGLUDecodeEntries so the MLP gate+up pair (which is\n * consumed by a SwiGLU node) is already collapsed and won't be matched here.\n */\n private fuseDualMatVecDecodeEntries(): void {\n let fusionCount = 0;\n // DualMatVecInt4 needs 10 storage/uniform bindings (9 storage + 1 uniform).\n const maxBindings = this.ctx.limits.maxStorageBuffersPerShaderStage;\n if (maxBindings < 9) return;\n\n for (let i = 0; i < this.decodeEntries.length - 1; i++) {\n const e0 = this.decodeEntries[i];\n const e1 = this.decodeEntries[i + 1];\n\n // Both must be INT4 matmuls.\n if (e0.node.opType !== \"MatMulInt4\" || e1.node.opType !== \"MatMulInt4\") continue;\n // Same input activation (first input tensor).\n if (e0.node.inputs[0] !== e1.node.inputs[0]) continue;\n // Same K, N, group_size.\n if (e0.node.attributes.K !== e1.node.attributes.K) continue;\n if (e0.node.attributes.N !== e1.node.attributes.N) continue;\n if (e0.node.attributes.group_size !== e1.node.attributes.group_size) continue;\n\n const out0 = e0.node.outputs[0];\n const out1 = e1.node.outputs[0];\n const inputBuf = this.getBuffer(e0.node.inputs[0]);\n const out0Buf = this.getBuffer(out0);\n const out1Buf = this.getBuffer(out1);\n if (!inputBuf || !out0Buf || !out1Buf) continue;\n\n // Pooled-buffer aliasing guard: the single fused dispatch reads the shared\n // input and writes both outputs. None of these may share a pooled buffer\n // (separate dispatches are hazard-synchronized; one dispatch is not), and\n // the two outputs must be distinct buffers.\n if (inputBuf === out0Buf || inputBuf === out1Buf || out0Buf === out1Buf) continue;\n\n const fusedSpec = DUAL_MATVEC_INT4_SPEC;\n const fusedPipeline = getOrCreatePipeline(\n this.ctx,\n `dual_mv_${e0.nodeId}`,\n fusedSpec.shaderCode,\n fusedSpec.entryPoint,\n );\n\n const fusedUniformData = fusedSpec.buildParams(e0.node, {}, { seqPos: 0 });\n const fusedUniform = createUniformBuffer(\n this.ctx,\n `uniform_dual_mv_${e0.nodeId}`,\n fusedUniformData,\n );\n\n // Bind group: A, B0_q, B0_s, B0_z, B1_q, B1_s, B1_z, out0, out1, params\n const b0q = this.getBuffer(e0.node.inputs[1])!;\n const b0s = this.getBuffer(e0.node.inputs[2])!;\n const b0z = this.getBuffer(e0.node.inputs[3])!;\n const b1q = this.getBuffer(e1.node.inputs[1])!;\n const b1s = this.getBuffer(e1.node.inputs[2])!;\n const b1z = this.getBuffer(e1.node.inputs[3])!;\n\n const bufferEntries = [\n { buffer: inputBuf },\n { buffer: b0q },\n { buffer: b0s },\n { buffer: b0z },\n { buffer: b1q },\n { buffer: b1s },\n { buffer: b1z },\n { buffer: out0Buf },\n { buffer: out1Buf },\n { buffer: fusedUniform },\n ];\n\n const fusedBindGroup = createBindGroup(\n this.ctx,\n fusedPipeline,\n bufferEntries,\n `bg_dual_mv_${e0.nodeId}`,\n );\n\n const fusedEntry: DispatchEntry = {\n nodeId: `fused_dual_${e0.nodeId}`,\n node: e0.node, // K/N/group_size attributes shared\n spec: fusedSpec,\n pipeline: fusedPipeline,\n bindGroup: fusedBindGroup,\n uniformBuffer: fusedUniform,\n lastParamsBytes: null,\n lastDispatchSize: null,\n };\n\n this.decodeEntries.splice(i, 2, fusedEntry);\n fusionCount++;\n // Don't increment i — the next pair may also be fusable.\n }\n\n if (fusionCount > 0) {\n console.log(\n `[executor] Fused ${fusionCount} dual INT4 projections (saved ${fusionCount} dispatches)`,\n );\n }\n }\n\n /**\n * Fuse the adjacent K-cache and V-cache appends in each full-attention layer\n * into a single DualKVCacheAppend dispatch. Both are pure memcpys into f32\n * caches sharing the same width and dst_offset, so one dispatch with two\n * src/dst buffers writes both — removing one GPU round-trip per layer.\n *\n * Supports f32, native-f16, and packed-f16 caches (the dual kernel mirrors the\n * single-append kernel selected for the active kvMode). Numerically identical\n * to the separate appends — WebKit-safe (the packed-f16 variant is the Safari\n * path and uses pack2x16float, no `enable f16`).\n */\n private fuseDualKVCacheAppendEntries(): void {\n let fusionCount = 0;\n\n for (let i = 0; i < this.decodeEntries.length - 1; i++) {\n const e0 = this.decodeEntries[i]; // K append\n const e1 = this.decodeEntries[i + 1]; // V append\n\n if (e0.node.opType !== \"KVCacheAppend\" || e1.node.opType !== \"KVCacheAppend\") continue;\n // Same per-element width (kv_dim) → one param set drives both.\n if (e0.node.attributes.width !== e1.node.attributes.width) continue;\n\n // Select the dual kernel matching the cache dtype/mode. Both destinations\n // must share the same dtype (they always do: K and V caches are symmetric).\n const dstK = e0.node.outputs[0];\n const dstV = e1.node.outputs[0];\n const dtK = this.graph.tensors[dstK]?.dtype;\n const dtV = this.graph.tensors[dstV]?.dtype;\n if (dtK !== dtV) continue;\n let fusedSpec: KernelSpec;\n if (dtK === \"f32\") {\n fusedSpec = DUAL_KV_CACHE_APPEND_SPEC;\n } else if (dtK === \"f16\") {\n fusedSpec =\n this.kvMode === \"packed-f16\"\n ? DUAL_KV_CACHE_APPEND_PACKED_F16_SPEC\n : DUAL_KV_CACHE_APPEND_F16_SPEC;\n } else {\n continue;\n }\n\n const srcK = this.getBuffer(e0.node.inputs[0]);\n const srcV = this.getBuffer(e1.node.inputs[0]);\n const dstKBuf = this.getBuffer(dstK);\n const dstVBuf = this.getBuffer(dstV);\n if (!srcK || !srcV || !dstKBuf || !dstVBuf) continue;\n // Distinct caches; sources distinct from caches.\n if (dstKBuf === dstVBuf) continue;\n\n const fusedPipeline = getOrCreatePipeline(\n this.ctx,\n `dual_kv_${e0.nodeId}`,\n fusedSpec.shaderCode,\n fusedSpec.entryPoint,\n );\n\n const dummyShapes = this.resolveShapes(1);\n const fusedUniformData = fusedSpec.buildParams(e0.node, dummyShapes, { seqPos: 0 });\n const fusedUniform = createUniformBuffer(\n this.ctx,\n `uniform_dual_kv_${e0.nodeId}`,\n fusedUniformData,\n );\n\n const bufferEntries = [\n { buffer: srcK },\n { buffer: srcV },\n { buffer: dstKBuf },\n { buffer: dstVBuf },\n { buffer: fusedUniform },\n ];\n\n const fusedBindGroup = createBindGroup(\n this.ctx,\n fusedPipeline,\n bufferEntries,\n `bg_dual_kv_${e0.nodeId}`,\n );\n\n const fusedEntry: DispatchEntry = {\n nodeId: `fused_kv_${e0.nodeId}`,\n node: e0.node, // width + T_tensor for per-token param rebuild\n spec: fusedSpec,\n pipeline: fusedPipeline,\n bindGroup: fusedBindGroup,\n uniformBuffer: fusedUniform,\n lastParamsBytes: null,\n lastDispatchSize: null,\n };\n\n this.decodeEntries.splice(i, 2, fusedEntry);\n fusionCount++;\n }\n\n if (fusionCount > 0) {\n console.log(\n `[executor] Fused ${fusionCount} dual KV-cache appends (saved ${fusionCount} dispatches)`,\n );\n }\n }\n\n /**\n * Fuse the attention SigmoidGate (attn_out * sigmoid(gate)) into the INT4\n * o_proj that consumes it: a GatedMatVecInt4 reads attn_out and gate directly,\n * applies the sigmoid gate to its input vector, and runs the projection in ONE\n * dispatch — removing the standalone SigmoidGate (one round-trip per\n * full-attention layer).\n *\n * Numerically identical to SigmoidGate→MatVecInt4 (same gate formula, same INT4\n * dequant + reduction). Slight extra ALU: the gated input is recomputed per\n * output column, but A reads hit L1 and the saved submit+drain dominates on\n * mobile. WebKit risk: low — reuses the proven INT4 matvec, only the A vector\n * is built from two reads + a sigmoid (no new reduction/barrier pattern).\n *\n * Runs after the dual fusions so it only sees the post-attention SigmoidGate.\n */\n private fuseGatedOProjDecodeEntries(): void {\n let fusionCount = 0;\n\n for (let i = 0; i < this.decodeEntries.length - 1; i++) {\n const eg = this.decodeEntries[i]; // SigmoidGate\n const eo = this.decodeEntries[i + 1]; // o_proj MatMulInt4\n\n if (eg.node.opType !== \"SigmoidGate\") continue;\n if (eo.node.opType !== \"MatMulInt4\") continue;\n\n // o_proj must consume the gate's output as its activation input.\n const gatedOut = eg.node.outputs[0];\n if (eo.node.inputs[0] !== gatedOut) continue;\n\n // SigmoidGate inputs: [x (attn_out), gate]\n const attnName = eg.node.inputs[0];\n const gateName = eg.node.inputs[1];\n const attnBuf = this.getBuffer(attnName);\n const gateBuf = this.getBuffer(gateName);\n const wQ = this.getBuffer(eo.node.inputs[1]);\n const wS = this.getBuffer(eo.node.inputs[2]);\n const wZ = this.getBuffer(eo.node.inputs[3]);\n const outBuf = this.getBuffer(eo.node.outputs[0]);\n if (!attnBuf || !gateBuf || !wQ || !wS || !wZ || !outBuf) continue;\n\n // Pooled aliasing: the single fused dispatch reads attn + gate and writes\n // the projection output — none may share a pooled buffer.\n if (outBuf === attnBuf || outBuf === gateBuf || attnBuf === gateBuf) continue;\n\n const fusedSpec = GATED_MATVEC_INT4_SPEC;\n const fusedPipeline = getOrCreatePipeline(\n this.ctx,\n `gated_o_${eo.nodeId}`,\n fusedSpec.shaderCode,\n fusedSpec.entryPoint,\n );\n\n const fusedUniformData = fusedSpec.buildParams(eo.node, {}, { seqPos: 0 });\n const fusedUniform = createUniformBuffer(\n this.ctx,\n `uniform_gated_o_${eo.nodeId}`,\n fusedUniformData,\n );\n\n const bufferEntries = [\n { buffer: attnBuf },\n { buffer: gateBuf },\n { buffer: wQ },\n { buffer: wS },\n { buffer: wZ },\n { buffer: outBuf },\n { buffer: fusedUniform },\n ];\n\n const fusedBindGroup = createBindGroup(\n this.ctx,\n fusedPipeline,\n bufferEntries,\n `bg_gated_o_${eo.nodeId}`,\n );\n\n const fusedEntry: DispatchEntry = {\n nodeId: `fused_gated_o_${eo.nodeId}`,\n node: eo.node, // K/N/group_size attributes for per-token param rebuild\n spec: fusedSpec,\n pipeline: fusedPipeline,\n bindGroup: fusedBindGroup,\n uniformBuffer: fusedUniform,\n lastParamsBytes: null,\n lastDispatchSize: null,\n };\n\n this.decodeEntries.splice(i, 2, fusedEntry);\n fusionCount++;\n }\n\n if (fusionCount > 0) {\n console.log(\n `[executor] Fused ${fusionCount} gated o_proj blocks (saved ${fusionCount} dispatches)`,\n );\n }\n }\n\n /**\n * Fuse a standalone SwiGLU (silu(gate) * up) into the INT4 projection that\n * consumes its output: a SwiGLUGatedMatVecInt4 reads gate and up directly,\n * builds the gated input vector, and runs the projection in ONE dispatch.\n * Targets the Mamba block's mamba_swiglu (silu(z) * norm_out) feeding out_proj\n * — one round-trip saved per linear-attention layer.\n *\n * The MLP SwiGLU is already collapsed into a SwiGLUMatVec entry by\n * fuseSwiGLUDecodeEntries (it has no surviving standalone SwiGLU node), so only\n * the Mamba SwiGLU matches here. Numerically identical to SwiGLU→MatVecInt4;\n * WebKit risk low (reuses the proven INT4 matvec, only the A vector changes).\n */\n private fuseSwiGLUGatedProjDecodeEntries(): void {\n let fusionCount = 0;\n\n for (let i = 0; i < this.decodeEntries.length - 1; i++) {\n const es = this.decodeEntries[i]; // SwiGLU\n const ep = this.decodeEntries[i + 1]; // projection MatMulInt4\n\n if (es.node.opType !== \"SwiGLU\") continue;\n if (ep.node.opType !== \"MatMulInt4\") continue;\n\n const swigluOut = es.node.outputs[0];\n if (ep.node.inputs[0] !== swigluOut) continue;\n\n // SwiGLU inputs: [gate, up]\n const gateName = es.node.inputs[0];\n const upName = es.node.inputs[1];\n const gateBuf = this.getBuffer(gateName);\n const upBuf = this.getBuffer(upName);\n const wQ = this.getBuffer(ep.node.inputs[1]);\n const wS = this.getBuffer(ep.node.inputs[2]);\n const wZ = this.getBuffer(ep.node.inputs[3]);\n const outBuf = this.getBuffer(ep.node.outputs[0]);\n if (!gateBuf || !upBuf || !wQ || !wS || !wZ || !outBuf) continue;\n\n // Pooled aliasing: the single fused dispatch reads gate + up and writes the\n // projection output — none may share a pooled buffer.\n if (outBuf === gateBuf || outBuf === upBuf || gateBuf === upBuf) continue;\n\n const fusedSpec = SWIGLU_GATED_MATVEC_INT4_SPEC;\n const fusedPipeline = getOrCreatePipeline(\n this.ctx,\n `swiglu_gated_${ep.nodeId}`,\n fusedSpec.shaderCode,\n fusedSpec.entryPoint,\n );\n\n const fusedUniformData = fusedSpec.buildParams(ep.node, {}, { seqPos: 0 });\n const fusedUniform = createUniformBuffer(\n this.ctx,\n `uniform_swiglu_gated_${ep.nodeId}`,\n fusedUniformData,\n );\n\n const bufferEntries = [\n { buffer: gateBuf },\n { buffer: upBuf },\n { buffer: wQ },\n { buffer: wS },\n { buffer: wZ },\n { buffer: outBuf },\n { buffer: fusedUniform },\n ];\n\n const fusedBindGroup = createBindGroup(\n this.ctx,\n fusedPipeline,\n bufferEntries,\n `bg_swiglu_gated_${ep.nodeId}`,\n );\n\n const fusedEntry: DispatchEntry = {\n nodeId: `fused_swiglu_gated_${ep.nodeId}`,\n node: ep.node,\n spec: fusedSpec,\n pipeline: fusedPipeline,\n bindGroup: fusedBindGroup,\n uniformBuffer: fusedUniform,\n lastParamsBytes: null,\n lastDispatchSize: null,\n };\n\n this.decodeEntries.splice(i, 2, fusedEntry);\n fusionCount++;\n }\n\n if (fusionCount > 0) {\n console.log(\n `[executor] Fused ${fusionCount} SwiGLU-gated projections (saved ${fusionCount} dispatches)`,\n );\n }\n }\n\n /**\n * Fuse two adjacent per-row RMSNorms sharing hidden_size + eps into a single\n * DualRMSNorm dispatch (e.g. the per-head q_norm and k_norm in full-attention\n * decode). One workgroup still handles one row; the fused grid just spans both\n * inputs' rows, so each row's reduction is unchanged — numerically identical to\n * two separate RMSNorm dispatches. One round-trip saved per fused pair.\n *\n * WebKit risk: low — same single-workgroup reduction as the proven RMSNorm\n * kernel, only the row→input routing is added.\n */\n private fuseDualRMSNormDecodeEntries(): void {\n let fusionCount = 0;\n\n for (let i = 0; i < this.decodeEntries.length - 1; i++) {\n const e0 = this.decodeEntries[i];\n const e1 = this.decodeEntries[i + 1];\n\n if (e0.node.opType !== \"RMSNorm\" || e1.node.opType !== \"RMSNorm\") continue;\n // Must share per-row width and eps (the dual kernel uses one of each).\n if (e0.node.attributes.hidden_size !== e1.node.attributes.hidden_size) continue;\n if (e0.node.attributes.eps !== e1.node.attributes.eps) continue;\n\n const in0 = e0.node.inputs[0];\n const w0 = e0.node.inputs[1];\n const out0 = e0.node.outputs[0];\n const in1 = e1.node.inputs[0];\n const w1 = e1.node.inputs[1];\n const out1 = e1.node.outputs[0];\n\n const in0Buf = this.getBuffer(in0);\n const w0Buf = this.getBuffer(w0);\n const out0Buf = this.getBuffer(out0);\n const in1Buf = this.getBuffer(in1);\n const w1Buf = this.getBuffer(w1);\n const out1Buf = this.getBuffer(out1);\n if (!in0Buf || !w0Buf || !out0Buf || !in1Buf || !w1Buf || !out1Buf) continue;\n\n // The two normalizations must write distinct buffers. (In-place RMSNorm\n // where input===output is fine: each row reads then writes its own row,\n // and the fused kernel preserves per-row ordering.) Cross-aliasing — where\n // one norm's input shares a pooled buffer with the OTHER norm's output —\n // would create an unsynchronized read/write hazard within the single\n // dispatch, so reject it.\n if (out0Buf === out1Buf) continue;\n if (in0Buf === out1Buf || in1Buf === out0Buf) continue;\n\n // Build a synthetic node carrying both row sources so the spec can resolve\n // per-token row counts each decode step.\n const fusedNode: OpNode = {\n id: `fused_dualnorm_${e0.nodeId}`,\n opType: \"RMSNorm\",\n inputs: [in0, w0, in1, w1],\n outputs: [out0, out1],\n attributes: {\n hidden_size: e0.node.attributes.hidden_size,\n eps: e0.node.attributes.eps,\n seq_len_tensor0: e0.node.attributes.seq_len_tensor,\n seq_len_tensor1: e1.node.attributes.seq_len_tensor,\n seq_len0: e0.node.attributes.seq_len,\n seq_len1: e1.node.attributes.seq_len,\n },\n };\n\n const fusedSpec = DUAL_RMSNORM_SPEC;\n const fusedPipeline = getOrCreatePipeline(\n this.ctx,\n `dual_norm_${e0.nodeId}`,\n fusedSpec.shaderCode,\n fusedSpec.entryPoint,\n );\n\n const dummyShapes = this.resolveShapes(1);\n const fusedUniformData = fusedSpec.buildParams(fusedNode, dummyShapes, { seqPos: 0 });\n const fusedUniform = createUniformBuffer(\n this.ctx,\n `uniform_dual_norm_${e0.nodeId}`,\n fusedUniformData,\n );\n\n const bufferEntries = [\n { buffer: in0Buf },\n { buffer: w0Buf },\n { buffer: in1Buf },\n { buffer: w1Buf },\n { buffer: out0Buf },\n { buffer: out1Buf },\n { buffer: fusedUniform },\n ];\n\n const fusedBindGroup = createBindGroup(\n this.ctx,\n fusedPipeline,\n bufferEntries,\n `bg_dual_norm_${e0.nodeId}`,\n );\n\n const fusedEntry: DispatchEntry = {\n nodeId: fusedNode.id,\n node: fusedNode,\n spec: fusedSpec,\n pipeline: fusedPipeline,\n bindGroup: fusedBindGroup,\n uniformBuffer: fusedUniform,\n lastParamsBytes: null,\n lastDispatchSize: null,\n };\n\n this.decodeEntries.splice(i, 2, fusedEntry);\n fusionCount++;\n }\n\n if (fusionCount > 0) {\n console.log(\n `[executor] Fused ${fusionCount} dual RMSNorms (saved ${fusionCount} dispatches)`,\n );\n }\n }\n\n /**\n * Gather buffer entries for a bind group, matching the kernel spec's binding layout.\n * Uses the pre-allocated inputIdsBuffer for the \"input_ids\" tensor.\n */\n private gatherBuffers(\n spec: KernelSpec,\n node: OpNode,\n uniformBuffer: GPUBuffer,\n ): Array<{ buffer: GPUBuffer }> {\n const entries: Array<{ buffer: GPUBuffer }> = [];\n const allTensorNames = [...node.inputs, ...node.outputs];\n\n // Track buffers already bound to a writable slot in this group so we can\n // detect (and break) read_write aliasing, which WebGPU rejects.\n const writableBound = new Set<GPUBuffer>();\n\n for (let i = 0; i < spec.bindings.length; i++) {\n const binding = spec.bindings[i];\n\n if (binding.type === \"uniform\") {\n entries.push({ buffer: uniformBuffer });\n } else {\n const tensorIdx = i - spec.bindings.filter((b, j) => j < i && b.type === \"uniform\").length;\n const tensorName = allTensorNames[tensorIdx];\n if (!tensorName) throw new Error(`Missing tensor for binding ${i} in op ${node.id}`);\n\n let buffer: GPUBuffer | undefined;\n if (tensorName === \"input_ids\") {\n buffer = this.inputIdsBuffer;\n } else {\n buffer = this.getBuffer(tensorName);\n }\n if (!buffer) throw new Error(`No GPU buffer for tensor \"${tensorName}\" in op ${node.id}`);\n\n // Break read_write aliasing: if a writable binding would reuse a buffer\n // already bound writable in this group (e.g. RoPE-Q-only nodes that list\n // the same tensor as input+output and leave the k slot unused), bind a\n // throwaway scratch buffer instead. Safe because the aliasing slot is not\n // actually accessed at dispatch time (num_kv_heads=0 → no k work).\n if (binding.type === \"storage-read-write\") {\n if (writableBound.has(buffer)) {\n buffer = this.getBindingScratchBuffer(buffer.size);\n } else {\n writableBound.add(buffer);\n }\n }\n\n entries.push({ buffer });\n }\n }\n\n return entries;\n }\n\n /** Lazily allocate a scratch storage buffer at least `minBytes` large. */\n private getBindingScratchBuffer(minBytes: number): GPUBuffer {\n if (!this.bindingScratchBuffer || this.bindingScratchBuffer.size < minBytes) {\n this.bindingScratchBuffer = createStorageBuffer(\n this.ctx,\n \"binding_scratch\",\n Math.max(minBytes, 256),\n );\n }\n return this.bindingScratchBuffer;\n }\n}\n","/**\n * GPTQ → Gerbil INT4 format adapter.\n *\n * Repacks GPTQ-quantized weights (qweight/qzeros/scales) into the flat\n * row-major packing expected by Gerbil's MatMulInt4 WGSL kernel.\n *\n * GPTQ layout (AutoGPTQ, per linear layer):\n * qweight: Int32Array, shape [K/8, N] — 8 nibbles per int32 along INPUT dim (K)\n * scales: Float32Array, shape [K/group_size, N] — per-group per-output-column\n * qzeros: Int32Array, shape [K/group_size, N/8] — packed 4-bit zero points along N\n *\n * Gerbil layout (per linear layer):\n * packed: Uint32Array, flat [N*K/8] — 8 nibbles per u32 in row-major (N,K) order\n * scales: Float32Array, flat [N * K/group_size] — sequential in (N,K) row-major\n * zeros: Float32Array, flat [N * K/group_size]\n *\n * The kernel indexes: flat_idx = col * K + k, group = flat_idx / group_size\n */\n\nexport interface GPTQTensors {\n qweight: Int32Array; // [K/8, N]\n scales: Float32Array; // [K/group_size, N]\n qzeros: Int32Array; // [K/group_size, N/8]\n K: number;\n N: number;\n groupSize: number;\n}\n\nexport interface GerbilINT4Tensors {\n packed: Uint32Array;\n scales: Float32Array;\n zeros: Float32Array;\n}\n\n/**\n * Repack GPTQ tensors into Gerbil's flat INT4 format.\n *\n * This is pure data reshuffling — no quantization math. The 4-bit values,\n * scales, and zero points are preserved exactly.\n */\nexport function repackGPTQ(gptq: GPTQTensors): GerbilINT4Tensors {\n const { qweight, scales: gptqScales, qzeros, K, N, groupSize } = gptq;\n const totalElements = N * K;\n const groupsPerCol = Math.ceil(K / groupSize);\n const totalGroups = N * groupsPerCol;\n\n const packed = new Uint32Array(Math.ceil(totalElements / 8));\n const scales = new Float32Array(totalGroups);\n const zeros = new Float32Array(totalGroups);\n\n // Repack nibbles: GPTQ [K/8, N] → Gerbil flat [N*K/8]\n // GPTQ: qweight[(k >> 3) * N + col], nibble at (k & 7) * 4\n // Gerbil: packed[(col*K+k)/8], nibble at ((col*K+k) % 8) * 4\n for (let k = 0; k < K; k++) {\n const gptqRow = k >>> 3;\n const gptqNibblePos = k & 7;\n const gptqRowBase = gptqRow * N;\n for (let col = 0; col < N; col++) {\n // Extract nibble from GPTQ\n const gptqWord = qweight[gptqRowBase + col];\n const nibble = (gptqWord >>> (gptqNibblePos * 4)) & 0xf;\n\n // Write nibble into Gerbil flat layout\n const flatIdx = col * K + k;\n const packedIdx = flatIdx >>> 3;\n const nibblePos = flatIdx & 7;\n packed[packedIdx] |= nibble << (nibblePos * 4);\n }\n }\n\n // Repack scales: GPTQ [groups_per_col, N] → Gerbil flat [N * groups_per_col]\n // GPTQ: scales[g * N + col]\n // Gerbil: scales[col * groups_per_col + g]\n for (let g = 0; g < groupsPerCol; g++) {\n const srcRow = g * N;\n for (let col = 0; col < N; col++) {\n scales[col * groupsPerCol + g] = gptqScales[srcRow + col];\n }\n }\n\n // Repack zeros: GPTQ [groups_per_col, N/8] packed int4 → Gerbil flat float32\n // GPTQ: qzeros[g * (N/8) + (col >> 3)], nibble at (col & 7) * 4\n // Gerbil: zeros[col * groups_per_col + g] as float32\n //\n // GPTQ convention: stored qzeros = actual_zero_point - 1.\n // All standard GPTQ kernels (exllama, marlin) add +1 during dequant.\n // We bake the +1 into the stored value so the kernel formula stays simple:\n // dequant = (nibble - zero) * scale\n const zerosNDiv8 = N >>> 3;\n for (let g = 0; g < groupsPerCol; g++) {\n const srcRow = g * zerosNDiv8;\n for (let col = 0; col < N; col++) {\n const zWord = qzeros[srcRow + (col >>> 3)];\n const zNibblePos = col & 7;\n const zeroVal = (zWord >>> (zNibblePos * 4)) & 0xf;\n zeros[col * groupsPerCol + g] = zeroVal + 1; // +1: GPTQ packing convention\n }\n }\n\n return { packed, scales, zeros };\n}\n","/**\n * IndexedDB-backed blob cache for model weights.\n *\n * Why IndexedDB and not the Cache API / OPFS we hand-rolled before:\n * - The Cache API ignores URL fragments in `match()`, which silently collapsed\n * our per-tensor `${url}#t:start-len` keys onto one entry → wrong bytes → the\n * `RRRR` garbage and `wt.data` crashes. IDB keys on the exact string.\n * - OPFS via Worker (`createSyncAccessHandle`) is finicky on iOS Safari (OOM /\n * transient failures) and added a lot of moving parts.\n * IndexedDB is the boring, proven large-binary store every browser supports. It\n * keys on arbitrary strings (no fragment footgun), stores ArrayBuffers directly,\n * and is durable (subject to the same per-origin quota as Cache/OPFS — install to\n * Home Screen lifts that ceiling; IDB doesn't change it, it just makes caching\n * actually CORRECT).\n *\n * Inert on Node (no indexedDB) — every export resolves to a miss/no-op so the\n * heap load path is unchanged off the browser.\n */\n\nconst DB_NAME = \"gerbil-weights-v1\";\nconst STORE = \"tensors\";\n\nfunction idbAvailable(): boolean {\n return typeof indexedDB !== \"undefined\";\n}\n\nlet _dbPromise: Promise<IDBDatabase | null> | null = null;\n\nfunction openDB(): Promise<IDBDatabase | null> {\n if (_dbPromise) return _dbPromise;\n _dbPromise = new Promise<IDBDatabase | null>((resolve) => {\n if (!idbAvailable()) {\n resolve(null);\n return;\n }\n try {\n const req = indexedDB.open(DB_NAME, 1);\n req.onupgradeneeded = () => {\n const db = req.result;\n if (!db.objectStoreNames.contains(STORE)) db.createObjectStore(STORE);\n };\n req.onsuccess = () => resolve(req.result);\n req.onerror = () => resolve(null);\n req.onblocked = () => resolve(null);\n } catch {\n resolve(null);\n }\n });\n return _dbPromise;\n}\n\n/** A single transaction operation, promisified. Resolves the fallback on any error. */\nfunction tx<T>(\n mode: IDBTransactionMode,\n run: (store: IDBObjectStore) => IDBRequest,\n fallback: T,\n): Promise<T> {\n return openDB().then(\n (db) =>\n new Promise<T>((resolve) => {\n if (!db) {\n resolve(fallback);\n return;\n }\n try {\n const t = db.transaction(STORE, mode);\n const req = run(t.objectStore(STORE));\n req.onsuccess = () => resolve((req.result as T) ?? fallback);\n req.onerror = () => resolve(fallback);\n t.onabort = () => resolve(fallback);\n } catch {\n resolve(fallback);\n }\n }),\n );\n}\n\n/** Read a key's bytes, or null on miss / no IDB / any error. */\nexport async function idbGet(key: string): Promise<ArrayBuffer | null> {\n const v = await tx<ArrayBuffer | null>(\"readonly\", (s) => s.get(key), null);\n return v instanceof ArrayBuffer ? v : null;\n}\n\n/** Cheap presence check (reads only the key via getKey — no body materialized). */\nexport async function idbHas(key: string): Promise<boolean> {\n const k = await tx<IDBValidKey | undefined>(\"readonly\", (s) => s.getKey(key), undefined);\n return k !== undefined;\n}\n\n/**\n * All cached keys, as a Set, in ONE transaction. Probing presence per-tensor\n * (hundreds of parallel getKey transactions) storms Safari's IndexedDB and hangs\n * the load; getAllKeys() does it in a single transaction so callers can check\n * membership in memory. Empty set on miss / no IDB / error.\n */\nexport async function idbAllKeys(): Promise<Set<string>> {\n const keys = await tx<IDBValidKey[]>(\"readonly\", (s) => s.getAllKeys(), []);\n return new Set((keys ?? []).map((k) => String(k)));\n}\n\n/** Write a key's bytes. Returns true on success, false on quota/error (caller\n * falls back to network — never throws). */\nexport async function idbPut(key: string, buf: ArrayBuffer): Promise<boolean> {\n // put returns the key on success; map to boolean.\n const r = await tx<IDBValidKey | null>(\"readwrite\", (s) => s.put(buf, key), null);\n return r !== null;\n}\n\n/** Delete a key (best-effort). */\nexport async function idbDelete(key: string): Promise<void> {\n await tx<unknown>(\"readwrite\", (s) => s.delete(key), null);\n}\n\n/** True when IndexedDB is usable in this environment (browser, not Node). */\nexport function idbCacheAvailable(): boolean {\n return idbAvailable();\n}\n","/**\n * MLX 4-bit → Gerbil INT4 format adapter.\n *\n * Converts MLX quantized weights (weight/scales/biases) into the flat\n * row-major packing expected by Gerbil's MatMulInt4 / EmbeddingInt4 kernels.\n *\n * MLX layout (per quantized layer):\n * weight: Uint32Array, shape [N, K/8] — 8 nibbles per u32, packed along K (last axis)\n * scales: Float32Array, shape [N, K/group_size] — per-group scale factors\n * biases: Float32Array, shape [N, K/group_size] — per-group bias values\n *\n * MLX dequant formula (affine mode):\n * w = scale * nibble + bias\n *\n * Gerbil dequant formula:\n * w = (nibble - zero) * scale\n *\n * Conversion: scale_gerbil = scale_mlx, zero_gerbil = -bias_mlx / scale_mlx\n *\n * MLX [N, K/8] is already row-major with nibbles packed along K — same layout\n * as Gerbil flat [N*K/8]. The nibble data is directly usable; only scales/zeros\n * need conversion.\n */\n\nimport type { GerbilINT4Tensors } from \"./gptq-adapter.js\";\n\nexport interface MLXTensors {\n weight: Uint32Array; // [N, K/8] packed 4-bit\n scales: Float32Array; // [N, K/group_size]\n biases: Float32Array; // [N, K/group_size]\n N: number;\n K: number;\n groupSize: number;\n}\n\n/**\n * Convert MLX 4-bit tensors into Gerbil's flat INT4 format.\n *\n * The packed nibbles are already in the correct layout — MLX [N, K/8] row-major\n * matches Gerbil flat [N*K/8]. We just convert the affine scale/bias to\n * Gerbil's (nibble - zero) * scale convention.\n */\nexport function repackMLX(mlx: MLXTensors): GerbilINT4Tensors {\n const { weight, scales: mlxScales, biases: mlxBiases, N, K, groupSize } = mlx;\n const groupsPerRow = Math.ceil(K / groupSize);\n const totalGroups = N * groupsPerRow;\n\n // Nibbles: MLX [N, K/8] is already flat [N*K/8] in row-major order\n // The packing is identical: 8 nibbles per u32, little-nibble-first\n const packed = weight;\n\n // Convert affine scale/bias to Gerbil scale/zero\n // MLX: w = scale * nibble + bias\n // Gerbil: w = (nibble - zero) * scale\n // Therefore: zero = -bias / scale, scale stays the same\n const scales = new Float32Array(totalGroups);\n const zeros = new Float32Array(totalGroups);\n\n // MLX stores [N, groups_per_row] which matches Gerbil flat [N * groups_per_row]\n // Both are row-major with groups sequential per output row — same layout.\n for (let i = 0; i < totalGroups; i++) {\n const s = mlxScales[i];\n const b = mlxBiases[i];\n scales[i] = s;\n // When scale is zero, bias should also be zero (constant group)\n zeros[i] = s !== 0 ? -b / s : 0;\n }\n\n return { packed, scales, zeros };\n}\n","/**\n * OPFS-backed blob cache for durable model-weight persistence.\n *\n * WHY THIS EXISTS\n * ---------------\n * On iOS Safari the CacheStorage API (`caches.open(...)`) used for model weights\n * is evicted under quota pressure in a plain tab — `navigator.storage.persist()`\n * is not granted outside an installed PWA — so the ~404 MB model is re-downloaded\n * on every visit. The Origin Private File System (OPFS) is the durable path, but:\n *\n * • Main-thread OPFS `createWritable` throws \"out of quota\" mid-write on iOS\n * Safari and can leave orphaned, unclearable bytes.\n * • The ONLY iOS-safe OPFS write path is `FileSystemSyncAccessHandle`\n * (`createSyncAccessHandle`), which is available ONLY inside a Worker.\n *\n * So this module runs all OPFS I/O on a dedicated Worker created from an inlined\n * blob URL (no build wiring, zero extra deps). The main thread talks to it over\n * postMessage; payload ArrayBuffers are TRANSFERRED (zero-copy) so a write does\n * not double peak memory. Writes are chunked inside the worker so the sync handle\n * never has to hold the whole tensor at once.\n *\n * KEY → FILENAME\n * --------------\n * Cache keys are URLs with `/`, `#`, `?` etc. (e.g.\n * `https://huggingface.co/...model.safetensors#t:8388608-2359296`). OPFS file\n * names cannot contain those, so each key is mapped to a stable, collision-\n * resistant filename via a 53-bit FNV-style hash plus the key length. Files live\n * in a versioned OPFS subdirectory (see OPFS_DIR_NAME).\n *\n * DESKTOP / NODE\n * --------------\n * Everything here is gated by `opfsAvailable()`, which is false in Node (no\n * `navigator.storage.getDirectory`, no `Worker`, no `Blob`). Callers must short-\n * circuit on that before touching any export here. The Node weight path never\n * reaches this module.\n */\n\n/** Versioned OPFS directory. Bump when the on-disk layout/semantics change. */\nconst OPFS_DIR_NAME = \"gerbil-models-v4\";\n\n/** Per-message write chunk inside the worker (bounded peak memory). */\nconst WRITE_CHUNK_BYTES = 8 * 1024 * 1024;\n\n/**\n * True when the durable OPFS+Worker path is usable. False in Node (no navigator/\n * Worker/Blob) and in browsers without OPFS or Workers. Cheap and synchronous so\n * the seam functions can bail before any async work.\n */\nexport function opfsAvailable(): boolean {\n return (\n typeof navigator !== \"undefined\" &&\n typeof (navigator as { storage?: { getDirectory?: unknown } }).storage?.getDirectory ===\n \"function\" &&\n typeof Worker !== \"undefined\" &&\n typeof Blob !== \"undefined\" &&\n typeof URL !== \"undefined\" &&\n typeof URL.createObjectURL === \"function\"\n );\n}\n\n/**\n * Map an arbitrary cache key to a safe OPFS filename. FNV-1a over UTF-16 code\n * units folded into a 53-bit safe integer, combined with the key length so two\n * different keys are astronomically unlikely to collide. Deterministic across\n * reloads (this is what makes a tensor written last visit re-resolve this visit).\n */\nfunction keyToFileName(key: string): string {\n // FNV-1a (32-bit) mixed twice for a wider hash; emit two hex halves + length.\n let h1 = 0x81_1c_9d_c5;\n let h2 = 0x01_00_01_93;\n for (let i = 0; i < key.length; i++) {\n const c = key.charCodeAt(i);\n h1 ^= c;\n h1 = Math.imul(h1, 0x01_00_01_93);\n h2 ^= c + i;\n h2 = Math.imul(h2, 0x85_eb_ca_6b);\n }\n const a = (h1 >>> 0).toString(16).padStart(8, \"0\");\n const b = (h2 >>> 0).toString(16).padStart(8, \"0\");\n return `${a}${b}-${key.length.toString(16)}.bin`;\n}\n\n// ── Inline worker source ─────────────────────────────────────────────────────\n//\n// Self-contained worker that owns one OPFS directory handle and performs\n// has/read/write/delete via createSyncAccessHandle. Kept as a string so the\n// existing browser tsdown entry emits it inline (no separate worker bundle).\n// The worker has no imports — everything it needs is inlined here.\nconst WORKER_SOURCE = `\nconst DIR_NAME = ${JSON.stringify(OPFS_DIR_NAME)};\nlet dirPromise = null;\n\nfunction getDir() {\n if (!dirPromise) {\n dirPromise = navigator.storage\n .getDirectory()\n .then((root) => root.getDirectoryHandle(DIR_NAME, { create: true }));\n }\n return dirPromise;\n}\n\nasync function handleHas(name) {\n const dir = await getDir();\n try {\n const fh = await dir.getFileHandle(name, { create: false });\n const access = await fh.createSyncAccessHandle();\n const size = access.getSize();\n access.close();\n // A zero-byte file is a half-written/aborted entry — treat as absent.\n return size > 0;\n } catch {\n return false;\n }\n}\n\nasync function handleRead(name) {\n const dir = await getDir();\n const fh = await dir.getFileHandle(name, { create: false });\n const access = await fh.createSyncAccessHandle();\n try {\n const size = access.getSize();\n if (size === 0) {\n access.close();\n return null;\n }\n const buf = new ArrayBuffer(size);\n access.read(new Uint8Array(buf), { at: 0 });\n access.close();\n return buf;\n } catch (e) {\n try { access.close(); } catch {}\n throw e;\n }\n}\n\nasync function handleWrite(name, buf) {\n const dir = await getDir();\n const fh = await dir.getFileHandle(name, { create: true });\n const access = await fh.createSyncAccessHandle();\n try {\n access.truncate(0);\n const total = buf.byteLength;\n const view = new Uint8Array(buf);\n const CHUNK = ${WRITE_CHUNK_BYTES};\n let off = 0;\n while (off < total) {\n const end = Math.min(off + CHUNK, total);\n // Subarray is a zero-copy view; write returns bytes written.\n access.write(view.subarray(off, end), { at: off });\n off = end;\n }\n access.flush();\n const finalSize = access.getSize();\n access.close();\n if (finalSize !== total) {\n // Write came up short (quota) — remove the partial so it never reads back\n // as a valid (wrong-size) entry.\n try { await dir.removeEntry(name); } catch {}\n throw new Error(\"OPFS short write: \" + finalSize + \"/\" + total);\n }\n } catch (e) {\n try { access.close(); } catch {}\n // Best-effort cleanup of any partial file.\n try { await dir.removeEntry(name); } catch {}\n throw e;\n }\n}\n\nasync function handleDelete(name) {\n const dir = await getDir();\n try { await dir.removeEntry(name); } catch {}\n}\n\nself.onmessage = async (ev) => {\n const { id, op, name, buf } = ev.data;\n try {\n if (op === \"has\") {\n const present = await handleHas(name);\n self.postMessage({ id, ok: true, present });\n } else if (op === \"read\") {\n const out = await handleRead(name);\n if (out) self.postMessage({ id, ok: true, buf: out }, [out]);\n else self.postMessage({ id, ok: true, buf: null });\n } else if (op === \"write\") {\n await handleWrite(name, buf);\n self.postMessage({ id, ok: true });\n } else if (op === \"delete\") {\n await handleDelete(name);\n self.postMessage({ id, ok: true });\n } else {\n self.postMessage({ id, ok: false, error: \"unknown op: \" + op });\n }\n } catch (e) {\n self.postMessage({ id, ok: false, error: String((e && e.message) || e) });\n }\n};\n`;\n\ninterface PendingResolver {\n resolve: (value: { present?: boolean; buf?: ArrayBuffer | null }) => void;\n reject: (err: Error) => void;\n}\n\n/**\n * Lazily-created singleton worker client. Created on first use; if creation or\n * any handshake fails the whole OPFS path is marked unavailable so callers fall\n * back to CacheStorage cleanly.\n */\nclass OpfsClient {\n private worker: Worker | null = null;\n private blobUrl: string | null = null;\n private pending = new Map<number, PendingResolver>();\n private nextId = 1;\n private broken = false;\n\n private ensureWorker(): Worker | null {\n if (this.broken) return null;\n if (this.worker) return this.worker;\n try {\n const blob = new Blob([WORKER_SOURCE], { type: \"application/javascript\" });\n this.blobUrl = URL.createObjectURL(blob);\n const worker = new Worker(this.blobUrl);\n worker.onmessage = (ev: MessageEvent) => {\n const { id, ok, present, buf, error } = ev.data as {\n id: number;\n ok: boolean;\n present?: boolean;\n buf?: ArrayBuffer | null;\n error?: string;\n };\n const p = this.pending.get(id);\n if (!p) return;\n this.pending.delete(id);\n if (ok) p.resolve({ present, buf });\n else p.reject(new Error(error || \"OPFS worker error\"));\n };\n worker.onerror = () => {\n // Fatal worker error — reject everything in flight and disable OPFS.\n this.broken = true;\n for (const p of this.pending.values()) {\n p.reject(new Error(\"OPFS worker crashed\"));\n }\n this.pending.clear();\n };\n this.worker = worker;\n return worker;\n } catch {\n this.broken = true;\n return null;\n }\n }\n\n private call(\n op: string,\n name: string,\n buf?: ArrayBuffer,\n ): Promise<{ present?: boolean; buf?: ArrayBuffer | null }> {\n const worker = this.ensureWorker();\n if (!worker) return Promise.reject(new Error(\"OPFS unavailable\"));\n const id = this.nextId++;\n return new Promise((resolve, reject) => {\n this.pending.set(id, { resolve, reject });\n try {\n if (buf) {\n // Transfer the buffer (zero-copy). The caller must not reuse it after.\n worker.postMessage({ id, op, name, buf }, [buf]);\n } else {\n worker.postMessage({ id, op, name });\n }\n } catch (e) {\n this.pending.delete(id);\n reject(e instanceof Error ? e : new Error(String(e)));\n }\n });\n }\n\n async has(key: string): Promise<boolean> {\n const { present } = await this.call(\"has\", keyToFileName(key));\n return Boolean(present);\n }\n\n async read(key: string): Promise<ArrayBuffer | null> {\n const { buf } = await this.call(\"read\", keyToFileName(key));\n return buf ?? null;\n }\n\n async write(key: string, buf: ArrayBuffer): Promise<void> {\n await this.call(\"write\", keyToFileName(key), buf);\n }\n\n async delete(key: string): Promise<void> {\n await this.call(\"delete\", keyToFileName(key));\n }\n}\n\nlet _client: OpfsClient | null = null;\nfunction client(): OpfsClient {\n if (!_client) _client = new OpfsClient();\n return _client;\n}\n\n/**\n * Presence check for a key in the OPFS store. Returns false (never throws) so the\n * caller can fall through to CacheStorage on any error.\n */\nexport async function opfsHas(key: string): Promise<boolean> {\n if (!opfsAvailable()) return false;\n try {\n return await client().has(key);\n } catch {\n return false;\n }\n}\n\n/**\n * Read a key's bytes from OPFS. Returns null on miss or any error (caller falls\n * back to CacheStorage / network).\n */\nexport async function opfsRead(key: string): Promise<ArrayBuffer | null> {\n if (!opfsAvailable()) return null;\n try {\n return await client().read(key);\n } catch {\n return null;\n }\n}\n\n/**\n * Write a key's bytes to OPFS durably. The buffer is TRANSFERRED to the worker\n * (do not reuse `buf` after calling — pass a copy if you still need it). Returns\n * true on success, false on any failure (quota, worker crash, …) so the caller\n * can fall back to CacheStorage without crashing the load.\n */\nexport async function opfsWrite(key: string, buf: ArrayBuffer): Promise<boolean> {\n if (!opfsAvailable()) return false;\n try {\n await client().write(key, buf);\n return true;\n } catch {\n return false;\n }\n}\n\n/** Delete a key from OPFS (best-effort, never throws). */\nexport async function opfsDelete(key: string): Promise<void> {\n if (!opfsAvailable()) return;\n try {\n await client().delete(key);\n } catch {\n /* best-effort */\n }\n}\n\n/**\n * Delete superseded OPFS model directories (old namespaces, e.g. a previous\n * `gerbil-models-v3`) to reclaim quota. On iOS the per-origin quota is ~1 GB and\n * orphaned namespaces can fill it, blocking the current model from caching at all\n * (every write then fails with quota/OOM → redownload every load). Directory\n * removal works on the MAIN thread — only createSyncAccessHandle needs a Worker —\n * so this is cheap and best-effort. Inert on Node (no navigator.storage).\n */\nexport async function opfsEvictStale(): Promise<void> {\n try {\n const root = (await (\n navigator as { storage?: { getDirectory?: () => Promise<unknown> } }\n )?.storage?.getDirectory?.()) as\n | {\n entries?: () => AsyncIterable<[string, { kind: string }]>;\n removeEntry: (n: string, o?: { recursive?: boolean }) => Promise<void>;\n }\n | undefined;\n if (!root) return;\n const stale: string[] = [];\n for await (const [name, handle] of root.entries?.() ?? []) {\n if (\n handle.kind === \"directory\" &&\n name.startsWith(\"gerbil-models-\") &&\n name !== OPFS_DIR_NAME\n ) {\n stale.push(name);\n }\n }\n for (const name of stale) {\n await root.removeEntry(name, { recursive: true }).catch(() => {\n /* best-effort */\n });\n }\n } catch {\n /* best-effort */\n }\n}\n\n/**\n * One-time health probe: actually write a few bytes to OPFS, read them back,\n * verify they round-trip, then clean up. Memoized. iOS Safari can report OPFS as\n * \"available\" (getDirectory/Worker/Blob all present) yet have the Worker's\n * createSyncAccessHandle writes silently fail — in which case we must NOT trust\n * OPFS, because the per-tensor write path transfers (detaches) the buffer and a\n * failed OPFS write would then have no CacheStorage fallback (→ nothing cached →\n * redownload every load). Callers gate OPFS use on THIS, not the cheap synchronous\n * opfsAvailable(). Resolves false if OPFS can't actually persist a round-trip.\n */\nlet _healthy: Promise<boolean> | null = null;\nexport function opfsHealthy(): Promise<boolean> {\n if (_healthy) return _healthy;\n _healthy = (async () => {\n if (!opfsAvailable()) return false;\n try {\n const key = \"__opfs_health_probe__\";\n const ok = await opfsWrite(key, new Uint8Array([7, 13, 42, 99]).buffer);\n if (!ok) return false;\n const back = await opfsRead(key);\n await opfsDelete(key);\n if (!back || back.byteLength !== 4) return false;\n const b = new Uint8Array(back);\n return b[0] === 7 && b[1] === 13 && b[2] === 42 && b[3] === 99;\n } catch {\n return false;\n }\n })();\n return _healthy;\n}\n","/**\n * Safetensors binary format parser.\n *\n * Parses the HuggingFace safetensors format into typed array views\n * over the raw buffer — zero-copy where possible.\n */\n\nexport interface SafetensorEntry {\n /** Tensor name as it appears in the file (e.g. \"model.layers.0.self_attn.q_proj.weight\"). */\n name: string;\n /** Data type string from the header. */\n dtype: SafetensorDType;\n /** Shape dimensions. */\n shape: number[];\n /** Byte offset of this tensor's data relative to the start of the data section. */\n dataOffset: number;\n /** Byte length of this tensor's data. */\n dataLength: number;\n}\n\nexport type SafetensorDType =\n | \"F16\"\n | \"F32\"\n | \"BF16\"\n | \"I32\"\n | \"I8\"\n | \"U8\"\n | \"F64\"\n | \"BOOL\"\n | \"I16\"\n | \"U16\"\n | \"U32\"\n | \"I64\"\n | \"U64\";\n\nexport interface SafetensorsFile {\n /** Header length in bytes. */\n headerLength: number;\n /** Byte offset where tensor data begins (8 + headerLength). */\n dataStart: number;\n /** All tensor entries (excludes __metadata__). */\n entries: SafetensorEntry[];\n /** Optional metadata from __metadata__ key. */\n metadata: Record<string, string> | null;\n}\n\n/**\n * Parse safetensors header from an ArrayBuffer.\n *\n * Can be called with just the header bytes (for streaming — parse header first,\n * then fetch tensor data by offset) or with the entire file.\n */\nexport function parseSafetensorsHeader(buffer: ArrayBuffer): SafetensorsFile {\n if (buffer.byteLength < 8) {\n throw new Error(\n `Buffer too small to contain safetensors header length (need 8 bytes, got ${buffer.byteLength}).`,\n );\n }\n\n const view = new DataView(buffer);\n\n // First 8 bytes: header length as little-endian uint64.\n // JS doesn't have native uint64, but headers are always < 4 GB.\n const headerLength = Number(view.getBigUint64(0, true));\n\n if (headerLength > buffer.byteLength - 8) {\n throw new Error(\n `Safetensors header length (${headerLength}) exceeds buffer size (${buffer.byteLength - 8}). ` +\n `Pass at least the first ${headerLength + 8} bytes.`,\n );\n }\n\n // Decode the JSON header\n const headerBytes = new Uint8Array(buffer, 8, headerLength);\n const headerStr = new TextDecoder().decode(headerBytes);\n const header: Record<string, unknown> = JSON.parse(headerStr);\n\n const dataStart = 8 + headerLength;\n const entries: SafetensorEntry[] = [];\n let metadata: Record<string, string> | null = null;\n\n for (const [name, info] of Object.entries(header)) {\n if (name === \"__metadata__\") {\n metadata = info as Record<string, string>;\n continue;\n }\n\n const { dtype, shape, data_offsets } = info as {\n dtype: SafetensorDType;\n shape: number[];\n data_offsets: [number, number];\n };\n\n entries.push({\n name,\n dtype,\n shape,\n dataOffset: data_offsets[0],\n dataLength: data_offsets[1] - data_offsets[0],\n });\n }\n\n // Sort by offset for sequential access patterns\n entries.sort((a, b) => a.dataOffset - b.dataOffset);\n\n return { headerLength, dataStart, entries, metadata };\n}\n\n/** Byte alignment required for each dtype. */\nfunction dtypeAlignment(dtype: SafetensorDType): number {\n switch (dtype) {\n case \"F64\":\n case \"I64\":\n case \"U64\":\n return 8;\n case \"F32\":\n case \"I32\":\n case \"U32\":\n return 4;\n case \"F16\":\n case \"BF16\":\n case \"I16\":\n case \"U16\":\n return 2;\n case \"I8\":\n case \"U8\":\n case \"BOOL\":\n return 1;\n default:\n return 1;\n }\n}\n\n/**\n * Create a typed array for the given dtype.\n *\n * If `offset` is aligned to the element size, returns a zero-copy view\n * into `buffer`. Otherwise, copies the relevant slice into a new\n * properly-aligned ArrayBuffer.\n */\nfunction makeTypedView(\n buffer: ArrayBuffer,\n offset: number,\n byteLength: number,\n dtype: SafetensorDType,\n): ArrayBufferView {\n const align = dtypeAlignment(dtype);\n const aligned = offset % align === 0;\n\n // For single-byte types, alignment is always satisfied.\n const src = aligned ? buffer : buffer.slice(offset, offset + byteLength);\n const base = aligned ? offset : 0;\n\n switch (dtype) {\n case \"F32\":\n return new Float32Array(src, base, byteLength / 4);\n case \"F16\":\n // F16 has no native TypedArray — return Uint16Array (bitwise).\n return new Uint16Array(src, base, byteLength / 2);\n case \"BF16\":\n // BF16 also has no native TypedArray — return Uint16Array (bitwise).\n return new Uint16Array(src, base, byteLength / 2);\n case \"I32\":\n return new Int32Array(src, base, byteLength / 4);\n case \"U32\":\n return new Uint32Array(src, base, byteLength / 4);\n case \"I8\":\n return new Int8Array(src, base, byteLength);\n case \"U8\":\n return new Uint8Array(src, base, byteLength);\n case \"I16\":\n return new Int16Array(src, base, byteLength / 2);\n case \"U16\":\n return new Uint16Array(src, base, byteLength / 2);\n case \"F64\":\n return new Float64Array(src, base, byteLength / 8);\n case \"I64\":\n return new BigInt64Array(src, base, byteLength / 8);\n case \"U64\":\n return new BigUint64Array(src, base, byteLength / 8);\n case \"BOOL\":\n return new Uint8Array(src, base, byteLength);\n default:\n throw new Error(`Unsupported safetensors dtype: ${dtype}`);\n }\n}\n\n/**\n * Get a typed array view for a tensor entry from a complete safetensors buffer.\n * Zero-copy when the byte offset is properly aligned; copies otherwise.\n */\nexport function getTensorData(\n buffer: ArrayBuffer,\n file: SafetensorsFile,\n entry: SafetensorEntry,\n): ArrayBufferView {\n const offset = file.dataStart + entry.dataOffset;\n return makeTypedView(buffer, offset, entry.dataLength, entry.dtype);\n}\n\n/**\n * Find a tensor entry by name (exact match).\n */\nexport function findTensor(file: SafetensorsFile, name: string): SafetensorEntry | undefined {\n return file.entries.find((e) => e.name === name);\n}\n\n/**\n * Parse just the header from a Response (streaming-friendly).\n *\n * Reads the full response into memory and parses the header.\n * Returns the parsed header and the full buffer for tensor access.\n */\nexport async function parseSafetensorsFromResponse(\n response: Response,\n): Promise<{ file: SafetensorsFile; fullBuffer: ArrayBuffer }> {\n const fullBuffer = await response.arrayBuffer();\n const file = parseSafetensorsHeader(fullBuffer);\n return { file, fullBuffer };\n}\n\n/**\n * Compute the total byte size of all tensor data in a safetensors file.\n */\nexport function totalTensorBytes(file: SafetensorsFile): number {\n let total = 0;\n for (const entry of file.entries) {\n total += entry.dataLength;\n }\n return total;\n}\n\n/**\n * Convert BF16 (bfloat16) data to F32.\n * BF16 is the upper 16 bits of an IEEE 754 float32, so conversion is a simple left-shift.\n */\nexport function bf16ToF32(bf16Bytes: Uint8Array): Float32Array {\n const bf16 = new Uint16Array(bf16Bytes.buffer, bf16Bytes.byteOffset, bf16Bytes.byteLength / 2);\n const f32 = new Float32Array(bf16.length);\n const u32 = new Uint32Array(f32.buffer);\n for (let i = 0; i < bf16.length; i++) {\n u32[i] = bf16[i] << 16;\n }\n return f32;\n}\n\n/**\n * Convert F16 (IEEE 754 half-precision) data to F32.\n */\nexport function f16ToF32(f16View: ArrayBufferView): Float32Array {\n const u16 =\n f16View instanceof Uint16Array\n ? f16View\n : new Uint16Array(f16View.buffer, f16View.byteOffset, f16View.byteLength / 2);\n const f32 = new Float32Array(u16.length);\n for (let i = 0; i < u16.length; i++) {\n const h = u16[i];\n const sign = (h >> 15) & 1;\n const exp = (h >> 10) & 0x1f;\n const frac = h & 0x3ff;\n if (exp === 0) {\n // Subnormal or zero\n f32[i] = (sign ? -1 : 1) * 2 ** -14 * (frac / 1024);\n } else if (exp === 31) {\n // Inf or NaN\n f32[i] = frac === 0 ? (sign ? -Infinity : Infinity) : NaN;\n } else {\n f32[i] = (sign ? -1 : 1) * 2 ** (exp - 15) * (1 + frac / 1024);\n }\n }\n return f32;\n}\n","/**\n * Pure JavaScript BPE tokenizer.\n *\n * Reads HuggingFace tokenizer.json — no WASM, no external dependencies.\n * Supports encoding, decoding, and chat template application.\n */\n\n// Byte-level BPE unicode mapping (same as HuggingFace's bytes_to_unicode).\n// Maps each byte (0-255) to a unique unicode codepoint such that printable\n// ASCII characters map to themselves, and non-printable bytes are shifted\n// into the U+0100+ range.\nconst BYTE_TO_UNICODE = new Map<number, number>();\nconst UNICODE_TO_BYTE = new Map<number, number>();\n\n{\n // Printable bytes that map to themselves: 33-126 (! through ~), 161-172, 174-255\n const bs: number[] = [];\n for (let i = 33; i <= 126; i++) bs.push(i); // ! through ~\n for (let i = 161; i <= 172; i++) bs.push(i); // ¡ through ¬\n for (let i = 174; i <= 255; i++) bs.push(i); // ® through ÿ\n\n const cs = [...bs]; // unicode codepoints (same for these ranges)\n\n // Non-printable bytes get shifted to U+0100+\n let n = 0;\n for (let b = 0; b < 256; b++) {\n if (!bs.includes(b)) {\n bs.push(b);\n cs.push(256 + n);\n n++;\n }\n }\n\n for (let i = 0; i < bs.length; i++) {\n BYTE_TO_UNICODE.set(bs[i], cs[i]);\n UNICODE_TO_BYTE.set(cs[i], bs[i]);\n }\n}\n\nexport interface TokenizerConfig {\n bosToken: string | null;\n eosToken: string | null;\n bosTokenId: number | null;\n eosTokenId: number | null;\n chatTemplate: string | null;\n addBosToken: boolean;\n addEosToken: boolean;\n}\n\nexport interface ChatMessage {\n role: \"system\" | \"user\" | \"assistant\";\n content: string;\n}\n\nexport class Tokenizer {\n private vocab: Map<string, number>; // token string → id\n private vocabReverse: Map<number, string>; // id → token string\n private merges: Map<string, number>; // \"tokenA tokenB\" → priority (lower = merge first)\n private specialTokens: Map<string, number>; // special token string → id\n private addedTokens: Map<string, number>; // ALL added tokens (special + non-special like <think>)\n private byteFallback: Map<number, string>; // byte value → byte-level token\n /**\n * SentencePiece mode (Gemma/Llama-style). When true, vocab uses U+2581 (▁) for\n * spaces and raw UTF-8 tokens (NOT the GPT-2 byte-to-unicode \"Ġ\" mapping), and\n * raw bytes fall back to <0xHH> tokens. When false, GPT-2 byte-level BPE.\n */\n private spmMode: boolean;\n readonly config: TokenizerConfig;\n readonly vocabSize: number;\n\n private constructor(\n vocab: Map<string, number>,\n vocabReverse: Map<number, string>,\n merges: Map<string, number>,\n specialTokens: Map<string, number>,\n addedTokens: Map<string, number>,\n config: TokenizerConfig,\n vocabSize: number,\n spmMode: boolean,\n ) {\n this.vocab = vocab;\n this.vocabReverse = vocabReverse;\n this.merges = merges;\n this.specialTokens = specialTokens;\n this.addedTokens = addedTokens;\n this.byteFallback = new Map();\n this.spmMode = spmMode;\n this.config = config;\n this.vocabSize = vocabSize;\n\n // Build byte fallback map (for handling unknown characters)\n // HF tokenizers use <0xHH> tokens for raw bytes\n for (let b = 0; b < 256; b++) {\n const hex = b.toString(16).toUpperCase().padStart(2, \"0\");\n const key = `<0x${hex}>`;\n if (vocab.has(key)) {\n this.byteFallback.set(b, key);\n }\n }\n }\n\n /**\n * Create a tokenizer from HuggingFace JSON files.\n */\n static fromJSON(tokenizerJSON: any, tokenizerConfigJSON?: any): Tokenizer {\n const model = tokenizerJSON.model;\n if (!model || model.type !== \"BPE\") {\n throw new Error(\n `Unsupported tokenizer type: ${model?.type ?? \"unknown\"}. Only BPE is supported.`,\n );\n }\n\n // Build vocab\n const vocab = new Map<string, number>();\n const vocabReverse = new Map<number, string>();\n for (const [token, id] of Object.entries(model.vocab as Record<string, number>)) {\n vocab.set(token, id);\n vocabReverse.set(id, token);\n }\n\n // Detect SentencePiece mode (Gemma/Llama): the normalizer replaces spaces\n // with U+2581 (▁) and/or the vocab is dominated by ▁-prefixed tokens. In SPM\n // mode tokens are raw UTF-8 with ▁ for spaces and bytes fall back to <0xHH>,\n // rather than the GPT-2 byte-to-unicode (Ġ) mapping.\n const normalizerReplacesSpace = (() => {\n const norm = tokenizerJSON.normalizer;\n const checkOne = (n: any) =>\n n?.type === \"Replace\" && n?.pattern?.String === \" \" && n?.content === \"▁\";\n if (!norm) return false;\n if (checkOne(norm)) return true;\n if (Array.isArray(norm.normalizers)) return norm.normalizers.some(checkOne);\n return false;\n })();\n const byteFallbackFlag = model.byte_fallback === true;\n const spmMode = normalizerReplacesSpace || (byteFallbackFlag && \"▁the\" in model.vocab);\n\n // Build merge priority map. Merges are either \"a b\" strings (GPT-2 style) or\n // [\"a\", \"b\"] arrays (newer/Gemma tokenizer.json). Normalize both to \"a b\".\n const merges = new Map<string, number>();\n const mergeList = (model.merges ?? []) as Array<string | [string, string]>;\n for (let i = 0; i < mergeList.length; i++) {\n const m = mergeList[i];\n const key = Array.isArray(m) ? `${m[0]} ${m[1]}` : m;\n merges.set(key, i);\n }\n\n // Register added tokens — ALL of them go into addedTokensMap for splitting,\n // only special:true ones go into specialTokens for skip-during-decode\n const specialTokens = new Map<string, number>();\n const addedTokensMap = new Map<string, number>();\n const addedTokensList = tokenizerJSON.added_tokens as\n | Array<{ id: number; content: string; special?: boolean }>\n | undefined;\n if (addedTokensList) {\n for (const t of addedTokensList) {\n vocab.set(t.content, t.id);\n vocabReverse.set(t.id, t.content);\n addedTokensMap.set(t.content, t.id);\n if (t.special) {\n specialTokens.set(t.content, t.id);\n }\n }\n }\n\n // Parse config\n const bosToken = tokenizerConfigJSON?.bos_token ?? null;\n const eosToken = tokenizerConfigJSON?.eos_token ?? null;\n const chatTemplate = tokenizerConfigJSON?.chat_template ?? null;\n const addBosToken = tokenizerConfigJSON?.add_bos_token ?? false;\n const addEosToken = tokenizerConfigJSON?.add_eos_token ?? false;\n\n const config: TokenizerConfig = {\n bosToken,\n eosToken,\n bosTokenId: bosToken ? (vocab.get(bosToken) ?? null) : null,\n eosTokenId: eosToken ? (vocab.get(eosToken) ?? null) : null,\n chatTemplate,\n addBosToken,\n addEosToken,\n };\n\n return new Tokenizer(\n vocab,\n vocabReverse,\n merges,\n specialTokens,\n addedTokensMap,\n config,\n vocab.size,\n spmMode,\n );\n }\n\n /**\n * Resolve a literal token string (e.g. \"<|endoftext|>\") to its vocab id,\n * or null if it isn't in the vocabulary.\n */\n tokenToId(token: string): number | null {\n return this.vocab.get(token) ?? null;\n }\n\n /**\n * Encode text into token IDs.\n */\n encode(text: string): number[] {\n if (!text) return [];\n\n const ids: number[] = [];\n\n // Add BOS token if configured\n if (this.config.addBosToken && this.config.bosTokenId !== null) {\n ids.push(this.config.bosTokenId);\n }\n\n // Check for special tokens first — split text around them\n const parts = this.splitOnSpecialTokens(text);\n\n for (const part of parts) {\n if (part.special) {\n const id = this.addedTokens.get(part.text);\n if (id !== undefined) {\n ids.push(id);\n }\n } else {\n // Pre-tokenize: split into words/chunks\n // Use a simple regex-based pre-tokenizer similar to GPT-style\n const chunks = this.preTokenize(part.text);\n for (const chunk of chunks) {\n const chunkIds = this.bpeEncode(chunk);\n ids.push(...chunkIds);\n }\n }\n }\n\n // Add EOS token if configured\n if (this.config.addEosToken && this.config.eosTokenId !== null) {\n ids.push(this.config.eosTokenId);\n }\n\n return ids;\n }\n\n /**\n * Decode token IDs back to text.\n */\n decode(ids: number[], skipSpecialTokens: boolean = true): string {\n const pieces: string[] = [];\n\n for (const id of ids) {\n const token = this.vocabReverse.get(id);\n if (!token) continue;\n\n // Skip special tokens if requested\n if (skipSpecialTokens && this.specialTokens.has(token)) {\n continue;\n }\n\n pieces.push(token);\n }\n\n if (this.spmMode) {\n // SentencePiece (Gemma/Llama): tokens are raw UTF-8 with ▁ for spaces and\n // <0xHH> byte-fallback pieces. Concatenate the <0xHH> bytes into proper\n // UTF-8 runs (Fuse), decode, and turn ▁ back into spaces.\n let out = \"\";\n let byteRun: number[] = [];\n const flushBytes = () => {\n if (byteRun.length === 0) return;\n out += new TextDecoder().decode(new Uint8Array(byteRun));\n byteRun = [];\n };\n for (const piece of pieces) {\n const m = /^<0x([0-9A-Fa-f]{2})>$/.exec(piece);\n if (m) {\n byteRun.push(Number.parseInt(m[1], 16));\n } else {\n flushBytes();\n out += piece;\n }\n }\n flushBytes();\n return out.replace(/▁/g, \" \");\n }\n\n // Join and decode byte-level BPE tokens.\n // GPT/Qwen tokenizers use a byte-to-unicode mapping where non-printable\n // bytes are shifted into the U+0100+ range:\n // byte 0x00-0x20 → U+0100-0x0120 (e.g. 0x0A=\\n → U+010A=Ċ, 0x20=space → U+0120=Ġ)\n // byte 0x7F-0xA0 → U+0121-0x0142\n // We decode by reversing this mapping back to raw bytes.\n let text = pieces.join(\"\");\n\n // Build the reverse byte-level mapping\n const chars: number[] = [];\n for (let i = 0; i < text.length; i++) {\n const cp = text.codePointAt(i)!;\n const byte = UNICODE_TO_BYTE.get(cp);\n if (byte !== undefined) {\n chars.push(byte);\n } else {\n // Multi-byte UTF-8: encode the codepoint as UTF-8 bytes\n const encoded = new TextEncoder().encode(String.fromCodePoint(cp));\n for (const b of encoded) chars.push(b);\n if (cp > 0xffff) i++; // skip surrogate pair\n }\n }\n text = new TextDecoder().decode(new Uint8Array(chars));\n\n // Handle byte-level fallback tokens like <0x0A> → \\n\n text = text.replace(/<0x([0-9A-Fa-f]{2})>/g, (_, hex) => {\n return String.fromCharCode(parseInt(hex, 16));\n });\n\n return text;\n }\n\n /**\n * Apply chat template to messages.\n *\n * For now, implements the common ChatML format used by Qwen models:\n * <|im_start|>system\\n{content}<|im_end|>\\n\n * <|im_start|>user\\n{content}<|im_end|>\\n\n * <|im_start|>assistant\\n\n *\n * TODO: Parse Jinja2 templates from tokenizer_config.json for full generality.\n */\n /**\n * Gemma 4 turn format: `<bos><|turn>user\\n{content}<turn|>\\n<|turn>model\\n`.\n * Gemma has no \"system\" role, so a system message is folded into the next user\n * turn (matching the reference chat template).\n */\n private applyGemmaTurnTemplate(messages: ChatMessage[], addGenPrompt: boolean): string {\n const parts: string[] = [];\n if (this.config.bosToken) parts.push(this.config.bosToken);\n let systemText = \"\";\n for (const msg of messages) {\n if (msg.role === \"system\") {\n systemText += `${msg.content}\\n\\n`;\n continue;\n }\n const role = msg.role === \"assistant\" ? \"model\" : msg.role;\n const content = role === \"user\" && systemText ? systemText + msg.content : msg.content;\n if (role === \"user\") systemText = \"\";\n parts.push(`<|turn>${role}\\n${content}<turn|>\\n`);\n }\n if (addGenPrompt) parts.push(\"<|turn>model\\n\");\n return parts.join(\"\");\n }\n\n applyChatTemplate(messages: ChatMessage[], options?: { addGenerationPrompt?: boolean }): string {\n const addGenPrompt = options?.addGenerationPrompt ?? true;\n const parts: string[] = [];\n\n // ── Gemma 4 turn format ──\n // Detected by the turn-delimiter tokens `<|turn>`/`<turn|>` (unique to the\n // Gemma 4 vocab, absent from the ChatML/Qwen vocabs handled below).\n if (this.addedTokens.has(\"<|turn>\") && this.addedTokens.has(\"<turn|>\")) {\n return this.applyGemmaTurnTemplate(messages, addGenPrompt);\n }\n\n // Models whose chat template prepends bos at the start (e.g. LFM2's\n // `{{- bos_token -}}` ...). The hardcoded ChatML body is otherwise shared.\n const template = this.config.chatTemplate;\n if (template?.includes(\"bos_token\") && this.config.bosToken) {\n parts.push(this.config.bosToken);\n }\n\n for (const msg of messages) {\n parts.push(`<|im_start|>${msg.role}\\n${msg.content}<|im_end|>\\n`);\n }\n\n if (addGenPrompt) {\n parts.push(`<|im_start|>assistant\\n`);\n // Some reasoning models (e.g. Qwen3/3.5) pre-fill an (empty) think block\n // after the generation prompt for non-thinking mode. Only inject it when\n // the model's own chat template actually emits `<think>` at generation\n // time — otherwise models that merely *have* a <think> token but don't\n // pre-fill it (e.g. LFM2.5) would be corrupted into degenerate output.\n const templateWantsThink = template\n ? template.includes(\"<think>\")\n : this.addedTokens.has(\"<think>\");\n if (templateWantsThink) {\n parts.push(`<think>\\n\\n</think>\\n\\n`);\n }\n }\n\n return parts.join(\"\");\n }\n\n /**\n * Encode a chat conversation into token IDs.\n */\n encodeChat(messages: ChatMessage[], options?: { addGenerationPrompt?: boolean }): number[] {\n const text = this.applyChatTemplate(messages, options);\n return this.encode(text);\n }\n\n // ── Internal BPE implementation ──────────────────────────────────\n\n private splitOnSpecialTokens(text: string): Array<{ text: string; special: boolean }> {\n if (this.addedTokens.size === 0) return [{ text, special: false }];\n\n const parts: Array<{ text: string; special: boolean }> = [];\n // Sort added tokens by length (longest first) for greedy matching\n // Uses ALL added tokens (not just special:true) so <think>/<|tool_call|> etc. are split\n const sortedSpecials = [...this.addedTokens.keys()].sort((a, b) => b.length - a.length);\n\n // Build regex that matches any special token\n const escaped = sortedSpecials.map((s) => s.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\"));\n const regex = new RegExp(`(${escaped.join(\"|\")})`, \"g\");\n\n let lastIndex = 0;\n let match: RegExpExecArray | null;\n while ((match = regex.exec(text)) !== null) {\n if (match.index > lastIndex) {\n parts.push({\n text: text.slice(lastIndex, match.index),\n special: false,\n });\n }\n parts.push({ text: match[0], special: true });\n lastIndex = regex.lastIndex;\n }\n if (lastIndex < text.length) {\n parts.push({ text: text.slice(lastIndex), special: false });\n }\n\n return parts;\n }\n\n private preTokenize(text: string): string[] {\n if (!text) return [];\n\n if (this.spmMode) {\n // SentencePiece (Gemma/Llama): normalize spaces → ▁ (U+2581), then split on\n // the ▁ boundary keeping it with the FOLLOWING piece (Split \" \"\n // MergedWithPrevious on the original, equivalent to ▁-prefixed words).\n // BPE then runs on the raw ▁-encoded text — no byte-to-unicode mapping.\n const encoded = text.replace(/ /g, \"▁\");\n const chunks: string[] = [];\n let i = 0;\n while (i < encoded.length) {\n let j = i + 1;\n while (j < encoded.length && encoded[j] !== \"▁\") j++;\n chunks.push(encoded.slice(i, j));\n i = j;\n }\n return chunks;\n }\n\n // GPT-style pre-tokenization regex\n // Splits on whitespace boundaries, keeping the space as a prefix (Ġ convention)\n // Also splits numbers and punctuation\n const pattern = /'s|'t|'re|'ve|'m|'ll|'d| ?\\p{L}+| ?\\p{N}+| ?[^\\s\\p{L}\\p{N}]+|\\s+/gu;\n const matches = text.match(pattern);\n if (!matches) return [];\n\n // Convert to the byte-level representation HF uses:\n // space → Ġ, etc. (the GPT2 byte-to-unicode mapping)\n return matches.map((chunk) => this.textToTokenRepr(chunk));\n }\n\n private textToTokenRepr(text: string): string {\n // Convert text to the representation used in the vocab\n // In HF BPE tokenizers, spaces are typically represented as Ġ (U+0120)\n // and other bytes have specific unicode mappings\n let result = \"\";\n for (let i = 0; i < text.length; i++) {\n const c = text.charCodeAt(i);\n if (c === 32) {\n result += \"\\u0120\"; // space → Ġ\n } else if (c < 33 || (c > 126 && c < 161)) {\n // Control chars and non-printable → byte-level tokens\n // Use the GPT2 byte encoder mapping\n result += String.fromCharCode(c + 256);\n } else {\n result += text[i];\n }\n }\n return result;\n }\n\n private bpeEncode(word: string): number[] {\n if (word.length === 0) return [];\n\n // Check if the whole word is in vocab\n if (this.vocab.has(word)) {\n return [this.vocab.get(word)!];\n }\n\n // Split into individual characters\n let symbols = [...word];\n\n // If any symbol isn't in vocab, try byte fallback\n // Convert to individual characters and check\n if (symbols.length === 1) {\n const id = this.vocab.get(symbols[0]);\n if (id !== undefined) return [id];\n // Byte fallback\n return this.encodeByteFallback(word);\n }\n\n // Iteratively merge the most frequent pair\n while (symbols.length > 1) {\n // Find the pair with the lowest merge rank (highest priority)\n let _bestPair = \"\";\n let bestRank = Infinity;\n let bestIdx = -1;\n\n for (let i = 0; i < symbols.length - 1; i++) {\n const pair = `${symbols[i]} ${symbols[i + 1]}`;\n const rank = this.merges.get(pair);\n if (rank !== undefined && rank < bestRank) {\n bestRank = rank;\n _bestPair = pair;\n bestIdx = i;\n }\n }\n\n // No more merges possible\n if (bestIdx === -1) break;\n\n // Apply the merge\n const merged = symbols[bestIdx] + symbols[bestIdx + 1];\n symbols = [...symbols.slice(0, bestIdx), merged, ...symbols.slice(bestIdx + 2)];\n }\n\n // Convert symbols to IDs\n const ids: number[] = [];\n for (const sym of symbols) {\n const id = this.vocab.get(sym);\n if (id !== undefined) {\n ids.push(id);\n } else {\n // Byte fallback for unknown symbols\n ids.push(...this.encodeByteFallback(sym));\n }\n }\n return ids;\n }\n\n private encodeByteFallback(text: string): number[] {\n const encoder = new TextEncoder();\n const bytes = encoder.encode(text);\n const ids: number[] = [];\n for (const b of bytes) {\n const tok = this.byteFallback.get(b);\n if (tok) {\n const id = this.vocab.get(tok);\n if (id !== undefined) ids.push(id);\n }\n }\n return ids;\n }\n}\n","/**\n * WeightSource — a tensor store the loader/executor can pull from one tensor at a\n * time, so the *whole* model never has to sit in the JS heap simultaneously.\n *\n * Two backends share one async-shaped interface:\n *\n * • HeapWeightStore (Node / desktop) — wraps a plain `Map`. Identical behavior\n * to the original code path: tensors are held in heap, gets/sets are O(1) heap\n * ops (the async methods resolve synchronously). Zero behavior change.\n *\n * • CacheWeightStore (browser / mobile) — keeps every tensor's BYTES out of the\n * heap. Downloaded tensors live in the per-tensor download cache (IndexedDB,\n * populated by model-loader's browserCacheWrite); transform OUTPUTS written by\n * `set()` stage into a CacheStorage bucket. The heap holds only lightweight\n * descriptors ({ shape, dtype, cacheName, cacheKey }) plus a small set of tiny\n * \"override\" tensors. `get()` reads one tensor's bytes back on demand —\n * IndexedDB for downloads, CacheStorage for staged outputs — applying any\n * BF16/F16→F32 conversion. This bounds peak heap to roughly ONE tensor at a\n * time instead of the entire weight set, which is what lets a 2.5 GB model\n * load on iOS Safari without the tab being jetsam-killed.\n *\n * The browser path is selected automatically when CacheStorage is available.\n */\n\nimport { idbCacheAvailable, idbGet } from \"./idb-cache.js\";\nimport { opfsAvailable, opfsRead } from \"./opfs-cache.js\";\nimport { bf16ToF32, f16ToF32, type SafetensorDType } from \"./safetensors.js\";\n\n/** A single tensor's data + shape (the unit the executor uploads to a GPU buffer). */\nexport interface WeightEntry {\n data: ArrayBufferView;\n shape: number[];\n}\n\n/**\n * Read-side view consumed by the executor's streaming `uploadWeights`. Async by\n * design so a cache-backed store can fetch one tensor's bytes at a time.\n */\nexport interface WeightSource {\n has(name: string): boolean;\n keys(): string[];\n readonly size: number;\n /** Pull a single tensor (bytes materialized + dtype-converted on demand). */\n get(name: string): Promise<WeightEntry | undefined>;\n /**\n * Release any transient backing storage (e.g. the browser transform-staging\n * cache) once the consumer has finished uploading. No-op for the heap backend.\n */\n dispose?(): Promise<void>;\n}\n\n/**\n * Mutable store used while loading. The loader's post-download transforms\n * (RMSNorm +1 bake, fused-Q split, GPTQ/MLX repack, INT4 quantize, PLE/vision\n * fixups) read tensors, produce new ones, and drop sources through this\n * interface. All ops are async so the cache backend can stream bytes; the heap\n * backend resolves them synchronously.\n */\nexport interface MutableWeightStore extends WeightSource {\n set(name: string, entry: WeightEntry): Promise<void>;\n delete(name: string): Promise<void>;\n}\n\n// ── Heap backend (Node / desktop) — unchanged behavior ──────────────────────\n\nexport class HeapWeightStore implements MutableWeightStore {\n private map: Map<string, WeightEntry>;\n\n constructor(map?: Map<string, WeightEntry>) {\n this.map = map ?? new Map();\n }\n\n has(name: string): boolean {\n return this.map.has(name);\n }\n\n keys(): string[] {\n return [...this.map.keys()];\n }\n\n get size(): number {\n return this.map.size;\n }\n\n get(name: string): Promise<WeightEntry | undefined> {\n return Promise.resolve(this.map.get(name));\n }\n\n set(name: string, entry: WeightEntry): Promise<void> {\n this.map.set(name, entry);\n return Promise.resolve();\n }\n\n delete(name: string): Promise<void> {\n this.map.delete(name);\n return Promise.resolve();\n }\n\n /** Escape hatch for the few Node paths that still want the raw Map. */\n asMap(): Map<string, WeightEntry> {\n return this.map;\n }\n}\n\n// ── Cache backend (browser / mobile) — bounded peak heap ─────────────────────\n\n/**\n * A descriptor of a tensor whose bytes live in a durable cache rather than the\n * heap: IndexedDB for downloaded tensors, CacheStorage for `set()` staging\n * outputs. `dtype` is the *source* safetensors dtype so `get()` can reproduce the\n * same BF16/F16→F32 conversion the heap path applies inline during download.\n */\ninterface CacheDescriptor {\n shape: number[];\n /** Source safetensors dtype, or \"RAW\" for already-converted/derived bytes. */\n dtype: SafetensorDType | \"RAW\";\n /** Logical bucket tag: download cache (IndexedDB by cacheKey) vs staging cache (CacheStorage). */\n cacheName: string;\n cacheKey: string;\n /** TypedArray ctor tag for RAW (transform-produced) bytes. */\n view?: \"f32\" | \"u32\" | \"i32\" | \"u16\" | \"u8\";\n}\n\n/**\n * Build a typed-array view over a freshly-read (offset-0) ArrayBuffer. A cache\n * read always returns a fresh, offset-0 buffer, so it is aligned for every dtype\n * — no copy needed (unlike the merged-range download path which can be unaligned).\n */\nfunction viewForSourceDtype(buf: ArrayBuffer, dtype: SafetensorDType): ArrayBufferView {\n switch (dtype) {\n case \"F32\":\n return new Float32Array(buf, 0, buf.byteLength / 4);\n case \"I32\":\n return new Int32Array(buf, 0, buf.byteLength / 4);\n case \"U32\":\n return new Uint32Array(buf, 0, buf.byteLength / 4);\n case \"F16\":\n case \"BF16\":\n return new Uint16Array(buf, 0, buf.byteLength / 2);\n default:\n return new Uint8Array(buf, 0, buf.byteLength);\n }\n}\n\nfunction viewForRaw(buf: ArrayBuffer, tag: CacheDescriptor[\"view\"]): ArrayBufferView {\n switch (tag) {\n case \"u32\":\n return new Uint32Array(buf, 0, buf.byteLength / 4);\n case \"i32\":\n return new Int32Array(buf, 0, buf.byteLength / 4);\n case \"u16\":\n return new Uint16Array(buf, 0, buf.byteLength / 2);\n case \"u8\":\n return new Uint8Array(buf, 0, buf.byteLength);\n default:\n return new Float32Array(buf, 0, buf.byteLength / 4);\n }\n}\n\nfunction rawTag(data: ArrayBufferView): CacheDescriptor[\"view\"] {\n if (data instanceof Float32Array) return \"f32\";\n if (data instanceof Uint32Array) return \"u32\";\n if (data instanceof Int32Array) return \"i32\";\n if (data instanceof Uint16Array) return \"u16\";\n return \"u8\";\n}\n\nconst CACHE_BACKED_NAME = \"gerbil-weights-staging-v2\";\n\nexport class CacheWeightStore implements MutableWeightStore {\n private descriptors = new Map<string, CacheDescriptor>();\n /**\n * Small heap-resident tensors that are NOT worth a cache round-trip: synthetic\n * fill constants, sub-MB norm/scalar tensors, and anything a caller explicitly\n * pins (e.g. the vision pos-embed table that index.ts snapshots before upload).\n * Keeping these in heap is fine — they are individually tiny.\n */\n private overrides = new Map<string, WeightEntry>();\n private cacheName: string;\n\n /** Tensors at or below this size stay in heap rather than round-tripping cache. */\n private static readonly HEAP_THRESHOLD = 512 * 1024; // 512 KB\n\n constructor(cacheName: string = CACHE_BACKED_NAME) {\n this.cacheName = cacheName;\n }\n\n private static stagingKey(name: string): string {\n return `gerbil-staging:${name}`;\n }\n\n /**\n * Register a tensor whose bytes already live in CacheStorage under\n * `downloadCacheName`/`cacheKey` (the per-tensor download cache). No bytes are\n * retained in heap. The descriptor records which cache bucket to read from so\n * `get()` opens the right one (download cache vs this store's staging cache).\n */\n registerCached(\n name: string,\n shape: number[],\n dtype: SafetensorDType,\n cacheKey: string,\n downloadCacheName: string,\n ): void {\n this.overrides.delete(name);\n this.descriptors.set(name, { shape, dtype, cacheName: downloadCacheName, cacheKey });\n }\n\n has(name: string): boolean {\n return this.overrides.has(name) || this.descriptors.has(name);\n }\n\n keys(): string[] {\n const set = new Set<string>(this.descriptors.keys());\n for (const k of this.overrides.keys()) set.add(k);\n return [...set];\n }\n\n get size(): number {\n return this.keys().length;\n }\n\n async get(name: string): Promise<WeightEntry | undefined> {\n const pinned = this.overrides.get(name);\n if (pinned) return pinned;\n const desc = this.descriptors.get(name);\n if (!desc) return undefined;\n\n // RAW entries are transform outputs written by set() straight into this\n // store's CacheStorage staging bucket — read them back from there.\n if (desc.dtype === \"RAW\") {\n const cache = await caches.open(desc.cacheName);\n const resp = await cache.match(new Request(desc.cacheKey));\n if (!resp) return undefined;\n const rawBuf = await resp.arrayBuffer();\n return { data: viewForRaw(rawBuf, desc.view), shape: desc.shape };\n }\n\n // Download tensors (registerCached) live in the per-tensor download cache,\n // which is now IndexedDB — the SAME backend model-loader's browserCacheWrite\n // writes to. Read from IDB first; fall back to OPFS / CacheStorage so any\n // tensor cached by an older build still resolves. (Reading only OPFS +\n // CacheStorage — the pre-IDB backends — is what made every streamed get()\n // miss and crash model-loader's `(await store.get(...))!.data`.)\n let buf: ArrayBuffer | null = null;\n if (idbCacheAvailable()) {\n buf = await idbGet(desc.cacheKey);\n }\n if (!buf && opfsAvailable()) {\n buf = await opfsRead(desc.cacheKey);\n }\n if (!buf) {\n const cache = await caches.open(desc.cacheName);\n const resp = await cache.match(new Request(desc.cacheKey));\n if (!resp) return undefined;\n buf = await resp.arrayBuffer();\n }\n let data = viewForSourceDtype(buf, desc.dtype);\n // Mirror the heap path's inline conversion (model-loader.ts ~860-933).\n if (desc.dtype === \"BF16\") {\n data = bf16ToF32(new Uint8Array(data.buffer, data.byteOffset, data.byteLength));\n } else if (desc.dtype === \"F16\") {\n data = f16ToF32(data);\n }\n return { data, shape: desc.shape };\n }\n\n async set(name: string, entry: WeightEntry): Promise<void> {\n this.descriptors.delete(name);\n // Tiny tensors stay in heap; large ones write through to cache so their bytes\n // do not accumulate in the heap as transforms run layer-by-layer.\n if (entry.data.byteLength <= CacheWeightStore.HEAP_THRESHOLD) {\n this.overrides.set(name, entry);\n return;\n }\n this.overrides.delete(name);\n const cacheKey = CacheWeightStore.stagingKey(name);\n const bytes = entry.data.buffer.slice(\n entry.data.byteOffset,\n entry.data.byteOffset + entry.data.byteLength,\n ) as ArrayBuffer;\n const cache = await caches.open(this.cacheName);\n await cache.put(new Request(cacheKey), new Response(bytes));\n this.descriptors.set(name, {\n shape: entry.shape,\n dtype: \"RAW\",\n cacheName: this.cacheName,\n cacheKey,\n view: rawTag(entry.data),\n });\n }\n\n async delete(name: string): Promise<void> {\n this.overrides.delete(name);\n const desc = this.descriptors.get(name);\n this.descriptors.delete(name);\n // Only evict staging entries we created; never evict the durable per-tensor\n // download cache (a future load reuses those by stable byte-range key).\n if (desc && desc.dtype === \"RAW\" && desc.cacheName === this.cacheName) {\n try {\n const cache = await caches.open(this.cacheName);\n await cache.delete(new Request(desc.cacheKey));\n } catch {\n /* best-effort */\n }\n }\n }\n\n /** Drop all transform-staging cache entries (call after upload completes). */\n async dispose(): Promise<void> {\n try {\n await caches.delete(this.cacheName);\n } catch {\n /* best-effort */\n }\n }\n}\n\n/** True when the browser CacheStorage API is available (i.e. not Node). */\nexport function cacheStorageAvailable(): boolean {\n return typeof caches !== \"undefined\";\n}\n","/**\n * Model loader -- fetches model files from HuggingFace Hub.\n *\n * Handles:\n * - Fetching config.json, tokenizer.json, tokenizer_config.json\n * - Downloading safetensors weight files (with progress callbacks)\n * - HF key -> canonical key mapping\n * - Uploading weight data to GPU buffers via the Executor\n */\n\nimport { type GraphDType, generateGraph, type KVDType } from \"./architectures/index.js\";\nimport { foldNanoCodecWeightNorm, nanoCodecWeightMap } from \"./architectures/kani_tts.js\";\nimport { repackGPTQ } from \"./gptq-adapter.js\";\nimport { idbAllKeys, idbCacheAvailable, idbGet, idbHas, idbPut } from \"./idb-cache.js\";\nimport type { ModelGraph } from \"./ir.js\";\nimport { CANONICAL_KEYS, createDefaultHFKeyMapper, type HFKeyMapper } from \"./ir.js\";\nimport { repackMLX } from \"./mlx-adapter.js\";\nimport { opfsEvictStale } from \"./opfs-cache.js\";\nimport { DEFAULT_GROUP_SIZE, quantizeInt4 } from \"./quantize.js\";\nimport {\n bf16ToF32,\n f16ToF32,\n getTensorData,\n parseSafetensorsHeader,\n type SafetensorEntry,\n type SafetensorsFile,\n} from \"./safetensors.js\";\nimport { Tokenizer } from \"./tokenizer.js\";\nimport {\n CacheWeightStore,\n cacheStorageAvailable,\n HeapWeightStore,\n type MutableWeightStore,\n type WeightSource,\n} from \"./weight-source.js\";\n\nexport interface LoadModelOptions {\n /** HF repo ID (e.g. \"Qwen/Qwen3.5-0.8B\") or full URL. */\n repo: string;\n /** Progress callback: (loaded, total, message) */\n onProgress?: (loaded: number, total: number, message: string) => void;\n /** Custom HF key mapper (defaults to stripping \"model.\" prefix). */\n keyMapper?: HFKeyMapper;\n /** HuggingFace API token for gated models. */\n hfToken?: string;\n /** Revision/branch (default: \"main\"). */\n revision?: string;\n /** Local cache directory for downloaded files (Node.js only). */\n cacheDir?: string;\n /**\n * Weight dtype:\n * - \"f32\" full precision (or the repo's native quantization, e.g. MLX/GPTQ q4)\n * - \"q4\" on-the-fly INT4 quantization (~4× smaller)\n * - \"auto\" (recommended) picks q4 on mobile (iOS/Android) to fit in device\n * memory and f32/native on desktop. Already-quantized repos\n * (MLX/GPTQ 4-bit) stay q4 regardless.\n */\n dtype?: GraphDType | \"auto\";\n /** KV cache dtype: \"f16\" halves memory traffic during attention. Requires GPU f16 support. */\n kvDtype?: KVDType;\n /**\n * Build an embedding graph (last-token pool + L2 norm) instead of an LM head.\n * Only valid for Qwen2/Qwen3 CausalLM architectures (e.g. Qwen3-Embedding).\n */\n embedding?: boolean;\n /**\n * Build the multimodal LM graph variant (M-RoPE + image-embedding splice) so\n * the text model can consume spliced image tokens. Only meaningful for\n * Qwen3_5ForConditionalGeneration. Reserves `maxVisionTokens` rows for the\n * vision-embedding buffer. Text-only generation through this graph is\n * numerically identical to the non-multimodal graph (M-RoPE fed linear\n * positions == standard 1D RoPE).\n */\n multimodal?: { maxVisionTokens: number };\n /**\n * Force-download and key-map the vision tower even without the multimodal LM\n * graph. `enableVision` (via `multimodal`) already implies this; this flag is\n * for callers that load weights directly (e.g. the vision-encoder validation\n * scripts) and build the vision graph/executor themselves. When neither this\n * nor `multimodal` is set, the ~201MB ViT is excluded from the download.\n */\n loadVisionTower?: boolean;\n}\n\n/**\n * Gemma 4 Per-Layer-Embeddings (PLE) source, kept CPU-resident.\n *\n * The PLE table (`embed_tokens_per_layer`, [vocab, num_layers*256]) is ~1.17GB\n * at 4-bit. Uploading it to a GPU buffer would make the model non-mobile-viable\n * and would hit the per-binding size cap. Instead the loader hands the quantized\n * table to the executor in JS memory; the executor gathers + dequantizes only the\n * rows for the current input tokens each forward step (a tiny [T, width] upload).\n */\nexport interface PleSource {\n /**\n * Flat row-major INT4 nibbles (Gerbil packing, 8 per u32). HEAP-RESIDENT path\n * (Node/desktop). In the browser this is empty and `cache` is set instead so\n * the ~1.17 GB table never sits in the JS heap during load.\n */\n packed: Uint32Array;\n /** Per-group scales (Gerbil (nibble - zero) * scale convention). */\n scales: Float32Array;\n /** Per-group zero points. */\n zeros: Float32Array;\n /** Row width = num_layers * hidden_size_per_layer_input (E2B: 35*256 = 8960). */\n width: number;\n /** Dequant group size (MLX: 64). */\n groupSize: number;\n /** Activation tensor the per-step gathered rows are written into. */\n targetTensor: string;\n /**\n * Browser only: when set, the quantized PLE table's bytes live in CacheStorage\n * (not the heap). The executor reads the slice of nibbles/scales/zeros it needs\n * for the current tokens on demand. Keeps peak load heap bounded.\n */\n cache?: {\n cacheName: string;\n packedKey: string;\n scalesKey: string;\n zerosKey: string;\n /** packed.length (u32 count) — for bounds/Range math. */\n packedLen: number;\n };\n}\n\nexport interface LoadedModel {\n /** The generated computation graph (IR). */\n graph: ModelGraph;\n /** The tokenizer. */\n tokenizer: Tokenizer;\n /**\n * Weight tensors mapped to canonical names. A `WeightSource` so the executor\n * can pull one tensor at a time (cache-backed in the browser, heap-backed on\n * Node) instead of requiring the whole model to sit in heap at once. Use\n * `get(name)` (async) to materialize a tensor's bytes on demand.\n */\n weights: WeightSource;\n /** Raw config.json for reference. */\n rawConfig: Record<string, unknown>;\n /**\n * CPU-resident Gemma 4 PLE table (set only for Gemma 4). Pass to\n * `executor.setPleSource()` so the big table never becomes GPU-resident.\n */\n pleSource?: PleSource;\n}\n\n/**\n * Resolve a repo string to a base URL for HuggingFace Hub API.\n */\nfunction resolveHFBaseURL(repo: string, revision: string): string {\n // If it's already a full URL, use it\n if (repo.startsWith(\"http://\") || repo.startsWith(\"https://\")) {\n return repo;\n }\n // Standard HF Hub URL\n return `https://huggingface.co/${repo}/resolve/${revision}`;\n}\n\n// ── File cache helpers (Node.js only) ──────────────────────────────────\n\nlet _fs: typeof import(\"node:fs\") | null = null;\nlet _path: typeof import(\"node:path\") | null = null;\nlet _fsInitialized = false;\n\nasync function initFs(): Promise<void> {\n if (_fsInitialized) return;\n _fsInitialized = true;\n try {\n _fs = await import(\"node:fs\");\n _path = await import(\"node:path\");\n } catch {\n // Not in Node.js (browser) — caching disabled\n }\n}\n\nfunction getCachePath(cacheDir: string, filename: string): string {\n return _path!.join(cacheDir, filename.replace(/\\//g, \"_\"));\n}\n\nfunction readCachedFile(cacheDir: string, filename: string): Buffer | null {\n if (!_fs || !_path) return null;\n const p = getCachePath(cacheDir, filename);\n try {\n return _fs.readFileSync(p);\n } catch {\n return null;\n }\n}\n\nfunction writeCacheFile(\n cacheDir: string,\n filename: string,\n data: ArrayBuffer | Buffer | Uint8Array,\n): void {\n if (!_fs || !_path) return;\n _fs.mkdirSync(cacheDir, { recursive: true });\n const p = getCachePath(cacheDir, filename);\n let buf: Buffer;\n if (Buffer.isBuffer(data)) {\n buf = data;\n } else if (data instanceof Uint8Array) {\n buf = Buffer.from(data);\n } else {\n buf = Buffer.from(data);\n }\n _fs.writeFileSync(p, buf);\n}\n\n// ── Browser cache helpers (OPFS-durable, CacheStorage fallback) ─────────\n//\n// Two-tier durable cache behind a single read/write/has seam:\n//\n// 1. OPFS via a dedicated Worker (`createSyncAccessHandle`, chunked writes) —\n// the ONLY iOS-safe durable path. On iOS Safari the CacheStorage API is\n// evicted under quota pressure in a plain tab (`navigator.storage.persist()`\n// is not granted outside an installed PWA), so weights cached there are\n// re-downloaded every visit. OPFS survives a full tab close + reopen. Main-\n// thread OPFS `createWritable` throws \"out of quota\" mid-write on iOS, so all\n// OPFS I/O runs on a blob-URL worker with sync access handles. See\n// src/gpu/opfs-cache.ts.\n//\n// 2. CacheStorage (`caches.open(...)`) — fallback when OPFS/Worker is\n// unavailable (older browsers, some embedded webviews). Best-effort durable;\n// fine on desktop where quota pressure is rare.\n//\n// Reads prefer OPFS, then fall back to CacheStorage. Writes go to OPFS when\n// available and only to CacheStorage otherwise (no double-write — OPFS is the\n// durable winner and double-storing would waste the ~404 MB twice against quota).\n// All of this is inert on Node: `opfsAvailable()` and `typeof caches` are both\n// false there, so the helpers short-circuit and the Node weight path is\n// unaffected.\n\n// v4: storage layout changed (OPFS subdirectory `gerbil-models-v4` + CacheStorage\n// bucket of the same name). A fresh namespace also abandons any entries poisoned\n// during earlier buggy builds (the reload-loop era wrote right-size-wrong-content\n// tensors that size validation can't detect). The per-tensor cache logic itself\n// is verified correct by scripts/engine/test-cache-roundtrip.mjs (load → cache →\n// reload-from-cache → identical coherent output).\nconst BROWSER_CACHE_NAME = \"gerbil-models-v4\";\n\n// Per-tensor weight caching. ON by default — the round-trip test\n// (scripts/engine/test-cache-roundtrip.mjs) proves the cache read path is\n// correct, and the v3 namespace abandons entries poisoned by earlier buggy\n// builds. Can be force-disabled via GERBIL_TENSOR_CACHE=0 / globalThis flag if a\n// corruption issue ever resurfaces.\nconst TENSOR_CACHE_ENABLED =\n (typeof process === \"undefined\" || process.env?.GERBIL_TENSOR_CACHE !== \"0\") &&\n (globalThis as { GERBIL_TENSOR_CACHE?: boolean }).GERBIL_TENSOR_CACHE !== false;\n\nlet _persistRequested = false;\nasync function requestPersistence(): Promise<void> {\n if (_persistRequested) return;\n _persistRequested = true;\n try {\n // Only granted in an installed PWA on iOS; harmless best-effort elsewhere.\n await (navigator as any)?.storage?.persist?.();\n } catch {\n /* best-effort */\n }\n}\n\n// Model weights are cached in IndexedDB (see idb-cache.ts for why: the Cache API's\n// fragment-stripping collapsed our per-tensor keys → wrong bytes/garbage, and OPFS\n// via Worker was fragile on iOS). IDB keys on the exact string — no footguns. All\n// three helpers are inert on Node (no indexedDB → miss/no-op), so the heap load\n// path is unchanged off the browser.\n\n/** Read a cache key's bytes from IndexedDB, or null on miss. */\nasync function browserCacheRead(cacheKey: string): Promise<ArrayBuffer | null> {\n return idbGet(cacheKey);\n}\n\n/** Cheap presence check — true without materializing the body. */\nasync function browserCacheHas(cacheKey: string): Promise<boolean> {\n return idbHas(cacheKey);\n}\n\n/**\n * Write a cache key's bytes to IndexedDB (best-effort; never throws). The\n * `transferable` flag is accepted for call-site compatibility but unused — IDB\n * structured-clones the value, so the caller's buffer is never detached.\n */\nasync function browserCacheWrite(\n cacheKey: string,\n data: ArrayBuffer,\n _transferable = false,\n): Promise<void> {\n await requestPersistence();\n await idbPut(cacheKey, data);\n}\n\n/**\n * Reclaim quota from the LEGACY caches, ONCE per session. The model cache is now\n * IndexedDB; the old CacheStorage (`gerbil-models-*`, fragment-keyed → the `RRRR`\n * corruption) and OPFS-worker stores are dead. iOS Safari's per-origin quota is\n * ~1 GB and that orphaned junk could consume nearly all of it, so we purge ALL of\n * it to free room for the IDB cache. Best-effort; inert on Node.\n */\nlet _evictedStale = false;\nasync function evictStaleCaches(): Promise<void> {\n if (_evictedStale) return;\n _evictedStale = true;\n try {\n if (typeof caches !== \"undefined\") {\n const keys = await caches.keys();\n await Promise.all(\n keys.filter((k) => k.startsWith(\"gerbil-models-\")).map((k) => caches.delete(k)),\n );\n }\n } catch {\n /* best-effort */\n }\n await opfsEvictStale();\n}\n\n// ── Fetch helpers ──────────────────────────────────────────────────────\n\n/**\n * Fetch a JSON file from HF with optional auth and caching.\n */\nasync function fetchJSON(\n baseURL: string,\n filename: string,\n hfToken?: string,\n cacheDir?: string,\n): Promise<any> {\n // Check Node.js cache first\n if (cacheDir) {\n const cached = readCachedFile(cacheDir, filename);\n if (cached) return JSON.parse(cached.toString(\"utf-8\"));\n }\n\n // Check browser cache\n const browserKey = `${baseURL}/${filename}`;\n const browserCached = await browserCacheRead(browserKey);\n if (browserCached) {\n return JSON.parse(new TextDecoder().decode(browserCached));\n }\n\n const url = `${baseURL}/${filename}`;\n const headers: Record<string, string> = {};\n if (hfToken) {\n headers.Authorization = `Bearer ${hfToken}`;\n }\n const response = await fetch(url, { headers });\n if (!response.ok) {\n throw new Error(`Failed to fetch ${url}: ${response.status} ${response.statusText}`);\n }\n const text = await response.text();\n\n // Write to Node.js cache\n if (cacheDir) {\n writeCacheFile(cacheDir, filename, Buffer.from(text, \"utf-8\"));\n }\n\n // Write to browser cache\n await browserCacheWrite(browserKey, new TextEncoder().encode(text).buffer);\n\n return JSON.parse(text);\n}\n\n/**\n * Fetch a UTF-8 text file from HF (with caching). Returns null on 404 / error.\n * Used for sidecar files like `chat_template.jinja` that some models ship\n * instead of an inline `chat_template` in tokenizer_config.json.\n */\nasync function fetchText(\n baseURL: string,\n filename: string,\n hfToken?: string,\n cacheDir?: string,\n): Promise<string | null> {\n if (cacheDir) {\n const cached = readCachedFile(cacheDir, filename);\n if (cached) return cached.toString(\"utf-8\");\n }\n const browserKey = `${baseURL}/${filename}`;\n const browserCached = await browserCacheRead(browserKey);\n if (browserCached) {\n return new TextDecoder().decode(browserCached);\n }\n const url = `${baseURL}/${filename}`;\n const headers: Record<string, string> = {};\n if (hfToken) {\n headers.Authorization = `Bearer ${hfToken}`;\n }\n const response = await fetch(url, { headers });\n if (!response.ok) return null;\n const text = await response.text();\n if (cacheDir) {\n writeCacheFile(cacheDir, filename, Buffer.from(text, \"utf-8\"));\n }\n await browserCacheWrite(browserKey, new TextEncoder().encode(text).buffer);\n return text;\n}\n\n/**\n * Fetch a binary file from HF with progress tracking and caching.\n */\nasync function fetchBinary(\n baseURL: string,\n filename: string,\n hfToken?: string,\n onProgress?: (loaded: number, total: number) => void,\n cacheDir?: string,\n): Promise<ArrayBuffer> {\n // Check cache first\n if (cacheDir) {\n const cached = readCachedFile(cacheDir, filename);\n if (cached) {\n onProgress?.(cached.byteLength, cached.byteLength);\n return cached.buffer.slice(\n cached.byteOffset,\n cached.byteOffset + cached.byteLength,\n ) as ArrayBuffer;\n }\n }\n\n const url = `${baseURL}/${filename}`;\n const headers: Record<string, string> = {};\n if (hfToken) {\n headers.Authorization = `Bearer ${hfToken}`;\n }\n\n const response = await fetch(url, { headers });\n if (!response.ok) {\n throw new Error(`Failed to fetch ${url}: ${response.status} ${response.statusText}`);\n }\n\n const contentLength = Number(response.headers.get(\"content-length\") || 0);\n\n if (!response.body || !onProgress) {\n const buf = await response.arrayBuffer();\n if (cacheDir) writeCacheFile(cacheDir, filename, buf);\n return buf;\n }\n\n // Stream with progress\n const reader = response.body.getReader();\n const chunks: Uint8Array[] = [];\n let loaded = 0;\n\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n chunks.push(value);\n loaded += value.byteLength;\n onProgress(loaded, contentLength);\n }\n\n // Combine chunks into single ArrayBuffer\n const result = new Uint8Array(loaded);\n let offset = 0;\n for (const chunk of chunks) {\n result.set(chunk, offset);\n offset += chunk.byteLength;\n }\n\n if (cacheDir) writeCacheFile(cacheDir, filename, result);\n return result.buffer;\n}\n\n/**\n * Discover safetensors files in the repo.\n *\n * Models can have a single `model.safetensors` or multiple shards:\n * `model-00001-of-00002.safetensors`, `model-00002-of-00002.safetensors`\n *\n * We check for the index file first, then fall back to single file.\n */\nasync function discoverSafetensorsFiles(\n baseURL: string,\n hfToken?: string,\n cacheDir?: string,\n): Promise<string[]> {\n // Try the index file first (for sharded models)\n try {\n const index = await fetchJSON(baseURL, \"model.safetensors.index.json\", hfToken, cacheDir);\n if (index.weight_map) {\n // Get unique filenames from the weight map\n const files = [...new Set(Object.values(index.weight_map))] as string[];\n return files.sort();\n }\n } catch {\n // No index file -- try single file\n }\n\n return [\"model.safetensors\"];\n}\n\n/**\n * Fetch a byte range from a URL over the network (no caching).\n */\nasync function fetchRangeRaw(\n url: string,\n start: number,\n end: number,\n hfToken?: string,\n): Promise<ArrayBuffer> {\n const headers: Record<string, string> = {\n Range: `bytes=${start}-${end - 1}`,\n };\n if (hfToken) {\n headers.Authorization = `Bearer ${hfToken}`;\n }\n // Retry transient failures with backoff. HuggingFace rate-limits unauthenticated\n // downloads (403/429), and CDN/network hiccups happen; a few backed-off retries\n // turn those into a slightly slower load instead of a hard failure. (Once the\n // model is cached in IDB these requests don't happen again.)\n let lastStatus = 0;\n for (let attempt = 0; attempt < 4; attempt++) {\n let resp: Response;\n try {\n resp = await fetch(url, { headers });\n } catch {\n // Network error — back off and retry.\n if (attempt < 3) {\n await new Promise((r) => setTimeout(r, 600 * 2 ** attempt));\n continue;\n }\n throw new Error(`Range request failed: network error for ${url}`);\n }\n if (resp.ok || resp.status === 206) return resp.arrayBuffer();\n lastStatus = resp.status;\n // 403/429 = rate limit (HF), 5xx = transient server error → retry; else fail.\n const retriable = resp.status === 403 || resp.status === 429 || resp.status >= 500;\n if (retriable && attempt < 3) {\n await new Promise((r) => setTimeout(r, 600 * 2 ** attempt));\n continue;\n }\n break;\n }\n throw new Error(\n `Range request failed: ${lastStatus}${lastStatus === 403 || lastStatus === 429 ? \" (HuggingFace rate limit — wait a bit and retry; once cached this won't recur)\" : \"\"}`,\n );\n}\n\n/**\n * Fetch a byte range from a URL, caching the exact range.\n *\n * Used for small, stable ranges (e.g. safetensors headers) whose boundaries\n * don't depend on which tensors a particular load needs. Tensor *bodies* are\n * NOT fetched through here — they're cached per-tensor by stable byte-range in\n * fetchSelectiveTensors so a vision load can reuse a text load's tensors.\n */\nasync function fetchRange(\n url: string,\n start: number,\n end: number,\n hfToken?: string,\n): Promise<ArrayBuffer> {\n // Check browser cache for this range\n const browserKey = `${url}?_range=${start}-${end}`;\n const browserCached = await browserCacheRead(browserKey);\n if (browserCached) return browserCached;\n\n const buf = await fetchRangeRaw(url, start, end, hfToken);\n\n // Cache for next time\n await browserCacheWrite(browserKey, buf);\n\n return buf;\n}\n\n/**\n * Fetch the safetensors header only (two range requests: 8 bytes + header).\n */\nasync function fetchSafetensorsHeader(url: string, hfToken?: string): Promise<SafetensorsFile> {\n // Fetch first 8 bytes to get header length\n const lengthBuf = await fetchRange(url, 0, 8, hfToken);\n const headerLength = Number(new DataView(lengthBuf).getBigUint64(0, true));\n\n // Fetch full header (8 + headerLength bytes)\n const headerBuf = await fetchRange(url, 0, 8 + headerLength, hfToken);\n return parseSafetensorsHeader(headerBuf);\n}\n\n/** A contiguous byte range to download. */\ninterface ByteRange {\n start: number;\n end: number;\n entries: SafetensorEntry[];\n}\n\n/**\n * Merge close byte ranges to reduce HTTP requests.\n *\n * - `gapThreshold`: merge two ranges if the gap between them is smaller than this\n * (avoids an extra request for a tiny skipped gap).\n * - `maxRangeBytes`: hard cap on a single merged range. Without this, a\n * contiguously-packed safetensors merges into ONE range = the whole model, so\n * the loader buffers the entire file in a single fetch — no streaming progress\n * (the bar sits at 0/N until done) and a transient allocation big enough to\n * crash mobile Safari. Capping splits the download into bounded segments that\n * report progress as they land and keep peak memory low.\n */\nfunction mergeRanges(\n entries: SafetensorEntry[],\n dataStart: number,\n gapThreshold: number = 1024 * 1024,\n maxRangeBytes: number = Number.POSITIVE_INFINITY,\n): ByteRange[] {\n if (entries.length === 0) return [];\n\n // Sort by offset\n const sorted = [...entries].sort((a, b) => a.dataOffset - b.dataOffset);\n\n const ranges: ByteRange[] = [];\n let current: ByteRange = {\n start: dataStart + sorted[0].dataOffset,\n end: dataStart + sorted[0].dataOffset + sorted[0].dataLength,\n entries: [sorted[0]],\n };\n\n for (let i = 1; i < sorted.length; i++) {\n const entryStart = dataStart + sorted[i].dataOffset;\n const entryEnd = entryStart + sorted[i].dataLength;\n\n const withinGap = entryStart - current.end <= gapThreshold;\n const withinCap = entryEnd - current.start <= maxRangeBytes;\n if (withinGap && withinCap) {\n // Merge: extend the range (may include some skipped bytes, but avoids extra request)\n current.end = Math.max(current.end, entryEnd);\n current.entries.push(sorted[i]);\n } else {\n ranges.push(current);\n current = { start: entryStart, end: entryEnd, entries: [sorted[i]] };\n }\n }\n ranges.push(current);\n return ranges;\n}\n\n/**\n * Selectively download needed tensors from a safetensors file using Range requests.\n * Returns a map of tensor name → { data, shape }.\n */\n/** Byte alignment required for a safetensors dtype's typed-array view. */\nfunction dtypeAlign(dtype: string): number {\n if (dtype === \"F64\" || dtype === \"I64\" || dtype === \"U64\") return 8;\n if (dtype === \"F32\" || dtype === \"I32\" || dtype === \"U32\") return 4;\n if (dtype === \"F16\" || dtype === \"BF16\" || dtype === \"I16\" || dtype === \"U16\") return 2;\n return 1;\n}\n\n/** Build the appropriate typed-array view of a tensor at `byteOffset` in `buf`. */\nfunction makeTensorView(\n buf: ArrayBuffer,\n byteOffset: number,\n entry: SafetensorEntry,\n): ArrayBufferView {\n // Aligned views can alias the buffer directly; unaligned ones must be copied\n // into a fresh (offset-0) buffer so the typed-array constructor accepts them.\n const aligned = byteOffset % dtypeAlign(entry.dtype) === 0;\n const src = aligned ? buf : buf.slice(byteOffset, byteOffset + entry.dataLength);\n const off = aligned ? byteOffset : 0;\n switch (entry.dtype) {\n case \"F32\":\n return new Float32Array(src, off, entry.dataLength / 4);\n case \"I32\":\n return new Int32Array(src, off, entry.dataLength / 4);\n case \"U32\":\n return new Uint32Array(src, off, entry.dataLength / 4);\n case \"F16\":\n case \"BF16\":\n return new Uint16Array(src, off, entry.dataLength / 2);\n default:\n return new Uint8Array(src, off, entry.dataLength);\n }\n}\n\n/**\n * Stable per-tensor browser-cache key. Keyed by the tensor's *absolute* byte\n * range in the file (offset + length), which is fixed regardless of which other\n * tensors a given load requests. This is what lets a vision load reuse the\n * tensors a prior text load already downloaded — they hit the same keys —\n * instead of re-downloading because the merged Range boundaries differ.\n */\nfunction tensorCacheKey(url: string, file: SafetensorsFile, entry: SafetensorEntry): string {\n const absStart = file.dataStart + entry.dataOffset;\n return `${url}#t:${absStart}-${entry.dataLength}`;\n}\n\n/**\n * Selectively download needed tensors from a safetensors file using Range\n * requests.\n *\n * Two modes, gated by `cacheStore`:\n *\n * • `cacheStore` supplied (browser): each tensor's bytes are written to the\n * per-tensor download cache (as before) and the tensor is REGISTERED in the\n * store as a cache-backed descriptor under its canonical name — its bytes are\n * NOT retained in heap. The returned Map is empty. This is the change that\n * bounds peak heap during download: instead of accumulating the whole model\n * in the returned Map, the heap only ever holds the current range segment\n * (≤ maxRangeBytes, 24 MB on mobile) which is released each iteration.\n *\n * • `cacheStore` omitted (used by direct-load helpers): returns a Map of\n * name → { data, shape } exactly as before.\n */\nasync function fetchSelectiveTensors(\n url: string,\n file: SafetensorsFile,\n neededEntries: SafetensorEntry[],\n hfToken?: string,\n onProgress?: (loaded: number, total: number, message?: string) => void,\n cacheStore?: { store: CacheWeightStore; keyMapper: HFKeyMapper },\n): Promise<Map<string, { data: ArrayBufferView; shape: number[] }>> {\n const results = new Map<string, { data: ArrayBufferView; shape: number[] }>();\n const totalBytes = neededEntries.reduce((sum, e) => sum + e.dataLength, 0);\n let loadedBytes = 0;\n\n const isMobile =\n typeof navigator !== \"undefined\" && /iPhone|iPad|iPod|Android/i.test(navigator.userAgent);\n // Cap merged-range size so the download streams in bounded segments: keeps\n // peak transient memory low (critical on iOS) and lets the progress bar move.\n const maxRangeBytes = isMobile ? 24 * 1024 * 1024 : 256 * 1024 * 1024;\n\n // Register a downloaded tensor: either retain it in the result Map (Node-ish\n // direct loads) or — in browser cache mode — record only a cache-backed\n // descriptor under the canonical name so no bytes stay in heap.\n const record = (entry: SafetensorEntry, key: string): void => {\n if (cacheStore) {\n const canonical = cacheStore.keyMapper(entry.name);\n if (canonical) {\n // Bytes live in the per-tensor download cache (BROWSER_CACHE_NAME).\n cacheStore.store.registerCached(\n canonical,\n entry.shape,\n entry.dtype,\n key,\n BROWSER_CACHE_NAME,\n );\n }\n return;\n }\n // Fallback (no cache backend): caller still wants bytes in the Map.\n // (This branch is only reachable when CacheStorage is unavailable.)\n };\n\n // Reclaim quota from the dead legacy CacheStorage/OPFS caches before we\n // probe/write IDB — runs on the default (heap) browser path too, since that's\n // where per-tensor IDB caching now happens.\n // Fire-and-forget: legacy-cache cleanup must NEVER block (or hang) the load —\n // OPFS dir iteration can stall on Safari, and it's only reclaiming dead junk.\n if (TENSOR_CACHE_ENABLED) void evictStaleCaches();\n\n // Pass 1: serve any tensor already in the per-tensor browser cache. A tensor\n // cached by an earlier load (e.g. text-only) is reused here even if this load\n // (e.g. enableVision) needs a different overall set — the key is per-tensor.\n // Probe in parallel: hundreds of sequential CacheStorage.match() calls are slow\n // on mobile and would make a fresh load look frozen before any download starts.\n // In cache mode we only need to know WHICH tensors are present (HEAD-style), not\n // their bytes — so probe with `caches.match` but discard bodies immediately.\n const uncached: SafetensorEntry[] = [];\n // Probe presence CHEAPLY (getKey, no bodies) in parallel. Reading the actual\n // bytes for hundreds of tensors at once (the old heap-path Promise.all over\n // browserCacheRead) stormed IndexedDB with ~696 concurrent transactions AND\n // pulled ~404 MB into heap in one burst → Safari hung on a warm cache (the lean\n // harness only ever hit a cold cache, so it never showed). Presence is cheap and\n // parallel-safe; the heap path then reads the HIT bytes one at a time below.\n // ONE getAllKeys() transaction, then membership in memory — NOT hundreds of\n // parallel getKey transactions (that storm hangs Safari on a warm cache).\n const cachedKeys = TENSOR_CACHE_ENABLED ? await idbAllKeys() : new Set<string>();\n const presence = neededEntries.map((entry) => cachedKeys.has(tensorCacheKey(url, file, entry)));\n for (let i = 0; i < neededEntries.length; i++) {\n const entry = neededEntries[i];\n const key = tensorCacheKey(url, file, entry);\n if (!presence[i]) {\n uncached.push(entry);\n continue;\n }\n if (cacheStore) {\n // Presence-only (the store reads bytes lazily later).\n record(entry, key);\n loadedBytes += entry.dataLength;\n onProgress?.(loadedBytes, totalBytes);\n continue;\n }\n // Heap path: read the cached bytes ONE AT A TIME (never all in parallel — that\n // storms IDB and OOMs Safari). VALIDATE size; a partial/corrupt entry is\n // treated as a miss so pass 2 re-downloads and overwrites it (self-healing).\n const buf = await browserCacheRead(key);\n if (buf && buf.byteLength >= entry.dataLength) {\n results.set(entry.name, {\n data: makeTensorView(buf, 0, entry),\n shape: entry.shape,\n });\n loadedBytes += entry.dataLength;\n onProgress?.(loadedBytes, totalBytes);\n } else {\n uncached.push(entry);\n }\n }\n\n // Cache visibility: report how many tensors were reused vs must be downloaded,\n // and which backend served them — so a silent \"redownloads everything\" cache\n // miss is diagnosable on-device (most users have no Safari console). Surfaces\n // through onProgress, which the iPad runner streams back to the dashboard.\n if (TENSOR_CACHE_ENABLED) {\n const hits = neededEntries.length - uncached.length;\n const backend = idbCacheAvailable() ? \"idb\" : \"none\";\n const msg = `cache[${backend}]: ${hits}/${neededEntries.length} tensors reused, ${uncached.length} to download`;\n console.log(`[gerbil] ${msg}`);\n onProgress?.(loadedBytes, totalBytes, msg);\n }\n\n // Pass 2: download only the uncached tensors (merged into bounded contiguous\n // ranges), then cache each one individually by its stable key.\n const ranges = mergeRanges(uncached, file.dataStart, 1024 * 1024, maxRangeBytes);\n for (const range of ranges) {\n const rangeBuf = await fetchRangeRaw(url, range.start, range.end, hfToken);\n // A truncated range response (e.g. an aborted/partial download) would yield\n // short tensor slices — bad views now and a poisoned cache later. Reject it\n // loudly instead of silently caching garbage.\n const expectedRangeBytes = range.end - range.start;\n if (rangeBuf.byteLength < expectedRangeBytes) {\n throw new Error(\n `Incomplete weight download: got ${rangeBuf.byteLength} of ${expectedRangeBytes} bytes for ${url}. Retry.`,\n );\n }\n\n for (const entry of range.entries) {\n const localOffset = file.dataStart + entry.dataOffset - range.start;\n const key = tensorCacheKey(url, file, entry);\n // Cache the exact tensor bytes (a standalone copy) for cross-load reuse.\n const tensorBytes = rangeBuf.slice(localOffset, localOffset + entry.dataLength);\n // Only cache a full-size slice so we never write a short (poisoning) entry.\n if (TENSOR_CACHE_ENABLED && tensorBytes.byteLength === entry.dataLength) {\n // `tensorBytes` is a standalone slice not reused below (the heap path\n // views `rangeBuf`, a different buffer), so it can be transferred to the\n // OPFS worker zero-copy — this is the big-memory path, so avoiding a copy\n // matters most here.\n await browserCacheWrite(key, tensorBytes, true);\n }\n if (cacheStore) {\n // Bytes are now durably in cache; do NOT retain them in heap.\n record(entry, key);\n } else {\n results.set(entry.name, {\n data: makeTensorView(rangeBuf, localOffset, entry),\n shape: entry.shape,\n });\n }\n loadedBytes += entry.dataLength;\n onProgress?.(loadedBytes, totalBytes);\n }\n // `rangeBuf` (and its slices for the non-cache path) go out of scope here;\n // in cache mode nothing references it, so the segment is freed before the\n // next range is fetched — peak heap stays ≈ one range segment.\n }\n\n return results;\n}\n\n/**\n * Create the appropriate HF key mapper for a given architecture.\n *\n * Qwen3.5 conditional generation models use \"model.language_model.\" prefix for\n * text model weights and include visual encoder + MTP weights that we skip.\n * Standard models just use \"model.\" prefix.\n *\n * `wantVision` controls whether the vision tower is kept. When false (text-only\n * loads) the ViT is dropped from the selective download so the ~201MB tower is\n * never fetched. When true (enableVision) the tower is mapped to the canonical\n * \"visual.*\" keys the vision graph expects, regardless of whether the source\n * checkpoint names it \"model.visual.*\" (HF BF16) or \"vision_tower.*\" (MLX).\n */\nfunction createKeyMapperForArch(architectureName: string, wantVision = false): HFKeyMapper {\n // KaniTTS2 ships the LFM2 backbone verbatim — the only structural rename it needs\n // is the same self_attn.out_proj → o_proj as LFM2. Its extra tensors\n // (learnable_rope_layers.*, speaker_emb_projection) pass through after stripping\n // the \"model.\" prefix and are picked up by the codec-LM graph when it lands.\n if (architectureName === \"Lfm2ForCausalLM\" || architectureName === \"KaniTTS2ForCausalLM\") {\n // LFM2 weights live under \"model.\" like the others, but the attention\n // output projection is named \"out_proj\" where the canonical IR (shared with\n // Qwen) expects \"o_proj\". Rename it so the o_proj graph tensors resolve.\n // All other LFM2 keys (operator_norm, ffn_norm, embedding_norm, conv.*,\n // feed_forward.w1/w2/w3, q_layernorm/k_layernorm) are referenced verbatim\n // by the generator via per-tensor safetensorsKey, so they pass through.\n return (hfKey: string): string | null => {\n let key = hfKey;\n if (key.startsWith(\"model.\")) {\n key = key.slice(6);\n }\n key = key.replace(/\\.self_attn\\.out_proj\\./, \".self_attn.o_proj.\");\n return key;\n };\n }\n if (architectureName === \"Qwen3_5ForConditionalGeneration\") {\n return (hfKey: string): string | null => {\n // Vision tower lives under one of two prefixes depending on the converter:\n // • \"model.visual.*\" — HF BF16 checkpoint (Qwen/Qwen3.5-0.8B)\n // • \"vision_tower.*\" — mlx-vlm 4-bit convert (mlx-community/…-4bit).\n // In MLX-4bit repos the text tower is quantized (U32 + scales/biases)\n // but the ViT is left BF16, so the suffix structure is identical to\n // the HF tower — only the prefix differs. Both map to the canonical\n // \"visual.*\" keys the vision graph generator expects.\n // When vision is not requested we drop the tower so the selective\n // downloader never fetches its ~201MB of weights.\n if (hfKey.startsWith(\"model.visual.\")) {\n return wantVision ? hfKey.slice(6) : null; // \"model.visual.*\" → \"visual.*\"\n }\n if (hfKey.startsWith(\"vision_tower.\")) {\n // \"vision_tower.\".length === 13 → replace with \"visual.\"\n return wantVision ? `visual.${hfKey.slice(13)}` : null;\n }\n // Always skip the multi-token-prediction head (an inference-speed\n // optimization, not a modality).\n if (hfKey.startsWith(\"mtp.\")) {\n return null;\n }\n let key = hfKey;\n // HF/GPTQ uses \"model.language_model.\" prefix, MLX uses \"language_model.model.\"\n if (key.startsWith(\"model.language_model.\")) {\n key = key.slice(21); // \"model.language_model.\".length\n } else if (key.startsWith(\"language_model.model.\")) {\n key = key.slice(21); // \"language_model.model.\".length (also 21 chars)\n } else if (key.startsWith(\"model.\")) {\n key = key.slice(6); // \"model.\".length\n }\n return key;\n };\n }\n if (architectureName === \"Gemma4ForConditionalGeneration\") {\n // Gemma 4 nests the text tower under \"language_model.\" and carries vision +\n // audio towers we drop for text-only loads. Prefix shapes observed:\n // HF BF16: \"model.language_model.*\" (+ \"model.vision_tower.*\", \"model.audio_tower.*\",\n // \"model.embed_vision.*\", \"model.embed_audio.*\")\n // MLX-4bit: \"language_model.model.*\" (+ \"vision_tower.*\", \"audio_tower.*\")\n // The text tower keys after stripping match the canonical Gemma layout used\n // by gemma4.ts (embed_tokens, embed_tokens_per_layer, per_layer_*, layers.N.*,\n // norm). PLE/projection tensors pass through verbatim.\n return (hfKey: string): string | null => {\n // Vision tower: keep + canonicalize when wantVision; otherwise drop so the\n // selective downloader never fetches it. Both prefixes (\"model.vision_tower.*\"\n // HF BF16, \"vision_tower.*\" MLX) and the multimodal projector\n // (\"model.embed_vision.*\" / \"embed_vision.*\") map to the canonical\n // \"vision_tower.*\" / \"embed_vision.*\" keys gemma4_vision.ts expects.\n if (hfKey.includes(\"vision_tower.\")) {\n if (!wantVision) return null;\n const idx = hfKey.indexOf(\"vision_tower.\");\n return hfKey.slice(idx); // strip any \"model.\" prefix before vision_tower.\n }\n if (hfKey.includes(\"embed_vision.\")) {\n if (!wantVision) return null;\n const idx = hfKey.indexOf(\"embed_vision.\");\n return hfKey.slice(idx);\n }\n // Always drop the audio modality (not supported in the vision path).\n if (hfKey.includes(\"audio_tower.\") || hfKey.includes(\"embed_audio.\")) {\n return null;\n }\n let key = hfKey;\n if (key.startsWith(\"model.language_model.\")) {\n key = key.slice(21); // \"model.language_model.\".length\n } else if (key.startsWith(\"language_model.model.\")) {\n key = key.slice(21); // \"language_model.model.\".length (also 21)\n } else if (key.startsWith(\"language_model.\")) {\n key = key.slice(15); // \"language_model.\".length (lm_head etc.)\n } else if (key.startsWith(\"model.\")) {\n key = key.slice(6);\n }\n return key;\n };\n }\n if (architectureName === \"MoonshineForConditionalGeneration\") {\n // Moonshine's HF keys already match the canonical Moonshine graph names once\n // the \"model.\" prefix is stripped, e.g.\n // model.encoder.conv1.weight → encoder.conv1.weight\n // model.encoder.layers.0.self_attn.q_proj.weight → encoder.layers.0.self_attn.q_proj.weight\n // model.decoder.layers.0.encoder_attn.q_proj.weight→ decoder.layers.0.encoder_attn.q_proj.weight\n // model.decoder.embed_tokens.weight → decoder.embed_tokens.weight (tied lm_head)\n // The default \"strip model.\" mapper is therefore exactly correct. (Moonshine\n // weight loading runs through the dual-graph STT loader — remaining work —\n // not the single-graph generate() path, which throws by design.)\n return createDefaultHFKeyMapper();\n }\n return createDefaultHFKeyMapper();\n}\n\nconst PLE_CACHE_NAME = \"gerbil-ple-v2\";\n\n/**\n * Build a `PleSource` from the quantized PLE table.\n *\n * Node/desktop (`cacheStore` null): keep the arrays heap-resident — unchanged\n * behavior, the executor streams rows directly from them.\n *\n * Browser (`cacheStore` set): write the (already-compact INT4) packed/scales/\n * zeros to CacheStorage and return EMPTY heap arrays plus a `cache` descriptor.\n * This keeps the ~1.17 GB table out of the JS heap during the load/upload peak;\n * the executor lazily materializes it from cache after the GPU upload has\n * completed (past the memory high-water mark). See Executor.setPleSource.\n */\nasync function buildPleSource(\n packed: Uint32Array,\n scales: Float32Array,\n zeros: Float32Array,\n width: number,\n groupSize: number,\n cacheStore: CacheWeightStore | null,\n): Promise<PleSource> {\n if (!cacheStore) {\n return { packed, scales, zeros, width, groupSize, targetTensor: \"ple_embed_out\" };\n }\n const cache = await caches.open(PLE_CACHE_NAME);\n const packedKey = \"ple:packed\";\n const scalesKey = \"ple:scales\";\n const zerosKey = \"ple:zeros\";\n const copy = (a: ArrayBufferView): ArrayBuffer =>\n a.buffer.slice(a.byteOffset, a.byteOffset + a.byteLength) as ArrayBuffer;\n await cache.put(new Request(packedKey), new Response(copy(packed)));\n await cache.put(new Request(scalesKey), new Response(copy(scales)));\n await cache.put(new Request(zerosKey), new Response(copy(zeros)));\n return {\n // Empty heap arrays — the bytes live in cache now.\n packed: new Uint32Array(0),\n scales: new Float32Array(0),\n zeros: new Float32Array(0),\n width,\n groupSize,\n targetTensor: \"ple_embed_out\",\n cache: {\n cacheName: PLE_CACHE_NAME,\n packedKey,\n scalesKey,\n zerosKey,\n packedLen: packed.length,\n },\n };\n}\n\n/**\n * Load a model from HuggingFace Hub.\n *\n * 1. Fetch config.json -> determine architecture -> generate IR graph\n * 2. Fetch tokenizer.json + tokenizer_config.json -> build tokenizer\n * 3. Download safetensors -> parse headers -> extract weight data\n * 4. Map HF tensor keys -> canonical names\n */\nexport async function loadModel(options: LoadModelOptions): Promise<LoadedModel> {\n const { repo, onProgress, hfToken, revision = \"main\", cacheDir, dtype } = options;\n\n // Initialize Node.js fs for caching (no-op in browser)\n await initFs();\n\n // Resolve cache directory: use provided path or default to ~/.cache/gerbil/<repo>\n let resolvedCacheDir = cacheDir;\n if (!resolvedCacheDir && _fs && _path) {\n const home = process.env.HOME || process.env.USERPROFILE || \"\";\n if (home) {\n resolvedCacheDir = _path.join(home, \".cache\", \"gerbil\", repo.replace(/\\//g, \"_\"), revision);\n }\n }\n\n const baseURL = resolveHFBaseURL(repo, revision);\n\n // -- Step 1: Fetch config.json --\n onProgress?.(0, 100, \"Fetching model config...\");\n const rawConfig = await fetchJSON(baseURL, \"config.json\", hfToken, resolvedCacheDir);\n\n const architectureName = rawConfig.architectures?.[0];\n if (!architectureName) {\n throw new Error(\"config.json missing 'architectures' field\");\n }\n\n // Detect pre-quantized formats — force q4 graph when loading quantized models.\n // MLX-lm writes the quant block under EITHER \"quantization_config\" (newer\n // converts, often with mode:\"affine\") OR \"quantization\" (standard mlx-lm,\n // typically just {bits, group_size} with no mode field). Read whichever exists.\n const quantConfig = (rawConfig.quantization_config ?? rawConfig.quantization) as\n | Record<string, unknown>\n | undefined;\n const isGPTQ = quantConfig?.quant_method === \"gptq\";\n\n // MLX 4-bit detection. Two shapes both indicate a standard affine MLX convert:\n // 1. {bits:4, mode:\"affine\", ...} (newer converts)\n // 2. {bits:4, group_size:N} (no mode field) (standard mlx-lm)\n // CAUTION: DWQ converts are config-INDISTINGUISHABLE from (2) — both write only\n // {bits:4, group_size}. The repack treats the weights as affine MLX, which\n // silently produces garbage for DWQ. So a no-mode MLX config is only accepted\n // for repos we've verified are standard (non-DWQ) affine converts. A verified\n // load must still be sanity-checked at runtime (cosine reference) — see the\n // embedding validation scripts.\n const quantMode = quantConfig?.mode as string | undefined;\n const hasMlxShape =\n !isGPTQ &&\n quantConfig?.bits === 4 &&\n (quantMode === \"affine\" ||\n (quantMode === undefined && typeof quantConfig?.group_size === \"number\"));\n\n // Allowlist of repos verified to be standard (non-DWQ) MLX-4bit converts.\n // Match is substring-insensitive so revision-pinned / mirrored repo strings\n // still resolve. DWQ repos (…-4bit-DWQ) must NOT appear here.\n const VERIFIED_MLX_REPOS = [\"mlx-community/embeddinggemma-300m-4bit\"];\n const repoLower = repo.toLowerCase();\n const isDWQ = repoLower.includes(\"dwq\");\n const isVerifiedMlxRepo =\n !isDWQ && VERIFIED_MLX_REPOS.some((r) => repoLower.includes(r.toLowerCase()));\n\n // Accept MLX when the config explicitly says affine, OR when the no-mode shape\n // is present AND the repo is on the verified allowlist (DWQ-trap guard).\n const isMLX = hasMlxShape && (quantMode === \"affine\" || isVerifiedMlxRepo);\n // Resolve \"auto\": on mobile (iOS/Android) prefer on-the-fly q4 so large f16\n // checkpoints (e.g. LFM2.5-350M ~676 MB f16 → ~190 MB q4) fit in device memory;\n // on desktop keep the repo's native precision. Already-quantized repos (MLX/\n // GPTQ) are always q4.\n const isMobileUA =\n typeof navigator !== \"undefined\" && /iPhone|iPad|iPod|Android/i.test(navigator.userAgent);\n const requestedDtype: GraphDType | undefined =\n dtype === \"auto\" ? (isMobileUA ? \"q4\" : undefined) : dtype;\n const resolvedDtype: GraphDType | undefined = isGPTQ || isMLX ? \"q4\" : requestedDtype;\n\n // MLX uses a different group_size (typically 64 vs Gerbil default 128)\n const quantGroupSize = isMLX ? (quantConfig?.group_size as number) : undefined;\n\n if (isGPTQ) {\n onProgress?.(2, 100, \"Detected GPTQ model — will repack to native INT4...\");\n } else if (isMLX) {\n onProgress?.(\n 2,\n 100,\n `Detected MLX 4-bit model (group_size=${quantGroupSize}) — will repack...`,\n );\n }\n\n // Select key mapper: Qwen3.5 conditional generation models have weights\n // under \"model.language_model.\" prefix instead of the standard \"model.\" prefix.\n // Vision is wanted when the caller requested the multimodal graph variant\n // (enableVision) or explicitly asked for the tower (loadVisionTower — used by\n // the direct-load vision validation scripts). Gate the ViT key-mapping on this\n // so text-only loads stay lean (the ~201MB tower is excluded from download).\n const wantVision = Boolean(options.multimodal || options.loadVisionTower);\n const keyMapper = options.keyMapper ?? createKeyMapperForArch(architectureName, wantVision);\n\n // Generate the computation graph (with INT4 ops if dtype is \"q4\" or GPTQ/MLX detected)\n const graph = generateGraph(\n architectureName,\n rawConfig,\n resolvedDtype,\n quantGroupSize,\n options.kvDtype,\n options.embedding,\n options.multimodal,\n );\n\n // -- Step 2: Fetch tokenizer --\n onProgress?.(5, 100, \"Fetching tokenizer...\");\n const [tokenizerJSON, tokenizerConfigJSON, chatTemplateJinja] = await Promise.all([\n fetchJSON(baseURL, \"tokenizer.json\", hfToken, resolvedCacheDir),\n fetchJSON(baseURL, \"tokenizer_config.json\", hfToken, resolvedCacheDir).catch(() => null),\n // Some models (e.g. LFM2) ship the chat template as a sidecar `.jinja` file\n // instead of inline in tokenizer_config.json. Fetch it as a fallback signal.\n fetchText(baseURL, \"chat_template.jinja\", hfToken, resolvedCacheDir).catch(() => null),\n ]);\n\n // Prefer the inline template; otherwise fall back to the sidecar .jinja.\n const configWithTemplate =\n tokenizerConfigJSON && !tokenizerConfigJSON.chat_template && chatTemplateJinja\n ? { ...tokenizerConfigJSON, chat_template: chatTemplateJinja }\n : (tokenizerConfigJSON ?? (chatTemplateJinja ? { chat_template: chatTemplateJinja } : null));\n\n const tokenizer = Tokenizer.fromJSON(tokenizerJSON, configWithTemplate);\n\n // -- Step 3: Download safetensors --\n onProgress?.(10, 100, \"Discovering weight files...\");\n const safetensorsFiles = await discoverSafetensorsFiles(baseURL, hfToken, resolvedCacheDir);\n\n // Cache-backed streaming store keeps the browser heap bounded (tensor bytes\n // live in CacheStorage; the heap holds only descriptors) so very large models\n // load on mobile without OOM. It is the DEFAULT on mobile — the heap-backed\n // path keeps every tensor resident in the JS heap on top of the GPU buffers\n // (and expands f16/bf16 -> f32 in-heap), doubling peak memory and OOM-crashing\n // iOS WebKit right after the safetensors header read. Desktop/Node keep the\n // proven heap path. Opt in elsewhere with `?stream=1` /\n // `globalThis.GERBIL_STREAM_WEIGHTS = true`; force the heap path anywhere with\n // `globalThis.GERBIL_STREAM_WEIGHTS = false`.\n const streamFlag = (globalThis as { GERBIL_STREAM_WEIGHTS?: boolean }).GERBIL_STREAM_WEIGHTS;\n const streamOptIn =\n streamFlag === true ||\n (typeof location !== \"undefined\" &&\n typeof URLSearchParams !== \"undefined\" &&\n new URLSearchParams(location.search).has(\"stream\"));\n const useCacheBacked =\n streamFlag !== false && (streamOptIn || isMobileUA) && cacheStorageAvailable();\n const cacheStore = useCacheBacked ? new CacheWeightStore() : null;\n const store: MutableWeightStore = cacheStore ?? new HeapWeightStore();\n\n let filesLoaded = 0;\n const totalFiles = safetensorsFiles.length;\n\n for (const filename of safetensorsFiles) {\n const fileUrl = `${baseURL}/${filename}`;\n\n // Check cache first — if cached, use the old full-buffer path\n let cachedBuffer: ArrayBuffer | null = null;\n if (resolvedCacheDir) {\n const cached = readCachedFile(resolvedCacheDir, filename);\n if (cached) {\n cachedBuffer = cached.buffer.slice(\n cached.byteOffset,\n cached.byteOffset + cached.byteLength,\n ) as ArrayBuffer;\n }\n }\n\n if (cachedBuffer) {\n // Cached (Node disk cache) — extract all needed tensors from full buffer.\n const file = parseSafetensorsHeader(cachedBuffer);\n for (const entry of file.entries) {\n const canonicalName = keyMapper(entry.name);\n if (!canonicalName) continue;\n let data: ArrayBufferView = getTensorData(cachedBuffer, file, entry);\n if (entry.dtype === \"BF16\") {\n data = bf16ToF32(new Uint8Array(data.buffer, data.byteOffset, data.byteLength));\n }\n if (entry.dtype === \"F16\") {\n data = f16ToF32(data);\n }\n await store.set(canonicalName, { data, shape: entry.shape });\n }\n } else {\n // Not cached — use selective Range-request downloading\n const fileWeight = 85 / totalFiles;\n const base = 10 + filesLoaded * fileWeight;\n\n // Step 1: Fetch header only. This + the index probe above can take a few\n // seconds with no bytes yet — without this emit the bar looks frozen at 10%\n // (\"stuck at discovering weight files\") until the first chunk lands.\n onProgress?.(base, 100, `Reading ${filename} header (${filesLoaded + 1}/${totalFiles})...`);\n const file = await fetchSafetensorsHeader(fileUrl, hfToken);\n\n // Step 2: Filter to needed entries\n const neededEntries = file.entries.filter((e) => keyMapper(e.name) !== null);\n const skippedBytes = file.entries\n .filter((e) => keyMapper(e.name) === null)\n .reduce((sum, e) => sum + e.dataLength, 0);\n const neededBytes = neededEntries.reduce((sum, e) => sum + e.dataLength, 0);\n\n const totalMB = (neededBytes / 1048576).toFixed(0);\n if (skippedBytes > 0) {\n const savedMB = (skippedBytes / 1048576).toFixed(0);\n onProgress?.(\n base,\n 100,\n `Selective download: ${totalMB} MB needed, skipping ${savedMB} MB (vision/MTP)`,\n );\n }\n // Show the size up front so the first (latency-heavy) chunk doesn't read as a freeze.\n onProgress?.(base, 100, `Downloading ${filename} (0/${totalMB} MB)`);\n\n // Step 3: Download needed tensors via Range requests\n let lastReportedPct = -1;\n\n const rawTensors = await fetchSelectiveTensors(\n fileUrl,\n file,\n neededEntries,\n hfToken,\n (loaded, total) => {\n const filePct = total > 0 ? (loaded / total) * fileWeight : 0;\n const overallPct = Math.round(base + filePct);\n if (overallPct <= lastReportedPct) return;\n lastReportedPct = overallPct;\n const sizeMB =\n total > 0\n ? ` (${(loaded / 1048576).toFixed(0)}/${(total / 1048576).toFixed(0)} MB)`\n : \"\";\n onProgress?.(base + filePct, 100, `Downloading ${filename}${sizeMB}`);\n },\n // Browser: register tensors as cache-backed descriptors under their\n // canonical names — no bytes retained in heap. Node: omitted, so the\n // returned Map carries the bytes (handled below).\n cacheStore ? { store: cacheStore, keyMapper } : undefined,\n );\n\n // Step 4 (Node / non-cache only): map to canonical names with dtype\n // conversion. In cache-backed mode `rawTensors` is empty — tensors were\n // already registered into the store with the canonical name + source dtype,\n // and `store.get()` applies BF16/F16→F32 on demand.\n for (const [hfName, { data: rawData, shape }] of rawTensors) {\n const canonicalName = keyMapper(hfName);\n if (!canonicalName) continue;\n\n let data: ArrayBufferView = rawData;\n // Find the entry to check dtype\n const entry = neededEntries.find((e) => e.name === hfName);\n if (entry) {\n if (entry.dtype === \"BF16\") {\n data = bf16ToF32(new Uint8Array(data.buffer, data.byteOffset, data.byteLength));\n }\n if (entry.dtype === \"F16\") {\n data = f16ToF32(data);\n }\n }\n\n await store.set(canonicalName, { data, shape });\n }\n }\n\n filesLoaded++;\n }\n\n onProgress?.(95, 100, \"Weights loaded.\");\n\n // -- Split fused Q projection for Qwen3.5 full attention layers --\n // HF stores q_proj.weight as [num_heads * 2 * head_dim, hidden_size].\n // The layout is per-head interleaved: for each head h, rows\n // [h*2*head_dim .. h*2*head_dim + head_dim) are Q weights, and\n // [h*2*head_dim + head_dim .. (h+1)*2*head_dim) are gate weights.\n // We deinterleave into q_proj.weight [num_heads*head_dim, hidden_size]\n // and attn_gate.weight [num_heads*head_dim, hidden_size].\n if (architectureName === \"Qwen3_5ForConditionalGeneration\") {\n const textCfg = (rawConfig.text_config as Record<string, unknown>) ?? rawConfig;\n const numLayers = textCfg.num_hidden_layers as number;\n const layerTypesArr = (textCfg.layer_types as string[]) ?? [];\n const fullAttnInterval = (textCfg.full_attention_interval as number) ?? 4;\n const numHeads = textCfg.num_attention_heads as number; // 8\n const headDim =\n (textCfg.head_dim as number) ?? Math.floor((textCfg.hidden_size as number) / numHeads);\n\n for (let i = 0; i < numLayers; i++) {\n // Check if this is a full attention layer\n let isFullAttn: boolean;\n if (layerTypesArr.length > i) {\n isFullAttn = layerTypesArr[i] === \"full_attention\";\n } else {\n isFullAttn = i % fullAttnInterval === fullAttnInterval - 1;\n }\n if (!isFullAttn) continue;\n\n const qProjKey = `layers.${i}.self_attn.q_proj.weight`;\n const attnGateKey = `layers.${i}.self_attn.attn_gate.weight`;\n const fusedWeight = await store.get(qProjKey);\n\n if (fusedWeight && !store.has(attnGateKey) && !isMLX) {\n const fusedData = fusedWeight.data;\n const [fusedRows, cols] = fusedWeight.shape;\n const halfRows = fusedRows / 2; // num_heads * head_dim\n\n const qData = new Float32Array(halfRows * cols);\n const gateData = new Float32Array(halfRows * cols);\n\n const src =\n fusedData instanceof Float32Array\n ? fusedData\n : new Float32Array(fusedData.buffer, fusedData.byteOffset, fusedRows * cols);\n\n // Deinterleave per-head: each head has a block of 2*head_dim rows\n // First head_dim rows = Q, second head_dim rows = gate\n const headBlock = 2 * headDim; // rows per head in fused weight\n for (let h = 0; h < numHeads; h++) {\n const srcBase = h * headBlock * cols;\n const dstBase = h * headDim * cols;\n qData.set(src.subarray(srcBase, srcBase + headDim * cols), dstBase);\n gateData.set(src.subarray(srcBase + headDim * cols, srcBase + headBlock * cols), dstBase);\n }\n\n await store.set(qProjKey, { data: qData, shape: [halfRows, cols] });\n await store.set(attnGateKey, { data: gateData, shape: [halfRows, cols] });\n }\n\n // GPTQ: split fused q_proj GPTQ tensors into q_proj + attn_gate\n // GPTQ q_proj has N=4096 (fused), graph expects N=2048 each.\n // The deinterleave is per-head: each head's block of 2*head_dim output columns\n // has head_dim Q columns followed by head_dim gate columns.\n const qProjQweight = `layers.${i}.self_attn.q_proj.qweight`;\n const attnGateQweight = `layers.${i}.self_attn.attn_gate.qweight`;\n if (store.has(qProjQweight) && !store.has(attnGateQweight)) {\n const fusedN = numHeads * 2 * headDim; // 4096\n const halfN = numHeads * headDim; // 2048\n const K_val = textCfg.hidden_size as number; // 1024\n\n // Split qweight [K/8, fusedN] → two [K/8, halfN] with per-head deinterleave\n const qw = (await store.get(qProjQweight))!;\n const qwSrc =\n qw.data instanceof Int32Array\n ? qw.data\n : new Int32Array(qw.data.buffer, qw.data.byteOffset, qw.data.byteLength / 4);\n const kDiv8 = K_val >>> 3;\n const qwQ = new Int32Array(kDiv8 * halfN);\n const qwG = new Int32Array(kDiv8 * halfN);\n const headBlock2 = 2 * headDim; // output columns per head in fused tensor\n for (let row = 0; row < kDiv8; row++) {\n for (let h = 0; h < numHeads; h++) {\n const srcOff = row * fusedN + h * headBlock2;\n const dstOff = row * halfN + h * headDim;\n for (let d = 0; d < headDim; d++) {\n qwQ[dstOff + d] = qwSrc[srcOff + d];\n qwG[dstOff + d] = qwSrc[srcOff + headDim + d];\n }\n }\n }\n await store.set(qProjQweight, { data: qwQ, shape: [kDiv8, halfN] });\n await store.set(attnGateQweight, { data: qwG, shape: [kDiv8, halfN] });\n\n // Split scales [groups, fusedN] → two [groups, halfN]\n const qProjScales = `layers.${i}.self_attn.q_proj.scales`;\n const attnGateScales = `layers.${i}.self_attn.attn_gate.scales`;\n const sc = (await store.get(qProjScales))!;\n const scSrc =\n sc.data instanceof Float32Array\n ? sc.data\n : new Float32Array(sc.data.buffer, sc.data.byteOffset, sc.data.byteLength / 4);\n const groups = sc.shape[0];\n const scQ = new Float32Array(groups * halfN);\n const scG = new Float32Array(groups * halfN);\n for (let g = 0; g < groups; g++) {\n for (let h = 0; h < numHeads; h++) {\n const srcOff = g * fusedN + h * headBlock2;\n const dstOff = g * halfN + h * headDim;\n for (let d = 0; d < headDim; d++) {\n scQ[dstOff + d] = scSrc[srcOff + d];\n scG[dstOff + d] = scSrc[srcOff + headDim + d];\n }\n }\n }\n await store.set(qProjScales, { data: scQ, shape: [groups, halfN] });\n await store.set(attnGateScales, { data: scG, shape: [groups, halfN] });\n\n // Split qzeros [groups, fusedN/8] → two [groups, halfN/8]\n // qzeros packs 8 output columns per int32. Since headDim (256) is divisible by 8,\n // the deinterleave works on int32 words directly: headDim/8 words per head.\n const qProjQzeros = `layers.${i}.self_attn.q_proj.qzeros`;\n const attnGateQzeros = `layers.${i}.self_attn.attn_gate.qzeros`;\n const qz = (await store.get(qProjQzeros))!;\n const qzSrc =\n qz.data instanceof Int32Array\n ? qz.data\n : new Int32Array(qz.data.buffer, qz.data.byteOffset, qz.data.byteLength / 4);\n const fusedNDiv8 = fusedN >>> 3;\n const halfNDiv8 = halfN >>> 3;\n const headBlockDiv8 = headBlock2 >>> 3;\n const headDimDiv8 = headDim >>> 3;\n const qzQ = new Int32Array(groups * halfNDiv8);\n const qzG = new Int32Array(groups * halfNDiv8);\n for (let g = 0; g < groups; g++) {\n for (let h = 0; h < numHeads; h++) {\n const srcOff = g * fusedNDiv8 + h * headBlockDiv8;\n const dstOff = g * halfNDiv8 + h * headDimDiv8;\n for (let d = 0; d < headDimDiv8; d++) {\n qzQ[dstOff + d] = qzSrc[srcOff + d];\n qzG[dstOff + d] = qzSrc[srcOff + headDimDiv8 + d];\n }\n }\n }\n await store.set(qProjQzeros, { data: qzQ, shape: [groups, halfNDiv8] });\n await store.set(attnGateQzeros, { data: qzG, shape: [groups, halfNDiv8] });\n\n // Clean up g_idx tensors if present (we assume sequential grouping)\n await store.delete(`layers.${i}.self_attn.q_proj.g_idx`);\n }\n\n // MLX: split fused q_proj MLX tensors into q_proj + attn_gate\n // MLX q_proj.weight is packed [fusedN, K/8], scales/biases are [fusedN, K/gs]\n // The split is along the N (output/row) dimension — same deinterleave pattern.\n const mlxQProjWeight = `layers.${i}.self_attn.q_proj.weight`;\n const mlxAttnGateWeight = `layers.${i}.self_attn.attn_gate.weight`;\n if (isMLX && store.has(mlxQProjWeight) && !store.has(mlxAttnGateWeight)) {\n const K_val = textCfg.hidden_size as number; // 1024\n const fusedN = numHeads * 2 * headDim; // 4096\n const halfN = numHeads * headDim; // 2048\n const headBlock2 = 2 * headDim;\n const mlxGS = (quantConfig?.group_size as number) ?? 64;\n\n // Deinterleave [fusedN, cols] → two [halfN, cols] with per-head split\n const deinterleaveMLX = <T extends Float32Array | Uint32Array>(\n src: T,\n fusedRows: number,\n cols: number,\n Ctor: { new (len: number): T },\n ): [T, T] => {\n const halfRows = fusedRows / 2;\n const qArr = new Ctor(halfRows * cols);\n const gArr = new Ctor(halfRows * cols);\n for (let h = 0; h < numHeads; h++) {\n const srcBase = h * headBlock2 * cols;\n const dstBase = h * headDim * cols;\n for (let r = 0; r < headDim; r++) {\n const s0 = srcBase + r * cols;\n const s1 = srcBase + (headDim + r) * cols;\n const d0 = dstBase + r * cols;\n for (let c = 0; c < cols; c++) {\n qArr[d0 + c] = src[s0 + c];\n gArr[d0 + c] = src[s1 + c];\n }\n }\n }\n return [qArr, gArr];\n };\n\n // Split packed weight [fusedN, K/8]\n const wt = (await store.get(mlxQProjWeight))!;\n const wtSrc =\n wt.data instanceof Uint32Array\n ? wt.data\n : new Uint32Array(wt.data.buffer, wt.data.byteOffset, wt.data.byteLength / 4);\n const colsW = K_val >>> 3; // K/8\n const [wtQ, wtG] = deinterleaveMLX(wtSrc, fusedN, colsW, Uint32Array);\n await store.set(mlxQProjWeight, { data: wtQ, shape: [halfN, colsW] });\n await store.set(mlxAttnGateWeight, { data: wtG, shape: [halfN, colsW] });\n\n // Split scales [fusedN, K/gs]\n const mlxQProjScales = `layers.${i}.self_attn.q_proj.scales`;\n const mlxAttnGateScales = `layers.${i}.self_attn.attn_gate.scales`;\n const sc = (await store.get(mlxQProjScales))!;\n const scSrc =\n sc.data instanceof Float32Array\n ? sc.data\n : new Float32Array(sc.data.buffer, sc.data.byteOffset, sc.data.byteLength / 4);\n const colsS = Math.ceil(K_val / mlxGS);\n const [scQ, scG] = deinterleaveMLX(scSrc, fusedN, colsS, Float32Array);\n await store.set(mlxQProjScales, { data: scQ, shape: [halfN, colsS] });\n await store.set(mlxAttnGateScales, { data: scG, shape: [halfN, colsS] });\n\n // Split biases [fusedN, K/gs]\n const mlxQProjBiases = `layers.${i}.self_attn.q_proj.biases`;\n const mlxAttnGateBiases = `layers.${i}.self_attn.attn_gate.biases`;\n const bi = (await store.get(mlxQProjBiases))!;\n const biSrc =\n bi.data instanceof Float32Array\n ? bi.data\n : new Float32Array(bi.data.buffer, bi.data.byteOffset, bi.data.byteLength / 4);\n const [biQ, biG] = deinterleaveMLX(biSrc, fusedN, colsS, Float32Array);\n await store.set(mlxQProjBiases, { data: biQ, shape: [halfN, colsS] });\n await store.set(mlxAttnGateBiases, { data: biG, shape: [halfN, colsS] });\n }\n }\n }\n\n // -- Apply (1 + weight) offset for ALL Qwen3.5 RMSNorm weights --\n // Qwen3_5RMSNorm initializes weight=0 and applies (1 + weight) * normalized.\n // ALL norms in the model use this class: input_layernorm, post_attention_layernorm,\n // final_norm, q_norm, k_norm, and linear_attn.norm. We bake +1 into weights so\n // the standard RMSNorm kernel (which does weight * normalized) produces correct results.\n //\n // EXCEPTION: MLX Community models already absorb the +1 during conversion —\n // their RMSNorm uses `weight * normalized` (no +1), so the stored weights\n // are already `1 + original_weight`. Applying +1 again would double-offset.\n if (architectureName === \"Qwen3_5ForConditionalGeneration\" && !isMLX) {\n const textCfg = (rawConfig.text_config as Record<string, unknown>) ?? rawConfig;\n const numLayers = textCfg.num_hidden_layers as number;\n const layerTypesArr = (textCfg.layer_types as string[]) ?? [];\n const fullAttnInterval = (textCfg.full_attention_interval as number) ?? 4;\n\n // Collect all norm weight keys\n const normKeys: string[] = [];\n for (let i = 0; i < numLayers; i++) {\n // All layers have input/post-attention layernorms\n normKeys.push(`layers.${i}.input_layernorm.weight`);\n normKeys.push(`layers.${i}.post_attention_layernorm.weight`);\n\n let isFullAttn: boolean;\n if (layerTypesArr.length > i) {\n isFullAttn = layerTypesArr[i] === \"full_attention\";\n } else {\n isFullAttn = i % fullAttnInterval === fullAttnInterval - 1;\n }\n\n if (isFullAttn) {\n normKeys.push(`layers.${i}.self_attn.q_norm.weight`);\n normKeys.push(`layers.${i}.self_attn.k_norm.weight`);\n }\n // NOTE: linear_attn.norm.weight uses Qwen3_5RMSNormGated which\n // initializes weight=1 (not 0), so it does NOT need the +1 offset.\n }\n // Final norm\n normKeys.push(\"norm.weight\");\n\n for (const normKey of normKeys) {\n const entry = await store.get(normKey);\n if (entry) {\n const data =\n entry.data instanceof Float32Array\n ? entry.data\n : new Float32Array(entry.data.buffer, entry.data.byteOffset, entry.data.byteLength / 4);\n const adjusted = new Float32Array(data.length);\n for (let j = 0; j < data.length; j++) {\n adjusted[j] = 1.0 + data[j];\n }\n await store.set(normKey, { data: adjusted, shape: entry.shape });\n }\n }\n }\n\n // -- Apply (1 + weight) offset for ALL Gemma3 RMSNorm weights --\n // Gemma3RMSNorm computes `(1.0 + weight) * normalized` (mlx-lm:\n // mx.fast.rms_norm(x, 1.0 + self.weight, eps)). The stored weight is the raw\n // trained value centered near 0; we bake +1 so the standard RMSNorm kernel\n // (weight * normalized) is correct. Unlike Qwen3.5, the MLX convert does NOT\n // absorb the +1 for Gemma, so this runs for MLX too. Every per-layer norm uses\n // this class: input_layernorm, post_attention_layernorm, pre_feedforward_\n // layernorm, post_feedforward_layernorm, q_norm, k_norm, plus the final norm.\n if (architectureName === \"Gemma3TextModel\" || architectureName === \"Gemma3Model\") {\n const gemmaNumLayers = rawConfig.num_hidden_layers as number;\n const gemmaNormKeys: string[] = [];\n for (let i = 0; i < gemmaNumLayers; i++) {\n gemmaNormKeys.push(`layers.${i}.input_layernorm.weight`);\n gemmaNormKeys.push(`layers.${i}.post_attention_layernorm.weight`);\n gemmaNormKeys.push(`layers.${i}.pre_feedforward_layernorm.weight`);\n gemmaNormKeys.push(`layers.${i}.post_feedforward_layernorm.weight`);\n gemmaNormKeys.push(`layers.${i}.self_attn.q_norm.weight`);\n gemmaNormKeys.push(`layers.${i}.self_attn.k_norm.weight`);\n }\n gemmaNormKeys.push(\"norm.weight\");\n\n for (const normKey of gemmaNormKeys) {\n const entry = await store.get(normKey);\n if (entry) {\n const data =\n entry.data instanceof Float32Array\n ? entry.data\n : new Float32Array(entry.data.buffer, entry.data.byteOffset, entry.data.byteLength / 4);\n const adjusted = new Float32Array(data.length);\n for (let j = 0; j < data.length; j++) {\n adjusted[j] = 1.0 + data[j];\n }\n await store.set(normKey, { data: adjusted, shape: entry.shape });\n }\n }\n }\n\n // NOTE on Gemma 4 RMSNorm: unlike Gemma 3 (weights centered near 0, runtime\n // `(1+weight)`), the released Gemma 4 / Gemma3n checkpoints store the FULL norm\n // gain directly (raw input_layernorm values are ~7–68, rms ~12). So NO +1 bake\n // is applied here — the standard `weight * normalized` kernel is already correct,\n // and the large residual-stream magnitudes are expected for this model.\n\n // -- Gemma 4 per-layer output scalar (`layer_scalar`) --\n // Gemma4TextDecoderLayer ends with `hidden_states *= self.layer_scalar` — a single\n // learned scalar per layer (shape [1], values ~0.018–0.5, NOT ones). The graph\n // emits a placeholder Scale node (id `layer{i}_layer_scalar`); we read the scalar\n // value here and patch the node's `scale` attribute, then drop the weight (it is a\n // baked constant, not a GPU tensor). Without this the residual stream is unscaled\n // and generation is incoherent.\n if (architectureName === \"Gemma4ForConditionalGeneration\") {\n for (const node of graph.nodes) {\n const key = node.attributes?.layer_scalar_key as string | undefined;\n if (!key) continue;\n const entry = await store.get(key);\n if (!entry) continue;\n const data =\n entry.data instanceof Float32Array\n ? entry.data\n : new Float32Array(entry.data.buffer, entry.data.byteOffset, entry.data.byteLength / 4);\n node.attributes.scale = data[0];\n await store.delete(key);\n }\n }\n\n // -- Handle tied embeddings --\n // When safetensorsKey differs from the tensor name (e.g. lm_head.weight\n // pointing to embed_tokens.weight for tied embeddings), copy the data.\n for (const [name, desc] of Object.entries(graph.tensors)) {\n if (\n desc.storage === \"constant\" &&\n desc.safetensorsKey &&\n desc.safetensorsKey !== name &&\n !store.has(name)\n ) {\n const sourceData = await store.get(desc.safetensorsKey);\n if (sourceData) {\n await store.set(name, sourceData);\n }\n }\n }\n\n // -- Repack GPTQ weights into Gerbil INT4 format --\n if (isGPTQ) {\n onProgress?.(96, 100, \"Repacking GPTQ weights...\");\n const gptqGroupSize = (quantConfig?.group_size as number) ?? 128;\n\n for (const node of graph.nodes) {\n if (node.opType !== \"MatMulInt4\") continue;\n\n const K = node.attributes.K as number;\n const N = node.attributes.N as number;\n\n const qTensorName = node.inputs[1]; // e.g. layers.0.self_attn.q_proj.weight.q\n const scalesTensorName = node.inputs[2]; // e.g. layers.0.self_attn.q_proj.weight.scales\n const zerosTensorName = node.inputs[3]; // e.g. layers.0.self_attn.q_proj.weight.zeros\n\n // Derive GPTQ tensor names from graph names:\n // Graph: \"layers.0.self_attn.q_proj.weight.q\" → strip \".q\" → strip \".weight\" → base\n const sourceName = qTensorName.slice(0, -2); // \"layers.0.self_attn.q_proj.weight\"\n const gptqBase = sourceName.slice(0, -7); // \"layers.0.self_attn.q_proj\"\n\n const gptqQweight = await store.get(`${gptqBase}.qweight`);\n const gptqScales = await store.get(`${gptqBase}.scales`);\n const gptqQzeros = await store.get(`${gptqBase}.qzeros`);\n\n // Skip layers not quantized in GPTQ — they'll be handled by on-the-fly quantization below\n if (!gptqQweight || !gptqScales || !gptqQzeros) continue;\n\n // Ensure correct typed arrays for the adapter\n const qweightData =\n gptqQweight.data instanceof Int32Array\n ? gptqQweight.data\n : new Int32Array(\n gptqQweight.data.buffer,\n gptqQweight.data.byteOffset,\n gptqQweight.data.byteLength / 4,\n );\n const scalesData =\n gptqScales.data instanceof Float32Array\n ? gptqScales.data\n : new Float32Array(\n gptqScales.data.buffer,\n gptqScales.data.byteOffset,\n gptqScales.data.byteLength / 4,\n );\n const qzerosData =\n gptqQzeros.data instanceof Int32Array\n ? gptqQzeros.data\n : new Int32Array(\n gptqQzeros.data.buffer,\n gptqQzeros.data.byteOffset,\n gptqQzeros.data.byteLength / 4,\n );\n\n const repacked = repackGPTQ({\n qweight: qweightData,\n scales: scalesData,\n qzeros: qzerosData,\n K,\n N,\n groupSize: gptqGroupSize,\n });\n\n await store.set(qTensorName, { data: repacked.packed, shape: [repacked.packed.length] });\n await store.set(scalesTensorName, { data: repacked.scales, shape: [repacked.scales.length] });\n await store.set(zerosTensorName, { data: repacked.zeros, shape: [repacked.zeros.length] });\n\n // Clean up GPTQ source tensors to free memory\n await store.delete(`${gptqBase}.qweight`);\n await store.delete(`${gptqBase}.scales`);\n await store.delete(`${gptqBase}.qzeros`);\n }\n }\n\n // -- Repack MLX 4-bit weights into Gerbil INT4 format --\n if (isMLX) {\n onProgress?.(96, 100, \"Repacking MLX weights...\");\n const mlxGS = (quantConfig?.group_size as number) ?? 64;\n\n for (const node of graph.nodes) {\n if (node.opType !== \"MatMulInt4\" && node.opType !== \"EmbeddingInt4\") continue;\n\n // Derive N and K for this layer\n let N: number, K: number;\n if (node.opType === \"EmbeddingInt4\") {\n N = node.attributes.vocab_size as number;\n K = node.attributes.hidden_size as number;\n } else {\n N = node.attributes.N as number;\n K = node.attributes.K as number;\n }\n\n const qTensorName = node.inputs[1]; // e.g. layers.0.self_attn.q_proj.weight.q\n const scalesTensorName = node.inputs[2]; // e.g. layers.0.self_attn.q_proj.weight.scales\n const zerosTensorName = node.inputs[3]; // e.g. layers.0.self_attn.q_proj.weight.zeros\n\n // Derive MLX source tensor names:\n // Graph: \"base.weight.q\" → strip \".q\" → \"base.weight\" (MLX packed weight)\n // MLX scales: \"base.scales\", MLX biases: \"base.biases\"\n const sourceName = qTensorName.slice(0, -2); // \"base.weight\"\n const baseName = sourceName.slice(0, -7); // \"base\" (strip \".weight\")\n\n const mlxWeight = await store.get(sourceName); // packed U32\n const mlxScales = await store.get(`${baseName}.scales`); // F32 (already converted from BF16)\n const mlxBiases = await store.get(`${baseName}.biases`); // F32 (already converted from BF16)\n\n if (!mlxWeight || !mlxScales || !mlxBiases) continue;\n\n const weightData =\n mlxWeight.data instanceof Uint32Array\n ? mlxWeight.data\n : new Uint32Array(\n mlxWeight.data.buffer,\n mlxWeight.data.byteOffset,\n mlxWeight.data.byteLength / 4,\n );\n const scalesData =\n mlxScales.data instanceof Float32Array\n ? mlxScales.data\n : new Float32Array(\n mlxScales.data.buffer,\n mlxScales.data.byteOffset,\n mlxScales.data.byteLength / 4,\n );\n const biasesData =\n mlxBiases.data instanceof Float32Array\n ? mlxBiases.data\n : new Float32Array(\n mlxBiases.data.buffer,\n mlxBiases.data.byteOffset,\n mlxBiases.data.byteLength / 4,\n );\n\n const repacked = repackMLX({\n weight: weightData,\n scales: scalesData,\n biases: biasesData,\n N,\n K,\n groupSize: mlxGS,\n });\n\n await store.set(qTensorName, { data: repacked.packed, shape: [repacked.packed.length] });\n await store.set(scalesTensorName, { data: repacked.scales, shape: [repacked.scales.length] });\n await store.set(zerosTensorName, { data: repacked.zeros, shape: [repacked.zeros.length] });\n\n // Clean up MLX source tensors to free memory\n await store.delete(sourceName);\n await store.delete(`${baseName}.scales`);\n await store.delete(`${baseName}.biases`);\n }\n }\n\n // -- Quantize weights for INT4 if requested (or for non-GPTQ/MLX layers) --\n if (resolvedDtype === \"q4\") {\n onProgress?.(96, 100, \"Quantizing weights to INT4...\");\n for (const node of graph.nodes) {\n if (node.opType !== \"MatMulInt4\" && node.opType !== \"EmbeddingInt4\") continue;\n\n const groupSz = (node.attributes.group_size as number) ?? DEFAULT_GROUP_SIZE;\n\n const qTensorName = node.inputs[1]; // weight.q\n const scalesTensorName = node.inputs[2]; // weight.scales\n const zerosTensorName = node.inputs[3]; // weight.zeros\n\n // Skip nodes already handled by GPTQ or MLX repacking\n if (store.has(qTensorName)) continue;\n\n // Derive source weight name by stripping \".q\" suffix\n const sourceName = qTensorName.slice(0, -2);\n let sourceWeight = await store.get(sourceName);\n // Tied embeddings fallback: lm_head.weight may not exist in safetensors\n // when tie_word_embeddings=true — use embed_tokens.weight instead.\n if (!sourceWeight && sourceName === \"lm_head.weight\") {\n sourceWeight = await store.get(\"embed_tokens.weight\");\n }\n\n if (sourceWeight) {\n const f32Data =\n sourceWeight.data instanceof Float32Array\n ? sourceWeight.data\n : new Float32Array(\n sourceWeight.data.buffer,\n sourceWeight.data.byteOffset,\n sourceWeight.data.byteLength / 4,\n );\n\n const { packed, scales, zeros } = quantizeInt4(f32Data, groupSz);\n\n await store.set(qTensorName, { data: packed, shape: [packed.length] });\n await store.set(scalesTensorName, { data: scales, shape: [scales.length] });\n await store.set(zerosTensorName, { data: zeros, shape: [zeros.length] });\n\n // Free the original F32 weight to save memory\n await store.delete(sourceName);\n }\n }\n }\n\n // -- MLX vision patch_embed axis fixup --\n // The ViT patch_embed conv weight is a 5D tensor whose flattening must match\n // the host patch vector's [C, T, ph, pw] order. The HF BF16 checkpoint stores\n // it channels-first as [out, C, T, ph, pw] → flattens correctly as-is. The\n // mlx-vlm convert stores it channels-LAST as [out, T, ph, pw, C], so the\n // flat [out, 1536] inner order is permuted and the first matmul would be fed\n // mismatched weights (manifests as low cosine, not NaN). Detect the\n // channels-last 5D shape and permute back to [out, C, T, ph, pw] so the\n // flattened weight matches the host patch layout on both checkpoints.\n if (isMLX) {\n const pe = await store.get(CANONICAL_KEYS.visPatchEmbedWeight);\n const s = pe?.shape;\n // channels-last conv weight: [out, T, ph, pw, C] with C === in_channels (3)\n if (pe && s && s.length === 5 && s[4] === 3) {\n const [outC, T, ph, pw, C] = s;\n const src =\n pe.data instanceof Float32Array\n ? pe.data\n : new Float32Array(pe.data.buffer, pe.data.byteOffset, pe.data.byteLength / 4);\n const dst = new Float32Array(src.length);\n // src index (channels-last): ((((o*T + t)*ph + y)*pw + x)*C + c)\n // dst index (channels-first): ((((o*C + c)*T + t)*ph + y)*pw + x)\n for (let o = 0; o < outC; o++) {\n for (let t = 0; t < T; t++) {\n for (let y = 0; y < ph; y++) {\n for (let x = 0; x < pw; x++) {\n for (let c = 0; c < C; c++) {\n const si = (((o * T + t) * ph + y) * pw + x) * C + c;\n const di = (((o * C + c) * T + t) * ph + y) * pw + x;\n dst[di] = src[si];\n }\n }\n }\n }\n }\n // Store flattened [out, C*T*ph*pw] = [out, 1536] in channels-first order.\n await store.set(CANONICAL_KEYS.visPatchEmbedWeight, {\n data: dst,\n shape: [outC, C * T * ph * pw],\n });\n }\n }\n\n // -- Gemma 4: extract the PLE table to CPU-resident storage (NOT GPU) --\n // The per-layer embedding table `embed_tokens_per_layer` is ~1.17GB at 4-bit.\n // We keep it in JS memory and stream per-token rows at inference, so it never\n // becomes a GPU buffer. Repack MLX 4-bit → Gerbil INT4 here; for an unquantized\n // checkpoint, encode the f32 rows with a unit scale (zero=0) so the executor's\n // single dequant path handles both. The PLE EmbeddingInt4 node was intentionally\n // omitted from the graph (gemma4.ts), so the standard repack loop skips this\n // table — it is the loader's job to divert it out of `weights`.\n let pleSource: PleSource | undefined;\n const isGemma4 = String(architectureName).startsWith(\"Gemma4\");\n if (isGemma4) {\n const pleBase = CANONICAL_KEYS.GEMMA4_PLE_EMBED.slice(0, -7); // \"embed_tokens_per_layer\"\n const textCfg = (rawConfig.text_config as Record<string, unknown>) ?? rawConfig;\n const pleDim = (textCfg.hidden_size_per_layer_input as number) ?? 256;\n const numLayers = textCfg.num_hidden_layers as number;\n const pleWidth = numLayers * pleDim; // L*256\n const pleVocab =\n (textCfg.vocab_size_per_layer_input as number) ?? (textCfg.vocab_size as number);\n\n const packedEntry = await store.get(`${pleBase}.weight`);\n const scalesEntry = await store.get(`${pleBase}.scales`);\n const biasesEntry = await store.get(`${pleBase}.biases`);\n\n if (isMLX && packedEntry && scalesEntry && biasesEntry) {\n const mlxGS = (quantConfig?.group_size as number) ?? 64;\n const packedU32 =\n packedEntry.data instanceof Uint32Array\n ? packedEntry.data\n : new Uint32Array(\n packedEntry.data.buffer,\n packedEntry.data.byteOffset,\n packedEntry.data.byteLength / 4,\n );\n const scalesF32 =\n scalesEntry.data instanceof Float32Array\n ? scalesEntry.data\n : new Float32Array(\n scalesEntry.data.buffer,\n scalesEntry.data.byteOffset,\n scalesEntry.data.byteLength / 4,\n );\n const biasesF32 =\n biasesEntry.data instanceof Float32Array\n ? biasesEntry.data\n : new Float32Array(\n biasesEntry.data.buffer,\n biasesEntry.data.byteOffset,\n biasesEntry.data.byteLength / 4,\n );\n const repacked = repackMLX({\n weight: packedU32,\n scales: scalesF32,\n biases: biasesF32,\n N: pleVocab,\n K: pleWidth,\n groupSize: mlxGS,\n });\n pleSource = await buildPleSource(\n repacked.packed,\n repacked.scales,\n repacked.zeros,\n pleWidth,\n mlxGS,\n cacheStore,\n );\n // Divert out of the store so uploadWeights() never sends it to the GPU.\n await store.delete(`${pleBase}.weight`);\n await store.delete(`${pleBase}.scales`);\n await store.delete(`${pleBase}.biases`);\n console.log(\n `[Gerbil] Gemma4 PLE table kept ${cacheStore ? \"cache-resident\" : \"CPU-resident\"}: ` +\n `[${pleVocab}, ${pleWidth}] int4 ` +\n `(~${((repacked.packed.byteLength + repacked.scales.byteLength + repacked.zeros.byteLength) / 1e6).toFixed(0)} MB, 0 MB GPU)`,\n );\n } else if (packedEntry?.data instanceof Float32Array) {\n // Unquantized f32 table: quantize to INT4 for compact storage and a\n // single shared dequant path. (Quantization-sensitive precision concerns\n // only apply when forced GPU-resident; here we still dequantize per-row.)\n const { packed, scales, zeros } = quantizeInt4(packedEntry.data, DEFAULT_GROUP_SIZE);\n pleSource = await buildPleSource(\n packed,\n scales,\n zeros,\n pleWidth,\n DEFAULT_GROUP_SIZE,\n cacheStore,\n );\n await store.delete(`${pleBase}.weight`);\n } else {\n console.warn(\n \"[Gerbil] Gemma4: PLE table not found in weights — per-layer embeddings \" +\n \"will be zero. Check the checkpoint ships embed_tokens_per_layer.\",\n );\n }\n }\n\n // -- Materialize synthetic fill-value constants (e.g. Gemma 4 v_norm ones) --\n // Tensors declared with `fillValue` carry no checkpoint weight; the loader fills\n // them with a constant at the declared shape. Used for parameter-free norms.\n for (const [name, desc] of Object.entries(graph.tensors)) {\n if (desc.storage === \"constant\" && desc.fillValue !== undefined && !store.has(name)) {\n const length = desc.shape.reduce(\n (acc: number, dim) => acc * (typeof dim === \"number\" ? dim : 1),\n 1,\n );\n const data = new Float32Array(length).fill(desc.fillValue);\n await store.set(name, {\n data,\n shape: desc.shape.map((d) => (typeof d === \"number\" ? d : 1)),\n });\n }\n }\n\n // -- Verify required tensors --\n const missingTensors: string[] = [];\n for (const [name, desc] of Object.entries(graph.tensors)) {\n if (desc.storage === \"constant\" && !store.has(name)) {\n missingTensors.push(name);\n }\n }\n\n if (missingTensors.length > 0) {\n console.warn(\n `[Gerbil] Missing ${missingTensors.length} weight tensors:`,\n missingTensors.slice(0, 10),\n );\n }\n\n onProgress?.(100, 100, \"Model loaded.\");\n\n return { graph, tokenizer, weights: store, rawConfig, pleSource };\n}\n\n// ── Moonshine STT loader ───────────────────────────────────────────────────\n//\n// Moonshine is an encoder-decoder ASR model that cannot be expressed as a single\n// causal-LM graph (the standard loadModel path). It needs two graphs (a conv\n// encoder run once, an AR decoder with cross-attention) sharing one set of f32\n// weights. This loader downloads config + tokenizer + the single model.safetensors\n// and returns the canonical-named f32 weights for both graphs. Keys are mapped by\n// stripping the \"model.\" prefix the checkpoint uses (e.g. \"model.encoder.conv1.\n// weight\" → \"encoder.conv1.weight\"), which is exactly the safetensorsKey the\n// Moonshine graph generators reference.\n\nexport interface LoadedMoonshine {\n /** Canonical-named f32 weights (data + shape), shared by encoder + decoder graphs. */\n weights: Map<string, { data: ArrayBufferView; shape: number[] }>;\n /** The (decode-capable) tokenizer. */\n tokenizer: Tokenizer;\n /** Raw config.json. */\n rawConfig: Record<string, unknown>;\n}\n\nexport async function loadMoonshine(options: {\n repo: string;\n revision?: string;\n hfToken?: string;\n cacheDir?: string;\n onProgress?: (loaded: number, total: number, message: string) => void;\n}): Promise<LoadedMoonshine> {\n const revision = options.revision ?? \"main\";\n const baseURL = resolveHFBaseURL(options.repo, revision);\n await initFs();\n\n options.onProgress?.(0, 100, \"Fetching config + tokenizer…\");\n const [rawConfig, tokenizerJSON] = await Promise.all([\n fetchJSON(baseURL, \"config.json\", options.hfToken, options.cacheDir),\n fetchJSON(baseURL, \"tokenizer.json\", options.hfToken, options.cacheDir),\n ]);\n const tokenizerConfig = await fetchJSON(\n baseURL,\n \"tokenizer_config.json\",\n options.hfToken,\n options.cacheDir,\n ).catch(() => null);\n const tokenizer = Tokenizer.fromJSON(tokenizerJSON, tokenizerConfig);\n\n options.onProgress?.(10, 100, \"Downloading weights…\");\n const buf = await fetchBinary(\n baseURL,\n \"model.safetensors\",\n options.hfToken,\n (loaded, total) => options.onProgress?.(10 + (loaded / (total || 1)) * 85, 100, \"weights\"),\n options.cacheDir,\n );\n\n const file = parseSafetensorsHeader(buf);\n const weights = new Map<string, { data: ArrayBufferView; shape: number[] }>();\n for (const entry of file.entries) {\n // Strip the \"model.\" prefix the Moonshine checkpoint uses on every tensor.\n const canonical = entry.name.startsWith(\"model.\") ? entry.name.slice(6) : entry.name;\n let data: ArrayBufferView = getTensorData(buf, file, entry);\n if (entry.dtype === \"BF16\") {\n data = bf16ToF32(new Uint8Array(data.buffer, data.byteOffset, data.byteLength));\n } else if (entry.dtype === \"F16\") {\n data = f16ToF32(data);\n }\n weights.set(canonical, { data, shape: entry.shape });\n }\n\n options.onProgress?.(100, 100, \"Moonshine loaded.\");\n return { weights, tokenizer, rawConfig };\n}\n\n// ════════════════════════════════════════════════════════════════════════════\n// Kani-TTS-2 dual-checkpoint loader (LFM2-350M codec-LM + NVIDIA NeMo NanoCodec).\n//\n// Kani-TTS-2 is a two-stage TTS model that, like Moonshine, cannot be one graph:\n// 1. the codec-LM backbone (LFM2-350M body) lives in the main repo\n// (nineninesix/kani-tts-2-en, model.safetensors, \"model.\" prefix), and\n// 2. the NanoCodec decoder weights live in a SEPARATE repo\n// (nineninesix/nemo-nano-codec-22khz-0.6kbps-12.5fps-MLX, model.safetensors)\n// stored as weight-norm parametrizations under verbose NeMo key paths.\n//\n// This loader downloads both, folds the codec's weight-norm (W = g·v/‖v‖) and\n// renames its keys to the canonical `nanocodec.*` names the decoder graph expects,\n// and returns one merged f32 weight map (+ tokenizer + config) for the KaniTTS\n// consumer (src/gpu/kani-tts.ts), which builds the backbone + decoder graphs.\n// ════════════════════════════════════════════════════════════════════════════\n\n/** Default NanoCodec checkpoint Kani-TTS-2 uses (NeMo 22 kHz, 0.6 kbps, 12.5 fps). */\nexport const KANI_NANOCODEC_REPO = \"nineninesix/nemo-nano-codec-22khz-0.6kbps-12.5fps-MLX\";\n\nexport interface LoadedKaniTTS {\n /** Canonical-named f32 weights for the codec-LM backbone (LFM2 keys). */\n backboneWeights: Map<string, { data: ArrayBufferView; shape: number[] }>;\n /** Folded NanoCodec decoder weights under canonical `nanocodec.*` names. */\n codecWeights: Map<string, { data: ArrayBufferView; shape: number[] }>;\n /** The text tokenizer. */\n tokenizer: Tokenizer;\n /** Raw backbone config.json (LFM2 dims + KaniTTS2 fields). */\n rawConfig: Record<string, unknown>;\n}\n\n/** Matches the LFM2 attention out_proj key (renamed to o_proj for the canonical map). */\nconst KANI_OUT_PROJ_RE = /\\.self_attn\\.out_proj\\./;\n\n/** Coerce a safetensors tensor view (bf16/f16→f32) to a contiguous Float32Array. */\nfunction toF32(entry: SafetensorEntry, buf: ArrayBuffer, file: SafetensorsFile): Float32Array {\n let data: ArrayBufferView = getTensorData(buf, file, entry);\n if (entry.dtype === \"BF16\") {\n data = bf16ToF32(new Uint8Array(data.buffer, data.byteOffset, data.byteLength));\n } else if (entry.dtype === \"F16\") {\n data = f16ToF32(data);\n }\n return data instanceof Float32Array\n ? data\n : new Float32Array(data.buffer, data.byteOffset, data.byteLength / 4);\n}\n\n/** Fold + rename the raw NanoCodec tensors into the canonical `nanocodec.*` map. */\nfunction buildKaniCodecWeights(\n codecRaw: Map<string, { data: Float32Array; shape: number[] }>,\n): Map<string, { data: ArrayBufferView; shape: number[] }> {\n const out = new Map<string, { data: ArrayBufferView; shape: number[] }>();\n const { convs, plains } = nanoCodecWeightMap();\n for (const c of convs) {\n const g = codecRaw.get(c.gKey);\n const v = codecRaw.get(c.vKey);\n if (!(g && v)) {\n throw new Error(\n `NanoCodec weight-norm pair missing for ${c.graphKey}: ${c.gKey} / ${c.vKey}. ` +\n \"Check the codec checkpoint (expected weight-norm parametrizations).\",\n );\n }\n out.set(c.graphKey, {\n data: foldNanoCodecWeightNorm(g.data, v.data, v.shape),\n shape: v.shape,\n });\n }\n for (const pl of plains) {\n const t = codecRaw.get(pl.mlxKey);\n if (!t) {\n throw new Error(`NanoCodec tensor missing: ${pl.mlxKey} (for ${pl.graphKey}).`);\n }\n out.set(pl.graphKey, { data: t.data, shape: t.shape });\n }\n return out;\n}\n\nexport async function loadKaniTTS(options: {\n /** Backbone repo (default nineninesix/kani-tts-2-en). */\n repo?: string;\n /** NanoCodec repo (default KANI_NANOCODEC_REPO). */\n codecRepo?: string;\n revision?: string;\n hfToken?: string;\n cacheDir?: string;\n onProgress?: (loaded: number, total: number, message: string) => void;\n}): Promise<LoadedKaniTTS> {\n const revision = options.revision ?? \"main\";\n const repo = options.repo ?? \"nineninesix/kani-tts-2-en\";\n const codecRepo = options.codecRepo ?? KANI_NANOCODEC_REPO;\n const baseURL = resolveHFBaseURL(repo, revision);\n const codecURL = resolveHFBaseURL(codecRepo, \"main\");\n await initFs();\n\n options.onProgress?.(0, 100, \"Fetching config + tokenizer…\");\n const [rawConfig, tokenizerJSON] = await Promise.all([\n fetchJSON(baseURL, \"config.json\", options.hfToken, options.cacheDir),\n fetchJSON(baseURL, \"tokenizer.json\", options.hfToken, options.cacheDir),\n ]);\n const tokenizerConfig = await fetchJSON(\n baseURL,\n \"tokenizer_config.json\",\n options.hfToken,\n options.cacheDir,\n ).catch(() => null);\n const tokenizer = Tokenizer.fromJSON(tokenizerJSON, tokenizerConfig);\n\n // ── Backbone weights (LFM2-350M): strip \"model.\" and rename out_proj→o_proj. ──\n options.onProgress?.(8, 100, \"Downloading codec-LM backbone…\");\n const backboneBuf = await fetchBinary(\n baseURL,\n \"model.safetensors\",\n options.hfToken,\n (loaded, total) =>\n options.onProgress?.(8 + (loaded / (total || 1)) * 45, 100, \"backbone weights\"),\n options.cacheDir,\n );\n const backboneFile = parseSafetensorsHeader(backboneBuf);\n const backboneWeights = new Map<string, { data: ArrayBufferView; shape: number[] }>();\n for (const entry of backboneFile.entries) {\n const key = (entry.name.startsWith(\"model.\") ? entry.name.slice(6) : entry.name).replace(\n KANI_OUT_PROJ_RE,\n \".self_attn.o_proj.\",\n );\n backboneWeights.set(key, {\n data: toF32(entry, backboneBuf, backboneFile),\n shape: entry.shape,\n });\n }\n\n // ── NanoCodec weights: fold weight-norm + rename to canonical nanocodec.* ──\n options.onProgress?.(55, 100, \"Downloading NanoCodec decoder…\");\n // The codec's model.safetensors shares its name with the backbone's, and\n // fetchBinary caches by filename only — give the codec its own cache subdir so\n // the two downloads do not collide.\n const codecCacheDir = options.cacheDir ? `${options.cacheDir}/nanocodec` : undefined;\n const codecBuf = await fetchBinary(\n codecURL,\n \"model.safetensors\",\n options.hfToken,\n (loaded, total) =>\n options.onProgress?.(55 + (loaded / (total || 1)) * 40, 100, \"codec weights\"),\n codecCacheDir,\n );\n const codecFile = parseSafetensorsHeader(codecBuf);\n const codecRaw = new Map<string, { data: Float32Array; shape: number[] }>();\n for (const entry of codecFile.entries) {\n codecRaw.set(entry.name, { data: toF32(entry, codecBuf, codecFile), shape: entry.shape });\n }\n const codecWeights = buildKaniCodecWeights(codecRaw);\n\n options.onProgress?.(100, 100, \"Kani-TTS-2 loaded.\");\n return { backboneWeights, codecWeights, tokenizer, rawConfig };\n}\n","/**\n * MoonshineEncoderExecutor — runs the Moonshine STT encoder graph on WebGPU.\n *\n * Modeled on VisionExecutor: a single non-autoregressive prefill (conv frontend +\n * bidirectional transformer) run ONCE per utterance. The graph is length-static\n * (the Conv1dFull ops carry concrete L/Lout), so it is regenerated per utterance\n * from the input sample count (moonshineEncoderFrames) and a fresh executor is\n * built for that length.\n *\n * Beyond the encoder output, the graph also projects the frozen encoder hidden\n * state through each DECODER layer's encoder_attn.k_proj / v_proj, producing\n * enc_k_layer{i} / enc_v_layer{i}. The host reads these back and binds them frozen\n * into the decoder's CrossAttention across every AR decode step.\n *\n * WebKit discipline mirrors VisionExecutor: per-dispatch submit + drain, and the\n * bidirectional Attention op is windowed by query-row so no single dispatch runs\n * long enough to trip Metal's GPU watchdog. Dawn uses one submission.\n */\n\nimport type { GPUContext } from \"./device.js\";\nimport {\n createBindGroup,\n createStorageBuffer,\n createUniformBuffer,\n destroyBuffers,\n getOrCreatePipeline,\n} from \"./device.js\";\nimport { DTYPE_BYTES, type ModelGraph, type OpNode } from \"./ir.js\";\nimport {\n KERNEL_REGISTRY,\n type KernelSpec,\n LAYERNORM_NO_BIAS_SPEC,\n ROPE_INTERLEAVED_SPEC,\n} from \"./kernels/registry.js\";\n\nconst MAP_MODE_READ = 0x0001;\n\n/**\n * WebKit only: window the bidirectional encoder Attention grid into chunks of this\n * many query rows, each its own submit + drain (same watchdog mitigation the vision\n * tower uses). Encoder frames are far fewer than ViT patches, but a long clip can\n * still produce a few hundred frames, so we keep the guard.\n */\nconst WEBKIT_ATTN_CHUNK_ROWS = 64;\n\ninterface MoonshineDispatch {\n node: OpNode;\n spec: KernelSpec;\n pipeline: GPUComputePipeline;\n bindGroup: GPUBindGroup;\n uniformBuffer: GPUBuffer;\n}\n\nexport interface EncoderResult {\n /** encoder_out [S_enc, hidden] (debug / parity). */\n encoderOut: Float32Array;\n /** Per-decoder-layer frozen K, indexed by layer. */\n encK: Float32Array[];\n /** Per-decoder-layer frozen V, indexed by layer. */\n encV: Float32Array[];\n /** Encoder length (frames). */\n sEnc: number;\n}\n\nexport class MoonshineEncoderExecutor {\n private ctx: GPUContext;\n private graph: ModelGraph;\n private decLayers: number;\n private hidden: number;\n\n private weightBuffers = new Map<string, GPUBuffer>();\n private activationBuffers = new Map<string, GPUBuffer>();\n private dispatches: MoonshineDispatch[] = [];\n\n constructor(ctx: GPUContext, graph: ModelGraph, decLayers: number) {\n this.ctx = ctx;\n this.graph = graph;\n this.decLayers = decLayers;\n this.hidden = graph.config.hidden_size;\n this.allocateActivationBuffers();\n }\n\n /**\n * Upload the encoder constants. `weights` holds canonical-named f32 tensors;\n * only those referenced by the graph are uploaded (the decoder weights are\n * uploaded into the decoder Executor separately).\n */\n uploadWeights(weights: Map<string, { data: ArrayBufferView; shape: number[] }>): void {\n for (const [name, desc] of Object.entries(this.graph.tensors)) {\n if (desc.storage !== \"constant\") continue;\n const w = weights.get(name);\n if (w) {\n const buffer = createStorageBuffer(this.ctx, `menc_${name}`, w.data.byteLength, w.data);\n this.weightBuffers.set(name, buffer);\n } else if (!desc.safetensorsKey) {\n // Synthetic constant with no checkpoint source (e.g. conv1's zero bias —\n // Conv1dFull requires a bias binding but Moonshine's conv1 has none).\n // Allocate it zero-filled.\n const count = (desc.shape as number[]).reduce((a, b) => a * (b as number), 1);\n const zeros = new Float32Array(count);\n const buffer = createStorageBuffer(this.ctx, `menc_${name}`, zeros.byteLength, zeros);\n this.weightBuffers.set(name, buffer);\n } else {\n throw new Error(`MoonshineEncoderExecutor: missing weight \"${name}\"`);\n }\n }\n }\n\n initBindGroups(): void {\n const shapes = this.resolveShapes();\n for (const nodeId of this.graph.executionOrder) {\n const node = this.graph.nodes.find((n) => n.id === nodeId)!;\n let spec = KERNEL_REGISTRY[node.opType];\n if (!spec) throw new Error(`MoonshineEncoderExecutor: no kernel for op \"${node.opType}\"`);\n // Moonshine norms are weight-only — pick the no-bias LayerNorm variant.\n if (node.opType === \"LayerNorm\" && node.inputs.length === 2) {\n spec = LAYERNORM_NO_BIAS_SPEC;\n } else if (node.opType === \"RoPE\" && node.attributes.interleaved === true) {\n spec = ROPE_INTERLEAVED_SPEC;\n }\n const pipeline = getOrCreatePipeline(\n this.ctx,\n `mkernel_${nodeId}`,\n spec.shaderCode,\n spec.entryPoint,\n );\n const paramsData = spec.buildParams(node, shapes, { seqPos: 0 });\n const uniformBuffer = createUniformBuffer(this.ctx, `muniform_${nodeId}`, paramsData);\n const bufferEntries = this.gatherBuffers(spec, node, uniformBuffer);\n const bindGroup = createBindGroup(this.ctx, pipeline, bufferEntries, `mbg_${nodeId}`);\n this.dispatches.push({ node, spec, pipeline, bindGroup, uniformBuffer });\n }\n }\n\n /** Run the conv frontend + encoder + K/V projection. `pcm` is raw 16kHz mono. */\n async encode(pcm: Float32Array): Promise<EncoderResult> {\n const q = this.ctx.device.queue;\n q.writeBuffer(this.activationBuffers.get(\"pcm\")!, 0, pcm as BufferSource);\n\n const shapes = this.resolveShapes();\n const ctxRt = { seqPos: 0 };\n const sizes: Array<[number, number, number]> = [];\n for (const d of this.dispatches) {\n const params = d.spec.buildParams(d.node, shapes, ctxRt);\n q.writeBuffer(d.uniformBuffer, 0, params);\n sizes.push(d.spec.getDispatchSize(d.node, shapes, ctxRt));\n }\n\n if (this.ctx.isWebKitWebGPU) {\n for (let i = 0; i < this.dispatches.length; i++) {\n const d = this.dispatches[i];\n if (d.node.opType === \"Attention\") {\n // Window the O(S²) bidirectional attention by query-row (watchdog guard).\n const [gridX, gridY, gridZ] = sizes[i];\n for (let start = 0; start < gridX; start += WEBKIT_ATTN_CHUNK_ROWS) {\n const rows = Math.min(WEBKIT_ATTN_CHUNK_ROWS, gridX - start);\n const params = d.spec.buildParams(d.node, shapes, { seqPos: 0, qOffset: start });\n q.writeBuffer(d.uniformBuffer, 0, params);\n const enc = this.ctx.device.createCommandEncoder();\n const p = enc.beginComputePass();\n p.setPipeline(d.pipeline);\n p.setBindGroup(0, d.bindGroup);\n p.dispatchWorkgroups(rows, gridY, gridZ);\n p.end();\n q.submit([enc.finish()]);\n await q.onSubmittedWorkDone();\n }\n continue;\n }\n const enc = this.ctx.device.createCommandEncoder();\n const p = enc.beginComputePass();\n p.setPipeline(d.pipeline);\n p.setBindGroup(0, d.bindGroup);\n p.dispatchWorkgroups(...sizes[i]);\n p.end();\n q.submit([enc.finish()]);\n await q.onSubmittedWorkDone();\n }\n } else {\n const enc = this.ctx.device.createCommandEncoder({ label: \"moonshine_encode\" });\n const pass = enc.beginComputePass({ label: \"moonshine_enc_pass\" });\n for (let i = 0; i < this.dispatches.length; i++) {\n pass.setPipeline(this.dispatches[i].pipeline);\n pass.setBindGroup(0, this.dispatches[i].bindGroup);\n pass.dispatchWorkgroups(...sizes[i]);\n }\n pass.end();\n q.submit([enc.finish()]);\n }\n\n const sEnc = this.encoderFrames();\n const encoderOut = await this.readBack(\"encoder_out\", sEnc * this.hidden);\n const encK: Float32Array[] = [];\n const encV: Float32Array[] = [];\n for (let i = 0; i < this.decLayers; i++) {\n encK.push(await this.readBack(`enc_k_layer${i}`, sEnc * this.hidden));\n encV.push(await this.readBack(`enc_v_layer${i}`, sEnc * this.hidden));\n }\n return { encoderOut, encK, encV, sEnc };\n }\n\n /** Read back a named activation buffer after encode() (debug / parity checks). */\n async readActivation(name: string, maxElements = 4096): Promise<Float32Array> {\n return this.readBack(name, maxElements);\n }\n\n destroy(): void {\n destroyBuffers([...this.weightBuffers.values()]);\n destroyBuffers([...this.activationBuffers.values()]);\n for (const d of this.dispatches) d.uniformBuffer.destroy();\n this.weightBuffers.clear();\n this.activationBuffers.clear();\n this.dispatches = [];\n }\n\n // ── Private ──\n\n /** Encoder frame count = first dim of encoder_out (resolved, numeric). */\n private encoderFrames(): number {\n return this.graph.tensors.encoder_out.shape[0] as number;\n }\n\n private resolveShapes(): Record<string, number[]> {\n // The Moonshine encoder graph is fully concrete (no symbolic dims) — every\n // tensor shape is numeric for the chosen sample count.\n const resolved: Record<string, number[]> = {};\n for (const [name, desc] of Object.entries(this.graph.tensors)) {\n resolved[name] = desc.shape.map((dim) => dim as number);\n }\n return resolved;\n }\n\n private allocateActivationBuffers(): void {\n for (const [name, desc] of Object.entries(this.graph.tensors)) {\n if (desc.storage !== \"activation\") continue;\n const bytes =\n (desc.shape as number[]).reduce((a, b) => a * (b as number), 1) * DTYPE_BYTES[desc.dtype];\n this.activationBuffers.set(name, createStorageBuffer(this.ctx, `mact_${name}`, bytes));\n }\n }\n\n private async readBack(name: string, elements: number): Promise<Float32Array> {\n const buffer = this.activationBuffers.get(name);\n if (!buffer) throw new Error(`MoonshineEncoderExecutor: no buffer \"${name}\"`);\n const byteLen = Math.min(elements * 4, buffer.size);\n const readback = this.ctx.device.createBuffer({ size: byteLen, usage: 0x0001 | 0x0008 });\n const enc = this.ctx.device.createCommandEncoder();\n enc.copyBufferToBuffer(buffer, 0, readback, 0, byteLen);\n this.ctx.device.queue.submit([enc.finish()]);\n await readback.mapAsync(MAP_MODE_READ, 0, byteLen);\n const data = new Float32Array(readback.getMappedRange(0, byteLen).slice(0));\n readback.unmap();\n readback.destroy();\n return data;\n }\n\n private gatherBuffers(\n spec: KernelSpec,\n node: OpNode,\n uniformBuffer: GPUBuffer,\n ): Array<{ buffer: GPUBuffer }> {\n const entries: Array<{ buffer: GPUBuffer }> = [];\n const tensorNames = [...node.inputs, ...node.outputs];\n let tensorIdx = 0;\n for (const binding of spec.bindings) {\n if (binding.type === \"uniform\") {\n entries.push({ buffer: uniformBuffer });\n } else {\n const tensorName = tensorNames[tensorIdx++];\n const buffer = this.weightBuffers.get(tensorName) ?? this.activationBuffers.get(tensorName);\n if (!buffer) {\n throw new Error(\n `MoonshineEncoderExecutor: no buffer for \"${tensorName}\" in op ${node.id}`,\n );\n }\n entries.push({ buffer });\n }\n }\n return entries;\n }\n}\n","/**\n * MoonshineSTT — native speech-to-text engine for Gerbil's WebGPU backend.\n *\n * Moonshine is an encoder-decoder ASR model with a raw-waveform conv frontend\n * (no FFT / mel spectrogram). Unlike the causal-LM path, it needs two graphs:\n *\n * 1. ENCODER (run once per utterance): conv frontend → bidirectional transformer\n * → encoder hidden state, which is then projected through every decoder\n * layer's cross-attention k_proj/v_proj into FROZEN K/V buffers.\n * 2. DECODER (autoregressive): causal self-attention with a growing KV-cache,\n * plus cross-attention into the frozen encoder K/V at every step.\n *\n * The conv frontend is length-static (Conv1dFull carries concrete L/Lout), so the\n * encoder graph is regenerated per utterance from the input sample count, and the\n * decoder graph is regenerated with the resulting encoder frame count (S_enc).\n * Weights are uploaded once and reused across utterances via per-call executors.\n *\n * Validated on Dawn (desktop) via scripts/engine/test-moonshine-transcribe.mjs.\n * The kernels are mobile-safe (≤16KB workgroup memory, clamped exp/tanh, no\n * select(), no `enable f16`) and the executors use the WebKit submit/drain\n * discipline, so the same path runs on iPad.\n */\n\nimport {\n generateMoonshineDecoderGraph,\n generateMoonshineEncoderGraph,\n moonshineEncoderFrames,\n parseMoonshineConfig,\n} from \"./architectures/moonshine.js\";\nimport type { GPUContext } from \"./device.js\";\nimport { initGPU } from \"./device.js\";\nimport { Executor } from \"./executor.js\";\nimport { loadMoonshine } from \"./model-loader.js\";\nimport { MoonshineEncoderExecutor } from \"./moonshine-executor.js\";\nimport type { Tokenizer } from \"./tokenizer.js\";\n\nconst MOONSHINE_SAMPLE_RATE = 16_000;\n/** Hard cap so a stuck decode can't loop forever (config max_position_embeddings). */\nconst DEFAULT_MAX_NEW_TOKENS = 194;\n\nexport interface MoonshineSTTOptions {\n /** HF repo (default UsefulSensors/moonshine-base). */\n repo?: string;\n revision?: string;\n hfToken?: string;\n cacheDir?: string;\n onProgress?: (loaded: number, total: number, message: string) => void;\n}\n\nexport interface TranscribeOptions {\n /** Stop after this many decoded tokens (default 194). */\n maxNewTokens?: number;\n /**\n * RMS energy below which a clip is treated as silence/noise (`noSpeech`).\n * Default 0.01 (normalized 16 kHz float PCM).\n */\n minRms?: number;\n /**\n * Clips shorter than this are treated as no-speech — a sub-threshold clip is\n * almost always silence, and any transcript on it is a hallucination.\n * Default 0.35 seconds.\n */\n minSpeechSeconds?: number;\n}\n\nexport interface TranscribeResult {\n text: string;\n /** Decoded token ids (excluding the start token, including the trailing EOS). */\n tokens: number[];\n /** Number of encoder frames produced by the conv frontend. */\n encoderFrames: number;\n /** Audio duration in seconds (samples / 16000). */\n audioSeconds: number;\n /** RMS energy of the input PCM (~0..1). */\n speechRms: number;\n /**\n * True when the clip almost certainly contains no speech — too quiet, too\n * short, an empty transcript, or a non-speech marker like \"[BLANK_AUDIO]\".\n * Skip empty/hallucinated transcripts when this is set.\n */\n noSpeech: boolean;\n}\n\n/** Non-speech markers Moonshine can emit on silence, e.g. \"[BLANK_AUDIO]\", \"(silence)\". */\nconst NON_SPEECH_RE = /^\\s*[[(][^)\\]]*[)\\]]\\s*$/;\n\nfunction computeRms(pcm: Float32Array): number {\n if (pcm.length === 0) {\n return 0;\n }\n let sum = 0;\n for (let i = 0; i < pcm.length; i++) {\n sum += pcm[i] * pcm[i];\n }\n return Math.sqrt(sum / pcm.length);\n}\n\nexport class MoonshineSTT {\n private ctx: GPUContext;\n private weights: Map<string, { data: ArrayBufferView; shape: number[] }>;\n private tokenizer: Tokenizer;\n private rawConfig: Record<string, unknown>;\n private bosTokenId: number;\n private eosTokenId: number;\n private decoderStartTokenId: number;\n private _destroyed = false;\n\n /** HF architecture string, for parity with WebGPUEngine. */\n readonly architecture = \"MoonshineForConditionalGeneration\";\n\n private constructor(\n ctx: GPUContext,\n weights: Map<string, { data: ArrayBufferView; shape: number[] }>,\n tokenizer: Tokenizer,\n rawConfig: Record<string, unknown>,\n ) {\n this.ctx = ctx;\n this.weights = weights;\n this.tokenizer = tokenizer;\n this.rawConfig = rawConfig;\n this.bosTokenId = (rawConfig.bos_token_id as number) ?? 1;\n this.eosTokenId = (rawConfig.eos_token_id as number) ?? 2;\n // Moonshine seeds the decoder with decoder_start_token_id (== bos == 1).\n this.decoderStartTokenId = (rawConfig.decoder_start_token_id as number) ?? this.bosTokenId;\n }\n\n /** Download + initialize a Moonshine STT engine. */\n static async create(options: MoonshineSTTOptions = {}): Promise<MoonshineSTT> {\n const ctx = await initGPU();\n const repo = options.repo ?? \"UsefulSensors/moonshine-base\";\n const { weights, tokenizer, rawConfig } = await loadMoonshine({\n repo,\n revision: options.revision,\n hfToken: options.hfToken,\n cacheDir: options.cacheDir,\n onProgress: options.onProgress,\n });\n return new MoonshineSTT(ctx, weights, tokenizer, rawConfig);\n }\n\n /**\n * Transcribe raw 16 kHz mono PCM. Runs the conv frontend + encoder once, then\n * greedily AR-decodes with cross-attention into the frozen encoder K/V, stopping\n * on EOS. Returns the detokenized transcript.\n */\n async transcribe(pcm: Float32Array, opts: TranscribeOptions = {}): Promise<TranscribeResult> {\n if (this._destroyed) throw new Error(\"MoonshineSTT has been destroyed.\");\n if (pcm.length < 127) {\n throw new Error(`PCM too short: ${pcm.length} samples (need >= conv1 kernel size 127).`);\n }\n const d = parseMoonshineConfig(this.rawConfig);\n const sEnc = moonshineEncoderFrames(pcm.length);\n if (sEnc < 1) {\n throw new Error(`Audio too short: produces ${sEnc} encoder frames.`);\n }\n\n const audioSeconds = pcm.length / MOONSHINE_SAMPLE_RATE;\n const speechRms = computeRms(pcm);\n const minRms = opts.minRms ?? 0.01;\n const minSpeechSeconds = opts.minSpeechSeconds ?? 0.35;\n\n // Cheap VAD: skip the encode/decode entirely for clips too quiet or too short\n // to contain speech (a sub-threshold clip is almost always silence; any\n // transcript on it is a hallucination).\n if (speechRms < minRms || audioSeconds < minSpeechSeconds) {\n return {\n text: \"\",\n tokens: [],\n encoderFrames: 0,\n audioSeconds,\n speechRms,\n noSpeech: true,\n };\n }\n\n // ── Encoder (once) ──\n const encoderGraph = generateMoonshineEncoderGraph(this.rawConfig, pcm.length);\n const encExec = new MoonshineEncoderExecutor(this.ctx, encoderGraph, d.dec_layers);\n let encK: Float32Array[];\n let encV: Float32Array[];\n let frames: number;\n try {\n encExec.uploadWeights(this.weights);\n encExec.initBindGroups();\n const enc = await encExec.encode(pcm);\n encK = enc.encK;\n encV = enc.encV;\n frames = enc.sEnc;\n } finally {\n encExec.destroy();\n }\n\n // ── Decoder (autoregressive, greedy) ──\n const decoderGraph = generateMoonshineDecoderGraph(this.rawConfig, frames);\n // maxSeqLen bounds the self-attn KV cache. The decode can never exceed the\n // model's max_position_embeddings, so cap there.\n const maxSeqLen = Math.min(opts.maxNewTokens ?? DEFAULT_MAX_NEW_TOKENS, d.context_length);\n const decExec = new Executor(this.ctx, decoderGraph, { maxSeqLen, kvMode: \"f32\" });\n const tokens: number[] = [];\n try {\n decExec.uploadWeightsMap(selectGraphWeights(decoderGraph, this.weights));\n decExec.initBindGroups();\n // Bind the frozen encoder K/V into each decoder layer's cross-attention.\n for (let i = 0; i < d.dec_layers; i++) {\n decExec.writeInput(`enc_k_layer${i}`, encK[i]);\n decExec.writeInput(`enc_v_layer${i}`, encV[i]);\n }\n decExec.reset();\n\n // Greedy decode: seed with the decoder start token, feed argmax back in.\n let nextToken = this.decoderStartTokenId;\n const limit = maxSeqLen;\n for (let step = 0; step < limit; step++) {\n const tok = await decExec.forwardArgmax(new Uint32Array([nextToken]));\n tokens.push(tok);\n if (tok === this.eosTokenId) break;\n nextToken = tok;\n }\n } finally {\n decExec.destroy();\n }\n\n const text = this.tokenizer.decode(tokens, /* skipSpecialTokens */ true).trim();\n const noSpeech = text.length === 0 || NON_SPEECH_RE.test(text);\n return {\n text,\n tokens,\n encoderFrames: frames,\n audioSeconds,\n speechRms,\n noSpeech,\n };\n }\n\n destroy(): void {\n this._destroyed = true;\n this.weights.clear();\n }\n}\n\n/**\n * Build a fresh map of just the constant weights a graph references.\n *\n * Executor.uploadWeights() DELETES entries from the map it is given (to free JS\n * memory as each weight reaches the GPU). Moonshine reuses the same weights across\n * utterances and across the encoder+decoder, so we hand each executor its OWN map\n * (deletes only touch the copy; the shared ArrayBufferViews are uploaded, never\n * mutated). Scoping to graph-referenced constants also avoids uploading the\n * encoder's weights into the decoder executor.\n */\nfunction selectGraphWeights(\n graph: { tensors: Record<string, { storage: string }> },\n weights: Map<string, { data: ArrayBufferView; shape: number[] }>,\n): Map<string, { data: ArrayBufferView; shape: number[] }> {\n const out = new Map<string, { data: ArrayBufferView; shape: number[] }>();\n for (const [name, desc] of Object.entries(graph.tensors)) {\n if (desc.storage !== \"constant\") continue;\n const w = weights.get(name);\n if (w) out.set(name, w);\n }\n return out;\n}\n"],"mappings":"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"}
|