@seanhogg/builderforce-memory-engine 2026.6.35 → 2026.7.11
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 +30 -2
- package/dist/codec/evermind_video.d.ts +35 -0
- package/dist/codec/evermind_video.d.ts.map +1 -0
- package/dist/codec/evermind_video.js +37 -0
- package/dist/codec/evermind_video.js.map +1 -0
- package/dist/codec/image_rvq.d.ts +28 -0
- package/dist/codec/image_rvq.d.ts.map +1 -0
- package/dist/codec/image_rvq.js +39 -0
- package/dist/codec/image_rvq.js.map +1 -0
- package/dist/codec/index.d.ts +13 -0
- package/dist/codec/index.d.ts.map +1 -0
- package/dist/codec/index.js +11 -0
- package/dist/codec/index.js.map +1 -0
- package/dist/codec/multimodal_vocab.d.ts +79 -0
- package/dist/codec/multimodal_vocab.d.ts.map +1 -0
- package/dist/codec/multimodal_vocab.js +96 -0
- package/dist/codec/multimodal_vocab.js.map +1 -0
- package/dist/codec/video_rvq.d.ts +116 -0
- package/dist/codec/video_rvq.d.ts.map +1 -0
- package/dist/codec/video_rvq.js +450 -0
- package/dist/codec/video_rvq.js.map +1 -0
- package/dist/import/evermind.d.ts +48 -0
- package/dist/import/evermind.d.ts.map +1 -0
- package/dist/import/evermind.js +111 -0
- package/dist/import/evermind.js.map +1 -0
- package/dist/import/index.d.ts +11 -0
- package/dist/import/index.d.ts.map +1 -0
- package/dist/import/index.js +10 -0
- package/dist/import/index.js.map +1 -0
- package/dist/import/safetensors.d.ts +13 -0
- package/dist/import/safetensors.d.ts.map +1 -0
- package/dist/import/safetensors.js +64 -0
- package/dist/import/safetensors.js.map +1 -0
- package/dist/index.d.ts +8 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -1
- package/dist/index.js.map +1 -1
- package/dist/kernels/selective_scan.d.ts.map +1 -1
- package/dist/kernels/selective_scan.js +9 -3
- package/dist/kernels/selective_scan.js.map +1 -1
- package/dist/kernels/ssd.d.ts.map +1 -1
- package/dist/kernels/ssd.js +8 -2
- package/dist/kernels/ssd.js.map +1 -1
- package/dist/kernels/weight_update.d.ts.map +1 -1
- package/dist/kernels/weight_update.js +22 -3
- package/dist/kernels/weight_update.js.map +1 -1
- package/dist/limbic/index.d.ts +2 -2
- package/dist/limbic/index.d.ts.map +1 -1
- package/dist/limbic/index.js +1 -1
- package/dist/limbic/index.js.map +1 -1
- package/dist/limbic/regions.d.ts +31 -0
- package/dist/limbic/regions.d.ts.map +1 -1
- package/dist/limbic/regions.js +34 -0
- package/dist/limbic/regions.js.map +1 -1
- package/dist/model/mamba_model.d.ts +17 -0
- package/dist/model/mamba_model.d.ts.map +1 -1
- package/dist/model/mamba_model.js +27 -0
- package/dist/model/mamba_model.js.map +1 -1
- package/dist/moe/index.d.ts +1 -1
- package/dist/moe/index.d.ts.map +1 -1
- package/dist/moe/moe_package.d.ts +40 -2
- package/dist/moe/moe_package.d.ts.map +1 -1
- package/dist/moe/moe_package.js +90 -7
- package/dist/moe/moe_package.js.map +1 -1
- package/dist/tokenizer/bpe.d.ts +37 -0
- package/dist/tokenizer/bpe.d.ts.map +1 -1
- package/dist/tokenizer/bpe.js +47 -0
- package/dist/tokenizer/bpe.js.map +1 -1
- package/dist/training/trainer.d.ts +34 -2
- package/dist/training/trainer.d.ts.map +1 -1
- package/dist/training/trainer.js +76 -26
- package/dist/training/trainer.js.map +1 -1
- package/dist/utils/quantization.d.ts.map +1 -1
- package/dist/utils/quantization.js +4 -1
- package/dist/utils/quantization.js.map +1 -1
- package/package.json +1 -1
- package/src/codec/evermind_video.ts +55 -0
- package/src/codec/image_rvq.ts +50 -0
- package/src/codec/index.ts +16 -0
- package/src/codec/multimodal_vocab.ts +115 -0
- package/src/codec/video_rvq.ts +496 -0
- package/src/import/evermind.ts +128 -0
- package/src/import/index.ts +11 -0
- package/src/import/safetensors.ts +67 -0
- package/src/index.ts +35 -0
- package/src/kernels/selective_scan.ts +9 -3
- package/src/kernels/ssd.ts +8 -2
- package/src/kernels/weight_update.ts +22 -3
- package/src/limbic/index.ts +2 -1
- package/src/limbic/regions.ts +60 -0
- package/src/model/mamba_model.ts +27 -0
- package/src/moe/index.ts +1 -0
- package/src/moe/moe_package.ts +107 -6
- package/src/tokenizer/bpe.ts +59 -0
- package/src/training/trainer.ts +92 -28
- package/src/utils/quantization.ts +4 -1
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* import/safetensors.ts — read weights FROM the HF-native `.safetensors` format.
|
|
3
|
+
*
|
|
4
|
+
* The exact inverse of {@link ../export/safetensors.ts}: parse the 8-byte
|
|
5
|
+
* little-endian u64 header length, the JSON header (name → {dtype, shape,
|
|
6
|
+
* data_offsets}), then decode each tensor's raw bytes into a Float32Array.
|
|
7
|
+
* F32 and F16 are supported (F16 is widened to f32 on read). This is the reader
|
|
8
|
+
* half of the warm-start / weight-port path — export was one-directional before.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import type { NamedTensor } from "../export/tensors.js";
|
|
12
|
+
import { fp16ToFloat } from "../utils/quantization.js";
|
|
13
|
+
|
|
14
|
+
interface HeaderEntry {
|
|
15
|
+
dtype: string;
|
|
16
|
+
shape: number[];
|
|
17
|
+
data_offsets: [number, number];
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** Decode a `.safetensors` byte buffer into named F32 tensors (header order). */
|
|
21
|
+
export function safetensorsToTensors(bytes: Uint8Array): NamedTensor[] {
|
|
22
|
+
if (bytes.length < 8) throw new Error("safetensors: buffer too short for header length");
|
|
23
|
+
const dv = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
24
|
+
const headerLen = Number(dv.getBigUint64(0, true));
|
|
25
|
+
const headerEnd = 8 + headerLen;
|
|
26
|
+
if (headerEnd > bytes.length) throw new Error(`safetensors: header length ${headerLen} exceeds buffer`);
|
|
27
|
+
|
|
28
|
+
const headerJson = new TextDecoder().decode(bytes.subarray(8, headerEnd));
|
|
29
|
+
let header: Record<string, unknown>;
|
|
30
|
+
try {
|
|
31
|
+
header = JSON.parse(headerJson) as Record<string, unknown>;
|
|
32
|
+
} catch (e) {
|
|
33
|
+
throw new Error(`safetensors: malformed JSON header (${e instanceof Error ? e.message : String(e)})`);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const dataBase = headerEnd;
|
|
37
|
+
const out: NamedTensor[] = [];
|
|
38
|
+
for (const [name, raw] of Object.entries(header)) {
|
|
39
|
+
if (name === "__metadata__") continue;
|
|
40
|
+
const entry = raw as HeaderEntry;
|
|
41
|
+
if (!entry || !Array.isArray(entry.shape) || !Array.isArray(entry.data_offsets)) {
|
|
42
|
+
throw new Error(`safetensors: malformed entry for "${name}"`);
|
|
43
|
+
}
|
|
44
|
+
const [start, end] = entry.data_offsets;
|
|
45
|
+
const numel = entry.shape.reduce((n, d) => n * d, 1);
|
|
46
|
+
const seg = bytes.subarray(dataBase + start, dataBase + end);
|
|
47
|
+
out.push({ name, shape: entry.shape, data: decodeTensor(seg, entry.dtype, numel, name) });
|
|
48
|
+
}
|
|
49
|
+
return out;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function decodeTensor(seg: Uint8Array, dtype: string, numel: number, name: string): Float32Array {
|
|
53
|
+
const dv = new DataView(seg.buffer, seg.byteOffset, seg.byteLength);
|
|
54
|
+
const data = new Float32Array(numel);
|
|
55
|
+
const d = dtype.toUpperCase();
|
|
56
|
+
if (d === "F32" || d === "FLOAT32") {
|
|
57
|
+
if (seg.length < numel * 4) throw new Error(`safetensors: "${name}" F32 bytes ${seg.length} < ${numel * 4}`);
|
|
58
|
+
for (let i = 0; i < numel; i++) data[i] = dv.getFloat32(i * 4, true);
|
|
59
|
+
return data;
|
|
60
|
+
}
|
|
61
|
+
if (d === "F16" || d === "FLOAT16") {
|
|
62
|
+
if (seg.length < numel * 2) throw new Error(`safetensors: "${name}" F16 bytes ${seg.length} < ${numel * 2}`);
|
|
63
|
+
for (let i = 0; i < numel; i++) data[i] = fp16ToFloat(dv.getUint16(i * 2, true));
|
|
64
|
+
return data;
|
|
65
|
+
}
|
|
66
|
+
throw new Error(`safetensors: unsupported dtype "${dtype}" for "${name}" (expected F32 or F16)`);
|
|
67
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -33,6 +33,8 @@ export type {
|
|
|
33
33
|
MoEEpochResult,
|
|
34
34
|
EvermindModelManifest,
|
|
35
35
|
EvermindModelCard,
|
|
36
|
+
EvermindModelType,
|
|
37
|
+
EvermindModality,
|
|
36
38
|
PackageMeta,
|
|
37
39
|
ValidationResult,
|
|
38
40
|
} from './moe/index.js';
|
|
@@ -60,6 +62,27 @@ export {
|
|
|
60
62
|
// ── Tokenizer ─────────────────────────────────────────────────────────────────
|
|
61
63
|
|
|
62
64
|
export { BPETokenizer } from './tokenizer/bpe.js';
|
|
65
|
+
export type { BPEEncodeOptions, PadSide, HuggingFaceTokenizerSpec, SpecialTokenOverrides } from './tokenizer/bpe.js';
|
|
66
|
+
|
|
67
|
+
// ── Modality codecs (media ⇄ tokens; lets EvermindLM generate video) ────────────
|
|
68
|
+
|
|
69
|
+
export {
|
|
70
|
+
MultimodalVocab,
|
|
71
|
+
VIDEO_BANK_INTRA,
|
|
72
|
+
VIDEO_BANK_INTER,
|
|
73
|
+
VideoRVQCodec,
|
|
74
|
+
ImageRVQCodec,
|
|
75
|
+
buildVideoSequence,
|
|
76
|
+
generateVideo,
|
|
77
|
+
generateImage,
|
|
78
|
+
} from './codec/index.js';
|
|
79
|
+
export type {
|
|
80
|
+
MultimodalVocabConfig,
|
|
81
|
+
TokenKind,
|
|
82
|
+
VideoRVQConfig,
|
|
83
|
+
Frame,
|
|
84
|
+
Video,
|
|
85
|
+
} from './codec/index.js';
|
|
63
86
|
|
|
64
87
|
// ── Model export (the publishing step: ONNX / safetensors / GGUF / HF repo) ─────
|
|
65
88
|
|
|
@@ -90,6 +113,16 @@ export type {
|
|
|
90
113
|
TensorSpec,
|
|
91
114
|
} from './export/index.js';
|
|
92
115
|
|
|
116
|
+
// ── Model import (warm-start / weight-port: safetensors → EvermindLM) ────────────
|
|
117
|
+
|
|
118
|
+
export {
|
|
119
|
+
safetensorsToTensors,
|
|
120
|
+
importEvermind,
|
|
121
|
+
importEvermindTensors,
|
|
122
|
+
inferArchFromTensors,
|
|
123
|
+
} from './import/index.js';
|
|
124
|
+
export type { ImportOptions } from './import/index.js';
|
|
125
|
+
|
|
93
126
|
// ── Benchmarking (held-out perplexity / accuracy / throughput + A/B) ──────────
|
|
94
127
|
|
|
95
128
|
export {
|
|
@@ -220,6 +253,7 @@ export {
|
|
|
220
253
|
neutralState,
|
|
221
254
|
stateToRecord,
|
|
222
255
|
recordToState,
|
|
256
|
+
personalitySetpoint,
|
|
223
257
|
LimbicModel,
|
|
224
258
|
DEFAULT_LIMBIC_CONFIG,
|
|
225
259
|
DEFAULT_LIMBIC_SEED,
|
|
@@ -228,6 +262,7 @@ export {
|
|
|
228
262
|
export type {
|
|
229
263
|
Region,
|
|
230
264
|
LimbicDimName,
|
|
265
|
+
PersonalityTraits,
|
|
231
266
|
LimbicModelConfig,
|
|
232
267
|
LimbicForward,
|
|
233
268
|
LimbicParam,
|
|
@@ -60,12 +60,15 @@ fn softplus(x: f32) -> f32 {
|
|
|
60
60
|
// B_bar = (A_bar - 1) / A * B ≈ Δ * B (first-order for simplicity)
|
|
61
61
|
fn discretise_A(delta_val: f32, a_log: f32) -> f32 {
|
|
62
62
|
// A is stored as -exp(a_log) to ensure A_bar < 1 (stable)
|
|
63
|
-
|
|
63
|
+
// Clamp log-decay so -exp(a_log) can't overflow to -Inf (A_bar→0, state
|
|
64
|
+
// death) nor collapse toward 0 (A_bar→1, no decay). Keeps A_bar strictly in
|
|
65
|
+
// (0,1) across repeated adapts. Belt-and-suspenders: WSLA also freezes A_log.
|
|
66
|
+
let a_cont = -exp(clamp(a_log, -10.0, 5.0));
|
|
64
67
|
return exp(delta_val * a_cont);
|
|
65
68
|
}
|
|
66
69
|
|
|
67
70
|
fn discretise_B(delta_val: f32, a_log: f32, b_val: f32) -> f32 {
|
|
68
|
-
let a_cont = -exp(a_log);
|
|
71
|
+
let a_cont = -exp(clamp(a_log, -10.0, 5.0));
|
|
69
72
|
let a_bar = exp(delta_val * a_cont);
|
|
70
73
|
// (A_bar - 1) / A_cont * B
|
|
71
74
|
let b_bar = (a_bar - 1.0) / a_cont * b_val;
|
|
@@ -264,7 +267,10 @@ fn softplus_grad(x: f32) -> f32 {
|
|
|
264
267
|
}
|
|
265
268
|
|
|
266
269
|
fn discretise_A(delta_val: f32, a_log: f32) -> f32 {
|
|
267
|
-
|
|
270
|
+
// Clamp log-decay so -exp(a_log) can't overflow to -Inf (A_bar→0, state
|
|
271
|
+
// death) nor collapse toward 0 (A_bar→1, no decay). Keeps A_bar strictly in
|
|
272
|
+
// (0,1) across repeated adapts. Belt-and-suspenders: WSLA also freezes A_log.
|
|
273
|
+
let a_cont = -exp(clamp(a_log, -10.0, 5.0));
|
|
268
274
|
return exp(delta_val * a_cont);
|
|
269
275
|
}
|
|
270
276
|
|
package/src/kernels/ssd.ts
CHANGED
|
@@ -50,7 +50,10 @@ struct SsdParams {
|
|
|
50
50
|
@group(0) @binding(9) var<storage, read_write> state_carry : array<f32>; // [n_chunks+1,B,H,N,d_head]
|
|
51
51
|
|
|
52
52
|
fn softplus(x: f32) -> f32 {
|
|
53
|
-
|
|
53
|
+
// Numerically stable: max(x,0) + log1p(exp(-|x|)). The naive log(1+exp(x))
|
|
54
|
+
// overflows to +Inf for x ≳ 88 (f32 exp range) — and a downstream
|
|
55
|
+
// exp(-Inf*0) then yields NaN, poisoning the state. This form never overflows.
|
|
56
|
+
return max(x, 0.0) + log(1.0 + exp(-abs(x)));
|
|
54
57
|
}
|
|
55
58
|
|
|
56
59
|
// Workgroup: one chunk × one head × one batch item
|
|
@@ -186,7 +189,10 @@ struct SsdParams {
|
|
|
186
189
|
@group(0) @binding(14) var<storage, read_write> dD_vec : array<f32>;
|
|
187
190
|
|
|
188
191
|
fn softplus(x: f32) -> f32 {
|
|
189
|
-
|
|
192
|
+
// Numerically stable: max(x,0) + log1p(exp(-|x|)). The naive log(1+exp(x))
|
|
193
|
+
// overflows to +Inf for x ≳ 88 (f32 exp range) — and a downstream
|
|
194
|
+
// exp(-Inf*0) then yields NaN, poisoning the state. This form never overflows.
|
|
195
|
+
return max(x, 0.0) + log(1.0 + exp(-abs(x)));
|
|
190
196
|
}
|
|
191
197
|
fn d_softplus(x: f32) -> f32 {
|
|
192
198
|
return 1.0 / (1.0 + exp(-x));
|
|
@@ -19,6 +19,10 @@ struct AdamParams {
|
|
|
19
19
|
weight_decay : f32, // default 0.01
|
|
20
20
|
beta1_t : f32, // beta1^t (precomputed bias correction term)
|
|
21
21
|
beta2_t : f32, // beta2^t
|
|
22
|
+
max_delta : f32, // trust region: max |Δθ| per step (0 ⇒ unbounded)
|
|
23
|
+
_pad0 : f32, // pad struct to a 16-byte multiple (uniform layout)
|
|
24
|
+
_pad1 : f32,
|
|
25
|
+
_pad2 : f32,
|
|
22
26
|
};
|
|
23
27
|
|
|
24
28
|
@group(0) @binding(0) var<uniform> adam : AdamParams;
|
|
@@ -52,9 +56,24 @@ fn adamw_update(
|
|
|
52
56
|
let m_hat = m_new / (1.0 - adam.beta1_t);
|
|
53
57
|
let v_hat = v_new / (1.0 - adam.beta2_t);
|
|
54
58
|
|
|
55
|
-
//
|
|
56
|
-
|
|
57
|
-
|
|
59
|
+
// Adam step.
|
|
60
|
+
var step = adam.lr * m_hat / (sqrt(v_hat) + adam.eps);
|
|
61
|
+
|
|
62
|
+
// Numerical guard: never write a non-finite step into a weight. A NaN or Inf
|
|
63
|
+
// here (from a bad gradient, a zero v_hat, an overflow) would permanently
|
|
64
|
+
// poison the parameter and every future forward — the "model dies" failure.
|
|
65
|
+
// NaN fails self-comparison; treat ±Inf-magnitude as non-finite too.
|
|
66
|
+
if (step != step || step > 3.4e38 || step < -3.4e38) { step = 0.0; }
|
|
67
|
+
|
|
68
|
+
// Trust region: bound how far ONE step can move a weight. With write-through
|
|
69
|
+
// adaptation running repeatedly, an unbounded step (even from a noisy
|
|
70
|
+
// gradient) compounds across executions and blows the weights up; clamping
|
|
71
|
+
// the per-element delta keeps every adapt small and reversible. max_delta==0
|
|
72
|
+
// disables the bound (full-training callers that don't set it).
|
|
73
|
+
if (adam.max_delta > 0.0) { step = clamp(step, -adam.max_delta, adam.max_delta); }
|
|
74
|
+
|
|
75
|
+
// Weight decay (decoupled) + bounded gradient step
|
|
76
|
+
param[i] = p * (1.0 - adam.lr * adam.weight_decay) - step;
|
|
58
77
|
}
|
|
59
78
|
`;
|
|
60
79
|
|
package/src/limbic/index.ts
CHANGED
|
@@ -18,8 +18,9 @@ export {
|
|
|
18
18
|
neutralState,
|
|
19
19
|
stateToRecord,
|
|
20
20
|
recordToState,
|
|
21
|
+
personalitySetpoint,
|
|
21
22
|
} from "./regions.js";
|
|
22
|
-
export type { Region, LimbicDimName } from "./regions.js";
|
|
23
|
+
export type { Region, LimbicDimName, PersonalityTraits } from "./regions.js";
|
|
23
24
|
|
|
24
25
|
export { LimbicModel, DEFAULT_LIMBIC_CONFIG, DEFAULT_LIMBIC_SEED } from "./limbic_model.js";
|
|
25
26
|
export type { LimbicModelConfig, LimbicForward, LimbicParam } from "./limbic_model.js";
|
package/src/limbic/regions.ts
CHANGED
|
@@ -139,3 +139,63 @@ export function recordToState(rec: Partial<Record<LimbicDimName, number>>): Floa
|
|
|
139
139
|
}
|
|
140
140
|
return s;
|
|
141
141
|
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* The static psychometric traits (0..100, 50 = neutral) that pull the resting
|
|
145
|
+
* affective setpoint away from {@link NEUTRAL_STATE}. A subset of the full
|
|
146
|
+
* personality vector — only the dimensions that map to a limbic drive. All
|
|
147
|
+
* optional; an omitted trait is treated as neutral (50).
|
|
148
|
+
*
|
|
149
|
+
* Keep the mapping in {@link personalitySetpoint} in sync with the runtime
|
|
150
|
+
* compiler `deriveLimbicSetpoints` in `@builderforce/agent-tools` so the on-prem
|
|
151
|
+
* runtime, the cloud engine, and this WebGPU trainer settle on the SAME baseline
|
|
152
|
+
* for the same personality. The two are coupled only by this mapping.
|
|
153
|
+
*/
|
|
154
|
+
export interface PersonalityTraits {
|
|
155
|
+
openness?: number;
|
|
156
|
+
emotionality?: number;
|
|
157
|
+
conscientiousness?: number;
|
|
158
|
+
extraversion?: number;
|
|
159
|
+
/** Regulatory focus: 0 = prevention, 100 = promotion. */
|
|
160
|
+
regulatoryFocus?: number;
|
|
161
|
+
riskTolerance?: number;
|
|
162
|
+
grit?: number;
|
|
163
|
+
/** Schwartz "stimulation" value. */
|
|
164
|
+
stimulation?: number;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/** Signed influence of a 0..100 trait around its neutral midpoint → [-1, 1]. */
|
|
168
|
+
function infl(s: number | undefined): number {
|
|
169
|
+
const v = typeof s === "number" && !Number.isNaN(s) ? Math.max(0, Math.min(100, s)) : 50;
|
|
170
|
+
return (v - 50) / 50;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Derive the personality-conditioned resting SETPOINT from a trait vector — the
|
|
175
|
+
* homeostatic target the limbic dynamics relax toward and that the trainable
|
|
176
|
+
* affect model rides on top of ("personality = setpoints, limbic = dynamics").
|
|
177
|
+
* Returns a fresh clamped 8-dim state vector. A fully-neutral trait vector yields
|
|
178
|
+
* {@link NEUTRAL_STATE}. Mirrors `deriveLimbicSetpoints` in `@builderforce/agent-tools`.
|
|
179
|
+
*/
|
|
180
|
+
export function personalitySetpoint(traits: PersonalityTraits | undefined): Float32Array {
|
|
181
|
+
const s = neutralState();
|
|
182
|
+
if (!traits) return s;
|
|
183
|
+
const open = infl(traits.openness);
|
|
184
|
+
const emo = infl(traits.emotionality);
|
|
185
|
+
const consc = infl(traits.conscientiousness);
|
|
186
|
+
const extra = infl(traits.extraversion);
|
|
187
|
+
const reg = infl(traits.regulatoryFocus);
|
|
188
|
+
const risk = infl(traits.riskTolerance);
|
|
189
|
+
const grit = infl(traits.grit);
|
|
190
|
+
const stim = infl(traits.stimulation);
|
|
191
|
+
|
|
192
|
+
s[LIMBIC_DIM.driveCuriosity] = clampDim(LIMBIC_DIM.driveCuriosity, 0.5 + 0.35 * open + 0.15 * stim);
|
|
193
|
+
s[LIMBIC_DIM.exploration] = clampDim(LIMBIC_DIM.exploration, 0.4 + 0.3 * open + 0.25 * risk + 0.15 * reg);
|
|
194
|
+
s[LIMBIC_DIM.driveCaution] = clampDim(LIMBIC_DIM.driveCaution, 0.5 + 0.3 * consc - 0.3 * risk - 0.2 * reg + 0.15 * emo);
|
|
195
|
+
s[LIMBIC_DIM.arousal] = clampDim(LIMBIC_DIM.arousal, 0.2 + 0.2 * emo + 0.1 * extra);
|
|
196
|
+
s[LIMBIC_DIM.driveSocial] = clampDim(LIMBIC_DIM.driveSocial, 0.5 + 0.35 * extra);
|
|
197
|
+
s[LIMBIC_DIM.driveEffort] = clampDim(LIMBIC_DIM.driveEffort, 0.8 + 0.15 * grit + 0.1 * consc);
|
|
198
|
+
s[LIMBIC_DIM.valence] = clampDim(LIMBIC_DIM.valence, 0.0 + 0.1 * reg - 0.1 * emo);
|
|
199
|
+
s[LIMBIC_DIM.attention] = clampDim(LIMBIC_DIM.attention, 0.7 + 0.1 * consc);
|
|
200
|
+
return s;
|
|
201
|
+
}
|
package/src/model/mamba_model.ts
CHANGED
|
@@ -413,6 +413,33 @@ export class HybridMambaModel {
|
|
|
413
413
|
return params;
|
|
414
414
|
}
|
|
415
415
|
|
|
416
|
+
/**
|
|
417
|
+
* The parameters a `learn()` / `adapt()` step is allowed to update.
|
|
418
|
+
*
|
|
419
|
+
* Under WSLA (write-through / narrow adaptation — the Evermind "update ==
|
|
420
|
+
* replace" path) this is ONLY the per-layer narrow subset (the input
|
|
421
|
+
* projections that produce Δ/B/C). The backbone — token embedding, final
|
|
422
|
+
* norm, and every block's `A_log` / conv / output projection — stays FROZEN.
|
|
423
|
+
* Freezing `A_log` is the key stability guarantee: the state-transition
|
|
424
|
+
* decay can never drift into a degenerate regime (state death / no-decay)
|
|
425
|
+
* across repeated adapts, which is what made the model "die after several
|
|
426
|
+
* executions". Outside WSLA every parameter trains (full fine-tune / distill).
|
|
427
|
+
*
|
|
428
|
+
* Param names are namespaced `layer{i}.{name}` to match {@link parameters},
|
|
429
|
+
* so a name-keyed optimizer (see MambaTrainer) reuses the same Adam moments
|
|
430
|
+
* whether it's stepping the full set or just the WSLA subset.
|
|
431
|
+
*/
|
|
432
|
+
getTrainableParams(): LayerParam[] {
|
|
433
|
+
if (!this._wslaMode) return this.parameters();
|
|
434
|
+
const params: LayerParam[] = [];
|
|
435
|
+
for (let i = 0; i < this.layers.length; i++) {
|
|
436
|
+
for (const p of this.layers[i]!.getTrainableParams()) {
|
|
437
|
+
params.push({ ...p, name: `layer${i}.${p.name}` });
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
return params;
|
|
441
|
+
}
|
|
442
|
+
|
|
416
443
|
setWSLAMode(enabled: boolean): void {
|
|
417
444
|
for (const layer of this.layers) layer.setWSLAMode(enabled);
|
|
418
445
|
this._wslaMode = enabled;
|
package/src/moe/index.ts
CHANGED
package/src/moe/moe_package.ts
CHANGED
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
|
|
14
14
|
import { SharedExpertMoE, type MoEConfig } from "./moe_model.js";
|
|
15
15
|
import { EvermindLM, type EvermindLMConfig } from "../lm/evermind_lm.js";
|
|
16
|
+
import { VideoRVQCodec } from "../codec/video_rvq.js";
|
|
16
17
|
|
|
17
18
|
/** First 4 bytes of a serialised package: "EVM1". */
|
|
18
19
|
const PKG_MAGIC = 0x45564d31;
|
|
@@ -21,6 +22,9 @@ const PKG_VERSION = 1;
|
|
|
21
22
|
/** The kinds of model an `.evermind` package can carry. */
|
|
22
23
|
export type EvermindModelType = "shared-expert-moe" | "evermind-lm";
|
|
23
24
|
|
|
25
|
+
/** Output modality. `text` (default) needs no codec; `video`/`image` bundle a VRQ codec. */
|
|
26
|
+
export type EvermindModality = "text" | "video" | "image";
|
|
27
|
+
|
|
24
28
|
/** Human-facing description published with the model (the "model card"). */
|
|
25
29
|
export interface EvermindModelCard {
|
|
26
30
|
description: string;
|
|
@@ -38,14 +42,26 @@ export interface EvermindModelManifest {
|
|
|
38
42
|
name: string;
|
|
39
43
|
version: string;
|
|
40
44
|
modelType: EvermindModelType;
|
|
45
|
+
/** Output modality; absent ⇒ "text". Video/image also carry a `codec` section. */
|
|
46
|
+
modality?: EvermindModality;
|
|
41
47
|
/** Flat numeric model config (the constructor args), serialised verbatim. */
|
|
42
48
|
config: Record<string, number>;
|
|
43
49
|
/** Total trainable scalar parameters (for sizing / pricing / display). */
|
|
44
50
|
paramCount: number;
|
|
45
51
|
checkpointFormat: "MoE0" | "EVL0";
|
|
46
52
|
checkpointFp16: boolean;
|
|
53
|
+
/**
|
|
54
|
+
* Byte length of the checkpoint section — REQUIRED when a codec section follows
|
|
55
|
+
* (so the reader knows where the checkpoint ends). Absent ⇒ checkpoint runs to
|
|
56
|
+
* end-of-blob (the original text-only layout, still read verbatim).
|
|
57
|
+
*/
|
|
58
|
+
checkpointBytes?: number;
|
|
47
59
|
/** 32-bit FNV-1a over the checkpoint bytes — integrity for download/purchase. */
|
|
48
60
|
checksum: number;
|
|
61
|
+
/** Media codec section format (present for video/image packages). */
|
|
62
|
+
codecFormat?: "VRQ0";
|
|
63
|
+
/** 32-bit FNV-1a over the codec bytes — integrity for the bundled codec. */
|
|
64
|
+
codecChecksum?: number;
|
|
49
65
|
card: EvermindModelCard;
|
|
50
66
|
/** ISO timestamp, caller-supplied (the engine avoids Date for determinism). */
|
|
51
67
|
createdAt?: string;
|
|
@@ -84,6 +100,8 @@ export class EvermindModelPackage {
|
|
|
84
100
|
constructor(
|
|
85
101
|
readonly manifest: EvermindModelManifest,
|
|
86
102
|
readonly checkpoint: ArrayBuffer,
|
|
103
|
+
/** Serialized media codec ("VRQ0" blob), present for video/image packages. */
|
|
104
|
+
readonly codec?: ArrayBuffer,
|
|
87
105
|
) {}
|
|
88
106
|
|
|
89
107
|
/** Package a trained model with its publishing metadata. */
|
|
@@ -126,17 +144,61 @@ export class EvermindModelPackage {
|
|
|
126
144
|
return new EvermindModelPackage(manifest, checkpoint);
|
|
127
145
|
}
|
|
128
146
|
|
|
129
|
-
/**
|
|
147
|
+
/**
|
|
148
|
+
* Package a video/image generative model: its {@link EvermindLM} weights AND the
|
|
149
|
+
* {@link VideoRVQCodec} needed to turn generated tokens back into frames. Without
|
|
150
|
+
* the bundled codec a served media model would emit undecodable token ids — this
|
|
151
|
+
* is what makes an `.evermind` media artifact self-contained and servable.
|
|
152
|
+
*/
|
|
153
|
+
static fromMediaLM(
|
|
154
|
+
lm: EvermindLM,
|
|
155
|
+
codec: VideoRVQCodec,
|
|
156
|
+
meta: PackageMeta & { modality: "video" | "image" },
|
|
157
|
+
): EvermindModelPackage {
|
|
158
|
+
if (lm.config.vocabSize !== codec.vocabSize) {
|
|
159
|
+
throw new Error(
|
|
160
|
+
`fromMediaLM: LM vocabSize (${lm.config.vocabSize}) must equal codec.vocabSize (${codec.vocabSize})`,
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
const fp16 = meta.fp16 ?? false;
|
|
164
|
+
const checkpoint = lm.exportWeights({ fp16 });
|
|
165
|
+
const codecBlob = codec.serialize();
|
|
166
|
+
const manifest: EvermindModelManifest = {
|
|
167
|
+
schema: "evermind.model/1",
|
|
168
|
+
name: meta.name,
|
|
169
|
+
version: meta.version,
|
|
170
|
+
modelType: "evermind-lm",
|
|
171
|
+
modality: meta.modality,
|
|
172
|
+
config: lm.config as unknown as Record<string, number>,
|
|
173
|
+
paramCount: lm.parameters().reduce((n, p) => n + p.data.length, 0),
|
|
174
|
+
checkpointFormat: "EVL0",
|
|
175
|
+
checkpointFp16: fp16,
|
|
176
|
+
checkpointBytes: checkpoint.byteLength,
|
|
177
|
+
checksum: fnv1a(new Uint8Array(checkpoint)),
|
|
178
|
+
codecFormat: "VRQ0",
|
|
179
|
+
codecChecksum: fnv1a(new Uint8Array(codecBlob)),
|
|
180
|
+
card: meta.card,
|
|
181
|
+
...(meta.createdAt ? { createdAt: meta.createdAt } : {}),
|
|
182
|
+
};
|
|
183
|
+
return new EvermindModelPackage(manifest, checkpoint, codecBlob);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/** Serialise to a single `.evermind` blob: magic, version, manifest, checkpoint[, codec]. */
|
|
130
187
|
toBlob(): ArrayBuffer {
|
|
131
188
|
const manifestBytes = new TextEncoder().encode(JSON.stringify(this.manifest));
|
|
132
189
|
const headerBytes = 12; // magic, version, manifestLen
|
|
133
|
-
const
|
|
190
|
+
const codecBytes = this.codec?.byteLength ?? 0;
|
|
191
|
+
const out = new ArrayBuffer(headerBytes + manifestBytes.byteLength + this.checkpoint.byteLength + codecBytes);
|
|
134
192
|
const head = new Uint32Array(out, 0, 3);
|
|
135
193
|
head[0] = PKG_MAGIC;
|
|
136
194
|
head[1] = PKG_VERSION;
|
|
137
195
|
head[2] = manifestBytes.byteLength;
|
|
138
|
-
|
|
139
|
-
new Uint8Array(out,
|
|
196
|
+
let o = headerBytes;
|
|
197
|
+
new Uint8Array(out, o, manifestBytes.byteLength).set(manifestBytes);
|
|
198
|
+
o += manifestBytes.byteLength;
|
|
199
|
+
new Uint8Array(out, o, this.checkpoint.byteLength).set(new Uint8Array(this.checkpoint));
|
|
200
|
+
o += this.checkpoint.byteLength;
|
|
201
|
+
if (this.codec) new Uint8Array(out, o, codecBytes).set(new Uint8Array(this.codec));
|
|
140
202
|
return out;
|
|
141
203
|
}
|
|
142
204
|
|
|
@@ -152,8 +214,19 @@ export class EvermindModelPackage {
|
|
|
152
214
|
}
|
|
153
215
|
const manifestBytes = new Uint8Array(buffer, headerBytes, manifestLen);
|
|
154
216
|
const manifest = JSON.parse(new TextDecoder().decode(manifestBytes)) as EvermindModelManifest;
|
|
155
|
-
const
|
|
156
|
-
|
|
217
|
+
const bodyStart = headerBytes + manifestLen;
|
|
218
|
+
// A codec section follows the checkpoint only when the manifest fixed the
|
|
219
|
+
// checkpoint length; otherwise the checkpoint runs to end-of-blob (text layout).
|
|
220
|
+
const cpBytes = manifest.checkpointBytes;
|
|
221
|
+
if (cpBytes != null) {
|
|
222
|
+
if (bodyStart + cpBytes > buffer.byteLength) {
|
|
223
|
+
throw new Error("EvermindModelPackage.fromBlob: truncated (checkpointBytes exceeds blob)");
|
|
224
|
+
}
|
|
225
|
+
const checkpoint = buffer.slice(bodyStart, bodyStart + cpBytes);
|
|
226
|
+
const codec = bodyStart + cpBytes < buffer.byteLength ? buffer.slice(bodyStart + cpBytes) : undefined;
|
|
227
|
+
return new EvermindModelPackage(manifest, checkpoint, codec);
|
|
228
|
+
}
|
|
229
|
+
return new EvermindModelPackage(manifest, buffer.slice(bodyStart));
|
|
157
230
|
}
|
|
158
231
|
|
|
159
232
|
/** Verify integrity + structural sanity before trusting a downloaded package. */
|
|
@@ -176,6 +249,19 @@ export class EvermindModelPackage {
|
|
|
176
249
|
if (actual !== this.manifest.checksum) {
|
|
177
250
|
errors.push(`checksum mismatch (manifest ${this.manifest.checksum}, actual ${actual}) — corrupt or tampered checkpoint`);
|
|
178
251
|
}
|
|
252
|
+
// Media packages must carry an integral codec — a served media model can't
|
|
253
|
+
// decode tokens → frames without it.
|
|
254
|
+
const modality = this.manifest.modality ?? "text";
|
|
255
|
+
if (modality === "video" || modality === "image") {
|
|
256
|
+
if (!this.codec) {
|
|
257
|
+
errors.push(`modality '${modality}' package is missing its codec section`);
|
|
258
|
+
} else {
|
|
259
|
+
const codecActual = fnv1a(new Uint8Array(this.codec));
|
|
260
|
+
if (codecActual !== this.manifest.codecChecksum) {
|
|
261
|
+
errors.push(`codec checksum mismatch (manifest ${this.manifest.codecChecksum}, actual ${codecActual}) — corrupt codec`);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
179
265
|
return { ok: errors.length === 0, errors };
|
|
180
266
|
}
|
|
181
267
|
|
|
@@ -202,4 +288,19 @@ export class EvermindModelPackage {
|
|
|
202
288
|
lm.loadWeights(this.checkpoint);
|
|
203
289
|
return lm;
|
|
204
290
|
}
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Reconstruct a video/image model: the generative {@link EvermindLM} plus its
|
|
294
|
+
* {@link VideoRVQCodec}. Pair with `generateVideo` / `generateImage` to produce
|
|
295
|
+
* frames. This is what a media-serving path (gateway) loads and runs.
|
|
296
|
+
*/
|
|
297
|
+
loadMediaLM(): { lm: EvermindLM; codec: VideoRVQCodec; modality: EvermindModality } {
|
|
298
|
+
const v = this.validate();
|
|
299
|
+
if (!v.ok) throw new Error(`EvermindModelPackage.loadMediaLM: ${v.errors.join("; ")}`);
|
|
300
|
+
const modality = this.manifest.modality ?? "text";
|
|
301
|
+
if (modality !== "video" && modality !== "image") {
|
|
302
|
+
throw new Error(`loadMediaLM: package modality is '${modality}', use loadLM()`);
|
|
303
|
+
}
|
|
304
|
+
return { lm: this.loadLM(), codec: VideoRVQCodec.deserialize(this.codec!), modality };
|
|
305
|
+
}
|
|
205
306
|
}
|
package/src/tokenizer/bpe.ts
CHANGED
|
@@ -9,6 +9,21 @@ export interface BPEEncodeOptions {
|
|
|
9
9
|
|
|
10
10
|
export type PadSide = 'right' | 'left';
|
|
11
11
|
|
|
12
|
+
/** A Hugging Face `tokenizer.json` (or a bare `{ vocab, merges }`) for import. */
|
|
13
|
+
export interface HuggingFaceTokenizerSpec {
|
|
14
|
+
model?: { vocab?: Record<string, number>; merges?: Array<string | [string, string]> };
|
|
15
|
+
vocab?: Record<string, number>;
|
|
16
|
+
merges?: Array<string | [string, string]>;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** Override the special-token strings to match an imported tokenizer. */
|
|
20
|
+
export interface SpecialTokenOverrides {
|
|
21
|
+
bos?: string;
|
|
22
|
+
eos?: string;
|
|
23
|
+
pad?: string;
|
|
24
|
+
unk?: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
12
27
|
function buildByteEncoder(): Map<number, string> {
|
|
13
28
|
const enc = new Map<number, string>();
|
|
14
29
|
const ranges: [number, number][] = [
|
|
@@ -177,6 +192,50 @@ export class BPETokenizer {
|
|
|
177
192
|
this.padId = this.vocab.get(this.padToken) ?? null;
|
|
178
193
|
}
|
|
179
194
|
|
|
195
|
+
/**
|
|
196
|
+
* Seed this tokenizer from an existing Hugging Face `tokenizer.json` — the
|
|
197
|
+
* *import-merges* path (vs `train`, which learns a fresh vocab from a corpus).
|
|
198
|
+
*
|
|
199
|
+
* BPE merges + vocab are portable DATA, not architecture: a proven code
|
|
200
|
+
* tokenizer (GPT-2 / Llama / StarCoder family) gives Evermind a battle-tested
|
|
201
|
+
* code vocabulary on day one. Those tokenizers use the same GPT-2 byte→unicode
|
|
202
|
+
* mapping this class already implements ({@link BYTE_ENCODER}), so the imported
|
|
203
|
+
* vocab is directly compatible with `encode`/`decode`/`_bpe`.
|
|
204
|
+
*
|
|
205
|
+
* Accepts the full `tokenizer.json` object (reads `.model.vocab` / `.model.merges`)
|
|
206
|
+
* or a bare `{ vocab, merges }`. `merges` may be the classic `"a b"` strings or
|
|
207
|
+
* the newer `["a", "b"]` pair arrays. Special-token strings can be overridden to
|
|
208
|
+
* match the source tokenizer (e.g. `<|endoftext|>`, `<s>`, `</s>`).
|
|
209
|
+
*/
|
|
210
|
+
loadHuggingFace(spec: HuggingFaceTokenizerSpec, specials: SpecialTokenOverrides = {}): void {
|
|
211
|
+
const model = (spec.model ?? spec) as { vocab?: Record<string, number>; merges?: Array<string | [string, string]> };
|
|
212
|
+
const vocabObj = model.vocab ?? (spec as HuggingFaceTokenizerSpec).vocab;
|
|
213
|
+
const rawMerges = model.merges ?? (spec as HuggingFaceTokenizerSpec).merges;
|
|
214
|
+
if (!vocabObj || typeof vocabObj !== 'object') {
|
|
215
|
+
throw new Error('loadHuggingFace: missing model.vocab (expected a Hugging Face tokenizer.json)');
|
|
216
|
+
}
|
|
217
|
+
if (!Array.isArray(rawMerges)) {
|
|
218
|
+
throw new Error('loadHuggingFace: missing model.merges (expected a BPE tokenizer.json)');
|
|
219
|
+
}
|
|
220
|
+
const mergeArr = rawMerges.map((m) => (Array.isArray(m) ? `${m[0]} ${m[1]}` : String(m)));
|
|
221
|
+
if (specials.bos) this.bosToken = specials.bos;
|
|
222
|
+
if (specials.eos) this.eosToken = specials.eos;
|
|
223
|
+
if (specials.pad) this.padToken = specials.pad;
|
|
224
|
+
if (specials.unk) this.unkToken = specials.unk;
|
|
225
|
+
this.loadFromObjects(vocabObj, mergeArr);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Fetch and import a Hugging Face `tokenizer.json` by URL — the IDE's
|
|
230
|
+
* "import merges from a repo" path. Resolves once the tokenizer is loaded.
|
|
231
|
+
*/
|
|
232
|
+
async loadHuggingFaceUrl(url: string, specials: SpecialTokenOverrides = {}): Promise<void> {
|
|
233
|
+
const res = await fetch(url);
|
|
234
|
+
if (!res.ok) throw new Error(`loadHuggingFaceUrl: ${res.status} fetching ${url}`);
|
|
235
|
+
const spec = (await res.json()) as HuggingFaceTokenizerSpec;
|
|
236
|
+
this.loadHuggingFace(spec, specials);
|
|
237
|
+
}
|
|
238
|
+
|
|
180
239
|
encode(text: string, opts: BPEEncodeOptions = {}): number[] {
|
|
181
240
|
const words = text.match(PRE_TOKENIZE_RE) ?? [];
|
|
182
241
|
const ids: number[] = [];
|