@seanhogg/builderforce-memory-engine 2026.9.28 → 2026.9.30

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 (112) hide show
  1. package/dist/evermind/adapt.d.ts +13 -0
  2. package/dist/evermind/adapt.d.ts.map +1 -1
  3. package/dist/evermind/adapt.js +13 -1
  4. package/dist/evermind/adapt.js.map +1 -1
  5. package/dist/evermind/index.d.ts +3 -3
  6. package/dist/evermind/index.d.ts.map +1 -1
  7. package/dist/evermind/index.js +2 -2
  8. package/dist/evermind/index.js.map +1 -1
  9. package/dist/evermind/package.d.ts +16 -0
  10. package/dist/evermind/package.d.ts.map +1 -1
  11. package/dist/evermind/package.js +40 -14
  12. package/dist/evermind/package.js.map +1 -1
  13. package/dist/experience/approval.d.ts +16 -0
  14. package/dist/experience/approval.d.ts.map +1 -0
  15. package/dist/experience/approval.js +40 -0
  16. package/dist/experience/approval.js.map +1 -0
  17. package/dist/experience/corpus.d.ts +24 -0
  18. package/dist/experience/corpus.d.ts.map +1 -0
  19. package/dist/experience/corpus.js +90 -0
  20. package/dist/experience/corpus.js.map +1 -0
  21. package/dist/experience/index.d.ts +18 -0
  22. package/dist/experience/index.d.ts.map +1 -0
  23. package/dist/experience/index.js +8 -0
  24. package/dist/experience/index.js.map +1 -0
  25. package/dist/experience/overview.d.ts +52 -0
  26. package/dist/experience/overview.d.ts.map +1 -0
  27. package/dist/experience/overview.js +92 -0
  28. package/dist/experience/overview.js.map +1 -0
  29. package/dist/experience/schedule.d.ts +10 -0
  30. package/dist/experience/schedule.d.ts.map +1 -0
  31. package/dist/experience/schedule.js +22 -0
  32. package/dist/experience/schedule.js.map +1 -0
  33. package/dist/experience/store.d.ts +88 -0
  34. package/dist/experience/store.d.ts.map +1 -0
  35. package/dist/experience/store.js +128 -0
  36. package/dist/experience/store.js.map +1 -0
  37. package/dist/experience/trainOnce.d.ts +18 -0
  38. package/dist/experience/trainOnce.d.ts.map +1 -0
  39. package/dist/experience/trainOnce.js +74 -0
  40. package/dist/experience/trainOnce.js.map +1 -0
  41. package/dist/experience/types.d.ts +171 -0
  42. package/dist/experience/types.d.ts.map +1 -0
  43. package/dist/experience/types.js +22 -0
  44. package/dist/experience/types.js.map +1 -0
  45. package/dist/export/onnx.d.ts.map +1 -1
  46. package/dist/export/onnx.js +0 -8
  47. package/dist/export/onnx.js.map +1 -1
  48. package/dist/import/foreign/falcon_mamba.d.ts +15 -2
  49. package/dist/import/foreign/falcon_mamba.d.ts.map +1 -1
  50. package/dist/import/foreign/falcon_mamba.js +53 -36
  51. package/dist/import/foreign/falcon_mamba.js.map +1 -1
  52. package/dist/import/foreign/index.d.ts +5 -2
  53. package/dist/import/foreign/index.d.ts.map +1 -1
  54. package/dist/import/foreign/index.js +4 -2
  55. package/dist/import/foreign/index.js.map +1 -1
  56. package/dist/import/foreign/infer.d.ts +20 -0
  57. package/dist/import/foreign/infer.d.ts.map +1 -0
  58. package/dist/import/foreign/infer.js +58 -0
  59. package/dist/import/foreign/infer.js.map +1 -0
  60. package/dist/import/foreign/registry.d.ts.map +1 -1
  61. package/dist/import/foreign/registry.js +2 -1
  62. package/dist/import/foreign/registry.js.map +1 -1
  63. package/dist/import/foreign/vocab.d.ts +19 -0
  64. package/dist/import/foreign/vocab.d.ts.map +1 -0
  65. package/dist/import/foreign/vocab.js +40 -0
  66. package/dist/import/foreign/vocab.js.map +1 -0
  67. package/dist/import/index.d.ts +2 -2
  68. package/dist/import/index.d.ts.map +1 -1
  69. package/dist/import/index.js +1 -1
  70. package/dist/import/index.js.map +1 -1
  71. package/dist/import/safetensors.d.ts +1 -1
  72. package/dist/import/safetensors.js +11 -2
  73. package/dist/import/safetensors.js.map +1 -1
  74. package/dist/index.d.ts +5 -4
  75. package/dist/index.d.ts.map +1 -1
  76. package/dist/index.js +5 -3
  77. package/dist/index.js.map +1 -1
  78. package/dist/moe/moe_model.d.ts +2 -0
  79. package/dist/moe/moe_model.d.ts.map +1 -1
  80. package/dist/moe/moe_model.js +9 -7
  81. package/dist/moe/moe_model.js.map +1 -1
  82. package/dist/moe/moe_trainer.d.ts.map +1 -1
  83. package/dist/moe/moe_trainer.js +5 -8
  84. package/dist/moe/moe_trainer.js.map +1 -1
  85. package/dist/utils/rng.d.ts +7 -3
  86. package/dist/utils/rng.d.ts.map +1 -1
  87. package/dist/utils/rng.js +10 -6
  88. package/dist/utils/rng.js.map +1 -1
  89. package/package.json +1 -1
  90. package/src/evermind/adapt.ts +26 -1
  91. package/src/evermind/index.ts +3 -3
  92. package/src/evermind/package.ts +39 -12
  93. package/src/experience/approval.ts +49 -0
  94. package/src/experience/corpus.ts +103 -0
  95. package/src/experience/index.ts +35 -0
  96. package/src/experience/overview.ts +143 -0
  97. package/src/experience/schedule.ts +29 -0
  98. package/src/experience/store.ts +188 -0
  99. package/src/experience/trainOnce.ts +85 -0
  100. package/src/experience/types.ts +162 -0
  101. package/src/export/onnx.ts +0 -7
  102. package/src/import/foreign/falcon_mamba.ts +63 -40
  103. package/src/import/foreign/index.ts +5 -2
  104. package/src/import/foreign/infer.ts +62 -0
  105. package/src/import/foreign/registry.ts +2 -1
  106. package/src/import/foreign/vocab.ts +50 -0
  107. package/src/import/index.ts +5 -0
  108. package/src/import/safetensors.ts +9 -2
  109. package/src/index.ts +14 -1
  110. package/src/moe/moe_model.ts +9 -7
  111. package/src/moe/moe_trainer.ts +5 -8
  112. package/src/utils/rng.ts +10 -6
@@ -0,0 +1,162 @@
1
+ /**
2
+ * experience/types.ts — what Evermind has DONE, as opposed to what it knows.
3
+ *
4
+ * Facts live in `@seanhogg/builderforce-memory` (MemoryStore / EvermindCognition) and
5
+ * weights in the model; this is the third kind of learned item: experience.
6
+ * - an **Episode** is one demonstration — a person doing a task in a program, recorded
7
+ * as semantic steps (the UI element acted on and the value set, never raw keystrokes);
8
+ * - a **Skill** is what Train Once compiles from an episode — a procedure that runs
9
+ * again with new values, with approval gates on irreversible steps;
10
+ * - a **Run** is one execution of a skill, with its step-by-step audit trail.
11
+ *
12
+ * Plain data, JSON-shaped (camelCase, actions tagged by `kind`), so any recorder — the
13
+ * native Windows UI Automation one in Synapse included — produces it and any runtime
14
+ * persists it. Timestamps are epoch milliseconds.
15
+ */
16
+
17
+ /** A UI element, identified the way the platform's accessibility API sees it. */
18
+ export interface ElementRef {
19
+ name: string;
20
+ automationId: string;
21
+ /** `Button`, `Edit`, `MenuItem`, … */
22
+ controlType: string;
23
+ className: string;
24
+ /** The top-level window the element sits in. */
25
+ windowTitle: string;
26
+ processName: string;
27
+ /** Where it was acted on, relative to the window's top-left — the replay fallback. */
28
+ relX: number;
29
+ relY: number;
30
+ }
31
+
32
+ /** One thing the person did. A secret field records that it was filled, never the value. */
33
+ export type Action =
34
+ | { kind: 'click'; target: ElementRef }
35
+ | { kind: 'setValue'; target: ElementRef; value: string; secret?: boolean }
36
+ | { kind: 'keys'; keys: string; target?: ElementRef | null };
37
+
38
+ export interface RecordedStep {
39
+ id: string;
40
+ /** Milliseconds since the recording started. */
41
+ atMs: number;
42
+ action: Action;
43
+ /** Screenshot file name inside the episode's folder, if one was taken. */
44
+ screenshot?: string | null;
45
+ }
46
+
47
+ export interface Episode {
48
+ id: string;
49
+ name: string;
50
+ program: string;
51
+ args: string[];
52
+ startedAt: number;
53
+ endedAt?: number | null;
54
+ steps: RecordedStep[];
55
+ }
56
+
57
+ export interface EpisodeSummary {
58
+ id: string;
59
+ name: string;
60
+ program: string;
61
+ startedAt: number;
62
+ steps: number;
63
+ }
64
+
65
+ /** A value a skill asks for at run time. */
66
+ export interface SkillParam {
67
+ /** Stable key, e.g. `amount`. */
68
+ name: string;
69
+ /** What the field was called where it was typed, e.g. `Amount`. */
70
+ label: string;
71
+ /** The value from the demonstration; never set for a secret. */
72
+ default?: string | null;
73
+ secret: boolean;
74
+ }
75
+
76
+ export type SkillValue = { from: 'param'; name: string } | { from: 'literal'; text: string };
77
+
78
+ export type SkillAction =
79
+ | { kind: 'click'; target: ElementRef }
80
+ | { kind: 'setValue'; target: ElementRef; value: SkillValue }
81
+ | { kind: 'keys'; keys: string; target?: ElementRef | null };
82
+
83
+ export interface SkillStep {
84
+ id: string;
85
+ action: SkillAction;
86
+ /** Pause and ask the person before this step. */
87
+ requiresApproval: boolean;
88
+ screenshot?: string | null;
89
+ }
90
+
91
+ /** When a skill runs by itself. Local time of day for `day`. */
92
+ export type Schedule = { every: 'minutes'; minutes: number } | { every: 'day'; hour: number; minute: number };
93
+
94
+ /** A skill's routine: at most one per skill, so it lives on the skill. */
95
+ export interface SkillRoutine {
96
+ schedule: Schedule;
97
+ /** Values for the non-secret parameters; secrets come from the host's vault. */
98
+ values: Record<string, string>;
99
+ enabled: boolean;
100
+ lastRunAt?: number | null;
101
+ }
102
+
103
+ export interface Skill {
104
+ id: string;
105
+ name: string;
106
+ program: string;
107
+ args: string[];
108
+ params: SkillParam[];
109
+ steps: SkillStep[];
110
+ sourceEpisode: string;
111
+ createdAt: number;
112
+ routine?: SkillRoutine | null;
113
+ }
114
+
115
+ export type RunStatus = 'running' | 'succeeded' | 'failed' | 'stopped' | 'denied';
116
+
117
+ export type StepOutcome = 'ok' | 'fallback' | 'failed' | 'approved' | 'denied';
118
+
119
+ /** One line of a run's audit trail. A step can have two (approved, then ok). */
120
+ export interface RunStepLog {
121
+ idx: number;
122
+ stepId: string;
123
+ outcome: StepOutcome;
124
+ detail?: string | null;
125
+ at: number;
126
+ }
127
+
128
+ export interface Run {
129
+ id: string;
130
+ skillId: string;
131
+ skillName: string;
132
+ /** `manual` or `routine`. */
133
+ trigger: string;
134
+ status: RunStatus;
135
+ startedAt: number;
136
+ endedAt?: number | null;
137
+ error?: string | null;
138
+ steps: RunStepLog[];
139
+ }
140
+
141
+ /**
142
+ * One adaptation of the private model on experience: what it learned and how the fit
143
+ * went. Measured when the adaptation ran, never estimated afterwards.
144
+ */
145
+ export interface Adaptation {
146
+ /** N of the `+expN` version this adaptation wrote. */
147
+ index: number;
148
+ version: string;
149
+ /** Episode and skill ids it learned. */
150
+ learned: string[];
151
+ passes: number;
152
+ /** Mean fit loss over the passes. */
153
+ loss: number;
154
+ at: number;
155
+ }
156
+
157
+ /** A short random id — unique enough for one person's store. */
158
+ export function newExperienceId(): string {
159
+ const bytes = new Uint8Array(8);
160
+ globalThis.crypto.getRandomValues(bytes);
161
+ return Array.from(bytes, (b) => b.toString(16).padStart(2, '0')).join('');
162
+ }
@@ -95,13 +95,6 @@ class GraphBuilder {
95
95
  }
96
96
  }
97
97
 
98
- /** Transpose a 2-D row-major matrix [rows, cols] → [cols, rows]. */
99
- function transpose2d(data: Float32Array, rows: number, cols: number): Float32Array {
100
- const out = new Float32Array(data.length);
101
- for (let r = 0; r < rows; r++) for (let c = 0; c < cols; c++) out[c * rows + r] = data[r * cols + c]!;
102
- return out;
103
- }
104
-
105
98
  /** RMSNorm(x, gain) as graph ops. Returns the normalised value name. */
106
99
  function rmsNorm(g: GraphBuilder, x: string, gainInit: string, eps: string, axes: string): string {
107
100
  const sq = g.op("Mul", [x, x], "rms_sq");
@@ -1,6 +1,9 @@
1
1
  /**
2
- * import/foreign/falcon_mamba.ts — weight port for Falcon-Mamba
3
- * (`FalconMambaForCausalLM`, `model_type: "falcon_mamba"`, Mamba-1 / S6).
2
+ * import/foreign/falcon_mamba.ts — weight port for the Mamba-1 / S6 family:
3
+ * Falcon-Mamba (`FalconMambaForCausalLM`, `model_type: "falcon_mamba"`) and the
4
+ * original Mamba (`MambaForCausalLM`, `model_type: "mamba"` — `state-spaces/mamba-*`).
5
+ * They share one tensor layout and one config vocabulary, so one rule table and one
6
+ * `describe` serve both ({@link mamba1Adapter}).
4
7
  *
5
8
  * Source layout confirmed against `tiiuae/falcon-mamba-7b`'s published
6
9
  * `config.json`, `model.safetensors.index.json` (643 tensors = 10 per layer × 64
@@ -34,6 +37,8 @@
34
37
  * • Linear weights are `[out, in]` row-major on both sides (no transpose).
35
38
  *
36
39
  * Fidelity caveats recorded on the plan rather than hidden:
40
+ * • (Falcon-Mamba only) the dt/B/C norms below. Plain Mamba has none, so its port
41
+ * is exact.
37
42
  * • `use_bias: false` upstream, so `in_proj` / `x_proj` / `out_proj` ship no
38
43
  * bias; this engine's blocks always own one, and they are filled with zeros
39
44
  * (an exact no-op, not an approximation).
@@ -59,8 +64,6 @@ import {
59
64
  type PortTarget,
60
65
  } from "./adapter.js";
61
66
 
62
- const ID = "falcon_mamba";
63
-
64
67
  /** `backbone.embeddings` (HF) vs `backbone.embedding` (Mamba-SSM native). */
65
68
  const EMBEDDING_NAMES = ["backbone.embeddings.weight", "backbone.embedding.weight"];
66
69
 
@@ -131,43 +134,63 @@ function rules(a: {
131
134
  return out;
132
135
  }
133
136
 
134
- export const falconMambaAdapter: ForeignMambaAdapter = {
135
- id: ID,
136
- label: "Falcon-Mamba (Mamba-1 / S6)",
137
- modelTypes: ["falcon_mamba"],
138
- architectures: ["FalconMambaForCausalLM"],
137
+ /** A Mamba-1 family adapter: the shared rule table under one checkpoint family's identity. */
138
+ export function mamba1Adapter(identity: {
139
+ id: string;
140
+ label: string;
141
+ modelTypes: readonly string[];
142
+ architectures: readonly string[];
143
+ }): ForeignMambaAdapter {
144
+ const ID = identity.id;
145
+ return {
146
+ ...identity,
147
+
148
+ describe(config: ForeignConfig): PortTarget {
149
+ const dModel = requireInt(config, "hidden_size");
150
+ const numLayers = requireInt(config, "num_hidden_layers");
151
+ const vocabSize = requireInt(config, "vocab_size");
152
+ const dState = requireInt(config, "state_size");
153
+ const dConv = requireInt(config, "conv_kernel");
154
+ const dInner = readInnerSize(config, dModel);
155
+ const expand = expandFactor(dInner, dModel, identity.label);
156
+ const dtRank = readTimeStepRank(config, dModel);
139
157
 
140
- describe(config: ForeignConfig): PortTarget {
141
- const dModel = requireInt(config, "hidden_size");
142
- const numLayers = requireInt(config, "num_hidden_layers");
143
- const vocabSize = requireInt(config, "vocab_size");
144
- const dState = requireInt(config, "state_size");
145
- const dConv = requireInt(config, "conv_kernel");
146
- const dInner = readInnerSize(config, dModel);
147
- const expand = expandFactor(dInner, dModel, "Falcon-Mamba");
148
- const dtRank = readTimeStepRank(config, dModel);
158
+ const modelConfig: HybridMambaModelConfig = {
159
+ vocabSize,
160
+ dModel,
161
+ numLayers,
162
+ dState,
163
+ dConv,
164
+ expand,
165
+ // Every layer is a Mamba-1 mixer (the model's default schedule), but the
166
+ // dt rank and conv bias are per-block settings the shorthand can't carry.
167
+ defaultMamba1: { dtRank, biasConv: true },
168
+ };
149
169
 
150
- const modelConfig: HybridMambaModelConfig = {
151
- vocabSize,
152
- dModel,
153
- numLayers,
154
- dState,
155
- dConv,
156
- expand,
157
- // Every layer is a Mamba-1 mixer (the model's default schedule), but the
158
- // dt rank and conv bias are per-block settings the shorthand can't carry.
159
- defaultMamba1: { dtRank, biasConv: true },
160
- };
170
+ const plan: PortPlan = {
171
+ adapter: ID,
172
+ rules: rules({ vocabSize, dModel, numLayers, dInner, dState, dConv, dtRank }),
173
+ // Nothing is partially consumed: every Mamba-1 tensor this plan
174
+ // touches is taken whole. `lm_head.weight` is untouched entirely and
175
+ // surfaces through `unmappedSources`.
176
+ discards: [],
177
+ };
161
178
 
162
- const plan: PortPlan = {
163
- adapter: ID,
164
- rules: rules({ vocabSize, dModel, numLayers, dInner, dState, dConv, dtRank }),
165
- // Nothing is partially consumed: every Falcon-Mamba tensor this plan
166
- // touches is taken whole. `lm_head.weight` is untouched entirely and
167
- // surfaces through `unmappedSources`.
168
- discards: [],
169
- };
179
+ return { modelConfig, plan };
180
+ },
181
+ };
182
+ }
183
+
184
+ export const falconMambaAdapter = mamba1Adapter({
185
+ id: "falcon_mamba",
186
+ label: "Falcon-Mamba (Mamba-1 / S6)",
187
+ modelTypes: ["falcon_mamba"],
188
+ architectures: ["FalconMambaForCausalLM"],
189
+ });
170
190
 
171
- return { modelConfig, plan };
172
- },
173
- };
191
+ export const mambaAdapter = mamba1Adapter({
192
+ id: "mamba",
193
+ label: "Mamba (Mamba-1 / S6)",
194
+ modelTypes: ["mamba"],
195
+ architectures: ["MambaForCausalLM"],
196
+ });
@@ -6,7 +6,7 @@
6
6
  * to build plus a name-keyed set of weights, together with an explicit account of
7
7
  * anything that did not map cleanly.
8
8
  *
9
- * Supported: Falcon-Mamba (Mamba-1) and Codestral-Mamba (Mamba-2). Transformer
9
+ * Supported: Mamba and Falcon-Mamba (Mamba-1) and Codestral-Mamba (Mamba-2). Transformer
10
10
  * checkpoints are NOT portable at any tensor naming — distillation is the only
11
11
  * route, and {@link foreignMambaAdapterFor} says so when it rejects one.
12
12
  */
@@ -33,5 +33,8 @@ export type {
33
33
 
34
34
  export type { ForeignConfig, ForeignMambaAdapter, PortTarget } from "./adapter.js";
35
35
 
36
- export { falconMambaAdapter } from "./falcon_mamba.js";
36
+ export { falconMambaAdapter, mambaAdapter, mamba1Adapter } from "./falcon_mamba.js";
37
+ export { mambaConfigFromTensors } from "./infer.js";
38
+ export { resizePortedVocab } from "./vocab.js";
39
+ export type { ResizeVocabOptions } from "./vocab.js";
37
40
  export { codestralMambaAdapter } from "./codestral_mamba.js";
@@ -0,0 +1,62 @@
1
+ /**
2
+ * import/foreign/infer.ts — a Mamba-1 checkpoint's config, read off its tensors.
3
+ *
4
+ * `state-spaces/mamba-*` repositories publish `model.safetensors` with a
5
+ * `config.json` in the original Mamba-SSM vocabulary (`d_model`, `n_layer`, …)
6
+ * that no adapter reads, and a dropped safetensors file often arrives with no
7
+ * config at all. Every geometry fact the port needs is in the tensor shapes, so
8
+ * this derives the Hugging Face `MambaConfig` the {@link mambaAdapter} expects
9
+ * instead of anyone restating the derivation.
10
+ */
11
+
12
+ import type { NamedTensor } from "../../export/tensors.js";
13
+ import type { ForeignConfig } from "./adapter.js";
14
+ import { normaliseSourceName } from "./plan.js";
15
+
16
+ function fail(detail: string): never {
17
+ throw new Error(`import/foreign: cannot read a Mamba-1 config from these tensors — ${detail}`);
18
+ }
19
+
20
+ /**
21
+ * The `MambaForCausalLM` config implied by a Mamba-1 checkpoint's tensors:
22
+ * embedding `[vocab, d_model]`, per-layer `in_proj [2·d_inner, d_model]`,
23
+ * `conv1d [d_inner, (1,) d_conv]`, `A_log [d_inner, d_state]` and
24
+ * `x_proj [dt_rank + 2·d_state, d_inner]`.
25
+ */
26
+ export function mambaConfigFromTensors(tensors: readonly NamedTensor[]): ForeignConfig {
27
+ const byName = new Map(tensors.map((t) => [normaliseSourceName(t.name), t]));
28
+ const shape = (name: string) => byName.get(name)?.shape;
29
+
30
+ const emb = shape("backbone.embeddings.weight") ?? shape("backbone.embedding.weight");
31
+ if (!emb || emb.length !== 2) fail("no backbone.embedding(s).weight [vocab, d_model]");
32
+ const [vocabSize, dModel] = emb as [number, number];
33
+
34
+ let numLayers = 0;
35
+ while (byName.has(`backbone.layers.${numLayers}.mixer.in_proj.weight`)) numLayers++;
36
+ if (numLayers === 0) fail("no backbone.layers.N.mixer.in_proj.weight");
37
+
38
+ const m = "backbone.layers.0.mixer";
39
+ const inProj = shape(`${m}.in_proj.weight`)!;
40
+ const conv = shape(`${m}.conv1d.weight`);
41
+ const aLog = shape(`${m}.A_log`);
42
+ const xProj = shape(`${m}.x_proj.weight`);
43
+ if (!conv || !aLog || !xProj) fail(`layer 0 is missing conv1d.weight, A_log or x_proj.weight`);
44
+
45
+ const dInner = inProj[0]! / 2;
46
+ const dState = aLog[1]!;
47
+ const dConv = conv[conv.length - 1]!;
48
+ const dtRank = xProj[0]! - 2 * dState;
49
+ if (!Number.isInteger(dInner) || dtRank <= 0) fail(`inconsistent shapes (in_proj ${inProj.join("×")}, x_proj ${xProj.join("×")})`);
50
+
51
+ return {
52
+ model_type: "mamba",
53
+ architectures: ["MambaForCausalLM"],
54
+ vocab_size: vocabSize,
55
+ hidden_size: dModel,
56
+ num_hidden_layers: numLayers,
57
+ intermediate_size: dInner,
58
+ state_size: dState,
59
+ conv_kernel: dConv,
60
+ time_step_rank: dtRank,
61
+ };
62
+ }
@@ -12,12 +12,13 @@ import type { NamedTensor } from "../../export/tensors.js";
12
12
  import type { HybridMambaModelConfig } from "../../model/mamba_model.js";
13
13
  import { safetensorsToTensors } from "../safetensors.js";
14
14
  import { executePortPlan, type PortDiscard, type PortedTensors, type SynthesisedTarget } from "./plan.js";
15
- import { falconMambaAdapter } from "./falcon_mamba.js";
15
+ import { falconMambaAdapter, mambaAdapter } from "./falcon_mamba.js";
16
16
  import { codestralMambaAdapter } from "./codestral_mamba.js";
17
17
  import type { ForeignConfig, ForeignMambaAdapter } from "./adapter.js";
18
18
 
19
19
  /** Every foreign SSM architecture this engine can warm-start from. */
20
20
  export const FOREIGN_MAMBA_ADAPTERS: readonly ForeignMambaAdapter[] = [
21
+ mambaAdapter,
21
22
  falconMambaAdapter,
22
23
  codestralMambaAdapter,
23
24
  ];
@@ -0,0 +1,50 @@
1
+ /**
2
+ * import/foreign/vocab.ts — fit a ported checkpoint to a different vocabulary size.
3
+ *
4
+ * A ported model keeps its source tokenizer's vocabulary. To pair it with another
5
+ * tokenizer the embedding table (tied to the LM head) must have that tokenizer's row
6
+ * count: surplus rows are dropped, missing rows are drawn small and random so the
7
+ * new tokens start near the embedding cloud's centre. The new rows are recorded on
8
+ * the port as synthesised, never passed off as ported.
9
+ */
10
+
11
+ import { SeededRng, gaussianArray } from "../../utils/rng.js";
12
+ import type { PortedCheckpoint } from "./registry.js";
13
+
14
+ export interface ResizeVocabOptions {
15
+ /** Standard deviation of new rows. Default 0.02, the usual embedding init. */
16
+ std?: number;
17
+ /** Seed for the new rows, so the same resize produces the same checkpoint. Default 1. */
18
+ seed?: number;
19
+ }
20
+
21
+ /** `ported` with an embedding of `vocabSize` rows and a model config to match. */
22
+ export function resizePortedVocab(ported: PortedCheckpoint, vocabSize: number, opts: ResizeVocabOptions = {}): PortedCheckpoint {
23
+ if (!Number.isInteger(vocabSize) || vocabSize <= 0) {
24
+ throw new Error(`import/foreign: vocabulary size must be a positive integer, got ${vocabSize}`);
25
+ }
26
+ const from = ported.modelConfig.vocabSize;
27
+ if (vocabSize === from) return ported;
28
+
29
+ const emb = ported.weights.get("embedding");
30
+ if (!emb) throw new Error('import/foreign: the port has no "embedding" to resize');
31
+ const dModel = ported.modelConfig.dModel;
32
+
33
+ const resized = new Float32Array(vocabSize * dModel);
34
+ resized.set(emb.subarray(0, Math.min(from, vocabSize) * dModel));
35
+ const synthesisedTargets = [...ported.synthesisedTargets];
36
+ if (vocabSize > from) {
37
+ const std = opts.std ?? 0.02;
38
+ const rng = new SeededRng(opts.seed ?? 1);
39
+ resized.set(gaussianArray((vocabSize - from) * dModel, std, () => rng.next()), from * dModel);
40
+ synthesisedTargets.push({
41
+ target: "embedding",
42
+ value: std,
43
+ reason: `rows ${from}..${vocabSize - 1} drawn from N(0, ${std}²) to reach a ${vocabSize}-token vocabulary`,
44
+ });
45
+ }
46
+
47
+ const weights = new Map(ported.weights);
48
+ weights.set("embedding", resized);
49
+ return { ...ported, weights, synthesisedTargets, modelConfig: { ...ported.modelConfig, vocabSize } };
50
+ }
@@ -25,7 +25,11 @@ export {
25
25
  executePortPlan,
26
26
  normaliseSourceName,
27
27
  falconMambaAdapter,
28
+ mambaAdapter,
29
+ mamba1Adapter,
28
30
  codestralMambaAdapter,
31
+ mambaConfigFromTensors,
32
+ resizePortedVocab,
29
33
  } from "./foreign/index.js";
30
34
  export type {
31
35
  ForeignConfig,
@@ -38,4 +42,5 @@ export type {
38
42
  PortedCheckpoint,
39
43
  PortedTensors,
40
44
  SynthesisedTarget,
45
+ ResizeVocabOptions,
41
46
  } from "./foreign/index.js";
@@ -4,7 +4,7 @@
4
4
  * The exact inverse of {@link ../export/safetensors.ts}: parse the 8-byte
5
5
  * little-endian u64 header length, the JSON header (name → {dtype, shape,
6
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
7
+ * F32, F16 and BF16 are supported (both halves are widened to f32 on read). This is the reader
8
8
  * half of the warm-start / weight-port path — export was one-directional before.
9
9
  */
10
10
 
@@ -63,5 +63,12 @@ function decodeTensor(seg: Uint8Array, dtype: string, numel: number, name: strin
63
63
  for (let i = 0; i < numel; i++) data[i] = fp16ToFloat(dv.getUint16(i * 2, true));
64
64
  return data;
65
65
  }
66
- throw new Error(`safetensors: unsupported dtype "${dtype}" for "${name}" (expected F32 or F16)`);
66
+ if (d === "BF16" || d === "BFLOAT16") {
67
+ // bfloat16 is the top half of an f32: shift it back into place.
68
+ if (seg.length < numel * 2) throw new Error(`safetensors: "${name}" BF16 bytes ${seg.length} < ${numel * 2}`);
69
+ const bits = new Uint32Array(data.buffer);
70
+ for (let i = 0; i < numel; i++) bits[i] = dv.getUint16(i * 2, true) << 16;
71
+ return data;
72
+ }
73
+ throw new Error(`safetensors: unsupported dtype "${dtype}" for "${name}" (expected F32, F16 or BF16)`);
67
74
  }
package/src/index.ts CHANGED
@@ -35,7 +35,9 @@ export type {
35
35
  // ── Evermind (the .evermind artifact + the shared learning pipeline) ───────────
36
36
  export {
37
37
  EvermindModelPackage,
38
+ PACKAGE_HEADER_BYTES,
38
39
  adaptAndDiff,
40
+ adaptPackage,
39
41
  EVERMIND_ADAPT_MAX_CHARS,
40
42
  parseDeltaLearnPayload,
41
43
  buildDeltaLearnPayload,
@@ -53,6 +55,7 @@ export type {
53
55
  PackageMeta,
54
56
  ValidationResult,
55
57
  AdaptResult,
58
+ AdaptedPackage,
56
59
  AdaptOptions,
57
60
  AdaptTokenizer,
58
61
  DeltaLearnPayload,
@@ -178,7 +181,7 @@ export {
178
181
  } from './import/index.js';
179
182
  export type { ImportOptions } from './import/index.js';
180
183
 
181
- // ── Foreign weight port (Falcon-Mamba / Codestral-Mamba → HybridMambaModel) ────
184
+ // ── Foreign weight port (Mamba / Falcon-Mamba / Codestral-Mamba → HybridMambaModel) ──
182
185
  // Transformer checkpoints are NOT portable — distillation is the only route, and
183
186
  // `foreignMambaAdapterFor` rejects them saying so.
184
187
 
@@ -191,7 +194,11 @@ export {
191
194
  executePortPlan,
192
195
  normaliseSourceName,
193
196
  falconMambaAdapter,
197
+ mambaAdapter,
198
+ mamba1Adapter,
194
199
  codestralMambaAdapter,
200
+ mambaConfigFromTensors,
201
+ resizePortedVocab,
195
202
  } from './import/index.js';
196
203
  export type {
197
204
  ForeignConfig,
@@ -204,6 +211,7 @@ export type {
204
211
  PortedCheckpoint,
205
212
  PortedTensors,
206
213
  SynthesisedTarget,
214
+ ResizeVocabOptions,
207
215
  } from './import/index.js';
208
216
 
209
217
  // ── Benchmarking (held-out perplexity / accuracy / throughput + A/B) ──────────
@@ -363,3 +371,8 @@ export type {
363
371
  } from './limbic/index.js';
364
372
 
365
373
  export { LIMBIC_AFFECT_WGSL } from './kernels/limbic_affect.js';
374
+
375
+
376
+ // ── Experience (demonstrations, Train Once skills, runs) ─────────────────────
377
+
378
+ export * from './experience/index.js';
@@ -188,18 +188,20 @@ export class LoadBalanceAccumulator {
188
188
  for (const e of route.experts) this.counts[e] = this.counts[e]! + 1;
189
189
  for (let e = 0; e < this.numExperts; e++) this.probSum[e] = this.probSum[e]! + route.probs[e]!;
190
190
  }
191
+ /** `f_e`: the fraction of all dispatches that went to each expert (sums to 1 once anything is observed). */
192
+ dispatchFractions(): Float32Array {
193
+ const dispatched = this.counts.reduce((a, b) => a + b, 0) || 1; // = tokens·topK
194
+ return Float32Array.from(this.counts, (c) => c / dispatched);
195
+ }
191
196
  /** The load-balance loss over everything observed so far (0 if no tokens). */
192
197
  loss(): number {
193
198
  if (this.tokens === 0) return 0;
194
- const E = this.numExperts;
195
- const dispatched = this.counts.reduce((a, b) => a + b, 0) || 1; // = tokens·topK
199
+ const f = this.dispatchFractions();
196
200
  let sum = 0;
197
- for (let e = 0; e < E; e++) {
198
- const f = this.counts[e]! / dispatched; // fraction of dispatches to e
199
- const p = this.probSum[e]! / this.tokens; // mean router prob for e
200
- sum += f * p;
201
+ for (let e = 0; e < this.numExperts; e++) {
202
+ sum += f[e]! * (this.probSum[e]! / this.tokens); // f_e · mean router prob for e
201
203
  }
202
- return E * sum;
204
+ return this.numExperts * sum;
203
205
  }
204
206
  }
205
207
 
@@ -8,7 +8,7 @@
8
8
  * WebGPU optimiser kernel would accelerate.
9
9
  */
10
10
 
11
- import { SharedExpertMoE } from "./moe_model.js";
11
+ import { LoadBalanceAccumulator, SharedExpertMoE } from "./moe_model.js";
12
12
  import { AdamW } from "../optim/adamw.js";
13
13
 
14
14
  export interface MoESample {
@@ -87,7 +87,7 @@ export class MoETrainer {
87
87
  // Forward + task backward, retaining (x, probs) for the batch aux gradient.
88
88
  const xs: Float32Array[] = [];
89
89
  const probsList: Float32Array[] = [];
90
- const counts = new Float32Array(numExperts);
90
+ const balance = new LoadBalanceAccumulator(numExperts);
91
91
  let batchLoss = 0;
92
92
 
93
93
  for (const s of batch) {
@@ -101,12 +101,11 @@ export class MoETrainer {
101
101
  this.model.backward(dOut, f.cache);
102
102
  xs.push(f.cache.x);
103
103
  probsList.push(f.route.probs);
104
- for (const ex of f.route.experts) counts[ex] = counts[ex]! + 1;
104
+ balance.observe(f.route);
105
105
  }
106
106
 
107
107
  // Load-balancing aux gradient (batch-level): f = dispatch fractions.
108
- const dispatched = counts.reduce((a, b) => a + b, 0) || 1;
109
- const fVec = Float32Array.from(counts, (c) => c / dispatched);
108
+ const fVec = balance.dispatchFractions();
110
109
  const scale = (this.opt.auxWeight * numExperts) / batch.length;
111
110
  for (let i = 0; i < xs.length; i++) {
112
111
  this.model.auxGradStep(xs[i]!, probsList[i]!, fVec, scale);
@@ -117,9 +116,7 @@ export class MoETrainer {
117
116
  this.adam.step();
118
117
 
119
118
  epochLoss += batchLoss;
120
- lastAux = numExperts * fVec.reduce((sum, f, e) => sum + f * (probsList.length
121
- ? probsList.reduce((s, p) => s + p[e]!, 0) / probsList.length
122
- : 0), 0);
119
+ lastAux = balance.loss();
123
120
  }
124
121
 
125
122
  return { loss: epochLoss / Math.max(1, samples.length), auxLoss: lastAux };
package/src/utils/rng.ts CHANGED
@@ -50,16 +50,20 @@ export function setInitSeed(seed: number | undefined): void {
50
50
  }
51
51
  }
52
52
 
53
- /** Box–Muller Gaussian sample from the active source. */
54
- export function randn(std = 1): number {
55
- const u1 = Math.max(_next(), 1e-12);
56
- const u2 = _next();
53
+ /**
54
+ * Box–Muller Gaussian sample. Draws from the active init source unless `next` names
55
+ * another uniform [0,1) source — a caller-owned {@link SeededRng}, say, when the draw
56
+ * must be reproducible without touching the process-wide seed.
57
+ */
58
+ export function randn(std = 1, next: () => number = _next): number {
59
+ const u1 = Math.max(next(), 1e-12);
60
+ const u2 = next();
57
61
  return std * Math.sqrt(-2 * Math.log(u1)) * Math.cos(2 * Math.PI * u2);
58
62
  }
59
63
 
60
64
  /** Returns a Float32Array of `n` Gaussian samples with the given standard deviation. */
61
- export function gaussianArray(n: number, std: number): Float32Array {
65
+ export function gaussianArray(n: number, std: number, next?: () => number): Float32Array {
62
66
  const a = new Float32Array(n);
63
- for (let i = 0; i < n; i++) a[i] = randn(std);
67
+ for (let i = 0; i < n; i++) a[i] = randn(std, next);
64
68
  return a;
65
69
  }