pi-mega-compact 0.20.42 → 0.20.44
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/assets/vector-cortex/encoder-v1/manifest.json +1 -1
- package/assets/vector-cortex/encoder-v1/model-card.json +1 -1
- package/assets/vector-cortex/encoder-v1/model.onnx +0 -0
- package/assets/vector-cortex/encoder-v1/tokenizer.json +1 -1
- package/dist/config/vector-cortex-enc0a.js +32 -0
- package/dist/config/vector-cortex-enc0b.js +34 -0
- package/dist/config/vector-cortex.js +3 -3
- package/dist/config.js +1 -1
- package/dist/extensions/dashboard-server/routes-rag-settings-vector-cortex.js +2 -0
- package/dist/src/config/vector-cortex-enc0a.js +32 -0
- package/dist/src/config/vector-cortex-enc0b.js +34 -0
- package/dist/src/config/vector-cortex.js +3 -3
- package/dist/src/config.js +1 -1
- package/dist/src/vector-cortex/_acceptance-enc0a-contract.js +60 -0
- package/dist/src/vector-cortex/_acceptance-vc2a-conformance.js +90 -0
- package/dist/src/vector-cortex/_acceptance-vc2a-runtime.js +285 -0
- package/dist/src/vector-cortex/_acceptance-vc2b-conformance.js +92 -0
- package/dist/src/vector-cortex/_acceptance-vc2b-heads.js +221 -0
- package/dist/src/vector-cortex/encoder/asset.js +3 -2
- package/dist/src/vector-cortex/encoder/decision.js +75 -0
- package/dist/src/vector-cortex/encoder/emit.js +2 -0
- package/dist/src/vector-cortex/encoder/encoder-onnx-dispatch.js +58 -0
- package/dist/src/vector-cortex/encoder/onnx.js +147 -0
- package/dist/src/vector-cortex/encoder/runtime-wasm.js +17 -4
- package/dist/src/vector-cortex/encoder/runtime.js +17 -7
- package/dist/src/vector-cortex/encoder/types-vc2c.js +50 -0
- package/dist/src/vector-cortex/encoder/types.js +13 -34
- package/dist/vector-cortex/_acceptance-enc0a-contract.js +60 -0
- package/dist/vector-cortex/_acceptance-vc2a-conformance.js +90 -0
- package/dist/vector-cortex/_acceptance-vc2a-runtime.js +285 -0
- package/dist/vector-cortex/_acceptance-vc2b-conformance.js +92 -0
- package/dist/vector-cortex/_acceptance-vc2b-heads.js +221 -0
- package/dist/vector-cortex/encoder/asset.js +3 -2
- package/dist/vector-cortex/encoder/decision.js +75 -0
- package/dist/vector-cortex/encoder/emit.js +2 -0
- package/dist/vector-cortex/encoder/encoder-onnx-dispatch.js +58 -0
- package/dist/vector-cortex/encoder/onnx.js +147 -0
- package/dist/vector-cortex/encoder/runtime-wasm.js +17 -4
- package/dist/vector-cortex/encoder/runtime.js +17 -7
- package/dist/vector-cortex/encoder/types-vc2c.js +50 -0
- package/dist/vector-cortex/encoder/types.js +13 -34
- package/extensions/dashboard-server/routes-rag-settings-vector-cortex.ts +12 -0
- package/package.json +3 -2
- package/src/config/vector-cortex-enc0a.ts +34 -0
- package/src/config/vector-cortex-enc0b.ts +36 -0
- package/src/config/vector-cortex.ts +3 -3
- package/src/config.ts +2 -0
- package/src/vector-cortex/_acceptance-enc0a-contract.ts +71 -0
- package/src/vector-cortex/_acceptance-vc2a-conformance.ts +119 -0
- package/src/vector-cortex/_acceptance-vc2a-runtime.ts +300 -0
- package/src/vector-cortex/_acceptance-vc2b-conformance.ts +121 -0
- package/src/vector-cortex/_acceptance-vc2b-heads.ts +234 -0
- package/src/vector-cortex/encoder/asset.ts +3 -2
- package/src/vector-cortex/encoder/bench-export.ts +2 -2
- package/src/vector-cortex/encoder/decision.ts +125 -0
- package/src/vector-cortex/encoder/emit.ts +5 -1
- package/src/vector-cortex/encoder/encoder-onnx-dispatch.ts +76 -0
- package/src/vector-cortex/encoder/onnx.ts +198 -0
- package/src/vector-cortex/encoder/runtime-native.ts +1 -1
- package/src/vector-cortex/encoder/runtime-wasm.ts +21 -8
- package/src/vector-cortex/encoder/runtime.ts +27 -9
- package/src/vector-cortex/encoder/types-vc2c.ts +134 -0
- package/src/vector-cortex/encoder/types.ts +19 -115
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
/** VC2B acceptance — multi-head invariant + independence + forced triad through
|
|
2
|
+
* the REAL encode-or-fallback router (no mocks, including unique failure
|
|
3
|
+
* injection where the learned asset is deleted after A selection but before
|
|
4
|
+
* inference). Extracted from vc2b-acceptance.test.ts so the aggregator stays
|
|
5
|
+
* under the soft line limit. Shared context (withFlagsOn, stageVerifyingAssetDir,
|
|
6
|
+
* token/order constants) is injected by the aggregator to avoid a circular import.
|
|
7
|
+
*/
|
|
8
|
+
import { test, describe } from "node:test";
|
|
9
|
+
import assert from "node:assert/strict";
|
|
10
|
+
import { rmSync } from "node:fs";
|
|
11
|
+
import { join } from "node:path";
|
|
12
|
+
|
|
13
|
+
import { ENC_FAIL, ENCODER_HEAD_DIMS } from "./encoder/types.js";
|
|
14
|
+
import { encodeVectorSet, l2Norm } from "./encoder/heads.js";
|
|
15
|
+
import { ENCODER_TRIGRAM_WIDTH } from "./encoder/trigram.js";
|
|
16
|
+
import { embedLexical, ENCODER_LEXICAL_WIDTH } from "./encoder/lexical.js";
|
|
17
|
+
import { encodeOrFallback } from "./encoder/router.js";
|
|
18
|
+
import { createEncoderRuntime } from "./encoder/runtime.js";
|
|
19
|
+
import { createEncoderHeadsReporter } from "./encoder/emit-vc2b.js";
|
|
20
|
+
|
|
21
|
+
export interface HeadsCtx {
|
|
22
|
+
withFlagsOn: (fn: () => void) => void;
|
|
23
|
+
stageVerifyingAssetDir: (maxTokens?: number) => string;
|
|
24
|
+
SET_TOKENS: number[];
|
|
25
|
+
EMPTY_TOKENS: number[];
|
|
26
|
+
ORDERED_DIMS: number[];
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function registerHeads(ctx: HeadsCtx): void {
|
|
30
|
+
const { withFlagsOn, stageVerifyingAssetDir, SET_TOKENS, EMPTY_TOKENS, ORDERED_DIMS } = ctx;
|
|
31
|
+
|
|
32
|
+
// Suite 4 — invariant + unique failure injection + forced triad
|
|
33
|
+
describe("multi-head invariant + independence + triad", () => {
|
|
34
|
+
test("invariant: every emitted norm is 0 or within 1e-6 of 1", () => {
|
|
35
|
+
for (const tokens of [SET_TOKENS, EMPTY_TOKENS, [7], Array.from({ length: 300 }, (_, i) => i)]) {
|
|
36
|
+
const set = encodeVectorSet(tokens);
|
|
37
|
+
for (const hv of set.heads) {
|
|
38
|
+
const n = l2Norm(hv.values);
|
|
39
|
+
assert.equal(n === 0 || Math.abs(n - 1) <= 1e-6, true, `${hv.head} norm ${n}`);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
test("repeat drift <= 1e-6 across repeated seeded exports (all five heads)", () => {
|
|
45
|
+
for (let rep = 0; rep < 3; rep++) {
|
|
46
|
+
const a = encodeVectorSet(SET_TOKENS);
|
|
47
|
+
const b = encodeVectorSet(SET_TOKENS);
|
|
48
|
+
for (let i = 0; i < a.heads.length; i++) {
|
|
49
|
+
for (let j = 0; j < a.heads[i]!.values.length; j++) {
|
|
50
|
+
assert.equal(Math.abs(a.heads[i]!.values[j]! - b.heads[i]!.values[j]!) <= 1e-6, true);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test("unique failure injection: delete model after A selection but before inference; router catches the real load() failure and selects independently initialized B", () => {
|
|
57
|
+
// This scenario is ON-dependent: it asserts a fallback emission, which is
|
|
58
|
+
// VC2B-flag-gated, and drives the VC2A runtime into an A load — so it self-pins
|
|
59
|
+
// both flags ON and is thus valid under either the default-ON run or the
|
|
60
|
+
// MEGACOMPACT_VC2B=0 parity run.
|
|
61
|
+
withFlagsOn(() => {
|
|
62
|
+
// Stage a learned asset the VC2A runtime would VERIFY into mode A, then
|
|
63
|
+
// REMOVE model.onnx before encoding. The router's load() returns the real
|
|
64
|
+
// ENC_ASSET_UNREADABLE failure code and must hand off to the independently
|
|
65
|
+
// initialized asset-free trigram B — emitting vector_cortex_encoder_fallback_selected
|
|
66
|
+
// from the production seam (S2: a true end-to-end router test, not a
|
|
67
|
+
// simulated direct call to selectTrigramBFallback).
|
|
68
|
+
const dir = stageVerifyingAssetDir();
|
|
69
|
+
const emitted: string[] = [];
|
|
70
|
+
const reporter = createEncoderHeadsReporter((e) => emitted.push(e));
|
|
71
|
+
try {
|
|
72
|
+
// A is selectable at this point (a staging runtime verifies it).
|
|
73
|
+
const probe = createEncoderRuntime();
|
|
74
|
+
assert.equal(probe.load(dir).ok, true, "staged asset verifies into A");
|
|
75
|
+
// "After A selection but before inference": the on-disk model is gone.
|
|
76
|
+
rmSync(join(dir, "model.onnx"), { force: true });
|
|
77
|
+
const verdict = encodeOrFallback({ tokens: SET_TOKENS }, dir, { reporter });
|
|
78
|
+
assert.equal(verdict.ok, true);
|
|
79
|
+
assert.equal(verdict.mode, "B", "router handoff selects independently initialized B");
|
|
80
|
+
assert.equal(verdict.width, 512);
|
|
81
|
+
if (verdict.ok) {
|
|
82
|
+
assert.equal(verdict.vector.length, 512);
|
|
83
|
+
assert.equal(verdict.code, ENC_FAIL.ASSET_UNREADABLE, "load() reported the real failure code");
|
|
84
|
+
}
|
|
85
|
+
assert.ok(
|
|
86
|
+
emitted.includes("vector_cortex_encoder_fallback_selected"),
|
|
87
|
+
"fallback-selected fired from the real router seam: " + emitted.join(","),
|
|
88
|
+
);
|
|
89
|
+
// Distinct vectors for distinct inputs — the fallback is not a constant.
|
|
90
|
+
const again = encodeOrFallback({ tokens: [9, 8, 7, 6] }, dir, { reporter });
|
|
91
|
+
assert.equal(again.ok, true);
|
|
92
|
+
if (verdict.ok && again.ok && verdict.mode === "B" && again.mode === "B") {
|
|
93
|
+
let diff = 0;
|
|
94
|
+
for (let i = 0; i < verdict.vector.length; i++) diff += Math.abs(verdict.vector[i]! - again.vector[i]!);
|
|
95
|
+
assert.ok(diff > 1e-3, "independent trigram B is input-sensitive");
|
|
96
|
+
}
|
|
97
|
+
} finally {
|
|
98
|
+
rmSync(dir, { recursive: true, force: true });
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
test("forced triad A / B / C through the encode-or-fallback router", () => {
|
|
104
|
+
// ON-dependent: asserts heads_emitted / fallback_selected emissions, so it
|
|
105
|
+
// self-pins both flags ON (valid under either the default-ON run or the
|
|
106
|
+
// MEGACOMPACT_VC2B=0 parity run).
|
|
107
|
+
withFlagsOn(() => {
|
|
108
|
+
// A = learned projections: a verifying asset dir routes to a VectorSetV1 with
|
|
109
|
+
// the five heads in ordered dims (emitting heads_emitted).
|
|
110
|
+
const dirA = stageVerifyingAssetDir();
|
|
111
|
+
const emittedA: string[] = [];
|
|
112
|
+
try {
|
|
113
|
+
const reporterA = createEncoderHeadsReporter((e) => emittedA.push(e));
|
|
114
|
+
const a = encodeOrFallback({ tokens: SET_TOKENS }, dirA, { reporter: reporterA });
|
|
115
|
+
assert.equal(a.ok, true);
|
|
116
|
+
assert.equal(a.mode, "A");
|
|
117
|
+
if (a.ok) {
|
|
118
|
+
assert.equal(a.vectorSet.heads.length, 5);
|
|
119
|
+
assert.deepEqual(a.vectorSet.heads.map((h) => h.dim), ORDERED_DIMS);
|
|
120
|
+
}
|
|
121
|
+
assert.ok(emittedA.includes("vector_cortex_encoder_heads_emitted"));
|
|
122
|
+
} finally {
|
|
123
|
+
rmSync(dirA, { recursive: true, force: true });
|
|
124
|
+
}
|
|
125
|
+
// B = 512d trigram selected when the learned asset directory is REMOVED: the
|
|
126
|
+
// router's load() fails (no manual fetch) and hands off to B. Remove a staged
|
|
127
|
+
// asset dir so the directory is genuinely absent, proving B needs no asset.
|
|
128
|
+
const dirB = stageVerifyingAssetDir();
|
|
129
|
+
rmSync(dirB, { recursive: true, force: true }); // asset directory REMOVED
|
|
130
|
+
const emittedB: string[] = [];
|
|
131
|
+
const reporterB = createEncoderHeadsReporter((e) => emittedB.push(e));
|
|
132
|
+
const b = encodeOrFallback({ tokens: SET_TOKENS }, dirB, { reporter: reporterB });
|
|
133
|
+
assert.equal(b.ok, true);
|
|
134
|
+
assert.equal(b.mode, "B", "B works without an asset dir");
|
|
135
|
+
assert.equal(b.width, 512);
|
|
136
|
+
if (b.ok) {
|
|
137
|
+
assert.equal(b.vector.length, 512);
|
|
138
|
+
assert.equal(b.limitation, null);
|
|
139
|
+
}
|
|
140
|
+
assert.ok(emittedB.includes("vector_cortex_encoder_fallback_selected"), "B selection emits fallback-selected");
|
|
141
|
+
// C = token/phrase lexical forced when both A and B runtimes are disabled.
|
|
142
|
+
const emittedC: string[] = [];
|
|
143
|
+
const reporterC = createEncoderHeadsReporter((e) => emittedC.push(e));
|
|
144
|
+
const c = encodeOrFallback({ tokens: SET_TOKENS }, dirB, { reporter: reporterC, forceFallback: "C" });
|
|
145
|
+
assert.equal(c.ok, true);
|
|
146
|
+
assert.equal(c.mode, "C");
|
|
147
|
+
assert.equal(c.width, ENCODER_LEXICAL_WIDTH);
|
|
148
|
+
if (c.ok && c.mode === "C") {
|
|
149
|
+
assert.equal(c.vector.length, ENCODER_LEXICAL_WIDTH);
|
|
150
|
+
assert.ok((c.limitation ?? "").length > 0, "C reports its semantic-context limitation");
|
|
151
|
+
// Q04: a FORCED C is an intentional selection, not a demotion or rollback,
|
|
152
|
+
// so it must NOT be stamped with ENC_FAIL.ROLLBACK.
|
|
153
|
+
assert.equal(c.code, null, "forced C is not a rollback/demotion (code = null)");
|
|
154
|
+
}
|
|
155
|
+
assert.ok(emittedC.includes("vector_cortex_encoder_fallback_selected"), "C selection emits fallback-selected");
|
|
156
|
+
// Widths are disjoint across the triad (no shared feature space).
|
|
157
|
+
const aWidths = Object.values(ENCODER_HEAD_DIMS);
|
|
158
|
+
for (const w of [...aWidths, ENCODER_LEXICAL_WIDTH]) assert.notEqual(w, ENCODER_TRIGRAM_WIDTH);
|
|
159
|
+
});
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
test("forced fallback (B/C) wins over the empty-input degenerate case (Q02)", () => {
|
|
163
|
+
// Q02: a caller that explicitly forces a fallback mode must get that mode
|
|
164
|
+
// even for EMPTY input — empty tokens must NOT silently short-circuit to B.
|
|
165
|
+
// Asserts fallback_selected emissions → ON-dependent, so it self-pins via
|
|
166
|
+
// withFlagsOn (valid under either external env, same as the forced-triad test).
|
|
167
|
+
withFlagsOn(() => {
|
|
168
|
+
const emittedC: string[] = [];
|
|
169
|
+
const reporterC = createEncoderHeadsReporter((e) => emittedC.push(e));
|
|
170
|
+
const c = encodeOrFallback({ tokens: EMPTY_TOKENS }, "", { reporter: reporterC, forceFallback: "C" });
|
|
171
|
+
assert.equal(c.ok, true);
|
|
172
|
+
assert.equal(c.mode, "C", "forced C must win over empty-input B selection");
|
|
173
|
+
if (c.ok && c.mode === "C") {
|
|
174
|
+
assert.equal(c.vector.length, ENCODER_LEXICAL_WIDTH);
|
|
175
|
+
assert.equal(c.code, null, "forced C carries no rollback/demotion code (Q04)");
|
|
176
|
+
}
|
|
177
|
+
assert.ok(emittedC.includes("vector_cortex_encoder_fallback_selected"));
|
|
178
|
+
// Forced B on empty input also stays B (force mode is honored, no failure
|
|
179
|
+
// code — a forced mode is not a rollback/demotion, so code is null).
|
|
180
|
+
const emittedB: string[] = [];
|
|
181
|
+
const reporterB = createEncoderHeadsReporter((e) => emittedB.push(e));
|
|
182
|
+
const b = encodeOrFallback({ tokens: EMPTY_TOKENS }, "", { reporter: reporterB, forceFallback: "B" });
|
|
183
|
+
assert.equal(b.ok, true);
|
|
184
|
+
assert.equal(b.mode, "B");
|
|
185
|
+
if (b.ok && b.mode === "B") {
|
|
186
|
+
assert.equal(b.vector.length, ENCODER_TRIGRAM_WIDTH);
|
|
187
|
+
assert.equal(b.code, null, "forced B carries no rollback/demotion code (Q04)");
|
|
188
|
+
}
|
|
189
|
+
});
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
test("A/B/C use disjoint widths and independent algorithms", () => {
|
|
193
|
+
// A head widths are 384/128/128/64/32; B is 512; C is 256 — no shared space.
|
|
194
|
+
const aWidths = Object.values(ENCODER_HEAD_DIMS);
|
|
195
|
+
const bWidth = ENCODER_TRIGRAM_WIDTH;
|
|
196
|
+
const cWidth = ENCODER_LEXICAL_WIDTH;
|
|
197
|
+
for (const w of [...aWidths, cWidth]) assert.notEqual(w, bWidth);
|
|
198
|
+
// B works with the asset absent; C works with both vector runtimes disabled
|
|
199
|
+
// (C does not depend on B or A — it embeds tokens directly).
|
|
200
|
+
const cTokens = embedLexical("independently computed lexical with vector runtimes disabled");
|
|
201
|
+
assert.equal(cTokens.length, ENCODER_LEXICAL_WIDTH);
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
test("router seam enforces the verified per-manifest token capacity: over-cap input routes to B with ENC_SHAPE_INVALID, never an over-cap A VectorSet", () => {
|
|
205
|
+
// Q01: the router's mode-A path must enforce the VC2A contract
|
|
206
|
+
// "only batch1/<=maxTokens verified assets reach inference" at its own seam.
|
|
207
|
+
// A verified asset declaring maxTokens=64 with an input of 100 tokens must
|
|
208
|
+
// NOT produce an ok:true mode-A VectorSetV1 whose inputTokens breach the
|
|
209
|
+
// model's declared capacity — instead the router rejects it and falls back
|
|
210
|
+
// to the asset-free trigram B, reporting the real shape failure code.
|
|
211
|
+
withFlagsOn(() => {
|
|
212
|
+
const dir = stageVerifyingAssetDir(64); // verified low-cap manifest
|
|
213
|
+
try {
|
|
214
|
+
const over = encodeOrFallback({ tokens: Array.from({ length: 100 }, (_, i) => i) }, dir);
|
|
215
|
+
assert.equal(over.ok, true, "over-cap input still yields a usable (fallback) verdict");
|
|
216
|
+
assert.equal(over.mode, "B", "over-cap input must route to the B fallback, not an A VectorSet");
|
|
217
|
+
if (over.ok) {
|
|
218
|
+
assert.equal(over.code, ENC_FAIL.SHAPE_INVALID, "reported the real shape failure code");
|
|
219
|
+
assert.equal(over.vector.length, ENCODER_TRIGRAM_WIDTH);
|
|
220
|
+
}
|
|
221
|
+
// A within-cap input against the SAME verified manifest still reaches a
|
|
222
|
+
// qualified mode-A VectorSet — the capacity rejection is input-scoped,
|
|
223
|
+
// not a blanket demotion of the verified asset.
|
|
224
|
+
const within = encodeOrFallback({ tokens: SET_TOKENS }, dir);
|
|
225
|
+
assert.equal(within.ok, true);
|
|
226
|
+
assert.equal(within.mode, "A", "within-cap input still reaches mode A");
|
|
227
|
+
if (within.ok) assert.equal(within.vectorSet.heads.length, 5);
|
|
228
|
+
} finally {
|
|
229
|
+
rmSync(dir, { recursive: true, force: true });
|
|
230
|
+
}
|
|
231
|
+
});
|
|
232
|
+
});
|
|
233
|
+
});
|
|
234
|
+
}
|
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
* vector-cortex/encoder/asset.ts — VC2A asset verification (task 2).
|
|
3
3
|
*
|
|
4
4
|
* Verifies a ModelManifestV1 before any allocation: SHA-256 the ONNX and
|
|
5
|
-
* tokenizer against the manifest digests, require opset
|
|
5
|
+
* tokenizer against the manifest digests, require opset 21 (ENC-0a re-baseline),
|
|
6
|
+
* batch exactly 1 and
|
|
6
7
|
* maximum 512 tokens, and confirm the current platform is in the supported
|
|
7
8
|
* matrix. On ANY of these the caller demotes to mode B (asset-free trigram) —
|
|
8
9
|
* never a remote fetch (PREVENT-PI-004). A truncated/unreadable asset during
|
|
@@ -88,7 +89,7 @@ function isManifest(m: unknown): m is ModelManifestV1 {
|
|
|
88
89
|
*
|
|
89
90
|
* - manifest parse/shape failure -> ENC_MANIFEST_INVALID -> mode B
|
|
90
91
|
* - unsupported platform -> ENC_PLATFORM_UNSUPPORTED -> mode B
|
|
91
|
-
* - opset !=
|
|
92
|
+
* - opset != 21 -> ENC_OPSET_INVALID -> mode B
|
|
92
93
|
* - batch != 1 -> ENC_BATCH_INVALID -> mode B
|
|
93
94
|
* - maxTokens > 512 -> ENC_TOKENS_EXCEEDED -> mode B
|
|
94
95
|
* - on-disk digest != manifest digest -> ENC_DIGEST_MISMATCH (one-byte mutation)
|
|
@@ -17,7 +17,7 @@ export interface BenchGatesV1 {
|
|
|
17
17
|
readonly latency: boolean;
|
|
18
18
|
/** steady-state marginal RSS over the process baseline <= 150 MiB. */
|
|
19
19
|
readonly rss: boolean;
|
|
20
|
-
/** the loaded model's declared opset_import equals
|
|
20
|
+
/** the loaded model's declared opset_import equals ENCODER_OPSET (21, ENC-0a re-baseline). */
|
|
21
21
|
readonly opset: boolean;
|
|
22
22
|
/** SHA-256 of the embedding output identical across 3 runs (maxAbsDelta=0). */
|
|
23
23
|
readonly determinism: boolean;
|
|
@@ -53,7 +53,7 @@ export interface BenchResultV1 {
|
|
|
53
53
|
readonly rssBaselineMib: number | null;
|
|
54
54
|
/** rssMib - rssBaselineMib: the encoder's marginal footprint. */
|
|
55
55
|
readonly rssMarginalMib: number | null;
|
|
56
|
-
/** declared opset_import (
|
|
56
|
+
/** declared opset_import (21, ENC-0a re-baseline); null when no asset manifest is readable. */
|
|
57
57
|
readonly opset: number | null;
|
|
58
58
|
/** true when the output SHA-256 is identical across 3 runs. */
|
|
59
59
|
readonly deterministic: boolean;
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* vector-cortex/encoder/decision.ts — ENC-0a backend-decision contract.
|
|
3
|
+
*
|
|
4
|
+
* The durable EncoderBackendDecisionV1 record: which ONNX runtime backend the
|
|
5
|
+
* real learned encoder ships on (transformers.js/WASM vs onnxruntime-node
|
|
6
|
+
* native), whether the 80 MiB install budget holds, the per-platform install
|
|
7
|
+
* matrix, the opset baseline (locked 21 by ENC-0a), the license verdict, and
|
|
8
|
+
* the pinned model/tokenizer sha256 digests.
|
|
9
|
+
*
|
|
10
|
+
* ENC-0a is the DECISION + MEASUREMENT sprint: this contract is what the
|
|
11
|
+
* deterministic resolver (`scripts/encoder/resolve-backend-decision.mjs`) and
|
|
12
|
+
* the durable record (`docs/vector-cortex/encoder-backend-decision.md`) both
|
|
13
|
+
* consume. ENC-0a also owns the opset flip: ENCODER_OPSET is re-baselined to 21
|
|
14
|
+
* in `types.ts` alongside the placeholder manifest (the 2026-08-05 trunk
|
|
15
|
+
* research dropped the earlier Xenova opset-17 requirement). ENC-0b asserts the
|
|
16
|
+
* staged real asset is opset 21 — no further constant change.
|
|
17
|
+
*
|
|
18
|
+
* Contract-first (ENGINEERING_PRACTICES §3). Pi-agnostic, dependency-free
|
|
19
|
+
* (PREVENT-PI-004 — local computation only, never a fetch). No `any`
|
|
20
|
+
* (PREVENT-011).
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
import type { EncoderPlatform } from "./types.js";
|
|
24
|
+
|
|
25
|
+
/** Backend demotion disposition for a platform row. */
|
|
26
|
+
export type BackendDemotion = "none" | "wasm" | "modeB";
|
|
27
|
+
|
|
28
|
+
/** One platform row: the concrete runtime + install size + demotion disposition. */
|
|
29
|
+
export interface EncoderPlatformRow {
|
|
30
|
+
readonly runtime: string;
|
|
31
|
+
readonly installMiB: number;
|
|
32
|
+
readonly demotion: BackendDemotion;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* EncoderBackendDecisionV1 — the locked learned-encoder runtime-backend decision.
|
|
37
|
+
*
|
|
38
|
+
* `opset` is pinned to the literal 21 (the ENC-0a re-baseline); `artifacts`
|
|
39
|
+
* carries only aggregate digests/sizes — never message content (EVAL-REDACT-002);
|
|
40
|
+
* `blockedBy` names the open hard-gate items that DEFER part of the decision
|
|
41
|
+
* (e.g. HG-4 — the darwin-x64 demotion is ENC-0e's job).
|
|
42
|
+
*/
|
|
43
|
+
export interface EncoderBackendDecisionV1 {
|
|
44
|
+
readonly schema: "encoder-backend-decision-v1";
|
|
45
|
+
readonly backend: "wasm" | "native";
|
|
46
|
+
readonly budgetOk: boolean;
|
|
47
|
+
readonly opset: 21;
|
|
48
|
+
readonly platformMatrix: Readonly<Record<EncoderPlatform, EncoderPlatformRow>>;
|
|
49
|
+
readonly license: { readonly spdx: "MIT"; readonly redistribution: true };
|
|
50
|
+
readonly artifacts: {
|
|
51
|
+
readonly model: { readonly path: string; readonly bytes: number; readonly sha256: string };
|
|
52
|
+
readonly tokenizer: { readonly path: string; readonly bytes: number; readonly sha256: string };
|
|
53
|
+
};
|
|
54
|
+
readonly p95Ms: number | null;
|
|
55
|
+
readonly blockedBy: readonly string[];
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* The normative ENC-0a budget: the 80 MiB install/asset cap (MODEL_ASSET.md
|
|
60
|
+
* §Qualification). Backend qualifies (budgetOk) iff the shipped byte-count fits.
|
|
61
|
+
*/
|
|
62
|
+
export const ENCODER_INSTALL_BUDGET_MIB = 80;
|
|
63
|
+
|
|
64
|
+
/** The p95 latency gate at 512 tokens / 4 threads on linux-x64 (ms). */
|
|
65
|
+
export const ENCODER_DECISION_P95_MS = 40;
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* buildDecision — assemble a valid, platform-complete EncoderBackendDecisionV1.
|
|
69
|
+
*
|
|
70
|
+
* Pure helper consumed by the acceptance aggregator (and any TS consumer of the
|
|
71
|
+
* decision). Every EncoderPlatform must resolve to a row, and the passed
|
|
72
|
+
* per-platform rows must be complete (no row omitted) — a partial matrix is a
|
|
73
|
+
* contract violation, not a valid decision. All fields are passed in; this is a
|
|
74
|
+
* structural constructor, not a rules engine (the resolver owns the decision
|
|
75
|
+
* rule).
|
|
76
|
+
*/
|
|
77
|
+
export function buildDecision(input: {
|
|
78
|
+
readonly backend: "wasm" | "native";
|
|
79
|
+
readonly budgetOk: boolean;
|
|
80
|
+
readonly p95Ms: number | null;
|
|
81
|
+
readonly platformMatrix: Readonly<Record<EncoderPlatform, EncoderPlatformRow>>;
|
|
82
|
+
readonly modelPath: string;
|
|
83
|
+
readonly modelBytes: number;
|
|
84
|
+
readonly modelSha256: string;
|
|
85
|
+
readonly tokenizerPath: string;
|
|
86
|
+
readonly tokenizerBytes: number;
|
|
87
|
+
readonly tokenizerSha256: string;
|
|
88
|
+
readonly blockedBy: readonly string[];
|
|
89
|
+
}): EncoderBackendDecisionV1 {
|
|
90
|
+
const { platformMatrix } = input;
|
|
91
|
+
const platforms: readonly EncoderPlatform[] = [
|
|
92
|
+
"linux-x64",
|
|
93
|
+
"linux-arm64",
|
|
94
|
+
"darwin-x64",
|
|
95
|
+
"darwin-arm64",
|
|
96
|
+
"win32-x64",
|
|
97
|
+
];
|
|
98
|
+
for (const p of platforms) {
|
|
99
|
+
if (!Object.prototype.hasOwnProperty.call(platformMatrix, p)) {
|
|
100
|
+
throw new Error(`platform matrix is incomplete: missing row for ${p}`);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return {
|
|
104
|
+
schema: "encoder-backend-decision-v1",
|
|
105
|
+
backend: input.backend,
|
|
106
|
+
budgetOk: input.budgetOk,
|
|
107
|
+
opset: 21,
|
|
108
|
+
platformMatrix,
|
|
109
|
+
license: { spdx: "MIT", redistribution: true },
|
|
110
|
+
artifacts: {
|
|
111
|
+
model: {
|
|
112
|
+
path: input.modelPath,
|
|
113
|
+
bytes: input.modelBytes,
|
|
114
|
+
sha256: input.modelSha256,
|
|
115
|
+
},
|
|
116
|
+
tokenizer: {
|
|
117
|
+
path: input.tokenizerPath,
|
|
118
|
+
bytes: input.tokenizerBytes,
|
|
119
|
+
sha256: input.tokenizerSha256,
|
|
120
|
+
},
|
|
121
|
+
},
|
|
122
|
+
p95Ms: input.p95Ms,
|
|
123
|
+
blockedBy: input.blockedBy,
|
|
124
|
+
};
|
|
125
|
+
}
|
|
@@ -17,16 +17,19 @@ import { VC2A_ENABLED } from "../../config/vector-cortex.js";
|
|
|
17
17
|
|
|
18
18
|
export type EncoderEmit = (event: string, fields: Record<string, unknown>) => void;
|
|
19
19
|
|
|
20
|
-
/** The
|
|
20
|
+
/** The reporter surface consumed by the runtime + ONNX seams. */
|
|
21
21
|
export interface EncoderReporter {
|
|
22
22
|
readonly assetVerified: (fields: Record<string, unknown>) => void;
|
|
23
23
|
readonly runtimeDemoted: (fields: Record<string, unknown>) => void;
|
|
24
|
+
/** ENC-0b: fires when a real ONNX InferenceSession is successfully created. */
|
|
25
|
+
readonly onnxSessionLoaded: (fields: Record<string, unknown>) => void;
|
|
24
26
|
}
|
|
25
27
|
|
|
26
28
|
/** A flag-gated no-op reporter (zero emissions, default when none injected). */
|
|
27
29
|
export const NOOP_ENCODER_REPORTER: EncoderReporter = {
|
|
28
30
|
assetVerified: () => {},
|
|
29
31
|
runtimeDemoted: () => {},
|
|
32
|
+
onnxSessionLoaded: () => {},
|
|
30
33
|
};
|
|
31
34
|
|
|
32
35
|
/**
|
|
@@ -47,5 +50,6 @@ export function createEncoderReporter(emit?: EncoderEmit): EncoderReporter {
|
|
|
47
50
|
return {
|
|
48
51
|
assetVerified: (fields) => fire("vector_cortex_encoder_asset_verified", fields),
|
|
49
52
|
runtimeDemoted: (fields) => fire("vector_cortex_encoder_runtime_demoted", fields),
|
|
53
|
+
onnxSessionLoaded: (fields) => fire("vector_cortex_encoder_onnx_loaded", fields),
|
|
50
54
|
};
|
|
51
55
|
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* encoder-onnx-dispatch.ts — ENC-0b ONNX dispatch: session lifecycle glue.
|
|
3
|
+
*
|
|
4
|
+
* Extracted from runtime.ts so runtime.ts stays under the 300-line soft limit.
|
|
5
|
+
* Encapsulates the ENC-0b real ONNX session build during load().
|
|
6
|
+
*
|
|
7
|
+
* IMPORTANT (interface constraint): EncoderRuntime.load() and infer() are
|
|
8
|
+
* SYNCHRONOUS — the router (router.ts:121-132) and all acceptance tests call
|
|
9
|
+
* them synchronously. ONNX session creation is ASYNC (WASM init). ENC-0b
|
|
10
|
+
* therefore expose the ONNX session as a SEPARATE async verification: load()
|
|
11
|
+
* fires-and-forgets the session build, verifySession() awaits it for tests,
|
|
12
|
+
* and infer() continues serving the LCG placeholder until the router is wired
|
|
13
|
+
* for async inference (ENC-0c scope). This preserves the sync contract while
|
|
14
|
+
* proving the real ONNX pipeline work end-to-end.
|
|
15
|
+
*
|
|
16
|
+
* ENC-0b gate: when MEGACOMPACT_ENC_0B is OFF, none of this code runs —
|
|
17
|
+
* the LCG stub serves mode A byte-identical to the predecessor.
|
|
18
|
+
*
|
|
19
|
+
* Pi-agnostic (PREVENT-PI-004 / PREVENT-011).
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
import { ENC_0B_ENABLED } from "../../config/vector-cortex.js";
|
|
23
|
+
import { buildOnnxSession, type OnnxInferenceSession } from "./onnx.js";
|
|
24
|
+
import type { EncoderReporter } from "./emit.js";
|
|
25
|
+
import type { ModelManifestV1 } from "./types.js";
|
|
26
|
+
|
|
27
|
+
/** State held by the runtime for the ENC-0b ONNX dispatch. */
|
|
28
|
+
export interface OnnxDispatchState {
|
|
29
|
+
/** The built ONNX session (null when not built, flag off, or build failed). */
|
|
30
|
+
readonly session: OnnxInferenceSession | null;
|
|
31
|
+
/** Resolved when the session build attempt settles (ok or fail). */
|
|
32
|
+
readonly ready: Promise<void>;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** No-op state when ENC-0b is off or session build not attempted. */
|
|
36
|
+
export const NO_ONNX: OnnxDispatchState = { session: null, ready: Promise.resolve() };
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Attempt a real ONNX session build during load(). Fire-and-forget: returns
|
|
40
|
+
* immediately with a state whose `ready` Promise resolves once the async
|
|
41
|
+
* build settles. On failure the state's `session` stays null.
|
|
42
|
+
*/
|
|
43
|
+
export function tryBuildOnnx(
|
|
44
|
+
assetDir: string,
|
|
45
|
+
manifest: ModelManifestV1,
|
|
46
|
+
reporter: EncoderReporter,
|
|
47
|
+
allocatedBytes: number,
|
|
48
|
+
): OnnxDispatchState {
|
|
49
|
+
if (!ENC_0B_ENABLED()) return NO_ONNX;
|
|
50
|
+
let session: OnnxInferenceSession | null = null;
|
|
51
|
+
const ready = buildOnnxSession(assetDir, manifest, reporter, allocatedBytes)
|
|
52
|
+
.then((result) => { if (result.ok) session = result.session; })
|
|
53
|
+
.catch(() => {});
|
|
54
|
+
// Return a state whose `session` getter reads the mutable binding after
|
|
55
|
+
// ready resolves (the getter runs at test assertion time, not at load()).
|
|
56
|
+
return { get session() { return session; }, ready };
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Await the ONNX session build and verify it with a real inference.
|
|
61
|
+
* Returns the embedding on success, null on any failure.
|
|
62
|
+
* Called by tests and the ENC-0b acceptance aggregator, not by production
|
|
63
|
+
* infer().
|
|
64
|
+
*/
|
|
65
|
+
export async function verifyOnnxSession(
|
|
66
|
+
state: OnnxDispatchState,
|
|
67
|
+
tokens: readonly number[],
|
|
68
|
+
): Promise<Float32Array | null> {
|
|
69
|
+
await state.ready;
|
|
70
|
+
if (!state.session) return null;
|
|
71
|
+
try {
|
|
72
|
+
return await state.session.infer(tokens);
|
|
73
|
+
} catch {
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
}
|