@seanhogg/builderforce-memory-engine 2026.6.30 → 2026.6.34

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (90) hide show
  1. package/README.md +2 -0
  2. package/dist/bench/harness.d.ts +37 -0
  3. package/dist/bench/harness.d.ts.map +1 -0
  4. package/dist/bench/harness.js +171 -0
  5. package/dist/bench/harness.js.map +1 -0
  6. package/dist/bench/index.d.ts +11 -0
  7. package/dist/bench/index.d.ts.map +1 -0
  8. package/dist/bench/index.js +10 -0
  9. package/dist/bench/index.js.map +1 -0
  10. package/dist/bench/metrics.d.ts +36 -0
  11. package/dist/bench/metrics.d.ts.map +1 -0
  12. package/dist/bench/metrics.js +83 -0
  13. package/dist/bench/metrics.js.map +1 -0
  14. package/dist/bench/types.d.ts +119 -0
  15. package/dist/bench/types.d.ts.map +1 -0
  16. package/dist/bench/types.js +16 -0
  17. package/dist/bench/types.js.map +1 -0
  18. package/dist/export/gguf.d.ts +23 -0
  19. package/dist/export/gguf.d.ts.map +1 -0
  20. package/dist/export/gguf.js +158 -0
  21. package/dist/export/gguf.js.map +1 -0
  22. package/dist/export/hf.d.ts +32 -0
  23. package/dist/export/hf.d.ts.map +1 -0
  24. package/dist/export/hf.js +138 -0
  25. package/dist/export/hf.js.map +1 -0
  26. package/dist/export/index.d.ts +57 -0
  27. package/dist/export/index.d.ts.map +1 -0
  28. package/dist/export/index.js +85 -0
  29. package/dist/export/index.js.map +1 -0
  30. package/dist/export/onnx.d.ts +32 -0
  31. package/dist/export/onnx.d.ts.map +1 -0
  32. package/dist/export/onnx.js +265 -0
  33. package/dist/export/onnx.js.map +1 -0
  34. package/dist/export/protobuf.d.ts +34 -0
  35. package/dist/export/protobuf.d.ts.map +1 -0
  36. package/dist/export/protobuf.js +77 -0
  37. package/dist/export/protobuf.js.map +1 -0
  38. package/dist/export/safetensors.d.ts +22 -0
  39. package/dist/export/safetensors.d.ts.map +1 -0
  40. package/dist/export/safetensors.js +66 -0
  41. package/dist/export/safetensors.js.map +1 -0
  42. package/dist/export/tensors.d.ts +51 -0
  43. package/dist/export/tensors.d.ts.map +1 -0
  44. package/dist/export/tensors.js +76 -0
  45. package/dist/export/tensors.js.map +1 -0
  46. package/dist/index.d.ts +9 -1
  47. package/dist/index.d.ts.map +1 -1
  48. package/dist/index.js +8 -1
  49. package/dist/index.js.map +1 -1
  50. package/dist/kernels/selective_scan.d.ts.map +1 -1
  51. package/dist/kernels/selective_scan.js +7 -3
  52. package/dist/kernels/selective_scan.js.map +1 -1
  53. package/dist/lm/evermind_lm.d.ts +17 -1
  54. package/dist/lm/evermind_lm.d.ts.map +1 -1
  55. package/dist/lm/evermind_lm.js +49 -5
  56. package/dist/lm/evermind_lm.js.map +1 -1
  57. package/dist/model/mamba_model.d.ts.map +1 -1
  58. package/dist/model/mamba_model.js +9 -1
  59. package/dist/model/mamba_model.js.map +1 -1
  60. package/dist/utils/crc32.d.ts +37 -0
  61. package/dist/utils/crc32.d.ts.map +1 -0
  62. package/dist/utils/crc32.js +70 -0
  63. package/dist/utils/crc32.js.map +1 -0
  64. package/dist/utils/delta.d.ts +37 -0
  65. package/dist/utils/delta.d.ts.map +1 -0
  66. package/dist/utils/delta.js +108 -0
  67. package/dist/utils/delta.js.map +1 -0
  68. package/dist/utils/gpu_utils.d.ts +21 -0
  69. package/dist/utils/gpu_utils.d.ts.map +1 -1
  70. package/dist/utils/gpu_utils.js +64 -5
  71. package/dist/utils/gpu_utils.js.map +1 -1
  72. package/package.json +1 -1
  73. package/src/bench/harness.ts +219 -0
  74. package/src/bench/index.ts +37 -0
  75. package/src/bench/metrics.ts +98 -0
  76. package/src/bench/types.ts +121 -0
  77. package/src/export/gguf.ts +168 -0
  78. package/src/export/hf.ts +155 -0
  79. package/src/export/index.ts +128 -0
  80. package/src/export/onnx.ts +331 -0
  81. package/src/export/protobuf.ts +86 -0
  82. package/src/export/safetensors.ts +77 -0
  83. package/src/export/tensors.ts +107 -0
  84. package/src/index.ts +63 -0
  85. package/src/kernels/selective_scan.ts +7 -3
  86. package/src/lm/evermind_lm.ts +53 -8
  87. package/src/model/mamba_model.ts +9 -1
  88. package/src/utils/crc32.ts +82 -0
  89. package/src/utils/delta.ts +117 -0
  90. package/src/utils/gpu_utils.ts +60 -5
@@ -0,0 +1,70 @@
1
+ /**
2
+ * utils/crc32.ts — checkpoint integrity (CRC-32) + a backward-compatible trailer.
3
+ *
4
+ * Checkpoints had no integrity check: a truncated or bit-rotted `.bin` would
5
+ * either throw deep in the parser or, worse, load corrupt weights silently. This
6
+ * adds a standard IEEE CRC-32 and a self-describing trailer that callers append
7
+ * on export and verify on load.
8
+ *
9
+ * The trailer is appended AFTER the existing payload as `[magic u32][crc u32]`
10
+ * (8 bytes, little-endian). Because every checkpoint reader consumes a
11
+ * length-bounded payload (header declares element counts) and ignores trailing
12
+ * bytes, an old reader still loads a trailer-bearing file — the trailer is
13
+ * purely additive. The CRC covers the payload bytes only (everything before the
14
+ * trailer).
15
+ */
16
+ /** Trailer sentinel: 'EVCR' (Evermind CRc), little-endian uint32. */
17
+ export const CRC_TRAILER_MAGIC = 0x52435645; // bytes 45 56 43 52 = "EVCR"
18
+ const TRAILER_BYTES = 8;
19
+ /** Precomputed CRC-32 (IEEE 802.3, reflected) lookup table. */
20
+ const CRC_TABLE = (() => {
21
+ const t = new Uint32Array(256);
22
+ for (let n = 0; n < 256; n++) {
23
+ let c = n;
24
+ for (let k = 0; k < 8; k++)
25
+ c = c & 1 ? 0xedb88320 ^ (c >>> 1) : c >>> 1;
26
+ t[n] = c >>> 0;
27
+ }
28
+ return t;
29
+ })();
30
+ /** IEEE CRC-32 of a byte buffer, returned as an unsigned 32-bit number. */
31
+ export function crc32(bytes) {
32
+ let crc = 0xffffffff;
33
+ for (let i = 0; i < bytes.length; i++) {
34
+ crc = CRC_TABLE[(crc ^ bytes[i]) & 0xff] ^ (crc >>> 8);
35
+ }
36
+ return (crc ^ 0xffffffff) >>> 0;
37
+ }
38
+ /** Return a new ArrayBuffer = `payload` followed by the CRC trailer. */
39
+ export function appendCrcTrailer(payload) {
40
+ const body = new Uint8Array(payload);
41
+ const out = new ArrayBuffer(body.length + TRAILER_BYTES);
42
+ const u8 = new Uint8Array(out);
43
+ u8.set(body, 0);
44
+ const view = new DataView(out);
45
+ view.setUint32(body.length, CRC_TRAILER_MAGIC, true);
46
+ view.setUint32(body.length + 4, crc32(body), true);
47
+ return out;
48
+ }
49
+ /**
50
+ * Inspect a buffer for a CRC trailer. When present, verifies the CRC over the
51
+ * preceding payload. When absent (legacy file), reports `ok: true` and returns
52
+ * the buffer unchanged — so verification is opt-in by the writer, never a
53
+ * hard requirement that would reject pre-CRC checkpoints.
54
+ */
55
+ export function verifyCrcTrailer(buffer) {
56
+ if (buffer.byteLength < TRAILER_BYTES) {
57
+ return { hasTrailer: false, ok: true, body: buffer };
58
+ }
59
+ const view = new DataView(buffer);
60
+ const bodyLen = buffer.byteLength - TRAILER_BYTES;
61
+ const magic = view.getUint32(bodyLen, true);
62
+ if (magic !== CRC_TRAILER_MAGIC) {
63
+ return { hasTrailer: false, ok: true, body: buffer };
64
+ }
65
+ const stored = view.getUint32(bodyLen + 4, true);
66
+ const body = buffer.slice(0, bodyLen);
67
+ const ok = crc32(new Uint8Array(body)) === stored;
68
+ return { hasTrailer: true, ok, body };
69
+ }
70
+ //# sourceMappingURL=crc32.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"crc32.js","sourceRoot":"","sources":["../../src/utils/crc32.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,37 @@
1
+ /**
2
+ * utils/delta.ts — sparse, row-granular weight deltas for online checkpoints.
3
+ *
4
+ * Online adaptation (WSLA) restricts the trainable set to a few
5
+ * selective-projection ROWS, yet each `adapt()` previously rewrote the WHOLE
6
+ * model to disk. That is wasteful in both size and I/O: 99% of the bytes are
7
+ * identical to the base checkpoint. A delta persists only the rows that actually
8
+ * changed, as a diff against a base, so an online update writes kilobytes
9
+ * instead of megabytes.
10
+ *
11
+ * Format (little-endian), CRC-trailed via {@link appendCrcTrailer}:
12
+ * magic 'EVD0' u32 | version u32 | rowSize u32 | nRows u32 |
13
+ * rowIndex[nRows] u32 | float32 row data[nRows * rowSize]
14
+ *
15
+ * The base + delta reconstruct the exact current weights; a row counts as
16
+ * "changed" if any element differs beyond an optional epsilon.
17
+ */
18
+ export interface RowDelta {
19
+ /** Width of one row (elements). `base.length` must be a multiple of it. */
20
+ rowSize: number;
21
+ /** Indices (row-major) of the rows that changed. */
22
+ rows: number[];
23
+ /** Concatenated new values for the changed rows (rows.length * rowSize). */
24
+ data: Float32Array;
25
+ }
26
+ /**
27
+ * Compute the row-sparse delta of `current` vs `base` (same length + layout).
28
+ * A row is included when any element differs by more than `eps` (default 0).
29
+ */
30
+ export declare function computeRowDelta(base: Float32Array, current: Float32Array, rowSize: number, eps?: number): RowDelta;
31
+ /** Apply a delta onto a copy of `base`, returning the reconstructed weights. */
32
+ export declare function applyRowDelta(base: Float32Array, delta: RowDelta): Float32Array;
33
+ /** Serialize a delta to a CRC-trailed binary. */
34
+ export declare function serializeRowDelta(delta: RowDelta): ArrayBuffer;
35
+ /** Parse a binary produced by {@link serializeRowDelta}; verifies CRC + magic. */
36
+ export declare function deserializeRowDelta(buffer: ArrayBuffer): RowDelta;
37
+ //# sourceMappingURL=delta.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"delta.d.ts","sourceRoot":"","sources":["../../src/utils/delta.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAOH,MAAM,WAAW,QAAQ;IACrB,2EAA2E;IAC3E,OAAO,EAAE,MAAM,CAAC;IAChB,oDAAoD;IACpD,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,4EAA4E;IAC5E,IAAI,EAAE,YAAY,CAAC;CACtB;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAC3B,IAAI,EAAE,YAAY,EAClB,OAAO,EAAE,YAAY,EACrB,OAAO,EAAE,MAAM,EACf,GAAG,SAAI,GACR,QAAQ,CAoBV;AAED,gFAAgF;AAChF,wBAAgB,aAAa,CAAC,IAAI,EAAE,YAAY,EAAE,KAAK,EAAE,QAAQ,GAAG,YAAY,CAa/E;AAED,iDAAiD;AACjD,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,QAAQ,GAAG,WAAW,CAgB9D;AAED,kFAAkF;AAClF,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,WAAW,GAAG,QAAQ,CAiBjE"}
@@ -0,0 +1,108 @@
1
+ /**
2
+ * utils/delta.ts — sparse, row-granular weight deltas for online checkpoints.
3
+ *
4
+ * Online adaptation (WSLA) restricts the trainable set to a few
5
+ * selective-projection ROWS, yet each `adapt()` previously rewrote the WHOLE
6
+ * model to disk. That is wasteful in both size and I/O: 99% of the bytes are
7
+ * identical to the base checkpoint. A delta persists only the rows that actually
8
+ * changed, as a diff against a base, so an online update writes kilobytes
9
+ * instead of megabytes.
10
+ *
11
+ * Format (little-endian), CRC-trailed via {@link appendCrcTrailer}:
12
+ * magic 'EVD0' u32 | version u32 | rowSize u32 | nRows u32 |
13
+ * rowIndex[nRows] u32 | float32 row data[nRows * rowSize]
14
+ *
15
+ * The base + delta reconstruct the exact current weights; a row counts as
16
+ * "changed" if any element differs beyond an optional epsilon.
17
+ */
18
+ import { appendCrcTrailer, verifyCrcTrailer } from './crc32.js';
19
+ const DELTA_MAGIC = 0x30445645; // 'EVD0'
20
+ const DELTA_VERSION = 1;
21
+ /**
22
+ * Compute the row-sparse delta of `current` vs `base` (same length + layout).
23
+ * A row is included when any element differs by more than `eps` (default 0).
24
+ */
25
+ export function computeRowDelta(base, current, rowSize, eps = 0) {
26
+ if (base.length !== current.length) {
27
+ throw new Error(`computeRowDelta: length mismatch (base ${base.length}, current ${current.length})`);
28
+ }
29
+ if (rowSize <= 0 || base.length % rowSize !== 0) {
30
+ throw new Error(`computeRowDelta: base length ${base.length} is not a multiple of rowSize ${rowSize}`);
31
+ }
32
+ const nRowsTotal = base.length / rowSize;
33
+ const rows = [];
34
+ for (let r = 0; r < nRowsTotal; r++) {
35
+ const off = r * rowSize;
36
+ let changed = false;
37
+ for (let c = 0; c < rowSize; c++) {
38
+ if (Math.abs(current[off + c] - base[off + c]) > eps) {
39
+ changed = true;
40
+ break;
41
+ }
42
+ }
43
+ if (changed)
44
+ rows.push(r);
45
+ }
46
+ const data = new Float32Array(rows.length * rowSize);
47
+ rows.forEach((r, i) => data.set(current.subarray(r * rowSize, r * rowSize + rowSize), i * rowSize));
48
+ return { rowSize, rows, data };
49
+ }
50
+ /** Apply a delta onto a copy of `base`, returning the reconstructed weights. */
51
+ export function applyRowDelta(base, delta) {
52
+ if (base.length % delta.rowSize !== 0) {
53
+ throw new Error(`applyRowDelta: base length ${base.length} is not a multiple of rowSize ${delta.rowSize}`);
54
+ }
55
+ const out = Float32Array.from(base);
56
+ delta.rows.forEach((r, i) => {
57
+ const dst = r * delta.rowSize;
58
+ if (dst + delta.rowSize > out.length) {
59
+ throw new Error(`applyRowDelta: row ${r} out of range for base of ${out.length} elements`);
60
+ }
61
+ out.set(delta.data.subarray(i * delta.rowSize, i * delta.rowSize + delta.rowSize), dst);
62
+ });
63
+ return out;
64
+ }
65
+ /** Serialize a delta to a CRC-trailed binary. */
66
+ export function serializeRowDelta(delta) {
67
+ const nRows = delta.rows.length;
68
+ const headerEls = 4; // magic, version, rowSize, nRows
69
+ const headerBytes = headerEls * 4;
70
+ const rowIdxBytes = nRows * 4;
71
+ const dataBytes = delta.data.length * 4;
72
+ const buf = new ArrayBuffer(headerBytes + rowIdxBytes + dataBytes);
73
+ const view = new DataView(buf);
74
+ view.setUint32(0, DELTA_MAGIC, true);
75
+ view.setUint32(4, DELTA_VERSION, true);
76
+ view.setUint32(8, delta.rowSize, true);
77
+ view.setUint32(12, nRows, true);
78
+ let off = headerBytes;
79
+ for (const r of delta.rows) {
80
+ view.setUint32(off, r, true);
81
+ off += 4;
82
+ }
83
+ new Float32Array(buf, off, delta.data.length).set(delta.data);
84
+ return appendCrcTrailer(buf);
85
+ }
86
+ /** Parse a binary produced by {@link serializeRowDelta}; verifies CRC + magic. */
87
+ export function deserializeRowDelta(buffer) {
88
+ const crc = verifyCrcTrailer(buffer);
89
+ if (crc.hasTrailer && !crc.ok) {
90
+ throw new Error('deserializeRowDelta: failed CRC integrity check (corrupt or truncated)');
91
+ }
92
+ const body = crc.body;
93
+ const view = new DataView(body);
94
+ if (view.getUint32(0, true) !== DELTA_MAGIC) {
95
+ throw new Error('deserializeRowDelta: bad magic (not an EVD0 delta)');
96
+ }
97
+ const rowSize = view.getUint32(8, true);
98
+ const nRows = view.getUint32(12, true);
99
+ let off = 16;
100
+ const rows = [];
101
+ for (let i = 0; i < nRows; i++) {
102
+ rows.push(view.getUint32(off, true));
103
+ off += 4;
104
+ }
105
+ const data = new Float32Array(body.slice(off, off + nRows * rowSize * 4));
106
+ return { rowSize, rows, data };
107
+ }
108
+ //# sourceMappingURL=delta.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"delta.js","sourceRoot":"","sources":["../../src/utils/delta.ts"],"names":[],"mappings":"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"}
@@ -1,6 +1,27 @@
1
1
  /**
2
2
  * gpu_utils.ts – WebGPU device management and buffer helpers.
3
3
  */
4
+ /**
5
+ * Shape-keyed GPUBuffer pool (EVM-8). Hot paths (e.g. `readBuffer` staging)
6
+ * allocated and destroyed a GPUBuffer on EVERY call — costly under load. The
7
+ * pool reuses buffers keyed by (byteSize, usage): `acquire` hands back a free
8
+ * buffer of that shape or creates one; `release` returns it for reuse. Buffers
9
+ * of one shape are interchangeable, so reuse is always safe.
10
+ */
11
+ export declare class BufferPool {
12
+ private readonly device;
13
+ private readonly _free;
14
+ constructor(device: GPUDevice);
15
+ private _key;
16
+ /** A free buffer of this shape, or a freshly created one. */
17
+ acquire(byteSize: number, usage: number): GPUBuffer;
18
+ /** Return a buffer to the pool for later reuse (do not destroy it yourself). */
19
+ release(buffer: GPUBuffer, byteSize: number, usage: number): void;
20
+ /** Number of pooled (free) buffers across all shapes. */
21
+ get freeCount(): number;
22
+ /** Destroy every pooled buffer and empty the pool. */
23
+ clear(): void;
24
+ }
4
25
  export interface InitWebGPUOptions {
5
26
  powerPreference?: 'high-performance' | 'low-power';
6
27
  }
@@ -1 +1 @@
1
- {"version":3,"file":"gpu_utils.d.ts","sourceRoot":"","sources":["../../src/utils/gpu_utils.ts"],"names":[],"mappings":"AAAA;;GAEG;AAUH,MAAM,WAAW,iBAAiB;IAChC,eAAe,CAAC,EAAE,kBAAkB,GAAG,WAAW,CAAC;CACpD;AAED,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,SAAS,CAAC;IAClB,OAAO,EAAE,UAAU,CAAC;CACrB;AAED,wBAAsB,UAAU,CAAC,IAAI,GAAE,iBAAsB,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAwCxF;AAED,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,SAAS,EAAE,IAAI,EAAE,YAAY,GAAG,WAAW,GAAG,MAAM,EAAE,EAAE,QAAQ,UAAQ,GAAG,SAAS,CAW/H;AAED,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,UAAQ,GAAG,SAAS,CAGzG;AAED,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,SAAS,EAAE,IAAI,EAAE,WAAW,GAAG,eAAe,GAAG,SAAS,CAUrG;AAED,wBAAsB,UAAU,CAAC,MAAM,EAAE,SAAS,EAAE,SAAS,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,CAgBjH;AAED,wBAAgB,YAAY,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,IAAI,EAAE,YAAY,EAAE,UAAU,SAAI,GAAG,IAAI,CAE3G;AAED,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,kBAAkB,CAMnH;AAED,wBAAgB,eAAe,CAAC,MAAM,EAAE,SAAS,EAAE,QAAQ,EAAE,kBAAkB,EAAE,OAAO,EAAE,SAAS,EAAE,EAAE,UAAU,SAAI,GAAG,YAAY,CASnI;AAED,wBAAgB,cAAc,CAAC,MAAM,EAAE,SAAS,EAAE,QAAQ,EAAE,kBAAkB,EAAE,SAAS,EAAE,YAAY,EAAE,UAAU,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI,CAQnJ;AAED,wBAAgB,IAAI,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAEjD"}
1
+ {"version":3,"file":"gpu_utils.d.ts","sourceRoot":"","sources":["../../src/utils/gpu_utils.ts"],"names":[],"mappings":"AAAA;;GAEG;AAUH;;;;;;GAMG;AACH,qBAAa,UAAU;IAEP,OAAO,CAAC,QAAQ,CAAC,MAAM;IADnC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAkC;gBAC3B,MAAM,EAAE,SAAS;IAE9C,OAAO,CAAC,IAAI;IAIZ,6DAA6D;IAC7D,OAAO,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,SAAS;IAMnD,gFAAgF;IAChF,OAAO,CAAC,MAAM,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI;IAOjE,yDAAyD;IACzD,IAAI,SAAS,IAAI,MAAM,CAItB;IAED,sDAAsD;IACtD,KAAK,IAAI,IAAI;CAMhB;AAUD,MAAM,WAAW,iBAAiB;IAChC,eAAe,CAAC,EAAE,kBAAkB,GAAG,WAAW,CAAC;CACpD;AAED,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,SAAS,CAAC;IAClB,OAAO,EAAE,UAAU,CAAC;CACrB;AAED,wBAAsB,UAAU,CAAC,IAAI,GAAE,iBAAsB,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAwCxF;AAED,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,SAAS,EAAE,IAAI,EAAE,YAAY,GAAG,WAAW,GAAG,MAAM,EAAE,EAAE,QAAQ,UAAQ,GAAG,SAAS,CAW/H;AAED,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,UAAQ,GAAG,SAAS,CAGzG;AAED,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,SAAS,EAAE,IAAI,EAAE,WAAW,GAAG,eAAe,GAAG,SAAS,CAUrG;AAED,wBAAsB,UAAU,CAAC,MAAM,EAAE,SAAS,EAAE,SAAS,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,CAiBjH;AAED,wBAAgB,YAAY,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,IAAI,EAAE,YAAY,EAAE,UAAU,SAAI,GAAG,IAAI,CAE3G;AAED,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,kBAAkB,CAMnH;AAED,wBAAgB,eAAe,CAAC,MAAM,EAAE,SAAS,EAAE,QAAQ,EAAE,kBAAkB,EAAE,OAAO,EAAE,SAAS,EAAE,EAAE,UAAU,SAAI,GAAG,YAAY,CASnI;AAED,wBAAgB,cAAc,CAAC,MAAM,EAAE,SAAS,EAAE,QAAQ,EAAE,kBAAkB,EAAE,SAAS,EAAE,YAAY,EAAE,UAAU,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI,CAQnJ;AAED,wBAAgB,IAAI,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAEjD"}
@@ -8,6 +8,64 @@ const STORAGE = _gpu.GPUBufferUsage?.STORAGE ?? 0x80;
8
8
  const COPY_SRC = _gpu.GPUBufferUsage?.COPY_SRC ?? 0x04;
9
9
  const COPY_DST = _gpu.GPUBufferUsage?.COPY_DST ?? 0x08;
10
10
  const MAP_READ = _gpu.GPUBufferUsage?.MAP_READ ?? 0x01;
11
+ /**
12
+ * Shape-keyed GPUBuffer pool (EVM-8). Hot paths (e.g. `readBuffer` staging)
13
+ * allocated and destroyed a GPUBuffer on EVERY call — costly under load. The
14
+ * pool reuses buffers keyed by (byteSize, usage): `acquire` hands back a free
15
+ * buffer of that shape or creates one; `release` returns it for reuse. Buffers
16
+ * of one shape are interchangeable, so reuse is always safe.
17
+ */
18
+ export class BufferPool {
19
+ device;
20
+ _free = new Map();
21
+ constructor(device) {
22
+ this.device = device;
23
+ }
24
+ _key(byteSize, usage) {
25
+ return `${byteSize}:${usage}`;
26
+ }
27
+ /** A free buffer of this shape, or a freshly created one. */
28
+ acquire(byteSize, usage) {
29
+ const list = this._free.get(this._key(byteSize, usage));
30
+ const reused = list?.pop();
31
+ return reused ?? this.device.createBuffer({ size: byteSize, usage });
32
+ }
33
+ /** Return a buffer to the pool for later reuse (do not destroy it yourself). */
34
+ release(buffer, byteSize, usage) {
35
+ const key = this._key(byteSize, usage);
36
+ let list = this._free.get(key);
37
+ if (!list) {
38
+ list = [];
39
+ this._free.set(key, list);
40
+ }
41
+ list.push(buffer);
42
+ }
43
+ /** Number of pooled (free) buffers across all shapes. */
44
+ get freeCount() {
45
+ let n = 0;
46
+ for (const list of this._free.values())
47
+ n += list.length;
48
+ return n;
49
+ }
50
+ /** Destroy every pooled buffer and empty the pool. */
51
+ clear() {
52
+ for (const list of this._free.values()) {
53
+ for (const b of list)
54
+ b.destroy();
55
+ }
56
+ this._free.clear();
57
+ }
58
+ }
59
+ /** One staging-buffer pool per device, used by {@link readBuffer}. */
60
+ const _stagingPools = new WeakMap();
61
+ function stagingPool(device) {
62
+ let pool = _stagingPools.get(device);
63
+ if (!pool) {
64
+ pool = new BufferPool(device);
65
+ _stagingPools.set(device, pool);
66
+ }
67
+ return pool;
68
+ }
11
69
  export async function initWebGPU(opts = {}) {
12
70
  if (typeof navigator === 'undefined' || !navigator.gpu) {
13
71
  throw new Error('WebGPU is not available in this environment. ' +
@@ -63,17 +121,18 @@ export function createUniformBuffer(device, data) {
63
121
  }
64
122
  export async function readBuffer(device, srcBuffer, byteSize) {
65
123
  const MAP_READ_FLAG = _gpu.GPUMapMode?.READ ?? 0x01;
66
- const stagingBuffer = device.createBuffer({
67
- size: byteSize,
68
- usage: MAP_READ | COPY_DST,
69
- });
124
+ // EVM-8: reuse a pooled staging buffer instead of allocating + destroying one
125
+ // per call. After unmap a staging buffer is fully reusable for the next read.
126
+ const stagingUsage = MAP_READ | COPY_DST;
127
+ const pool = stagingPool(device);
128
+ const stagingBuffer = pool.acquire(byteSize, stagingUsage);
70
129
  const encoder = device.createCommandEncoder();
71
130
  encoder.copyBufferToBuffer(srcBuffer, 0, stagingBuffer, 0, byteSize);
72
131
  device.queue.submit([encoder.finish()]);
73
132
  await stagingBuffer.mapAsync(MAP_READ_FLAG);
74
133
  const result = new Float32Array(stagingBuffer.getMappedRange().slice(0));
75
134
  stagingBuffer.unmap();
76
- stagingBuffer.destroy();
135
+ pool.release(stagingBuffer, byteSize, stagingUsage);
77
136
  return result;
78
137
  }
79
138
  export function uploadBuffer(device, buffer, data, byteOffset = 0) {
@@ -1 +1 @@
1
- {"version":3,"file":"gpu_utils.js","sourceRoot":"","sources":["../../src/utils/gpu_utils.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"gpu_utils.js","sourceRoot":"","sources":["../../src/utils/gpu_utils.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@seanhogg/builderforce-memory-engine",
3
- "version": "2026.6.30",
3
+ "version": "2026.6.34",
4
4
  "description": "BuilderForce Agent Memory — engine layer. TypeScript/WGSL Mamba SSM kernels, model blocks, autograd, training, and BPE tokenizer (zero runtime deps, WebGPU-native).",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.js",
@@ -0,0 +1,219 @@
1
+ /**
2
+ * bench/harness.ts — run a benchmark and A/B two models.
3
+ *
4
+ * The harness drives a model over held-out token sequences, scores next-token
5
+ * prediction with {@link metrics}, and produces a {@link BenchmarkReport}. It
6
+ * also offers a one-call {@link trainAndBenchmark} that builds a fresh
7
+ * `EvermindLM` from a corpus, holds out a slice, trains on the rest, and scores
8
+ * — the path the Studio uses to benchmark a model in the browser.
9
+ */
10
+
11
+ import { EvermindLM, EvermindLMTrainer } from "../lm/evermind_lm.js";
12
+ import { BPETokenizer } from "../tokenizer/bpe.js";
13
+ import { SeededRng } from "../utils/rng.js";
14
+ import { newAccumulator, scoreInto, perplexity, bitsPerToken } from "./metrics.js";
15
+ import type {
16
+ AsyncLogitsModel,
17
+ BenchmarkOptions,
18
+ BenchmarkReport,
19
+ ComparisonReport,
20
+ LogitsModel,
21
+ TrainAndBenchmarkOptions,
22
+ TrainAndBenchmarkResult,
23
+ } from "./types.js";
24
+
25
+ /** A monotonic clock, preferring `performance.now()` when present. */
26
+ function defaultNow(): number {
27
+ const g = globalThis as { performance?: { now(): number } };
28
+ return typeof g.performance?.now === "function" ? g.performance.now() : Date.now();
29
+ }
30
+
31
+ /** Next-token targets for a sequence: tokens[1..T-1] predicted from positions 0..T-2. */
32
+ function nextTokenTargets(seq: number[]): number[] {
33
+ return seq.slice(1);
34
+ }
35
+
36
+ function finalize(
37
+ acc: { tokens: number; ceSum: number; top1Hits: number; topKHits: number },
38
+ sequences: number,
39
+ topK: number,
40
+ elapsedMs: number | undefined,
41
+ ): BenchmarkReport {
42
+ const meanCe = acc.tokens > 0 ? acc.ceSum / acc.tokens : 0;
43
+ const report: BenchmarkReport = {
44
+ sequences,
45
+ tokens: acc.tokens,
46
+ crossEntropy: meanCe,
47
+ perplexity: perplexity(meanCe),
48
+ bitsPerToken: bitsPerToken(meanCe),
49
+ top1Accuracy: acc.tokens > 0 ? acc.top1Hits / acc.tokens : 0,
50
+ topKAccuracy: acc.tokens > 0 ? acc.topKHits / acc.tokens : 0,
51
+ topK,
52
+ };
53
+ if (elapsedMs !== undefined) {
54
+ report.elapsedMs = elapsedMs;
55
+ report.tokensPerSecond = elapsedMs > 0 ? (acc.tokens / elapsedMs) * 1000 : 0;
56
+ }
57
+ return report;
58
+ }
59
+
60
+ /**
61
+ * Benchmark a model (synchronous forward) over held-out token sequences.
62
+ * Sequences shorter than 2 tokens have no prediction target and are skipped.
63
+ */
64
+ export function benchmarkModel(
65
+ model: LogitsModel,
66
+ sequences: number[][],
67
+ opts: BenchmarkOptions = {},
68
+ ): BenchmarkReport {
69
+ const topK = opts.topK ?? 5;
70
+ const measure = opts.measureLatency ?? true;
71
+ const now = opts.now ?? defaultNow;
72
+ const acc = newAccumulator();
73
+ let scored = 0;
74
+ let elapsed = 0;
75
+ for (const seq of sequences) {
76
+ if (seq.length < 2) continue;
77
+ const t0 = measure ? now() : 0;
78
+ const { logits } = model.forward(seq);
79
+ if (measure) elapsed += now() - t0;
80
+ scoreInto(acc, logits, nextTokenTargets(seq), topK);
81
+ scored++;
82
+ }
83
+ return finalize(acc, scored, topK, measure ? elapsed : undefined);
84
+ }
85
+
86
+ /** Benchmark a model whose forward pass is asynchronous (e.g. a WebGPU backend). */
87
+ export async function benchmarkModelAsync(
88
+ model: AsyncLogitsModel,
89
+ sequences: number[][],
90
+ opts: BenchmarkOptions = {},
91
+ ): Promise<BenchmarkReport> {
92
+ const topK = opts.topK ?? 5;
93
+ const measure = opts.measureLatency ?? true;
94
+ const now = opts.now ?? defaultNow;
95
+ const acc = newAccumulator();
96
+ let scored = 0;
97
+ let elapsed = 0;
98
+ for (const seq of sequences) {
99
+ if (seq.length < 2) continue;
100
+ const t0 = measure ? now() : 0;
101
+ const { logits } = await model.forward(seq);
102
+ if (measure) elapsed += now() - t0;
103
+ scoreInto(acc, logits, nextTokenTargets(seq), topK);
104
+ scored++;
105
+ }
106
+ return finalize(acc, scored, topK, measure ? elapsed : undefined);
107
+ }
108
+
109
+ /** A/B two models on the same eval set. Lower perplexity wins (the primary metric). */
110
+ export function compareModels(
111
+ candidate: LogitsModel,
112
+ baseline: LogitsModel,
113
+ sequences: number[][],
114
+ opts: BenchmarkOptions = {},
115
+ ): ComparisonReport {
116
+ return compareReports(benchmarkModel(candidate, sequences, opts), benchmarkModel(baseline, sequences, opts));
117
+ }
118
+
119
+ /** Build a {@link ComparisonReport} from two already-computed scorecards. */
120
+ export function compareReports(candidate: BenchmarkReport, baseline: BenchmarkReport): ComparisonReport {
121
+ const perplexityDelta = candidate.perplexity - baseline.perplexity;
122
+ const perplexityRatio = baseline.perplexity > 0 ? candidate.perplexity / baseline.perplexity : Infinity;
123
+ const top1Delta = candidate.top1Accuracy - baseline.top1Accuracy;
124
+ // A small relative band counts as a tie so noise doesn't flip the verdict.
125
+ const tieBand = 0.005;
126
+ let winner: ComparisonReport["winner"];
127
+ if (Math.abs(perplexityRatio - 1) <= tieBand) winner = "tie";
128
+ else winner = perplexityDelta < 0 ? "candidate" : "baseline";
129
+ const pct = ((1 - perplexityRatio) * 100).toFixed(1);
130
+ const summary =
131
+ winner === "tie"
132
+ ? `Tie: perplexity ${candidate.perplexity.toFixed(2)} vs ${baseline.perplexity.toFixed(2)}`
133
+ : winner === "candidate"
134
+ ? `Candidate wins: ${pct}% lower perplexity (${candidate.perplexity.toFixed(2)} vs ${baseline.perplexity.toFixed(2)})`
135
+ : `Baseline wins: candidate ${(-Number(pct)).toFixed(1)}% higher perplexity (${candidate.perplexity.toFixed(2)} vs ${baseline.perplexity.toFixed(2)})`;
136
+ return { candidate, baseline, perplexityDelta, perplexityRatio, top1Delta, winner, summary };
137
+ }
138
+
139
+ /** Split a corpus into next-token training sequences (one per sentence). */
140
+ export function corpusToSequences(corpus: string, codec: { encode(text: string): number[] }): number[][] {
141
+ return corpus
142
+ .split(/(?<=\.)\s+/)
143
+ .map((s) => codec.encode(s.trim()))
144
+ .filter((ids) => ids.length >= 2);
145
+ }
146
+
147
+ /** Benchmark an existing model against a raw-text corpus (tokenized per sentence). */
148
+ export function benchmarkText(
149
+ model: LogitsModel,
150
+ codec: { encode(text: string): number[] },
151
+ corpus: string,
152
+ opts: BenchmarkOptions = {},
153
+ ): BenchmarkReport {
154
+ return benchmarkModel(model, corpusToSequences(corpus, codec), opts);
155
+ }
156
+
157
+ /** Deterministic Fisher–Yates shuffle (in place) driven by a seeded RNG. */
158
+ function shuffle<T>(arr: T[], rng: SeededRng): T[] {
159
+ for (let i = arr.length - 1; i > 0; i--) {
160
+ const j = Math.floor(rng.next() * (i + 1));
161
+ const tmp = arr[i]!;
162
+ arr[i] = arr[j]!;
163
+ arr[j] = tmp;
164
+ }
165
+ return arr;
166
+ }
167
+
168
+ /**
169
+ * Train a fresh `EvermindLM` on a corpus and benchmark it on a held-out slice —
170
+ * the canonical "build + score a model from text" path. A real benchmark must
171
+ * score data the model never trained on, so `heldOutRatio` is clamped to leave
172
+ * at least one eval sequence and one train sequence.
173
+ */
174
+ export function trainAndBenchmark(
175
+ corpus: string,
176
+ opts: TrainAndBenchmarkOptions = {},
177
+ ): TrainAndBenchmarkResult {
178
+ const seed = opts.seed ?? 7;
179
+ const tok = new BPETokenizer();
180
+ tok.train(corpus, { numMerges: opts.numMerges ?? 100 });
181
+
182
+ const all = corpusToSequences(corpus, tok);
183
+ if (all.length < 2) {
184
+ throw new Error(
185
+ `corpus produced ${all.length} trainable sequence(s); need at least 2 to hold out an eval split (add more sentences)`,
186
+ );
187
+ }
188
+
189
+ // Deterministic split: shuffle by seed, reserve heldOutRatio for eval.
190
+ const order = shuffle([...all], new SeededRng((seed >>> 0) || 1));
191
+ const ratio = Math.min(0.9, Math.max(0.05, opts.heldOutRatio ?? 0.25));
192
+ let evalCount = Math.round(order.length * ratio);
193
+ evalCount = Math.max(1, Math.min(order.length - 1, evalCount));
194
+ const evalSeqs = order.slice(0, evalCount);
195
+ const trainSeqs = order.slice(evalCount);
196
+
197
+ const model = new EvermindLM({
198
+ vocabSize: tok.vocabSize,
199
+ dModel: opts.dModel ?? 32,
200
+ numLayers: opts.numLayers ?? 2,
201
+ hiddenDim: opts.hiddenDim ?? 48,
202
+ seed,
203
+ });
204
+ const epochs = opts.epochs ?? 30;
205
+ const history = new EvermindLMTrainer(model, { lr: opts.lr ?? 0.03, epochs }).fit(trainSeqs);
206
+
207
+ const report = benchmarkModel(model, evalSeqs, { topK: opts.topK ?? 5, measureLatency: true });
208
+ const sample = model.generateText(opts.prompt ?? "The", tok, { maxNewTokens: 8, temperature: 0 });
209
+
210
+ return {
211
+ ...report,
212
+ trainSequences: trainSeqs.length,
213
+ evalSequences: evalSeqs.length,
214
+ initialTrainLoss: history[0] ?? 0,
215
+ finalTrainLoss: history.at(-1) ?? 0,
216
+ vocabSize: tok.vocabSize,
217
+ sample,
218
+ };
219
+ }
@@ -0,0 +1,37 @@
1
+ /**
2
+ * bench — the Evermind benchmarking harness.
3
+ *
4
+ * Measures a trained model on held-out data (perplexity, bits-per-token,
5
+ * next-token accuracy, throughput), A/Bs two models, and offers a one-call
6
+ * train-and-score path the Studio drives in the browser.
7
+ */
8
+
9
+ export {
10
+ benchmarkModel,
11
+ benchmarkModelAsync,
12
+ benchmarkText,
13
+ compareModels,
14
+ compareReports,
15
+ corpusToSequences,
16
+ trainAndBenchmark,
17
+ } from "./harness.js";
18
+
19
+ export {
20
+ argmax,
21
+ topKIndices,
22
+ perplexity,
23
+ bitsPerToken,
24
+ newAccumulator,
25
+ scoreInto,
26
+ LN2,
27
+ } from "./metrics.js";
28
+
29
+ export type {
30
+ LogitsModel,
31
+ AsyncLogitsModel,
32
+ BenchmarkOptions,
33
+ BenchmarkReport,
34
+ ComparisonReport,
35
+ TrainAndBenchmarkOptions,
36
+ TrainAndBenchmarkResult,
37
+ } from "./types.js";