@seanhogg/builderforce-memory-engine 2026.6.31 → 2026.6.35
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.
- package/README.md +2 -0
- package/dist/bench/harness.d.ts +37 -0
- package/dist/bench/harness.d.ts.map +1 -0
- package/dist/bench/harness.js +171 -0
- package/dist/bench/harness.js.map +1 -0
- package/dist/bench/index.d.ts +11 -0
- package/dist/bench/index.d.ts.map +1 -0
- package/dist/bench/index.js +10 -0
- package/dist/bench/index.js.map +1 -0
- package/dist/bench/metrics.d.ts +36 -0
- package/dist/bench/metrics.d.ts.map +1 -0
- package/dist/bench/metrics.js +83 -0
- package/dist/bench/metrics.js.map +1 -0
- package/dist/bench/types.d.ts +119 -0
- package/dist/bench/types.d.ts.map +1 -0
- package/dist/bench/types.js +16 -0
- package/dist/bench/types.js.map +1 -0
- package/dist/export/gguf.d.ts +23 -0
- package/dist/export/gguf.d.ts.map +1 -0
- package/dist/export/gguf.js +158 -0
- package/dist/export/gguf.js.map +1 -0
- package/dist/export/hf.d.ts +32 -0
- package/dist/export/hf.d.ts.map +1 -0
- package/dist/export/hf.js +138 -0
- package/dist/export/hf.js.map +1 -0
- package/dist/export/index.d.ts +57 -0
- package/dist/export/index.d.ts.map +1 -0
- package/dist/export/index.js +85 -0
- package/dist/export/index.js.map +1 -0
- package/dist/export/onnx.d.ts +32 -0
- package/dist/export/onnx.d.ts.map +1 -0
- package/dist/export/onnx.js +265 -0
- package/dist/export/onnx.js.map +1 -0
- package/dist/export/protobuf.d.ts +34 -0
- package/dist/export/protobuf.d.ts.map +1 -0
- package/dist/export/protobuf.js +77 -0
- package/dist/export/protobuf.js.map +1 -0
- package/dist/export/safetensors.d.ts +22 -0
- package/dist/export/safetensors.d.ts.map +1 -0
- package/dist/export/safetensors.js +66 -0
- package/dist/export/safetensors.js.map +1 -0
- package/dist/export/tensors.d.ts +51 -0
- package/dist/export/tensors.d.ts.map +1 -0
- package/dist/export/tensors.js +76 -0
- package/dist/export/tensors.js.map +1 -0
- package/dist/index.d.ts +9 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -1
- package/dist/index.js.map +1 -1
- package/dist/kernels/selective_scan.d.ts.map +1 -1
- package/dist/kernels/selective_scan.js +7 -3
- package/dist/kernels/selective_scan.js.map +1 -1
- package/dist/lm/evermind_lm.d.ts +17 -1
- package/dist/lm/evermind_lm.d.ts.map +1 -1
- package/dist/lm/evermind_lm.js +49 -5
- package/dist/lm/evermind_lm.js.map +1 -1
- package/dist/model/mamba_model.d.ts.map +1 -1
- package/dist/model/mamba_model.js +9 -1
- package/dist/model/mamba_model.js.map +1 -1
- package/dist/utils/crc32.d.ts +37 -0
- package/dist/utils/crc32.d.ts.map +1 -0
- package/dist/utils/crc32.js +70 -0
- package/dist/utils/crc32.js.map +1 -0
- package/dist/utils/delta.d.ts +55 -0
- package/dist/utils/delta.d.ts.map +1 -0
- package/dist/utils/delta.js +148 -0
- package/dist/utils/delta.js.map +1 -0
- package/dist/utils/gpu_utils.d.ts +21 -0
- package/dist/utils/gpu_utils.d.ts.map +1 -1
- package/dist/utils/gpu_utils.js +64 -5
- package/dist/utils/gpu_utils.js.map +1 -1
- package/package.json +1 -1
- package/src/bench/harness.ts +219 -0
- package/src/bench/index.ts +37 -0
- package/src/bench/metrics.ts +98 -0
- package/src/bench/types.ts +121 -0
- package/src/export/gguf.ts +168 -0
- package/src/export/hf.ts +155 -0
- package/src/export/index.ts +128 -0
- package/src/export/onnx.ts +331 -0
- package/src/export/protobuf.ts +86 -0
- package/src/export/safetensors.ts +77 -0
- package/src/export/tensors.ts +107 -0
- package/src/index.ts +70 -0
- package/src/kernels/selective_scan.ts +7 -3
- package/src/lm/evermind_lm.ts +53 -8
- package/src/model/mamba_model.ts +9 -1
- package/src/utils/crc32.ts +82 -0
- package/src/utils/delta.ts +156 -0
- package/src/utils/gpu_utils.ts +60 -5
|
@@ -0,0 +1,156 @@
|
|
|
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
|
+
|
|
19
|
+
import { appendCrcTrailer, verifyCrcTrailer } from './crc32.js';
|
|
20
|
+
|
|
21
|
+
const DELTA_MAGIC = 0x30445645; // 'EVD0'
|
|
22
|
+
const DELTA_VERSION = 1;
|
|
23
|
+
|
|
24
|
+
export interface RowDelta {
|
|
25
|
+
/** Width of one row (elements). `base.length` must be a multiple of it. */
|
|
26
|
+
rowSize: number;
|
|
27
|
+
/** Indices (row-major) of the rows that changed. */
|
|
28
|
+
rows: number[];
|
|
29
|
+
/** Concatenated new values for the changed rows (rows.length * rowSize). */
|
|
30
|
+
data: Float32Array;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Compute the row-sparse delta of `current` vs `base` (same length + layout).
|
|
35
|
+
* A row is included when any element differs by more than `eps` (default 0).
|
|
36
|
+
*/
|
|
37
|
+
export function computeRowDelta(
|
|
38
|
+
base: Float32Array,
|
|
39
|
+
current: Float32Array,
|
|
40
|
+
rowSize: number,
|
|
41
|
+
eps = 0,
|
|
42
|
+
): RowDelta {
|
|
43
|
+
if (base.length !== current.length) {
|
|
44
|
+
throw new Error(`computeRowDelta: length mismatch (base ${base.length}, current ${current.length})`);
|
|
45
|
+
}
|
|
46
|
+
if (rowSize <= 0 || base.length % rowSize !== 0) {
|
|
47
|
+
throw new Error(`computeRowDelta: base length ${base.length} is not a multiple of rowSize ${rowSize}`);
|
|
48
|
+
}
|
|
49
|
+
const nRowsTotal = base.length / rowSize;
|
|
50
|
+
const rows: number[] = [];
|
|
51
|
+
for (let r = 0; r < nRowsTotal; r++) {
|
|
52
|
+
const off = r * rowSize;
|
|
53
|
+
let changed = false;
|
|
54
|
+
for (let c = 0; c < rowSize; c++) {
|
|
55
|
+
if (Math.abs(current[off + c]! - base[off + c]!) > eps) { changed = true; break; }
|
|
56
|
+
}
|
|
57
|
+
if (changed) rows.push(r);
|
|
58
|
+
}
|
|
59
|
+
const data = new Float32Array(rows.length * rowSize);
|
|
60
|
+
rows.forEach((r, i) => data.set(current.subarray(r * rowSize, r * rowSize + rowSize), i * rowSize));
|
|
61
|
+
return { rowSize, rows, data };
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** Apply a delta onto a copy of `base`, returning the reconstructed weights. */
|
|
65
|
+
export function applyRowDelta(base: Float32Array, delta: RowDelta): Float32Array {
|
|
66
|
+
if (base.length % delta.rowSize !== 0) {
|
|
67
|
+
throw new Error(`applyRowDelta: base length ${base.length} is not a multiple of rowSize ${delta.rowSize}`);
|
|
68
|
+
}
|
|
69
|
+
const out = Float32Array.from(base);
|
|
70
|
+
delta.rows.forEach((r, i) => {
|
|
71
|
+
const dst = r * delta.rowSize;
|
|
72
|
+
if (dst + delta.rowSize > out.length) {
|
|
73
|
+
throw new Error(`applyRowDelta: row ${r} out of range for base of ${out.length} elements`);
|
|
74
|
+
}
|
|
75
|
+
out.set(delta.data.subarray(i * delta.rowSize, i * delta.rowSize + delta.rowSize), dst);
|
|
76
|
+
});
|
|
77
|
+
return out;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** Serialize a delta to a CRC-trailed binary. */
|
|
81
|
+
export function serializeRowDelta(delta: RowDelta): ArrayBuffer {
|
|
82
|
+
const nRows = delta.rows.length;
|
|
83
|
+
const headerEls = 4; // magic, version, rowSize, nRows
|
|
84
|
+
const headerBytes = headerEls * 4;
|
|
85
|
+
const rowIdxBytes = nRows * 4;
|
|
86
|
+
const dataBytes = delta.data.length * 4;
|
|
87
|
+
const buf = new ArrayBuffer(headerBytes + rowIdxBytes + dataBytes);
|
|
88
|
+
const view = new DataView(buf);
|
|
89
|
+
view.setUint32(0, DELTA_MAGIC, true);
|
|
90
|
+
view.setUint32(4, DELTA_VERSION, true);
|
|
91
|
+
view.setUint32(8, delta.rowSize, true);
|
|
92
|
+
view.setUint32(12, nRows, true);
|
|
93
|
+
let off = headerBytes;
|
|
94
|
+
for (const r of delta.rows) { view.setUint32(off, r, true); off += 4; }
|
|
95
|
+
new Float32Array(buf, off, delta.data.length).set(delta.data);
|
|
96
|
+
return appendCrcTrailer(buf);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Diff two whole checkpoint buffers of IDENTICAL structure (same model/config,
|
|
101
|
+
* f32 data) into a sparse delta — the online-save win for `adapt()` (EVM-6b).
|
|
102
|
+
* Both buffers are produced by the same `exportWeights()` so their headers are
|
|
103
|
+
* byte-identical; only the weights the adapt actually changed appear in the
|
|
104
|
+
* delta. Operates on the raw bytes (header rows diff to nothing), so it needs no
|
|
105
|
+
* knowledge of the header layout and is GPU-free / unit-testable.
|
|
106
|
+
*
|
|
107
|
+
* Element-granular (each changed f32 stored as index+value) so a WSLA update
|
|
108
|
+
* that touches a few rows persists kilobytes, not the whole model.
|
|
109
|
+
*/
|
|
110
|
+
export function diffCheckpoints(base: ArrayBuffer, current: ArrayBuffer): ArrayBuffer {
|
|
111
|
+
const b = verifyCrcTrailer(base);
|
|
112
|
+
if (b.hasTrailer && !b.ok) throw new Error('diffCheckpoints: base failed CRC integrity check');
|
|
113
|
+
const c = verifyCrcTrailer(current);
|
|
114
|
+
if (c.hasTrailer && !c.ok) throw new Error('diffCheckpoints: current failed CRC integrity check');
|
|
115
|
+
if (b.body.byteLength !== c.body.byteLength) {
|
|
116
|
+
throw new Error(`diffCheckpoints: shape mismatch (base ${b.body.byteLength}B, current ${c.body.byteLength}B) — only same-config checkpoints can be diffed`);
|
|
117
|
+
}
|
|
118
|
+
if (b.body.byteLength % 4 !== 0) {
|
|
119
|
+
throw new Error('diffCheckpoints: checkpoint body is not 4-byte aligned (not an f32 checkpoint)');
|
|
120
|
+
}
|
|
121
|
+
return serializeRowDelta(computeRowDelta(new Float32Array(b.body), new Float32Array(c.body), 1));
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Reconstruct the `current` checkpoint from `base` + a diff produced by
|
|
126
|
+
* {@link diffCheckpoints}. Returns a CRC-trailed buffer byte-identical to the
|
|
127
|
+
* original current checkpoint, ready for `loadWeights`.
|
|
128
|
+
*/
|
|
129
|
+
export function applyCheckpointDiff(base: ArrayBuffer, diff: ArrayBuffer): ArrayBuffer {
|
|
130
|
+
const b = verifyCrcTrailer(base);
|
|
131
|
+
if (b.hasTrailer && !b.ok) throw new Error('applyCheckpointDiff: base failed CRC integrity check');
|
|
132
|
+
const patched = applyRowDelta(new Float32Array(b.body), deserializeRowDelta(diff));
|
|
133
|
+
const out = new ArrayBuffer(patched.byteLength);
|
|
134
|
+
new Float32Array(out).set(patched);
|
|
135
|
+
return appendCrcTrailer(out);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/** Parse a binary produced by {@link serializeRowDelta}; verifies CRC + magic. */
|
|
139
|
+
export function deserializeRowDelta(buffer: ArrayBuffer): RowDelta {
|
|
140
|
+
const crc = verifyCrcTrailer(buffer);
|
|
141
|
+
if (crc.hasTrailer && !crc.ok) {
|
|
142
|
+
throw new Error('deserializeRowDelta: failed CRC integrity check (corrupt or truncated)');
|
|
143
|
+
}
|
|
144
|
+
const body = crc.body;
|
|
145
|
+
const view = new DataView(body);
|
|
146
|
+
if (view.getUint32(0, true) !== DELTA_MAGIC) {
|
|
147
|
+
throw new Error('deserializeRowDelta: bad magic (not an EVD0 delta)');
|
|
148
|
+
}
|
|
149
|
+
const rowSize = view.getUint32(8, true);
|
|
150
|
+
const nRows = view.getUint32(12, true);
|
|
151
|
+
let off = 16;
|
|
152
|
+
const rows: number[] = [];
|
|
153
|
+
for (let i = 0; i < nRows; i++) { rows.push(view.getUint32(off, true)); off += 4; }
|
|
154
|
+
const data = new Float32Array(body.slice(off, off + nRows * rowSize * 4));
|
|
155
|
+
return { rowSize, rows, data };
|
|
156
|
+
}
|
package/src/utils/gpu_utils.ts
CHANGED
|
@@ -10,6 +10,60 @@ const COPY_SRC: number = _gpu.GPUBufferUsage?.COPY_SRC ?? 0x04;
|
|
|
10
10
|
const COPY_DST: number = _gpu.GPUBufferUsage?.COPY_DST ?? 0x08;
|
|
11
11
|
const MAP_READ: number = _gpu.GPUBufferUsage?.MAP_READ ?? 0x01;
|
|
12
12
|
|
|
13
|
+
/**
|
|
14
|
+
* Shape-keyed GPUBuffer pool (EVM-8). Hot paths (e.g. `readBuffer` staging)
|
|
15
|
+
* allocated and destroyed a GPUBuffer on EVERY call — costly under load. The
|
|
16
|
+
* pool reuses buffers keyed by (byteSize, usage): `acquire` hands back a free
|
|
17
|
+
* buffer of that shape or creates one; `release` returns it for reuse. Buffers
|
|
18
|
+
* of one shape are interchangeable, so reuse is always safe.
|
|
19
|
+
*/
|
|
20
|
+
export class BufferPool {
|
|
21
|
+
private readonly _free = new Map<string, GPUBuffer[]>();
|
|
22
|
+
constructor(private readonly device: GPUDevice) {}
|
|
23
|
+
|
|
24
|
+
private _key(byteSize: number, usage: number): string {
|
|
25
|
+
return `${byteSize}:${usage}`;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** A free buffer of this shape, or a freshly created one. */
|
|
29
|
+
acquire(byteSize: number, usage: number): GPUBuffer {
|
|
30
|
+
const list = this._free.get(this._key(byteSize, usage));
|
|
31
|
+
const reused = list?.pop();
|
|
32
|
+
return reused ?? this.device.createBuffer({ size: byteSize, usage });
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** Return a buffer to the pool for later reuse (do not destroy it yourself). */
|
|
36
|
+
release(buffer: GPUBuffer, byteSize: number, usage: number): void {
|
|
37
|
+
const key = this._key(byteSize, usage);
|
|
38
|
+
let list = this._free.get(key);
|
|
39
|
+
if (!list) { list = []; this._free.set(key, list); }
|
|
40
|
+
list.push(buffer);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** Number of pooled (free) buffers across all shapes. */
|
|
44
|
+
get freeCount(): number {
|
|
45
|
+
let n = 0;
|
|
46
|
+
for (const list of this._free.values()) n += list.length;
|
|
47
|
+
return n;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** Destroy every pooled buffer and empty the pool. */
|
|
51
|
+
clear(): void {
|
|
52
|
+
for (const list of this._free.values()) {
|
|
53
|
+
for (const b of list) b.destroy();
|
|
54
|
+
}
|
|
55
|
+
this._free.clear();
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** One staging-buffer pool per device, used by {@link readBuffer}. */
|
|
60
|
+
const _stagingPools = new WeakMap<GPUDevice, BufferPool>();
|
|
61
|
+
function stagingPool(device: GPUDevice): BufferPool {
|
|
62
|
+
let pool = _stagingPools.get(device);
|
|
63
|
+
if (!pool) { pool = new BufferPool(device); _stagingPools.set(device, pool); }
|
|
64
|
+
return pool;
|
|
65
|
+
}
|
|
66
|
+
|
|
13
67
|
export interface InitWebGPUOptions {
|
|
14
68
|
powerPreference?: 'high-performance' | 'low-power';
|
|
15
69
|
}
|
|
@@ -93,10 +147,11 @@ export function createUniformBuffer(device: GPUDevice, data: ArrayBuffer | Array
|
|
|
93
147
|
|
|
94
148
|
export async function readBuffer(device: GPUDevice, srcBuffer: GPUBuffer, byteSize: number): Promise<Float32Array> {
|
|
95
149
|
const MAP_READ_FLAG: number = _gpu.GPUMapMode?.READ ?? 0x01;
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
150
|
+
// EVM-8: reuse a pooled staging buffer instead of allocating + destroying one
|
|
151
|
+
// per call. After unmap a staging buffer is fully reusable for the next read.
|
|
152
|
+
const stagingUsage = MAP_READ | COPY_DST;
|
|
153
|
+
const pool = stagingPool(device);
|
|
154
|
+
const stagingBuffer = pool.acquire(byteSize, stagingUsage);
|
|
100
155
|
|
|
101
156
|
const encoder = device.createCommandEncoder();
|
|
102
157
|
encoder.copyBufferToBuffer(srcBuffer, 0, stagingBuffer, 0, byteSize);
|
|
@@ -105,7 +160,7 @@ export async function readBuffer(device: GPUDevice, srcBuffer: GPUBuffer, byteSi
|
|
|
105
160
|
await stagingBuffer.mapAsync(MAP_READ_FLAG);
|
|
106
161
|
const result = new Float32Array(stagingBuffer.getMappedRange().slice(0));
|
|
107
162
|
stagingBuffer.unmap();
|
|
108
|
-
|
|
163
|
+
pool.release(stagingBuffer, byteSize, stagingUsage);
|
|
109
164
|
return result;
|
|
110
165
|
}
|
|
111
166
|
|