borkiss's picture
Upload folder using huggingface_hub
68d9676 verified
Raw
History Blame Contribute Delete
4.03 kB
// 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;
}