@seanhogg/builderforce-memory-engine 2026.10.1 → 2026.10.3
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/evermind/adapt.d.ts +7 -0
- package/dist/evermind/adapt.d.ts.map +1 -1
- package/dist/evermind/adapt.js +1 -1
- package/dist/evermind/adapt.js.map +1 -1
- package/dist/evermind/delta_wire.d.ts +16 -1
- package/dist/evermind/delta_wire.d.ts.map +1 -1
- package/dist/evermind/delta_wire.js +22 -2
- package/dist/evermind/delta_wire.js.map +1 -1
- package/dist/evermind/index.d.ts +2 -2
- package/dist/evermind/index.d.ts.map +1 -1
- package/dist/evermind/index.js +1 -1
- package/dist/evermind/index.js.map +1 -1
- package/dist/evermind/merge.d.ts +5 -2
- package/dist/evermind/merge.d.ts.map +1 -1
- package/dist/evermind/merge.js +5 -2
- package/dist/evermind/merge.js.map +1 -1
- package/dist/evermind/ml_head.d.ts +28 -0
- package/dist/evermind/ml_head.d.ts.map +1 -0
- package/dist/evermind/ml_head.js +47 -0
- package/dist/evermind/ml_head.js.map +1 -0
- package/dist/evermind/package.d.ts +2 -2
- package/dist/evermind/package.d.ts.map +1 -1
- package/dist/evermind/package.js +1 -1
- package/dist/evermind/package.js.map +1 -1
- package/dist/experience/index.d.ts +2 -2
- package/dist/experience/index.d.ts.map +1 -1
- package/dist/experience/index.js.map +1 -1
- package/dist/experience/store.d.ts +19 -1
- package/dist/experience/store.d.ts.map +1 -1
- package/dist/experience/store.js +14 -2
- package/dist/experience/store.js.map +1 -1
- package/dist/experience/types.d.ts +18 -0
- package/dist/experience/types.d.ts.map +1 -1
- package/dist/experience/types.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/kernels/weight_update.d.ts.map +1 -1
- package/dist/kernels/weight_update.js +12 -0
- package/dist/kernels/weight_update.js.map +1 -1
- package/dist/limbic/limbic_model.d.ts.map +1 -1
- package/dist/limbic/limbic_model.js +1 -3
- package/dist/limbic/limbic_model.js.map +1 -1
- package/dist/ml/classical/anomaly_composite.d.ts +32 -0
- package/dist/ml/classical/anomaly_composite.d.ts.map +1 -0
- package/dist/ml/classical/anomaly_composite.js +29 -0
- package/dist/ml/classical/anomaly_composite.js.map +1 -0
- package/dist/ml/classical/ewma.d.ts +7 -1
- package/dist/ml/classical/ewma.d.ts.map +1 -1
- package/dist/ml/classical/ewma.js +6 -4
- package/dist/ml/classical/ewma.js.map +1 -1
- package/dist/ml/classical/gbm.d.ts.map +1 -1
- package/dist/ml/classical/gbm.js +2 -1
- package/dist/ml/classical/gbm.js.map +1 -1
- package/dist/ml/classical/index.d.ts +6 -2
- package/dist/ml/classical/index.d.ts.map +1 -1
- package/dist/ml/classical/index.js +5 -1
- package/dist/ml/classical/index.js.map +1 -1
- package/dist/ml/classical/kmeans.d.ts.map +1 -1
- package/dist/ml/classical/kmeans.js +23 -4
- package/dist/ml/classical/kmeans.js.map +1 -1
- package/dist/ml/classical/knn.d.ts +15 -5
- package/dist/ml/classical/knn.d.ts.map +1 -1
- package/dist/ml/classical/knn.js +28 -7
- package/dist/ml/classical/knn.js.map +1 -1
- package/dist/ml/classical/logistic.d.ts +1 -9
- package/dist/ml/classical/logistic.d.ts.map +1 -1
- package/dist/ml/classical/logistic.js +17 -9
- package/dist/ml/classical/logistic.js.map +1 -1
- package/dist/ml/classical/naive_bayes.d.ts +5 -1
- package/dist/ml/classical/naive_bayes.d.ts.map +1 -1
- package/dist/ml/classical/naive_bayes.js +8 -3
- package/dist/ml/classical/naive_bayes.js.map +1 -1
- package/dist/ml/classical/robust_z.d.ts +15 -1
- package/dist/ml/classical/robust_z.d.ts.map +1 -1
- package/dist/ml/classical/robust_z.js +6 -1
- package/dist/ml/classical/robust_z.js.map +1 -1
- package/dist/ml/eval_loss.d.ts +15 -0
- package/dist/ml/eval_loss.d.ts.map +1 -0
- package/dist/ml/eval_loss.js +20 -0
- package/dist/ml/eval_loss.js.map +1 -0
- package/dist/ml/federate.d.ts +11 -25
- package/dist/ml/federate.d.ts.map +1 -1
- package/dist/ml/federate.js +15 -38
- package/dist/ml/federate.js.map +1 -1
- package/dist/ml/head_trainer.d.ts +16 -2
- package/dist/ml/head_trainer.d.ts.map +1 -1
- package/dist/ml/head_trainer.js +64 -27
- package/dist/ml/head_trainer.js.map +1 -1
- package/dist/ml/run.d.ts +11 -2
- package/dist/ml/run.d.ts.map +1 -1
- package/dist/ml/run.js +56 -17
- package/dist/ml/run.js.map +1 -1
- package/dist/ml/tasks/experience.d.ts +95 -13
- package/dist/ml/tasks/experience.d.ts.map +1 -1
- package/dist/ml/tasks/experience.js +172 -20
- package/dist/ml/tasks/experience.js.map +1 -1
- package/dist/ml/tasks/run_anomaly.d.ts +37 -19
- package/dist/ml/tasks/run_anomaly.d.ts.map +1 -1
- package/dist/ml/tasks/run_anomaly.js +50 -31
- package/dist/ml/tasks/run_anomaly.js.map +1 -1
- package/dist/ml/train.d.ts +8 -7
- package/dist/ml/train.d.ts.map +1 -1
- package/dist/ml/train.js +21 -30
- package/dist/ml/train.js.map +1 -1
- package/dist/ml/types.d.ts +6 -3
- package/dist/ml/types.d.ts.map +1 -1
- package/dist/ml/types.js +3 -2
- package/dist/ml/types.js.map +1 -1
- package/dist/optim/adamw.d.ts +11 -5
- package/dist/optim/adamw.d.ts.map +1 -1
- package/dist/optim/adamw.js +19 -4
- package/dist/optim/adamw.js.map +1 -1
- package/package.json +1 -1
- package/src/evermind/adapt.ts +8 -2
- package/src/evermind/delta_wire.ts +173 -148
- package/src/evermind/index.ts +36 -35
- package/src/evermind/merge.ts +10 -4
- package/src/evermind/ml_head.ts +63 -0
- package/src/evermind/package.ts +488 -488
- package/src/experience/index.ts +2 -1
- package/src/experience/store.ts +35 -3
- package/src/experience/types.ts +19 -0
- package/src/index.ts +2 -0
- package/src/kernels/weight_update.ts +12 -0
- package/src/limbic/limbic_model.ts +1 -4
- package/src/limbic/limbic_trainer.ts +91 -91
- package/src/ml/classical/anomaly_composite.ts +46 -0
- package/src/ml/classical/ewma.ts +8 -2
- package/src/ml/classical/gbm.ts +144 -143
- package/src/ml/classical/index.ts +8 -2
- package/src/ml/classical/kmeans.ts +22 -4
- package/src/ml/classical/knn.ts +31 -8
- package/src/ml/classical/logistic.ts +9 -9
- package/src/ml/classical/math.ts +93 -93
- package/src/ml/classical/naive_bayes.ts +10 -3
- package/src/ml/classical/robust_z.ts +21 -2
- package/src/ml/eval_loss.ts +27 -0
- package/src/ml/federate.ts +22 -52
- package/src/ml/head_trainer.ts +66 -48
- package/src/ml/registry.ts +44 -44
- package/src/ml/run.ts +57 -13
- package/src/ml/tasks/experience.ts +222 -23
- package/src/ml/tasks/run_anomaly.ts +62 -37
- package/src/ml/train.ts +34 -31
- package/src/ml/types.ts +135 -132
- package/src/optim/adamw.ts +21 -5
- package/src/training/cpu_ops.ts +141 -141
package/src/ml/train.ts
CHANGED
|
@@ -12,10 +12,11 @@
|
|
|
12
12
|
*/
|
|
13
13
|
import { fitClassical, type ClassicalOptions } from "./classical/index.js";
|
|
14
14
|
import { DenseHead } from "./dense_head.js";
|
|
15
|
+
import { mlEvalLoss } from "./eval_loss.js";
|
|
15
16
|
import { extractFeatureMatrix, type TextEmbedder } from "./features.js";
|
|
16
17
|
import { HeadTrainer, type HeadTrainOptions } from "./head_trainer.js";
|
|
17
18
|
import type { MlModelStore } from "./run.js";
|
|
18
|
-
import { engineKey, type MlEngineRef, type MlPredictor, type MlSample, type MlTask
|
|
19
|
+
import { engineKey, type MlEngineRef, type MlPredictor, type MlSample, type MlTask } from "./types.js";
|
|
19
20
|
|
|
20
21
|
/** One labelled example in input space. */
|
|
21
22
|
export interface MlTrainExample<I> {
|
|
@@ -48,26 +49,15 @@ export interface MlTrainResult {
|
|
|
48
49
|
/** Size of the held-out tail, and the model's loss on it (null when none was held out). */
|
|
49
50
|
holdout: number;
|
|
50
51
|
holdoutLoss: number | null;
|
|
52
|
+
/**
|
|
53
|
+
* Head engines: parameter elements the final fit skipped for a non-finite gradient
|
|
54
|
+
* ({@link HeadTrainer.skippedNonFinite}) — above 0, the fit diverged somewhere. 0
|
|
55
|
+
* for classical engines and task-owned fits.
|
|
56
|
+
*/
|
|
57
|
+
skippedNonFinite: number;
|
|
51
58
|
}
|
|
52
59
|
|
|
53
|
-
|
|
54
|
-
* Mean loss of `predictor` on `samples` for a task of `kind` — classify: weighted
|
|
55
|
-
* cross-entropy of the true class; regress: weighted ½·squared error. Null for
|
|
56
|
-
* kinds without a supervised loss, or an empty set.
|
|
57
|
-
*/
|
|
58
|
-
export function mlEvalLoss(kind: MlTaskKind, predictor: MlPredictor, samples: readonly MlSample[]): number | null {
|
|
59
|
-
if (samples.length === 0 || (kind !== "classify" && kind !== "regress")) return null;
|
|
60
|
-
let sum = 0;
|
|
61
|
-
let wSum = 0;
|
|
62
|
-
for (const s of samples) {
|
|
63
|
-
const w = s.weight ?? 1;
|
|
64
|
-
const p = predictor.predict(s.x);
|
|
65
|
-
const loss = kind === "classify" ? -Math.log(Math.max(p.scores[s.y] ?? 0, 1e-12)) : 0.5 * (p.scores[0]! - s.y) ** 2;
|
|
66
|
-
sum += w * loss;
|
|
67
|
-
wSum += w;
|
|
68
|
-
}
|
|
69
|
-
return wSum > 0 ? sum / wSum : null;
|
|
70
|
-
}
|
|
60
|
+
export { mlEvalLoss } from "./eval_loss.js";
|
|
71
61
|
|
|
72
62
|
/** The task's first local engine the seam can train. */
|
|
73
63
|
function defaultEngine(task: MlTask<any, any>): MlEngineRef {
|
|
@@ -76,9 +66,14 @@ function defaultEngine(task: MlTask<any, any>): MlEngineRef {
|
|
|
76
66
|
return e;
|
|
77
67
|
}
|
|
78
68
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
69
|
+
interface FittedEngine {
|
|
70
|
+
predictor: MlPredictor;
|
|
71
|
+
skippedNonFinite: number;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
async function fitEngine(task: MlTask<any, any>, ref: MlEngineRef, samples: MlSample[], opts: MlTrainOptions): Promise<FittedEngine> {
|
|
75
|
+
if (task.train?.fit) return { predictor: await task.train.fit(samples, ref), skippedNonFinite: 0 };
|
|
76
|
+
if (ref.engine === "classical") return { predictor: fitClassical(ref.algorithm, samples, opts.classical), skippedNonFinite: 0 };
|
|
82
77
|
if (ref.engine === "head") {
|
|
83
78
|
const objective = task.kind === "classify" ? "softmax" : task.kind === "regress" ? "mse" : null;
|
|
84
79
|
if (!objective) throw new Error(`trainMlTask(${task.id}): a head engine trains classify or regress tasks only`);
|
|
@@ -89,8 +84,9 @@ async function fitEngine(task: MlTask<any, any>, ref: MlEngineRef, samples: MlSa
|
|
|
89
84
|
objective,
|
|
90
85
|
...(opts.head?.seed != null ? { seed: opts.head.seed } : {}),
|
|
91
86
|
});
|
|
92
|
-
|
|
93
|
-
|
|
87
|
+
const trainer = new HeadTrainer(head, opts.device ?? null, `trainMlTask(${task.id})`);
|
|
88
|
+
await trainer.train(samples, opts.head?.train);
|
|
89
|
+
return { predictor: head, skippedNonFinite: trainer.skippedNonFinite };
|
|
94
90
|
}
|
|
95
91
|
throw new Error(`trainMlTask(${task.id}): engine '${engineKey(ref)}' is not trained here`);
|
|
96
92
|
}
|
|
@@ -123,14 +119,21 @@ export async function trainMlTask<I>(
|
|
|
123
119
|
if (nHold >= 1 && samples.length - nHold >= 1) {
|
|
124
120
|
const train = samples.slice(0, samples.length - nHold);
|
|
125
121
|
const hold = samples.slice(samples.length - nHold);
|
|
126
|
-
const
|
|
127
|
-
holdoutLoss = mlEvalLoss(task.kind, predictor, hold);
|
|
122
|
+
const fit = await fitEngine(task, ref, train, opts);
|
|
123
|
+
holdoutLoss = mlEvalLoss(task.kind, fit.predictor, hold);
|
|
128
124
|
if (opts.refitOnAll === false) {
|
|
129
|
-
opts.models?.set(task.id, engineKey(ref), predictor);
|
|
130
|
-
return { predictor, engine: ref, samples: train.length, holdout: nHold, holdoutLoss };
|
|
125
|
+
opts.models?.set(task.id, engineKey(ref), fit.predictor);
|
|
126
|
+
return { predictor: fit.predictor, engine: ref, samples: train.length, holdout: nHold, holdoutLoss, skippedNonFinite: fit.skippedNonFinite };
|
|
131
127
|
}
|
|
132
128
|
}
|
|
133
|
-
const
|
|
134
|
-
opts.models?.set(task.id, engineKey(ref), predictor);
|
|
135
|
-
return {
|
|
129
|
+
const fit = await fitEngine(task, ref, samples, opts);
|
|
130
|
+
opts.models?.set(task.id, engineKey(ref), fit.predictor);
|
|
131
|
+
return {
|
|
132
|
+
predictor: fit.predictor,
|
|
133
|
+
engine: ref,
|
|
134
|
+
samples: samples.length,
|
|
135
|
+
holdout: nHold >= 1 ? nHold : 0,
|
|
136
|
+
holdoutLoss,
|
|
137
|
+
skippedNonFinite: fit.skippedNonFinite,
|
|
138
|
+
};
|
|
136
139
|
}
|
package/src/ml/types.ts
CHANGED
|
@@ -1,132 +1,135 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* ml/types.ts — the ML task catalog contract (docs/ml-task-catalog.md §3).
|
|
3
|
-
*
|
|
4
|
-
* One bounded question Evermind answers without an LLM is ONE data row: how its
|
|
5
|
-
* input becomes a vector, which engines may answer it (in preference order), how
|
|
6
|
-
* it trains, and what its output means. Pure types — no vscode, no DOM, no network.
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
| "
|
|
16
|
-
| "
|
|
17
|
-
| "
|
|
18
|
-
| "
|
|
19
|
-
| "
|
|
20
|
-
| "
|
|
21
|
-
| "
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
| { tier: "
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
*
|
|
36
|
-
* - `
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
export
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
/**
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
/**
|
|
121
|
-
export
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
1
|
+
/**
|
|
2
|
+
* ml/types.ts — the ML task catalog contract (docs/ml-task-catalog.md §3).
|
|
3
|
+
*
|
|
4
|
+
* One bounded question Evermind answers without an LLM is ONE data row: how its
|
|
5
|
+
* input becomes a vector, which engines may answer it (in preference order), how
|
|
6
|
+
* it trains, and what its output means. Pure types — no vscode, no DOM, no network.
|
|
7
|
+
* (Imports only the leaf `evermind/ml_head.ts`; evermind/ never imports ml/.)
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/** The shape of the question. */
|
|
11
|
+
export type MlTaskKind = "classify" | "regress" | "cluster" | "anomaly" | "rank";
|
|
12
|
+
|
|
13
|
+
/** Pure-TypeScript algorithms that run everywhere (`ml/classical/`). */
|
|
14
|
+
export type ClassicalAlgorithm =
|
|
15
|
+
| "knn"
|
|
16
|
+
| "naive-bayes"
|
|
17
|
+
| "logistic"
|
|
18
|
+
| "linear"
|
|
19
|
+
| "kmeans"
|
|
20
|
+
| "robust-z"
|
|
21
|
+
| "ewma"
|
|
22
|
+
/** robust z + EWMA as one fitted engine (`classical/anomaly_composite.ts`). */
|
|
23
|
+
| "anomaly-composite"
|
|
24
|
+
| "isolation-forest"
|
|
25
|
+
| "gbm-small";
|
|
26
|
+
|
|
27
|
+
/** An engine that can answer a task. Cloud engines are a port the HOST supplies. */
|
|
28
|
+
export type MlEngineRef =
|
|
29
|
+
| { tier: "local"; engine: "classical"; algorithm: ClassicalAlgorithm }
|
|
30
|
+
| { tier: "local"; engine: "head"; head: "mlp" | "linear" }
|
|
31
|
+
| { tier: "local"; engine: "onnx"; model: string }
|
|
32
|
+
| { tier: "cloud"; engine: "workers-ai"; model: string };
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Which engines a run may use — the person's inference setting.
|
|
36
|
+
* - `local-only`: local tiers only, never the network.
|
|
37
|
+
* - `hybrid`: local first, the cloud port when no local engine can answer (or the
|
|
38
|
+
* local answer is below the caller's confidence floor).
|
|
39
|
+
* - `specific`: exactly the engine the caller names.
|
|
40
|
+
*/
|
|
41
|
+
export type InferenceMode = "local-only" | "hybrid" | "specific";
|
|
42
|
+
|
|
43
|
+
/** What an engine returns before the task decodes it. */
|
|
44
|
+
export interface MlPrediction {
|
|
45
|
+
/**
|
|
46
|
+
* classify: one probability per label; regress: the predicted value(s);
|
|
47
|
+
* cluster: `[clusterId]`; anomaly: `[score]` where ≥ 1 is anomalous;
|
|
48
|
+
* rank: one score per candidate.
|
|
49
|
+
*/
|
|
50
|
+
scores: number[];
|
|
51
|
+
/** Calibrated confidence in [0, 1]. */
|
|
52
|
+
confidence: number;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** A fitted model that answers on a feature vector. */
|
|
56
|
+
export interface MlPredictor {
|
|
57
|
+
predict(x: Float32Array): MlPrediction;
|
|
58
|
+
/** Trainable state as one flat vector — present on models that federate. */
|
|
59
|
+
exportParams?(): Float32Array;
|
|
60
|
+
/** Load a flat vector produced by {@link exportParams} (e.g. after a merge). */
|
|
61
|
+
importParams?(params: ArrayLike<number>): void;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** How an input becomes a vector: an embedded text part, numeric fields, or both. */
|
|
65
|
+
export interface FeatureSpec<I> {
|
|
66
|
+
/** Text to embed (needs an embedder in the run context). */
|
|
67
|
+
text?: (input: I) => string;
|
|
68
|
+
/** Numeric fields, already on a stable scale (so federated weights mean the same thing everywhere). */
|
|
69
|
+
numeric?: (input: I) => number[];
|
|
70
|
+
/** Names of the numeric fields, index-aligned — documentation and Insights labels. */
|
|
71
|
+
numericNames?: readonly string[];
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** One labelled training example in feature space. */
|
|
75
|
+
export interface MlSample {
|
|
76
|
+
x: Float32Array;
|
|
77
|
+
/** classify: class index; regress: target value; cluster/anomaly: ignored. */
|
|
78
|
+
y: number;
|
|
79
|
+
/** Sample weight (default 1). */
|
|
80
|
+
weight?: number;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** How a task trains. Absent on the task = rule-based or pre-trained only. */
|
|
84
|
+
export interface TrainSpec {
|
|
85
|
+
/** Fewer labelled examples than this and the task stays untrained. */
|
|
86
|
+
minSamples: number;
|
|
87
|
+
/** Fraction held out for the eval gate (default 0.2). */
|
|
88
|
+
holdoutFraction?: number;
|
|
89
|
+
/**
|
|
90
|
+
* A task-specific fit (e.g. a composite of two classical detectors). When absent
|
|
91
|
+
* the trainer fits the engine named by the run (`ml/train.ts`).
|
|
92
|
+
*/
|
|
93
|
+
fit?: (samples: readonly MlSample[], engine: MlEngineRef) => MlPredictor;
|
|
94
|
+
/** Who trains it when not `ml/train.ts` (e.g. `LimbicTrainer` for `limbic.affect`). */
|
|
95
|
+
trainedBy?: string;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** What a task's answer means. */
|
|
99
|
+
export interface OutputSpec<O> {
|
|
100
|
+
/** Label set for classify tasks (index-aligned with `MlPrediction.scores`). */
|
|
101
|
+
labels?: readonly string[];
|
|
102
|
+
/** Decode an engine prediction into the task's output. */
|
|
103
|
+
decode(p: MlPrediction): O;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** One bounded question Evermind can answer without an LLM. DATA, not a class tree. */
|
|
107
|
+
export interface MlTask<I = unknown, O = unknown> {
|
|
108
|
+
kind: MlTaskKind;
|
|
109
|
+
/** Stable id, e.g. `experience.quality`. */
|
|
110
|
+
id: string;
|
|
111
|
+
/** One line on what it answers. */
|
|
112
|
+
description: string;
|
|
113
|
+
features: FeatureSpec<I>;
|
|
114
|
+
/** In preference order; filtered by the inference mode. */
|
|
115
|
+
engines: readonly MlEngineRef[];
|
|
116
|
+
train?: TrainSpec;
|
|
117
|
+
output: OutputSpec<O>;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/** Task ids: lowercase dotted segments — defined once with the artifact's head contract. */
|
|
121
|
+
export { ML_TASK_ID_RE } from "../evermind/ml_head.js";
|
|
122
|
+
|
|
123
|
+
/** A stable string key for an engine ref, e.g. `local:classical:logistic`. */
|
|
124
|
+
export function engineKey(ref: MlEngineRef): string {
|
|
125
|
+
switch (ref.engine) {
|
|
126
|
+
case "classical":
|
|
127
|
+
return `local:classical:${ref.algorithm}`;
|
|
128
|
+
case "head":
|
|
129
|
+
return `local:head:${ref.head}`;
|
|
130
|
+
case "onnx":
|
|
131
|
+
return `local:onnx:${ref.model}`;
|
|
132
|
+
case "workers-ai":
|
|
133
|
+
return `cloud:workers-ai:${ref.model}`;
|
|
134
|
+
}
|
|
135
|
+
}
|
package/src/optim/adamw.ts
CHANGED
|
@@ -138,20 +138,29 @@ export interface AdamWKernelStep {
|
|
|
138
138
|
gradScale?: number;
|
|
139
139
|
}
|
|
140
140
|
|
|
141
|
+
/** Largest finite f32 — a moment above it would be stored as ±Inf. */
|
|
142
|
+
const F32_MAX = 3.4028234663852886e38;
|
|
143
|
+
|
|
141
144
|
/**
|
|
142
145
|
* One AdamW step over a single parameter tensor — the exact CPU mirror of the
|
|
143
|
-
* `adamw_update` entry point in `WEIGHT_UPDATE_WGSL`, including its
|
|
146
|
+
* `adamw_update` entry point in `WEIGHT_UPDATE_WGSL`, including its safety
|
|
144
147
|
* properties:
|
|
145
148
|
*
|
|
146
|
-
* • the **
|
|
147
|
-
*
|
|
149
|
+
* • the **non-finite gradient skip** — an element whose gradient is NaN/±Inf
|
|
150
|
+
* (or whose new moments would overflow f32) is skipped ENTIRELY: m, v and the
|
|
151
|
+
* weight keep their values (no decay either). Writing the moment first and
|
|
152
|
+
* dropping only the step stored NaN in m/v, and every later step of that
|
|
153
|
+
* element was then NaN too — silently dropped, so the weight froze forever;
|
|
154
|
+
* • the **NaN/Inf step guard** — a non-finite step from finite moments is
|
|
155
|
+
* dropped rather than written into a weight;
|
|
148
156
|
* • the **trust region** — `maxDelta` bounds how far a single step can move a
|
|
149
157
|
* weight, which is what keeps repeated write-through adapts reversible.
|
|
150
158
|
*
|
|
151
159
|
* Decoupled weight decay (`θ * (1 - lr·wd)`), matching the kernel — deliberately
|
|
152
160
|
* NOT the coupled form {@link AdamW} uses for the MoE/EvermindLM trainers.
|
|
153
161
|
*
|
|
154
|
-
* `param`, `m` and `v` are updated in place.
|
|
162
|
+
* `param`, `m` and `v` are updated in place. Returns how many elements were skipped
|
|
163
|
+
* for a non-finite gradient (0 on a healthy step).
|
|
155
164
|
*/
|
|
156
165
|
export function adamwUpdateInPlace(
|
|
157
166
|
param: Float32Array,
|
|
@@ -159,15 +168,21 @@ export function adamwUpdateInPlace(
|
|
|
159
168
|
m: Float32Array,
|
|
160
169
|
v: Float32Array,
|
|
161
170
|
hp: AdamWKernelStep,
|
|
162
|
-
):
|
|
171
|
+
): number {
|
|
163
172
|
const { lr, beta1, beta2, eps, weightDecay, beta1_t, beta2_t, maxDelta } = hp;
|
|
164
173
|
const gradScale = hp.gradScale ?? 1;
|
|
165
174
|
const bc1 = 1 - beta1_t;
|
|
166
175
|
const bc2 = 1 - beta2_t;
|
|
176
|
+
let skipped = 0;
|
|
167
177
|
for (let i = 0; i < param.length; i++) {
|
|
168
178
|
const g = grad[i]! * gradScale;
|
|
169
179
|
const mNew = beta1 * m[i]! + (1 - beta1) * g;
|
|
170
180
|
const vNew = beta2 * v[i]! + (1 - beta2) * g * g;
|
|
181
|
+
// NaN fails every comparison, so this also catches a NaN gradient.
|
|
182
|
+
if (!(Math.abs(mNew) <= F32_MAX && vNew <= F32_MAX)) {
|
|
183
|
+
skipped++;
|
|
184
|
+
continue;
|
|
185
|
+
}
|
|
171
186
|
m[i] = mNew;
|
|
172
187
|
v[i] = vNew;
|
|
173
188
|
|
|
@@ -177,4 +192,5 @@ export function adamwUpdateInPlace(
|
|
|
177
192
|
|
|
178
193
|
param[i] = param[i]! * (1 - lr * weightDecay) - step;
|
|
179
194
|
}
|
|
195
|
+
return skipped;
|
|
180
196
|
}
|