AEUPH commited on
Commit
cdff0fa
·
verified ·
1 Parent(s): 7054026

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

Browse files
Files changed (1) hide show
  1. gguf.js +300 -0
gguf.js ADDED
@@ -0,0 +1,300 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* GGUF reader for the HDC engine — browser-native, no dependencies, no server.
2
+ *
3
+ * Same container spec as templates/static/gguf/gguf.js (GGUF v1/v2/v3; v1 uses 32-bit KV
4
+ * string lengths, v2+ use 64-bit) and the same type tables, which mirror
5
+ * templates/gguf_util.py so both implementations agree on every file.
6
+ *
7
+ * What this copy adds, and why the HDC engine needs its own reader:
8
+ *
9
+ * 1. REAL VOCABULARIES. The metadata block carries tokenizer.ggml.tokens — the model's own
10
+ * vocabulary (512 entries for stories260K, 151,936 for Qwen2.5-0.5B). The toolkit reader
11
+ * truncates every array to 16 entries to bound memory, which is right for an inspector
12
+ * and useless for generation. Here, arrays named in `fullArrayKeys` are kept in full.
13
+ *
14
+ * 2. BOUNDED PREFIXES. A page that streams a multi-GB model over HTTP only ever holds a
15
+ * prefix of it, so EOF can arrive in the middle of the tensor index. That is not an
16
+ * error: the metadata block precedes the index, so all KVs are already parsed and the
17
+ * result is returned with truncated:true.
18
+ *
19
+ * 3. ONE DECODER. 151,936 short strings decoder-per-string is pathologically slow; a single
20
+ * TextDecoder instance is reused for every string in the file.
21
+ */
22
+ (function (root) {
23
+ "use strict";
24
+
25
+ const MAGIC = 0x46554747; // "GGUF" read as a little-endian u32 (0x47,0x47,0x55,0x46)
26
+
27
+ const GGML_TYPES = {
28
+ 0: "F32", 1: "F16", 2: "Q4_0", 3: "Q4_1", 6: "Q5_0", 7: "Q5_1",
29
+ 8: "Q8_0", 9: "Q8_1", 10: "Q2_K", 11: "Q3_K", 12: "Q4_K", 13: "Q5_K",
30
+ 14: "Q6_K", 15: "Q8_K", 16: "IQ2_XXS", 17: "IQ2_XS", 18: "IQ3_XXS",
31
+ 19: "IQ1_S", 20: "IQ4_NL", 21: "IQ3_S", 22: "IQ2_S", 23: "IQ4_XS",
32
+ 24: "I8", 25: "I16", 26: "I32", 27: "I64", 28: "F64", 29: "IQ1_M",
33
+ 30: "BF16", 34: "TQ1_0", 35: "TQ2_0", 39: "MXFP4"
34
+ };
35
+
36
+ const BITS = {
37
+ F32: 32, F16: 16, BF16: 16, F64: 64,
38
+ "Q8_0": 8.5, "Q8_1": 8.5, I8: 8, I16: 16, I32: 32, I64: 64,
39
+ "Q6_K": 6.5625, "Q5_K": 5.5, "Q5_0": 5.5, "Q5_1": 6.0,
40
+ "Q4_K": 4.5, "Q4_0": 4.5, "Q4_1": 5.0, "IQ4_NL": 4.5, "IQ4_XS": 4.25,
41
+ "Q3_K": 3.4375, "IQ3_XXS": 3.06, "IQ3_S": 3.44,
42
+ "Q2_K": 2.5625, "IQ2_XXS": 2.06, "IQ2_XS": 2.31, "IQ2_S": 2.5,
43
+ "IQ1_S": 1.56, "IQ1_M": 1.75, "TQ1_0": 1.69, "TQ2_0": 2.06
44
+ };
45
+
46
+ const T_NAME = { 0: "u8", 1: "i8", 2: "u16", 3: "i16", 4: "u32", 5: "i32", 6: "f32",
47
+ 7: "bool", 8: "string", 9: "array", 10: "u64", 11: "i64", 12: "f64" };
48
+
49
+ // Arrays these keys name are the ones generation actually consumes: keep them whole.
50
+ const DEFAULT_FULL = ["tokenizer.ggml.tokens", "tokenizer.ggml.merges",
51
+ "tokenizer.ggml.token_type", "tokenizer.ggml.scores"];
52
+
53
+ const MAX_ARRAY_KEEP = 16; // bound memory on unrelated big arrays, as the toolkit does
54
+ const MAX_FULL_ITEMS = 400000;
55
+
56
+ // tokenizer.ggml.token_type values (llama.cpp)
57
+ const TOKEN_TYPE = { 1: "NORMAL", 2: "UNKNOWN", 3: "CONTROL", 4: "USER_DEFINED",
58
+ 5: "UNUSED", 6: "BYTE" };
59
+
60
+ const MARKER = /^\u2026?\.{3} (\d+) items total$/;
61
+
62
+ class GgufError extends Error {}
63
+
64
+ class Reader {
65
+ constructor(buffer, opts) {
66
+ this.view = new DataView(buffer);
67
+ this.bytes = new Uint8Array(buffer);
68
+ this.pos = 0;
69
+ this.sizeBytes = buffer.byteLength;
70
+ this.decoder = typeof TextDecoder !== "undefined" ? new TextDecoder("utf-8") : null;
71
+ this.maxArrayItems = opts.maxArrayItems;
72
+ this.maxFullItems = opts.maxFullItems;
73
+ // smallest legal header: magic 4 + version 4 + tensor_count 8 + kv_count 8
74
+ if (buffer.byteLength < 24) throw new GgufError("buffer too small for a GGUF header");
75
+ this.magic = this.view.getUint32(0, true);
76
+ if (this.magic !== MAGIC) throw new GgufError("not a GGUF file (bad magic)");
77
+ this.pos = 4;
78
+ this.version = this.u32();
79
+ if (this.version !== 1 && this.version !== 2 && this.version !== 3) {
80
+ throw new GgufError("unsupported GGUF version " + this.version);
81
+ }
82
+ this.nTensors = this.u64();
83
+ this.nKv = this.u64();
84
+ }
85
+ need(n) {
86
+ if (this.pos + n > this.sizeBytes) throw new GgufError("unexpected end of file");
87
+ }
88
+ u8() { this.need(1); return this.view.getUint8(this.pos++); }
89
+ i8() { this.need(1); return this.view.getInt8(this.pos++); }
90
+ u16() { this.need(2); const v = this.view.getUint16(this.pos, true); this.pos += 2; return v; }
91
+ i16() { this.need(2); const v = this.view.getInt16(this.pos, true); this.pos += 2; return v; }
92
+ u32() { this.need(4); const v = this.view.getUint32(this.pos, true); this.pos += 4; return v; }
93
+ i32() { this.need(4); const v = this.view.getInt32(this.pos, true); this.pos += 4; return v; }
94
+ f32() { this.need(4); const v = this.view.getFloat32(this.pos, true); this.pos += 4; return v; }
95
+ f64() { this.need(8); const v = this.view.getFloat64(this.pos, true); this.pos += 8; return v; }
96
+ u64() {
97
+ this.need(8);
98
+ const v = this.view.getBigUint64(this.pos, true);
99
+ this.pos += 8;
100
+ return v <= BigInt(Number.MAX_SAFE_INTEGER) ? Number(v) : v.toString();
101
+ }
102
+ i64() {
103
+ this.need(8);
104
+ const v = this.view.getBigInt64(this.pos, true);
105
+ this.pos += 8;
106
+ return v <= BigInt(Number.MAX_SAFE_INTEGER) && v >= BigInt(Number.MIN_SAFE_INTEGER)
107
+ ? Number(v) : v.toString();
108
+ }
109
+ bool() { return this.u8() !== 0; }
110
+ str() {
111
+ const len = this.version === 1 ? this.u32() : this.u64();
112
+ const n = Number(len);
113
+ this.need(n);
114
+ const at = this.pos;
115
+ this.pos += n;
116
+ const slice = this.bytes.subarray(at, at + n);
117
+ if (this.decoder) return this.decoder.decode(slice);
118
+ let s = "";
119
+ for (let i = 0; i < slice.length; i++) s += String.fromCharCode(slice[i]);
120
+ return s;
121
+ }
122
+ /* mode: "keep" -> at most maxArrayItems entries, then a "... N items total" marker
123
+ * "full" -> every entry (up to maxFullItems), for the tokenizer arrays
124
+ * "drop" -> nested array inside an array: consume it, keep only a marker */
125
+ value(type, mode) {
126
+ switch (type) {
127
+ case 0: return this.u8();
128
+ case 1: return this.i8();
129
+ case 2: return this.u16();
130
+ case 3: return this.i16();
131
+ case 4: return this.u32();
132
+ case 5: return this.i32();
133
+ case 6: return this.f32();
134
+ case 7: return this.bool();
135
+ case 8: return this.str();
136
+ case 9: {
137
+ const elem = this.u32();
138
+ const count = Number(this.u64());
139
+ if (mode === "drop") {
140
+ for (let i = 0; i < count; i++) this.value(elem, "drop");
141
+ return "<" + count + " items>";
142
+ }
143
+ const limit = Math.min(count, mode === "full" ? this.maxFullItems : this.maxArrayItems);
144
+ const vals = new Array(limit);
145
+ for (let i = 0; i < count; i++) {
146
+ const v = this.value(elem, "drop");
147
+ if (i < limit) vals[i] = v;
148
+ }
149
+ if (count > limit) vals.push("... " + count + " items total");
150
+ return vals;
151
+ }
152
+ case 10: return this.u64();
153
+ case 11: return this.i64();
154
+ case 12: return this.f64();
155
+ default: throw new GgufError("unknown metadata type " + type);
156
+ }
157
+ }
158
+ }
159
+
160
+ function wantFull(key, fullArrayKeys) {
161
+ for (let i = 0; i < fullArrayKeys.length; i++) {
162
+ const k = fullArrayKeys[i];
163
+ if (k === key) return true;
164
+ if (k.charAt(k.length - 1) === "*" && key.indexOf(k.slice(0, -1)) === 0) return true;
165
+ }
166
+ return false;
167
+ }
168
+
169
+ function parse(buffer, opts) {
170
+ opts = opts || {};
171
+ const maxArrayItems = opts.maxArrayItems === undefined ? MAX_ARRAY_KEEP : opts.maxArrayItems;
172
+ const maxFullItems = opts.maxFullItems === undefined ? MAX_FULL_ITEMS : opts.maxFullItems;
173
+ const fullArrayKeys = opts.fullArrayKeys || DEFAULT_FULL;
174
+ const metadataOnly = !!opts.metadataOnly;
175
+
176
+ const r = new Reader(buffer, { maxArrayItems: maxArrayItems, maxFullItems: maxFullItems });
177
+
178
+ // The metadata block comes first, so a prefix stream parses it completely. A truncation
179
+ // *inside* the KV block is genuinely unusable and is allowed to throw.
180
+ const kv = [];
181
+ let declared = {};
182
+ for (let i = 0; i < r.nKv; i++) {
183
+ const key = r.str();
184
+ const type = r.u32();
185
+ const mode = wantFull(key, fullArrayKeys) ? "full" : "keep";
186
+ const value = r.value(type, mode);
187
+ kv.push({ key: key, type: T_NAME[type] || String(type), value: value });
188
+ if (Array.isArray(value)) {
189
+ const m = typeof value[value.length - 1] === "string" ? MARKER.exec(value[value.length - 1]) : null;
190
+ if (m) declared[key] = Number(m[1]);
191
+ }
192
+ }
193
+
194
+ const tensors = [];
195
+ let truncated = false;
196
+ if (!metadataOnly) {
197
+ for (let i = 0; i < r.nTensors; i++) {
198
+ try {
199
+ const name = r.str();
200
+ const nd = r.u32();
201
+ const dims = [];
202
+ for (let d = 0; d < nd; d++) dims.push(r.u64());
203
+ const typeId = r.u32();
204
+ const offset = r.u64();
205
+ let params = 1;
206
+ for (let j = 0; j < dims.length; j++) params *= Number(dims[j]);
207
+ tensors.push({ name: name, dims: dims, type: GGML_TYPES[typeId] || String(typeId),
208
+ typeId: typeId, offset: offset, params: params });
209
+ } catch (e) {
210
+ if (!(e instanceof GgufError)) throw e;
211
+ truncated = true; // a prefix buffer ends mid-index; the KVs above are already good
212
+ break;
213
+ }
214
+ }
215
+ }
216
+
217
+ return { version: r.version, nTensors: r.nTensors, nKv: r.nKv, kv: kv, tensors: tensors,
218
+ sizeBytes: r.sizeBytes, truncated: truncated, headerBytes: r.pos,
219
+ declaredArraySizes: declared };
220
+ }
221
+
222
+ /* Pull a string/number array out of a KV list, reporting the declared size when the reader
223
+ * had to truncate it so the caller can tell "512 tokens" from "first 16 of 151,936". */
224
+ function arrayOf(kvValue) {
225
+ if (!Array.isArray(kvValue)) return { items: [], declared: 0, truncated: false };
226
+ const last = kvValue.length ? kvValue[kvValue.length - 1] : null;
227
+ const m = typeof last === "string" ? MARKER.exec(last) : null;
228
+ if (m) return { items: kvValue.slice(0, kvValue.length - 1), declared: Number(m[1]), truncated: true };
229
+ return { items: kvValue, declared: kvValue.length, truncated: false };
230
+ }
231
+
232
+ function vocab(header) {
233
+ const d = {};
234
+ for (let i = 0; i < header.kv.length; i++) d[header.kv[i].key] = header.kv[i].value;
235
+ const toks = arrayOf(d["tokenizer.ggml.tokens"]);
236
+ const merg = arrayOf(d["tokenizer.ggml.merges"]);
237
+ const ttyp = arrayOf(d["tokenizer.ggml.token_type"]);
238
+ const scor = arrayOf(d["tokenizer.ggml.scores"]);
239
+ const num = function (k) { const v = d[k]; return typeof v === "number" ? v : null; };
240
+ const str = function (k) { const v = d[k]; return typeof v === "string" ? v : null; };
241
+ return {
242
+ tokens: toks.items,
243
+ merges: merg.items,
244
+ tokenTypes: ttyp.items,
245
+ scores: scor.items,
246
+ model: str("tokenizer.ggml.model"),
247
+ pre: str("tokenizer.ggml.pre"),
248
+ architecture: str("general.architecture"),
249
+ name: str("general.name"),
250
+ bosId: num("tokenizer.ggml.bos_token_id"),
251
+ eosId: num("tokenizer.ggml.eos_token_id"),
252
+ unkId: num("tokenizer.ggml.unknown_token_id"),
253
+ padId: num("tokenizer.ggml.padding_token_id"),
254
+ addBos: d["tokenizer.ggml.add_bos_token"] === true,
255
+ vocabSize: toks.items.length,
256
+ declaredVocabSize: toks.declared,
257
+ mergeCount: merg.items.length,
258
+ declaredMergeCount: merg.declared,
259
+ truncated: toks.truncated || merg.truncated
260
+ };
261
+ }
262
+
263
+ /* Human-readable token kinds for a handful of ids — used by the UI to show that the
264
+ * vocabulary really came from the model (BPE pieces, byte fallbacks, control tokens). */
265
+ function tokenKind(v, id) {
266
+ const t = v && v.tokenTypes ? v.tokenTypes[id] : undefined;
267
+ return TOKEN_TYPE[t] || (typeof t === "number" ? String(t) : "?");
268
+ }
269
+
270
+ function quantHistogram(tensors) {
271
+ const h = {};
272
+ for (let i = 0; i < tensors.length; i++) h[tensors[i].type] = (h[tensors[i].type] || 0) + 1;
273
+ return Object.entries(h).sort(function (a, b) { return b[1] - a[1]; });
274
+ }
275
+
276
+ function fmtBytes(n) {
277
+ if (n === null || n === undefined) return "?";
278
+ const units = ["B", "KB", "MB", "GB", "TB"];
279
+ let i = 0, v = n;
280
+ while (v >= 1024 && i < units.length - 1) { v /= 1024; i++; }
281
+ return (i === 0 ? v.toFixed(0) : v.toFixed(2)) + " " + units[i];
282
+ }
283
+
284
+ function estimateSizes(nParams, overhead) {
285
+ overhead = overhead || 0;
286
+ return Object.entries(BITS)
287
+ .map(function (e) {
288
+ return { quant: e[0], bits: e[1], bytes: Math.round(nParams * e[1] / 8 * (1 + overhead)) };
289
+ })
290
+ .sort(function (a, b) { return a.bits - b.bits; });
291
+ }
292
+
293
+ const api = { parse: parse, vocab: vocab, arrayOf: arrayOf, tokenKind: tokenKind,
294
+ quantHistogram: quantHistogram, fmtBytes: fmtBytes, estimateSizes: estimateSizes,
295
+ GGML_TYPES: GGML_TYPES, BITS: BITS, T_NAME: T_NAME, TOKEN_TYPE: TOKEN_TYPE,
296
+ DEFAULT_FULL: DEFAULT_FULL, GgufError: GgufError };
297
+
298
+ if (typeof module !== "undefined" && module.exports) module.exports = api;
299
+ root.HDCGGUF = api;
300
+ })(typeof globalThis !== "undefined" ? globalThis : this);