@seanhogg/builderforce-memory-engine 2026.7.14 → 2026.9.28

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 (186) 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 +25 -12
  73. package/dist/index.d.ts.map +1 -1
  74. package/dist/index.js +21 -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 +110 -1
  87. package/dist/lm/evermind_lm.d.ts.map +1 -1
  88. package/dist/lm/evermind_lm.js +220 -35
  89. package/dist/lm/evermind_lm.js.map +1 -1
  90. package/dist/lm/index.d.ts +4 -2
  91. package/dist/lm/index.d.ts.map +1 -1
  92. package/dist/lm/index.js +3 -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 +32 -0
  115. package/dist/optim/adamw.d.ts.map +1 -1
  116. package/dist/optim/adamw.js +34 -0
  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/mamba1_cpu.d.ts +109 -0
  127. package/dist/training/mamba1_cpu.d.ts.map +1 -0
  128. package/dist/training/mamba1_cpu.js +316 -0
  129. package/dist/training/mamba1_cpu.js.map +1 -0
  130. package/dist/training/model_cpu.d.ts +100 -0
  131. package/dist/training/model_cpu.d.ts.map +1 -0
  132. package/dist/training/model_cpu.js +252 -0
  133. package/dist/training/model_cpu.js.map +1 -0
  134. package/dist/training/trainer.d.ts +156 -16
  135. package/dist/training/trainer.d.ts.map +1 -1
  136. package/dist/training/trainer.js +327 -101
  137. package/dist/training/trainer.js.map +1 -1
  138. package/dist/utils/base64.d.ts +12 -0
  139. package/dist/utils/base64.d.ts.map +1 -0
  140. package/dist/utils/base64.js +27 -0
  141. package/dist/utils/base64.js.map +1 -0
  142. package/package.json +3 -1
  143. package/src/bench/adaptation.ts +180 -0
  144. package/src/bench/harness.ts +1 -1
  145. package/src/bench/index.ts +13 -2
  146. package/src/bench/types.ts +93 -0
  147. package/src/evermind/adapt.ts +61 -0
  148. package/src/evermind/delta_wire.ts +114 -0
  149. package/src/evermind/eval.ts +62 -0
  150. package/src/evermind/index.ts +32 -0
  151. package/src/evermind/merge.ts +106 -0
  152. package/src/{moe/moe_package.ts → evermind/package.ts} +148 -17
  153. package/src/import/foreign/adapter.ts +106 -0
  154. package/src/import/foreign/apply.ts +54 -0
  155. package/src/import/foreign/codestral_mamba.ts +224 -0
  156. package/src/import/foreign/falcon_mamba.ts +173 -0
  157. package/src/import/foreign/index.ts +37 -0
  158. package/src/import/foreign/plan.ts +294 -0
  159. package/src/import/foreign/registry.ts +126 -0
  160. package/src/import/index.ts +30 -0
  161. package/src/index.ts +94 -13
  162. package/src/kernels/selective_scan.ts +15 -9
  163. package/src/kernels/slice.ts +64 -0
  164. package/src/kernels/weight_update.ts +30 -4
  165. package/src/lm/evermind_lm.ts +244 -34
  166. package/src/lm/index.ts +6 -2
  167. package/src/lm/text_embedder.ts +84 -0
  168. package/src/model/attention_block.ts +9 -5
  169. package/src/model/mamba1_block.ts +28 -12
  170. package/src/model/mamba2_block.ts +14 -9
  171. package/src/model/mamba3_block.ts +14 -9
  172. package/src/moe/index.ts +0 -9
  173. package/src/optim/adamw.ts +60 -0
  174. package/src/tokenizer/bpe.ts +55 -0
  175. package/src/training/cpu_ops.ts +127 -0
  176. package/src/training/mamba1_cpu.ts +421 -0
  177. package/src/training/model_cpu.ts +360 -0
  178. package/src/training/trainer.ts +397 -154
  179. package/src/utils/base64.ts +28 -0
  180. package/dist/model/mamba_block.d.ts +0 -64
  181. package/dist/model/mamba_block.d.ts.map +0 -1
  182. package/dist/model/mamba_block.js +0 -303
  183. package/dist/model/mamba_block.js.map +0 -1
  184. package/dist/moe/moe_package.d.ts.map +0 -1
  185. package/dist/moe/moe_package.js.map +0 -1
  186. package/src/model/mamba_block.ts +0 -401
@@ -1 +1 @@
1
- {"version":3,"file":"selective_scan.js","sourceRoot":"","sources":["../../src/kernels/selective_scan.ts"],"names":[],"mappings":"AAAA,sCAAsC;AACtC,qEAAqE;AACrE,4EAA4E;AAC5E,EAAE;AACF,2BAA2B;AAC3B,oCAAoC;AACpC,8BAA8B;AAC9B,EAAE;AACF,qEAAqE;AAErE,MAAM,CAAC,MAAM,2BAA2B,GAAW,UAAU,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA2N5D,CAAC;AAEF,gDAAgD;AAChD,uEAAuE;AAEvE,MAAM,CAAC,MAAM,4BAA4B,GAAW,UAAU,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA6H7D,CAAC"}
1
+ {"version":3,"file":"selective_scan.js","sourceRoot":"","sources":["../../src/kernels/selective_scan.ts"],"names":[],"mappings":"AAAA,sCAAsC;AACtC,qEAAqE;AACrE,4EAA4E;AAC5E,EAAE;AACF,2BAA2B;AAC3B,oCAAoC;AACpC,8BAA8B;AAC9B,EAAE;AACF,qEAAqE;AAErE,MAAM,CAAC,MAAM,2BAA2B,GAAW,UAAU,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAiO5D,CAAC;AAEF,gDAAgD;AAChD,uEAAuE;AAEvE,MAAM,CAAC,MAAM,4BAA4B,GAAW,UAAU,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA6H7D,CAAC"}
@@ -0,0 +1,26 @@
1
+ /**
2
+ * slice.ts — strided COLUMN slice of a row-major (rows, stride) tensor.
3
+ *
4
+ * Every block in this engine projects into one fused tensor and then splits it:
5
+ * Mamba-1 into (x, z) and (Δ, B, C), Mamba-2/3 into (x, Δ) and (x, B, C),
6
+ * attention into (Q, K, V). `LINEAR_FORWARD_WGSL` writes its output `(M, N)`
7
+ * ROW-major, so each of those parts is a contiguous range of COLUMNS **within
8
+ * every row** — not a contiguous range of the buffer.
9
+ *
10
+ * Splitting them with `copyBufferToBuffer` therefore took whole ROWS instead: it
11
+ * is only the right answer when `M == 1` (batch × seq_len), and for any real
12
+ * sequence it silently handed the block the wrong tensor. This kernel is the one
13
+ * correct implementation; every block uses it.
14
+ */
15
+ export declare const COL_SLICE_WGSL: string;
16
+ /** Entry point name of {@link COL_SLICE_WGSL}. */
17
+ export declare const COL_SLICE_ENTRY = "col_slice";
18
+ /**
19
+ * `dst[r, 0..width) = src[r, offset..offset+width)` for every row.
20
+ *
21
+ * @param pipeline a pipeline built from {@link COL_SLICE_WGSL} / {@link COL_SLICE_ENTRY}
22
+ * @param rows batch * seqLen
23
+ * @param stride source row width (the fused projection's output features)
24
+ */
25
+ export declare function dispatchColumnSlice(device: GPUDevice, pipeline: GPUComputePipeline, src: GPUBuffer, dst: GPUBuffer, rows: number, stride: number, offset: number, width: number): void;
26
+ //# sourceMappingURL=slice.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"slice.d.ts","sourceRoot":"","sources":["../../src/kernels/slice.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAIH,eAAO,MAAM,cAAc,EAAE,MAoB5B,CAAC;AAEF,kDAAkD;AAClD,eAAO,MAAM,eAAe,cAAc,CAAC;AAE3C;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CAC/B,MAAM,EAAE,SAAS,EACjB,QAAQ,EAAE,kBAAkB,EAC5B,GAAG,EAAE,SAAS,EACd,GAAG,EAAE,SAAS,EACd,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,MAAM,GACd,IAAI,CAKN"}
@@ -0,0 +1,52 @@
1
+ /**
2
+ * slice.ts — strided COLUMN slice of a row-major (rows, stride) tensor.
3
+ *
4
+ * Every block in this engine projects into one fused tensor and then splits it:
5
+ * Mamba-1 into (x, z) and (Δ, B, C), Mamba-2/3 into (x, Δ) and (x, B, C),
6
+ * attention into (Q, K, V). `LINEAR_FORWARD_WGSL` writes its output `(M, N)`
7
+ * ROW-major, so each of those parts is a contiguous range of COLUMNS **within
8
+ * every row** — not a contiguous range of the buffer.
9
+ *
10
+ * Splitting them with `copyBufferToBuffer` therefore took whole ROWS instead: it
11
+ * is only the right answer when `M == 1` (batch × seq_len), and for any real
12
+ * sequence it silently handed the block the wrong tensor. This kernel is the one
13
+ * correct implementation; every block uses it.
14
+ */
15
+ import { createBindGroup, createUniformBuffer, dispatchKernel, cdiv } from '../utils/gpu_utils.js';
16
+ export const COL_SLICE_WGSL = /* wgsl */ `
17
+ struct SliceParams {
18
+ rows : u32,
19
+ stride : u32, // source row width
20
+ offset : u32, // first column to take
21
+ width : u32, // number of columns to take
22
+ };
23
+ @group(0) @binding(0) var<uniform> p : SliceParams;
24
+ @group(0) @binding(1) var<storage, read> src : array<f32>;
25
+ @group(0) @binding(2) var<storage, read_write> dst : array<f32>;
26
+
27
+ // Dispatch: (ceil(rows * width / 256), 1, 1)
28
+ @compute @workgroup_size(256)
29
+ fn col_slice(@builtin(global_invocation_id) gid: vec3<u32>) {
30
+ let i = gid.x;
31
+ if (i >= p.rows * p.width) { return; }
32
+ let r = i / p.width;
33
+ let c = i % p.width;
34
+ dst[i] = src[r * p.stride + p.offset + c];
35
+ }
36
+ `;
37
+ /** Entry point name of {@link COL_SLICE_WGSL}. */
38
+ export const COL_SLICE_ENTRY = 'col_slice';
39
+ /**
40
+ * `dst[r, 0..width) = src[r, offset..offset+width)` for every row.
41
+ *
42
+ * @param pipeline a pipeline built from {@link COL_SLICE_WGSL} / {@link COL_SLICE_ENTRY}
43
+ * @param rows batch * seqLen
44
+ * @param stride source row width (the fused projection's output features)
45
+ */
46
+ export function dispatchColumnSlice(device, pipeline, src, dst, rows, stride, offset, width) {
47
+ const pBuf = createUniformBuffer(device, new Uint32Array([rows, stride, offset, width]).buffer);
48
+ const bg = createBindGroup(device, pipeline, [pBuf, src, dst]);
49
+ dispatchKernel(device, pipeline, bg, [cdiv(rows * width, 256), 1, 1]);
50
+ pBuf.destroy();
51
+ }
52
+ //# sourceMappingURL=slice.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"slice.js","sourceRoot":"","sources":["../../src/kernels/slice.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAE,eAAe,EAAE,mBAAmB,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,uBAAuB,CAAC;AAEnG,MAAM,CAAC,MAAM,cAAc,GAAW,UAAU,CAAA;;;;;;;;;;;;;;;;;;;;CAoB/C,CAAC;AAEF,kDAAkD;AAClD,MAAM,CAAC,MAAM,eAAe,GAAG,WAAW,CAAC;AAE3C;;;;;;GAMG;AACH,MAAM,UAAU,mBAAmB,CAC/B,MAAiB,EACjB,QAA4B,EAC5B,GAAc,EACd,GAAc,EACd,IAAY,EACZ,MAAc,EACd,MAAc,EACd,KAAa;IAEb,MAAM,IAAI,GAAG,mBAAmB,CAAC,MAAM,EAAE,IAAI,WAAW,CAAC,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;IAChG,MAAM,EAAE,GAAG,eAAe,CAAC,MAAM,EAAE,QAAQ,EAAE,CAAC,IAAI,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC,CAAC;IAC/D,cAAc,CAAC,MAAM,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,GAAG,KAAK,EAAE,GAAG,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;IACtE,IAAI,CAAC,OAAO,EAAE,CAAC;AACnB,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"weight_update.d.ts","sourceRoot":"","sources":["../../src/kernels/weight_update.ts"],"names":[],"mappings":"AAUA,eAAO,MAAM,kBAAkB,EAAE,MAmEhC,CAAC;AAIF,eAAO,MAAM,cAAc,EAAE,MAyD5B,CAAC"}
1
+ {"version":3,"file":"weight_update.d.ts","sourceRoot":"","sources":["../../src/kernels/weight_update.ts"],"names":[],"mappings":"AAUA,eAAO,MAAM,kBAAkB,EAAE,MAmEhC,CAAC;AAkBF,eAAO,MAAM,cAAc,EAAE,MAqE5B,CAAC"}
@@ -77,6 +77,20 @@ fn adamw_update(
77
77
  `;
78
78
  // Gradient clipping kernel – clips global gradient norm to max_norm.
79
79
  // Run before weight updates. Two-pass: first compute squared norm, then scale.
80
+ //
81
+ // The accumulator is a REAL atomic. It used to be a plain `array<f32>` written as
82
+ // `norm_sq[0] = norm_sq[0] + local_sq[0]` by the lane-0 invocation of every
83
+ // workgroup — a read-modify-write with no synchronisation, so with more than one
84
+ // workgroup (i.e. any tensor above 256 elements) concurrent workgroups lost each
85
+ // other's contributions and the norm came out too SMALL, under-clipping exactly
86
+ // when clipping matters most. WGSL has no `atomic<f32>`, so the accumulator is an
87
+ // `atomic<u32>` holding the f32 bit pattern and updated with a
88
+ // compare-exchange loop — the standard portable float-atomic-add.
89
+ //
90
+ // Summation ORDER across workgroups is not deterministic (floating-point addition
91
+ // is not associative), so the norm can differ in the last ulp or two between runs.
92
+ // That is fine for a clip threshold and is the accepted trade for correctness;
93
+ // nothing downstream depends on a bit-exact norm.
80
94
  export const GRAD_CLIP_WGSL = /* wgsl */ `
81
95
 
82
96
  struct ClipParams {
@@ -86,10 +100,23 @@ struct ClipParams {
86
100
 
87
101
  @group(0) @binding(0) var<uniform> clip_p : ClipParams;
88
102
  @group(0) @binding(1) var<storage, read_write> grad : array<f32>;
89
- @group(0) @binding(2) var<storage, read_write> norm_sq : array<f32>; // size 1, atomic accumulator
103
+ // size 1 – the f32 bit pattern of the accumulated sum of squares, updated
104
+ // atomically. Zero-initialise it by writing 0.0f (bit pattern 0x00000000).
105
+ @group(0) @binding(2) var<storage, read_write> norm_sq : array<atomic<u32>>;
90
106
 
91
107
  var<workgroup> local_sq : array<f32, 256>;
92
108
 
109
+ // Portable atomic add on an f32 stored as its u32 bit pattern.
110
+ fn atomic_add_f32(value: f32) {
111
+ var old_bits : u32 = atomicLoad(&norm_sq[0]);
112
+ loop {
113
+ let new_bits = bitcast<u32>(bitcast<f32>(old_bits) + value);
114
+ let res = atomicCompareExchangeWeak(&norm_sq[0], old_bits, new_bits);
115
+ if (res.exchanged) { break; }
116
+ old_bits = res.old_value;
117
+ }
118
+ }
119
+
93
120
  // Pass 1: reduce sum of squares into norm_sq[0]
94
121
  @compute @workgroup_size(256, 1, 1)
95
122
  fn grad_norm_reduce(
@@ -115,8 +142,7 @@ fn grad_norm_reduce(
115
142
  }
116
143
 
117
144
  if (lid == 0u) {
118
- // Non-atomic accumulation (single workgroup assumption for small models)
119
- norm_sq[0] = norm_sq[0] + local_sq[0];
145
+ atomic_add_f32(local_sq[0]);
120
146
  }
121
147
  }
122
148
 
@@ -128,7 +154,7 @@ fn grad_clip_scale(
128
154
  let i = gid.x;
129
155
  if (i >= clip_p.num_elements) { return; }
130
156
 
131
- let ns = norm_sq[0];
157
+ let ns = bitcast<f32>(atomicLoad(&norm_sq[0]));
132
158
  if (ns > clip_p.max_norm_sq) {
133
159
  let scale = sqrt(clip_p.max_norm_sq / ns);
134
160
  grad[i] = grad[i] * scale;
@@ -1 +1 @@
1
- {"version":3,"file":"weight_update.js","sourceRoot":"","sources":["../../src/kernels/weight_update.ts"],"names":[],"mappings":"AAAA,8CAA8C;AAC9C,sDAAsD;AACtD,EAAE;AACF,qBAAqB;AACrB,8CAA8C;AAC9C,gDAAgD;AAChD,gCAAgC;AAChC,gCAAgC;AAChC,uFAAuF;AAEvF,MAAM,CAAC,MAAM,kBAAkB,GAAW,UAAU,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAmEnD,CAAC;AAEF,qEAAqE;AACrE,gFAAgF;AAChF,MAAM,CAAC,MAAM,cAAc,GAAW,UAAU,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAyD/C,CAAC"}
1
+ {"version":3,"file":"weight_update.js","sourceRoot":"","sources":["../../src/kernels/weight_update.ts"],"names":[],"mappings":"AAAA,8CAA8C;AAC9C,sDAAsD;AACtD,EAAE;AACF,qBAAqB;AACrB,8CAA8C;AAC9C,gDAAgD;AAChD,gCAAgC;AAChC,gCAAgC;AAChC,uFAAuF;AAEvF,MAAM,CAAC,MAAM,kBAAkB,GAAW,UAAU,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAmEnD,CAAC;AAEF,qEAAqE;AACrE,gFAAgF;AAChF,EAAE;AACF,kFAAkF;AAClF,4EAA4E;AAC5E,iFAAiF;AACjF,iFAAiF;AACjF,gFAAgF;AAChF,kFAAkF;AAClF,+DAA+D;AAC/D,kEAAkE;AAClE,EAAE;AACF,kFAAkF;AAClF,mFAAmF;AACnF,+EAA+E;AAC/E,kDAAkD;AAClD,MAAM,CAAC,MAAM,cAAc,GAAW,UAAU,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAqE/C,CAAC"}
@@ -82,8 +82,34 @@ export interface LMGenerateOptions {
82
82
  /** Stop generating when this token id is produced. */
83
83
  stopToken?: number;
84
84
  }
85
+ /**
86
+ * Incremental decode state — everything a continuation needs from the prefix.
87
+ *
88
+ * The model's only cross-position dependency is the depthwise causal conv, whose
89
+ * receptive field is `convKernel` positions, so the entire "KV cache" is the
90
+ * trailing `convKernel - 1` normalised conv inputs per layer. That makes an exact
91
+ * prefix cache cheap: process a prompt once, then extend it with each candidate
92
+ * continuation for the cost of the candidate alone.
93
+ */
94
+ export interface EvermindLMDecodeState {
95
+ /** [layer][slot] the trailing `convKernel - 1` RMSNormed conv inputs, oldest first. */
96
+ convWindow: Float32Array[][];
97
+ /** Positions consumed so far. */
98
+ length: number;
99
+ /** Logits at the LAST consumed position — what predicts the next token. */
100
+ lastLogits: Float32Array | null;
101
+ }
85
102
  export declare class EvermindLM {
86
103
  readonly config: Required<Omit<EvermindLMConfig, "seed">>;
104
+ /**
105
+ * Token-positions pushed through a block since the last {@link resetStats}.
106
+ *
107
+ * The cost metric that matters for the tool-calling path: scoring N candidate
108
+ * tool names used to replay the WHOLE prompt N times, so this counted
109
+ * N x (prompt + candidate) x layers. With {@link stepDecode} the prompt is paid
110
+ * for once. Exposed so a benchmark can assert the saving rather than assume it.
111
+ */
112
+ private _positionsEvaluated;
87
113
  /** Tied token embedding / output head: vocabSize × dModel (row-major). */
88
114
  emb: Float32Array;
89
115
  private gEmb;
@@ -108,6 +134,20 @@ export declare class EvermindLM {
108
134
  * instead of retaining every layer's cache at once.
109
135
  */
110
136
  private _forwardLayer;
137
+ /**
138
+ * ONE position through one (conv + MoE) block, given the trailing conv window.
139
+ *
140
+ * The single place the block's maths lives: the batch path
141
+ * ({@link _forwardLayer}, which needs the full activation cache for backward)
142
+ * and the incremental path ({@link stepDecode}, which needs none of it) both
143
+ * run this, so they cannot drift apart. `window` holds the previous
144
+ * `convKernel - 1` RMSNormed conv inputs, OLDEST FIRST, and is advanced in
145
+ * place — a shorter window is exactly the causal zero-padding at the start of
146
+ * a sequence.
147
+ */
148
+ private _stepLayerPosition;
149
+ /** One token's embedding row (a copy — callers write into it). */
150
+ private _embedOne;
111
151
  /** Tied output head: logits_t[v] = x_t · emb[v]. */
112
152
  private _head;
113
153
  /** Run the model over a token sequence; returns per-position logits + a cache. */
@@ -115,6 +155,33 @@ export declare class EvermindLM {
115
155
  logits: Float32Array[];
116
156
  cache: ForwardCache;
117
157
  };
158
+ /**
159
+ * Produces a single fixed-length embedding vector for a token sequence — the
160
+ * CPU counterpart of `HybridMambaModel.embed()`, to the same contract.
161
+ *
162
+ * Runs the full block stack and takes the final hidden state — i.e. exactly the
163
+ * state the tied LM head consumes in {@link forward} — then mean-pools across
164
+ * sequence positions and L2-normalises. The result has length `dModel`, so
165
+ * cosine similarity between two embeddings reduces to a dot product.
166
+ *
167
+ * Like the GPU path it skips the (expensive) head projection: it only needs the
168
+ * `dModel`-wide hidden state, not `vocabSize` logits. Unlike the GPU path it
169
+ * needs no WebGPU device at all, which is what lets a headless host (the stdio
170
+ * MCP server) rank recall by SSM embedding instead of word overlap.
171
+ *
172
+ * Streamed one position at a time through the SHARED block implementation
173
+ * ({@link _stepLayerPosition}, the same routine {@link forward} and
174
+ * {@link stepDecode} run), so it can never drift from them and it retains no
175
+ * per-position activation cache.
176
+ *
177
+ * The embedding reflects whatever the model currently knows — an untrained
178
+ * model behaves like a random projection of the token embeddings (still
179
+ * lexically discriminative), and the representation sharpens as the model is
180
+ * adapted/distilled.
181
+ */
182
+ embed(tokenIds: number[] | Uint32Array): Float32Array;
183
+ /** Text-level embedding: encode with `codec`, then {@link embed}. */
184
+ embedText(text: string, codec: TextCodec): Float32Array;
118
185
  /**
119
186
  * Head + tied-embedding gradient. Accumulates dL/d(head→emb) into gEmb and
120
187
  * returns the mean next-token loss plus dL/d(finalX). Shared by the full and
@@ -145,6 +212,36 @@ export declare class EvermindLM {
145
212
  * sequences / bigger models on a constrained device.
146
213
  */
147
214
  lossAndBackwardCheckpointed(tokens: number[]): number;
215
+ /** Positions pushed through a block since the last {@link resetStats}. */
216
+ get positionsEvaluated(): number;
217
+ resetStats(): void;
218
+ /** An empty decode state — the start of a sequence. */
219
+ newDecodeState(): EvermindLMDecodeState;
220
+ /** Deep copy, so one prefix state can seed many independent continuations. */
221
+ cloneDecodeState(state: EvermindLMDecodeState): EvermindLMDecodeState;
222
+ /**
223
+ * Consume `tokens` from `state`, returning the per-position logits and the
224
+ * ADVANCED state. `state` is not mutated — pass {@link cloneDecodeState} output
225
+ * or a fresh state; this clones internally so the caller's prefix stays reusable.
226
+ *
227
+ * Numerically identical to {@link forward} over the concatenated sequence: the
228
+ * shared {@link _stepLayerPosition} is the only implementation of the block.
229
+ */
230
+ stepDecode(state: EvermindLMDecodeState, tokens: number[]): {
231
+ logits: Float32Array[];
232
+ state: EvermindLMDecodeState;
233
+ };
234
+ /** Run a prompt once and keep the state, so continuations are cheap. */
235
+ prefixState(tokens: number[]): EvermindLMDecodeState;
236
+ /**
237
+ * Mean log-probability the model assigns to `continuation` following a cached
238
+ * prefix — the tool-decision vote, without replaying the prompt.
239
+ *
240
+ * Returns `-Infinity` for an empty continuation, and requires a prefix with at
241
+ * least one position (there must be a position to predict the first
242
+ * continuation token FROM).
243
+ */
244
+ scoreContinuation(prefix: EvermindLMDecodeState, continuation: number[]): number;
148
245
  /**
149
246
  * Text-level generation: encode the prompt, generate, decode. `codec` is any
150
247
  * tokenizer exposing encode/decode (the engine's `BPETokenizer` satisfies it),
@@ -152,7 +249,13 @@ export declare class EvermindLM {
152
249
  * `vocabSize` must match the codec's vocabulary.
153
250
  */
154
251
  generateText(prompt: string, codec: TextCodec, opts: LMGenerateOptions): string;
155
- /** Greedy / temperature-sampled autoregressive generation. Returns NEW token ids. */
252
+ /**
253
+ * Greedy / temperature-sampled autoregressive generation. Returns NEW token ids.
254
+ *
255
+ * Carries an incremental decode state, so the prompt is processed ONCE and each
256
+ * new token costs one position. The previous implementation re-ran the whole
257
+ * sequence per token, making generation quadratic in the prompt length.
258
+ */
156
259
  generate(prompt: number[], opts: LMGenerateOptions): number[];
157
260
  /** All trainable parameters as {data} (AdamW-compatible), canonical order. */
158
261
  parameters(): {
@@ -227,5 +330,11 @@ export declare class EvermindLMTrainer {
227
330
  /** Train on a set of token sequences; returns per-epoch mean loss. */
228
331
  fit(sequences: number[][]): number[];
229
332
  }
333
+ /**
334
+ * Log-probability the model assigned to `id` at a position, from that position's
335
+ * raw logits. Log-softmax computed max-shifted so a long-tail logit cannot
336
+ * overflow `exp`.
337
+ */
338
+ export declare function logProbOfToken(row: Float32Array, id: number): number;
230
339
  export {};
231
340
  //# sourceMappingURL=evermind_lm.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"evermind_lm.d.ts","sourceRoot":"","sources":["../../src/lm/evermind_lm.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"evermind_lm.d.ts","sourceRoot":"","sources":["../../src/lm/evermind_lm.ts"],"names":[],"mappings":"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"}
@@ -40,6 +40,15 @@ export const DEFAULT_LM_SEED = 0x45564c4d; // "EVLM"
40
40
  const MAGIC = 0x45564c30; // "EVL0"
41
41
  export class EvermindLM {
42
42
  config;
43
+ /**
44
+ * Token-positions pushed through a block since the last {@link resetStats}.
45
+ *
46
+ * The cost metric that matters for the tool-calling path: scoring N candidate
47
+ * tool names used to replay the WHOLE prompt N times, so this counted
48
+ * N x (prompt + candidate) x layers. With {@link stepDecode} the prompt is paid
49
+ * for once. Exposed so a benchmark can assert the saving rather than assume it.
50
+ */
51
+ _positionsEvaluated = 0;
43
52
  /** Tied token embedding / output head: vocabSize × dModel (row-major). */
44
53
  emb;
45
54
  gEmb;
@@ -132,47 +141,73 @@ export class EvermindLM {
132
141
  * instead of retaining every layer's cache at once.
133
142
  */
134
143
  _forwardLayer(l, layerIn) {
135
- const { dModel, convKernel } = this.config;
136
144
  const T = layerIn.length;
137
- const ker = this.conv[l];
138
- const nConv = this.nConv[l];
139
- const nMoe = this.nMoe[l];
145
+ const window = [];
140
146
  const normedConv = [];
141
147
  const rmsConv = [];
142
- for (let t = 0; t < T; t++) {
143
- const { y, r } = rmsNorm(layerIn[t], nConv);
144
- normedConv.push(y);
145
- rmsConv.push(r);
146
- }
147
148
  const afterConv = [];
148
- for (let t = 0; t < T; t++) {
149
- const out = Float32Array.from(layerIn[t]); // residual base
150
- for (let c = 0; c < dModel; c++) {
151
- let acc = 0;
152
- for (let j = 0; j < convKernel; j++) {
153
- const ti = t - j;
154
- if (ti >= 0)
155
- acc += ker[c * convKernel + j] * normedConv[ti][c];
156
- }
157
- out[c] = out[c] + acc;
158
- }
159
- afterConv.push(out);
160
- }
161
149
  const rmsMoe = [];
162
150
  const moeCache = [];
163
151
  const afterMoe = [];
164
152
  for (let t = 0; t < T; t++) {
165
- const { y, r } = rmsNorm(afterConv[t], nMoe);
166
- rmsMoe.push(r);
167
- const out = Float32Array.from(afterConv[t]); // residual base
168
- const mr = this.moe[l].forward(y);
169
- for (let c = 0; c < dModel; c++)
170
- out[c] = out[c] + mr.output[c];
171
- afterMoe.push(out);
172
- moeCache.push(mr.cache);
153
+ const st = this._stepLayerPosition(l, layerIn[t], window);
154
+ normedConv.push(st.normed);
155
+ rmsConv.push(st.rmsConv);
156
+ afterConv.push(st.afterConv);
157
+ rmsMoe.push(st.rmsMoe);
158
+ moeCache.push(st.moeCache);
159
+ afterMoe.push(st.out);
173
160
  }
174
161
  return { afterMoe, cache: { layerIn, normedConv, rmsConv, afterConv, rmsMoe, moeCache } };
175
162
  }
163
+ /**
164
+ * ONE position through one (conv + MoE) block, given the trailing conv window.
165
+ *
166
+ * The single place the block's maths lives: the batch path
167
+ * ({@link _forwardLayer}, which needs the full activation cache for backward)
168
+ * and the incremental path ({@link stepDecode}, which needs none of it) both
169
+ * run this, so they cannot drift apart. `window` holds the previous
170
+ * `convKernel - 1` RMSNormed conv inputs, OLDEST FIRST, and is advanced in
171
+ * place — a shorter window is exactly the causal zero-padding at the start of
172
+ * a sequence.
173
+ */
174
+ _stepLayerPosition(l, xt, window) {
175
+ const { dModel, convKernel } = this.config;
176
+ const ker = this.conv[l];
177
+ const { y: normed, r: rConv } = rmsNorm(xt, this.nConv[l]);
178
+ const afterConv = Float32Array.from(xt); // residual base
179
+ for (let c = 0; c < dModel; c++) {
180
+ let acc = ker[c * convKernel] * normed[c];
181
+ for (let j = 1; j < convKernel; j++) {
182
+ const prev = window[window.length - j]; // position t - j, or undefined before the start
183
+ if (prev)
184
+ acc += ker[c * convKernel + j] * prev[c];
185
+ }
186
+ afterConv[c] = afterConv[c] + acc;
187
+ }
188
+ window.push(normed);
189
+ while (window.length > convKernel - 1)
190
+ window.shift();
191
+ const { y: normedMoe, r: rMoe } = rmsNorm(afterConv, this.nMoe[l]);
192
+ const out = Float32Array.from(afterConv); // residual base
193
+ const mr = this.moe[l].forward(normedMoe);
194
+ for (let c = 0; c < dModel; c++)
195
+ out[c] = out[c] + mr.output[c];
196
+ this._positionsEvaluated++;
197
+ return {
198
+ normed, rmsConv: rConv, afterConv, rmsMoe: rMoe,
199
+ moeCache: mr.cache, out,
200
+ };
201
+ }
202
+ /** One token's embedding row (a copy — callers write into it). */
203
+ _embedOne(tok) {
204
+ const { dModel } = this.config;
205
+ const row = new Float32Array(dModel);
206
+ const off = tok * dModel;
207
+ for (let c = 0; c < dModel; c++)
208
+ row[c] = this.emb[off + c];
209
+ return row;
210
+ }
176
211
  /** Tied output head: logits_t[v] = x_t · emb[v]. */
177
212
  _head(x) {
178
213
  const { dModel, vocabSize } = this.config;
@@ -199,6 +234,62 @@ export class EvermindLM {
199
234
  }
200
235
  return { logits: this._head(x), cache: { tokens, layers, finalX: x } };
201
236
  }
237
+ /**
238
+ * Produces a single fixed-length embedding vector for a token sequence — the
239
+ * CPU counterpart of `HybridMambaModel.embed()`, to the same contract.
240
+ *
241
+ * Runs the full block stack and takes the final hidden state — i.e. exactly the
242
+ * state the tied LM head consumes in {@link forward} — then mean-pools across
243
+ * sequence positions and L2-normalises. The result has length `dModel`, so
244
+ * cosine similarity between two embeddings reduces to a dot product.
245
+ *
246
+ * Like the GPU path it skips the (expensive) head projection: it only needs the
247
+ * `dModel`-wide hidden state, not `vocabSize` logits. Unlike the GPU path it
248
+ * needs no WebGPU device at all, which is what lets a headless host (the stdio
249
+ * MCP server) rank recall by SSM embedding instead of word overlap.
250
+ *
251
+ * Streamed one position at a time through the SHARED block implementation
252
+ * ({@link _stepLayerPosition}, the same routine {@link forward} and
253
+ * {@link stepDecode} run), so it can never drift from them and it retains no
254
+ * per-position activation cache.
255
+ *
256
+ * The embedding reflects whatever the model currently knows — an untrained
257
+ * model behaves like a random projection of the token embeddings (still
258
+ * lexically discriminative), and the representation sharpens as the model is
259
+ * adapted/distilled.
260
+ */
261
+ embed(tokenIds) {
262
+ const { dModel, numLayers } = this.config;
263
+ const out = new Float32Array(dModel);
264
+ const seqLen = tokenIds.length;
265
+ if (seqLen === 0)
266
+ return out;
267
+ // One rolling conv window per layer — a shorter window at the start of the
268
+ // sequence IS the causal zero-padding, exactly as in stepDecode.
269
+ const windows = Array.from({ length: numLayers }, () => []);
270
+ for (let t = 0; t < seqLen; t++) {
271
+ let x = this._embedOne(tokenIds[t]);
272
+ for (let l = 0; l < numLayers; l++)
273
+ x = this._stepLayerPosition(l, x, windows[l]).out;
274
+ for (let c = 0; c < dModel; c++)
275
+ out[c] = out[c] + x[c];
276
+ }
277
+ // Mean-pool across positions.
278
+ for (let c = 0; c < dModel; c++)
279
+ out[c] = out[c] / seqLen;
280
+ // L2-normalise so cosine similarity reduces to a dot product.
281
+ let norm = 0;
282
+ for (let c = 0; c < dModel; c++)
283
+ norm += out[c] * out[c];
284
+ norm = Math.sqrt(norm) || 1;
285
+ for (let c = 0; c < dModel; c++)
286
+ out[c] = out[c] / norm;
287
+ return out;
288
+ }
289
+ /** Text-level embedding: encode with `codec`, then {@link embed}. */
290
+ embedText(text, codec) {
291
+ return this.embed(codec.encode(text));
292
+ }
202
293
  // ── Loss + backward ──────────────────────────────────────────────────────────
203
294
  /**
204
295
  * Head + tied-embedding gradient. Accumulates dL/d(head→emb) into gEmb and
@@ -335,6 +426,75 @@ export class EvermindLM {
335
426
  this._embedBackward(tokens, d);
336
427
  return loss;
337
428
  }
429
+ // ── Incremental decoding (prefix cache) ──────────────────────────────────────
430
+ /** Positions pushed through a block since the last {@link resetStats}. */
431
+ get positionsEvaluated() { return this._positionsEvaluated; }
432
+ resetStats() { this._positionsEvaluated = 0; }
433
+ /** An empty decode state — the start of a sequence. */
434
+ newDecodeState() {
435
+ return {
436
+ convWindow: Array.from({ length: this.config.numLayers }, () => []),
437
+ length: 0,
438
+ lastLogits: null,
439
+ };
440
+ }
441
+ /** Deep copy, so one prefix state can seed many independent continuations. */
442
+ cloneDecodeState(state) {
443
+ return {
444
+ convWindow: state.convWindow.map((w) => w.map((v) => Float32Array.from(v))),
445
+ length: state.length,
446
+ lastLogits: state.lastLogits ? Float32Array.from(state.lastLogits) : null,
447
+ };
448
+ }
449
+ /**
450
+ * Consume `tokens` from `state`, returning the per-position logits and the
451
+ * ADVANCED state. `state` is not mutated — pass {@link cloneDecodeState} output
452
+ * or a fresh state; this clones internally so the caller's prefix stays reusable.
453
+ *
454
+ * Numerically identical to {@link forward} over the concatenated sequence: the
455
+ * shared {@link _stepLayerPosition} is the only implementation of the block.
456
+ */
457
+ stepDecode(state, tokens) {
458
+ const next = this.cloneDecodeState(state);
459
+ const logits = [];
460
+ for (const tok of tokens) {
461
+ let x = this._embedOne(tok);
462
+ for (let l = 0; l < this.config.numLayers; l++) {
463
+ x = this._stepLayerPosition(l, x, next.convWindow[l]).out;
464
+ }
465
+ const lg = this._head([x])[0];
466
+ logits.push(lg);
467
+ next.lastLogits = lg;
468
+ next.length++;
469
+ }
470
+ return { logits, state: next };
471
+ }
472
+ /** Run a prompt once and keep the state, so continuations are cheap. */
473
+ prefixState(tokens) {
474
+ return this.stepDecode(this.newDecodeState(), tokens).state;
475
+ }
476
+ /**
477
+ * Mean log-probability the model assigns to `continuation` following a cached
478
+ * prefix — the tool-decision vote, without replaying the prompt.
479
+ *
480
+ * Returns `-Infinity` for an empty continuation, and requires a prefix with at
481
+ * least one position (there must be a position to predict the first
482
+ * continuation token FROM).
483
+ */
484
+ scoreContinuation(prefix, continuation) {
485
+ if (continuation.length === 0)
486
+ return -Infinity;
487
+ if (!prefix.lastLogits)
488
+ return -Infinity;
489
+ // The last prefix position predicts continuation[0]; each continuation
490
+ // position then predicts the next one, so the final one is never scored.
491
+ const { logits } = this.stepDecode(prefix, continuation.slice(0, -1));
492
+ let total = logProbOfToken(prefix.lastLogits, continuation[0]);
493
+ for (let i = 1; i < continuation.length; i++) {
494
+ total += logProbOfToken(logits[i - 1], continuation[i]);
495
+ }
496
+ return total / continuation.length;
497
+ }
338
498
  // ── Generation ───────────────────────────────────────────────────────────────
339
499
  /**
340
500
  * Text-level generation: encode the prompt, generate, decode. `codec` is any
@@ -345,20 +505,25 @@ export class EvermindLM {
345
505
  generateText(prompt, codec, opts) {
346
506
  return codec.decode(this.generate(codec.encode(prompt), opts));
347
507
  }
348
- /** Greedy / temperature-sampled autoregressive generation. Returns NEW token ids. */
508
+ /**
509
+ * Greedy / temperature-sampled autoregressive generation. Returns NEW token ids.
510
+ *
511
+ * Carries an incremental decode state, so the prompt is processed ONCE and each
512
+ * new token costs one position. The previous implementation re-ran the whole
513
+ * sequence per token, making generation quadratic in the prompt length.
514
+ */
349
515
  generate(prompt, opts) {
350
516
  const temperature = opts.temperature ?? 0;
351
517
  const rng = temperature > 0 ? new SeededRng((opts.seed ?? 1) >>> 0 || 1) : null;
352
- const tokens = [...prompt];
518
+ let state = this.stepDecode(this.newDecodeState(), prompt.length > 0 ? prompt : [0]).state;
353
519
  const produced = [];
354
520
  for (let n = 0; n < opts.maxNewTokens; n++) {
355
- const { logits } = this.forward(tokens.length > 0 ? tokens : [0]);
356
- const last = logits[logits.length - 1];
521
+ const last = state.lastLogits;
357
522
  const next = rng ? sampleTemperature(last, temperature, rng) : argmax(last);
358
523
  produced.push(next);
359
- tokens.push(next);
360
524
  if (opts.stopToken !== undefined && next === opts.stopToken)
361
525
  break;
526
+ state = this.stepDecode(state, [next]).state;
362
527
  }
363
528
  return produced;
364
529
  }
@@ -652,4 +817,24 @@ function sampleTemperature(logits, temperature, rng) {
652
817
  }
653
818
  return probs.length - 1;
654
819
  }
820
+ /**
821
+ * Log-probability the model assigned to `id` at a position, from that position's
822
+ * raw logits. Log-softmax computed max-shifted so a long-tail logit cannot
823
+ * overflow `exp`.
824
+ */
825
+ export function logProbOfToken(row, id) {
826
+ let max = -Infinity;
827
+ for (let i = 0; i < row.length; i++) {
828
+ const v = row[i];
829
+ if (v > max)
830
+ max = v;
831
+ }
832
+ if (!Number.isFinite(max))
833
+ return -Infinity;
834
+ let sum = 0;
835
+ for (let i = 0; i < row.length; i++)
836
+ sum += Math.exp(row[i] - max);
837
+ const logit = id >= 0 && id < row.length ? row[id] : -Infinity;
838
+ return logit - (max + Math.log(sum));
839
+ }
655
840
  //# sourceMappingURL=evermind_lm.js.map