AEUPH commited on
Commit
64c101b
·
verified ·
1 Parent(s): d4c0ee0

DMCRA Lab: elastic level-set quantisation over real GGUF weights, measured end to end

Browse files
Files changed (1) hide show
  1. weights.js +777 -0
weights.js ADDED
@@ -0,0 +1,777 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* weights.js — GGUF *weights* stored in HDC memory, and used for a forward pass.
2
+ *
3
+ * The engine in `vsa.js` reads a GGUF's vocabulary out of the metadata block and builds an
4
+ * association memory from the merge table and from text. Its weights were previously discarded,
5
+ * because the legacy page folded raw weight bytes into a D=10,000 hypervector by a monotone OR and
6
+ * measured at chance (15.6% against a 12.5% floor) — 1,185,376 bytes collapsed into 10,000
7
+ * dimensions, a 948:1 lossy reduction whose information did not survive.
8
+ *
9
+ * This file does the three things that actually make weight storage in HDC memory work, each
10
+ * grounded in published work and separately measurable:
11
+ *
12
+ * 1. DEQUANTISE real GGUF tensors exactly (F32/F16/Q4_0/Q4_1/Q5_0/Q5_1/Q8_0/Q6_K/IQ4_NL, plus the
13
+ * K-quants Q2_K/Q3_K/Q4_K/Q5_K and BF16). The block arithmetic below reproduces the archive's
14
+ * verified reference implementations case for case — `D:/JS-GGUF/GGUF-SURGERY/gguf-core.js`
15
+ * `dequantTensor` for every quantised type, and its `bf16ToF32` for BF16 (BF16 is the top 16
16
+ * bits of an IEEE-754 single, so it needs no block loop). Nothing is guessed: a type with no
17
+ * reference to port stays unsupported and returns null rather than a plausible-looking wrong
18
+ * number. That currently includes Q8_1 (9) and MXFP4 (39), for which the archive has no
19
+ * implementation.
20
+ *
21
+ * 2. LOSSLESS paged HDC weight memory. `D:/JS-GGUF/HDC-PROOFS/wan_hdc_weight_memory.py` states the
22
+ * archive's own hard-won conclusion: "Arbitrary FP16/BF16 model bytes cannot be losslessly
23
+ * represented by a fixed-size semantic hypervector. Instead, each source byte is represented by
24
+ * eight bipolar dimensions ... The page dimension is therefore N = byte_count * 8 and scales
25
+ * with every artifact." That is implemented here: one dimension per stored BIT, paged, and
26
+ * byte-exact on restore. It is a *representation*, not a compression — its dimension grows with
27
+ * the data, and saying otherwise is the error this file exists to avoid.
28
+ *
29
+ * 3. TERNARY storage, which IS compression, per arXiv 2402.17764 (Ma et al., "The Era of 1-bit
30
+ * LLMs: BitNet b1.58") — every weight becomes one of {-1,0,+1} as W = s * T with s = absmean.
31
+ * A ternary row is stored as two bipolar bit-planes (which weights are +1, which are -1), so the
32
+ * cost is 2 bits per weight plus one scale per block, and the reconstruction error against the
33
+ * original is MEASURED by `ternaryError()` rather than claimed.
34
+ *
35
+ * 4. A VSA LINEAR LAYER, which is what makes stored weights into inference rather than storage,
36
+ * following arXiv 2109.02157 (Plateau & Bellec, "Learning with Holographic Reduced
37
+ * Representations"): a dense layer is expressible as bind + bundle + similarity. Both the input
38
+ * and every weight row are encoded with the SAME positional key set, so an output row's score is
39
+ * the inner product of the input with that row — the layer's own arithmetic, performed by the
40
+ * hypervector algebra. `exactLinear` computes the same layer directly, so the coordinator's
41
+ * tests can measure the VSA path against the truth instead of asserting it.
42
+ */
43
+ (function (root) {
44
+ "use strict";
45
+
46
+ /* ---------------------------------------------------------------- type tables
47
+ * e = elements per block, b = bytes per block. F32/F16 have one element per "block".
48
+ * These are llama.cpp's block sizes and must not be approximated: a wrong stride silently
49
+ * misreads every value after the first block. */
50
+ var TYPE_BLOCK = {
51
+ 0: { e: 1, b: 4 }, // F32
52
+ 1: { e: 1, b: 2 }, // F16
53
+ 2: { e: 32, b: 18 }, // Q4_0 d(2) qs(16)
54
+ 3: { e: 32, b: 20 }, // Q4_1 d(2) m(2) qs(16)
55
+ 6: { e: 32, b: 22 }, // Q5_0 d(2) qh(4) qs(16)
56
+ 7: { e: 32, b: 24 }, // Q5_1 d(2) m(2) qh(4) qs(16)
57
+ 8: { e: 32, b: 34 }, // Q8_0 d(2) qs(32)
58
+ 10: { e: 256, b: 84 }, // Q2_K scales(16) qs(64) d(2) dmin(2)
59
+ 11: { e: 256, b: 110 }, // Q3_K hmask(32) qs(64) scales(12) d(2)
60
+ 12: { e: 256, b: 144 }, // Q4_K d(2) dmin(2) scales(12) qs(128)
61
+ 13: { e: 256, b: 176 }, // Q5_K d(2) dmin(2) scales(12) qh(32) qs(128)
62
+ 14: { e: 256, b: 210 }, // Q6_K ql(128) qh(64) scales(16) d(2)
63
+ 20: { e: 32, b: 18 }, // IQ4_NL d(2) qs(16)
64
+ 30: { e: 1, b: 2 } // BF16 one element per block: the top 16 bits of a float32
65
+ };
66
+
67
+ var TYPE_NAME = { 0: "F32", 1: "F16", 2: "Q4_0", 3: "Q4_1", 6: "Q5_0", 7: "Q5_1",
68
+ 8: "Q8_0", 10: "Q2_K", 11: "Q3_K", 12: "Q4_K", 13: "Q5_K",
69
+ 14: "Q6_K", 20: "IQ4_NL", 30: "BF16" };
70
+
71
+ /* IQ4_NL nibbles index a NON-LINEAR grid (llama.cpp kvalues_iq4nl), so the four-bit value is not
72
+ * a multiplier. Note: the archive has two copies of this table and they DISAGREE in their last two
73
+ * entries — gguf-core.js ends 69, 89, 113 while gguf-distill.js ends 69, 87, 108. This file uses
74
+ * gguf-core.js's values (the file the toolkit's dequantTensor is verified against) and flags the
75
+ * discrepancy rather than silently picking one. */
76
+ var KVALUES_IQ4NL = [-127, -104, -83, -65, -49, -35, -22, -10,
77
+ 1, 13, 25, 38, 53, 69, 89, 113];
78
+
79
+ /* ---------------------------------------------------------------- helpers */
80
+
81
+ function asBytes(buf) {
82
+ if (buf instanceof Uint8Array) return buf;
83
+ if (buf instanceof ArrayBuffer) return new Uint8Array(buf);
84
+ if (buf && buf.buffer instanceof ArrayBuffer) {
85
+ return new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength);
86
+ }
87
+ throw new Error("expected an ArrayBuffer or Uint8Array");
88
+ }
89
+
90
+ /* IEEE 754 half -> single. Exponent bias 15, 10 mantissa bits. Subnormals are 2^-14 * m/1024;
91
+ * exponent 31 is inf (m=0) or NaN, which must not be folded into a finite number. */
92
+ function f16ToF32(h) {
93
+ var s = (h & 0x8000) ? -1 : 1;
94
+ var e = (h >> 10) & 0x1f;
95
+ var m = h & 0x03ff;
96
+ if (e === 0) return s * Math.pow(2, -14) * (m / 1024);
97
+ if (e === 31) return m ? NaN : s * Infinity;
98
+ return s * Math.pow(2, e - 15) * (1 + m / 1024);
99
+ }
100
+
101
+ /* bfloat16 -> float32. A bfloat16 is literally the top 16 bits of an IEEE-754 single, so the
102
+ * conversion is a shift into the high half of a 32-bit word. Ported verbatim from
103
+ * D:/JS-GGUF/GGUF-SURGERY/gguf-core.js, `bf16ToF32`. */
104
+ function bf16ToF32(u16) {
105
+ var b = new DataView(new ArrayBuffer(4));
106
+ b.setUint32(0, (u16 << 16) >>> 0, true);
107
+ return b.getFloat32(0, true);
108
+ }
109
+
110
+ function fmtBytes(n) {
111
+ if (n === null || n === undefined || !isFinite(n)) return "?";
112
+ var units = ["B", "KB", "MB", "GB", "TB"], i = 0, v = n;
113
+ while (v >= 1024 && i < units.length - 1) { v /= 1024; i++; }
114
+ return (i === 0 ? Math.round(v) : v.toFixed(2)) + " " + units[i];
115
+ }
116
+
117
+ /* A 64-bit integrity digest, dependency-free. The archive's Python uses SHA-256; a browser page
118
+ * with no imports cannot, so this is an FNV-1a pair over two different primitives — explicitly NOT
119
+ * cryptographic. The byte-exact round-trip test is the real guarantee; this catches corruption. */
120
+ function digestFnv1a64(bytes) {
121
+ var h1 = 0x811c9dc5 >>> 0, h2 = 0x84222325 >>> 0;
122
+ for (var i = 0; i < bytes.length; i++) {
123
+ var b = bytes[i];
124
+ h1 = Math.imul(h1 ^ b, 0x01000193) >>> 0;
125
+ h2 = Math.imul(h2 ^ b, 0x85ebca6b) >>> 0;
126
+ h2 = (h2 ^ (h2 >>> 13)) >>> 0;
127
+ }
128
+ var hex = function (x) { return ("00000000" + x.toString(16)).slice(-8); };
129
+ return hex(h1) + hex(h2);
130
+ }
131
+
132
+ /* ---------------------------------------------------------------- part 1: dequantise */
133
+
134
+ /* Exact inverse of every quantisation llama.cpp writes for the supported types.
135
+ * Returns a Float32Array of `count` values, or null for an unsupported type.
136
+ * `count` may exceed what `bytes` holds (a prefix buffer): values past the available blocks stay 0
137
+ * and the block count is clamped, so a short buffer can never read past its end. */
138
+ function dequant(typeId, bytes, count) {
139
+ var u8 = asBytes(bytes);
140
+ var blk = TYPE_BLOCK[typeId];
141
+ if (!blk) return null;
142
+ var out = new Float32Array(count);
143
+ var dv = new DataView(u8.buffer, u8.byteOffset, u8.byteLength);
144
+ var f16 = function (o) { return f16ToF32(dv.getUint16(o, true)); };
145
+ var b, base, i, j, idx, q, ql, qh, d, m;
146
+
147
+ switch (typeId) {
148
+ case 0: { // F32, direct
149
+ var n0 = Math.min(count, Math.floor(u8.byteLength / 4));
150
+ for (i = 0; i < n0; i++) out[i] = dv.getFloat32(i * 4, true);
151
+ return out;
152
+ }
153
+ case 1: { // F16, direct
154
+ var n1 = Math.min(count, Math.floor(u8.byteLength / 2));
155
+ for (i = 0; i < n1; i++) out[i] = f16(i * 2);
156
+ return out;
157
+ }
158
+ case 2: { // Q4_0: byte j holds element j in its LOW nibble and element j+16 in its HIGH
159
+ var b2 = Math.min(Math.floor(u8.byteLength / 18), Math.ceil(count / 32));
160
+ for (b = 0; b < b2; b++) {
161
+ base = b * 18; d = f16(base);
162
+ for (i = 0; i < 32; i++) {
163
+ idx = b * 32 + i;
164
+ if (idx < count) {
165
+ q = (i < 16) ? (u8[base + 2 + i] & 0x0f) : (u8[base + 2 + (i - 16)] >> 4);
166
+ out[idx] = d * (q - 8); // 4-bit unsigned, zero-point 8
167
+ }
168
+ }
169
+ }
170
+ return out;
171
+ }
172
+ case 3: { // Q4_1: d, m, then the same nibble split; zero-point carried as a minimum
173
+ var b3 = Math.min(Math.floor(u8.byteLength / 20), Math.ceil(count / 32));
174
+ for (b = 0; b < b3; b++) {
175
+ base = b * 20; d = f16(base); m = f16(base + 2);
176
+ for (i = 0; i < 32; i++) {
177
+ idx = b * 32 + i;
178
+ if (idx < count) {
179
+ q = (i < 16) ? (u8[base + 4 + i] & 0x0f) : (u8[base + 4 + (i - 16)] >> 4);
180
+ out[idx] = d * q + m;
181
+ }
182
+ }
183
+ }
184
+ return out;
185
+ }
186
+ case 6: { // Q5_0: d, qh(4 bytes = the 5th bit of all 32 elements), qs(16)
187
+ var b6 = Math.min(Math.floor(u8.byteLength / 22), Math.ceil(count / 32));
188
+ for (b = 0; b < b6; b++) {
189
+ base = b * 22; d = f16(base);
190
+ var qh6 = (u8[base + 2] | (u8[base + 3] << 8) | (u8[base + 4] << 16) |
191
+ (u8[base + 5] << 24)) >>> 0;
192
+ for (i = 0; i < 32; i++) {
193
+ idx = b * 32 + i;
194
+ if (idx < count) {
195
+ ql = (i < 16) ? (u8[base + 6 + i] & 0x0f) : (u8[base + 6 + (i - 16)] >> 4);
196
+ q = ql | (((qh6 >> i) & 1) << 4); // 5-bit, zero-point 16
197
+ out[idx] = d * (q - 16);
198
+ }
199
+ }
200
+ }
201
+ return out;
202
+ }
203
+ case 7: { // Q5_1: d, m, qh(4), qs(16)
204
+ var b7 = Math.min(Math.floor(u8.byteLength / 24), Math.ceil(count / 32));
205
+ for (b = 0; b < b7; b++) {
206
+ base = b * 24; d = f16(base); m = f16(base + 2);
207
+ var qh7 = (u8[base + 4] | (u8[base + 5] << 8) | (u8[base + 6] << 16) |
208
+ (u8[base + 7] << 24)) >>> 0;
209
+ for (i = 0; i < 32; i++) {
210
+ idx = b * 32 + i;
211
+ if (idx < count) {
212
+ ql = (i < 16) ? (u8[base + 8 + i] & 0x0f) : (u8[base + 8 + (i - 16)] >> 4);
213
+ q = ql | (((qh7 >> i) & 1) << 4);
214
+ out[idx] = d * q + m;
215
+ }
216
+ }
217
+ }
218
+ return out;
219
+ }
220
+ case 8: { // Q8_0: d, then 32 SIGNED 8-bit values — no nibble packing to get wrong
221
+ var b8 = Math.min(Math.floor(u8.byteLength / 34), Math.ceil(count / 32));
222
+ for (b = 0; b < b8; b++) {
223
+ base = b * 34; d = f16(base);
224
+ for (i = 0; i < 32; i++) {
225
+ idx = b * 32 + i;
226
+ if (idx < count) out[idx] = dv.getInt8(base + 2 + i) * d;
227
+ }
228
+ }
229
+ return out;
230
+ }
231
+ case 10: { // Q2_K: scales(16) qs(64) d(2) dmin(2) — 256 elems/block, 16 sub-blocks of 16
232
+ var b10 = Math.min(Math.floor(u8.byteLength / 84), Math.ceil(count / 256));
233
+ for (b = 0; b < b10; b++) {
234
+ base = b * 84; d = f16(base + 80);
235
+ var dmin10 = f16(base + 82);
236
+ for (var s10 = 0; s10 < 16; s10++) {
237
+ var sc10 = u8[base + s10], dl10 = d * (sc10 & 0x0f), ml10 = dmin10 * (sc10 >> 4);
238
+ var qb10 = base + 16 + (s10 >> 3) * 32 + (s10 & 1) * 16, sh10 = ((s10 >> 1) & 3) * 2;
239
+ for (j = 0; j < 16; j++) {
240
+ idx = b * 256 + s10 * 16 + j;
241
+ if (idx < count) out[idx] = dl10 * ((u8[qb10 + j] >> sh10) & 3) - ml10;
242
+ }
243
+ }
244
+ }
245
+ return out;
246
+ }
247
+ case 11: { // Q3_K: hmask(32) qs(64) scales(12) d(2) — 16 sub-blocks of 16
248
+ var b11 = Math.min(Math.floor(u8.byteLength / 110), Math.ceil(count / 256));
249
+ for (b = 0; b < b11; b++) {
250
+ base = b * 110; d = f16(base + 108);
251
+ var lsc = [], hsc = [];
252
+ for (i = 0; i < 16; i++) lsc.push((u8[base + 96 + (i & 7)] >> (((i >> 3) & 1) * 4)) & 0x0f);
253
+ for (i = 0; i < 16; i++) hsc.push((u8[base + 104 + (i & 3)] >> (((i >> 2) & 3) * 2)) & 3);
254
+ for (var s11 = 0; s11 < 16; s11++) {
255
+ var dl11 = d * ((lsc[s11] | (hsc[s11] << 4)) - 32);
256
+ var qb11 = base + 32 + (s11 >> 3) * 32 + (s11 & 1) * 16, qs11 = ((s11 >> 1) & 3) * 2;
257
+ var hb11 = base + (s11 & 1) * 16, hs11 = (s11 >> 1) & 7;
258
+ for (j = 0; j < 16; j++) {
259
+ idx = b * 256 + s11 * 16 + j;
260
+ if (idx < count) {
261
+ var ql11 = (u8[qb11 + j] >> qs11) & 3;
262
+ var qh11 = (u8[hb11 + j] >> hs11) & 1;
263
+ out[idx] = dl11 * (ql11 - ((qh11 ^ 1) << 2));
264
+ }
265
+ }
266
+ }
267
+ }
268
+ return out;
269
+ }
270
+ case 12: { // Q4_K: d(2) dmin(2) scales(12) qs(128) — 8 sub-blocks of 32
271
+ var b12 = Math.min(Math.floor(u8.byteLength / 144), Math.ceil(count / 256));
272
+ for (b = 0; b < b12; b++) {
273
+ base = b * 144; d = f16(base);
274
+ var dmin12 = f16(base + 2);
275
+ var sc12 = [], mn12 = [];
276
+ for (i = 0; i < 4; i++) {
277
+ sc12.push(u8[base + 4 + i] & 0x3f);
278
+ mn12.push(u8[base + 8 + i] & 0x3f);
279
+ }
280
+ for (i = 0; i < 4; i++) {
281
+ sc12.push((u8[base + 12 + i] & 0x0f) | ((u8[base + 4 + i] >> 2) & 0x30));
282
+ mn12.push((u8[base + 12 + i] >> 4) | ((u8[base + 8 + i] >> 2) & 0x30));
283
+ }
284
+ for (var k12 = 0; k12 < 8; k12++) {
285
+ var dl12 = d * sc12[k12], ml12 = dmin12 * mn12[k12];
286
+ var qb12 = base + 16 + (k12 >> 1) * 32, sh12 = (k12 & 1) * 4;
287
+ for (i = 0; i < 32; i++) {
288
+ idx = b * 256 + k12 * 32 + i;
289
+ if (idx < count) out[idx] = dl12 * ((u8[qb12 + i] >> sh12) & 0x0f) - ml12;
290
+ }
291
+ }
292
+ }
293
+ return out;
294
+ }
295
+ case 13: { // Q5_K: d(2) dmin(2) scales(12) qh(32) qs(128) — 8 sub-blocks of 32
296
+ var b13 = Math.min(Math.floor(u8.byteLength / 176), Math.ceil(count / 256));
297
+ for (b = 0; b < b13; b++) {
298
+ base = b * 176; d = f16(base);
299
+ var dmin13 = f16(base + 2);
300
+ var sc13 = [], mn13 = [];
301
+ for (i = 0; i < 4; i++) {
302
+ sc13.push(u8[base + 4 + i] & 0x3f);
303
+ mn13.push(u8[base + 8 + i] & 0x3f);
304
+ }
305
+ for (i = 0; i < 4; i++) {
306
+ sc13.push((u8[base + 12 + i] & 0x0f) | ((u8[base + 4 + i] >> 2) & 0x30));
307
+ mn13.push((u8[base + 12 + i] >> 4) | ((u8[base + 8 + i] >> 2) & 0x30));
308
+ }
309
+ for (var k13 = 0; k13 < 8; k13++) {
310
+ var dl13 = d * sc13[k13], ml13 = dmin13 * mn13[k13];
311
+ var qb13 = base + 48 + (k13 >> 1) * 32, sh13 = (k13 & 1) * 4;
312
+ for (i = 0; i < 32; i++) {
313
+ idx = b * 256 + k13 * 32 + i;
314
+ if (idx < count) {
315
+ ql = (u8[qb13 + i] >> sh13) & 0x0f;
316
+ qh = (u8[base + 16 + i] >> k13) & 1;
317
+ out[idx] = dl13 * (ql | (qh << 4)) - ml13;
318
+ }
319
+ }
320
+ }
321
+ }
322
+ return out;
323
+ }
324
+ case 14: { // Q6_K: ql(128) qh(64) scales(16 int8) d(f16 at 208); 16 sub-blocks of 16
325
+ var b14 = Math.min(Math.floor(u8.byteLength / 210), Math.ceil(count / 256));
326
+ for (b = 0; b < b14; b++) {
327
+ base = b * 210; d = f16(base + 208);
328
+ for (var s = 0; s < 16; s++) {
329
+ var dl = d * dv.getInt8(base + 192 + s);
330
+ var qlb = base + (s >> 3) * 64 + (s & 3) * 16, qlShift = ((s >> 2) & 1) * 4;
331
+ var qhb = base + 128 + (s >> 3) * 32 + (s & 1) * 16, qhShift = ((s >> 1) & 3) * 2;
332
+ for (j = 0; j < 16; j++) {
333
+ idx = b * 256 + s * 16 + j;
334
+ if (idx < count) {
335
+ ql = (u8[qlb + j] >> qlShift) & 0x0f;
336
+ qh = (u8[qhb + j] >> qhShift) & 0x03;
337
+ out[idx] = dl * ((ql | (qh << 4)) - 32); // 6-bit, zero-point 32
338
+ }
339
+ }
340
+ }
341
+ }
342
+ return out;
343
+ }
344
+ case 20: { // IQ4_NL: d, 16 nibble bytes; low nibble -> element 2j, high -> 2j+1
345
+ var b20 = Math.min(Math.floor(u8.byteLength / 18), Math.ceil(count / 32));
346
+ for (b = 0; b < b20; b++) {
347
+ base = b * 18; d = f16(base);
348
+ for (j = 0; j < 16; j++) {
349
+ q = u8[base + 2 + j];
350
+ var i0 = b * 32 + 2 * j, i1 = i0 + 1;
351
+ if (i0 < count) out[i0] = d * KVALUES_IQ4NL[q & 0x0f];
352
+ if (i1 < count) out[i1] = d * KVALUES_IQ4NL[q >> 4];
353
+ }
354
+ }
355
+ return out;
356
+ }
357
+ case 30: { // BF16: no block packing — one 16-bit word per element, direct
358
+ var n30 = Math.min(count, Math.floor(u8.byteLength / 2));
359
+ for (i = 0; i < n30; i++) out[i] = bf16ToF32(dv.getUint16(i * 2, true));
360
+ return out;
361
+ }
362
+ default:
363
+ return null;
364
+ }
365
+ }
366
+
367
+ function supportedTypes() {
368
+ var out = [];
369
+ for (var k in TYPE_BLOCK) if (TYPE_BLOCK.hasOwnProperty(k)) out.push(Number(k));
370
+ return out.sort(function (a, b) { return a - b; });
371
+ }
372
+
373
+ /* ---------------------------------------------------------------- tensor access */
374
+
375
+ function findKV(header, key) {
376
+ var kv = header.kv || [];
377
+ for (var i = 0; i < kv.length; i++) if (kv[i].key === key) return kv[i].value;
378
+ return undefined;
379
+ }
380
+
381
+ /* GGUF stores a tensor's offset relative to the data section, which llama.cpp pads up to
382
+ * `general.alignment` (default 32). Getting this wrong shifts every value by up to 31 bytes and
383
+ * produces plausible-looking garbage, so it is derived from the file's own KV rather than assumed.
384
+ * A cheap way for a test to confirm it: a real F32 embedding tensor must dequantise to small
385
+ * finite floats, not denormals or NaNs. */
386
+ function alignment(header) {
387
+ var a = findKV(header, "general.alignment");
388
+ return (typeof a === "number" && a > 0) ? a : 32;
389
+ }
390
+
391
+ function dataOffset(header) {
392
+ var a = alignment(header);
393
+ return Math.ceil(header.headerBytes / a) * a;
394
+ }
395
+
396
+ function tensorByteLength(t) {
397
+ var blk = TYPE_BLOCK[t.typeId];
398
+ if (!blk) return null;
399
+ var n = Number(t.params);
400
+ return Math.ceil(n / blk.e) * blk.b;
401
+ }
402
+
403
+ function findTensor(header, name) {
404
+ var ts = header.tensors || [];
405
+ for (var i = 0; i < ts.length; i++) if (ts[i].name === name) return ts[i];
406
+ return null;
407
+ }
408
+
409
+ /* Compact index of what a file holds — used by the UI to show which tensors are readable. */
410
+ function listTensors(header, nameFilter) {
411
+ var ts = header.tensors || [], out = [];
412
+ for (var i = 0; i < ts.length; i++) {
413
+ var t = ts[i];
414
+ if (nameFilter && t.name.indexOf(nameFilter) === -1) continue;
415
+ out.push({
416
+ name: t.name,
417
+ dims: t.dims.slice(),
418
+ typeId: t.typeId,
419
+ typeName: TYPE_NAME[t.typeId] || String(t.typeId),
420
+ params: Number(t.params),
421
+ byteLength: tensorByteLength(t),
422
+ supported: !!TYPE_BLOCK[t.typeId]
423
+ });
424
+ }
425
+ return out;
426
+ }
427
+
428
+ /* Read and dequantise one whole tensor. Returns null values when the type is unsupported (the
429
+ * caller reports that) — never a partially-guessed matrix. */
430
+ function readTensor(buf, header, name) {
431
+ var t = findTensor(header, name);
432
+ if (!t) return null;
433
+ var len = tensorByteLength(t);
434
+ var bytes = asBytes(buf);
435
+ var start = dataOffset(header) + Number(t.offset);
436
+ if (len === null) {
437
+ return { name: name, typeId: t.typeId, typeName: TYPE_NAME[t.typeId] || String(t.typeId),
438
+ dims: t.dims.slice(), count: Number(t.params), byteLength: null, values: null,
439
+ supported: false, dataStart: start };
440
+ }
441
+ if (start + len > bytes.byteLength) {
442
+ return { name: name, typeId: t.typeId, typeName: TYPE_NAME[t.typeId] || String(t.typeId),
443
+ dims: t.dims.slice(), count: Number(t.params), byteLength: len, values: null,
444
+ supported: !!TYPE_BLOCK[t.typeId], dataStart: start,
445
+ error: "tensor extends past the buffer (need " + (start + len - bytes.byteLength) +
446
+ " more bytes)" };
447
+ }
448
+ var slice = bytes.subarray(start, start + len);
449
+ var values = dequant(t.typeId, slice, Number(t.params));
450
+ return { name: name, typeId: t.typeId, typeName: TYPE_NAME[t.typeId] || String(t.typeId),
451
+ dims: t.dims.slice(), count: Number(t.params), byteLength: len, values: values,
452
+ supported: !!TYPE_BLOCK[t.typeId], dataStart: start };
453
+ }
454
+
455
+ /* ---------------------------------------------------------------- part 2: lossless HDC memory */
456
+
457
+ /* One dimension per stored bit: bit 1 -> +1, bit 0 -> -1, bit-packed so the vector occupies
458
+ * exactly the same bytes as the source. This is the archive's scheme and it is EXACT: it is a
459
+ * representation, not a compression, and the dimension grows with the data (N = bytes * 8). */
460
+ function packBipolar(u8) {
461
+ var bytes = asBytes(u8);
462
+ return { bytes: bytes.slice(0), dimensionBits: bytes.length * 8 };
463
+ }
464
+
465
+ function unpackBipolar(packed, nBytes) {
466
+ var src = asBytes(packed);
467
+ if (nBytes === undefined || nBytes === null) nBytes = src.length;
468
+ if (nBytes > src.length) throw new Error("unpackBipolar: " + nBytes + " bytes requested from " +
469
+ src.length + " packed");
470
+ return src.slice(0, nBytes);
471
+ }
472
+
473
+ /* The expanded form, for callers that want the literal +1/-1 vector (8x memory — which is exactly
474
+ * why the stored form stays bit-packed). */
475
+ function toBipolarVector(u8) {
476
+ var bytes = asBytes(u8), out = new Int8Array(bytes.length * 8), n = 0, j, b;
477
+ for (var i = 0; i < bytes.length; i++) {
478
+ b = bytes[i];
479
+ for (j = 0; j < 8; j++) out[n++] = (b & (1 << j)) ? 1 : -1;
480
+ }
481
+ return out;
482
+ }
483
+
484
+ function fromBipolarVector(vec) {
485
+ var out = new Uint8Array(Math.ceil(vec.length / 8));
486
+ for (var i = 0; i < vec.length; i++) if (vec[i] > 0) out[i >> 3] |= 1 << (i & 7);
487
+ return out;
488
+ }
489
+
490
+ /* Page a tensor's exact bytes into independently verifiable HDC pages. */
491
+ function storeLossless(buf, header, name, opts) {
492
+ opts = opts || {};
493
+ var pageBytes = opts.pageBytes || (1024 * 1024);
494
+ var t = findTensor(header, name);
495
+ if (!t) return null;
496
+ var len = tensorByteLength(t);
497
+ if (len === null) return { name: name, supported: false,
498
+ typeName: TYPE_NAME[t.typeId] || String(t.typeId) };
499
+ var all = asBytes(buf);
500
+ var start = dataOffset(header) + Number(t.offset);
501
+ // Uint8Array.slice truncates silently, which would digest a short tensor as if it were whole
502
+ if (start + len > all.byteLength) {
503
+ return { name: name, supported: false,
504
+ typeName: TYPE_NAME[t.typeId] || String(t.typeId),
505
+ error: "tensor extends past the buffer (need " + (start + len - all.byteLength) +
506
+ " more bytes)" };
507
+ }
508
+ var tensorBytes = all.slice(start, start + len);
509
+ var pages = [], pageIndex = 0;
510
+ for (var off = 0; off < tensorBytes.length; off += pageBytes) {
511
+ var chunk = tensorBytes.subarray(off, Math.min(off + pageBytes, tensorBytes.length));
512
+ var packed = packBipolar(chunk);
513
+ pages.push({ index: pageIndex++, byteLength: chunk.length,
514
+ dimensionBits: packed.dimensionBits,
515
+ digest: digestFnv1a64(chunk), bytes: packed.bytes });
516
+ }
517
+ return {
518
+ name: name, typeName: TYPE_NAME[t.typeId] || String(t.typeId), dims: t.dims.slice(),
519
+ supported: true, byteLength: tensorBytes.length, dimensionBits: tensorBytes.length * 8,
520
+ pageBytes: pageBytes, pageCount: pages.length, pages: pages,
521
+ digest: digestFnv1a64(tensorBytes),
522
+ // the honest accounting: the dimension equals the data, so this saves nothing by itself
523
+ exact: true, compressive: false,
524
+ ggufBytes: len
525
+ };
526
+ }
527
+
528
+ /* Rebuild the exact source bytes. Verifies each page digest and the whole-tensor digest, so a
529
+ * corrupted or mis-sized store fails loudly instead of yielding wrong weights. */
530
+ function restoreLossless(store) {
531
+ var total = 0, i;
532
+ for (i = 0; i < store.pages.length; i++) total += store.pages[i].byteLength;
533
+ var out = new Uint8Array(total), at = 0;
534
+ for (i = 0; i < store.pages.length; i++) {
535
+ var p = store.pages[i];
536
+ var chunk = unpackBipolar(p.bytes, p.byteLength);
537
+ if (p.digest && digestFnv1a64(chunk) !== p.digest) {
538
+ throw new Error("HDC page " + p.index + " failed its digest");
539
+ }
540
+ out.set(chunk, at); at += chunk.length;
541
+ }
542
+ if (store.digest && digestFnv1a64(out) !== store.digest) {
543
+ throw new Error("restored tensor failed its digest");
544
+ }
545
+ return out;
546
+ }
547
+
548
+ /* ---------------------------------------------------------------- part 3: ternary (BitNet b1.58) */
549
+
550
+ function meanAbs(values, from, to) {
551
+ var s = 0, i;
552
+ for (i = from; i < to; i++) s += Math.abs(values[i]);
553
+ return (to > from) ? s / (to - from) : 0;
554
+ }
555
+
556
+ /* W = s * T, T in {-1,0,+1}, s = absmean (arXiv 2402.17764). Two bit-planes, so 2 bits per weight
557
+ * plus one 4-byte scale per block. blockSize = 0 means a single scale for the whole array. */
558
+ function toTernary(values, opts) {
559
+ opts = opts || {};
560
+ var blockSize = opts.blockSize | 0;
561
+ var n = values.length;
562
+ var pos = new Uint8Array((n + 7) >> 3), neg = new Uint8Array((n + 7) >> 3);
563
+ var scales = [];
564
+ var step = (blockSize > 0) ? blockSize : n;
565
+ for (var s0 = 0; s0 < n; s0 += step) {
566
+ var end = Math.min(n, s0 + step);
567
+ var sc = meanAbs(values, s0, end);
568
+ if (!(sc > 0)) sc = 1; // an all-zero block would divide by zero; T is 0 there anyway
569
+ scales.push(sc);
570
+ for (var i = s0; i < end; i++) {
571
+ var t = Math.round(values[i] / sc);
572
+ if (t > 1) t = 1; else if (t < -1) t = -1;
573
+ if (t === 1) pos[i >> 3] |= 1 << (i & 7);
574
+ else if (t === -1) neg[i >> 3] |= 1 << (i & 7);
575
+ }
576
+ }
577
+ return {
578
+ pos: pos, neg: neg, count: n, blockSize: blockSize, scales: scales,
579
+ scale: scales.length === 1 ? scales[0] : 0,
580
+ // 2 packed bit-planes; the information content of one ternary symbol is log2(3)
581
+ perValueBits: 2,
582
+ entropyBits: Math.log(3) / Math.LN2,
583
+ bytes: ternaryBytes(n, blockSize),
584
+ zeros: countZeros(pos, neg, n)
585
+ };
586
+ }
587
+
588
+ function countZeros(pos, neg, n) {
589
+ var z = 0;
590
+ for (var i = 0; i < n; i++) {
591
+ var p = (pos[i >> 3] >> (i & 7)) & 1, m = (neg[i >> 3] >> (i & 7)) & 1;
592
+ if (!p && !m) z++;
593
+ }
594
+ return z;
595
+ }
596
+
597
+ /* Exact storage cost: two bit-planes plus one 4-byte scale per block. */
598
+ function ternaryBytes(n, blockSize) {
599
+ var blocks = blockSize > 0 ? Math.ceil(n / blockSize) : 1;
600
+ return 2 * Math.ceil(n / 8) + blocks * 4;
601
+ }
602
+
603
+ function fromTernary(t, blockSize) {
604
+ var out = new Float32Array(t.count);
605
+ var bs = blockSize === undefined || blockSize === null ? t.blockSize : blockSize;
606
+ var step = (bs > 0) ? bs : t.count;
607
+ for (var i = 0; i < t.count; i++) {
608
+ var sc = (t.scales && t.scales.length > 1) ? t.scales[Math.floor(i / step)] : t.scale;
609
+ if (sc === undefined) sc = (t.scales && t.scales.length) ? t.scales[0] : 1;
610
+ var p = (t.pos[i >> 3] >> (i & 7)) & 1, m = (t.neg[i >> 3] >> (i & 7)) & 1;
611
+ out[i] = p ? sc : (m ? -sc : 0);
612
+ }
613
+ return out;
614
+ }
615
+
616
+ /* Measured against the ORIGINAL values, so the page can report real error rather than a claim. */
617
+ function ternaryError(values, blockSize) {
618
+ var t = toTernary(values, { blockSize: blockSize });
619
+ var rec = fromTernary(t, blockSize);
620
+ var se = 0, fro = 0, froErr = 0, maxAbs = 0, i, d;
621
+ for (i = 0; i < values.length; i++) {
622
+ d = rec[i] - values[i];
623
+ se += d * d;
624
+ fro += values[i] * values[i];
625
+ froErr += d * d;
626
+ if (Math.abs(d) > maxAbs) maxAbs = Math.abs(d);
627
+ }
628
+ return {
629
+ count: values.length, blockSize: blockSize, perValueBits: 2,
630
+ rmse: Math.sqrt(se / values.length),
631
+ maxAbs: maxAbs,
632
+ // ||W - rec||_F / ||W||_F — a RELATIVE ERROR, never a reconstruction
633
+ relativeFrobenius: fro > 0 ? Math.sqrt(froErr / fro) : 0,
634
+ zerosFraction: t.zeros / values.length,
635
+ bytes: t.bytes
636
+ };
637
+ }
638
+
639
+ /* ---------------------------------------------------------------- part 4: the VSA linear layer */
640
+
641
+ /* Positional keys. Deterministic from the VSA's own seed, so reloading reproduces the same layer
642
+ * exactly — which is what lets the *stored* rows be rebuilt rather than persisted. */
643
+ function makeKeys(vsa, n) {
644
+ var keys = new Array(n);
645
+ for (var i = 0; i < n; i++) keys[i] = vsa.vector("wkey:" + i);
646
+ return keys;
647
+ }
648
+
649
+ function complement(v) {
650
+ var o = new Uint8Array(v.length);
651
+ for (var i = 0; i < v.length; i++) o[i] = (~v[i]) & 0xff;
652
+ return o;
653
+ }
654
+
655
+ /* sign( sum_i w[i] * key[i] ), evaluated dimension-wise.
656
+ *
657
+ * This is exactly what vsa.bundle's weighted majority computes: at dimension d the bit is set iff
658
+ * the weight summed over the keys whose bit d is SET is at least half the total weight, and
659
+ * because total = S+ + S- that is precisely S+ >= S-, i.e. sign(sum_i w[i]*key_i[d]). Negative
660
+ * weights are therefore fine.
661
+ *
662
+ * Two guards are needed. vsa.bundle returns an all-clear vector when the weights sum to <= 0
663
+ * instead of the signs, so a negative-sum encoding is computed on negated weights and the result
664
+ * complemented (negating the weights negates the vector exactly). And an exactly-zero sum makes
665
+ * the half-threshold zero, which bundle's early return also short-circuits, so that single case is
666
+ * broken by a 1e-9 tie-break on one weight — it can only change dimensions whose sum is within
667
+ * 1e-9 of a tie. */
668
+ function signBundle(vsa, w, keys) {
669
+ var n = Math.min(w.length, keys.length);
670
+ var sum = 0, i;
671
+ for (i = 0; i < n; i++) sum += w[i];
672
+ var list = keys.slice(0, n);
673
+ var weights = new Float64Array(n);
674
+ for (i = 0; i < n; i++) weights[i] = w[i];
675
+
676
+ if (sum === 0) {
677
+ if (n === 0) return new Uint8Array(vsa.D >>> 3);
678
+ weights[n - 1] += 1e-9;
679
+ return vsa.bundle(list, weights);
680
+ }
681
+ if (sum > 0) return vsa.bundle(list, weights);
682
+ for (i = 0; i < n; i++) weights[i] = -weights[i];
683
+ return complement(vsa.bundle(list, weights));
684
+ }
685
+
686
+ /* Encode a real-valued input vector as one hypervector. */
687
+ function encodeVector(vsa, x, keys) { return signBundle(vsa, x, keys); }
688
+
689
+ /* Encode one hypervector per output row of a flat row-major matrix, using the SAME keys. */
690
+ function encodeRows(vsa, values, cols, keys) {
691
+ var rows = Math.floor(values.length / cols);
692
+ var out = new Array(rows), row = new Float64Array(cols);
693
+ for (var r = 0; r < rows; r++) {
694
+ for (var c = 0; c < cols; c++) row[c] = values[r * cols + c];
695
+ out[r] = signBundle(vsa, row, keys);
696
+ }
697
+ return out;
698
+ }
699
+
700
+ /* The layer's own arithmetic, performed by the hypervector algebra. Scores are similarities in
701
+ * [-1,1]: their RANKING corresponds to the linear layer's output ranking, which is what a decode
702
+ * consumes. Magnitudes are not preserved, and nothing here claims they are. */
703
+ function linearVSA(vsa, xhv, rowHvs) {
704
+ var out = new Float32Array(rowHvs.length);
705
+ for (var j = 0; j < rowHvs.length; j++) out[j] = vsa.similarity(xhv, rowHvs[j]);
706
+ return out;
707
+ }
708
+
709
+ /* The ground truth to measure the above against: y_j = sum_i W[j][i] * x[i]. */
710
+ function exactLinear(x, values, cols) {
711
+ var rows = Math.floor(values.length / cols);
712
+ var out = new Float32Array(rows);
713
+ for (var r = 0; r < rows; r++) {
714
+ var s = 0, off = r * cols;
715
+ for (var c = 0; c < cols; c++) s += values[off + c] * x[c];
716
+ out[r] = s;
717
+ }
718
+ return out;
719
+ }
720
+
721
+ /* Rank agreement between two score vectors — the honest way to compare the VSA layer with the
722
+ * exact one. 1.0 means identical order. */
723
+ function rankAgreement(a, b) {
724
+ var ia = order(a), ib = order(b), same = 0, i;
725
+ var n = Math.min(ia.length, ib.length);
726
+ for (i = 0; i < n; i++) if (ia[i] === ib[i]) same++;
727
+ return n ? same / n : 0;
728
+ }
729
+
730
+ /* Top-k agreement: the fraction of the k highest-ranked rows the two score vectors agree on.
731
+ * This is the measurement that matters for a decode (which row wins); full-order agreement is
732
+ * dominated by the tail and understates a layer that ranks its top candidates correctly. */
733
+ function topKAgreement(a, b, k) {
734
+ var ka = order(a).slice(0, k), kb = order(b).slice(0, k);
735
+ var set = {}, hits = 0, i;
736
+ for (i = 0; i < kb.length; i++) set[kb[i]] = true;
737
+ for (i = 0; i < ka.length; i++) if (set[ka[i]]) hits++;
738
+ return ka.length ? hits / ka.length : 0;
739
+ }
740
+
741
+ function argmax(v) {
742
+ var best = -Infinity, bi = -1;
743
+ for (var i = 0; i < v.length; i++) if (v[i] > best) { best = v[i]; bi = i; }
744
+ return bi;
745
+ }
746
+
747
+ function order(v) {
748
+ var idx = new Array(v.length);
749
+ for (var i = 0; i < v.length; i++) idx[i] = i;
750
+ idx.sort(function (x, y) { return v[y] - v[x]; });
751
+ return idx;
752
+ }
753
+
754
+ var api = {
755
+ // part 1
756
+ dequant: dequant, f16ToF32: f16ToF32, bf16ToF32: bf16ToF32, supportedTypes: supportedTypes,
757
+ listTensors: listTensors, readTensor: readTensor, findTensor: findTensor,
758
+ tensorByteLength: tensorByteLength, dataOffset: dataOffset, alignment: alignment,
759
+ TYPE_BLOCK: TYPE_BLOCK, TYPE_NAME: TYPE_NAME, KVALUES_IQ4NL: KVALUES_IQ4NL,
760
+ // part 2
761
+ packBipolar: packBipolar, unpackBipolar: unpackBipolar,
762
+ toBipolarVector: toBipolarVector, fromBipolarVector: fromBipolarVector,
763
+ storeLossless: storeLossless, restoreLossless: restoreLossless,
764
+ // part 3
765
+ toTernary: toTernary, fromTernary: fromTernary, ternaryError: ternaryError,
766
+ ternaryBytes: ternaryBytes,
767
+ // part 4
768
+ makeKeys: makeKeys, encodeVector: encodeVector, encodeRows: encodeRows,
769
+ linearVSA: linearVSA, exactLinear: exactLinear, rankAgreement: rankAgreement,
770
+ topKAgreement: topKAgreement, argmax: argmax, order: order,
771
+ // misc
772
+ fmtBytes: fmtBytes, digestFnv1a64: digestFnv1a64
773
+ };
774
+
775
+ if (typeof module !== "undefined" && module.exports) module.exports = api;
776
+ root.HDCWeights = api;
777
+ })(typeof globalThis !== "undefined" ? globalThis : this);