Spaces:
Running
Running
| // Thin WebGPU helpers shared by tests and the model runtime. | |
| import { setSubgroups, setF16 } from "./kernels.js"; | |
| export async function initDevice() { | |
| const adapter = await navigator.gpu?.requestAdapter({ powerPreference: "high-performance" }) | |
| ?? await navigator.gpu?.requestAdapter(); | |
| if (!adapter) throw new Error("WebGPU is not available — use Chrome/Edge 113+ or Safari 26+"); | |
| const params = typeof location !== "undefined" ? new URLSearchParams(location.search) : new URLSearchParams(); | |
| // shader-f16 is a fast path, not a requirement: without it (Dawn gates it | |
| // on NVIDIA/Linux; Qualcomm mobiles lack it) f16 tensors are converted to | |
| // f32 at load time and the KV cache lives in f32 | |
| const hasF16 = !params.has("nof16") && adapter.features.has("shader-f16"); | |
| setF16(hasF16); | |
| const features = hasF16 ? ["shader-f16"] : []; | |
| // subgroups are a fast path, not a requirement: without them (iOS Safari) | |
| // the matvec kernels fall back to workgroup-shared reductions | |
| const hasSubgroups = !params.has("nosg") && adapter.features.has("subgroups"); | |
| if (hasSubgroups) features.push("subgroups"); | |
| setSubgroups(hasSubgroups); | |
| if (adapter.features.has("timestamp-query")) features.push("timestamp-query"); | |
| if (hasSubgroups && adapter.features.has("chromium-experimental-subgroup-matrix")) | |
| features.push("chromium-experimental-subgroup-matrix"); | |
| const device = await adapter.requestDevice({ | |
| requiredFeatures: features, | |
| requiredLimits: { | |
| maxBufferSize: adapter.limits.maxBufferSize, | |
| maxStorageBufferBindingSize: adapter.limits.maxStorageBufferBindingSize, | |
| maxStorageBuffersPerShaderStage: adapter.limits.maxStorageBuffersPerShaderStage, | |
| maxComputeWorkgroupStorageSize: adapter.limits.maxComputeWorkgroupStorageSize, | |
| }, | |
| }); | |
| device.hasSubgroups = hasSubgroups; | |
| device.hasF16 = hasF16; | |
| // fused decode kernels replay a 32-lane subgroupAdd (qkvRope head norm) | |
| // bit-exactly — only enabled when the subgroup size is fixed at 32 | |
| device.sg32 = hasSubgroups && adapter.info?.subgroupMinSize === 32 && adapter.info?.subgroupMaxSize === 32; | |
| device.lost.then((info) => console.error("GPU device lost:", info.reason, info.message)); | |
| return device; | |
| } | |
| export function createBuffer(device, sizeBytes, { storage = true, uniform = false } = {}) { | |
| let usage = GPUBufferUsage.COPY_DST | GPUBufferUsage.COPY_SRC; | |
| if (storage) usage |= GPUBufferUsage.STORAGE; | |
| if (uniform) usage |= GPUBufferUsage.UNIFORM; | |
| return device.createBuffer({ size: Math.max(sizeBytes, 16), usage }); | |
| } | |
| export function uploadF32(device, data) { | |
| const buf = createBuffer(device, data.byteLength); | |
| device.queue.writeBuffer(buf, 0, data); | |
| return buf; | |
| } | |
| export function makePipeline(device, code, label = "") { | |
| const module = device.createShaderModule({ code, label }); | |
| return device.createComputePipeline({ label, layout: "auto", compute: { module } }); | |
| } | |
| export function bind(device, pipeline, entries) { | |
| return device.createBindGroup({ | |
| layout: pipeline.getBindGroupLayout(0), | |
| entries: Object.entries(entries).map(([binding, buffer]) => ({ | |
| binding: Number(binding), | |
| resource: { buffer }, | |
| })), | |
| }); | |
| } | |
| export async function readback(device, buffer, sizeBytes, offset = 0) { | |
| const rb = device.createBuffer({ | |
| size: sizeBytes, | |
| usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ, | |
| }); | |
| const enc = device.createCommandEncoder(); | |
| enc.copyBufferToBuffer(buffer, offset, rb, 0, sizeBytes); | |
| device.queue.submit([enc.finish()]); | |
| await rb.mapAsync(GPUMapMode.READ); | |
| const data = new Float32Array(rb.getMappedRange().slice(0)); | |
| rb.destroy(); | |
| return data; | |
| } | |
| // f16 helpers (Float16Array is available in Chrome 135+) | |
| export function toF16(f32arr) { | |
| return new Float16Array(f32arr); | |
| } | |
| export function uploadF16(device, f32arr) { | |
| const f16 = new Float16Array(f32arr); | |
| const buf = createBuffer(device, f16.byteLength); | |
| device.queue.writeBuffer(buf, 0, f16.buffer, f16.byteOffset, f16.byteLength); | |
| return buf; | |
| } | |