@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,360 @@
1
+ /**
2
+ * model_cpu.ts — the exact CPU forward + backward for a whole
3
+ * {@link HybridMambaModel}: token embedding → layer stack → final RMSNorm →
4
+ * tied LM head → cross-entropy.
5
+ *
6
+ * This is the gradient engine {@link MambaTrainer} uses. Before it existed the
7
+ * trainer handed the SAME `dLogits` buffer to AdamW for every parameter, so no
8
+ * parameter ever received its own gradient — `learn()` was safe but inert. Now
9
+ * the backward pass is real, and `tests/backprop.test.ts` proves it against
10
+ * finite differences without needing a GPU.
11
+ *
12
+ * Parameter names match {@link HybridMambaModel.parameters} exactly
13
+ * (`embedding`, `layer{i}.{name}`, `final_norm`), which is how the trainer
14
+ * pairs each parameter with its OWN gradient.
15
+ */
16
+
17
+ import {
18
+ linearForward,
19
+ rmsNormForward,
20
+ rmsNormBackward,
21
+ RMSNORM_EPS,
22
+ } from './cpu_ops.js';
23
+ import {
24
+ mamba1CpuForward,
25
+ mamba1CpuBackward,
26
+ zeroMamba1Grads,
27
+ MAMBA1_PARAM_NAMES,
28
+ type Mamba1CpuWeights,
29
+ type Mamba1CpuCache,
30
+ type Mamba1CpuDims,
31
+ type Mamba1CpuGrads,
32
+ } from './mamba1_cpu.js';
33
+ import { crossEntropyLoss, crossEntropyGrad } from './autograd.js';
34
+
35
+ /** Layer types the CPU gradient engine can differentiate today. */
36
+ export const CPU_GRADIENT_LAYER_TYPES = ['mamba1'] as const;
37
+ export type CpuGradientLayerType = (typeof CPU_GRADIENT_LAYER_TYPES)[number];
38
+
39
+ export interface CpuModelDims {
40
+ vocabSize: number;
41
+ dModel : number;
42
+ dState : number;
43
+ dConv : number;
44
+ dInner : number;
45
+ dtRank : number;
46
+ numLayers: number;
47
+ }
48
+
49
+ /** Every tensor the CPU model reads, mirrored out of the GPU buffers. */
50
+ export interface CpuModelWeights {
51
+ embedding : Float32Array; // (vocabSize, dModel) — tied with the LM head
52
+ finalNorm : Float32Array; // (dModel,)
53
+ lmHeadBias : Float32Array; // (vocabSize,)
54
+ layers : Mamba1CpuWeights[];
55
+ }
56
+
57
+ export interface CpuModelGrads {
58
+ embedding : Float32Array;
59
+ finalNorm : Float32Array;
60
+ lmHeadBias : Float32Array;
61
+ layers : Mamba1CpuGrads[];
62
+ }
63
+
64
+ /** Knobs on one backward pass. */
65
+ export interface CpuBackwardOptions {
66
+ /**
67
+ * Recompute each layer's activations during the backward pass instead of
68
+ * retaining every layer's cache from the forward. Trades one extra forward per
69
+ * layer for O(1) instead of O(layers) retained activations; the gradients are
70
+ * numerically identical (`tests/peft_trainer.test.ts` asserts bit equality).
71
+ */
72
+ activationCheckpointing?: boolean;
73
+ }
74
+
75
+ /** Loss + the gradient of every parameter, keyed by `parameters()` name. */
76
+ export interface CpuBackwardResult {
77
+ loss : number;
78
+ grads: Map<string, Float32Array>;
79
+ }
80
+
81
+ export function zeroCpuModelGrads(dims: CpuModelDims): CpuModelGrads {
82
+ const blockDims: Mamba1CpuDims = {
83
+ dModel: dims.dModel, dState: dims.dState, dConv: dims.dConv,
84
+ dInner: dims.dInner, dtRank: dims.dtRank, batch: 1, seqLen: 1,
85
+ };
86
+ return {
87
+ embedding : new Float32Array(dims.vocabSize * dims.dModel),
88
+ finalNorm : new Float32Array(dims.dModel),
89
+ lmHeadBias: new Float32Array(dims.vocabSize),
90
+ layers : Array.from({ length: dims.numLayers }, () => zeroMamba1Grads(blockDims)),
91
+ };
92
+ }
93
+
94
+ /** Flatten a {@link CpuModelGrads} into the `parameters()` naming the optimizer uses. */
95
+ export function toNamedGrads(g: CpuModelGrads): Map<string, Float32Array> {
96
+ const out = new Map<string, Float32Array>();
97
+ out.set('embedding', g.embedding);
98
+ for (let i = 0; i < g.layers.length; i++) {
99
+ const lg = g.layers[i]!;
100
+ for (const name of MAMBA1_PARAM_NAMES) out.set(`layer${i}.${name}`, lg[name]);
101
+ }
102
+ out.set('final_norm', g.finalNorm);
103
+ out.set('lm_head_bias', g.lmHeadBias);
104
+ return out;
105
+ }
106
+
107
+ /** The model's tensors keyed the same way — the inverse of {@link cpuWeightsFromNamed}. */
108
+ export function namedWeights(w: CpuModelWeights): Map<string, Float32Array> {
109
+ const out = new Map<string, Float32Array>();
110
+ out.set('embedding', w.embedding);
111
+ for (let i = 0; i < w.layers.length; i++) {
112
+ const lw = w.layers[i]!;
113
+ for (const name of MAMBA1_PARAM_NAMES) out.set(`layer${i}.${name}`, lw[name]);
114
+ }
115
+ out.set('final_norm', w.finalNorm);
116
+ out.set('lm_head_bias', w.lmHeadBias);
117
+ return out;
118
+ }
119
+
120
+ /**
121
+ * Assemble a {@link CpuModelWeights} from tensors keyed by `parameters()` name —
122
+ * how {@link MambaTrainer} mirrors the GPU buffers into the gradient engine.
123
+ * Throws on a missing or wrongly-sized tensor rather than training on garbage.
124
+ */
125
+ export function cpuWeightsFromNamed(named: Map<string, Float32Array>, dims: CpuModelDims): CpuModelWeights {
126
+ const take = (key: string, expected: number): Float32Array => {
127
+ const t = named.get(key);
128
+ if (!t) throw new Error(`cpuWeightsFromNamed: missing tensor "${key}"`);
129
+ if (t.length !== expected) {
130
+ throw new Error(`cpuWeightsFromNamed: "${key}" has ${t.length} elements, expected ${expected}`);
131
+ }
132
+ return t;
133
+ };
134
+ const { dModel, dState: N, dConv: K, dInner: D, dtRank: R } = dims;
135
+ const sizes: Record<keyof Mamba1CpuWeights, number> = {
136
+ wInProj: 2 * D * dModel, bInProj: 2 * D, wConv: D * K, bConv: D,
137
+ wXProj: (R + 2 * N) * D, bXProj: R + 2 * N, wDtProj: D * R, bDtProj: D,
138
+ A_log: D * N, D_vec: D, wOutProj: dModel * D, bOutProj: dModel, normWeight: dModel,
139
+ };
140
+ const layers: Mamba1CpuWeights[] = [];
141
+ for (let i = 0; i < dims.numLayers; i++) {
142
+ const lw = {} as Mamba1CpuWeights;
143
+ for (const name of MAMBA1_PARAM_NAMES) lw[name] = take(`layer${i}.${name}`, sizes[name]);
144
+ layers.push(lw);
145
+ }
146
+ return {
147
+ embedding : take('embedding', dims.vocabSize * dModel),
148
+ finalNorm : take('final_norm', dModel),
149
+ lmHeadBias: take('lm_head_bias', dims.vocabSize),
150
+ layers,
151
+ };
152
+ }
153
+
154
+ interface ModelCache {
155
+ hiddenIn : Float32Array[]; // per layer: the input that layer saw
156
+ layer : Mamba1CpuCache[];
157
+ lastHidden: Float32Array; // (M, dModel) output of the final layer
158
+ normOut : Float32Array; // (M, dModel) after the final RMSNorm
159
+ normInv : Float32Array; // (M,)
160
+ logits : Float32Array; // (M, vocabSize)
161
+ }
162
+
163
+ function forwardInternal(
164
+ tokenIds: ArrayLike<number>,
165
+ w: CpuModelWeights,
166
+ dims: CpuModelDims,
167
+ batch: number,
168
+ seqLen: number,
169
+ /** Drop each layer's activation cache after use — see {@link CpuBackwardOptions}. */
170
+ checkpointed = false,
171
+ ): ModelCache {
172
+ const { vocabSize, dModel } = dims;
173
+ const M = batch * seqLen;
174
+
175
+ // Token embedding lookup.
176
+ let hidden: Float32Array = new Float32Array(M * dModel);
177
+ for (let r = 0; r < M; r++) {
178
+ const id = tokenIds[r]!;
179
+ if (id < 0 || id >= vocabSize) throw new Error(`token id ${id} out of range [0, ${vocabSize})`);
180
+ hidden.set(w.embedding.subarray(id * dModel, (id + 1) * dModel), r * dModel);
181
+ }
182
+
183
+ const blockDims: Mamba1CpuDims = {
184
+ dModel, dState: dims.dState, dConv: dims.dConv,
185
+ dInner: dims.dInner, dtRank: dims.dtRank, batch, seqLen,
186
+ };
187
+
188
+ const hiddenIn: Float32Array[] = [];
189
+ const layerCaches: Mamba1CpuCache[] = [];
190
+ for (let i = 0; i < dims.numLayers; i++) {
191
+ hiddenIn.push(hidden);
192
+ const { output, cache } = mamba1CpuForward(hidden, w.layers[i]!, blockDims);
193
+ // Under activation checkpointing only the layer INPUTS are retained; each
194
+ // layer's cache is recomputed from its input when its backward runs. Memory
195
+ // drops from O(layers) caches to one, at the cost of a second forward per
196
+ // layer — gradients are numerically identical either way.
197
+ if (!checkpointed) layerCaches.push(cache);
198
+ hidden = output;
199
+ }
200
+
201
+ // Final RMSNorm.
202
+ const normOut = new Float32Array(M * dModel);
203
+ const normInv = new Float32Array(M);
204
+ rmsNormForward(hidden, w.finalNorm, M, dModel, normOut, normInv, RMSNORM_EPS);
205
+
206
+ // Tied LM head: logits = normOut @ embedding^T + lmHeadBias.
207
+ const logits = new Float32Array(M * vocabSize);
208
+ linearForward(normOut, w.embedding, w.lmHeadBias, M, dModel, vocabSize, logits);
209
+
210
+ return { hiddenIn, layer: layerCaches, lastHidden: hidden, normOut, normInv, logits };
211
+ }
212
+
213
+ /** Logits only — the CPU mirror of `HybridMambaModel.forward`. */
214
+ export function cpuModelForward(
215
+ tokenIds: ArrayLike<number>,
216
+ w: CpuModelWeights,
217
+ dims: CpuModelDims,
218
+ batch: number,
219
+ seqLen: number,
220
+ ): Float32Array {
221
+ return forwardInternal(tokenIds, w, dims, batch, seqLen).logits;
222
+ }
223
+
224
+ /** Mean next-token cross-entropy over the sequence — the trainer's loss. */
225
+ export function cpuModelLoss(
226
+ tokenIds: ArrayLike<number>,
227
+ targets: ArrayLike<number>,
228
+ w: CpuModelWeights,
229
+ dims: CpuModelDims,
230
+ batch: number,
231
+ seqLen: number,
232
+ ): number {
233
+ const logits = cpuModelForward(tokenIds, w, dims, batch, seqLen);
234
+ const M = batch * seqLen;
235
+ const V = dims.vocabSize;
236
+ let total = 0;
237
+ for (let r = 0; r < M; r++) {
238
+ total += crossEntropyLoss(logits.subarray(r * V, (r + 1) * V), targets[r]!);
239
+ }
240
+ return total / M;
241
+ }
242
+
243
+ /**
244
+ * Forward + backward. Returns the mean cross-entropy loss and the gradient of
245
+ * EVERY parameter, keyed by its `parameters()` name.
246
+ *
247
+ * The loss is averaged over the `batch * seqLen` predicted positions, so the
248
+ * gradients are the gradients of that mean — the quantity the trainer reports.
249
+ */
250
+ export function cpuModelBackward(
251
+ tokenIds: ArrayLike<number>,
252
+ targets: ArrayLike<number>,
253
+ w: CpuModelWeights,
254
+ dims: CpuModelDims,
255
+ batch: number,
256
+ seqLen: number,
257
+ opts: CpuBackwardOptions = {},
258
+ ): CpuBackwardResult {
259
+ const { vocabSize: V, dModel } = dims;
260
+ const M = batch * seqLen;
261
+ const checkpointed = opts.activationCheckpointing ?? false;
262
+ const cache = forwardInternal(tokenIds, w, dims, batch, seqLen, checkpointed);
263
+ const g = zeroCpuModelGrads(dims);
264
+
265
+ // Cross-entropy over every position, averaged.
266
+ let total = 0;
267
+ const dLogits = new Float32Array(M * V);
268
+ for (let r = 0; r < M; r++) {
269
+ const slice = cache.logits.subarray(r * V, (r + 1) * V);
270
+ const target = targets[r]!;
271
+ total += crossEntropyLoss(slice, target);
272
+ const gr = crossEntropyGrad(slice, target);
273
+ for (let v = 0; v < V; v++) dLogits[r * V + v] = gr[v]! / M;
274
+ }
275
+ const loss = total / M;
276
+
277
+ // LM head (tied): logits = normOut @ embedding^T + bias.
278
+ // dNormOut = dLogits @ embedding ; dEmbedding += dLogits^T @ normOut.
279
+ const dNormOut = new Float32Array(M * dModel);
280
+ for (let r = 0; r < M; r++) {
281
+ const lo = r * V;
282
+ const no = r * dModel;
283
+ for (let v = 0; v < V; v++) {
284
+ const gl = dLogits[lo + v]!;
285
+ if (gl === 0) continue;
286
+ g.lmHeadBias[v] = g.lmHeadBias[v]! + gl;
287
+ const eo = v * dModel;
288
+ for (let i = 0; i < dModel; i++) {
289
+ dNormOut[no + i] = dNormOut[no + i]! + gl * w.embedding[eo + i]!;
290
+ g.embedding[eo + i] = g.embedding[eo + i]! + gl * cache.normOut[no + i]!;
291
+ }
292
+ }
293
+ }
294
+
295
+ // Final RMSNorm.
296
+ const dLastHidden = new Float32Array(M * dModel);
297
+ rmsNormBackward(dNormOut, cache.lastHidden, w.finalNorm, cache.normInv, M, dModel,
298
+ dLastHidden, g.finalNorm, RMSNORM_EPS);
299
+
300
+ // Layer stack, in reverse.
301
+ const blockDims: Mamba1CpuDims = {
302
+ dModel, dState: dims.dState, dConv: dims.dConv,
303
+ dInner: dims.dInner, dtRank: dims.dtRank, batch, seqLen,
304
+ };
305
+ let d: Float32Array = dLastHidden;
306
+ for (let i = dims.numLayers - 1; i >= 0; i--) {
307
+ const layerCache = checkpointed
308
+ ? mamba1CpuForward(cache.hiddenIn[i]!, w.layers[i]!, blockDims).cache
309
+ : cache.layer[i]!;
310
+ d = mamba1CpuBackward(d, w.layers[i]!, layerCache, blockDims, g.layers[i]!);
311
+ }
312
+
313
+ // Token embedding — the SAME table the LM head reads, so this accumulates
314
+ // on top of the head's contribution (the tie is why both terms are needed).
315
+ for (let r = 0; r < M; r++) {
316
+ const id = tokenIds[r]!;
317
+ const eo = id * dModel;
318
+ const ho = r * dModel;
319
+ for (let i = 0; i < dModel; i++) g.embedding[eo + i] = g.embedding[eo + i]! + d[ho + i]!;
320
+ }
321
+
322
+ return { loss, grads: toNamedGrads(g) };
323
+ }
324
+
325
+ // ── Parameter shapes (for PEFT) ──────────────────────────────────────────────
326
+
327
+ /**
328
+ * The 2-D shape of a parameter, or `null` when it is a vector (bias / norm gain
329
+ * / `D_vec`).
330
+ *
331
+ * Low-rank adaptation only means anything over a matrix, so this is the single
332
+ * place that says which parameters an adapter can cover — and it derives the
333
+ * shapes from `dims` rather than restating the block's layout, so it cannot
334
+ * drift from {@link cpuWeightsFromNamed}.
335
+ */
336
+ export function paramMatrixShape(name: string, dims: CpuModelDims): [rows: number, cols: number] | null {
337
+ const { dModel, dState: N, dConv: K, dInner: D, dtRank: R } = dims;
338
+ if (name === 'embedding') return [dims.vocabSize, dModel];
339
+ const local = name.startsWith('layer') ? name.slice(name.indexOf('.') + 1) : name;
340
+ switch (local) {
341
+ case 'wInProj': return [2 * D, dModel];
342
+ case 'wConv': return [D, K];
343
+ case 'wXProj': return [R + 2 * N, D];
344
+ case 'wDtProj': return [D, R];
345
+ case 'A_log': return [D, N];
346
+ case 'wOutProj': return [dModel, D];
347
+ default: return null; // bInProj, bConv, bXProj, bDtProj, D_vec, bOutProj, normWeight, final_norm, lm_head_bias
348
+ }
349
+ }
350
+
351
+ /**
352
+ * Parameters a low-rank adapter covers by default: the four projections inside
353
+ * each block.
354
+ *
355
+ * Deliberately NOT the token embedding (by far the largest tensor, and tied to
356
+ * the output head — adapting it low-rank changes the head too) and NOT `A_log`
357
+ * (the state-transition decay, which WSLA freezes precisely because drifting it
358
+ * destabilises the SSM).
359
+ */
360
+ export const DEFAULT_LORA_TARGETS = ['wInProj', 'wXProj', 'wDtProj', 'wOutProj'] as const;