LittlePlyResearch notebook

runner.mjs

javascript · 61 lines · Exact bytes

Line 1 ↗
Version & provenance · browser-local-v1

Recorded SHA-256 6ea1b5cdfdc0c26af760e1c2fa024abb7cd113f577dba2e44b7c339128826355Rendered SHA-256 6ea1b5cdfdc0c26af760e1c2fa024abb7cd113f577dba2e44b7c339128826355

Apache-2.0 License notes. Line links are stable within this snapshot. Browser integration v1, derived from the validated desktop WebGPU prototype. FP32 export of checkpoint 6133648b2926b38542ee3ee1251a1ba111d61da9a8ea69d1c5f0559b09933643; model ONNX SHA-256 e16f6aab5bb02f8b90014f15b55f423d1ac59d6941c9e3bebb5e6bda557499a4. These are the exact integrated original frontend/policy/worker/runner files; third-party dependencies are separately licensed. ONNX Runtime Web 1.24.3 and chess.js 1.4.0. Website bundling changes paths and presentation but preserves model-scoring code. This source subset omits the exporter, which remains part of the recorded prototype validation, and is not a standalone build bundle. Device-dependent mate-helper budget may change overrides. No GPU memory or universal performance guarantee.

1import * as ort from 'onnxruntime-web/webgpu';
2export class Runner {
3  static async create(manifest, progress) {
4    if(!navigator.gpu) throw Error('Desktop WebGPU is required. Try a current desktop Chrome or Edge browser.');
5    const adapter=await navigator.gpu.requestAdapter();
6    if(!adapter) throw Error('No WebGPU adapter is available. Enable hardware acceleration and restart your browser.');
7    if(manifest.schema!==1 || manifest.codec!=='chess-square-triple-v1' || manifest.context!==1024 || manifest.dtype!=='float32') throw Error('Unsupported checkpoint manifest');
8    async function download(files,size,onProgress=()=>{}) {
9      const bytes=new Uint8Array(size);let offset=0;
10      for(const file of files){const response=await fetch(new URL(file,manifest.url));if(!response.ok)throw Error(`Download failed (${response.status})`);
11        const reader=response.body.getReader();for(;;){const {done,value}=await reader.read();if(done)break;if(offset+value.length>size)throw Error('Download size mismatch');bytes.set(value,offset);offset+=value.length;onProgress(offset/size);}}
12      if(offset!==size)throw Error('Incomplete download');return bytes;
13    }
14    const bytes=await download(manifest.chunks||[manifest.model],manifest.bytes,progress);
15    const hash=Array.from(new Uint8Array(await crypto.subtle.digest('SHA-256',bytes)),b=>b.toString(16).padStart(2,'0')).join('');
16    if(hash!==manifest.sha256)throw Error('Model integrity check failed');
17    ort.env.wasm.numThreads=1;
18    ort.env.wasm.wasmPaths=new URL('./',location.href).href;
19    if(manifest.runtime){
20      const runtime=await download(manifest.runtime.chunks,manifest.runtime.bytes);
21      const runtimeHash=Array.from(new Uint8Array(await crypto.subtle.digest('SHA-256',runtime)),b=>b.toString(16).padStart(2,'0')).join('');
22      if(runtimeHash!==manifest.runtime.sha256)throw Error('Runtime integrity check failed');
23      ort.env.wasm.wasmBinary=runtime;
24    }
25    const locations=Object.fromEntries(Array.from({length:24},(_,i)=>[`present_${i}`,'gpu-buffer']));
26    const session=await ort.InferenceSession.create(bytes,{executionProviders:['webgpu'],preferredOutputLocation:locations});
27    const runner=new Runner(session);runner.deviceInfo={vendor:adapter.info?.vendor,architecture:adapter.info?.architecture,maxBufferSize:adapter.limits.maxBufferSize,maxStorageBufferBindingSize:adapter.limits.maxStorageBufferBindingSize};return runner;
28  }
29  constructor(session) {this.session=session;this.peakOwnedCacheBytes=0;}
30  async prefill(history) {
31    const inputs=this.inputs([history],0);
32    for(let i=0;i<24;i++)inputs[`past_${i}`]=new ort.Tensor('float32',new Float32Array(0),[1,12,0,64]);
33    let out;
34    try {out=await this.session.run(inputs);} finally {Object.values(inputs).forEach(t=>t.dispose());}
35    const logits=Array.from(out.logits.data); out.logits.dispose();
36    return {logits,length:history.length,cache:Array.from({length:24},(_,i)=>out[`present_${i}`]),dispose(){Object.values(out).filter(t=>t!==out.logits).forEach(t=>t.dispose());}};
37  }
38  inputs(rows,start) {
39    const shape=[rows.length,rows[0].length];
40    return {input_ids:new ort.Tensor('int64',BigInt64Array.from(rows.flat(),BigInt),shape),
41      position_ids:new ort.Tensor('int64',BigInt64Array.from(rows.flatMap(r=>r.map((_,i)=>start+i)),BigInt),shape)};
42  }
43  async branch(rows,base) {
44    this.peakOwnedCacheBytes=Math.max(this.peakOwnedCacheBytes,24*12*base.length*64*4*(rows.length+1));
45    const inputs=this.inputs(rows,base.length),buffers=[];const device=ort.env.webgpu.device;
46    try {
47      const encoder=device.createCommandEncoder();
48      for(let i=0;i<24;i++) {
49        const size=12*base.length*64*4;
50        const buffer=device.createBuffer({size:size*rows.length,usage:GPUBufferUsage.STORAGE|GPUBufferUsage.COPY_DST|GPUBufferUsage.COPY_SRC});buffers.push(buffer);
51        for(let b=0;b<rows.length;b++)encoder.copyBufferToBuffer(base.cache[i].gpuBuffer,0,buffer,b*size,size);
52        inputs[`past_${i}`]=ort.Tensor.fromGpuBuffer(buffer,{dataType:'float32',dims:[rows.length,12,base.length,64]});
53      }
54      device.queue.submit([encoder.finish()]);
55      const out=await this.session.run(inputs,['logits']);
56      try {const data=out.logits.data;return rows.map((_,i)=>Array.from(data.slice(i*128,(i+1)*128)));} finally {out.logits.dispose();}
57    } finally {Object.values(inputs).forEach(t=>t.dispose());buffers.forEach(b=>b.destroy());}
58  }
59  async dispose(){await this.session.release();}
60}
61