Spaces:
Running
Running
Download engine.js from alperugurcan/phonon-2-webgpu: direct link, hf CLI and curl.
- Browser
- Download file 15.5 kB
-
https://huggingface.co/spaces/alperugurcan/phonon-2-webgpu/resolve/main/engine.js
- Command line
-
hf download hf://spaces/alperugurcan/phonon-2-webgpu/engine.js
-
curl -L -o engine.js https://huggingface.co/spaces/alperugurcan/phonon-2-webgpu/resolve/main/engine.js
15.5 kB
| 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<rows;r++)for(let c=0;c<cols;c++){const s=Math.floor(bytes[r*rb+Math.floor(c/5)]/3**(c%5))%3-1;sign[r*cols+c]=s;if(s)nnz++} | |
| const start=rows*rb,scales=start+Math.ceil(nnz/8),v=new DataView(bytes.buffer,bytes.byteOffset,bytes.byteLength); | |
| if(scales+4*rows!==bytes.length)throw Error('Invalid weight format.'); | |
| const w=new Float32Array(rows*cols);let bit=0; | |
| for(let r=0;r<rows;r++){const lo=half(v.getUint16(scales+r*2,true)),hi=half(v.getUint16(scales+rows*2+r*2,true)); | |
| for(let c=0;c<cols;c++){const i=r*cols+c;if(sign[i]){w[i]=sign[i]*((bytes[start+(bit>>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<j){[re[i],re[j]]=[re[j],re[i]];[im[i],im[j]]=[im[j],im[i]]}} | |
| for(let len=2;len<=512;len*=2){const angle=-2*Math.PI/len;for(let base=0;base<512;base+=len)for(let j=0;j<len/2;j++){const c=Math.cos(angle*j),s=Math.sin(angle*j),a=base+j,b=a+len/2,r=re[b]*c-im[b]*s,i=re[b]*s+im[b]*c;re[b]=re[a]-r;im[b]=im[a]-i;re[a]+=r;im[a]+=i}} | |
| } | |
| function logMel(audio){ | |
| const frames=Math.floor(audio.length/160)+1,pre=new Float64Array(audio.length),window=new Float64Array(512),mel=new Float64Array(frames*128),step=Math.log(6.4)/27; | |
| pre[0]=audio[0];for(let i=1;i<audio.length;i++)pre[i]=audio[i]-.97*audio[i-1];for(let i=0;i<400;i++)window[i+56]=.5-.5*Math.cos(2*Math.PI*i/399); | |
| const top=15+Math.log(8)/step,hz=Float64Array.from({length:130},(_,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<frames;t++){const re=new Float64Array(512),im=new Float64Array(512);for(let i=0;i<512;i++)re[i]=(pre[t*160+i-256]||0)*window[i];fft(re,im);for(let m=0;m<128;m++){let sum=0;for(let k=0;k<257;k++)sum+=(re[k]**2+im[k]**2)*filters[m][k];mel[t*128+m]=Math.log(sum+2**-24)}} | |
| for(let m=0;m<128;m++){let mean=0,v=0;for(let t=0;t<frames;t++)mean+=mel[t*128+m];mean/=frames;for(let t=0;t<frames;t++)v+=(mel[t*128+m]-mean)**2;const scale=Math.sqrt(v/Math.max(frames-1,1))+1e-5;for(let t=0;t<frames;t++)mel[t*128+m]=(mel[t*128+m]-mean)/scale}return Float32Array.from(mel); | |
| } | |
| // Keep packed weights and compiled pipelines for subsequent transcriptions. | |
| const packedWeights=new Map();let sessionPromise=null,manifestPromise=null,running=false; | |
| async function getSession(){ | |
| if(sessionPromise){const s=await sessionPromise;if(!s.lost)return s;s.device.destroy();sessionPromise=null} | |
| sessionPromise=(async()=>{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<storage,${i===inputs.length?'read_write':'read'}> ${'abcdefgh'[i]}:array<f32>;`).join('\n'); | |
| const shader=decl+`\n@compute @workgroup_size(64) fn main(@builtin(global_invocation_id) id:vec3<u32>){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(t<T){ | |
| if(++iterations>T*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!=='<unk>'&&p!=='<pad>'&&!/^<\|.*\|>$/.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} | |
| } | |