@seanhogg/builderforce-memory-engine 2026.7.14 → 2026.9.28
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/README.md +14 -0
- package/dist/bench/adaptation.d.ts +57 -0
- package/dist/bench/adaptation.d.ts.map +1 -0
- package/dist/bench/adaptation.js +167 -0
- package/dist/bench/adaptation.js.map +1 -0
- package/dist/bench/harness.d.ts +2 -0
- package/dist/bench/harness.d.ts.map +1 -1
- package/dist/bench/harness.js +1 -1
- package/dist/bench/harness.js.map +1 -1
- package/dist/bench/index.d.ts +5 -3
- package/dist/bench/index.d.ts.map +1 -1
- package/dist/bench/index.js +4 -2
- package/dist/bench/index.js.map +1 -1
- package/dist/bench/types.d.ts +88 -0
- package/dist/bench/types.d.ts.map +1 -1
- package/dist/evermind/adapt.d.ts +40 -0
- package/dist/evermind/adapt.d.ts.map +1 -0
- package/dist/evermind/adapt.js +25 -0
- package/dist/evermind/adapt.js.map +1 -0
- package/dist/evermind/delta_wire.d.ts +43 -0
- package/dist/evermind/delta_wire.d.ts.map +1 -0
- package/dist/evermind/delta_wire.js +94 -0
- package/dist/evermind/delta_wire.js.map +1 -0
- package/dist/evermind/eval.d.ts +31 -0
- package/dist/evermind/eval.d.ts.map +1 -0
- package/dist/evermind/eval.js +36 -0
- package/dist/evermind/eval.js.map +1 -0
- package/dist/evermind/index.d.ts +16 -0
- package/dist/evermind/index.d.ts.map +1 -0
- package/dist/evermind/index.js +11 -0
- package/dist/evermind/index.js.map +1 -0
- package/dist/evermind/merge.d.ts +21 -0
- package/dist/evermind/merge.d.ts.map +1 -0
- package/dist/evermind/merge.js +79 -0
- package/dist/evermind/merge.js.map +1 -0
- package/dist/{moe/moe_package.d.ts → evermind/package.d.ts} +55 -5
- package/dist/evermind/package.d.ts.map +1 -0
- package/dist/{moe/moe_package.js → evermind/package.js} +118 -19
- package/dist/evermind/package.js.map +1 -0
- package/dist/import/foreign/adapter.d.ts +59 -0
- package/dist/import/foreign/adapter.d.ts.map +1 -0
- package/dist/import/foreign/adapter.js +76 -0
- package/dist/import/foreign/adapter.js.map +1 -0
- package/dist/import/foreign/apply.d.ts +23 -0
- package/dist/import/foreign/apply.d.ts.map +1 -0
- package/dist/import/foreign/apply.js +43 -0
- package/dist/import/foreign/apply.js.map +1 -0
- package/dist/import/foreign/codestral_mamba.d.ts +57 -0
- package/dist/import/foreign/codestral_mamba.d.ts.map +1 -0
- package/dist/import/foreign/codestral_mamba.js +174 -0
- package/dist/import/foreign/codestral_mamba.js.map +1 -0
- package/dist/import/foreign/falcon_mamba.d.ts +51 -0
- package/dist/import/foreign/falcon_mamba.d.ts.map +1 -0
- package/dist/import/foreign/falcon_mamba.js +131 -0
- package/dist/import/foreign/falcon_mamba.js.map +1 -0
- package/dist/import/foreign/index.d.ts +21 -0
- package/dist/import/foreign/index.d.ts.map +1 -0
- package/dist/import/foreign/index.js +18 -0
- package/dist/import/foreign/index.js.map +1 -0
- package/dist/import/foreign/plan.d.ts +122 -0
- package/dist/import/foreign/plan.d.ts.map +1 -0
- package/dist/import/foreign/plan.js +171 -0
- package/dist/import/foreign/plan.js.map +1 -0
- package/dist/import/foreign/registry.d.ts +35 -0
- package/dist/import/foreign/registry.d.ts.map +1 -0
- package/dist/import/foreign/registry.js +89 -0
- package/dist/import/foreign/registry.js.map +1 -0
- package/dist/import/index.d.ts +7 -0
- package/dist/import/index.d.ts.map +1 -1
- package/dist/import/index.js +7 -0
- package/dist/import/index.js.map +1 -1
- package/dist/index.d.ts +25 -12
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +21 -9
- package/dist/index.js.map +1 -1
- package/dist/kernels/selective_scan.d.ts.map +1 -1
- package/dist/kernels/selective_scan.js +15 -9
- package/dist/kernels/selective_scan.js.map +1 -1
- package/dist/kernels/slice.d.ts +26 -0
- package/dist/kernels/slice.d.ts.map +1 -0
- package/dist/kernels/slice.js +52 -0
- package/dist/kernels/slice.js.map +1 -0
- package/dist/kernels/weight_update.d.ts.map +1 -1
- package/dist/kernels/weight_update.js +30 -4
- package/dist/kernels/weight_update.js.map +1 -1
- package/dist/lm/evermind_lm.d.ts +110 -1
- package/dist/lm/evermind_lm.d.ts.map +1 -1
- package/dist/lm/evermind_lm.js +220 -35
- package/dist/lm/evermind_lm.js.map +1 -1
- package/dist/lm/index.d.ts +4 -2
- package/dist/lm/index.d.ts.map +1 -1
- package/dist/lm/index.js +3 -1
- package/dist/lm/index.js.map +1 -1
- package/dist/lm/text_embedder.d.ts +55 -0
- package/dist/lm/text_embedder.d.ts.map +1 -0
- package/dist/lm/text_embedder.js +62 -0
- package/dist/lm/text_embedder.js.map +1 -0
- package/dist/model/attention_block.d.ts.map +1 -1
- package/dist/model/attention_block.js +9 -5
- package/dist/model/attention_block.js.map +1 -1
- package/dist/model/mamba1_block.d.ts.map +1 -1
- package/dist/model/mamba1_block.js +28 -12
- package/dist/model/mamba1_block.js.map +1 -1
- package/dist/model/mamba2_block.d.ts.map +1 -1
- package/dist/model/mamba2_block.js +14 -9
- package/dist/model/mamba2_block.js.map +1 -1
- package/dist/model/mamba3_block.d.ts.map +1 -1
- package/dist/model/mamba3_block.js +14 -9
- package/dist/model/mamba3_block.js.map +1 -1
- package/dist/moe/index.d.ts +0 -2
- package/dist/moe/index.d.ts.map +1 -1
- package/dist/moe/index.js +0 -1
- package/dist/moe/index.js.map +1 -1
- package/dist/optim/adamw.d.ts +32 -0
- package/dist/optim/adamw.d.ts.map +1 -1
- package/dist/optim/adamw.js +34 -0
- package/dist/optim/adamw.js.map +1 -1
- package/dist/tokenizer/bpe.d.ts +28 -0
- package/dist/tokenizer/bpe.d.ts.map +1 -1
- package/dist/tokenizer/bpe.js +47 -0
- package/dist/tokenizer/bpe.js.map +1 -1
- package/dist/training/cpu_ops.d.ts +44 -0
- package/dist/training/cpu_ops.d.ts.map +1 -0
- package/dist/training/cpu_ops.js +111 -0
- package/dist/training/cpu_ops.js.map +1 -0
- package/dist/training/mamba1_cpu.d.ts +109 -0
- package/dist/training/mamba1_cpu.d.ts.map +1 -0
- package/dist/training/mamba1_cpu.js +316 -0
- package/dist/training/mamba1_cpu.js.map +1 -0
- package/dist/training/model_cpu.d.ts +100 -0
- package/dist/training/model_cpu.d.ts.map +1 -0
- package/dist/training/model_cpu.js +252 -0
- package/dist/training/model_cpu.js.map +1 -0
- package/dist/training/trainer.d.ts +156 -16
- package/dist/training/trainer.d.ts.map +1 -1
- package/dist/training/trainer.js +327 -101
- package/dist/training/trainer.js.map +1 -1
- package/dist/utils/base64.d.ts +12 -0
- package/dist/utils/base64.d.ts.map +1 -0
- package/dist/utils/base64.js +27 -0
- package/dist/utils/base64.js.map +1 -0
- package/package.json +3 -1
- package/src/bench/adaptation.ts +180 -0
- package/src/bench/harness.ts +1 -1
- package/src/bench/index.ts +13 -2
- package/src/bench/types.ts +93 -0
- package/src/evermind/adapt.ts +61 -0
- package/src/evermind/delta_wire.ts +114 -0
- package/src/evermind/eval.ts +62 -0
- package/src/evermind/index.ts +32 -0
- package/src/evermind/merge.ts +106 -0
- package/src/{moe/moe_package.ts → evermind/package.ts} +148 -17
- package/src/import/foreign/adapter.ts +106 -0
- package/src/import/foreign/apply.ts +54 -0
- package/src/import/foreign/codestral_mamba.ts +224 -0
- package/src/import/foreign/falcon_mamba.ts +173 -0
- package/src/import/foreign/index.ts +37 -0
- package/src/import/foreign/plan.ts +294 -0
- package/src/import/foreign/registry.ts +126 -0
- package/src/import/index.ts +30 -0
- package/src/index.ts +94 -13
- package/src/kernels/selective_scan.ts +15 -9
- package/src/kernels/slice.ts +64 -0
- package/src/kernels/weight_update.ts +30 -4
- package/src/lm/evermind_lm.ts +244 -34
- package/src/lm/index.ts +6 -2
- package/src/lm/text_embedder.ts +84 -0
- package/src/model/attention_block.ts +9 -5
- package/src/model/mamba1_block.ts +28 -12
- package/src/model/mamba2_block.ts +14 -9
- package/src/model/mamba3_block.ts +14 -9
- package/src/moe/index.ts +0 -9
- package/src/optim/adamw.ts +60 -0
- package/src/tokenizer/bpe.ts +55 -0
- package/src/training/cpu_ops.ts +127 -0
- package/src/training/mamba1_cpu.ts +421 -0
- package/src/training/model_cpu.ts +360 -0
- package/src/training/trainer.ts +397 -154
- package/src/utils/base64.ts +28 -0
- package/dist/model/mamba_block.d.ts +0 -64
- package/dist/model/mamba_block.d.ts.map +0 -1
- package/dist/model/mamba_block.js +0 -303
- package/dist/model/mamba_block.js.map +0 -1
- package/dist/moe/moe_package.d.ts.map +0 -1
- package/dist/moe/moe_package.js.map +0 -1
- package/src/model/mamba_block.ts +0 -401
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@seanhogg/builderforce-memory-engine",
|
|
3
|
-
"version": "2026.
|
|
3
|
+
"version": "2026.9.28",
|
|
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",
|
|
@@ -76,6 +76,7 @@
|
|
|
76
76
|
"tsconfig": {
|
|
77
77
|
"module": "ES2022",
|
|
78
78
|
"moduleResolution": "bundler",
|
|
79
|
+
"rootDir": ".",
|
|
79
80
|
"strict": true,
|
|
80
81
|
"esModuleInterop": true,
|
|
81
82
|
"skipLibCheck": true,
|
|
@@ -94,6 +95,7 @@
|
|
|
94
95
|
"scripts": {
|
|
95
96
|
"build": "tsc",
|
|
96
97
|
"serve": "node serve.js",
|
|
98
|
+
"bench:adapt": "npm run build && node scripts/bench-adaptation.mjs",
|
|
97
99
|
"test": "node --experimental-vm-modules node_modules/jest/bin/jest.js",
|
|
98
100
|
"lint": "eslint src/ tests/",
|
|
99
101
|
"typecheck:native": "tsgo --noEmit"
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* bench/adaptation.ts — what does ON-PREM ADAPTATION COST?
|
|
3
|
+
*
|
|
4
|
+
* The rest of the harness measures how GOOD a model is. This measures how much
|
|
5
|
+
* one write-through adaptation COSTS, which is the number an on-prem operator
|
|
6
|
+
* needs before turning learning on: every run the host adapts on is CPU it is not
|
|
7
|
+
* spending on the agent.
|
|
8
|
+
*
|
|
9
|
+
* It became measurable only once the gradient path was real. Before that a "fit"
|
|
10
|
+
* was a forward pass plus an inert optimiser step, so any timing was a timing of
|
|
11
|
+
* the wrong thing.
|
|
12
|
+
*
|
|
13
|
+
* The three knobs are the ones the host actually exposes
|
|
14
|
+
* (`agent-runtime/src/infra/project-evermind-delta.ts`, and the cloud
|
|
15
|
+
* coordinator's identical constants):
|
|
16
|
+
*
|
|
17
|
+
* • WINDOW — `ADAPT_WINDOW_TOKENS = 64`, the length of each training sequence
|
|
18
|
+
* the run text is chunked into;
|
|
19
|
+
* • EPOCHS — `new EvermindLMTrainer(lm, { epochs: 1 })`, passes per contribution;
|
|
20
|
+
* • SKIP — `MIN_TEXT_CHARS = 20`, the floor below which a contribution is
|
|
21
|
+
* dropped before any work happens; expressed here as how OFTEN the
|
|
22
|
+
* floor fires.
|
|
23
|
+
*
|
|
24
|
+
* Two quantities are reported, and the difference matters. `positionsEvaluated`
|
|
25
|
+
* comes from the model's own counter: exact, integer, hardware-independent — the
|
|
26
|
+
* thing a curve should be asserted on. `elapsedMs` is what those positions cost on
|
|
27
|
+
* THIS machine, which is what an operator budgets with and what no assertion
|
|
28
|
+
* should depend on.
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
import { EvermindLM, EvermindLMTrainer } from "../lm/evermind_lm.js";
|
|
32
|
+
import { BPETokenizer } from "../tokenizer/bpe.js";
|
|
33
|
+
import { defaultNow } from "./harness.js";
|
|
34
|
+
import type { AdaptationCostOptions, AdaptationCostPoint, AdaptationCostReport } from "./types.js";
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Chunk token ids into fixed-length training windows, dropping any tail too short
|
|
38
|
+
* to have a next-token target.
|
|
39
|
+
*
|
|
40
|
+
* A byte-for-byte mirror of the host's own `windows()` helper, so a benchmarked
|
|
41
|
+
* fit sees exactly the batches a production adaptation would.
|
|
42
|
+
*/
|
|
43
|
+
export function tokenWindows(ids: number[], size: number): number[][] {
|
|
44
|
+
const out: number[][] = [];
|
|
45
|
+
const step = Math.max(1, Math.floor(size));
|
|
46
|
+
for (let i = 0; i + 1 < ids.length; i += step) {
|
|
47
|
+
const seq = ids.slice(i, i + step);
|
|
48
|
+
if (seq.length >= 2) out.push(seq);
|
|
49
|
+
}
|
|
50
|
+
return out;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* How many of `requests` actually adapt at a given skip rate.
|
|
55
|
+
*
|
|
56
|
+
* Deterministic rather than sampled: a benchmark whose cost curve moved with an
|
|
57
|
+
* RNG would be measuring the RNG.
|
|
58
|
+
*/
|
|
59
|
+
export function adaptationsAfterSkip(requests: number, skipRate: number): number {
|
|
60
|
+
const rate = Math.min(1, Math.max(0, skipRate));
|
|
61
|
+
return Math.round(requests * (1 - rate));
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** Default corpus — prose of roughly the shape a run-text contribution has. */
|
|
65
|
+
const DEFAULT_TEXT = [
|
|
66
|
+
"The planning loop retrieves context before generating a response.",
|
|
67
|
+
"Agents recall facts from the memory layer and act on them.",
|
|
68
|
+
"Deployment runs on Cloudflare Workers and Durable Objects.",
|
|
69
|
+
"Tools are gated by a capability registry and a policy pack.",
|
|
70
|
+
"The coordinator merges weight deltas from every contributing host.",
|
|
71
|
+
"A ticket moves through triage, execution, review and merge.",
|
|
72
|
+
"Recall is ranked by embedding cosine fused with lexical scoring.",
|
|
73
|
+
"The trainer adapts a private copy of the base checkpoint locally.",
|
|
74
|
+
].join(" ");
|
|
75
|
+
|
|
76
|
+
/** Total trainable scalars — what one optimiser step touches. */
|
|
77
|
+
function paramCountOf(model: EvermindLM): number {
|
|
78
|
+
let n = 0;
|
|
79
|
+
for (const p of model.parameters()) n += p.data.length;
|
|
80
|
+
return n;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Measure adaptation cost across a grid of (window, epochs, skip rate).
|
|
85
|
+
*
|
|
86
|
+
* Each grid point starts from a FRESH model, so the points are independent
|
|
87
|
+
* measurements rather than a single model drifting through the grid (a diverged
|
|
88
|
+
* checkpoint would otherwise make later points look different for reasons that
|
|
89
|
+
* have nothing to do with cost).
|
|
90
|
+
*/
|
|
91
|
+
export function benchmarkAdaptationCost(opts: AdaptationCostOptions = {}): AdaptationCostReport {
|
|
92
|
+
const text = opts.text ?? DEFAULT_TEXT;
|
|
93
|
+
const windowTokens = opts.windowTokens ?? [32, 64, 128];
|
|
94
|
+
const epochsGrid = opts.epochs ?? [1, 2, 4];
|
|
95
|
+
const skipRates = opts.skipRates ?? [0, 0.5];
|
|
96
|
+
const requests = Math.max(1, opts.requests ?? 4);
|
|
97
|
+
const dModel = opts.dModel ?? 32;
|
|
98
|
+
const numLayers = opts.numLayers ?? 2;
|
|
99
|
+
const hiddenDim = opts.hiddenDim ?? 48;
|
|
100
|
+
const seed = opts.seed ?? 7;
|
|
101
|
+
const lr = opts.lr ?? 0.01;
|
|
102
|
+
const now = opts.now ?? defaultNow;
|
|
103
|
+
|
|
104
|
+
const tok = new BPETokenizer();
|
|
105
|
+
tok.train(text, { numMerges: opts.numMerges ?? 120 });
|
|
106
|
+
const ids = tok.encode(text);
|
|
107
|
+
|
|
108
|
+
const newModel = (): EvermindLM =>
|
|
109
|
+
new EvermindLM({ vocabSize: tok.vocabSize, dModel, numLayers, hiddenDim, seed });
|
|
110
|
+
|
|
111
|
+
const points: AdaptationCostPoint[] = [];
|
|
112
|
+
for (const w of windowTokens) {
|
|
113
|
+
const windows = tokenWindows(ids, w);
|
|
114
|
+
for (const epochs of epochsGrid) {
|
|
115
|
+
for (const skipRate of skipRates) {
|
|
116
|
+
const adapted = adaptationsAfterSkip(requests, skipRate);
|
|
117
|
+
const model = newModel();
|
|
118
|
+
model.resetStats();
|
|
119
|
+
|
|
120
|
+
const t0 = now();
|
|
121
|
+
for (let r = 0; r < adapted; r++) {
|
|
122
|
+
// One contribution = one fit over the run text's windows, exactly as the
|
|
123
|
+
// host does. accumSteps pinned to 1 so `optimizerSteps` is exact.
|
|
124
|
+
new EvermindLMTrainer(model, { lr, epochs, accumSteps: 1 }).fit(windows);
|
|
125
|
+
}
|
|
126
|
+
const elapsedMs = now() - t0;
|
|
127
|
+
|
|
128
|
+
const positionsEvaluated = model.positionsEvaluated;
|
|
129
|
+
const sequences = windows.length * adapted;
|
|
130
|
+
points.push({
|
|
131
|
+
windowTokens: w,
|
|
132
|
+
epochs,
|
|
133
|
+
skipRate,
|
|
134
|
+
requests,
|
|
135
|
+
adapted,
|
|
136
|
+
sequences,
|
|
137
|
+
optimizerSteps: sequences * epochs,
|
|
138
|
+
positionsEvaluated,
|
|
139
|
+
elapsedMs,
|
|
140
|
+
msPerKPosition: positionsEvaluated > 0 ? (elapsedMs / positionsEvaluated) * 1000 : 0,
|
|
141
|
+
msPerAdaptation: adapted > 0 ? elapsedMs / adapted : 0,
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return {
|
|
148
|
+
points,
|
|
149
|
+
textTokens: ids.length,
|
|
150
|
+
vocabSize: tok.vocabSize,
|
|
151
|
+
dModel,
|
|
152
|
+
numLayers,
|
|
153
|
+
hiddenDim,
|
|
154
|
+
paramCount: paramCountOf(newModel()),
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/** Render an {@link AdaptationCostReport} as a fixed-width table. */
|
|
159
|
+
export function formatAdaptationCostReport(report: AdaptationCostReport): string {
|
|
160
|
+
const head =
|
|
161
|
+
`Evermind adaptation cost — dModel=${report.dModel} layers=${report.numLayers} ` +
|
|
162
|
+
`hidden=${report.hiddenDim} params=${report.paramCount.toLocaleString()} ` +
|
|
163
|
+
`vocab=${report.vocabSize} textTokens=${report.textTokens}`;
|
|
164
|
+
const cols = ["window", "epochs", "skip", "adapts", "seqs", "steps", "positions", "ms", "ms/adapt", "ms/1k pos"];
|
|
165
|
+
const rows = report.points.map((p) => [
|
|
166
|
+
String(p.windowTokens),
|
|
167
|
+
String(p.epochs),
|
|
168
|
+
p.skipRate.toFixed(2),
|
|
169
|
+
`${p.adapted}/${p.requests}`,
|
|
170
|
+
String(p.sequences),
|
|
171
|
+
String(p.optimizerSteps),
|
|
172
|
+
String(p.positionsEvaluated),
|
|
173
|
+
p.elapsedMs.toFixed(1),
|
|
174
|
+
p.msPerAdaptation.toFixed(1),
|
|
175
|
+
p.msPerKPosition.toFixed(3),
|
|
176
|
+
]);
|
|
177
|
+
const widths = cols.map((c, i) => Math.max(c.length, ...rows.map((r) => r[i]!.length)));
|
|
178
|
+
const line = (cells: string[]): string => cells.map((c, i) => c.padStart(widths[i]!)).join(" ");
|
|
179
|
+
return [head, "", line(cols), line(widths.map((w) => "-".repeat(w))), ...rows.map(line)].join("\n");
|
|
180
|
+
}
|
package/src/bench/harness.ts
CHANGED
|
@@ -23,7 +23,7 @@ import type {
|
|
|
23
23
|
} from "./types.js";
|
|
24
24
|
|
|
25
25
|
/** A monotonic clock, preferring `performance.now()` when present. */
|
|
26
|
-
function defaultNow(): number {
|
|
26
|
+
export function defaultNow(): number {
|
|
27
27
|
const g = globalThis as { performance?: { now(): number } };
|
|
28
28
|
return typeof g.performance?.now === "function" ? g.performance.now() : Date.now();
|
|
29
29
|
}
|
package/src/bench/index.ts
CHANGED
|
@@ -2,8 +2,9 @@
|
|
|
2
2
|
* bench — the Evermind benchmarking harness.
|
|
3
3
|
*
|
|
4
4
|
* Measures a trained model on held-out data (perplexity, bits-per-token,
|
|
5
|
-
* next-token accuracy, throughput), A/Bs two models,
|
|
6
|
-
* train-and-score path the Studio drives in the browser
|
|
5
|
+
* next-token accuracy, throughput), A/Bs two models, offers a one-call
|
|
6
|
+
* train-and-score path the Studio drives in the browser, and measures what one
|
|
7
|
+
* on-prem ADAPTATION costs across the host's own window/epoch/skip knobs.
|
|
7
8
|
*/
|
|
8
9
|
|
|
9
10
|
export {
|
|
@@ -16,6 +17,13 @@ export {
|
|
|
16
17
|
trainAndBenchmark,
|
|
17
18
|
} from "./harness.js";
|
|
18
19
|
|
|
20
|
+
export {
|
|
21
|
+
benchmarkAdaptationCost,
|
|
22
|
+
formatAdaptationCostReport,
|
|
23
|
+
tokenWindows,
|
|
24
|
+
adaptationsAfterSkip,
|
|
25
|
+
} from "./adaptation.js";
|
|
26
|
+
|
|
19
27
|
export {
|
|
20
28
|
argmax,
|
|
21
29
|
topKIndices,
|
|
@@ -34,4 +42,7 @@ export type {
|
|
|
34
42
|
ComparisonReport,
|
|
35
43
|
TrainAndBenchmarkOptions,
|
|
36
44
|
TrainAndBenchmarkResult,
|
|
45
|
+
AdaptationCostOptions,
|
|
46
|
+
AdaptationCostPoint,
|
|
47
|
+
AdaptationCostReport,
|
|
37
48
|
} from "./types.js";
|
package/src/bench/types.ts
CHANGED
|
@@ -119,3 +119,96 @@ export interface TrainAndBenchmarkResult extends BenchmarkReport {
|
|
|
119
119
|
/** A short greedy generation from {@link TrainAndBenchmarkOptions.prompt}. */
|
|
120
120
|
sample: string;
|
|
121
121
|
}
|
|
122
|
+
|
|
123
|
+
// ── Adaptation cost ───────────────────────────────────────────────────────────
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Knobs for {@link benchmarkAdaptationCost} — the measured COST of on-prem
|
|
127
|
+
* adaptation, as opposed to the quality metrics above.
|
|
128
|
+
*
|
|
129
|
+
* The defaults bracket what the on-prem host actually runs today
|
|
130
|
+
* (`agent-runtime/src/infra/project-evermind-delta.ts`, mirrored by the cloud
|
|
131
|
+
* coordinator): 64-token training windows, ONE epoch per contribution, over at
|
|
132
|
+
* most 4000 characters of run text, with contributions under 20 characters
|
|
133
|
+
* skipped before any work happens.
|
|
134
|
+
*/
|
|
135
|
+
export interface AdaptationCostOptions {
|
|
136
|
+
/** Corpus the tokenizer is learned from and the adaptation trains on. */
|
|
137
|
+
text?: string;
|
|
138
|
+
/** Tokens per training window. Default `[32, 64, 128]` (production: 64). */
|
|
139
|
+
windowTokens?: number[];
|
|
140
|
+
/** Epochs per adaptation. Default `[1, 2, 4]` (production: 1). */
|
|
141
|
+
epochs?: number[];
|
|
142
|
+
/**
|
|
143
|
+
* Fraction of incoming contributions skipped before any work, 0..1. The
|
|
144
|
+
* on-prem skip is a floor on contribution length (`MIN_TEXT_CHARS`), so this is
|
|
145
|
+
* that floor expressed as how often it fires. Default `[0, 0.5]`.
|
|
146
|
+
*/
|
|
147
|
+
skipRates?: number[];
|
|
148
|
+
/** Adaptation requests issued per grid point. Default 4. */
|
|
149
|
+
requests?: number;
|
|
150
|
+
/** BPE merges learned for the tokenizer. Default 120. */
|
|
151
|
+
numMerges?: number;
|
|
152
|
+
/** Model channel dimension. Default 32. */
|
|
153
|
+
dModel?: number;
|
|
154
|
+
/** Number of (conv + MoE) blocks. Default 2. */
|
|
155
|
+
numLayers?: number;
|
|
156
|
+
/** MoE expert FFN hidden width. Default 48. */
|
|
157
|
+
hiddenDim?: number;
|
|
158
|
+
/** AdamW learning rate for the adaptation fits. Default 0.01. */
|
|
159
|
+
lr?: number;
|
|
160
|
+
/** Deterministic seed for model init. Default 7. */
|
|
161
|
+
seed?: number;
|
|
162
|
+
/** Monotonic clock in ms, injectable for deterministic tests. */
|
|
163
|
+
now?: () => number;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/** Measured cost at one (window, epochs, skipRate) point of the grid. */
|
|
167
|
+
export interface AdaptationCostPoint {
|
|
168
|
+
/** Tokens per training window. */
|
|
169
|
+
windowTokens: number;
|
|
170
|
+
/** Epochs run per adaptation. */
|
|
171
|
+
epochs: number;
|
|
172
|
+
/** Fraction of requests skipped before any work. */
|
|
173
|
+
skipRate: number;
|
|
174
|
+
/** Adaptation requests issued at this point. */
|
|
175
|
+
requests: number;
|
|
176
|
+
/** Requests that actually ran a fit (the rest were skipped). */
|
|
177
|
+
adapted: number;
|
|
178
|
+
/** Training windows fitted, summed over the adaptations that ran. */
|
|
179
|
+
sequences: number;
|
|
180
|
+
/**
|
|
181
|
+
* AdamW steps issued: one per window per epoch (the trainer's default, with
|
|
182
|
+
* gradient accumulation pinned to 1 so the count is exact).
|
|
183
|
+
*/
|
|
184
|
+
optimizerSteps: number;
|
|
185
|
+
/**
|
|
186
|
+
* Block positions pushed through the model, read from
|
|
187
|
+
* `EvermindLM.positionsEvaluated`. Hardware-independent and exact — this is the
|
|
188
|
+
* quantity a cost curve should be asserted on; wall time is what it costs HERE.
|
|
189
|
+
*/
|
|
190
|
+
positionsEvaluated: number;
|
|
191
|
+
/** Wall-clock milliseconds spent inside the fits. */
|
|
192
|
+
elapsedMs: number;
|
|
193
|
+
/** Milliseconds per 1000 evaluated positions — the machine's unit cost. */
|
|
194
|
+
msPerKPosition: number;
|
|
195
|
+
/** Wall-clock milliseconds per adaptation that actually ran (0 when all skipped). */
|
|
196
|
+
msPerAdaptation: number;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/** The adaptation-cost scorecard: one point per (window, epochs, skipRate). */
|
|
200
|
+
export interface AdaptationCostReport {
|
|
201
|
+
points: AdaptationCostPoint[];
|
|
202
|
+
/** Tokens the source text produced (the adaptation's input size). */
|
|
203
|
+
textTokens: number;
|
|
204
|
+
/** Learned tokenizer vocabulary size. */
|
|
205
|
+
vocabSize: number;
|
|
206
|
+
/** Model channel dimension the grid was measured on. */
|
|
207
|
+
dModel: number;
|
|
208
|
+
/** Number of blocks the grid was measured on. */
|
|
209
|
+
numLayers: number;
|
|
210
|
+
/** MoE expert hidden width the grid was measured on. */
|
|
211
|
+
hiddenDim: number;
|
|
212
|
+
/** Trainable scalar parameters — what one optimiser step touches. */
|
|
213
|
+
paramCount: number;
|
|
214
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* evermind/adapt.ts — ONE adaptation recipe: fit a private copy of a model version
|
|
3
|
+
* on some text, then diff it against that version.
|
|
4
|
+
*
|
|
5
|
+
* Every learner that contributes to a shared Evermind (an on-prem host pushing a
|
|
6
|
+
* delta, or the single writer fitting queued text itself) must run the SAME recipe,
|
|
7
|
+
* so a locally-produced delta is the same size of update the writer would have
|
|
8
|
+
* produced from the same text. That recipe lives here and nowhere else.
|
|
9
|
+
*/
|
|
10
|
+
import type { EvermindModelPackage } from "./package.js";
|
|
11
|
+
import { EvermindLMTrainer } from "../lm/evermind_lm.js";
|
|
12
|
+
import { diffCheckpoints } from "../utils/delta.js";
|
|
13
|
+
import { tokenWindows } from "../bench/adaptation.js";
|
|
14
|
+
|
|
15
|
+
/** Chars of text fed to one adaptation pass. */
|
|
16
|
+
export const EVERMIND_ADAPT_MAX_CHARS = 4000;
|
|
17
|
+
/** Token window length of the adaptation training sequences. */
|
|
18
|
+
export const EVERMIND_ADAPT_WINDOW_TOKENS = 64;
|
|
19
|
+
|
|
20
|
+
/** The tokenizer surface adaptation needs (the engine's `BPETokenizer` satisfies it). */
|
|
21
|
+
export interface AdaptTokenizer {
|
|
22
|
+
encode(text: string): number[];
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** The outcome of one adaptation. */
|
|
26
|
+
export interface AdaptResult {
|
|
27
|
+
/** Sparse serialized RowDelta of adapted vs base. */
|
|
28
|
+
diff: ArrayBuffer;
|
|
29
|
+
/** The trainer's final-epoch mean loss. */
|
|
30
|
+
loss: number;
|
|
31
|
+
/** How many training windows the text produced. */
|
|
32
|
+
sequences: number;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface AdaptOptions {
|
|
36
|
+
maxChars?: number;
|
|
37
|
+
windowTokens?: number;
|
|
38
|
+
epochs?: number;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Adapt a freshly-loaded copy of `pkg` on `text` and return the diff, or null when
|
|
43
|
+
* the text yields no trainable window. The package's own checkpoint is never
|
|
44
|
+
* mutated, so the diff is exactly "this text's update", never an accumulation.
|
|
45
|
+
* Throws when `pkg` is not an `evermind-lm` (as `loadLM` does).
|
|
46
|
+
*/
|
|
47
|
+
export function adaptAndDiff(
|
|
48
|
+
pkg: EvermindModelPackage,
|
|
49
|
+
tokenizer: AdaptTokenizer,
|
|
50
|
+
text: string,
|
|
51
|
+
opts: AdaptOptions = {},
|
|
52
|
+
): AdaptResult | null {
|
|
53
|
+
const maxChars = opts.maxChars ?? EVERMIND_ADAPT_MAX_CHARS;
|
|
54
|
+
const windowTokens = opts.windowTokens ?? EVERMIND_ADAPT_WINDOW_TOKENS;
|
|
55
|
+
const seqs = tokenWindows(tokenizer.encode(text.slice(0, maxChars)), windowTokens);
|
|
56
|
+
if (seqs.length === 0) return null;
|
|
57
|
+
const lm = pkg.loadLM();
|
|
58
|
+
const history = new EvermindLMTrainer(lm, { epochs: opts.epochs ?? 1 }).fit(seqs);
|
|
59
|
+
const loss = history.length > 0 ? history[history.length - 1]! : 0;
|
|
60
|
+
return { diff: diffCheckpoints(pkg.checkpoint, lm.exportWeights()), loss, sequences: seqs.length };
|
|
61
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* evermind/delta_wire.ts — the wire contract of a pre-diffed weight-delta contribution.
|
|
3
|
+
*
|
|
4
|
+
* A learner with CPU to spare adapts a private copy of a model version, diffs it
|
|
5
|
+
* against that version (`diffCheckpoints`) and ships only the sparse diff; the single
|
|
6
|
+
* writer then merges it (`mergeCheckpointDiffs`) instead of running the fit.
|
|
7
|
+
* Everything the PRODUCER and the WRITER must agree on lives here, once:
|
|
8
|
+
* - the payload shape (`diff` base64 + integer `baseVersion`, optional weight/label);
|
|
9
|
+
* - the size cap, so a producer can fall back before it is refused;
|
|
10
|
+
* - the structural check a delta must pass before it may join a merge batch.
|
|
11
|
+
*
|
|
12
|
+
* That last one is load-bearing: `mergeCheckpointDiffs` throws on a malformed diff,
|
|
13
|
+
* and one throw drops the whole batch. A single bad push must cost only itself.
|
|
14
|
+
*/
|
|
15
|
+
import { deserializeRowDelta } from "../utils/delta.js";
|
|
16
|
+
import { verifyCrcTrailer } from "../utils/crc32.js";
|
|
17
|
+
import { base64ToBytes, bytesToBase64 } from "../utils/base64.js";
|
|
18
|
+
|
|
19
|
+
/** Max accepted base64 delta, in characters (~8 MiB). */
|
|
20
|
+
export const MAX_DELTA_B64_CHARS = 8 * 1024 * 1024;
|
|
21
|
+
|
|
22
|
+
/** Chars of provenance label kept — a diff carries no text, so the label is its only description. */
|
|
23
|
+
export const DELTA_LABEL_MAX_CHARS = 800;
|
|
24
|
+
|
|
25
|
+
/** Standard base64 alphabet with optional padding. */
|
|
26
|
+
const BASE64_RE = /^[A-Za-z0-9+/]+={0,2}$/;
|
|
27
|
+
|
|
28
|
+
/** A validated delta contribution — the exact body a producer sends. */
|
|
29
|
+
export interface DeltaLearnPayload {
|
|
30
|
+
/** base64 serialized RowDelta (`diffCheckpoints(base, adapted)`). */
|
|
31
|
+
diff: string;
|
|
32
|
+
/** The model version the delta was diffed against. */
|
|
33
|
+
baseVersion: number;
|
|
34
|
+
/** FedAvg sample weight (> 0). Absent means the writer's default (1). */
|
|
35
|
+
weight?: number;
|
|
36
|
+
/** Provenance for the merged row (e.g. the run's task). */
|
|
37
|
+
label?: string;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Why a body was refused: `invalid` (malformed) or `too-large` (over {@link MAX_DELTA_B64_CHARS}). */
|
|
41
|
+
export type DeltaParseResult =
|
|
42
|
+
| { ok: true; payload: DeltaLearnPayload }
|
|
43
|
+
| { ok: false; reason: "invalid" | "too-large"; error: string };
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Parse + validate a delta body. Pure — a front door calls it to refuse a bad body
|
|
47
|
+
* early, and the single writer calls it again because it trusts no caller.
|
|
48
|
+
*/
|
|
49
|
+
export function parseDeltaLearnPayload(raw: unknown): DeltaParseResult {
|
|
50
|
+
const body = raw && typeof raw === "object" ? (raw as Record<string, unknown>) : {};
|
|
51
|
+
const diff = typeof body.diff === "string" ? body.diff : "";
|
|
52
|
+
const baseVersion = typeof body.baseVersion === "number" ? body.baseVersion : Number.NaN;
|
|
53
|
+
if (!diff || !Number.isInteger(baseVersion)) {
|
|
54
|
+
return {
|
|
55
|
+
ok: false,
|
|
56
|
+
reason: "invalid",
|
|
57
|
+
error: "diff (base64) and baseVersion (integer head version the delta was taken against) are required",
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
if (diff.length > MAX_DELTA_B64_CHARS) {
|
|
61
|
+
return { ok: false, reason: "too-large", error: `delta too large (max ${MAX_DELTA_B64_CHARS} base64 characters)` };
|
|
62
|
+
}
|
|
63
|
+
if (diff.length % 4 !== 0 || !BASE64_RE.test(diff)) {
|
|
64
|
+
return { ok: false, reason: "invalid", error: "diff must be standard base64" };
|
|
65
|
+
}
|
|
66
|
+
const weight = typeof body.weight === "number" && Number.isFinite(body.weight) && body.weight > 0 ? body.weight : undefined;
|
|
67
|
+
const label = typeof body.label === "string" && body.label.trim() ? body.label.trim().slice(0, DELTA_LABEL_MAX_CHARS) : undefined;
|
|
68
|
+
return {
|
|
69
|
+
ok: true,
|
|
70
|
+
payload: { diff, baseVersion, ...(weight !== undefined ? { weight } : {}), ...(label ? { label } : {}) },
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Build the payload for one computed diff. The caller owns the size guard (compare
|
|
76
|
+
* `payload.diff.length` with {@link MAX_DELTA_B64_CHARS}) so it can choose a fallback.
|
|
77
|
+
*/
|
|
78
|
+
export function buildDeltaLearnPayload(diff: ArrayBuffer, baseVersion: number, weight?: number, label?: string): DeltaLearnPayload {
|
|
79
|
+
const trimmed = (label ?? "").trim();
|
|
80
|
+
return {
|
|
81
|
+
diff: bytesToBase64(diff),
|
|
82
|
+
baseVersion,
|
|
83
|
+
...(typeof weight === "number" && Number.isFinite(weight) && weight > 0 ? { weight } : {}),
|
|
84
|
+
...(trimmed ? { label: trimmed.slice(0, DELTA_LABEL_MAX_CHARS) } : {}),
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** Decode a payload's base64 `diff` to the serialized RowDelta bytes. Throws on malformed base64. */
|
|
89
|
+
export function decodeDeltaPayload(payload: Pick<DeltaLearnPayload, "diff">): ArrayBuffer {
|
|
90
|
+
return base64ToBytes(payload.diff);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Structural check of ONE delta against the base it claims to diff: it must
|
|
95
|
+
* deserialize, be element-granular, index only elements the base has, and carry
|
|
96
|
+
* finite values. Returns why it is unusable, or null when it can join a merge.
|
|
97
|
+
*/
|
|
98
|
+
export function deltaUnusableReason(delta: ArrayBuffer, baseCheckpoint: ArrayBuffer): string | null {
|
|
99
|
+
try {
|
|
100
|
+
const rd = deserializeRowDelta(delta);
|
|
101
|
+
if (rd.rowSize !== 1) return `expected an element-granular delta (rowSize 1), got ${rd.rowSize}`;
|
|
102
|
+
const elements = verifyCrcTrailer(baseCheckpoint).body.byteLength / 4;
|
|
103
|
+
for (let i = 0; i < rd.rows.length; i++) {
|
|
104
|
+
const idx = rd.rows[i]!;
|
|
105
|
+
if (!Number.isInteger(idx) || idx < 0 || idx >= elements) {
|
|
106
|
+
return `row index ${idx} is outside the base checkpoint (${elements} elements)`;
|
|
107
|
+
}
|
|
108
|
+
if (!Number.isFinite(rd.data[i]!)) return `non-finite value at row ${idx}`;
|
|
109
|
+
}
|
|
110
|
+
return null;
|
|
111
|
+
} catch (error) {
|
|
112
|
+
return `not a serialized RowDelta: ${error instanceof Error ? error.message : String(error)}`;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* evermind/eval.ts — the pre/post-merge regression check.
|
|
3
|
+
*
|
|
4
|
+
* Each merge produces a NEW version from the PREVIOUS one. To tell whether it helped
|
|
5
|
+
* or quietly regressed (catastrophic forgetting), both models are scored on a small
|
|
6
|
+
* held-out set of previously-learned examples and their mean next-token loss compared.
|
|
7
|
+
*
|
|
8
|
+
* Forward-only: the loss is the same mean cross-entropy the trainer reports, computed
|
|
9
|
+
* from the logits alone, so scoring never pays for a backward pass or touches grads.
|
|
10
|
+
*/
|
|
11
|
+
import type { LogitsModel } from "../bench/types.js";
|
|
12
|
+
import { crossEntropyLoss } from "../training/autograd.js";
|
|
13
|
+
|
|
14
|
+
/** One held-out example: the learned text, optionally with the task prompt it answered. */
|
|
15
|
+
export interface EvalExample {
|
|
16
|
+
prompt?: string;
|
|
17
|
+
text: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** Max tokens scored per example — bounds the forward cost of eval. */
|
|
21
|
+
export const EVAL_MAX_TOKENS = 128;
|
|
22
|
+
|
|
23
|
+
/** The tokenizer surface eval needs (the engine's `BPETokenizer` satisfies it). */
|
|
24
|
+
export interface EvalTokenizer {
|
|
25
|
+
encode(text: string): number[];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** Mean next-token cross-entropy over one token sequence, or null when it has no target. */
|
|
29
|
+
export function sequenceLoss(model: LogitsModel, ids: number[]): number | null {
|
|
30
|
+
if (ids.length < 2) return null;
|
|
31
|
+
const { logits } = model.forward(ids);
|
|
32
|
+
const n = Math.min(logits.length, ids.length) - 1;
|
|
33
|
+
if (n < 1) return null;
|
|
34
|
+
let sum = 0;
|
|
35
|
+
for (let t = 0; t < n; t++) sum += crossEntropyLoss(logits[t]!, ids[t + 1]!);
|
|
36
|
+
const loss = sum / n;
|
|
37
|
+
return Number.isFinite(loss) && loss >= 0 ? loss : null;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Mean loss of `model` across `examples` (unscorable ones skipped), each example
|
|
42
|
+
* weighted equally. Null when nothing was scorable, so a caller can decline to
|
|
43
|
+
* record a meaningless eval point.
|
|
44
|
+
*/
|
|
45
|
+
export function meanEvalLoss(
|
|
46
|
+
model: LogitsModel,
|
|
47
|
+
tokenizer: EvalTokenizer,
|
|
48
|
+
examples: readonly EvalExample[],
|
|
49
|
+
maxTokens: number = EVAL_MAX_TOKENS,
|
|
50
|
+
): number | null {
|
|
51
|
+
let sum = 0;
|
|
52
|
+
let n = 0;
|
|
53
|
+
for (const ex of examples) {
|
|
54
|
+
const text = (ex.prompt ? `${ex.prompt}\n` : "") + ex.text;
|
|
55
|
+
const loss = sequenceLoss(model, tokenizer.encode(text).slice(0, maxTokens));
|
|
56
|
+
if (loss != null) {
|
|
57
|
+
sum += loss;
|
|
58
|
+
n++;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return n > 0 ? sum / n : null;
|
|
62
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Evermind — the engine half of the Evermind module: the portable `.evermind`
|
|
3
|
+
* artifact plus the learning pipeline every learner shares (adapt, diff wire
|
|
4
|
+
* contract, FedAvg merge, pre/post-merge eval).
|
|
5
|
+
*/
|
|
6
|
+
export { EvermindModelPackage } from "./package.js";
|
|
7
|
+
export type {
|
|
8
|
+
EvermindModelManifest,
|
|
9
|
+
EvermindModelCard,
|
|
10
|
+
EvermindModelType,
|
|
11
|
+
EvermindModality,
|
|
12
|
+
PackageMeta,
|
|
13
|
+
ValidationResult,
|
|
14
|
+
} from "./package.js";
|
|
15
|
+
|
|
16
|
+
export { adaptAndDiff, EVERMIND_ADAPT_MAX_CHARS } from "./adapt.js";
|
|
17
|
+
export type { AdaptResult, AdaptOptions, AdaptTokenizer } from "./adapt.js";
|
|
18
|
+
|
|
19
|
+
export {
|
|
20
|
+
parseDeltaLearnPayload,
|
|
21
|
+
buildDeltaLearnPayload,
|
|
22
|
+
decodeDeltaPayload,
|
|
23
|
+
deltaUnusableReason,
|
|
24
|
+
MAX_DELTA_B64_CHARS,
|
|
25
|
+
} from "./delta_wire.js";
|
|
26
|
+
export type { DeltaLearnPayload, DeltaParseResult } from "./delta_wire.js";
|
|
27
|
+
|
|
28
|
+
export { mergeCheckpointDiffs } from "./merge.js";
|
|
29
|
+
export type { MergeResult } from "./merge.js";
|
|
30
|
+
|
|
31
|
+
export { meanEvalLoss } from "./eval.js";
|
|
32
|
+
export type { EvalExample, EvalTokenizer } from "./eval.js";
|