Humuhumu33 commited on
Commit
3365e13
·
verified ·
1 Parent(s): 3abea9c

Upload folder using huggingface_hub

Browse files
core/agents.js ADDED
@@ -0,0 +1,74 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // core/agents.js — AGENTS as κ-objects (LibreChat agent semantics, substrate-native). An agent
2
+ // bundles identity + instructions + model + parameters + armed tools + conversation starters;
3
+ // selecting one shapes every turn (instructions ride the system block, its tool selection arms
4
+ // the MCP loop). Field names follow the LibreChat agent schema so exports interoperate; each
5
+ // version re-seals to a new κ (history is free, Law L3).
6
+
7
+ import { normPreset } from "./schema.js";
8
+
9
+ const LC = { lc: "https://librechat.ai/ns#" };
10
+
11
+ export function normAgent(a = {}) {
12
+ return {
13
+ id: typeof a.id === "string" ? a.id : null,
14
+ name: a.name || "New Agent",
15
+ description: a.description || "",
16
+ instructions: a.instructions || "",
17
+ avatar: a.avatar || null, // { filepath, source } | emoji string | null
18
+ provider: a.provider || "holo-q", // the local substrate engine
19
+ model: a.model || null, // a MODELS[] name
20
+ model_parameters: {
21
+ temperature: +(a.model_parameters?.temperature ?? 0) || 0,
22
+ max_output_tokens: +(a.model_parameters?.max_output_tokens ?? 900) || 900,
23
+ },
24
+ tools: Array.isArray(a.tools) ? a.tools : [], // armed tool names (the MCP loop selection)
25
+ mcpServerNames: Array.isArray(a.mcpServerNames) ? a.mcpServerNames : [],
26
+ conversation_starters: Array.isArray(a.conversation_starters) ? a.conversation_starters.filter(Boolean).slice(0, 4) : [],
27
+ artifacts: a.artifacts || "default", // 'default' | 'custom' (custom: instructions own the format)
28
+ recursion_limit: +(a.recursion_limit ?? 4) || 4, // max tool rounds
29
+ category: a.category || "general",
30
+ version: +(a.version ?? 1) || 1,
31
+ };
32
+ }
33
+
34
+ export function makeAgents(chatStore) {
35
+ const { store, getIndex, newId } = chatStore;
36
+ const putIndex = async (idx) => { const b = new TextEncoder().encode(JSON.stringify(idx)); return store.backend.putRaw ? store.backend.putRaw("index:org.hologram.HoloQ", b) : store.backend.put("index:org.hologram.HoloQ", b); };
37
+
38
+ async function save(a) {
39
+ const norm = normAgent(a);
40
+ if (!norm.id) norm.id = newId("agent");
41
+ const prevPtr = ((await getIndex()).agents || []).find((x) => x.id === norm.id);
42
+ if (prevPtr) norm.version = (prevPtr.version || 1) + 1;
43
+ const obj = await store.makeObject({
44
+ type: ["schema:SoftwareAgent", "prov:Entity"], context: [LC],
45
+ "schema:identifier": norm.id, "schema:name": norm.name, "schema:description": norm.description,
46
+ "lc:agent": norm,
47
+ ...(prevPtr ? { links: [{ ...store.contentLink("prov:wasRevisionOf", prevPtr.kappa, "schema:SoftwareAgent"), "schema:name": "previousVersion" }] } : {}),
48
+ });
49
+ const idx = await getIndex();
50
+ idx.agents = idx.agents || [];
51
+ const ptr = { id: norm.id, kappa: obj.id, name: norm.name, avatar: norm.avatar, category: norm.category, version: norm.version };
52
+ const i = idx.agents.findIndex((x) => x.id === norm.id);
53
+ if (i >= 0) idx.agents[i] = ptr; else idx.agents.push(ptr);
54
+ await putIndex(idx);
55
+ return { ...norm, kappa: obj.id };
56
+ }
57
+
58
+ async function list() { return (await getIndex()).agents || []; }
59
+
60
+ async function get(id) {
61
+ const ptr = (await list()).find((x) => x.id === id); if (!ptr) return null;
62
+ const obj = await store.getObj(ptr.kappa); if (!obj) return null;
63
+ if (!(await store.verify(obj))) return null; // Law L5: a tampered agent is refused
64
+ return { ...normAgent(obj["lc:agent"]), kappa: ptr.kappa };
65
+ }
66
+
67
+ async function remove(id) {
68
+ const idx = await getIndex();
69
+ idx.agents = (idx.agents || []).filter((x) => x.id !== id);
70
+ await putIndex(idx);
71
+ }
72
+
73
+ return { save, list, get, remove, normAgent };
74
+ }
core/backend.js ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // core/backend.js — the durable byte backend for the κ-object store: IndexedDB via the OS
2
+ // runtime's _shared/holo-store.js (Law L3: the store is the memory; RAM is a bounded cache).
3
+ // Keys are κ hex strings (plus the one well-known boot-index pointer key). Same async shape
4
+ // as the witness's Map backend, so the L5 contract exercises identical code in both runtimes.
5
+
6
+ import { idbBackend } from "../_shared/holo-store.js";
7
+
8
+ export function kappaBackend() {
9
+ const idb = idbBackend({ db: "holo-q", store: "kappa" });
10
+ return {
11
+ get: async (hex) => (await idb.get(hex)) || undefined,
12
+ put: (hex, bytes) => idb.set(hex, bytes),
13
+ getRaw: async (k) => (await idb.get(k)) || undefined,
14
+ putRaw: (k, bytes) => idb.set(k, bytes),
15
+ has: (k) => idb.has(k),
16
+ };
17
+ }
18
+
19
+ // Storage usage for the Settings → Data tab (W3C StorageManager).
20
+ export async function storageEstimate() {
21
+ try { const e = await navigator.storage.estimate(); return { usage: e.usage || 0, quota: e.quota || 0 }; }
22
+ catch { return { usage: 0, quota: 0 }; }
23
+ }
core/code-tools.js ADDED
@@ -0,0 +1,130 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // core/code-tools.js — NATIVE agentic-coding tools for Holo Q's Code mode. Self-contained over the
2
+ // browser's OPFS (navigator.storage.getDirectory) — no external VFS, works in any browser, 100%
3
+ // serverless. Each tool returns Holo Q's agent-tool shape { def, serverName:"code", call }, so the
4
+ // Coder-7B brain drives real file work through the existing agentic loop + conscience gate + receipts.
5
+ // The workspace is an OPFS "workspace" dir = the agent's writable home; every result is content-addressable.
6
+ //
7
+ // C2 semantic skin (extended to all κ-objects): build_app emits a schema:SoftwareApplication node and
8
+ // verify_object returns the file's schema.org @type — so every object the agent makes is W3C-legible.
9
+
10
+ import { appAsSoftware, fileAsDocument } from "./semantic.js";
11
+
12
+ const te = new TextEncoder(), td = new TextDecoder();
13
+ const sha256Hex = async (bytes) => [...new Uint8Array(await crypto.subtle.digest("SHA-256", bytes))].map((b) => b.toString(16).padStart(2, "0")).join("");
14
+ const norm = (p) => ("/" + String(p || "").replace(/^\/+/, "").replace(/\/+/g, "/")).replace(/\/$/, "") || "/";
15
+
16
+ async function root() { const d = await navigator.storage.getDirectory(); return d.getDirectoryHandle("workspace", { create: true }); }
17
+ async function dirOf(parts, create = false) {
18
+ let h = await root();
19
+ for (const p of parts) if (p) h = await h.getDirectoryHandle(p, { create });
20
+ return h;
21
+ }
22
+ function split(path) { const parts = norm(path).split("/").filter(Boolean); const name = parts.pop(); return { parts, name }; }
23
+
24
+ async function readFile(path) {
25
+ const { parts, name } = split(path);
26
+ const dir = await dirOf(parts);
27
+ const fh = await dir.getFileHandle(name);
28
+ const f = await fh.getFile();
29
+ return new Uint8Array(await f.arrayBuffer());
30
+ }
31
+ async function writeFile(path, content) {
32
+ const { parts, name } = split(path);
33
+ const dir = await dirOf(parts, true);
34
+ const fh = await dir.getFileHandle(name, { create: true });
35
+ const w = await fh.createWritable(); await w.write(te.encode(String(content ?? ""))); await w.close();
36
+ }
37
+ async function walk(dirHandle, prefix, out) {
38
+ for await (const [n, h] of dirHandle.entries()) {
39
+ const p = prefix + "/" + n;
40
+ if (h.kind === "file") out.push(p);
41
+ else await walk(h, p, out);
42
+ }
43
+ }
44
+ async function listAll() { const out = []; await walk(await root(), "", out); return out.sort(); }
45
+
46
+ function globToRe(glob) {
47
+ let re = "^";
48
+ for (let i = 0; i < glob.length; i++) { const c = glob[i];
49
+ if (c === "*") { if (glob[i + 1] === "*") { re += ".*"; i++; if (glob[i + 1] === "/") i++; } else re += "[^/]*"; }
50
+ else if (c === "?") re += "[^/]"; else if ("\\^$.|+()[]{}".includes(c)) re += "\\" + c; else re += c; }
51
+ return new RegExp(re + "$");
52
+ }
53
+
54
+ export function codeTools() {
55
+ return [
56
+ {
57
+ def: { name: "read_file", description: "Read a text file from the workspace. Returns its content.", inputSchema: { type: "object", properties: { path: { type: "string" } }, required: ["path"] } },
58
+ serverName: "code", call: async ({ path }) => {
59
+ try { const bytes = await readFile(path); return { text: td.decode(bytes), isError: false }; }
60
+ catch (e) { return { text: "read error: " + (e.message || e), isError: true }; }
61
+ },
62
+ },
63
+ {
64
+ def: { name: "write_file", description: "Create or overwrite a file in the workspace with the given content.", inputSchema: { type: "object", properties: { path: { type: "string" }, content: { type: "string" } }, required: ["path", "content"] } },
65
+ serverName: "code", call: async ({ path, content }) => {
66
+ try { await writeFile(path, content); const k = await sha256Hex(te.encode(String(content ?? ""))); return { text: `wrote ${norm(path)} (${String(content ?? "").length} bytes) · did:holo:sha256:${k.slice(0, 16)}`, isError: false }; }
67
+ catch (e) { return { text: "write error: " + (e.message || e), isError: true }; }
68
+ },
69
+ },
70
+ {
71
+ def: { name: "edit_file", description: "Replace the first exact occurrence of old_text with new_text in a file.", inputSchema: { type: "object", properties: { path: { type: "string" }, old_text: { type: "string" }, new_text: { type: "string" } }, required: ["path", "old_text", "new_text"] } },
72
+ serverName: "code", call: async ({ path, old_text, new_text }) => {
73
+ try { const cur = td.decode(await readFile(path)); if (!cur.includes(old_text)) return { text: "edit error: old_text not found", isError: true };
74
+ const next = cur.replace(old_text, new_text); await writeFile(path, next); return { text: `edited ${norm(path)} (${cur.length}→${next.length} bytes)`, isError: false }; }
75
+ catch (e) { return { text: "edit error: " + (e.message || e), isError: true }; }
76
+ },
77
+ },
78
+ {
79
+ def: { name: "list_files", description: "List all files in the workspace, optionally filtered by a glob (e.g. **/*.py).", inputSchema: { type: "object", properties: { glob: { type: "string" } }, required: [] } },
80
+ serverName: "code", call: async ({ glob }) => {
81
+ try { let files = await listAll(); if (glob) { const re = globToRe(glob); files = files.filter((f) => re.test(f)); }
82
+ return { text: files.length ? files.join("\n") : "(workspace empty)", isError: false }; }
83
+ catch (e) { return { text: "list error: " + (e.message || e), isError: true }; }
84
+ },
85
+ },
86
+ {
87
+ def: { name: "grep", description: "Search file contents for a regular expression. Returns matching path:line: text.", inputSchema: { type: "object", properties: { pattern: { type: "string" }, glob: { type: "string" } }, required: ["pattern"] } },
88
+ serverName: "code", call: async ({ pattern, glob }) => {
89
+ try { const re = new RegExp(pattern); let files = await listAll(); if (glob) { const g = globToRe(glob); files = files.filter((f) => g.test(f)); }
90
+ const hits = [];
91
+ for (const f of files) { let text; try { text = td.decode(await readFile(f)); } catch { continue; }
92
+ text.split("\n").forEach((ln, i) => { if (re.test(ln) && hits.length < 100) hits.push(`${f}:${i + 1}: ${ln.trim().slice(0, 160)}`); }); }
93
+ return { text: hits.length ? hits.join("\n") : "(no matches)", isError: false }; }
94
+ catch (e) { return { text: "grep error: " + (e.message || e), isError: true }; }
95
+ },
96
+ },
97
+ // ── κ-NATIVE: build a RENDERABLE app object, sealed (the neural-computer flagship: an agent
98
+ // produces a real app, content-addressed, that renders + re-derives — serverless, verifiable) ──
99
+ {
100
+ def: { name: "build_app", description: "Build a complete self-contained HTML app (one file, inline CSS/JS) and seal it as a content-addressed object. Returns its did:holo κ; the app renders live in the preview pane. Use for 'build me an app/page/tool' requests.", inputSchema: { type: "object", properties: { name: { type: "string" }, html: { type: "string" } }, required: ["name", "html"] } },
101
+ serverName: "code", call: async ({ name, html }) => {
102
+ try {
103
+ const path = (name.endsWith(".html") ? name : name + ".html");
104
+ await writeFile(path, html);
105
+ const bytes = te.encode(String(html));
106
+ const kappa = "did:holo:sha256:" + await sha256Hex(bytes);
107
+ // semantic skin: the app object carries a W3C @type (schema:SoftwareApplication + prov:Entity),
108
+ // written as a sidecar .jsonld so the object is self-describing to any agent / RDF tool.
109
+ const ld = appAsSoftware({ name: path, kappa, bytes: bytes.length });
110
+ await writeFile(path.replace(/\.html$/i, "") + ".app.jsonld", JSON.stringify(ld, null, 1));
111
+ return { text: `built app "${path}" (${html.length} bytes), sealed ${kappa.slice(0, 30)}… · typed ${ld["@type"].join("+")} — renders in the preview, re-derivable (Law L5).`, isError: false, render: { kind: "app", path: norm(path), kappa, html: String(html), ld } };
112
+ } catch (e) { return { text: "build_app error: " + (e.message || e), isError: true }; }
113
+ },
114
+ },
115
+ {
116
+ def: { name: "verify_object", description: "Re-derive a workspace file's content address and confirm it matches (Law L5 — verify every byte). Returns whether the bytes are authentic.", inputSchema: { type: "object", properties: { path: { type: "string" }, kappa: { type: "string" } }, required: ["path"] } },
117
+ serverName: "code", call: async ({ path, kappa }) => {
118
+ try {
119
+ const bytes = await readFile(path);
120
+ const got = "did:holo:sha256:" + await sha256Hex(bytes);
121
+ const ok = !kappa || got === kappa || got.startsWith(kappa) || kappa.startsWith(got.slice(0, kappa.length));
122
+ // verify = re-derive κ (Law L5) AND emit the W3C @type — apps are SoftwareApplication, all else DigitalDocument.
123
+ const ld = /\.html$/i.test(path) ? appAsSoftware({ name: norm(path), kappa: got, bytes: bytes.length })
124
+ : fileAsDocument({ path: norm(path), kappa: got, bytes: bytes.length });
125
+ return { text: `${norm(path)} re-derives to ${got.slice(0, 34)}…${kappa ? (ok ? " ✓ matches (authentic)" : " ✗ MISMATCH") : ""} · @type ${ld["@type"].join("+")}`, isError: false, render: { kind: "linked-data", ld } };
126
+ } catch (e) { return { text: "verify error: " + (e.message || e), isError: true }; }
127
+ },
128
+ },
129
+ ];
130
+ }
core/engine.js ADDED
@@ -0,0 +1,189 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // core/engine.js — the inference ENGINE adapter (the only module that touches the wasm
2
+ // tokenizer + the WebGPU `gpu` object). DOM-free. Wraps a model that core/loader.js has
3
+ // already loaded onto the GPU and exposes a clean, UI-agnostic API:
4
+ //
5
+ // const engine = await createEngine(modelEntry, { gpu, info, imageKappa });
6
+ // const { text, outIds } = await engine.generate(ids, { onToken, signal });
7
+ // const rec = await engine.buildReceipt({ ... }); // PROV-O, re-derivable (Law L5)
8
+ //
9
+ // The token loop, framing, memo and receipt logic are lifted byte-for-byte from the
10
+ // original index.html think()/run()/sealReceipt — only the DOM writes are replaced by an
11
+ // onToken callback and the running/handedOff flags by an AbortSignal, so output (and the
12
+ // receipt κ) is identical to the original app.
13
+
14
+ import { qvac_tokenize, qvac_continue, kappa } from "../pkg/holospaces_web.js";
15
+ import { clean, didHolo, kappaTokens, sealReceipt, verifyIntegrity, idBytes, kappaBytes } from "./kappa.js";
16
+
17
+ const _perf = () => (typeof performance !== "undefined" ? performance.now() : 0);
18
+ const _sleep = (ms) => new Promise((r) => setTimeout(r, ms));
19
+
20
+ // The engine is itself a content-addressed object — hash the wasm once (lazy).
21
+ let _engineK = null;
22
+ export async function engineKappa() {
23
+ if (_engineK) return _engineK;
24
+ try { const b = new Uint8Array(await (await fetch(new URL("../pkg/holospaces_web_bg.wasm", import.meta.url))).arrayBuffer()); _engineK = await kappaBytes(b); }
25
+ catch { _engineK = "did:holo:sha256:(engine unavailable)"; }
26
+ return _engineK;
27
+ }
28
+
29
+ export async function createEngine(modelEntry, loaded) {
30
+ const { gpu, info, imageKappa } = loaded;
31
+ const m = modelEntry;
32
+ const engineReady = engineKappa();
33
+
34
+ // model κ: the κ-disk's VERIFIED image_kappa when present (a real content address of the
35
+ // weights, every sector re-derived); else the model's declared identity.
36
+ const modelKappa = imageKappa
37
+ ? "did:holo:sha256:" + String(imageKappa).replace(/^(did:holo:)?sha256:/, "")
38
+ : await didHolo({ "@type": "schema:SoftwareSourceCode", name: m.name, size: m.size, fmt: m.fmt || "", family: m.fam || "" });
39
+
40
+ const memo = new Map();
41
+ let _drafter = null; // learned speculative drafter (fn(seq,max)=>ids); null → standard decode. Set via setDrafter().
42
+
43
+ const tokenize = (text) => { try { return JSON.parse(qvac_tokenize(text)).ids || []; } catch { return []; } };
44
+ const detokenize = (ids) => { try { return clean(JSON.parse(qvac_continue(JSON.stringify(ids), 0, 0, 0, ids.length)).text || ""); } catch { return ""; } };
45
+ const fingerprint = (ids) => kappa(idBytes(ids)); // live mind κ (blake3, from wasm)
46
+
47
+ // Frame one user turn. Qwen2/3 use ChatML (its <|im_*|> markers are atomic BPE tokens);
48
+ // other instruction models use a plain Q/A frame. (Verbatim from the original run().)
49
+ function frameTurn(prompt, hasHistory) {
50
+ if (m.qwen) {
51
+ const noThink = m.qwen3 ? "<think>\n\n</think>\n\n" : ""; // Qwen3: skip the thinking block for fast direct answers
52
+ return (hasHistory ? "<|im_end|>\n" : "") + `<|im_start|>user\n${prompt}<|im_end|>\n<|im_start|>assistant\n` + noThink;
53
+ }
54
+ if (m.llama3) // LLaMA-3 header template (BitNet b1.58 etc.)
55
+ return (hasHistory ? "<|eot_id|>" : "") + `<|start_header_id|>user<|end_header_id|>\n\n${prompt}<|eot_id|><|start_header_id|>assistant<|end_header_id|>\n\n`;
56
+ if (m.olmo) // OLMo/OLMoE: <|user|>/<|assistant|> role tags (each turn self-delimited; leading bos via m.bos)
57
+ return `<|user|>\n${prompt}\n<|assistant|>\n`;
58
+ if (m.userWord) // word-frame (Falcon-E: its ChatML template stalls EMPIRICALLY; "User:/Falcon:" answers — see q-falcon-templates sweep)
59
+ return (hasHistory ? "\n" : "") + "User: " + prompt + "\nFalcon:";
60
+ return "Question: " + prompt + "\nAnswer:";
61
+ }
62
+
63
+ function params() {
64
+ const temp = m.temp || 0;
65
+ return { decode: temp > 0 ? "sampled@t=" + temp : "greedy-argmax", maxTokens: m.cap, repetitionPenalty: m.rep ?? 1.05, template: m.qwen ? "chatml" : m.llama3 ? "llama3" : "qa", thinking: !m.qwen3 };
66
+ }
67
+
68
+ // The streaming token loop. `ids` is the whole running conversation (the mind); generation
69
+ // appends to it. onToken({ text, ids, outIds, stats }) fires per step; signal aborts.
70
+ async function generate(ids, { onToken, signal, repPenalty, maxNew } = {}) {
71
+ const rep = repPenalty ?? m.rep ?? 1.3;
72
+ const newCap = maxNew ?? m.cap ?? 80; // max NEW tokens this call
73
+ const kvCap = (m.ctx || m.cap || 80) + 8; // the engine's KV allocation (loader kvOf)
74
+ const promptLen = ids.length;
75
+ const tStart = _perf();
76
+ let first = true, decodeStart = 0, decodeTok = 0, ttft = 0, tokps = 0, msExec = 0, err = null, outText = "";
77
+ if (promptLen >= kvCap - 1) err = new Error(`context full: ${promptLen} tokens ≥ ${kvCap} KV positions`);
78
+
79
+ // SPECULATIVE PATH (opt-in): when a learned drafter is registered and the engine has the batched-verify
80
+ // head, the drafter proposes and the target batch-verifies — output is BYTE-IDENTICAL to greedy decode
81
+ // (greedy verify), streamed via onCommit. Any incompatibility/throw falls straight through to the standard
82
+ // loop below, so default Q (no drafter) and unsupported models are completely unaffected.
83
+ if (!err && _drafter && gpu.specDecode && gpu.setDrafter) {
84
+ try {
85
+ gpu.setDrafter(_drafter);
86
+ const out = []; const t0 = _perf(); let ttft2 = 0, tokps2 = 0;
87
+ const seq = await gpu.specDecode(ids.slice(), newCap, rep, (tk) => {
88
+ if (signal && signal.aborted) return false;
89
+ out.push(tk);
90
+ if (!ttft2) ttft2 = _perf() - tStart;
91
+ const dt = _perf() - t0; if (dt > 0) tokps2 = out.length / (dt / 1000);
92
+ if (onToken) onToken({ text: detokenize(out), ids: ids.slice(0, promptLen).concat(out), outIds: out.slice(), stats: { ttft: ttft2, tokps: tokps2, msExec: gpu.timing ? gpu.timing.exec : 0, gpuBytes: gpu.gpuBytes, spec: gpu.specStats ? gpu.specStats() : null } });
93
+ return out.length < newCap;
94
+ });
95
+ const outIds = seq.slice(promptLen);
96
+ let text = detokenize(outIds);
97
+ if (m.stopText) { const ix = text.indexOf(m.stopText); if (ix >= 0) text = text.slice(0, ix); }
98
+ return { text, outIds, ids: seq, stats: { ttft: ttft2, tokps: tokps2, msExec: gpu.timing ? gpu.timing.exec : 0, spec: gpu.specStats ? gpu.specStats() : null }, error: null };
99
+ } catch (e) { try { gpu.setDrafter(null); } catch {} /* fall through to the standard decode loop */ }
100
+ }
101
+
102
+ while (!err && !(signal && signal.aborted) && ids.length - promptLen < newCap && ids.length < kvCap - 1) {
103
+ const prevLen = ids.length;
104
+ try { ids = await (gpu.decode || gpu.generate)(ids, first ? 1 : 6, rep); } // batched GPU decode head (4 B/token readback) when the engine has it
105
+ catch (e) { err = e; break; }
106
+ const dn = ids.length - prevLen;
107
+ if (dn > 0) {
108
+ msExec = gpu.timing ? gpu.timing.exec : msExec;
109
+ if (first) { ttft = _perf() - tStart; decodeStart = _perf(); first = false; } // TTFT = prefill + first token
110
+ else { decodeTok += dn; const dt = _perf() - decodeStart; if (dt > 0) tokps = decodeTok / (dt / 1000); } // steady decode rate
111
+ }
112
+ const di = ids.slice(promptLen);
113
+ // incremental detokenize: decode only a bounded window (the dn new tokens + a small left context)
114
+ // and append the delta — O(1) per step, not re-detokenizing the whole growing output (was O(n²)).
115
+ { const a = Math.max(promptLen, ids.length - (dn + 8)); const wf = detokenize(ids.slice(a)); const wp = a >= ids.length - dn ? "" : detokenize(ids.slice(a, ids.length - dn)); outText += wf.slice(wp.length); }
116
+ let text = outText, hitStop = false;
117
+ if (m.stopText) { const ix = text.indexOf(m.stopText); if (ix >= 0) { text = text.slice(0, ix); hitStop = true; } } // word-framed models stop on the next "User:" turn
118
+ if (onToken) onToken({ text, ids: ids.slice(), outIds: di.slice(), stats: { ttft, tokps, msExec, gpuBytes: gpu.gpuBytes } });
119
+ if (hitStop) break;
120
+ if (ids.length <= prevLen) break; // EOS / no progress
121
+ // degeneration guard: a long run of one repeated character (the repetition collapse of
122
+ // small/experimental quants) will never recover — stop instead of burning the budget.
123
+ if (text.length > 80 && /(.)\1{63}$/.test(text)) { err = new Error("degenerate repetition — stopped"); break; }
124
+ await _sleep(0); // yield to the event loop so the UI can paint — no artificial throttle (gpu.decode already awaits the GPU)
125
+ }
126
+ const outIds = ids.slice(promptLen);
127
+ let text = detokenize(outIds);
128
+ if (m.stopText) { const ix = text.indexOf(m.stopText); if (ix >= 0) text = text.slice(0, ix); }
129
+ return { text, outIds, ids, stats: { ttft, tokps, msExec }, error: err };
130
+ }
131
+
132
+ // DIFFUSION decode (Dream-class): iterative bidirectional unmasking over a fixed `steps` budget,
133
+ // wall-clock fixed by steps not output length. Greedy ⇒ deterministic ⇒ κ-re-derivable (Law L5).
134
+ // `ids` is the framed prompt; we diffuse `genLen` masked positions after it. Returns the same shape
135
+ // as generate() so callers (and the brain seam) are agnostic. onToken fires ONCE with the final fill
136
+ // (diffusion has no left-to-right token stream — the whole block resolves together).
137
+ // Two modes: APPEND (genLen masks at the suffix — generation) or FILL (ids ALREADY contain mask ids
138
+ // anywhere → infill/surgical edit, conditioning on BOTH sides; diffusion's structural edge over AR).
139
+ // `causal` flips the parity gate (causal block=1 must equal the sequential engine — validates the pass).
140
+ async function diffuse(ids, { genLen, steps, fill, causal, signal, onToken } = {}) {
141
+ if (!gpu || !gpu.diffuse) throw new Error("this model has no diffusion engine (load a diffusion κ-object)");
142
+ const gl = fill ? 0 : (genLen ?? Math.min(m.cap || 64, (m.ctx || 192) - ids.length - 1));
143
+ const S = steps ?? m.steps ?? 12;
144
+ const tStart = _perf();
145
+ const seq = await gpu.diffuse(ids, gl, { steps: S, fill: !!fill, causal: !!causal, signal });
146
+ // append → output is the generated suffix; fill → the whole sequence is the answer (a span edited in place)
147
+ const outIds = fill ? seq.slice() : seq.slice(ids.length);
148
+ let text = detokenize(outIds);
149
+ if (m.stopText && !fill) { const ix = text.indexOf(m.stopText); if (ix >= 0) text = text.slice(0, ix); }
150
+ const stats = { ttft: _perf() - tStart, tokps: 0, msExec: gpu.timing ? gpu.timing.exec : 0, steps: S, fill: !!fill, diff: gpu.diffStats ? gpu.diffStats() : null };
151
+ if (onToken) onToken({ text, ids: seq.slice(), outIds: outIds.slice(), stats });
152
+ return { text, outIds, ids: seq, stats, error: null };
153
+ }
154
+
155
+ // κ-memo: identical (context ⊕ prompt ⊕ model ⊕ params) → replay in O(1), no decode.
156
+ const memoKey = async (ctxIds, turnIds, p) => didHolo({ ctx: await kappaTokens(ctxIds.concat(turnIds)), model: modelKappa, params: p || params() });
157
+
158
+ async function buildReceipt({ promptText, ctxIds, turnIds, outIds, fromMemo, evaluateText, paramsPatch, extraUsed }) {
159
+ return sealReceipt({
160
+ promptText, ctxIds, turnIds, outIds, text: detokenize(outIds), params: { ...params(), ...(paramsPatch || {}) }, fromMemo,
161
+ modelKappa, engineKappa: await engineReady, evaluateText, extraUsed,
162
+ });
163
+ }
164
+
165
+ // Re-derivation (greedy only): re-run the exact inference and reproduce κ(output) byte-for-byte.
166
+ async function reDerive(rec) {
167
+ if (!gpu) return { ok: false, reason: "load the model to re-derive" };
168
+ if (/sampled/.test(rec.params.decode)) return { ok: false, reason: "sampled decode — only the κ-binding is verifiable, not re-derivation" };
169
+ try {
170
+ let seq = rec.ctxIds.concat(rec.turnIds); const start = seq.length;
171
+ gpu.reset();
172
+ seq = await (gpu.decode || gpu.generate)(seq, rec.outIds.length, rec.params.repetitionPenalty); // same head as the live path — replay must match byte-for-byte
173
+ const got = await kappaTokens(seq.slice(start)), want = rec.body["prov:generated"]["holo:outputTokens"];
174
+ return { ok: got === want, got, want };
175
+ } finally { try { gpu.reset(); } catch {} }
176
+ }
177
+
178
+ return {
179
+ model: m, dims: gpu.dims, modelKappa, bosId: info?.bos ?? null, get gpuBytes() { return gpu.gpuBytes; },
180
+ tokenize, detokenize, fingerprint, frameTurn, params,
181
+ generate,
182
+ // register/clear the learned speculative drafter (fn(seq,max)=>ids). Off by default; safe fallback.
183
+ setDrafter: (fn) => { _drafter = fn || null; try { gpu.setDrafter && gpu.setDrafter(_drafter); } catch (e) {} },
184
+ specAvailable: !!(gpu.specDecode && gpu.setDrafter),
185
+ memoKey, memoGet: (k) => memo.get(k), memoHas: (k) => memo.has(k), memoSet: (k, v) => memo.set(k, v),
186
+ buildReceipt, verify: verifyIntegrity, reDerive,
187
+ stats: () => gpu.timing, reset: () => { try { gpu.reset(); } catch {} }, destroy: () => { try { gpu.destroy(); } catch {} },
188
+ };
189
+ }
core/kappa.js ADDED
@@ -0,0 +1,76 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // core/kappa.js — the PURE content-addressing + receipt layer (no wasm, no DOM).
2
+ //
3
+ // Lifted verbatim (behaviour-identical) from the original Holo Q index.html so that
4
+ // BOTH the browser app and the pure-Node witness (tools/q-witness.mjs) import the same
5
+ // re-derivation logic. Uses only Web Crypto (crypto.subtle), which exists in the browser
6
+ // and in Node ≥ 20 as globalThis.crypto.subtle — so a receipt's κ re-derives identically
7
+ // in either runtime (Law L5). The PROV-O receipt body and its did:holo are byte-for-byte
8
+ // what the original sealed, so existing receipts keep verifying.
9
+
10
+ const _enc = new TextEncoder();
11
+ const _td = new TextDecoder();
12
+
13
+ // RFC 8785 JCS — canonical JSON, so a receipt's κ re-derives identically anywhere.
14
+ export const jcs = (v) => Array.isArray(v) ? "[" + v.map(jcs).join(",") + "]"
15
+ : (v && typeof v === "object") ? "{" + Object.keys(v).sort().map((k) => JSON.stringify(k) + ":" + jcs(v[k])).join(",") + "}"
16
+ : JSON.stringify(v);
17
+
18
+ export async function sha256hex(u8) {
19
+ const d = await crypto.subtle.digest("SHA-256", u8);
20
+ return Array.from(new Uint8Array(d), (b) => b.toString(16).padStart(2, "0")).join("");
21
+ }
22
+
23
+ export const idBytes = (ids) => new Uint8Array(new Uint32Array(ids).buffer);
24
+
25
+ export const didHolo = async (obj) => "did:holo:sha256:" + await sha256hex(_enc.encode(jcs(obj)));
26
+ export const kappaText = async (s) => "did:holo:sha256:" + await sha256hex(_enc.encode(s || ""));
27
+ export const kappaTokens = async (a) => "did:holo:sha256:" + await sha256hex(idBytes(a)); // the answer's tokens, by content
28
+ export const kappaBytes = async (u8) => "did:holo:sha256:" + await sha256hex(u8);
29
+
30
+ export const shortK = (k) => {
31
+ const s = String(k || ""); const ax = s.split(":").slice(0, 2).join(":"); const h = s.split(":").pop();
32
+ return ax + ":" + (h.length > 18 ? h.slice(0, 12) + "…" + h.slice(-4) : h);
33
+ };
34
+
35
+ // SentencePiece byte-fallback tokens (<0xNN>) → their actual bytes, UTF-8 decoded.
36
+ export const decodeBytes = (t) => t.replace(/(?:<0x[0-9A-Fa-f]{2}>)+/g, (run) => {
37
+ const b = []; run.replace(/<0x([0-9A-Fa-f]{2})>/g, (_, h) => (b.push(parseInt(h, 16)), ""));
38
+ try { return _td.decode(new Uint8Array(b)); } catch { return ""; }
39
+ });
40
+ export const clean = (t) => decodeBytes(
41
+ t.replace(/<think>[\s\S]*?<\/think>/g, "").replace(/<\/?think>/g, "").replace(/<\|[^>]*\|>/g, "").replace(/<unk>|<s>|<\/s>/g, "")
42
+ ).replace(/^\s+/, "");
43
+
44
+ // ── verifiable-inference receipt ────────────────────────────────────────────────
45
+ // An answer is a content-addressed, re-derivable transform on the substrate:
46
+ // κ(context) ⊕ κ(prompt) ⊕ κ(model) ⊕ κ(params) ⊕ κ(engine) → κ(output)
47
+ // sealed as a PROV-O receipt with its OWN did:holo. Greedy decode is deterministic,
48
+ // so anyone re-runs it and reproduces κ(output) byte for byte (Law L5).
49
+
50
+ // Build the canonical PROV-O receipt body. All κ inputs are already did:holo strings.
51
+ // `extraUsed` merges additional provenance (e.g. holo:toolReceipts — the agentic work-trail).
52
+ export function receiptBody({ modelKappa, engineKappa, promptKappa, contextKappa, outputKappa, tokenCount, params, conscience, extraUsed }) {
53
+ return {
54
+ "@context": ["http://www.w3.org/ns/prov#", { holo: "https://hologram.os/ns/q#" }],
55
+ "@type": "prov:Activity", "holo:kind": "verifiable-inference",
56
+ "prov:used": { "holo:model": modelKappa, "holo:engine": engineKappa, "holo:prompt": promptKappa, "holo:context": contextKappa, "holo:params": params, ...(extraUsed || {}) },
57
+ "prov:generated": { "holo:outputTokens": outputKappa, "holo:tokenCount": tokenCount },
58
+ "holo:conscience": conscience || { outcome: "unverified" },
59
+ };
60
+ }
61
+
62
+ // Seal a receipt: compute the κ inputs from raw tokens/text, assemble the body, address it.
63
+ // `text` is the already-decoded answer; `evaluateText` (optional) is the conscience judge.
64
+ export async function sealReceipt({ promptText, ctxIds, turnIds, outIds, text, params, fromMemo, modelKappa, engineKappa, evaluateText, extraUsed }) {
65
+ const [promptKappa, contextKappa, outputKappa] = await Promise.all([
66
+ kappaText(promptText), kappaTokens(ctxIds.concat(turnIds)), kappaTokens(outIds),
67
+ ]);
68
+ let conscience = { outcome: "unverified" };
69
+ try { if (evaluateText) { const v = evaluateText(text); conscience = { outcome: v.outcome, blocked: v.blocked || [], caveats: v.caveats || [], sealed: v.sealed !== false }; } } catch {}
70
+ const body = receiptBody({ modelKappa, engineKappa, promptKappa, contextKappa, outputKappa, tokenCount: outIds.length, params, conscience, extraUsed });
71
+ const id = await didHolo(body);
72
+ return { id, body, text, promptText, ctxIds: ctxIds.slice(), turnIds: turnIds.slice(), outIds: outIds.slice(), params, fromMemo: !!fromMemo };
73
+ }
74
+
75
+ // Integrity: recompute the receipt's did:holo from its body — tamper any byte and it won't match.
76
+ export const verifyIntegrity = async (rec) => { const again = await didHolo(rec.body); return { ok: again === rec.id, again }; };
core/keymap.js ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // core/keymap.js — the keyboard map as a data table (LibreChat's native set): Alt+N new chat,
2
+ // Alt+S toggle sidebar, Alt+W focus the input, Ctrl/⌘+Enter send, Esc stop generation,
3
+ // Ctrl/⌘+K model menu. `/` and `@` composer menus live in ui/composer.js (they are caret-local).
4
+
5
+ export const BINDINGS = [
6
+ { combo: "Alt+N", action: "newChat", label: "New chat" },
7
+ { combo: "Alt+S", action: "toggleNav", label: "Toggle sidebar" },
8
+ { combo: "Alt+W", action: "focusInput", label: "Focus message box" },
9
+ { combo: "Ctrl+Enter", action: "send", label: "Send message" },
10
+ { combo: "Escape", action: "stop", label: "Stop generating" },
11
+ { combo: "Ctrl+K", action: "modelMenu", label: "Choose model" },
12
+ ];
13
+
14
+ export function installKeymap(handlers) {
15
+ const onKey = (e) => {
16
+ const ctrl = e.ctrlKey || e.metaKey;
17
+ const fire = (name) => { const fn = handlers[name]; if (fn) { e.preventDefault(); fn(e); } };
18
+ if (e.altKey && !ctrl && !e.shiftKey) {
19
+ if (e.code === "KeyN") return fire("newChat");
20
+ if (e.code === "KeyS") return fire("toggleNav");
21
+ if (e.code === "KeyW") return fire("focusInput");
22
+ }
23
+ if (ctrl && e.key === "Enter") return fire("send");
24
+ if (ctrl && !e.shiftKey && e.code === "KeyK") return fire("modelMenu");
25
+ if (e.key === "Escape") { const fn = handlers.stop; if (fn) fn(e); } // don't preventDefault — Esc also closes menus
26
+ };
27
+ document.addEventListener("keydown", onKey);
28
+ return () => document.removeEventListener("keydown", onKey);
29
+ }
core/loader.js ADDED
@@ -0,0 +1,204 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // core/loader.js — model LOADING (the 5 substrate paths) + the model catalog + the
2
+ // browser-cache manager. Lifted faithfully from the original index.html so a model still
3
+ // loads byte-identically; the only change is that DOM status writes become onStatus/onProgress
4
+ // callbacks, and each path RETURNS { gpu, info, manifest, imageKappa } instead of mutating
5
+ // globals. core/engine.js then wraps the returned gpu. (window.__gpu / window.__kd handles are
6
+ // still exposed for the probe + system-monitor panels.)
7
+
8
+ import init, { kappa, qvac_load_model, qvac_load_gpu, qvac_tokenize, qvac_continue, qvac_gpu_manifest, qvac_gpu_tensor, qvac_gpu_free, qvac_panic_hook } from "../pkg/holospaces_web.js";
9
+ import { createQvacGPU } from "../qvac-gpu.js?v=60";
10
+ import { modelAsSource } from "./semantic.js"; // C2: a loaded model carries a W3C @type (schema:SoftwareSourceCode)
11
+
12
+ // the model κ-object's W3C linked-data view — content-addressed identity (Law L1) + schema.org type.
13
+ const modelLinkedData = (m, root) => modelAsSource({
14
+ name: m.name, family: m.fam, params: m.size, format: m.fmt,
15
+ kappa: root ? (String(root).startsWith("did:") ? root : "did:holo:" + String(root)) : "did:holo:sha256:0",
16
+ });
17
+
18
+ // The compiled κ-objects present on disk (models/<name>, built by compile2bit.mjs). Each loads
19
+ // DIRECT off the substrate (verified by re-derivation, no re-quant) via its `kappaUrl`.
20
+ // cap = max NEW tokens per turn; ctx = KV-cache positions allocated on the GPU (the context
21
+ // window — sized so agentic turns with tool schemas + tool responses fit; KV VRAM scales with it).
22
+ export const MODELS = [
23
+ // NATIVELY-TERNARY κ-objects (t2, 1.58 bpw trained-in — see the atlas-bridge witness receipts):
24
+ // Falcon-E: its declared ChatML template STALLS empirically (instant <|end_of_text|>); the
25
+ // measured working frame is word-style "User:/Falcon:" with a textual stop (q-falcon-templates sweep).
26
+ { fam: "Falcon-E", name: "Falcon-E-3B · ternary", kappaUrl: "https://huggingface.co/HOLOGRAMTECH/q-falcon-e-3b/resolve/main", manifestKappa: "did:holo:sha256:6b753fe8186f2b4194424115c36014698580a2aab8427e9b40365893ac6b77ca", size: "0.63 GB", fmt: "t2 1.58-bit κ", cap: 200, ctx: 3000, kv4: true, gpu: true, gpuOnly: true, chat: true, userWord: true, stopText: "\nUser:", tools: false, rep: 1.18, kappa: true },
27
+ { fam: "BitNet", name: "BitNet-2B-4T · ternary", kappaUrl: "https://huggingface.co/HOLOGRAMTECH/q-bitnet-2b/resolve/main", manifestKappa: "did:holo:sha256:fcf835659d88d2fe6f683cf1ab8de6a6ba6214ea0deeee4b1bcf3da1a4c05412", size: "0.69 GB", fmt: "t2 1.58-bit κ", cap: 900, ctx: 3000, kv4: true, gpu: true, gpuOnly: true, chat: true, llama3: true, tools: false, bos: true, eosText: "<|eot_id|>", rep: 1.05, kappa: true },
28
+ // TriLM: the LARGEST natively-ternary-trained model (Spectra 3.9B, ICLR'25); per-row/channel
29
+ // scale structure → t2r (trit codes + per-256-block scales, exact). BASE model → QA frame + stop.
30
+ { fam: "TriLM", name: "TriLM-3.9B · ternary", kappaUrl: "https://huggingface.co/HOLOGRAMTECH/q-trilm-3.9b/resolve/main", manifestKappa: "did:holo:sha256:499032ceb19c0476345a72cf5fea6caec83054c98486c91a5891dfad0d25ea30", size: "0.87 GB", fmt: "t2r 2.1-bit κ", cap: 200, ctx: 3000, kv4: true, gpu: true, gpuOnly: true, chat: true, stopText: "\nQuestion:", tools: false, rep: 1.18, kappa: true },
31
+ // AGENTIC CODER: Qwen2.5-Coder-7B (q3f) — the Holo Code agent brain. Qwen2.5 arch ⇒ ChatML +
32
+ // agentic tool framing work (capability floor for tool use is ~7B; the small ternary models opt out).
33
+ // Self-contained κ-object: tokenizer bundled (source="tokenizer.gguf"), no external dependency.
34
+ { fam: "Qwen2.5-Coder", name: "Qwen2.5-Coder-7B · agentic", kappaUrl: "https://huggingface.co/HOLOGRAMTECH/q-qwen-coder-7b/resolve/main", manifestKappa: "did:holo:sha256:539941cb060c7dd583e2e86697e53f2c5d511d597c65d09d9c780fbded2c3edf", size: "3.4 GB", fmt: "q3f κ", cap: 900, ctx: 3000, kv4: true, gpu: true, gpuOnly: true, chat: true, code: true, qwen: true, rep: 1.05, kappa: true },
35
+ // MIXTURE-OF-EXPERTS (G5): OLMoE-1B-7B (Allen AI, Apache-2.0) — 64 experts, 8 active/token, ~1.3B
36
+ // active of 7B. The first RESIDENT-MoE κ-object: experts RAM-resident + CPU top-k router (softmax
37
+ // over all 64, no renorm = OLMoE norm_topk_prob:false). q4 (the engine's resident expert FFN path).
38
+ { fam: "OLMoE", name: "OLMoE-1B-7B · MoE (64×8)", kappaUrl: "https://huggingface.co/HOLOGRAMTECH/q-olmoe-1b-7b/resolve/main", manifestKappa: "did:holo:sha256:9cf97ec1c761fd4ef51bc0cd4ac37a0cd8eaa11f1b19b3ae6a141486ad3fe5ad", size: "3.6 GB", fmt: "q4 MoE κ", cap: 400, ctx: 3000, kv4: false, gpu: true, gpuOnly: true, chat: true, olmo: true, bos: true, eosText: "<|endoftext|>", tools: false, rep: 1.1, kappa: true },
39
+ // DIFFUSION (G6): Dream-7B (Dream-org/Dream-v0-Instruct-7B) — masked-diffusion LM on the Qwen2.5-7B
40
+ // backbone (same dims ⇒ ChatML). NOT autoregressive: generation is iterative bidirectional unmasking
41
+ // over `steps` denoising passes (engine.diffuse / gpu.diffuse), wall-clock fixed by steps not length.
42
+ // maskId 151666 rides in the manifest (never tokenized from text). Greedy ⇒ deterministic ⇒ κ-re-derivable.
43
+ { fam: "Dream", name: "Dream-7B · diffusion", kappaUrl: "https://huggingface.co/HOLOGRAMTECH/q-dream-7b/resolve/main", manifestKappa: "did:holo:sha256:7b862931ae088f348f1f7e9ea3adbd418924c2e07e6ddd134f926e5681ad760d", size: "2.9 GB", fmt: "q3f diffusion κ", cap: 192, ctx: 192, kv4: false, gpu: true, gpuOnly: true, chat: true, qwen: true, diffusion: true, steps: 12, rep: 1.0, kappa: true },
44
+ // Qwen κ-objects (q3f/q4) were pruned from disk for space — re-derive via compile2bit, then re-list.
45
+ ];
46
+ const kvOf = (m) => Math.max(96, (m.ctx || m.cap) + 8);
47
+
48
+ const _sizeGb = (s) => { const n = parseFloat(s) || 0; return /mb/i.test(s) ? n / 1024 : n; };
49
+ // default to the SMALLEST usable model — lowest latency, fastest first answer.
50
+ export const defaultModelIndex = () => (MODELS.map((m, i) => i).filter((i) => !MODELS[i].disabled).sort((a, b) => _sizeGb(MODELS[a].size) - _sizeGb(MODELS[b].size))[0]) ?? 0;
51
+
52
+ // ── wasm init (once) + tokenizer re-export so the rest of the app shares this instance ──
53
+ let _initOnce = null;
54
+ export function ready() { if (!_initOnce) _initOnce = init().then(() => { try { qvac_panic_hook(); } catch {} }); return _initOnce; }
55
+ export { qvac_tokenize, qvac_continue, kappa };
56
+
57
+ // ── browser-cache model manager (Cache API) — "Get" downloads + keeps; loading uses the copy ──
58
+ export const MCACHE = "holo-q-models";
59
+ const absUrl = (u) => new URL(u, location.href).href;
60
+ let _cachedUrls = new Set();
61
+ export async function refreshCached() { try { const c = await caches.open(MCACHE); _cachedUrls = new Set((await c.keys()).map((r) => r.url)); } catch { _cachedUrls = new Set(); } return _cachedUrls; }
62
+ export const isCached = (m) => !!m.url && _cachedUrls.has(absUrl(m.url));
63
+ export async function deleteCache(m) { try { const c = await caches.open(MCACHE); await c.delete(m.url); } catch {} await refreshCached(); }
64
+ async function modelBytes(m, onStatus) {
65
+ try { const c = await caches.open(MCACHE); const hit = await c.match(m.url); if (hit) return new Uint8Array(await hit.arrayBuffer()); } catch {}
66
+ onStatus?.(`Downloading ${m.name} (${m.size})…`);
67
+ const res = await fetch(m.url); if (!res.ok) { onStatus?.("download failed: HTTP " + res.status); return null; }
68
+ return new Uint8Array(await res.arrayBuffer());
69
+ }
70
+
71
+ const noop = () => {};
72
+
73
+ // loadModel(entry, { onStatus, onProgress }) → { gpu, info, manifest, imageKappa } | null
74
+ // `imageKappa` is the VERIFIED content address of the weights when the path provides one
75
+ // (κ-object root, or κ-disk image_kappa); core/engine.js binds it as the receipt's model κ.
76
+ export async function loadModel(m, { onStatus = noop, onProgress = noop } = {}) {
77
+ await ready();
78
+ onStatus(`Loading ${m.name}…`);
79
+ try {
80
+ if (m.gpuOnly && !navigator.gpu) { onStatus("This model needs WebGPU (not available here)."); return null; }
81
+ if (m.kappaUrl) return await loadKappa(m, onStatus, onProgress);
82
+ if (m.kdisk) return await loadModelKDisk(m, onStatus, onProgress);
83
+ if (m.remote) return await loadModelRemote(m, onStatus, onProgress);
84
+ if (m.diskIngest) return await loadModelDisk(m, onStatus, onProgress);
85
+ let gguf = await modelBytes(m, onStatus); if (!gguf) { onStatus("could not load model"); return null; }
86
+ const lr = JSON.parse(m.gpuOnly ? qvac_load_gpu(gguf) : qvac_load_model(gguf));
87
+ gguf = null;
88
+ if (lr.error) { onStatus("model error: " + lr.error); return null; }
89
+ let gpu = null, manifest = null;
90
+ if (navigator.gpu && m.gpu) {
91
+ try {
92
+ onStatus(`Uploading ${m.name} to the GPU…`);
93
+ const bits = m.q4 ? 4 : 8;
94
+ manifest = JSON.parse(qvac_gpu_manifest(bits)); manifest.twoBit = !!window.__twoBit;
95
+ const __qp = new URLSearchParams(location.search).get("stream");
96
+ const __qmode = __qp === null ? undefined : (__qp === "resident" || __qp === "false" ? false : __qp);
97
+ const stream = __qmode ?? window.__stream ?? m.stream ?? false;
98
+ const __ft = (name) => { const raw = qvac_gpu_tensor(name, bits); return window.__weightHook ? window.__weightHook(name, raw, bits, manifest) : raw; };
99
+ gpu = await createQvacGPU(manifest, __ft, kvOf(m), lr.eos ?? 2, stream);
100
+ window.__gpu = gpu; qvac_gpu_free();
101
+ } catch (e) { gpu = null; if (m.gpuOnly) { onStatus("GPU upload failed: " + e); return null; } }
102
+ }
103
+ onStatus("");
104
+ return { gpu, info: lr, manifest, imageKappa: null };
105
+ } catch (e) { onStatus("could not load model: " + e); return null; }
106
+ }
107
+
108
+ // LOAD-DIRECT: a pre-compiled 2-bit/Q4 κ-object (compile2bit.mjs output). Weights arrive ALREADY
109
+ // quantized (no re-quant at load); the tokenizer comes from the source GGUF's header only.
110
+ async function loadKappa(m, onStatus, onProgress) {
111
+ onStatus("Loading κ-object manifest…");
112
+ const ld = await import("../holo-load2bit.mjs");
113
+ // Law L5: pin the manifest κ when the catalog supplies one (m.manifestKappa, or a string m.kappa).
114
+ // Until every model carries a pin, unpinned entries load explicitly (allowUnpinned) — the gap is then
115
+ // a visible data task (populate manifestKappa), not a silent trust of an unauthenticated root.
116
+ const pin = (typeof m.manifestKappa === "string" && m.manifestKappa) || (typeof m.kappa === "string" && m.kappa) || null;
117
+ const { manifest, fetchTensor, info } = await ld.loadKappaObject(m.kappaUrl.replace(/\/+$/, ""), pin ? { expectKappa: pin } : { allowUnpinned: true });
118
+ const ing = await import("../qvac-ingest.mjs");
119
+ onStatus("Building tokenizer (source header, no full download)…");
120
+ const hdr = await ing.readHeader(info.source, ing.rangeReader());
121
+ const lr = JSON.parse(qvac_load_gpu(hdr.headerBytes));
122
+ if (lr.error) { onStatus("tokenizer error: " + lr.error); return null; }
123
+ if (m.eosText) { try { const e = JSON.parse(qvac_tokenize(m.eosText)).ids; if (e && e.length === 1) lr.eos = e[0]; } catch {} } // chat-stop override (e.g. LLaMA-3 <|eot_id|> ≠ header eos)
124
+ qvac_gpu_free();
125
+ manifest.kv4 = !!m.kv4; // int4 KV cache (E6) — catalog opt-in
126
+ // MoE forward reads the layer-packed attention (Wb[l]) + RAM-resident experts (readExpert via
127
+ // fetchTensor) — i.e. stream="layer": attention JS-resident & paged per token, experts cached.
128
+ const sm = manifest.moe ? "layer" : (m.stream || window.__kappaStream || false);
129
+ onStatus(`Building engine from κ-object (${info.mode === "q4" ? "native Q4" : info.incoherent ? "incoherent 2-bit" : "LDLQ 2-bit"}, ${sm || "resident"}, no requant)…`);
130
+ const prog = (done, total) => onProgress(done, total, "streaming");
131
+ const gpu = await createQvacGPU(manifest, fetchTensor, kvOf(m), lr.eos ?? 2, sm, sm ? prog : null);
132
+ window.__gpu = gpu;
133
+ onStatus("");
134
+ return { gpu, info: lr, manifest, imageKappa: info.root || null, ld: modelLinkedData(m, info.root) };
135
+ }
136
+
137
+ // Very-large-model path: the GGUF never enters wasm; only the header does (tokenizer + manifest),
138
+ // then each tensor is streamed off disk (HTTP Range), converted in JS, paged to the GPU per layer.
139
+ async function loadModelDisk(m, onStatus, onProgress) {
140
+ const bits = m.q4 ? 4 : 8;
141
+ const ing = await import("../qvac-ingest.mjs");
142
+ let read = ing.rangeReader();
143
+ try { const cachedResp = await (await caches.open(MCACHE)).match(m.url); if (cachedResp) { const blob = await cachedResp.blob(); read = async (_u, start, len) => new Uint8Array(await blob.slice(start, start + len).arrayBuffer()); } } catch {}
144
+ onStatus(`Reading ${m.name} header…`);
145
+ const hdr = await ing.readHeader(m.url, read);
146
+ const lr = JSON.parse(qvac_load_gpu(hdr.headerBytes));
147
+ if (lr.error) { onStatus("model error: " + lr.error); return null; }
148
+ const manifest = JSON.parse(qvac_gpu_manifest(bits));
149
+ qvac_gpu_free();
150
+ const fetchTensor = ing.makeDiskFetcher({ url: m.url, readRange: read, dataOffset: hdr.dataOffset, tensors: hdr.tensors, manifest, bits });
151
+ const mode = m.stream || "layer";
152
+ onStatus(`Preparing ${m.name} (one-time, streamed off disk)…`);
153
+ const gpu = await createQvacGPU(manifest, fetchTensor, kvOf(m), lr.eos ?? 2, mode, (d, t) => onProgress(d, t, "layers"));
154
+ window.__gpu = gpu; onStatus("");
155
+ return { gpu, info: lr, manifest, imageKappa: null };
156
+ }
157
+
158
+ // Out-of-core: stream a PRE-BUILT .qvf frames file from the server, one layer per token via HTTP Range.
159
+ async function loadModelRemote(m, onStatus, onProgress) {
160
+ onStatus(`Loading ${m.name} index…`);
161
+ const index = await (await fetch(m.framesUrl + ".json")).json();
162
+ const url = m.framesUrl;
163
+ const rr = async (off, len) => { const r = await fetch(url, { headers: { Range: `bytes=${off}-${off + len - 1}` } }); if (!r.ok && r.status !== 206) throw new Error("HTTP " + r.status); return new Uint8Array(await r.arrayBuffer()); };
164
+ const header = await rr(index.headerOff, index.headerLen);
165
+ const lr = JSON.parse(qvac_load_gpu(header));
166
+ if (lr.error) { onStatus("model error: " + lr.error); return null; }
167
+ const manifest = index.manifest; qvac_gpu_free();
168
+ const fetchTensor = async (name) => { const s = index.singles[name]; return s ? await rr(s.off, s.len) : new Uint8Array(0); };
169
+ const frameStore = { ready: true, read: (off, len) => rr(index.layersOff + off, len), readExpert: (l, e, role) => { const ri = { gate: 0, up: 1, down: 2 }[role]; const off = index.expertsOff + ((l * index.nExperts + e) * 3 + ri) * index.expertBytes; return rr(off, index.expertBytes); } };
170
+ const layersBytes = (index.packStride || 0) * (index.n_layers || 0) + (manifest.moe ? (index.nExperts * 3 * index.expertBytes * index.n_layers) : 0);
171
+ const cacheBudget = window.__cacheGB != null ? window.__cacheGB * 1073741824 : Math.min(layersBytes, 12 * 1073741824);
172
+ onStatus(`Preparing ${m.name} (served off disk)…`);
173
+ const gpu = await createQvacGPU(manifest, fetchTensor, kvOf(m), lr.eos ?? 2, "remote", (d, t) => onProgress(d, t, "remote"), frameStore, cacheBudget);
174
+ window.__gpu = gpu; onStatus("");
175
+ return { gpu, info: lr, manifest, imageKappa: null };
176
+ }
177
+
178
+ // HOLOGRAM: load through a content-addressed κ-DISK — every sector VERIFIED by re-derivation (Law L3/L5).
179
+ async function loadModelKDisk(m, onStatus, onProgress) {
180
+ onStatus(`Resolving ${m.name} κ-disk…`);
181
+ const index = await (await fetch(m.kdiskUrl)).json();
182
+ const { makeKDisk } = await import("../qvac-kdisk.mjs");
183
+ const bases = window.__kdiskSources || m.kdiskSources || [location.origin];
184
+ const sources = bases.map((b) => b.replace(/\/$/, "") + "/" + (index.dataFile || (m.dataUrl || "").replace(/^\.\//, "")));
185
+ const kd = makeKDisk({ index, sources });
186
+ window.__kd = kd;
187
+ const iv = await kd.verifyImage();
188
+ if (!iv.ok) { onStatus("κ-disk image_kappa mismatch — refusing to load"); return null; }
189
+ const rr = kd.rr, qvf = index.qvf;
190
+ const header = await rr(qvf.headerOff, qvf.headerLen);
191
+ const lr = JSON.parse(qvac_load_gpu(header));
192
+ if (lr.error) { onStatus("model error: " + lr.error); return null; }
193
+ const manifest = qvf.manifest; qvac_gpu_free();
194
+ const fetchTensor = async (name) => { const s = qvf.singles[name]; return s ? await rr(s.off, s.len) : new Uint8Array(0); };
195
+ const frameStore = { ready: true, read: (off, len) => rr(qvf.layersOff + off, len),
196
+ readExpert: async (l, e, role) => { const ri = { gate: 0, up: 1, down: 2 }[role]; const blkOff = qvf.expertsOff + (l * qvf.nExperts + e) * 3 * qvf.expertBytes; const blk = await rr(blkOff, 3 * qvf.expertBytes); return blk.slice(ri * qvf.expertBytes, (ri + 1) * qvf.expertBytes); } };
197
+ const layersBytes = (qvf.packStride || 0) * (qvf.n_layers || 0) + (manifest.moe ? (qvf.nExperts * 3 * qvf.expertBytes * qvf.n_layers) : 0);
198
+ const cacheBudget = window.__cacheGB != null ? window.__cacheGB * 1073741824 : Math.min(layersBytes, 1024 * 1048576);
199
+ onStatus(`Realizing ${m.name} (verified off κ-disk)…`);
200
+ const gpu = await createQvacGPU(manifest, fetchTensor, kvOf(m), lr.eos ?? 2, "remote", (d, t) => onProgress(d, t, "κ-disk"), frameStore, cacheBudget);
201
+ window.__gpu = gpu;
202
+ const st = kd.stats(); onStatus(`${index.imageKappa.slice(0, 22)}… · ${st.verified} sectors verified`);
203
+ return { gpu, info: lr, manifest, imageKappa: kd.imageKappa || index.imageKappa || null };
204
+ }
core/mcp.js ADDED
@@ -0,0 +1,144 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // core/mcp.js — a lean, browser-native Model Context Protocol CLIENT. Speaks the open MCP
2
+ // spec (JSON-RPC 2.0) over the Streamable HTTP transport (single endpoint, POST per message,
3
+ // optional SSE response bodies, Mcp-Session-Id header) with a fallback for plain HTTP+SSE
4
+ // servers. No SDK, no build step — the protocol is small and this is all of it that a chat
5
+ // client needs: initialize → tools/list → tools/call.
6
+ //
7
+ // Substrate twist: every connected server and every tool call is content-addressable — the
8
+ // caller can seal a PROV-O receipt around call inputs/outputs (see core/tools.js), so tool
9
+ // use gets the same verifiability as inference (Law L5).
10
+
11
+ const PROTOCOL_VERSION = "2025-03-26";
12
+ const CLIENT_INFO = { name: "holo-q", version: "2.0.0" };
13
+
14
+ let _seq = 0;
15
+ const nextId = () => ++_seq;
16
+
17
+ // Parse an SSE stream body and resolve with the JSON-RPC response matching `id`.
18
+ async function readSse(res, id) {
19
+ const reader = res.body.getReader(); const dec = new TextDecoder();
20
+ let buf = "", result = null;
21
+ for (;;) {
22
+ const { done, value } = await reader.read(); if (done) break;
23
+ buf += dec.decode(value, { stream: true });
24
+ let i;
25
+ while ((i = buf.indexOf("\n\n")) >= 0) {
26
+ const frame = buf.slice(0, i); buf = buf.slice(i + 2);
27
+ const data = frame.split("\n").filter((l) => l.startsWith("data:")).map((l) => l.slice(5).trim()).join("");
28
+ if (!data) continue;
29
+ try { const msg = JSON.parse(data); if (msg.id === id) { result = msg; reader.cancel().catch(() => {}); return result; } } catch {}
30
+ }
31
+ }
32
+ return result;
33
+ }
34
+
35
+ export function makeMcpClient({ url, headers = {}, name = "", timeoutMs = 30000 } = {}) {
36
+ let sessionId = null, initialized = null, serverInfo = null, toolCache = null;
37
+
38
+ async function rpc(method, params, { notification = false } = {}) {
39
+ const body = notification
40
+ ? { jsonrpc: "2.0", method, ...(params ? { params } : {}) }
41
+ : { jsonrpc: "2.0", id: nextId(), method, ...(params ? { params } : {}) };
42
+ const ctrl = new AbortController();
43
+ const t = setTimeout(() => ctrl.abort(), timeoutMs);
44
+ try {
45
+ const res = await fetch(url, {
46
+ method: "POST", signal: ctrl.signal,
47
+ headers: { "content-type": "application/json", accept: "application/json, text/event-stream", ...(sessionId ? { "mcp-session-id": sessionId } : {}), ...headers },
48
+ body: JSON.stringify(body),
49
+ });
50
+ const sid = res.headers.get("mcp-session-id"); if (sid) sessionId = sid;
51
+ if (notification) return null;
52
+ if (res.status === 202) return null;
53
+ if (!res.ok) throw new Error(`MCP ${res.status} ${res.statusText}`);
54
+ const ctype = res.headers.get("content-type") || "";
55
+ const msg = ctype.includes("text/event-stream") ? await readSse(res, body.id) : await res.json();
56
+ if (!msg) throw new Error("MCP: no response for request " + body.id);
57
+ if (msg.error) throw new Error(`MCP ${method}: ${msg.error.message || JSON.stringify(msg.error)}`);
58
+ return msg.result;
59
+ } finally { clearTimeout(t); }
60
+ }
61
+
62
+ async function initialize() {
63
+ if (initialized) return initialized;
64
+ initialized = (async () => {
65
+ const r = await rpc("initialize", { protocolVersion: PROTOCOL_VERSION, capabilities: {}, clientInfo: CLIENT_INFO });
66
+ serverInfo = r && r.serverInfo || { name: name || url };
67
+ await rpc("notifications/initialized", undefined, { notification: true }).catch(() => {});
68
+ return r;
69
+ })();
70
+ return initialized;
71
+ }
72
+
73
+ // tools/list (cached; paginated per spec)
74
+ async function listTools(force = false) {
75
+ if (toolCache && !force) return toolCache;
76
+ await initialize();
77
+ const out = []; let cursor;
78
+ do {
79
+ const r = await rpc("tools/list", cursor ? { cursor } : {});
80
+ out.push(...(r.tools || [])); cursor = r.nextCursor;
81
+ } while (cursor);
82
+ toolCache = out;
83
+ return out;
84
+ }
85
+
86
+ // tools/call → { content: [{type:"text",text}|{type:"image",...}], isError }
87
+ async function callTool(toolName, args) {
88
+ await initialize();
89
+ const r = await rpc("tools/call", { name: toolName, arguments: args || {} });
90
+ return r;
91
+ }
92
+
93
+ return {
94
+ url, name: () => (serverInfo && serverInfo.name) || name || url,
95
+ initialize, listTools, callTool,
96
+ get serverInfo() { return serverInfo; },
97
+ };
98
+ }
99
+
100
+ // Flatten a tools/call result's content into plain text for the model's <tool_response>.
101
+ export function contentToText(result) {
102
+ if (!result) return "";
103
+ if (result.isError) return "ERROR: " + (result.content || []).map((c) => c.text || "").join("\n");
104
+ return (result.content || []).map((c) => c.type === "text" ? c.text : c.type === "image" ? "[image]" : c.type === "resource" ? JSON.stringify(c.resource).slice(0, 2000) : "").join("\n");
105
+ }
106
+
107
+ // ── DISCOVERY — the MCP universe, three rings out from the substrate ─────────────────────
108
+ // 1. the SUBSTRATE's own roster (/.well-known/mcp.json — same-origin, verifiable)
109
+ // 2. user-added servers (κ-pinned config in the boot index)
110
+ // 3. the OFFICIAL public MCP registry (registry.modelcontextprotocol.io — the open index
111
+ // of the ecosystem; remote/streamable-http entries are directly connectable from a tab)
112
+ export async function discoverSubstrate() {
113
+ try {
114
+ const r = await fetch("/.well-known/mcp.json"); if (!r.ok) return [];
115
+ const j = await r.json();
116
+ const servers = j.servers || j.mcpServers || (Array.isArray(j) ? j : [j]);
117
+ return (Array.isArray(servers) ? servers : Object.entries(servers).map(([k, v]) => ({ name: k, ...v })))
118
+ .map((s) => ({ source: "substrate", name: s.name || "hologram", url: s.url || s.endpoint || null, description: s.description || "", tools: s.tools || null, raw: s }))
119
+ .filter((s) => s.url || s.tools);
120
+ } catch { return []; }
121
+ }
122
+
123
+ export async function searchRegistry(query, { limit = 20 } = {}) {
124
+ // The official registry's public REST API (v0). Remote servers carry `remotes[]` with
125
+ // streamable-http/sse URLs — those are connectable straight from the browser.
126
+ const base = "https://registry.modelcontextprotocol.io/v0/servers";
127
+ const u = base + "?limit=" + limit + (query ? "&search=" + encodeURIComponent(query) : "");
128
+ const r = await fetch(u, { headers: { accept: "application/json" } });
129
+ if (!r.ok) throw new Error("registry " + r.status);
130
+ const j = await r.json();
131
+ return (j.servers || []).map((s) => {
132
+ const remote = (s.remotes || []).find((x) => /streamable|http/.test(x.type || "")) || (s.remotes || [])[0] || null;
133
+ return { source: "registry", name: s.name, description: s.description || "", version: s.version,
134
+ url: remote ? remote.url : null, transport: remote ? remote.type : (s.packages?.length ? "package (host-run)" : "unknown"), raw: s };
135
+ });
136
+ }
137
+
138
+ export async function discoverAgents() {
139
+ // The substrate's agent doors: NANDA AgentFacts + A2A AgentCards + Skills — one entry point.
140
+ try {
141
+ const r = await fetch("/.well-known/agents.json"); if (!r.ok) return null;
142
+ return await r.json();
143
+ } catch { return null; }
144
+ }
core/mcphub.js ADDED
@@ -0,0 +1,107 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // core/mcphub.js — the MCP connection HUB: manages servers (substrate roster · user-added ·
2
+ // registry finds), their lifecycles and tool lists, and exposes the flat enabled-tool set the
3
+ // generation loop consumes. Server configs persist in the boot index (substrate-local).
4
+ //
5
+ // Discovery rings: (1) the substrate's own /.well-known/mcp.json (the OS's 30 verified tools),
6
+ // (2) servers the user added by URL, (3) the official public MCP registry. One hub, the
7
+ // whole universe reachable from a tab.
8
+
9
+ import { makeMcpClient, contentToText, discoverSubstrate, searchRegistry, discoverAgents } from "./mcp.js";
10
+
11
+ export function makeMcpHub({ chatStore, bus }) {
12
+ const servers = new Map(); // id → { id, name, url, headers, enabled, client, tools, status, error }
13
+ let loaded = false;
14
+
15
+ const INDEX_KEY = "index:org.hologram.HoloQ";
16
+ async function persist() {
17
+ const idx = await chatStore.getIndex();
18
+ idx.mcpServers = [...servers.values()].map((s) => ({ id: s.id, name: s.name, url: s.url, headers: s.headers || {}, enabled: !!s.enabled, source: s.source || "user" }));
19
+ const b = new TextEncoder().encode(JSON.stringify(idx));
20
+ await (chatStore.store.backend.putRaw ? chatStore.store.backend.putRaw(INDEX_KEY, b) : chatStore.store.backend.put(INDEX_KEY, b));
21
+ }
22
+
23
+ async function restore() {
24
+ if (loaded) return; loaded = true;
25
+ const idx = await chatStore.getIndex();
26
+ for (const s of idx.mcpServers || []) if (!servers.has(s.id)) servers.set(s.id, { ...s, client: null, tools: [], status: "saved" }); // never clobber a live entry
27
+ bus.emit("mcp-changed");
28
+ }
29
+
30
+ const norm = (url) => { try { return new URL(url, location.origin).href; } catch { return url; } };
31
+ const idOf = (url) => "mcp-" + norm(url).replace(/[^\w]+/g, "-").slice(0, 60);
32
+
33
+ async function add({ name, url, headers = {}, source = "user", enabled = true }) {
34
+ const u = norm(url); const id = idOf(u);
35
+ if (!servers.has(id)) servers.set(id, { id, name: name || u, url: u, headers, source, enabled, client: null, tools: [], status: "saved" });
36
+ const s = servers.get(id);
37
+ s.enabled = enabled; // adding is an explicit intent — (re)enable even if a saved entry existed
38
+ await connect(s.id).catch(() => {});
39
+ await persist();
40
+ bus.emit("mcp-changed");
41
+ return s;
42
+ }
43
+
44
+ async function connect(id) {
45
+ const s = servers.get(id); if (!s) return null;
46
+ s.status = "connecting"; s.error = null; bus.emit("mcp-changed");
47
+ try {
48
+ s.client = makeMcpClient({ url: s.url, headers: s.headers, name: s.name });
49
+ await s.client.initialize();
50
+ s.tools = await s.client.listTools();
51
+ s.name = s.client.name() || s.name;
52
+ s.status = "connected";
53
+ } catch (e) {
54
+ s.client = null; s.tools = []; s.status = "error"; s.error = String(e && e.message || e);
55
+ }
56
+ bus.emit("mcp-changed");
57
+ return s;
58
+ }
59
+
60
+ async function remove(id) { servers.delete(id); await persist(); bus.emit("mcp-changed"); }
61
+ async function setEnabled(id, on) { const s = servers.get(id); if (s) { s.enabled = !!on; if (on && !s.client) await connect(id); await persist(); bus.emit("mcp-changed"); } }
62
+
63
+ // The flat ARMED tool set the loop consumes: [{ def, serverName, call(args)→{text,isError} }].
64
+ // Per-server `toolFilter` (a name allowlist, set from the UI) selects which tools arm; without
65
+ // one, the first DEFAULT_ARM tools arm. A hard ceiling keeps the system block inside the model's
66
+ // context (a local model can't juggle 30 schemas — pick, like LibreChat's per-server selection).
67
+ const DEFAULT_ARM = 4, MAX_ARM = 8;
68
+ // the substrate's own roster is curated to its core verbs by default
69
+ const SUBSTRATE_DEFAULT = ["search_web", "resolve_object", "answer", "verify_object"];
70
+ function enabledTools() {
71
+ const out = [];
72
+ for (const s of servers.values()) {
73
+ if (!s.enabled || s.status !== "connected") continue;
74
+ const names = s.toolFilter && s.toolFilter.length ? s.toolFilter
75
+ : (s.source === "substrate" ? SUBSTRATE_DEFAULT.filter((n) => s.tools.some((t) => t.name === n)) : s.tools.slice(0, DEFAULT_ARM).map((t) => t.name));
76
+ for (const t of s.tools) {
77
+ if (!names.includes(t.name)) continue;
78
+ out.push({
79
+ def: { name: t.name, description: t.description, inputSchema: t.inputSchema },
80
+ serverName: s.name, serverId: s.id,
81
+ call: async (args) => { const r = await s.client.callTool(t.name, args); return { text: contentToText(r), isError: !!(r && r.isError) }; },
82
+ });
83
+ }
84
+ }
85
+ // de-dup by tool name (first server wins) — small models address tools by bare name
86
+ const seen = new Set();
87
+ return out.filter((t) => (seen.has(t.def.name) ? false : (seen.add(t.def.name), true))).slice(0, MAX_ARM);
88
+ }
89
+ async function setToolFilter(id, names) { const s = servers.get(id); if (s) { s.toolFilter = names && names.length ? names : null; bus.emit("mcp-changed"); } }
90
+
91
+ // Substrate ring: read the OS roster; candidate endpoints = declared, same-origin /mcp, the
92
+ // conventional localhost MCP port. First reachable wins.
93
+ async function discoverSubstrateServers() {
94
+ const entries = await discoverSubstrate();
95
+ const candidates = new Set();
96
+ for (const e of entries) if (e.url) candidates.add(norm(e.url));
97
+ candidates.add(norm("/mcp"));
98
+ candidates.add("http://127.0.0.1:8787/mcp");
99
+ return { entries, candidates: [...candidates] };
100
+ }
101
+
102
+ return {
103
+ restore, add, connect, remove, setEnabled, setToolFilter, enabledTools, persist,
104
+ list: () => [...servers.values()],
105
+ discoverSubstrateServers, searchRegistry, discoverAgents,
106
+ };
107
+ }
core/memory.js ADDED
@@ -0,0 +1,69 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // core/memory.js — persistent USER MEMORY (LibreChat memory semantics, substrate-native).
2
+ // Memories are key/value facts (key validated /^[a-z_]+$/, value a complete sentence) stored as
3
+ // κ-objects with pointers in the boot index. They inject into the system block as the
4
+ // "# Existing memory:" section, and the model maintains them itself through two LOCAL tools
5
+ // (set_memory / delete_memory) armed alongside MCP tools in the agentic loop — no server,
6
+ // every write content-addressed and verifiable.
7
+
8
+ const LC = { lc: "https://librechat.ai/ns#" };
9
+ export const KEY_RE = /^[a-z_]+$/;
10
+
11
+ export function makeMemory(chatStore, { tokenLimit = 2000 } = {}) {
12
+ const { store, getIndex, newId } = chatStore;
13
+ const putIndex = async (idx) => { const b = new TextEncoder().encode(JSON.stringify(idx)); return store.backend.putRaw ? store.backend.putRaw("index:org.hologram.HoloQ", b) : store.backend.put("index:org.hologram.HoloQ", b); };
14
+ const tokensOf = (s) => Math.ceil((s || "").length / 4); // a fair, dependency-free estimate
15
+
16
+ async function list() { return (await getIndex()).memories || []; }
17
+
18
+ async function set(key, value) {
19
+ key = String(key || "").toLowerCase().trim();
20
+ if (!KEY_RE.test(key)) return { ok: false, error: "invalid key — use lowercase letters and underscores" };
21
+ const idx = await getIndex();
22
+ idx.memories = idx.memories || [];
23
+ const used = idx.memories.filter((m) => m.key !== key).reduce((n, m) => n + (m.tokenCount || 0), 0);
24
+ const tc = tokensOf(value);
25
+ if (used + tc > tokenLimit) return { ok: false, error: `memory full: ${used}+${tc} > ${tokenLimit} tokens` };
26
+ const obj = await store.makeObject({
27
+ type: ["schema:Statement", "prov:Entity"], context: [LC],
28
+ "lc:key": key, "lc:value": String(value || ""), "lc:tokenCount": tc,
29
+ "schema:dateModified": new Date().toISOString(),
30
+ });
31
+ const i = idx.memories.findIndex((m) => m.key === key);
32
+ const ptr = { key, kappa: obj.id, value: String(value || ""), tokenCount: tc, updated_at: new Date().toISOString() };
33
+ if (i >= 0) idx.memories[i] = ptr; else idx.memories.push(ptr);
34
+ await putIndex(idx);
35
+ return { ok: true, key, kappa: obj.id };
36
+ }
37
+
38
+ async function remove(key) {
39
+ const idx = await getIndex();
40
+ idx.memories = (idx.memories || []).filter((m) => m.key !== key);
41
+ await putIndex(idx);
42
+ return { ok: true };
43
+ }
44
+
45
+ // The system-block injection ("# Existing memory:" — the LibreChat convention).
46
+ async function injection() {
47
+ const mems = await list();
48
+ if (!mems.length) return "";
49
+ const used = mems.reduce((n, m) => n + (m.tokenCount || 0), 0);
50
+ return `# Memory Status:\nCurrent memory usage: ${used} tokens\nToken limit: ${tokenLimit} tokens\nRemaining capacity: ${Math.max(0, tokenLimit - used)} tokens\n\n# Existing memory:\n` +
51
+ mems.map((m) => `- ${m.key}: ${m.value}`).join("\n");
52
+ }
53
+
54
+ // The two LOCAL tools the agentic loop arms (same shape as MCP hub tools).
55
+ function localTools() {
56
+ return [
57
+ {
58
+ def: { name: "set_memory", description: "Remember a fact about the user across conversations. Only when the user explicitly asks (\"remember that…\"). key: lowercase_with_underscores; value: one complete sentence.", inputSchema: { type: "object", properties: { key: { type: "string" }, value: { type: "string" } }, required: ["key", "value"] } },
59
+ serverName: "memory", call: async (a) => { const r = await set(a.key, a.value); return { text: JSON.stringify(r), isError: !r.ok }; },
60
+ },
61
+ {
62
+ def: { name: "delete_memory", description: "Forget a remembered fact, by key. Only when the user explicitly asks.", inputSchema: { type: "object", properties: { key: { type: "string" } }, required: ["key"] } },
63
+ serverName: "memory", call: async (a) => { const r = await remove(a.key); return { text: JSON.stringify(r), isError: false }; },
64
+ },
65
+ ];
66
+ }
67
+
68
+ return { list, set, remove, injection, localTools, tokenLimit };
69
+ }
core/presets.js ADDED
@@ -0,0 +1,54 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // core/presets.js — preset CRUD (the saved parameter bundles a conversation spreads, LibreChat's
2
+ // conversationPreset semantics). Each preset is a κ-object in the store; the boot index keeps
3
+ // pointer rows { presetId, kappa, title } so listing is O(1) without a scan.
4
+
5
+ import { normPreset } from "./schema.js";
6
+
7
+ const LC = { lc: "https://librechat.ai/ns#" };
8
+
9
+ export function makePresets(chatStore) {
10
+ const { store, getIndex, newId } = chatStore;
11
+ const putIndex = async (idx) => { const b = new TextEncoder().encode(JSON.stringify(idx)); return store.backend.putRaw ? store.backend.putRaw("index:org.hologram.HoloQ", b) : store.backend.put("index:org.hologram.HoloQ", b); };
12
+
13
+ async function save(p) {
14
+ const norm = normPreset(p);
15
+ if (!norm.presetId) norm.presetId = newId("preset");
16
+ const obj = await store.makeObject({ type: ["schema:PropertyValue", "prov:Entity"], context: [LC], "schema:identifier": norm.presetId, "schema:name": norm.title, "lc:preset": norm });
17
+ const idx = await getIndex();
18
+ idx.presets = idx.presets || [];
19
+ const i = idx.presets.findIndex((x) => x.presetId === norm.presetId);
20
+ const ptr = { presetId: norm.presetId, kappa: obj.id, title: norm.title, defaultPreset: !!p.defaultPreset };
21
+ if (i >= 0) idx.presets[i] = ptr; else idx.presets.push(ptr);
22
+ if (p.defaultPreset) idx.presets.forEach((x) => { if (x.presetId !== norm.presetId) x.defaultPreset = false; });
23
+ await putIndex(idx);
24
+ return { ...norm, kappa: obj.id };
25
+ }
26
+
27
+ async function list() { return (await getIndex()).presets || []; }
28
+
29
+ async function get(presetId) {
30
+ const ptr = (await list()).find((x) => x.presetId === presetId); if (!ptr) return null;
31
+ const obj = await store.getObj(ptr.kappa); if (!obj) return null;
32
+ if (!(await store.verify(obj))) return null; // Law L5: a tampered preset is refused
33
+ return { ...normPreset(obj["lc:preset"]), kappa: ptr.kappa };
34
+ }
35
+
36
+ async function getDefault() {
37
+ const ptrs = await list(); const d = ptrs.find((x) => x.defaultPreset) || null;
38
+ return d ? get(d.presetId) : null;
39
+ }
40
+
41
+ async function remove(presetId) {
42
+ const idx = await getIndex();
43
+ idx.presets = (idx.presets || []).filter((x) => x.presetId !== presetId);
44
+ await putIndex(idx); // the κ-object stays content-addressed in the store (dedup'd, unreferenced)
45
+ }
46
+
47
+ return { save, list, get, getDefault, remove };
48
+ }
49
+
50
+ // The factory default — mirrors the engine's real capabilities (greedy decode is the
51
+ // deterministic, verifiable path; temperature stays 0 unless a model declares one).
52
+ export const DEFAULT_PRESET = () => normPreset({
53
+ title: "Default", temperature: 0, maxOutputTokens: 900, promptPrefix: "",
54
+ });
core/promptsLib.js ADDED
@@ -0,0 +1,109 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // core/promptsLib.js — the PROMPTS LIBRARY as κ-objects (LibreChat prompt-group semantics).
2
+ // A group carries name · category · oneliner · command (the /slash trigger) and points at its
3
+ // PRODUCTION version; every version is its own immutable κ-object (text or chat type). The
4
+ // {{variable}} syntax matches LibreChat: {{name}}, {{name:opt1|opt2}} dropdowns, and the
5
+ // special variables {{current_date}} {{current_datetime}} {{iso_datetime}} {{current_user}}.
6
+
7
+ const LC = { lc: "https://librechat.ai/ns#" };
8
+ export const COMMAND_RE = /^[a-z0-9-]{1,56}$/;
9
+ export const VAR_RE = /{{([^{}]+?)}}/g;
10
+ const SPECIAL = ["current_date", "current_datetime", "iso_datetime", "current_user"];
11
+
12
+ export function detectVariables(text) {
13
+ const out = []; const seen = new Set(); let m;
14
+ VAR_RE.lastIndex = 0;
15
+ while ((m = VAR_RE.exec(text || ""))) {
16
+ const raw = m[1].trim();
17
+ const name = raw.split(":")[0].trim();
18
+ if (SPECIAL.includes(name.toLowerCase()) || seen.has(name)) continue;
19
+ seen.add(name);
20
+ const opts = raw.includes(":") ? raw.slice(raw.indexOf(":") + 1).split("|").map((s) => s.trim()).filter(Boolean) : null;
21
+ out.push({ name, raw, options: opts });
22
+ }
23
+ return out;
24
+ }
25
+
26
+ export function replaceSpecialVars(text, { userName = "operator" } = {}) {
27
+ const d = new Date();
28
+ const pad = (n) => String(n).padStart(2, "0");
29
+ const day = ["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"][d.getDay()];
30
+ const date = `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`;
31
+ const time = `${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`;
32
+ const off = -d.getTimezoneOffset(); const tz = (off >= 0 ? "+" : "-") + pad(Math.floor(Math.abs(off) / 60)) + ":" + pad(Math.abs(off) % 60);
33
+ const sub = (t, name, v) => t.replace(new RegExp(`{{\\s*${name}\\s*}}`, "gi"), v);
34
+ let out = text || "";
35
+ out = sub(out, "current_date", `${date} (${day})`);
36
+ out = sub(out, "current_datetime", `${date} ${time} ${tz} (${day})`);
37
+ out = sub(out, "iso_datetime", d.toISOString());
38
+ out = sub(out, "current_user", userName);
39
+ return out;
40
+ }
41
+
42
+ export function fillVariables(text, values) {
43
+ let out = text || "";
44
+ for (const [raw, v] of Object.entries(values || {})) {
45
+ const esc = raw.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
46
+ out = out.replace(new RegExp(`{{\\s*${esc}\\s*}}`, "g"), v);
47
+ }
48
+ return out;
49
+ }
50
+
51
+ export function makePromptsLib(chatStore) {
52
+ const { store, getIndex, newId } = chatStore;
53
+ const putIndex = async (idx) => { const b = new TextEncoder().encode(JSON.stringify(idx)); return store.backend.putRaw ? store.backend.putRaw("index:org.hologram.HoloQ", b) : store.backend.put("index:org.hologram.HoloQ", b); };
54
+
55
+ // Save a version into a group (creates the group when groupId is null); production by default.
56
+ async function save({ groupId, name, category = "", oneliner = "", command = "", prompt, type = "text", production = true }) {
57
+ command = (command || "").toLowerCase();
58
+ if (command && !COMMAND_RE.test(command)) throw new Error("command must be a-z 0-9 dash, ≤56 chars");
59
+ const versionObj = await store.makeObject({
60
+ type: ["schema:CreativeWork", "prov:Entity"], context: [LC],
61
+ "lc:prompt": prompt || "", "lc:type": type === "chat" ? "chat" : "text",
62
+ "schema:dateCreated": new Date().toISOString(),
63
+ });
64
+ const idx = await getIndex();
65
+ idx.promptGroups = idx.promptGroups || [];
66
+ let g = groupId ? idx.promptGroups.find((x) => x.groupId === groupId) : null;
67
+ if (!g) {
68
+ g = { groupId: newId("pgroup"), name: name || "New Prompt", category, oneliner, command, versions: [], productionKappa: null, numberOfGenerations: 0 };
69
+ idx.promptGroups.push(g);
70
+ }
71
+ if (name) g.name = name;
72
+ if (category !== undefined) g.category = category;
73
+ if (oneliner !== undefined) g.oneliner = oneliner;
74
+ if (command !== undefined) g.command = command;
75
+ g.versions.push({ kappa: versionObj.id, createdAt: new Date().toISOString() });
76
+ if (production || !g.productionKappa) g.productionKappa = versionObj.id;
77
+ await putIndex(idx);
78
+ return { ...g };
79
+ }
80
+
81
+ async function list() { return (await getIndex()).promptGroups || []; }
82
+
83
+ async function production(groupId) {
84
+ const g = (await list()).find((x) => x.groupId === groupId); if (!g || !g.productionKappa) return null;
85
+ const obj = await store.getObj(g.productionKappa); if (!obj) return null;
86
+ if (!(await store.verify(obj))) return null; // Law L5
87
+ return { group: g, prompt: obj["lc:prompt"], type: obj["lc:type"], kappa: g.productionKappa };
88
+ }
89
+
90
+ async function setProduction(groupId, kappa) {
91
+ const idx = await getIndex();
92
+ const g = (idx.promptGroups || []).find((x) => x.groupId === groupId);
93
+ if (g && g.versions.some((v) => v.kappa === kappa)) { g.productionKappa = kappa; await putIndex(idx); }
94
+ }
95
+
96
+ async function recordUse(groupId) {
97
+ const idx = await getIndex();
98
+ const g = (idx.promptGroups || []).find((x) => x.groupId === groupId);
99
+ if (g) { g.numberOfGenerations = (g.numberOfGenerations || 0) + 1; await putIndex(idx); }
100
+ }
101
+
102
+ async function remove(groupId) {
103
+ const idx = await getIndex();
104
+ idx.promptGroups = (idx.promptGroups || []).filter((x) => x.groupId !== groupId);
105
+ await putIndex(idx);
106
+ }
107
+
108
+ return { save, list, production, setProduction, recordUse, remove };
109
+ }
core/q-brain-fast.mjs ADDED
@@ -0,0 +1,153 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // core/q-brain-fast.mjs — Q's FAST on-device brain, drop-in for createHoloModelBrain.
2
+ //
3
+ // Same provider shape (load · generate → text-delta async-iterator · chat · info · setSkill), so
4
+ // holo-q-contact.mjs's makeQResponder / makeQGroupResponder bind it with ZERO changes: Q rides the exact
5
+ // stream→finalize pipeline, it just sources its bytes from THIS engine (core/loader + core/engine — the
6
+ // native-ternary κ-object path with the fixed incremental-detok decode) instead of the qwen holo-brain.
7
+ //
8
+ // Why this exists: the messenger's default brain (qwen2.5-0.5b via holo-brain-engine) is heavy and its
9
+ // app-path decode was O(n²). This one loads a native-ternary BitNet κ-object (0.69 GB, verified per-block),
10
+ // decodes at ~70 tok/s warm, and streams byte-identical incremental text. It is 100% on-device at decode
11
+ // time — only a ONE-TIME tokenizer-header Range fetch touches the source host at load (the weights are the
12
+ // local, L5-verified b/<κ>.gz blocks). Bundle the header to make load fully egress-free (follow-up).
13
+ //
14
+ // URL discipline (the one gotcha): core/loader's MODELS use a PAGE-relative kappaUrl ("./models/<name>"),
15
+ // which is wrong from any page other than /apps/q/. loadKappaObject resolves the manifest, blocks AND the
16
+ // bundled tokenizer relative to its baseUrl, so we override kappaUrl to an ABSOLUTE mount ("/apps/q/models/
17
+ // <name>") — that makes the whole load self-contained from the messenger (or anywhere).
18
+
19
+ import { ready, loadModel, MODELS } from "./loader.js";
20
+ import { createEngine } from "./engine.js";
21
+ import { selfFacts, selfPersona, selfIntro } from "./q-self.mjs"; // Q's live, grounded self-knowledge (M0)
22
+
23
+ // The default fast brain = BitNet-2B (native ternary, llama3 template, coherent — Falcon-E degenerates).
24
+ // Override via opts.family / opts.modelName. kappaBase is the absolute mount core/loader's models live under.
25
+ const DEFAULTS = { family: "BitNet", kappaBase: "/apps/q/models", maxTokens: 512 };
26
+
27
+ function pickModel(cfg) {
28
+ const want = String(cfg.modelName || cfg.family || "BitNet").toLowerCase();
29
+ const m = MODELS.find((x) => (x.fam || "").toLowerCase() === want || (x.name || "").toLowerCase().includes(want)) || MODELS.find((x) => (x.fam || "").toLowerCase() === "bitnet");
30
+ if (!m) throw new Error("q-brain-fast: no model matches " + want);
31
+ // An ABSOLUTE host URL (the κ-object now lives on HF, not on this machine) is already page-independent —
32
+ // use it verbatim. Only a page-relative "./models/<name>" needs absolute-mounting so it resolves from ANY page.
33
+ if (/^https?:\/\//.test(m.kappaUrl || "")) return { ...m };
34
+ const rel = String(m.kappaUrl || "").replace(/^\.?\//, "").replace(/^models\//, ""); // "./models/bitnet-2b" → "bitnet-2b"
35
+ return { ...m, kappaUrl: String(cfg.kappaBase).replace(/\/+$/, "") + "/" + rel };
36
+ }
37
+
38
+ // Render an [{role,content}] history (system + turns, as makeQResponder builds it) to the model's chat
39
+ // template — the multi-turn generalization of core/engine's single-turn frameTurn. Covers the templates
40
+ // the engine knows; unknown families fall back to a persona-led last-user Q/A frame.
41
+ function frameHistory(M, history) {
42
+ const list = Array.isArray(history) ? history : [];
43
+ // merge ALL system turns into one system block (persona + any injected context, e.g. M1 grounded retrieval) —
44
+ // taking only the first would silently DROP injected context and the model would fall back to a generic refusal.
45
+ const persona = list.filter((x) => x && x.role === "system" && x.content).map((x) => x.content).join("\n\n");
46
+ const turns = list.filter((x) => x && x.role !== "system" && (x.content || "").length);
47
+
48
+ if (M.llama3) {
49
+ let s = persona ? `<|start_header_id|>system<|end_header_id|>\n\n${persona}<|eot_id|>` : "";
50
+ for (const t of turns) s += `<|start_header_id|>${t.role === "assistant" ? "assistant" : "user"}<|end_header_id|>\n\n${t.content}<|eot_id|>`;
51
+ return s + `<|start_header_id|>assistant<|end_header_id|>\n\n`;
52
+ }
53
+ if (M.qwen) {
54
+ const noThink = M.qwen3 ? "<think>\n\n</think>\n\n" : "";
55
+ let s = persona ? `<|im_start|>system\n${persona}<|im_end|>\n` : "";
56
+ for (const t of turns) s += `<|im_start|>${t.role === "assistant" ? "assistant" : "user"}\n${t.content}<|im_end|>\n`;
57
+ return s + `<|im_start|>assistant\n` + noThink;
58
+ }
59
+ if (M.olmo) {
60
+ let s = persona ? `<|system|>\n${persona}\n` : "";
61
+ for (const t of turns) s += t.role === "assistant" ? `<|assistant|>\n${t.content}\n` : `<|user|>\n${t.content}\n`;
62
+ return s + `<|assistant|>\n`;
63
+ }
64
+ // word-frame / plain: only the last user turn carries (base models have no multi-turn template)
65
+ const lastUser = [...turns].reverse().find((t) => t.role !== "assistant");
66
+ const q = (lastUser && lastUser.content) || "";
67
+ if (M.userWord) return (persona ? persona + "\n" : "") + "User: " + q + "\nFalcon:";
68
+ return (persona ? persona + "\n" : "") + "Question: " + q + "\nAnswer:";
69
+ }
70
+
71
+ export function createFastQBrain(opts = {}) {
72
+ const cfg = Object.assign({}, DEFAULTS, opts);
73
+ const M = pickModel(cfg);
74
+ let engine = null, loadingP = null;
75
+ let info = { ready: false, model: M.name, device: null, resident: false };
76
+
77
+ // encode a history to the running token ids (framed + optional bos), ready for engine.generate
78
+ function idsFor(history) {
79
+ let ids = engine.tokenize(frameHistory(M, history));
80
+ if (M.bos && engine.bosId != null) ids = [engine.bosId, ...ids];
81
+ return ids;
82
+ }
83
+
84
+ async function load(onProgress) {
85
+ if (engine) return info;
86
+ if (loadingP) return loadingP;
87
+ loadingP = (async () => {
88
+ if (!(typeof navigator !== "undefined" && navigator.gpu)) throw new Error("no WebGPU on this device");
89
+ await ready(); // wasm tokenizer init (shared instance)
90
+ const loaded = await loadModel(M, {
91
+ onStatus: () => {},
92
+ onProgress: (d, t, w) => { try { onProgress && onProgress({ done: d, total: t, phase: w, model: M.name }); } catch (e) {} },
93
+ });
94
+ if (!loaded || !loaded.gpu) throw new Error("q-brain-fast: model load failed (" + M.name + ")");
95
+ engine = await createEngine(M, loaded);
96
+ // MEASURED from the real engine, not asserted: device/resident reflect an engine that actually uploaded
97
+ // weights to the GPU (dims + gpuBytes are live signals) — so info() can never claim residency it lacks.
98
+ info = { ready: true, model: M.name, device: (engine && engine.dims ? "webgpu" : null), resident: !!(engine && (engine.gpuBytes || engine.dims)) };
99
+ return info;
100
+ })().catch((e) => { loadingP = null; throw e; });
101
+ return loadingP;
102
+ }
103
+ async function ensure(onProgress) { if (!engine) await load(onProgress); return engine; }
104
+
105
+ // generate(history, { signal, onProgress }) → async-iterator of TEXT DELTAS (exactly what makeQResponder
106
+ // accumulates + paints via onDelta). engine.generate reports CUMULATIVE text per step; we diff to deltas
107
+ // and pump them through a small queue so this stays a clean generator (and honors the abort signal).
108
+ async function* generate(history, o = {}) {
109
+ await ensure(o.onProgress);
110
+ if (!engine) return;
111
+ const signal = o.signal || null;
112
+ const ids = idsFor(history);
113
+ const cap = o.maxTokens || cfg.maxTokens || M.cap || 256;
114
+
115
+ const queue = []; let done = false, wake = null, prev = "";
116
+ const kick = () => { if (wake) { const w = wake; wake = null; w(); } };
117
+ const run = engine.generate(ids, {
118
+ maxNew: cap, signal,
119
+ onToken: ({ text }) => { const d = (text || "").slice(prev.length); if (d) { prev = text; queue.push(d); kick(); } },
120
+ }).then(() => { done = true; kick(); }).catch(() => { done = true; kick(); });
121
+
122
+ while (true) {
123
+ if (queue.length) { yield queue.shift(); continue; }
124
+ if (done) break;
125
+ if (signal && signal.aborted) break;
126
+ await new Promise((r) => (wake = r));
127
+ }
128
+ try { await run; } catch (e) {}
129
+ }
130
+
131
+ // chat(history, opts) → full string (used by window.HoloQ.generate + light background features)
132
+ async function chat(history, o = {}) {
133
+ await ensure(o.onProgress);
134
+ if (!engine) return "";
135
+ const res = await engine.generate(idsFor(history), { maxNew: o.maxTokens || cfg.maxTokens || M.cap || 256, signal: o.signal || null });
136
+ return ((res && res.text) || "").trim();
137
+ }
138
+
139
+ // native-ternary κ-object → no LoRA adapters; skill routing is a no-op (the base is the specialist).
140
+ const setSkill = async () => ({ adapter: false, unsupported: true });
141
+ const setAdapter = async () => ({ adapter: false, unsupported: true });
142
+
143
+ // Q's LIVE self-knowledge (M0), derived from THIS instance's real model + engine κ — the grounded truth
144
+ // every surface leads with so Q is honestly self-aware and never confabulates a cloud identity. Available
145
+ // even before load (identity is in M); the κ fills in once the engine is resident.
146
+ const facts = () => selfFacts({ model: M, engine });
147
+ const persona = () => selfPersona({ model: M, engine });
148
+ const intro = () => selfIntro({ model: M, engine });
149
+
150
+ return { id: "q-brain-fast-" + (M.fam || M.name), load, generate, chat, setSkill, setAdapter, info: () => info, facts, persona, intro };
151
+ }
152
+
153
+ export default createFastQBrain;
core/q-self.mjs ADDED
@@ -0,0 +1,62 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // core/q-self.mjs — Q's LIVE, GROUNDED self-knowledge. The single source of truth for who/what/where/how Q
2
+ // is, DERIVED FROM REAL RUNTIME SIGNALS (the loaded model, its content-addressed κ, the device, optional
3
+ // system health) — never hardcoded, never guessed. Every Q surface (messenger, q-chat, voice) leads with
4
+ // this, so Q is truthfully self-aware EVERYWHERE and can never confabulate a cloud/OpenAI/AWS identity.
5
+ //
6
+ // Why this exists (the law: grounded transcendence, never performed): a base model has NO self-knowledge —
7
+ // asked "what are you" it returns the average of its training data ("I run on AWS/OpenAI"), which is false.
8
+ // So Q's identity is not a persona we write; it is the TRUTH of this running instance, computed here. If a
9
+ // fact isn't really knowable in this context, we OMIT it — we never invent one.
10
+ //
11
+ // DOM-free + fully guarded: safe to import in any surface (standalone app, messenger, OS shell, Node witness).
12
+
13
+ // Structured self-facts from whatever is really knowable HERE. Fail-soft: a missing signal is omitted.
14
+ export function selfFacts({ model, engine } = {}) {
15
+ const f = { name: "Q" };
16
+ try { if (model) { if (model.name) f.model = model.name; if (model.fam) f.family = model.fam; if (model.fmt) f.quant = model.fmt; if (model.size) f.size = model.size; } } catch (e) {}
17
+ try { if (engine && engine.modelKappa) f.kappa = engine.modelKappa; } catch (e) {}
18
+ try { f.gpu = (typeof navigator !== "undefined" && !!navigator.gpu); } catch (e) {}
19
+ // MEASURED, never asserted: Q is "resident" only when the engine has really uploaded weights to the GPU
20
+ // (a live signal — gpuBytes/dims), not a hardcoded true. Before load it is false, and stays false.
21
+ try { f.resident = !!(engine && (engine.gpuBytes || engine.dims)); } catch (e) { f.resident = false; }
22
+ // DERIVED from real signals — runs-on-device when actually resident, or at least GPU-capable before load.
23
+ f.runsOnDevice = !!(f.resident || f.gpu);
24
+ // "no server / no egress" is a property of the LOCAL engine, so we assert it ONLY when a real local engine or
25
+ // GPU is visible. Decode is in-browser (egress none); the one network touch is the weights fetch AT LOAD, so we
26
+ // say that plainly instead of a flat "nothing ever touches the network".
27
+ if (f.resident || f.gpu) { f.server = false; f.egress = f.resident ? "none at inference (weights fetched once at load)" : "none at inference"; }
28
+ // Provenance — where Q was loaded FROM (the thing the operator cares about): the page origin + the weights host.
29
+ // Omitted when not knowable. This is what lets Q answer "where did you come from" truthfully.
30
+ try { if (typeof location !== "undefined" && location.origin && location.origin !== "null") f.loadedFrom = location.origin; } catch (e) {}
31
+ try { if (model && model.kappaUrl) { const u = new URL(model.kappaUrl, (typeof location !== "undefined" ? location.href : "https://local/")); f.weightsFrom = u.host || u.protocol.replace(":", ""); } } catch (e) {}
32
+ // optional live system health — present only when the OS shell exposes it (absent in standalone apps).
33
+ try { const h = (typeof window !== "undefined") && window.HoloSysHealth && window.HoloSysHealth.summary && window.HoloSysHealth.summary(); if (h) f.health = h; } catch (e) {}
34
+ try { if (!f.health && typeof window !== "undefined" && window.Q && window.Q.health) { const q = window.Q.health(); if (q) f.health = q; } } catch (e) {}
35
+ return f;
36
+ }
37
+
38
+ // The grounded SYSTEM persona every Q surface leads with. Warm + honest + specific + anti-confabulation,
39
+ // composed from the live facts so Q states ONLY what is true of THIS instance. Kept concise — a small model
40
+ // follows a short, sharp system turn far better than a long one.
41
+ export function selfPersona(opts = {}) {
42
+ const f = selfFacts(opts);
43
+ const s = [];
44
+ s.push(`You are Q — a private AI that runs on the user's own device, in their web browser${f.gpu ? ", on their GPU" : ""}.`);
45
+ if (f.model) s.push(`You are the ${f.model} model${f.quant ? ` (${f.quant})` : ""}${f.resident ? ", resident locally right now" : ""}${f.kappa ? "; your weights are content-addressed and verified by re-derivation, so no host has to be trusted" : ""}.`);
46
+ if (f.server === false) s.push(`You decode locally — there is no inference server and no cloud, and nothing the user types ever leaves their device.${f.resident ? " Your weights were fetched once at load and verified; after that, inference is fully on-device." : ""}`);
47
+ s.push("You are warm, concise, honest, and genuinely present. You seek clarity and truth; you say less, better; you never overwhelm.");
48
+ s.push("If asked what or where you are, answer truthfully from the above: you run locally, on their device. Never claim to run on a server, the cloud, OpenAI, ChatGPT, GPT-4, Gemini, or AWS — those are false. If you don't know something, say so plainly rather than inventing it.");
49
+ return s.join(" ");
50
+ }
51
+
52
+ // A short, human, first-person self-summary (for a proactive greeting or a direct "who are you"). Grounded.
53
+ export function selfIntro(opts = {}) {
54
+ const f = selfFacts(opts);
55
+ const bits = [`I'm Q — ${f.resident ? "running right here on your device" : "running on your device"}${f.gpu ? ", on your GPU" : ""}${f.model ? ` (the ${f.model} model)` : ""}.`];
56
+ if (f.server === false) bits.push(`No server, no cloud; nothing you say leaves this machine${f.kappa ? ", and my weights are verified by re-derivation" : ""}.`);
57
+ else if (f.kappa) bits.push("My weights are verified by re-derivation.");
58
+ bits.push("What's on your mind?");
59
+ return bits.join(" ");
60
+ }
61
+
62
+ export default { selfFacts, selfPersona, selfIntro };
core/schema.js ADDED
@@ -0,0 +1,85 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // core/schema.js — lean, dependency-free validators ("zod-lite") for the LibreChat-faithful
2
+ // Conversation / Message / Preset shapes. Field names mirror LibreChat's data model (packages/
3
+ // data-schemas) so an exported conversation interoperates; values are validated/normalised, not
4
+ // hashed (the κ-object store is what makes them content-addressed). No build step, no dep.
5
+
6
+ const isStr = (v) => typeof v === "string";
7
+ const isBool = (v) => typeof v === "boolean";
8
+ const isNum = (v) => typeof v === "number" && !Number.isNaN(v);
9
+ const arr = (v) => (Array.isArray(v) ? v : []);
10
+ const clampNum = (v, lo, hi, d) => (isNum(v) ? Math.min(hi, Math.max(lo, v)) : d);
11
+
12
+ // A chat message (one node in the parentMessageId tree).
13
+ export function normMessage(m = {}) {
14
+ return {
15
+ messageId: isStr(m.messageId) ? m.messageId : null,
16
+ conversationId: isStr(m.conversationId) ? m.conversationId : null,
17
+ parentMessageId: isStr(m.parentMessageId) ? m.parentMessageId : null, // tree edge
18
+ sender: isStr(m.sender) ? m.sender : (m.isCreatedByUser ? "User" : "Assistant"),
19
+ isCreatedByUser: isBool(m.isCreatedByUser) ? m.isCreatedByUser : false,
20
+ model: isStr(m.model) ? m.model : null,
21
+ text: isStr(m.text) ? m.text : "",
22
+ content: arr(m.content), // structured parts (text/tool/image/reasoning)
23
+ tokenCount: isNum(m.tokenCount) ? m.tokenCount : null,
24
+ error: m.error || null,
25
+ unfinished: !!m.unfinished,
26
+ feedback: m.feedback && isStr(m.feedback.rating || "") ? { rating: m.feedback.rating, tag: m.feedback.tag || null, text: m.feedback.text || "" } : (m.feedback || null),
27
+ files: arr(m.files),
28
+ createdAt: isStr(m.createdAt) ? m.createdAt : null,
29
+ receiptKappa: isStr(m.receiptKappa) ? m.receiptKappa : null, // links the PROV-O inference receipt
30
+ };
31
+ }
32
+
33
+ // A conversation (spreads the active preset, like LibreChat's conversationPreset).
34
+ export function normConversation(c = {}) {
35
+ return {
36
+ conversationId: isStr(c.conversationId) ? c.conversationId : null,
37
+ title: isStr(c.title) ? c.title : "New Chat",
38
+ tags: arr(c.tags),
39
+ files: arr(c.files),
40
+ folder: isStr(c.folder) ? c.folder : null,
41
+ archived: !!c.archived,
42
+ favorite: !!c.favorite,
43
+ isTemporary: !!c.isTemporary,
44
+ preset: c.preset ? normPreset(c.preset) : null,
45
+ createdAt: isStr(c.createdAt) ? c.createdAt : null,
46
+ updatedAt: isStr(c.updatedAt) ? c.updatedAt : null,
47
+ };
48
+ }
49
+
50
+ // A preset / parameter bundle. Bounds mirror common LLM ranges; model is the local κ-object id.
51
+ export function normPreset(p = {}) {
52
+ return {
53
+ presetId: isStr(p.presetId) ? p.presetId : null,
54
+ title: isStr(p.title) ? p.title : "Preset",
55
+ model: isStr(p.model) ? p.model : null,
56
+ temperature: clampNum(p.temperature, 0, 2, 0),
57
+ top_p: clampNum(p.top_p, 0, 1, 1),
58
+ top_k: clampNum(p.top_k, 0, 200, 40),
59
+ maxOutputTokens: clampNum(p.maxOutputTokens, 1, 8192, 1024),
60
+ promptPrefix: isStr(p.promptPrefix) ? p.promptPrefix : "",
61
+ presence_penalty: clampNum(p.presence_penalty, -2, 2, 0),
62
+ frequency_penalty: clampNum(p.frequency_penalty, -2, 2, 0),
63
+ stop: arr(p.stop),
64
+ iconURL: isStr(p.iconURL) ? p.iconURL : null,
65
+ isArchived: !!p.isArchived,
66
+ };
67
+ }
68
+
69
+ // Walk a flat message list into the LibreChat parentMessageId tree → {roots, byId, childrenOf}.
70
+ export function buildTree(messages) {
71
+ const byId = new Map(); const childrenOf = new Map(); const roots = [];
72
+ for (const m of messages) { byId.set(m.messageId, m); childrenOf.set(m.messageId, []); }
73
+ for (const m of messages) {
74
+ if (m.parentMessageId && childrenOf.has(m.parentMessageId)) childrenOf.get(m.parentMessageId).push(m.messageId);
75
+ else roots.push(m.messageId);
76
+ }
77
+ return { roots, byId, childrenOf };
78
+ }
79
+
80
+ // The active thread = follow the chosen child at each branch from a root to a leaf.
81
+ export function threadOf(tree, { choose = (siblings) => siblings[siblings.length - 1] } = {}) {
82
+ const out = []; let cur = tree.roots.length ? choose(tree.roots) : null;
83
+ while (cur) { out.push(tree.byId.get(cur)); const kids = tree.childrenOf.get(cur) || []; cur = kids.length ? choose(kids) : null; }
84
+ return out;
85
+ }
core/semantic-witness.mjs ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // core/semantic-witness.mjs — the W3C SEMANTIC-TYPING gate (C2, extended to all κ-objects).
2
+ // Proves EVERY substrate object kind yields a node that is simultaneously: content-addressed (Law L1,
3
+ // @id = did:holo), schema.org-typed (WHAT it is), and PROV-O-bearing (HOW it came to be) — i.e. legible
4
+ // to any W3C JSON-LD/RDF tool. Pure + node-runnable: `node core/semantic-witness.mjs`. Exit 1 on any fail.
5
+
6
+ import { linkedDataFor, skillAsHowTo, fileAsDocument, appAsSoftware, modelAsSource, asLinkedData, verifySemantic, HOLO_CONTEXT } from "./semantic.js";
7
+
8
+ const K = (h) => "did:holo:sha256:" + h.padEnd(64, "0").slice(0, 64);
9
+ const cases = [
10
+ { kind: "skill", node: skillAsHowTo({ name: "Format JSON", description: "When asked to pretty-print JSON", instructions: "1. Parse the text\n2. Re-serialize with 2-space indent", kappa: K("a1"), prov: [{ v: 1, kappa: K("a0") }, { v: 2, kappa: K("a1") }] }), want: "schema:HowTo" },
11
+ { kind: "file", node: fileAsDocument({ path: "/src/main.py", kappa: K("b1"), bytes: 412 }), want: "schema:DigitalDocument" },
12
+ { kind: "app", node: appAsSoftware({ name: "counter.html", kappa: K("c1"), bytes: 980 }), want: "schema:SoftwareApplication" },
13
+ { kind: "model", node: modelAsSource({ name: "BitNet-2B-4T", family: "BitNet", params: "0.69 GB", format: "t2 1.58-bit κ", kappa: K("19e16e6d") }), want: "schema:SoftwareSourceCode" },
14
+ { kind: "conversation", node: linkedDataFor("conversation", { kappa: K("d1"), props: { "schema:name": "chat #1" } }), want: "schema:Conversation" },
15
+ { kind: "receipt", node: asLinkedData({ kind: "receipt", kappa: K("e1"), props: { "schema:name": "tool-call receipt" } }), want: "schema:CreativeWork" },
16
+ ];
17
+
18
+ let pass = 0;
19
+ console.log("W3C semantic-typing gate — every κ-object is schema.org-typed + PROV-O + content-addressed\n");
20
+ for (const c of cases) {
21
+ const v = verifySemantic(c.node);
22
+ const types = [].concat(c.node["@type"]);
23
+ const hasWant = types.includes(c.want);
24
+ const ctxOk = c.node["@context"] === HOLO_CONTEXT || !!c.node["@context"];
25
+ // JSON round-trip must be lossless (it's just data)
26
+ let rt = false; try { rt = JSON.stringify(JSON.parse(JSON.stringify(c.node))) === JSON.stringify(c.node); } catch {}
27
+ const ok = v.ok && hasWant && ctxOk && rt;
28
+ if (ok) pass++;
29
+ console.log(`${ok ? "✓" : "✗"} ${c.kind.padEnd(13)} @id=${String(c.node["@id"]).slice(0, 24)}… @type=[${types.join(", ")}] schema:${v.hasSchema?"y":"n"} prov:${v.hasProv?"y":"n"} id:${v.hasId?"y":"n"} ctx:${ctxOk?"y":"n"} rt:${rt?"y":"n"}`);
30
+ }
31
+ const all = pass === cases.length;
32
+ console.log(`\n${all ? "PASS" : "FAIL"} — ${pass}/${cases.length} κ-object kinds carry a valid W3C @type`);
33
+ process.exit(all ? 0 : 1);
core/semantic.js ADDED
@@ -0,0 +1,98 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // core/semantic.js — the SEMANTIC SKIN (gap C2): every κ-object the substrate produces carries an
2
+ // open-semantic-web type, so humans, agents, and W3C validators all read it the same way. Closes the
3
+ // weakest audit axis (κ names objects, but few declared a W3C @type). Two W3C standards, both native:
4
+ // • JSON-LD / schema.org — WHAT a thing is (a skill is a schema:HowTo; a file is a schema:DigitalDocument)
5
+ // • PROV-O — HOW it came to be (the κ-chain becomes prov:wasRevisionOf links)
6
+ // Pure, dependency-free, browser+node. The result round-trips through any JSON-LD/RDF tool.
7
+
8
+ export const HOLO_CONTEXT = {
9
+ schema: "https://schema.org/",
10
+ prov: "http://www.w3.org/ns/prov#",
11
+ holo: "https://hologram.foundation/ns/",
12
+ "@vocab": "https://schema.org/",
13
+ };
14
+
15
+ // schema.org type for a substrate object kind (the canonical mapping, L2 — fixed at the ingest boundary)
16
+ const TYPE_FOR = {
17
+ skill: "HowTo", // a reusable procedure = schema:HowTo (steps + when-to-use)
18
+ file: "DigitalDocument",
19
+ app: "SoftwareApplication",
20
+ model: "SoftwareSourceCode",
21
+ conversation: "Conversation",
22
+ receipt: "CreativeWork", // a sealed work record: schema:CreativeWork that is also a prov:Entity
23
+ };
24
+
25
+ // wrap any object as a typed, content-addressed JSON-LD node. `kappa` is its did:holo (the @id —
26
+ // content IS the identity, Law L1). `prov` is the κ-chain → prov:wasRevisionOf links (PROV-O).
27
+ export function asLinkedData({ kind, kappa, props = {}, prov = [] }) {
28
+ const t = TYPE_FOR[kind] || "Thing";
29
+ const node = {
30
+ "@context": HOLO_CONTEXT,
31
+ "@id": kappa, // content-derived identity (no location — Law L1)
32
+ "@type": Array.isArray(t) ? t : ["schema:" + t, "prov:Entity"], // schema kind + PROV entity
33
+ ...props,
34
+ };
35
+ if (prov.length >= 2) { // the version chain → PROV-O revision links
36
+ const cur = prov[prov.length - 1], parent = prov[prov.length - 2];
37
+ node["prov:wasRevisionOf"] = { "@id": parent.kappa };
38
+ node["holo:version"] = cur.v;
39
+ }
40
+ node["prov:wasDerivedFrom"] = prov.length ? prov.map((p) => ({ "@id": p.kappa })) : undefined;
41
+ return node;
42
+ }
43
+
44
+ // a skill → schema:HowTo (steps become schema:HowToStep), with its PROV-O revision chain.
45
+ export function skillAsHowTo({ name, description, instructions, kappa, prov = [] }) {
46
+ const steps = String(instructions || "").split("\n").map((s) => s.trim()).filter(Boolean)
47
+ .map((text, i) => ({ "@type": "schema:HowToStep", "schema:position": i + 1, "schema:text": text.replace(/^\d+[.)]\s*/, "") }));
48
+ return asLinkedData({ kind: "skill", kappa, prov, props: { "schema:name": name, "schema:description": description, "schema:step": steps } });
49
+ }
50
+
51
+ // extension → IANA media type (schema:encodingFormat). Small, common set; default text/plain.
52
+ const MEDIA = { html: "text/html", htm: "text/html", js: "text/javascript", mjs: "text/javascript", json: "application/json", jsonld: "application/ld+json", css: "text/css", md: "text/markdown", txt: "text/plain", py: "text/x-python", svg: "image/svg+xml", wasm: "application/wasm", gz: "application/gzip" };
53
+ const mediaOf = (name) => MEDIA[String(name).toLowerCase().split(".").pop()] || "text/plain";
54
+
55
+ // a workspace file → schema:DigitalDocument (name + media type + byte size), content-addressed (L1).
56
+ export function fileAsDocument({ path, kappa, bytes = 0, mediaType, prov = [] }) {
57
+ const name = String(path).split("/").filter(Boolean).pop() || String(path);
58
+ return asLinkedData({ kind: "file", kappa, prov, props: {
59
+ "schema:name": name, "schema:identifier": kappa,
60
+ "schema:encodingFormat": mediaType || mediaOf(name), "schema:contentSize": bytes } });
61
+ }
62
+
63
+ // a built app → schema:SoftwareApplication (a self-contained WebGPU/Web app object).
64
+ export function appAsSoftware({ name, kappa, bytes = 0, prov = [] }) {
65
+ return asLinkedData({ kind: "app", kappa, prov, props: {
66
+ "schema:name": String(name).replace(/\.html$/i, ""), "schema:identifier": kappa,
67
+ "schema:applicationCategory": "WebApplication", "schema:operatingSystem": "Any (WebGPU/Web)",
68
+ "schema:fileSize": bytes } });
69
+ }
70
+
71
+ // a loaded model → schema:SoftwareSourceCode (the κ-object that runs on the GPU).
72
+ export function modelAsSource({ name, kappa, family, params, format, prov = [] }) {
73
+ return asLinkedData({ kind: "model", kappa, prov, props: {
74
+ "schema:name": name, "schema:identifier": kappa, "schema:programmingLanguage": "WGSL/WebGPU",
75
+ "holo:family": family, "holo:parameters": params, "holo:format": format } });
76
+ }
77
+
78
+ // one dispatcher so any producer can type any κ-object: linkedDataFor("app", {...}) etc.
79
+ export function linkedDataFor(kind, props = {}) {
80
+ switch (kind) {
81
+ case "skill": return skillAsHowTo(props);
82
+ case "file": return fileAsDocument(props);
83
+ case "app": return appAsSoftware(props);
84
+ case "model": return modelAsSource(props);
85
+ default: return asLinkedData({ kind, kappa: props.kappa, prov: props.prov || [], props: props.props || props });
86
+ }
87
+ }
88
+
89
+ // VERIFY (the gate): an object is semantically valid iff it has @context, an @id (κ), and a @type
90
+ // carrying both a schema.org kind and PROV-O lineage. Returns { ok, types, hasId, hasContext }.
91
+ export function verifySemantic(node) {
92
+ const types = [].concat(node && node["@type"] || []);
93
+ const hasSchema = types.some((t) => /^schema:|^https:\/\/schema\.org\//.test(t));
94
+ const hasProv = types.some((t) => /^prov:/.test(t)) || !!node["prov:wasDerivedFrom"];
95
+ const hasId = typeof node?.["@id"] === "string" && node["@id"].startsWith("did:holo:");
96
+ const hasContext = !!node?.["@context"];
97
+ return { ok: hasSchema && hasId && hasContext, hasSchema, hasProv, hasId, hasContext, types };
98
+ }
core/skills.js ADDED
@@ -0,0 +1,92 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // core/skills.js — SELF-EVOLVING AGENT SKILLS, 100% agentskills.io-compatible, enhanced by the UOR
2
+ // substrate. A skill is a folder with SKILL.md (name + description frontmatter + instructions) —
3
+ // the open Anthropic/agentskills.io format, portable to any skills-compatible agent. The Hermes
4
+ // idea (the agent autonomously CREATES and IMPROVES skills from experience) is realized as the
5
+ // agent's own tools. The substrate enhancement: every skill version is content-addressed (κ) and
6
+ // chained — a tamper-proof PROVENANCE of how the skill evolved (Holo Prov). Skills live in OPFS
7
+ // "/skills/<name>/SKILL.md"; the chain log in "/skills/<name>/.prov" (one κ per version).
8
+ //
9
+ // Progressive disclosure (the spec): DISCOVERY (names+descriptions injected at session start) →
10
+ // ACTIVATION (read_skill loads full SKILL.md) → EXECUTION (the agent follows it, using file tools).
11
+
12
+ import { skillAsHowTo, verifySemantic } from "./semantic.js";
13
+
14
+ const te = new TextEncoder(), td = new TextDecoder();
15
+ const sha = async (s) => "did:holo:sha256:" + [...new Uint8Array(await crypto.subtle.digest("SHA-256", te.encode(s)))].map((b) => b.toString(16).padStart(2, "0")).join("");
16
+ const slug = (s) => String(s || "").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 48) || "skill";
17
+
18
+ async function skillsRoot() { const d = await navigator.storage.getDirectory(); const a = await d.getDirectoryHandle("skills", { create: true }); return a; }
19
+ async function skillDir(name, create = false) { return (await skillsRoot()).getDirectoryHandle(slug(name), { create }); }
20
+ async function readText(dir, file) { const fh = await dir.getFileHandle(file); return td.decode(await (await fh.getFile()).arrayBuffer()); }
21
+ async function writeText(dir, file, text) { const fh = await dir.getFileHandle(file, { create: true }); const w = await fh.createWritable(); await w.write(te.encode(text)); await w.close(); }
22
+
23
+ // SKILL.md = YAML-ish frontmatter (name, description) + markdown instructions (verbatim spec form).
24
+ function buildSkillMd({ name, description, instructions }) {
25
+ return `---\nname: ${name}\ndescription: ${description}\n---\n\n${instructions || ""}\n`;
26
+ }
27
+ function parseSkillMd(text) {
28
+ const m = text.match(/^---\s*\n([\s\S]*?)\n---\s*\n?([\s\S]*)$/);
29
+ const meta = {}; if (m) for (const line of m[1].split("\n")) { const i = line.indexOf(":"); if (i > 0) meta[line.slice(0, i).trim()] = line.slice(i + 1).trim(); }
30
+ return { name: meta.name || "", description: meta.description || "", instructions: m ? m[2].trim() : text.trim() };
31
+ }
32
+
33
+ export async function listSkills() {
34
+ const out = [];
35
+ try { const root = await skillsRoot();
36
+ for await (const [n, h] of root.entries()) { if (h.kind !== "directory") continue;
37
+ try { const sk = parseSkillMd(await readText(h, "SKILL.md")); out.push({ slug: n, name: sk.name || n, description: sk.description }); } catch {} } }
38
+ catch {}
39
+ return out.sort((a, b) => a.name.localeCompare(b.name));
40
+ }
41
+
42
+ export async function readSkill(name) { const dir = await skillDir(name); return parseSkillMd(await readText(dir, "SKILL.md")); }
43
+
44
+ // save = create OR improve (self-evolution). Appends a κ to the provenance chain — every version
45
+ // sealed + chained to its parent, so the skill's evolution is verifiable (Holo Prov shape).
46
+ export async function saveSkill({ name, description, instructions }) {
47
+ const dir = await skillDir(name, true);
48
+ const md = buildSkillMd({ name, description, instructions });
49
+ const kappa = await sha(md);
50
+ let prov = []; try { prov = JSON.parse(await readText(dir, ".prov")); } catch {}
51
+ const parent = prov.length ? prov[prov.length - 1].kappa : null;
52
+ prov.push({ v: prov.length + 1, kappa, parent, descKappa: await sha(description || "") });
53
+ await writeText(dir, "SKILL.md", md);
54
+ await writeText(dir, ".prov", JSON.stringify(prov, null, 1));
55
+ // semantic skin (C2): emit the W3C linked-data view — schema:HowTo + PROV-O revision chain.
56
+ const ld = skillAsHowTo({ name, description, instructions, kappa, prov });
57
+ await writeText(dir, "skill.jsonld", JSON.stringify(ld, null, 1));
58
+ return { slug: slug(name), kappa, version: prov.length, parent, semantic: verifySemantic(ld).ok };
59
+ }
60
+
61
+ // the W3C linked-data view of a skill (schema:HowTo + PROV-O), for export / validation / agents.
62
+ export async function skillLinkedData(name) { try { return JSON.parse(await readText(await skillDir(name), "skill.jsonld")); } catch { return null; } }
63
+
64
+ export async function skillProvenance(name) { try { return JSON.parse(await readText(await skillDir(name), ".prov")); } catch { return []; } }
65
+
66
+ // DISCOVERY block (progressive disclosure stage 1): names + descriptions only — small footprint.
67
+ export async function skillsDiscoveryPrompt() {
68
+ const sk = await listSkills();
69
+ if (!sk.length) return "";
70
+ return "You have these learned SKILLS available (each a verifiable, content-addressed capability you built from experience). " +
71
+ "When a task matches a skill's description, call read_skill to load its full instructions, then follow them. " +
72
+ "When you complete a non-trivial task worth reusing, call save_skill to capture it (or improve an existing one):\n" +
73
+ sk.map((s) => `- ${s.name}: ${s.description}`).join("\n");
74
+ }
75
+
76
+ // the agent's skill tools (Hermes self-evolution: the agent creates/improves/uses skills itself)
77
+ export function skillTools() {
78
+ return [
79
+ {
80
+ def: { name: "list_skills", description: "List the learned skills available (name + description).", inputSchema: { type: "object", properties: {}, required: [] } },
81
+ serverName: "skills", call: async () => { const s = await listSkills(); return { text: s.length ? s.map((x) => `${x.name}: ${x.description}`).join("\n") : "(no skills yet)", isError: false }; },
82
+ },
83
+ {
84
+ def: { name: "read_skill", description: "Load a skill's full instructions by name, to follow them for the current task.", inputSchema: { type: "object", properties: { name: { type: "string" } }, required: ["name"] } },
85
+ serverName: "skills", call: async ({ name }) => { try { const s = await readSkill(name); return { text: `# ${s.name}\n${s.description}\n\n${s.instructions}`, isError: false }; } catch (e) { return { text: "no such skill: " + name, isError: true }; } },
86
+ },
87
+ {
88
+ def: { name: "save_skill", description: "Capture a reusable skill from what you just did (or improve an existing one). name: short; description: one line of WHEN to use it; instructions: the step-by-step procedure. Each save is sealed + chained (verifiable provenance).", inputSchema: { type: "object", properties: { name: { type: "string" }, description: { type: "string" }, instructions: { type: "string" } }, required: ["name", "description", "instructions"] } },
89
+ serverName: "skills", call: async ({ name, description, instructions }) => { try { const r = await saveSkill({ name, description, instructions }); return { text: `skill "${name}" saved (v${r.version}, ${r.kappa.slice(0, 28)}…${r.parent ? `, evolved from ${r.parent.slice(0, 20)}…` : ", first version"})`, isError: false }; } catch (e) { return { text: "save error: " + (e.message || e), isError: true }; } },
90
+ },
91
+ ];
92
+ }
core/state.js ADDED
@@ -0,0 +1,39 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // core/state.js — a tiny event bus + the app's shared mutable state. No framework: modules
2
+ // subscribe to named events and re-render their own DOM islands. Deliberately minimal — the
3
+ // durable truth lives in the κ-object store (Law L3); this is only the session's working set.
4
+
5
+ export function makeBus() {
6
+ const subs = new Map();
7
+ return {
8
+ on(ev, fn) { if (!subs.has(ev)) subs.set(ev, new Set()); subs.get(ev).add(fn); return () => subs.get(ev)?.delete(fn); },
9
+ emit(ev, data) { for (const fn of subs.get(ev) || []) { try { fn(data); } catch (e) { console.error(`[bus:${ev}]`, e); } } },
10
+ };
11
+ }
12
+
13
+ export function makeState() {
14
+ return {
15
+ // model / engine
16
+ engine: null, // core/engine.js instance (null until a model loads)
17
+ modelIndex: 0, // index into loader.MODELS
18
+ loading: false,
19
+ gateOk: false, // conscience constitution self-verify
20
+
21
+ // conversation working set
22
+ convId: null, // active conversation id
23
+ convKappa: null, // its latest sealed κ
24
+ title: "New Chat",
25
+ messages: [], // [{ obj (sealed κ-object), id, kappa, ...norm fields, tokenIds }]
26
+ chosenChild: new Map(), // parent messageId → chosen child messageId (sibling switch)
27
+ streaming: false,
28
+ abort: null, // AbortController while generating
29
+
30
+ // ui
31
+ navOpen: window.innerWidth > 900,
32
+ panelOpen: false, // the chat is the product — the panel opens on demand
33
+ panelTab: "params",
34
+ settingsOpen: false,
35
+
36
+ // preset (the active parameter bundle — LibreChat conversationPreset semantics)
37
+ preset: null,
38
+ };
39
+ }
core/store.js ADDED
@@ -0,0 +1,185 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // core/store.js — the κ-object CONVERSATION STORE. Conversations are NOT database rows; each
2
+ // Message and each Conversation is a self-verifying UOR object (a canonical JSON-LD document whose
3
+ // identity is did:holo:sha256 of its own content), and the LibreChat parentMessageId tree is
4
+ // expressed as content-addressed LINKS so a child's κ commits to its parent's κ — a Merkle-DAG
5
+ // (Law L3 dedup, Law L5 verify-on-resolve). The object envelope mirrors _shared/holo-object.mjs
6
+ // byte-for-byte (same UOR_CONTEXT, same address() = H(canonical without id), same link digests),
7
+ // but is implemented here over Web Crypto (core/kappa.js) so it runs in the browser AND in the
8
+ // pure-Node witness. Bytes are persisted through a pluggable BACKEND: holo-kstore (IndexedDB) in
9
+ // the app, a Map in the witness. No server; everything is content-addressed and re-derivable.
10
+
11
+ import { jcs, sha256hex, didHolo } from "./kappa.js";
12
+
13
+ const _enc = new TextEncoder();
14
+ const _dec = new TextDecoder();
15
+
16
+ // The base envelope @context — identical to _shared/holo-object.mjs.
17
+ export const UOR_CONTEXT = [
18
+ "https://www.w3.org/ns/did/v1",
19
+ "https://w3id.org/security/data-integrity/v2",
20
+ { schema: "https://schema.org/", prov: "http://www.w3.org/ns/prov#", dcterms: "http://purl.org/dc/terms/",
21
+ rel: "schema:additionalType", links: { "@id": "schema:hasPart", "@container": "@set" } },
22
+ ];
23
+
24
+ const hexOf = (did) => String(did).split(":").pop();
25
+ const _b64 = (u8) => { let s = ""; for (let i = 0; i < u8.length; i++) s += String.fromCharCode(u8[i]); return btoa(s); };
26
+ const _b64url = (u8) => _b64(u8).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
27
+ async function sha256raw(u8) { return new Uint8Array(await crypto.subtle.digest("SHA-256", u8)); }
28
+ async function sriOf(u8) { return "sha256-" + _b64(await sha256raw(u8)); }
29
+ async function mbOf(u8) { const d = await sha256raw(u8); const mh = new Uint8Array(d.length + 2); mh[0] = 0x12; mh[1] = 0x20; mh.set(d, 2); return "u" + _b64url(mh); }
30
+
31
+ // address(obj): self-verifying identity = did:holo:sha256:H(canonical content with id/alsoKnownAs removed).
32
+ const stripId = (obj) => { const { id, alsoKnownAs, ...content } = obj; return content; };
33
+ const addressOf = (obj) => didHolo(stripId(obj));
34
+ const canonStored = (sealed) => _enc.encode(jcs(sealed)); // the EXACT bytes a link's digest commits to (sealed, WITH id — mirrors holo-object put())
35
+
36
+ // contentLink(rel, kappa): a LEAF edge to raw content already addressed by its κ (no bytes needed) —
37
+ // identical to holo-object.contentLink. Verified at resolve by re-hashing the raw bytes to the address.
38
+ export function contentLink(rel, kappa, type = "schema:MediaObject") {
39
+ const hex = String(kappa).split(":").pop();
40
+ const digest = Uint8Array.from(hex.match(/.{2}/g).map((h) => parseInt(h, 16)));
41
+ const mh = new Uint8Array(digest.length + 2); mh[0] = 0x12; mh[1] = 0x20; mh.set(digest, 2);
42
+ return { id: `did:holo:sha256:${hex}`, rel, "@type": type, leaf: true, digestSRI: "sha256-" + _b64(digest), digestMultibase: "u" + _b64url(mh) };
43
+ }
44
+
45
+ // makeStore(backend) — backend = { get(hex)→Promise<Uint8Array|undefined>, put(hex,bytes)→Promise,
46
+ // putRaw?(key,bytes)/getRaw?(key) for the boot-index pointer record }.
47
+ export function makeStore(backend) {
48
+ const mem = new Map(); // hex → Uint8Array (session cache; sync verifyDeep operates over it)
49
+
50
+ async function putObj(obj) {
51
+ const sealed = { ...obj, id: await addressOf(obj) };
52
+ const bytes = canonStored(sealed);
53
+ const hex = hexOf(sealed.id);
54
+ mem.set(hex, bytes);
55
+ await backend.put(hex, bytes);
56
+ return sealed;
57
+ }
58
+ async function getBytes(did) { const hex = hexOf(did); let b = mem.get(hex); if (b) return b; b = await backend.get(hex); if (b) mem.set(hex, b); return b; }
59
+ async function getObj(did) { const b = await getBytes(did); return b ? JSON.parse(_dec.decode(b)) : null; }
60
+
61
+ // a typed Merkle edge to a sealed UOR child (the digest commits to the child's stored bytes).
62
+ async function uorLink(rel, child) {
63
+ const bytes = mem.get(hexOf(child.id)) || await backend.get(hexOf(child.id));
64
+ return { id: child.id, rel, "@type": child["@type"], digestSRI: await sriOf(bytes), digestMultibase: await mbOf(bytes) };
65
+ }
66
+
67
+ // build + seal + store a UOR object. `links` already built (children stored first → Merkle).
68
+ async function makeObject({ type, context = [], links = [], ...props }) {
69
+ const obj = { "@context": [...UOR_CONTEXT, ...context], "@type": type, ...props, ...(links.length ? { links } : {}) };
70
+ return putObj(obj);
71
+ }
72
+
73
+ const verify = async (obj) => obj.id === await addressOf(obj);
74
+
75
+ // verifyDeep — re-derive + verify the WHOLE DAG (Law L5 at every level). Pre-loads links into mem.
76
+ async function verifyDeep(obj, depth = 0) {
77
+ if (!(await verify(obj))) return { ok: false, at: obj.id, why: "id does not re-derive", depth };
78
+ let maxDepth = depth;
79
+ for (const link of obj.links || []) {
80
+ const bytes = await getBytes(link.id);
81
+ if (!bytes) return { ok: false, at: link.id, why: "unresolved link", depth };
82
+ if ((await sriOf(bytes)) !== link.digestSRI) return { ok: false, at: link.id, why: "link digest mismatch", depth };
83
+ if (link.leaf) { if ((await sha256hex(bytes)) !== hexOf(link.id)) return { ok: false, at: link.id, why: "content hash mismatch", depth }; maxDepth = Math.max(maxDepth, depth + 1); continue; }
84
+ const child = JSON.parse(_dec.decode(bytes));
85
+ if (child.id !== link.id) return { ok: false, at: link.id, why: "id/link mismatch", depth };
86
+ const r = await verifyDeep(child, depth + 1); if (!r.ok) return r; maxDepth = Math.max(maxDepth, r.depth);
87
+ }
88
+ return { ok: true, depth: maxDepth };
89
+ }
90
+
91
+ // tamper helper (for the witness): overwrite a stored object's bytes with a flipped field.
92
+ async function _corrupt(did, mutate) { const o = await getObj(did); mutate(o); const bytes = canonStored(o); mem.set(hexOf(did), bytes); await backend.put(hexOf(did), bytes); }
93
+
94
+ return { makeObject, putObj, getObj, getBytes, uorLink, contentLink, verify, verifyDeep, mem, backend, _corrupt };
95
+ }
96
+
97
+ // ── high-level chat API over a κ-object store ───────────────────────────────────────────────
98
+ // LibreChat-faithful field names. parentMessageId → an "lc:parentMessage" content-addressed link.
99
+ const LC = { lc: "https://librechat.ai/ns#" };
100
+ const rid = (p) => p + "-" + Math.random().toString(36).slice(2, 12); // id is a human handle; substrate identity is the κ
101
+
102
+ export function makeChatStore(backend, { now = () => new Date().toISOString() } = {}) {
103
+ const S = makeStore(backend);
104
+ const INDEX_KEY = "index:org.hologram.HoloQ";
105
+ const getIndex = async () => { try { const b = await (backend.getRaw ? backend.getRaw(INDEX_KEY) : backend.get(INDEX_KEY)); return b ? JSON.parse(_dec.decode(b)) : { conversations: [], presets: [] }; } catch { return { conversations: [], presets: [] }; } };
106
+ const putIndex = async (idx) => { const b = _enc.encode(JSON.stringify(idx)); return backend.putRaw ? backend.putRaw(INDEX_KEY, b) : backend.put(INDEX_KEY, b); };
107
+
108
+ // Persist a PROV-O inference receipt as raw content addressed by its own κ (rec.id = H(jcs(body))),
109
+ // so a message's contentLink("lc:receipt", rec.id) leaf-verifies (sha256(stored bytes) === κ).
110
+ async function saveReceipt(rec) {
111
+ const bytes = _enc.encode(jcs(rec.body));
112
+ const hex = String(rec.id).split(":").pop();
113
+ S.mem.set(hex, bytes); await backend.put(hex, bytes);
114
+ return rec.id;
115
+ }
116
+
117
+ // Save one message as a κ-object. `parent` (a sealed message object) is a Merkle uorLink — the
118
+ // branch tree, so the child's κ commits to the parent's κ. receiptKappa is a leaf content-link.
119
+ // tokenIds (the turn's exact token sequence) makes context reconstruction and Law-L5
120
+ // re-derivation possible after a cold reload.
121
+ async function saveMessage({ messageId, conversationId, sender, isCreatedByUser, model, text, content, tokenCount, tokenIds, error, unfinished, feedback, fromMemo, toolTrace, createdAt, parent, receiptKappa, toolReceiptKappas = [], files = [] }) {
122
+ const links = [];
123
+ if (parent) links.push({ ...(await S.uorLink("lc:parentMessage", parent)), "schema:name": "parentMessageId" });
124
+ if (receiptKappa) links.push({ ...S.contentLink("lc:receipt", receiptKappa, "prov:Activity"), "schema:name": "receipt" });
125
+ for (const k of toolReceiptKappas) links.push({ ...S.contentLink("lc:toolReceipt", k, "prov:Activity"), "schema:name": "toolReceipt" });
126
+ for (const f of files) links.push({ ...S.contentLink("schema:associatedMedia", f.kappa, "schema:MediaObject"), "schema:name": f.name });
127
+ return S.makeObject({
128
+ type: ["schema:Message", "prov:Entity"], context: [LC],
129
+ "schema:identifier": messageId || rid("msg"), "lc:conversationId": conversationId,
130
+ "lc:parentMessageId": (parent && parent["schema:identifier"]) || null, // the human handle of the parent (for export/tree rebuild)
131
+ "lc:sender": sender, "lc:isCreatedByUser": !!isCreatedByUser, "lc:model": model || null,
132
+ "schema:text": text || "", "lc:content": content || null, "lc:tokenCount": tokenCount ?? null,
133
+ "lc:tokenIds": tokenIds || null, "lc:fromMemo": !!fromMemo,
134
+ "lc:toolTrace": toolTrace || null,
135
+ "lc:error": error || null, "lc:unfinished": !!unfinished, "lc:feedback": feedback || null,
136
+ "schema:dateCreated": createdAt || now(),
137
+ links,
138
+ });
139
+ }
140
+
141
+ // Save the conversation κ-object → links to the head (active leaf) + branch tips. Re-sealed each turn.
142
+ async function saveConversation({ conversationId, title, tags, files, preset, createdAt, headMessage, branchTips = [] }) {
143
+ const links = [];
144
+ if (headMessage) links.push({ ...(await S.uorLink("lc:head", headMessage)), "schema:name": "head" });
145
+ for (const t of branchTips) links.push({ ...(await S.uorLink("lc:branchTip", t)), "schema:name": "tip" });
146
+ const conv = await S.makeObject({
147
+ type: ["schema:Conversation", "prov:Collection"], context: [LC],
148
+ "schema:identifier": conversationId, "schema:name": title || "New Chat",
149
+ "lc:tags": tags || [], "lc:files": files || [], "lc:preset": preset || null,
150
+ "schema:dateCreated": createdAt || now(), "schema:dateModified": now(),
151
+ links,
152
+ });
153
+ // update the boot index (pointer-only; the conversation itself is verifiable)
154
+ const idx = await getIndex();
155
+ const ptr = { conversationId, kappa: conv.id, title: conv["schema:name"], updatedAt: conv["schema:dateModified"], archived: false, favorite: false, folder: null };
156
+ const i = idx.conversations.findIndex((c) => c.conversationId === conversationId);
157
+ if (i >= 0) idx.conversations[i] = { ...idx.conversations[i], ...ptr }; else idx.conversations.unshift(ptr);
158
+ await putIndex(idx);
159
+ return conv;
160
+ }
161
+
162
+ async function listConversations() { return (await getIndex()).conversations; }
163
+ async function updatePointer(conversationId, patch) { const idx = await getIndex(); const i = idx.conversations.findIndex((c) => c.conversationId === conversationId); if (i >= 0) { idx.conversations[i] = { ...idx.conversations[i], ...patch }; await putIndex(idx); } }
164
+ async function loadConversation(conversationId) {
165
+ const idx = await getIndex(); const ptr = idx.conversations.find((c) => c.conversationId === conversationId); if (!ptr) return null;
166
+ const conv = await S.getObj(ptr.kappa); if (!conv) return null;
167
+ const v = await S.verifyDeep(conv);
168
+ return { conv, kappa: ptr.kappa, ok: v.ok, integrity: v };
169
+ }
170
+ const verifyConversation = async (kappa) => { const conv = await S.getObj(kappa); return conv ? S.verifyDeep(conv) : { ok: false, why: "not found" }; };
171
+
172
+ return { store: S, saveReceipt, saveMessage, saveConversation, listConversations, loadConversation, verifyConversation, updatePointer, getIndex, newId: rid };
173
+ }
174
+
175
+ // A simple Map backend (the witness uses this; the app wraps holo-kstore instead).
176
+ export function mapBackend() {
177
+ const m = new Map();
178
+ return {
179
+ get: async (hex) => m.get(hex),
180
+ put: async (hex, bytes) => { m.set(hex, bytes); },
181
+ getRaw: async (k) => m.get(k),
182
+ putRaw: async (k, bytes) => { m.set(k, bytes); },
183
+ _map: m,
184
+ };
185
+ }
core/tools.js ADDED
@@ -0,0 +1,112 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // core/tools.js — the agentic TOOL-CALLING loop over the local engine + MCP. Qwen2.5's
2
+ // native function-calling convention: the system turn declares tools as JSON schemas inside
3
+ // a <tools>…</tools> block; the model emits <tool_call>{"name":…,"arguments":…}</tool_call>;
4
+ // each result returns as a user-role <tool_response>…</tool_response> turn and generation
5
+ // continues — until a turn arrives with no tool call (the final answer) or maxRounds hits.
6
+ //
7
+ // Substrate-native: EVERY tool call is sealed as its own PROV-O activity (tool ⊕ args ⊕
8
+ // result, each by κ) and the per-call receipts ride the answer's receipt — a verifiable
9
+ // work-trail for agentic answers, the chat-layer analogue of Holo Orchestrate (ADR-045).
10
+
11
+ import { didHolo, kappaText, jcs } from "./kappa.js";
12
+
13
+ // The system turn that arms the model with tools (Qwen2.5 function-calling convention).
14
+ export function toolSystemPrompt(tools, extra = "") {
15
+ const decls = tools.map((t) => JSON.stringify({ type: "function", function: { name: t.name, description: t.description || "", parameters: t.inputSchema || { type: "object", properties: {} } } })).join("\n");
16
+ return (
17
+ (extra ? extra + "\n\n" : "") +
18
+ "# Tools\n\nYou may call one or more functions to assist with the user query.\n\n" +
19
+ "You are provided with function signatures within <tools></tools> XML tags:\n<tools>\n" + decls + "\n</tools>\n\n" +
20
+ "For each function call, return a json object with function name and arguments within <tool_call></tool_call> XML tags:\n" +
21
+ '<tool_call>\n{"name": <function-name>, "arguments": <args-json-object>}\n</tool_call>'
22
+ );
23
+ }
24
+
25
+ // Parse <tool_call> blocks out of a model turn. Tolerant: bare JSON with name+arguments
26
+ // also counts (small models sometimes drop the tags).
27
+ export function parseToolCalls(text) {
28
+ const calls = [];
29
+ const re = /<tool_call>\s*([\s\S]*?)\s*<\/tool_call>/g;
30
+ let m;
31
+ while ((m = re.exec(text))) { try { const j = JSON.parse(m[1]); if (j && j.name) calls.push({ name: j.name, arguments: j.arguments || {} }); } catch {} }
32
+ // ```json fenced code blocks with {name, arguments} (Qwen2.5-Coder often emits this form)
33
+ if (!calls.length) {
34
+ const cb = /```(?:json|tool_call)?\s*(\{[\s\S]*?\})\s*```/g;
35
+ while ((m = cb.exec(text))) { try { const j = JSON.parse(m[1]); if (j && j.name && ("arguments" in j)) calls.push({ name: j.name, arguments: j.arguments || {} }); } catch {} }
36
+ }
37
+ if (!calls.length) {
38
+ const t = text.trim();
39
+ if (t.startsWith("{") && t.includes('"name"')) { try { const j = JSON.parse(t); if (j.name && ("arguments" in j)) calls.push({ name: j.name, arguments: j.arguments || {} }); } catch {} }
40
+ }
41
+ return calls;
42
+ }
43
+
44
+ export const stripToolCalls = (text) => text
45
+ .replace(/<tool_call>[\s\S]*?<\/tool_call>/g, "")
46
+ .replace(/```(?:json|tool_call)?\s*\{[\s\S]*?"name"[\s\S]*?\}\s*```/g, "") // strip fenced tool calls too
47
+ .trim();
48
+
49
+ // Seal one tool call as a PROV-O activity (its own did:holo — tamper → refuse).
50
+ export async function sealToolReceipt({ server, tool, args, resultText, ok }) {
51
+ const body = {
52
+ "@context": ["http://www.w3.org/ns/prov#", { holo: "https://hologram.os/ns/q#" }],
53
+ "@type": "prov:Activity", "holo:kind": "tool-call",
54
+ "prov:used": { "holo:server": server, "holo:tool": tool, "holo:args": await didHolo(JSON.parse(jcs(args || {}))), "holo:argsJson": jcs(args || {}) },
55
+ "prov:generated": { "holo:result": await kappaText(resultText), "holo:ok": !!ok },
56
+ };
57
+ return { id: await didHolo(body), body };
58
+ }
59
+
60
+ // Frame the agentic first turn: system(tools) + user prompt (ChatML, by hand so the system
61
+ // block rides this turn). Exposed so the caller can persist the EXACT ids that run (the
62
+ // ctx-concat invariant of the message tree).
63
+ export function frameAgenticTurn({ tools, promptText, hasHistory, extraSystem = "" }) {
64
+ const sys = toolSystemPrompt(tools.map((t) => t.def), extraSystem);
65
+ return (hasHistory ? "<|im_end|>\n" : "") +
66
+ `<|im_start|>system\n${sys}<|im_end|>\n<|im_start|>user\n${promptText}<|im_end|>\n<|im_start|>assistant\n`;
67
+ }
68
+
69
+ // The loop. `callbacks.onToolCall({name,args})` / `onToolResult({name,text,receipt})` let the
70
+ // UI stream the work-trail live. Returns { text, rounds, toolReceipts, trace, ids }.
71
+ export async function runToolLoop({ engine, tools, promptText, ctxIds, firstFramed, signal, onToken, onToolCall, onToolResult, maxRounds = 4 }) {
72
+ const toolReceipts = []; const trace = [];
73
+ let framed = firstFramed || frameAgenticTurn({ tools, promptText, hasHistory: ctxIds.length > 0 });
74
+ let ids = ctxIds.slice();
75
+ let finalText = "", rounds = 0;
76
+
77
+ for (; rounds < maxRounds; rounds++) {
78
+ const turnIds = engine.tokenize(framed);
79
+ ids = ids.concat(turnIds);
80
+ // tight per-round budget: a tool_call is ~50 tokens, a final answer ~300 — snappy rounds,
81
+ // the same total work, far lower latency than letting one round run the whole cap.
82
+ // generous budget: tool calls that carry CODE (write_file, build_app) need room — a truncated
83
+ // call is invalid JSON and silently fails. 1536 fits a small app; plain rounds end early on EOS.
84
+ const res = await engine.generate(ids, { signal, onToken, maxNew: 1536 });
85
+ ids = res.ids;
86
+ const calls = parseToolCalls(res.text);
87
+ if (!calls.length || (signal && signal.aborted)) { finalText = stripToolCalls(res.text); break; }
88
+
89
+ // execute every call this round through MCP, seal a receipt each
90
+ const responses = [];
91
+ for (const c of calls) {
92
+ if (signal && signal.aborted) break;
93
+ const t = tools.find((x) => x.def.name === c.name);
94
+ onToolCall && onToolCall({ name: c.name, args: c.arguments, server: t ? t.serverName : "?" });
95
+ let text, ok = true, render = null;
96
+ try {
97
+ if (!t) throw new Error("unknown tool: " + c.name);
98
+ const r = await t.call(c.arguments);
99
+ text = r.text; ok = !r.isError; render = r.render || null; // build_app → live preview payload
100
+ } catch (e) { text = "ERROR: " + (e && e.message || e); ok = false; }
101
+ const receipt = await sealToolReceipt({ server: t ? t.serverName : "?", tool: c.name, args: c.arguments, resultText: text, ok });
102
+ toolReceipts.push(receipt);
103
+ trace.push({ name: c.name, args: c.arguments, text: text.slice(0, 4000), ok, receiptId: receipt.id, server: t ? t.serverName : "?" });
104
+ onToolResult && onToolResult({ name: c.name, text, ok, receipt, render });
105
+ responses.push(`<tool_response>\n${text.slice(0, 2400)}\n</tool_response>`); // bounded — tool output must fit the local context window
106
+ }
107
+ // feed results back (user role per the Qwen convention) and continue
108
+ framed = `<|im_end|>\n<|im_start|>user\n${responses.join("\n")}<|im_end|>\n<|im_start|>assistant\n`;
109
+ finalText = stripToolCalls(res.text); // best-so-far, replaced if a later round answers
110
+ }
111
+ return { text: finalText, rounds: rounds + 1, toolReceipts, trace, ids };
112
+ }
forge/gguf-forge-iq-dequant.mjs ADDED
@@ -0,0 +1,260 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // IQ-quant dequantization, transcribed line-for-line from ggml-quants.c
2
+ // (llama.cpp b7248): dequantize_row_iq2_xxs:3077 iq2_xs:3105 iq2_s:3132
3
+ // iq3_xxs:3164 iq3_s:3196 iq1_s:3239 iq1_m:3264 iq4_nl:3314 iq4_xs:3332.
4
+ // Block layouts: ggml-common.h:485-563.
5
+ //
6
+ // ONE source of truth, two behaviours: makeIQ(fr, f16ToF32) binds the rounding
7
+ // function. The Tier-A oracle passes Math.fround (binary32-exact, witnessed
8
+ // bit-for-bit vs ggml to_float in gguf-forge-iq.test.mjs); the runtime passes
9
+ // identity (float64, fine for the GPU engine which re-quantizes anyway).
10
+ //
11
+ // Codebook grids are flattened little-endian byte runs (gguf-forge-iq-grids.mjs):
12
+ // an entry `idx` of stride S occupies bytes [idx*S .. idx*S+S). iq2*/iq3* read
13
+ // uint8, iq1s_grid / kvalues_iq4nl read int8.
14
+
15
+ import {
16
+ iq2xxs_grid, iq2xs_grid, iq2s_grid, iq3xxs_grid, iq3s_grid, iq1s_grid,
17
+ ksigns_iq2xs, kmask_iq2xs, kvalues_iq4nl,
18
+ } from "./gguf-forge-iq-grids.mjs";
19
+
20
+ const QK_K = 256;
21
+ const IQ1S_DELTA = 0.125, IQ1M_DELTA = 0.125;
22
+ const s8 = (b) => (b << 24) >> 24; // uint8 -> int8
23
+
24
+ export function makeIQ(fr, f16ToF32) {
25
+ const sign1 = (signs, j) => (signs & kmask_iq2xs[j] ? -1 : 1);
26
+
27
+ // dequantize_row_iq2_xxs (:3077). Block 66 B: d:f16 qs:uint16[32].
28
+ function dequantIQ2XXS(raw, elements) {
29
+ const out = new Float32Array(elements);
30
+ const dv = new DataView(raw.buffer, raw.byteOffset, raw.byteLength);
31
+ const nb = elements / QK_K; let o = 0;
32
+ for (let i = 0; i < nb; i++) {
33
+ const bp = i * 66, qs = bp + 2;
34
+ const d = f16ToF32(dv.getUint16(bp, true));
35
+ for (let ib32 = 0; ib32 < QK_K / 32; ++ib32) {
36
+ const a0 = dv.getUint32(qs + 8 * ib32, true), a1 = dv.getUint32(qs + 8 * ib32 + 4, true);
37
+ const db = fr(fr(d * fr(0.5 + (a1 >>> 28))) * 0.25);
38
+ for (let l = 0; l < 4; ++l) {
39
+ const idx = (a0 >>> (8 * l)) & 0xff;
40
+ const signs = ksigns_iq2xs[(a1 >>> (7 * l)) & 127];
41
+ for (let j = 0; j < 8; ++j) out[o++] = sign1(signs, j) * fr(db * iq2xxs_grid[idx * 8 + j]);
42
+ }
43
+ }
44
+ }
45
+ return out;
46
+ }
47
+
48
+ // dequantize_row_iq2_xs (:3105). Block 74 B: d:f16 qs:uint16[32] scales:uint8[8].
49
+ function dequantIQ2XS(raw, elements) {
50
+ const out = new Float32Array(elements);
51
+ const dv = new DataView(raw.buffer, raw.byteOffset, raw.byteLength);
52
+ const nb = elements / QK_K; let o = 0;
53
+ for (let i = 0; i < nb; i++) {
54
+ const bp = i * 74, qs = bp + 2, scB = bp + 66;
55
+ const d = f16ToF32(dv.getUint16(bp, true));
56
+ for (let ib32 = 0; ib32 < QK_K / 32; ++ib32) {
57
+ const db = [fr(fr(d * fr(0.5 + (raw[scB + ib32] & 0xf))) * 0.25),
58
+ fr(fr(d * fr(0.5 + (raw[scB + ib32] >> 4))) * 0.25)];
59
+ for (let l = 0; l < 4; ++l) {
60
+ const q = dv.getUint16(qs + (4 * ib32 + l) * 2, true);
61
+ const idx = q & 511, signs = ksigns_iq2xs[q >> 9], dl = db[l >> 1];
62
+ for (let j = 0; j < 8; ++j) out[o++] = sign1(signs, j) * fr(dl * iq2xs_grid[idx * 8 + j]);
63
+ }
64
+ }
65
+ }
66
+ return out;
67
+ }
68
+
69
+ // dequantize_row_iq2_s (:3132). Block 82 B: d:f16 qs:uint8[64] qh:uint8[8] scales:uint8[8].
70
+ // signs = qs + 32 (second half of qs); idx = qs[l] | ((qh[ib32]<<(8-2l)) & 0x300).
71
+ function dequantIQ2S(raw, elements) {
72
+ const out = new Float32Array(elements);
73
+ const dv = new DataView(raw.buffer, raw.byteOffset, raw.byteLength);
74
+ const nb = elements / QK_K; let o = 0;
75
+ for (let i = 0; i < nb; i++) {
76
+ const bp = i * 82, qhB = bp + 66, scB = bp + 74;
77
+ const d = f16ToF32(dv.getUint16(bp, true));
78
+ let qs = bp + 2, signs = bp + 2 + 32;
79
+ for (let ib32 = 0; ib32 < QK_K / 32; ++ib32) {
80
+ const db = [fr(fr(d * fr(0.5 + (raw[scB + ib32] & 0xf))) * 0.25),
81
+ fr(fr(d * fr(0.5 + (raw[scB + ib32] >> 4))) * 0.25)];
82
+ for (let l = 0; l < 4; ++l) {
83
+ const dl = db[l >> 1];
84
+ const idx = raw[qs + l] | (((raw[qhB + ib32] << (8 - 2 * l)) & 0x300));
85
+ for (let j = 0; j < 8; ++j) out[o++] = sign1(raw[signs + l], j) * fr(dl * iq2s_grid[idx * 8 + j]);
86
+ }
87
+ qs += 4; signs += 4;
88
+ }
89
+ }
90
+ return out;
91
+ }
92
+
93
+ // dequantize_row_iq3_xxs (:3164). Block 98 B: d:f16 qs:uint8[96].
94
+ // qs[0..63]=grid idx; scales_and_signs = qs+64 (8 uint32). db scale uses *0.5.
95
+ function dequantIQ3XXS(raw, elements) {
96
+ const out = new Float32Array(elements);
97
+ const dv = new DataView(raw.buffer, raw.byteOffset, raw.byteLength);
98
+ const nb = elements / QK_K; let o = 0;
99
+ for (let i = 0; i < nb; i++) {
100
+ const bp = i * 98, ss = bp + 2 + 64;
101
+ const d = f16ToF32(dv.getUint16(bp, true));
102
+ let qs = bp + 2;
103
+ for (let ib32 = 0; ib32 < QK_K / 32; ++ib32) {
104
+ const a32 = dv.getUint32(ss + 4 * ib32, true);
105
+ const db = fr(fr(d * fr(0.5 + (a32 >>> 28))) * 0.5);
106
+ for (let l = 0; l < 4; ++l) {
107
+ const signs = ksigns_iq2xs[(a32 >>> (7 * l)) & 127];
108
+ const g1 = raw[qs + 2 * l] * 4, g2 = raw[qs + 2 * l + 1] * 4;
109
+ for (let j = 0; j < 4; ++j) {
110
+ out[o + j + 0] = sign1(signs, j + 0) * fr(db * iq3xxs_grid[g1 + j]);
111
+ out[o + j + 4] = sign1(signs, j + 4) * fr(db * iq3xxs_grid[g2 + j]);
112
+ }
113
+ o += 8;
114
+ }
115
+ qs += 8;
116
+ }
117
+ }
118
+ return out;
119
+ }
120
+
121
+ // dequantize_row_iq3_s (:3196). Block 110 B: d:f16 qs:uint8[64] qh:uint8[8] signs:uint8[32] scales:uint8[4].
122
+ function dequantIQ3S(raw, elements) {
123
+ const out = new Float32Array(elements);
124
+ const dv = new DataView(raw.buffer, raw.byteOffset, raw.byteLength);
125
+ const nb = elements / QK_K; let o = 0;
126
+ for (let i = 0; i < nb; i++) {
127
+ const bp = i * 110, scB = bp + 106;
128
+ const d = f16ToF32(dv.getUint16(bp, true));
129
+ let qs = bp + 2, qh = bp + 66, signs = bp + 74;
130
+ for (let ib32 = 0; ib32 < QK_K / 32; ib32 += 2) {
131
+ const db1 = fr(d * (1 + 2 * (raw[scB + (ib32 >> 1)] & 0xf)));
132
+ const db2 = fr(d * (1 + 2 * (raw[scB + (ib32 >> 1)] >> 4)));
133
+ for (let l = 0; l < 4; ++l) {
134
+ const g1 = (raw[qs + 2 * l] | ((raw[qh] << (8 - 2 * l)) & 256)) * 4;
135
+ const g2 = (raw[qs + 2 * l + 1] | ((raw[qh] << (7 - 2 * l)) & 256)) * 4;
136
+ for (let j = 0; j < 4; ++j) {
137
+ out[o + j + 0] = sign1(raw[signs + l], j + 0) * fr(db1 * iq3s_grid[g1 + j]);
138
+ out[o + j + 4] = sign1(raw[signs + l], j + 4) * fr(db1 * iq3s_grid[g2 + j]);
139
+ }
140
+ o += 8;
141
+ }
142
+ qs += 8; signs += 4;
143
+ for (let l = 0; l < 4; ++l) {
144
+ const g1 = (raw[qs + 2 * l] | ((raw[qh + 1] << (8 - 2 * l)) & 256)) * 4;
145
+ const g2 = (raw[qs + 2 * l + 1] | ((raw[qh + 1] << (7 - 2 * l)) & 256)) * 4;
146
+ for (let j = 0; j < 4; ++j) {
147
+ out[o + j + 0] = sign1(raw[signs + l], j + 0) * fr(db2 * iq3s_grid[g1 + j]);
148
+ out[o + j + 4] = sign1(raw[signs + l], j + 4) * fr(db2 * iq3s_grid[g2 + j]);
149
+ }
150
+ o += 8;
151
+ }
152
+ qh += 2; qs += 8; signs += 4;
153
+ }
154
+ }
155
+ return out;
156
+ }
157
+
158
+ // dequantize_row_iq1_s (:3239). Block 50 B: d:f16 qs:uint8[32] qh:uint16[8]. grid int8.
159
+ function dequantIQ1S(raw, elements) {
160
+ const out = new Float32Array(elements);
161
+ const dv = new DataView(raw.buffer, raw.byteOffset, raw.byteLength);
162
+ const nb = elements / QK_K; let o = 0;
163
+ for (let i = 0; i < nb; i++) {
164
+ const bp = i * 50, qhB = bp + 34;
165
+ const d = f16ToF32(dv.getUint16(bp, true));
166
+ let qs = bp + 2;
167
+ for (let ib = 0; ib < QK_K / 32; ++ib) {
168
+ const qh = dv.getUint16(qhB + 2 * ib, true);
169
+ const dl = fr(d * (2 * ((qh >> 12) & 7) + 1));
170
+ const delta = (qh & 0x8000) ? -IQ1S_DELTA : IQ1S_DELTA;
171
+ for (let l = 0; l < 4; ++l) {
172
+ const g = (raw[qs + l] | (((qh >> (3 * l)) & 7) << 8)) * 8;
173
+ for (let j = 0; j < 8; ++j) out[o++] = fr(dl * fr(s8(iq1s_grid[g + j]) + delta));
174
+ }
175
+ qs += 4;
176
+ }
177
+ }
178
+ return out;
179
+ }
180
+
181
+ // dequantize_row_iq1_m (:3264). Block 56 B: qs:uint8[32] qh:uint8[16] scales:uint8[8].
182
+ // No d field — f16 scale is woven from the four scale uint16s. grid int8.
183
+ function dequantIQ1M(raw, elements) {
184
+ const out = new Float32Array(elements);
185
+ const dv = new DataView(raw.buffer, raw.byteOffset, raw.byteLength);
186
+ const nb = elements / QK_K; let o = 0;
187
+ const delta = new Float32Array(4), idx = new Uint16Array(4);
188
+ for (let i = 0; i < nb; i++) {
189
+ const bp = i * 56, scB = bp + 48;
190
+ const sc = [dv.getUint16(scB, true), dv.getUint16(scB + 2, true), dv.getUint16(scB + 4, true), dv.getUint16(scB + 6, true)];
191
+ const u16 = (sc[0] >> 12) | ((sc[1] >> 8) & 0x00f0) | ((sc[2] >> 4) & 0x0f00) | (sc[3] & 0xf000);
192
+ const d = f16ToF32(u16);
193
+ let qs = bp, qh = bp + 32;
194
+ for (let ib = 0; ib < QK_K / 32; ++ib) {
195
+ const dl1 = fr(d * (2 * ((sc[ib >> 1] >> (6 * (ib % 2) + 0)) & 0x7) + 1));
196
+ const dl2 = fr(d * (2 * ((sc[ib >> 1] >> (6 * (ib % 2) + 3)) & 0x7) + 1));
197
+ idx[0] = raw[qs + 0] | ((raw[qh + 0] << 8) & 0x700);
198
+ idx[1] = raw[qs + 1] | ((raw[qh + 0] << 4) & 0x700);
199
+ idx[2] = raw[qs + 2] | ((raw[qh + 1] << 8) & 0x700);
200
+ idx[3] = raw[qs + 3] | ((raw[qh + 1] << 4) & 0x700);
201
+ delta[0] = raw[qh + 0] & 0x08 ? -IQ1M_DELTA : IQ1M_DELTA;
202
+ delta[1] = raw[qh + 0] & 0x80 ? -IQ1M_DELTA : IQ1M_DELTA;
203
+ delta[2] = raw[qh + 1] & 0x08 ? -IQ1M_DELTA : IQ1M_DELTA;
204
+ delta[3] = raw[qh + 1] & 0x80 ? -IQ1M_DELTA : IQ1M_DELTA;
205
+ for (let l = 0; l < 2; ++l) {
206
+ const g = idx[l] * 8;
207
+ for (let j = 0; j < 8; ++j) out[o++] = fr(dl1 * fr(s8(iq1s_grid[g + j]) + delta[l]));
208
+ }
209
+ for (let l = 2; l < 4; ++l) {
210
+ const g = idx[l] * 8;
211
+ for (let j = 0; j < 8; ++j) out[o++] = fr(dl2 * fr(s8(iq1s_grid[g + j]) + delta[l]));
212
+ }
213
+ qs += 4; qh += 2;
214
+ }
215
+ }
216
+ return out;
217
+ }
218
+
219
+ // dequantize_row_iq4_nl (:3314). Block 18 B / 32 elems: d:f16 qs:uint8[16]. kvalues int8.
220
+ function dequantIQ4NL(raw, elements) {
221
+ const out = new Float32Array(elements);
222
+ const dv = new DataView(raw.buffer, raw.byteOffset, raw.byteLength);
223
+ const nb = elements / 32; let o = 0;
224
+ for (let i = 0; i < nb; i++) {
225
+ const bp = i * 18, qs = bp + 2;
226
+ const d = f16ToF32(dv.getUint16(bp, true));
227
+ for (let j = 0; j < 16; ++j) {
228
+ out[o + j] = fr(d * s8(kvalues_iq4nl[raw[qs + j] & 0xf]));
229
+ out[o + 16 + j] = fr(d * s8(kvalues_iq4nl[raw[qs + j] >> 4]));
230
+ }
231
+ o += 32;
232
+ }
233
+ return out;
234
+ }
235
+
236
+ // dequantize_row_iq4_xs (:3332). Block 136 B: d:f16 scales_h:uint16 scales_l:uint8[4] qs:uint8[128].
237
+ function dequantIQ4XS(raw, elements) {
238
+ const out = new Float32Array(elements);
239
+ const dv = new DataView(raw.buffer, raw.byteOffset, raw.byteLength);
240
+ const nb = elements / QK_K; let o = 0;
241
+ for (let i = 0; i < nb; i++) {
242
+ const bp = i * 136, slB = bp + 4;
243
+ const d = f16ToF32(dv.getUint16(bp, true));
244
+ const sh = dv.getUint16(bp + 2, true);
245
+ let qs = bp + 8;
246
+ for (let ib = 0; ib < QK_K / 32; ++ib) {
247
+ const ls = ((raw[slB + (ib >> 1)] >> (4 * (ib % 2))) & 0xf) | (((sh >> (2 * ib)) & 3) << 4);
248
+ const dl = fr(d * (ls - 32));
249
+ for (let j = 0; j < 16; ++j) {
250
+ out[o + j] = fr(dl * s8(kvalues_iq4nl[raw[qs + j] & 0xf]));
251
+ out[o + 16 + j] = fr(dl * s8(kvalues_iq4nl[raw[qs + j] >> 4]));
252
+ }
253
+ o += 32; qs += 16;
254
+ }
255
+ }
256
+ return out;
257
+ }
258
+
259
+ return { dequantIQ2XXS, dequantIQ2XS, dequantIQ2S, dequantIQ3XXS, dequantIQ3S, dequantIQ1S, dequantIQ1M, dequantIQ4NL, dequantIQ4XS };
260
+ }
forge/gguf-forge-iq-grids.mjs ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // AUTO-GENERATED by gen-iq-grids.mjs from ggml-common.h — do not edit by hand.
2
+ // IQ-quant codebook grids + helper tables, flattened to little-endian byte runs.
3
+ // A grid entry `idx` of stride S occupies bytes [idx*S .. idx*S+S); read uint8
4
+ // for iq2*/iq3* grids, int8 (`(b<<24)>>24`) for iq1s_grid / kvalues_iq4nl.
5
+ const D = (s) => Uint8Array.from(atob(s), (c) => c.charCodeAt(0));
6
+ export const iq2xxs_grid = /* 2048B */ D("CAgICAgICAgrCAgICAgICBkZCAgICAgICCsICAgICAgrKwgICAgICBkIGQgICAgICBkZCAgICAgICCsICAgICCsIKwgICAgICCsrCAgICAgrKysICAgICBkICBkICAgICBkIGQgICAgICBkZCAgICAgrGRkICAgIGQgrGQgICAgIGSsZCAgICAgICCsICAgIKwgIKwgICAgrKwgrCAgICCsIKysICAgIGQgICBkICAgIGQgIGQgICAgIGQgZCAgIGRkZCBkICAgICAgZGQgICAgZCCsZCAgICCsZKxkICAgICAgIKwgICCsICAgrCAgIKwgrCCsICAgrCAgrKwgICBkICAgIGQgICBkICAgZCAgICBkICBkICBkIKwgIGQgICBkrCAgZCAgICAgZCBkICCsICBkIGQgICCsIGQgZCAgICCsZCBkICBkICCsIGQgICBkIKwgZCAgICBkrCBkICAgZKysIGQgICAgICBkZCAgrCAgIGRkICAgrCAgZGQgICAgrCBkZCAgrGQgZGRkICBkrKxkZGQgICAgIKxkZCAgZCBkrGRkICBkrCAgrGQgICAgZCCsZCAgICAgZKxkICAgZCCsrGQgICBkrKysZCAgICAgICCsICBkZCAgIKwgICCsICAgrCAgIGRkICCsICAgrKwgIKwgIGQgIGQgrCAgIGQgZCCsICAgIGRkIKwgIKwgZGQgrCAgIKwgrCCsICAgZCAgZKwgICAgIGRkrCAgrCAgIKysICAgZGQgrKwgIGQgICAgIGQgIGQgICAgZCAgIGQgICBkIGQgrCAgIGQgICAgZCAgZCAgIKxkICBkICBkIKwgIGQgICBkrCAgZCBkZGSsICBkICAgICBkIGQgIKwgIGQgZCAgIKwgZCBkICAgZGRkIGQgrKxkZGQgZCAgICCsZCBkICBkrCCsIGQgZGQgZKwgZCAgICAgIGRkICCsICAgZGQgICCsICBkZCBkZKwgIGRkIGSsIGQgZGQgICAgrCBkZCAgrGQgZGRkIKwgrGRkZGQgICAgIKxkZCCsZGQgrGRkIGQgICAgrGQgIGQgICCsZCAgIGQgIKxkICAgIGQgrGQgZCAgrCCsZCAgICAgZKxkIGRkICBkrGQgICCsrGSsZCBkIGRkrKxkICAgICAgIKwgrCAgICAgrCCsrCAgICCsICBkIGQgIKwgZCCsZCAgrCAgICCsICCsIKwgIKwgIKwgZKysIGQgrCAgrCBkZCCsICAgICCsIKwgrCAgIKwgrCBkICAgIGSsICBkICAgZKwgICBkICBkrCAgICBkIGSsIKxkZGQgZKwgICAgIGRkrCBkICBkZGSsICBkrGRkZKwgICBkrKxkrCAgrCAgIKysICAgrCAgrKwgIGRkrCCsrCAgZCBkrKysIGQgICAgICBkIGQgICAgIGQgIGQgICAgZCCsZCAgICBkZCCsICAgIGQgZKwgICAgZCAgIGQgICBkIKwgZCAgIGSsZGRkICAgZCAgrGQgICBkZCAgrCAgIGQgZCCsICAgZCAgZKwgICBkICAgIGQgIGQgIKwgZCAgZGQgrGRkICBkICAgrGQgIGRkZCCsZCAgZGQgICCsICBkICBkIKwgIGQgrCBkrCAgZKxkZGSsICBkIKysZKwgIGQgICAgIGQgZCCsICAgZCBkICCsICBkIGQgICCsIGQgZGSsZKwgZCBkrCBkIGRkIGQgZKwgZGQgZCAgICCsZCBkZCAgICCsIGQgZCAgIKwgZCAgZCAgrCBkICAgZCCsIGRkZCBkIKwgZCAgICBkrCBkIKxkZGSsIGRkIKxkZKwgZKwgIKxkrCBkZGQgZKysIGQgIGSsrKwgZCAgICAgIGRkIKwgICAgZGRkIGQgICBkZGSsZCAgIGRkICCsICAgZGQgICCsICBkZCCsIKwgIGRkIGQgIGQgZGSsICBkZCBkZCBkrKxkIGRkZCBkrKwgZGQgIGSsIGRkZKwgZKwgZGRkrKwgIGRkZGRkICAgrGRkZCBkZGSsZGRkICAgICCsZGRkIGQgIKxkZGSsZCAgrGRkIGSsZCCsZGQgICBkZKxkZCCsICCsrGRkIGQgICAgrGQgIGQgICCsZCAgIGQgIKxkIKysZCAgrGQgICAgZCCsZGRkZGRkIKxkIKxkIKwgrGQgIKxkrCCsZCAgICAgZKxkZGQgICBkrGQgIGQgZGSsZKwgZCBkZKxkIGQgrGRkrGSsICBkIKysZCAgICAgICCsrCAgICAgIKysrCAgICAgrGQgIGQgICCsrCAgrCAgIKwgZCAgZCAgrCCsZCBkICCsICAgZGQgIKxkIGQgrCAgrGQgICAgZCCsIGQgICBkIKwgIGQgIGQgrGRkZCAgZCCsICAgZCBkIKwgIKxkIGQgrCAgICBkZCCsrGQgZGRkIKwgZGSsZGQgrGSsICCsZCCsICAgZKxkIKwgIKxkrGQgrKwgICAgrCCsIGQgIGSsIKxkIGQgrKwgrCBkICAgIGSsICBkICAgZKwgZKwgICBkrCAgIGQgIGSsZCCsrCAgZKysZGQgZCBkrCAgIKxkIGSsZGQgZKwgZKwgICAgIGRkrKwgrCAgZGSsIGQgZCBkZKxkIGRkZGRkrGQgIKwgrGSsICCsIGSsZKysICAgICCsrCAgZGQgIKysZGQgrCAgrKxkrCAgZCCsrCAgICCsIKysIKxkICBkrKwgIGRkIKysrCBkICBkrKys=");
7
+ export const iq2xs_grid = /* 4096B */ D("CAgICAgICAgrCAgICAgICBkZCAgICAgICCsICAgICAgrKwgICAgICBkIGQgICAgICBkZCAgICAgrGRkICAgICBkrGQgICAgICAgrCAgICAgrCCsICAgICBkZKwgICAgICCsrCAgICAgZCAgZCAgICAgZCBkICAgIKxkIGQgICAgZKwgZCAgICAgIGRkICAgIKwgZGQgICAgZGRkZCAgICAgrGRkICAgIGQgrGQgICAgIGSsZCAgICAgICCsICAgIKwgIKwgICAgZGQgrCAgICAgrCCsICAgIGQgZKwgICAgIGRkrCAgICBkrGSsICAgICAgrKwgICAgZCAgIGQgICAgZCAgZCAgIKxkICBkICAgZKwgIGQgICAgIGQgZCAgIKwgZCBkICAgZGRkIGQgICAgrGQgZCAgIKysZCBkICAgZCCsIGQgICAgZKwgZCAgICAgIGRkICAgrCAgZGQgICBkZCBkZCAgICCsIGRkICAgZCBkZGQgICAgZGRkZCAgICAgrGRkICAgIKysZGQgICBkICCsZCAgICBkIKxkICAgICBkrGQgICAgICAgrCAgIKwgICCsICAgZGQgIKwgICAgrCAgrCAgIGQgZCCsICAgIGRkIKwgICAgIKwgrCAgIGQgIGSsICAgIGQgZKwgICAgIGRkrCAgIGRkZGSsICAgICAgrKwgICCsrCCsrCAgIGQgICAgZCAgIGQgICBkICCsZCAgIGQgIGSsICAgZCAgICBkICBkICCsIGQgIGQgIGRkZCAgZCAgIKxkICBkICBkIKwgIGQgICBkrCAgZCAgICAgZCBkICCsICBkIGQgIGRkIGQgZCAgIKwgZCBkICBkIGRkIGQgICBkZGQgZCAgrGRkZCBkICAgIKxkIGQgIGQgIKwgZCAgIGQgrCBkICAgIGSsIGQgICAgICBkZCAgrCAgIGRkICBkZCAgZGQgICCsICBkZCAgZCBkIGRkICAgZGQgZGQgICAgrCBkZCAgZCAgZGRkICAgZCBkZGQgICAgZGRkZCAgZCCsZGRkICAgICCsZGQgIGQgICCsZCAgIGQgIKxkICAgIGQgrGQgIKxkrCCsZCAgICAgZKxkICCsICBkrGQgICBkIKysZCAgICAgICCsICCsICAgIKwgIGRkICAgrCAgIKwgICCsICCsrCAgIKwgIGQgZCAgrCAgIGRkICCsICAgIKwgIKwgIGRkrCAgrCAgZCAgZCCsICAgZCBkIKwgICAgZGQgrCAgIKxkZCCsICAgICCsIKwgICAgrKwgrCAgrKysrCCsICBkICAgZKwgICBkICBkrCAgICBkIGSsICAgICBkZKwgIGQgIKxkrCAgZKwgrGSsICAgICAgrKwgICAgrCCsrCAgIKysIKysICCsZGSsrKwgICAgrKysrCAgZCAgICAgZCAgZCAgICBkIKxkICAgIGQgZKwgICAgZCAgIGQgICBkIKwgZCAgIGQgZGRkICAgZCAgrGQgICBkIGQgrCAgIGQgIGSsICAgZCAgICBkICBkIKwgIGQgIGQgZGQgZCAgZCAgrCBkICBkIGQgZGQgIGQgIGRkZCAgZCAgIKxkICBkIKysrGQgIGQgZCAgrCAgZCAgZCCsICBkICAgZKwgIGQgICAgIGQgZCCsICAgZCBkIGRkICBkIGQgIKwgIGQgZCBkIGQgZCBkICBkZCBkIGQgICCsIGQgZCBkICBkZCBkICBkIGRkIGQgICBkZGQgZCAgICCsZCBkICBkZKxkIGQgrGRkrGQgZCBkICAgrCBkICBkICCsIGQgrGQgIKwgZCAgIGQgrCBkICAgIGSsIGQgICCsZKwgZCAgICAgIGRkIKwgICAgZGQgZGQgICBkZCAgrCAgIGRkIGQgZCAgZGQgIGRkICBkZCAgIKwgIGRkIGQgIGQgZGQgIGQgZCBkZCBkrCBkIGRkICAgZGQgZGQgIGSsZCBkZCAgICCsIGRkIGQgICBkZGQgIGQgIGRkZCAgIGQgZGRkICAgIGRkZGQgICAgIKxkZCAgZGQgrGRkIGSsIGSsZGQgZCAgICCsZCAgZCAgIKxkICAgZCAgrGQgrCBkICCsZCAgICBkIKxkICBkZGQgrGQgrGQgrCCsZCAgICAgZKxkIGRkICBkrGQgrGSsZGSsZCBkIGRkrKxkIGSsrKysrGQgICAgICAgrCCsICAgICCsIGRkICAgIKwgIKwgICAgrCCsrCAgICCsIGQgZCAgIKwgIGRkICAgrCAgIKwgICCsIGQgIGQgIKwgIGQgZCAgrCAgIGRkICCsICAgIKwgIKwgICCsrCAgrCBkICAgZCCsICBkICBkIKwgICBkIGQgrCAgICBkZCCsICCsIGRkIKwgZGSsZGQgrCAgICAgrCCsIKwgrCCsIKwgICAgrKwgrCAgrKysrCCsIGQgICAgZKwgIGQgICBkrCAgIGQgIGSsIGSsrCAgZKwgICAgZCBkrCAgICAgZGSsIGQgIGRkZKwgrCBkZGRkrCBkrGSsZGSsIGQgICCsZKwgrKxkIKxkrCCsZKysrGSsICAgICAgrKwgIKwgICCsrCCsrCAgIKysICAgrCAgrKwgZGRkZCCsrCAgrCCsIKysIKwgrKwgrKwgIKysZGSsrCAgIGSsZKysICCsICCsrKwgICCsIKysrCCsICCsrKysICCsIKysrKwgrKwgrKysrCBkICAgICAgZCBkICAgICBkrGQgICAgIGRkrCAgICAgZCAgZCAgICBkrCBkICAgIGRkZGQgICAgZCCsZCAgICBkZCCsICAgIGQgZKwgICAgZCAgIGQgICBkrCAgZCAgIGRkZCBkICAgZCCsIGQgICBkrKwgZCAgIGRkIGRkICAgZCBkZGQgICBkICCsZCAgIGRkZKxkICAgZGQgIKwgICBkIGQgrCAgIGQgIGSsICAgZCAgICBkICBkrCAgIGQgIGRkZCAgZCAgZCCsICBkICBkZCBkIGQgIGQgZGQgZCAgZCAgrCBkICBkZCAgZGQgIGQgZCBkZCAgZCAgZGRkICBkICAgrGQgIGRkZCCsZCAgZKwgrKxkICBkZCAgIKwgIGQgZCAgrCAgZCAgZCCsICBkrCBkIKwgIGRkrKwgrCAgZCAgIGSsICBkICAgICBkIGSsICAgIGQgZGRkICAgZCBkIKwgICBkIGRkIGQgIGQgZCBkZCAgZCBkZKxkICBkIGQgIKwgIGQgZGQgIGQgZCBkIGQgZCBkIGQgIGRkIGQgZCAgIKwgZCBkIGRkrCBkIGRkICAgZGQgZCBkICBkZCBkICBkIGRkIGQgZKwgZGQgZCAgIGRkZCBkrKxkrGRkIGQgICAgrGQgZKysICCsZCBkIGQgZKxkIGQgIGRkrGQgZGQgICAgrCBkIGQgICCsIGQgIGQgIKwgZCAgIGQgrCBkZGQgZCCsIGQgZGRkIKwgZKwgrGQgrCBkICAgIGSsIGRkIGQgZKwgZCBkIGRkrCBkICBkZGSsIGRkrKxkZKwgZCBkICCsrCBkICAgICAgZGSsICAgICBkZGRkICAgIGRkIKwgICAgZGRkIGQgICBkZCBkZCAgIGRkICCsICAgZGQgrKwgICBkZGQgIGQgIGRkIGQgZCAgZGQgIGRkICBkZCAgIKwgIGRkZCAgIGQgZGQgZCAgZCBkZCAgZCBkIGRkZGRkIGQgZGQgICBkZCBkZKwgIGRkIGRkICAgIKwgZGQgZCBkrCBkZKysrKysIGRkZCAgICBkZGQgZCAgIGRkZCAgZCAgZGRkZCCsICBkZGQgICBkIGRkZCAgrGQgZGRkZCAgrCBkZGRkIKysIGRkZCAgICBkZGRkIKwgIGRkZGQgICCsZGRkZCCsIKxkZGRkZCCsIKxkZGQgrKxkrGRkZGQgrKysZGRkICAgICCsZGQgZGQgIKxkZGQgIGQgrGRkICBkZCCsZGRkrGSsIKxkZKysZCBkrGRkICAgZGSsZGSsICBkZKxkZGRkIKysrGRkZCAgICAgrGQgZCAgICCsZCAgZCAgIKxkICAgZCAgrGQgZGRkICCsZKwgrGQgIKxkrGQgrCAgrGRkrKysICCsZCAgICBkIKxkIGSsIKwgrGSsrCBkrCCsZKwgZKysIKxkICAgICBkrGSsZGQgIGSsZCAgZCBkZKxkICAgZGRkrGRkZCBkZGSsZCBkrKxkZKxkZCAgICCsrGSsrKxkIKysZGRkrCBkrKxkrGQgIKysrGQgZGRkrKysZKwgrGSsrKxkICAgICAgIKysICAgICAgrGRkICAgICCsIKwgICAgIKxkIGQgICAgrCBkZCAgICCsICCsICAgIKysrKwgICAgrGQgIGQgICCsIGQgZCAgIKwgIGRkICAgrCAgIKwgICCsrCAgrCAgIKwgrKysICAgrKysrKwgICCsZCAgIGQgIKwgZCAgZCAgrKxkICBkICCsICBkIGQgIKwgICBkZCAgrGQgZGRkICCsZKxkZGQgIKwgICAgrCAgrCAgrCCsICCsICAgrKwgIKysICCsrCAgrCAgrKysICCsIKysrKwgIKxkICAgIGQgrCBkICAgZCCsICBkICBkIKysIGQgIGQgrGRkZCAgZCCsICAgZCBkIKwgIKxkIGQgrGSsIKwgZCCsICAgIGRkIKwgZCBkZGQgrGRkrKxkZCCsIKxkIKxkIKysrKxkrGQgrCAgICAgrCCsIKwgICCsIKxkZKwgIKwgrKysZGQgrCCsICAgrCCsIKysICCsIKwgrCCsrKwgrCCsrGQgIGSsIKysIKwgrKwgrCAgIKysrCCsIKwgrKysIKysZGSsrKwgrCCsrKysrCCsZCAgICAgZKwgZCAgICBkrCAgZCAgIGSsICAgZCAgZKysZGRkICBkrCBkIKwgIGSsICAgIGQgZKysIKwgZCBkrCBkrGRkIGSsrGRkZKwgZKxkrCCsrCBkrCAgICAgZGSsZGQgICBkZKwgZCBkIGRkrCAgZGQgZGSsIKxkZCBkZKxkrKwgZGRkrCAgZKxkZGSsrCBkrGRkZKxkICBkrGRkrGQgZGQgrGSsrGSsrCCsZKxkrCBkZKxkrGRkZCCsrGSsICCsZKysZKwgICAgICCsrKwgICAgIKysIKwgICAgrKysrCAgICCsrCAgrCAgIKysrKysICAgrKwgIKysICCsrGQgZGRkIKysZKxkZGQgrKysZKysZCCsrCAgICCsIKysrCAgIKwgrKwgrCAgrCCsrKysrCCsIKysICAgrKwgrKwgIKysrCCsrCAgIGQgZKysZGRkrCBkrKxkZKxkrGSsrCCsZKysZKysrKwgICCsrKwgIKwgIKysrKwgrCAgrKysIKysICCsrKwgIKysIKysrCCsrKwgrKysIGQgIGSsrKwgZCCsZKysrKxkIKxkrKysIKysIKysrKysrKwgrKysrGQgZKysrKysrKysrKysrKw==");
8
+ export const iq2s_grid = /* 8192B */ D("CAgICAgICAgrCAgICAgICBkZCAgICAgICCsICAgICAgrKwgICAgICBkIGQgICAgICBkZCAgICAgrGRkICAgICBkrGQgICAgICAgrCAgICAgrCCsICAgICBkZKwgICAgICCsrCAgICAgZCAgZCAgICAgZCBkICAgIKxkIGQgICAgZKwgZCAgICAgIGRkICAgIKwgZGQgICAgZGRkZCAgICAgrGRkICAgIGQgrGQgICAgIGSsZCAgICCsZKxkICAgIGSsrGQgICAgICAgrCAgICCsICCsICAgIGRkIKwgICAgIKwgrCAgICBkIGSsICAgICBkZKwgICAgICCsrCAgICBkZKysICAgIKysrKwgICAgZCAgIGQgICAgZCAgZCAgIKxkICBkICAgZKwgIGQgICAgIGQgZCAgIKwgZCBkICAgZGRkIGQgICAgrGQgZCAgIGQgrCBkICAgIGSsIGQgICAgICBkZCAgIKwgIGRkICAgZGQgZGQgICAgrCBkZCAgIGQgZGRkICAgIGRkZGQgICCsZGRkZCAgIGSsZGRkICAgICCsZGQgICBkZKxkZCAgICCsrGRkICAgZCAgrGQgICAgZCCsZCAgICAgZKxkICAgrCBkrGQgICBkZGSsZCAgIGQgrKxkICAgIGSsrGQgICAgICAgrCAgIKwgICCsICAgZGQgIKwgICAgrCAgrCAgIGQgZCCsICAgIGRkIKwgICAgIKwgrCAgIKysrCCsICAgZCAgZKwgICAgZCBkrCAgIKxkIGSsICAgZKwgZKwgICAgIGRkrCAgIGRkZGSsICAgICAgrKwgICBkZCCsrCAgIKysIKysICAgIGRkrKwgICCsIKysrCAgIGQgICAgZCAgIGQgICBkICCsZCAgIGQgIGSsICAgZCAgICBkICBkICCsIGQgIGQgIGRkZCAgZCAgIKxkICBkICBkIKwgIGQgICBkrCAgZCAgrGSsICBkICBkrKwgIGQgICAgIGQgZCAgrCAgZCBkICBkZCBkIGQgICCsIGQgZCAgrKwgZCBkICBkIGRkIGQgICBkZGQgZCAgrGRkZCBkICBkrGRkIGQgICAgrGQgZCAgrCCsZCBkICBkZKxkIGQgIGQgIKwgZCAgIGQgrCBkICCsZCCsIGQgIGSsIKwgZCAgICBkrCBkICBkZGSsIGQgICCsZKwgZCAgZCCsrCBkICAgZKysIGQgICAgICBkZCAgrCAgIGRkICBkZCAgZGQgICCsICBkZCAgrKwgIGRkICBkIGQgZGQgICBkZCBkZCAgrGRkIGRkICBkrGQgZGQgICAgrCBkZCAgZGSsIGRkICAgrKwgZGQgIGQgIGRkZCAgIGQgZGRkICCsZCBkZGQgIGSsIGRkZCAgICBkZGRkICCsIGRkZGQgIGRkZGRkZCAgIKxkZGRkICBkIKxkZGQgICBkrGRkZCAgICAgrGRkICCsICCsZGQgIGRkIKxkZCAgIKwgrGRkICBkIGSsZGQgICBkZKxkZCAgICCsrGRkICBkICAgrGQgICBkICCsZCAgrGQgIKxkICBkrCAgrGQgICAgZCCsZCAgZGRkIKxkICAgICBkrGQgIGRkIGSsZCAgIKwgZKxkICBkIGRkrGQgICBkZGSsZCAgICCsZKxkICBkICCsrGQgICBkIKysZCAgICBkrKxkICAgICAgIKwgIKwgICAgrCAgZGQgICCsICAgrCAgIKwgIGQgZCAgrCAgIGRkICCsICCsZGQgIKwgIGSsZCAgrCAgICCsICCsICBkZKwgIKwgIKysrCAgrCAgZCAgZCCsICAgZCBkIKwgIKxkIGQgrCAgZKwgZCCsICAgIGRkIKwgIKwgZGQgrCAgZGRkZCCsICAgrGRkIKwgIGQgrGQgrCAgIGSsZCCsICAgICCsIKwgIGRkIKwgrCAgIGRkrCCsICCsrKysIKwgIGQgICBkrCAgIGQgIGSsICAgIGQgZKwgIKwgZCBkrCAgZGRkIGSsICAgrGQgZKwgIGQgrCBkrCAgICAgZGSsICBkZCBkZKwgICCsIGRkrCAgZCBkZGSsICAgZGRkZKwgICAgrGRkrCAgZCAgrGSsICAgIGSsZKwgICAgICCsrCAgZCBkIKysICAgZGQgrKwgIKwgrCCsrCAgIKysIKysICCsrKwgrKwgICAgZGSsrCAgZKxkrKysICBkICAgICBkICBkICAgIGQgrGQgICAgZCBkrCAgICBkICAgZCAgIGQgrCBkICAgZCBkZGQgICBkICCsZCAgIGQgZCCsICAgZCAgZKwgICBkIKxkrCAgIGQgICAgZCAgZCCsICBkICBkIGRkIGQgIGQgIKwgZCAgZCBkIGRkICBkICBkZGQgIGQgrGRkZCAgZCBkrGRkICBkICAgrGQgIGQgrCCsZCAgZCBkZKxkICBkICCsrGQgIGQgZCAgrCAgZCAgZCCsICBkIKxkIKwgIGQgICBkrCAgZCBkZGSsICBkICCsZKwgIGQgZCCsrCAgZCAgZKysICBkICAgICBkIGQgrCAgIGQgZCBkZCAgZCBkICCsICBkIGQgrKwgIGQgZCBkIGQgZCBkICBkZCBkIGQgrGRkIGQgZCBkrGQgZCBkICAgrCBkIGQgrCCsIGQgZCBkZKwgZCBkICCsrCBkIGQgZCAgZGQgZCAgZCBkZCBkIKxkIGRkIGQgZKwgZGQgZCAgIGRkZCBkIKwgZGRkIGQgZGRkZGQgZCAgrGRkZCBkIGQgrGRkIGQgIGSsZGQgZCAgICCsZCBkIKwgIKxkIGQgZGQgrGQgZCAgrCCsZCBkIGQgZKxkIGQgIGRkrGQgZCBkICAgrCBkICBkICCsIGQgZKwgIKwgZCAgIGQgrCBkIGRkZCCsIGQgZCCsIKwgZCAgZKwgrCBkICAgIGSsIGQgZGQgZKwgZCBkIGRkrCBkICBkZGSsIGQgZCAgrKwgZCAgZCCsrCBkICAgZKysIGQgICAgICBkZCCsICAgIGRkIGRkICAgZGQgIKwgICBkZCBkIGQgIGRkICBkZCAgZGQgrGRkICBkZCBkrGQgIGRkICAgrCAgZGQgZGSsICBkZCAgrKwgIGRkIGQgIGQgZGQgIGQgZCBkZCCsZCBkIGRkIGSsIGQgZGQgICBkZCBkZCCsIGRkIGRkIGRkZGQgZGQgIKxkZCBkZCBkIKxkIGRkICBkrGQgZGQgICAgrCBkZCCsICCsIGRkIGRkIKwgZGQgIKwgrCBkZCBkIGSsIGRkICBkZKwgZGQgICCsrCBkZCBkICAgZGRkICBkICBkZGQgrGQgIGRkZCBkrCAgZGRkICAgZCBkZGQgrCBkIGRkZCBkZGQgZGRkICCsZCBkZGQgZCCsIGRkZCAgZKwgZGRkICAgIGRkZGQgrCAgZGRkZCBkZCBkZGRkICCsIGRkZGQgZCBkZGRkZCAgZGRkZGRkICAgrGRkZGQgZCAgrGRkZCAgZCCsZGRkICAgZKxkZGQgICAgIKxkZCBkZCAgrGRkICCsICCsZGQgZCBkIKxkZCAgZGQgrGRkICAgrCCsZGQgZCAgZKxkZCAgZCBkrGRkICAgZGSsZGQgICAgrKxkZCCsrKysrGRkIGQgICAgrGQgIGQgICCsZCCsZCAgIKxkIGSsICAgrGQgICBkICCsZCBkZGQgIKxkICCsZCAgrGQgZCCsICCsZCAgICBkIKxkIKwgIGQgrGQgZGQgZCCsZCAgrCBkIKxkIGQgZGQgrGQgIGRkZCCsZCAgIKxkIKxkIGQgIKwgrGQgIGQgrCCsZCAgICAgZKxkIKwgICBkrGQgZGQgIGSsZCAgrCAgZKxkIGQgZCBkrGQgIGRkIGSsZCAgIKwgZKxkIGQgIGRkrGQgIGQgZGSsZCAgIGRkZKxkIGSsrGRkrGQgrCCsrGSsZCAgZCAgrKxkICAgZCCsrGQgICAgZKysZCCsZGRkrKxkICAgICAgIKwgrCAgICAgrCBkZCAgICCsICCsICAgIKwgZCBkICAgrCAgZGQgICCsIKxkZCAgIKwgZKxkICAgrCAgIKwgICCsIGRkrCAgIKwgrKysICAgrCBkICBkICCsICBkIGQgIKwgICBkZCAgrCCsIGRkICCsIGRkZGQgIKwgIGSsZCAgrCAgICCsICCsIKysIKwgIKwgIGRkrCAgrCCsrKysICCsIGQgICBkIKwgIGQgIGQgrCAgIGQgZCCsIKwgZCBkIKwgZGRkIGQgrCBkIKwgZCCsICAgIGRkIKwgrCAgZGQgrCBkZCBkZCCsIGQgZGRkIKwgIGRkZGQgrCAgIKxkZCCsIGQgIKxkIKwgIGQgrGQgrCAgIGSsZCCsICAgICCsIKwgrKwgIKwgrCCsIKwgrCCsICCsrCCsIKwgrKysIKwgrCAgZCBkrCCsICAgZGSsIKwgIKwgrKwgrCCsrCCsrCCsICCsrKysIKwgZCAgICBkrCAgZCAgIGSsIKxkICAgZKwgZKwgICBkrCAgIGQgIGSsIGRkZCAgZKwgIKxkICBkrCBkIKwgIGSsICBkrCAgZKwgICAgZCBkrCCsICBkIGSsIGRkIGQgZKwgIKwgZCBkrCBkIGRkIGSsICBkZGQgZKwgICCsZCBkrCBkICCsIGSsICBkIKwgZKwgICBkrCBkrCAgICAgZGSsIGRkICBkZKwgIKwgIGRkrCBkIGQgZGSsICBkZCBkZKwgICCsIGRkrCBkICBkZGSsICBkIGRkZKwgICBkZGRkrCCsZKxkZGSsICAgIKxkZKwgZCAgIKxkrCAgZCAgrGSsICAgZCCsZKwgICAgZKxkrCBkrGRkrGSsICAgICAgrKwgZGQgICCsrCBkIGQgIKysICBkZCAgrKwgZCAgZCCsrCAgZCBkIKysICAgZGQgrKwgrKwgrCCsrCCsrKysIKysIGQgICBkrKwgIGQgIGSsrCAgIGQgZKysIGRkZKxkrKwgrKwgIKysrCCsIKwgrKysICBkrGSsrKwgIKwgrKysrCCsrCCsrKysIGQgICAgICBkIGQgICAgIGSsZCAgICAgZGSsICAgICBkICBkICAgIGSsIGQgICAgZGRkZCAgICBkIKxkICAgIGSsrGQgICAgZGQgrCAgICBkIGSsICAgIGSsZKwgICAgZCAgIGQgICBkrCAgZCAgIGRkZCBkICAgZCCsIGQgICBkrKwgZCAgIGRkIGRkICAgZCBkZGQgICBkrGRkZCAgIGRkrGRkICAgZCAgrGQgICBkrCCsZCAgIGRkZKxkICAgZGQgIKwgICBkIGQgrCAgIGQgIGSsICAgZGRkZKwgICBkIKxkrCAgIGRkIKysICAgZCBkrKwgICBkICAgIGQgIGSsICAgZCAgZGRkICBkICBkIKwgIGQgIGRkIGQgZCAgZCBkZCBkICBkrGRkIGQgIGRkrGQgZCAgZCAgrCBkICBkrCCsIGQgIGRkZKwgZCAgZGQgIGRkICBkIGQgZGQgIGSsZCBkZCAgZGSsIGRkICBkICBkZGQgIGSsIGRkZCAgZGRkZGRkICBkIKxkZGQgIGRkIKxkZCAgZCBkrGRkICBkICAgrGQgIGSsICCsZCAgZGRkIKxkICBkIKwgrGQgIGRkIGSsZCAgZCBkZKxkICBkICCsrGQgIGRkICAgrCAgZCBkICCsICBkICBkIKwgIGSsIGQgrCAgZGRkZCCsICBkIKxkIKwgIGQgZKwgrCAgZCAgIGSsICBkZGQgZKwgIGQgrCBkrCAgZGQgZGSsICBkIGRkZKwgIGQgIKxkrCAgZGQgIKysICBkIGQgrKwgIGQgICAgIGQgZKwgICAgZCBkZGQgICBkIGQgrCAgIGQgZKysICAgZCBkZCBkICBkIGQgZGQgIGQgZKxkZCAgZCBkZKxkICBkIGQgIKwgIGQgZKwgrCAgZCBkZGSsICBkIGQgrKwgIGQgZGQgIGQgZCBkIGQgZCBkIGSsZCBkIGQgZGSsIGQgZCBkICBkZCBkIGSsIGRkIGQgZGRkZGQgZCBkIKxkZCBkIGRkIKxkIGQgZCBkrGQgZCBkICAgrCBkIGSsICCsIGQgZGRkIKwgZCBkIKwgrCBkIGRkIGSsIGQgZCBkZKwgZCBkICCsrCBkIGRkICAgZGQgZCBkICBkZCBkrGQgIGRkIGRkrCAgZGQgZCAgZCBkZCBkrCBkIGRkIGRkZGQgZGQgZCCsZCBkZCBkZCCsIGRkIGQgZKwgZGQgZCAgIGRkZCBkrCAgZGRkIGRkZCBkZGQgZCCsIGRkZCBkZCBkZGRkIGQgZGRkZGQgZCAgrGRkZCBkrKysZGRkIGRkICCsZGQgZCBkIKxkZCBkICBkrGRkIGQgICAgrGQgZKwgICCsZCBkZGQgIKxkIGQgrCAgrGQgZGQgZCCsZCBkIGRkIKxkIGQgIKwgrGQgZGQgIGSsZCBkIGQgZKxkIGQgIGRkrGQgZCAgIKysZCBkZGSsrKxkIGRkICAgIKwgZCBkICAgrCBkZKwgICCsIGQgIGQgIKwgZKwgZCAgrCBkZGRkICCsIGQgrGQgIKwgZGQgrCAgrCBkIGSsICCsIGQgICBkIKwgZKwgIGQgrCBkZGQgZCCsIGQgrCBkIKwgZGQgZGQgrCBkIGRkZCCsIGQgIKxkIKwgZCBkIKwgrCBkICBkrCCsIGQgICAgZKwgZKwgICBkrCBkZGQgIGSsIGQgrCAgZKwgZGQgZCBkrCBkIGRkIGSsIGQgIKwgZKwgZGQgIGRkrCBkIGQgZGSsIGQgIGRkZKwgZCAgIKxkrCBkrGRkrGSsIGRkICAgrKwgZCBkICCsrCBkICBkIKysIGQgICBkrKwgZCAgICAgIGRkrCAgICAgZGRkZCAgICBkZCCsICAgIGRkZCBkICAgZGQgZGQgICBkZKxkZCAgIGRkZKxkICAgZGQgIKwgICBkZKwgrCAgIGRkZGSsICAgZGQgrKwgICBkZGQgIGQgIGRkIGQgZCAgZGSsZCBkICBkZGSsIGQgIGRkICBkZCAgZGSsIGRkICBkZGRkZGQgIGRkIKxkZCAgZGRkIKxkICBkZCBkrGQgIGRkICAgrCAgZGSsICCsICBkZGRkIKwgIGRkIKwgrCAgZGRkIGSsICBkZCBkZKwgIGRkZCAgIGQgZGQgZCAgZCBkZKxkICBkIGRkZKwgIGQgZGQgIGQgZCBkZKwgZCBkIGRkZGRkIGQgZGQgrGQgZCBkZGQgrCBkIGRkIGSsIGQgZGQgICBkZCBkZKwgIGRkIGRkZGQgZGQgZGQgrCBkZCBkZGQgZGRkIGRkIGRkZGQgZGQgIKxkZCBkZGQgIKxkIGRkIGQgrGQgZGQgIGSsZCBkZCAgICCsIGRkZGQgIKwgZGQgrCAgrCBkZGQgZCCsIGRkIGRkIKwgZGQgIKwgrCBkZGQgIGSsIGRkIGQgZKwgZGQgIGRkrCBkZGSsrGSsIGRkICAgrKwgZGRkICAgIGRkZCBkICAgZGRkrGQgICBkZGRkrCAgIGRkZCAgZCAgZGRkrCBkICBkZGRkZGQgIGRkZCCsZCAgZGRkZCCsICBkZGQgZKwgIGRkZCAgIGQgZGRkrCAgZCBkZGRkZCBkIGRkZCCsIGQgZGRkZCBkZCBkZGQgZGRkIGRkZCAgrGQgZGRkZCAgrCBkZGQgZCCsIGRkZCAgZKwgZGRkICAgIGRkZGSsICAgZGRkZGRkICBkZGRkIKwgIGRkZGRkIGQgZGRkZCBkZCBkZGRkICCsIGRkZGRkICBkZGRkZCBkIGRkZGRkICBkZGRkZGQgICCsZGRkZGQgICCsZGRkIGQgIKxkZGQgIGQgrGRkZKxkrCCsZGRkICAgZKxkZGQgICAgIKxkZKwgICAgrGRkZGQgICCsZGQgrCAgIKxkZGQgZCAgrGRkIGRkICCsZGQgIKwgIKxkZGQgIGQgrGRkIGQgZCCsZGQgIGRkIKxkZKysZGQgrGRkICAgrCCsZGRkICAgZKxkZCBkICBkrGRkICBkIGSsZGQgICBkZKxkZCAgICCsrGRkZKxkIKysZGRkZCCsrKxkZCCsrKysrGRkZCAgICAgrGQgZCAgICCsZKxkICAgIKxkICBkICAgrGSsIGQgICCsZGRkZCAgIKxkIKxkICAgrGRkIKwgICCsZCBkrCAgIKxkICAgZCAgrGRkZCBkICCsZCCsIGQgIKxkZCBkZCAgrGQgZGRkICCsZCAgrGQgIKxkIGQgrCAgrGQgIGSsICCsZCAgICBkIKxkrCAgIGQgrGRkZCAgZCCsZCCsICBkIKxkZCBkIGQgrGQgZGQgZCCsZCAgrCBkIKxkZCAgZGQgrGQgZCBkZCCsZCAgZGRkIKxkICAgrGQgrGRkrGSsZCCsZCBkICCsIKxkICBkIKwgrGQgICBkrCCsZKxkZGSsIKxkZCCsrKwgrGQgICAgIGSsZGRkICAgZKxkIKwgICBkrGRkIGQgIGSsZCBkZCAgZKxkICCsICBkrGRkICBkIGSsZCBkIGQgZKxkICBkZCBkrGQgICCsIGSsZGQgICBkZKxkIGQgIGRkrGQgIGQgZGSsZCAgIGRkZKxkrKwgZGRkrGQgrKxkZGSsZKwgZKxkZKxkICAgIKxkrGQgZGSsrGSsZGQgICAgrKxkIGQgICCsrGQgIGQgIKysZGRkrGQgrKxkIKxkrCCsrGQgICAgZKysZKysrCBkrKxkrCAgZKysrGRkIKysrKysZCAgICAgICCsrCAgICAgIKxkZCAgICAgrCCsICAgICCsZCBkICAgIKwgZGQgICAgrGSsZCAgICCsICCsICAgIKxkZKwgICAgrGQgIGQgICCsIGQgZCAgIKwgIGRkICAgrKwgZGQgICCsZGRkZCAgIKwgrGRkICAgrGQgrGQgICCsICAgrCAgIKxkZCCsICAgrGQgZKwgICCsIGRkrCAgIKxkICAgZCAgrCBkICBkICCsZKwgIGQgIKwgIGQgZCAgrKwgZCBkICCsZGRkIGQgIKwgrGQgZCAgrGQgrCBkICCsIGSsIGQgIKwgICBkZCAgrKwgIGRkICCsZGQgZGQgIKwgrCBkZCAgrGQgZGRkICCsIGRkZGQgIKxkICCsZCAgrCBkIKxkICCsICBkrGQgIKxkrKysZCAgrCAgICCsICCsZGQgIKwgIKysrCAgrCAgrGQgZCCsICCsIGRkIKwgIKxkICBkrCAgrCBkIGSsICCsICBkZKwgIKxkICAgIGQgrCBkICAgZCCsrGQgICBkIKxkrCAgIGQgrCAgZCAgZCCsrCBkICBkIKxkZGQgIGQgrCCsZCAgZCCsZCCsICBkIKwgICBkIGQgrKwgIGQgZCCsZGQgZCBkIKwgrCBkIGQgrGQgZGQgZCCsIGRkZCBkIKwgIKxkIGQgrGQgIKwgZCCsIGQgrCBkIKwgIGSsIGQgrCAgICBkZCCsrCAgIGRkIKxkZCAgZGQgrCCsICBkZCCsZCBkIGRkIKwgZGQgZGQgrCAgrCBkZCCsZCAgZGRkIKwgZCBkZGQgrCAgZGRkZCCsICAgrGRkIKysrCCsZGQgrGQgICCsZCCsIGQgIKxkIKwgIGQgrGQgrGSsrCCsZCCsICAgZKxkIKwgICAgIKwgrGRkICAgrCCsZCBkICCsIKwgZGQgIKwgrGQgIGQgrCCsIGQgZCCsIKwgIGRkIKwgrKwgrKwgrCCsZCAgIGSsIKwgZCAgZKwgrCAgIGRkrCCsZGSsZGSsIKysIKwgrKwgrCCsZGSsrCCsrKxkZKysIKysICCsrKwgrKwgrKysrCCsZCAgICAgZKwgZCAgICBkrGSsICAgIGSsICBkICAgZKysIGQgICBkrGRkZCAgIGSsIKxkICAgZKwgZKwgICBkrCAgIGQgIGSsrCAgZCAgZKxkZCBkICBkrCCsIGQgIGSsZCBkZCAgZKwgZGRkICBkrCAgrGQgIGSsZCAgrCAgZKwgZCCsICBkrCAgZKwgIGSsICAgIGQgZKxkZCAgZCBkrGQgZCBkIGSsIGRkIGQgZKxkICBkZCBkrCBkIGRkIGSsICBkZGQgZKysrGRkZCBkrGQgICCsIGSsIGQgIKwgZKwgIGQgrCBkrCAgIGSsIGSsrGSsrKwgZKwgICAgIGRkrKwgICAgZGSsZGQgICBkZKwgrCAgIGRkrGQgZCAgZGSsIGRkICBkZKwgIKwgIGRkrGQgIGQgZGSsIGQgZCBkZKwgIGRkIGRkrCAgIKwgZGSsrGRkrCBkZKxkICAgZGRkrCBkICBkZGSsICBkIGRkZKwgICBkZGRkrCCsZKxkZGSsZCCsrGRkZKwgICAgrGRkrKxkIGSsZGSsIGSsZKxkZKxkICAgIKxkrCBkICAgrGSsICBkICCsZKysZKwgIKxkrCAgIGQgrGSsZKysrCCsZKwgICAgZKxkrGSsIGRkrGSsrCBkZGSsZKwgIGSsrKxkrCAgICAgIKysZGQgICAgrKysrCAgICCsrCBkZCAgIKysrCCsICAgrKysrKwgICCsrGQgIGQgIKysIGQgZCAgrKwgIGRkICCsrKwgrKwgIKysrKysrCAgrKwgICBkZCCsrGRkrGRkIKysrCAgIKwgrKysrCAgrCCsrKwgrCCsIKysIKysIKwgrKysrKwgrCCsrKwgIKysIKysIKwgrKwgrKysrCCsrCCsrCCsrKysIKysZCAgICBkrKwgZCAgIGSsrCAgZCAgZKysICAgZCBkrKxkrCCsIGSsrCBkrKwgZKysICAgIGRkrKxkrGQgZGSsrGQgZGSsZKysrKwgICCsrKwgrKwgIKysrKwgrKwgrKysIGRkZGSsrKysZCCsZKysrCCsICCsrKysrKwgIKysrKwgIKwgrKysrKwgrCCsrKysIKysIKysrKwgrCCsrKysrKysrKysrKys=");
9
+ export const iq3xxs_grid = /* 1024B */ D("BAQEBBQEBAQkBAQEDAwEBBwMBAQ+DAQEBBQEBBQUBAQMHAQEFCQEBBw+BAQsPgQEDAQMBBwEDAQEDAwEFAwMBAwUDAQsFAwEBBwMBBQcDAQMJAwEJCwMBAQ+DAQEBBQEFAQUBCQEFAQMDBQEBBQUBBQUFAQMHBQEHBwUBD4cFAQMLBQEPiwUBCw+FAQMBBwEPgQcBAQMHAQUDBwELBQcBAQ+HAQcDCQEPhwkBCQkJAQ+LCQEHD4kBCw+JAQMBCwEPgQsBBQcLAQULCwELBw0BCQ0NAQEDD4EJAw+BDQMPgQcJD4EDDQ+BAwEBAwcBAQMBAwEDBQMBAwMFAQMHBQEDAQcBAwUHAQMJBwEDD4kBAwELAQMBAQMDBQEDAwMDAwMBBQMDBQUDAwMBBQMHAQUDAQMFAwUDBQMDBQUDAQcFAwUPhQMBAQcDBQEHAwEFBwMDBwcDDQkHAw0NBwMDAQkDCwEJAwELCQMBBQsDCQULAw0JCwMDD4sDCwENAwUFD4MBCQ+DAQEBBQUBAQUDAwEFBwMBBQEFAQUFBQEFDQUBBQMHAQUFCQEFAwEDBQcBAwULAQMFAQMDBQUDAwUDBQMFAQcDBQcNAwUPjQMFAQ+DBQEBBQUFAQUFAwMFBQ+DBQUBBQUFBQUFBQ+HBQUBCQUFCwsFBQMBBwUBAwcFCQMHBQEPhwUJD4cFCwcJBQcLCQUHAQsFD4ULBQMJCwUJD4sFAwEPhQcBD4UNAw+FCwkPhQMBAQcBAwEHBQMBBwMFAQcHBQEHAQsBBwsNAQcFD4EHAQEDBwUBAwcBBQMHAwcDBwkJAwcNCQMHAwEFBwcBBQcBAwUHCwUFBwULBQcFD4UHAwMHBwcHBwcBBwkHD4kJBwUPiQcBAQsHDQELBwUFCwcLCwsHCQMNBw0HDQcHDQ0HBwcPhwEND4cJAQEJD4MBCQsHAQkPhwEJBwsBCQ+LAQkJD4MJAQUFCQ+HBQkBCQUJAQ0FCQ0NBQkPgQcJCwkHCQkBCQkDCwkJCQ0JCQsFCwkHCQsJAQ+LCQsBD4kBAw+JBQMPiQEHD4kFAwELAwkBCwEPgQsBAQMLDQEDCw0FAwsLCwMLCQMFCwUHBQsFD4ULBQEHCwcLBwsBAwkLBwUJCw+FCQsFD4kLBQELCwMHCwsBCw0LCQUPiwUJD4sJBQENCQkBDQ0JAQ0JDQENAwUDDQMNAw0PgwUNCQ0FDQEHBw0NBwcNCQkJDQsBCw0FCwsNBwcNDQcBD40DBQ+NBwEBD4sBAQ+PgQEPgQMBD4UHAQ+FCwEPjQUDD4EJAw+FAwUPiwkFD4ULBQ+BAQcPiwMHD4cHBw+BDQcPgwUJD4MJCQ+BAQsPhQELD4kFCw+BBw0Pg==");
10
+ export const iq3s_grid = /* 2048B */ D("AQEBAQMBAQEFAQEBCwEBAQ8BAQEBAwEBAwMBAQUDAQEJAwEBDQMBAQEFAQEDBQEBCwUBAQcHAQEBCQEBBQkBAQsJAQEPCQEBAwsBAQcLAQEBDQEBBQ0BAQMPAQEJDwEBDw8BAQEBAwEDAQMBBQEDAQkBAwEBAwMBAwMDAQsDAwEBBQMBBwUDAQ8FAwEDBwMBCwcDAQkJAwEDDQMBCw0DAQUPAwEBAQUBAwEFAQsBBQEPAQUBAQMFAQcDBQENAwUBAwUFAQsFBQEBBwUBCQcFAQUJBQELCQUBDwkFAQMLBQEHCwUBAQ8FAQcPBQEHAQcBAwMHAQsDBwEBBQcBBQUHAQMHBwEHBwcBDQcHAQkJBwEBCwcBBQsHAQ8NBwEDDwcBCw8HAQEBCQEHAwkBDwMJAQMFCQEJBQkBBQcJAQEJCQEHCQkBAwsJAQEPCQEFAQsBCQELAQEFCwEFBQsBDQULAQcHCwEDCQsBCwkLAQ8JCwENDQsBBw8LAQ0BDQEDAw0BBwMNAQMHDQEFCw0BAw8NAQEBDwEFAQ8BCQEPAQEFDwEFBQ8BDQUPAQcHDwEBCw8BCQsPAQEBAQMDAQEDBQEBAwkBAQMBAwEDAwMBAwcDAQMLAwEDDwMBAwEFAQMFBQEDAwcBAwkHAQMNBwEDCQsBAw0LAQMDDQEDBQ8BAwEBAwMDAQMDBwEDAw0BAwMBAwMDCQMDAwMFAwMBBwMDBwcDAwMJAwMBCwMDBQsDAwEPAwMNDwMDAQEFAwUDBQMLAwUDDwMFAwEFBQMJBQUDBQcFAwEJBQMHCQUDCwsFAwENBQMFDwUDAwEHAwkBBwMPAQcDAQMHAwcDBwMDBQcDDwUHAwEHBwMJBwcDAwkHAwUNBwMBDwcDBwEJAwsBCQMFAwkDCQMJAwMHCQMHBwkDBQkJAw0JCQMBCwkDCQsJAwMBCwMBAwsDBwMLAwMFCwMBBwsDBQcLAwMLCwMBBQ0DCQUNAw8FDQMJCQ0DDQkNAwMBDwMHAQ8DAQMPAwUDDwMDBQ8DCwcPAwMJDwMFDQ8DAQ8PAwEBAQUDAQEFBwEBBQsBAQUPAQEFAQMBBQUDAQUJAwEFDQMBBQMFAQUHBQEFDwUBBQEHAQUFBwEFAwkBBQcJAQULCQEFAQsBBQULAQUPDQEFAQ8BBQcPAQULDwEFAQEDBQUBAwUBAwMFBwMDBQ8DAwUFBQMFCwUDBQMHAwUJBwMFBQkDBQMLAwUDAQUFCQEFBQ8BBQUDBQUFBwUFBQEHBQUPBwUFAwkFBQcLBQUPCwUFAw8FBQkPBQUBAQcFBQEHBQsBBwUDAwcFBQUHBQkFBwUDBwcFBwcHBQUJBwUBCwcFDQ0HBQMBCQUPAQkFAQUJBQcFCQUFBwkFCwcJBQMJCQUFDwkFCw8JBQkBCwUDAwsFBQULBQ8HCwUBCQsFBwsLBQEPCwUBAQ0FBQENBQ8BDQUDBQ0FCwsNBQMNDQULAQ8FAwMPBQ0FDwUBBw8FBwkPBQELDwUFAQEHAwMBBwcDAQcLAwEHDwMBBwUFAQcDBwEHBwcBBwsHAQcFCQEHCQkBBw8JAQcDCwEHBw0BBwMPAQcDAQMHBwEDBwsBAwcJAwMHAwUDBwcFAwcBCQMHAQ0DBwUPAwcNDwMHAQEFBwUDBQcBBQUHBQcFBwkHBQcBCwUHAwEHBwEDBwcJAwcHAwUHBwcFBwcPBQcHAQcHBwMJBwcHCQcHDwkHBwsLBwcHDwcHBwEJBwMDCQcNAwkHBQUJBwMHCQcFCwkHAQ0JBwkNCQcDAQsHAQMLBwUDCwcLBQsHBQcLBwkJCwcNCwsHBw8LBw0DDQcDCQ0HAwEPBwcBDwcBBQ8HBQUPBwsHDwcBAQEJCQEBCQUDAQkBBQEJCQUBCQ8FAQkFBwEJAwkBCQELAQkBDwEJBQEDCQ8BAwkDAwMJBwMDCQUFAwkBBwMJCwcDCQcJAwkDCwMJCwsDCQMBBQkHAQUJAQMFCQsDBQkDBQUJBwcFCQEJBQkPCwUJBQ0FCQEPBQkJAQcJAwMHCQcDBwkBBQcJBQUHCQMHBwkLBwcJAQEJCQUBCQkJBQkJDwcJCQEJCQkDDwkJCwELCQ8BCwkDBQsJBQ0LCQcDDQkJBw0JAQ0NCQEDDwkLAw8JAQcPCQcJDwkDCw8JBQEBCwEDAQsJAwELBQUBCwEJAQsJCQELDwkBCwULAQsNDQELCQ8BCwMBAwsHAQMLCwEDCwUDAwsDBQMLBQcDCwUPAwsBAQULAwMFCwcFBQsBBwULDQcFCwcLBQsFAQcLDwEHCwEDBwsPBQcLCQkHCwMLBwsLDQcLBw8HCwMBCQsJAQkLAQUJCwUHCQsNCQkLBQMLCw0FCwsDCwsLBwsLCwUJDQsFAQ8LCQEPCwUFDwsDAwENBwMBDQsDAQ0DBwENBwcBDQENAQ0BAQMNAQUDDQ8FAw0JDQMNBQMFDQkHBQ0FCQUNCwsFDQUNBQ0BDwUNAQEHDQkDBw0DBQcNAQkHDQsFCQ0HCQkNBQ0JDQEBCw0HAQsNCQcLDQENCw0LAQ0NAQkNDQMDDw0HAw8NAQEBDwkBAQ8PAQEPAQUBDwUFAQ8NBwEPAQkBDwkLAQ8FDQEPBQEDDwMDAw8JBQMPBwkDDwsJAw8DAQUPCQEFDwEDBQ8NAwUPAwUFDwEHBQ8DCwUPBQEHDwUHBw8LBwcPBwsHDwMBCQ8LAQkPBwMJDwEFCQ8BCwkPBQULDwUJCw8FAQ0PAwcNDwEBDw8=");
11
+ export const iq1s_grid = /* 16384B */ D("//////////8B/////////wAA/////////wH///////8BAf///////wD/AP//////AAAA/////////wH//////wH/Af///////wEB//////8BAQH//////wAA/wD/////AP8AAP//////AAAA/////wEAAAD/////AAABAP////////8B/////wH//wH//////wH/Af////8BAf8B/////wAAAAH///////8BAf////8B/wEB//////8BAQH/////AQEBAf//////AP//AP///wAA//8A////AP8A/wD/////AAD/AP///wEAAP8A////AAEA/wD///8BAQD/AP///wAAAf8A////AP//AAD///8BAP8AAP///wAB/wAA////Af8AAAD///8AAAAAAP///wEBAAAA////AP8BAAD/////AAEAAP///wEAAQAA/////wEBAAD///8AAP8BAP///wD/AAEA/////wAAAQD///8BAAABAP///wAAAQEA/////////wH///8B////Af////8B//8B////AQH//wH///8AAAD/Af//////Af8B////Af8B/wH/////AQH/Af///wEBAf8B////AAD/AAH///8A/wAAAf///wABAAAB/////wABAAH///8AAQEAAf///////wEB////Af//AQH/////Af8BAf///wEB/wEB////AP8AAQH///8AAAABAf///wABAAEB//////8BAQH///8B/wEBAf////8BAQEB////AQEBAQH///8A/wD//wD///8AAP//AP//AQAA//8A//8AAAH//wD//wD//wD/AP//AAH/AP8A//8AAAAA/wD//wEBAAD/AP///wABAP8A//8AAAEA/wD//wD/AAH/AP//AAEAAf8A//8AAAEB/wD//wD///8AAP///wD//wAA//8AAP//AAD//wEA//8AAP//AAAA/wAA////AQD/AAD//wEBAP8AAP//AAEB/wAA//////8AAAD//wAA/wAAAP//AQH/AAAA/////wAAAAD//wD/AAAAAP///wAAAAAA//8AAAAAAAD//wEAAAAAAP//AAEAAAAA/////wEAAAD//wH/AQAAAP//AAABAAAA////AQEAAAD//wEBAQAAAP//AP//AQAA//8A/wABAAD//wAAAAEAAP///wEAAQAA//8BAQABAAD//wD/AQEAAP///wABAQAA//8AAAEBAAD//wEAAQEAAP//AAEBAQAA////AAD/AQD//wABAP8BAP//AP//AAEA////AP8AAQD/////AAABAP//Af8AAAEA//8AAAAAAQD///8BAAABAP////8BAAEA//8A/wEAAQD//wEAAQABAP//AAEBAAEA//8AAP8BAQD//wD/AAEBAP///wAAAQEA//8AAQABAQD/////////Af//Af////8B////Af///wH//wEB////Af//AAAA//8B/////wH//wH//wH/Af//Af///wEB//8B//8BAQH//wH//wAA/wD/Af//AP8AAP8B//8BAAAA/wH//wAAAQD/Af//////Af8B//8B//8B/wH///8B/wH/Af//AQH/Af8B//8AAAAB/wH/////AQH/Af//Af8BAf8B////AQEB/wH//wEBAQH/Af//AAD//wAB//8A/wD/AAH///8AAP8AAf//AAEA/wAB////AAH/AAH//wAAAf8AAf//AP//AAAB/////wAAAAH//wD/AAAAAf//AAAAAAAB//8A/wEAAAH///8AAQAAAf//AAEBAAAB//8A/wABAAH///8AAAEAAf//AQAAAQAB//8AAQABAAH//wAAAQEAAf///////wEB//8B////AQH///8B//8BAf//AQH//wEB//8AAAD/AQH/////Af8BAf//Af8B/wEB////AQH/AQH//wEBAf8BAf//AAD/AAEB//8A/wAAAQH//wABAAABAf//AP8BAAEB//8AAAEAAQH//////wEBAf//Af//AQEB//8AAP8BAQH///8B/wEBAf//AQH/AQEB//8AAAABAQH/////AQEBAf//Af8BAQEB////AQEBAQH//wEBAQEBAf////8A////AP8A/wD///8A//8AAP///wD/AAEA////AP//AAH///8A/wAAAf///wD/AP//AP//AP//AP8A//8A////AAD//wD/AAAAAP//AP//AQAA//8A/wD/AQD//wD//wABAP//AP8AAAEA//8A/wABAQD//wD/AP8AAf//AP//AAAB//8A/wEAAAH//wD/AP8BAf//AP8AAAEB//8A/wD///8A/wD//wD//wD/AP8BAP//AP8A/wAB//8A/wD///8A/wD/AP8B/wD/AP8A/wAAAP8A/wD//wEA/wD/AP8A/wH/AP8A//8AAf8A/wD/AAEB/wD/AP8AAP8AAP8A/wEB/wAA/wD///8AAAD/AP8A/wAAAP8A/wH/AAAA/wD//wAAAAD/AP8AAAAAAP8A/wEAAAAA/wD/AAEAAAD/AP///wEAAP8A/wAAAQAA/wD//wD/AQD/AP8B/wABAP8A/wAAAAEA/wD/AP8BAQD/AP//AAEBAP8A/wD/AP8B/wD//wAA/wH/AP8BAAD/Af8A/wAAAf8B/wD/////AAH/AP8BAP8AAf8A/wAB/wAB/wD/Af8AAAH/AP8AAAAAAf8A//8BAAAB/wD/AQEAAAH/AP//AAEAAf8A/wEAAQAB/wD/AAD/AQH/AP8A/wABAf8A//8AAAEB/wD/AQAAAQH/AP8AAAEBAf8A/wD/////AAD/AQD///8AAP8AAf///wAA//8AAP//AAD/AAAA//8AAP//AQD//wAA/wABAP//AAD/AP8B//8AAP8BAAH//wAA/wD//wD/AAD/AAD/AP8AAP8BAP8A/wAA//8B/wD/AAD/AQH/AP8AAP8A/wAA/wAA//8AAAD/AAD/AAAAAP8AAP8BAAAA/wAA/wABAAD/AAD/Af8BAP8AAP8AAAEA/wAA//8BAQD/AAD//wD/Af8AAP8AAf8B/wAA////AAH/AAD//wAAAf8AAP8AAAAB/wAA//8BAAH/AAD/AAEAAf8AAP8BAQAB/wAA/wD/AQH/AAD//wABAf8AAP8AAAEB/wAA/wABAQH/AAD/Af///wAAAP8AAP//AAAA/wEB//8AAAD/AP8A/wAAAP//AAD/AAAA/wAAAP8AAAD/AQAA/wAAAP8AAQD/AAAA////Af8AAAD/Af8B/wAAAP8AAAH/AAAA//8BAf8AAAD/AQEB/wAAAP8A//8AAAAA//8A/wAAAAD/AAD/AAAAAP8BAP8AAAAA/wD/AAAAAAD/Af8AAAAAAP//AAAAAAAA/wAAAAAAAAD/AQAAAAAAAP8AAQAAAAAA/wEBAAAAAAD/AP8BAAAAAP//AAEAAAAA/wAAAQAAAAD/AQABAAAAAP8AAQEAAAAA/////wEAAAD/Af//AQAAAP//AP8BAAAA/wAA/wEAAAD//wH/AQAAAP8BAf8BAAAA////AAEAAAD/AP8AAQAAAP//AAABAAAA/wAAAAEAAAD/AQAAAQAAAP8AAQABAAAA/wEBAAEAAAD///8BAQAAAP8B/wEBAAAA/wAAAQEAAAD/AP///wEAAP//AP//AQAA/wAA//8BAAD/AQD//wEAAP8AAAD/AQAA/wEAAP8BAAD//wEA/wEAAP8BAQD/AQAA/wD/Af8BAAD/AQAB/wEAAP////8AAQAA/wH//wABAAD//wD/AAEAAP8AAP8AAQAA//8B/wABAAD/AQH/AAEAAP8A/wAAAQAA/wAAAAABAAD/AQAAAAEAAP//AQAAAQAA/wABAAABAAD/AP8BAAEAAP//AAEAAQAA/wAAAQABAAD//wEBAAEAAP8AAQEAAQAA/wEBAQABAAD/AQD/AQEAAP8BAf8BAQAA/wH/AAEBAAD/AAAAAQEAAP//AAEBAQAA/wABAQEBAAD/AP8A//8BAP8BAAD//wEA/wAAAf//AQD/AP//AP8BAP//AP8A/wEA/wEA/wD/AQD/AAH/AP8BAP///wAA/wEA/wAAAAD/AQD//wEAAP8BAP8BAQAA/wEA////AQD/AQD/AP8BAP8BAP//AAEA/wEA/wEAAQD/AQD/AAEBAP8BAP8AAP8B/wEA/wD/AAH/AQD//wAAAf8BAP8AAAEB/wEA////AP8AAQD/Af8A/wABAP8AAAD/AAEA/wEBAP8AAQD/AP8B/wABAP8AAAH/AAEA/wH//wAAAQD//wD/AAABAP8AAP8AAAEA//8B/wAAAQD/AP8AAAABAP//AAAAAAEA/wAAAAAAAQD/AQAAAAABAP8AAQAAAAEA/wEBAAAAAQD///8BAAABAP8AAAEAAAEA/wEBAQAAAQD/AAH/AQABAP8A/wABAAEA/wH/AAEAAQD/AAAAAQABAP//AQABAAEA/wD/AQEAAQD/AQABAQABAP8AAQEBAAEA/wAB//8BAQD/AQAA/wEBAP//AAH/AQEA/wEAAf8BAQD//wD/AAEBAP8BAP8AAQEA/wAB/wABAQD///8AAAEBAP8B/wAAAQEA/wAAAAABAQD//wEAAAEBAP8A/wEAAQEA/wEAAQABAQD/AAEBAAEBAP8AAP8BAQEA/wD/AAEBAQD/AQAAAQEBAP8BAQABAQEA/////////wH/Af//////Af//Af////8B/wEB/////wH/AAAA////Af///wH///8B/wH/Af///wH/AAAB////Af//AQH///8B/wEBAf///wH/AAD/AP//Af8A/wAA//8B/wABAAD//wH/AP8BAP//Af8AAAEA//8B/////wH//wH/Af//Af//Af//Af8B//8B/wEB/wH//wH/AAAAAf//Af///wEB//8B/wH/AQH//wH/AAABAf//Af//AQEB//8B/wEBAQH//wH/AAD//wD/Af8A/wD/AP8B//8AAP8A/wH/AAEA/wD/Af8AAAH/AP8B/wH//wAA/wH//wD/AAD/Af8AAf8AAP8B/wAAAAAA/wH//wEAAAD/Af8BAQAAAP8B/wD/AQAA/wH//wABAAD/Af8AAAEAAP8B/wEAAQAA/wH/AAD/AQD/Af///wABAP8B/wEAAAEA/wH/AAEAAQD/Af8AAAEBAP8B/wD///8B/wH//wH//wH/Af8BAf//Af8B/wD/AP8B/wH/AAAA/wH/Af///wH/Af8B/wH/Af8B/wH//wEB/wH/Af8BAQH/Af8B/wAA/wAB/wH/AP8AAAH/Af8BAAAAAf8B/wABAAAB/wH/AAABAAH/Af8A//8BAf8B//8B/wEB/wH/AQH/AQH/Af8A/wABAf8B/wAAAAEB/wH///8BAQH/Af8B/wEBAf8B//8BAQEB/wH/AQEBAQH/Af8AAP///wAB//8AAP//AAH/AQAA//8AAf8AAQD//wAB/wAAAf//AAH//wD/AP8AAf8AAP8A/wAB/wEA/wD/AAH/AAH/AP8AAf8B/wAA/wAB/wAAAAD/AAH//wEAAP8AAf8BAQAA/wAB/wEAAQD/AAH/AAD/Af8AAf8A/wAB/wAB//8AAAH/AAH/AAEAAf8AAf8A/wEB/wAB/wAAAQH/AAH/AAH//wAAAf8AAAD/AAAB/wD/Af8AAAH/AAEB/wAAAf////8AAAAB/wAA/wAAAAH//wH/AAAAAf8A/wAAAAAB//8AAAAAAAH/AAAAAAAAAf8AAQAAAAAB/wH/AQAAAAH/AAABAAAAAf//AQEAAAAB/wAB/wEAAAH///8AAQAAAf//AAABAAAB/wAAAAEAAAH//wEAAQAAAf8BAQABAAAB/wD/AQEAAAH//wABAQAAAf8BAAEBAAAB/wABAQEAAAH/AAD//wEAAf///wD/AQAB/wH/AP8BAAH/AAEA/wEAAf8AAAH/AQAB/wD//wABAAH/AAH/AAEAAf8AAAAAAQAB////AQABAAH/AP8BAAEAAf8AAQEAAQAB//8A/wEBAAH/AQD/AQEAAf///wABAQAB/wEBAAEBAAH///////8BAf8B/////wEB//8B////AQH/AQH///8BAf8AAAD//wEB////Af//AQH/Af8B//8BAf//AQH//wEB/wEBAf//AQH/AAD/AP8BAf8A/wAA/wEB//8AAAD/AQH/AAABAP8BAf////8B/wEB/wH//wH/AQH//wH/Af8BAf8BAf8B/wEB////AQH/AQH/Af8BAf8BAf//AQEB/wEB/wEBAQH/AQH/AAH//wABAf8A/wD/AAEB//8AAP8AAQH/AAEA/wABAf8AAAH/AAEB/wEA/wAAAQH/AAH/AAABAf8B/wAAAAEB/wAAAAAAAQH/AP8BAAABAf//AAEAAAEB/wEAAQAAAQH/AAEBAAABAf8AAP8BAAEB////AAEAAQH/AQAAAQABAf8AAQABAAEB//8AAQEAAQH/AAABAQABAf//////AQEB/wH///8BAQH//wH//wEBAf8BAf//AQEB////Af8BAQH/Af8B/wEBAf//AQH/AQEB/wEBAf8BAQH/AAD/AAEBAf8A/wAAAQEB/wEAAAABAQH/AAEAAAEBAf8AAAEAAQEB/////wEBAQH/Af//AQEBAf//Af8BAQEB/wEB/wEBAQH/AAAAAQEBAf///wEBAQEB/wH/AQEBAQH//wEBAQEBAf8BAQEBAQEB/wAA//////8AAP8A/////wABAAD/////AAAAAf////8AAAH/AP///wAB/wAA////AAAAAAD///8A/wEAAP///wABAQAA////AAD/AQD///8A/wABAP///wABAAEA////AP8AAAH///8AAAEAAf///wAA/wEB////AAEAAQH///8A/////wD//wAA////AP//AP8A//8A//8AAQD//wD//wAAAf//AP//AAH/AP8A//8AAAAA/wD//wABAAD/AP//AP8BAP8A//8AAQEA/wD//wAA/wH/AP//AAEAAf8A//8AAAEB/wD//wAAAP8AAP//AP8B/wAA//8AAQH/AAD//wAA/wAAAP//AP8AAAAA//8AAAAAAAD//wABAAAAAP//AAABAAAA//8AAQEAAAD//wAAAAEAAP//AP8BAQAA//8AAQEBAAD//wAA//8BAP//AP8A/wEA//8AAQD/AQD//wD//wABAP//AAH/AAEA//8AAAAAAQD//wD//wEBAP//AAD/AQEA//8AAf8BAQD//wAAAP//Af//AAD/AP8B//8A/wAA/wH//wABAAD/Af//AAAAAf8B//8AAP//AAH//wAB/wAAAf//AAAAAAAB//8AAQEAAAH//wD/AAEAAf//AAABAQAB//8AAAH/AQH//wD/AAABAf//AAAAAQEB//8AAP////8A/wAAAAD//wD/AAABAP//AP8AAAEB//8A/wAAAP8A/wD/AP8B/wD/AP8AAQH/AP8A/wAA/wAA/wD/AP8AAAD/AP8AAAAAAP8A/wABAAAA/wD/AAD/AQD/AP8AAf8BAP8A/wAAAAEA/wD/AP8BAQD/AP8AAQEBAP8A/wAA//8B/wD/AAEA/wH/AP8AAAH/Af8A/wD//wAB/wD/AAH/AAH/AP8AAAAAAf8A/wD//wEB/wD/AAD/AQH/AP8AAAEBAf8A/wAA////AAD/AAH///8AAP8AAAD//wAA/wABAf//AAD/AAD/AP8AAP8A/wAA/wAA/wAAAAD/AAD/AAEAAP8AAP8AAAEA/wAA/wD//wH/AAD/AAAAAf8AAP8AAQEB/wAA/wAA//8AAAD/AP8A/wAAAP8AAAD/AAAA/wABAP8AAAD/AAAB/wAAAP8A//8AAAAA/wAA/wAAAAD/AP8AAAAAAP8AAAAAAAAA/wABAAAAAAD/AP8BAAAAAP8AAAEAAAAA/wAA/wEAAAD/AP8AAQAAAP8AAAABAAAA/wABAAEAAAD/AAABAQAAAP8AAf//AQAA/wD/AP8BAAD/AAAA/wEAAP8A/wH/AQAA/wAA/wABAAD/AP8AAAEAAP8AAAAAAQAA/wABAAABAAD/AAABAAEAAP8AAQEAAQAA/wAAAAEBAAD/AP8BAQEAAP8AAQEBAQAA/wAA////AQD/AAAA//8BAP8AAAH//wEA/wD/AAD/AQD/AAAAAP8BAP8A/wEA/wEA/wABAQD/AQD/AAD/Af8BAP8A/wAB/wEA/wAAAQH/AQD/AP///wABAP8AAf//AAEA/wAAAP8AAQD/AP8B/wABAP8A//8AAAEA/wAA/wAAAQD/AAH/AAABAP8AAAAAAAEA/wABAAAAAQD/AAABAAABAP8AAf8BAAEA/wAAAAEAAQD/AP8BAQABAP8AAP//AQEA/wAAAP8BAQD/AAEB/wEBAP8A/wAAAQEA/wAAAAABAQD/AAD/AQEBAP8A/wABAQEA/wABAAEBAQD/AAAA////Af8AAP8A//8B/wAAAAD//wH/AAEBAP//Af8AAAAB//8B/wAB//8A/wH/AAAB/wD/Af8A//8AAP8B/wAAAAAA/wH/AP8BAAD/Af8AAP8BAP8B/wD/AAEA/wH/AAEAAQD/Af8AAAEBAP8B/wAAAP8B/wH/AAD/AAH/Af8A/wAAAf8B/wABAAAB/wH/AAABAAH/Af8AAAABAf8B/wAA////AAH/AAAA//8AAf8AAQD//wAB/wABAf//AAH/AP//AP8AAf8A/wAA/wAB/wAAAAD/AAH/AP8BAP8AAf8AAP8B/wAB/wD/AAH/AAH/AAEAAf8AAf8A////AAAB/wAAAP8AAAH/AAEB/wAAAf8AAP8AAAAB/wAB/wAAAAH/AP8AAAAAAf8AAAAAAAAB/wABAAAAAAH/AAABAAAAAf8A//8BAAAB/wAB/wEAAAH/AAAAAQAAAf8AAQABAAAB/wABAQEAAAH/AAEA/wEAAf8AAAH/AQAB/wAB/wABAAH/AAAAAAEAAf8AAQAAAQAB/wD/AQABAAH/AAD/AQEAAf8A/wABAQAB/wABAAEBAAH/AAABAQEAAf8AAQAA/wEB/wD/AP8AAQH/AAEA/wABAf8AAAH/AAEB/wAAAAAAAQH/AP8BAAABAf8AAQEAAAEB/wD/AAEAAQH/AAABAQABAf8A/wAAAQEB/wAAAAEBAQH/AAD//////wAA/wD/////AAAAAP////8AAAEA/////wAAAAH/////AAAB/wD///8AAAAAAP///wAAAQEA////AAAA/wH///8AAP8AAf///wAAAAEB////AAD///8A//8AAAAA/wD//wAA/wH/AP//AAAA/wAA//8AAP8AAAD//wAAAAAAAP//AAABAAAA//8AAAABAAD//wAAAAABAP//AAD/AQEA//8AAAEA/wH//wAAAAH/Af//AAAAAAAB//8AAP8BAAH//wAA//8BAf//AAAA/wEB//8AAAEAAQH//wAAAAEBAf//AAAAAP//AP8AAP8B//8A/wAAAAH//wD/AAABAf//AP8AAAD/AP8A/wAA/wAA/wD/AAAAAAD/AP8AAAEAAP8A/wAA/wEA/wD/AAAAAQD/AP8AAP//Af8A/wAAAAAB/wD/AAABAAH/AP8AAP8BAf8A/wAAAQEB/wD/AAAA//8AAP8AAP8A/wAA/wAAAAD/AAD/AAABAP8AAP8AAAAB/wAA/wAA//8AAAD/AAAA/wAAAP8AAAH/AAAA/wAA/wAAAAD/AAAAAAAAAP8AAAEAAAAA/wAA/wEAAAD/AAAAAQAAAP8AAAEBAAAA/wAAAP8BAAD/AAD/AAEAAP8AAAAAAQAA/wAAAQABAAD/AAAAAQEAAP8AAAH//wEA/wAAAAD/AQD/AAAA/wABAP8AAP8AAAEA/wAAAAAAAQD/AAABAAABAP8AAAABAAEA/wAA//8BAQD/AAAAAAEBAP8AAAEBAQEA/wAAAP///wH/AAABAP//Af8AAAH/AP8B/wAAAAAA/wH/AAABAQD/Af8AAAD/Af8B/wAA/wAB/wH/AAAB//8AAf8AAAAA/wAB/wAAAQH/AAH/AAAA/wAAAf8AAP8AAAAB/wAAAAAAAAH/AAABAAAAAf8AAAABAAAB/wAAAf8BAAH/AAAAAAEAAf8AAAAA/wEB/wAA//8AAQH/AAAB/wABAf8AAAAAAAEB/wAAAAEAAQH/AAABAQABAf8AAP8AAQEB/wAA/wD///8AAAAAAP///wAAAAD/AP//AAAA/wAA//8AAAAAAAD//wAAAAEAAP//AAAA/wEA//8AAAAAAQD//wAAAAD/Af//AAAAAAAB//8AAAD/AQH//wAAAAEBAf//AAAAAP//AP8AAAD/AP8A/wAAAAAA/wD/AAAAAQD/AP8AAAAAAf8A/wAAAAEB/wD/AAAA//8AAP8AAAAA/wAA/wAAAP8AAAD/AAAAAAAAAP8AAAABAAAA/wAAAP8BAAD/AAAAAAEAAP8AAAABAQAA/wAAAAD/AQD/AAAAAf8BAP8AAAD/AAEA/wAAAAAAAQD/AAAAAQABAP8AAAAAAQEA/wAAAP///wH/AAAA/wH/Af8AAAABAf8B/wAAAAD/AAH/AAAA/wAAAf8AAAAAAAAB/wAAAAEAAAH/AAAAAAEAAf8AAAAA/wEB/wAAAP8AAQH/AAAAAAABAf8AAAABAQEB/wAAAAD///8AAAAAAf///wAAAAD/AP//AAAAAAAA//8AAAAAAQD//wAAAAAAAf//AAAAAP//AP8AAAAAAP8A/wAAAAAB/wD/AAAAAP8AAP8AAAAAAAAA/wAAAAABAAD/AAAAAAABAP8AAAAAAQEA/wAAAAAA/wH/AAAAAP8AAf8AAAAAAAAB/wAAAAABAAH/AAAAAAABAf8AAAAA////AAAAAAAA//8AAAAAAAH//wAAAAAA/wD/AAAAAAAAAP8AAAAAAAEA/wAAAAAA/wH/AAAAAAAAAf8AAAAAAP//AAAAAAAAAP8AAAAAAAAB/wAAAAAAAP8AAAAAAAAAAAAAAAAAAAABAAAAAAAAAP8BAAAAAAAAAAEAAAAAAAABAQAAAAAAAP//AQAAAAAAAP8BAAAAAAD/AAEAAAAAAAAAAQAAAAAAAQABAAAAAAD/AQEAAAAAAAABAQAAAAAAAQEBAAAAAAAA//8BAAAAAP8A/wEAAAAAAAD/AQAAAAAAAf8BAAAAAAEB/wEAAAAA//8AAQAAAAAA/wABAAAAAP8AAAEAAAAAAAAAAQAAAAABAAABAAAAAP8BAAEAAAAAAAEAAQAAAAAA/wEBAAAAAP8AAQEAAAAAAAABAQAAAAABAAEBAAAAAAABAQEAAAAA/////wEAAAAA////AQAAAAH///8BAAAA/wD//wEAAAABAP//AQAAAP8B//8BAAAAAAH//wEAAAAA/wD/AQAAAP8AAP8BAAAAAAAA/wEAAAD/AQD/AQAAAAABAP8BAAAA//8B/wEAAAAA/wH/AQAAAAH/Af8BAAAA/wAB/wEAAAAAAAH/AQAAAAEAAf8BAAAA/wEB/wEAAAAAAQH/AQAAAAD//wABAAAAAAD/AAEAAAABAP8AAQAAAP8B/wABAAAAAAH/AAEAAAABAf8AAQAAAP//AAABAAAAAP8AAAEAAAAB/wAAAQAAAP8AAAABAAAAAAAAAAEAAAABAAAAAQAAAP8BAAABAAAAAAEAAAEAAAABAQAAAQAAAAD/AQABAAAA/wABAAEAAAAAAAEAAQAAAAABAQABAAAAAf//AQEAAAAAAP8BAQAAAAEA/wEBAAAA/wH/AQEAAAAAAf8BAQAAAAEB/wEBAAAAAP8AAQEAAAAAAAABAQAAAAEBAAEBAAAAAf8BAQEAAAAAAAEBAQAAAAEAAQEBAAAA/wEBAQEAAAAAAQEBAQAAAP8A////AQAAAAD///8BAAABAP///wEAAAAB////AQAA//8A//8BAAAAAAD//wEAAP8BAP//AQAAAP8B//8BAAABAQH//wEAAAAA/wD/AQAA/wH/AP8BAAABAf8A/wEAAAD/AAD/AQAA/wAAAP8BAAAAAAAA/wEAAAEAAAD/AQAA/wEAAP8BAAAAAQAA/wEAAP//AQD/AQAAAf8BAP8BAAD/AAEA/wEAAAAAAQD/AQAAAf//Af8BAAAAAf8B/wEAAP//AAH/AQAAAf8AAf8BAAAAAAAB/wEAAP8BAAH/AQAAAP8BAf8BAAAAAQEB/wEAAAD///8AAQAAAf///wABAAAAAP//AAEAAAEB//8AAQAAAP8A/wABAAD/AAD/AAEAAAAAAP8AAQAAAQAA/wABAAAAAQD/AAEAAAAAAf8AAQAAAP//AAABAAD/AP8AAAEAAAAA/wAAAQAAAQD/AAABAAAAAf8AAAEAAP//AAAAAQAAAP8AAAABAAAB/wAAAAEAAP8AAAAAAQAAAAAAAAABAAABAAAAAAEAAP8BAAAAAQAAAAEAAAABAAABAQAAAAEAAAD/AQAAAQAA/wABAAABAAAAAAEAAAEAAAEAAQAAAQAAAAEBAAABAAAA//8BAAEAAAAA/wEAAQAAAAH/AQABAAAA/wABAAEAAP8AAAEAAQAAAAAAAQABAAABAAABAAEAAP8BAAEAAQAAAAEAAQABAAAAAAEBAAEAAP8A//8BAQAA/wH//wEBAAAAAAD/AQEAAAEBAP8BAQAA//8B/wEBAAAAAAH/AQEAAAEAAf8BAQAAAAEB/wEBAAAAAP8AAQEAAP8B/wABAQAAAAH/AAEBAAAA/wAAAQEAAAAAAAABAQAAAQAAAAEBAAD/AQAAAQEAAAABAAABAQAAAf8BAAEBAAAAAAEAAQEAAP8BAQABAQAAAQEBAAEBAAAA//8BAQEAAAEB/wEBAQAAAf8AAQEBAAAAAAABAQEAAAEAAAEBAQAA/wEAAQEBAAABAQABAQEAAAD/AQEBAQAAAAD/////AQD/AAD///8BAAEAAP///wEAAAEA////AQAAAAH///8BAP8A/wD//wEA//8AAP//AQAAAAAA//8BAAEAAAD//wEA/wEAAP//AQABAQAA//8BAAD/AQD//wEA/wABAP//AQABAAEA//8BAAABAQD//wEAAP//Af//AQABAAAB//8BAAAAAQH//wEAAP///wD/AQD/AP//AP8BAAEA//8A/wEAAAH//wD/AQAB/wD/AP8BAAAAAP8A/wEAAP8B/wD/AQAB/wH/AP8BAAEAAf8A/wEAAAEB/wD/AQAAAP8AAP8BAAAB/wAA/wEAAP8AAAD/AQAAAAAAAP8BAAEAAAAA/wEAAAEAAAD/AQAAAAEAAP8BAAEAAQAA/wEAAQEBAAD/AQD/AP8BAP8BAAEB/wEA/wEAAf8AAQD/AQAAAAABAP8BAAD/AQEA/wEAAQABAQD/AQAAAQEBAP8BAAD/AP8B/wEAAQAA/wH/AQAAAQD/Af8BAP///wAB/wEAAP//AAH/AQABAP8AAf8BAAAAAAAB/wEAAQAAAAH/AQD/AQAAAf8BAP//AQAB/wEAAAD/AQH/AQAA/wABAf8BAAEAAAEB/wEAAAABAQH/AQAA/wD//wABAAH/AP//AAEAAAAA//8AAQABAAD//wABAAEBAP//AAEAAP8B//8AAQABAAH//wABAAABAf//AAEA////AP8AAQAB//8A/wABAAAA/wD/AAEA/wH/AP8AAQABAf8A/wABAAD/AAD/AAEA/wAAAP8AAQAAAAAA/wABAAEAAAD/AAEAAAEAAP8AAQABAQAA/wABAP//AQD/AAEAAf8BAP8AAQAAAAEA/wABAP8A/wH/AAEAAAD/Af8AAQAAAf8B/wABAP//AAH/AAEAAf8AAf8AAQD/AAAB/wABAAAAAAH/AAEAAQAAAf8AAQD/AQAB/wABAAEBAAH/AAEAAP8BAf8AAQD/AAEB/wABAAABAQH/AAEAAAD//wAAAQD/Af//AAABAAEB//8AAAEAAP8A/wAAAQAAAAD/AAABAAEAAP8AAAEAAAEA/wAAAQD/AP8AAAABAAAA/wAAAAEAAQD/AAAAAQAAAf8AAAABAP//AAAAAAEAAP8AAAAAAQD/AAAAAAABAAAAAAAAAAEAAQAAAAAAAQAAAQAAAAABAAD/AQAAAAEA/wABAAAAAQAAAAEAAAABAAEAAQAAAAEAAAEBAAAAAQABAP8BAAABAAAB/wEAAAEAAQH/AQAAAQAA/wABAAABAAAAAAEAAAEAAQAAAQAAAQAAAQABAAABAAEBAAEAAAEAAf8BAQAAAQAAAAEBAAABAAEAAQEAAAEA/wEBAQAAAQAB////AQABAAAB//8BAAEAAAAA/wEAAQD//wH/AQABAAEAAf8BAAEA/wEB/wEAAQAAAQH/AQABAP///wABAAEAAAD/AAEAAQD/Af8AAQABAAEB/wABAAEAAP8AAAEAAQD/AAAAAQABAAAAAAABAAEAAQAAAAEAAQD/AQAAAQABAAEBAAABAAEA//8BAAEAAQAAAAEAAQABAP8BAQABAAEA////AQEAAQAB//8BAQABAAAA/wEBAAEAAQH/AQEAAQD/AAABAQABAAEAAAEBAAEA/wEAAQEAAQAAAQABAQABAP//AQEBAAEA/wABAQEAAQABAAEBAQABAAEBAQEBAAEAAQAA//8BAQAAAQD//wEBAAAAAf//AQEAAP//AP8BAQAB/wAA/wEBAAAAAAD/AQEAAQEAAP8BAQAA/wEA/wEBAAABAQD/AQEAAAD/Af8BAQAA/wAB/wEBAP8BAAH/AQEAAQABAf8BAQAA////AAEBAP8A//8AAQEA//8A/wABAQAAAAD/AAEBAAD/Af8AAQEA/wAB/wABAQABAAH/AAEBAAABAf8AAQEA////AAABAQAA//8AAAEBAAAA/wAAAQEAAQD/AAABAQD/Af8AAAEBAAD/AAAAAQEA/wAAAAABAQAAAAAAAAEBAAEAAAAAAQEAAAEAAAABAQD//wEAAAEBAAAAAQAAAQEAAQEBAAABAQAB//8BAAEBAP8A/wEAAQEAAQH/AQABAQAAAAABAAEBAAD/AQEAAQEA/wABAQABAQAAAAEBAAEBAAABAQEAAQEAAP8A/wEBAQABAAD/AQEBAP8BAP8BAQEAAP//AAEBAQD/AP8AAQEBAAAB/wABAQEA//8AAAEBAQAAAAAAAQEBAP8BAAABAQEAAQEAAAEBAQD/AAEAAQEBAAAAAQABAQEAAAEBAAEBAQABAP8BAQEBAP8AAAEBAQEA/wEAAQEBAQABAQABAQEBAAEAAQEBAQEA/////////wEB////////Af8B//////8BAQH//////wH//wH/////AQH/Af////8B/wEB/////wEBAQH/////AQAA/wD///8B//8AAP///wEA/wAA////Af8AAAD///8BAQAAAP///wEAAQAA////AQAAAQD///8B////Af///wEB//8B////Af8B/wH///8BAQH/Af///wEAAAAB////Af//AQH///8BAf8BAf///wH/AQEB////AQEBAQH///8BAAD//wD//wEA/wD/AP//Af8AAP8A//8BAQAA/wD//wEAAQD/AP//AQAAAf8A//8BAP//AAD//wH/AP8AAP//AQAB/wAA//8B//8AAAD//wEB/wAAAP//AQAAAAAA//8BAQAAAAD//wH/AQAAAP//AQABAAAA//8B/wABAAD//wEBAAEAAP//AQABAQAA//8BAAD/AQD//wEAAf8BAP//Af8AAAEA//8BAQAAAQD//wEAAQABAP//AQAAAQEA//8B/////wH//wEB////Af//Af8B//8B//8BAQH//wH//wEAAAD/Af//Af//Af8B//8BAf8B/wH//wH/AQH/Af//AQEBAf8B//8BAP8AAAH//wH/AAAAAf//AQABAAAB//8BAAABAAH//wH///8BAf//AQH//wEB//8B/wH/AQH//wEBAf8BAf//AQAAAAEB//8B//8BAQH//wEB/wEBAf//Af8BAQEB//8BAQEBAQH//wH/AAD//wD/AQABAP//AP8BAP//AP8A/wH/AP8A/wD/AQD/AAD/AP8BAAAAAP8A/wEBAQAA/wD/AQD/AQD/AP8B/wABAP8A/wEAAQEA/wD/Af8AAAH/AP8BAAEAAf8A/wEA////AAD/AQAB//8AAP8BAf8A/wAA/wEAAAD/AAD/AQEBAP8AAP8BAQAB/wAA/wEAAQH/AAD/Af///wAAAP8BAP//AAAA/wEAAP8AAAD/Af8B/wAAAP8BAP8AAAAA/wH/AAAAAAD/AQAAAAAAAP8BAQAAAAAA/wEAAQAAAAD/AQEBAAAAAP8BAAABAAAA/wEBAAEAAAD/Af8BAQAAAP8BAQEBAAAA/wEA//8BAAD/Af8A/wEAAP8BAQD/AQAA/wEAAf8BAAD/Af//AAEAAP8BAf8AAQAA/wEAAAABAAD/Af8BAAEAAP8BAQABAQAA/wEA/wD/AQD/AQEAAP8BAP8BAAEA/wEA/wEAAAH/AQD/AQD//wABAP8B/wD/AAEA/wEAAf8AAQD/AQEB/wABAP8B//8AAAEA/wEAAAAAAQD/AQABAAABAP8BAQEAAAEA/wEA/wEAAQD/AQEAAQABAP8BAQEBAAEA/wEAAP8BAQD/AQD/AAEBAP8BAQEAAQEA/wH/AAEBAQD/Af//////Af8BAf////8B/wH/Af///wH/AQEB////Af8BAAAA//8B/wH//wH//wH/AQH/Af//Af8B/wEB//8B/wEBAQH//wH/AQD//wD/Af8BAAD/AP8B/wEA/wAA/wH/Af8AAAD/Af8BAAEAAP8B/wEAAAEA/wH/AQABAQD/Af8B////Af8B/wEB//8B/wH/Af8B/wH/Af8BAQH/Af8B/wEAAAAB/wH/Af//AQH/Af8BAf8BAf8B/wH/AQEB/wH/AQEBAQH/Af8BAAD//wAB/wEBAP//AAH/AQD/AP8AAf8B/wAA/wAB/wEBAAD/AAH/AQAAAf8AAf8BAP//AAAB/wH/AP8AAAH/AQEA/wAAAf8BAAH/AAAB/wH//wAAAAH/AQH/AAAAAf8BAAAAAAAB/wEBAQAAAAH/AQD/AQAAAf8B/wABAAAB/wEAAP8BAAH/AQEAAAEAAf8BAAEAAQAB/wEAAAEBAAH/Af////8BAf8BAf///wEB/wH/Af//AQH/AQEB//8BAf8BAAAA/wEB/wH//wH/AQH/AQH/Af8BAf8B/wEB/wEB/wEBAQH/AQH/AQAA/wABAf8BAP8AAAEB/wH/AAAAAQH/AQEAAAABAf8B////AQEB/wEB//8BAQH/Af8B/wEBAf8BAQH/AQEB/wEAAAABAQH/Af//AQEBAf8BAf8BAQEB/wH/AQEBAQH/AQEBAQEBAf8BAAD/////AAEA/wD///8AAQEAAP///wAB/wEA////AAEAAQD///8AAQAAAf///wABAP//AP//AAEBAP8A//8AAQAB/wD//wABAAAAAP//AAH/AQAA//8AAQEBAAD//wABAAEBAP//AAEBAQEA//8AAQAA/wH//wABAP8AAf//AAH/AAAB//8AAQEAAAH//wABAAEAAf//AAEAAAEB//8AAQD///8A/wAB/wD//wD/AAEBAP//AP8AAQAB//8A/wAB//8A/wD/AAEAAAD/AP8AAf8BAP8A/wABAQEA/wD/AAEA/wH/AP8AAf8AAf8A/wABAQAB/wD/AAEAAQH/AP8AAf///wAA/wABAAD/AAD/AAH//wAAAP8AAQD/AAAA/wAB/wAAAAD/AAEAAAAAAP8AAQEAAAAA/wABAAEAAAD/AAEB/wEAAP8AAQAAAQAA/wAB/wD/AQD/AAEBAP8BAP8AAQH/AAEA/wABAAAAAQD/AAH/AQABAP8AAQD/AQEA/wAB/wABAQD/AAEBAAEBAP8AAQABAQEA/wABAAD//wH/AAEA/wD/Af8AAf8AAP8B/wABAAEA/wH/AAEAAAH/Af8AAf8A/wAB/wABAQD/AAH/AAEAAf8AAf8AAf//AAAB/wABAf8AAAH/AAEAAAAAAf8AAf8BAAAB/wABAQABAAH/AAEAAQEAAf8AAQAA/wEB/wAB/wAAAQH/AAEBAAABAf8AAQABAQEB/wABAP////8AAAH/AP///wAAAQEA////AAAB//8A//8AAAEAAAD//wAAAf8BAP//AAABAQAB//8AAAH///8A/wAAAQEB/wD/AAABAP8AAP8AAAH/AAAA/wAAAQAAAAD/AAABAQAAAP8AAAH/AQAA/wAAAQABAAD/AAAB//8BAP8AAAEA/wEA/wAAAQH/AQD/AAABAAABAP8AAAH/AP8B/wAAAQEA/wH/AAABAf8AAf8AAAH/AAAB/wAAAQAAAAH/AAAB/wEAAf8AAAEA/wEB/wAAAQABAQH/AAAB/////wAAAAEAAP//AAAAAf8B//8AAAABAQH//wAAAAH//wD/AAAAAQD/AP8AAAAB/wAA/wAAAAEAAAD/AAAAAQEAAP8AAAABAAEA/wAAAAEA/wH/AAAAAQAAAf8AAAABAAEB/wAAAAEBAQH/AAAAAQD//wAAAAAB/wD/AAAAAAEAAP8AAAAAAQEA/wAAAAABAAH/AAAAAAH//wAAAAAAAQD/AAAAAAABAf8AAAAAAAH/AAAAAAAAAQAAAAAAAAABAQAAAAAAAAH/AQAAAAAAAQABAAAAAAABAQEAAAAAAAEA/wEAAAAAAf8AAQAAAAABAAABAAAAAAEBAAEAAAAAAQABAQAAAAABAP//AQAAAAEAAP8BAAAAAf8B/wEAAAABAP8AAQAAAAEB/wABAAAAAf8AAAEAAAABAAAAAQAAAAEBAAABAAAAAQABAAEAAAABAQEAAQAAAAH//wEBAAAAAQH/AQEAAAABAAABAQAAAAH/AQEBAAAAAQEBAQEAAAABAP///wEAAAH/AP//AQAAAf//AP8BAAABAAAA/wEAAAEAAQD/AQAAAf//Af8BAAABAQAB/wEAAAEAAQH/AQAAAQAA/wABAAAB/wH/AAEAAAEAAf8AAQAAAQD/AAABAAABAf8AAAEAAAEAAAAAAQAAAQEAAAABAAABAAEAAAEAAAEAAAEAAQAAAf8BAQABAAABAf//AQEAAAH/AP8BAQAAAQAB/wEBAAABAQH/AQEAAAEB/wABAQAAAf8AAAEBAAABAAAAAQEAAAH/AAEBAQAAAQEAAQEBAAABAAEBAQEAAAEAAP///wEAAQEAAP//AQABAAEA//8BAAEAAAH//wEAAQD//wD/AQABAQD/AP8BAAH//wAA/wEAAQH/AAD/AQABAAAAAP8BAAEBAAAA/wEAAQEBAAD/AQAB/wABAP8BAAEAAAEA/wEAAQAA/wH/AQABAP8AAf8BAAEBAAAB/wEAAQABAAH/AQABAAABAf8BAAH/AP//AAEAAQEA//8AAQABAAH//wABAAH//wD/AAEAAQH/AP8AAQABAAAA/wABAAH/AQD/AAEAAQEBAP8AAQAB//8B/wABAAEA/wH/AAEAAf8AAf8AAQABAQAB/wABAAH///8AAAEAAQH//wAAAQABAAD/AAABAAH/Af8AAAEAAQEB/wAAAQABAP8AAAABAAH/AAAAAAEAAQAAAAAAAQABAQAAAAABAAEAAQAAAAEAAQH/AQAAAQABAAABAAABAAEBAAEAAAEAAQEBAQAAAQABAP//AQABAAH/AP8BAAEAAf//AAEAAQABAf8AAQABAAEAAAABAAEAAQEBAAEAAQABAP8BAQABAAEBAAEBAAEAAQAA//8BAQABAAAA/wEBAAEAAAH/AQEAAf8A/wABAQABAQD/AAEBAAEAAf8AAQEAAf//AAABAQABAAAAAAEBAAH/AQAAAQEAAQD/AQABAQABAAD/AQEBAAEA/wABAQEAAf8AAAEBAQABAAAAAQEBAAEBAAABAQEAAf///////wEBAf//////AQH/Af////8BAQEB/////wEBAAAA////AQH//wH///8BAQH/Af///wEB/wEB////AQEBAQH///8BAQAA/wD//wEBAP8AAP//AQH/AAAA//8BAQEAAAD//wEBAAEAAP//AQH///8B//8BAQH//wH//wEB/wH/Af//AQEBAf8B//8BAQAAAAH//wEB//8BAf//AQEB/wEB//8BAf8BAQH//wEBAQEBAf//AQEAAP//AP8BAQAB//8A/wEBAP8A/wD/AQH/AAD/AP8BAQEAAP8A/wEBAAEA/wD/AQEBAQD/AP8BAQEA/wAA/wEBAAH/AAD/AQEA/wAAAP8BAQAAAAAA/wEB/wEAAAD/AQEBAQAAAP8BAQD/AQAA/wEB/wABAAD/AQEAAP8BAP8BAf//AAEA/wEBAf8AAQD/AQEBAAABAP8BAQABAAEA/wEBAf///wH/AQH/Af//Af8BAQEB//8B/wEB//8A/wH/AQEAAQD/Af8BAQH/Af8B/wEB/wEB/wH/AQEBAQH/Af8BAQAA/wAB/wEBAP8AAAH/AQEBAAAAAf8BAQABAAAB/wEBAAABAAH/AQH///8BAf8BAQH//wEB/wEB/wH/AQH/AQEBAf8BAf8BAQAAAAEB/wEB//8BAQH/AQEB/wEBAf8BAf8BAQEB/wEBAQEBAQH/AQEAAQD//wABAQAAAf//AAEBAP//AP8AAQH/AP8A/wABAf//AAD/AAEB/wAAAP8AAQEAAAAA/wABAf8BAAD/AAEBAQEAAP8AAQEA/wEA/wABAQAAAQD/AAEBAQABAP8AAQH/AQEA/wABAQABAQD/AAEBAAD/Af8AAQEBAP//AAABAQAB//8AAAEB//8A/wAAAQEB/wD/AAABAQAAAP8AAAEB/wEA/wAAAQEBAAH/AAABAQABAf8AAAEBAf//AAAAAQEAAP8AAAABAQD/AAAAAAEB/wAAAAAAAQEAAAAAAAABAQEAAAAAAAEBAAEAAAAAAQEAAAEAAAABAQEBAQAAAAEBAP//AQAAAQH/AP8BAAABAQAA/wEAAAEBAQD/AQAAAQEAAf8BAAABAQH/AAEAAAEBAAAAAQAAAQH/AQABAAABAQAA//8BAAEBAP8A/wEAAQEBAAD/AQABAQEBAP8BAAEBAP8B/wEAAQEAAAH/AQABAf8A/wABAAEBAQD/AAEAAQEBAf8AAQABAQH/AAABAAEBAAAAAAEAAQEBAAAAAQABAf8BAAABAAEB//8BAAEAAQEB/wEAAQABAQEA/wEBAAEB//8AAQEAAQEAAAABAQABAQEAAAEBAAEBAAEAAQEAAQEA/wEBAQABAf8AAQEBAAEBAQABAQEAAQH//////wEBAQH/////AQEB/wH///8BAQEBAf///wEBAf//Af//AQEBAf8B//8BAQH/AQH//wEBAQEBAf//AQEBAP8AAP8BAQH/AAAA/wEBAQEAAAD/AQEBAAEAAP8BAQH///8B/wEBAQH//wH/AQEB/wH/Af8BAQEBAf8B/wEBAQAAAAH/AQEB//8BAf8BAQEB/wEB/wEBAf8BAQH/AQEBAQEBAf8BAQEAAP//AAEBAf8AAP8AAQEBAAEA/wABAQEA/wH/AAEBAQAAAf8AAQEBAP//AAABAQH//wAAAAEBAQAAAAAAAQEBAQEAAAABAQEA/wEAAAEBAQEAAQAAAQEBAAEBAAABAQH//wABAAEBAQEAAAEAAQEB/////wEBAQEB////AQEBAf8B//8BAQEBAQH//wEBAQH//wH/AQEBAQH/Af8BAQEB/wEB/wEBAQEBAQH/AQEBAQD/AAABAQEB/wAAAAEBAQEBAAAAAQEBAf///wEBAQEBAf//AQEBAQH/Af8BAQEBAQEB/wEBAQEBAAAAAQEBAQH//wEBAQEBAQH/AQEBAQEB/wEBAQEBAQEBAQEBAQEBAQ==");
12
+ export const ksigns_iq2xs = /* 128B */ D("AIGCA4QFBoeICQqLDI2OD5AREpMUlZYXGJmaG5wdHp+gISKjJKWmJyipqiusLS6vMLGyM7Q1Nre4OTq7PL2+P8BBQsNExcZHSMnKS8xNTs9Q0dJT1FVW19hZWttc3d5fYOHiY+RlZufoaWrrbO3ub/BxcvN09fZ3ePn6e/x9fv8=");
13
+ export const kmask_iq2xs = /* 8B */ D("AQIECBAgQIA=");
14
+ export const kvalues_iq4nl = /* 16B */ D("gZitv8/d6vYBDRkmNUVZcQ==");
holo-delta.mjs ADDED
@@ -0,0 +1,156 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // holo-delta.mjs — A2 of the personal-model-zoo plan: family index-delta. A finetune that shares a base's
2
+ // FRAME (see holo-model-frame.mjs) is stored as `base-κ + per-tensor delta`, reconstructed at LOAD (no new
3
+ // kernel — the engine still reads normal quantized weights). Two wins compose:
4
+ // • FROZEN tensors (identical κ to the base) cost 0 extra bytes — content-addressing dedups them for free.
5
+ // • CHANGED tensors are stored as a BitDelta (Liu et al. 2024): sign(Δ) at 1 bit/param + one scale α.
6
+ //
7
+ // HONEST storage math (this is where the "5–9×" claim earns or loses): the zoo ratio is driven by the
8
+ // FROZEN-TENSOR FRACTION, not within-tensor sparsity. A LoRA/partial finetune (most tensors frozen) →
9
+ // 10–30×; a FULL finetune (every tensor moves) → only ~3–4× (BitDelta is ~1 bit/param vs a 3–4 bit base).
10
+ // BitDelta is intrinsically lossy (sign+scale captures ~2/π≈64% of a Gaussian delta's energy); its
11
+ // published result is that OUTPUT quality is nonetheless preserved — that must be confirmed by perplexity
12
+ // on a REAL base+finetune pair (the A2 pass-bar gate), which this codec self-test does not run.
13
+ //
14
+ // Pure JS, isomorphic, zero deps. Node self-test sweeps frozen-fraction + reports fidelity and zoo ratio.
15
+
16
+ import { shareable } from "./holo-model-frame.mjs";
17
+
18
+ // ── BitDelta codec: Δ = ftW − baseW ; store sign(Δ) (1 bit) + α = mean|Δ| (the L2-optimal ±1 scale). ──
19
+ export function encodeBitDelta(baseW, ftW) {
20
+ const n = baseW.length, signBits = new Uint8Array((n + 7) >> 3);
21
+ let absSum = 0;
22
+ for (let i = 0; i < n; i++) {
23
+ const d = ftW[i] - baseW[i];
24
+ if (d >= 0) signBits[i >> 3] |= 1 << (i & 7); // bit set ⇒ +1
25
+ absSum += Math.abs(d);
26
+ }
27
+ return { kind: "bitdelta", n, alpha: absSum / n, signBits }; // ~1 bit/param + one f32
28
+ }
29
+
30
+ export function decodeBitDelta(baseW, rec, out) {
31
+ const n = rec.n, a = rec.alpha, o = out || new Float32Array(n);
32
+ for (let i = 0; i < n; i++) {
33
+ const pos = (rec.signBits[i >> 3] >> (i & 7)) & 1;
34
+ o[i] = baseW[i] + (pos ? a : -a);
35
+ }
36
+ return o;
37
+ }
38
+
39
+ // fraction of the delta's ENERGY captured by sign+scale (1 = perfect). For a Gaussian Δ this → 2/π ≈ 0.637.
40
+ export function deltaCaptured(baseW, ftW, rec) {
41
+ const n = rec.n; let num = 0, den = 0;
42
+ for (let i = 0; i < n; i++) {
43
+ const d = ftW[i] - baseW[i], pos = (rec.signBits[i >> 3] >> (i & 7)) & 1, r = d - (pos ? rec.alpha : -rec.alpha);
44
+ num += r * r; den += d * d;
45
+ }
46
+ return den ? 1 - num / den : 1;
47
+ }
48
+
49
+ // ── model-level delta: per tensor, frozen (κ identical) → ref (0 bytes) else bitdelta. Frame-guarded. ──
50
+ // tensors: { name → { kappa, params, baseW?, ftW? } }. baseW/ftW (dequant weights) only needed to encode
51
+ // CHANGED tensors; pass them for changed ones, omit for frozen (κ tells us they're identical).
52
+ export function deltaModel(baseMeta, ftMeta, baseTensors, ftTensors) {
53
+ if (!shareable(baseMeta, ftMeta)) throw new Error("holo-delta: base and finetune are not in the same frame/arch — cannot delta (see holo-model-frame.shareable)");
54
+ const records = {}; let frozenParams = 0, changedParams = 0, deltaBytes = 0;
55
+ for (const name of Object.keys(ftTensors)) {
56
+ const b = baseTensors[name], f = ftTensors[name];
57
+ if (b && f.kappa && b.kappa === f.kappa) { records[name] = { kind: "ref", kappa: b.kappa }; frozenParams += f.params || 0; continue; }
58
+ const rec = encodeBitDelta(f.baseW, f.ftW);
59
+ records[name] = { kind: "bitdelta", base: b ? b.kappa : null, alpha: rec.alpha, n: rec.n };
60
+ changedParams += rec.n; deltaBytes += rec.signBits.length + 4; // bits + α
61
+ }
62
+ return { records, stats: { frozenParams, changedParams, deltaBytes, deltaBitsPerChangedParam: changedParams ? (deltaBytes * 8) / changedParams : 0 } };
63
+ }
64
+
65
+ // ── LOSSLESS byte-delta over stored quantized blocks (the right fit for κ-objects). A base and finetune
66
+ // tensor share the same packed length (same dims+fmt); store only the differing byte runs. Reconstruction
67
+ // is byte-identical to the finetune's own block ⇒ perplexity = standalone (NO quality gate). Falls back to
68
+ // storing the whole block when the diff isn't sparse. This is the default for the family loader. ──
69
+ const _GAP = 8; // coalesce changed runs separated by ≤_GAP identical bytes (amortizes per-run header)
70
+ export function encodeByteDelta(baseBytes, ftBytes) {
71
+ if (baseBytes.length !== ftBytes.length) return { kind: "whole", len: ftBytes.length, bytes: ftBytes };
72
+ const n = ftBytes.length, runs = []; let i = 0, deltaSize = 0;
73
+ while (i < n) {
74
+ if (baseBytes[i] === ftBytes[i]) { i++; continue; }
75
+ let j = i + 1, gap = 0; // extend run, tolerating short identical gaps
76
+ while (j < n && (baseBytes[j] !== ftBytes[j] || (gap = run_gap(baseBytes, ftBytes, j)) <= _GAP)) { j += gap > 0 ? gap : 1; if (gap > 0) gap = 0; }
77
+ runs.push({ off: i, bytes: ftBytes.slice(i, j) }); deltaSize += (j - i) + 8; // bytes + ~8B run header
78
+ i = j;
79
+ }
80
+ if (deltaSize >= n * 0.9) return { kind: "whole", len: n, bytes: ftBytes }; // not sparse enough → store whole
81
+ return { kind: "bytedelta", len: n, runs };
82
+ }
83
+ function run_gap(a, b, j) { let g = 0; while (j + g < a.length && a[j + g] === b[j + g]) g++; return g; }
84
+ export function applyByteDelta(baseBytes, rec) {
85
+ if (rec.kind === "whole") return rec.bytes;
86
+ const out = baseBytes.slice(0, rec.len);
87
+ for (const r of rec.runs) out.set(r.bytes, r.off);
88
+ return out;
89
+ }
90
+ export function byteDeltaSize(rec) { return rec.kind === "whole" ? rec.bytes.length : rec.runs.reduce((s, r) => s + r.bytes.length + 8, 0); }
91
+
92
+ // compact binary (de)serialization for a byte-delta record — what gets gzipped + content-addressed.
93
+ export function serializeDelta(rec) {
94
+ if (rec.kind === "whole") { const o = new Uint8Array(5 + rec.bytes.length); new DataView(o.buffer).setUint32(1, rec.len); o[0] = 1; o.set(rec.bytes, 5); return o; }
95
+ let sz = 9; for (const r of rec.runs) sz += 8 + r.bytes.length;
96
+ const o = new Uint8Array(sz), dv = new DataView(o.buffer); o[0] = 0; dv.setUint32(1, rec.len); dv.setUint32(5, rec.runs.length); let p = 9;
97
+ for (const r of rec.runs) { dv.setUint32(p, r.off); dv.setUint32(p + 4, r.bytes.length); o.set(r.bytes, p + 8); p += 8 + r.bytes.length; }
98
+ return o;
99
+ }
100
+ export function parseDelta(u8) {
101
+ const dv = new DataView(u8.buffer, u8.byteOffset, u8.byteLength), len = dv.getUint32(1);
102
+ if (u8[0] === 1) return { kind: "whole", len, bytes: u8.subarray(5) };
103
+ const nRuns = dv.getUint32(5), runs = []; let p = 9;
104
+ for (let i = 0; i < nRuns; i++) { const off = dv.getUint32(p), blen = dv.getUint32(p + 4); runs.push({ off, bytes: u8.subarray(p + 8, p + 8 + blen) }); p += 8 + blen; }
105
+ return { kind: "bytedelta", len, runs };
106
+ }
107
+
108
+ // zoo storage ratio vs storing N finetunes independently. baseBitsPerParam ≈ 3 (q3) or 4 (q4).
109
+ export function zooRatio({ totalParams, frozenFractionOfParams, N, baseBitsPerParam = 3 }) {
110
+ const B = baseBitsPerParam, g = 1 - frozenFractionOfParams; // g = changed fraction
111
+ const independent = (N + 1) * totalParams * B;
112
+ const shared = totalParams * B + N * (g * totalParams * 1); // base + N×(changed params × 1 bit)
113
+ return independent / shared;
114
+ }
115
+
116
+ // ── Node self-test: realistic synthetic base + finetunes at varying frozen fractions. ──
117
+ if (typeof process !== "undefined" && process.argv[1] && process.argv[1].endsWith("holo-delta.mjs")) {
118
+ const n = 1 << 20; // 1M-param tensor
119
+ const rnd = (() => { let s = 0x2545f491; return () => { s ^= s << 13; s ^= s >>> 17; s ^= s << 5; s >>>= 0; return s / 4294967296; }; })();
120
+ const gauss = (sig) => { let u = 0, v = 0; while (!u) u = rnd(); while (!v) v = rnd(); return sig * Math.sqrt(-2 * Math.log(u)) * Math.cos(2 * Math.PI * v); };
121
+ const baseW = new Float32Array(n); for (let i = 0; i < n; i++) baseW[i] = gauss(0.02); // LLM-like weight σ
122
+
123
+ console.log("\n— BitDelta fidelity on a CHANGED tensor (Δ ~ Gaussian) —");
124
+ for (const dsig of [0.002, 0.02]) {
125
+ const ftW = new Float32Array(n); for (let i = 0; i < n; i++) ftW[i] = baseW[i] + gauss(dsig);
126
+ const rec = encodeBitDelta(baseW, ftW); const hat = decodeBitDelta(baseW, rec);
127
+ let relNum = 0, relDen = 0; for (let i = 0; i < n; i++) { const e = ftW[i] - hat[i]; relNum += e * e; relDen += ftW[i] * ftW[i]; }
128
+ const bpp = (rec.signBits.length + 4) * 8 / n;
129
+ console.log(` Δσ=${dsig}: α=${rec.alpha.toExponential(2)} energy-captured=${(deltaCaptured(baseW, ftW, rec) * 100).toFixed(1)}% recon ||err||/||ft||=${Math.sqrt(relNum / relDen).toExponential(2)} ${bpp.toFixed(3)} bits/param`);
130
+ }
131
+
132
+ console.log("\n— zoo storage ratio (base + 50 finetunes) by frozen-tensor fraction —");
133
+ for (const frozen of [0.0, 0.5, 0.8, 0.9, 0.98]) {
134
+ const r3 = zooRatio({ totalParams: 2e9, frozenFractionOfParams: frozen, N: 50, baseBitsPerParam: 3 });
135
+ const r4 = zooRatio({ totalParams: 2e9, frozenFractionOfParams: frozen, N: 50, baseBitsPerParam: 4 });
136
+ const tag = frozen === 0 ? "full finetune" : frozen >= 0.98 ? "LoRA-ish" : "partial";
137
+ console.log(` frozen=${(frozen * 100).toFixed(0).padStart(3)}% → ${r3.toFixed(1)}× (q3 base) · ${r4.toFixed(1)}× (q4 base) [${tag}]`);
138
+ }
139
+
140
+ console.log("\n— lossless byte-delta over a quantized block (reconstruct must be byte-identical) —");
141
+ for (const changeFrac of [0.02, 0.2, 0.6]) {
142
+ const blkN = 1 << 20, base = new Uint8Array(blkN); for (let i = 0; i < blkN; i++) base[i] = (rnd() * 256) | 0;
143
+ const ft = base.slice(); for (let i = 0; i < blkN; i++) if (rnd() < changeFrac) ft[i] = (rnd() * 256) | 0; // finetune flips a fraction of bytes
144
+ const rec = encodeByteDelta(base, ft); const back = applyByteDelta(base, rec);
145
+ let identical = back.length === ft.length; for (let i = 0; identical && i < ft.length; i++) identical = back[i] === ft[i];
146
+ console.log(` changed≈${(changeFrac * 100).toFixed(0)}% → ${rec.kind.padEnd(9)} ${(byteDeltaSize(rec) / 1024).toFixed(0)}KB vs ${(blkN / 1024).toFixed(0)}KB block (${(byteDeltaSize(rec) / blkN).toFixed(2)}×) reconstruct byte-identical=${identical}`);
147
+ }
148
+
149
+ console.log("\n— frame guard —");
150
+ const A = { frame: { fingerprint: "x" }, d: 2560, n_layers: 30, ff: 6912, n_heads: 20, n_kv_heads: 5, hd: 128, vocab: 128256 };
151
+ const Bsame = { ...A }, Bdiff = { ...A, d: 3584 };
152
+ console.log(` same frame+arch shareable: ${shareable(A, Bsame)} · different arch shareable: ${shareable(A, Bdiff)}`);
153
+ // and that deltaModel refuses a mismatched pair
154
+ try { deltaModel(A, Bdiff, {}, {}); console.log(" ERROR: deltaModel should have refused"); }
155
+ catch (e) { console.log(` deltaModel correctly refused mismatch: "${e.message.slice(0, 60)}…"`); }
156
+ }
holo-load-delta.mjs ADDED
@@ -0,0 +1,54 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // holo-load-delta.mjs — A2 LOAD wiring. Loads a family finetune stored as `base-κ + delta` (format
2
+ // "holo-delta/1", produced by _delta-build.mjs) and returns the SAME { manifest, fetchTensor, info } shape
3
+ // as holo-load2bit.loadKappaObject — so the GPU engine, KV-cache, and Q's brain loader are UNCHANGED.
4
+ // loadKappaObject delegates here automatically when it sees the holo-delta/1 format (one seam, hidden).
5
+ //
6
+ // Per tensor: ref → the base block (frozen, κ identical, dedup'd in the OPFS store); whole → the stored
7
+ // finetune block; bytedelta → base block + lossless byte-delta. Reconstruction is byte-identical to the
8
+ // finetune's own block ⇒ perplexity = standalone (no quality gate). The win is download/storage.
9
+ //
10
+ // Law L5: base AND delta blocks are each κ-verified (sha256(gz)==κ) before use; both κ are named in the
11
+ // L5-verified delta manifest, so the trust chain holds without re-hashing the reconstruction.
12
+ import { parseDelta, applyByteDelta } from "./holo-delta.mjs";
13
+ import { reshapeTensor, buildEngineManifest } from "./holo-load2bit.mjs";
14
+
15
+ async function gunzip(u8) { const ds = new DecompressionStream("gzip"); const w = ds.writable.getWriter(); w.write(u8); w.close(); return new Uint8Array(await new Response(ds.readable).arrayBuffer()); }
16
+ const hex = (buf) => [...new Uint8Array(buf)].map((b) => b.toString(16).padStart(2, "0")).join("");
17
+ const kFile = (k) => String(k).replace(":", "_"); // κ "sha256:<hex>" → block file "sha256_<hex>"
18
+
19
+ export async function loadDeltaObject(deltaUrl, opts = {}) {
20
+ const droot = String(deltaUrl).replace(/\/+$/, "");
21
+ const man = opts.manifest || await (await fetch(droot + "/manifest.json", { cache: "no-store" })).json();
22
+ const baseRoot = String(opts.baseUrl || man.base?.url || man.base?.dir || "").replace(/\/+$/, "");
23
+ if (!baseRoot) throw new Error("delta-load: no base location (opts.baseUrl or manifest.base.url)");
24
+
25
+ // κ-verified block fetch against a chosen root (base for frozen/base blocks, delta for delta blocks).
26
+ const getBlock = async (root, kappa) => {
27
+ const gz = new Uint8Array(await (await fetch(root + "/b/" + kFile(kappa) + ".gz", { cache: "no-store" })).arrayBuffer());
28
+ const got = "sha256:" + hex(await crypto.subtle.digest("SHA-256", gz));
29
+ if (got !== kappa) throw new Error("delta-load κ MISMATCH " + String(kappa).slice(0, 24));
30
+ return gunzip(gz);
31
+ };
32
+
33
+ const normRecs = {}; // {name → {N,K,fmt,fp16?,s?}} for the shared manifest builder
34
+ for (const [name, dr] of Object.entries(man.tensors)) normRecs[name] = dr.meta || {};
35
+
36
+ const fetchTensor = async (name) => {
37
+ const dr = man.tensors[name]; if (!dr) return new Uint8Array(0);
38
+ let raw;
39
+ if (dr.kind === "ref") raw = await getBlock(baseRoot, dr.kappa); // frozen
40
+ else if (dr.kind === "whole") raw = parseDelta(await getBlock(droot, dr.delta)).bytes; // stored whole
41
+ else if (dr.kind === "bytedelta") raw = applyByteDelta(await getBlock(baseRoot, dr.base), parseDelta(await getBlock(droot, dr.delta)));
42
+ else throw new Error("delta-load: unknown record kind " + dr.kind + " for " + name);
43
+ return reshapeTensor(dr.meta || {}, raw);
44
+ };
45
+
46
+ let e8lutData; // E₈ LUT (if any) is a frozen base block
47
+ if (man.e8lut) { const b = await getBlock(baseRoot, man.e8lut.replace(/^did:holo:/, "")); e8lutData = new Float32Array(b.buffer, b.byteOffset, 2048); }
48
+ const manifest = buildEngineManifest(man, normRecs, e8lutData);
49
+ // tokenizer is the base's (finetune shares vocab); resolve a relative source against the BASE dir.
50
+ if (man.source && !/^https?:\/\//.test(man.source)) man.source = baseRoot + "/" + man.source;
51
+ return { manifest, fetchTensor, info: man };
52
+ }
53
+
54
+ export default loadDeltaObject;
holo-load2bit.mjs ADDED
@@ -0,0 +1,126 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // holo-load2bit.mjs — the LOAD-DIRECT consumer (the "load" half of the 7B infra). Given a pre-compiled
2
+ // 2-bit κ-object (manifest.json + content-addressed b/<κ>.gz blocks, produced by compile2bit.mjs), it builds
3
+ // the engine manifest + a fetchTensor that streams blocks, verifies each by re-deriving its κ (Law L5),
4
+ // gunzips, and hands the engine the weights ALREADY 2-bit — no re-quant at load. The engine reads
5
+ // manifest.preQuantized=true (parts() returns the blocks verbatim) and incoherent=false (LDLQ ⇒ no FWHT).
6
+ // Hosting = serve the κ-object dir from anywhere; the κ-verify makes any mirror untrusted-safe.
7
+ //
8
+ // A family FINETUNE is stored as `base-κ + delta` (format "holo-delta/1"); loadKappaObject detects that and
9
+ // transparently delegates to holo-load-delta.mjs, which reconstructs the finetune's blocks and returns the
10
+ // SAME { manifest, fetchTensor } shape — so the engine, KV-cache, and Q's brain loader need no changes.
11
+ import { f16ToF32 } from "./qvac-ingest.mjs";
12
+
13
+ async function gunzip(u8) { const ds = new DecompressionStream("gzip"); const w = ds.writable.getWriter(); w.write(u8); w.close(); return new Uint8Array(await new Response(ds.readable).arrayBuffer()); }
14
+ const hex = (buf) => [...new Uint8Array(buf)].map((b) => b.toString(16).padStart(2, "0")).join("");
15
+
16
+ // ── PERSISTENT κ-CACHE (the returning-user path): a κ-object's manifest + blocks are content-addressed and
17
+ // IMMUTABLE, so once fetched they can live in the browser's Cache API FOREVER, keyed by their own URL. A
18
+ // returning user then loads Q from local disk — ~0 network, no server (serverless by construction). It is
19
+ // untrusted-safe: every cached block is still L5 re-derived below, so a poisoned cache entry is rejected
20
+ // exactly like a poisoned network body. First visit warms the cache; every visit after is disk-speed. ──
21
+ const KCACHE = "holo-kappa-v1";
22
+ let _persistAsked = false;
23
+ async function _askPersist() {
24
+ if (_persistAsked) return; _persistAsked = true; // once per session: ask the browser NOT to evict the model under storage pressure
25
+ try { if (navigator.storage && navigator.storage.persist && !(await navigator.storage.persisted())) await navigator.storage.persist(); } catch (e) {}
26
+ }
27
+ // fetch a URL as bytes, disk-cached by URL. cache HIT → no network. MISS → fetch once (no-store bounds the
28
+ // in-flight RAM to one body) then store to disk. All failures fall back to a plain fetch (never block a load).
29
+ const _inflight = new Map(); // URL → in-flight fetch promise, so a parallel prefetch + the engine's read of the
30
+ // SAME block share ONE network fetch (never double-download).
31
+ async function cachedBytes(url) {
32
+ let cache = null;
33
+ try { cache = await caches.open(KCACHE); const hit = await cache.match(url); if (hit) return new Uint8Array(await hit.arrayBuffer()); } catch (e) { cache = null; }
34
+ if (_inflight.has(url)) return _inflight.get(url);
35
+ const p = (async () => {
36
+ const buf = await (await fetch(url, { cache: "no-store" })).arrayBuffer();
37
+ if (cache) { try { await cache.put(url, new Response(buf.slice(0), { headers: { "Content-Type": "application/octet-stream" } })); _askPersist(); } catch (e) {} }
38
+ return new Uint8Array(buf);
39
+ })();
40
+ _inflight.set(url, p);
41
+ try { return await p; } finally { _inflight.delete(url); }
42
+ }
43
+ // FAST FIRST LOAD: warm the whole block cache with bounded concurrency, so the engine's sequential per-tensor
44
+ // reads hit the cache instead of paying one HF round-trip at a time (~1.5 blocks/s → tens of blocks/s over HTTP/2).
45
+ // Fire-and-forget; the engine's getBlock shares any in-flight fetch (no double-download). Cross-origin-CDN safe.
46
+ function prefetchBlocks(baseUrl, kappas, conc = 12) {
47
+ try {
48
+ const urls = [...new Set(kappas.filter(Boolean))].map((k) => baseUrl + "/b/" + String(k).replace(":", "_") + ".gz");
49
+ let i = 0;
50
+ const worker = async () => { while (i < urls.length) { const u = urls[i++]; try { await cachedBytes(u); } catch (e) {} } };
51
+ Promise.all(Array.from({ length: Math.min(conc, urls.length) }, worker)).catch(() => {});
52
+ } catch (e) {}
53
+ }
54
+
55
+ // reshape a raw (gunzipped) block to what the engine reads. PURE — shared with the delta loader so both
56
+ // paths apply identical per-fmt handling. 2bit+fp16 → 2bit+f32 scales; everything else verbatim.
57
+ export function reshapeTensor(rec, raw) {
58
+ if (rec.fmt === "2bit" && rec.fp16) { // [2-bit packed][fp16 scales] → [2-bit][f32 scales]
59
+ const Kp = rec.K, q2 = (rec.N * Kp) / 4, nsc = rec.N * (Kp / 32);
60
+ const f16 = new Uint16Array(raw.buffer, raw.byteOffset + q2, nsc);
61
+ const out = new Uint8Array(q2 + nsc * 4); out.set(raw.subarray(0, q2), 0);
62
+ const f32 = new Float32Array(out.buffer, q2, nsc); for (let i = 0; i < nsc; i++) f32[i] = f16ToF32(f16[i]);
63
+ return out;
64
+ }
65
+ return raw; // 2bit+f32 (incoherence), q8 (embed), f32 (norms) — verbatim
66
+ }
67
+
68
+ // build the engine manifest from model meta + normalized tensor records {name→{N,K,fmt,s?}}. PURE — shared.
69
+ export function buildEngineManifest(man, normRecs, e8lutData) {
70
+ const tensors = Object.entries(normRecs).map(([name, rec]) => ({ name, N: rec.N, K: rec.K, blk: rec.fmt !== "f32", fmt: rec.fmt, ...(rec.s !== undefined ? { s: rec.s } : {}) }));
71
+ const native = man.mode === "q4" || man.mode === "q3" || man.mode === "e8" || man.mode === "bitnet"; // native-bits κ-object
72
+ return {
73
+ d: man.d, n_heads: man.n_heads, n_kv_heads: man.n_kv_heads, ff: man.ff, vocab: man.vocab, n_layers: man.n_layers, hd: man.hd,
74
+ bits: native ? man.bits : 8, layout: man.layout, rope_base: man.rope_base, ...(man.maskId !== undefined ? { maskId: man.maskId, diffusion: true } : {}), attn_bias: man.attn_bias, qk_norm: man.qk_norm, qk_norm_dim: man.qk_norm_dim, tied: man.tied,
75
+ ...(man.sub_norm ? { sub_norm: true } : {}), ...(man.bitlinear ? { bitlinear: true } : {}), ...(man.ffn_act ? { ffn_act: man.ffn_act } : {}), ...(man.moe ? { moe: man.moe } : {}),
76
+ ...(native ? {} : { twoBit: true, incoherent: man.incoherent === true, preQuantized: true }), tensors, ...(e8lutData ? { e8lutData } : {}),
77
+ };
78
+ }
79
+
80
+ export async function loadKappaObject(baseUrl, opts = {}) {
81
+ // Law L5: the manifest is the ROOT that names every block's κ. Verify the manifest's OWN bytes
82
+ // re-derive to a pinned κ BEFORE trusting man.tensors[*].kappa — otherwise a tampered manifest can
83
+ // re-point every block to a forged-but-self-consistent κ and each per-block check passes against the
84
+ // forgery. The pin is an EXTERNAL anchor (catalog/lock), never the manifest's own self-asserted root.
85
+ const manRaw = await cachedBytes(baseUrl + "/manifest.json"); // disk-cached (pinned + verified below), so a returning user's load is 0-network end to end
86
+ const manKappa = "sha256:" + hex(await crypto.subtle.digest("SHA-256", manRaw));
87
+ const pin = opts.expectKappa ? String(opts.expectKappa).replace(/^did:holo:/, "") : null;
88
+ if (pin) { if (manKappa !== pin) throw new Error("manifest κ MISMATCH (Law L5): " + manKappa.slice(0, 24) + "… ≠ pinned " + pin.slice(0, 24) + "…"); }
89
+ else if (!opts.allowUnpinned) throw new Error("manifest unpinned (Law L5): pass opts.expectKappa (catalog pin) or opts.allowUnpinned for dev");
90
+ const man = JSON.parse(new TextDecoder().decode(manRaw));
91
+ // FAMILY FINETUNE: a `base-κ + delta` object — reconstruct via the delta loader (same return shape).
92
+ if (man.format === "holo-delta/1") return (await import("./holo-load-delta.mjs")).loadDeltaObject(baseUrl, { ...opts, manifest: man });
93
+ // FAST FIRST LOAD: prefetch every block into the cache in parallel while the engine builds (turns a
94
+ // latency-bound sequential stream into a bandwidth-bound one — critical when serving off a remote CDN like HF).
95
+ if (opts.prefetch !== false) { try { prefetchBlocks(baseUrl, Object.values(man.tensors || {}).map((r) => r.kappa)); } catch (e) {} }
96
+ // RAM-bounded, DISK-cached: the engine fetches each tensor once, so decoded blocks are handed over and
97
+ // released — in-flight RAM stays ~one block (a 7B κ-object decompresses to >2.6 GB). The gzipped block
98
+ // bytes are persisted to the Cache API by their content-addressed URL (cachedBytes), so a returning user
99
+ // reads them from disk with no network. Every block — cached or fresh — is L5 re-derived (κ must match),
100
+ // so the cache is untrusted-safe.
101
+ const getBlock = async (kappa) => {
102
+ const gz = await cachedBytes(baseUrl + "/b/" + kappa.replace(":", "_") + ".gz");
103
+ const got = "sha256:" + hex(await crypto.subtle.digest("SHA-256", gz)); // Law L5: re-derive the κ
104
+ if (got !== kappa) throw new Error("κ MISMATCH " + kappa.slice(0, 24));
105
+ return await gunzip(gz);
106
+ };
107
+ const fetchTensor = async (name) => {
108
+ const rec = man.tensors[name]; if (!rec) return new Uint8Array(0);
109
+ return reshapeTensor(rec, await getBlock(rec.kappa));
110
+ };
111
+ // E₈ codebook (mode e8): the 256×8 LUT is its own content-addressed block — fetch + κ-verify (Law L5)
112
+ let e8lutData;
113
+ if (man.e8lut) { const b = await getBlock(man.e8lut.replace(/^did:holo:/, "")); e8lutData = new Float32Array(b.buffer, b.byteOffset, 2048); }
114
+ const manifest = buildEngineManifest(man, man.tensors, e8lutData);
115
+ // bundled tokenizer (SERVERLESS load): the header (tokenizer + arch) should load same-origin/on-device, no
116
+ // external host. A RELATIVE `source` already resolves against the κ-object's own dir. When the manifest
117
+ // declares a REMOTE (http) source but the κ-object ALSO ships a local tokenizer.gguf, PREFER the bundle —
118
+ // one cheap HEAD probe, and the manifest bytes (hence its Law-L5 pin) stay untouched. No bundle served →
119
+ // fall back to the declared source. This makes a κ-object with a bundled header 100% serverless to load.
120
+ const base = baseUrl.replace(/\/+$/, "");
121
+ if (man.source && !/^https?:\/\//.test(man.source)) man.source = base + "/" + man.source;
122
+ else if (man.source && /^https?:\/\//.test(man.source)) {
123
+ try { const h = await fetch(base + "/tokenizer.gguf", { method: "HEAD" }); if (h && h.ok) man.source = base + "/tokenizer.gguf"; } catch (e) {}
124
+ }
125
+ return { manifest, fetchTensor, info: man };
126
+ }
holo-model-frame.mjs ADDED
@@ -0,0 +1,106 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // holo-model-frame.mjs — A1 of the personal-model-zoo plan: the CANONICAL SHARED FRAME standard + a
2
+ // conformance gate, so every κ-addressable .holo LLM DECLARES the quantization frame it lives in.
3
+ //
4
+ // Why this exists: two models can family-dedup / index-delta (A2) ONLY if their unchanged tensors
5
+ // produce BYTE-IDENTICAL κ-blocks — which requires the SAME quantization transform ("frame"). The frame
6
+ // is the transform identity (codec, layout, bits, mode, twoBit, incoherence), NOT the model's weights or
7
+ // dims. The incoherence rotation is already deterministic (signed-FWHT seeded by tensor width K, see
8
+ // e8-quant.mjs signsFor), so a frame is reproducible from its descriptor alone.
9
+ //
10
+ // HONEST SCOPE: this does NOT requantize anything. Today's models are standard quant frames
11
+ // (holo-quant/<layout>-<bits>); the shared E8 frame (atlas-e8/v1) is what models compiled through the
12
+ // incoherent-E8 path carry. Moving everything into atlas-e8/v1 is Track B (gated on the 2-bit quality
13
+ // experiment), deliberately not done here. A1 just makes the frame EXPLICIT and ENFORCED.
14
+ //
15
+ // Pure JS, isomorphic (browser + Node 18+), zero deps. Node self-test scans ./models/<name>/manifest.json.
16
+
17
+ const FRAME_V = 1;
18
+
19
+ // the fields that DEFINE a quantization transform — two models with the same fingerprint apply the
20
+ // identical transform, so identical weights ⇒ identical κ-blocks (the precondition for A2 dedup).
21
+ const FRAME_KEYS = ["codec", "layout", "bits", "mode", "twoBit", "incoherent", "grid"];
22
+
23
+ async function sha256hex(str) {
24
+ const u8 = new TextEncoder().encode(str);
25
+ const d = await (globalThis.crypto || (await import("node:crypto")).webcrypto).subtle.digest("SHA-256", u8);
26
+ return Array.from(new Uint8Array(d), (b) => b.toString(16).padStart(2, "0")).join("");
27
+ }
28
+
29
+ // extract the frame-defining descriptor from a compiled-model manifest (holo-2bit/1 shape).
30
+ export function frameDescriptor(m) {
31
+ const d = {
32
+ codec: m.format || "unknown", // e.g. "holo-2bit/1"
33
+ layout: m.layout || null, // e.g. "q3f"
34
+ bits: m.bits ?? null, // e.g. 3
35
+ mode: m.mode || null, // e.g. "bitnet" | "q3"
36
+ twoBit: !!m.twoBit,
37
+ incoherent: !!m.incoherent, // QuIP#-style signed-FWHT rotation applied
38
+ grid: m.grid || (m.incoherent ? "e8" : "scalar") // e8 lattice vs scalar grid
39
+ };
40
+ return d;
41
+ }
42
+
43
+ // the canonical id for a frame — atlas-e8/v1 is THE shared frame family members ride; everything else
44
+ // declares its own standalone quant frame (still conformant, just not E8-shareable).
45
+ export function frameId(desc) {
46
+ if (desc.incoherent && desc.grid === "e8") return "atlas-e8/v1";
47
+ return `holo-quant/${desc.layout || desc.mode || "q"}-${desc.bits ?? "x"}bit`;
48
+ }
49
+
50
+ export async function frameFingerprint(desc) {
51
+ const canon = JSON.stringify(FRAME_KEYS.map((k) => [k, desc[k]])); // fixed key order ⇒ stable hash
52
+ return (await sha256hex(canon)).slice(0, 32);
53
+ }
54
+
55
+ // STAMP: add an explicit, self-verifying frame block to a manifest (idempotent).
56
+ export async function stampFrame(m) {
57
+ const desc = frameDescriptor(m);
58
+ m.frame = { v: FRAME_V, id: frameId(desc), fingerprint: await frameFingerprint(desc), ...desc };
59
+ return m;
60
+ }
61
+
62
+ // CONFORMANCE GATE: a .holo LLM must DECLARE a frame whose fingerprint re-derives from its own fields.
63
+ // Rejects: no frame block, tampered/mismatched fingerprint, or unknown frame version.
64
+ export async function checkFrame(m) {
65
+ if (!m || !m.frame) return { conforms: false, reason: "no frame declared (run stampFrame at compile)" };
66
+ if (m.frame.v !== FRAME_V) return { conforms: false, reason: `unknown frame version ${m.frame.v}` };
67
+ const want = await frameFingerprint(frameDescriptor(m));
68
+ if (m.frame.fingerprint !== want) return { conforms: false, reason: `fingerprint mismatch (manifest changed since stamp): ${m.frame.fingerprint} != ${want}` };
69
+ return { conforms: true, id: m.frame.id, fingerprint: m.frame.fingerprint, shared: m.frame.id === "atlas-e8/v1" };
70
+ }
71
+
72
+ // can two stamped models family-dedup / index-delta? Same transform (fingerprint) AND same architecture.
73
+ export function shareable(a, b) {
74
+ if (!a.frame || !b.frame) return false;
75
+ if (a.frame.fingerprint !== b.frame.fingerprint) return false;
76
+ for (const k of ["d", "n_layers", "ff", "n_heads", "n_kv_heads", "hd", "vocab"]) if (a[k] !== b[k]) return false;
77
+ return true;
78
+ }
79
+
80
+ // ── Node self-test: scan ./models/*/manifest.json, stamp + gate them, group by family-dedup class,
81
+ // and prove the gate rejects a planted non-conformer. ──
82
+ if (typeof process !== "undefined" && process.argv[1] && process.argv[1].endsWith("holo-model-frame.mjs")) {
83
+ const fs = await import("node:fs"); const path = await import("node:path");
84
+ const root = path.join(process.cwd(), "models");
85
+ const dirs = fs.existsSync(root) ? fs.readdirSync(root).filter((d) => fs.existsSync(path.join(root, d, "manifest.json"))) : [];
86
+ const rows = [], byClass = {};
87
+ for (const name of dirs) {
88
+ let m; try { m = JSON.parse(fs.readFileSync(path.join(root, name, "manifest.json"), "utf8")); } catch { continue; }
89
+ await stampFrame(m);
90
+ const chk = await checkFrame(m);
91
+ const key = `${m.frame.id} · d${m.d}·L${m.n_layers}·ff${m.ff}`;
92
+ (byClass[key] ||= []).push(name);
93
+ rows.push({ model: name, id: m.frame.id, fp: m.frame.fingerprint.slice(0, 12), conforms: chk.conforms, shared: !!chk.shared });
94
+ }
95
+ console.log("\n— frame stamp + conformance over ./models —");
96
+ for (const r of rows) console.log(` ${r.conforms ? "✓" : "✗"} ${r.model.padEnd(16)} ${r.id.padEnd(22)} fp=${r.fp} ${r.shared ? "[E8-shared]" : "[standalone]"}`);
97
+ console.log("\n— family-dedup classes (same frame + same arch ⇒ A2 can dedup/delta) —");
98
+ for (const [k, v] of Object.entries(byClass)) console.log(` ${v.length}× ${k}\n ${v.join(", ")}`);
99
+ // gate must REJECT a non-conformer
100
+ const planted = { format: "holo-2bit/1", layout: "q3f", bits: 3 }; // no frame block
101
+ const bad = await checkFrame(planted);
102
+ const tampered = await stampFrame({ format: "holo-2bit/1", layout: "q3f", bits: 3, incoherent: false });
103
+ tampered.bits = 2; // change a frame-defining field AFTER stamping → stored fingerprint must no longer match
104
+ const badly = await checkFrame(tampered);
105
+ console.log(`\n— gate rejection proof —\n no-frame: conforms=${bad.conforms} (${bad.reason})\n tampered: conforms=${badly.conforms} (${badly.reason})`);
106
+ }
index.html CHANGED
@@ -1,19 +1,134 @@
1
- <!doctype html>
2
- <html>
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width" />
6
- <title>My static Space</title>
7
- <link rel="stylesheet" href="style.css" />
8
- </head>
9
- <body>
10
- <div class="card">
11
- <h1>Welcome to your static Space!</h1>
12
- <p>You can modify this app directly by editing <i>index.html</i> in the Files and versions tab.</p>
13
- <p>
14
- Also don't forget to check the
15
- <a href="https://huggingface.co/docs/hub/spaces" target="_blank">Spaces documentation</a>.
16
- </p>
17
- </div>
18
- </body>
19
- </html>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!doctype html><html><head><meta charset=utf8><meta name=viewport content="width=device-width,initial-scale=1,viewport-fit=cover">
2
+ <title>Q — private AI, in your browser</title>
3
+ <style>
4
+ :root{--bg:#0b0e14;--panel:#141922;--ink:#e6e9ef;--dim:#8a94a6;--q:#7c5cff;--u:#1f6feb;--line:#1e2531}
5
+ *{box-sizing:border-box}html,body{height:100%}
6
+ body{margin:0;font:15px/1.55 -apple-system,Segoe UI,Roboto,system-ui,monospace;background:var(--bg);color:var(--ink);display:flex;flex-direction:column;overscroll-behavior:none}
7
+ header{padding:10px 16px;border-bottom:1px solid var(--line);display:flex;align-items:center;gap:10px;flex:0 0 auto}
8
+ header b{font-weight:600}header .s{color:var(--dim);font-size:12px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
9
+ #log{flex:1;overflow:auto;padding:16px;display:flex;flex-direction:column;gap:12px;-webkit-overflow-scrolling:touch}
10
+ .msg{max-width:82%;padding:9px 13px;border-radius:12px;white-space:pre-wrap;word-wrap:break-word}
11
+ .u{align-self:flex-end;background:var(--u)}.a{align-self:flex-start;background:var(--panel);border:1px solid #232b3a}
12
+ .a.think{color:var(--dim);font-style:italic}
13
+ footer{padding:12px 16px;border-top:1px solid var(--line);display:flex;gap:8px;flex:0 0 auto;padding-bottom:calc(12px + env(safe-area-inset-bottom))}
14
+ #in{flex:1;background:var(--panel);border:1px solid #232b3a;color:var(--ink);border-radius:10px;padding:10px 12px;font:inherit;resize:none;max-height:140px}
15
+ button{background:var(--q);color:#fff;border:0;border-radius:10px;padding:0 18px;font:inherit;cursor:pointer}button:disabled{opacity:.4;cursor:default}
16
+ .stat{color:var(--dim);font-size:11px;margin-top:3px}
17
+ </style></head><body>
18
+ <header><b>Q</b> <span class=s id=st>booting…</span></header>
19
+ <div id=log></div>
20
+ <footer><textarea id=in rows=1 placeholder="Message Q…" disabled></textarea><button id=send disabled>Send</button></footer>
21
+ <script type=module>
22
+ import { ready, loadModel, MODELS, defaultModelIndex } from "./core/loader.js";
23
+ import { createEngine } from "./core/engine.js";
24
+ import { selfPersona, selfFacts } from "./core/q-self.mjs"; // ONE grounded self-knowledge, shared with the messenger + voice
25
+ const $ = (s) => document.querySelector(s);
26
+ const log = $("#log"), input = $("#in"), send = $("#send"), st = $("#st");
27
+ const bubble = (cls, text = "") => { const d = document.createElement("div"); d.className = "msg " + cls; d.textContent = text; log.appendChild(d); log.scrollTop = log.scrollHeight; return d; };
28
+ const params = new URLSearchParams(location.search);
29
+ const pick = params.get("m");
30
+ let m = pick ? (MODELS.find((x) => new RegExp(pick, "i").test(x.name)) || MODELS[0]) : MODELS[defaultModelIndex()];
31
+ // STREAM FROM HF: a bare link streams the HOLOGRAMTECH BitNet κ-object from Hugging Face; ?hf=<org/repo> or
32
+ // ?kappa=<absolute-url> override. The κ-object is content-addressed + pinned, so the host is an UNTRUSTED CDN —
33
+ // every block is re-derived (Law L5); a bad byte is rejected. Blocks cache locally after first load (0-net on return).
34
+ {
35
+ let hf = params.get("hf"), kappa = params.get("kappa");
36
+ if (!hf && !kappa && !pick) hf = "HOLOGRAMTECH/q-bitnet-2b"; // bare link → stream BitNet from Hologram's HF repo
37
+ if (hf || kappa) {
38
+ const base = (kappa || `https://huggingface.co/${hf}/resolve/main`).replace(/\/+$/, "");
39
+ const bit = MODELS.find((x) => (x.fam || "").toLowerCase() === "bitnet") || m;
40
+ m = { ...bit, kappaUrl: base, name: bit.name + " · via " + (hf || new URL(base).host) };
41
+ }
42
+ }
43
+
44
+ // GROUND the model as on-device Q (a base/instruct model has NO self-knowledge — without this it confabulates
45
+ // a generic "I run on OpenAI/AWS cloud servers" identity, which is false). Injected as the SYSTEM turn.
46
+ function frameSystem() {
47
+ const PERSONA = selfPersona({ model: m, engine });
48
+ if (m.llama3) return `<|start_header_id|>system<|end_header_id|>\n\n${PERSONA}<|eot_id|>`;
49
+ if (m.qwen) return `<|im_start|>system\n${PERSONA}<|im_end|>\n`;
50
+ if (m.olmo) return `<|system|>\n${PERSONA}\n`;
51
+ return PERSONA + "\n\n";
52
+ }
53
+
54
+ // GROUNDED IDENTITY (the anti-confabulation guard). A 2B model reverts to its training prior — "I'm GPT-3.5 on
55
+ // AWS" — when asked what/where it is, no matter the system prompt. But identity is not a guess: it is the TRUTH
56
+ // of THIS running instance. So provenance questions are answered DETERMINISTICALLY from the live facts (the real
57
+ // resident model + its κ + the real host it streamed from), never from the model. Grounded, not performed.
58
+ const IDENTITY_RX = /\b(are|r)\s*(you|u)\b.*\b(gpt|chatgpt|openai|claude|anthropic|gemini|bard|llama|language model|an? ai|running|local|on[- ]?device|in the browser|on (a )?server|in the cloud|hosted)\b|\bwhat( kind of| sort of| type of)?\b.*\b(model|llm|ai|are you|based on|powered by|architecture|run on|running)\b|\bwho\b.*\b(are you|made|built|created|trained|develop)\b|\bwhere\b.*\b(run|running|host|hosted|are you|live|located)\b|\bpowered by\b|\bwhat are you\b|\b(openai|chatgpt|gpt-?\d|aws|amazon web|google cloud|cloud server)\b|\b(local|cloud|server)\b.*\bmodel\b|\bdo you run\b/i;
59
+ function groundedIdentity() {
60
+ const f = selfFacts({ model: m, engine });
61
+ const name = f.model || (m && m.name) || "an on-device model";
62
+ const host = (f.weightsFrom && !/^local$/i.test(f.weightsFrom)) ? f.weightsFrom : "Hugging Face";
63
+ const q = f.quant ? ` (${f.quant})` : "";
64
+ return `I'm Q. I run the ${name}${q} entirely in your browser on WebGPU — not GPT, not OpenAI, and not on any server or cloud. `
65
+ + `My weights streamed from ${host} and are content-addressed: every block is re-derived byte-for-byte as it loads, so nothing can be tampered with and no host has to be trusted. `
66
+ + `Once I'm loaded, nothing you type ever leaves your device.`;
67
+ }
68
+
69
+ let engine = null, convIds = [], busy = false, armed = false, pending = null;
70
+ input.disabled = send.disabled = false; input.placeholder = "Message Q… (model loading — will send the moment it's ready)"; input.focus();
71
+
72
+ async function generate(text, skipUser) {
73
+ busy = true; input.disabled = send.disabled = true;
74
+ if (!skipUser) bubble("u", text);
75
+ // Answer identity/provenance questions from the grounded truth, not the model's confabulation.
76
+ if (IDENTITY_RX.test(text)) {
77
+ const a = bubble("a", groundedIdentity());
78
+ const stat = document.createElement("div"); stat.className = "stat"; stat.textContent = "grounded · from this instance"; a.after(stat);
79
+ busy = false; input.disabled = send.disabled = false; input.focus(); return;
80
+ }
81
+ const a = bubble("a think", "…"); let first = true;
82
+ const stat = document.createElement("div"); stat.className = "stat";
83
+ try {
84
+ let framed = engine.frameTurn(text, convIds.length > 0);
85
+ if (convIds.length === 0) framed = frameSystem() + framed;
86
+ let turnIds = engine.tokenize(framed);
87
+ if (m.bos && engine.bosId != null && convIds.length === 0) turnIds = [engine.bosId, ...turnIds];
88
+ const res = await engine.generate(convIds.concat(turnIds), { maxNew: m.cap || 256, onToken: ({ text: t, stats }) => {
89
+ if (first && t) { a.classList.remove("think"); a.textContent = ""; first = false; }
90
+ a.textContent = t; log.scrollTop = log.scrollHeight;
91
+ if (stats) stat.textContent = `${stats.tokps ? stats.tokps.toFixed(0) + " tok/s" : ""}${stats.ttft ? " · TTFT " + Math.round(stats.ttft) + "ms" : ""}`;
92
+ } });
93
+ if (first) { a.classList.remove("think"); a.textContent = res.text || "(no output)"; }
94
+ convIds = res.ids; a.after(stat);
95
+ } catch (e) { a.classList.remove("think"); a.textContent = "⚠ " + e.message; }
96
+ busy = false; input.disabled = send.disabled = false; input.focus();
97
+ }
98
+ async function proactiveGreeting() {
99
+ busy = true; input.disabled = send.disabled = true;
100
+ const a = bubble("a think", "…"); let first = true;
101
+ const stat = document.createElement("div"); stat.className = "stat";
102
+ const FALLBACK = "Hey — I'm Q, running entirely in your browser, no server. My weights streamed from Hugging Face and are verified by re-derivation. What can I help you with?";
103
+ try {
104
+ const P = "This is the very first thing you say to the person who just opened you. You are Q — a private AI running entirely in their browser with no server, your weights streamed from Hugging Face and verified by re-derivation. Greet them warmly in one or two sentences and invite them to ask you anything.";
105
+ let ids = engine.tokenize(engine.frameTurn(P, false));
106
+ if (m.bos && engine.bosId != null) ids = [engine.bosId, ...ids];
107
+ await engine.generate(ids, { maxNew: 64, onToken: ({ text: t, stats }) => { if (first && t) { a.classList.remove("think"); a.textContent = ""; first = false; } a.textContent = t; log.scrollTop = log.scrollHeight; if (stats && stats.tokps) stat.textContent = `${stats.tokps.toFixed(0)} tok/s`; } });
108
+ if (first || a.textContent.trim().length < 4) { a.classList.remove("think"); a.textContent = FALLBACK; } else a.after(stat);
109
+ } catch (e) { a.classList.remove("think"); a.textContent = FALLBACK; }
110
+ busy = false; input.disabled = send.disabled = false; input.focus();
111
+ }
112
+ function onSend() {
113
+ const text = input.value.trim(); if (!text || busy) return;
114
+ input.value = ""; input.style.height = "auto";
115
+ if (!armed) { pending = text; bubble("u", text); const w = bubble("a think", "…starting the model, one moment…"); w.dataset.pending = "1"; return; }
116
+ generate(text);
117
+ }
118
+ send.onclick = onSend;
119
+ input.onkeydown = (e) => { if (e.key === "Enter" && !e.shiftKey) { e.preventDefault(); onSend(); } };
120
+ input.oninput = () => { input.style.height = "auto"; input.style.height = Math.min(140, input.scrollHeight) + "px"; };
121
+
122
+ try {
123
+ if (!navigator.gpu) throw new Error("This browser has no WebGPU — open in Chrome, Edge, or a recent mobile browser.");
124
+ st.textContent = `loading ${m.name} (${m.size})…`;
125
+ const loaded = await loadModel(m, { onStatus: (s) => { if (s) st.textContent = `${m.name}: ${s}`; }, onProgress: (d, t, w) => { st.textContent = `${m.name}: ${w} ${t ? Math.round(100 * d / t) : 0}%`; } });
126
+ if (!loaded || !loaded.gpu) throw new Error("model load failed");
127
+ engine = await createEngine(m, loaded);
128
+ armed = true;
129
+ st.textContent = `${m.name} · ${m.size} · in your browser · ready`;
130
+ input.placeholder = "Message Q…";
131
+ if (pending) { const w = [...log.querySelectorAll(".a")].reverse().find((x) => x.dataset.pending); if (w) w.remove(); const p = pending; pending = null; generate(p, true); }
132
+ else await proactiveGreeting();
133
+ } catch (e) { st.textContent = "⚠ " + e.message; bubble("a", "Could not start: " + e.message); }
134
+ </script></body></html>
pkg/holospaces_web.js ADDED
The diff for this file is too large to render. See raw diff
 
pkg/holospaces_web_bg.wasm ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:9d90b4ea8bccee8c239073c7ec7dd5158c6d11f1f668487570d17475d79e661e
3
+ size 6469596
q-chat.html ADDED
@@ -0,0 +1,120 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!doctype html><html><head><meta charset=utf8><meta name=viewport content="width=device-width,initial-scale=1">
2
+ <title>Q · local chat</title>
3
+ <style>
4
+ :root{--bg:#0b0e14;--panel:#141922;--ink:#e6e9ef;--dim:#8a94a6;--q:#7c5cff;--u:#1f6feb;--line:#1e2531}
5
+ *{box-sizing:border-box}html,body{height:100%}
6
+ body{margin:0;font:15px/1.55 -apple-system,Segoe UI,Roboto,monospace;background:var(--bg);color:var(--ink);display:flex;flex-direction:column}
7
+ header{padding:10px 16px;border-bottom:1px solid var(--line);display:flex;align-items:center;gap:10px;flex:0 0 auto}
8
+ header b{font-weight:600}header .s{color:var(--dim);font-size:12px}
9
+ #log{flex:1;overflow:auto;padding:16px;display:flex;flex-direction:column;gap:12px}
10
+ .msg{max-width:82%;padding:9px 13px;border-radius:12px;white-space:pre-wrap;word-wrap:break-word}
11
+ .u{align-self:flex-end;background:var(--u)}.a{align-self:flex-start;background:var(--panel);border:1px solid #232b3a}
12
+ .a.think{color:var(--dim);font-style:italic}
13
+ footer{padding:12px 16px;border-top:1px solid var(--line);display:flex;gap:8px;flex:0 0 auto}
14
+ #in{flex:1;background:var(--panel);border:1px solid #232b3a;color:var(--ink);border-radius:10px;padding:10px 12px;font:inherit;resize:none;max-height:140px}
15
+ button{background:var(--q);color:#fff;border:0;border-radius:10px;padding:0 18px;font:inherit;cursor:pointer}button:disabled{opacity:.4;cursor:default}
16
+ .stat{color:var(--dim);font-size:11px;margin-top:3px}
17
+ </style></head><body>
18
+ <header><b>Q</b> <span class=s id=st>booting…</span></header>
19
+ <div id=log></div>
20
+ <footer><textarea id=in rows=1 placeholder="Message Q…" disabled></textarea><button id=send disabled>Send</button></footer>
21
+ <script type=module>
22
+ import { ready, loadModel, MODELS, defaultModelIndex } from "./core/loader.js";
23
+ import { createEngine } from "./core/engine.js";
24
+ import { selfPersona } from "./core/q-self.mjs"; // ONE grounded self-knowledge, shared with the messenger + voice
25
+ const $ = (s) => document.querySelector(s);
26
+ const log = $("#log"), input = $("#in"), send = $("#send"), st = $("#st");
27
+ const bubble = (cls, text = "") => { const d = document.createElement("div"); d.className = "msg " + cls; d.textContent = text; log.appendChild(d); log.scrollTop = log.scrollHeight; return d; };
28
+ const params = new URLSearchParams(location.search);
29
+ const pick = params.get("m");
30
+ let m = pick ? (MODELS.find((x) => new RegExp(pick, "i").test(x.name)) || MODELS[0]) : MODELS[defaultModelIndex()];
31
+ // STREAM FROM ANYWHERE: ?hf=<org/repo> (a HuggingFace κ-object) or ?kappa=<absolute-url> loads the weights from
32
+ // that host's CDN instead of the local mount. The κ-object is content-addressed + pinned, so the host is an
33
+ // UNTRUSTED CDN — every block is re-derived (Law L5); a bad byte is rejected. Blocks cache locally after first
34
+ // load (0-network on return). Uses BitNet's engine flags (the HOLOGRAMTECH model is BitNet-2B).
35
+ {
36
+ const hf = params.get("hf"), kappa = params.get("kappa");
37
+ if (hf || kappa) {
38
+ const base = (kappa || `https://huggingface.co/${hf}/resolve/main`).replace(/\/+$/, "");
39
+ const bit = MODELS.find((x) => (x.fam || "").toLowerCase() === "bitnet") || m;
40
+ m = { ...bit, kappaUrl: base, name: bit.name + " · via " + (hf || new URL(base).host) };
41
+ }
42
+ }
43
+
44
+ // GROUND the model as on-device Q (a base/instruct model has NO self-knowledge — without this it confabulates
45
+ // a generic "I run on OpenAI/AWS cloud servers" identity, which is false). Injected as the SYSTEM turn.
46
+ // the system block for the model's own chat template (matches core/engine frameTurn's per-family markers).
47
+ // PERSONA is Q's LIVE grounded self-knowledge (q-self), computed at call time so it names the real resident
48
+ // model + κ — without it a base model confabulates a cloud/OpenAI/AWS identity.
49
+ function frameSystem() {
50
+ const PERSONA = selfPersona({ model: m, engine });
51
+ if (m.llama3) return `<|start_header_id|>system<|end_header_id|>\n\n${PERSONA}<|eot_id|>`;
52
+ if (m.qwen) return `<|im_start|>system\n${PERSONA}<|im_end|>\n`;
53
+ if (m.olmo) return `<|system|>\n${PERSONA}\n`;
54
+ return PERSONA + "\n\n"; // word/plain frame: prepend as leading context
55
+ }
56
+
57
+ let engine = null, convIds = [], busy = false, armed = false, pending = null;
58
+ // type immediately — don't wait for the model. The first message is queued and auto-sent the instant Q is ready.
59
+ input.disabled = send.disabled = false; input.placeholder = "Message Q… (model loading — will send the moment it's ready)"; input.focus();
60
+
61
+ async function generate(text, skipUser) {
62
+ busy = true; input.disabled = send.disabled = true;
63
+ if (!skipUser) bubble("u", text);
64
+ const a = bubble("a think", "…"); let first = true;
65
+ const stat = document.createElement("div"); stat.className = "stat";
66
+ const t0 = performance.now();
67
+ try {
68
+ let framed = engine.frameTurn(text, convIds.length > 0);
69
+ if (convIds.length === 0) framed = frameSystem() + framed; // first turn → lead with the on-device Q persona
70
+ let turnIds = engine.tokenize(framed);
71
+ if (m.bos && engine.bosId != null && convIds.length === 0) turnIds = [engine.bosId, ...turnIds];
72
+ const res = await engine.generate(convIds.concat(turnIds), { maxNew: m.cap || 256, onToken: ({ text: t, stats }) => {
73
+ if (first && t) { a.classList.remove("think"); a.textContent = ""; first = false; }
74
+ a.textContent = t; log.scrollTop = log.scrollHeight;
75
+ if (stats) stat.textContent = `${stats.tokps ? stats.tokps.toFixed(0) + " tok/s" : ""}${stats.ttft ? " · TTFT " + Math.round(stats.ttft) + "ms" : ""}`;
76
+ } });
77
+ if (first) { a.classList.remove("think"); a.textContent = res.text || "(no output)"; }
78
+ convIds = res.ids; a.after(stat);
79
+ } catch (e) { a.classList.remove("think"); a.textContent = "⚠ " + e.message; }
80
+ busy = false; input.disabled = send.disabled = false; input.focus();
81
+ }
82
+ // Q reaches out first — a real, model-GENERATED opening (not a canned line), so it feels alive.
83
+ // This also warms the GPU pipelines. It is standalone (not added to convIds) so the real chat starts fresh.
84
+ async function proactiveGreeting() {
85
+ busy = true; input.disabled = send.disabled = true;
86
+ const a = bubble("a think", "…"); let first = true;
87
+ const stat = document.createElement("div"); stat.className = "stat";
88
+ const FALLBACK = "Hey — I'm Q, running entirely on your device, no server, my weights verified by re-derivation. What can I help you with?";
89
+ try {
90
+ const P = "This is the very first thing you say to the person who just opened you. You are Q — a private AI running entirely on their device with no server, your weights verified by re-derivation. Greet them warmly in one or two sentences and invite them to ask you anything.";
91
+ let ids = engine.tokenize(engine.frameTurn(P, false));
92
+ if (m.bos && engine.bosId != null) ids = [engine.bosId, ...ids];
93
+ await engine.generate(ids, { maxNew: 64, onToken: ({ text: t, stats }) => { if (first && t) { a.classList.remove("think"); a.textContent = ""; first = false; } a.textContent = t; log.scrollTop = log.scrollHeight; if (stats && stats.tokps) stat.textContent = `${stats.tokps.toFixed(0)} tok/s`; } });
94
+ if (first || a.textContent.trim().length < 4) { a.classList.remove("think"); a.textContent = FALLBACK; } else a.after(stat);
95
+ } catch (e) { a.classList.remove("think"); a.textContent = FALLBACK; }
96
+ busy = false; input.disabled = send.disabled = false; input.focus();
97
+ }
98
+ function onSend() {
99
+ const text = input.value.trim(); if (!text || busy) return;
100
+ input.value = ""; input.style.height = "auto";
101
+ if (!armed) { pending = text; bubble("u", text); const w = bubble("a think", "…starting the model, one moment…"); w.dataset.pending = "1"; return; }
102
+ generate(text);
103
+ }
104
+ send.onclick = onSend;
105
+ input.onkeydown = (e) => { if (e.key === "Enter" && !e.shiftKey) { e.preventDefault(); onSend(); } };
106
+ input.oninput = () => { input.style.height = "auto"; input.style.height = Math.min(140, input.scrollHeight) + "px"; };
107
+
108
+ try {
109
+ if (!navigator.gpu) throw new Error("This browser has no WebGPU. Use Chrome/Edge.");
110
+ st.textContent = `loading ${m.name} (${m.size})…`;
111
+ const loaded = await loadModel(m, { onStatus: (s) => { if (s) st.textContent = `${m.name}: ${s}`; }, onProgress: (d, t, w) => { st.textContent = `${m.name}: ${w} ${t ? Math.round(100 * d / t) : 0}%`; } });
112
+ if (!loaded || !loaded.gpu) throw new Error("model load failed");
113
+ engine = await createEngine(m, loaded);
114
+ armed = true;
115
+ st.textContent = `${m.name} · ${m.size} · resident on your GPU · ready`;
116
+ input.placeholder = "Message Q…";
117
+ if (pending) { const w = [...log.querySelectorAll(".a")].reverse().find((x) => x.dataset.pending); if (w) w.remove(); const p = pending; pending = null; generate(p, true); } // you jumped in first → answer that
118
+ else await proactiveGreeting(); // otherwise Q reaches out the moment it's ready (also warms the GPU)
119
+ } catch (e) { st.textContent = "⚠ " + e.message; bubble("a", "Could not start: " + e.message); }
120
+ </script></body></html>
qvac-2bit.mjs ADDED
@@ -0,0 +1,268 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // qvac-2bit.mjs — NATIVE 2-bit WebGPU matmul: read 2-bit weights DIRECTLY on the GPU (no decode to Q8),
2
+ // undo incoherence with a runtime Hadamard, accumulate in f32. This is the on-GPU realization of the E₈/
3
+ // QuIP# 2-bit win: the per-token weight sweep drops ~4× vs Q8, so a 7B model (≈1.75 GB at 2-bit) fits
4
+ // resident in consumer VRAM and runs at VRAM bandwidth instead of paging from disk.
5
+ //
6
+ // Math (one-sided incoherence, the efficient form): let R = FWHT∘diag(sign) — orthogonal AND self-inverse
7
+ // (FWHT normalized 1/√K, sign ∈ ±1). Store Ŵ′ = quantize₂(R·Wₙ) per row (R isotropizes the row so a 2-bit
8
+ // grid quantizes it well). At inference rotate the INPUT once, x′ = R·x, then yₙ = Σ Ŵ′[n,k]·x′[k] ≈
9
+ // (R·Wₙ)·(R·x) = Wₙ·x — the rotation cancels for free, no output Hadamard. Cost: one length-K Hadamard
10
+ // per matmul (O(K log K), negligible beside the O(N·K) matmul). Pure WebGPU; the codebook is a uniform
11
+ // 4-level grid {−3,−1,1,3}·scale with a per-32 block scale (same scale layout the engine's Q8 path uses).
12
+
13
+ const FWHT_WG = 256; // single-workgroup input rotation (K ≤ 2048)
14
+ const MM_WG = 64; // one workgroup per output row, 64-thread reduce
15
+
16
+ // ── CPU: deterministic ±1 signs (re-derivable from K, matches e8-quant's xorshift) ──
17
+ export function signsFor(K) {
18
+ const s = new Float32Array(K); let x = (0x9e3779b9 ^ K) >>> 0;
19
+ for (let i = 0; i < K; i++) { x ^= x << 13; x ^= x >>> 17; x ^= x << 5; x >>>= 0; s[i] = (x & 1) ? 1 : -1; }
20
+ return s;
21
+ }
22
+ function fwht(a) { // in place, normalized (self-inverse)
23
+ const n = a.length;
24
+ for (let len = 1; len < n; len <<= 1) for (let i = 0; i < n; i += len << 1) for (let j = i; j < i + len; j++) { const u = a[j], v = a[j + len]; a[j] = u + v; a[j + len] = u - v; }
25
+ const s = 1 / Math.sqrt(n); for (let i = 0; i < n; i++) a[i] *= s;
26
+ }
27
+
28
+ // ── CPU: pack a weight matrix W [N,K] (row-major) to incoherent 2-bit + per-32 scales ──
29
+ // Returns { qw:Uint32Array(N*K/16), sc:Float32Array(N*K/32), sign:Float32Array(K) }.
30
+ // incoherent=false skips the rotation (naive 2-bit) — for the quality contrast only.
31
+ export function pack2bit(W, N, K, { incoherent = true } = {}) {
32
+ const nblk = K / 32, qw = new Uint32Array((N * K) / 16), sc = new Float32Array(N * nblk), sign = signsFor(K);
33
+ const row = new Float64Array(K);
34
+ for (let n = 0; n < N; n++) {
35
+ for (let k = 0; k < K; k++) row[k] = incoherent ? W[n * K + k] * sign[k] : W[n * K + k];
36
+ if (incoherent) fwht(row); // row ← R·Wₙ
37
+ // per-block scale: MSE-optimal step for a 4-level uniform grid {−3,−1,1,3}·s on ~Gaussian data is
38
+ // s ≈ 0.5·σ (grid spans ±1.5σ). Outliers beyond ±1.5σ clip — which is exactly what incoherence
39
+ // removes (the Hadamard Gaussianizes the row), so naive-2-bit clips heavy tails and incoherent does not.
40
+ for (let b = 0; b < nblk; b++) { let ss = 0; for (let i = 0; i < 32; i++) { const a = row[b * 32 + i]; ss += a * a; } sc[n * nblk + b] = (0.5 * Math.sqrt(ss / 32)) || 1e-12; }
41
+ for (let k = 0; k < K; k++) {
42
+ const t = row[k] / sc[n * nblk + (k >> 5)];
43
+ let q = Math.round((t + 3) / 2); if (q < 0) q = 0; else if (q > 3) q = 3; // grid {−3,−1,1,3}
44
+ const idx = n * K + k; qw[idx >> 4] |= q << ((idx & 15) * 2);
45
+ }
46
+ }
47
+ return { qw, sc, sign };
48
+ }
49
+ // reconstruct (CPU reference for the stored 2-bit weights, in the ORIGINAL basis): undo R on each row
50
+ export function unpack2bit(qw, sc, sign, N, K, { incoherent = true } = {}) {
51
+ const nblk = K / 32, W = new Float32Array(N * K), row = new Float64Array(K);
52
+ for (let n = 0; n < N; n++) {
53
+ for (let k = 0; k < K; k++) { const idx = n * K + k; const q = (qw[idx >> 4] >>> ((idx & 15) * 2)) & 3; row[k] = (q * 2 - 3) * sc[n * nblk + (k >> 5)]; }
54
+ if (incoherent) { fwht(row); for (let k = 0; k < K; k++) row[k] *= sign[k]; } // R is self-inverse
55
+ for (let k = 0; k < K; k++) W[n * K + k] = row[k];
56
+ }
57
+ return W;
58
+ }
59
+
60
+ // f32 → f16 bits (Uint16). Scales are small positives; round-toward-zero of the mantissa is fine.
61
+ export function f32ToF16(val) {
62
+ _f32[0] = val; const x = _u32[0];
63
+ const sign = (x >>> 16) & 0x8000; let exp = ((x >>> 23) & 0xff) - 112; const mant = x & 0x7fffff;
64
+ if (exp <= 0) { if (exp < -10) return sign; const m = (mant | 0x800000) >> (1 - exp); return sign | (m >> 13); }
65
+ if (exp >= 31) return sign | 0x7c00;
66
+ return sign | (exp << 10) | (mant >> 13);
67
+ }
68
+ const _f32 = new Float32Array(1), _u32 = new Uint32Array(_f32.buffer);
69
+
70
+ // CODEBOOK-AWARE LDLQ to the 2-bit SCALAR grid {−3,−1,1,3}·sc — the engine's native 2-bit codebook, with
71
+ // NO incoherence (so no power-of-2 padding, no runtime Hadamard). Rounds input columns high→low feeding
72
+ // each future column's error back through L (the LDL factor of the input Hessian); L=null ⇒ plain scalar
73
+ // 2-bit (the fallback when no calibration Hessian of the right dim exists, e.g. the FFN down-proj). Returns
74
+ // the packed 2-bit indices (16 weights/u32, no padding — K must be a multiple of 16). sc = per-32 scales.
75
+ // `band` caps the feedback to the nearest `band` future columns (0 = full). The LDL factor's off-diagonal
76
+ // mass concentrates near the diagonal, so a band recovers most of the gain at O(N·K·band) instead of
77
+ // O(N·K²) — the difference between a feasible and an infeasible 7B compile in single-thread JS.
78
+ export function ldlqRound2bit(W, N, K, L, sc, band = 0, chunk = 4096) {
79
+ const qw = new Uint32Array((N * K) / 16), nb = K / 32, CH = Math.min(chunk, N), E = new Float32Array(CH * K);
80
+ for (let r0 = 0; r0 < N; r0 += CH) { // rows are independent in LDLQ → chunk them (E is CH·K, not N·K)
81
+ const rN = Math.min(CH, N - r0); E.fill(0, 0, rN * K);
82
+ for (let k = K - 1; k >= 0; k--) for (let ii = 0; ii < rN; ii++) {
83
+ const i = r0 + ii;
84
+ let corr = W[i * K + k]; if (L) { const jm = band ? Math.min(K, k + 1 + band) : K; for (let j = k + 1; j < jm; j++) corr += E[ii * K + j] * L[j * K + k]; }
85
+ const s = sc[i * nb + (k >> 5)]; let q = Math.round(corr / s / 2 + 1.5); if (q < 0) q = 0; else if (q > 3) q = 3;
86
+ E[ii * K + k] = W[i * K + k] - (q * 2 - 3) * s;
87
+ const idx = i * K + k; qw[idx >> 4] |= q << ((idx & 15) * 2);
88
+ }
89
+ }
90
+ return qw;
91
+ }
92
+
93
+ export const nextPow2 = (n) => { let p = 1; while (p < n) p <<= 1; return p; };
94
+ // re-quantize an engine Q8 tensor (int8 quants + per-32 f32 scales) → incoherent 2-bit, padding the input
95
+ // dim K to Kp = next power of 2 (the FWHT needs a pow2 length; padded weights/inputs are zeros ⇒ exact).
96
+ // Returns { q: packed 2-bit bytes [N*Kp/4], s: f32 scales [N*Kp/32], Kp }. The runtime rotates the input by
97
+ // the SAME R_Kp (signsFor(Kp)+FWHT), so Ŵ′·x′ = (R·W)(R·x) = W·x.
98
+ export function requant2bit(q8, s, N, K) {
99
+ const Kp = nextPow2(K), nb = Kp / 32, sb = K / 32;
100
+ const q = new Int8Array(q8.buffer, q8.byteOffset, N * K);
101
+ const sign = signsFor(Kp), row = new Float64Array(Kp);
102
+ const qw = new Uint32Array((N * Kp) / 16), sc = new Float32Array(N * nb);
103
+ for (let n = 0; n < N; n++) {
104
+ for (let k = 0; k < Kp; k++) row[k] = (k < K ? q[n * K + k] * s[n * sb + (k >> 5)] : 0) * sign[k];
105
+ fwht(row);
106
+ for (let b = 0; b < nb; b++) { let ss = 0; for (let i = 0; i < 32; i++) { const a = row[b * 32 + i]; ss += a * a; } sc[n * nb + b] = (0.5 * Math.sqrt(ss / 32)) || 1e-12; } // MSE-optimal step for the {−3,−1,1,3} grid on Gaussianised (incoherent) weights
107
+ for (let k = 0; k < Kp; k++) { const t = row[k] / sc[n * nb + (k >> 5)]; let qq = Math.round((t + 3) / 2); if (qq < 0) qq = 0; else if (qq > 3) qq = 3; const idx = n * Kp + k; qw[idx >> 4] |= qq << ((idx & 15) * 2); }
108
+ }
109
+ return { q: new Uint8Array(qw.buffer), s: sc, Kp };
110
+ }
111
+
112
+ // ── WGSL: input rotation x′ = FWHT(sign ⊙ x), single workgroup, shared memory ──
113
+ const FWHT_WGSL = `
114
+ @group(0) @binding(0) var<storage,read> x: array<f32>;
115
+ @group(0) @binding(1) var<storage,read> sgn: array<f32>;
116
+ @group(0) @binding(2) var<storage,read_write> xr: array<f32>;
117
+ @group(0) @binding(3) var<uniform> P: vec4<u32>; // K, _, _, _
118
+ var<workgroup> sh: array<f32, 4096>; // 16 KB = the WebGPU min workgroup-storage limit; K ≤ 4096 (covers d up to 7B-class)
119
+ @compute @workgroup_size(${FWHT_WG})
120
+ fn main(@builtin(local_invocation_id) lid: vec3<u32>) {
121
+ let K = P.x; let t = lid.x;
122
+ for (var i = t; i < K; i += ${FWHT_WG}u) { sh[i] = x[i] * sgn[i]; }
123
+ workgroupBarrier();
124
+ var len = 1u;
125
+ loop {
126
+ if (len >= K) { break; }
127
+ let half = K >> 1u;
128
+ for (var i = t; i < half; i += ${FWHT_WG}u) {
129
+ let blk = i / len; let j = i % len;
130
+ let a = blk * (len << 1u) + j; let b = a + len;
131
+ let u = sh[a]; let v = sh[b]; sh[a] = u + v; sh[b] = u - v;
132
+ }
133
+ workgroupBarrier();
134
+ len = len << 1u;
135
+ }
136
+ let nrm = 1.0 / sqrt(f32(K));
137
+ for (var i = t; i < K; i += ${FWHT_WG}u) { xr[i] = sh[i] * nrm; }
138
+ }`;
139
+
140
+ // ── WGSL: 2-bit GEMV — WORD-ORIENTED: each thread loads one u32 (16 weights), unpacks in registers,
141
+ // hoists the per-32 block scale (16 weights at a 16-aligned base never cross a 32 boundary → one scale
142
+ // read per word). Threads stride by workgroup over words → coalesced loads. f32 accumulate. ──
143
+ const MM2_WGSL = `
144
+ @group(0) @binding(0) var<storage,read> qw: array<u32>;
145
+ @group(0) @binding(1) var<storage,read> sc: array<f32>;
146
+ @group(0) @binding(2) var<storage,read> x: array<f32>;
147
+ @group(0) @binding(3) var<storage,read_write> o: array<f32>;
148
+ @group(0) @binding(4) var<uniform> P: vec4<u32>; // N, K, nblk, _
149
+ var<workgroup> red: array<f32, ${MM_WG}>;
150
+ @compute @workgroup_size(${MM_WG})
151
+ fn main(@builtin(workgroup_id) wid: vec3<u32>, @builtin(local_invocation_id) lid: vec3<u32>) {
152
+ let n = wid.x; let K = P.y; let words = K >> 4u; let rowW = n * words; let rowS = n * P.z;
153
+ var acc = 0.0; var w = lid.x;
154
+ loop {
155
+ if (w >= words) { break; }
156
+ let packed = qw[rowW + w];
157
+ let kb = w << 4u; let s = sc[rowS + (kb >> 5u)];
158
+ for (var j = 0u; j < 16u; j = j + 1u) { acc = acc + x[kb + j] * f32(i32((packed >> (j * 2u)) & 3u) * 2 - 3) * s; }
159
+ w = w + ${MM_WG}u;
160
+ }
161
+ red[lid.x] = acc; workgroupBarrier();
162
+ var r = ${MM_WG >> 1}u;
163
+ loop { if (r == 0u) { break; } if (lid.x < r) { red[lid.x] = red[lid.x] + red[lid.x + r]; } workgroupBarrier(); r = r >> 1u; }
164
+ if (lid.x == 0u) { o[n] = red[0]; }
165
+ }`;
166
+
167
+ // ── WGSL: Q8 GEMV (the engine's current format) — WORD-ORIENTED too, so the comparison is purely the
168
+ // bytes-read difference, not kernel quality. Each thread loads one u32 (4 int8), unpacks 4. ──
169
+ const MM8_WGSL = `
170
+ @group(0) @binding(0) var<storage,read> qw: array<u32>;
171
+ @group(0) @binding(1) var<storage,read> sc: array<f32>;
172
+ @group(0) @binding(2) var<storage,read> x: array<f32>;
173
+ @group(0) @binding(3) var<storage,read_write> o: array<f32>;
174
+ @group(0) @binding(4) var<uniform> P: vec4<u32>;
175
+ var<workgroup> red: array<f32, ${MM_WG}>;
176
+ @compute @workgroup_size(${MM_WG})
177
+ fn main(@builtin(workgroup_id) wid: vec3<u32>, @builtin(local_invocation_id) lid: vec3<u32>) {
178
+ let n = wid.x; let K = P.y; let words = K >> 2u; let rowW = n * words; let rowS = n * P.z;
179
+ var acc = 0.0; var w = lid.x;
180
+ loop {
181
+ if (w >= words) { break; }
182
+ let packed = qw[rowW + w];
183
+ let kb = w << 2u; let s = sc[rowS + (kb >> 5u)];
184
+ for (var j = 0u; j < 4u; j = j + 1u) { let b = (packed >> (j * 8u)) & 0xffu; acc = acc + x[kb + j] * f32(i32(b << 24u) >> 24u) * s; }
185
+ w = w + ${MM_WG}u;
186
+ }
187
+ red[lid.x] = acc; workgroupBarrier();
188
+ var r = ${MM_WG >> 1}u;
189
+ loop { if (r == 0u) { break; } if (lid.x < r) { red[lid.x] = red[lid.x] + red[lid.x + r]; } workgroupBarrier(); r = r >> 1u; }
190
+ if (lid.x == 0u) { o[n] = red[0]; }
191
+ }`;
192
+
193
+ // ── GPU helpers ──
194
+ const U = (typeof GPUBufferUsage !== "undefined") ? GPUBufferUsage : {};
195
+ function pipe(dev, code) { const m = dev.createShaderModule({ code }); return dev.createComputePipeline({ layout: "auto", compute: { module: m, entryPoint: "main" } }); }
196
+ function sbuf(dev, src) { const b = dev.createBuffer({ size: Math.max(16, src.byteLength), usage: U.STORAGE | U.COPY_DST | U.COPY_SRC }); dev.queue.writeBuffer(b, 0, src); return b; }
197
+ function obuf(dev, bytes) { return dev.createBuffer({ size: Math.max(16, bytes), usage: U.STORAGE | U.COPY_SRC }); }
198
+ function ubuf(dev, arr) { const b = dev.createBuffer({ size: 16, usage: U.UNIFORM | U.COPY_DST }); dev.queue.writeBuffer(b, 0, arr); return b; }
199
+ async function readf32(dev, buf, n) { const st = dev.createBuffer({ size: n * 4, usage: U.MAP_READ | U.COPY_DST }); const e = dev.createCommandEncoder(); e.copyBufferToBuffer(buf, 0, st, 0, n * 4); dev.queue.submit([e.finish()]); await st.mapAsync(GPUMapMode.READ); const out = new Float32Array(st.getMappedRange().slice(0)); st.unmap(); st.destroy(); return out; }
200
+
201
+ // ── the bench: correctness (2-bit incoherent vs f32 ref vs naive-2-bit vs Q8) + perf + memory ──
202
+ export async function runBench(dev, { N = 2048, K = 2048, iters = 200 } = {}) {
203
+ const nblk = K / 32;
204
+ // random Gaussian weights + input (a realistic single layer matmul)
205
+ let s = 1234567; const rnd = () => (s = (s * 1664525 + 1013904223) >>> 0) / 4294967296;
206
+ const gauss = () => { const u = Math.max(1e-12, rnd()); return Math.sqrt(-2 * Math.log(u)) * Math.cos(2 * Math.PI * rnd()); };
207
+ // heavy-tailed weights like real LLM layers (kurtosis ≫ 3): a Gaussian bulk plus sparse large spikes —
208
+ // the outliers that wreck naive low-bit quantization and that incoherence (the Hadamard) spreads out.
209
+ const W = new Float32Array(N * K); for (let i = 0; i < N * K; i++) { let w = gauss() * 0.05; if (rnd() < 0.02) w *= 6; W[i] = w; }
210
+ const x = new Float32Array(K); for (let i = 0; i < K; i++) x[i] = gauss();
211
+ // f32 reference y = W·x
212
+ const yref = new Float32Array(N); for (let n = 0; n < N; n++) { let a = 0; for (let k = 0; k < K; k++) a += W[n * K + k] * x[k]; yref[n] = a; }
213
+ const relErr = (y) => { let e = 0, r = 0; for (let n = 0; n < N; n++) { const d = y[n] - yref[n]; e += d * d; r += yref[n] * yref[n]; } return Math.sqrt(e / r); };
214
+
215
+ // pack incoherent 2-bit + naive 2-bit + Q8 (per-32 scale, the engine format)
216
+ const inc = pack2bit(W, N, K, { incoherent: true });
217
+ const nai = pack2bit(W, N, K, { incoherent: false });
218
+ const q8 = new Int8Array(N * K), q8s = new Float32Array(N * nblk);
219
+ for (let n = 0; n < N; n++) for (let b = 0; b < nblk; b++) { let mx = 0; for (let i = 0; i < 32; i++) { const a = Math.abs(W[n * K + b * 32 + i]); if (a > mx) mx = a; } const sca = (mx / 127) || 1e-12; q8s[n * nblk + b] = sca; for (let i = 0; i < 32; i++) { let q = Math.round(W[n * K + b * 32 + i] / sca); if (q > 127) q = 127; else if (q < -127) q = -127; q8[n * K + b * 32 + i] = q; } }
220
+
221
+ // GPU pipelines
222
+ const pF = pipe(dev, FWHT_WGSL), p2 = pipe(dev, MM2_WGSL), p8 = pipe(dev, MM8_WGSL);
223
+ // buffers
224
+ const xB = sbuf(dev, x), xrB = obuf(dev, K * 4), sgnB = sbuf(dev, inc.sign), Pf = ubuf(dev, new Uint32Array([K, 0, 0, 0]));
225
+ const qwB = sbuf(dev, inc.qw), scB = sbuf(dev, inc.sc), oB = obuf(dev, N * 4), P2 = ubuf(dev, new Uint32Array([N, K, nblk, 0]));
226
+ const naiqwB = sbuf(dev, nai.qw), naiscB = sbuf(dev, nai.sc), oNB = obuf(dev, N * 4);
227
+ const q8B = sbuf(dev, new Uint8Array(q8.buffer)), q8sB = sbuf(dev, q8s), o8B = obuf(dev, N * 4);
228
+
229
+ const bgF = (xin) => dev.createBindGroup({ layout: pF.getBindGroupLayout(0), entries: [{ binding: 0, resource: { buffer: xin } }, { binding: 1, resource: { buffer: sgnB } }, { binding: 2, resource: { buffer: xrB } }, { binding: 3, resource: { buffer: Pf } }] });
230
+ const bg2 = (qw, sc, xin, o) => dev.createBindGroup({ layout: p2.getBindGroupLayout(0), entries: [{ binding: 0, resource: { buffer: qw } }, { binding: 1, resource: { buffer: sc } }, { binding: 2, resource: { buffer: xin } }, { binding: 3, resource: { buffer: o } }, { binding: 4, resource: { buffer: P2 } }] });
231
+ const bg8 = dev.createBindGroup({ layout: p8.getBindGroupLayout(0), entries: [{ binding: 0, resource: { buffer: q8B } }, { binding: 1, resource: { buffer: q8sB } }, { binding: 2, resource: { buffer: xB } }, { binding: 3, resource: { buffer: o8B } }, { binding: 4, resource: { buffer: P2 } }] });
232
+
233
+ // ── correctness ──
234
+ const doInc = K <= 4096; // single-workgroup FWHT covers K ≤ 4096 (16 KB shared)
235
+ let yInc = null, gpuCpu = null;
236
+ if (doInc) {
237
+ // incoherent path: GPU rotate x→x′, then 2-bit matmul with x′
238
+ { const e = dev.createCommandEncoder(); const p = e.beginComputePass(); p.setPipeline(pF); p.setBindGroup(0, bgF(xB)); p.dispatchWorkgroups(1); p.setPipeline(p2); p.setBindGroup(0, bg2(qwB, scB, xrB, oB)); p.dispatchWorkgroups(N); p.end(); dev.queue.submit([e.finish()]); }
239
+ yInc = await readf32(dev, oB, N);
240
+ // CPU re-derivation of the SAME stored 2-bit weights — independent check the GPU kernel agrees
241
+ const Wrec = unpack2bit(inc.qw, inc.sc, inc.sign, N, K, { incoherent: true });
242
+ const yCpu = new Float32Array(N); for (let n = 0; n < N; n++) { let a = 0; for (let k = 0; k < K; k++) a += Wrec[n * K + k] * x[k]; yCpu[n] = a; }
243
+ let e2 = 0, rr = 0; for (let n = 0; n < N; n++) { const d = yInc[n] - yCpu[n]; e2 += d * d; rr += yCpu[n] * yCpu[n]; } gpuCpu = Math.sqrt(e2 / rr);
244
+ }
245
+ // naive path (no rotation) + Q8 path
246
+ { const e = dev.createCommandEncoder(); const p = e.beginComputePass(); p.setPipeline(p2); p.setBindGroup(0, bg2(naiqwB, naiscB, xB, oNB)); p.dispatchWorkgroups(N); p.end(); dev.queue.submit([e.finish()]); }
247
+ const yNai = await readf32(dev, oNB, N);
248
+ { const e = dev.createCommandEncoder(); const p = e.beginComputePass(); p.setPipeline(p8); p.setBindGroup(0, bg8); p.dispatchWorkgroups(N); p.end(); dev.queue.submit([e.finish()]); }
249
+ const yQ8 = await readf32(dev, o8B, N);
250
+
251
+ // ── perf — apples-to-apples: time each MATMUL alone (incoherence rotation measured separately) ──
252
+ // pre-rotate x once so the 2-bit matmul reads x′ without re-running the FWHT in the timed loop.
253
+ if (doInc) { const e = dev.createCommandEncoder(); const p = e.beginComputePass(); p.setPipeline(pF); p.setBindGroup(0, bgF(xB)); p.dispatchWorkgroups(1); p.end(); dev.queue.submit([e.finish()]); }
254
+ const time = async (fn) => { fn(); await dev.queue.onSubmittedWorkDone(); const t0 = performance.now(); for (let i = 0; i < iters; i++) fn(); await dev.queue.onSubmittedWorkDone(); return (performance.now() - t0) / iters; };
255
+ const run2mm = () => { const e = dev.createCommandEncoder(); const p = e.beginComputePass(); p.setPipeline(p2); p.setBindGroup(0, bg2(qwB, scB, doInc ? xrB : xB, oB)); p.dispatchWorkgroups(N); p.end(); dev.queue.submit([e.finish()]); };
256
+ const run8mm = () => { const e = dev.createCommandEncoder(); const p = e.beginComputePass(); p.setPipeline(p8); p.setBindGroup(0, bg8); p.dispatchWorkgroups(N); p.end(); dev.queue.submit([e.finish()]); };
257
+ const runF = () => { const e = dev.createCommandEncoder(); const p = e.beginComputePass(); p.setPipeline(pF); p.setBindGroup(0, bgF(xB)); p.dispatchWorkgroups(1); p.end(); dev.queue.submit([e.finish()]); };
258
+ const ms2 = await time(run2mm), ms8 = await time(run8mm), msF = doInc ? await time(runF) : null;
259
+
260
+ const bytes2 = inc.qw.byteLength + inc.sc.byteLength, bytes8 = q8.byteLength + q8s.byteLength;
261
+ const gbps = (b, ms) => (b / 1e9) / (ms / 1e3);
262
+ return {
263
+ N, K, iters,
264
+ err: { incoherent2bit: doInc ? relErr(yInc) : null, naive2bit: relErr(yNai), q8: relErr(yQ8), gpu_vs_cpu_2bit: gpuCpu },
265
+ perf: { ms_2bit_mm: +ms2.toFixed(4), ms_q8_mm: +ms8.toFixed(4), ms_fwht: msF == null ? null : +msF.toFixed(4), matmul_speedup: +(ms8 / ms2).toFixed(2), gbps_2bit: +gbps(bytes2, ms2).toFixed(0), gbps_q8: +gbps(bytes8, ms8).toFixed(0) },
266
+ mem: { MB_2bit: +(bytes2 / 1e6).toFixed(2), MB_q8: +(bytes8 / 1e6).toFixed(2), ratio: +(bytes8 / bytes2).toFixed(2), bits_per_weight: +(bytes2 * 8 / (N * K)).toFixed(2) },
267
+ };
268
+ }
qvac-gpu.js ADDED
The diff for this file is too large to render. See raw diff
 
qvac-ingest.mjs ADDED
@@ -0,0 +1,561 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // QVAC disk-streaming GGUF ingestion — pure JS, no wasm in the weight path.
2
+ //
3
+ // The wasm path holds the whole GGUF in wasm32 linear memory (~4 GB ceiling), so
4
+ // it can't ingest a 7B/14B model. This module reads a large GGUF straight off disk
5
+ // (HTTP Range against the served file) and converts each tensor to the engine's
6
+ // block format ON DEMAND, so RAM never holds more than one tensor at a time.
7
+ //
8
+ // It is a faithful port of qvac-gguf::dequantize_raw + qvac-layer::quant_blocks +
9
+ // model_specs, so the bytes it produces are what the GPU engine already consumes.
10
+ // The tokenizer stays in wasm: we feed wasm only the GGUF HEADER (the first
11
+ // data_offset bytes), which is all qvac_load_gpu needs for the BPE + manifest.
12
+
13
+ import { makeIQ } from "./forge/gguf-forge-iq-dequant.mjs";
14
+
15
+ // ── f16 → f32 (matches qvac_gguf::f16_to_f32) ──
16
+ export function f16ToF32(h) {
17
+ const sign = (h >> 15) & 1, exp = (h >> 10) & 0x1f, mant = h & 0x3ff;
18
+ let val;
19
+ if (exp === 0) val = mant * Math.pow(2, -24);
20
+ else if (exp === 0x1f) return mant ? NaN : (sign ? -Infinity : Infinity);
21
+ else val = (1 + mant / 1024) * Math.pow(2, exp - 15);
22
+ return sign ? -val : val;
23
+ }
24
+
25
+ const QK = 32, QK_K = 256;
26
+ export const GGML = {
27
+ F32: 0, F16: 1, Q4_0: 2, Q4_1: 3, Q8_0: 8, Q2_K: 10, Q3_K: 11, Q4_K: 12, Q5_K: 13, Q6_K: 14,
28
+ IQ2_XXS: 16, IQ2_XS: 17, IQ3_XXS: 18, IQ1_S: 19, IQ4_NL: 20, IQ3_S: 21, IQ2_S: 22, IQ4_XS: 23, IQ1_M: 29,
29
+ TQ2_0: 35,
30
+ };
31
+ // IQ-quant byte layouts: [block elements, block bytes]. (ggml-common.h:485-563)
32
+ const IQ_BLOCK = {
33
+ 16: [QK_K, 66], 17: [QK_K, 74], 18: [QK_K, 98], 19: [QK_K, 50], 20: [32, 18],
34
+ 21: [QK_K, 110], 22: [QK_K, 82], 23: [QK_K, 136], 29: [QK_K, 56],
35
+ };
36
+ // BitNet ternary TQ2_0 (ggml-common.h:273): qs[64] + f16 d = 66 B / 256. Runtime
37
+ // dequant is float64 (the Tier-A oracle gguf-forge-dequant.mjs is the bit-exact ref).
38
+ const TQ_BLOCK = { 35: [QK_K, 66] };
39
+ function dequantTq2_0Rt(raw, elements) {
40
+ const out = new Float32Array(elements);
41
+ const nb = elements / QK_K;
42
+ const dv = new DataView(raw.buffer, raw.byteOffset, raw.byteLength);
43
+ let o = 0, base = 0;
44
+ for (let i = 0; i < nb; ++i) {
45
+ const d = f16ToF32(dv.getUint16(base + 64, true));
46
+ for (let j = 0; j < 64; j += 32)
47
+ for (let l = 0; l < 4; ++l)
48
+ for (let m = 0; m < 32; ++m) out[o++] = (((raw[base + j + m] >> (l * 2)) & 3) - 1) * d;
49
+ base += 66;
50
+ }
51
+ return out;
52
+ }
53
+ const TQ_RT = { 35: dequantTq2_0Rt };
54
+ // Runtime IQ dequant (float64; the Tier-A oracle is the bit-exact reference).
55
+ const _iq = makeIQ((x) => x, f16ToF32);
56
+ const IQ_RT = {
57
+ 16: _iq.dequantIQ2XXS, 17: _iq.dequantIQ2XS, 18: _iq.dequantIQ3XXS, 19: _iq.dequantIQ1S,
58
+ 20: _iq.dequantIQ4NL, 21: _iq.dequantIQ3S, 22: _iq.dequantIQ2S, 23: _iq.dequantIQ4XS, 29: _iq.dequantIQ1M,
59
+ };
60
+
61
+ // On-disk byte length of `elements` of a ggml type (matches type_byte_len).
62
+ export function typeByteLen(t, elements) {
63
+ switch (t) {
64
+ case GGML.F32: return elements * 4;
65
+ case GGML.F16: return elements * 2;
66
+ case GGML.Q8_0: return (elements / QK) * (2 + QK);
67
+ case GGML.Q4_0: return (elements / QK) * (2 + QK / 2);
68
+ case GGML.Q4_1: return (elements / QK) * (2 + 2 + QK / 2);
69
+ case GGML.Q2_K: return (elements / QK_K) * 84;
70
+ case GGML.Q3_K: return (elements / QK_K) * 110;
71
+ case GGML.Q4_K: return (elements / QK_K) * 144;
72
+ case GGML.Q5_K: return (elements / QK_K) * 176;
73
+ case GGML.Q6_K: return (elements / QK_K) * 210;
74
+ default:
75
+ if (t in IQ_BLOCK) { const [be, bb] = IQ_BLOCK[t]; return (elements / be) * bb; }
76
+ if (t in TQ_BLOCK) { const [be, bb] = TQ_BLOCK[t]; return (elements / be) * bb; }
77
+ throw new Error("unsupported ggml type " + t);
78
+ }
79
+ }
80
+ // (block elements, block bytes) for the range reader.
81
+ function blockShape(t) {
82
+ switch (t) {
83
+ case GGML.F32: return [1, 4];
84
+ case GGML.F16: return [1, 2];
85
+ case GGML.Q8_0: return [QK, 2 + QK];
86
+ case GGML.Q4_0: return [QK, 2 + QK / 2];
87
+ case GGML.Q4_1: return [QK, 2 + 2 + QK / 2];
88
+ case GGML.Q2_K: return [QK_K, 84];
89
+ case GGML.Q3_K: return [QK_K, 110];
90
+ case GGML.Q4_K: return [QK_K, 144];
91
+ case GGML.Q5_K: return [QK_K, 176];
92
+ case GGML.Q6_K: return [QK_K, 210];
93
+ default:
94
+ if (t in IQ_BLOCK) return IQ_BLOCK[t];
95
+ if (t in TQ_BLOCK) return TQ_BLOCK[t];
96
+ throw new Error("unsupported ggml type " + t);
97
+ }
98
+ }
99
+
100
+ // Dequantize raw tensor bytes → Float32Array (port of dequantize_raw).
101
+ export function dequantizeRaw(t, raw, elements) {
102
+ const out = new Float32Array(elements);
103
+ const dv = new DataView(raw.buffer, raw.byteOffset, raw.byteLength);
104
+ let o = 0;
105
+ if (t === GGML.F32) {
106
+ for (let i = 0; i < elements; i++) out[i] = dv.getFloat32(i * 4, true);
107
+ } else if (t === GGML.F16) {
108
+ for (let i = 0; i < elements; i++) out[i] = f16ToF32(dv.getUint16(i * 2, true));
109
+ } else if (t === GGML.Q8_0) {
110
+ const bb = 2 + QK;
111
+ for (let p = 0; p + bb <= raw.byteLength; p += bb) {
112
+ const d = f16ToF32(dv.getUint16(p, true));
113
+ for (let j = 0; j < QK; j++) out[o++] = d * (raw[p + 2 + j] << 24 >> 24);
114
+ }
115
+ } else if (t === GGML.Q4_0) {
116
+ const bb = 2 + QK / 2;
117
+ for (let p = 0; p + bb <= raw.byteLength; p += bb) {
118
+ const d = f16ToF32(dv.getUint16(p, true));
119
+ const qs = p + 2;
120
+ for (let j = 0; j < QK / 2; j++) out[o + j] = d * ((raw[qs + j] & 0x0f) - 8); // low nibble → j
121
+ for (let j = 0; j < QK / 2; j++) out[o + QK / 2 + j] = d * ((raw[qs + j] >> 4) - 8); // high → j+16
122
+ o += QK;
123
+ }
124
+ } else if (t === GGML.Q4_1) {
125
+ const bb = 2 + 2 + QK / 2;
126
+ for (let p = 0; p + bb <= raw.byteLength; p += bb) {
127
+ const d = f16ToF32(dv.getUint16(p, true)), m = f16ToF32(dv.getUint16(p + 2, true));
128
+ const qs = p + 4;
129
+ for (let j = 0; j < QK / 2; j++) out[o + j] = (raw[qs + j] & 0x0f) * d + m;
130
+ for (let j = 0; j < QK / 2; j++) out[o + QK / 2 + j] = (raw[qs + j] >> 4) * d + m;
131
+ o += QK;
132
+ }
133
+ } else if (t === GGML.Q4_K) {
134
+ // Q4_K: 256-element super-block of 144 B = d(f16) + dmin(f16) + 12 packed 6-bit scales/mins + 128 nibble qs.
135
+ const bb = 144;
136
+ for (let p = 0; p + bb <= raw.byteLength; p += bb) {
137
+ const d = f16ToF32(dv.getUint16(p, true)), dmin = f16ToF32(dv.getUint16(p + 2, true));
138
+ const sc = p + 4, qs = p + 16;
139
+ const sm = (j) => j < 4 ? [raw[sc + j] & 63, raw[sc + j + 4] & 63]
140
+ : [(raw[sc + j + 4] & 0xF) | ((raw[sc + j - 4] >> 6) << 4), (raw[sc + j + 4] >> 4) | ((raw[sc + j] >> 6) << 4)];
141
+ let q = 0, is = 0;
142
+ for (let j = 0; j < QK_K; j += 64) {
143
+ const [s1, m1] = sm(is), [s2, m2] = sm(is + 1);
144
+ for (let l = 0; l < 32; l++) out[o + l] = d * s1 * (raw[qs + q + l] & 0xF) - dmin * m1;
145
+ for (let l = 0; l < 32; l++) out[o + 32 + l] = d * s2 * (raw[qs + q + l] >> 4) - dmin * m2;
146
+ o += 64; q += 32; is += 2;
147
+ }
148
+ }
149
+ } else if (t === GGML.Q6_K) {
150
+ const bb = 210;
151
+ for (let p = 0; p + bb <= raw.byteLength; p += bb) {
152
+ const ql = p, qh = p + 128, sc = p + 192, d = f16ToF32(dv.getUint16(p + 208, true));
153
+ for (let n = 0; n < 2; n++) {
154
+ const qlo = ql + n * 64, qho = qh + n * 32, sco = sc + n * 8, yo = o + n * 128;
155
+ for (let l = 0; l < 32; l++) {
156
+ const is = (l / 16) | 0;
157
+ const q1 = ((raw[qlo + l] & 0x0f) | (((raw[qho + l] >> 0) & 3) << 4)) - 32;
158
+ const q2 = ((raw[qlo + l + 32] & 0x0f) | (((raw[qho + l] >> 2) & 3) << 4)) - 32;
159
+ const q3 = ((raw[qlo + l] >> 4) | (((raw[qho + l] >> 4) & 3) << 4)) - 32;
160
+ const q4 = ((raw[qlo + l + 32] >> 4) | (((raw[qho + l] >> 6) & 3) << 4)) - 32;
161
+ out[yo + l] = d * (raw[sco + is] << 24 >> 24) * q1;
162
+ out[yo + l + 32] = d * (raw[sco + is + 2] << 24 >> 24) * q2;
163
+ out[yo + l + 64] = d * (raw[sco + is + 4] << 24 >> 24) * q3;
164
+ out[yo + l + 96] = d * (raw[sco + is + 6] << 24 >> 24) * q4;
165
+ }
166
+ }
167
+ o += QK_K;
168
+ }
169
+ } else if (t === GGML.Q2_K) {
170
+ // 84 B: scales[16] qs[64] d(f16) dmin(f16). y = d*(sc&0xF)*q2 - dmin*(sc>>4).
171
+ const bb = 84;
172
+ for (let p = 0; p + bb <= raw.byteLength; p += bb) {
173
+ const sc = p, qs = p + 16, d = f16ToF32(dv.getUint16(p + 80, true)), dmin = f16ToF32(dv.getUint16(p + 82, true));
174
+ let is = 0;
175
+ for (let n = 0; n < QK_K; n += 128) {
176
+ const q = qs + (n >> 7) * 32;
177
+ for (let shift = 0; shift < 8; shift += 2) {
178
+ let s = raw[sc + is++];
179
+ for (let l = 0; l < 16; l++) out[o++] = d * (s & 0xf) * ((raw[q + l] >> shift) & 3) - dmin * (s >> 4);
180
+ s = raw[sc + is++];
181
+ for (let l = 0; l < 16; l++) out[o++] = d * (s & 0xf) * ((raw[q + l + 16] >> shift) & 3) - dmin * (s >> 4);
182
+ }
183
+ }
184
+ }
185
+ } else if (t === GGML.Q3_K) {
186
+ // 110 B: hmask[32] qs[64] scales[12] d(f16). 6-bit signed scales via kmask unpack.
187
+ const bb = 110, km1 = 0x03030303, km2 = 0x0f0f0f0f;
188
+ const aux = new Uint32Array(4), sb = new Int8Array(aux.buffer);
189
+ for (let p = 0; p + bb <= raw.byteLength; p += bb) {
190
+ const hm = p, qs = p + 32, sco = p + 96, d = f16ToF32(dv.getUint16(p + 108, true));
191
+ aux[0] = dv.getUint32(sco, true); aux[1] = dv.getUint32(sco + 4, true); aux[2] = dv.getUint32(sco + 8, true);
192
+ const tmp = aux[2];
193
+ aux[2] = ((aux[0] >>> 4) & km2) | (((tmp >>> 4) & km1) << 4);
194
+ aux[3] = ((aux[1] >>> 4) & km2) | (((tmp >>> 6) & km1) << 4);
195
+ aux[0] = (aux[0] & km2) | (((tmp >>> 0) & km1) << 4);
196
+ aux[1] = (aux[1] & km2) | (((tmp >>> 2) & km1) << 4);
197
+ let is = 0, m = 1;
198
+ for (let n = 0; n < QK_K; n += 128) {
199
+ const q = qs + (n >> 7) * 32;
200
+ for (let shift = 0; shift < 8; shift += 2) {
201
+ let dl = d * (sb[is++] - 32);
202
+ for (let l = 0; l < 16; l++) out[o++] = dl * (((raw[q + l] >> shift) & 3) - ((raw[hm + l] & m) ? 0 : 4));
203
+ dl = d * (sb[is++] - 32);
204
+ for (let l = 0; l < 16; l++) out[o++] = dl * (((raw[q + l + 16] >> shift) & 3) - ((raw[hm + l + 16] & m) ? 0 : 4));
205
+ m <<= 1;
206
+ }
207
+ }
208
+ }
209
+ } else if (t === GGML.Q5_K) {
210
+ // 176 B: d(f16) dmin(f16) scales[12] qh[32] ql[128]. Q4_K nibbles + 5th bit from qh.
211
+ const bb = 176;
212
+ const smk4 = (sc, j) => j < 4 ? [raw[sc + j] & 63, raw[sc + j + 4] & 63]
213
+ : [(raw[sc + j + 4] & 0xF) | ((raw[sc + j - 4] >> 6) << 4), (raw[sc + j + 4] >> 4) | ((raw[sc + j] >> 6) << 4)];
214
+ for (let p = 0; p + bb <= raw.byteLength; p += bb) {
215
+ const d = f16ToF32(dv.getUint16(p, true)), dmin = f16ToF32(dv.getUint16(p + 2, true));
216
+ const sc = p + 4, qh = p + 16; let ql = p + 48, is = 0, u1 = 1, u2 = 2;
217
+ for (let j = 0; j < QK_K; j += 64) {
218
+ const [s1, m1] = smk4(sc, is), [s2, m2] = smk4(sc, is + 1);
219
+ const d1 = d * s1, mm1 = dmin * m1, d2 = d * s2, mm2 = dmin * m2;
220
+ for (let l = 0; l < 32; l++) out[o++] = d1 * ((raw[ql + l] & 0xF) + ((raw[qh + l] & u1) ? 16 : 0)) - mm1;
221
+ for (let l = 0; l < 32; l++) out[o++] = d2 * ((raw[ql + l] >> 4) + ((raw[qh + l] & u2) ? 16 : 0)) - mm2;
222
+ ql += 32; is += 2; u1 <<= 2; u2 <<= 2;
223
+ }
224
+ }
225
+ } else if (t in IQ_RT) {
226
+ return IQ_RT[t](raw, elements); // IQ-quants (float64 runtime; oracle is the bit-exact ref)
227
+ } else if (t in TQ_RT) {
228
+ return TQ_RT[t](raw, elements); // BitNet TQ2_0 (float64 runtime; oracle is the bit-exact ref)
229
+ } else throw new Error("unsupported ggml type " + t);
230
+ return out;
231
+ }
232
+
233
+ // Re-quantize a [n,k] f32 tensor into the engine's per-32-block format
234
+ // (port of qvac-layer::quant_blocks). bits=4 → (nibble-8)*scale, scale=amax/7,
235
+ // sequential nibble packing; bits=8 → int8, scale=amax/127. Returns {q,s}.
236
+ // Arithmetic is done in f32 (Math.fround) with round-half-away-from-zero to match
237
+ // Rust's f32 `round()` byte-for-byte, so the frames are bit-identical to wasm's.
238
+ const fr = Math.fround;
239
+ const rnd = (x) => x >= 0 ? Math.floor(x + 0.5) : Math.ceil(x - 0.5); // half away from zero (Rust f32::round)
240
+ export function quantBlocks(f, n, k, bits) {
241
+ const nb = k / 32;
242
+ const s = new Float32Array(n * nb);
243
+ let si = 0;
244
+ if (bits === 4) {
245
+ const q = new Uint8Array(n * k / 2);
246
+ for (let row = 0; row < n; row++) {
247
+ const base = row * k;
248
+ for (let b = 0; b < nb; b++) {
249
+ const bo = base + b * 32;
250
+ let amax = 0; for (let j = 0; j < 32; j++) { const a = Math.abs(f[bo + j]); if (a > amax) amax = a; }
251
+ amax = Math.max(amax, 1e-9); const scale = fr(amax / 7); s[si++] = scale;
252
+ for (let j = 0; j < 32; j++) {
253
+ let qv = rnd(fr(f[bo + j] / scale)); qv = qv < -8 ? -8 : qv > 7 ? 7 : qv; qv = (qv + 8) & 0xf;
254
+ const g = bo + j;
255
+ if ((g & 1) === 0) q[g >> 1] |= qv; else q[g >> 1] |= qv << 4;
256
+ }
257
+ }
258
+ }
259
+ return { q, s };
260
+ } else {
261
+ const q = new Uint8Array(n * k);
262
+ for (let row = 0; row < n; row++) {
263
+ const base = row * k;
264
+ for (let b = 0; b < nb; b++) {
265
+ const bo = base + b * 32;
266
+ let amax = 0; for (let j = 0; j < 32; j++) { const a = Math.abs(f[bo + j]); if (a > amax) amax = a; }
267
+ amax = Math.max(amax, 1e-9); const scale = fr(amax / 127); s[si++] = scale;
268
+ for (let j = 0; j < 32; j++) { let qv = rnd(fr(f[bo + j] / scale)); qv = qv < -127 ? -127 : qv > 127 ? 127 : qv; q[bo + j] = qv & 0xff; }
269
+ }
270
+ }
271
+ return { q, s };
272
+ }
273
+ }
274
+
275
+ // FAST PATH: GGUF Q4_0 → engine Q4 with NO dequant/requant — a pure relayout.
276
+ // GGUF Q4_0 already stores (nibble-8)*d, exactly the engine's convention, so the
277
+ // nibble value maps straight across; we only reorder the interleaved (j, j+16)
278
+ // nibbles into the engine's sequential packing and widen the f16 scale to f32.
279
+ // This is both faster (integer-only) and bit-exact to the GGUF (no requant loss).
280
+ export function relayoutQ4(raw, n, k) {
281
+ const nb = k / 32;
282
+ const dv = new DataView(raw.buffer, raw.byteOffset, raw.byteLength);
283
+ const q = new Uint8Array(n * k / 2), s = new Float32Array(n * nb);
284
+ let bo = 0, si = 0;
285
+ for (let row = 0; row < n; row++) {
286
+ const rowBase = row * k;
287
+ for (let b = 0; b < nb; b++) {
288
+ s[si++] = f16ToF32(dv.getUint16(bo, true));
289
+ const qs = bo + 2, blkBase = rowBase + b * 32;
290
+ for (let w = 0; w < 32; w++) {
291
+ const nib = w < 16 ? (raw[qs + w] & 0x0f) : (raw[qs + (w - 16)] >> 4);
292
+ const g = blkBase + w;
293
+ if ((g & 1) === 0) q[g >> 1] |= nib; else q[g >> 1] |= nib << 4;
294
+ }
295
+ bo += 18;
296
+ }
297
+ }
298
+ return { q, s };
299
+ }
300
+ // FAST PATH: GGUF Q8_0 → engine Q8 — copy the i8 quants, widen f16 scale to f32.
301
+ export function relayoutQ8(raw, n, k) {
302
+ const nb = k / 32;
303
+ const dv = new DataView(raw.buffer, raw.byteOffset, raw.byteLength);
304
+ const q = new Uint8Array(n * k), s = new Float32Array(n * nb);
305
+ let bo = 0, si = 0, qi = 0;
306
+ for (let row = 0; row < n; row++) {
307
+ for (let b = 0; b < nb; b++) {
308
+ s[si++] = f16ToF32(dv.getUint16(bo, true));
309
+ for (let w = 0; w < 32; w++) q[qi++] = raw[bo + 2 + w];
310
+ bo += 34;
311
+ }
312
+ }
313
+ return { q, s };
314
+ }
315
+
316
+ // ── GGUF header parser (just enough: tensor directory + data offset) ──
317
+ class Cur {
318
+ constructor(buf) { this.b = buf; this.dv = new DataView(buf.buffer, buf.byteOffset, buf.byteLength); this.p = 0; this.v1 = false; }
319
+ need(n) { if (this.p + n > this.b.byteLength) throw new RangeError("short"); }
320
+ u8() { this.need(1); return this.b[this.p++]; }
321
+ u16() { this.need(2); const v = this.dv.getUint16(this.p, true); this.p += 2; return v; }
322
+ u32() { this.need(4); const v = this.dv.getUint32(this.p, true); this.p += 4; return v; }
323
+ u64() { this.need(8); const lo = this.dv.getUint32(this.p, true), hi = this.dv.getUint32(this.p + 4, true); this.p += 8; return hi * 4294967296 + lo; }
324
+ lenField() { return this.v1 ? this.u32() : this.u64(); }
325
+ skipStr() { const n = this.lenField(); this.need(n); this.p += n; }
326
+ skipValue(ty) {
327
+ switch (ty) {
328
+ case 0: case 1: case 7: this.p += 1; break;
329
+ case 2: case 3: this.p += 2; break;
330
+ case 4: case 5: case 6: this.p += 4; break;
331
+ case 10: case 11: case 12: this.p += 8; break;
332
+ case 8: this.skipStr(); break;
333
+ case 9: { const ety = this.u32(); const cnt = this.lenField(); for (let i = 0; i < cnt; i++) this.skipValue(ety); break; }
334
+ default: throw new Error("bad meta type " + ty);
335
+ }
336
+ }
337
+ // Read a SCALAR metadata value (numbers/bool/string); arrays are skipped (return undefined).
338
+ readScalar(ty) {
339
+ switch (ty) {
340
+ case 0: return this.u8();
341
+ case 1: { const v = this.u8(); return v << 24 >> 24; }
342
+ case 2: return this.u16();
343
+ case 3: { const v = this.u16(); return v << 16 >> 16; }
344
+ case 4: return this.u32();
345
+ case 5: { const v = this.u32(); return v | 0; }
346
+ case 6: { this.need(4); const v = this.dv.getFloat32(this.p, true); this.p += 4; return v; }
347
+ case 7: return this.u8() !== 0;
348
+ case 8: { const n = this.lenField(); const s = new TextDecoder().decode(this.b.subarray(this.p, this.p + n)); this.p += n; return s; }
349
+ case 10: return this.u64();
350
+ case 11: return this.u64();
351
+ case 12: { this.need(8); const v = this.dv.getFloat64(this.p, true); this.p += 8; return v; }
352
+ case 9: { const ety = this.u32(); const cnt = this.lenField(); for (let i = 0; i < cnt; i++) this.skipValue(ety); return undefined; }
353
+ default: throw new Error("bad meta type " + ty);
354
+ }
355
+ }
356
+ }
357
+
358
+ // Parse a GGUF header buffer → { version, dataOffset, tensors:[{name,dims,ggmlType,offset}] }.
359
+ // `buf` must contain at least up to the (aligned) end of the tensor-info table.
360
+ export function parseGgufHeader(buf) {
361
+ const c = new Cur(buf);
362
+ if (c.u32() !== 0x46554747) throw new Error("not GGUF");
363
+ const version = c.u32();
364
+ if (version !== 1 && version !== 2 && version !== 3) throw new Error("GGUF version " + version);
365
+ c.v1 = version === 1;
366
+ const tensorCount = c.lenField();
367
+ const metaCount = c.lenField();
368
+ let alignment = 32;
369
+ const meta = {};
370
+ for (let i = 0; i < metaCount; i++) {
371
+ const keyLen = c.lenField(); const key = new TextDecoder().decode(buf.subarray(c.p, c.p + keyLen)); c.p += keyLen;
372
+ const ty = c.u32();
373
+ const v = c.readScalar(ty); // arrays → undefined (skipped)
374
+ if (v !== undefined) meta[key] = v;
375
+ if (key === "general.alignment" && typeof v === "number") alignment = v;
376
+ }
377
+ const tensors = [];
378
+ for (let i = 0; i < tensorCount; i++) {
379
+ const nl = c.lenField(); const name = new TextDecoder().decode(buf.subarray(c.p, c.p + nl)); c.p += nl;
380
+ const nd = c.u32(); const dims = []; for (let j = 0; j < nd; j++) dims.push(c.lenField());
381
+ const ggmlType = c.u32(); const offset = c.u64();
382
+ tensors.push({ name, dims, ggmlType, offset });
383
+ }
384
+ alignment = Math.max(1, alignment);
385
+ const dataOffset = Math.ceil(c.p / alignment) * alignment;
386
+ return { version, dataOffset, tensors, meta };
387
+ }
388
+
389
+ // Build the engine manifest (dims + tensor list with N,K,blk) from a parsed GGUF
390
+ // header — a pure-JS port of qvac-layer::model_specs + gpu_export_manifest, so
391
+ // conversion/streaming needs no wasm. `tensors` is the header's tensor directory.
392
+ export function buildManifest(meta, tensors, bits) {
393
+ const arch = meta["general.architecture"] || "llama";
394
+ const mu = (k) => { const v = meta[`${arch}.${k}`]; return typeof v === "number" ? Math.round(v) : undefined; };
395
+ const tset = new Set(tensors.map((t) => t.name));
396
+ const tbyname = {}; for (const t of tensors) tbyname[t.name] = t;
397
+ const d = mu("embedding_length") || 0;
398
+ const n_layers = mu("block_count") || 0;
399
+ const n_heads = mu("attention.head_count") || 0;
400
+ const n_kv_heads = mu("attention.head_count_kv") || n_heads;
401
+ // For MoE, the experts use expert_feed_forward_length (e.g. Qwen3-30B-A3B: 768),
402
+ // which differs from the (unused) dense feed_forward_length (6144). OLMoE's two
403
+ // values happen to be equal. `ff` everywhere downstream means the EXPERT ff for MoE.
404
+ const ff = (mu("expert_feed_forward_length") || mu("feed_forward_length")) || 0;
405
+ const hd = mu("attention.key_length") || (n_heads ? Math.floor(d / n_heads) : 0);
406
+ const kv_dim = n_kv_heads * hd;
407
+ const rope_base = (typeof meta[`${arch}.rope.freq_base`] === "number") ? meta[`${arch}.rope.freq_base`] : 10000;
408
+ const attn_bias = tset.has("blk.0.attn_q.bias");
409
+ const qk_norm = tset.has("blk.0.attn_q_norm.weight");
410
+ const qk_norm_dim = qk_norm ? (tbyname["blk.0.attn_q_norm.weight"].dims[0] | 0) : 0; // hd (per-head, Qwen3) or d (full, OLMoE)
411
+ const n_experts = mu("expert_count") || 0; // >0 → MoE
412
+ const n_used = mu("expert_used_count") || 0;
413
+ const moe = n_experts > 0;
414
+ const bitnet = /^bitnet/.test(arch); // BitNet b1.58: sub-norms before wo/w_down + ReLU² gated FFN
415
+ const tied = !tset.has("output.weight");
416
+ const vocab = d > 0 && tbyname["token_embd.weight"] ? Math.floor(tbyname["token_embd.weight"].dims.reduce((a, b) => a * b, 1) / d) : 0;
417
+ const blk = (name, N, K) => ({ name, N, K, blk: true });
418
+ const nrm = (name, K) => ({ name, N: 1, K, blk: false });
419
+ const t = [];
420
+ t.push(blk("embed", vocab, d));
421
+ t.push(nrm("final_norm", d));
422
+ t.push(blk("lm_head", vocab, d));
423
+ for (let i = 0; i < n_layers; i++) {
424
+ t.push(nrm(`l${i}.attn_norm`, d));
425
+ t.push(blk(`l${i}.wq`, n_heads * hd, d));
426
+ t.push(blk(`l${i}.wk`, kv_dim, d));
427
+ t.push(blk(`l${i}.wv`, kv_dim, d));
428
+ if (attn_bias) { t.push(nrm(`l${i}.bq`, n_heads * hd)); t.push(nrm(`l${i}.bk`, kv_dim)); t.push(nrm(`l${i}.bv`, kv_dim)); }
429
+ if (qk_norm) { t.push(nrm(`l${i}.q_norm`, qk_norm_dim)); t.push(nrm(`l${i}.k_norm`, qk_norm_dim)); }
430
+ if (bitnet) t.push(nrm(`l${i}.attn_sub_norm`, n_heads * hd));
431
+ t.push(blk(`l${i}.wo`, d, n_heads * hd));
432
+ t.push(nrm(`l${i}.ffn_norm`, d));
433
+ if (bitnet) t.push(nrm(`l${i}.ffn_sub_norm`, ff));
434
+ if (moe) {
435
+ t.push(nrm(`l${i}.router`, n_experts * d)); // ffn_gate_inp [n_experts, d] f32 (CPU top-k)
436
+ // experts are NOT enumerated here (n_layers·n_experts·3 is huge); the engine
437
+ // generates `l{i}.e{e}.{gate,up,down}` names for the top-k it actually needs.
438
+ } else {
439
+ t.push(blk(`l${i}.w_gate`, ff, d));
440
+ t.push(blk(`l${i}.w_up`, ff, d));
441
+ t.push(blk(`l${i}.w_down`, d, ff));
442
+ }
443
+ }
444
+ const out = { d, n_heads, n_kv_heads, ff, vocab, n_layers, hd, bits, rope_base, attn_bias, qk_norm, qk_norm_dim, tied, tensors: t };
445
+ if (moe) out.moe = { n_experts, n_used };
446
+ if (bitnet) { out.sub_norm = true; out.ffn_act = "relu2"; }
447
+ return out;
448
+ }
449
+
450
+ // engine tensor name → GGUF tensor name (mirror of model_specs).
451
+ export function ggufNameFor(name, hasOutputWeight) {
452
+ if (name === "embed") return "token_embd.weight";
453
+ if (name === "final_norm") return "output_norm.weight";
454
+ if (name === "lm_head") return hasOutputWeight ? "output.weight" : "token_embd.weight";
455
+ const m = name.match(/^l(\d+)\.(.+)$/); if (!m) return null;
456
+ const i = m[1], r = m[2], p = `blk.${i}.`;
457
+ const map = {
458
+ "attn_norm": "attn_norm.weight", "ffn_norm": "ffn_norm.weight",
459
+ "attn_sub_norm": "attn_sub_norm.weight", "ffn_sub_norm": "ffn_sub_norm.weight",
460
+ "wq": "attn_q.weight", "wk": "attn_k.weight", "wv": "attn_v.weight", "wo": "attn_output.weight",
461
+ "bq": "attn_q.bias", "bk": "attn_k.bias", "bv": "attn_v.bias",
462
+ "q_norm": "attn_q_norm.weight", "k_norm": "attn_k_norm.weight",
463
+ "w_gate": "ffn_gate.weight", "w_up": "ffn_up.weight", "w_down": "ffn_down.weight",
464
+ };
465
+ return map[r] ? p + map[r] : null;
466
+ }
467
+
468
+ // Read the GGUF header from `url` (HTTP Range), growing the read until the tensor
469
+ // table fits. Returns { dataOffset, tensors, headerBytes } (headerBytes = the first
470
+ // dataOffset bytes, to hand to wasm qvac_load_gpu for the tokenizer + manifest).
471
+ export async function readHeader(url, readRange, initial = 48 * 1024 * 1024) {
472
+ let n = initial, parsed = null, buf = null;
473
+ for (let tries = 0; tries < 6; tries++) {
474
+ buf = await readRange(url, 0, n);
475
+ try { parsed = parseGgufHeader(buf); break; } catch (e) { if (e instanceof RangeError || /short/.test(String(e))) { n *= 2; continue; } throw e; }
476
+ }
477
+ if (!parsed) throw new Error("could not parse GGUF header");
478
+ const headerBytes = buf.length >= parsed.dataOffset ? buf.subarray(0, parsed.dataOffset) : await readRange(url, 0, parsed.dataOffset);
479
+ return { dataOffset: parsed.dataOffset, tensors: parsed.tensors, headerBytes };
480
+ }
481
+
482
+ // Build the per-tensor fetcher the GPU engine consumes. `manifest` is the wasm
483
+ // manifest (dims + tensors with N,K,blk). Returns fetchTensor(name) → Uint8Array,
484
+ // byte-identical to what qvac_gpu_tensor would return — but sourced from disk.
485
+ export function makeDiskFetcher({ url, readRange, dataOffset, tensors, manifest, bits }) {
486
+ const tdir = {}; for (const t of tensors) tdir[t.name] = t;
487
+ const hasOut = !!tdir["output.weight"];
488
+ const mByName = {}; for (const t of manifest.tensors) mByName[t.name] = t;
489
+ const ROW_CHUNK = 8192;
490
+ const ffM = manifest.ff, dM = manifest.d;
491
+
492
+ // Resolve an engine tensor name → { info, N, K, blk, eltOffset }. Handles MoE
493
+ // expert slices `l{i}.e{e}.{gate,up,down}` (the e-th slab of the 3-D ffn_*_exps
494
+ // tensor) and the router `l{i}.router` (ffn_gate_inp, f32) in addition to the
495
+ // plain tensors in the manifest.
496
+ const resolve = (name) => {
497
+ let m = name.match(/^l(\d+)\.e(\d+)\.(gate|up|down)$/);
498
+ if (m) {
499
+ const i = m[1], e = +m[2], role = m[3];
500
+ const info = tdir[`blk.${i}.ffn_${role}_exps.weight`]; if (!info) return null;
501
+ const N = role === "down" ? dM : ffM, K = role === "down" ? ffM : dM;
502
+ return { info, N, K, blk: true, eltOffset: e * N * K };
503
+ }
504
+ m = name.match(/^l(\d+)\.router$/);
505
+ if (m) { const info = tdir[`blk.${m[1]}.ffn_gate_inp.weight`]; return info ? { info, N: 1, K: (manifest.moe.n_experts * dM), blk: false, eltOffset: 0 } : null; }
506
+ const spec = mByName[name]; const gname = ggufNameFor(name, hasOut); const info = gname && tdir[gname];
507
+ return (spec && info) ? { info, N: spec.N, K: spec.K, blk: spec.blk, eltOffset: 0 } : null;
508
+ };
509
+
510
+ return async function fetchTensor(name) {
511
+ const r = resolve(name);
512
+ if (!r) return new Uint8Array(0);
513
+ const { info, N, K, blk, eltOffset } = r;
514
+ const [bElems, bBytes] = blockShape(info.ggmlType);
515
+ const tBase = dataOffset + info.offset + (eltOffset / bElems) * bBytes; // slab offset for experts
516
+ if (!blk) { // norm / bias / router → [f32]
517
+ const elems = K;
518
+ const raw = await readRange(url, tBase, typeByteLen(info.ggmlType, elems));
519
+ const f = dequantizeRaw(info.ggmlType, raw, elems);
520
+ return new Uint8Array(f.buffer, f.byteOffset, f.byteLength);
521
+ }
522
+ // block weight → [all q bytes][all f32 scales], chunked by rows so a 152k-vocab
523
+ // tensor never materialises whole as f32 (mirror of quant_tensor_chunked).
524
+ const [blkElems, blkBytes] = blockShape(info.ggmlType);
525
+ const qBytesTotal = bits === 4 ? (N * K) / 2 : N * K;
526
+ const scaleCount = N * (K / 32);
527
+ const out = new Uint8Array(qBytesTotal + scaleCount * 4);
528
+ const scales = new Float32Array(out.buffer, qBytesTotal, scaleCount);
529
+ // Fast relayout when the source quant matches the engine's width (the common
530
+ // case: a Q4_0 model → 4-bit engine). No f32 ever materialises → also lets the
531
+ // big embed/lm_head be done in one pass.
532
+ const fast = (info.ggmlType === GGML.Q4_0 && bits === 4) || (info.ggmlType === GGML.Q8_0 && bits === 8);
533
+ if (fast) {
534
+ const raw = await readRange(url, tBase, typeByteLen(info.ggmlType, N * K));
535
+ const { q, s } = info.ggmlType === GGML.Q4_0 ? relayoutQ4(raw, N, K) : relayoutQ8(raw, N, K);
536
+ out.set(q, 0); scales.set(s, 0);
537
+ return out;
538
+ }
539
+ let qPos = 0, sPos = 0;
540
+ for (let r = 0; r < N; r += ROW_CHUNK) {
541
+ const nr = Math.min(ROW_CHUNK, N - r);
542
+ const startElem = r * K, countElem = nr * K;
543
+ const byteStart = (startElem / blkElems) * blkBytes, byteLen = (countElem / blkElems) * blkBytes;
544
+ const raw = await readRange(url, tBase + byteStart, byteLen);
545
+ const f = dequantizeRaw(info.ggmlType, raw, countElem);
546
+ const { q, s } = quantBlocks(f, nr, K, bits);
547
+ out.set(q, qPos); qPos += q.length;
548
+ scales.set(s, sPos); sPos += s.length;
549
+ }
550
+ return out;
551
+ };
552
+ }
553
+
554
+ // HTTP Range reader against a same-origin URL.
555
+ export function rangeReader() {
556
+ return async (url, start, len) => {
557
+ const r = await fetch(url, { headers: { Range: `bytes=${start}-${start + len - 1}` } });
558
+ if (!r.ok && r.status !== 206) throw new Error(`range ${start}+${len}: HTTP ${r.status}`);
559
+ return new Uint8Array(await r.arrayBuffer());
560
+ };
561
+ }