@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
|
@@ -3,8 +3,8 @@ Object.defineProperties(exports, {
|
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
5
|
const require_chunk = require("../chunk-D6vf50IK.js");
|
|
6
|
-
const require_dist = require("../dist-
|
|
7
|
-
const require_step_definitions = require("../step-definitions-
|
|
6
|
+
const require_dist = require("../dist-DzTjxf39.js");
|
|
7
|
+
const require_step_definitions = require("../step-definitions-Djy8aZLt.js");
|
|
8
8
|
const require_node_topology_platform = require("../node-topology-platform-CFZ7F4xW.js");
|
|
9
9
|
const require_model_download_service_Cp9f4dk6 = require("../model-download-service-Cp9f4dk6-Dv-oFwjN.js");
|
|
10
10
|
let node_child_process = require("node:child_process");
|
|
@@ -164,9 +164,6 @@ function pythonRequirementsFor(id) {
|
|
|
164
164
|
function tuningFor(id) {
|
|
165
165
|
return RUNTIME_DETAIL[id].tuning;
|
|
166
166
|
}
|
|
167
|
-
function runtimeLabel(id) {
|
|
168
|
-
return RUNTIME_DETAIL[id].label;
|
|
169
|
-
}
|
|
170
167
|
/**
|
|
171
168
|
* Proactive-install hint kept for back-compat (re-exported from index.ts).
|
|
172
169
|
* Intel on Linux warrants installing openvino; coremltools covers macOS;
|
|
@@ -202,117 +199,27 @@ function pickBestRuntime(env, bestBackendHint) {
|
|
|
202
199
|
device: defaultDeviceFor(chosen)
|
|
203
200
|
};
|
|
204
201
|
}
|
|
205
|
-
/**
|
|
206
|
-
* The ordered device candidates a runtime will try for an `'auto'`/`'all'`
|
|
207
|
-
* device pick — mirrors what the python inference pool actually resolves
|
|
208
|
-
* (openvino `_resolve_ov_devices`: NPU>GPU>CPU; coreml compute-units `all`:
|
|
209
|
-
* ANE>GPU>CPU). Surfaced in `probedBestEngine` so the UI shows the real
|
|
210
|
-
* accelerator order (`openvino/auto [NPU, GPU, CPU]`) instead of a bare `auto`
|
|
211
|
-
* tag. Returns `[]` for runtimes with a single fixed device (onnx → CPU) — the
|
|
212
|
-
* caller then keeps the plain `backend/device` label.
|
|
213
|
-
*/
|
|
214
|
-
function engineDeviceSequence(runtimeId, env) {
|
|
215
|
-
const hw = env.hardware;
|
|
216
|
-
switch (runtimeId) {
|
|
217
|
-
case "openvino": {
|
|
218
|
-
const seq = [];
|
|
219
|
-
if (hw?.npu?.type === "intel-npu") seq.push("NPU");
|
|
220
|
-
if (hw?.gpu?.type === "intel") seq.push("GPU");
|
|
221
|
-
seq.push("CPU");
|
|
222
|
-
return seq;
|
|
223
|
-
}
|
|
224
|
-
case "coreml": {
|
|
225
|
-
const seq = [];
|
|
226
|
-
if (hw?.npu?.type === "apple-ane") seq.push("ANE");
|
|
227
|
-
if (hw?.gpu != null) seq.push("GPU");
|
|
228
|
-
seq.push("CPU");
|
|
229
|
-
return seq;
|
|
230
|
-
}
|
|
231
|
-
default: return [];
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
202
|
//#endregion
|
|
235
203
|
//#region src/detection-pipeline/engine-store-keys.ts
|
|
236
204
|
/**
|
|
237
|
-
*
|
|
205
|
+
* Node-id normalisation for the detection pipeline.
|
|
238
206
|
*
|
|
239
|
-
* The
|
|
240
|
-
*
|
|
241
|
-
*
|
|
242
|
-
*
|
|
243
|
-
* `
|
|
244
|
-
*
|
|
245
|
-
* selections. Sharing them lets one node's pick (e.g. `openvino/npu`) override
|
|
246
|
-
* another node that has no NPU.
|
|
247
|
-
*
|
|
248
|
-
* These three keys are therefore persisted node-scoped as `<key>@<nodeId>`.
|
|
249
|
-
* Everything else in the store stays shared. There is NO fallback to a legacy
|
|
250
|
-
* un-scoped value for ANY node (the hub included) and no in-code migration: a
|
|
251
|
-
* bare key (written before scoping, or by an older build) is inert — invisible
|
|
252
|
-
* to every node. A node with no scoped key reads `undefined`, which drives its
|
|
253
|
-
* OWN live resolve (`resolveAutoEngine` picks the node's runtime from the local
|
|
254
|
-
* kernel each boot; `reprobeEngine` persists an explicit probe under
|
|
255
|
-
* `<key>@<node>`). Serving a bare value to any node
|
|
256
|
-
* would leak one node's hardware pick (e.g. `openvino/auto`) onto a node that
|
|
257
|
-
* cannot run it (the CB3 "Mac runs OpenVINO" / N100 AUTO-device bug). This
|
|
258
|
-
* mirrors the decoder's `readNodeDecoderBackend` no-fallback rule.
|
|
207
|
+
* The legacy per-node engine CASCADE keys (`engineBackend` / `engineDevice` /
|
|
208
|
+
* `probedBestEngine`) were removed together with the operator-facing engine
|
|
209
|
+
* election: each node now runs a device-array of inference pools balanced
|
|
210
|
+
* per-session, and the node-default pool is derived live from the node's own
|
|
211
|
+
* hardware (`resolveAutoEngine`). All that survives from the old cascade module
|
|
212
|
+
* is the node-id normaliser, still used to target the platform-probe per node.
|
|
259
213
|
*/
|
|
260
|
-
var ENGINE_CASCADE_KEYS = [
|
|
261
|
-
"engineBackend",
|
|
262
|
-
"engineDevice",
|
|
263
|
-
"probedBestEngine"
|
|
264
|
-
];
|
|
265
|
-
function isEngineCascadeKey(key) {
|
|
266
|
-
return ENGINE_CASCADE_KEYS.includes(key);
|
|
267
|
-
}
|
|
268
214
|
/**
|
|
269
|
-
* Normalise a raw kernel node id to the bare node id
|
|
270
|
-
*
|
|
271
|
-
*
|
|
215
|
+
* Normalise a raw kernel node id to the bare node id. `localNodeId` can carry a
|
|
216
|
+
* `<node>/<addon>` suffix; probe targeting is per-NODE, so strip the addon
|
|
217
|
+
* segment. Falls back to `hub`.
|
|
272
218
|
*/
|
|
273
219
|
function normalizeEngineNodeId(rawNodeId) {
|
|
274
220
|
const raw = rawNodeId ?? "hub";
|
|
275
221
|
return raw.includes("/") ? raw.split("/")[0] ?? "hub" : raw;
|
|
276
222
|
}
|
|
277
|
-
/** The node-scoped store key for an engine cascade field. */
|
|
278
|
-
function nodeEngineKey(base, nodeId) {
|
|
279
|
-
return `${base}@${normalizeEngineNodeId(nodeId)}`;
|
|
280
|
-
}
|
|
281
|
-
/**
|
|
282
|
-
* Read an engine cascade value for a node: the node-scoped key if present,
|
|
283
|
-
* otherwise `undefined` — for EVERY node, the hub included. There is no
|
|
284
|
-
* fallback to the legacy un-scoped key: a bare value in the store is inert.
|
|
285
|
-
* A node with no scoped key gets `undefined` so its own platform-driven
|
|
286
|
-
* auto-pick runs and persists a real per-node selection under `<key>@<node>`.
|
|
287
|
-
* (A bare fallback would leak one node's engine selection cluster-wide — CB3.)
|
|
288
|
-
*/
|
|
289
|
-
function readNodeEngineValue(store, base, nodeId) {
|
|
290
|
-
return store[nodeEngineKey(base, nodeId)];
|
|
291
|
-
}
|
|
292
|
-
/**
|
|
293
|
-
* Project a raw store onto the plain engine cascade keys for THIS node, so the
|
|
294
|
-
* UI schema (whose field keys are the bare `engineBackend` etc.) hydrates from
|
|
295
|
-
* the node's own selection. Non-engine keys are left untouched. Node-scoped
|
|
296
|
-
* keys for OTHER nodes are dropped from the projection (not relevant to this
|
|
297
|
-
* node's form).
|
|
298
|
-
*/
|
|
299
|
-
function projectNodeEngine(store, nodeId) {
|
|
300
|
-
const out = {};
|
|
301
|
-
const scopedForAnyNode = /* @__PURE__ */ new Set();
|
|
302
|
-
for (const key of Object.keys(store)) {
|
|
303
|
-
const atIdx = key.indexOf("@");
|
|
304
|
-
if (isEngineCascadeKey(atIdx >= 0 ? key.slice(0, atIdx) : key)) {
|
|
305
|
-
scopedForAnyNode.add(key);
|
|
306
|
-
continue;
|
|
307
|
-
}
|
|
308
|
-
out[key] = store[key];
|
|
309
|
-
}
|
|
310
|
-
for (const base of ENGINE_CASCADE_KEYS) {
|
|
311
|
-
const value = readNodeEngineValue(store, base, nodeId);
|
|
312
|
-
if (value !== void 0) out[base] = value;
|
|
313
|
-
}
|
|
314
|
-
return out;
|
|
315
|
-
}
|
|
316
223
|
//#endregion
|
|
317
224
|
//#region src/detection-pipeline/default-detection-model.ts
|
|
318
225
|
/** The object-detection step id — the only slot this resolver applies to. */
|
|
@@ -1550,28 +1457,17 @@ var EngineFactory = class {
|
|
|
1550
1457
|
//#endregion
|
|
1551
1458
|
//#region src/detection-pipeline/engine/resolve-device-engine.ts
|
|
1552
1459
|
/**
|
|
1553
|
-
* Backend → model format for a device pool. Mirrors the Phase-1 device
|
|
1554
|
-
* enumeration (`enumerateInferenceDevices`) and the pool-runtime mapping.
|
|
1555
|
-
*/
|
|
1556
|
-
var BACKEND_FORMAT = {
|
|
1557
|
-
openvino: "openvino",
|
|
1558
|
-
edgetpu: "tflite",
|
|
1559
|
-
onnx: "onnx",
|
|
1560
|
-
cpu: "onnx",
|
|
1561
|
-
cuda: "onnx",
|
|
1562
|
-
coreml: "coreml"
|
|
1563
|
-
};
|
|
1564
|
-
/**
|
|
1565
1460
|
* Resolve a Phase-1 device key (`<backend>:<device>`, or bare `cpu`) into the
|
|
1566
1461
|
* {@link PipelineEngineChoice} used to build that device's inference pool.
|
|
1567
1462
|
* Throws on an unknown backend token so a bad `deviceKey` fails loud rather
|
|
1568
|
-
* than silently building a wrong pool.
|
|
1463
|
+
* than silently building a wrong pool. Format comes from the shared
|
|
1464
|
+
* {@link DEVICE_BACKEND_TO_FORMAT} SSOT (`@camstack/types`) — no local copy.
|
|
1569
1465
|
*/
|
|
1570
1466
|
function resolveDeviceEngine(deviceKey) {
|
|
1571
1467
|
const colon = deviceKey.indexOf(":");
|
|
1572
1468
|
const backend = colon >= 0 ? deviceKey.slice(0, colon) : deviceKey;
|
|
1573
1469
|
const device = colon >= 0 ? deviceKey.slice(colon + 1) : deviceKey;
|
|
1574
|
-
const format =
|
|
1470
|
+
const format = require_dist.DEVICE_BACKEND_TO_FORMAT[backend];
|
|
1575
1471
|
if (format === void 0) throw new Error(`resolveDeviceEngine: unknown backend "${backend}" in deviceKey "${deviceKey}"`);
|
|
1576
1472
|
return {
|
|
1577
1473
|
runtime: "python",
|
|
@@ -1581,6 +1477,69 @@ function resolveDeviceEngine(deviceKey) {
|
|
|
1581
1477
|
};
|
|
1582
1478
|
}
|
|
1583
1479
|
//#endregion
|
|
1480
|
+
//#region src/detection-pipeline/engine/idle-pool-reaper.ts
|
|
1481
|
+
var IdlePoolReaper = class {
|
|
1482
|
+
lastUsed = /* @__PURE__ */ new Map();
|
|
1483
|
+
timers = /* @__PURE__ */ new Map();
|
|
1484
|
+
ttlMs;
|
|
1485
|
+
disposeFn;
|
|
1486
|
+
now;
|
|
1487
|
+
setTimer;
|
|
1488
|
+
clearTimer;
|
|
1489
|
+
onError;
|
|
1490
|
+
constructor(opts) {
|
|
1491
|
+
this.ttlMs = opts.ttlMs;
|
|
1492
|
+
this.disposeFn = opts.dispose;
|
|
1493
|
+
this.now = opts.now ?? (() => Date.now());
|
|
1494
|
+
this.setTimer = opts.setTimer ?? ((fn, ms) => setTimeout(fn, ms));
|
|
1495
|
+
this.clearTimer = opts.clearTimer ?? ((t) => clearTimeout(t));
|
|
1496
|
+
this.onError = opts.onError;
|
|
1497
|
+
}
|
|
1498
|
+
/** Record a use of `key` and (re)arm its idle-eviction timer. */
|
|
1499
|
+
touch(key) {
|
|
1500
|
+
this.lastUsed.set(key, this.now());
|
|
1501
|
+
this.arm(key);
|
|
1502
|
+
}
|
|
1503
|
+
/** Stop tracking `key` (the pool was disposed/removed elsewhere). */
|
|
1504
|
+
cancel(key) {
|
|
1505
|
+
const timer = this.timers.get(key);
|
|
1506
|
+
if (timer) this.clearTimer(timer);
|
|
1507
|
+
this.timers.delete(key);
|
|
1508
|
+
this.lastUsed.delete(key);
|
|
1509
|
+
}
|
|
1510
|
+
/** Clear every timer (shutdown). Does not dispose pools — the caller does. */
|
|
1511
|
+
disposeAll() {
|
|
1512
|
+
for (const timer of this.timers.values()) this.clearTimer(timer);
|
|
1513
|
+
this.timers.clear();
|
|
1514
|
+
this.lastUsed.clear();
|
|
1515
|
+
}
|
|
1516
|
+
arm(key) {
|
|
1517
|
+
const existing = this.timers.get(key);
|
|
1518
|
+
if (existing) this.clearTimer(existing);
|
|
1519
|
+
this.timers.set(key, this.setTimer(() => {
|
|
1520
|
+
this.reap(key);
|
|
1521
|
+
}, this.ttlMs));
|
|
1522
|
+
}
|
|
1523
|
+
async reap(key) {
|
|
1524
|
+
const last = this.lastUsed.get(key);
|
|
1525
|
+
if (last === void 0) return;
|
|
1526
|
+
const idleMs = this.now() - last;
|
|
1527
|
+
if (idleMs < this.ttlMs) {
|
|
1528
|
+
this.timers.set(key, this.setTimer(() => {
|
|
1529
|
+
this.reap(key);
|
|
1530
|
+
}, this.ttlMs - idleMs));
|
|
1531
|
+
return;
|
|
1532
|
+
}
|
|
1533
|
+
this.timers.delete(key);
|
|
1534
|
+
this.lastUsed.delete(key);
|
|
1535
|
+
try {
|
|
1536
|
+
await this.disposeFn(key);
|
|
1537
|
+
} catch (err) {
|
|
1538
|
+
this.onError?.(key, err);
|
|
1539
|
+
}
|
|
1540
|
+
}
|
|
1541
|
+
};
|
|
1542
|
+
//#endregion
|
|
1584
1543
|
//#region src/detection-pipeline/engine-provisioner.ts
|
|
1585
1544
|
/** Incremental backoff growing to a ~5 min cap; retries indefinitely at cap. */
|
|
1586
1545
|
var BACKOFF_SCHEDULE_MS = [
|
|
@@ -3937,26 +3896,6 @@ var PROBE_DIRECT_RPC_TIMEOUT_MS = 6e4;
|
|
|
3937
3896
|
* result is identical (onnx / cpu) but is now derived through the shared rules
|
|
3938
3897
|
* instead of duplicated inline.
|
|
3939
3898
|
*/
|
|
3940
|
-
function onnxFloorPick() {
|
|
3941
|
-
return ONNX_FLOOR;
|
|
3942
|
-
}
|
|
3943
|
-
/**
|
|
3944
|
-
* The platform-deterministic engine pick computed SYNCHRONOUSLY from this node's
|
|
3945
|
-
* own `process.platform`/`arch` alone (no probe): darwin → coreml, else → onnx
|
|
3946
|
-
* (gpu-dependent openvino/cuda need the probe and converge via the auto-pick).
|
|
3947
|
-
* Used as the ENGINE fallback when a persisted selection is unsupported on this
|
|
3948
|
-
* node — so a stale GLOBAL engine config (e.g. the cluster's OpenVINO choice)
|
|
3949
|
-
* can never force an impossible engine onto a node whose platform rejects it.
|
|
3950
|
-
*/
|
|
3951
|
-
function platformDefaultPick() {
|
|
3952
|
-
const pick = pickBestRuntime(runtimeEnvFromProcess(null), null);
|
|
3953
|
-
return {
|
|
3954
|
-
runtime: "python",
|
|
3955
|
-
backend: pick.runtimeId,
|
|
3956
|
-
format: modelFormatFor(pick.runtimeId),
|
|
3957
|
-
device: pick.device
|
|
3958
|
-
};
|
|
3959
|
-
}
|
|
3960
3899
|
/**
|
|
3961
3900
|
* Is `backend` even POSSIBLE on this node's OS/arch (ignoring gpu detail)?
|
|
3962
3901
|
* coreml ⇒ darwin only; openvino ⇒ x64 non-darwin only; onnx ⇒ anywhere. Used to
|
|
@@ -4072,6 +4011,22 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
4072
4011
|
*/
|
|
4073
4012
|
overrideCache = null;
|
|
4074
4013
|
overrideCacheTimer = null;
|
|
4014
|
+
/**
|
|
4015
|
+
* Idle-TTL for a per-device inference pool (multi-device C3). A device pool
|
|
4016
|
+
* with no dispatch for this long is disposed and recreated on next use — the
|
|
4017
|
+
* memory guardrail that keeps `factoriesByDevice` from accumulating warm pools
|
|
4018
|
+
* forever. Longer than the override cache (60s) since a device pool is heavier
|
|
4019
|
+
* to respin; still well above any real inter-session gap for an active camera.
|
|
4020
|
+
*/
|
|
4021
|
+
static DEVICE_POOL_IDLE_TTL_MS = 12e4;
|
|
4022
|
+
deviceReaper = new IdlePoolReaper({
|
|
4023
|
+
ttlMs: DetectionPipelineProvider.DEVICE_POOL_IDLE_TTL_MS,
|
|
4024
|
+
dispose: (deviceKey) => this.disposeDevicePool(deviceKey),
|
|
4025
|
+
onError: (deviceKey, err) => this.log.warn("Idle device pool dispose failed", { meta: {
|
|
4026
|
+
deviceKey,
|
|
4027
|
+
error: err instanceof Error ? err.message : String(err)
|
|
4028
|
+
} })
|
|
4029
|
+
});
|
|
4075
4030
|
static OVERRIDE_CACHE_TTL_MS = 6e4;
|
|
4076
4031
|
/** Read the addon settings store (all keys). */
|
|
4077
4032
|
readStore;
|
|
@@ -4099,20 +4054,15 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
4099
4054
|
format: this.currentEngine.format
|
|
4100
4055
|
} });
|
|
4101
4056
|
}
|
|
4102
|
-
/**
|
|
4057
|
+
/**
|
|
4058
|
+
* Call after construction. The node-default engine is always DERIVED live
|
|
4059
|
+
* from this node's own hardware (there is no persisted operator override
|
|
4060
|
+
* anymore — the elected single-engine cascade was removed). Defer the derive
|
|
4061
|
+
* to `setApi()` where the probe cap / kernel resolver is reachable.
|
|
4062
|
+
*/
|
|
4103
4063
|
async init() {
|
|
4104
|
-
|
|
4105
|
-
|
|
4106
|
-
this.currentEngine = stored;
|
|
4107
|
-
this.log.info("Engine loaded (persisted)", { meta: {
|
|
4108
|
-
runtime: stored.runtime,
|
|
4109
|
-
backend: stored.backend,
|
|
4110
|
-
format: stored.format
|
|
4111
|
-
} });
|
|
4112
|
-
} else {
|
|
4113
|
-
this.needsAutoPick = true;
|
|
4114
|
-
this.log.info("No persisted engine — auto-pick deferred to setApi()");
|
|
4115
|
-
}
|
|
4064
|
+
this.needsAutoPick = true;
|
|
4065
|
+
this.log.info("Node-default engine derive deferred to setApi()");
|
|
4116
4066
|
}
|
|
4117
4067
|
/**
|
|
4118
4068
|
* Lazy-install the pip requirements file matching `engine.backend` into
|
|
@@ -4186,6 +4136,9 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
4186
4136
|
await this.resolveAutoEngine();
|
|
4187
4137
|
this.needsAutoPick = false;
|
|
4188
4138
|
this.startProvisioningForCurrentEngine();
|
|
4139
|
+
await this.recomputeConfigIssues().catch((err) => {
|
|
4140
|
+
this.log.warn("config-issue recompute failed after node-default derive", { meta: { error: require_dist.errMsg(err) } });
|
|
4141
|
+
});
|
|
4189
4142
|
}
|
|
4190
4143
|
/**
|
|
4191
4144
|
* Authoritative platform-probe reconcile: query the probe provider
|
|
@@ -4213,34 +4166,38 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
4213
4166
|
}
|
|
4214
4167
|
}
|
|
4215
4168
|
/**
|
|
4216
|
-
*
|
|
4169
|
+
* Derive the node-default engine LIVE from this node's own kernel inference
|
|
4217
4170
|
* resolver (`ctx.kernel.inferenceEngine`) — the runtime mirror of
|
|
4218
4171
|
* `ctx.kernel.hwaccel`. Each node (hub + forked worker + remote agent)
|
|
4219
4172
|
* resolves its OWN accelerators in-process, so a forked agent child never
|
|
4220
4173
|
* adopts the hub's runtime from the `platform-probe` singleton.
|
|
4221
4174
|
*
|
|
4222
|
-
*
|
|
4223
|
-
*
|
|
4224
|
-
*
|
|
4225
|
-
*
|
|
4226
|
-
*
|
|
4227
|
-
*
|
|
4175
|
+
* The default is the TOP-SCORED CONCRETE device from the SAME
|
|
4176
|
+
* `enumerateInferenceDevices` enumeration the orchestrator balances over
|
|
4177
|
+
* (e.g. `openvino:npu`, not `openvino:auto`) — NOT `pickBestRuntime`, whose
|
|
4178
|
+
* `defaultDeviceFor('openvino')` is the abstract `'auto'` sentinel. This is
|
|
4179
|
+
* load-bearing for pool COLLAPSE (D2/NEW-1): a `deviceKey`-less/lone session
|
|
4180
|
+
* routes through the default pool, and a balanced session that picks the same
|
|
4181
|
+
* top device (`openvino:npu`) must reuse THAT pool via `resolveDeviceFactory`'s
|
|
4182
|
+
* `enginesEqual(engine, currentEngine)` check — which only matches when the
|
|
4183
|
+
* default carries the concrete device, not `'auto'`. An abstract `'auto'`
|
|
4184
|
+
* default would spawn a THIRD pool contending with the per-device npu/gpu pools
|
|
4185
|
+
* on the same physical accelerators (double model load / VRAM). Falls back to
|
|
4186
|
+
* the onnx/cpu floor when the enumeration is empty. NEVER persists — recomputed
|
|
4187
|
+
* every boot from the local hardware, inherently self-healing.
|
|
4228
4188
|
*/
|
|
4229
4189
|
async resolveAutoEngine() {
|
|
4230
4190
|
let hardware = null;
|
|
4231
4191
|
try {
|
|
4232
4192
|
hardware = await this.addonCtx?.kernel?.inferenceEngine?.resolveHardware() ?? null;
|
|
4233
4193
|
} catch {}
|
|
4234
|
-
const
|
|
4235
|
-
|
|
4236
|
-
|
|
4237
|
-
|
|
4238
|
-
|
|
4239
|
-
|
|
4240
|
-
|
|
4241
|
-
this.log.info("Resolved auto engine (local kernel)", { meta: {
|
|
4242
|
-
backend: pick.runtimeId,
|
|
4243
|
-
device: pick.device,
|
|
4194
|
+
const devices = require_dist.enumerateInferenceDevices(envToHardwareInfo(runtimeEnvFromProcess(hardware ? toProbedHardware(hardware) : null)));
|
|
4195
|
+
const top = devices.find((d) => d.available) ?? devices[0];
|
|
4196
|
+
this.currentEngine = top ? resolveDeviceEngine(top.key) : ONNX_FLOOR;
|
|
4197
|
+
this.log.info("Derived node-default engine (local kernel)", { meta: {
|
|
4198
|
+
deviceKey: top?.key ?? "onnx:cpu(floor)",
|
|
4199
|
+
backend: this.currentEngine.backend,
|
|
4200
|
+
device: this.currentEngine.device ?? null,
|
|
4244
4201
|
hadHardware: hardware !== null
|
|
4245
4202
|
} });
|
|
4246
4203
|
}
|
|
@@ -4319,33 +4276,6 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
4319
4276
|
this.provisioner.select(runtimeId, device);
|
|
4320
4277
|
}
|
|
4321
4278
|
/**
|
|
4322
|
-
* Re-trigger provisioning after the operator changes the engine
|
|
4323
|
-
* cascade (`engineBackend`/`engineDevice`). Reloads the persisted
|
|
4324
|
-
* selection into `this.currentEngine`, then `select()`s it — the
|
|
4325
|
-
* machine cancels any superseded provisioning and drives the new
|
|
4326
|
-
* runtime through installing → verifying → ready.
|
|
4327
|
-
*/
|
|
4328
|
-
async onEngineSelectionChanged() {
|
|
4329
|
-
const prev = this.currentEngine;
|
|
4330
|
-
const stored = await this.loadEngine();
|
|
4331
|
-
if (stored) this.currentEngine = stored;
|
|
4332
|
-
else await this.resolveAutoEngine();
|
|
4333
|
-
const engineChanged = prev.runtime !== this.currentEngine.runtime || prev.backend !== this.currentEngine.backend || prev.format !== this.currentEngine.format || (prev.device ?? "") !== (this.currentEngine.device ?? "");
|
|
4334
|
-
if (engineChanged && this.engineFactory) {
|
|
4335
|
-
this.log.info("engine selection changed — rebuilding pool in place", { meta: {
|
|
4336
|
-
from: `${prev.backend}/${prev.device ?? "default"}`,
|
|
4337
|
-
to: `${this.currentEngine.backend}/${this.currentEngine.device ?? "default"}`
|
|
4338
|
-
} });
|
|
4339
|
-
try {
|
|
4340
|
-
await this.engineFactory.dispose();
|
|
4341
|
-
} catch {}
|
|
4342
|
-
this.engineFactory = null;
|
|
4343
|
-
this.executor = null;
|
|
4344
|
-
}
|
|
4345
|
-
this.startProvisioningForCurrentEngine();
|
|
4346
|
-
if (engineChanged) await this.recomputeConfigIssues();
|
|
4347
|
-
}
|
|
4348
|
-
/**
|
|
4349
4279
|
* Gate A (config-correctness gate at engine change) — eagerly re-resolve
|
|
4350
4280
|
* the node-default step tree for `this.currentEngine.format` and compute
|
|
4351
4281
|
* human-readable config issues from the result:
|
|
@@ -5789,6 +5719,7 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
5789
5719
|
this.engineFactory = null;
|
|
5790
5720
|
this.executor = null;
|
|
5791
5721
|
}
|
|
5722
|
+
this.deviceReaper.disposeAll();
|
|
5792
5723
|
const deviceFactories = Array.from(this.factoriesByDevice.values());
|
|
5793
5724
|
this.factoriesByDevice.clear();
|
|
5794
5725
|
await Promise.all(deviceFactories.map((f) => f.dispose().catch(() => void 0)));
|
|
@@ -5917,6 +5848,14 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
5917
5848
|
* `this.engineFactory`; when the requested device equals the node's current
|
|
5918
5849
|
* engine the default pool is reused (no duplicate pool for the same device).
|
|
5919
5850
|
*/
|
|
5851
|
+
/**
|
|
5852
|
+
* In-flight device-pool creations, keyed by deviceKey. Single-flights
|
|
5853
|
+
* `resolveDeviceFactory` so two concurrent calls for the SAME device never
|
|
5854
|
+
* both spawn a pool — critical for single-context accelerators like the Coral
|
|
5855
|
+
* Edge TPU, where two pools racing to `load_delegate` on the one USB unit both
|
|
5856
|
+
* fail (the loser can't claim the already-held device).
|
|
5857
|
+
*/
|
|
5858
|
+
deviceFactoryInflight = /* @__PURE__ */ new Map();
|
|
5920
5859
|
async resolveDeviceFactory(deviceKey) {
|
|
5921
5860
|
const engine = resolveDeviceEngine(deviceKey);
|
|
5922
5861
|
if (enginesEqual(engine, this.currentEngine)) {
|
|
@@ -5924,86 +5863,56 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
5924
5863
|
return this.engineFactory;
|
|
5925
5864
|
}
|
|
5926
5865
|
const existing = this.factoriesByDevice.get(deviceKey);
|
|
5927
|
-
if (existing)
|
|
5928
|
-
|
|
5929
|
-
|
|
5930
|
-
engine,
|
|
5931
|
-
modelsDir: this.modelsDir,
|
|
5932
|
-
logger: this.log.child(`engine:${deviceKey}`),
|
|
5933
|
-
pythonPath: this.executorOptions.pythonPath ?? "",
|
|
5934
|
-
concurrency: this.executorOptions.concurrency,
|
|
5935
|
-
tuning: this.executorOptions.tuning,
|
|
5936
|
-
numWorkers: this.executorOptions.numWorkers
|
|
5937
|
-
});
|
|
5938
|
-
try {
|
|
5939
|
-
await factory.initialize([]);
|
|
5940
|
-
} catch (err) {
|
|
5941
|
-
await factory.dispose().catch(() => void 0);
|
|
5942
|
-
throw err;
|
|
5866
|
+
if (existing) {
|
|
5867
|
+
this.deviceReaper.touch(deviceKey);
|
|
5868
|
+
return existing;
|
|
5943
5869
|
}
|
|
5944
|
-
this.
|
|
5945
|
-
return
|
|
5946
|
-
|
|
5947
|
-
|
|
5948
|
-
|
|
5949
|
-
|
|
5950
|
-
|
|
5951
|
-
|
|
5952
|
-
|
|
5953
|
-
|
|
5954
|
-
|
|
5955
|
-
|
|
5956
|
-
|
|
5957
|
-
|
|
5958
|
-
|
|
5959
|
-
|
|
5960
|
-
|
|
5961
|
-
|
|
5962
|
-
const storedRuntime = store["engineRuntime"];
|
|
5963
|
-
const node = this.localProbeNodeId();
|
|
5964
|
-
const storedBackend = readNodeEngineValue(store, "engineBackend", node);
|
|
5965
|
-
if (storedBackend === "auto") return null;
|
|
5966
|
-
if (typeof storedBackend === "string" && storedBackend.length > 0 && storedBackend !== "auto") {
|
|
5967
|
-
const backend = storedBackend;
|
|
5968
|
-
const storedDeviceRaw = readNodeEngineValue(store, "engineDevice", node);
|
|
5969
|
-
const storedDevice = typeof storedDeviceRaw === "string" ? storedDeviceRaw : "";
|
|
5970
|
-
const floor = onnxFloorPick();
|
|
5971
|
-
const migratedBackend = typeof storedRuntime === "string" && storedRuntime === "node" && backend === "cpu" ? "onnx" : backend;
|
|
5972
|
-
if (!backendPossibleOnPlatform(migratedBackend)) {
|
|
5973
|
-
const platformDefault = platformDefaultPick();
|
|
5974
|
-
this.log.warn("Stored engine backend impossible on this platform — using platform default", { meta: {
|
|
5975
|
-
stored: migratedBackend,
|
|
5976
|
-
fallback: platformDefault.backend
|
|
5977
|
-
} });
|
|
5978
|
-
this.writeStore({
|
|
5979
|
-
[nodeEngineKey("engineBackend", node)]: platformDefault.backend,
|
|
5980
|
-
...platformDefault.device !== void 0 ? { [nodeEngineKey("engineDevice", node)]: platformDefault.device } : {}
|
|
5981
|
-
}).catch((err) => {
|
|
5982
|
-
this.log.warn("Failed to persist platform-default engine after impossible stored pick", { meta: { error: err instanceof Error ? err.message : String(err) } });
|
|
5983
|
-
});
|
|
5984
|
-
return platformDefault;
|
|
5870
|
+
const inflight = this.deviceFactoryInflight.get(deviceKey);
|
|
5871
|
+
if (inflight) return inflight;
|
|
5872
|
+
const creation = (async () => {
|
|
5873
|
+
await this.ensureBackendDeps(engine);
|
|
5874
|
+
const factory = new EngineFactory({
|
|
5875
|
+
engine,
|
|
5876
|
+
modelsDir: this.modelsDir,
|
|
5877
|
+
logger: this.log.child(`engine:${deviceKey}`),
|
|
5878
|
+
pythonPath: this.executorOptions.pythonPath ?? "",
|
|
5879
|
+
concurrency: this.executorOptions.concurrency,
|
|
5880
|
+
tuning: this.executorOptions.tuning,
|
|
5881
|
+
numWorkers: this.executorOptions.numWorkers
|
|
5882
|
+
});
|
|
5883
|
+
try {
|
|
5884
|
+
await factory.initialize([]);
|
|
5885
|
+
} catch (err) {
|
|
5886
|
+
await factory.dispose().catch(() => void 0);
|
|
5887
|
+
throw err;
|
|
5985
5888
|
}
|
|
5986
|
-
|
|
5987
|
-
|
|
5988
|
-
|
|
5989
|
-
|
|
5990
|
-
|
|
5991
|
-
|
|
5992
|
-
|
|
5889
|
+
this.factoriesByDevice.set(deviceKey, factory);
|
|
5890
|
+
this.deviceReaper.touch(deviceKey);
|
|
5891
|
+
return factory;
|
|
5892
|
+
})();
|
|
5893
|
+
this.deviceFactoryInflight.set(deviceKey, creation);
|
|
5894
|
+
try {
|
|
5895
|
+
return await creation;
|
|
5896
|
+
} finally {
|
|
5897
|
+
this.deviceFactoryInflight.delete(deviceKey);
|
|
5993
5898
|
}
|
|
5994
|
-
return null;
|
|
5995
5899
|
}
|
|
5996
5900
|
/**
|
|
5997
|
-
*
|
|
5998
|
-
*
|
|
5999
|
-
*
|
|
6000
|
-
*
|
|
6001
|
-
* here we do the write directly via the settings store and trigger
|
|
6002
|
-
* restart manually through the addons cap.
|
|
6003
|
-
*
|
|
6004
|
-
* Returns the chosen engine so the UI can confirm the pick without a
|
|
6005
|
-
* second round-trip.
|
|
5901
|
+
* Dispose a single idle device pool (invoked by the {@link deviceReaper} when
|
|
5902
|
+
* `deviceKey` has had no dispatch for the idle-TTL). Removed from the map
|
|
5903
|
+
* FIRST so a concurrent `resolveDeviceFactory` recreates it fresh via the
|
|
5904
|
+
* single-flight guard rather than handing back the disposing instance.
|
|
6006
5905
|
*/
|
|
5906
|
+
async disposeDevicePool(deviceKey) {
|
|
5907
|
+
const factory = this.factoriesByDevice.get(deviceKey);
|
|
5908
|
+
if (!factory) return;
|
|
5909
|
+
this.factoriesByDevice.delete(deviceKey);
|
|
5910
|
+
await factory.dispose();
|
|
5911
|
+
this.log.info("Idle device pool evicted", { meta: {
|
|
5912
|
+
deviceKey,
|
|
5913
|
+
idleTtlMs: DetectionPipelineProvider.DEVICE_POOL_IDLE_TTL_MS
|
|
5914
|
+
} });
|
|
5915
|
+
}
|
|
6007
5916
|
/**
|
|
6008
5917
|
* Phase 2c — flat per-addonId map holding the video-pipeline step
|
|
6009
5918
|
* config (`modelId` / `settings`). No `enabled` field: whether an
|
|
@@ -6189,35 +6098,6 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
6189
6098
|
} });
|
|
6190
6099
|
return { success: true };
|
|
6191
6100
|
}
|
|
6192
|
-
async reprobeEngine() {
|
|
6193
|
-
let hardware = null;
|
|
6194
|
-
try {
|
|
6195
|
-
hardware = await this.addonCtx?.kernel?.inferenceEngine?.resolveHardware() ?? null;
|
|
6196
|
-
} catch {}
|
|
6197
|
-
const env = runtimeEnvFromProcess(hardware ? toProbedHardware(hardware) : null);
|
|
6198
|
-
const pick = pickBestRuntime(env, null);
|
|
6199
|
-
const best = {
|
|
6200
|
-
runtime: "python",
|
|
6201
|
-
backend: pick.runtimeId,
|
|
6202
|
-
format: modelFormatFor(pick.runtimeId),
|
|
6203
|
-
device: pick.device
|
|
6204
|
-
};
|
|
6205
|
-
const deviceSeq = engineDeviceSequence(pick.runtimeId, env);
|
|
6206
|
-
const probedLabel = deviceSeq.length > 1 ? `${best.backend}/${best.device ?? "default"} [${deviceSeq.join(", ")}]` : `${best.backend}/${best.device ?? "default"}`;
|
|
6207
|
-
const rpNode = this.localProbeNodeId();
|
|
6208
|
-
if (pick.runtimeId !== "onnx" || hardware !== null) {
|
|
6209
|
-
await this.writeStore({ [nodeEngineKey("probedBestEngine", rpNode)]: probedLabel });
|
|
6210
|
-
this.log.info("Re-probed engine (local kernel) — updated probedBestEngine hint", { meta: {
|
|
6211
|
-
backend: best.backend,
|
|
6212
|
-
device: best.device ?? null,
|
|
6213
|
-
probedBestEngine: probedLabel
|
|
6214
|
-
} });
|
|
6215
|
-
} else this.log.info("Re-probe: onnx floor pending gpu probe — hint NOT persisted", { meta: {
|
|
6216
|
-
backend: best.backend,
|
|
6217
|
-
device: best.device ?? null
|
|
6218
|
-
} });
|
|
6219
|
-
return best;
|
|
6220
|
-
}
|
|
6221
6101
|
async getReferenceAudioFiles() {
|
|
6222
6102
|
const dir = resolveReferenceAudioDir();
|
|
6223
6103
|
if (!dir) return [];
|
|
@@ -6572,47 +6452,6 @@ function resolveAddonPythonDir() {
|
|
|
6572
6452
|
for (const c of candidates) if (node_fs.existsSync(node_path.join(c, "inference_pool.py"))) return c;
|
|
6573
6453
|
throw new Error(`addon-pipeline/detection-pipeline: python/ dir not found. Searched:\n${candidates.join("\n")}`);
|
|
6574
6454
|
}
|
|
6575
|
-
var BACKEND_TO_FORMAT = {
|
|
6576
|
-
cpu: "onnx",
|
|
6577
|
-
coreml: "coreml",
|
|
6578
|
-
openvino: "openvino",
|
|
6579
|
-
onnx: "onnx",
|
|
6580
|
-
edgetpu: "tflite"
|
|
6581
|
-
};
|
|
6582
|
-
/**
|
|
6583
|
-
* Every runtime the registry knows about, in offer order. Used only to build
|
|
6584
|
-
* the STATIC base schema (returned by `globalSettingsSchema()` before a live
|
|
6585
|
-
* ctx exists, e.g. addon-registry boot introspection). The live, hardware-
|
|
6586
|
-
* gated subset is injected by `getGlobalSettings()` via `supportedRuntimes(env)`.
|
|
6587
|
-
*/
|
|
6588
|
-
var ALL_RUNTIME_IDS = [
|
|
6589
|
-
"onnx",
|
|
6590
|
-
"openvino",
|
|
6591
|
-
"coreml",
|
|
6592
|
-
"edgetpu"
|
|
6593
|
-
];
|
|
6594
|
-
/**
|
|
6595
|
-
* Narrow an arbitrary stored backend string to a known `RuntimeId`. Legacy or
|
|
6596
|
-
* unknown values (e.g. the removed `'cpu'` backend) fall back to the `onnx`
|
|
6597
|
-
* floor so the registry lookup never throws.
|
|
6598
|
-
*/
|
|
6599
|
-
function toRuntimeId(backend) {
|
|
6600
|
-
return ALL_RUNTIME_IDS.find((id) => id === backend) ?? "onnx";
|
|
6601
|
-
}
|
|
6602
|
-
/** Static universe of backend options (every runtime). Labels from the registry. */
|
|
6603
|
-
var STATIC_BACKEND_OPTIONS = ALL_RUNTIME_IDS.map((id) => ({
|
|
6604
|
-
value: id,
|
|
6605
|
-
label: runtimeLabel(id)
|
|
6606
|
-
}));
|
|
6607
|
-
/**
|
|
6608
|
-
* Static device options for the schema's default backend. The live device
|
|
6609
|
-
* list per the selected backend + probed hardware is injected by
|
|
6610
|
-
* `getGlobalSettings()` via `runtimeDevices(backend, hardware)`.
|
|
6611
|
-
*/
|
|
6612
|
-
var STATIC_DEFAULT_DEVICE_OPTIONS = runtimeDevices(ALL_RUNTIME_IDS[0], null).map((d) => ({
|
|
6613
|
-
value: d.value,
|
|
6614
|
-
label: d.label
|
|
6615
|
-
}));
|
|
6616
6455
|
var DEFAULT_CONFIG = {
|
|
6617
6456
|
concurrency: 0,
|
|
6618
6457
|
batchMode: "",
|
|
@@ -6621,17 +6460,8 @@ var DEFAULT_CONFIG = {
|
|
|
6621
6460
|
numStreams: 0,
|
|
6622
6461
|
intraOpThreads: 0,
|
|
6623
6462
|
numWorkers: 0,
|
|
6624
|
-
engineRuntime: "python",
|
|
6625
|
-
engineBackend: "auto",
|
|
6626
|
-
engineDevice: "cpu",
|
|
6627
|
-
probedBestEngine: "",
|
|
6628
|
-
activeEngine: "",
|
|
6629
6463
|
activeModelSubstitutions: ""
|
|
6630
6464
|
};
|
|
6631
|
-
/** Derive the model-format from a backend value. Called by the provider. */
|
|
6632
|
-
function backendToFormat(backend) {
|
|
6633
|
-
return BACKEND_TO_FORMAT[backend] ?? "onnx";
|
|
6634
|
-
}
|
|
6635
6465
|
/**
|
|
6636
6466
|
* Cadence for the per-node engine-inventory snapshot. Engines change
|
|
6637
6467
|
* slowly (manual spin/kill, idle eviction, attach/detach). 5 s strikes
|
|
@@ -6724,63 +6554,18 @@ var DetectionPipelineAddon = class DetectionPipelineAddon extends require_dist.B
|
|
|
6724
6554
|
tab: "engine",
|
|
6725
6555
|
order: 0,
|
|
6726
6556
|
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.",
|
|
6727
|
-
fields: [
|
|
6728
|
-
|
|
6729
|
-
|
|
6730
|
-
|
|
6731
|
-
|
|
6732
|
-
|
|
6733
|
-
|
|
6734
|
-
|
|
6735
|
-
|
|
6736
|
-
|
|
6737
|
-
|
|
6738
|
-
|
|
6739
|
-
tooltip: "Re-probe engine"
|
|
6740
|
-
}]
|
|
6741
|
-
}),
|
|
6742
|
-
this.field({
|
|
6743
|
-
type: "text",
|
|
6744
|
-
key: "activeEngine",
|
|
6745
|
-
label: "Active engine",
|
|
6746
|
-
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.",
|
|
6747
|
-
readonlyField: true,
|
|
6748
|
-
default: ""
|
|
6749
|
-
}),
|
|
6750
|
-
this.field({
|
|
6751
|
-
type: "text",
|
|
6752
|
-
key: "activeModelSubstitutions",
|
|
6753
|
-
label: "Model substitutions",
|
|
6754
|
-
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.",
|
|
6755
|
-
readonlyField: true,
|
|
6756
|
-
default: "",
|
|
6757
|
-
showWhen: {
|
|
6758
|
-
field: "activeModelSubstitutions",
|
|
6759
|
-
notEquals: ""
|
|
6760
|
-
}
|
|
6761
|
-
}),
|
|
6762
|
-
this.field({
|
|
6763
|
-
type: "select",
|
|
6764
|
-
key: "engineBackend",
|
|
6765
|
-
label: "Execution provider",
|
|
6766
|
-
options: [{
|
|
6767
|
-
value: "auto",
|
|
6768
|
-
label: "Auto (best for this node)"
|
|
6769
|
-
}, ...STATIC_BACKEND_OPTIONS],
|
|
6770
|
-
default: DEFAULT_CONFIG.engineBackend,
|
|
6771
|
-
immediate: true,
|
|
6772
|
-
perNode: true
|
|
6773
|
-
}),
|
|
6774
|
-
this.field({
|
|
6775
|
-
type: "select",
|
|
6776
|
-
key: "engineDevice",
|
|
6777
|
-
label: "Hardware device",
|
|
6778
|
-
options: [...STATIC_DEFAULT_DEVICE_OPTIONS],
|
|
6779
|
-
default: DEFAULT_CONFIG.engineDevice,
|
|
6780
|
-
immediate: true,
|
|
6781
|
-
perNode: true
|
|
6782
|
-
})
|
|
6783
|
-
]
|
|
6557
|
+
fields: [this.field({
|
|
6558
|
+
type: "text",
|
|
6559
|
+
key: "activeModelSubstitutions",
|
|
6560
|
+
label: "Model substitutions",
|
|
6561
|
+
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.",
|
|
6562
|
+
readonlyField: true,
|
|
6563
|
+
default: "",
|
|
6564
|
+
showWhen: {
|
|
6565
|
+
field: "activeModelSubstitutions",
|
|
6566
|
+
notEquals: ""
|
|
6567
|
+
}
|
|
6568
|
+
})]
|
|
6784
6569
|
}, {
|
|
6785
6570
|
id: "inference-executor",
|
|
6786
6571
|
title: "Inference executor",
|
|
@@ -6809,15 +6594,7 @@ var DetectionPipelineAddon = class DetectionPipelineAddon extends require_dist.B
|
|
|
6809
6594
|
label: "Window (cross-camera accumulator)"
|
|
6810
6595
|
}
|
|
6811
6596
|
],
|
|
6812
|
-
default: ""
|
|
6813
|
-
showWhen: {
|
|
6814
|
-
field: "engineBackend",
|
|
6815
|
-
in: [
|
|
6816
|
-
"coreml",
|
|
6817
|
-
"onnx",
|
|
6818
|
-
"cpu"
|
|
6819
|
-
]
|
|
6820
|
-
}
|
|
6597
|
+
default: ""
|
|
6821
6598
|
}),
|
|
6822
6599
|
this.field({
|
|
6823
6600
|
type: "slider",
|
|
@@ -6883,59 +6660,31 @@ var DetectionPipelineAddon = class DetectionPipelineAddon extends require_dist.B
|
|
|
6883
6660
|
}] });
|
|
6884
6661
|
}
|
|
6885
6662
|
/**
|
|
6886
|
-
* Override to inject the
|
|
6663
|
+
* Override to inject the read-only model-substitution status + the
|
|
6664
|
+
* per-backend tuning-slider defaults.
|
|
6887
6665
|
*
|
|
6888
|
-
*
|
|
6889
|
-
*
|
|
6890
|
-
*
|
|
6891
|
-
*
|
|
6892
|
-
*
|
|
6893
|
-
*
|
|
6894
|
-
*
|
|
6666
|
+
* Engine SELECTION is no longer operator-facing (the elected single-engine
|
|
6667
|
+
* cascade was removed): each node runs a device-array of inference pools
|
|
6668
|
+
* balanced per-session, and the node-default pool is derived live from the
|
|
6669
|
+
* node's own hardware. So this override no longer offers a backend/device
|
|
6670
|
+
* dropdown or a probed/active-engine readout — it only:
|
|
6671
|
+
* 1. resolves the node's derived-default backend (via the probe) to suggest
|
|
6672
|
+
* the matrix-tuned slider defaults that won the bench for that backend, and
|
|
6673
|
+
* 2. surfaces the live `activeModelSubstitutions` readout.
|
|
6895
6674
|
*/
|
|
6896
6675
|
async getGlobalSettings(overlay, _cap, nodeId) {
|
|
6897
6676
|
const targetNode = nodeId ? normalizeEngineNodeId(nodeId) : this.localNodeId();
|
|
6898
6677
|
const rawStored = await this.resolveUiSettingsStore();
|
|
6899
6678
|
if (rawStored === null) return null;
|
|
6900
|
-
const stored = projectNodeEngine(rawStored, targetNode);
|
|
6901
|
-
const storedBackendRaw = stored["engineBackend"];
|
|
6902
|
-
if (typeof storedBackendRaw !== "string" || storedBackendRaw === "") return null;
|
|
6903
6679
|
const merged = overlay ? {
|
|
6904
|
-
...
|
|
6680
|
+
...rawStored,
|
|
6905
6681
|
...overlay
|
|
6906
|
-
} :
|
|
6907
|
-
const
|
|
6908
|
-
const hardware = env.hardware;
|
|
6909
|
-
const offered = supportedRuntimes(env);
|
|
6910
|
-
const runtimeBackends = [{
|
|
6911
|
-
value: "auto",
|
|
6912
|
-
label: "Auto (best for this node)"
|
|
6913
|
-
}, ...offered.map((id) => ({
|
|
6914
|
-
value: id,
|
|
6915
|
-
label: runtimeLabel(id)
|
|
6916
|
-
}))];
|
|
6917
|
-
const storedBackend = typeof merged.engineBackend === "string" ? merged.engineBackend : "";
|
|
6918
|
-
const isAuto = storedBackend === "auto";
|
|
6919
|
-
const backend = (() => {
|
|
6920
|
-
if (isAuto) return pickBestRuntime(env, null).runtimeId;
|
|
6921
|
-
const rid = toRuntimeId(storedBackend);
|
|
6922
|
-
if (rid === "onnx") return "onnx";
|
|
6923
|
-
if (offered.includes(rid)) return rid;
|
|
6924
|
-
return rid === "openvino" && env.platform !== "darwin" && env.arch === "x64" || rid === "coreml" && env.platform === "darwin" ? rid : offered[0] ?? "onnx";
|
|
6925
|
-
})();
|
|
6926
|
-
const deviceOptions = runtimeDevices(backend, hardware);
|
|
6927
|
-
const storedDevice = typeof merged.engineDevice === "string" ? merged.engineDevice : "";
|
|
6928
|
-
const device = deviceOptions.find((d) => d.value === storedDevice)?.value ?? defaultDeviceFor(backend);
|
|
6929
|
-
const prov = targetNode === this.localNodeId() ? this.provider?.getEngineProvisioning() : void 0;
|
|
6930
|
-
const activeEngine = prov && prov.runtimeId ? `${prov.runtimeId}/${prov.device ?? "default"} (${prov.state})` : "";
|
|
6682
|
+
} : rawStored;
|
|
6683
|
+
const backend = pickBestRuntime(await this.probeHardwareEnv(targetNode), null).runtimeId;
|
|
6931
6684
|
const substitutions = this.provider?.getActiveModelSubstitutions() ?? [];
|
|
6932
6685
|
const activeModelSubstitutions = substitutions.length === 0 ? "" : substitutions.map((s) => `${s.addonId}: ${s.chosen}→${s.running}`).join(", ");
|
|
6933
|
-
const displayBackend = isAuto ? "auto" : backend;
|
|
6934
6686
|
const raw = {
|
|
6935
6687
|
...merged,
|
|
6936
|
-
engineBackend: displayBackend,
|
|
6937
|
-
engineDevice: device,
|
|
6938
|
-
activeEngine,
|
|
6939
6688
|
activeModelSubstitutions
|
|
6940
6689
|
};
|
|
6941
6690
|
const schema = this.globalSettingsSchema();
|
|
@@ -6947,18 +6696,6 @@ var DetectionPipelineAddon = class DetectionPipelineAddon extends require_dist.B
|
|
|
6947
6696
|
...section,
|
|
6948
6697
|
fields: section.fields.map((field) => {
|
|
6949
6698
|
if (field.type === "select") {
|
|
6950
|
-
if (field.key === "engineBackend") return {
|
|
6951
|
-
...field,
|
|
6952
|
-
options: [...runtimeBackends]
|
|
6953
|
-
};
|
|
6954
|
-
if (field.key === "engineDevice") return {
|
|
6955
|
-
...field,
|
|
6956
|
-
options: deviceOptions.map((d) => ({
|
|
6957
|
-
value: d.value,
|
|
6958
|
-
label: d.label
|
|
6959
|
-
})),
|
|
6960
|
-
default: defaultDeviceFor(backend)
|
|
6961
|
-
};
|
|
6962
6699
|
if (field.key === "batchMode" && tuning["batchMode"] !== void 0) return {
|
|
6963
6700
|
...field,
|
|
6964
6701
|
default: tuning["batchMode"]
|
|
@@ -7091,7 +6828,7 @@ var DetectionPipelineAddon = class DetectionPipelineAddon extends require_dist.B
|
|
|
7091
6828
|
* a reason to disagree.
|
|
7092
6829
|
*/
|
|
7093
6830
|
resolveBackendTuning() {
|
|
7094
|
-
const t = tuningFor(this.
|
|
6831
|
+
const t = tuningFor(this.provider?.resolvedEngineBackend() ?? "onnx");
|
|
7095
6832
|
const num = (v, dflt) => typeof v === "number" && v > 0 ? v : dflt;
|
|
7096
6833
|
const batch = (v, dflt) => v === "none" || v === "list" || v === "window" ? v : dflt;
|
|
7097
6834
|
return {
|
|
@@ -7154,9 +6891,6 @@ var DetectionPipelineAddon = class DetectionPipelineAddon extends require_dist.B
|
|
|
7154
6891
|
await this.provider.init();
|
|
7155
6892
|
await this.provider.setApi(this.ctx);
|
|
7156
6893
|
}
|
|
7157
|
-
if (!this.config.probedBestEngine) await this.provider.reprobeEngine().catch((err) => {
|
|
7158
|
-
this.ctx.logger.warn("auto-reprobe engine failed", { meta: { error: err instanceof Error ? err.message : String(err) } });
|
|
7159
|
-
});
|
|
7160
6894
|
await this.provider.warmPool();
|
|
7161
6895
|
this.engineMetricsTimer = setInterval(() => this.emitEngineMetricsSnapshot(), ENGINE_METRICS_SNAPSHOT_INTERVAL_MS);
|
|
7162
6896
|
this.lastAppliedPoolConfig = this.snapshotPoolConfig();
|
|
@@ -7270,21 +7004,18 @@ var DetectionPipelineAddon = class DetectionPipelineAddon extends require_dist.B
|
|
|
7270
7004
|
}
|
|
7271
7005
|
/**
|
|
7272
7006
|
* BaseAddon calls `onConfigChanged` after every settings write. This is the
|
|
7273
|
-
* SOLE apply path for
|
|
7007
|
+
* SOLE apply path for tuning changes — none of those fields declare
|
|
7274
7008
|
* `requiresRestart` anymore (an addon restart re-probes hardware + reloads
|
|
7275
|
-
* every model on every set, which is exactly what we want to avoid)
|
|
7276
|
-
*
|
|
7277
|
-
*
|
|
7278
|
-
*
|
|
7279
|
-
*
|
|
7280
|
-
*
|
|
7281
|
-
*
|
|
7282
|
-
*
|
|
7009
|
+
* every model on every set, which is exactly what we want to avoid): pool-bound
|
|
7010
|
+
* tuning (`concurrency`/`batchMode`/`windowMs`/…) drives an in-place pool
|
|
7011
|
+
* respawn when the snapshot flips (`poolConfigChanged` below). The engine
|
|
7012
|
+
* SELECTION is no longer operator-facing — the node-default backend is derived
|
|
7013
|
+
* once at boot (`setApi` → `resolveAutoEngine`) from static hardware, so there
|
|
7014
|
+
* is no per-write engine re-select. The respawn applies optimistically — the
|
|
7015
|
+
* inference gate stays closed for the short re-spin, frames are dropped (never
|
|
7016
|
+
* crashed), no addon bounce.
|
|
7283
7017
|
*/
|
|
7284
7018
|
async onConfigChanged() {
|
|
7285
|
-
if (this.provider) await this.provider.onEngineSelectionChanged().catch((err) => {
|
|
7286
|
-
this.ctx.logger.warn("engine provisioning re-select failed on config change", { meta: { error: err instanceof Error ? err.message : String(err) } });
|
|
7287
|
-
});
|
|
7288
7019
|
if (!this.poolConfigChanged()) return;
|
|
7289
7020
|
if (!this.provider) {
|
|
7290
7021
|
this.lastAppliedPoolConfig = this.snapshotPoolConfig();
|
|
@@ -7311,7 +7042,6 @@ var DetectionPipelineAddon = class DetectionPipelineAddon extends require_dist.B
|
|
|
7311
7042
|
//#endregion
|
|
7312
7043
|
exports.ALL_STEPS = require_step_definitions.ALL_STEPS;
|
|
7313
7044
|
exports.DetectionPipelineProvider = DetectionPipelineProvider;
|
|
7314
|
-
exports.backendToFormat = backendToFormat;
|
|
7315
7045
|
exports.default = DetectionPipelineAddon;
|
|
7316
7046
|
exports.getDefaultModelForFormat = require_step_definitions.getDefaultModelForFormat;
|
|
7317
7047
|
exports.getStepDefinition = require_step_definitions.getStepDefinition;
|