@seanhogg/builderforce-memory-engine 2026.7.11 → 2026.7.14
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.
- package/dist/index.d.ts +6 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/limbic/limbic_trainer.d.ts +5 -1
- package/dist/limbic/limbic_trainer.d.ts.map +1 -1
- package/dist/limbic/limbic_trainer.js +4 -2
- package/dist/limbic/limbic_trainer.js.map +1 -1
- package/dist/lm/evermind_lm.d.ts +70 -3
- package/dist/lm/evermind_lm.d.ts.map +1 -1
- package/dist/lm/evermind_lm.js +242 -110
- package/dist/lm/evermind_lm.js.map +1 -1
- package/dist/lm/index.d.ts +5 -1
- package/dist/lm/index.d.ts.map +1 -1
- package/dist/lm/index.js +3 -0
- package/dist/lm/index.js.map +1 -1
- package/dist/optim/adamw.d.ts +26 -1
- package/dist/optim/adamw.d.ts.map +1 -1
- package/dist/optim/adamw.js +35 -4
- package/dist/optim/adamw.js.map +1 -1
- package/dist/training/lora.d.ts +153 -0
- package/dist/training/lora.d.ts.map +1 -0
- package/dist/training/lora.js +324 -0
- package/dist/training/lora.js.map +1 -0
- package/dist/training/mixed_precision.d.ts +67 -0
- package/dist/training/mixed_precision.d.ts.map +1 -0
- package/dist/training/mixed_precision.js +101 -0
- package/dist/training/mixed_precision.js.map +1 -0
- package/package.json +1 -1
- package/src/index.ts +8 -2
- package/src/limbic/limbic_trainer.ts +9 -4
- package/src/lm/evermind_lm.ts +254 -110
- package/src/lm/index.ts +7 -1
- package/src/optim/adamw.ts +55 -7
- package/src/training/lora.ts +365 -0
- package/src/training/mixed_precision.ts +119 -0
|
@@ -0,0 +1,67 @@
|
|
|
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
|
+
/** Round a value to what fp16 storage would preserve (half-precision simulation). */
|
|
20
|
+
export declare function roundFp16(x: number): number;
|
|
21
|
+
/** Round every element of a copy to fp16 precision (leaves the input untouched). */
|
|
22
|
+
export declare function fp16View(a: Float32Array): Float32Array;
|
|
23
|
+
export interface LossScalerOptions {
|
|
24
|
+
/** Initial scale. Default 2^14 = 16384. */
|
|
25
|
+
initScale?: number;
|
|
26
|
+
/** Multiply the scale by this after `growthInterval` clean steps. Default 2. */
|
|
27
|
+
growthFactor?: number;
|
|
28
|
+
/** Multiply the scale by this on overflow. Default 0.5. */
|
|
29
|
+
backoffFactor?: number;
|
|
30
|
+
/** Clean steps between growth attempts. Default 200. */
|
|
31
|
+
growthInterval?: number;
|
|
32
|
+
/** Floor for the scale so it can't collapse to zero. Default 1. */
|
|
33
|
+
minScale?: number;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Dynamic loss scaler — the standard AMP controller. Scale the loss/grads up
|
|
37
|
+
* before backward, {@link check} them for overflow, {@link unscale} the good ones,
|
|
38
|
+
* and {@link update} the scale based on whether this step overflowed.
|
|
39
|
+
*/
|
|
40
|
+
export declare class DynamicLossScaler {
|
|
41
|
+
private _scale;
|
|
42
|
+
private readonly growthFactor;
|
|
43
|
+
private readonly backoffFactor;
|
|
44
|
+
private readonly growthInterval;
|
|
45
|
+
private readonly minScale;
|
|
46
|
+
private cleanStreak;
|
|
47
|
+
private _overflows;
|
|
48
|
+
private _skipped;
|
|
49
|
+
constructor(opts?: LossScalerOptions);
|
|
50
|
+
get scale(): number;
|
|
51
|
+
get overflows(): number;
|
|
52
|
+
get skipped(): number;
|
|
53
|
+
/** True if any gradient is inf/nan (an fp16 overflow) — such a step must be skipped. */
|
|
54
|
+
check(grads: {
|
|
55
|
+
data: Float32Array;
|
|
56
|
+
}[]): boolean;
|
|
57
|
+
/** Divide gradients back down by the current scale, in place. */
|
|
58
|
+
unscale(grads: {
|
|
59
|
+
data: Float32Array;
|
|
60
|
+
}[]): void;
|
|
61
|
+
/**
|
|
62
|
+
* Advance the scale controller. Pass whether this step overflowed. Returns
|
|
63
|
+
* `true` if the optimiser step should PROCEED, `false` if it must be skipped.
|
|
64
|
+
*/
|
|
65
|
+
update(overflow: boolean): boolean;
|
|
66
|
+
}
|
|
67
|
+
//# sourceMappingURL=mixed_precision.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mixed_precision.d.ts","sourceRoot":"","sources":["../../src/training/mixed_precision.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAIH,qFAAqF;AACrF,wBAAgB,SAAS,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAE3C;AAED,oFAAoF;AACpF,wBAAgB,QAAQ,CAAC,CAAC,EAAE,YAAY,GAAG,YAAY,CAItD;AAED,MAAM,WAAW,iBAAiB;IAChC,2CAA2C;IAC3C,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,gFAAgF;IAChF,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,2DAA2D;IAC3D,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,wDAAwD;IACxD,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,mEAAmE;IACnE,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;;;GAIG;AACH,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAS;IACtC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAS;IACvC,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAS;IACxC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,WAAW,CAAK;IACxB,OAAO,CAAC,UAAU,CAAK;IACvB,OAAO,CAAC,QAAQ,CAAK;gBAET,IAAI,GAAE,iBAAsB;IAQxC,IAAI,KAAK,IAAI,MAAM,CAElB;IACD,IAAI,SAAS,IAAI,MAAM,CAEtB;IACD,IAAI,OAAO,IAAI,MAAM,CAEpB;IAED,wFAAwF;IACxF,KAAK,CAAC,KAAK,EAAE;QAAE,IAAI,EAAE,YAAY,CAAA;KAAE,EAAE,GAAG,OAAO;IAW/C,iEAAiE;IACjE,OAAO,CAAC,KAAK,EAAE;QAAE,IAAI,EAAE,YAAY,CAAA;KAAE,EAAE,GAAG,IAAI;IAQ9C;;;OAGG;IACH,MAAM,CAAC,QAAQ,EAAE,OAAO,GAAG,OAAO;CAcnC"}
|
|
@@ -0,0 +1,101 @@
|
|
|
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
|
+
import { floatToFp16, fp16ToFloat } from "../utils/quantization.js";
|
|
20
|
+
/** Round a value to what fp16 storage would preserve (half-precision simulation). */
|
|
21
|
+
export function roundFp16(x) {
|
|
22
|
+
return fp16ToFloat(floatToFp16(x));
|
|
23
|
+
}
|
|
24
|
+
/** Round every element of a copy to fp16 precision (leaves the input untouched). */
|
|
25
|
+
export function fp16View(a) {
|
|
26
|
+
const out = new Float32Array(a.length);
|
|
27
|
+
for (let i = 0; i < a.length; i++)
|
|
28
|
+
out[i] = roundFp16(a[i]);
|
|
29
|
+
return out;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Dynamic loss scaler — the standard AMP controller. Scale the loss/grads up
|
|
33
|
+
* before backward, {@link check} them for overflow, {@link unscale} the good ones,
|
|
34
|
+
* and {@link update} the scale based on whether this step overflowed.
|
|
35
|
+
*/
|
|
36
|
+
export class DynamicLossScaler {
|
|
37
|
+
_scale;
|
|
38
|
+
growthFactor;
|
|
39
|
+
backoffFactor;
|
|
40
|
+
growthInterval;
|
|
41
|
+
minScale;
|
|
42
|
+
cleanStreak = 0;
|
|
43
|
+
_overflows = 0;
|
|
44
|
+
_skipped = 0;
|
|
45
|
+
constructor(opts = {}) {
|
|
46
|
+
this._scale = opts.initScale ?? 16384;
|
|
47
|
+
this.growthFactor = opts.growthFactor ?? 2;
|
|
48
|
+
this.backoffFactor = opts.backoffFactor ?? 0.5;
|
|
49
|
+
this.growthInterval = opts.growthInterval ?? 200;
|
|
50
|
+
this.minScale = opts.minScale ?? 1;
|
|
51
|
+
}
|
|
52
|
+
get scale() {
|
|
53
|
+
return this._scale;
|
|
54
|
+
}
|
|
55
|
+
get overflows() {
|
|
56
|
+
return this._overflows;
|
|
57
|
+
}
|
|
58
|
+
get skipped() {
|
|
59
|
+
return this._skipped;
|
|
60
|
+
}
|
|
61
|
+
/** True if any gradient is inf/nan (an fp16 overflow) — such a step must be skipped. */
|
|
62
|
+
check(grads) {
|
|
63
|
+
for (const g of grads) {
|
|
64
|
+
const a = g.data;
|
|
65
|
+
for (let i = 0; i < a.length; i++) {
|
|
66
|
+
const x = a[i];
|
|
67
|
+
if (!Number.isFinite(x))
|
|
68
|
+
return true;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
/** Divide gradients back down by the current scale, in place. */
|
|
74
|
+
unscale(grads) {
|
|
75
|
+
const inv = 1 / this._scale;
|
|
76
|
+
for (const g of grads) {
|
|
77
|
+
const a = g.data;
|
|
78
|
+
for (let i = 0; i < a.length; i++)
|
|
79
|
+
a[i] = a[i] * inv;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Advance the scale controller. Pass whether this step overflowed. Returns
|
|
84
|
+
* `true` if the optimiser step should PROCEED, `false` if it must be skipped.
|
|
85
|
+
*/
|
|
86
|
+
update(overflow) {
|
|
87
|
+
if (overflow) {
|
|
88
|
+
this._overflows++;
|
|
89
|
+
this._skipped++;
|
|
90
|
+
this._scale = Math.max(this.minScale, this._scale * this.backoffFactor);
|
|
91
|
+
this.cleanStreak = 0;
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
if (++this.cleanStreak >= this.growthInterval) {
|
|
95
|
+
this._scale *= this.growthFactor;
|
|
96
|
+
this.cleanStreak = 0;
|
|
97
|
+
}
|
|
98
|
+
return true;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
//# sourceMappingURL=mixed_precision.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mixed_precision.js","sourceRoot":"","sources":["../../src/training/mixed_precision.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@seanhogg/builderforce-memory-engine",
|
|
3
|
-
"version": "2026.7.
|
|
3
|
+
"version": "2026.7.14",
|
|
4
4
|
"description": "BuilderForce Agent Memory — engine layer. TypeScript/WGSL Mamba SSM kernels, model blocks, autograd, training, and BPE tokenizer (zero runtime deps, WebGPU-native).",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.js",
|
package/src/index.ts
CHANGED
|
@@ -41,9 +41,15 @@ export type {
|
|
|
41
41
|
|
|
42
42
|
// ── EvermindLM (the generative model) + AdamW ──────────────────────────────────
|
|
43
43
|
export { EvermindLM, EvermindLMTrainer, DEFAULT_LM_CONFIG, DEFAULT_LM_SEED } from './lm/index.js';
|
|
44
|
-
export type { EvermindLMConfig, LMGenerateOptions, TextCodec } from './lm/index.js';
|
|
44
|
+
export type { EvermindLMConfig, LMGenerateOptions, TextCodec, EvermindLMTrainOptions } from './lm/index.js';
|
|
45
45
|
export { AdamW } from './optim/adamw.js';
|
|
46
|
-
export type { AdamWOptions, OptimTarget, OptimParam } from './optim/adamw.js';
|
|
46
|
+
export type { AdamWOptions, OptimTarget, OptimParam, ShardSpec } from './optim/adamw.js';
|
|
47
|
+
|
|
48
|
+
// PEFT / efficient-training toolkit (LoRA, QLoRA, mixed precision, checkpointing).
|
|
49
|
+
export { LoRAAdapter, EvermindLMLoRA, quantizeBase } from './training/lora.js';
|
|
50
|
+
export type { LoRAConfig, LoRAFitOptions, BaseQuant } from './training/lora.js';
|
|
51
|
+
export { DynamicLossScaler, roundFp16, fp16View } from './training/mixed_precision.js';
|
|
52
|
+
export type { LossScalerOptions } from './training/mixed_precision.js';
|
|
47
53
|
|
|
48
54
|
// ── Training ──────────────────────────────────────────────────────────────────
|
|
49
55
|
|
|
@@ -46,7 +46,10 @@ export interface LimbicTrainOptions {
|
|
|
46
46
|
eps?: number;
|
|
47
47
|
/** Max global gradient L2 norm before the optimiser step. Default 1.0. */
|
|
48
48
|
maxGradNorm?: number;
|
|
49
|
-
|
|
49
|
+
/** Per-epoch hook. `gradNorm` is the pre-clip gradient L2 norm this epoch (the
|
|
50
|
+
* instability early-warning) — computed CPU-side so it's always available, unlike
|
|
51
|
+
* the GPU trainer where it's opt-in. */
|
|
52
|
+
onEpochEnd?: ((epoch: number, loss: number, gradNorm?: number) => void) | null;
|
|
50
53
|
}
|
|
51
54
|
|
|
52
55
|
interface AdamMoment {
|
|
@@ -146,7 +149,7 @@ export class LimbicTrainer {
|
|
|
146
149
|
for (let i = 0; i < g.data.length; i++) g.data[i]! *= invN;
|
|
147
150
|
}
|
|
148
151
|
|
|
149
|
-
this._clipGradients(grads, maxGradNorm);
|
|
152
|
+
const gradNorm = this._clipGradients(grads, maxGradNorm);
|
|
150
153
|
|
|
151
154
|
this._step++;
|
|
152
155
|
const beta1_t = Math.pow(beta1, this._step);
|
|
@@ -160,7 +163,7 @@ export class LimbicTrainer {
|
|
|
160
163
|
|
|
161
164
|
const avg = epochLoss / samples.length;
|
|
162
165
|
losses.push(avg);
|
|
163
|
-
if (onEpochEnd) onEpochEnd(epoch + 1, avg);
|
|
166
|
+
if (onEpochEnd) onEpochEnd(epoch + 1, avg, gradNorm);
|
|
164
167
|
}
|
|
165
168
|
return losses;
|
|
166
169
|
}
|
|
@@ -184,7 +187,8 @@ export class LimbicTrainer {
|
|
|
184
187
|
return total / samples.length;
|
|
185
188
|
}
|
|
186
189
|
|
|
187
|
-
|
|
190
|
+
/** Clip grads to `maxNorm` in place; returns the PRE-clip L2 norm (the signal). */
|
|
191
|
+
private _clipGradients(grads: LimbicParam[], maxNorm: number): number {
|
|
188
192
|
let normSq = 0;
|
|
189
193
|
for (const g of grads) for (let i = 0; i < g.data.length; i++) normSq += g.data[i]! * g.data[i]!;
|
|
190
194
|
const norm = Math.sqrt(normSq);
|
|
@@ -192,6 +196,7 @@ export class LimbicTrainer {
|
|
|
192
196
|
const scale = maxNorm / norm;
|
|
193
197
|
for (const g of grads) for (let i = 0; i < g.data.length; i++) g.data[i]! *= scale;
|
|
194
198
|
}
|
|
199
|
+
return norm;
|
|
195
200
|
}
|
|
196
201
|
|
|
197
202
|
private _adamwStepCpu(
|