Spaces:
Sleeping
Sleeping
| // The wire protocol, as pure functions. | |
| // | |
| // Kept out of app.js on purpose: a codec that only exists inside a browser | |
| // event handler cannot be tested, and the failures this protocol can have are | |
| // precisely the ones that are invisible at runtime. A misread offset does not | |
| // throw — it yields a plausible number, and a plausible number in an | |
| // activation is a slightly wrong sentence nobody can attribute to anything. | |
| // So the codec lives here and test_wire.js round-trips it. | |
| // | |
| // Sentinels continue DaisyChain-Web's numbering (it uses -2..-8), so a client | |
| // pointed at the wrong server sees an unknown tag rather than a valid-looking | |
| // message of the wrong kind. | |
| (function (root) { | |
| "use strict"; | |
| const FRAG = -5, // fragment of a large message (shared with DaisyChain-Web) | |
| HELLO = -20, // capability report | |
| ASSIGN = -21, // model identity + which layers you own (no weights) | |
| READY = -22, // a stage has fetched its layers and is in the ring | |
| ACT = -23, // hidden state moving to the next stage | |
| TOKEN = -24, // a token was emitted | |
| DONE = -25; // run finished | |
| // The address of the head's return leg. Not a stage index: the head is both | |
| // stage 0 and the terminus, so "next = 0" is ambiguous — outbound it means | |
| // "stage 0, run your blocks", inbound it means "the lap is done". They need | |
| // distinct addresses or the head re-runs its own blocks and the lap never | |
| // closes. Negative, so it can never collide with a real stage index. | |
| const RETURN = -1; | |
| const enc = new TextEncoder(), dec = new TextDecoder(); | |
| function tagOf(buf) { return new Int32Array(buf, 0, 1)[0]; } | |
| // ---- hello ----------------------------------------------------------------- | |
| function packHello(capacity, probeHash, backend) { | |
| const nb = enc.encode(String(backend || "?").slice(0, 32)); | |
| const buf = new ArrayBuffer(16 + nb.length); | |
| new Int32Array(buf, 0, 1)[0] = HELLO; | |
| new Float32Array(buf, 4, 1)[0] = capacity; | |
| new Uint32Array(buf, 8, 1)[0] = probeHash >>> 0; | |
| new Int32Array(buf, 12, 1)[0] = nb.length; | |
| new Uint8Array(buf, 16).set(nb); | |
| return buf; | |
| } | |
| function unpackHello(buf) { | |
| const n = new Int32Array(buf, 12, 1)[0]; | |
| if (n < 0 || 16 + n > buf.byteLength) throw new Error("hello: bad backend length"); | |
| return { capacity: new Float32Array(buf, 4, 1)[0], | |
| probeHash: new Uint32Array(buf, 8, 1)[0], | |
| backend: dec.decode(new Uint8Array(buf, 16, n)) }; | |
| } | |
| // ---- assignment ------------------------------------------------------------- | |
| // Weights do NOT travel between peers. The head sends each device the model's | |
| // identity and which layers it owns; the device then fetches exactly those | |
| // tensors from the Hub itself, with its own credentials. So this message is | |
| // small, and a token never crosses the wire. | |
| // | |
| // [i32 ASSIGN][utf8 JSON] | |
| // | |
| // JSON rather than packed ints because the payload is a model spec whose | |
| // fields differ by architecture. It is validated on arrival — a malformed | |
| // assignment must fail here, not three layers deep in a GEMM. | |
| function packAssign(a) { | |
| const bytes = enc.encode(JSON.stringify(a)); | |
| const buf = new ArrayBuffer(4 + bytes.length); | |
| new Int32Array(buf, 0, 1)[0] = ASSIGN; | |
| new Uint8Array(buf, 4).set(bytes); | |
| return buf; | |
| } | |
| function unpackAssign(buf) { | |
| let a; | |
| try { a = JSON.parse(dec.decode(new Uint8Array(buf, 4))); } | |
| catch (e) { throw new Error("assignment: payload is not valid JSON"); } | |
| if (!a || typeof a.repo !== "string" || !a.repo) | |
| throw new Error("assignment: no repo id"); | |
| if (!/^[\w.-]+\/[\w.-]+$/.test(a.repo)) | |
| throw new Error(`assignment: "${a.repo}" is not a valid repo id`); | |
| if (!a.spec || !a.spec.layers || !a.spec.hidden) | |
| throw new Error("assignment: incomplete model spec"); | |
| if (!Array.isArray(a.plan) || !a.plan.length) | |
| throw new Error("assignment: no plan"); | |
| // A plan that does not cover every layer exactly once still generates | |
| // fluent text — with a layer missing. It has to be refused at the door. | |
| let cover = 0; | |
| for (const s of a.plan) { | |
| if (typeof s.lo !== "number" || typeof s.hi !== "number" || s.hi < s.lo || s.lo < 0 || s.hi > a.spec.layers) | |
| throw new Error("assignment: stage range out of bounds"); | |
| cover += s.hi - s.lo; | |
| } | |
| if (cover !== a.spec.layers) | |
| throw new Error(`assignment: stages cover ${cover} of ${a.spec.layers} layers`); | |
| if (!a.plan[0].head) throw new Error("assignment: stage 0 must be the head"); | |
| if (typeof a.mine !== "number" || a.mine < 0 || a.mine >= a.plan.length) | |
| throw new Error("assignment: no valid stage index for this device"); | |
| return a; | |
| } | |
| // ---- activation ------------------------------------------------------------ | |
| // [i32 ACT, seq, tokenIdx, nextIndex][u32 actHash, modelHash][f32 hidden] | |
| // actHash is an integrity check on the payload; modelHash answers a different | |
| // question — whether this activation belongs to the model I hold a slice of. | |
| // Two rings running at once on one device is not hypothetical: reload the | |
| // head with a different checkpoint and the old stages are still out there. | |
| function packAct(seq, tokenIdx, nextIndex, hidden, hashF32, modelHash) { | |
| const buf = new ArrayBuffer(24 + hidden.byteLength); | |
| new Int32Array(buf, 0, 4).set([ACT, seq, tokenIdx, nextIndex]); | |
| new Uint32Array(buf, 16, 2).set([hashF32(hidden) >>> 0, modelHash >>> 0]); | |
| new Float32Array(buf, 24).set(hidden); | |
| return buf; | |
| } | |
| function unpackAct(buf, hashF32) { | |
| if (buf.byteLength < 24) throw new Error("act: truncated header"); | |
| const iv = new Int32Array(buf, 0, 4); | |
| const [actHash, modelHash] = new Uint32Array(buf, 16, 2); | |
| const hidden = new Float32Array(buf.slice(24)); | |
| if (hashF32 && (hashF32(hidden) >>> 0) !== actHash) throw new Error("act: payload failed its integrity hash"); | |
| return { seq: iv[1], tokenIdx: iv[2], nextIndex: iv[3], actHash, modelHash, hidden }; | |
| } | |
| // ---- routing --------------------------------------------------------------- | |
| // Where an activation goes after this stage, and how to read one that arrives. | |
| // These are two lines each and they live here, as pure functions, only | |
| // because getting them wrong is invisible everywhere else: the math is | |
| // correct, the bytes are correct, and the lap simply never closes. Neither a | |
| // numeric oracle nor a codec round-trip can see that — it is a property of | |
| // the ROUTE, so it needs something that can walk one. | |
| function routeAfter(plan, myIndex) { | |
| const isLast = myIndex === plan.length - 1; | |
| const next = isLast ? plan[0] : plan[myIndex + 1]; | |
| return { to: next.id, address: isLast ? RETURN : next.index, isReturn: isLast }; | |
| } | |
| function classifyAct(nextIndex, myIndex) { | |
| if (nextIndex === RETURN) return "return"; // the lap is finished | |
| if (nextIndex === myIndex) return "mine"; // run my blocks, pass it on | |
| return "other"; // not addressed to me | |
| } | |
| // ---- token / done ---------------------------------------------------------- | |
| function packToken(seq, id, total) { | |
| const buf = new ArrayBuffer(16); | |
| new Int32Array(buf).set([TOKEN, seq, id, total]); | |
| return buf; | |
| } | |
| function unpackToken(buf) { | |
| const iv = new Int32Array(buf, 0, 4); | |
| return { seq: iv[1], id: iv[2], total: iv[3] }; | |
| } | |
| function packDone(seq) { return new Int32Array([DONE, seq]).buffer; } | |
| // ---- ready ------------------------------------------------------------------ | |
| function packReady(stageIndex, ok, note) { | |
| const bytes = enc.encode(JSON.stringify({ stageIndex, ok: !!ok, note: String(note || "").slice(0, 300) })); | |
| const buf = new ArrayBuffer(4 + bytes.length); | |
| new Int32Array(buf, 0, 1)[0] = READY; | |
| new Uint8Array(buf, 4).set(bytes); | |
| return buf; | |
| } | |
| function unpackReady(buf) { | |
| try { return JSON.parse(dec.decode(new Uint8Array(buf, 4))); } | |
| catch (e) { throw new Error("ready: payload is not valid JSON"); } | |
| } | |
| const api = { FRAG, HELLO, ASSIGN, READY, ACT, TOKEN, DONE, RETURN, tagOf, | |
| packHello, unpackHello, packAssign, unpackAssign, packReady, unpackReady, | |
| packAct, unpackAct, packToken, unpackToken, packDone, | |
| routeAfter, classifyAct }; | |
| if (typeof module !== "undefined" && module.exports) module.exports = api; | |
| else root.Wire = api; | |
| })(typeof self !== "undefined" ? self : this); | |