function half(v){const s=v&32768?-1:1,e=v>>10&31,m=v&1023;return s*(e===0?m*2**-24:e===31?m?NaN:Infinity:(1+m/1024)*2**(e-15))} function unpack(bytes,shape){ const [rows,cols]=shape,rb=Math.ceil(cols/5),sign=new Int8Array(rows*cols);let nnz=0; for(let r=0;r>3)]>>(bit%8))&1?hi:lo);bit++}}}return w; } function decode(b,r){ if(r.kind==='five_value')return unpack(b,r.shape); const count=r.shape.reduce((a,b)=>a*b,1),v=new DataView(b.buffer,b.byteOffset,b.byteLength); if(r.kind==='fp16')return Float32Array.from({length:count},(_,i)=>half(v.getUint16(i*2,true))); if(r.kind!=='int6')throw Error('Unsupported weight type: '+r.kind); const rows=r.shape[0],cols=count/rows,start=b.length-rows*2; return Float32Array.from({length:count},(_,i)=>{const j=Math.floor(i/4)*3,word=b[j]|b[j+1]<<8|b[j+2]<<16;return ((word>>((i%4)*6)&63)-32)*half(v.getUint16(start+Math.floor(i/cols)*2,true))}); } function fft(re,im){ for(let i=1,j=0;i<512;i++){let bit=256;for(;j&bit;bit>>=1)j^=bit;j^=bit;if(i{const m=i*top/129;return m<15?m*(200/3):1000*Math.exp(step*(m-15))}); const filters=Array.from({length:128},(_,m)=>Float64Array.from({length:257},(_,k)=>{const f=k*16000/512;return Math.max(0,Math.min((f-hz[m])/(hz[m+1]-hz[m]),(hz[m+2]-f)/(hz[m+2]-hz[m+1])))*2/(hz[m+2]-hz[m])})); for(let t=0;t{if(!navigator.gpu)throw Error('A browser with WebGPU support is required.');const adapter=await navigator.gpu.requestAdapter();if(!adapter)throw Error('No WebGPU adapter was found.');const device=await adapter.requestDevice(),session={device,pipelines:new Map(),prepared:new Map(),retained:new Set(),lost:null};device.lost.then(info=>session.lost=info.message||'The GPU connection was lost.');device.addEventListener('uncapturederror',e=>session.lost=e.error.message);return session})(); try{return await sessionPromise}catch(e){sessionPromise=null;throw e} } if(typeof window!=='undefined')window.addEventListener('pagehide',()=>{sessionPromise?.then(s=>s.device.destroy()).catch(()=>{})},{once:true}); export async function transcribe(audio,status,{keepPrepared=false}={}){ if(running)throw Error('A transcription is already running.');running=true;let session; try{session=await getSession()}catch(e){running=false;throw e} const {device,pipelines}=session,buffers=new Set();let ops=[],T=0,W={},shapes={}; function releasePrepared(){for(const b of session.retained)b.destroy();session.retained.clear();session.prepared.clear()} if(!keepPrepared)releasePrepared(); function buffer(count,data){const b=device.createBuffer({size:count*4,usage:GPUBufferUsage.STORAGE|GPUBufferUsage.COPY_SRC|GPUBufferUsage.COPY_DST});buffers.add(b);if(data)device.queue.writeBuffer(b,0,data);return b} function drop(b){if(b&&buffers.delete(b))b.destroy()} function clean(before,keep=[]){for(const b of buffers)if(!before.has(b)&&!keep.includes(b))drop(b)} async function flush(){if(session.lost)throw Error(session.lost);const enc=device.createCommandEncoder();ops.forEach(run=>run(enc));ops=[];device.queue.submit([enc.finish()]);await device.queue.onSubmittedWorkDone();if(session.lost)throw Error(session.lost);} async function op(code,inputs,count,threads=count){ const result=buffer(count),bindings=[...inputs,result],decl=bindings.map((_,i)=>`@group(0) @binding(${i}) var ${'abcdefgh'[i]}:array;`).join('\n'); const shader=decl+`\n@compute @workgroup_size(64) fn main(@builtin(global_invocation_id) id:vec3){let i=id.x+id.y*4194240u;if(i>=${threads}u){return;} ${code}}`; let pipeline=pipelines.get(shader);if(!pipeline){pipeline=await device.createComputePipelineAsync({layout:'auto',compute:{module:device.createShaderModule({code:shader}),entryPoint:'main'}});pipelines.set(shader,pipeline);if(pipelines.size>128)pipelines.delete(pipelines.keys().next().value)} const group=device.createBindGroup({layout:pipeline.getBindGroupLayout(0),entries:bindings.map((b,binding)=>({binding,resource:{buffer:b}}))});ops.push(enc=>{const pass=enc.beginComputePass();pass.setPipeline(pipeline);pass.setBindGroup(0,group);pass.dispatchWorkgroups(Math.min(65535,Math.ceil(threads/64)),Math.ceil(threads/64/65535));pass.end()});return result; } async function fetchBytes(path){const url=new URL(path,import.meta.url).href;let cache;try{cache=await caches.open('phonon2-native-v1')}catch{}try{const hit=await cache?.match(url);if(hit)return new Uint8Array(await hit.arrayBuffer())}catch{}status('Downloading model weights…');const r=await fetch(url);if(!r.ok)throw Error(path+': HTTP '+r.status);const b=new Uint8Array(await r.arrayBuffer());try{await cache?.put(url,new Response(b))}catch{}return b} async function load(key,manifest){if(keepPrepared&&session.prepared.has(key)){({W,shapes}=session.prepared.get(key));return}const spec=manifest.groups[key];let packed=packedWeights.get(key);if(!packed){packed=await fetchBytes('weights/'+key+'.bin');const digest=Array.from(new Uint8Array(await crypto.subtle.digest('SHA-256',packed)),b=>b.toString(16).padStart(2,'0')).join('');if(digest!==spec.sha256)throw Error(key+' weights failed verification.');packedWeights.set(key,packed)}W={};shapes={};for(const r of spec.records){const a=decode(packed.subarray(r.offset,r.offset+r.length),r);W[r.name]=buffer(a.length,a);shapes[r.name]=r.shape}if(keepPrepared){session.prepared.set(key,{W,shapes});for(const b of Object.values(W)){session.retained.add(b);buffers.delete(b)}}} async function linear(x,n,act=false,frames=T){const key=shapes[n]?n:n+'.weight',[rows,...dims]=shapes[key],cols=dims.reduce((a,b)=>a*b,1),bias=W[n+'.bias'];const inputs=[W[key],x,...(bias?[bias]:[])],dest=bias?'d':'c';return op(`let t=i/${rows}u;let r=i%${rows}u;var s=${bias?'c[r]':'0.0'};for(var j=0u;j<${cols}u;j++){s+=a[r*${cols}u+j]*b[t*${cols}u+j];}${dest}[i]=${act?'s/(1.0+exp(-s))':'s'};`,inputs,frames*rows)} async function norm(x,n){return op(`let base=i*1024u;var m=0.0;for(var j=0u;j<1024u;j++){m+=a[base+j];}m/=1024.0;var v=0.0;for(var j=0u;j<1024u;j++){let z=a[base+j]-m;v+=z*z;}let scale=inverseSqrt(v/1024.0+0.00001);for(var j=0u;j<1024u;j++){d[base+j]=(a[base+j]-m)*scale*b[j]+c[j];}`,[x,W[n+'.weight'],W[n+'.bias']],T*1024,T)} const residual=(a,b,s)=>op(`c[i]=a[i]+${s}*b[i];`,[a,b],T*1024); const ff=async(x,n)=>linear(await linear(x,n+'.linear1',true),n+'.linear2'); async function read(b,count){const r=device.createBuffer({size:count*4,usage:GPUBufferUsage.MAP_READ|GPUBufferUsage.COPY_DST});try{const enc=device.createCommandEncoder();enc.copyBufferToBuffer(b,0,r,0,count*4);device.queue.submit([enc.finish()]);await r.mapAsync(GPUMapMode.READ);return new Float32Array(r.getMappedRange()).slice()}finally{r.destroy()}} try{ const manifest=await (manifestPromise??=fetch(new URL('model.json',import.meta.url)).then(r=>{if(!r.ok)throw Error('Could not load model metadata.');return r.json()}).catch(e=>{manifestPromise=null;throw e})); const start=performance.now();status('Preparing audio features…');const mel=logMel(audio),frames=mel.length/128;let before=new Set(buffers);await load('subsampling',manifest); async function conv(x,n,depth=false,relu=false){const [t,f,c]=x.shape,ot=Math.ceil(t/2),of=Math.ceil(f/2);return {b:await op(`let ch=i%256u;let fr=(i/256u)%${of}u;let time=i/${of*256}u;var s=c[ch];for(var j=0u;j<3u;j++){for(var k=0u;k<3u;k++){let ti=i32(time*2u+j)-1;let fi=i32(fr*2u+k)-1;if(ti>=0&&ti<${t}&&fi>=0&&fi<${f}){s+=a[(u32(ti)*${f}u+u32(fi))*${c}u+${depth?'ch':'0u'}]*b[ch*9u+j*3u+k];}}}d[i]=${relu?'max(s,0.0)':'s'};`,[x.b,W[n+'.weight'],W[n+'.bias']],ot*of*256),shape:[ot,of,256]}} async function point(x,n){const [t,f,c]=x.shape;return {b:await op('let ch=i%256u;let base=(i/256u)*256u;var s=c[ch];for(var j=0u;j<256u;j++){s+=a[base+j]*b[ch*256u+j];}d[i]=max(s,0.0);',[x.b,W[n+'.weight'],W[n+'.bias']],t*f*c),shape:x.shape}} let x={b:buffer(mel.length,mel),shape:[frames,128,1]};x=await conv(x,'layers.0',false,true);x=await conv(x,'layers.2',true);x=await point(x,'layers.3');x=await conv(x,'layers.5',true);x=await point(x,'layers.6');T=x.shape[0]; let state=await op(`let t=i/1024u;let row=i%1024u;var s=c[row];for(var j=0u;j<4096u;j++){s+=a[t*4096u+(j%16u)*256u+j/16u]*b[row*4096u+j];}d[i]=s;`,[x.b,W['linear.weight'],W['linear.bias']],T*1024);await flush();clean(before,[state]); const pos=Float32Array.from({length:(2*T-1)*1024},(_,i)=>{const c=i%1024,f=(T-1-Math.floor(i/1024))/10000**(2*Math.floor(c/2)/1024);return c%2?Math.cos(f):Math.sin(f)}),positions=buffer(pos.length,pos); for(let layer=0;layer<24;layer++){ status(`Encoder ${layer+1}/24 · processing…`);before=new Set(buffers);await load('layer-'+layer,manifest);const old=state; let a=await ff(await norm(state,'norm_feed_forward1'),'feed_forward1');state=await residual(state,a,'0.5');a=await norm(state,'norm_self_att'); const q=await linear(a,'self_attn.q_proj'),k=await linear(a,'self_attn.k_proj'),v=await linear(a,'self_attn.v_proj'),rel=await linear(positions,'self_attn.relative_k_proj',false,2*T-1); const scores=await op(`let h=i/${T*T}u;let q=(i%${T*T}u)/${T}u;let k=i%${T}u;let p=${T-1}u+k-q;var s=0.0;for(var j=0u;j<128u;j++){let z=h*128u+j;let x=a[q*1024u+z];s+=(x+d[z])*b[k*1024u+z]+(x+e[z])*c[p*1024u+z];}f[i]=s*0.08838834764831845;`,[q,k,rel,W['self_attn.bias_u'],W['self_attn.bias_v']],8*T*T); const probs=await op(`let row=i/${T}u;var m=a[row*${T}u];for(var j=1u;j<${T}u;j++){m=max(m,a[row*${T}u+j]);}var s=0.0;for(var j=0u;j<${T}u;j++){s+=exp(a[row*${T}u+j]-m);}b[i]=exp(a[i]-m)/s;`,[scores],8*T*T); const context=await op(`let t=i/1024u;let z=i%1024u;let h=z/128u;var s=0.0;for(var j=0u;j<${T}u;j++){s+=a[h*${T*T}u+t*${T}u+j]*b[j*1024u+z];}c[i]=s;`,[probs,v],T*1024);state=await residual(state,await linear(context,'self_attn.o_proj'),'1.0'); a=await linear(await norm(state,'norm_conv'),'conv.pointwise_conv1');a=await op('let t=i/1024u;let z=i%1024u;b[i]=a[t*2048u+z]/(1.0+exp(-a[t*2048u+1024u+z]));',[a],T*1024); a=await op(`let t=i32(i/1024u);let z=i%1024u;var s=0.0;for(var j=0u;j<9u;j++){let at=t+i32(j)-4;if(at>=0&&at<${T}){s+=a[u32(at)*1024u+z]*b[z*9u+j];}}c[i]=s;`,[a,W['conv.depthwise_conv.weight']],T*1024); a=await op('let z=i%1024u;let s=(a[i]-b[z])*inverseSqrt(c[z]+0.00001)*d[z]+e[z];f[i]=s/(1.0+exp(-s));',[a,W['conv.norm.running_mean'],W['conv.norm.running_var'],W['conv.norm.weight'],W['conv.norm.bias']],T*1024);state=await residual(state,await linear(a,'conv.pointwise_conv2'),'1.0'); state=await residual(state,await ff(await norm(state,'norm_feed_forward2'),'feed_forward2'),'0.5');state=await norm(state,'norm_out');status(`Encoder ${layer+1}/24 running…`);await flush();clean(before,[state]);drop(old); } drop(positions);status('Preparing the decoder…');await load('decoder',manifest);const encoded=await linear(state,'encoder_projector');await flush();drop(state); let states=[buffer(1280,new Float32Array(1280)),buffer(1280,new Float32Array(1280))],candidate=null,dp=null,prev=8192,t=0,symbols=0,iterations=0;const tokenBuffer=buffer(1),frameBuffer=buffer(1),tokens=[]; while(tT*20)throw Error('The decoder exceeded its step limit.');status(`Decoding text · ${t+1}/${T}`); if(!dp){before=new Set(buffers);device.queue.writeBuffer(tokenBuffer,0,Float32Array.of(prev));let inp=await op('c[i]=a[u32(b[0])*640u+i];',[W['decoder.embedding.weight'],tokenBuffer],640);candidate=[]; for(let layer=0;layer<2;layer++){ const gates=await op('var s=e[i]+f[i];for(var j=0u;j<640u;j++){s+=a[i*640u+j]*c[j]+b[i*640u+j]*d[j];}g[i]=s;',[W[`decoder.lstm.weight_ih_l${layer}`],W[`decoder.lstm.weight_hh_l${layer}`],inp,states[layer],W[`decoder.lstm.bias_ih_l${layer}`],W[`decoder.lstm.bias_hh_l${layer}`]],2560); candidate[layer]=await op('let j=i%640u;let input=1.0/(1.0+exp(-a[j]));let forget=1.0/(1.0+exp(-a[640u+j]));let output=1.0/(1.0+exp(-a[1920u+j]));let cell=forget*b[640u+j]+input*tanh(a[1280u+j]);if(i<640u){c[i]=output*tanh(cell);}else{c[i]=cell;}',[gates,states[layer]],1280);inp=candidate[layer]; }dp=await linear(inp,'decoder.decoder_projector',false,1);await flush();clean(before,[...candidate,dp]); } before=new Set(buffers);device.queue.writeBuffer(frameBuffer,0,Float32Array.of(t));const joint=await op('d[i]=max(a[u32(c[0])*640u+i]+b[i],0.0);',[encoded,dp,frameBuffer],640),logits=await linear(joint,'joint.head',false,1);await flush();const scores=await read(logits,8198);if(scores.some(v=>!Number.isFinite(v)))throw Error('The decoder produced non-finite output.');clean(before); let tok=0,dur=0;for(let i=1;i<8193;i++)if(scores[i]>scores[tok])tok=i;for(let i=1;i<5;i++)if(scores[8193+i]>scores[8193+dur])dur=i; if(tok!==8192){tokens.push({id:tok,t});prev=tok;states.forEach(drop);states=candidate;candidate=null;drop(dp);dp=null;symbols++} if(tok===8192&&dur===0)dur=1;if(dur===0&&symbols>=10)dur=1;if(dur>0)symbols=0;t+=dur; } const text=tokens.map(({id})=>manifest.vocab[id]||'').filter(p=>p!==''&&p!==''&&!/^<\|.*\|>$/.test(p)).join('').replace(/▁/g,' ').trim();return {text,tokens,seconds:(performance.now()-start)/1000,frames:T}; }catch(e){releasePrepared();if(keepPrepared&&/memory|allocation|device lost|connection.*lost/i.test(e.message))throw Error(e.message+' Disable Keep prepared weights and retry.');throw e}finally{for(const b of buffers)b.destroy();running=false} }