@seanhogg/builderforce-memory-engine 2026.9.29 → 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 (57) hide show
  1. package/dist/export/onnx.d.ts.map +1 -1
  2. package/dist/export/onnx.js +0 -8
  3. package/dist/export/onnx.js.map +1 -1
  4. package/dist/import/foreign/falcon_mamba.d.ts +15 -2
  5. package/dist/import/foreign/falcon_mamba.d.ts.map +1 -1
  6. package/dist/import/foreign/falcon_mamba.js +53 -36
  7. package/dist/import/foreign/falcon_mamba.js.map +1 -1
  8. package/dist/import/foreign/index.d.ts +5 -2
  9. package/dist/import/foreign/index.d.ts.map +1 -1
  10. package/dist/import/foreign/index.js +4 -2
  11. package/dist/import/foreign/index.js.map +1 -1
  12. package/dist/import/foreign/infer.d.ts +20 -0
  13. package/dist/import/foreign/infer.d.ts.map +1 -0
  14. package/dist/import/foreign/infer.js +58 -0
  15. package/dist/import/foreign/infer.js.map +1 -0
  16. package/dist/import/foreign/registry.d.ts.map +1 -1
  17. package/dist/import/foreign/registry.js +2 -1
  18. package/dist/import/foreign/registry.js.map +1 -1
  19. package/dist/import/foreign/vocab.d.ts +19 -0
  20. package/dist/import/foreign/vocab.d.ts.map +1 -0
  21. package/dist/import/foreign/vocab.js +40 -0
  22. package/dist/import/foreign/vocab.js.map +1 -0
  23. package/dist/import/index.d.ts +2 -2
  24. package/dist/import/index.d.ts.map +1 -1
  25. package/dist/import/index.js +1 -1
  26. package/dist/import/index.js.map +1 -1
  27. package/dist/import/safetensors.d.ts +1 -1
  28. package/dist/import/safetensors.js +11 -2
  29. package/dist/import/safetensors.js.map +1 -1
  30. package/dist/index.d.ts +2 -2
  31. package/dist/index.d.ts.map +1 -1
  32. package/dist/index.js +2 -2
  33. package/dist/index.js.map +1 -1
  34. package/dist/moe/moe_model.d.ts +2 -0
  35. package/dist/moe/moe_model.d.ts.map +1 -1
  36. package/dist/moe/moe_model.js +9 -7
  37. package/dist/moe/moe_model.js.map +1 -1
  38. package/dist/moe/moe_trainer.d.ts.map +1 -1
  39. package/dist/moe/moe_trainer.js +5 -8
  40. package/dist/moe/moe_trainer.js.map +1 -1
  41. package/dist/utils/rng.d.ts +7 -3
  42. package/dist/utils/rng.d.ts.map +1 -1
  43. package/dist/utils/rng.js +10 -6
  44. package/dist/utils/rng.js.map +1 -1
  45. package/package.json +1 -1
  46. package/src/export/onnx.ts +0 -7
  47. package/src/import/foreign/falcon_mamba.ts +63 -40
  48. package/src/import/foreign/index.ts +5 -2
  49. package/src/import/foreign/infer.ts +62 -0
  50. package/src/import/foreign/registry.ts +2 -1
  51. package/src/import/foreign/vocab.ts +50 -0
  52. package/src/import/index.ts +5 -0
  53. package/src/import/safetensors.ts +9 -2
  54. package/src/index.ts +6 -1
  55. package/src/moe/moe_model.ts +9 -7
  56. package/src/moe/moe_trainer.ts +5 -8
  57. package/src/utils/rng.ts +10 -6
@@ -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
@@ -181,7 +181,7 @@ export {
181
181
  } from './import/index.js';
182
182
  export type { ImportOptions } from './import/index.js';
183
183
 
184
- // ── Foreign weight port (Falcon-Mamba / Codestral-Mamba → HybridMambaModel) ────
184
+ // ── Foreign weight port (Mamba / Falcon-Mamba / Codestral-Mamba → HybridMambaModel) ──
185
185
  // Transformer checkpoints are NOT portable — distillation is the only route, and
186
186
  // `foreignMambaAdapterFor` rejects them saying so.
187
187
 
@@ -194,7 +194,11 @@ export {
194
194
  executePortPlan,
195
195
  normaliseSourceName,
196
196
  falconMambaAdapter,
197
+ mambaAdapter,
198
+ mamba1Adapter,
197
199
  codestralMambaAdapter,
200
+ mambaConfigFromTensors,
201
+ resizePortedVocab,
198
202
  } from './import/index.js';
199
203
  export type {
200
204
  ForeignConfig,
@@ -207,6 +211,7 @@ export type {
207
211
  PortedCheckpoint,
208
212
  PortedTensors,
209
213
  SynthesisedTarget,
214
+ ResizeVocabOptions,
210
215
  } from './import/index.js';
211
216
 
212
217
  // ── Benchmarking (held-out perplexity / accuracy / throughput + A/B) ──────────
@@ -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
  }