Spaces:
Running
Running
Download src/worker.js from fernandofernandes/fly-recital: direct link, hf CLI and curl.
- Browser
- Download file 6.56 kB
-
https://huggingface.co/spaces/fernandofernandes/fly-recital/resolve/main/src/worker.js
- Command line
-
hf download hf://spaces/fernandofernandes/fly-recital/src/worker.js
-
curl -L -o worker.js https://huggingface.co/spaces/fernandofernandes/fly-recital/resolve/main/src/worker.js
6.56 kB
| // Inference worker: downloads the brain once, then runs one recurrent step per request. | |
| // | |
| // The main thread drives the pace so the text appears as the fly says it, rather than | |
| // racing ahead. Generation is cheap enough (tens of tokens per second) that the | |
| // bottleneck is speech, not the model. | |
| import { FlyEngine } from './engine.js'; | |
| let engine = null; | |
| let manifest = null; | |
| let cloudIndex = null; | |
| let rasterIndex = null; | |
| let logits = null; | |
| let generated = 0; | |
| const TENSORS = ['wte', 'in_proj', 'gain', 'rec_gain', 'bias', 'ln_weight', 'ln_bias', 'head_a', 'head_b']; | |
| async function fetchTracked(url, label, onBytes) { | |
| const response = await fetch(url); | |
| if (!response.ok) throw new Error(`${label}: HTTP ${response.status}`); | |
| const total = Number(response.headers.get('content-length')) || 0; | |
| if (!response.body) return { buffer: await response.arrayBuffer(), total }; | |
| const reader = response.body.getReader(); | |
| const chunks = []; | |
| let received = 0; | |
| for (;;) { | |
| const { done, value } = await reader.read(); | |
| if (done) break; | |
| chunks.push(value); | |
| received += value.length; | |
| onBytes(value.length, label); | |
| } | |
| const merged = new Uint8Array(received); | |
| let at = 0; | |
| for (const chunk of chunks) { | |
| merged.set(chunk, at); | |
| at += chunk.length; | |
| } | |
| return { buffer: merged.buffer, total: received }; | |
| } | |
| function pickIndices(valid, count, neurons) { | |
| // Even stride over neurons that have a measured soma position, so the sample is | |
| // spread across the brain instead of clustered at low indices. | |
| const usable = []; | |
| for (let i = 0; i < neurons; i += 1) if (valid[i]) usable.push(i); | |
| const step = usable.length / count; | |
| const out = new Int32Array(count); | |
| for (let i = 0; i < count; i += 1) out[i] = usable[Math.min(usable.length - 1, Math.floor(i * step))]; | |
| return out; | |
| } | |
| async function load({ modelBase, graphBase, cloudCount, rasterCount }) { | |
| let done = 0; | |
| const report = (bytes, label) => { | |
| done += bytes; | |
| self.postMessage({ type: 'progress', bytes: done, label }); | |
| }; | |
| manifest = await (await fetch(`${modelBase}/manifest.json`)).json(); | |
| const config = manifest.config; | |
| const params = {}; | |
| for (const name of TENSORS) { | |
| const { buffer } = await fetchTracked(`${modelBase}/${name}.f32`, name, report); | |
| params[name] = new Float32Array(buffer); | |
| } | |
| const [offsets, source, weight, inIndex, soma, valid] = await Promise.all([ | |
| fetchTracked(`${graphBase}/graph/edges_offsets.i32`, 'edges', report), | |
| fetchTracked(`${graphBase}/graph/edges_source.u16`, 'edges', report), | |
| fetchTracked(`${graphBase}/graph/edges_weight.f32`, 'synapses', report), | |
| fetchTracked(`${graphBase}/interface/in_index.i32`, 'interface', report), | |
| fetchTracked(`${graphBase}/neurons/soma_position.i32`, 'anatomy', report), | |
| fetchTracked(`${graphBase}/neurons/soma_valid.u8`, 'anatomy', report), | |
| ]); | |
| const graph = { | |
| offsets: new Int32Array(offsets.buffer), | |
| source: new Uint16Array(source.buffer), | |
| weight: new Float32Array(weight.buffer), | |
| inIndex: new Int32Array(inIndex.buffer), | |
| }; | |
| if (graph.offsets.length !== config.neurons + 1) throw new Error('CSR offsets do not match the model'); | |
| if (graph.source.length !== graph.weight.length) throw new Error('edge arrays disagree'); | |
| const positions = new Int32Array(soma.buffer); | |
| const somaValid = new Uint8Array(valid.buffer); | |
| cloudIndex = pickIndices(somaValid, cloudCount, config.neurons); | |
| rasterIndex = cloudIndex.filter((_value, i) => i % Math.floor(cloudCount / rasterCount) === 0) | |
| .slice(0, rasterCount); | |
| const cloudPositions = new Float32Array(cloudIndex.length * 3); | |
| for (let i = 0; i < cloudIndex.length; i += 1) { | |
| const at = cloudIndex[i] * 3; | |
| cloudPositions[i * 3] = positions[at]; | |
| cloudPositions[i * 3 + 1] = positions[at + 1]; | |
| cloudPositions[i * 3 + 2] = positions[at + 2]; | |
| } | |
| engine = new FlyEngine({ params, graph, config }); | |
| self.postMessage({ | |
| type: 'ready', | |
| manifest: { | |
| experiment: manifest.experiment, | |
| selector: manifest.selector, | |
| updates: manifest.updates, | |
| parameters: manifest.trainable_parameters, | |
| edges: graph.weight.length, | |
| neurons: config.neurons, | |
| eos: config.eos_token_id, | |
| bos: config.bos_token_id, | |
| }, | |
| cloudPositions, | |
| rasterCount: rasterIndex.length, | |
| }, [cloudPositions.buffer]); | |
| } | |
| function sample() { | |
| const state = engine.state; | |
| const cloud = new Float32Array(cloudIndex.length); | |
| for (let i = 0; i < cloudIndex.length; i += 1) cloud[i] = Math.abs(state[cloudIndex[i]]); | |
| const raster = new Float32Array(rasterIndex.length); | |
| for (let i = 0; i < rasterIndex.length; i += 1) raster[i] = Math.abs(state[rasterIndex[i]]); | |
| // tanh saturates, so a peak magnitude is pinned near 1 and says nothing. Count how | |
| // many neurons are strongly driven instead; that number actually moves. | |
| let active = 0; | |
| for (let i = 0; i < state.length; i += 1) { | |
| const value = state[i] < 0 ? -state[i] : state[i]; | |
| if (value > 0.5) active += 1; | |
| } | |
| return { cloud, raster, active }; | |
| } | |
| function prime(promptIds) { | |
| engine.reset(); | |
| generated = 0; | |
| for (const id of promptIds) logits = engine.step(id); | |
| const { cloud, raster, active } = sample(); | |
| self.postMessage({ type: 'primed', meanAbs: engine.meanAbsState, active, cloud, raster }, | |
| [cloud.buffer, raster.buffer]); | |
| } | |
| function next({ temperature, topP }) { | |
| const started = performance.now(); | |
| const probabilities = engine.probabilities(logits); | |
| const token = engine.pick(logits, { temperature, topP }); | |
| const probability = probabilities[token]; | |
| let best = 0; | |
| for (let v = 1; v < probabilities.length; v += 1) if (probabilities[v] > probabilities[best]) best = v; | |
| generated += 1; | |
| const eos = token === manifest.config.eos_token_id; | |
| if (!eos) logits = engine.step(token); | |
| const { cloud, raster, active } = sample(); | |
| self.postMessage({ | |
| type: 'token', | |
| token, | |
| eos, | |
| index: generated, | |
| probability, | |
| topProbability: probabilities[best], | |
| meanAbs: engine.meanAbsState, | |
| active, | |
| milliseconds: performance.now() - started, | |
| cloud, | |
| raster, | |
| }, [cloud.buffer, raster.buffer]); | |
| } | |
| self.onmessage = async (event) => { | |
| const message = event.data; | |
| try { | |
| if (message.type === 'load') await load(message); | |
| else if (message.type === 'prime') prime(message.promptIds); | |
| else if (message.type === 'next') next(message); | |
| } catch (error) { | |
| self.postMessage({ type: 'error', message: String(error && error.message ? error.message : error) }); | |
| } | |
| }; | |