pi-mega-compact 0.20.35 → 0.20.38
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/config/vector-cortex-ml5a.js +28 -0
- package/dist/config/vector-cortex-ml5b.js +26 -0
- package/dist/config/vector-cortex.js +5 -5
- package/dist/config.js +1 -1
- package/dist/extensions/dashboard-server/routes-rag-settings-vector-cortex.js +1 -0
- package/dist/monitoring.js +172 -0
- package/dist/src/config/vector-cortex-ml5a.js +28 -0
- package/dist/src/config/vector-cortex-ml5b.js +26 -0
- package/dist/src/config/vector-cortex.js +5 -5
- package/dist/src/config.js +1 -1
- package/dist/src/monitoring.js +19 -0
- package/dist/src/store/backfill.js +1 -0
- package/dist/src/vector-cortex/encoder/bench-export.js +13 -0
- package/dist/src/vector-cortex/encoder/bench.js +100 -0
- package/dist/src/vector-cortex/encoder/calibrate.js +55 -0
- package/dist/src/vector-cortex/encoder/heads.js +87 -0
- package/dist/src/vector-cortex/encoder/select.js +10 -0
- package/dist/vector-cortex/encoder/bench-export.js +13 -0
- package/dist/vector-cortex/encoder/bench.js +100 -0
- package/dist/vector-cortex/encoder/calibrate.js +55 -0
- package/dist/vector-cortex/encoder/heads.js +87 -0
- package/dist/vector-cortex/encoder/select.js +10 -0
- package/dist/vectorStore/dedup-audit.js +104 -0
- package/extensions/dashboard-server/routes-rag-settings-vector-cortex.ts +6 -0
- package/package.json +1 -1
- package/src/config/vector-cortex-ml5a.ts +30 -0
- package/src/config/vector-cortex-ml5b.ts +28 -0
- package/src/config/vector-cortex.ts +5 -5
- package/src/config.ts +2 -0
- package/src/monitoring.ts +24 -0
- package/src/store/backfill.ts +1 -0
- package/src/vector-cortex/encoder/bench-export.ts +65 -0
- package/src/vector-cortex/encoder/bench.ts +109 -0
- package/src/vector-cortex/encoder/calibrate.ts +49 -0
- package/src/vector-cortex/encoder/heads.ts +106 -0
- package/src/vector-cortex/encoder/select.ts +17 -0
package/src/monitoring.ts
CHANGED
|
@@ -205,6 +205,30 @@ export function logRecallQuality(path: string, ev: RecallQualityEvent): void {
|
|
|
205
205
|
}
|
|
206
206
|
}
|
|
207
207
|
|
|
208
|
+
// ---------------------------------------------------------------------------
|
|
209
|
+
// ML5-B encoder bench events (consumer-facing for the dashboard + evidence)
|
|
210
|
+
// ---------------------------------------------------------------------------
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Append a structured ML5-B bench event to events.log (best-effort, non-fatal).
|
|
214
|
+
* Mirrors the extension's appendEvent schema ({ ts, event, ...fields }) so the
|
|
215
|
+
* dashboard live-stream tail and evidence tooling parse the four
|
|
216
|
+
* `vector_cortex_encoder_bench_*` events identically. The bench is developer/
|
|
217
|
+
* evidence tooling with no runtime gating; this only records its results.
|
|
218
|
+
*/
|
|
219
|
+
export function logBenchEvent(
|
|
220
|
+
path: string,
|
|
221
|
+
event: string,
|
|
222
|
+
fields: Record<string, unknown>,
|
|
223
|
+
): void {
|
|
224
|
+
try {
|
|
225
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
226
|
+
appendFileSync(path, JSON.stringify({ ts: Date.now(), event, ...fields }) + "\n", "utf8");
|
|
227
|
+
} catch {
|
|
228
|
+
/* best-effort — never break the caller */
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
208
232
|
// ---------------------------------------------------------------------------
|
|
209
233
|
// Dedup audit trail (external-audit item #2)
|
|
210
234
|
// ---------------------------------------------------------------------------
|
package/src/store/backfill.ts
CHANGED
|
@@ -134,6 +134,7 @@ export function backfillContentHashes(stateDir: string = getStateDir()): Backfil
|
|
|
134
134
|
|
|
135
135
|
if (THROTTLE_MS > 0) {
|
|
136
136
|
// No-op in this synchronous build; placeholder for future streaming backfill.
|
|
137
|
+
// guardrails-allow PREVENT-STUB-001: ML5-C
|
|
137
138
|
}
|
|
138
139
|
|
|
139
140
|
return { processed, updated, duplicatesResolved };
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* vector-cortex/encoder/bench-export.ts — ML5-B bench result contract.
|
|
3
|
+
*
|
|
4
|
+
* BenchResultV1 is the typed surface `bench.ts` parses from the qualification
|
|
5
|
+
* harness (`scripts/ml5/bench-onnx-prod.mjs`) and the dashboard / evidence
|
|
6
|
+
* tooling consume. It carries AGGREGATE measurements + a digest only — never
|
|
7
|
+
* chunk/message content (EVAL-REDACT-002).
|
|
8
|
+
*
|
|
9
|
+
* Contract-first (ENGINEERING_PRACTICES §3): this types file is the reviewed
|
|
10
|
+
* gate; implementations import from it. Pi-agnostic, dependency-free
|
|
11
|
+
* (PREVENT-PI-004 / PREVENT-011).
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/** The four ML5-B bench gates, each independently measured. */
|
|
15
|
+
export interface BenchGatesV1 {
|
|
16
|
+
/** p95 latency at 512 tokens on `threads` threads <= 40 ms. */
|
|
17
|
+
readonly latency: boolean;
|
|
18
|
+
/** steady-state marginal RSS over the process baseline <= 150 MiB. */
|
|
19
|
+
readonly rss: boolean;
|
|
20
|
+
/** the loaded model's declared opset_import equals 17. */
|
|
21
|
+
readonly opset: boolean;
|
|
22
|
+
/** SHA-256 of the embedding output identical across 3 runs (maxAbsDelta=0). */
|
|
23
|
+
readonly determinism: boolean;
|
|
24
|
+
/** conjunctive: every gate passed. */
|
|
25
|
+
readonly all: boolean;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* BenchResultV1 — one qualification run of the ONNX encoder bench.
|
|
30
|
+
*
|
|
31
|
+
* Shape is fixed by the ML5-B spec (task 4). `p95Ms`/`rssMib`/`rssMarginalMib`/
|
|
32
|
+
* `digest` are null when the runtime package is absent (degraded run) or a
|
|
33
|
+
* gate could not be measured; `gates.all` is false in that case and `error`
|
|
34
|
+
* (optional) records the honest degradation reason.
|
|
35
|
+
*/
|
|
36
|
+
export interface BenchResultV1 {
|
|
37
|
+
readonly timestamp: number;
|
|
38
|
+
/** `${process.platform}-${process.arch}` (e.g. linux-x64, darwin-arm64). */
|
|
39
|
+
readonly platform: string;
|
|
40
|
+
/** true = onnxruntime-node (native); false = onnxruntime-web (WASM). */
|
|
41
|
+
readonly encoderNative: boolean;
|
|
42
|
+
/** intraOpNumThreads used for the latency gate (normative 4). */
|
|
43
|
+
readonly threads: number;
|
|
44
|
+
/** token count per inference (normative 512). */
|
|
45
|
+
readonly tokens: number;
|
|
46
|
+
/** total tokens in the corpus the bench streamed over. */
|
|
47
|
+
readonly corpusTokens: number;
|
|
48
|
+
/** p95 latency in ms (null on degraded/absent runtime). */
|
|
49
|
+
readonly p95Ms: number | null;
|
|
50
|
+
/** steady-state RSS (MiB) over the process baseline, post-GC. */
|
|
51
|
+
readonly rssMib: number | null;
|
|
52
|
+
/** RSS (MiB) sampled at process start before loading the encoder. */
|
|
53
|
+
readonly rssBaselineMib: number | null;
|
|
54
|
+
/** rssMib - rssBaselineMib: the encoder's marginal footprint. */
|
|
55
|
+
readonly rssMarginalMib: number | null;
|
|
56
|
+
/** declared opset_import (17); null when no asset manifest is readable. */
|
|
57
|
+
readonly opset: number | null;
|
|
58
|
+
/** true when the output SHA-256 is identical across 3 runs. */
|
|
59
|
+
readonly deterministic: boolean;
|
|
60
|
+
/** SHA-256 of the embedding output buffer (null on degraded/absent). */
|
|
61
|
+
readonly digest: string | null;
|
|
62
|
+
readonly gates: BenchGatesV1;
|
|
63
|
+
/** Optional: honest degradation / failure reason (no runtime package, etc.). */
|
|
64
|
+
readonly error?: string;
|
|
65
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* vector-cortex/encoder/bench.ts — ML5-B bench runner (consumer-facing shell).
|
|
3
|
+
*
|
|
4
|
+
* Calls `scripts/ml5/bench-onnx-prod.mjs` via child_process, parses the
|
|
5
|
+
* BenchResultV1 it emits, and writes the four `vector_cortex_encoder_bench_*`
|
|
6
|
+
* events to the monitoring events.log (the dashboard / ML5-D surface consume
|
|
7
|
+
* them later). This is NOT a runtime path — it is developer/evidence tooling.
|
|
8
|
+
*
|
|
9
|
+
* Events written (all best-effort / non-fatal):
|
|
10
|
+
* - vector_cortex_encoder_bench_p95_ms
|
|
11
|
+
* - vector_cortex_encoder_bench_rss_mib
|
|
12
|
+
* - vector_cortex_encoder_bench_opset_ok
|
|
13
|
+
* - vector_cortex_encoder_bench_deterministic
|
|
14
|
+
*
|
|
15
|
+
* Pi-agnostic, dependency-free (PREVENT-PI-004 — the child bench is pure local
|
|
16
|
+
* computation). No `any` (PREVENT-011).
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import { spawnSync } from "node:child_process";
|
|
20
|
+
import { dirname, join } from "node:path";
|
|
21
|
+
import { fileURLToPath } from "node:url";
|
|
22
|
+
import { getStateDir } from "../../store.js";
|
|
23
|
+
import { defaultEventsPath, logBenchEvent } from "../../monitoring.js";
|
|
24
|
+
import type { BenchResultV1 } from "./bench-export.js";
|
|
25
|
+
|
|
26
|
+
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
27
|
+
const REPO_ROOT = join(HERE, "..", "..", "..");
|
|
28
|
+
const BENCH_SCRIPT = join(REPO_ROOT, "scripts", "ml5", "bench-onnx-prod.mjs");
|
|
29
|
+
|
|
30
|
+
/** Default events.log beside the state dir (mirrors defaultEventsPath). */
|
|
31
|
+
function benchEventsPath(stateDir: string): string {
|
|
32
|
+
return defaultEventsPath(stateDir);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Run the ONNX bench once and record its four events. Returns the parsed
|
|
37
|
+
* BenchResultV1. On any failure (script missing, non-zero exit, unparsable
|
|
38
|
+
* output) it returns a degraded result with gates.all:false — never throws, so
|
|
39
|
+
* the caller's agent loop is never broken (non-fatal store/write contract).
|
|
40
|
+
*/
|
|
41
|
+
export function runBench(stateDir: string = getStateDir()): BenchResultV1 {
|
|
42
|
+
const noop = (error: string): BenchResultV1 => ({
|
|
43
|
+
timestamp: Date.now(),
|
|
44
|
+
platform: `${process.platform}-${process.arch}`,
|
|
45
|
+
encoderNative: false,
|
|
46
|
+
threads: 4,
|
|
47
|
+
tokens: 512,
|
|
48
|
+
corpusTokens: 0,
|
|
49
|
+
p95Ms: null,
|
|
50
|
+
rssMib: null,
|
|
51
|
+
rssBaselineMib: null,
|
|
52
|
+
rssMarginalMib: null,
|
|
53
|
+
opset: null,
|
|
54
|
+
deterministic: false,
|
|
55
|
+
digest: null,
|
|
56
|
+
gates: { latency: false, rss: false, opset: false, determinism: false, all: false },
|
|
57
|
+
error,
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
const fallback = (error: string): BenchResultV1 => {
|
|
61
|
+
const r = noop(error);
|
|
62
|
+
emitEvents(stateDir, r);
|
|
63
|
+
return r;
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
try {
|
|
67
|
+
const res = spawnSync(process.execPath, ["--expose-gc", BENCH_SCRIPT], {
|
|
68
|
+
cwd: REPO_ROOT,
|
|
69
|
+
encoding: "utf8",
|
|
70
|
+
timeout: 600_000,
|
|
71
|
+
});
|
|
72
|
+
const stdout = (res.stdout ?? "").trim();
|
|
73
|
+
if (res.status === null) {
|
|
74
|
+
return fallback("bench timed out or failed to spawn");
|
|
75
|
+
}
|
|
76
|
+
const parsed: unknown = JSON.parse(stdout || "");
|
|
77
|
+
if (!isBenchResultV1(parsed)) {
|
|
78
|
+
return fallback("bench output was not a BenchResultV1");
|
|
79
|
+
}
|
|
80
|
+
emitEvents(stateDir, parsed);
|
|
81
|
+
return parsed;
|
|
82
|
+
} catch (e) {
|
|
83
|
+
return fallback(`bench failed: ${(e as Error)?.message ?? String(e)}`);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function isBenchResultV1(v: unknown): v is BenchResultV1 {
|
|
88
|
+
if (typeof v !== "object" || v === null) return false;
|
|
89
|
+
const o = v as Record<string, unknown>;
|
|
90
|
+
return (
|
|
91
|
+
typeof o.timestamp === "number" &&
|
|
92
|
+
typeof o.platform === "string" &&
|
|
93
|
+
typeof o.encoderNative === "boolean" &&
|
|
94
|
+
typeof o.threads === "number" &&
|
|
95
|
+
typeof o.tokens === "number" &&
|
|
96
|
+
typeof o.corpusTokens === "number" &&
|
|
97
|
+
typeof o.gates === "object" && o.gates !== null &&
|
|
98
|
+
typeof (o.gates as Record<string, unknown>).all === "boolean"
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function emitEvents(stateDir: string, r: BenchResultV1): void {
|
|
103
|
+
const path = benchEventsPath(stateDir);
|
|
104
|
+
const run = { platform: r.platform, encoderNative: r.encoderNative, threads: r.threads, tokens: r.tokens, digest: r.digest, corpusTokens: r.corpusTokens };
|
|
105
|
+
logBenchEvent(path, "vector_cortex_encoder_bench_p95_ms", { ...run, p95Ms: r.p95Ms, pass: r.gates.latency });
|
|
106
|
+
logBenchEvent(path, "vector_cortex_encoder_bench_rss_mib", { ...run, rssMib: r.rssMib, rssBaselineMib: r.rssBaselineMib, rssMarginalMib: r.rssMarginalMib, pass: r.gates.rss });
|
|
107
|
+
logBenchEvent(path, "vector_cortex_encoder_bench_opset_ok", { ...run, opset: r.opset, pass: r.gates.opset });
|
|
108
|
+
logBenchEvent(path, "vector_cortex_encoder_bench_deterministic", { ...run, deterministic: r.deterministic, pass: r.gates.determinism });
|
|
109
|
+
}
|
|
@@ -23,6 +23,8 @@
|
|
|
23
23
|
*/
|
|
24
24
|
|
|
25
25
|
import { createHash } from "node:crypto";
|
|
26
|
+
import { readFileSync } from "node:fs";
|
|
27
|
+
import { ML5A_ENABLED } from "../../config/vector-cortex.js";
|
|
26
28
|
import {
|
|
27
29
|
ENCODER_HEAD_ORDER,
|
|
28
30
|
ENCODER_SEED,
|
|
@@ -223,3 +225,50 @@ export function fitCalibration(
|
|
|
223
225
|
};
|
|
224
226
|
return { ok: true, calibration };
|
|
225
227
|
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Load a persisted `CalibrationV1` artifact (schema "calibration-v1") from disk.
|
|
231
|
+
* ML5-A: gated on MEGACOMPACT_ML5_A; flag-off, absent file, malformed JSON,
|
|
232
|
+
* wrong schema, non-canonical five-head order, or non-finite temp/threshold each
|
|
233
|
+
* return null (non-fatal, never throws). Deterministic, local (PREVENT-PI-004).
|
|
234
|
+
*/
|
|
235
|
+
export function loadCalibrationV1(path: string): CalibrationV1 | null {
|
|
236
|
+
if (!ML5A_ENABLED()) return null;
|
|
237
|
+
let raw: string;
|
|
238
|
+
try {
|
|
239
|
+
raw = readFileSync(path, "utf8");
|
|
240
|
+
} catch {
|
|
241
|
+
return null;
|
|
242
|
+
}
|
|
243
|
+
let parsed: unknown;
|
|
244
|
+
try {
|
|
245
|
+
parsed = JSON.parse(raw);
|
|
246
|
+
} catch {
|
|
247
|
+
return null;
|
|
248
|
+
}
|
|
249
|
+
const r = parsed as Record<string, unknown> | null;
|
|
250
|
+
if (!r || r["schema"] !== "calibration-v1") return null;
|
|
251
|
+
const order = r["headOrder"];
|
|
252
|
+
if (!Array.isArray(order)) return null;
|
|
253
|
+
if (order.length !== ENCODER_HEAD_ORDER.length || !ENCODER_HEAD_ORDER.every((h, i) => order[i] === h)) {
|
|
254
|
+
return null;
|
|
255
|
+
}
|
|
256
|
+
const temperatures = r["temperatures"] as Record<string, unknown> | undefined;
|
|
257
|
+
const thresholds = r["thresholds"] as Record<string, unknown> | undefined;
|
|
258
|
+
const splitDigest = r["calibrationSplitDigest"];
|
|
259
|
+
if (!temperatures || !thresholds || typeof splitDigest !== "string" || splitDigest.length !== 64) return null;
|
|
260
|
+
for (const h of ENCODER_HEAD_ORDER) {
|
|
261
|
+
const t = Number(temperatures[h]);
|
|
262
|
+
const th = Number(thresholds[h]);
|
|
263
|
+
if (!Number.isFinite(t) || !Number.isFinite(th)) return null;
|
|
264
|
+
}
|
|
265
|
+
return {
|
|
266
|
+
schema: "calibration-v1",
|
|
267
|
+
headOrder: [...ENCODER_HEAD_ORDER],
|
|
268
|
+
calibrationSplitDigest: splitDigest,
|
|
269
|
+
fittedOnCalibrationOnly: true,
|
|
270
|
+
temperatures: { ...(temperatures as Record<EncoderHeadName, number>) },
|
|
271
|
+
thresholds: { ...(thresholds as Record<EncoderHeadName, number>) },
|
|
272
|
+
seed: Number(r["seed"] ?? ENCODER_SEED),
|
|
273
|
+
};
|
|
274
|
+
}
|
|
@@ -20,6 +20,8 @@
|
|
|
20
20
|
* Pi-agnostic, zero network (PREVENT-PI-004), no `any` (PREVENT-011).
|
|
21
21
|
*/
|
|
22
22
|
|
|
23
|
+
import { readFileSync } from "node:fs";
|
|
24
|
+
import { ML5A_ENABLED } from "../../config/vector-cortex.js";
|
|
23
25
|
import {
|
|
24
26
|
ENCODER_HEAD_DIMS,
|
|
25
27
|
ENCODER_HEAD_ORDER,
|
|
@@ -139,4 +141,108 @@ export function headLossWeights(): Readonly<Record<EncoderHeadName, number>> {
|
|
|
139
141
|
return { ...ENCODER_HEAD_LOSS_WEIGHTS };
|
|
140
142
|
}
|
|
141
143
|
|
|
144
|
+
// ---------------------------------------------------------------------------
|
|
145
|
+
// ML5-A real trained-head loading. Produces the projection matrices that the
|
|
146
|
+
// deterministic placeholder `projectHead` approximates: row-major
|
|
147
|
+
// `weights[h]` of length `headDim * trunkDim`, applied `W[i*t+j]*trunk[j]`,
|
|
148
|
+
// L2-normalized. Loaded from the `trained-heads-v1` JSON that
|
|
149
|
+
// `training/vector-cortex/train.py` emits, under the MEGACOMPACT_ML5_A gate.
|
|
150
|
+
// Non-fatal: any violation (flag off, absent, malformed, wrong seed, wrong
|
|
151
|
+
// shape) yields null, never a throw (all loaders return null on violation).
|
|
152
|
+
// ---------------------------------------------------------------------------
|
|
153
|
+
|
|
154
|
+
/** The real loadable form of the trained five-head projection table. */
|
|
155
|
+
export interface HeadProjectionTable {
|
|
156
|
+
readonly schema: "trained-heads-v1";
|
|
157
|
+
readonly seed: number;
|
|
158
|
+
/** Input/trunk embedding dimension every head projects from (uniform 384). */
|
|
159
|
+
readonly trunkDim: number;
|
|
160
|
+
/** Per-head OUTPUT dimension (semantic 384 / ... / payloadRouting 32). */
|
|
161
|
+
readonly dims: Readonly<Record<EncoderHeadName, number>>;
|
|
162
|
+
/** Row-major `[headDim * trunkDim]` projection matrix per head. */
|
|
163
|
+
readonly weights: Readonly<Record<EncoderHeadName, Float32Array>>;
|
|
164
|
+
readonly temperatures: Readonly<Record<EncoderHeadName, number>>;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/** True when every head's output dim + weight length matches the contract. */
|
|
168
|
+
export function headsShapeValid(t: HeadProjectionTable): boolean {
|
|
169
|
+
return ENCODER_HEAD_ORDER.every(
|
|
170
|
+
(h) => t.dims[h] === ENCODER_HEAD_DIMS[h] && t.weights[h].length === ENCODER_HEAD_DIMS[h] * t.trunkDim,
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Load a `trained-heads-v1` artifact into a `HeadProjectionTable`. Gated on
|
|
176
|
+
* MEGACOMPACT_ML5_A: flag-off, absent file, malformed JSON, wrong schema,
|
|
177
|
+
* wrong seed, or a shape mismatch each return null (non-fatal). Deterministic
|
|
178
|
+
* and local (PREVENT-PI-004).
|
|
179
|
+
*/
|
|
180
|
+
export function loadHeadProjections(path: string): HeadProjectionTable | null {
|
|
181
|
+
if (!ML5A_ENABLED()) return null;
|
|
182
|
+
let raw: string;
|
|
183
|
+
try {
|
|
184
|
+
raw = readFileSync(path, "utf8");
|
|
185
|
+
} catch {
|
|
186
|
+
return null;
|
|
187
|
+
}
|
|
188
|
+
let parsed: unknown;
|
|
189
|
+
try {
|
|
190
|
+
parsed = JSON.parse(raw);
|
|
191
|
+
} catch {
|
|
192
|
+
return null;
|
|
193
|
+
}
|
|
194
|
+
const r = parsed as Record<string, unknown> | null;
|
|
195
|
+
if (!r || r["schema"] !== "trained-heads-v1") return null;
|
|
196
|
+
if (r["seed"] !== ENCODER_SEED) return null;
|
|
197
|
+
const dims = r["dims"] as Record<string, unknown> | undefined;
|
|
198
|
+
const heads = r["heads"] as Record<string, unknown> | undefined;
|
|
199
|
+
if (!dims || !heads) return null;
|
|
200
|
+
const trunkDim = Number(r["trunkDim"] ?? 0);
|
|
201
|
+
if (!Number.isFinite(trunkDim) || trunkDim <= 0) return null;
|
|
202
|
+
const weights: Record<string, Float32Array> = {};
|
|
203
|
+
const temperatures: Record<string, number> = {};
|
|
204
|
+
for (const h of ENCODER_HEAD_ORDER) {
|
|
205
|
+
const hd = heads[h] as Record<string, unknown> | undefined;
|
|
206
|
+
if (!hd || typeof hd !== "object") return null;
|
|
207
|
+
const w = hd["weights"];
|
|
208
|
+
if (!Array.isArray(w)) return null;
|
|
209
|
+
weights[h] = Float32Array.from(w as number[]);
|
|
210
|
+
if (Number(hd["dim"] ?? 0) !== ENCODER_HEAD_DIMS[h]) return null;
|
|
211
|
+
temperatures[h] = Number(hd["temperature"] ?? 1);
|
|
212
|
+
if (!Number.isFinite(dims[h])) return null;
|
|
213
|
+
}
|
|
214
|
+
const table: HeadProjectionTable = {
|
|
215
|
+
schema: "trained-heads-v1",
|
|
216
|
+
seed: Number(r["seed"]),
|
|
217
|
+
trunkDim,
|
|
218
|
+
dims: { semantic: 384, dependency: 128, contradiction: 128, cacheStability: 64, payloadRouting: 32 } as unknown as Record<EncoderHeadName, number>,
|
|
219
|
+
weights: weights as unknown as Record<EncoderHeadName, Float32Array>,
|
|
220
|
+
temperatures: temperatures as unknown as Record<EncoderHeadName, number>,
|
|
221
|
+
};
|
|
222
|
+
if (!headsShapeValid(table)) return null;
|
|
223
|
+
return table;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Project a trunk embedding through a trained head's real weights, applying the
|
|
228
|
+
* row-major matrix then L2-normalizing (all-zero on zero norm). Returns a
|
|
229
|
+
* `HeadVector` of the head's normative dimension.
|
|
230
|
+
*/
|
|
231
|
+
export function projectHeadFromTrunk(
|
|
232
|
+
head: EncoderHeadName,
|
|
233
|
+
trunk: Float32Array,
|
|
234
|
+
table: HeadProjectionTable,
|
|
235
|
+
): HeadVector {
|
|
236
|
+
const dim = ENCODER_HEAD_DIMS[head];
|
|
237
|
+
const W = table.weights[head];
|
|
238
|
+
const t = table.trunkDim;
|
|
239
|
+
const out = new Float32Array(dim);
|
|
240
|
+
for (let i = 0; i < dim; i++) {
|
|
241
|
+
let acc = 0;
|
|
242
|
+
for (let j = 0; j < t; j++) acc += W[i * t + j]! * (trunk[j] ?? 0);
|
|
243
|
+
out[i] = acc;
|
|
244
|
+
}
|
|
245
|
+
return { head, dim, values: l2Normalize(out) };
|
|
246
|
+
}
|
|
247
|
+
|
|
142
248
|
export { ENCODER_HEAD_ORDER, ENCODER_HEAD_DIMS, ENCODER_HEAD_LOSS_SUM, ENCODER_SEED, NOOP_VC2B_REPORTER };
|
|
@@ -34,6 +34,8 @@
|
|
|
34
34
|
*/
|
|
35
35
|
|
|
36
36
|
import { createHash } from "node:crypto";
|
|
37
|
+
import { ML5A_ENABLED } from "../../config/vector-cortex.js";
|
|
38
|
+
import { loadHeadProjections } from "./heads.js";
|
|
37
39
|
import {
|
|
38
40
|
ENC_QUALIFICATION_FAIL,
|
|
39
41
|
EVALUATION_THRESHOLDS,
|
|
@@ -83,6 +85,13 @@ export interface QualificationCandidate {
|
|
|
83
85
|
* corrupt-qualification-manifest injection (ENC_QUALIFICATION_DIGEST_MISMATCH).
|
|
84
86
|
*/
|
|
85
87
|
readonly expectedQualificationManifestDigest?: string;
|
|
88
|
+
/**
|
|
89
|
+
* ML5-A: path to the `trained-heads-v1` artifact. When supplied AND the
|
|
90
|
+
* MEGACOMPACT_ML5_A gate is on, the candidate qualifies to mode A only if the
|
|
91
|
+
* real trained head weights load; a load failure atomically demotes all of A
|
|
92
|
+
* to B (non-fatal, reported as THRESHOLD_FAILED / head.weights.trainedHeadsPath).
|
|
93
|
+
*/
|
|
94
|
+
readonly trainedHeadsPath?: string;
|
|
86
95
|
}
|
|
87
96
|
|
|
88
97
|
/**
|
|
@@ -183,6 +192,14 @@ export function selectQualifiedEncoder(
|
|
|
183
192
|
|
|
184
193
|
// Atomic: collect EVERY failed field across asset + all heads + reconstruction.
|
|
185
194
|
const failed: string[] = [];
|
|
195
|
+
// ML5-A: real trained-head weights must load for mode A. When the gate is on
|
|
196
|
+
// and a trained-heads path is pinned, an unloadable/wrong-seed/malformed
|
|
197
|
+
// artifact is a qualification failure (any failed field demotes ALL of A).
|
|
198
|
+
if (ML5A_ENABLED() && candidate.trainedHeadsPath !== undefined) {
|
|
199
|
+
if (loadHeadProjections(candidate.trainedHeadsPath) === null) {
|
|
200
|
+
failed.push("head.weights.trainedHeadsPath");
|
|
201
|
+
}
|
|
202
|
+
}
|
|
186
203
|
assetPasses(candidate.asset, failed);
|
|
187
204
|
const heads: EncoderHeadName[] = ["semantic", "dependency", "contradiction", "cacheStability", "payloadRouting"];
|
|
188
205
|
for (const h of heads) {
|