pi-mega-compact 0.20.36 → 0.20.39
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-ml5b.js +26 -0
- package/dist/config/vector-cortex-ml5c.js +28 -0
- package/dist/config/vector-cortex.js +3 -1
- 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-ml5b.js +26 -0
- package/dist/src/config/vector-cortex-ml5c.js +28 -0
- package/dist/src/config/vector-cortex.js +3 -1
- package/dist/src/config.js +1 -1
- package/dist/src/monitoring.js +19 -0
- package/dist/src/store/backfill.js +0 -8
- 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/runtime-emit.js +42 -0
- package/dist/src/vector-cortex/encoder/runtime-native.js +77 -0
- package/dist/src/vector-cortex/encoder/runtime-select.js +122 -0
- package/dist/src/vector-cortex/encoder/runtime-stub.js +35 -0
- package/dist/src/vector-cortex/encoder/runtime-wasm.js +71 -0
- package/dist/src/vector-cortex/encoder/runtime.js +49 -61
- package/dist/vector-cortex/encoder/bench-export.js +13 -0
- package/dist/vector-cortex/encoder/bench.js +100 -0
- package/dist/vector-cortex/encoder/runtime-emit.js +42 -0
- package/dist/vector-cortex/encoder/runtime-native.js +77 -0
- package/dist/vector-cortex/encoder/runtime-select.js +122 -0
- package/dist/vector-cortex/encoder/runtime-stub.js +35 -0
- package/dist/vector-cortex/encoder/runtime-wasm.js +71 -0
- package/dist/vector-cortex/encoder/runtime.js +49 -61
- 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-ml5b.ts +28 -0
- package/src/config/vector-cortex-ml5c.ts +30 -0
- package/src/config/vector-cortex.ts +3 -2
- package/src/config.ts +2 -0
- package/src/monitoring.ts +24 -0
- package/src/store/backfill.ts +0 -6
- package/src/vector-cortex/encoder/bench-export.ts +65 -0
- package/src/vector-cortex/encoder/bench.ts +109 -0
- package/src/vector-cortex/encoder/runtime-emit.ts +47 -0
- package/src/vector-cortex/encoder/runtime-native.ts +117 -0
- package/src/vector-cortex/encoder/runtime-select.ts +167 -0
- package/src/vector-cortex/encoder/runtime-stub.ts +38 -0
- package/src/vector-cortex/encoder/runtime-wasm.ts +110 -0
- package/src/vector-cortex/encoder/runtime.ts +59 -66
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* config/vector-cortex-ml5b.ts — ML5-B production bench harness flag.
|
|
3
|
+
*
|
|
4
|
+
* Sibling extract mirroring vector-cortex-ml5a.ts, so vector-cortex.ts stays
|
|
5
|
+
* under its 300-line soft limit (soft-as-hard gate). This is the ONNX Runtime
|
|
6
|
+
* evaluation/benchmark sprint flag. vector-cortex.ts re-exports the ENUM below
|
|
7
|
+
* and root src/config.ts re-exports it, so no consumer import path changes.
|
|
8
|
+
*
|
|
9
|
+
* ML5-B introduces NO runtime code path: the bench harness and corpus export
|
|
10
|
+
* are developer/evidence tooling (scripts/) plus a consumer-facing TypeScript
|
|
11
|
+
* shell (src/vector-cortex/encoder/bench.ts) that only writes monitoring
|
|
12
|
+
* events. The flag records intent and scopes the sprint's evidence assets; it
|
|
13
|
+
* gates nothing at runtime today. There is no HTTP endpoint and no dashboard
|
|
14
|
+
* change, so there is no SETTINGS toggle and no EXCLUDED_SETTINGS interaction.
|
|
15
|
+
*
|
|
16
|
+
* Pi-agnostic, dependency-free (PREVENT-PI-004 / PREVENT-011).
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import { sprintFlag } from "./vector-cortex-flag.js";
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* ML5-B — production bench harness (ONNX Runtime eval). Default ON.
|
|
23
|
+
* `MEGACOMPACT_ML5_B=0` disables and is byte-identical to the ML5-A survivor:
|
|
24
|
+
* no bench endpoint exists and mode B continues to serve all clients exactly as
|
|
25
|
+
* before. The flag does not gate the harness itself — the harness is an on-demand
|
|
26
|
+
* developer tool with no runtime path.
|
|
27
|
+
*/
|
|
28
|
+
export const ML5B_ENABLED = (): boolean => sprintFlag("MEGACOMPACT_ML5_B");
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* config/vector-cortex-ml5c.ts — ML5-C runtime decision + packaging flag.
|
|
3
|
+
*
|
|
4
|
+
* Sibling extract mirroring vector-cortex-ml5a.ts / vector-cortex-ml5b.ts, so
|
|
5
|
+
* vector-cortex.ts stays under its 300-line soft limit (soft-as-hard gate).
|
|
6
|
+
* This is the ONNX Runtime backend selection + packaging sprint flag.
|
|
7
|
+
* vector-cortex.ts re-exports the ENUM below and root src/config.ts re-exports
|
|
8
|
+
* it, so no consumer import path changes.
|
|
9
|
+
*
|
|
10
|
+
* ML5-C selects the ONNX runtime backend (WASM vs native) based on the ML5-B
|
|
11
|
+
* bench record and platform support. The flag gates the runtime-selection
|
|
12
|
+
* dispatch only; when OFF the encoder serves mode B trigram exactly as before
|
|
13
|
+
* (byte-identical to the ML5-B survivor — no `vector_cortex_runtime_selected`
|
|
14
|
+
* event is emitted, no session-selection dispatch runs).
|
|
15
|
+
*
|
|
16
|
+
* Pi-agnostic, dependency-free (PREVENT-PI-004 / PREVENT-011).
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import { sprintFlag } from "./vector-cortex-flag.js";
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* ML5-C — runtime decision + packaging (WASM vs native). Default ON.
|
|
23
|
+
* `MEGACOMPACT_ML5_C=0` disables and is byte-identical to the ML5-B survivor:
|
|
24
|
+
* no runtime selection runs — the encoder continues to serve mode B trigram,
|
|
25
|
+
* exactly as before, with no `vector_cortex_runtime_selected` event emitted.
|
|
26
|
+
* The flag gates the runtime-selection dispatch only; it does not gate the
|
|
27
|
+
* underlying WASM/native backends (which are exercised by ML5-B's bench
|
|
28
|
+
* harness and ML5-A's trained asset independently).
|
|
29
|
+
*/
|
|
30
|
+
export const ML5C_ENABLED = (): boolean => sprintFlag("MEGACOMPACT_ML5_C");
|
|
@@ -272,14 +272,15 @@ export const VC8B_ENABLED = (): boolean => sprintFlag("MEGACOMPACT_VC8B");
|
|
|
272
272
|
// VC8C (canary selection + Rust parity) extracted to vector-cortex-vc8c.ts;
|
|
273
273
|
// re-exported so existing `./config/vector-cortex.js` imports keep resolving.
|
|
274
274
|
export { VC8C_ENABLED } from "./vector-cortex-vc8c.js";
|
|
275
|
-
|
|
276
|
-
// VC9A/VC9B/VC9C/VC9D/PCC/ML5A split to sibling files to stay under the 300-line soft limit.
|
|
275
|
+
// Sibling extracts to stay under the 300-line soft limit.
|
|
277
276
|
export { VC9A_ENABLED } from "./vector-cortex-vc9a.js";
|
|
278
277
|
export { VC9B_ENABLED } from "./vector-cortex-vc9b.js";
|
|
279
278
|
export { VC9C_ENABLED } from "./vector-cortex-vc9c.js";
|
|
280
279
|
export { VC9D_ENABLED } from "./vector-cortex-vc9d.js";
|
|
281
280
|
export { PCC_ENABLED } from "./vector-cortex-pcc.js";
|
|
282
281
|
export { ML5A_ENABLED } from "./vector-cortex-ml5a.js";
|
|
282
|
+
export { ML5B_ENABLED } from "./vector-cortex-ml5b.js";
|
|
283
|
+
export { ML5C_ENABLED } from "./vector-cortex-ml5c.js";
|
|
283
284
|
|
|
284
285
|
// Breaker constants (TRIAD_RESILIENCE.md §breaker) extracted to vector-cortex-breakers.ts.
|
|
285
286
|
export {
|
package/src/config.ts
CHANGED
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
|
@@ -28,7 +28,6 @@ import { defaultEmbedder } from "../embedder.js";
|
|
|
28
28
|
import { getStateDir } from "../store.js";
|
|
29
29
|
|
|
30
30
|
const BATCH = 1000;
|
|
31
|
-
const THROTTLE_MS = 0; // synchronous backfill; no cross-process yield needed
|
|
32
31
|
|
|
33
32
|
/** Backfill phases, in order (Sprint 14 full-pipeline wiring). */
|
|
34
33
|
export type BackfillPhase = "L0" | "L1" | "L2" | "RAPTOR";
|
|
@@ -132,10 +131,6 @@ export function backfillContentHashes(stateDir: string = getStateDir()): Backfil
|
|
|
132
131
|
).run(lastSid, lastId, updated, duplicatesResolved);
|
|
133
132
|
}
|
|
134
133
|
|
|
135
|
-
if (THROTTLE_MS > 0) {
|
|
136
|
-
// No-op in this synchronous build; placeholder for future streaming backfill.
|
|
137
|
-
}
|
|
138
|
-
|
|
139
134
|
return { processed, updated, duplicatesResolved };
|
|
140
135
|
}
|
|
141
136
|
|
|
@@ -219,7 +214,6 @@ export function backfillPhase(
|
|
|
219
214
|
});
|
|
220
215
|
savePhaseCursor(db, phase, cursor ?? null, processed);
|
|
221
216
|
batches++;
|
|
222
|
-
if (THROTTLE_MS > 0) { const end = Date.now() + THROTTLE_MS; while (Date.now() < end) { /* throttle */ } }
|
|
223
217
|
if (opts.interruptAfterBatches && batches >= opts.interruptAfterBatches) {
|
|
224
218
|
interrupted = true;
|
|
225
219
|
break;
|
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* vector-cortex/encoder/runtime-emit.ts — ML5-C seller event emitter.
|
|
3
|
+
*
|
|
4
|
+
* Emits the `vector_cortex_runtime_selected` seller event to the local
|
|
5
|
+
* events.log so the dashboard Setup Cortex blockers card can surface the HG-3
|
|
6
|
+
* (install budget) / HG-4 (darwin-x64 demotion) closure state. Aggregate
|
|
7
|
+
* fields only — never payload bytes (EVAL-REDACT-002).
|
|
8
|
+
*
|
|
9
|
+
* Extracted from runtime.ts so the runtime delegate-shell stays under the
|
|
10
|
+
* 300-line soft limit after the ML5-C dispatch was added. All writes are
|
|
11
|
+
* best-effort / non-fatal; a disk-full or missing state dir never breaks the
|
|
12
|
+
* encoder loop.
|
|
13
|
+
*
|
|
14
|
+
* Pi-agnostic, dependency-free (PREVENT-PI-004 — local filesystem append only;
|
|
15
|
+
* no network). No `any` (PREVENT-011).
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { appendFileSync, mkdirSync } from "node:fs";
|
|
19
|
+
import { dirname } from "node:path";
|
|
20
|
+
import { defaultEventsPath } from "../../monitoring.js";
|
|
21
|
+
import type { RuntimeSelectionResult } from "./runtime-select.js";
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Emit the ML5-C `vector_cortex_runtime_selected` seller event (best-effort).
|
|
25
|
+
* The event carries ONLY the four aggregate fields the sprint spec pins
|
|
26
|
+
* ({backend, p95Ms, budgetOk, platform}) — never message content.
|
|
27
|
+
*/
|
|
28
|
+
export function emitRuntimeSelected(
|
|
29
|
+
stateDir: string,
|
|
30
|
+
result: Pick<RuntimeSelectionResult, "backend" | "p95Ms" | "budgetOk" | "platform">,
|
|
31
|
+
): void {
|
|
32
|
+
try {
|
|
33
|
+
const path = defaultEventsPath(stateDir);
|
|
34
|
+
const payload = {
|
|
35
|
+
ts: Date.now(),
|
|
36
|
+
event: "vector_cortex_runtime_selected",
|
|
37
|
+
backend: result.backend,
|
|
38
|
+
p95Ms: result.p95Ms,
|
|
39
|
+
budgetOk: result.budgetOk,
|
|
40
|
+
platform: result.platform,
|
|
41
|
+
};
|
|
42
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
43
|
+
appendFileSync(path, JSON.stringify(payload) + "\n", "utf8");
|
|
44
|
+
} catch {
|
|
45
|
+
/* best-effort — never break the encoder loop */
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* vector-cortex/encoder/runtime-native.ts — ML5-C native backend (Option N).
|
|
3
|
+
*
|
|
4
|
+
* Loads an `InferenceSession` from the `onnxruntime-node` native binding for
|
|
5
|
+
* the committed encoder-v1 ONNX asset. This is the CHOSEN selection when
|
|
6
|
+
* `MEGACOMPACT_ENCODER_NATIVE=1` (the native opt-in marker) is set AND the
|
|
7
|
+
* package is present — it uses the platform-specific prebuilt binary (no
|
|
8
|
+
* postinstall compilation needed; per vc2-model-prep §1 the allowScripts
|
|
9
|
+
* removal is safe because only CUDA/TensorRT downloads use it, and pi blocks
|
|
10
|
+
* all scripts anyway).
|
|
11
|
+
*
|
|
12
|
+
* The package is NOT declared in package.json dependencies — it is a lazily-
|
|
13
|
+
* resolved peer that the runtime loads ONLY when the native opt-in is set AND
|
|
14
|
+
* selected. Loading uses dynamic `import()` so the module graph compiles
|
|
15
|
+
* cleanly on hosts without the package (absent installs return null, never
|
|
16
|
+
* throw), so the ML5-C dispatch demotes to mode B trigram rather than
|
|
17
|
+
* breaking.
|
|
18
|
+
*
|
|
19
|
+
* Pi-agnostic, dependency-free (PREVENT-PI-004 — the native binary + model are
|
|
20
|
+
* committed local files). No `any` (PREVENT-011).
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
import {
|
|
24
|
+
ENCODER_OPSET,
|
|
25
|
+
ENCODER_SEMANTIC_WIDTH,
|
|
26
|
+
ENCODER_MAX_TOKENS,
|
|
27
|
+
} from "./types.js";
|
|
28
|
+
|
|
29
|
+
/** The shape of the optionalImport result when onnxruntime-node is present.
|
|
30
|
+
* Shadow-types instead of `import("onnxruntime-node")` so the module graph
|
|
31
|
+
* builds without the package being declared in package.json (ML5-B precedent). */
|
|
32
|
+
export interface OrtNativeModule {
|
|
33
|
+
InferenceSession: {
|
|
34
|
+
create(
|
|
35
|
+
path: string,
|
|
36
|
+
opts: { executionProviders: string[]; intraOpNumThreads: number },
|
|
37
|
+
): Promise<{
|
|
38
|
+
run(
|
|
39
|
+
feeds: Record<string, Float32Array>,
|
|
40
|
+
outputNames: string[],
|
|
41
|
+
): Promise<Record<string, { data: Float32Array }>>;
|
|
42
|
+
}>;
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** The backend's inference session — a thin wrapper over the real native session. */
|
|
47
|
+
export interface NativeSession {
|
|
48
|
+
/** The declared ONNX opset in the loaded manifest (normative 17). */
|
|
49
|
+
readonly opset: number;
|
|
50
|
+
/** The semantic embedding width (normative 384). */
|
|
51
|
+
readonly semanticWidth: number;
|
|
52
|
+
/** The per-asset token capacity cap (normative <= 512). */
|
|
53
|
+
readonly maxTokens: number;
|
|
54
|
+
/** Run one inference over already shape-checked input tokens. */
|
|
55
|
+
infer(inputIds: Float32Array): Promise<Float32Array>;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** True when `MEGACOMPACT_ENCODER_NATIVE=1` (the native opt-in operator flag). */
|
|
59
|
+
export function nativeOptIn(): boolean {
|
|
60
|
+
return process.env.MEGACOMPACT_ENCODER_NATIVE === "1";
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** True if `onnxruntime-node` resolves on this host (loading is best-effort).
|
|
64
|
+
* Absent installs return null (never throw) so the ML5-C dispatch can demote
|
|
65
|
+
* to mode B trigram cleanly. */
|
|
66
|
+
async function loadOrtNative(): Promise<OrtNativeModule | null> {
|
|
67
|
+
try {
|
|
68
|
+
// @ts-expect-error — optional peer; the shadow type above covers the surface
|
|
69
|
+
const mod = (await import("onnxruntime-node")) as OrtNativeModule;
|
|
70
|
+
return mod;
|
|
71
|
+
} catch {
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Create a native-backed `NativeSession` over the committed ONNX asset, gated
|
|
78
|
+
* first on `nativeOptIn()`. Returns null (never throws) on any failure
|
|
79
|
+
* (opt-in off, absent package, unreadable asset, bad session creation) so the
|
|
80
|
+
* caller demotes to mode B trigram.
|
|
81
|
+
*/
|
|
82
|
+
export async function createNativeSession(
|
|
83
|
+
modelPath: string,
|
|
84
|
+
options: { threads?: number; maxTokens?: number } = {},
|
|
85
|
+
): Promise<NativeSession | null> {
|
|
86
|
+
if (!nativeOptIn()) return null;
|
|
87
|
+
|
|
88
|
+
const ort = await loadOrtNative();
|
|
89
|
+
if (!ort || !ort.InferenceSession?.create) return null;
|
|
90
|
+
|
|
91
|
+
const threads = options.threads ?? 4;
|
|
92
|
+
const maxTokens = options.maxTokens ?? ENCODER_MAX_TOKENS;
|
|
93
|
+
|
|
94
|
+
try {
|
|
95
|
+
const session = await ort.InferenceSession.create(modelPath, {
|
|
96
|
+
executionProviders: ["cpu"],
|
|
97
|
+
intraOpNumThreads: threads,
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
return {
|
|
101
|
+
opset: ENCODER_OPSET,
|
|
102
|
+
semanticWidth: ENCODER_SEMANTIC_WIDTH,
|
|
103
|
+
maxTokens,
|
|
104
|
+
async infer(inputIds: Float32Array): Promise<Float32Array> {
|
|
105
|
+
const feeds = { input_ids: inputIds };
|
|
106
|
+
const results = await session.run(feeds, ["embedding"]);
|
|
107
|
+
const out = results["embedding"];
|
|
108
|
+
if (!out || !(out.data instanceof Float32Array)) {
|
|
109
|
+
return new Float32Array(ENCODER_SEMANTIC_WIDTH);
|
|
110
|
+
}
|
|
111
|
+
return out.data;
|
|
112
|
+
},
|
|
113
|
+
};
|
|
114
|
+
} catch {
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* vector-cortex/encoder/runtime-select.ts — ML5-C decision-rule dispatch.
|
|
3
|
+
*
|
|
4
|
+
* Pure function of {platform, benchRecord, nativeOptIn} → the chosen ONNX
|
|
5
|
+
* runtime backend. This is the deterministic selection that closes HG-3
|
|
6
|
+
* (install budget) and HG-4 (darwin-x64 disposition) per the ML5-C spec:
|
|
7
|
+
*
|
|
8
|
+
* - Measured p95 at 512 tokens on 4 threads (linux-x64) <= 40 ms → Option W (WASM)
|
|
9
|
+
* - Measured p95 > 40 ms or absent (degraded) → Option N (native)
|
|
10
|
+
* - Platform is darwin-x64 (Intel Mac, HG-1 deferral) → WASM demotion or mode B
|
|
11
|
+
*
|
|
12
|
+
* The `platform` comes from `detectPlatform()` (already in `asset.ts`); the
|
|
13
|
+
* `benchRecord` is the latest `BenchResultV1` (from ML5-B / JSONL); the
|
|
14
|
+
* `nativeOptIn` helper reads `MEGACOMPACT_ENCODER_NATIVE=1`. The selection is
|
|
15
|
+
* PURE — no side effects, no I/O, no network. The caller stamps the result on
|
|
16
|
+
* the `vector_cortex_runtime_selected` event carried to the dashboard.
|
|
17
|
+
*
|
|
18
|
+
* Pi-agnostic, dependency-free (PREVENT-PI-004 — selection is computed+in memory;
|
|
19
|
+
* no fetch/HTTP). No `any` (PREVENT-011).
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
import {
|
|
23
|
+
ENCODER_LATENCY_P95_MS,
|
|
24
|
+
type EncoderPlatform,
|
|
25
|
+
} from "./types.js";
|
|
26
|
+
import type { BenchResultV1 } from "./bench-export.js";
|
|
27
|
+
import { ML5C_ENABLED } from "../../config/vector-cortex.js";
|
|
28
|
+
|
|
29
|
+
/** The chosen runtime backend name as it appears on the seller event. */
|
|
30
|
+
export type RuntimeBackendChoice = "wasm" | "native" | "modeB";
|
|
31
|
+
|
|
32
|
+
/** Inputs to the backend selection. `platform` is `${process.platform}-${process.arch}`. */
|
|
33
|
+
export interface RuntimeSelectionInput {
|
|
34
|
+
/** The platform this process is running on (from detectPlatform()). */
|
|
35
|
+
readonly platform: EncoderPlatform | "unsupported";
|
|
36
|
+
/**
|
|
37
|
+
* The ML5-B bench record for this platform (linux-x64 normative). Null when
|
|
38
|
+
* no bench has run yet (degraded: the 42-byte placeholder has no real p95).
|
|
39
|
+
* Must provide a numeric p95Ms for WASM consideration; a null/error record
|
|
40
|
+
* (gates.all:false) forces the native fallback per the decision rule.
|
|
41
|
+
*/
|
|
42
|
+
readonly benchRecord: BenchResultV1 | null;
|
|
43
|
+
/** True when the operator opted into onnxruntime-node via MEGACOMPACT_ENCODER_NATIVE=1. */
|
|
44
|
+
readonly nativeOptIn: boolean;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** The output of the ML5-C decision-rule dispatch. */
|
|
48
|
+
export interface RuntimeSelectionResult {
|
|
49
|
+
/** The chosen backend. */
|
|
50
|
+
readonly backend: RuntimeBackendChoice;
|
|
51
|
+
/** HG-3 closure state: whether the 80 MiB install budget is satisfied. */
|
|
52
|
+
readonly budgetOk: boolean;
|
|
53
|
+
/** The measured p95 (ms) that drove the decision, from BenchResultV1. */
|
|
54
|
+
readonly p95Ms: number | null;
|
|
55
|
+
/** The platform the selection is valid for. */
|
|
56
|
+
readonly platform: string;
|
|
57
|
+
/** Short human-readable rationale for the choice (record on the event). */
|
|
58
|
+
readonly rationale: string;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** The 80 MiB install budget in bytes (the HG-3 ceiling, unamended). */
|
|
62
|
+
export const RUNTIME_NATIVE_INSTALL_BUDGET_MIB = 80;
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Per-platform optionalDependency footprint of onnxruntime-node (MiB, approx).
|
|
66
|
+
* These sum to ~160 MiB SHIPPED in the npm package (every target platform is
|
|
67
|
+
* included so the resolver lands on a concrete row at install time) — which is
|
|
68
|
+
* what exceeds the 80 MiB HG-3 budget and forces the amendment the fixtures
|
|
69
|
+
* record (ML5-RUNTIME-001). The per-host INSTALLED footprint (one row only)
|
|
70
|
+
* is 28–35 MiB and irrelevant to the 80 MiB ceiling — the budget covers the
|
|
71
|
+
* shipped tarball, not the single-platform install.
|
|
72
|
+
*/
|
|
73
|
+
export const NATIVE_FOOTPRINT_MIB: Readonly<Record<EncoderPlatform, number>> = {
|
|
74
|
+
"linux-x64": 33,
|
|
75
|
+
"darwin-arm64": 28,
|
|
76
|
+
"darwin-x64": 33,
|
|
77
|
+
"linux-arm64": 31,
|
|
78
|
+
"win32-x64": 35,
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* ML5-C decision-rule dispatch: choose the ONNX runtime backend (pure).
|
|
83
|
+
*
|
|
84
|
+
* When the flag is OFF (`MEGACOMPACT_ML5_C=0`), returns mode B trigram — byte-
|
|
85
|
+
* identical to the ML5-B survivor with no selection event emitted.
|
|
86
|
+
*
|
|
87
|
+
* The rule (from the sprint spec):
|
|
88
|
+
* - If nativeOptIn && platform is supported → native (Option N)
|
|
89
|
+
* - If benchRecord has p95Ms <= 40 ms on linux-x64 → WASM (Option W)
|
|
90
|
+
* - Else → native (Option N) with the budget amendment recorded (p95 exceeds
|
|
91
|
+
* the WASM gate or is absent — the placeholder has no measured p95)
|
|
92
|
+
* - darwin-x64 → WASM or mode B demotion per HG-4 (never native here)
|
|
93
|
+
*/
|
|
94
|
+
export function selectRuntimeBackend(input: RuntimeSelectionInput): RuntimeSelectionResult {
|
|
95
|
+
if (!ML5C_ENABLED()) {
|
|
96
|
+
return {
|
|
97
|
+
backend: "modeB",
|
|
98
|
+
budgetOk: true,
|
|
99
|
+
p95Ms: null,
|
|
100
|
+
platform: input.platform,
|
|
101
|
+
rationale: "flag-off: byte-identical mode-B trigram (no selection)",
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// HG-4: Intel Mac (darwin-x64) is out-of-scope per HG-1's deferral — always demote.
|
|
106
|
+
if (input.platform === "darwin-x64") {
|
|
107
|
+
return {
|
|
108
|
+
backend: "wasm",
|
|
109
|
+
budgetOk: true,
|
|
110
|
+
p95Ms: null,
|
|
111
|
+
platform: input.platform,
|
|
112
|
+
rationale: "darwin-x64 demoted to WASM per HG-4 (never native on this platform)",
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Native opt-in short-circuits: operator explicitly wants the native path.
|
|
117
|
+
// The HG-3 budget compares the SHIPPED byte-count (sum across every platform
|
|
118
|
+
// row in the package's optionalDependencies map) against the 80 MiB ceiling
|
|
119
|
+
// — not the single-platform install size. Native always exceeds 80 MiB across
|
|
120
|
+
// 5 platforms (~160 MiB shipped), so budgetOk is false and the evidence
|
|
121
|
+
// records the amended budget (the ML5-C spec, HG-3 closure).
|
|
122
|
+
if (input.nativeOptIn) {
|
|
123
|
+
const shippedMib = Object.values(NATIVE_FOOTPRINT_MIB).reduce((a, b) => a + b, 0);
|
|
124
|
+
return {
|
|
125
|
+
backend: "native",
|
|
126
|
+
budgetOk: shippedMib <= RUNTIME_NATIVE_INSTALL_BUDGET_MIB,
|
|
127
|
+
p95Ms: input.benchRecord?.p95Ms ?? null,
|
|
128
|
+
platform: input.platform,
|
|
129
|
+
rationale: `native opt-in (MEGACOMPACT_ENCODER_NATIVE=1); shipped ${shippedMib} MiB across 5 platforms → budget amended to ${shippedMib} MiB`,
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// No bench record or degraded (gates.all:false) → WASM cannot qualify (the
|
|
134
|
+
// placeholder 42-byte asset has no measured real p95), so native is selected
|
|
135
|
+
// with the SAME amended-budget disposition as the opt-in path above: the
|
|
136
|
+
// evidence records the closed HG-3 amendment.
|
|
137
|
+
if (!input.benchRecord || !input.benchRecord.gates.all || input.benchRecord.p95Ms === null) {
|
|
138
|
+
const shippedMib = Object.values(NATIVE_FOOTPRINT_MIB).reduce((a, b) => a + b, 0);
|
|
139
|
+
return {
|
|
140
|
+
backend: "native",
|
|
141
|
+
budgetOk: false, // amended: native ships > 80 MiB across the 5-platform matrix
|
|
142
|
+
p95Ms: input.benchRecord?.p95Ms ?? null,
|
|
143
|
+
platform: input.platform,
|
|
144
|
+
rationale: `no qualifying bench record — native fallback with budget amendment (${shippedMib} MiB shipped, HG-3 amendment recorded)`,
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// The decision rule: WASM iff p95 <= 40 ms (linux-x64, 512 tokens, 4 threads) —
|
|
149
|
+
// native required otherwise, with the same budget amendment recorded.
|
|
150
|
+
if (input.benchRecord.p95Ms <= ENCODER_LATENCY_P95_MS) {
|
|
151
|
+
return {
|
|
152
|
+
backend: "wasm",
|
|
153
|
+
budgetOk: true,
|
|
154
|
+
p95Ms: input.benchRecord.p95Ms,
|
|
155
|
+
platform: input.platform,
|
|
156
|
+
rationale: `WASM qualifies: p95 ${input.benchRecord.p95Ms}ms <= ${ENCODER_LATENCY_P95_MS}ms`,
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return {
|
|
161
|
+
backend: "native",
|
|
162
|
+
budgetOk: false, // amended: native exceeds the 80 MiB budget per the evidence
|
|
163
|
+
p95Ms: input.benchRecord.p95Ms,
|
|
164
|
+
platform: input.platform,
|
|
165
|
+
rationale: `native required: p95 ${input.benchRecord.p95Ms}ms > ${ENCODER_LATENCY_P95_MS}ms on WASM`,
|
|
166
|
+
};
|
|
167
|
+
}
|