@camstack/addon-pipeline 1.1.69 → 1.1.71
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/audio-analyzer/index.js +1 -1
- package/dist/audio-analyzer/index.mjs +1 -1
- package/dist/detection-pipeline/index.js +215 -485
- package/dist/detection-pipeline/index.mjs +216 -485
- package/dist/{dist-BT4Ug2Bf.mjs → dist-DyALtmD7.mjs} +240 -45
- package/dist/{dist-DUcDVlg7.js → dist-DzTjxf39.js} +251 -44
- package/dist/motion-wasm/index.js +1 -1
- package/dist/motion-wasm/index.mjs +1 -1
- package/dist/pipeline-runner/index.js +24 -2
- package/dist/pipeline-runner/index.mjs +24 -2
- package/dist/recorder/index.js +1 -1
- package/dist/recorder/index.mjs +1 -1
- package/dist/session-decode/decode-worker-child.js +177 -12
- package/dist/session-decode/decode-worker-child.mjs +177 -12
- package/dist/{step-definitions-4tt-nRM4.js → step-definitions-Djy8aZLt.js} +6 -2
- package/dist/{step-definitions-DO0hIDMx.mjs → step-definitions-rmBALHX4.mjs} +6 -2
- package/dist/stream-broker/_stub.js +2 -2
- package/dist/stream-broker/{_virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-CP4DpABx.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-CMP316sx.mjs} +2 -2
- package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-BflyNuZv.mjs +26 -0
- package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-5Zc9wj-A.mjs +26 -0
- package/dist/stream-broker/{hostInit-Do2ymS0O.mjs → hostInit-DLJNx0Ly.mjs} +2 -2
- package/dist/stream-broker/index.js +1 -1
- package/dist/stream-broker/index.mjs +1 -1
- package/dist/stream-broker/remoteEntry.js +1 -1
- package/embed-dist/assets/{MaskShapeCanvas-DI4BY7W2-DlI3udxg.js → MaskShapeCanvas-DI4BY7W2-sOOxiGvP.js} +1 -1
- package/embed-dist/assets/{MotionZonesSettings-NcxxQN8r-Du8J0kED.js → MotionZonesSettings-NcxxQN8r-DdTQuT23.js} +1 -1
- package/embed-dist/assets/{PrivacyMaskSettings-APgPLF7p-DSST47WI.js → PrivacyMaskSettings-APgPLF7p-BrR0L3ql.js} +1 -1
- package/embed-dist/assets/index-D4MGuECB.css +2 -0
- package/embed-dist/assets/{index-B2ux2xJ7.js → index-Dk21l1_3.js} +14 -14
- package/embed-dist/index.html +2 -2
- package/package.json +1 -1
- package/python/inference_pool.py +30 -5
- package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-D68Lj69v.mjs +0 -26
- package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-DlALQ_wp.mjs +0 -26
- package/embed-dist/assets/index-D2pF9Z3W.css +0 -2
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { t as __require } from "../chunk-BdkLduGY.mjs";
|
|
2
|
-
import {
|
|
3
|
-
import { a as getStepDefinition, i as getStep, n as ALL_STEPS, o as resolveModelForFormat, r as getDefaultModelForFormat, t as ALL_PIPELINE_STEPS } from "../step-definitions-
|
|
2
|
+
import { B as supportedRuntimes$1, C as detectionPipelineCapability, H as errMsg, J as hydrateSchema, L as runtimeDevices$1, M as pipelineExecutorCapability, Q as parseJsonUnknown, T as evaluateZoneRules, U as BaseAddon, dt as string, ft as union, h as YAMNET_TO_MACRO, j as nodePin, lt as object, pt as EventCategory, q as createEvent, rt as array, s as DEVICE_BACKEND_TO_FORMAT, t as APPLE_SA_TO_MACRO, tt as sleep, w as enumerateInferenceDevices, x as defaultDeviceFor$1 } from "../dist-DyALtmD7.mjs";
|
|
3
|
+
import { a as getStepDefinition, i as getStep, n as ALL_STEPS, o as resolveModelForFormat, r as getDefaultModelForFormat, t as ALL_PIPELINE_STEPS } from "../step-definitions-rmBALHX4.mjs";
|
|
4
4
|
import { t as pickNodePlatformArch } from "../node-topology-platform-BkR_k6WT.mjs";
|
|
5
5
|
import { a as ensureModel, o as isModelDownloaded, r as deleteModelFromDisk } from "../model-download-service-Cp9f4dk6-CwUb5v3Y.mjs";
|
|
6
6
|
import { spawn } from "node:child_process";
|
|
@@ -156,9 +156,6 @@ function pythonRequirementsFor(id) {
|
|
|
156
156
|
function tuningFor(id) {
|
|
157
157
|
return RUNTIME_DETAIL[id].tuning;
|
|
158
158
|
}
|
|
159
|
-
function runtimeLabel(id) {
|
|
160
|
-
return RUNTIME_DETAIL[id].label;
|
|
161
|
-
}
|
|
162
159
|
/**
|
|
163
160
|
* Proactive-install hint kept for back-compat (re-exported from index.ts).
|
|
164
161
|
* Intel on Linux warrants installing openvino; coremltools covers macOS;
|
|
@@ -194,117 +191,27 @@ function pickBestRuntime(env, bestBackendHint) {
|
|
|
194
191
|
device: defaultDeviceFor(chosen)
|
|
195
192
|
};
|
|
196
193
|
}
|
|
197
|
-
/**
|
|
198
|
-
* The ordered device candidates a runtime will try for an `'auto'`/`'all'`
|
|
199
|
-
* device pick — mirrors what the python inference pool actually resolves
|
|
200
|
-
* (openvino `_resolve_ov_devices`: NPU>GPU>CPU; coreml compute-units `all`:
|
|
201
|
-
* ANE>GPU>CPU). Surfaced in `probedBestEngine` so the UI shows the real
|
|
202
|
-
* accelerator order (`openvino/auto [NPU, GPU, CPU]`) instead of a bare `auto`
|
|
203
|
-
* tag. Returns `[]` for runtimes with a single fixed device (onnx → CPU) — the
|
|
204
|
-
* caller then keeps the plain `backend/device` label.
|
|
205
|
-
*/
|
|
206
|
-
function engineDeviceSequence(runtimeId, env) {
|
|
207
|
-
const hw = env.hardware;
|
|
208
|
-
switch (runtimeId) {
|
|
209
|
-
case "openvino": {
|
|
210
|
-
const seq = [];
|
|
211
|
-
if (hw?.npu?.type === "intel-npu") seq.push("NPU");
|
|
212
|
-
if (hw?.gpu?.type === "intel") seq.push("GPU");
|
|
213
|
-
seq.push("CPU");
|
|
214
|
-
return seq;
|
|
215
|
-
}
|
|
216
|
-
case "coreml": {
|
|
217
|
-
const seq = [];
|
|
218
|
-
if (hw?.npu?.type === "apple-ane") seq.push("ANE");
|
|
219
|
-
if (hw?.gpu != null) seq.push("GPU");
|
|
220
|
-
seq.push("CPU");
|
|
221
|
-
return seq;
|
|
222
|
-
}
|
|
223
|
-
default: return [];
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
194
|
//#endregion
|
|
227
195
|
//#region src/detection-pipeline/engine-store-keys.ts
|
|
228
196
|
/**
|
|
229
|
-
*
|
|
197
|
+
* Node-id normalisation for the detection pipeline.
|
|
230
198
|
*
|
|
231
|
-
* The
|
|
232
|
-
*
|
|
233
|
-
*
|
|
234
|
-
*
|
|
235
|
-
* `
|
|
236
|
-
*
|
|
237
|
-
* selections. Sharing them lets one node's pick (e.g. `openvino/npu`) override
|
|
238
|
-
* another node that has no NPU.
|
|
239
|
-
*
|
|
240
|
-
* These three keys are therefore persisted node-scoped as `<key>@<nodeId>`.
|
|
241
|
-
* Everything else in the store stays shared. There is NO fallback to a legacy
|
|
242
|
-
* un-scoped value for ANY node (the hub included) and no in-code migration: a
|
|
243
|
-
* bare key (written before scoping, or by an older build) is inert — invisible
|
|
244
|
-
* to every node. A node with no scoped key reads `undefined`, which drives its
|
|
245
|
-
* OWN live resolve (`resolveAutoEngine` picks the node's runtime from the local
|
|
246
|
-
* kernel each boot; `reprobeEngine` persists an explicit probe under
|
|
247
|
-
* `<key>@<node>`). Serving a bare value to any node
|
|
248
|
-
* would leak one node's hardware pick (e.g. `openvino/auto`) onto a node that
|
|
249
|
-
* cannot run it (the CB3 "Mac runs OpenVINO" / N100 AUTO-device bug). This
|
|
250
|
-
* mirrors the decoder's `readNodeDecoderBackend` no-fallback rule.
|
|
199
|
+
* The legacy per-node engine CASCADE keys (`engineBackend` / `engineDevice` /
|
|
200
|
+
* `probedBestEngine`) were removed together with the operator-facing engine
|
|
201
|
+
* election: each node now runs a device-array of inference pools balanced
|
|
202
|
+
* per-session, and the node-default pool is derived live from the node's own
|
|
203
|
+
* hardware (`resolveAutoEngine`). All that survives from the old cascade module
|
|
204
|
+
* is the node-id normaliser, still used to target the platform-probe per node.
|
|
251
205
|
*/
|
|
252
|
-
var ENGINE_CASCADE_KEYS = [
|
|
253
|
-
"engineBackend",
|
|
254
|
-
"engineDevice",
|
|
255
|
-
"probedBestEngine"
|
|
256
|
-
];
|
|
257
|
-
function isEngineCascadeKey(key) {
|
|
258
|
-
return ENGINE_CASCADE_KEYS.includes(key);
|
|
259
|
-
}
|
|
260
206
|
/**
|
|
261
|
-
* Normalise a raw kernel node id to the bare node id
|
|
262
|
-
*
|
|
263
|
-
*
|
|
207
|
+
* Normalise a raw kernel node id to the bare node id. `localNodeId` can carry a
|
|
208
|
+
* `<node>/<addon>` suffix; probe targeting is per-NODE, so strip the addon
|
|
209
|
+
* segment. Falls back to `hub`.
|
|
264
210
|
*/
|
|
265
211
|
function normalizeEngineNodeId(rawNodeId) {
|
|
266
212
|
const raw = rawNodeId ?? "hub";
|
|
267
213
|
return raw.includes("/") ? raw.split("/")[0] ?? "hub" : raw;
|
|
268
214
|
}
|
|
269
|
-
/** The node-scoped store key for an engine cascade field. */
|
|
270
|
-
function nodeEngineKey(base, nodeId) {
|
|
271
|
-
return `${base}@${normalizeEngineNodeId(nodeId)}`;
|
|
272
|
-
}
|
|
273
|
-
/**
|
|
274
|
-
* Read an engine cascade value for a node: the node-scoped key if present,
|
|
275
|
-
* otherwise `undefined` — for EVERY node, the hub included. There is no
|
|
276
|
-
* fallback to the legacy un-scoped key: a bare value in the store is inert.
|
|
277
|
-
* A node with no scoped key gets `undefined` so its own platform-driven
|
|
278
|
-
* auto-pick runs and persists a real per-node selection under `<key>@<node>`.
|
|
279
|
-
* (A bare fallback would leak one node's engine selection cluster-wide — CB3.)
|
|
280
|
-
*/
|
|
281
|
-
function readNodeEngineValue(store, base, nodeId) {
|
|
282
|
-
return store[nodeEngineKey(base, nodeId)];
|
|
283
|
-
}
|
|
284
|
-
/**
|
|
285
|
-
* Project a raw store onto the plain engine cascade keys for THIS node, so the
|
|
286
|
-
* UI schema (whose field keys are the bare `engineBackend` etc.) hydrates from
|
|
287
|
-
* the node's own selection. Non-engine keys are left untouched. Node-scoped
|
|
288
|
-
* keys for OTHER nodes are dropped from the projection (not relevant to this
|
|
289
|
-
* node's form).
|
|
290
|
-
*/
|
|
291
|
-
function projectNodeEngine(store, nodeId) {
|
|
292
|
-
const out = {};
|
|
293
|
-
const scopedForAnyNode = /* @__PURE__ */ new Set();
|
|
294
|
-
for (const key of Object.keys(store)) {
|
|
295
|
-
const atIdx = key.indexOf("@");
|
|
296
|
-
if (isEngineCascadeKey(atIdx >= 0 ? key.slice(0, atIdx) : key)) {
|
|
297
|
-
scopedForAnyNode.add(key);
|
|
298
|
-
continue;
|
|
299
|
-
}
|
|
300
|
-
out[key] = store[key];
|
|
301
|
-
}
|
|
302
|
-
for (const base of ENGINE_CASCADE_KEYS) {
|
|
303
|
-
const value = readNodeEngineValue(store, base, nodeId);
|
|
304
|
-
if (value !== void 0) out[base] = value;
|
|
305
|
-
}
|
|
306
|
-
return out;
|
|
307
|
-
}
|
|
308
215
|
//#endregion
|
|
309
216
|
//#region src/detection-pipeline/default-detection-model.ts
|
|
310
217
|
/** The object-detection step id — the only slot this resolver applies to. */
|
|
@@ -1542,28 +1449,17 @@ var EngineFactory = class {
|
|
|
1542
1449
|
//#endregion
|
|
1543
1450
|
//#region src/detection-pipeline/engine/resolve-device-engine.ts
|
|
1544
1451
|
/**
|
|
1545
|
-
* Backend → model format for a device pool. Mirrors the Phase-1 device
|
|
1546
|
-
* enumeration (`enumerateInferenceDevices`) and the pool-runtime mapping.
|
|
1547
|
-
*/
|
|
1548
|
-
var BACKEND_FORMAT = {
|
|
1549
|
-
openvino: "openvino",
|
|
1550
|
-
edgetpu: "tflite",
|
|
1551
|
-
onnx: "onnx",
|
|
1552
|
-
cpu: "onnx",
|
|
1553
|
-
cuda: "onnx",
|
|
1554
|
-
coreml: "coreml"
|
|
1555
|
-
};
|
|
1556
|
-
/**
|
|
1557
1452
|
* Resolve a Phase-1 device key (`<backend>:<device>`, or bare `cpu`) into the
|
|
1558
1453
|
* {@link PipelineEngineChoice} used to build that device's inference pool.
|
|
1559
1454
|
* Throws on an unknown backend token so a bad `deviceKey` fails loud rather
|
|
1560
|
-
* than silently building a wrong pool.
|
|
1455
|
+
* than silently building a wrong pool. Format comes from the shared
|
|
1456
|
+
* {@link DEVICE_BACKEND_TO_FORMAT} SSOT (`@camstack/types`) — no local copy.
|
|
1561
1457
|
*/
|
|
1562
1458
|
function resolveDeviceEngine(deviceKey) {
|
|
1563
1459
|
const colon = deviceKey.indexOf(":");
|
|
1564
1460
|
const backend = colon >= 0 ? deviceKey.slice(0, colon) : deviceKey;
|
|
1565
1461
|
const device = colon >= 0 ? deviceKey.slice(colon + 1) : deviceKey;
|
|
1566
|
-
const format =
|
|
1462
|
+
const format = DEVICE_BACKEND_TO_FORMAT[backend];
|
|
1567
1463
|
if (format === void 0) throw new Error(`resolveDeviceEngine: unknown backend "${backend}" in deviceKey "${deviceKey}"`);
|
|
1568
1464
|
return {
|
|
1569
1465
|
runtime: "python",
|
|
@@ -1573,6 +1469,69 @@ function resolveDeviceEngine(deviceKey) {
|
|
|
1573
1469
|
};
|
|
1574
1470
|
}
|
|
1575
1471
|
//#endregion
|
|
1472
|
+
//#region src/detection-pipeline/engine/idle-pool-reaper.ts
|
|
1473
|
+
var IdlePoolReaper = class {
|
|
1474
|
+
lastUsed = /* @__PURE__ */ new Map();
|
|
1475
|
+
timers = /* @__PURE__ */ new Map();
|
|
1476
|
+
ttlMs;
|
|
1477
|
+
disposeFn;
|
|
1478
|
+
now;
|
|
1479
|
+
setTimer;
|
|
1480
|
+
clearTimer;
|
|
1481
|
+
onError;
|
|
1482
|
+
constructor(opts) {
|
|
1483
|
+
this.ttlMs = opts.ttlMs;
|
|
1484
|
+
this.disposeFn = opts.dispose;
|
|
1485
|
+
this.now = opts.now ?? (() => Date.now());
|
|
1486
|
+
this.setTimer = opts.setTimer ?? ((fn, ms) => setTimeout(fn, ms));
|
|
1487
|
+
this.clearTimer = opts.clearTimer ?? ((t) => clearTimeout(t));
|
|
1488
|
+
this.onError = opts.onError;
|
|
1489
|
+
}
|
|
1490
|
+
/** Record a use of `key` and (re)arm its idle-eviction timer. */
|
|
1491
|
+
touch(key) {
|
|
1492
|
+
this.lastUsed.set(key, this.now());
|
|
1493
|
+
this.arm(key);
|
|
1494
|
+
}
|
|
1495
|
+
/** Stop tracking `key` (the pool was disposed/removed elsewhere). */
|
|
1496
|
+
cancel(key) {
|
|
1497
|
+
const timer = this.timers.get(key);
|
|
1498
|
+
if (timer) this.clearTimer(timer);
|
|
1499
|
+
this.timers.delete(key);
|
|
1500
|
+
this.lastUsed.delete(key);
|
|
1501
|
+
}
|
|
1502
|
+
/** Clear every timer (shutdown). Does not dispose pools — the caller does. */
|
|
1503
|
+
disposeAll() {
|
|
1504
|
+
for (const timer of this.timers.values()) this.clearTimer(timer);
|
|
1505
|
+
this.timers.clear();
|
|
1506
|
+
this.lastUsed.clear();
|
|
1507
|
+
}
|
|
1508
|
+
arm(key) {
|
|
1509
|
+
const existing = this.timers.get(key);
|
|
1510
|
+
if (existing) this.clearTimer(existing);
|
|
1511
|
+
this.timers.set(key, this.setTimer(() => {
|
|
1512
|
+
this.reap(key);
|
|
1513
|
+
}, this.ttlMs));
|
|
1514
|
+
}
|
|
1515
|
+
async reap(key) {
|
|
1516
|
+
const last = this.lastUsed.get(key);
|
|
1517
|
+
if (last === void 0) return;
|
|
1518
|
+
const idleMs = this.now() - last;
|
|
1519
|
+
if (idleMs < this.ttlMs) {
|
|
1520
|
+
this.timers.set(key, this.setTimer(() => {
|
|
1521
|
+
this.reap(key);
|
|
1522
|
+
}, this.ttlMs - idleMs));
|
|
1523
|
+
return;
|
|
1524
|
+
}
|
|
1525
|
+
this.timers.delete(key);
|
|
1526
|
+
this.lastUsed.delete(key);
|
|
1527
|
+
try {
|
|
1528
|
+
await this.disposeFn(key);
|
|
1529
|
+
} catch (err) {
|
|
1530
|
+
this.onError?.(key, err);
|
|
1531
|
+
}
|
|
1532
|
+
}
|
|
1533
|
+
};
|
|
1534
|
+
//#endregion
|
|
1576
1535
|
//#region src/detection-pipeline/engine-provisioner.ts
|
|
1577
1536
|
/** Incremental backoff growing to a ~5 min cap; retries indefinitely at cap. */
|
|
1578
1537
|
var BACKOFF_SCHEDULE_MS = [
|
|
@@ -3929,26 +3888,6 @@ var PROBE_DIRECT_RPC_TIMEOUT_MS = 6e4;
|
|
|
3929
3888
|
* result is identical (onnx / cpu) but is now derived through the shared rules
|
|
3930
3889
|
* instead of duplicated inline.
|
|
3931
3890
|
*/
|
|
3932
|
-
function onnxFloorPick() {
|
|
3933
|
-
return ONNX_FLOOR;
|
|
3934
|
-
}
|
|
3935
|
-
/**
|
|
3936
|
-
* The platform-deterministic engine pick computed SYNCHRONOUSLY from this node's
|
|
3937
|
-
* own `process.platform`/`arch` alone (no probe): darwin → coreml, else → onnx
|
|
3938
|
-
* (gpu-dependent openvino/cuda need the probe and converge via the auto-pick).
|
|
3939
|
-
* Used as the ENGINE fallback when a persisted selection is unsupported on this
|
|
3940
|
-
* node — so a stale GLOBAL engine config (e.g. the cluster's OpenVINO choice)
|
|
3941
|
-
* can never force an impossible engine onto a node whose platform rejects it.
|
|
3942
|
-
*/
|
|
3943
|
-
function platformDefaultPick() {
|
|
3944
|
-
const pick = pickBestRuntime(runtimeEnvFromProcess(null), null);
|
|
3945
|
-
return {
|
|
3946
|
-
runtime: "python",
|
|
3947
|
-
backend: pick.runtimeId,
|
|
3948
|
-
format: modelFormatFor(pick.runtimeId),
|
|
3949
|
-
device: pick.device
|
|
3950
|
-
};
|
|
3951
|
-
}
|
|
3952
3891
|
/**
|
|
3953
3892
|
* Is `backend` even POSSIBLE on this node's OS/arch (ignoring gpu detail)?
|
|
3954
3893
|
* coreml ⇒ darwin only; openvino ⇒ x64 non-darwin only; onnx ⇒ anywhere. Used to
|
|
@@ -4064,6 +4003,22 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
4064
4003
|
*/
|
|
4065
4004
|
overrideCache = null;
|
|
4066
4005
|
overrideCacheTimer = null;
|
|
4006
|
+
/**
|
|
4007
|
+
* Idle-TTL for a per-device inference pool (multi-device C3). A device pool
|
|
4008
|
+
* with no dispatch for this long is disposed and recreated on next use — the
|
|
4009
|
+
* memory guardrail that keeps `factoriesByDevice` from accumulating warm pools
|
|
4010
|
+
* forever. Longer than the override cache (60s) since a device pool is heavier
|
|
4011
|
+
* to respin; still well above any real inter-session gap for an active camera.
|
|
4012
|
+
*/
|
|
4013
|
+
static DEVICE_POOL_IDLE_TTL_MS = 12e4;
|
|
4014
|
+
deviceReaper = new IdlePoolReaper({
|
|
4015
|
+
ttlMs: DetectionPipelineProvider.DEVICE_POOL_IDLE_TTL_MS,
|
|
4016
|
+
dispose: (deviceKey) => this.disposeDevicePool(deviceKey),
|
|
4017
|
+
onError: (deviceKey, err) => this.log.warn("Idle device pool dispose failed", { meta: {
|
|
4018
|
+
deviceKey,
|
|
4019
|
+
error: err instanceof Error ? err.message : String(err)
|
|
4020
|
+
} })
|
|
4021
|
+
});
|
|
4067
4022
|
static OVERRIDE_CACHE_TTL_MS = 6e4;
|
|
4068
4023
|
/** Read the addon settings store (all keys). */
|
|
4069
4024
|
readStore;
|
|
@@ -4091,20 +4046,15 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
4091
4046
|
format: this.currentEngine.format
|
|
4092
4047
|
} });
|
|
4093
4048
|
}
|
|
4094
|
-
/**
|
|
4049
|
+
/**
|
|
4050
|
+
* Call after construction. The node-default engine is always DERIVED live
|
|
4051
|
+
* from this node's own hardware (there is no persisted operator override
|
|
4052
|
+
* anymore — the elected single-engine cascade was removed). Defer the derive
|
|
4053
|
+
* to `setApi()` where the probe cap / kernel resolver is reachable.
|
|
4054
|
+
*/
|
|
4095
4055
|
async init() {
|
|
4096
|
-
|
|
4097
|
-
|
|
4098
|
-
this.currentEngine = stored;
|
|
4099
|
-
this.log.info("Engine loaded (persisted)", { meta: {
|
|
4100
|
-
runtime: stored.runtime,
|
|
4101
|
-
backend: stored.backend,
|
|
4102
|
-
format: stored.format
|
|
4103
|
-
} });
|
|
4104
|
-
} else {
|
|
4105
|
-
this.needsAutoPick = true;
|
|
4106
|
-
this.log.info("No persisted engine — auto-pick deferred to setApi()");
|
|
4107
|
-
}
|
|
4056
|
+
this.needsAutoPick = true;
|
|
4057
|
+
this.log.info("Node-default engine derive deferred to setApi()");
|
|
4108
4058
|
}
|
|
4109
4059
|
/**
|
|
4110
4060
|
* Lazy-install the pip requirements file matching `engine.backend` into
|
|
@@ -4178,6 +4128,9 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
4178
4128
|
await this.resolveAutoEngine();
|
|
4179
4129
|
this.needsAutoPick = false;
|
|
4180
4130
|
this.startProvisioningForCurrentEngine();
|
|
4131
|
+
await this.recomputeConfigIssues().catch((err) => {
|
|
4132
|
+
this.log.warn("config-issue recompute failed after node-default derive", { meta: { error: errMsg(err) } });
|
|
4133
|
+
});
|
|
4181
4134
|
}
|
|
4182
4135
|
/**
|
|
4183
4136
|
* Authoritative platform-probe reconcile: query the probe provider
|
|
@@ -4205,34 +4158,38 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
4205
4158
|
}
|
|
4206
4159
|
}
|
|
4207
4160
|
/**
|
|
4208
|
-
*
|
|
4161
|
+
* Derive the node-default engine LIVE from this node's own kernel inference
|
|
4209
4162
|
* resolver (`ctx.kernel.inferenceEngine`) — the runtime mirror of
|
|
4210
4163
|
* `ctx.kernel.hwaccel`. Each node (hub + forked worker + remote agent)
|
|
4211
4164
|
* resolves its OWN accelerators in-process, so a forked agent child never
|
|
4212
4165
|
* adopts the hub's runtime from the `platform-probe` singleton.
|
|
4213
4166
|
*
|
|
4214
|
-
*
|
|
4215
|
-
*
|
|
4216
|
-
*
|
|
4217
|
-
*
|
|
4218
|
-
*
|
|
4219
|
-
*
|
|
4167
|
+
* The default is the TOP-SCORED CONCRETE device from the SAME
|
|
4168
|
+
* `enumerateInferenceDevices` enumeration the orchestrator balances over
|
|
4169
|
+
* (e.g. `openvino:npu`, not `openvino:auto`) — NOT `pickBestRuntime`, whose
|
|
4170
|
+
* `defaultDeviceFor('openvino')` is the abstract `'auto'` sentinel. This is
|
|
4171
|
+
* load-bearing for pool COLLAPSE (D2/NEW-1): a `deviceKey`-less/lone session
|
|
4172
|
+
* routes through the default pool, and a balanced session that picks the same
|
|
4173
|
+
* top device (`openvino:npu`) must reuse THAT pool via `resolveDeviceFactory`'s
|
|
4174
|
+
* `enginesEqual(engine, currentEngine)` check — which only matches when the
|
|
4175
|
+
* default carries the concrete device, not `'auto'`. An abstract `'auto'`
|
|
4176
|
+
* default would spawn a THIRD pool contending with the per-device npu/gpu pools
|
|
4177
|
+
* on the same physical accelerators (double model load / VRAM). Falls back to
|
|
4178
|
+
* the onnx/cpu floor when the enumeration is empty. NEVER persists — recomputed
|
|
4179
|
+
* every boot from the local hardware, inherently self-healing.
|
|
4220
4180
|
*/
|
|
4221
4181
|
async resolveAutoEngine() {
|
|
4222
4182
|
let hardware = null;
|
|
4223
4183
|
try {
|
|
4224
4184
|
hardware = await this.addonCtx?.kernel?.inferenceEngine?.resolveHardware() ?? null;
|
|
4225
4185
|
} catch {}
|
|
4226
|
-
const
|
|
4227
|
-
|
|
4228
|
-
|
|
4229
|
-
|
|
4230
|
-
|
|
4231
|
-
|
|
4232
|
-
|
|
4233
|
-
this.log.info("Resolved auto engine (local kernel)", { meta: {
|
|
4234
|
-
backend: pick.runtimeId,
|
|
4235
|
-
device: pick.device,
|
|
4186
|
+
const devices = enumerateInferenceDevices(envToHardwareInfo(runtimeEnvFromProcess(hardware ? toProbedHardware(hardware) : null)));
|
|
4187
|
+
const top = devices.find((d) => d.available) ?? devices[0];
|
|
4188
|
+
this.currentEngine = top ? resolveDeviceEngine(top.key) : ONNX_FLOOR;
|
|
4189
|
+
this.log.info("Derived node-default engine (local kernel)", { meta: {
|
|
4190
|
+
deviceKey: top?.key ?? "onnx:cpu(floor)",
|
|
4191
|
+
backend: this.currentEngine.backend,
|
|
4192
|
+
device: this.currentEngine.device ?? null,
|
|
4236
4193
|
hadHardware: hardware !== null
|
|
4237
4194
|
} });
|
|
4238
4195
|
}
|
|
@@ -4311,33 +4268,6 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
4311
4268
|
this.provisioner.select(runtimeId, device);
|
|
4312
4269
|
}
|
|
4313
4270
|
/**
|
|
4314
|
-
* Re-trigger provisioning after the operator changes the engine
|
|
4315
|
-
* cascade (`engineBackend`/`engineDevice`). Reloads the persisted
|
|
4316
|
-
* selection into `this.currentEngine`, then `select()`s it — the
|
|
4317
|
-
* machine cancels any superseded provisioning and drives the new
|
|
4318
|
-
* runtime through installing → verifying → ready.
|
|
4319
|
-
*/
|
|
4320
|
-
async onEngineSelectionChanged() {
|
|
4321
|
-
const prev = this.currentEngine;
|
|
4322
|
-
const stored = await this.loadEngine();
|
|
4323
|
-
if (stored) this.currentEngine = stored;
|
|
4324
|
-
else await this.resolveAutoEngine();
|
|
4325
|
-
const engineChanged = prev.runtime !== this.currentEngine.runtime || prev.backend !== this.currentEngine.backend || prev.format !== this.currentEngine.format || (prev.device ?? "") !== (this.currentEngine.device ?? "");
|
|
4326
|
-
if (engineChanged && this.engineFactory) {
|
|
4327
|
-
this.log.info("engine selection changed — rebuilding pool in place", { meta: {
|
|
4328
|
-
from: `${prev.backend}/${prev.device ?? "default"}`,
|
|
4329
|
-
to: `${this.currentEngine.backend}/${this.currentEngine.device ?? "default"}`
|
|
4330
|
-
} });
|
|
4331
|
-
try {
|
|
4332
|
-
await this.engineFactory.dispose();
|
|
4333
|
-
} catch {}
|
|
4334
|
-
this.engineFactory = null;
|
|
4335
|
-
this.executor = null;
|
|
4336
|
-
}
|
|
4337
|
-
this.startProvisioningForCurrentEngine();
|
|
4338
|
-
if (engineChanged) await this.recomputeConfigIssues();
|
|
4339
|
-
}
|
|
4340
|
-
/**
|
|
4341
4271
|
* Gate A (config-correctness gate at engine change) — eagerly re-resolve
|
|
4342
4272
|
* the node-default step tree for `this.currentEngine.format` and compute
|
|
4343
4273
|
* human-readable config issues from the result:
|
|
@@ -5781,6 +5711,7 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
5781
5711
|
this.engineFactory = null;
|
|
5782
5712
|
this.executor = null;
|
|
5783
5713
|
}
|
|
5714
|
+
this.deviceReaper.disposeAll();
|
|
5784
5715
|
const deviceFactories = Array.from(this.factoriesByDevice.values());
|
|
5785
5716
|
this.factoriesByDevice.clear();
|
|
5786
5717
|
await Promise.all(deviceFactories.map((f) => f.dispose().catch(() => void 0)));
|
|
@@ -5909,6 +5840,14 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
5909
5840
|
* `this.engineFactory`; when the requested device equals the node's current
|
|
5910
5841
|
* engine the default pool is reused (no duplicate pool for the same device).
|
|
5911
5842
|
*/
|
|
5843
|
+
/**
|
|
5844
|
+
* In-flight device-pool creations, keyed by deviceKey. Single-flights
|
|
5845
|
+
* `resolveDeviceFactory` so two concurrent calls for the SAME device never
|
|
5846
|
+
* both spawn a pool — critical for single-context accelerators like the Coral
|
|
5847
|
+
* Edge TPU, where two pools racing to `load_delegate` on the one USB unit both
|
|
5848
|
+
* fail (the loser can't claim the already-held device).
|
|
5849
|
+
*/
|
|
5850
|
+
deviceFactoryInflight = /* @__PURE__ */ new Map();
|
|
5912
5851
|
async resolveDeviceFactory(deviceKey) {
|
|
5913
5852
|
const engine = resolveDeviceEngine(deviceKey);
|
|
5914
5853
|
if (enginesEqual(engine, this.currentEngine)) {
|
|
@@ -5916,86 +5855,56 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
5916
5855
|
return this.engineFactory;
|
|
5917
5856
|
}
|
|
5918
5857
|
const existing = this.factoriesByDevice.get(deviceKey);
|
|
5919
|
-
if (existing)
|
|
5920
|
-
|
|
5921
|
-
|
|
5922
|
-
engine,
|
|
5923
|
-
modelsDir: this.modelsDir,
|
|
5924
|
-
logger: this.log.child(`engine:${deviceKey}`),
|
|
5925
|
-
pythonPath: this.executorOptions.pythonPath ?? "",
|
|
5926
|
-
concurrency: this.executorOptions.concurrency,
|
|
5927
|
-
tuning: this.executorOptions.tuning,
|
|
5928
|
-
numWorkers: this.executorOptions.numWorkers
|
|
5929
|
-
});
|
|
5930
|
-
try {
|
|
5931
|
-
await factory.initialize([]);
|
|
5932
|
-
} catch (err) {
|
|
5933
|
-
await factory.dispose().catch(() => void 0);
|
|
5934
|
-
throw err;
|
|
5858
|
+
if (existing) {
|
|
5859
|
+
this.deviceReaper.touch(deviceKey);
|
|
5860
|
+
return existing;
|
|
5935
5861
|
}
|
|
5936
|
-
this.
|
|
5937
|
-
return
|
|
5938
|
-
|
|
5939
|
-
|
|
5940
|
-
|
|
5941
|
-
|
|
5942
|
-
|
|
5943
|
-
|
|
5944
|
-
|
|
5945
|
-
|
|
5946
|
-
|
|
5947
|
-
|
|
5948
|
-
|
|
5949
|
-
|
|
5950
|
-
|
|
5951
|
-
|
|
5952
|
-
|
|
5953
|
-
|
|
5954
|
-
const storedRuntime = store["engineRuntime"];
|
|
5955
|
-
const node = this.localProbeNodeId();
|
|
5956
|
-
const storedBackend = readNodeEngineValue(store, "engineBackend", node);
|
|
5957
|
-
if (storedBackend === "auto") return null;
|
|
5958
|
-
if (typeof storedBackend === "string" && storedBackend.length > 0 && storedBackend !== "auto") {
|
|
5959
|
-
const backend = storedBackend;
|
|
5960
|
-
const storedDeviceRaw = readNodeEngineValue(store, "engineDevice", node);
|
|
5961
|
-
const storedDevice = typeof storedDeviceRaw === "string" ? storedDeviceRaw : "";
|
|
5962
|
-
const floor = onnxFloorPick();
|
|
5963
|
-
const migratedBackend = typeof storedRuntime === "string" && storedRuntime === "node" && backend === "cpu" ? "onnx" : backend;
|
|
5964
|
-
if (!backendPossibleOnPlatform(migratedBackend)) {
|
|
5965
|
-
const platformDefault = platformDefaultPick();
|
|
5966
|
-
this.log.warn("Stored engine backend impossible on this platform — using platform default", { meta: {
|
|
5967
|
-
stored: migratedBackend,
|
|
5968
|
-
fallback: platformDefault.backend
|
|
5969
|
-
} });
|
|
5970
|
-
this.writeStore({
|
|
5971
|
-
[nodeEngineKey("engineBackend", node)]: platformDefault.backend,
|
|
5972
|
-
...platformDefault.device !== void 0 ? { [nodeEngineKey("engineDevice", node)]: platformDefault.device } : {}
|
|
5973
|
-
}).catch((err) => {
|
|
5974
|
-
this.log.warn("Failed to persist platform-default engine after impossible stored pick", { meta: { error: err instanceof Error ? err.message : String(err) } });
|
|
5975
|
-
});
|
|
5976
|
-
return platformDefault;
|
|
5862
|
+
const inflight = this.deviceFactoryInflight.get(deviceKey);
|
|
5863
|
+
if (inflight) return inflight;
|
|
5864
|
+
const creation = (async () => {
|
|
5865
|
+
await this.ensureBackendDeps(engine);
|
|
5866
|
+
const factory = new EngineFactory({
|
|
5867
|
+
engine,
|
|
5868
|
+
modelsDir: this.modelsDir,
|
|
5869
|
+
logger: this.log.child(`engine:${deviceKey}`),
|
|
5870
|
+
pythonPath: this.executorOptions.pythonPath ?? "",
|
|
5871
|
+
concurrency: this.executorOptions.concurrency,
|
|
5872
|
+
tuning: this.executorOptions.tuning,
|
|
5873
|
+
numWorkers: this.executorOptions.numWorkers
|
|
5874
|
+
});
|
|
5875
|
+
try {
|
|
5876
|
+
await factory.initialize([]);
|
|
5877
|
+
} catch (err) {
|
|
5878
|
+
await factory.dispose().catch(() => void 0);
|
|
5879
|
+
throw err;
|
|
5977
5880
|
}
|
|
5978
|
-
|
|
5979
|
-
|
|
5980
|
-
|
|
5981
|
-
|
|
5982
|
-
|
|
5983
|
-
|
|
5984
|
-
|
|
5881
|
+
this.factoriesByDevice.set(deviceKey, factory);
|
|
5882
|
+
this.deviceReaper.touch(deviceKey);
|
|
5883
|
+
return factory;
|
|
5884
|
+
})();
|
|
5885
|
+
this.deviceFactoryInflight.set(deviceKey, creation);
|
|
5886
|
+
try {
|
|
5887
|
+
return await creation;
|
|
5888
|
+
} finally {
|
|
5889
|
+
this.deviceFactoryInflight.delete(deviceKey);
|
|
5985
5890
|
}
|
|
5986
|
-
return null;
|
|
5987
5891
|
}
|
|
5988
5892
|
/**
|
|
5989
|
-
*
|
|
5990
|
-
*
|
|
5991
|
-
*
|
|
5992
|
-
*
|
|
5993
|
-
* here we do the write directly via the settings store and trigger
|
|
5994
|
-
* restart manually through the addons cap.
|
|
5995
|
-
*
|
|
5996
|
-
* Returns the chosen engine so the UI can confirm the pick without a
|
|
5997
|
-
* second round-trip.
|
|
5893
|
+
* Dispose a single idle device pool (invoked by the {@link deviceReaper} when
|
|
5894
|
+
* `deviceKey` has had no dispatch for the idle-TTL). Removed from the map
|
|
5895
|
+
* FIRST so a concurrent `resolveDeviceFactory` recreates it fresh via the
|
|
5896
|
+
* single-flight guard rather than handing back the disposing instance.
|
|
5998
5897
|
*/
|
|
5898
|
+
async disposeDevicePool(deviceKey) {
|
|
5899
|
+
const factory = this.factoriesByDevice.get(deviceKey);
|
|
5900
|
+
if (!factory) return;
|
|
5901
|
+
this.factoriesByDevice.delete(deviceKey);
|
|
5902
|
+
await factory.dispose();
|
|
5903
|
+
this.log.info("Idle device pool evicted", { meta: {
|
|
5904
|
+
deviceKey,
|
|
5905
|
+
idleTtlMs: DetectionPipelineProvider.DEVICE_POOL_IDLE_TTL_MS
|
|
5906
|
+
} });
|
|
5907
|
+
}
|
|
5999
5908
|
/**
|
|
6000
5909
|
* Phase 2c — flat per-addonId map holding the video-pipeline step
|
|
6001
5910
|
* config (`modelId` / `settings`). No `enabled` field: whether an
|
|
@@ -6181,35 +6090,6 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
6181
6090
|
} });
|
|
6182
6091
|
return { success: true };
|
|
6183
6092
|
}
|
|
6184
|
-
async reprobeEngine() {
|
|
6185
|
-
let hardware = null;
|
|
6186
|
-
try {
|
|
6187
|
-
hardware = await this.addonCtx?.kernel?.inferenceEngine?.resolveHardware() ?? null;
|
|
6188
|
-
} catch {}
|
|
6189
|
-
const env = runtimeEnvFromProcess(hardware ? toProbedHardware(hardware) : null);
|
|
6190
|
-
const pick = pickBestRuntime(env, null);
|
|
6191
|
-
const best = {
|
|
6192
|
-
runtime: "python",
|
|
6193
|
-
backend: pick.runtimeId,
|
|
6194
|
-
format: modelFormatFor(pick.runtimeId),
|
|
6195
|
-
device: pick.device
|
|
6196
|
-
};
|
|
6197
|
-
const deviceSeq = engineDeviceSequence(pick.runtimeId, env);
|
|
6198
|
-
const probedLabel = deviceSeq.length > 1 ? `${best.backend}/${best.device ?? "default"} [${deviceSeq.join(", ")}]` : `${best.backend}/${best.device ?? "default"}`;
|
|
6199
|
-
const rpNode = this.localProbeNodeId();
|
|
6200
|
-
if (pick.runtimeId !== "onnx" || hardware !== null) {
|
|
6201
|
-
await this.writeStore({ [nodeEngineKey("probedBestEngine", rpNode)]: probedLabel });
|
|
6202
|
-
this.log.info("Re-probed engine (local kernel) — updated probedBestEngine hint", { meta: {
|
|
6203
|
-
backend: best.backend,
|
|
6204
|
-
device: best.device ?? null,
|
|
6205
|
-
probedBestEngine: probedLabel
|
|
6206
|
-
} });
|
|
6207
|
-
} else this.log.info("Re-probe: onnx floor pending gpu probe — hint NOT persisted", { meta: {
|
|
6208
|
-
backend: best.backend,
|
|
6209
|
-
device: best.device ?? null
|
|
6210
|
-
} });
|
|
6211
|
-
return best;
|
|
6212
|
-
}
|
|
6213
6093
|
async getReferenceAudioFiles() {
|
|
6214
6094
|
const dir = resolveReferenceAudioDir();
|
|
6215
6095
|
if (!dir) return [];
|
|
@@ -6564,47 +6444,6 @@ function resolveAddonPythonDir() {
|
|
|
6564
6444
|
for (const c of candidates) if (fs.existsSync(path$1.join(c, "inference_pool.py"))) return c;
|
|
6565
6445
|
throw new Error(`addon-pipeline/detection-pipeline: python/ dir not found. Searched:\n${candidates.join("\n")}`);
|
|
6566
6446
|
}
|
|
6567
|
-
var BACKEND_TO_FORMAT = {
|
|
6568
|
-
cpu: "onnx",
|
|
6569
|
-
coreml: "coreml",
|
|
6570
|
-
openvino: "openvino",
|
|
6571
|
-
onnx: "onnx",
|
|
6572
|
-
edgetpu: "tflite"
|
|
6573
|
-
};
|
|
6574
|
-
/**
|
|
6575
|
-
* Every runtime the registry knows about, in offer order. Used only to build
|
|
6576
|
-
* the STATIC base schema (returned by `globalSettingsSchema()` before a live
|
|
6577
|
-
* ctx exists, e.g. addon-registry boot introspection). The live, hardware-
|
|
6578
|
-
* gated subset is injected by `getGlobalSettings()` via `supportedRuntimes(env)`.
|
|
6579
|
-
*/
|
|
6580
|
-
var ALL_RUNTIME_IDS = [
|
|
6581
|
-
"onnx",
|
|
6582
|
-
"openvino",
|
|
6583
|
-
"coreml",
|
|
6584
|
-
"edgetpu"
|
|
6585
|
-
];
|
|
6586
|
-
/**
|
|
6587
|
-
* Narrow an arbitrary stored backend string to a known `RuntimeId`. Legacy or
|
|
6588
|
-
* unknown values (e.g. the removed `'cpu'` backend) fall back to the `onnx`
|
|
6589
|
-
* floor so the registry lookup never throws.
|
|
6590
|
-
*/
|
|
6591
|
-
function toRuntimeId(backend) {
|
|
6592
|
-
return ALL_RUNTIME_IDS.find((id) => id === backend) ?? "onnx";
|
|
6593
|
-
}
|
|
6594
|
-
/** Static universe of backend options (every runtime). Labels from the registry. */
|
|
6595
|
-
var STATIC_BACKEND_OPTIONS = ALL_RUNTIME_IDS.map((id) => ({
|
|
6596
|
-
value: id,
|
|
6597
|
-
label: runtimeLabel(id)
|
|
6598
|
-
}));
|
|
6599
|
-
/**
|
|
6600
|
-
* Static device options for the schema's default backend. The live device
|
|
6601
|
-
* list per the selected backend + probed hardware is injected by
|
|
6602
|
-
* `getGlobalSettings()` via `runtimeDevices(backend, hardware)`.
|
|
6603
|
-
*/
|
|
6604
|
-
var STATIC_DEFAULT_DEVICE_OPTIONS = runtimeDevices(ALL_RUNTIME_IDS[0], null).map((d) => ({
|
|
6605
|
-
value: d.value,
|
|
6606
|
-
label: d.label
|
|
6607
|
-
}));
|
|
6608
6447
|
var DEFAULT_CONFIG = {
|
|
6609
6448
|
concurrency: 0,
|
|
6610
6449
|
batchMode: "",
|
|
@@ -6613,17 +6452,8 @@ var DEFAULT_CONFIG = {
|
|
|
6613
6452
|
numStreams: 0,
|
|
6614
6453
|
intraOpThreads: 0,
|
|
6615
6454
|
numWorkers: 0,
|
|
6616
|
-
engineRuntime: "python",
|
|
6617
|
-
engineBackend: "auto",
|
|
6618
|
-
engineDevice: "cpu",
|
|
6619
|
-
probedBestEngine: "",
|
|
6620
|
-
activeEngine: "",
|
|
6621
6455
|
activeModelSubstitutions: ""
|
|
6622
6456
|
};
|
|
6623
|
-
/** Derive the model-format from a backend value. Called by the provider. */
|
|
6624
|
-
function backendToFormat(backend) {
|
|
6625
|
-
return BACKEND_TO_FORMAT[backend] ?? "onnx";
|
|
6626
|
-
}
|
|
6627
6457
|
/**
|
|
6628
6458
|
* Cadence for the per-node engine-inventory snapshot. Engines change
|
|
6629
6459
|
* slowly (manual spin/kill, idle eviction, attach/detach). 5 s strikes
|
|
@@ -6716,63 +6546,18 @@ var DetectionPipelineAddon = class DetectionPipelineAddon extends BaseAddon {
|
|
|
6716
6546
|
tab: "engine",
|
|
6717
6547
|
order: 0,
|
|
6718
6548
|
description: "Vision-detection execution provider (object detection, face detection, plate OCR). Inference always runs via the embedded Python runtime. Provider and hardware options are gated by what the platform probe reports as available on this host. Changes restart the addon.",
|
|
6719
|
-
fields: [
|
|
6720
|
-
|
|
6721
|
-
|
|
6722
|
-
|
|
6723
|
-
|
|
6724
|
-
|
|
6725
|
-
|
|
6726
|
-
|
|
6727
|
-
|
|
6728
|
-
|
|
6729
|
-
|
|
6730
|
-
|
|
6731
|
-
tooltip: "Re-probe engine"
|
|
6732
|
-
}]
|
|
6733
|
-
}),
|
|
6734
|
-
this.field({
|
|
6735
|
-
type: "text",
|
|
6736
|
-
key: "activeEngine",
|
|
6737
|
-
label: "Active engine",
|
|
6738
|
-
description: "The runtime/device the inference pool ACTUALLY loaded on this node, with its provisioning state (format: provider/device (state)). If this differs from the selected provider below — e.g. \"onnx/cpu (ready)\" while OpenVINO is selected — the chosen accelerator could not load on this host and the engine fell back to the ONNX-CPU baseline.",
|
|
6739
|
-
readonlyField: true,
|
|
6740
|
-
default: ""
|
|
6741
|
-
}),
|
|
6742
|
-
this.field({
|
|
6743
|
-
type: "text",
|
|
6744
|
-
key: "activeModelSubstitutions",
|
|
6745
|
-
label: "Model substitutions",
|
|
6746
|
-
description: "Node-local model substitutions currently in effect: a step's chosen model has no build for this node's engine format, so a compatible default is running instead (format: \"step: chosen→running\"). Hidden when every step matches.",
|
|
6747
|
-
readonlyField: true,
|
|
6748
|
-
default: "",
|
|
6749
|
-
showWhen: {
|
|
6750
|
-
field: "activeModelSubstitutions",
|
|
6751
|
-
notEquals: ""
|
|
6752
|
-
}
|
|
6753
|
-
}),
|
|
6754
|
-
this.field({
|
|
6755
|
-
type: "select",
|
|
6756
|
-
key: "engineBackend",
|
|
6757
|
-
label: "Execution provider",
|
|
6758
|
-
options: [{
|
|
6759
|
-
value: "auto",
|
|
6760
|
-
label: "Auto (best for this node)"
|
|
6761
|
-
}, ...STATIC_BACKEND_OPTIONS],
|
|
6762
|
-
default: DEFAULT_CONFIG.engineBackend,
|
|
6763
|
-
immediate: true,
|
|
6764
|
-
perNode: true
|
|
6765
|
-
}),
|
|
6766
|
-
this.field({
|
|
6767
|
-
type: "select",
|
|
6768
|
-
key: "engineDevice",
|
|
6769
|
-
label: "Hardware device",
|
|
6770
|
-
options: [...STATIC_DEFAULT_DEVICE_OPTIONS],
|
|
6771
|
-
default: DEFAULT_CONFIG.engineDevice,
|
|
6772
|
-
immediate: true,
|
|
6773
|
-
perNode: true
|
|
6774
|
-
})
|
|
6775
|
-
]
|
|
6549
|
+
fields: [this.field({
|
|
6550
|
+
type: "text",
|
|
6551
|
+
key: "activeModelSubstitutions",
|
|
6552
|
+
label: "Model substitutions",
|
|
6553
|
+
description: "Node-local model substitutions currently in effect: a step's chosen model has no build for this node's engine format, so a compatible default is running instead (format: \"step: chosen→running\"). Hidden when every step matches.",
|
|
6554
|
+
readonlyField: true,
|
|
6555
|
+
default: "",
|
|
6556
|
+
showWhen: {
|
|
6557
|
+
field: "activeModelSubstitutions",
|
|
6558
|
+
notEquals: ""
|
|
6559
|
+
}
|
|
6560
|
+
})]
|
|
6776
6561
|
}, {
|
|
6777
6562
|
id: "inference-executor",
|
|
6778
6563
|
title: "Inference executor",
|
|
@@ -6801,15 +6586,7 @@ var DetectionPipelineAddon = class DetectionPipelineAddon extends BaseAddon {
|
|
|
6801
6586
|
label: "Window (cross-camera accumulator)"
|
|
6802
6587
|
}
|
|
6803
6588
|
],
|
|
6804
|
-
default: ""
|
|
6805
|
-
showWhen: {
|
|
6806
|
-
field: "engineBackend",
|
|
6807
|
-
in: [
|
|
6808
|
-
"coreml",
|
|
6809
|
-
"onnx",
|
|
6810
|
-
"cpu"
|
|
6811
|
-
]
|
|
6812
|
-
}
|
|
6589
|
+
default: ""
|
|
6813
6590
|
}),
|
|
6814
6591
|
this.field({
|
|
6815
6592
|
type: "slider",
|
|
@@ -6875,59 +6652,31 @@ var DetectionPipelineAddon = class DetectionPipelineAddon extends BaseAddon {
|
|
|
6875
6652
|
}] });
|
|
6876
6653
|
}
|
|
6877
6654
|
/**
|
|
6878
|
-
* Override to inject the
|
|
6655
|
+
* Override to inject the read-only model-substitution status + the
|
|
6656
|
+
* per-backend tuning-slider defaults.
|
|
6879
6657
|
*
|
|
6880
|
-
*
|
|
6881
|
-
*
|
|
6882
|
-
*
|
|
6883
|
-
*
|
|
6884
|
-
*
|
|
6885
|
-
*
|
|
6886
|
-
*
|
|
6658
|
+
* Engine SELECTION is no longer operator-facing (the elected single-engine
|
|
6659
|
+
* cascade was removed): each node runs a device-array of inference pools
|
|
6660
|
+
* balanced per-session, and the node-default pool is derived live from the
|
|
6661
|
+
* node's own hardware. So this override no longer offers a backend/device
|
|
6662
|
+
* dropdown or a probed/active-engine readout — it only:
|
|
6663
|
+
* 1. resolves the node's derived-default backend (via the probe) to suggest
|
|
6664
|
+
* the matrix-tuned slider defaults that won the bench for that backend, and
|
|
6665
|
+
* 2. surfaces the live `activeModelSubstitutions` readout.
|
|
6887
6666
|
*/
|
|
6888
6667
|
async getGlobalSettings(overlay, _cap, nodeId) {
|
|
6889
6668
|
const targetNode = nodeId ? normalizeEngineNodeId(nodeId) : this.localNodeId();
|
|
6890
6669
|
const rawStored = await this.resolveUiSettingsStore();
|
|
6891
6670
|
if (rawStored === null) return null;
|
|
6892
|
-
const stored = projectNodeEngine(rawStored, targetNode);
|
|
6893
|
-
const storedBackendRaw = stored["engineBackend"];
|
|
6894
|
-
if (typeof storedBackendRaw !== "string" || storedBackendRaw === "") return null;
|
|
6895
6671
|
const merged = overlay ? {
|
|
6896
|
-
...
|
|
6672
|
+
...rawStored,
|
|
6897
6673
|
...overlay
|
|
6898
|
-
} :
|
|
6899
|
-
const
|
|
6900
|
-
const hardware = env.hardware;
|
|
6901
|
-
const offered = supportedRuntimes(env);
|
|
6902
|
-
const runtimeBackends = [{
|
|
6903
|
-
value: "auto",
|
|
6904
|
-
label: "Auto (best for this node)"
|
|
6905
|
-
}, ...offered.map((id) => ({
|
|
6906
|
-
value: id,
|
|
6907
|
-
label: runtimeLabel(id)
|
|
6908
|
-
}))];
|
|
6909
|
-
const storedBackend = typeof merged.engineBackend === "string" ? merged.engineBackend : "";
|
|
6910
|
-
const isAuto = storedBackend === "auto";
|
|
6911
|
-
const backend = (() => {
|
|
6912
|
-
if (isAuto) return pickBestRuntime(env, null).runtimeId;
|
|
6913
|
-
const rid = toRuntimeId(storedBackend);
|
|
6914
|
-
if (rid === "onnx") return "onnx";
|
|
6915
|
-
if (offered.includes(rid)) return rid;
|
|
6916
|
-
return rid === "openvino" && env.platform !== "darwin" && env.arch === "x64" || rid === "coreml" && env.platform === "darwin" ? rid : offered[0] ?? "onnx";
|
|
6917
|
-
})();
|
|
6918
|
-
const deviceOptions = runtimeDevices(backend, hardware);
|
|
6919
|
-
const storedDevice = typeof merged.engineDevice === "string" ? merged.engineDevice : "";
|
|
6920
|
-
const device = deviceOptions.find((d) => d.value === storedDevice)?.value ?? defaultDeviceFor(backend);
|
|
6921
|
-
const prov = targetNode === this.localNodeId() ? this.provider?.getEngineProvisioning() : void 0;
|
|
6922
|
-
const activeEngine = prov && prov.runtimeId ? `${prov.runtimeId}/${prov.device ?? "default"} (${prov.state})` : "";
|
|
6674
|
+
} : rawStored;
|
|
6675
|
+
const backend = pickBestRuntime(await this.probeHardwareEnv(targetNode), null).runtimeId;
|
|
6923
6676
|
const substitutions = this.provider?.getActiveModelSubstitutions() ?? [];
|
|
6924
6677
|
const activeModelSubstitutions = substitutions.length === 0 ? "" : substitutions.map((s) => `${s.addonId}: ${s.chosen}→${s.running}`).join(", ");
|
|
6925
|
-
const displayBackend = isAuto ? "auto" : backend;
|
|
6926
6678
|
const raw = {
|
|
6927
6679
|
...merged,
|
|
6928
|
-
engineBackend: displayBackend,
|
|
6929
|
-
engineDevice: device,
|
|
6930
|
-
activeEngine,
|
|
6931
6680
|
activeModelSubstitutions
|
|
6932
6681
|
};
|
|
6933
6682
|
const schema = this.globalSettingsSchema();
|
|
@@ -6939,18 +6688,6 @@ var DetectionPipelineAddon = class DetectionPipelineAddon extends BaseAddon {
|
|
|
6939
6688
|
...section,
|
|
6940
6689
|
fields: section.fields.map((field) => {
|
|
6941
6690
|
if (field.type === "select") {
|
|
6942
|
-
if (field.key === "engineBackend") return {
|
|
6943
|
-
...field,
|
|
6944
|
-
options: [...runtimeBackends]
|
|
6945
|
-
};
|
|
6946
|
-
if (field.key === "engineDevice") return {
|
|
6947
|
-
...field,
|
|
6948
|
-
options: deviceOptions.map((d) => ({
|
|
6949
|
-
value: d.value,
|
|
6950
|
-
label: d.label
|
|
6951
|
-
})),
|
|
6952
|
-
default: defaultDeviceFor(backend)
|
|
6953
|
-
};
|
|
6954
6691
|
if (field.key === "batchMode" && tuning["batchMode"] !== void 0) return {
|
|
6955
6692
|
...field,
|
|
6956
6693
|
default: tuning["batchMode"]
|
|
@@ -7083,7 +6820,7 @@ var DetectionPipelineAddon = class DetectionPipelineAddon extends BaseAddon {
|
|
|
7083
6820
|
* a reason to disagree.
|
|
7084
6821
|
*/
|
|
7085
6822
|
resolveBackendTuning() {
|
|
7086
|
-
const t = tuningFor(this.
|
|
6823
|
+
const t = tuningFor(this.provider?.resolvedEngineBackend() ?? "onnx");
|
|
7087
6824
|
const num = (v, dflt) => typeof v === "number" && v > 0 ? v : dflt;
|
|
7088
6825
|
const batch = (v, dflt) => v === "none" || v === "list" || v === "window" ? v : dflt;
|
|
7089
6826
|
return {
|
|
@@ -7146,9 +6883,6 @@ var DetectionPipelineAddon = class DetectionPipelineAddon extends BaseAddon {
|
|
|
7146
6883
|
await this.provider.init();
|
|
7147
6884
|
await this.provider.setApi(this.ctx);
|
|
7148
6885
|
}
|
|
7149
|
-
if (!this.config.probedBestEngine) await this.provider.reprobeEngine().catch((err) => {
|
|
7150
|
-
this.ctx.logger.warn("auto-reprobe engine failed", { meta: { error: err instanceof Error ? err.message : String(err) } });
|
|
7151
|
-
});
|
|
7152
6886
|
await this.provider.warmPool();
|
|
7153
6887
|
this.engineMetricsTimer = setInterval(() => this.emitEngineMetricsSnapshot(), ENGINE_METRICS_SNAPSHOT_INTERVAL_MS);
|
|
7154
6888
|
this.lastAppliedPoolConfig = this.snapshotPoolConfig();
|
|
@@ -7262,21 +6996,18 @@ var DetectionPipelineAddon = class DetectionPipelineAddon extends BaseAddon {
|
|
|
7262
6996
|
}
|
|
7263
6997
|
/**
|
|
7264
6998
|
* BaseAddon calls `onConfigChanged` after every settings write. This is the
|
|
7265
|
-
* SOLE apply path for
|
|
6999
|
+
* SOLE apply path for tuning changes — none of those fields declare
|
|
7266
7000
|
* `requiresRestart` anymore (an addon restart re-probes hardware + reloads
|
|
7267
|
-
* every model on every set, which is exactly what we want to avoid)
|
|
7268
|
-
*
|
|
7269
|
-
*
|
|
7270
|
-
*
|
|
7271
|
-
*
|
|
7272
|
-
*
|
|
7273
|
-
*
|
|
7274
|
-
*
|
|
7001
|
+
* every model on every set, which is exactly what we want to avoid): pool-bound
|
|
7002
|
+
* tuning (`concurrency`/`batchMode`/`windowMs`/…) drives an in-place pool
|
|
7003
|
+
* respawn when the snapshot flips (`poolConfigChanged` below). The engine
|
|
7004
|
+
* SELECTION is no longer operator-facing — the node-default backend is derived
|
|
7005
|
+
* once at boot (`setApi` → `resolveAutoEngine`) from static hardware, so there
|
|
7006
|
+
* is no per-write engine re-select. The respawn applies optimistically — the
|
|
7007
|
+
* inference gate stays closed for the short re-spin, frames are dropped (never
|
|
7008
|
+
* crashed), no addon bounce.
|
|
7275
7009
|
*/
|
|
7276
7010
|
async onConfigChanged() {
|
|
7277
|
-
if (this.provider) await this.provider.onEngineSelectionChanged().catch((err) => {
|
|
7278
|
-
this.ctx.logger.warn("engine provisioning re-select failed on config change", { meta: { error: err instanceof Error ? err.message : String(err) } });
|
|
7279
|
-
});
|
|
7280
7011
|
if (!this.poolConfigChanged()) return;
|
|
7281
7012
|
if (!this.provider) {
|
|
7282
7013
|
this.lastAppliedPoolConfig = this.snapshotPoolConfig();
|
|
@@ -7301,4 +7032,4 @@ var DetectionPipelineAddon = class DetectionPipelineAddon extends BaseAddon {
|
|
|
7301
7032
|
}
|
|
7302
7033
|
};
|
|
7303
7034
|
//#endregion
|
|
7304
|
-
export { ALL_STEPS, DetectionPipelineProvider,
|
|
7035
|
+
export { ALL_STEPS, DetectionPipelineProvider, DetectionPipelineAddon as default, getDefaultModelForFormat, getStepDefinition, shouldInstallOpenvino };
|