| |
| |
|
|
| import gemmSource from './kernels/gemm.wgsl.js'; |
| import gemvSource from './kernels/gemm_gemv.wgsl.js'; |
| import gemmTiledSource from './kernels/gemm_tiled.wgsl.js'; |
| import gemmTiled2Source from './kernels/gemm_tiled2.wgsl.js'; |
| import attentionSource from './kernels/attention.wgsl.js'; |
| import attentionBlockSource from './kernels/attention_block.wgsl.js'; |
| import addLnSource from './kernels/add_layernorm.wgsl.js'; |
| import gemmRowLnSource from './kernels/gemm_row_ln.wgsl.js'; |
| import gemmReduceSource from './kernels/gemm_reduce.wgsl.js'; |
| import embedSource from './kernels/embed.wgsl.js'; |
| import scatterRowsSource from './kernels/scatter_rows.wgsl.js'; |
| import compactGatherSource from './kernels/compact_gather.wgsl.js'; |
| import decoderMegaSource from './kernels/decoder_mega.wgsl.js'; |
| import kvAppendSource from './kernels/kv_append.wgsl.js'; |
| import argmaxSource from './kernels/argmax_penalty.wgsl.js'; |
| import argmaxReduceSource from './kernels/argmax_reduce.wgsl.js'; |
| import { |
| D_MODEL, HEADS, HEAD_DIM, FFN, SCORES_CAP, ATTN_SCALE, LN_EPS, EMBED_SCALE, DECODER_START, |
| VOCAB, EOS, PAD, REP_PENALTY, BITMASK_WORDS, DECODE_CAP, |
| } from './constants.js'; |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| |
| |
| |
| function normalizeFlags(flags = {}) { |
| const defines = flags.defines ?? {}; |
| return { |
| t: flags.t ?? 'f32', |
| outT: flags.outT ?? flags.t ?? 'f32', |
| wg: flags.wg ?? 64, |
| bias: !!flags.bias, |
| silu: !!flags.silu, |
| wt: !!flags.wt, |
| sg: !!flags.sg, |
| immediate: !!flags.immediate, |
| |
| defines: Object.fromEntries(Object.keys(defines).sort().map((k) => [k, defines[k]])), |
| }; |
| } |
|
|
| export function buildShader(source, flags = {}) { |
| const { t, outT, wg, bias, silu, wt, sg, immediate, defines } = normalizeFlags(flags); |
| const values = { |
| T: t, |
| OUT_T: outT, |
| WG: String(wg), |
| ENABLE_F16: t === 'f16' || outT === 'f16' ? 'enable f16;' : '', |
| |
| |
| |
| |
| ENABLE_SG: sg ? 'enable subgroups;' : '', |
| ENABLE_IMMEDIATE: immediate ? 'requires immediate_address_space;' : '', |
| PARAM_BINDING: immediate ? '' : '@group(0) @binding(0) ', |
| PARAM_ADDRESS: immediate ? 'immediate' : 'uniform', |
| }; |
| const conds = { BIAS: !!bias, SILU: !!silu, WT: !!wt, SG: !!sg, NOSG: !sg }; |
| for (const [name, value] of Object.entries(defines)) { |
| if (typeof value === 'boolean') conds[name.toUpperCase()] = value; |
| else values[name.toUpperCase()] = String(value); |
| } |
| |
| |
| |
| |
| let code = source; |
| for (let prev = null; prev !== code;) { |
| prev = code; |
| code = code.replace(/\{\{IF_([A-Z0-9_]+)\}\}([\s\S]*?)\{\{\/IF_\1\}\}/g, (_m, name, body) => { |
| if (!(name in conds)) throw new Error(`buildShader: unknown conditional {{IF_${name}}}`); |
| return conds[name] ? body : ''; |
| }); |
| } |
| code = code.replace(/\{\{([A-Z0-9_/]+)\}\}/g, (_m, name) => { |
| if (!(name in values)) throw new Error(`buildShader: unresolved placeholder {{${name}}}`); |
| return values[name]; |
| }); |
| return code; |
| } |
|
|
| |
| |
| |
| |
| const pipelineCache = new WeakMap(); |
|
|
| const DEFAULT_BIND_GROUP_CACHE_LIMIT = 256; |
| const DEFAULT_UNIFORM_POOL_BANK_BYTES = 256 * 1024; |
| export const MAX_UNIFORM_POOL_BATCH = 64; |
| const dispatchStates = new WeakMap(); |
| const objectIds = new WeakMap(); |
| const pooledUniformBuffers = new WeakSet(); |
| let nextObjectId = 1; |
|
|
| function objectId(object) { |
| let id = objectIds.get(object); |
| if (!id) { |
| id = nextObjectId++; |
| objectIds.set(object, id); |
| } |
| return id; |
| } |
|
|
| function dispatchState(device) { |
| let state = dispatchStates.get(device); |
| if (!state) { |
| state = { |
| bindGroups: new Map(), |
| bindGroupLimit: DEFAULT_BIND_GROUP_CACHE_LIMIT, |
| activeUniformPools: 0, |
| uniformPoolOriginalBindGroupLimit: null, |
| dummyStorage: null, |
| uniformFrame: null, |
| stats: { |
| uniformBuffersCreated: 0, |
| uniformPoolBuffersCreated: 0, |
| uniformPoolBuffersDestroyed: 0, |
| uniformPoolFramesBegun: 0, |
| uniformPoolFramesFlushed: 0, |
| uniformPoolBlocks: 0, |
| uniformPoolBytes: 0, |
| uniformPoolBindGroupCacheHits: 0, |
| uniformPoolWarmBindGroupLookups: 0, |
| uniformPoolWarmBindGroupCacheHits: 0, |
| uniformPoolWarmBindGroupResets: 0, |
| uniformPoolGenerationInvalidations: 0, |
| uniformPoolCachePurges: 0, |
| dummyBuffersCreated: 0, |
| bindGroupsCreated: 0, |
| bindGroupCacheHits: 0, |
| bindGroupEvictions: 0, |
| bindGroupTargetedPurgeCalls: 0, |
| bindGroupTargetedPurges: 0, |
| immediateSets: 0, |
| }, |
| }; |
| dispatchStates.set(device, state); |
| } |
| return state; |
| } |
|
|
| export function getDispatchStats(device) { |
| const state = dispatchState(device); |
| return { |
| ...state.stats, |
| bindGroupCacheSize: state.bindGroups.size, |
| bindGroupCacheLimit: state.bindGroupLimit, |
| }; |
| } |
|
|
| export function resetDispatchStats(device, { clearCache = false } = {}) { |
| const state = dispatchState(device); |
| for (const key of Object.keys(state.stats)) state.stats[key] = 0; |
| if (clearCache) state.bindGroups.clear(); |
| } |
|
|
| export function setBindGroupCacheLimit(device, limit) { |
| if (!Number.isInteger(limit) || limit < 0) { |
| throw new Error(`bind-group cache limit must be a non-negative integer, got ${limit}`); |
| } |
| const state = dispatchState(device); |
| state.bindGroupLimit = limit; |
| while (state.bindGroups.size > limit) { |
| state.bindGroups.delete(state.bindGroups.keys().next().value); |
| state.stats.bindGroupEvictions++; |
| } |
| } |
|
|
| |
| |
| |
| |
| |
| export function shouldUseUniformParamPool(enabled, { B, immediate } = {}) { |
| return !!enabled |
| && immediate === false |
| && Number.isInteger(B) |
| && B >= 1 |
| && B <= MAX_UNIFORM_POOL_BATCH; |
| } |
|
|
| |
| |
| |
| |
| export function createUniformParamPool(device, { |
| banks = 2, |
| bankBytes = DEFAULT_UNIFORM_POOL_BANK_BYTES, |
| alignment = device?.limits?.minUniformBufferOffsetAlignment ?? 256, |
| } = {}) { |
| if (!Number.isInteger(banks) || banks < 1) { |
| throw new Error(`uniform pool banks must be a positive integer, got ${banks}`); |
| } |
| if (!Number.isInteger(alignment) || alignment < 16 || alignment % 16 !== 0) { |
| throw new Error(`uniform pool alignment must be a positive multiple of 16, got ${alignment}`); |
| } |
| if (!Number.isInteger(bankBytes) || bankBytes < alignment) { |
| throw new Error(`uniform pool bankBytes must be an integer >= alignment, got ${bankBytes}`); |
| } |
| bankBytes = Math.ceil(bankBytes / alignment) * alignment; |
|
|
| const state = dispatchState(device); |
| const statsAtCreate = { |
| uniformBuffersCreated: state.stats.uniformBuffersCreated, |
| bindGroupsCreated: state.stats.bindGroupsCreated, |
| bindGroupCacheHits: state.stats.bindGroupCacheHits, |
| pooledBindGroupCacheHits: state.stats.uniformPoolBindGroupCacheHits, |
| warmBindGroupLookups: state.stats.uniformPoolWarmBindGroupLookups, |
| warmBindGroupCacheHits: state.stats.uniformPoolWarmBindGroupCacheHits, |
| warmBindGroupResets: state.stats.uniformPoolWarmBindGroupResets, |
| generationInvalidations: state.stats.uniformPoolGenerationInvalidations, |
| cachePurges: state.stats.uniformPoolCachePurges, |
| }; |
| const cacheLimitStats = { highWater: state.bindGroupLimit }; |
| const poolBanks = Array.from({ length: banks }, (_, i) => { |
| const buffer = device.createBuffer({ |
| label: `uniform params bank ${i}`, |
| size: bankBytes, |
| usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST, |
| }); |
| pooledUniformBuffers.add(buffer); |
| return { |
| buffer, |
| cpu: new Uint32Array(bankBytes / 4), |
| cursor: 0, |
| blocks: 0, |
| busy: false, |
| flushed: false, |
| highWaterBytes: 0, |
| highWaterBlocks: 0, |
| }; |
| }); |
| if (state.activeUniformPools === 0) { |
| state.uniformPoolOriginalBindGroupLimit = state.bindGroupLimit; |
| } |
| state.activeUniformPools++; |
| const cacheEnabled = state.uniformPoolOriginalBindGroupLimit > 0; |
| state.stats.uniformPoolBuffersCreated += poolBanks.length; |
| const bankIds = poolBanks.map((bank) => objectId(bank.buffer)); |
|
|
| |
| |
| |
| |
| const purgeBindings = () => { |
| let removed = 0; |
| for (const key of [...state.bindGroups.keys()]) { |
| if (bankIds.some((id) => key.includes(`|${id}@`))) { |
| state.bindGroups.delete(key); |
| state.stats.uniformPoolCachePurges++; |
| removed++; |
| } |
| } |
| return removed; |
| }; |
|
|
| let destroyed = false; |
| const needLive = () => { |
| if (destroyed) throw new Error('uniform pool is destroyed'); |
| }; |
| const needBank = (index) => { |
| if (!Number.isInteger(index) || index < 0 || index >= poolBanks.length) { |
| throw new Error(`uniform pool bank ${index} out of range 0..${poolBanks.length - 1}`); |
| } |
| return poolBanks[index]; |
| }; |
|
|
| const pool = { |
| begin(index) { |
| needLive(); |
| if (state.uniformFrame) throw new Error('uniform pool frame already active'); |
| const bank = needBank(index); |
| if (bank.busy) throw new Error(`uniform pool bank ${index} reused while busy`); |
| bank.cursor = 0; |
| bank.blocks = 0; |
| bank.busy = true; |
| bank.flushed = false; |
| state.uniformFrame = { |
| pool, |
| bank, |
| index, |
| alignment, |
| bankBytes, |
| cacheBanks: poolBanks.length, |
| cacheEnabled, |
| cacheLimitStats, |
| warm: bank.highWaterBlocks > 0, |
| }; |
| state.stats.uniformPoolFramesBegun++; |
| return index; |
| }, |
|
|
| flush() { |
| needLive(); |
| const frame = state.uniformFrame; |
| if (!frame || frame.pool !== pool) throw new Error('uniform pool has no active frame to flush'); |
| const { bank, index } = frame; |
| const usedBytes = Math.ceil(bank.cursor / 4) * 4; |
| if (usedBytes > 0) { |
| device.queue.writeBuffer(bank.buffer, 0, bank.cpu.buffer, bank.cpu.byteOffset, usedBytes); |
| } |
| bank.flushed = true; |
| bank.highWaterBytes = Math.max(bank.highWaterBytes, usedBytes); |
| bank.highWaterBlocks = Math.max(bank.highWaterBlocks, bank.blocks); |
| state.uniformFrame = null; |
| state.stats.uniformPoolFramesFlushed++; |
| state.stats.uniformPoolBlocks += bank.blocks; |
| state.stats.uniformPoolBytes += usedBytes; |
| return { bank: index, blocks: bank.blocks, usedBytes }; |
| }, |
|
|
| abort() { |
| needLive(); |
| const frame = state.uniformFrame; |
| if (!frame || frame.pool !== pool) throw new Error('uniform pool has no active frame to abort'); |
| frame.bank.cursor = 0; |
| frame.bank.blocks = 0; |
| frame.bank.busy = false; |
| frame.bank.flushed = false; |
| state.uniformFrame = null; |
| }, |
|
|
| release(index) { |
| needLive(); |
| const bank = needBank(index); |
| if (state.uniformFrame?.bank === bank) { |
| throw new Error(`uniform pool bank ${index} released while its frame is active`); |
| } |
| if (!bank.busy || !bank.flushed) { |
| throw new Error(`uniform pool bank ${index} released before a flushed submission`); |
| } |
| bank.busy = false; |
| bank.flushed = false; |
| }, |
|
|
| |
| |
| |
| |
| invalidateBindings() { |
| needLive(); |
| if (state.uniformFrame?.pool === pool) { |
| throw new Error('uniform pool generation invalidated while its frame is active'); |
| } |
| const busy = poolBanks.findIndex((bank) => bank.busy); |
| if (busy >= 0) { |
| throw new Error(`uniform pool bank ${busy} is busy during generation invalidation`); |
| } |
| state.stats.uniformPoolGenerationInvalidations++; |
| return purgeBindings(); |
| }, |
|
|
| snapshot() { |
| return { |
| banks: poolBanks.length, |
| bankBytes, |
| alignment, |
| bindGroupCacheLimit: state.bindGroupLimit, |
| bindGroupCacheLimitHighWater: cacheLimitStats.highWater, |
| busyBanks: poolBanks.filter((bank) => bank.busy).length, |
| highWaterBytes: Math.max(0, ...poolBanks.map((bank) => bank.highWaterBytes)), |
| highWaterBlocks: Math.max(0, ...poolBanks.map((bank) => bank.highWaterBlocks)), |
| transientUniformBuffersCreated: |
| state.stats.uniformBuffersCreated - statsAtCreate.uniformBuffersCreated, |
| bindGroupsCreated: state.stats.bindGroupsCreated - statsAtCreate.bindGroupsCreated, |
| bindGroupCacheHits: state.stats.bindGroupCacheHits - statsAtCreate.bindGroupCacheHits, |
| pooledBindGroupCacheHits: |
| state.stats.uniformPoolBindGroupCacheHits - statsAtCreate.pooledBindGroupCacheHits, |
| warmBindGroupLookups: |
| state.stats.uniformPoolWarmBindGroupLookups - statsAtCreate.warmBindGroupLookups, |
| warmBindGroupCacheHits: |
| state.stats.uniformPoolWarmBindGroupCacheHits - statsAtCreate.warmBindGroupCacheHits, |
| warmBindGroupResets: |
| state.stats.uniformPoolWarmBindGroupResets - statsAtCreate.warmBindGroupResets, |
| generationInvalidations: |
| state.stats.uniformPoolGenerationInvalidations - statsAtCreate.generationInvalidations, |
| bindGroupsPurged: |
| state.stats.uniformPoolCachePurges - statsAtCreate.cachePurges, |
| }; |
| }, |
|
|
| destroy() { |
| if (destroyed) return; |
| if (state.uniformFrame?.pool === pool) { |
| const bank = state.uniformFrame.bank; |
| bank.busy = false; |
| bank.flushed = false; |
| state.uniformFrame = null; |
| } |
| purgeBindings(); |
| for (const bank of poolBanks) { |
| pooledUniformBuffers.delete(bank.buffer); |
| bank.busy = false; |
| bank.flushed = false; |
| bank.buffer.destroy(); |
| } |
| state.activeUniformPools--; |
| if (state.activeUniformPools === 0) { |
| state.bindGroupLimit = state.uniformPoolOriginalBindGroupLimit; |
| state.uniformPoolOriginalBindGroupLimit = null; |
| while (state.bindGroups.size > state.bindGroupLimit) { |
| state.bindGroups.delete(state.bindGroups.keys().next().value); |
| state.stats.bindGroupEvictions++; |
| } |
| } |
| state.stats.uniformPoolBuffersDestroyed += poolBanks.length; |
| destroyed = true; |
| }, |
| }; |
| return pool; |
| } |
|
|
| |
| |
| export function getPipeline(device, key, source, flags = {}) { |
| let map = pipelineCache.get(device); |
| if (!map) { |
| map = new Map(); |
| pipelineCache.set(device, map); |
| } |
| |
| const cacheKey = `${key}:${JSON.stringify(normalizeFlags(flags))}`; |
| let entry = map.get(cacheKey); |
| if (entry) { |
| if (entry.source !== source) throw new Error(`pipeline cache key collision: ${cacheKey}`); |
| return entry.pipeline; |
| } |
| const module = device.createShaderModule({ label: cacheKey, code: buildShader(source, flags) }); |
| const pipeline = device.createComputePipeline({ |
| label: cacheKey, |
| layout: 'auto', |
| compute: { module, entryPoint: 'main' }, |
| }); |
| map.set(cacheKey, { pipeline, source }); |
| return pipeline; |
| } |
|
|
| |
| |
| |
| function asResource(buf) { |
| return buf.buffer ? buf : { buffer: buf }; |
| } |
|
|
| |
| |
| function makeUniform(device, label, vals) { |
| dispatchState(device).stats.uniformBuffersCreated++; |
| const size = Math.max(16, Math.ceil((vals.length * 4) / 16) * 16); |
| const buf = device.createBuffer({ label, size, usage: GPUBufferUsage.UNIFORM, mappedAtCreation: true }); |
| new Uint32Array(buf.getMappedRange()).set(vals); |
| buf.unmap(); |
| return buf; |
| } |
|
|
| function dummyStorage(device) { |
| const state = dispatchState(device); |
| if (!state.dummyStorage) { |
| state.dummyStorage = device.createBuffer({ |
| label: 'shared dummy storage', size: 4, usage: GPUBufferUsage.STORAGE, |
| }); |
| state.stats.dummyBuffersCreated++; |
| } |
| return state.dummyStorage; |
| } |
|
|
| |
| |
| function makeParams(device, label, vals, immediate) { |
| if (immediate) { |
| return { resource: null, values: Uint32Array.from(vals), scratch: [] }; |
| } |
| const state = dispatchState(device); |
| const frame = state.uniformFrame; |
| if (frame) { |
| const size = Math.max(16, Math.ceil((vals.length * 4) / 16) * 16); |
| const offset = Math.ceil(frame.bank.cursor / frame.alignment) * frame.alignment; |
| const end = offset + size; |
| if (end > frame.bankBytes) { |
| throw new Error( |
| `uniform pool bank ${frame.index} overflow: need ${end} bytes, cap ${frame.bankBytes}`, |
| ); |
| } |
| frame.bank.cpu.fill(0, offset / 4, end / 4); |
| frame.bank.cpu.set(vals, offset / 4); |
| frame.bank.cursor = end; |
| frame.bank.blocks++; |
| |
| |
| |
| if (frame.cacheEnabled) { |
| state.bindGroupLimit = Math.max( |
| state.bindGroupLimit, |
| frame.bank.blocks * frame.cacheBanks, |
| ); |
| frame.cacheLimitStats.highWater = Math.max( |
| frame.cacheLimitStats.highWater, |
| state.bindGroupLimit, |
| ); |
| } |
| return { |
| resource: { buffer: frame.bank.buffer, offset, size }, |
| values: null, |
| scratch: [], |
| }; |
| } |
| const resource = makeUniform(device, label, vals); |
| return { resource, values: null, scratch: [resource] }; |
| } |
|
|
| function paramResources(params, resources) { |
| return params.resource ? [params.resource, ...resources] : resources; |
| } |
|
|
| function bindGroupKey(pipeline, resources, immediate) { |
| const resourceKeys = resources.map((resource) => { |
| const normalized = asResource(resource); |
| return `${objectId(normalized.buffer)}@${normalized.offset ?? 0}:${normalized.size ?? '*'}`; |
| }); |
| return `${immediate ? 'i' : 'u'}|p${objectId(pipeline)}|${resourceKeys.join('|')}`; |
| } |
|
|
| function record(pass, pipeline, device, resources, wgX, wgY = 1, wgZ = 1, immediateValues = null) { |
| const firstBinding = immediateValues ? 1 : 0; |
| const state = dispatchState(device); |
| const pooledUniform = !immediateValues && resources.length > 0 |
| && pooledUniformBuffers.has(asResource(resources[0]).buffer); |
| let bindGroup = null; |
| let cacheKey = null; |
| if ((immediateValues || pooledUniform) && state.bindGroupLimit > 0) { |
| cacheKey = bindGroupKey(pipeline, resources, !!immediateValues); |
| bindGroup = state.bindGroups.get(cacheKey) ?? null; |
| const warmPooledLookup = pooledUniform && state.uniformFrame?.warm; |
| if (warmPooledLookup) { |
| state.stats.uniformPoolWarmBindGroupLookups++; |
| } |
| if (bindGroup) { |
| state.bindGroups.delete(cacheKey); |
| state.bindGroups.set(cacheKey, bindGroup); |
| state.stats.bindGroupCacheHits++; |
| if (pooledUniform) { |
| state.stats.uniformPoolBindGroupCacheHits++; |
| if (state.uniformFrame?.warm) state.stats.uniformPoolWarmBindGroupCacheHits++; |
| } |
| } else if (warmPooledLookup) { |
| |
| |
| |
| state.uniformFrame.warm = false; |
| state.stats.uniformPoolWarmBindGroupResets++; |
| } |
| } |
| if (!bindGroup) { |
| bindGroup = device.createBindGroup({ |
| layout: pipeline.getBindGroupLayout(0), |
| entries: resources.map((r, binding) => ({ |
| binding: binding + firstBinding, |
| resource: asResource(r), |
| })), |
| }); |
| state.stats.bindGroupsCreated++; |
| if (cacheKey) { |
| state.bindGroups.set(cacheKey, bindGroup); |
| if (state.bindGroups.size > state.bindGroupLimit) { |
| state.bindGroups.delete(state.bindGroups.keys().next().value); |
| state.stats.bindGroupEvictions++; |
| } |
| } |
| } |
| pass.setPipeline(pipeline); |
| if (immediateValues) { |
| if (typeof pass.setImmediates !== 'function') { |
| throw new Error('WebGPU immediate shader selected but pass.setImmediates is unavailable'); |
| } |
| pass.setImmediates(0, immediateValues); |
| state.stats.immediateSets++; |
| } |
| pass.setBindGroup(0, bindGroup); |
| pass.dispatchWorkgroups(wgX, wgY, wgZ); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| export function dispatchGemm(device, pass, { x, w, b = null, y, M, K, N, storeKV = null, scales = null, fusedArgmax = null, splitK = null, flags = {} }) { |
| if (flags.tiled && flags.gemv) throw new Error('flags.tiled and flags.gemv are exclusive'); |
| if (flags.tiled) { |
| if (storeKV) throw new Error('storeKV requires flags.gemv'); |
| return dispatchGemmTiled(device, pass, { x, w, b, y, M, K, N, scales, fusedArgmax, splitK, flags }); |
| } |
| if (fusedArgmax) throw new Error('fusedArgmax requires flags.tiled'); |
| if (splitK) throw new Error('splitK requires flags.tiled'); |
| if (flags.gemv) return dispatchGemv(device, pass, { x, w, b, y, M, K, N, storeKV, scales, flags }); |
| if (scales) throw new Error('scales (wq8) requires flags.tiled or flags.gemv'); |
| if (storeKV) throw new Error('storeKV requires flags.gemv'); |
| const wg = flags.wg ?? 64; |
| const pipeline = getPipeline(device, 'gemm', gemmSource, { ...flags, bias: !!b }); |
|
|
| const dims = makeParams(device, 'gemm dims', [M, K, N, 0], !!flags.immediate); |
| const scratch = [...dims.scratch]; |
| let bias = b; |
| if (!bias) { |
| bias = dummyStorage(device); |
| } |
|
|
| |
| |
| |
| record(pass, pipeline, device, paramResources(dims, [x, w, bias, y]), |
| Math.ceil(N / wg), M, 1, dims.values); |
| return { pipeline, scratch }; |
| } |
|
|
| |
| |
| function dispatchGemv(device, pass, { x, w, b, y, M, K, N, storeKV = null, scales = null, flags }) { |
| const wt = !!flags.wt; |
| |
| |
| |
| const wq8 = !!flags.wq8; |
| if (wq8 && (!wt || !scales || storeKV)) { |
| throw new Error('gemv wq8: needs wt layout and scales, excludes storeKV'); |
| } |
| if (wt && K % 4 !== 0) throw new Error(`gemv wt requires K%4==0, got K=${K}`); |
| if (!wt && N % 4 !== 0) throw new Error(`gemv requires N%4==0, got N=${N}`); |
| if (storeKV && N % 3 !== 0) throw new Error('storeKV requires fused QKV (N=3·H·D)'); |
| const TK = flags.tk ?? 16; |
| const TN = flags.tn ?? (wt ? 8 : 4); |
| const MT = wt ? (flags.mt ?? 8) : 1; |
| const pipeline = getPipeline(device, 'gemm_gemv', gemvSource, { |
| t: flags.t, outT: flags.outT, wg: TK * TN, bias: !!b, silu: flags.silu, wt, |
| immediate: !!flags.immediate, |
| |
| |
| sg: !!flags.sg && wt, |
| defines: { TK, TN, NWT: !wt, STORE_KV: !!storeKV, WQ8: wq8, WQF: !wq8, ...(wt ? { MT } : {}) }, |
| }); |
|
|
| const dims = makeParams(device, 'gemv dims', storeKV |
| ? [M, K, N, 0, storeKV.t, storeKV.Lmax, 0, 0] |
| : [M, K, N, 0], !!flags.immediate); |
| const scratch = [...dims.scratch]; |
| let bias = b; |
| if (!bias) { |
| bias = dummyStorage(device); |
| } |
| const wgX = wt ? Math.ceil(N / TN) : Math.ceil(N / (4 * TN)); |
| const wgY = wt ? Math.ceil(M / MT) : M; |
| const resources = [x, w, bias, y]; |
| if (storeKV) resources.push(storeKV.kCache, storeKV.vCache); |
| if (wq8) resources.push(scales); |
| record(pass, pipeline, device, paramResources(dims, resources), wgX, wgY, 1, dims.values); |
| return { pipeline, scratch }; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| export function splitKParts(K, sk, BK = 16) { |
| const KSL = Math.ceil(K / sk / BK) * BK; |
| return { KSL, nz: Math.ceil(K / KSL) }; |
| } |
|
|
| function dispatchGemmTiled(device, pass, { x, w, b, y, M, K, N, scales = null, fusedArgmax = null, splitK = null, flags }) { |
| const BM = flags.bm ?? 64; |
| const BN = flags.bn ?? 64; |
| const BK = flags.bkk ?? 16; |
| if (BM % 4 !== 0 || BN % 4 !== 0) { |
| throw new Error(`gemm_tiled: BM/BN must be multiples of 4 (${BM}, ${BN})`); |
| } |
| |
| |
| |
| |
| |
| const sh16 = !!flags.sh16; |
| const dbuf = !!flags.dbuf; |
| if (fusedArgmax && (sh16 || dbuf)) { |
| throw new Error('gemm_tiled2 fused argmax: sh16/dbuf unsupported (pVal aliases f32 Xs)'); |
| } |
| |
| |
| if (splitK) { |
| if (fusedArgmax) throw new Error('gemm_tiled2 splitK: exclusive with fusedArgmax'); |
| if (!(splitK.sk >= 2)) throw new Error(`gemm_tiled2 splitK: sk must be >= 2, got ${splitK.sk}`); |
| if (b || flags.silu) throw new Error('gemm_tiled2 splitK: pass bias/silu to dispatchGemmReduce, not the GEMM'); |
| } |
| |
| |
| const fusedShared = fusedArgmax ? BM * (BN / 4) * 4 : 0; |
| const laneBytes = sh16 && flags.t === 'f16' ? 2 : 4; |
| const sharedBytes = (BM + BN) * BK * laneBytes * (dbuf ? 2 : 1) + fusedShared; |
| if (sharedBytes > 16384) { |
| throw new Error(`gemm_tiled: shared memory ${sharedBytes} bytes > 16384 limit`); |
| } |
| if (fusedArgmax && BK < BN / 4) { |
| throw new Error(`gemm_tiled2 fused argmax: BK=${BK} < BN/4=${BN / 4} — pVal cannot alias Xs`); |
| } |
| |
| |
| const wq8 = !!flags.wq8; |
| if (wq8 && (!scales || !flags.wt)) { |
| throw new Error('gemm_tiled2 wq8: needs scales and wt layout'); |
| } |
| const v2Eligible = K % 4 === 0 && BK % 4 === 0 && (flags.wt || N % 4 === 0); |
| const useV2 = (flags.tiledV ?? (v2Eligible ? 2 : 1)) === 2; |
| if (useV2 && !v2Eligible) { |
| throw new Error(`gemm_tiled2: shape M=${M} K=${K} N=${N} wt=${!!flags.wt} BK=${BK} not vec4-eligible`); |
| } |
| if (wq8 && !useV2) throw new Error('gemm_tiled2 wq8: v1 fallback has no int8 path'); |
| if (fusedArgmax && !useV2) throw new Error('gemm_tiled2 fused argmax: v2 only'); |
| if ((sh16 || dbuf) && !useV2) throw new Error('gemm_tiled2 sh16/dbuf: v2 only'); |
| if (splitK && (!useV2 || wq8)) throw new Error('gemm_tiled2 splitK: v2 only, no wq8'); |
| const kp = splitK ? splitKParts(K, splitK.sk, BK) : null; |
| const TM = useV2 && flags.tm8 ? 8 : 4; |
| if (BM % TM !== 0) throw new Error(`gemm_tiled: BM=${BM} not a multiple of TM=${TM}`); |
| const threads = (BM / TM) * (BN / 4); |
| if (threads > 256) throw new Error(`gemm_tiled: ${threads} threads > 256 workgroup limit`); |
| const pipeline = useV2 |
| ? getPipeline(device, 'gemm_tiled2', gemmTiled2Source, { |
| t: flags.t, outT: flags.outT, wg: threads, bias: !!b, silu: flags.silu, |
| immediate: !!flags.immediate, |
| wt: flags.wt && !wq8, |
| defines: { |
| BM, BN, BK, TM8: TM === 8, WNT: !flags.wt && !wq8, WQ8: wq8, WQF: !wq8, |
| STORE_Y: !fusedArgmax && !splitK, ARGMAX: !!fusedArgmax, |
| SPLITK: !!splitK, NOSPLITK: !splitK, |
| SH16: sh16, SH32: !sh16, DBUF: dbuf, SBUF: !dbuf, |
| ...(splitK ? { KSL: kp.KSL } : {}), |
| ...(fusedArgmax ? { PENALTY: REP_PENALTY, MASK_WORDS: fusedArgmax.maskWords ?? BITMASK_WORDS } : {}), |
| }, |
| }) |
| : getPipeline(device, 'gemm_tiled', gemmTiledSource, { |
| t: flags.t, outT: flags.outT, wg: threads, bias: !!b, silu: flags.silu, wt: flags.wt, |
| immediate: !!flags.immediate, |
| defines: { BM, BN, BK }, |
| }); |
|
|
| const dims = makeParams(device, 'gemm_tiled dims', [M, K, N, 0], !!flags.immediate); |
| const scratch = [...dims.scratch]; |
| let bias = b; |
| if (!bias) { |
| bias = dummyStorage(device); |
| } |
| |
| |
| |
| const resources = [x, w, bias, fusedArgmax?.partials ?? splitK?.parts ?? y]; |
| if (wq8) resources.push(scales); |
| if (fusedArgmax) resources.push(fusedArgmax.lbias, fusedArgmax.seen); |
| record(pass, pipeline, device, paramResources(dims, resources), |
| Math.ceil(N / BN), Math.ceil(M / BM), kp?.nz ?? 1, dims.values); |
| return { pipeline, scratch }; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| export function dispatchGemmReduce(device, pass, { parts, b = null, y, M, N, nz, storeKV = null, flags = {} }) { |
| if (storeKV && N % 3 !== 0) throw new Error('gemm_reduce storeKV requires fused QKV (N=3·H·D)'); |
| const wg = flags.wg ?? 128; |
| const pipeline = getPipeline(device, 'gemm_reduce', gemmReduceSource, { |
| t: flags.t, outT: flags.outT, wg, bias: !!b, silu: !!flags.silu, |
| immediate: !!flags.immediate, |
| defines: { STORE_KV: !!storeKV }, |
| }); |
| const dims = makeParams(device, 'gemm_reduce dims', |
| [M, N, nz, storeKV?.t ?? 0, storeKV?.Lmax ?? 0, 0, 0, 0], !!flags.immediate); |
| const scratch = [...dims.scratch]; |
| let bias = b; |
| if (!bias) { |
| bias = dummyStorage(device); |
| } |
| const resources = [parts, bias, y]; |
| if (storeKV) resources.push(storeKV.kCache, storeKV.vCache); |
| record(pass, pipeline, device, paramResources(dims, resources), |
| Math.ceil((M * N) / wg), 1, 1, dims.values); |
| return { pipeline, scratch }; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| export function attnBlockTile({ D4, t, qb = null, jb = null, qbAlign8 = false }) { |
| let QB = qb ?? Math.max(1, Math.min(16, Math.floor(256 / D4))); |
| if (qbAlign8 && qb == null && (QB * D4) % 16 !== 0) { |
| while (QB > 1 && (QB * D4) % 16 !== 0) QB--; |
| if ((QB * D4) % 16 !== 0) { |
| throw new Error(`attention block: no QB <= 256 threads aligns D4=${D4} to 16 threads`); |
| } |
| } |
| const kvBytes = t === 'f16' ? 8 : 16; |
| |
| |
| const sharedFor = (j) => QB * D4 * 16 + 2 * j * D4 * kvBytes + QB * j * 4 + 3 * QB * 4; |
| let JB = jb ?? 32; |
| if (jb == null) while (JB > 8 && sharedFor(JB) > 16384) JB >>= 1; |
| return { QB, JB, shared: sharedFor(JB) }; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| export function dispatchAttention(device, pass, { |
| q, k, v, lens = null, y, B, M, L, lenMode, step = 0, starts = null, |
| qStride = HEADS * HEAD_DIM, qOff = 0, kvStride = HEADS * HEAD_DIM, kOff = 0, vOff = 0, |
| flags = {}, |
| }) { |
| |
| |
| for (const [name, val] of [['qStride', qStride], ['qOff', qOff], ['kvStride', kvStride], |
| ['kOff', kOff], ['vOff', vOff], ['HEAD_DIM', HEAD_DIM]]) { |
| if (val % 4 !== 0) throw new Error(`attention: ${name}=${val} not vec4-aligned`); |
| } |
| if (flags.block) { |
| |
| |
| |
| if (lenMode !== 1) throw new Error('attention block: lenMode must be 1'); |
| if (!lens) throw new Error('attention block: lens buffer required'); |
| if (flags.packed && !starts) throw new Error('attention block: packed needs a starts buffer'); |
| const D4 = HEAD_DIM / 4; |
| const { QB, JB, shared } = attnBlockTile({ |
| D4, t: flags.t, qb: flags.qb ?? null, jb: flags.jb ?? null, |
| qbAlign8: !!flags.qbAlign8, |
| }); |
| const threads = QB * D4; |
| if (threads > 256) throw new Error(`attention block: QB=${QB} needs ${threads} > 256 threads`); |
| if (shared > 16384) throw new Error(`attention block: QB=${QB} JB=${JB} needs ${shared}B shared > 16384`); |
| const pipeline = getPipeline(device, 'attention_block', attentionBlockSource, { |
| t: flags.t, immediate: !!flags.immediate, |
| defines: { |
| H: HEADS, D: HEAD_DIM, QB, JB, ATTN_SCALE, |
| Q_STRIDE: qStride, Q_OFF: qOff, KV_STRIDE: kvStride, K_OFF: kOff, V_OFF: vOff, |
| PACKED: !!flags.packed, NOPACKED: !flags.packed, |
| }, |
| }); |
| const params = makeParams(device, 'attn params', |
| [B, M, L, lenMode, step, 0, 0, 0], !!flags.immediate); |
| const resources = [q, k, v, lens, y]; |
| if (flags.packed) resources.push(starts); |
| record(pass, pipeline, device, paramResources(params, resources), |
| Math.ceil(M / QB), HEADS, B, params.values); |
| return { pipeline, scratch: params.scratch }; |
| } |
| const pipeline = getPipeline(device, 'attention', attentionSource, { |
| t: flags.t, wg: flags.wg ?? 128, sg: !!flags.sg, |
| immediate: !!flags.immediate, |
| defines: { |
| H: HEADS, D: HEAD_DIM, SCORES_CAP, ATTN_SCALE, |
| Q_STRIDE: qStride, Q_OFF: qOff, KV_STRIDE: kvStride, K_OFF: kOff, V_OFF: vOff, |
| }, |
| }); |
| const params = makeParams(device, 'attn params', |
| [B, M, L, lenMode, step, 0, 0, 0], !!flags.immediate); |
| const scratch = [...params.scratch]; |
| let lensBuf = lens; |
| if (!lensBuf) { |
| lensBuf = dummyStorage(device); |
| } |
| record(pass, pipeline, device, paramResources(params, [q, k, v, lensBuf, y]), |
| B * M, HEADS, 1, params.values); |
| return { pipeline, scratch }; |
| } |
|
|
| |
| |
| |
| export function dispatchAddLn(device, pass, { x, r, gamma, beta, y, rows, flags = {} }) { |
| const pipeline = getPipeline(device, 'add_ln', addLnSource, { |
| t: flags.t, wg: flags.wg ?? 256, sg: !!flags.sg, immediate: !!flags.immediate, |
| defines: { D: D_MODEL, EPS: LN_EPS }, |
| }); |
| const params = makeParams(device, 'add_ln params', [rows, 0, 0, 0], !!flags.immediate); |
| record(pass, pipeline, device, paramResources(params, [x, r, gamma, beta, y]), |
| rows, 1, 1, params.values); |
| return { pipeline, scratch: params.scratch }; |
| } |
|
|
| |
| |
| |
| |
| |
| export function dispatchGemmRowLn(device, pass, { x, w, b, r, gamma, beta, y, M, K, N, flags = {} }) { |
| if (K % 4 !== 0 || N % 4 !== 0) throw new Error(`gemm_row_ln: K=${K}/N=${N} must be vec4-aligned`); |
| if ((K + N) * 4 + (flags.wg ?? 128) * 4 > 16384) { |
| throw new Error(`gemm_row_ln: shared memory over budget at K=${K}, N=${N}`); |
| } |
| const pipeline = getPipeline(device, 'gemm_row_ln', gemmRowLnSource, { |
| t: flags.t, wg: flags.wg ?? 128, sg: !!flags.sg, immediate: !!flags.immediate, |
| defines: { KDIM: K, D: N, EPS: LN_EPS }, |
| }); |
| const params = makeParams(device, 'gemm_row_ln params', [M, 0, 0, 0], !!flags.immediate); |
| record(pass, pipeline, device, paramResources(params, [x, w, b, r, gamma, beta, y]), |
| M, 1, 1, params.values); |
| return { pipeline, scratch: params.scratch }; |
| } |
|
|
| |
| |
| |
| |
| |
| export function dispatchEmbed(device, pass, { ids, table, posEmbed, y, mode, nRows, step = 0, batch, s = 0, packed = false, flags = {} }) { |
| if (packed && (mode !== 'src' || s > 0xffff)) { |
| throw new Error(`embed: packed needs mode 'src' and s < 65536 (got ${mode}, s=${s})`); |
| } |
| const pipeline = getPipeline(device, 'embed', embedSource, { |
| t: flags.t, wg: flags.wg ?? 224, immediate: !!flags.immediate, |
| defines: { |
| D: D_MODEL, EMBED_SCALE, SRC_IDS: mode === 'src', DECODE: mode === 'decode', DECODER_START, |
| PACKED: !!packed, NOPACKED: !packed, |
| }, |
| }); |
| const params = makeParams(device, 'embed params', |
| [nRows, step, batch, s], !!flags.immediate); |
| record(pass, pipeline, device, paramResources(params, [ids, table, posEmbed, y]), |
| nRows, 1, 1, params.values); |
| return { pipeline, scratch: params.scratch }; |
| } |
|
|
| |
| |
| |
| export function decodeMegaSharedBytes(wg = 256) { |
| const hd4 = (HEADS * HEAD_DIM) / 4; |
| const tmp4 = Math.max(FFN / 4, hd4 + wg); |
| |
| |
| return (2 * hd4 + tmp4) * 16 + SCORES_CAP * 4 + wg * 4; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| export function dispatchDecoderMega(device, pass, { |
| weights, layer, embed = false, ring, kCache, vCache, crossKV, lens, x, |
| B, t, S, kvCapacity = DECODE_CAP, flags = {}, |
| }) { |
| if (weights.dtype !== 'f16') throw new Error('decoder mega: needs f16 weights'); |
| const shared = decodeMegaSharedBytes(flags.wg ?? 256); |
| if (shared > 16384) { |
| throw new Error(`decoder mega: shared memory ${shared} bytes > 16384 limit at these dims`); |
| } |
| const off4 = (name) => { |
| const ten = weights.tensors.get(name); |
| if (!ten) throw new Error(`decoder mega: missing tensor ${name}`); |
| if (ten.byteOffset % 8 !== 0) throw new Error(`decoder mega: ${name} offset not vec4-aligned`); |
| return ten.byteOffset / 8; |
| }; |
| const p = (n) => `dec.${layer}.${n}`; |
| |
| |
| |
| const pipeline = getPipeline(device, 'decoder_mega', decoderMegaSource, { |
| t: 'f16', wg: flags.wg ?? 256, sg: !!flags.sg, immediate: !!flags.immediate, |
| defines: { |
| EMBED: !!embed, NOEMBED: !embed, |
| ...(embed ? { |
| TABLE4: off4('shared.weight'), POS4: off4('pos_embed'), |
| EMBED_SCALE, DECODER_START, |
| } : {}), |
| H: HEADS, D: HEAD_DIM, FFN4: FFN / 4, |
| LMAX: kvCapacity, SCORES_CAP, ATTN_SCALE, EPS: LN_EPS, |
| QKVW4: off4(p('self_qkv.weight')), QKVB4: off4(p('self_qkv.bias')), |
| OUTW4: off4(p('self_out.weight')), OUTB4: off4(p('self_out.bias')), |
| LN1G4: off4(p('ln1.weight')), LN1B4: off4(p('ln1.bias')), |
| CQW4: off4(p('cross_q.weight')), CQB4: off4(p('cross_q.bias')), |
| COW4: off4(p('cross_out.weight')), COB4: off4(p('cross_out.bias')), |
| LN2G4: off4(p('ln2.weight')), LN2B4: off4(p('ln2.bias')), |
| FC1W4: off4(p('fc1.weight')), FC1B4: off4(p('fc1.bias')), |
| FC2W4: off4(p('fc2.weight')), FC2B4: off4(p('fc2.bias')), |
| LN3G4: off4(p('ln3.weight')), LN3B4: off4(p('ln3.bias')), |
| }, |
| }); |
| const params = makeParams(device, 'mega params', [B, t, S, 0], !!flags.immediate); |
| record(pass, pipeline, device, |
| paramResources(params, [weights.buffer, ring, kCache, vCache, crossKV, lens, x]), |
| B, 1, 1, params.values); |
| return { pipeline, scratch: params.scratch }; |
| } |
|
|
| |
| |
| |
| |
| export function dispatchScatterRows(device, pass, { x, y, starts, lens, B, S, N, flags = {} }) { |
| if (N % 4 !== 0) throw new Error(`scatter_rows: N=${N} not vec4-aligned`); |
| const pipeline = getPipeline(device, 'scatter_rows', scatterRowsSource, { |
| t: flags.t, wg: flags.wg ?? 128, immediate: !!flags.immediate, |
| }); |
| const params = makeParams(device, 'scatter_rows params', |
| [B, S, N / 4, 0], !!flags.immediate); |
| record(pass, pipeline, device, paramResources(params, [starts, lens, x, y]), |
| B * S, 1, 1, params.values); |
| return { pipeline, scratch: params.scratch }; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| export function purgeBindGroupsForBuffers(device, buffers) { |
| if (!Array.isArray(buffers)) { |
| throw new Error('bind-group targeted purge needs an array of buffers'); |
| } |
| const state = dispatchState(device); |
| state.stats.bindGroupTargetedPurgeCalls++; |
| const ids = new Set(); |
| for (const item of buffers) { |
| if (!item) continue; |
| const buffer = item.buffer ?? item; |
| const id = objectIds.get(buffer); |
| if (id) ids.add(id); |
| } |
| if (ids.size === 0) return 0; |
| const needles = [...ids].map((id) => `|${id}@`); |
| let removed = 0; |
| for (const key of [...state.bindGroups.keys()]) { |
| if (!needles.some((needle) => key.includes(needle))) continue; |
| state.bindGroups.delete(key); |
| removed++; |
| } |
| state.stats.bindGroupTargetedPurges += removed; |
| return removed; |
| } |
|
|
| |
| |
| |
| |
| |
| export function dispatchCompactGather(device, pass, { |
| data, map, params, rowStrideU32, copyLenU32, flags = {}, |
| }) { |
| if (!Number.isInteger(rowStrideU32) || rowStrideU32 < 1 |
| || !Number.isInteger(copyLenU32) || copyLenU32 < 1 |
| || copyLenU32 > rowStrideU32) { |
| throw new Error( |
| `compact_gather: bad row shape stride=${rowStrideU32} copy=${copyLenU32}`, |
| ); |
| } |
| const pipeline = getPipeline(device, 'compact_gather', compactGatherSource, { |
| wg: flags.wg ?? 256, |
| }); |
| record(pass, pipeline, device, [params, map, data], 1, 1, 1, null); |
| return { pipeline, scratch: [] }; |
| } |
|
|
| |
| |
| |
| export function dispatchKvAppend(device, pass, { fused, kCache, vCache, B, t, Lmax, flags = {} }) { |
| const wg = flags.wg ?? 128; |
| const pipeline = getPipeline(device, 'kv_append', kvAppendSource, { |
| t: flags.t, wg, immediate: !!flags.immediate, |
| defines: { H: HEADS, D: HEAD_DIM, APPEND: true, SPLIT: false }, |
| }); |
| const params = makeParams(device, 'kv_append params', [B, t, Lmax, 0], !!flags.immediate); |
| record(pass, pipeline, device, paramResources(params, [fused, kCache, vCache]), |
| Math.ceil((B * HEADS * HEAD_DIM) / wg), 1, 1, params.values); |
| return { pipeline, scratch: params.scratch }; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| export function dispatchArgmaxPenalty(device, pass, { logits, bias, bitmask, done, tokens, B, t, short = null, flags = {} }) { |
| const pipeline = getPipeline(device, 'argmax_penalty', argmaxSource, { |
| wg: flags.wg ?? 256, immediate: !!flags.immediate, |
| defines: { |
| V: short?.n ?? VOCAB, EOS, PAD, PENALTY: REP_PENALTY, |
| MASK_WORDS: short?.maskWords ?? BITMASK_WORDS, |
| SHORT: !!short, NOSHORT: !short, |
| ...(short ? { GMASK_WORDS: BITMASK_WORDS } : {}), |
| }, |
| }); |
| const params = makeParams(device, 'argmax params', [B, t, 0, 0], !!flags.immediate); |
| const resources = [logits, bias, bitmask, done, tokens]; |
| if (short) resources.push(short.idmap, short.gmask); |
| record(pass, pipeline, device, paramResources(params, resources), B, 1, 1, params.values); |
| return { pipeline, scratch: params.scratch }; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| export function dispatchArgmaxReduce(device, pass, { partials, bitmask, done, tokens, B, t, NT, short = null, flags = {} }) { |
| const pipeline = getPipeline(device, 'argmax_reduce', argmaxReduceSource, { |
| wg: flags.wg ?? 256, immediate: !!flags.immediate, |
| defines: { |
| EOS, PAD, MASK_WORDS: short?.maskWords ?? BITMASK_WORDS, |
| SHORT: !!short, NOSHORT: !short, |
| ...(short ? { GMASK_WORDS: BITMASK_WORDS } : {}), |
| }, |
| }); |
| const params = makeParams(device, 'argmax_reduce params', [B, t, NT, 0], !!flags.immediate); |
| const resources = [partials, bitmask, done, tokens]; |
| if (short) resources.push(short.idmap, short.gmask); |
| record(pass, pipeline, device, paramResources(params, resources), B, 1, 1, params.values); |
| return { pipeline, scratch: params.scratch }; |
| } |
|
|
| |
| export function runGemmOnce(device, opts) { |
| const encoder = device.createCommandEncoder(); |
| const pass = encoder.beginComputePass(); |
| const { scratch } = dispatchGemm(device, pass, opts); |
| pass.end(); |
| device.queue.submit([encoder.finish()]); |
| for (const buf of scratch) buf.destroy(); |
| } |
|
|