DMCRA-LAB / weights.js
AEUPH's picture
DMCRA Lab: elastic level-set quantisation over real GGUF weights, measured end to end
64c101b verified
Raw History Blame Contribute Delete
35.1 kB
/* weights.js — GGUF *weights* stored in HDC memory, and used for a forward pass.
*
* The engine in `vsa.js` reads a GGUF's vocabulary out of the metadata block and builds an
* association memory from the merge table and from text. Its weights were previously discarded,
* because the legacy page folded raw weight bytes into a D=10,000 hypervector by a monotone OR and
* measured at chance (15.6% against a 12.5% floor) — 1,185,376 bytes collapsed into 10,000
* dimensions, a 948:1 lossy reduction whose information did not survive.
*
* This file does the three things that actually make weight storage in HDC memory work, each
* grounded in published work and separately measurable:
*
* 1. DEQUANTISE real GGUF tensors exactly (F32/F16/Q4_0/Q4_1/Q5_0/Q5_1/Q8_0/Q6_K/IQ4_NL, plus the
* K-quants Q2_K/Q3_K/Q4_K/Q5_K and BF16). The block arithmetic below reproduces the archive's
* verified reference implementations case for case — `D:/JS-GGUF/GGUF-SURGERY/gguf-core.js`
* `dequantTensor` for every quantised type, and its `bf16ToF32` for BF16 (BF16 is the top 16
* bits of an IEEE-754 single, so it needs no block loop). Nothing is guessed: a type with no
* reference to port stays unsupported and returns null rather than a plausible-looking wrong
* number. That currently includes Q8_1 (9) and MXFP4 (39), for which the archive has no
* implementation.
*
* 2. LOSSLESS paged HDC weight memory. `D:/JS-GGUF/HDC-PROOFS/wan_hdc_weight_memory.py` states the
* archive's own hard-won conclusion: "Arbitrary FP16/BF16 model bytes cannot be losslessly
* represented by a fixed-size semantic hypervector. Instead, each source byte is represented by
* eight bipolar dimensions ... The page dimension is therefore N = byte_count * 8 and scales
* with every artifact." That is implemented here: one dimension per stored BIT, paged, and
* byte-exact on restore. It is a *representation*, not a compression — its dimension grows with
* the data, and saying otherwise is the error this file exists to avoid.
*
* 3. TERNARY storage, which IS compression, per arXiv 2402.17764 (Ma et al., "The Era of 1-bit
* LLMs: BitNet b1.58") — every weight becomes one of {-1,0,+1} as W = s * T with s = absmean.
* A ternary row is stored as two bipolar bit-planes (which weights are +1, which are -1), so the
* cost is 2 bits per weight plus one scale per block, and the reconstruction error against the
* original is MEASURED by `ternaryError()` rather than claimed.
*
* 4. A VSA LINEAR LAYER, which is what makes stored weights into inference rather than storage,
* following arXiv 2109.02157 (Plateau & Bellec, "Learning with Holographic Reduced
* Representations"): a dense layer is expressible as bind + bundle + similarity. Both the input
* and every weight row are encoded with the SAME positional key set, so an output row's score is
* the inner product of the input with that row — the layer's own arithmetic, performed by the
* hypervector algebra. `exactLinear` computes the same layer directly, so the coordinator's
* tests can measure the VSA path against the truth instead of asserting it.
*/
(function (root) {
"use strict";
/* ---------------------------------------------------------------- type tables
* e = elements per block, b = bytes per block. F32/F16 have one element per "block".
* These are llama.cpp's block sizes and must not be approximated: a wrong stride silently
* misreads every value after the first block. */
var TYPE_BLOCK = {
0: { e: 1, b: 4 }, // F32
1: { e: 1, b: 2 }, // F16
2: { e: 32, b: 18 }, // Q4_0 d(2) qs(16)
3: { e: 32, b: 20 }, // Q4_1 d(2) m(2) qs(16)
6: { e: 32, b: 22 }, // Q5_0 d(2) qh(4) qs(16)
7: { e: 32, b: 24 }, // Q5_1 d(2) m(2) qh(4) qs(16)
8: { e: 32, b: 34 }, // Q8_0 d(2) qs(32)
10: { e: 256, b: 84 }, // Q2_K scales(16) qs(64) d(2) dmin(2)
11: { e: 256, b: 110 }, // Q3_K hmask(32) qs(64) scales(12) d(2)
12: { e: 256, b: 144 }, // Q4_K d(2) dmin(2) scales(12) qs(128)
13: { e: 256, b: 176 }, // Q5_K d(2) dmin(2) scales(12) qh(32) qs(128)
14: { e: 256, b: 210 }, // Q6_K ql(128) qh(64) scales(16) d(2)
20: { e: 32, b: 18 }, // IQ4_NL d(2) qs(16)
30: { e: 1, b: 2 } // BF16 one element per block: the top 16 bits of a float32
};
var TYPE_NAME = { 0: "F32", 1: "F16", 2: "Q4_0", 3: "Q4_1", 6: "Q5_0", 7: "Q5_1",
8: "Q8_0", 10: "Q2_K", 11: "Q3_K", 12: "Q4_K", 13: "Q5_K",
14: "Q6_K", 20: "IQ4_NL", 30: "BF16" };
/* IQ4_NL nibbles index a NON-LINEAR grid (llama.cpp kvalues_iq4nl), so the four-bit value is not
* a multiplier. Note: the archive has two copies of this table and they DISAGREE in their last two
* entries — gguf-core.js ends 69, 89, 113 while gguf-distill.js ends 69, 87, 108. This file uses
* gguf-core.js's values (the file the toolkit's dequantTensor is verified against) and flags the
* discrepancy rather than silently picking one. */
var KVALUES_IQ4NL = [-127, -104, -83, -65, -49, -35, -22, -10,
1, 13, 25, 38, 53, 69, 89, 113];
/* ---------------------------------------------------------------- helpers */
function asBytes(buf) {
if (buf instanceof Uint8Array) return buf;
if (buf instanceof ArrayBuffer) return new Uint8Array(buf);
if (buf && buf.buffer instanceof ArrayBuffer) {
return new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength);
}
throw new Error("expected an ArrayBuffer or Uint8Array");
}
/* IEEE 754 half -> single. Exponent bias 15, 10 mantissa bits. Subnormals are 2^-14 * m/1024;
* exponent 31 is inf (m=0) or NaN, which must not be folded into a finite number. */
function f16ToF32(h) {
var s = (h & 0x8000) ? -1 : 1;
var e = (h >> 10) & 0x1f;
var m = h & 0x03ff;
if (e === 0) return s * Math.pow(2, -14) * (m / 1024);
if (e === 31) return m ? NaN : s * Infinity;
return s * Math.pow(2, e - 15) * (1 + m / 1024);
}
/* bfloat16 -> float32. A bfloat16 is literally the top 16 bits of an IEEE-754 single, so the
* conversion is a shift into the high half of a 32-bit word. Ported verbatim from
* D:/JS-GGUF/GGUF-SURGERY/gguf-core.js, `bf16ToF32`. */
function bf16ToF32(u16) {
var b = new DataView(new ArrayBuffer(4));
b.setUint32(0, (u16 << 16) >>> 0, true);
return b.getFloat32(0, true);
}
function fmtBytes(n) {
if (n === null || n === undefined || !isFinite(n)) return "?";
var units = ["B", "KB", "MB", "GB", "TB"], i = 0, v = n;
while (v >= 1024 && i < units.length - 1) { v /= 1024; i++; }
return (i === 0 ? Math.round(v) : v.toFixed(2)) + " " + units[i];
}
/* A 64-bit integrity digest, dependency-free. The archive's Python uses SHA-256; a browser page
* with no imports cannot, so this is an FNV-1a pair over two different primitives — explicitly NOT
* cryptographic. The byte-exact round-trip test is the real guarantee; this catches corruption. */
function digestFnv1a64(bytes) {
var h1 = 0x811c9dc5 >>> 0, h2 = 0x84222325 >>> 0;
for (var i = 0; i < bytes.length; i++) {
var b = bytes[i];
h1 = Math.imul(h1 ^ b, 0x01000193) >>> 0;
h2 = Math.imul(h2 ^ b, 0x85ebca6b) >>> 0;
h2 = (h2 ^ (h2 >>> 13)) >>> 0;
}
var hex = function (x) { return ("00000000" + x.toString(16)).slice(-8); };
return hex(h1) + hex(h2);
}
/* ---------------------------------------------------------------- part 1: dequantise */
/* Exact inverse of every quantisation llama.cpp writes for the supported types.
* Returns a Float32Array of `count` values, or null for an unsupported type.
* `count` may exceed what `bytes` holds (a prefix buffer): values past the available blocks stay 0
* and the block count is clamped, so a short buffer can never read past its end. */
function dequant(typeId, bytes, count) {
var u8 = asBytes(bytes);
var blk = TYPE_BLOCK[typeId];
if (!blk) return null;
var out = new Float32Array(count);
var dv = new DataView(u8.buffer, u8.byteOffset, u8.byteLength);
var f16 = function (o) { return f16ToF32(dv.getUint16(o, true)); };
var b, base, i, j, idx, q, ql, qh, d, m;
switch (typeId) {
case 0: { // F32, direct
var n0 = Math.min(count, Math.floor(u8.byteLength / 4));
for (i = 0; i < n0; i++) out[i] = dv.getFloat32(i * 4, true);
return out;
}
case 1: { // F16, direct
var n1 = Math.min(count, Math.floor(u8.byteLength / 2));
for (i = 0; i < n1; i++) out[i] = f16(i * 2);
return out;
}
case 2: { // Q4_0: byte j holds element j in its LOW nibble and element j+16 in its HIGH
var b2 = Math.min(Math.floor(u8.byteLength / 18), Math.ceil(count / 32));
for (b = 0; b < b2; b++) {
base = b * 18; d = f16(base);
for (i = 0; i < 32; i++) {
idx = b * 32 + i;
if (idx < count) {
q = (i < 16) ? (u8[base + 2 + i] & 0x0f) : (u8[base + 2 + (i - 16)] >> 4);
out[idx] = d * (q - 8); // 4-bit unsigned, zero-point 8
}
}
}
return out;
}
case 3: { // Q4_1: d, m, then the same nibble split; zero-point carried as a minimum
var b3 = Math.min(Math.floor(u8.byteLength / 20), Math.ceil(count / 32));
for (b = 0; b < b3; b++) {
base = b * 20; d = f16(base); m = f16(base + 2);
for (i = 0; i < 32; i++) {
idx = b * 32 + i;
if (idx < count) {
q = (i < 16) ? (u8[base + 4 + i] & 0x0f) : (u8[base + 4 + (i - 16)] >> 4);
out[idx] = d * q + m;
}
}
}
return out;
}
case 6: { // Q5_0: d, qh(4 bytes = the 5th bit of all 32 elements), qs(16)
var b6 = Math.min(Math.floor(u8.byteLength / 22), Math.ceil(count / 32));
for (b = 0; b < b6; b++) {
base = b * 22; d = f16(base);
var qh6 = (u8[base + 2] | (u8[base + 3] << 8) | (u8[base + 4] << 16) |
(u8[base + 5] << 24)) >>> 0;
for (i = 0; i < 32; i++) {
idx = b * 32 + i;
if (idx < count) {
ql = (i < 16) ? (u8[base + 6 + i] & 0x0f) : (u8[base + 6 + (i - 16)] >> 4);
q = ql | (((qh6 >> i) & 1) << 4); // 5-bit, zero-point 16
out[idx] = d * (q - 16);
}
}
}
return out;
}
case 7: { // Q5_1: d, m, qh(4), qs(16)
var b7 = Math.min(Math.floor(u8.byteLength / 24), Math.ceil(count / 32));
for (b = 0; b < b7; b++) {
base = b * 24; d = f16(base); m = f16(base + 2);
var qh7 = (u8[base + 4] | (u8[base + 5] << 8) | (u8[base + 6] << 16) |
(u8[base + 7] << 24)) >>> 0;
for (i = 0; i < 32; i++) {
idx = b * 32 + i;
if (idx < count) {
ql = (i < 16) ? (u8[base + 8 + i] & 0x0f) : (u8[base + 8 + (i - 16)] >> 4);
q = ql | (((qh7 >> i) & 1) << 4);
out[idx] = d * q + m;
}
}
}
return out;
}
case 8: { // Q8_0: d, then 32 SIGNED 8-bit values — no nibble packing to get wrong
var b8 = Math.min(Math.floor(u8.byteLength / 34), Math.ceil(count / 32));
for (b = 0; b < b8; b++) {
base = b * 34; d = f16(base);
for (i = 0; i < 32; i++) {
idx = b * 32 + i;
if (idx < count) out[idx] = dv.getInt8(base + 2 + i) * d;
}
}
return out;
}
case 10: { // Q2_K: scales(16) qs(64) d(2) dmin(2) — 256 elems/block, 16 sub-blocks of 16
var b10 = Math.min(Math.floor(u8.byteLength / 84), Math.ceil(count / 256));
for (b = 0; b < b10; b++) {
base = b * 84; d = f16(base + 80);
var dmin10 = f16(base + 82);
for (var s10 = 0; s10 < 16; s10++) {
var sc10 = u8[base + s10], dl10 = d * (sc10 & 0x0f), ml10 = dmin10 * (sc10 >> 4);
var qb10 = base + 16 + (s10 >> 3) * 32 + (s10 & 1) * 16, sh10 = ((s10 >> 1) & 3) * 2;
for (j = 0; j < 16; j++) {
idx = b * 256 + s10 * 16 + j;
if (idx < count) out[idx] = dl10 * ((u8[qb10 + j] >> sh10) & 3) - ml10;
}
}
}
return out;
}
case 11: { // Q3_K: hmask(32) qs(64) scales(12) d(2) — 16 sub-blocks of 16
var b11 = Math.min(Math.floor(u8.byteLength / 110), Math.ceil(count / 256));
for (b = 0; b < b11; b++) {
base = b * 110; d = f16(base + 108);
var lsc = [], hsc = [];
for (i = 0; i < 16; i++) lsc.push((u8[base + 96 + (i & 7)] >> (((i >> 3) & 1) * 4)) & 0x0f);
for (i = 0; i < 16; i++) hsc.push((u8[base + 104 + (i & 3)] >> (((i >> 2) & 3) * 2)) & 3);
for (var s11 = 0; s11 < 16; s11++) {
var dl11 = d * ((lsc[s11] | (hsc[s11] << 4)) - 32);
var qb11 = base + 32 + (s11 >> 3) * 32 + (s11 & 1) * 16, qs11 = ((s11 >> 1) & 3) * 2;
var hb11 = base + (s11 & 1) * 16, hs11 = (s11 >> 1) & 7;
for (j = 0; j < 16; j++) {
idx = b * 256 + s11 * 16 + j;
if (idx < count) {
var ql11 = (u8[qb11 + j] >> qs11) & 3;
var qh11 = (u8[hb11 + j] >> hs11) & 1;
out[idx] = dl11 * (ql11 - ((qh11 ^ 1) << 2));
}
}
}
}
return out;
}
case 12: { // Q4_K: d(2) dmin(2) scales(12) qs(128) — 8 sub-blocks of 32
var b12 = Math.min(Math.floor(u8.byteLength / 144), Math.ceil(count / 256));
for (b = 0; b < b12; b++) {
base = b * 144; d = f16(base);
var dmin12 = f16(base + 2);
var sc12 = [], mn12 = [];
for (i = 0; i < 4; i++) {
sc12.push(u8[base + 4 + i] & 0x3f);
mn12.push(u8[base + 8 + i] & 0x3f);
}
for (i = 0; i < 4; i++) {
sc12.push((u8[base + 12 + i] & 0x0f) | ((u8[base + 4 + i] >> 2) & 0x30));
mn12.push((u8[base + 12 + i] >> 4) | ((u8[base + 8 + i] >> 2) & 0x30));
}
for (var k12 = 0; k12 < 8; k12++) {
var dl12 = d * sc12[k12], ml12 = dmin12 * mn12[k12];
var qb12 = base + 16 + (k12 >> 1) * 32, sh12 = (k12 & 1) * 4;
for (i = 0; i < 32; i++) {
idx = b * 256 + k12 * 32 + i;
if (idx < count) out[idx] = dl12 * ((u8[qb12 + i] >> sh12) & 0x0f) - ml12;
}
}
}
return out;
}
case 13: { // Q5_K: d(2) dmin(2) scales(12) qh(32) qs(128) — 8 sub-blocks of 32
var b13 = Math.min(Math.floor(u8.byteLength / 176), Math.ceil(count / 256));
for (b = 0; b < b13; b++) {
base = b * 176; d = f16(base);
var dmin13 = f16(base + 2);
var sc13 = [], mn13 = [];
for (i = 0; i < 4; i++) {
sc13.push(u8[base + 4 + i] & 0x3f);
mn13.push(u8[base + 8 + i] & 0x3f);
}
for (i = 0; i < 4; i++) {
sc13.push((u8[base + 12 + i] & 0x0f) | ((u8[base + 4 + i] >> 2) & 0x30));
mn13.push((u8[base + 12 + i] >> 4) | ((u8[base + 8 + i] >> 2) & 0x30));
}
for (var k13 = 0; k13 < 8; k13++) {
var dl13 = d * sc13[k13], ml13 = dmin13 * mn13[k13];
var qb13 = base + 48 + (k13 >> 1) * 32, sh13 = (k13 & 1) * 4;
for (i = 0; i < 32; i++) {
idx = b * 256 + k13 * 32 + i;
if (idx < count) {
ql = (u8[qb13 + i] >> sh13) & 0x0f;
qh = (u8[base + 16 + i] >> k13) & 1;
out[idx] = dl13 * (ql | (qh << 4)) - ml13;
}
}
}
}
return out;
}
case 14: { // Q6_K: ql(128) qh(64) scales(16 int8) d(f16 at 208); 16 sub-blocks of 16
var b14 = Math.min(Math.floor(u8.byteLength / 210), Math.ceil(count / 256));
for (b = 0; b < b14; b++) {
base = b * 210; d = f16(base + 208);
for (var s = 0; s < 16; s++) {
var dl = d * dv.getInt8(base + 192 + s);
var qlb = base + (s >> 3) * 64 + (s & 3) * 16, qlShift = ((s >> 2) & 1) * 4;
var qhb = base + 128 + (s >> 3) * 32 + (s & 1) * 16, qhShift = ((s >> 1) & 3) * 2;
for (j = 0; j < 16; j++) {
idx = b * 256 + s * 16 + j;
if (idx < count) {
ql = (u8[qlb + j] >> qlShift) & 0x0f;
qh = (u8[qhb + j] >> qhShift) & 0x03;
out[idx] = dl * ((ql | (qh << 4)) - 32); // 6-bit, zero-point 32
}
}
}
}
return out;
}
case 20: { // IQ4_NL: d, 16 nibble bytes; low nibble -> element 2j, high -> 2j+1
var b20 = Math.min(Math.floor(u8.byteLength / 18), Math.ceil(count / 32));
for (b = 0; b < b20; b++) {
base = b * 18; d = f16(base);
for (j = 0; j < 16; j++) {
q = u8[base + 2 + j];
var i0 = b * 32 + 2 * j, i1 = i0 + 1;
if (i0 < count) out[i0] = d * KVALUES_IQ4NL[q & 0x0f];
if (i1 < count) out[i1] = d * KVALUES_IQ4NL[q >> 4];
}
}
return out;
}
case 30: { // BF16: no block packing — one 16-bit word per element, direct
var n30 = Math.min(count, Math.floor(u8.byteLength / 2));
for (i = 0; i < n30; i++) out[i] = bf16ToF32(dv.getUint16(i * 2, true));
return out;
}
default:
return null;
}
}
function supportedTypes() {
var out = [];
for (var k in TYPE_BLOCK) if (TYPE_BLOCK.hasOwnProperty(k)) out.push(Number(k));
return out.sort(function (a, b) { return a - b; });
}
/* ---------------------------------------------------------------- tensor access */
function findKV(header, key) {
var kv = header.kv || [];
for (var i = 0; i < kv.length; i++) if (kv[i].key === key) return kv[i].value;
return undefined;
}
/* GGUF stores a tensor's offset relative to the data section, which llama.cpp pads up to
* `general.alignment` (default 32). Getting this wrong shifts every value by up to 31 bytes and
* produces plausible-looking garbage, so it is derived from the file's own KV rather than assumed.
* A cheap way for a test to confirm it: a real F32 embedding tensor must dequantise to small
* finite floats, not denormals or NaNs. */
function alignment(header) {
var a = findKV(header, "general.alignment");
return (typeof a === "number" && a > 0) ? a : 32;
}
function dataOffset(header) {
var a = alignment(header);
return Math.ceil(header.headerBytes / a) * a;
}
function tensorByteLength(t) {
var blk = TYPE_BLOCK[t.typeId];
if (!blk) return null;
var n = Number(t.params);
return Math.ceil(n / blk.e) * blk.b;
}
function findTensor(header, name) {
var ts = header.tensors || [];
for (var i = 0; i < ts.length; i++) if (ts[i].name === name) return ts[i];
return null;
}
/* Compact index of what a file holds — used by the UI to show which tensors are readable. */
function listTensors(header, nameFilter) {
var ts = header.tensors || [], out = [];
for (var i = 0; i < ts.length; i++) {
var t = ts[i];
if (nameFilter && t.name.indexOf(nameFilter) === -1) continue;
out.push({
name: t.name,
dims: t.dims.slice(),
typeId: t.typeId,
typeName: TYPE_NAME[t.typeId] || String(t.typeId),
params: Number(t.params),
byteLength: tensorByteLength(t),
supported: !!TYPE_BLOCK[t.typeId]
});
}
return out;
}
/* Read and dequantise one whole tensor. Returns null values when the type is unsupported (the
* caller reports that) — never a partially-guessed matrix. */
function readTensor(buf, header, name) {
var t = findTensor(header, name);
if (!t) return null;
var len = tensorByteLength(t);
var bytes = asBytes(buf);
var start = dataOffset(header) + Number(t.offset);
if (len === null) {
return { name: name, typeId: t.typeId, typeName: TYPE_NAME[t.typeId] || String(t.typeId),
dims: t.dims.slice(), count: Number(t.params), byteLength: null, values: null,
supported: false, dataStart: start };
}
if (start + len > bytes.byteLength) {
return { name: name, typeId: t.typeId, typeName: TYPE_NAME[t.typeId] || String(t.typeId),
dims: t.dims.slice(), count: Number(t.params), byteLength: len, values: null,
supported: !!TYPE_BLOCK[t.typeId], dataStart: start,
error: "tensor extends past the buffer (need " + (start + len - bytes.byteLength) +
" more bytes)" };
}
var slice = bytes.subarray(start, start + len);
var values = dequant(t.typeId, slice, Number(t.params));
return { name: name, typeId: t.typeId, typeName: TYPE_NAME[t.typeId] || String(t.typeId),
dims: t.dims.slice(), count: Number(t.params), byteLength: len, values: values,
supported: !!TYPE_BLOCK[t.typeId], dataStart: start };
}
/* ---------------------------------------------------------------- part 2: lossless HDC memory */
/* One dimension per stored bit: bit 1 -> +1, bit 0 -> -1, bit-packed so the vector occupies
* exactly the same bytes as the source. This is the archive's scheme and it is EXACT: it is a
* representation, not a compression, and the dimension grows with the data (N = bytes * 8). */
function packBipolar(u8) {
var bytes = asBytes(u8);
return { bytes: bytes.slice(0), dimensionBits: bytes.length * 8 };
}
function unpackBipolar(packed, nBytes) {
var src = asBytes(packed);
if (nBytes === undefined || nBytes === null) nBytes = src.length;
if (nBytes > src.length) throw new Error("unpackBipolar: " + nBytes + " bytes requested from " +
src.length + " packed");
return src.slice(0, nBytes);
}
/* The expanded form, for callers that want the literal +1/-1 vector (8x memory — which is exactly
* why the stored form stays bit-packed). */
function toBipolarVector(u8) {
var bytes = asBytes(u8), out = new Int8Array(bytes.length * 8), n = 0, j, b;
for (var i = 0; i < bytes.length; i++) {
b = bytes[i];
for (j = 0; j < 8; j++) out[n++] = (b & (1 << j)) ? 1 : -1;
}
return out;
}
function fromBipolarVector(vec) {
var out = new Uint8Array(Math.ceil(vec.length / 8));
for (var i = 0; i < vec.length; i++) if (vec[i] > 0) out[i >> 3] |= 1 << (i & 7);
return out;
}
/* Page a tensor's exact bytes into independently verifiable HDC pages. */
function storeLossless(buf, header, name, opts) {
opts = opts || {};
var pageBytes = opts.pageBytes || (1024 * 1024);
var t = findTensor(header, name);
if (!t) return null;
var len = tensorByteLength(t);
if (len === null) return { name: name, supported: false,
typeName: TYPE_NAME[t.typeId] || String(t.typeId) };
var all = asBytes(buf);
var start = dataOffset(header) + Number(t.offset);
// Uint8Array.slice truncates silently, which would digest a short tensor as if it were whole
if (start + len > all.byteLength) {
return { name: name, supported: false,
typeName: TYPE_NAME[t.typeId] || String(t.typeId),
error: "tensor extends past the buffer (need " + (start + len - all.byteLength) +
" more bytes)" };
}
var tensorBytes = all.slice(start, start + len);
var pages = [], pageIndex = 0;
for (var off = 0; off < tensorBytes.length; off += pageBytes) {
var chunk = tensorBytes.subarray(off, Math.min(off + pageBytes, tensorBytes.length));
var packed = packBipolar(chunk);
pages.push({ index: pageIndex++, byteLength: chunk.length,
dimensionBits: packed.dimensionBits,
digest: digestFnv1a64(chunk), bytes: packed.bytes });
}
return {
name: name, typeName: TYPE_NAME[t.typeId] || String(t.typeId), dims: t.dims.slice(),
supported: true, byteLength: tensorBytes.length, dimensionBits: tensorBytes.length * 8,
pageBytes: pageBytes, pageCount: pages.length, pages: pages,
digest: digestFnv1a64(tensorBytes),
// the honest accounting: the dimension equals the data, so this saves nothing by itself
exact: true, compressive: false,
ggufBytes: len
};
}
/* Rebuild the exact source bytes. Verifies each page digest and the whole-tensor digest, so a
* corrupted or mis-sized store fails loudly instead of yielding wrong weights. */
function restoreLossless(store) {
var total = 0, i;
for (i = 0; i < store.pages.length; i++) total += store.pages[i].byteLength;
var out = new Uint8Array(total), at = 0;
for (i = 0; i < store.pages.length; i++) {
var p = store.pages[i];
var chunk = unpackBipolar(p.bytes, p.byteLength);
if (p.digest && digestFnv1a64(chunk) !== p.digest) {
throw new Error("HDC page " + p.index + " failed its digest");
}
out.set(chunk, at); at += chunk.length;
}
if (store.digest && digestFnv1a64(out) !== store.digest) {
throw new Error("restored tensor failed its digest");
}
return out;
}
/* ---------------------------------------------------------------- part 3: ternary (BitNet b1.58) */
function meanAbs(values, from, to) {
var s = 0, i;
for (i = from; i < to; i++) s += Math.abs(values[i]);
return (to > from) ? s / (to - from) : 0;
}
/* W = s * T, T in {-1,0,+1}, s = absmean (arXiv 2402.17764). Two bit-planes, so 2 bits per weight
* plus one 4-byte scale per block. blockSize = 0 means a single scale for the whole array. */
function toTernary(values, opts) {
opts = opts || {};
var blockSize = opts.blockSize | 0;
var n = values.length;
var pos = new Uint8Array((n + 7) >> 3), neg = new Uint8Array((n + 7) >> 3);
var scales = [];
var step = (blockSize > 0) ? blockSize : n;
for (var s0 = 0; s0 < n; s0 += step) {
var end = Math.min(n, s0 + step);
var sc = meanAbs(values, s0, end);
if (!(sc > 0)) sc = 1; // an all-zero block would divide by zero; T is 0 there anyway
scales.push(sc);
for (var i = s0; i < end; i++) {
var t = Math.round(values[i] / sc);
if (t > 1) t = 1; else if (t < -1) t = -1;
if (t === 1) pos[i >> 3] |= 1 << (i & 7);
else if (t === -1) neg[i >> 3] |= 1 << (i & 7);
}
}
return {
pos: pos, neg: neg, count: n, blockSize: blockSize, scales: scales,
scale: scales.length === 1 ? scales[0] : 0,
// 2 packed bit-planes; the information content of one ternary symbol is log2(3)
perValueBits: 2,
entropyBits: Math.log(3) / Math.LN2,
bytes: ternaryBytes(n, blockSize),
zeros: countZeros(pos, neg, n)
};
}
function countZeros(pos, neg, n) {
var z = 0;
for (var i = 0; i < n; i++) {
var p = (pos[i >> 3] >> (i & 7)) & 1, m = (neg[i >> 3] >> (i & 7)) & 1;
if (!p && !m) z++;
}
return z;
}
/* Exact storage cost: two bit-planes plus one 4-byte scale per block. */
function ternaryBytes(n, blockSize) {
var blocks = blockSize > 0 ? Math.ceil(n / blockSize) : 1;
return 2 * Math.ceil(n / 8) + blocks * 4;
}
function fromTernary(t, blockSize) {
var out = new Float32Array(t.count);
var bs = blockSize === undefined || blockSize === null ? t.blockSize : blockSize;
var step = (bs > 0) ? bs : t.count;
for (var i = 0; i < t.count; i++) {
var sc = (t.scales && t.scales.length > 1) ? t.scales[Math.floor(i / step)] : t.scale;
if (sc === undefined) sc = (t.scales && t.scales.length) ? t.scales[0] : 1;
var p = (t.pos[i >> 3] >> (i & 7)) & 1, m = (t.neg[i >> 3] >> (i & 7)) & 1;
out[i] = p ? sc : (m ? -sc : 0);
}
return out;
}
/* Measured against the ORIGINAL values, so the page can report real error rather than a claim. */
function ternaryError(values, blockSize) {
var t = toTernary(values, { blockSize: blockSize });
var rec = fromTernary(t, blockSize);
var se = 0, fro = 0, froErr = 0, maxAbs = 0, i, d;
for (i = 0; i < values.length; i++) {
d = rec[i] - values[i];
se += d * d;
fro += values[i] * values[i];
froErr += d * d;
if (Math.abs(d) > maxAbs) maxAbs = Math.abs(d);
}
return {
count: values.length, blockSize: blockSize, perValueBits: 2,
rmse: Math.sqrt(se / values.length),
maxAbs: maxAbs,
// ||W - rec||_F / ||W||_F — a RELATIVE ERROR, never a reconstruction
relativeFrobenius: fro > 0 ? Math.sqrt(froErr / fro) : 0,
zerosFraction: t.zeros / values.length,
bytes: t.bytes
};
}
/* ---------------------------------------------------------------- part 4: the VSA linear layer */
/* Positional keys. Deterministic from the VSA's own seed, so reloading reproduces the same layer
* exactly — which is what lets the *stored* rows be rebuilt rather than persisted. */
function makeKeys(vsa, n) {
var keys = new Array(n);
for (var i = 0; i < n; i++) keys[i] = vsa.vector("wkey:" + i);
return keys;
}
function complement(v) {
var o = new Uint8Array(v.length);
for (var i = 0; i < v.length; i++) o[i] = (~v[i]) & 0xff;
return o;
}
/* sign( sum_i w[i] * key[i] ), evaluated dimension-wise.
*
* This is exactly what vsa.bundle's weighted majority computes: at dimension d the bit is set iff
* the weight summed over the keys whose bit d is SET is at least half the total weight, and
* because total = S+ + S- that is precisely S+ >= S-, i.e. sign(sum_i w[i]*key_i[d]). Negative
* weights are therefore fine.
*
* Two guards are needed. vsa.bundle returns an all-clear vector when the weights sum to <= 0
* instead of the signs, so a negative-sum encoding is computed on negated weights and the result
* complemented (negating the weights negates the vector exactly). And an exactly-zero sum makes
* the half-threshold zero, which bundle's early return also short-circuits, so that single case is
* broken by a 1e-9 tie-break on one weight — it can only change dimensions whose sum is within
* 1e-9 of a tie. */
function signBundle(vsa, w, keys) {
var n = Math.min(w.length, keys.length);
var sum = 0, i;
for (i = 0; i < n; i++) sum += w[i];
var list = keys.slice(0, n);
var weights = new Float64Array(n);
for (i = 0; i < n; i++) weights[i] = w[i];
if (sum === 0) {
if (n === 0) return new Uint8Array(vsa.D >>> 3);
weights[n - 1] += 1e-9;
return vsa.bundle(list, weights);
}
if (sum > 0) return vsa.bundle(list, weights);
for (i = 0; i < n; i++) weights[i] = -weights[i];
return complement(vsa.bundle(list, weights));
}
/* Encode a real-valued input vector as one hypervector. */
function encodeVector(vsa, x, keys) { return signBundle(vsa, x, keys); }
/* Encode one hypervector per output row of a flat row-major matrix, using the SAME keys. */
function encodeRows(vsa, values, cols, keys) {
var rows = Math.floor(values.length / cols);
var out = new Array(rows), row = new Float64Array(cols);
for (var r = 0; r < rows; r++) {
for (var c = 0; c < cols; c++) row[c] = values[r * cols + c];
out[r] = signBundle(vsa, row, keys);
}
return out;
}
/* The layer's own arithmetic, performed by the hypervector algebra. Scores are similarities in
* [-1,1]: their RANKING corresponds to the linear layer's output ranking, which is what a decode
* consumes. Magnitudes are not preserved, and nothing here claims they are. */
function linearVSA(vsa, xhv, rowHvs) {
var out = new Float32Array(rowHvs.length);
for (var j = 0; j < rowHvs.length; j++) out[j] = vsa.similarity(xhv, rowHvs[j]);
return out;
}
/* The ground truth to measure the above against: y_j = sum_i W[j][i] * x[i]. */
function exactLinear(x, values, cols) {
var rows = Math.floor(values.length / cols);
var out = new Float32Array(rows);
for (var r = 0; r < rows; r++) {
var s = 0, off = r * cols;
for (var c = 0; c < cols; c++) s += values[off + c] * x[c];
out[r] = s;
}
return out;
}
/* Rank agreement between two score vectors — the honest way to compare the VSA layer with the
* exact one. 1.0 means identical order. */
function rankAgreement(a, b) {
var ia = order(a), ib = order(b), same = 0, i;
var n = Math.min(ia.length, ib.length);
for (i = 0; i < n; i++) if (ia[i] === ib[i]) same++;
return n ? same / n : 0;
}
/* Top-k agreement: the fraction of the k highest-ranked rows the two score vectors agree on.
* This is the measurement that matters for a decode (which row wins); full-order agreement is
* dominated by the tail and understates a layer that ranks its top candidates correctly. */
function topKAgreement(a, b, k) {
var ka = order(a).slice(0, k), kb = order(b).slice(0, k);
var set = {}, hits = 0, i;
for (i = 0; i < kb.length; i++) set[kb[i]] = true;
for (i = 0; i < ka.length; i++) if (set[ka[i]]) hits++;
return ka.length ? hits / ka.length : 0;
}
function argmax(v) {
var best = -Infinity, bi = -1;
for (var i = 0; i < v.length; i++) if (v[i] > best) { best = v[i]; bi = i; }
return bi;
}
function order(v) {
var idx = new Array(v.length);
for (var i = 0; i < v.length; i++) idx[i] = i;
idx.sort(function (x, y) { return v[y] - v[x]; });
return idx;
}
var api = {
// part 1
dequant: dequant, f16ToF32: f16ToF32, bf16ToF32: bf16ToF32, supportedTypes: supportedTypes,
listTensors: listTensors, readTensor: readTensor, findTensor: findTensor,
tensorByteLength: tensorByteLength, dataOffset: dataOffset, alignment: alignment,
TYPE_BLOCK: TYPE_BLOCK, TYPE_NAME: TYPE_NAME, KVALUES_IQ4NL: KVALUES_IQ4NL,
// part 2
packBipolar: packBipolar, unpackBipolar: unpackBipolar,
toBipolarVector: toBipolarVector, fromBipolarVector: fromBipolarVector,
storeLossless: storeLossless, restoreLossless: restoreLossless,
// part 3
toTernary: toTernary, fromTernary: fromTernary, ternaryError: ternaryError,
ternaryBytes: ternaryBytes,
// part 4
makeKeys: makeKeys, encodeVector: encodeVector, encodeRows: encodeRows,
linearVSA: linearVSA, exactLinear: exactLinear, rankAgreement: rankAgreement,
topKAgreement: topKAgreement, argmax: argmax, order: order,
// misc
fmtBytes: fmtBytes, digestFnv1a64: digestFnv1a64
};
if (typeof module !== "undefined" && module.exports) module.exports = api;
root.HDCWeights = api;
})(typeof globalThis !== "undefined" ? globalThis : this);