@seanhogg/builderforce-memory-engine 2026.7.14 → 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 (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 +33 -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 +98 -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
@@ -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
+ }