@seanhogg/builderforce-memory-engine 2026.7.12 → 2026.9.27

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 (196) hide show
  1. package/README.md +14 -0
  2. package/dist/bench/adaptation.d.ts +57 -0
  3. package/dist/bench/adaptation.d.ts.map +1 -0
  4. package/dist/bench/adaptation.js +167 -0
  5. package/dist/bench/adaptation.js.map +1 -0
  6. package/dist/bench/harness.d.ts +2 -0
  7. package/dist/bench/harness.d.ts.map +1 -1
  8. package/dist/bench/harness.js +1 -1
  9. package/dist/bench/harness.js.map +1 -1
  10. package/dist/bench/index.d.ts +5 -3
  11. package/dist/bench/index.d.ts.map +1 -1
  12. package/dist/bench/index.js +4 -2
  13. package/dist/bench/index.js.map +1 -1
  14. package/dist/bench/types.d.ts +88 -0
  15. package/dist/bench/types.d.ts.map +1 -1
  16. package/dist/evermind/adapt.d.ts +40 -0
  17. package/dist/evermind/adapt.d.ts.map +1 -0
  18. package/dist/evermind/adapt.js +25 -0
  19. package/dist/evermind/adapt.js.map +1 -0
  20. package/dist/evermind/delta_wire.d.ts +43 -0
  21. package/dist/evermind/delta_wire.d.ts.map +1 -0
  22. package/dist/evermind/delta_wire.js +94 -0
  23. package/dist/evermind/delta_wire.js.map +1 -0
  24. package/dist/evermind/eval.d.ts +31 -0
  25. package/dist/evermind/eval.d.ts.map +1 -0
  26. package/dist/evermind/eval.js +36 -0
  27. package/dist/evermind/eval.js.map +1 -0
  28. package/dist/evermind/index.d.ts +16 -0
  29. package/dist/evermind/index.d.ts.map +1 -0
  30. package/dist/evermind/index.js +11 -0
  31. package/dist/evermind/index.js.map +1 -0
  32. package/dist/evermind/merge.d.ts +21 -0
  33. package/dist/evermind/merge.d.ts.map +1 -0
  34. package/dist/evermind/merge.js +79 -0
  35. package/dist/evermind/merge.js.map +1 -0
  36. package/dist/{moe/moe_package.d.ts → evermind/package.d.ts} +55 -5
  37. package/dist/evermind/package.d.ts.map +1 -0
  38. package/dist/{moe/moe_package.js → evermind/package.js} +118 -19
  39. package/dist/evermind/package.js.map +1 -0
  40. package/dist/import/foreign/adapter.d.ts +59 -0
  41. package/dist/import/foreign/adapter.d.ts.map +1 -0
  42. package/dist/import/foreign/adapter.js +76 -0
  43. package/dist/import/foreign/adapter.js.map +1 -0
  44. package/dist/import/foreign/apply.d.ts +23 -0
  45. package/dist/import/foreign/apply.d.ts.map +1 -0
  46. package/dist/import/foreign/apply.js +43 -0
  47. package/dist/import/foreign/apply.js.map +1 -0
  48. package/dist/import/foreign/codestral_mamba.d.ts +57 -0
  49. package/dist/import/foreign/codestral_mamba.d.ts.map +1 -0
  50. package/dist/import/foreign/codestral_mamba.js +174 -0
  51. package/dist/import/foreign/codestral_mamba.js.map +1 -0
  52. package/dist/import/foreign/falcon_mamba.d.ts +51 -0
  53. package/dist/import/foreign/falcon_mamba.d.ts.map +1 -0
  54. package/dist/import/foreign/falcon_mamba.js +131 -0
  55. package/dist/import/foreign/falcon_mamba.js.map +1 -0
  56. package/dist/import/foreign/index.d.ts +21 -0
  57. package/dist/import/foreign/index.d.ts.map +1 -0
  58. package/dist/import/foreign/index.js +18 -0
  59. package/dist/import/foreign/index.js.map +1 -0
  60. package/dist/import/foreign/plan.d.ts +122 -0
  61. package/dist/import/foreign/plan.d.ts.map +1 -0
  62. package/dist/import/foreign/plan.js +171 -0
  63. package/dist/import/foreign/plan.js.map +1 -0
  64. package/dist/import/foreign/registry.d.ts +35 -0
  65. package/dist/import/foreign/registry.d.ts.map +1 -0
  66. package/dist/import/foreign/registry.js +89 -0
  67. package/dist/import/foreign/registry.js.map +1 -0
  68. package/dist/import/index.d.ts +7 -0
  69. package/dist/import/index.d.ts.map +1 -1
  70. package/dist/import/index.js +7 -0
  71. package/dist/import/index.js.map +1 -1
  72. package/dist/index.d.ts +29 -12
  73. package/dist/index.d.ts.map +1 -1
  74. package/dist/index.js +24 -9
  75. package/dist/index.js.map +1 -1
  76. package/dist/kernels/selective_scan.d.ts.map +1 -1
  77. package/dist/kernels/selective_scan.js +15 -9
  78. package/dist/kernels/selective_scan.js.map +1 -1
  79. package/dist/kernels/slice.d.ts +26 -0
  80. package/dist/kernels/slice.d.ts.map +1 -0
  81. package/dist/kernels/slice.js +52 -0
  82. package/dist/kernels/slice.js.map +1 -0
  83. package/dist/kernels/weight_update.d.ts.map +1 -1
  84. package/dist/kernels/weight_update.js +30 -4
  85. package/dist/kernels/weight_update.js.map +1 -1
  86. package/dist/lm/evermind_lm.d.ts +180 -4
  87. package/dist/lm/evermind_lm.d.ts.map +1 -1
  88. package/dist/lm/evermind_lm.js +433 -116
  89. package/dist/lm/evermind_lm.js.map +1 -1
  90. package/dist/lm/index.d.ts +8 -2
  91. package/dist/lm/index.d.ts.map +1 -1
  92. package/dist/lm/index.js +6 -1
  93. package/dist/lm/index.js.map +1 -1
  94. package/dist/lm/text_embedder.d.ts +55 -0
  95. package/dist/lm/text_embedder.d.ts.map +1 -0
  96. package/dist/lm/text_embedder.js +62 -0
  97. package/dist/lm/text_embedder.js.map +1 -0
  98. package/dist/model/attention_block.d.ts.map +1 -1
  99. package/dist/model/attention_block.js +9 -5
  100. package/dist/model/attention_block.js.map +1 -1
  101. package/dist/model/mamba1_block.d.ts.map +1 -1
  102. package/dist/model/mamba1_block.js +28 -12
  103. package/dist/model/mamba1_block.js.map +1 -1
  104. package/dist/model/mamba2_block.d.ts.map +1 -1
  105. package/dist/model/mamba2_block.js +14 -9
  106. package/dist/model/mamba2_block.js.map +1 -1
  107. package/dist/model/mamba3_block.d.ts.map +1 -1
  108. package/dist/model/mamba3_block.js +14 -9
  109. package/dist/model/mamba3_block.js.map +1 -1
  110. package/dist/moe/index.d.ts +0 -2
  111. package/dist/moe/index.d.ts.map +1 -1
  112. package/dist/moe/index.js +0 -1
  113. package/dist/moe/index.js.map +1 -1
  114. package/dist/optim/adamw.d.ts +58 -1
  115. package/dist/optim/adamw.d.ts.map +1 -1
  116. package/dist/optim/adamw.js +69 -4
  117. package/dist/optim/adamw.js.map +1 -1
  118. package/dist/tokenizer/bpe.d.ts +28 -0
  119. package/dist/tokenizer/bpe.d.ts.map +1 -1
  120. package/dist/tokenizer/bpe.js +47 -0
  121. package/dist/tokenizer/bpe.js.map +1 -1
  122. package/dist/training/cpu_ops.d.ts +44 -0
  123. package/dist/training/cpu_ops.d.ts.map +1 -0
  124. package/dist/training/cpu_ops.js +111 -0
  125. package/dist/training/cpu_ops.js.map +1 -0
  126. package/dist/training/lora.d.ts +153 -0
  127. package/dist/training/lora.d.ts.map +1 -0
  128. package/dist/training/lora.js +324 -0
  129. package/dist/training/lora.js.map +1 -0
  130. package/dist/training/mamba1_cpu.d.ts +109 -0
  131. package/dist/training/mamba1_cpu.d.ts.map +1 -0
  132. package/dist/training/mamba1_cpu.js +316 -0
  133. package/dist/training/mamba1_cpu.js.map +1 -0
  134. package/dist/training/mixed_precision.d.ts +67 -0
  135. package/dist/training/mixed_precision.d.ts.map +1 -0
  136. package/dist/training/mixed_precision.js +101 -0
  137. package/dist/training/mixed_precision.js.map +1 -0
  138. package/dist/training/model_cpu.d.ts +100 -0
  139. package/dist/training/model_cpu.d.ts.map +1 -0
  140. package/dist/training/model_cpu.js +252 -0
  141. package/dist/training/model_cpu.js.map +1 -0
  142. package/dist/training/trainer.d.ts +156 -16
  143. package/dist/training/trainer.d.ts.map +1 -1
  144. package/dist/training/trainer.js +327 -101
  145. package/dist/training/trainer.js.map +1 -1
  146. package/dist/utils/base64.d.ts +12 -0
  147. package/dist/utils/base64.d.ts.map +1 -0
  148. package/dist/utils/base64.js +27 -0
  149. package/dist/utils/base64.js.map +1 -0
  150. package/package.json +3 -1
  151. package/src/bench/adaptation.ts +180 -0
  152. package/src/bench/harness.ts +1 -1
  153. package/src/bench/index.ts +13 -2
  154. package/src/bench/types.ts +93 -0
  155. package/src/evermind/adapt.ts +61 -0
  156. package/src/evermind/delta_wire.ts +114 -0
  157. package/src/evermind/eval.ts +62 -0
  158. package/src/evermind/index.ts +33 -0
  159. package/src/evermind/merge.ts +106 -0
  160. package/src/{moe/moe_package.ts → evermind/package.ts} +148 -17
  161. package/src/import/foreign/adapter.ts +106 -0
  162. package/src/import/foreign/apply.ts +54 -0
  163. package/src/import/foreign/codestral_mamba.ts +224 -0
  164. package/src/import/foreign/falcon_mamba.ts +173 -0
  165. package/src/import/foreign/index.ts +37 -0
  166. package/src/import/foreign/plan.ts +294 -0
  167. package/src/import/foreign/registry.ts +126 -0
  168. package/src/import/index.ts +30 -0
  169. package/src/index.ts +104 -13
  170. package/src/kernels/selective_scan.ts +15 -9
  171. package/src/kernels/slice.ts +64 -0
  172. package/src/kernels/weight_update.ts +30 -4
  173. package/src/lm/evermind_lm.ts +470 -116
  174. package/src/lm/index.ts +12 -2
  175. package/src/lm/text_embedder.ts +84 -0
  176. package/src/model/attention_block.ts +9 -5
  177. package/src/model/mamba1_block.ts +28 -12
  178. package/src/model/mamba2_block.ts +14 -9
  179. package/src/model/mamba3_block.ts +14 -9
  180. package/src/moe/index.ts +0 -9
  181. package/src/optim/adamw.ts +115 -7
  182. package/src/tokenizer/bpe.ts +55 -0
  183. package/src/training/cpu_ops.ts +127 -0
  184. package/src/training/lora.ts +365 -0
  185. package/src/training/mamba1_cpu.ts +421 -0
  186. package/src/training/mixed_precision.ts +119 -0
  187. package/src/training/model_cpu.ts +360 -0
  188. package/src/training/trainer.ts +397 -154
  189. package/src/utils/base64.ts +28 -0
  190. package/dist/model/mamba_block.d.ts +0 -64
  191. package/dist/model/mamba_block.d.ts.map +0 -1
  192. package/dist/model/mamba_block.js +0 -303
  193. package/dist/model/mamba_block.js.map +0 -1
  194. package/dist/moe/moe_package.d.ts.map +0 -1
  195. package/dist/moe/moe_package.js.map +0 -1
  196. package/src/model/mamba_block.ts +0 -401
package/src/lm/index.ts CHANGED
@@ -2,5 +2,15 @@
2
2
  * EvermindLM — the generative language model (the runnable "AI").
3
3
  */
4
4
 
5
- export { EvermindLM, EvermindLMTrainer, DEFAULT_LM_CONFIG, DEFAULT_LM_SEED } from "./evermind_lm.js";
6
- export type { EvermindLMConfig, LMGenerateOptions, TextCodec } from "./evermind_lm.js";
5
+ export { EvermindLM, EvermindLMTrainer, DEFAULT_LM_CONFIG, DEFAULT_LM_SEED, logProbOfToken } from "./evermind_lm.js";
6
+ export type { EvermindLMConfig, LMGenerateOptions, TextCodec, EvermindLMTrainOptions, EvermindLMDecodeState } from "./evermind_lm.js";
7
+
8
+ // CPU text→vector embedder (semantic recall with no GPU).
9
+ export { EvermindTextEmbedder } from "./text_embedder.js";
10
+ export type { TextEmbedder, EmbedderCodec } from "./text_embedder.js";
11
+
12
+ // PEFT / efficient-training toolkit (LoRA, QLoRA, mixed precision).
13
+ export { LoRAAdapter, EvermindLMLoRA, quantizeBase } from "../training/lora.js";
14
+ export type { LoRAConfig, LoRAFitOptions, BaseQuant } from "../training/lora.js";
15
+ export { DynamicLossScaler, roundFp16, fp16View } from "../training/mixed_precision.js";
16
+ export type { LossScalerOptions } from "../training/mixed_precision.js";
@@ -0,0 +1,84 @@
1
+ /**
2
+ * lm/text_embedder.ts — text → vector, on the CPU, from an `EvermindLM`.
3
+ *
4
+ * `EvermindLM.embed()` turns TOKENS into a `dModel` vector. Every consumer that
5
+ * wants SEMANTIC RECALL wants it over TEXT, and needs the vector to be stable
6
+ * across processes, so it also needs (a) the tokenizer that produced those ids
7
+ * and (b) a way to tell whether a cached vector came from this exact model. This
8
+ * class is that pairing, in one place, so no consumer re-derives it.
9
+ *
10
+ * It deliberately satisfies the SAME structural shape the memory runtime's
11
+ * `embed(text)` seam expects (`MemoryStore.recallRanked(query, k, runtime)`
12
+ * accepts anything with an async `embed(text): Float32Array`), so a headless host
13
+ * can hand one of these straight to the memory layer and get embedding-ranked
14
+ * recall with NO GPU — the CPU LM is the whole dependency.
15
+ */
16
+
17
+ import type { EvermindLM, TextCodec } from "./evermind_lm.js";
18
+ import { crc32 } from "../utils/crc32.js";
19
+
20
+ /** The minimal text-embedder seam the memory/recall layers consume. */
21
+ export interface TextEmbedder {
22
+ /** Embed `text` as an L2-normalised vector of length {@link dimensions}. */
23
+ embed(text: string): Promise<Float32Array>;
24
+ /** Vector width (the model's `dModel`). */
25
+ readonly dimensions: number;
26
+ /**
27
+ * Identity of the model+tokenizer that produces these vectors. Vectors are only
28
+ * comparable within one fingerprint, so a persistent cache MUST discard entries
29
+ * stamped with a different one.
30
+ */
31
+ readonly fingerprint: string;
32
+ }
33
+
34
+ /** A tokenizer this embedder can encode through (the engine's `BPETokenizer` fits). */
35
+ export type EmbedderCodec = Pick<TextCodec, "encode">;
36
+
37
+ /**
38
+ * Pairs an `EvermindLM` with the tokenizer whose ids it was trained on.
39
+ *
40
+ * Pure compute and stateless: caching belongs to whoever owns a lifetime (the
41
+ * memory store's in-process LRU, or a host's on-disk vector cache), not here.
42
+ */
43
+ export class EvermindTextEmbedder implements TextEmbedder {
44
+ readonly dimensions: number;
45
+ readonly fingerprint: string;
46
+
47
+ constructor(
48
+ private readonly model: EvermindLM,
49
+ private readonly codec: EmbedderCodec,
50
+ /** Override the derived fingerprint (e.g. with a published model version). */
51
+ fingerprint?: string,
52
+ ) {
53
+ this.dimensions = model.config.dModel;
54
+ this.fingerprint = fingerprint ?? derivedFingerprint(model);
55
+ }
56
+
57
+ /**
58
+ * Embed `text`. Async only to match the seam — the work is synchronous CPU, so
59
+ * a caller that wants the vector without a microtask can use
60
+ * {@link embedSync}.
61
+ */
62
+ async embed(text: string): Promise<Float32Array> {
63
+ return this.embedSync(text);
64
+ }
65
+
66
+ /** The synchronous form — encode with the codec, then run the CPU LM. */
67
+ embedSync(text: string): Float32Array {
68
+ return this.model.embedText(text, this.codec as TextCodec);
69
+ }
70
+ }
71
+
72
+ /**
73
+ * Model identity: architecture plus a CRC-32 over the tied embedding table.
74
+ *
75
+ * The embedding table is the one tensor every position's vector flows through, so
76
+ * an adapted checkpoint (which is what a delta merge produces) always changes it —
77
+ * making this a cheap but honest "are these vectors still comparable?" stamp.
78
+ */
79
+ function derivedFingerprint(model: EvermindLM): string {
80
+ const c = model.config;
81
+ const bytes = new Uint8Array(model.emb.buffer, model.emb.byteOffset, model.emb.byteLength);
82
+ const arch = [c.vocabSize, c.dModel, c.numLayers, c.convKernel, c.hiddenDim, c.numExperts, c.topK].join("-");
83
+ return `evermind-lm/${arch}/${crc32(bytes).toString(16)}`;
84
+ }
@@ -36,6 +36,7 @@ import {
36
36
  import { LINEAR_FORWARD_WGSL } from '../kernels/linear_projection.js';
37
37
  import { gaussianArray } from '../utils/rng.js';
38
38
  import { ACTIVATIONS_WGSL } from '../kernels/activations.js';
39
+ import { COL_SLICE_WGSL, COL_SLICE_ENTRY, dispatchColumnSlice } from '../kernels/slice.js';
39
40
 
40
41
  import type { SequenceLayer, LayerForwardResult, LayerParam } from './sequence_layer.js';
41
42
 
@@ -147,6 +148,7 @@ export class AttentionBlock implements SequenceLayer {
147
148
  attn_fwd: createComputePipeline(d, ATTENTION_FORWARD_WGSL, 'attention_forward'),
148
149
  attn_val: createComputePipeline(d, ATTENTION_FORWARD_WGSL, 'attention_value'),
149
150
  softmax : createComputePipeline(d, SOFTMAX_WGSL, 'softmax_forward'),
151
+ colSlice: createComputePipeline(d, COL_SLICE_WGSL, COL_SLICE_ENTRY),
150
152
  elAdd : createComputePipeline(d, ADD_SHADER, 'main'),
151
153
  };
152
154
 
@@ -194,11 +196,13 @@ export class AttentionBlock implements SequenceLayer {
194
196
  const KBuf = createEmptyStorageBuffer(d, M * dModel * 4, true);
195
197
  const VBuf = createEmptyStorageBuffer(d, M * dModel * 4, true);
196
198
  {
197
- const enc = d.createCommandEncoder();
198
- enc.copyBufferToBuffer(qkvOut, 0, QBuf, 0, M * dModel * 4);
199
- enc.copyBufferToBuffer(qkvOut, M * dModel * 4, KBuf, 0, M * dModel * 4);
200
- enc.copyBufferToBuffer(qkvOut, 2 * M * dModel * 4, VBuf, 0, M * dModel * 4);
201
- d.queue.submit([enc.finish()]);
199
+ // COLUMN split. `linear_forward` writes (M, 3*dModel) ROW-major, so Q, K
200
+ // and V are ranges of columns WITHIN every row — a strided gather, not a
201
+ // contiguous copy (which only coincides for M == 1).
202
+ const slice = this.pipelines['colSlice']!;
203
+ dispatchColumnSlice(d, slice, qkvOut, QBuf, M, 3 * dModel, 0, dModel);
204
+ dispatchColumnSlice(d, slice, qkvOut, KBuf, M, 3 * dModel, dModel, dModel);
205
+ dispatchColumnSlice(d, slice, qkvOut, VBuf, M, 3 * dModel, 2 * dModel, dModel);
202
206
  }
203
207
  qkvOut.destroy();
204
208
 
@@ -20,6 +20,7 @@ import { gaussianArray } from '../utils/rng.js';
20
20
  import { CONV1D_FORWARD_WGSL } from '../kernels/conv1d.js';
21
21
  import { LINEAR_FORWARD_WGSL } from '../kernels/linear_projection.js';
22
22
  import { ACTIVATIONS_WGSL } from '../kernels/activations.js';
23
+ import { COL_SLICE_WGSL, COL_SLICE_ENTRY, dispatchColumnSlice } from '../kernels/slice.js';
23
24
 
24
25
  import type { SequenceLayer, LayerForwardResult, LayerParam } from './sequence_layer.js';
25
26
 
@@ -210,6 +211,7 @@ export class Mamba1Block implements SequenceLayer {
210
211
  rmsnorm : createComputePipeline(d, ACTIVATIONS_WGSL, 'rmsnorm_forward'),
211
212
  scan_fwd : createComputePipeline(d, SELECTIVE_SCAN_FORWARD_WGSL, 'forward_scan'),
212
213
  scan_reduce : createComputePipeline(d, SELECTIVE_SCAN_FORWARD_WGSL, 'forward_reduce'),
214
+ colSlice : createComputePipeline(d, COL_SLICE_WGSL, COL_SLICE_ENTRY),
213
215
  elMul : createComputePipeline(d, MUL_SHADER, 'main'),
214
216
  elAdd : createComputePipeline(d, ADD_SHADER, 'main'),
215
217
  };
@@ -253,14 +255,18 @@ export class Mamba1Block implements SequenceLayer {
253
255
  dispatchKernel(d, this.pipelines['linear']!, bg, [cdiv(M, 16), cdiv(2 * D, 16), 1]);
254
256
  }
255
257
 
256
- // 3. Split into x and z
258
+ // 3. Split into x and z.
259
+ // in_proj writes (M, 2D) ROW-major, so x and z are interleaved COLUMN
260
+ // slices — one per row — not two contiguous halves of the buffer. A
261
+ // `copyBufferToBuffer` of the first M*D floats is only the right answer
262
+ // when M == 1; for any real sequence it hands the block the first half of
263
+ // the ROWS twice. Split with a strided gather instead.
257
264
  const xConvIn = createEmptyStorageBuffer(d, M * D * 4, true);
258
265
  const zBuf = createEmptyStorageBuffer(d, M * D * 4, true);
259
266
  {
260
- const enc = d.createCommandEncoder();
261
- enc.copyBufferToBuffer(inProjOut, 0, xConvIn, 0, M * D * 4);
262
- enc.copyBufferToBuffer(inProjOut, M * D * 4, zBuf, 0, M * D * 4);
263
- d.queue.submit([enc.finish()]);
267
+ const slice = this.pipelines['colSlice']!;
268
+ dispatchColumnSlice(d, slice, inProjOut, xConvIn, M, 2 * D, 0, D);
269
+ dispatchColumnSlice(d, slice, inProjOut, zBuf, M, 2 * D, D, D);
264
270
  }
265
271
  inProjOut.destroy();
266
272
  cache.zBuf = zBuf;
@@ -270,7 +276,11 @@ export class Mamba1Block implements SequenceLayer {
270
276
  const convOut = createEmptyStorageBuffer(d, M * D * 4, true);
271
277
  cache.convOut = convOut;
272
278
  {
273
- const params = new Uint32Array([L, D, dConv, B]).buffer;
279
+ // ConvParams is FIVE u32s (seq_len, d_channels, kernel_size, batch,
280
+ // groups). A four-element uniform is smaller than the struct's minimum
281
+ // binding size and WebGPU rejects the bind group outright. groups = 1
282
+ // is standard depthwise, matching Mamba-2/3's call.
283
+ const params = new Uint32Array([L, D, dConv, B, 1]).buffer;
274
284
  const pBuf = createUniformBuffer(d, params);
275
285
  const bg = createBindGroup(d, this.pipelines['conv1d']!,
276
286
  [pBuf, xConvIn, this.gpuWeights['wConv']!, this.gpuWeights['bConv']!, convOut]);
@@ -298,15 +308,17 @@ export class Mamba1Block implements SequenceLayer {
298
308
  dispatchKernel(d, this.pipelines['linear']!, bg, [cdiv(M, 16), cdiv(R + 2 * N, 16), 1]);
299
309
  }
300
310
 
311
+ // Same story as the (x, z) split: Δ, B and C are COLUMN slices of the
312
+ // (M, R+2N) projection, so they need a strided gather, not a prefix copy.
301
313
  const dtRaw = createEmptyStorageBuffer(d, M * R * 4, true);
302
314
  const B_raw = createEmptyStorageBuffer(d, B * L * N * 4, true);
303
315
  const C_raw = createEmptyStorageBuffer(d, B * L * N * 4, true);
304
316
  {
305
- const enc = d.createCommandEncoder();
306
- enc.copyBufferToBuffer(xProjOut, 0, dtRaw, 0, M * R * 4);
307
- enc.copyBufferToBuffer(xProjOut, M * R * 4, B_raw, 0, B * L * N * 4);
308
- enc.copyBufferToBuffer(xProjOut, M * (R + N) * 4, C_raw, 0, B * L * N * 4);
309
- d.queue.submit([enc.finish()]);
317
+ const P = R + 2 * N;
318
+ const slice = this.pipelines['colSlice']!;
319
+ dispatchColumnSlice(d, slice, xProjOut, dtRaw, M, P, 0, R);
320
+ dispatchColumnSlice(d, slice, xProjOut, B_raw, M, P, R, N);
321
+ dispatchColumnSlice(d, slice, xProjOut, C_raw, M, P, R + N, N);
310
322
  }
311
323
  xProjOut.destroy();
312
324
  cache.B_raw = B_raw;
@@ -335,7 +347,11 @@ export class Mamba1Block implements SequenceLayer {
335
347
  const bg1 = createBindGroup(d, this.pipelines['scan_fwd']!,
336
348
  [pBuf, siluOut, deltaFull, this.gpuWeights['A_log']!, B_raw, C_raw,
337
349
  this.gpuWeights['D_vec']!, scanY, hCache]);
338
- dispatchKernel(d, this.pipelines['scan_fwd']!, bg1, [cdiv(D, 8), cdiv(N, 8), B]);
350
+ // One workgroup per (d, n, batch): the kernel reads `wgid.x` as d and
351
+ // `wgid.y` as n, so the grid must be D x N x B. Dispatching
352
+ // ceil(D/8) x ceil(N/8) left all but a 1/64th corner of the state
353
+ // matrix unscanned (and unwritten in h_cache).
354
+ dispatchKernel(d, this.pipelines['scan_fwd']!, bg1, [D, N, B]);
339
355
 
340
356
  const bg2 = createBindGroup(d, this.pipelines['scan_reduce']!,
341
357
  [pBuf, siluOut, deltaFull, this.gpuWeights['A_log']!, B_raw, C_raw,
@@ -24,6 +24,7 @@ import {
24
24
  import { SSD_FORWARD_WGSL } from '../kernels/ssd.js';
25
25
  import { gaussianArray } from '../utils/rng.js';
26
26
  import { CONV1D_FORWARD_WGSL } from '../kernels/conv1d.js';
27
+ import { COL_SLICE_WGSL, COL_SLICE_ENTRY, dispatchColumnSlice } from '../kernels/slice.js';
27
28
  import { LINEAR_FORWARD_WGSL } from '../kernels/linear_projection.js';
28
29
  import { ACTIVATIONS_WGSL } from '../kernels/activations.js';
29
30
 
@@ -127,6 +128,7 @@ export class Mamba2Block implements SequenceLayer {
127
128
  this.pipelines = {
128
129
  linear : createComputePipeline(d, LINEAR_FORWARD_WGSL, 'linear_forward'),
129
130
  conv1d : createComputePipeline(d, CONV1D_FORWARD_WGSL, 'conv1d_forward'),
131
+ colSlice : createComputePipeline(d, COL_SLICE_WGSL, COL_SLICE_ENTRY),
130
132
  rmsnorm : createComputePipeline(d, ACTIVATIONS_WGSL, 'rmsnorm_forward'),
131
133
  ssd_fwd : createComputePipeline(d, SSD_FORWARD_WGSL, 'ssd_chunk_forward'),
132
134
  elAdd : createComputePipeline(d, ADD_SHADER, 'main'),
@@ -181,10 +183,13 @@ export class Mamba2Block implements SequenceLayer {
181
183
  const xConvBuf = createEmptyStorageBuffer(d, M * convD * 4, true);
182
184
  const dtBuf = createEmptyStorageBuffer(d, M * H * 4, true);
183
185
  {
184
- const enc = d.createCommandEncoder();
185
- enc.copyBufferToBuffer(inProjOut, 0, xConvBuf, 0, M * convD * 4);
186
- enc.copyBufferToBuffer(inProjOut, M * convD * 4, dtBuf, 0, M * H * 4);
187
- d.queue.submit([enc.finish()]);
186
+ // COLUMN split. `linear_forward` writes (M, convD + H) ROW-major, so each
187
+ // part is a range of columns WITHIN every row — a strided gather, not a
188
+ // contiguous copy (which only coincides for M == 1).
189
+ const slice = this.pipelines['colSlice']!;
190
+ const stride = convD + H;
191
+ dispatchColumnSlice(d, slice, inProjOut, xConvBuf, M, stride, 0, convD);
192
+ dispatchColumnSlice(d, slice, inProjOut, dtBuf, M, stride, convD, H);
188
193
  }
189
194
  inProjOut.destroy();
190
195
 
@@ -204,11 +209,11 @@ export class Mamba2Block implements SequenceLayer {
204
209
  const bProjBuf = createEmptyStorageBuffer(d, M * G * N * 4, true);
205
210
  const cProjBuf = createEmptyStorageBuffer(d, M * G * N * 4, true);
206
211
  {
207
- const enc = d.createCommandEncoder();
208
- enc.copyBufferToBuffer(convOut, 0, xSsdBuf, 0, M * D * 4);
209
- enc.copyBufferToBuffer(convOut, M * D * 4, bProjBuf, 0, M * G * N * 4);
210
- enc.copyBufferToBuffer(convOut, M * (D + G * N) * 4, cProjBuf, 0, M * G * N * 4);
211
- d.queue.submit([enc.finish()]);
212
+ // COLUMN split of the (M, convD) conv output — same reasoning as above.
213
+ const slice = this.pipelines['colSlice']!;
214
+ dispatchColumnSlice(d, slice, convOut, xSsdBuf, M, convD, 0, D);
215
+ dispatchColumnSlice(d, slice, convOut, bProjBuf, M, convD, D, G * N);
216
+ dispatchColumnSlice(d, slice, convOut, cProjBuf, M, convD, D + G * N, G * N);
212
217
  }
213
218
  convOut.destroy();
214
219
 
@@ -33,6 +33,7 @@ import {
33
33
  import { COMPLEX_SSD_FORWARD_WGSL } from '../kernels/complex_ssd.js';
34
34
  import { gaussianArray } from '../utils/rng.js';
35
35
  import { CONV1D_FORWARD_WGSL } from '../kernels/conv1d.js';
36
+ import { COL_SLICE_WGSL, COL_SLICE_ENTRY, dispatchColumnSlice } from '../kernels/slice.js';
36
37
  import { LINEAR_FORWARD_WGSL } from '../kernels/linear_projection.js';
37
38
  import { ACTIVATIONS_WGSL } from '../kernels/activations.js';
38
39
 
@@ -145,6 +146,7 @@ export class Mamba3Block implements SequenceLayer {
145
146
  this.pipelines = {
146
147
  linear : createComputePipeline(d, LINEAR_FORWARD_WGSL, 'linear_forward'),
147
148
  conv1d : createComputePipeline(d, CONV1D_FORWARD_WGSL, 'conv1d_forward'),
149
+ colSlice : createComputePipeline(d, COL_SLICE_WGSL, COL_SLICE_ENTRY),
148
150
  rmsnorm : createComputePipeline(d, ACTIVATIONS_WGSL, 'rmsnorm_forward'),
149
151
  cssd_fwd : createComputePipeline(d, COMPLEX_SSD_FORWARD_WGSL, 'complex_ssd_forward'),
150
152
  elAdd : createComputePipeline(d, ADD_SHADER, 'main'),
@@ -198,10 +200,13 @@ export class Mamba3Block implements SequenceLayer {
198
200
  const xConvBuf = createEmptyStorageBuffer(d, M * convD * 4, true);
199
201
  const dtBuf = createEmptyStorageBuffer(d, M * H * 4, true);
200
202
  {
201
- const enc = d.createCommandEncoder();
202
- enc.copyBufferToBuffer(inProjOut, 0, xConvBuf, 0, M * convD * 4);
203
- enc.copyBufferToBuffer(inProjOut, M * convD * 4, dtBuf, 0, M * H * 4);
204
- d.queue.submit([enc.finish()]);
203
+ // COLUMN split. `linear_forward` writes (M, convD + H) ROW-major, so each
204
+ // part is a range of columns WITHIN every row — a strided gather, not a
205
+ // contiguous copy (which only coincides for M == 1).
206
+ const slice = this.pipelines['colSlice']!;
207
+ const stride = convD + H;
208
+ dispatchColumnSlice(d, slice, inProjOut, xConvBuf, M, stride, 0, convD);
209
+ dispatchColumnSlice(d, slice, inProjOut, dtBuf, M, stride, convD, H);
205
210
  }
206
211
  inProjOut.destroy();
207
212
 
@@ -221,11 +226,11 @@ export class Mamba3Block implements SequenceLayer {
221
226
  const bProjBuf = createEmptyStorageBuffer(d, M * G * Nc * 2 * 4, true);
222
227
  const cProjBuf = createEmptyStorageBuffer(d, M * G * Nc * 2 * 4, true);
223
228
  {
224
- const enc = d.createCommandEncoder();
225
- enc.copyBufferToBuffer(convOut, 0, xSsdBuf, 0, M * D * 4);
226
- enc.copyBufferToBuffer(convOut, M * D * 4, bProjBuf, 0, M * G * Nc * 2 * 4);
227
- enc.copyBufferToBuffer(convOut, M * (D + G * Nc * 2) * 4, cProjBuf, 0, M * G * Nc * 2 * 4);
228
- d.queue.submit([enc.finish()]);
229
+ // COLUMN split of the (M, convD) conv output — same reasoning as above.
230
+ const slice = this.pipelines['colSlice']!;
231
+ dispatchColumnSlice(d, slice, convOut, xSsdBuf, M, convD, 0, D);
232
+ dispatchColumnSlice(d, slice, convOut, bProjBuf, M, convD, D, G * Nc * 2);
233
+ dispatchColumnSlice(d, slice, convOut, cProjBuf, M, convD, D + G * Nc * 2, G * Nc * 2);
229
234
  }
230
235
  convOut.destroy();
231
236
 
package/src/moe/index.ts CHANGED
@@ -13,12 +13,3 @@ export type { MoEConfig, MoEParam, RouteResult } from "./moe_model.js";
13
13
  export { MoETrainer } from "./moe_trainer.js";
14
14
  export type { MoESample, MoETrainOptions, MoEEpochResult } from "./moe_trainer.js";
15
15
 
16
- export { EvermindModelPackage } from "./moe_package.js";
17
- export type {
18
- EvermindModelManifest,
19
- EvermindModelCard,
20
- EvermindModelType,
21
- EvermindModality,
22
- PackageMeta,
23
- ValidationResult,
24
- } from "./moe_package.js";
@@ -15,19 +15,42 @@ export interface OptimTarget {
15
15
  gradients(): OptimParam[];
16
16
  }
17
17
 
18
+ /**
19
+ * Optimizer-state sharding (ZeRO-1 analog). When set, this optimiser instance
20
+ * OWNS only parameter tensors where `paramIndex % count === index`, and only
21
+ * allocates the AdamW moments (m, v — 2× the parameter bytes, the dominant cost
22
+ * of full fine-tuning per the cookbook's memory accounting) for its shard.
23
+ *
24
+ * Run `count` instances over the SAME model, one per `index`, and the union of
25
+ * their steps equals one unsharded step — but each holds only 1/count of the
26
+ * optimizer state. That is exactly how FSDP/ZeRO fits a larger model on the same
27
+ * hardware; here it is the seam a multi-device trainer partitions on, and even
28
+ * single-process it caps resident moment memory.
29
+ */
30
+ export interface ShardSpec {
31
+ /** This shard's rank, 0-based. */
32
+ index: number;
33
+ /** Total number of shards. */
34
+ count: number;
35
+ }
36
+
18
37
  export interface AdamWOptions {
19
38
  lr?: number;
20
39
  beta1?: number;
21
40
  beta2?: number;
22
41
  eps?: number;
23
42
  weightDecay?: number;
43
+ /** Optimizer-state sharding. Omit for the full (single-owner) optimiser. */
44
+ shard?: ShardSpec;
24
45
  }
25
46
 
26
47
  export class AdamW {
27
- private readonly m: Float32Array[] = [];
28
- private readonly v: Float32Array[] = [];
48
+ // Per-parameter moments, or null for tensors this shard does not own (no memory).
49
+ private readonly m: (Float32Array | null)[] = [];
50
+ private readonly v: (Float32Array | null)[] = [];
29
51
  private t = 0;
30
- private readonly opt: Required<AdamWOptions>;
52
+ private readonly opt: Required<Omit<AdamWOptions, "shard">>;
53
+ private readonly shard: ShardSpec | null;
31
54
 
32
55
  constructor(
33
56
  private readonly target: OptimTarget,
@@ -40,13 +63,29 @@ export class AdamW {
40
63
  eps: options.eps ?? 1e-8,
41
64
  weightDecay: options.weightDecay ?? 0,
42
65
  };
43
- for (const p of target.parameters()) {
44
- this.m.push(new Float32Array(p.data.length));
45
- this.v.push(new Float32Array(p.data.length));
66
+ const shard = options.shard ?? null;
67
+ if (shard && (shard.count <= 0 || shard.index < 0 || shard.index >= shard.count)) {
68
+ throw new Error(`AdamW: invalid shard ${shard.index}/${shard.count}`);
46
69
  }
70
+ this.shard = shard;
71
+ const params = target.parameters();
72
+ for (let p = 0; p < params.length; p++) {
73
+ if (this._owns(p)) {
74
+ this.m.push(new Float32Array(params[p]!.data.length));
75
+ this.v.push(new Float32Array(params[p]!.data.length));
76
+ } else {
77
+ this.m.push(null);
78
+ this.v.push(null);
79
+ }
80
+ }
81
+ }
82
+
83
+ /** Whether this shard owns (and updates) parameter tensor `p`. */
84
+ private _owns(p: number): boolean {
85
+ return this.shard === null || p % this.shard.count === this.shard.index;
47
86
  }
48
87
 
49
- /** One optimiser step from the currently-accumulated gradients. */
88
+ /** One optimiser step from the currently-accumulated gradients (owned tensors only). */
50
89
  step(): void {
51
90
  this.t++;
52
91
  const { lr, beta1, beta2, eps, weightDecay } = this.opt;
@@ -55,6 +94,7 @@ export class AdamW {
55
94
  const bc1 = 1 - Math.pow(beta1, this.t);
56
95
  const bc2 = 1 - Math.pow(beta2, this.t);
57
96
  for (let p = 0; p < params.length; p++) {
97
+ if (!this._owns(p)) continue;
58
98
  const w = params[p]!.data;
59
99
  const g = grads[p]!.data;
60
100
  const m = this.m[p]!;
@@ -69,4 +109,72 @@ export class AdamW {
69
109
  }
70
110
  }
71
111
  }
112
+
113
+ /** Bytes of optimizer state this shard holds (m + v) — 1/count of the full state. */
114
+ stateBytes(): number {
115
+ let n = 0;
116
+ for (const arr of this.m) if (arr) n += arr.byteLength;
117
+ for (const arr of this.v) if (arr) n += arr.byteLength;
118
+ return n;
119
+ }
120
+ }
121
+
122
+ // ── The CPU mirror of WEIGHT_UPDATE_WGSL ─────────────────────────────────────
123
+
124
+ /** Hyperparameters of one {@link adamwUpdateInPlace} step. */
125
+ export interface AdamWKernelStep {
126
+ lr: number;
127
+ beta1: number;
128
+ beta2: number;
129
+ eps: number;
130
+ weightDecay: number;
131
+ /** beta1^t — the precomputed bias-correction term (matches the kernel uniform). */
132
+ beta1_t: number;
133
+ /** beta2^t. */
134
+ beta2_t: number;
135
+ /** Trust region: max |Δθ| per step. 0 disables the bound. */
136
+ maxDelta: number;
137
+ /** Global gradient-clip factor, applied to `grad` on the fly (1 = no clipping). */
138
+ gradScale?: number;
139
+ }
140
+
141
+ /**
142
+ * One AdamW step over a single parameter tensor — the exact CPU mirror of the
143
+ * `adamw_update` entry point in `WEIGHT_UPDATE_WGSL`, including its two safety
144
+ * properties:
145
+ *
146
+ * • the **NaN/Inf guard** — a non-finite step is dropped rather than written
147
+ * into a weight, so one bad gradient cannot permanently poison the model;
148
+ * • the **trust region** — `maxDelta` bounds how far a single step can move a
149
+ * weight, which is what keeps repeated write-through adapts reversible.
150
+ *
151
+ * Decoupled weight decay (`θ * (1 - lr·wd)`), matching the kernel — deliberately
152
+ * NOT the coupled form {@link AdamW} uses for the MoE/EvermindLM trainers.
153
+ *
154
+ * `param`, `m` and `v` are updated in place.
155
+ */
156
+ export function adamwUpdateInPlace(
157
+ param: Float32Array,
158
+ grad: Float32Array,
159
+ m: Float32Array,
160
+ v: Float32Array,
161
+ hp: AdamWKernelStep,
162
+ ): void {
163
+ const { lr, beta1, beta2, eps, weightDecay, beta1_t, beta2_t, maxDelta } = hp;
164
+ const gradScale = hp.gradScale ?? 1;
165
+ const bc1 = 1 - beta1_t;
166
+ const bc2 = 1 - beta2_t;
167
+ for (let i = 0; i < param.length; i++) {
168
+ const g = grad[i]! * gradScale;
169
+ const mNew = beta1 * m[i]! + (1 - beta1) * g;
170
+ const vNew = beta2 * v[i]! + (1 - beta2) * g * g;
171
+ m[i] = mNew;
172
+ v[i] = vNew;
173
+
174
+ let step = (lr * (mNew / bc1)) / (Math.sqrt(vNew / bc2) + eps);
175
+ if (!Number.isFinite(step)) step = 0;
176
+ if (maxDelta > 0) step = Math.min(maxDelta, Math.max(-maxDelta, step));
177
+
178
+ param[i] = param[i]! * (1 - lr * weightDecay) - step;
179
+ }
72
180
  }
@@ -52,6 +52,14 @@ const BYTE_DECODER = new Map([...BYTE_ENCODER].map(([k, v]) => [v, k]));
52
52
  const PRE_TOKENIZE_RE =
53
53
  /(?:'s|'t|'re|'ve|'m|'ll|'d)|[^\r\n\p{L}\p{N}]?\p{L}+|\p{N}{1,3}| ?[^\s\p{L}\p{N}]+[\r\n]*|\s*[\r\n]+|\s+(?!\S)|\s+/gu;
54
54
 
55
+ /** A round-trippable snapshot of a {@link BPETokenizer} — see `toObject`/`deserialize`. */
56
+ export interface BPETokenizerSpec {
57
+ vocab: Record<string, number>;
58
+ /** Merge rules in rank order ("a b" strings). */
59
+ merges: string[];
60
+ specials?: { bos?: string; eos?: string; pad?: string; unk?: string };
61
+ }
62
+
55
63
  export class BPETokenizer {
56
64
  vocab: Map<string, number>;
57
65
  idToToken: Map<number, string>;
@@ -225,6 +233,53 @@ export class BPETokenizer {
225
233
  this.loadFromObjects(vocabObj, mergeArr);
226
234
  }
227
235
 
236
+ /**
237
+ * The tokenizer as a plain, JSON-serialisable object: vocab, merges and the
238
+ * four special-token strings.
239
+ *
240
+ * This is what lets an `.evermind` package EMBED its tokenizer instead of
241
+ * expecting a consumer to source the matching vocab separately — token ids are
242
+ * meaningless without the exact vocabulary that produced them, so shipping a
243
+ * checkpoint without one is shipping half an artifact.
244
+ */
245
+ toObject(): BPETokenizerSpec {
246
+ return {
247
+ vocab: Object.fromEntries(this.vocab),
248
+ merges: [...this.merges.entries()].sort((a, b) => a[1] - b[1]).map(([m]) => m),
249
+ specials: { bos: this.bosToken, eos: this.eosToken, pad: this.padToken, unk: this.unkToken },
250
+ };
251
+ }
252
+
253
+ /** Restore a tokenizer from {@link toObject}. */
254
+ loadFromSpec(spec: BPETokenizerSpec): void {
255
+ const sp = spec.specials;
256
+ if (sp?.bos) this.bosToken = sp.bos;
257
+ if (sp?.eos) this.eosToken = sp.eos;
258
+ if (sp?.pad) this.padToken = sp.pad;
259
+ if (sp?.unk) this.unkToken = sp.unk;
260
+ this.loadFromObjects(spec.vocab, spec.merges);
261
+ }
262
+
263
+ /** UTF-8 JSON bytes of {@link toObject} — the `.evermind` tokenizer section. */
264
+ serialize(): ArrayBuffer {
265
+ const bytes = new TextEncoder().encode(JSON.stringify(this.toObject()));
266
+ // Copy into a standalone ArrayBuffer (the view may be a slice of a pool).
267
+ const out = new ArrayBuffer(bytes.byteLength);
268
+ new Uint8Array(out).set(bytes);
269
+ return out;
270
+ }
271
+
272
+ /** Inverse of {@link serialize}. */
273
+ static deserialize(buffer: ArrayBuffer): BPETokenizer {
274
+ const spec = JSON.parse(new TextDecoder().decode(new Uint8Array(buffer))) as BPETokenizerSpec;
275
+ if (!spec || typeof spec.vocab !== 'object' || !Array.isArray(spec.merges)) {
276
+ throw new Error('BPETokenizer.deserialize: not a serialised BPE tokenizer (expected { vocab, merges })');
277
+ }
278
+ const tok = new BPETokenizer();
279
+ tok.loadFromSpec(spec);
280
+ return tok;
281
+ }
282
+
228
283
  /**
229
284
  * Fetch and import a Hugging Face `tokenizer.json` by URL — the IDE's
230
285
  * "import merges from a repo" path. Resolves once the tokenizer is loaded.