DMCRA Lab: elastic level-set quantisation over real GGUF weights, measured end to end
Browse files- 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);
|