@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
@@ -0,0 +1,421 @@
1
+ /**
2
+ * mamba1_cpu.ts — exact CPU forward + backward for a Mamba-1 (S6) block.
3
+ *
4
+ * This is the REFERENCE implementation of the same maths the WGSL kernels run
5
+ * (`rmsnorm_forward`, `linear_forward`, `conv1d_forward`, `silu_forward`,
6
+ * `forward_scan` + `forward_reduce`). It exists so real per-parameter gradients
7
+ * can be computed — and, crucially, PROVEN correct by finite differences —
8
+ * without a WebGPU device. {@link MambaTrainer} consumes it as its gradient
9
+ * engine: the optimizer (AdamW + trust region + NaN guard) stays on the GPU,
10
+ * the gradients are exact.
11
+ *
12
+ * Every formula below is a line-for-line mirror of the corresponding kernel; if
13
+ * a kernel changes, this file changes with it and `tests/backprop.test.ts`
14
+ * catches the drift (the gradient check fails the moment forward and backward
15
+ * disagree).
16
+ *
17
+ * Layout conventions (identical to the GPU buffers):
18
+ * x (M, dModel) M = batch * seqLen, row r = b * seqLen + t
19
+ * wInProj (2*dInner, dModel) row-major, Y = X @ W^T + b
20
+ * wConv (dInner, dConv) depthwise, causal
21
+ * wXProj (dtRank + 2*dState, dInner)
22
+ * wDtProj (dInner, dtRank)
23
+ * A_log (dInner, dState)
24
+ * wOutProj (dModel, dInner)
25
+ * hCache (B, L, dInner, dState)
26
+ */
27
+
28
+ import {
29
+ linearForward,
30
+ linearBackward,
31
+ rmsNormForward,
32
+ rmsNormBackward,
33
+ silu,
34
+ siluGrad,
35
+ softplus,
36
+ sigmoid,
37
+ RMSNORM_EPS,
38
+ } from './cpu_ops.js';
39
+
40
+ /** The 13 learnable tensors of a Mamba-1 block, keyed by the SAME names
41
+ * {@link Mamba1Block.parameters} publishes. */
42
+ export interface Mamba1CpuWeights {
43
+ wInProj : Float32Array;
44
+ bInProj : Float32Array;
45
+ wConv : Float32Array;
46
+ bConv : Float32Array;
47
+ wXProj : Float32Array;
48
+ bXProj : Float32Array;
49
+ wDtProj : Float32Array;
50
+ bDtProj : Float32Array;
51
+ A_log : Float32Array;
52
+ D_vec : Float32Array;
53
+ wOutProj : Float32Array;
54
+ bOutProj : Float32Array;
55
+ normWeight: Float32Array;
56
+ }
57
+
58
+ /** Parameter names of a Mamba-1 block, in `parameters()` order. */
59
+ export const MAMBA1_PARAM_NAMES = [
60
+ 'wInProj', 'bInProj', 'wConv', 'bConv', 'wXProj', 'bXProj',
61
+ 'wDtProj', 'bDtProj', 'A_log', 'D_vec', 'wOutProj', 'bOutProj', 'normWeight',
62
+ ] as const satisfies ReadonlyArray<keyof Mamba1CpuWeights>;
63
+
64
+ export interface Mamba1CpuDims {
65
+ dModel : number;
66
+ dState : number;
67
+ dConv : number;
68
+ dInner : number;
69
+ dtRank : number;
70
+ batch : number;
71
+ seqLen : number;
72
+ }
73
+
74
+ /** Everything the backward pass needs from the forward pass. */
75
+ export interface Mamba1CpuCache {
76
+ x : Float32Array; // (M, dModel) block input (also the residual)
77
+ normInv : Float32Array; // (M,)
78
+ normOut : Float32Array; // (M, dModel)
79
+ xConvIn : Float32Array; // (M, D)
80
+ z : Float32Array; // (M, D)
81
+ convOut : Float32Array; // (M, D)
82
+ u : Float32Array; // (M, D) = SiLU(convOut)
83
+ dtRaw : Float32Array; // (M, R)
84
+ bRaw : Float32Array; // (M, N)
85
+ cRaw : Float32Array; // (M, N)
86
+ deltaFull: Float32Array; // (M, D)
87
+ dv : Float32Array; // (M, D) = softplus(deltaFull)
88
+ aCont : Float32Array; // (D, N)
89
+ aBar : Float32Array; // (M, D, N)
90
+ bBar : Float32Array; // (M, D, N)
91
+ h : Float32Array; // (M, D, N)
92
+ scanY : Float32Array; // (M, D)
93
+ siluZ : Float32Array; // (M, D)
94
+ gated : Float32Array; // (M, D)
95
+ }
96
+
97
+ export interface Mamba1CpuForwardResult {
98
+ output: Float32Array; // (M, dModel)
99
+ cache : Mamba1CpuCache;
100
+ }
101
+
102
+ /** Gradients for every Mamba-1 parameter, same keys as {@link Mamba1CpuWeights}. */
103
+ export type Mamba1CpuGrads = Record<keyof Mamba1CpuWeights, Float32Array>;
104
+
105
+ /**
106
+ * Clamp bounds on `A_log` applied by `discretise_A`/`discretise_B` in
107
+ * `SELECTIVE_SCAN_FORWARD_WGSL`. Outside this range the kernel's output is
108
+ * constant in `A_log`, so the gradient is exactly zero — mirrored here so the
109
+ * analytic gradient matches the function the model actually computes.
110
+ */
111
+ export const A_LOG_CLAMP_LO = -10;
112
+ export const A_LOG_CLAMP_HI = 5;
113
+
114
+ /** Allocate a zeroed gradient set matching `dims`. */
115
+ export function zeroMamba1Grads(dims: Mamba1CpuDims): Mamba1CpuGrads {
116
+ const { dModel, dState: N, dConv: K, dInner: D, dtRank: R } = dims;
117
+ return {
118
+ wInProj : new Float32Array(2 * D * dModel),
119
+ bInProj : new Float32Array(2 * D),
120
+ wConv : new Float32Array(D * K),
121
+ bConv : new Float32Array(D),
122
+ wXProj : new Float32Array((R + 2 * N) * D),
123
+ bXProj : new Float32Array(R + 2 * N),
124
+ wDtProj : new Float32Array(D * R),
125
+ bDtProj : new Float32Array(D),
126
+ A_log : new Float32Array(D * N),
127
+ D_vec : new Float32Array(D),
128
+ wOutProj : new Float32Array(dModel * D),
129
+ bOutProj : new Float32Array(dModel),
130
+ normWeight: new Float32Array(dModel),
131
+ };
132
+ }
133
+
134
+ /**
135
+ * Exact CPU forward for one Mamba-1 block.
136
+ *
137
+ * Mirrors `Mamba1Block.forward` step for step:
138
+ * RMSNorm → in_proj → split(x, z) → causal conv1d → SiLU → x_proj(Δ, B, C)
139
+ * → dt_proj → selective scan → gate by SiLU(z) → out_proj → residual add.
140
+ */
141
+ export function mamba1CpuForward(
142
+ x: Float32Array,
143
+ w: Mamba1CpuWeights,
144
+ dims: Mamba1CpuDims,
145
+ ): Mamba1CpuForwardResult {
146
+ const { dModel, dState: N, dConv: K, dInner: D, dtRank: R, batch: B, seqLen: L } = dims;
147
+ const M = B * L;
148
+ const P = R + 2 * N;
149
+
150
+ // 1. Pre-block RMSNorm.
151
+ const normOut = new Float32Array(M * dModel);
152
+ const normInv = new Float32Array(M);
153
+ rmsNormForward(x, w.normWeight, M, dModel, normOut, normInv);
154
+
155
+ // 2. Input projection → (x, z).
156
+ const inProj = new Float32Array(M * 2 * D);
157
+ linearForward(normOut, w.wInProj, w.bInProj, M, dModel, 2 * D, inProj);
158
+
159
+ // 3. COLUMN split: x = inProj[:, 0:D], z = inProj[:, D:2D].
160
+ const xConvIn = new Float32Array(M * D);
161
+ const z = new Float32Array(M * D);
162
+ for (let r = 0; r < M; r++) {
163
+ for (let d = 0; d < D; d++) {
164
+ xConvIn[r * D + d] = inProj[r * 2 * D + d]!;
165
+ z[r * D + d] = inProj[r * 2 * D + D + d]!;
166
+ }
167
+ }
168
+
169
+ // 4. Depthwise causal conv1d over the sequence axis.
170
+ const convOut = new Float32Array(M * D);
171
+ for (let b = 0; b < B; b++) {
172
+ for (let t = 0; t < L; t++) {
173
+ const base = (b * L + t) * D;
174
+ for (let d = 0; d < D; d++) {
175
+ let acc = w.bConv[d]!;
176
+ for (let k = 0; k < K; k++) {
177
+ if (t >= k) acc += w.wConv[d * K + k]! * xConvIn[(b * L + t - k) * D + d]!;
178
+ }
179
+ convOut[base + d] = acc;
180
+ }
181
+ }
182
+ }
183
+
184
+ // 5. SiLU.
185
+ const u = new Float32Array(M * D);
186
+ for (let i = 0; i < M * D; i++) u[i] = silu(convOut[i]!);
187
+
188
+ // 6. x_proj → Δ_raw, B, C (COLUMN slices of the (M, R+2N) projection).
189
+ const xProj = new Float32Array(M * P);
190
+ linearForward(u, w.wXProj, w.bXProj, M, D, P, xProj);
191
+ const dtRaw = new Float32Array(M * R);
192
+ const bRaw = new Float32Array(M * N);
193
+ const cRaw = new Float32Array(M * N);
194
+ for (let r = 0; r < M; r++) {
195
+ for (let i = 0; i < R; i++) dtRaw[r * R + i] = xProj[r * P + i]!;
196
+ for (let n = 0; n < N; n++) {
197
+ bRaw[r * N + n] = xProj[r * P + R + n]!;
198
+ cRaw[r * N + n] = xProj[r * P + R + N + n]!;
199
+ }
200
+ }
201
+
202
+ // 7. dt_proj: expand Δ to the inner dimension.
203
+ const deltaFull = new Float32Array(M * D);
204
+ linearForward(dtRaw, w.wDtProj, w.bDtProj, M, R, D, deltaFull);
205
+
206
+ // 8. Selective scan (S6). ZOH discretisation, exactly as the kernel does it.
207
+ const dv = new Float32Array(M * D);
208
+ for (let i = 0; i < M * D; i++) dv[i] = softplus(deltaFull[i]!);
209
+
210
+ const aCont = new Float32Array(D * N);
211
+ for (let i = 0; i < D * N; i++) {
212
+ aCont[i] = -Math.exp(Math.min(A_LOG_CLAMP_HI, Math.max(A_LOG_CLAMP_LO, w.A_log[i]!)));
213
+ }
214
+
215
+ const aBar = new Float32Array(M * D * N);
216
+ const bBar = new Float32Array(M * D * N);
217
+ const h = new Float32Array(M * D * N);
218
+ const scanY = new Float32Array(M * D);
219
+
220
+ for (let b = 0; b < B; b++) {
221
+ for (let t = 0; t < L; t++) {
222
+ const r = b * L + t;
223
+ for (let d = 0; d < D; d++) {
224
+ const dvv = dv[r * D + d]!;
225
+ const uv = u[r * D + d]!;
226
+ let acc = w.D_vec[d]! * uv;
227
+ for (let n = 0; n < N; n++) {
228
+ const ac = aCont[d * N + n]!;
229
+ const ab = Math.exp(dvv * ac);
230
+ const bb = ((ab - 1) / ac) * bRaw[r * N + n]!;
231
+ const idx = (r * D + d) * N + n;
232
+ aBar[idx] = ab;
233
+ bBar[idx] = bb;
234
+ const hPrev = t === 0 ? 0 : h[((r - 1) * D + d) * N + n]!;
235
+ const hv = ab * hPrev + bb * uv;
236
+ h[idx] = hv;
237
+ acc += cRaw[r * N + n]! * hv;
238
+ }
239
+ scanY[r * D + d] = acc;
240
+ }
241
+ }
242
+ }
243
+
244
+ // 9. Gate: y ⊗ SiLU(z).
245
+ const siluZ = new Float32Array(M * D);
246
+ const gated = new Float32Array(M * D);
247
+ for (let i = 0; i < M * D; i++) {
248
+ siluZ[i] = silu(z[i]!);
249
+ gated[i] = scanY[i]! * siluZ[i]!;
250
+ }
251
+
252
+ // 10 + 11. Output projection, then the residual add.
253
+ const output = new Float32Array(M * dModel);
254
+ linearForward(gated, w.wOutProj, w.bOutProj, M, D, dModel, output);
255
+ for (let i = 0; i < M * dModel; i++) output[i] = output[i]! + x[i]!;
256
+
257
+ return {
258
+ output,
259
+ cache: { x, normInv, normOut, xConvIn, z, convOut, u, dtRaw, bRaw, cRaw, deltaFull, dv, aCont, aBar, bBar, h, scanY, siluZ, gated },
260
+ };
261
+ }
262
+
263
+ /**
264
+ * Exact CPU backward for one Mamba-1 block.
265
+ *
266
+ * @param dOut dL/d(block output), (M, dModel)
267
+ * @param grads accumulator — parameter gradients are ADDED into it
268
+ * @returns dL/d(block input), (M, dModel)
269
+ */
270
+ export function mamba1CpuBackward(
271
+ dOut: Float32Array,
272
+ w: Mamba1CpuWeights,
273
+ cache: Mamba1CpuCache,
274
+ dims: Mamba1CpuDims,
275
+ grads: Mamba1CpuGrads,
276
+ ): Float32Array {
277
+ const { dModel, dState: N, dConv: K, dInner: D, dtRank: R, batch: B, seqLen: L } = dims;
278
+ const M = B * L;
279
+ const P = R + 2 * N;
280
+
281
+ // 11. Residual: the block input receives dOut directly, plus whatever flows
282
+ // back through the mixer branch (accumulated at the very end).
283
+ const dx = new Float32Array(M * dModel);
284
+ dx.set(dOut);
285
+
286
+ // 10. out_proj.
287
+ const dGated = new Float32Array(M * D);
288
+ linearBackward(dOut, cache.gated, w.wOutProj, M, D, dModel, dGated, grads.wOutProj, grads.bOutProj);
289
+
290
+ // 9. Gate: gated = scanY * SiLU(z).
291
+ const dScanY = new Float32Array(M * D);
292
+ const dz = new Float32Array(M * D);
293
+ for (let i = 0; i < M * D; i++) {
294
+ dScanY[i] = dGated[i]! * cache.siluZ[i]!;
295
+ dz[i] = dGated[i]! * cache.scanY[i]! * siluGrad(cache.z[i]!);
296
+ }
297
+
298
+ // 8. Selective scan, walked backwards in time.
299
+ const du = new Float32Array(M * D);
300
+ const dBRaw = new Float32Array(M * N);
301
+ const dCRaw = new Float32Array(M * N);
302
+ const dDv = new Float32Array(M * D); // dL/d softplus(Δ)
303
+ const dACont = new Float32Array(D * N);
304
+
305
+ // dhNext[d*N+n] carries dL/dh_t propagated from step t+1 for the current batch row.
306
+ const dhNext = new Float32Array(D * N);
307
+ for (let b = 0; b < B; b++) {
308
+ dhNext.fill(0);
309
+ for (let t = L - 1; t >= 0; t--) {
310
+ const r = b * L + t;
311
+ for (let d = 0; d < D; d++) {
312
+ const dY = dScanY[r * D + d]!;
313
+ const uv = cache.u[r * D + d]!;
314
+ const dvv = cache.dv[r * D + d]!;
315
+ // Skip connection: y_t[d] += D_vec[d] * u_t[d].
316
+ grads.D_vec[d] = grads.D_vec[d]! + dY * uv;
317
+ du[r * D + d] = du[r * D + d]! + dY * w.D_vec[d]!;
318
+
319
+ let ddvAcc = 0;
320
+ for (let n = 0; n < N; n++) {
321
+ const idx = (r * D + d) * N + n;
322
+ const hv = cache.h[idx]!;
323
+ const ab = cache.aBar[idx]!;
324
+ const bb = cache.bBar[idx]!;
325
+ const ac = cache.aCont[d * N + n]!;
326
+ const cv = cache.cRaw[r * N + n]!;
327
+
328
+ // y_t[d] = Σ_n C_t[n] * h_t[d,n]
329
+ dCRaw[r * N + n] = dCRaw[r * N + n]! + dY * hv;
330
+ const dh = dY * cv + dhNext[d * N + n]!;
331
+
332
+ // h_t = a_bar * h_{t-1} + b_bar * u_t
333
+ const hPrev = t === 0 ? 0 : cache.h[((r - 1) * D + d) * N + n]!;
334
+ const dABar = dh * hPrev;
335
+ const dBBar = dh * uv;
336
+ du[r * D + d] = du[r * D + d]! + dh * bb;
337
+ dhNext[d * N + n] = dh * ab; // flows to step t-1
338
+
339
+ // a_bar = exp(dv * a_cont); b_bar = (a_bar - 1) / a_cont * B_raw
340
+ const bv = cache.bRaw[r * N + n]!;
341
+ ddvAcc += dABar * ac * ab + dBBar * ab * bv;
342
+ dACont[d * N + n] = dACont[d * N + n]!
343
+ + dABar * dvv * ab
344
+ + dBBar * bv * ((dvv * ab * ac - (ab - 1)) / (ac * ac));
345
+ dBRaw[r * N + n] = dBRaw[r * N + n]! + dBBar * ((ab - 1) / ac);
346
+ }
347
+ dDv[r * D + d] = dDv[r * D + d]! + ddvAcc;
348
+ }
349
+ }
350
+ }
351
+
352
+ // a_cont = -exp(clamp(A_log)) ⇒ d a_cont / d A_log = a_cont inside the clamp,
353
+ // and exactly 0 outside it (the kernel's output is constant there).
354
+ for (let i = 0; i < D * N; i++) {
355
+ const al = w.A_log[i]!;
356
+ if (al <= A_LOG_CLAMP_LO || al >= A_LOG_CLAMP_HI) continue;
357
+ grads.A_log[i] = grads.A_log[i]! + dACont[i]! * cache.aCont[i]!;
358
+ }
359
+
360
+ // softplus: dv = softplus(deltaFull)
361
+ const dDeltaFull = new Float32Array(M * D);
362
+ for (let i = 0; i < M * D; i++) dDeltaFull[i] = dDv[i]! * sigmoid(cache.deltaFull[i]!);
363
+
364
+ // 7. dt_proj.
365
+ const dDtRaw = new Float32Array(M * R);
366
+ linearBackward(dDeltaFull, cache.dtRaw, w.wDtProj, M, R, D, dDtRaw, grads.wDtProj, grads.bDtProj);
367
+
368
+ // 6. x_proj — re-assemble the (M, P) column slices, then one linear backward.
369
+ const dXProj = new Float32Array(M * P);
370
+ for (let r = 0; r < M; r++) {
371
+ for (let i = 0; i < R; i++) dXProj[r * P + i] = dDtRaw[r * R + i]!;
372
+ for (let n = 0; n < N; n++) {
373
+ dXProj[r * P + R + n] = dBRaw[r * N + n]!;
374
+ dXProj[r * P + R + N + n] = dCRaw[r * N + n]!;
375
+ }
376
+ }
377
+ const duFromXProj = new Float32Array(M * D);
378
+ linearBackward(dXProj, cache.u, w.wXProj, M, D, P, duFromXProj, grads.wXProj, grads.bXProj);
379
+ for (let i = 0; i < M * D; i++) du[i] = du[i]! + duFromXProj[i]!;
380
+
381
+ // 5. SiLU.
382
+ const dConvOut = new Float32Array(M * D);
383
+ for (let i = 0; i < M * D; i++) dConvOut[i] = du[i]! * siluGrad(cache.convOut[i]!);
384
+
385
+ // 4. Depthwise causal conv1d.
386
+ const dXConvIn = new Float32Array(M * D);
387
+ for (let b = 0; b < B; b++) {
388
+ for (let t = 0; t < L; t++) {
389
+ const r = b * L + t;
390
+ for (let d = 0; d < D; d++) {
391
+ const g = dConvOut[r * D + d]!;
392
+ if (g === 0) continue;
393
+ grads.bConv[d] = grads.bConv[d]! + g;
394
+ for (let k = 0; k < K; k++) {
395
+ if (t < k) continue;
396
+ const src = (b * L + t - k) * D + d;
397
+ grads.wConv[d * K + k] = grads.wConv[d * K + k]! + g * cache.xConvIn[src]!;
398
+ dXConvIn[src] = dXConvIn[src]! + g * w.wConv[d * K + k]!;
399
+ }
400
+ }
401
+ }
402
+ }
403
+
404
+ // 3. Un-split the (x, z) columns.
405
+ const dInProj = new Float32Array(M * 2 * D);
406
+ for (let r = 0; r < M; r++) {
407
+ for (let d = 0; d < D; d++) {
408
+ dInProj[r * 2 * D + d] = dXConvIn[r * D + d]!;
409
+ dInProj[r * 2 * D + D + d] = dz[r * D + d]!;
410
+ }
411
+ }
412
+
413
+ // 2. in_proj.
414
+ const dNormOut = new Float32Array(M * dModel);
415
+ linearBackward(dInProj, cache.normOut, w.wInProj, M, dModel, 2 * D, dNormOut, grads.wInProj, grads.bInProj);
416
+
417
+ // 1. RMSNorm — adds into the residual path already sitting in `dx`.
418
+ rmsNormBackward(dNormOut, cache.x, w.normWeight, cache.normInv, M, dModel, dx, grads.normWeight, RMSNORM_EPS);
419
+
420
+ return dx;
421
+ }
@@ -0,0 +1,119 @@
1
+ /**
2
+ * mixed_precision.ts — mixed-precision training discipline (AMP) for the CPU
3
+ * reference, faithful to the cookbook's recipe:
4
+ *
5
+ * • fp16 compute, fp32 MASTER weights. The model's Float32Array params ARE the
6
+ * master copy; the forward runs on an fp16-rounded view, so tiny weight
7
+ * updates still accumulate in fp32 instead of vanishing.
8
+ * • Dynamic LOSS SCALING. fp16 gradients underflow to zero below ~6e-5; scaling
9
+ * the loss by a large factor S lifts them into fp16 range, then we unscale by
10
+ * S before the optimiser step. On overflow (inf/nan) we skip the step and back
11
+ * the scale off; after a clean streak we grow it. This is the mechanism that
12
+ * makes fp16 training numerically stable.
13
+ *
14
+ * The payoff is memory/throughput: activations and gradients at half precision.
15
+ * On CPU we can't reclaim the bytes, so this module models the NUMERICS exactly
16
+ * (fp16 rounding + loss scaling) — the property the WGSL path will inherit — and
17
+ * its tests prove a small gradient survives WITH scaling and underflows WITHOUT.
18
+ */
19
+
20
+ import { floatToFp16, fp16ToFloat } from "../utils/quantization.js";
21
+
22
+ /** Round a value to what fp16 storage would preserve (half-precision simulation). */
23
+ export function roundFp16(x: number): number {
24
+ return fp16ToFloat(floatToFp16(x));
25
+ }
26
+
27
+ /** Round every element of a copy to fp16 precision (leaves the input untouched). */
28
+ export function fp16View(a: Float32Array): Float32Array {
29
+ const out = new Float32Array(a.length);
30
+ for (let i = 0; i < a.length; i++) out[i] = roundFp16(a[i]!);
31
+ return out;
32
+ }
33
+
34
+ export interface LossScalerOptions {
35
+ /** Initial scale. Default 2^14 = 16384. */
36
+ initScale?: number;
37
+ /** Multiply the scale by this after `growthInterval` clean steps. Default 2. */
38
+ growthFactor?: number;
39
+ /** Multiply the scale by this on overflow. Default 0.5. */
40
+ backoffFactor?: number;
41
+ /** Clean steps between growth attempts. Default 200. */
42
+ growthInterval?: number;
43
+ /** Floor for the scale so it can't collapse to zero. Default 1. */
44
+ minScale?: number;
45
+ }
46
+
47
+ /**
48
+ * Dynamic loss scaler — the standard AMP controller. Scale the loss/grads up
49
+ * before backward, {@link check} them for overflow, {@link unscale} the good ones,
50
+ * and {@link update} the scale based on whether this step overflowed.
51
+ */
52
+ export class DynamicLossScaler {
53
+ private _scale: number;
54
+ private readonly growthFactor: number;
55
+ private readonly backoffFactor: number;
56
+ private readonly growthInterval: number;
57
+ private readonly minScale: number;
58
+ private cleanStreak = 0;
59
+ private _overflows = 0;
60
+ private _skipped = 0;
61
+
62
+ constructor(opts: LossScalerOptions = {}) {
63
+ this._scale = opts.initScale ?? 16384;
64
+ this.growthFactor = opts.growthFactor ?? 2;
65
+ this.backoffFactor = opts.backoffFactor ?? 0.5;
66
+ this.growthInterval = opts.growthInterval ?? 200;
67
+ this.minScale = opts.minScale ?? 1;
68
+ }
69
+
70
+ get scale(): number {
71
+ return this._scale;
72
+ }
73
+ get overflows(): number {
74
+ return this._overflows;
75
+ }
76
+ get skipped(): number {
77
+ return this._skipped;
78
+ }
79
+
80
+ /** True if any gradient is inf/nan (an fp16 overflow) — such a step must be skipped. */
81
+ check(grads: { data: Float32Array }[]): boolean {
82
+ for (const g of grads) {
83
+ const a = g.data;
84
+ for (let i = 0; i < a.length; i++) {
85
+ const x = a[i]!;
86
+ if (!Number.isFinite(x)) return true;
87
+ }
88
+ }
89
+ return false;
90
+ }
91
+
92
+ /** Divide gradients back down by the current scale, in place. */
93
+ unscale(grads: { data: Float32Array }[]): void {
94
+ const inv = 1 / this._scale;
95
+ for (const g of grads) {
96
+ const a = g.data;
97
+ for (let i = 0; i < a.length; i++) a[i] = a[i]! * inv;
98
+ }
99
+ }
100
+
101
+ /**
102
+ * Advance the scale controller. Pass whether this step overflowed. Returns
103
+ * `true` if the optimiser step should PROCEED, `false` if it must be skipped.
104
+ */
105
+ update(overflow: boolean): boolean {
106
+ if (overflow) {
107
+ this._overflows++;
108
+ this._skipped++;
109
+ this._scale = Math.max(this.minScale, this._scale * this.backoffFactor);
110
+ this.cleanStreak = 0;
111
+ return false;
112
+ }
113
+ if (++this.cleanStreak >= this.growthInterval) {
114
+ this._scale *= this.growthFactor;
115
+ this.cleanStreak = 0;
116
+ }
117
+ return true;
118
+ }
119
+ }