@adhd/sox-embedding-provider 0.4.1 → 0.5.1
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 +162 -13
- package/dist/embedWorker.js +6 -0
- package/dist/embedWorker.js.map +1 -1
- package/dist/fastembedLock.d.ts +47 -0
- package/dist/fastembedLock.d.ts.map +1 -1
- package/dist/fastembedLock.js +35 -0
- package/dist/fastembedLock.js.map +1 -1
- package/dist/fastembedProcessHost.d.ts.map +1 -1
- package/dist/fastembedProcessHost.js +37 -8
- package/dist/fastembedProcessHost.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/onnxStderrFilter.d.ts +78 -0
- package/dist/onnxStderrFilter.d.ts.map +1 -0
- package/dist/onnxStderrFilter.js +100 -0
- package/dist/onnxStderrFilter.js.map +1 -0
- package/dist/package.json +1 -1
- package/dist/sharedFastembedProcess.d.ts +34 -4
- package/dist/sharedFastembedProcess.d.ts.map +1 -1
- package/dist/sharedFastembedProcess.js +127 -17
- package/dist/sharedFastembedProcess.js.map +1 -1
- package/dist/sharedOnnxWorker.d.ts +15 -0
- package/dist/sharedOnnxWorker.d.ts.map +1 -1
- package/dist/sharedOnnxWorker.js +44 -2
- package/dist/sharedOnnxWorker.js.map +1 -1
- package/package.json +2 -2
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* (BUG-EMBED-ONNX-COREML-STDERR-NOISE-001) Filters the ONNX Runtime CoreML
|
|
3
|
+
* graph-partitioning warning out of the fastembed child's inherited stderr,
|
|
4
|
+
* while forwarding every other byte through verbatim, unbuffered, and in
|
|
5
|
+
* order.
|
|
6
|
+
*
|
|
7
|
+
* ── Why this exists ─────────────────────────────────────────────────────────
|
|
8
|
+
*
|
|
9
|
+
* `sharedFastembedProcess.ts` forks `fastembedProcessHost.ts` with
|
|
10
|
+
* `resolveExecutionProviders()` returning `['coreml', 'cpu']` on darwin
|
|
11
|
+
* (`fastembedProcessHost.ts`). bge-base's `word_embeddings` tensor is shaped
|
|
12
|
+
* `{30522,768}`, and CoreML's execution provider cannot host any input
|
|
13
|
+
* dimension over 16384 — so onnxruntime's native CoreML EP logs, at EVERY
|
|
14
|
+
* model load:
|
|
15
|
+
*
|
|
16
|
+
* [W:onnxruntime:, helper.cc:83 IsInputSupported] CoreML does not support
|
|
17
|
+
* input dim > 16384. Input:embeddings.word_embeddings.weight,
|
|
18
|
+
* shape: {30522,768}
|
|
19
|
+
*
|
|
20
|
+
* This is emitted by the native onnxruntime addon straight to the process's
|
|
21
|
+
* real stderr fd (not through any JS logger this package controls), and
|
|
22
|
+
* `sharedFastembedProcess.ts` forks the child with
|
|
23
|
+
* `stdio: ['ignore', 'inherit', 'inherit', 'ipc']` — so it lands on every
|
|
24
|
+
* `adhd-backlog` (or any other consumer) invocation's terminal, on every
|
|
25
|
+
* single command, even though the node in question simply falls back to CPU
|
|
26
|
+
* silently and nothing is actually wrong.
|
|
27
|
+
*
|
|
28
|
+
* ── Why this is a stderr *filter*, not a log-severity option ────────────────
|
|
29
|
+
*
|
|
30
|
+
* onnxruntime-node's `InferenceSession.SessionOptions` DOES expose a
|
|
31
|
+
* `logSeverityLevel` (0=Verbose…4=Fatal) that would suppress Warning-level
|
|
32
|
+
* messages at the source — a cleaner fix, if it were reachable. It is not:
|
|
33
|
+
* `fastembed@2.1.0`'s `FlagEmbedding.init()` (`fastembed.js`'s
|
|
34
|
+
* `ort.InferenceSession.create(modelPath, { executionProviders,
|
|
35
|
+
* graphOptimizationLevel: "all" })`) hardcodes its own `SessionOptions`
|
|
36
|
+
* literal with no passthrough for caller options, and `fastembed`'s public
|
|
37
|
+
* `InitOptions` type has no `sessionOptions`/`logSeverityLevel` field either
|
|
38
|
+
* (verified against the installed `fastembed@2.1.0` and
|
|
39
|
+
* `onnxruntime-node@1.21.0` packages — not assumed). Filtering the child's
|
|
40
|
+
* stderr byte stream is therefore the only lever this package can pull.
|
|
41
|
+
*
|
|
42
|
+
* ── Why this must NOT become "throw away all child stderr" ──────────────────
|
|
43
|
+
*
|
|
44
|
+
* `fastembedProcessHost.ts`'s BL-331 lock check intentionally
|
|
45
|
+
* `console.error`s a loud, greppable line when a competing fastembed host is
|
|
46
|
+
* detected (guarding against a silent 25-50x embed-latency regression), and
|
|
47
|
+
* `resolveExecutionProviders()` intentionally `console.error`s when
|
|
48
|
+
* `SOX_EMBED_EXECUTION_PROVIDER` forces a non-default provider. A blanket
|
|
49
|
+
* "pipe stderr, discard everything" fix would silently destroy both of those
|
|
50
|
+
* — reintroducing exactly the silent-failure class BL-331 exists to prevent.
|
|
51
|
+
* So this filter matches ONLY the known-benign CoreML dim-limit warning text
|
|
52
|
+
* and passes every other line — including genuine errors and both lines
|
|
53
|
+
* above — straight through unmodified.
|
|
54
|
+
*/
|
|
55
|
+
/**
|
|
56
|
+
* True if `line` is the known-benign ONNX Runtime CoreML dim-limit warning
|
|
57
|
+
* (see module doc comment). This is a plain substring match — deliberately
|
|
58
|
+
* narrow (not a broad `onnxruntime`/`[W:` pattern) so this filter can never
|
|
59
|
+
* swallow an unrelated onnxruntime warning or error that happens to share the
|
|
60
|
+
* `[W:onnxruntime:...]` prefix format.
|
|
61
|
+
*/
|
|
62
|
+
export declare function isBenignOnnxCoreMlWarning(line: string): boolean;
|
|
63
|
+
/**
|
|
64
|
+
* Attach a line-buffering filter to a child process's stderr `Readable`,
|
|
65
|
+
* dropping only lines matching {@link isBenignOnnxCoreMlWarning} and writing
|
|
66
|
+
* every other line through to `dest` (defaults to `process.stderr`)
|
|
67
|
+
* unbuffered, verbatim (original line content, `\n`-terminated), and in the
|
|
68
|
+
* order it was received.
|
|
69
|
+
*
|
|
70
|
+
* Chunk boundaries never line up with `\n` boundaries in a real pipe, so
|
|
71
|
+
* this buffers a trailing partial line across `data` events instead of
|
|
72
|
+
* filtering mid-line: `source` is split on `\n` as chunks arrive, each
|
|
73
|
+
* complete line is tested and (if not benign) written immediately, and any
|
|
74
|
+
* remaining partial line is flushed on `end` (also filtered, so a benign
|
|
75
|
+
* warning that happens to be the final unterminated write is still caught).
|
|
76
|
+
*/
|
|
77
|
+
export declare function attachOnnxStderrFilter(source: NodeJS.ReadableStream, dest?: NodeJS.WritableStream): void;
|
|
78
|
+
//# sourceMappingURL=onnxStderrFilter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"onnxStderrFilter.d.ts","sourceRoot":"","sources":["../src/onnxStderrFilter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqDG;AAIH;;;;;;GAMG;AACH,wBAAgB,yBAAyB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAE/D;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,sBAAsB,CACpC,MAAM,EAAE,MAAM,CAAC,cAAc,EAC7B,IAAI,GAAE,MAAM,CAAC,cAA+B,GAC3C,IAAI,CAmBN"}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* (BUG-EMBED-ONNX-COREML-STDERR-NOISE-001) Filters the ONNX Runtime CoreML
|
|
3
|
+
* graph-partitioning warning out of the fastembed child's inherited stderr,
|
|
4
|
+
* while forwarding every other byte through verbatim, unbuffered, and in
|
|
5
|
+
* order.
|
|
6
|
+
*
|
|
7
|
+
* ── Why this exists ─────────────────────────────────────────────────────────
|
|
8
|
+
*
|
|
9
|
+
* `sharedFastembedProcess.ts` forks `fastembedProcessHost.ts` with
|
|
10
|
+
* `resolveExecutionProviders()` returning `['coreml', 'cpu']` on darwin
|
|
11
|
+
* (`fastembedProcessHost.ts`). bge-base's `word_embeddings` tensor is shaped
|
|
12
|
+
* `{30522,768}`, and CoreML's execution provider cannot host any input
|
|
13
|
+
* dimension over 16384 — so onnxruntime's native CoreML EP logs, at EVERY
|
|
14
|
+
* model load:
|
|
15
|
+
*
|
|
16
|
+
* [W:onnxruntime:, helper.cc:83 IsInputSupported] CoreML does not support
|
|
17
|
+
* input dim > 16384. Input:embeddings.word_embeddings.weight,
|
|
18
|
+
* shape: {30522,768}
|
|
19
|
+
*
|
|
20
|
+
* This is emitted by the native onnxruntime addon straight to the process's
|
|
21
|
+
* real stderr fd (not through any JS logger this package controls), and
|
|
22
|
+
* `sharedFastembedProcess.ts` forks the child with
|
|
23
|
+
* `stdio: ['ignore', 'inherit', 'inherit', 'ipc']` — so it lands on every
|
|
24
|
+
* `adhd-backlog` (or any other consumer) invocation's terminal, on every
|
|
25
|
+
* single command, even though the node in question simply falls back to CPU
|
|
26
|
+
* silently and nothing is actually wrong.
|
|
27
|
+
*
|
|
28
|
+
* ── Why this is a stderr *filter*, not a log-severity option ────────────────
|
|
29
|
+
*
|
|
30
|
+
* onnxruntime-node's `InferenceSession.SessionOptions` DOES expose a
|
|
31
|
+
* `logSeverityLevel` (0=Verbose…4=Fatal) that would suppress Warning-level
|
|
32
|
+
* messages at the source — a cleaner fix, if it were reachable. It is not:
|
|
33
|
+
* `fastembed@2.1.0`'s `FlagEmbedding.init()` (`fastembed.js`'s
|
|
34
|
+
* `ort.InferenceSession.create(modelPath, { executionProviders,
|
|
35
|
+
* graphOptimizationLevel: "all" })`) hardcodes its own `SessionOptions`
|
|
36
|
+
* literal with no passthrough for caller options, and `fastembed`'s public
|
|
37
|
+
* `InitOptions` type has no `sessionOptions`/`logSeverityLevel` field either
|
|
38
|
+
* (verified against the installed `fastembed@2.1.0` and
|
|
39
|
+
* `onnxruntime-node@1.21.0` packages — not assumed). Filtering the child's
|
|
40
|
+
* stderr byte stream is therefore the only lever this package can pull.
|
|
41
|
+
*
|
|
42
|
+
* ── Why this must NOT become "throw away all child stderr" ──────────────────
|
|
43
|
+
*
|
|
44
|
+
* `fastembedProcessHost.ts`'s BL-331 lock check intentionally
|
|
45
|
+
* `console.error`s a loud, greppable line when a competing fastembed host is
|
|
46
|
+
* detected (guarding against a silent 25-50x embed-latency regression), and
|
|
47
|
+
* `resolveExecutionProviders()` intentionally `console.error`s when
|
|
48
|
+
* `SOX_EMBED_EXECUTION_PROVIDER` forces a non-default provider. A blanket
|
|
49
|
+
* "pipe stderr, discard everything" fix would silently destroy both of those
|
|
50
|
+
* — reintroducing exactly the silent-failure class BL-331 exists to prevent.
|
|
51
|
+
* So this filter matches ONLY the known-benign CoreML dim-limit warning text
|
|
52
|
+
* and passes every other line — including genuine errors and both lines
|
|
53
|
+
* above — straight through unmodified.
|
|
54
|
+
*/
|
|
55
|
+
const BENIGN_CORE_ML_WARNING_SUBSTRING = 'CoreML does not support input dim > 16384';
|
|
56
|
+
/**
|
|
57
|
+
* True if `line` is the known-benign ONNX Runtime CoreML dim-limit warning
|
|
58
|
+
* (see module doc comment). This is a plain substring match — deliberately
|
|
59
|
+
* narrow (not a broad `onnxruntime`/`[W:` pattern) so this filter can never
|
|
60
|
+
* swallow an unrelated onnxruntime warning or error that happens to share the
|
|
61
|
+
* `[W:onnxruntime:...]` prefix format.
|
|
62
|
+
*/
|
|
63
|
+
export function isBenignOnnxCoreMlWarning(line) {
|
|
64
|
+
return line.includes(BENIGN_CORE_ML_WARNING_SUBSTRING);
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Attach a line-buffering filter to a child process's stderr `Readable`,
|
|
68
|
+
* dropping only lines matching {@link isBenignOnnxCoreMlWarning} and writing
|
|
69
|
+
* every other line through to `dest` (defaults to `process.stderr`)
|
|
70
|
+
* unbuffered, verbatim (original line content, `\n`-terminated), and in the
|
|
71
|
+
* order it was received.
|
|
72
|
+
*
|
|
73
|
+
* Chunk boundaries never line up with `\n` boundaries in a real pipe, so
|
|
74
|
+
* this buffers a trailing partial line across `data` events instead of
|
|
75
|
+
* filtering mid-line: `source` is split on `\n` as chunks arrive, each
|
|
76
|
+
* complete line is tested and (if not benign) written immediately, and any
|
|
77
|
+
* remaining partial line is flushed on `end` (also filtered, so a benign
|
|
78
|
+
* warning that happens to be the final unterminated write is still caught).
|
|
79
|
+
*/
|
|
80
|
+
export function attachOnnxStderrFilter(source, dest = process.stderr) {
|
|
81
|
+
let buffer = '';
|
|
82
|
+
source.on('data', (chunk) => {
|
|
83
|
+
buffer += chunk.toString('utf8');
|
|
84
|
+
let idx;
|
|
85
|
+
while ((idx = buffer.indexOf('\n')) !== -1) {
|
|
86
|
+
const line = buffer.slice(0, idx);
|
|
87
|
+
buffer = buffer.slice(idx + 1);
|
|
88
|
+
if (!isBenignOnnxCoreMlWarning(line)) {
|
|
89
|
+
dest.write(`${line}\n`);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
source.on('end', () => {
|
|
94
|
+
if (buffer.length > 0 && !isBenignOnnxCoreMlWarning(buffer)) {
|
|
95
|
+
dest.write(buffer);
|
|
96
|
+
}
|
|
97
|
+
buffer = '';
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
//# sourceMappingURL=onnxStderrFilter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"onnxStderrFilter.js","sourceRoot":"","sources":["../src/onnxStderrFilter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqDG;AAEH,MAAM,gCAAgC,GAAG,2CAA2C,CAAC;AAErF;;;;;;GAMG;AACH,MAAM,UAAU,yBAAyB,CAAC,IAAY;IACpD,OAAO,IAAI,CAAC,QAAQ,CAAC,gCAAgC,CAAC,CAAC;AACzD,CAAC;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,sBAAsB,CACpC,MAA6B,EAC7B,OAA8B,OAAO,CAAC,MAAM;IAE5C,IAAI,MAAM,GAAG,EAAE,CAAC;IAChB,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,KAAsB,EAAE,EAAE;QAC3C,MAAM,IAAI,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;QACjC,IAAI,GAAW,CAAC;QAChB,OAAO,CAAC,GAAG,GAAG,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC;YAC3C,MAAM,IAAI,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;YAClC,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC;YAC/B,IAAI,CAAC,yBAAyB,CAAC,IAAI,CAAC,EAAE,CAAC;gBACrC,IAAI,CAAC,KAAK,CAAC,GAAG,IAAI,IAAI,CAAC,CAAC;YAC1B,CAAC;QACH,CAAC;IACH,CAAC,CAAC,CAAC;IACH,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,GAAG,EAAE;QACpB,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,IAAI,CAAC,yBAAyB,CAAC,MAAM,CAAC,EAAE,CAAC;YAC5D,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;QACrB,CAAC;QACD,MAAM,GAAG,EAAE,CAAC;IACd,CAAC,CAAC,CAAC;AACL,CAAC"}
|
package/dist/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adhd/sox-embedding-provider",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Pluggable text→vector embedding provider — generic EmbeddingProvider interface, config-driven model resolution, async batch-first API (AsyncIterable). Default: fastembed (local ONNX, >=3 model dims proven). Loud-fail: createEmbeddingProvider() throws ResolutionError if config is invalid or model cannot load — no silent downgrade.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"private": false,
|
|
@@ -28,10 +28,18 @@ export declare function __resetCompetingHostCacheForTests(): void;
|
|
|
28
28
|
* `competing_host_pid` telemetry field would read as permanently "contended"
|
|
29
29
|
* for every pooled request, which is exactly the false-positive noise BL-331's
|
|
30
30
|
* own postmortem warns against trusting.
|
|
31
|
+
*
|
|
32
|
+
* (BL-432) `ownService`, when supplied, does the same for SERVICE identity: a
|
|
33
|
+
* lock entry whose `service` equals our own is the sequential-CLI false
|
|
34
|
+
* positive (our own service's earlier/second host), not cross-service
|
|
35
|
+
* contention, and is suppressed. Requires BOTH sides to carry a real identity —
|
|
36
|
+
* `ownService === undefined` or an unlabelled/old lock keeps the original
|
|
37
|
+
* behaviour. The returned `service` is `null` when the lock carries none.
|
|
31
38
|
*/
|
|
32
|
-
export declare function detectCompetingFastembedHost(ownPid: number | undefined, ownPoolGroup?: string): {
|
|
39
|
+
export declare function detectCompetingFastembedHost(ownPid: number | undefined, ownPoolGroup?: string, ownService?: string): {
|
|
33
40
|
pid: number;
|
|
34
41
|
startedAt: string;
|
|
42
|
+
service: string | null;
|
|
35
43
|
} | null;
|
|
36
44
|
/**
|
|
37
45
|
* (BUG-MEMORY-EMBED-HEAD-OF-LINE-BLOCKING-001) The structural shape both
|
|
@@ -224,9 +232,12 @@ export declare class SharedFastembedProcessClient implements SharedFastembedClie
|
|
|
224
232
|
* `queue_depth` > 0 alongside a `response_ms` that scales with it) is
|
|
225
233
|
* distinguishable from "the child itself was slow on a solo request"
|
|
226
234
|
* (`queue_depth` === 0 with a large `response_ms`).
|
|
227
|
-
* 3. `competing_host_pid`
|
|
228
|
-
*
|
|
229
|
-
*
|
|
235
|
+
* 3. `competing_host_pid` (+ `competing_host_service`, BL-432) — present
|
|
236
|
+
* only when a second, still-live `fastembedProcessHost` process belonging
|
|
237
|
+
* to a DIFFERENT service is detected (BL-331's advisory lock), since
|
|
238
|
+
* that changes embed latency 25-50x independent of queueing. A
|
|
239
|
+
* same-service lock (the sequential-CLI false positive) is suppressed,
|
|
240
|
+
* so this field means genuine cross-service contention.
|
|
230
241
|
*
|
|
231
242
|
* BL-576: `signal`, when supplied, is an external cancellation source —
|
|
232
243
|
* e.g. `operation-guard.ts`'s `withOperationDeadline` abort controller, so
|
|
@@ -541,4 +552,23 @@ export declare function getSharedFastembedProcess(): SharedFastembedClient;
|
|
|
541
552
|
* needed.
|
|
542
553
|
*/
|
|
543
554
|
export declare function __resetSharedFastembedProcessForTests(): void;
|
|
555
|
+
/**
|
|
556
|
+
* (BUG-MEMORYSERVER-EMBED-HEAL-NOOPERATOR-001) Kill AND clear the shared
|
|
557
|
+
* fastembed host singleton, so the next `getSharedFastembedProcess()` forks a
|
|
558
|
+
* genuinely fresh child process (and re-inits the model). This is the
|
|
559
|
+
* embedding-provider half of memory-core's `reinitEmbedProvider()` — the
|
|
560
|
+
* self-heal path that recovers a wedged fastembed child (BUG-021's
|
|
561
|
+
* "Model not initialized" respawn-without-reinit state) by tearing the whole
|
|
562
|
+
* shared host down and re-forking from scratch, rather than waiting for the
|
|
563
|
+
* dead child to be reaped.
|
|
564
|
+
*
|
|
565
|
+
* Unlike `__resetSharedFastembedProcessForTests` (test-only, does NOT
|
|
566
|
+
* terminate), this TERMINATES the existing singleton first — killing the child
|
|
567
|
+
* process(es) via the normal `terminate()` shutdown sequence (BL-405's clean
|
|
568
|
+
* `__shutdown` message, `kill()` fallback) — and only then drops the singleton
|
|
569
|
+
* reference. Safe to call when no singleton has ever been constructed (no-op).
|
|
570
|
+
* Any in-flight request is rejected with the standard termination error, which
|
|
571
|
+
* the heal loop surfaces as a row failure — never a hang.
|
|
572
|
+
*/
|
|
573
|
+
export declare function resetSharedFastembedProcess(): Promise<void>;
|
|
544
574
|
//# sourceMappingURL=sharedFastembedProcess.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sharedFastembedProcess.d.ts","sourceRoot":"","sources":["../src/sharedFastembedProcess.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;
|
|
1
|
+
{"version":3,"file":"sharedFastembedProcess.d.ts","sourceRoot":"","sources":["../src/sharedFastembedProcess.ts"],"names":[],"mappings":"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"}
|
|
@@ -15,14 +15,15 @@
|
|
|
15
15
|
* process alive) but forks a child **process** instead of constructing a
|
|
16
16
|
* `worker_threads.Worker`.
|
|
17
17
|
*/
|
|
18
|
-
import {
|
|
18
|
+
import { execSync } from 'node:child_process';
|
|
19
19
|
import { fileURLToPath } from 'node:url';
|
|
20
20
|
import { dirname, join } from 'node:path';
|
|
21
21
|
import { existsSync, readFileSync } from 'node:fs';
|
|
22
22
|
import * as os from 'node:os';
|
|
23
23
|
import { performance } from 'node:perf_hooks';
|
|
24
|
-
import { log } from '@adhd/sox-telemetry';
|
|
25
|
-
import { resolveFastembedLockPath } from './fastembedLock.js';
|
|
24
|
+
import { log, forkChild, _recordChildTelemetry, currentRuntimeState } from '@adhd/sox-telemetry';
|
|
25
|
+
import { resolveFastembedLockPath, resolveFastembedServiceLabel } from './fastembedLock.js';
|
|
26
|
+
import { attachOnnxStderrFilter } from './onnxStderrFilter.js';
|
|
26
27
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
27
28
|
/**
|
|
28
29
|
* Resolve `fastembedProcessHost.js` regardless of whether this module is
|
|
@@ -97,12 +98,14 @@ const COMPETING_HOST_CACHE_TTL_MS = 3000;
|
|
|
97
98
|
let _competingHostCache = null;
|
|
98
99
|
let _competingHostCacheOwnPid;
|
|
99
100
|
let _competingHostCacheOwnPoolGroup;
|
|
101
|
+
let _competingHostCacheOwnService;
|
|
100
102
|
let _competingHostCacheAt = -Infinity;
|
|
101
103
|
/** TEST-ONLY: clear the TTL cache so a test can force a fresh fs read. */
|
|
102
104
|
export function __resetCompetingHostCacheForTests() {
|
|
103
105
|
_competingHostCache = null;
|
|
104
106
|
_competingHostCacheOwnPid = undefined;
|
|
105
107
|
_competingHostCacheOwnPoolGroup = undefined;
|
|
108
|
+
_competingHostCacheOwnService = undefined;
|
|
106
109
|
_competingHostCacheAt = -Infinity;
|
|
107
110
|
}
|
|
108
111
|
/**
|
|
@@ -116,17 +119,26 @@ export function __resetCompetingHostCacheForTests() {
|
|
|
116
119
|
* `competing_host_pid` telemetry field would read as permanently "contended"
|
|
117
120
|
* for every pooled request, which is exactly the false-positive noise BL-331's
|
|
118
121
|
* own postmortem warns against trusting.
|
|
122
|
+
*
|
|
123
|
+
* (BL-432) `ownService`, when supplied, does the same for SERVICE identity: a
|
|
124
|
+
* lock entry whose `service` equals our own is the sequential-CLI false
|
|
125
|
+
* positive (our own service's earlier/second host), not cross-service
|
|
126
|
+
* contention, and is suppressed. Requires BOTH sides to carry a real identity —
|
|
127
|
+
* `ownService === undefined` or an unlabelled/old lock keeps the original
|
|
128
|
+
* behaviour. The returned `service` is `null` when the lock carries none.
|
|
119
129
|
*/
|
|
120
|
-
export function detectCompetingFastembedHost(ownPid, ownPoolGroup) {
|
|
130
|
+
export function detectCompetingFastembedHost(ownPid, ownPoolGroup, ownService) {
|
|
121
131
|
const now = performance.now();
|
|
122
132
|
if (now - _competingHostCacheAt < COMPETING_HOST_CACHE_TTL_MS &&
|
|
123
133
|
_competingHostCacheOwnPid === ownPid &&
|
|
124
|
-
_competingHostCacheOwnPoolGroup === ownPoolGroup
|
|
134
|
+
_competingHostCacheOwnPoolGroup === ownPoolGroup &&
|
|
135
|
+
_competingHostCacheOwnService === ownService) {
|
|
125
136
|
return _competingHostCache;
|
|
126
137
|
}
|
|
127
138
|
_competingHostCacheAt = now;
|
|
128
139
|
_competingHostCacheOwnPid = ownPid;
|
|
129
140
|
_competingHostCacheOwnPoolGroup = ownPoolGroup;
|
|
141
|
+
_competingHostCacheOwnService = ownService;
|
|
130
142
|
try {
|
|
131
143
|
const lockPath = resolveFastembedLockPath();
|
|
132
144
|
if (!existsSync(lockPath)) {
|
|
@@ -136,11 +148,17 @@ export function detectCompetingFastembedHost(ownPid, ownPoolGroup) {
|
|
|
136
148
|
const raw = JSON.parse(readFileSync(lockPath, 'utf8'));
|
|
137
149
|
const pid = typeof raw.pid === 'number' ? raw.pid : null;
|
|
138
150
|
const isKnownPoolSibling = typeof raw.poolGroup === 'string' && ownPoolGroup !== undefined && raw.poolGroup === ownPoolGroup;
|
|
139
|
-
|
|
151
|
+
// (BL-432) Same-service suppression — see this function's doc comment.
|
|
152
|
+
const isSameService = typeof raw.service === 'string' && ownService !== undefined && raw.service === ownService;
|
|
153
|
+
if (pid === null || pid === ownPid || !isPidAlive(pid) || isKnownPoolSibling || isSameService) {
|
|
140
154
|
_competingHostCache = null;
|
|
141
155
|
return null;
|
|
142
156
|
}
|
|
143
|
-
_competingHostCache = {
|
|
157
|
+
_competingHostCache = {
|
|
158
|
+
pid,
|
|
159
|
+
startedAt: typeof raw.startedAt === 'string' ? raw.startedAt : 'unknown',
|
|
160
|
+
service: typeof raw.service === 'string' ? raw.service : null,
|
|
161
|
+
};
|
|
144
162
|
return _competingHostCache;
|
|
145
163
|
}
|
|
146
164
|
catch {
|
|
@@ -317,17 +335,53 @@ export class SharedFastembedProcessClient {
|
|
|
317
335
|
return this.startingPromise;
|
|
318
336
|
this.startingPromise = new Promise((resolveStart) => {
|
|
319
337
|
const hostPath = this.hostPath ?? resolveFastembedHostPath();
|
|
320
|
-
|
|
321
|
-
|
|
338
|
+
// (BL-432) This client's owning-service identity, forwarded to the child
|
|
339
|
+
// via `SOX_FASTEMBED_SERVICE` so the BL-331 lock it writes names the
|
|
340
|
+
// SERVICE, and so same-service locks can be suppressed. Resolved from the
|
|
341
|
+
// parent's own telemetry service (`currentRuntimeState().service`), or a
|
|
342
|
+
// propagated `SOX_FASTEMBED_SERVICE` if one is already set on us.
|
|
343
|
+
const childService = resolveFastembedServiceLabel(currentRuntimeState().service);
|
|
344
|
+
const c = forkChild(hostPath, { service: 'embedding-provider', role: 'harness', logSink: 'file' }, {
|
|
345
|
+
// (BUG-EMBED-ONNX-COREML-STDERR-NOISE-001) stderr is 'pipe', not
|
|
346
|
+
// 'inherit' — see `onnxStderrFilter.ts`'s doc comment for why: the
|
|
347
|
+
// native onnxruntime CoreML EP logs a known-benign "CoreML does not
|
|
348
|
+
// support input dim > 16384" warning on every model load (bge-base's
|
|
349
|
+
// {30522,768} word_embeddings tensor), and it must be dropped
|
|
350
|
+
// without touching any other line (the BL-331 competing-host
|
|
351
|
+
// warning and the forced-execution-provider line above both go
|
|
352
|
+
// through `console.error` in the SAME child and must reach the
|
|
353
|
+
// real terminal unmodified). stdout stays 'inherit' — this package
|
|
354
|
+
// never wanted stdout piped, only stderr filtered.
|
|
355
|
+
stdio: ['ignore', 'inherit', 'pipe', 'ipc'],
|
|
322
356
|
// Real inference is CPU-bound in native code; no need to keep the
|
|
323
357
|
// parent process alive on this child's account.
|
|
324
358
|
detached: false,
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
: {}),
|
|
359
|
+
env: {
|
|
360
|
+
...process.env,
|
|
361
|
+
...(this.poolGroup !== undefined ? { SOX_FASTEMBED_POOL_GROUP: this.poolGroup } : {}),
|
|
362
|
+
...(childService !== undefined ? { SOX_FASTEMBED_SERVICE: childService } : {}),
|
|
363
|
+
},
|
|
364
|
+
});
|
|
365
|
+
if (c.stderr)
|
|
366
|
+
attachOnnxStderrFilter(c.stderr);
|
|
367
|
+
_recordChildTelemetry({
|
|
368
|
+
service: 'embedding-provider',
|
|
369
|
+
role: 'harness',
|
|
370
|
+
logSink: 'file',
|
|
371
|
+
filePath: null,
|
|
372
|
+
pid: c.pid ?? 0,
|
|
373
|
+
source: 'embedding-provider',
|
|
328
374
|
});
|
|
329
375
|
c.unref();
|
|
330
376
|
c.on('message', (msg) => {
|
|
377
|
+
// BL-618: the child's telemetry.ready ack carries no request `id` — it
|
|
378
|
+
// must be handled BEFORE the pending lookup, and it never settles a
|
|
379
|
+
// pending request, only records the child's telemetry state.
|
|
380
|
+
const ready = msg;
|
|
381
|
+
if (ready.type === 'telemetry.ready' && ready.telemetry && typeof ready.telemetry === 'object') {
|
|
382
|
+
_recordChildTelemetry({ ...ready.telemetry, source: 'embedding-provider', acked: true });
|
|
383
|
+
return;
|
|
384
|
+
}
|
|
331
385
|
const pending = this.pending.get(msg.id);
|
|
332
386
|
if (!pending)
|
|
333
387
|
return;
|
|
@@ -385,6 +439,19 @@ export class SharedFastembedProcessClient {
|
|
|
385
439
|
// idle at 0% CPU. Cross-process ANE contention remains unproven; the
|
|
386
440
|
// measured cause of the live slowdown was scheduling QoS (BL-331).
|
|
387
441
|
c.channel?.unref();
|
|
442
|
+
// (BUG-EMBED-ONNX-COREML-STDERR-NOISE-001) Same BL-370 lesson applies to
|
|
443
|
+
// the new 'pipe' stderr handle: attaching the 'data' listener inside
|
|
444
|
+
// `attachOnnxStderrFilter()` puts the underlying Socket into flowing
|
|
445
|
+
// mode, which re-refs it — re-assert `unref()` here so a standalone
|
|
446
|
+
// script with no other outstanding work can still exit promptly instead
|
|
447
|
+
// of hanging on this pipe forever. `child.stderr` is typed as a plain
|
|
448
|
+
// `Readable`, but for `stdio: 'pipe'` it is actually a `net.Socket`
|
|
449
|
+
// (which alone carries `unref()`) — narrowed via a structural check
|
|
450
|
+
// instead of a blind cast so a future Node stdio-shape change fails
|
|
451
|
+
// safe (silently skips the unref) rather than throwing.
|
|
452
|
+
const stderrHandle = c.stderr;
|
|
453
|
+
if (typeof stderrHandle?.unref === 'function')
|
|
454
|
+
stderrHandle.unref();
|
|
388
455
|
this.child = c;
|
|
389
456
|
resolveStart(c);
|
|
390
457
|
});
|
|
@@ -437,9 +504,12 @@ export class SharedFastembedProcessClient {
|
|
|
437
504
|
* `queue_depth` > 0 alongside a `response_ms` that scales with it) is
|
|
438
505
|
* distinguishable from "the child itself was slow on a solo request"
|
|
439
506
|
* (`queue_depth` === 0 with a large `response_ms`).
|
|
440
|
-
* 3. `competing_host_pid`
|
|
441
|
-
*
|
|
442
|
-
*
|
|
507
|
+
* 3. `competing_host_pid` (+ `competing_host_service`, BL-432) — present
|
|
508
|
+
* only when a second, still-live `fastembedProcessHost` process belonging
|
|
509
|
+
* to a DIFFERENT service is detected (BL-331's advisory lock), since
|
|
510
|
+
* that changes embed latency 25-50x independent of queueing. A
|
|
511
|
+
* same-service lock (the sequential-CLI false positive) is suppressed,
|
|
512
|
+
* so this field means genuine cross-service contention.
|
|
443
513
|
*
|
|
444
514
|
* BL-576: `signal`, when supplied, is an external cancellation source —
|
|
445
515
|
* e.g. `operation-guard.ts`'s `withOperationDeadline` abort controller, so
|
|
@@ -488,10 +558,19 @@ export class SharedFastembedProcessClient {
|
|
|
488
558
|
}
|
|
489
559
|
const id = this.nextId++;
|
|
490
560
|
const queueDepth = this.pending.size;
|
|
491
|
-
|
|
561
|
+
// (BL-432) Our own service identity, so `detectCompetingFastembedHost` can
|
|
562
|
+
// suppress a same-service lock (the sequential-CLI false positive) while
|
|
563
|
+
// still reporting genuine cross-service contention with identity.
|
|
564
|
+
const ownService = resolveFastembedServiceLabel(currentRuntimeState().service);
|
|
565
|
+
const competing = detectCompetingFastembedHost(child.pid, this.poolGroup, ownService);
|
|
492
566
|
const baseFields = {
|
|
493
567
|
queue_depth: queueDepth,
|
|
494
|
-
...(competing
|
|
568
|
+
...(competing
|
|
569
|
+
? {
|
|
570
|
+
competing_host_pid: competing.pid,
|
|
571
|
+
competing_host_service: competing.service ?? 'unknown',
|
|
572
|
+
}
|
|
573
|
+
: {}),
|
|
495
574
|
// (BUG-EMBED-POOL-SIZE-DARWIN-FREEMEM-001) See the constructor's
|
|
496
575
|
// `poolSize`/`memberIndex` doc comment: makes "was this process even
|
|
497
576
|
// pooled, and at what size" a directly observable telemetry field
|
|
@@ -1371,4 +1450,35 @@ export function getSharedFastembedProcess() {
|
|
|
1371
1450
|
export function __resetSharedFastembedProcessForTests() {
|
|
1372
1451
|
_singleton = null;
|
|
1373
1452
|
}
|
|
1453
|
+
/**
|
|
1454
|
+
* (BUG-MEMORYSERVER-EMBED-HEAL-NOOPERATOR-001) Kill AND clear the shared
|
|
1455
|
+
* fastembed host singleton, so the next `getSharedFastembedProcess()` forks a
|
|
1456
|
+
* genuinely fresh child process (and re-inits the model). This is the
|
|
1457
|
+
* embedding-provider half of memory-core's `reinitEmbedProvider()` — the
|
|
1458
|
+
* self-heal path that recovers a wedged fastembed child (BUG-021's
|
|
1459
|
+
* "Model not initialized" respawn-without-reinit state) by tearing the whole
|
|
1460
|
+
* shared host down and re-forking from scratch, rather than waiting for the
|
|
1461
|
+
* dead child to be reaped.
|
|
1462
|
+
*
|
|
1463
|
+
* Unlike `__resetSharedFastembedProcessForTests` (test-only, does NOT
|
|
1464
|
+
* terminate), this TERMINATES the existing singleton first — killing the child
|
|
1465
|
+
* process(es) via the normal `terminate()` shutdown sequence (BL-405's clean
|
|
1466
|
+
* `__shutdown` message, `kill()` fallback) — and only then drops the singleton
|
|
1467
|
+
* reference. Safe to call when no singleton has ever been constructed (no-op).
|
|
1468
|
+
* Any in-flight request is rejected with the standard termination error, which
|
|
1469
|
+
* the heal loop surfaces as a row failure — never a hang.
|
|
1470
|
+
*/
|
|
1471
|
+
export async function resetSharedFastembedProcess() {
|
|
1472
|
+
const prev = _singleton;
|
|
1473
|
+
_singleton = null;
|
|
1474
|
+
if (prev) {
|
|
1475
|
+
try {
|
|
1476
|
+
await prev.terminate();
|
|
1477
|
+
}
|
|
1478
|
+
catch {
|
|
1479
|
+
// Terminate must never throw into the reset path — the singleton is
|
|
1480
|
+
// already dropped; a best-effort kill is all that remains.
|
|
1481
|
+
}
|
|
1482
|
+
}
|
|
1483
|
+
}
|
|
1374
1484
|
//# sourceMappingURL=sharedFastembedProcess.js.map
|