@camstack/addon-pipeline 1.1.70 → 1.2.0
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 +181 -469
- package/dist/detection-pipeline/index.mjs +182 -469
- package/dist/{dist-CurVt3xC.mjs → dist-DLWXeXC1.mjs} +239 -60
- package/dist/{dist-BWn5k0A5.js → dist-DWy-H6bo.js} +250 -59
- 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-B7e8rPe3.js → step-definitions-BQG_6dmZ.js} +6 -2
- package/dist/{step-definitions-BKm17nce.mjs → step-definitions-CVk19Y0u.mjs} +6 -2
- package/dist/stream-broker/_stub.js +2 -2
- package/dist/stream-broker/{_virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-yAAGMET5.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-CpB1jG0N.mjs} +3 -3
- 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-DUzRsTmt.mjs → hostInit--Zair8Av.mjs} +3 -3
- 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-D6bWMuew.js → MaskShapeCanvas-DI4BY7W2-sOOxiGvP.js} +1 -1
- package/embed-dist/assets/{MotionZonesSettings-NcxxQN8r-C-1enV21.js → MotionZonesSettings-NcxxQN8r-DdTQuT23.js} +1 -1
- package/embed-dist/assets/{PrivacyMaskSettings-APgPLF7p-By9pdGP7.js → PrivacyMaskSettings-APgPLF7p-BrR0L3ql.js} +1 -1
- package/embed-dist/assets/index-D4MGuECB.css +2 -0
- package/embed-dist/assets/{index-BPGh79kn.js → index-Dk21l1_3.js} +14 -14
- package/embed-dist/index.html +2 -2
- package/package.json +1 -1
- 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-CDBALHSV.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-DLWXeXC1.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-CVk19Y0u.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
|
-
*
|
|
230
|
-
*
|
|
231
|
-
* The detection addon's settings store is a single CLUSTER-SHARED blob (the
|
|
232
|
-
* settings-store cap is hub-resident; every node's detection instance reads and
|
|
233
|
-
* writes the same keys). That is correct for node-agnostic settings (pipeline
|
|
234
|
-
* steps, tuning) but WRONG for the engine cascade — `engineBackend` /
|
|
235
|
-
* `engineDevice` / `probedBestEngine` are hardware-specific, so the hub (NPU)
|
|
236
|
-
* and a remote agent (iGPU, or no accelerator at all) must hold INDEPENDENT
|
|
237
|
-
* selections. Sharing them lets one node's pick (e.g. `openvino/npu`) override
|
|
238
|
-
* another node that has no NPU.
|
|
197
|
+
* Node-id normalisation for the detection pipeline.
|
|
239
198
|
*
|
|
240
|
-
*
|
|
241
|
-
*
|
|
242
|
-
*
|
|
243
|
-
*
|
|
244
|
-
*
|
|
245
|
-
*
|
|
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)));
|
|
@@ -5924,7 +5855,10 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
5924
5855
|
return this.engineFactory;
|
|
5925
5856
|
}
|
|
5926
5857
|
const existing = this.factoriesByDevice.get(deviceKey);
|
|
5927
|
-
if (existing)
|
|
5858
|
+
if (existing) {
|
|
5859
|
+
this.deviceReaper.touch(deviceKey);
|
|
5860
|
+
return existing;
|
|
5861
|
+
}
|
|
5928
5862
|
const inflight = this.deviceFactoryInflight.get(deviceKey);
|
|
5929
5863
|
if (inflight) return inflight;
|
|
5930
5864
|
const creation = (async () => {
|
|
@@ -5945,6 +5879,7 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
5945
5879
|
throw err;
|
|
5946
5880
|
}
|
|
5947
5881
|
this.factoriesByDevice.set(deviceKey, factory);
|
|
5882
|
+
this.deviceReaper.touch(deviceKey);
|
|
5948
5883
|
return factory;
|
|
5949
5884
|
})();
|
|
5950
5885
|
this.deviceFactoryInflight.set(deviceKey, creation);
|
|
@@ -5955,66 +5890,22 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
5955
5890
|
}
|
|
5956
5891
|
}
|
|
5957
5892
|
/**
|
|
5958
|
-
*
|
|
5959
|
-
*
|
|
5960
|
-
* `
|
|
5961
|
-
*
|
|
5962
|
-
*
|
|
5963
|
-
* The `'auto'` sentinel is treated as NO override (returns null) so the
|
|
5964
|
-
* caller re-resolves LIVE via `resolveAutoEngine()` — `'auto'` is the
|
|
5965
|
-
* self-healing default, never a persisted concrete backend. An explicit
|
|
5966
|
-
* concrete backend (onnx/openvino/coreml) is honored as an operator override.
|
|
5967
|
-
* Returns null when neither source has a concrete selection; the caller keeps
|
|
5968
|
-
* the onnx floor set at construction until `resolveAutoEngine()` runs.
|
|
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.
|
|
5969
5897
|
*/
|
|
5970
|
-
async
|
|
5971
|
-
const
|
|
5972
|
-
|
|
5973
|
-
|
|
5974
|
-
|
|
5975
|
-
|
|
5976
|
-
|
|
5977
|
-
|
|
5978
|
-
|
|
5979
|
-
const storedDevice = typeof storedDeviceRaw === "string" ? storedDeviceRaw : "";
|
|
5980
|
-
const floor = onnxFloorPick();
|
|
5981
|
-
const migratedBackend = typeof storedRuntime === "string" && storedRuntime === "node" && backend === "cpu" ? "onnx" : backend;
|
|
5982
|
-
if (!backendPossibleOnPlatform(migratedBackend)) {
|
|
5983
|
-
const platformDefault = platformDefaultPick();
|
|
5984
|
-
this.log.warn("Stored engine backend impossible on this platform — using platform default", { meta: {
|
|
5985
|
-
stored: migratedBackend,
|
|
5986
|
-
fallback: platformDefault.backend
|
|
5987
|
-
} });
|
|
5988
|
-
this.writeStore({
|
|
5989
|
-
[nodeEngineKey("engineBackend", node)]: platformDefault.backend,
|
|
5990
|
-
...platformDefault.device !== void 0 ? { [nodeEngineKey("engineDevice", node)]: platformDefault.device } : {}
|
|
5991
|
-
}).catch((err) => {
|
|
5992
|
-
this.log.warn("Failed to persist platform-default engine after impossible stored pick", { meta: { error: err instanceof Error ? err.message : String(err) } });
|
|
5993
|
-
});
|
|
5994
|
-
return platformDefault;
|
|
5995
|
-
}
|
|
5996
|
-
const device = storedDevice || floor.device;
|
|
5997
|
-
return {
|
|
5998
|
-
runtime: "python",
|
|
5999
|
-
backend: migratedBackend,
|
|
6000
|
-
format: backendToFormat(migratedBackend),
|
|
6001
|
-
...device ? { device } : {}
|
|
6002
|
-
};
|
|
6003
|
-
}
|
|
6004
|
-
return null;
|
|
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
|
+
} });
|
|
6005
5907
|
}
|
|
6006
5908
|
/**
|
|
6007
|
-
* Re-run the platform probe for the inference engine and persist the
|
|
6008
|
-
* result into the addon's own global-settings keys. Because those
|
|
6009
|
-
* fields are declared `requiresRestart: true`, the
|
|
6010
|
-
* `updateGlobalSettings` pathway auto-schedules an addon restart —
|
|
6011
|
-
* here we do the write directly via the settings store and trigger
|
|
6012
|
-
* restart manually through the addons cap.
|
|
6013
|
-
*
|
|
6014
|
-
* Returns the chosen engine so the UI can confirm the pick without a
|
|
6015
|
-
* second round-trip.
|
|
6016
|
-
*/
|
|
6017
|
-
/**
|
|
6018
5909
|
* Phase 2c — flat per-addonId map holding the video-pipeline step
|
|
6019
5910
|
* config (`modelId` / `settings`). No `enabled` field: whether an
|
|
6020
5911
|
* addon step runs is a capability-binding concern, not a
|
|
@@ -6199,35 +6090,6 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
6199
6090
|
} });
|
|
6200
6091
|
return { success: true };
|
|
6201
6092
|
}
|
|
6202
|
-
async reprobeEngine() {
|
|
6203
|
-
let hardware = null;
|
|
6204
|
-
try {
|
|
6205
|
-
hardware = await this.addonCtx?.kernel?.inferenceEngine?.resolveHardware() ?? null;
|
|
6206
|
-
} catch {}
|
|
6207
|
-
const env = runtimeEnvFromProcess(hardware ? toProbedHardware(hardware) : null);
|
|
6208
|
-
const pick = pickBestRuntime(env, null);
|
|
6209
|
-
const best = {
|
|
6210
|
-
runtime: "python",
|
|
6211
|
-
backend: pick.runtimeId,
|
|
6212
|
-
format: modelFormatFor(pick.runtimeId),
|
|
6213
|
-
device: pick.device
|
|
6214
|
-
};
|
|
6215
|
-
const deviceSeq = engineDeviceSequence(pick.runtimeId, env);
|
|
6216
|
-
const probedLabel = deviceSeq.length > 1 ? `${best.backend}/${best.device ?? "default"} [${deviceSeq.join(", ")}]` : `${best.backend}/${best.device ?? "default"}`;
|
|
6217
|
-
const rpNode = this.localProbeNodeId();
|
|
6218
|
-
if (pick.runtimeId !== "onnx" || hardware !== null) {
|
|
6219
|
-
await this.writeStore({ [nodeEngineKey("probedBestEngine", rpNode)]: probedLabel });
|
|
6220
|
-
this.log.info("Re-probed engine (local kernel) — updated probedBestEngine hint", { meta: {
|
|
6221
|
-
backend: best.backend,
|
|
6222
|
-
device: best.device ?? null,
|
|
6223
|
-
probedBestEngine: probedLabel
|
|
6224
|
-
} });
|
|
6225
|
-
} else this.log.info("Re-probe: onnx floor pending gpu probe — hint NOT persisted", { meta: {
|
|
6226
|
-
backend: best.backend,
|
|
6227
|
-
device: best.device ?? null
|
|
6228
|
-
} });
|
|
6229
|
-
return best;
|
|
6230
|
-
}
|
|
6231
6093
|
async getReferenceAudioFiles() {
|
|
6232
6094
|
const dir = resolveReferenceAudioDir();
|
|
6233
6095
|
if (!dir) return [];
|
|
@@ -6582,47 +6444,6 @@ function resolveAddonPythonDir() {
|
|
|
6582
6444
|
for (const c of candidates) if (fs.existsSync(path$1.join(c, "inference_pool.py"))) return c;
|
|
6583
6445
|
throw new Error(`addon-pipeline/detection-pipeline: python/ dir not found. Searched:\n${candidates.join("\n")}`);
|
|
6584
6446
|
}
|
|
6585
|
-
var BACKEND_TO_FORMAT = {
|
|
6586
|
-
cpu: "onnx",
|
|
6587
|
-
coreml: "coreml",
|
|
6588
|
-
openvino: "openvino",
|
|
6589
|
-
onnx: "onnx",
|
|
6590
|
-
edgetpu: "tflite"
|
|
6591
|
-
};
|
|
6592
|
-
/**
|
|
6593
|
-
* Every runtime the registry knows about, in offer order. Used only to build
|
|
6594
|
-
* the STATIC base schema (returned by `globalSettingsSchema()` before a live
|
|
6595
|
-
* ctx exists, e.g. addon-registry boot introspection). The live, hardware-
|
|
6596
|
-
* gated subset is injected by `getGlobalSettings()` via `supportedRuntimes(env)`.
|
|
6597
|
-
*/
|
|
6598
|
-
var ALL_RUNTIME_IDS = [
|
|
6599
|
-
"onnx",
|
|
6600
|
-
"openvino",
|
|
6601
|
-
"coreml",
|
|
6602
|
-
"edgetpu"
|
|
6603
|
-
];
|
|
6604
|
-
/**
|
|
6605
|
-
* Narrow an arbitrary stored backend string to a known `RuntimeId`. Legacy or
|
|
6606
|
-
* unknown values (e.g. the removed `'cpu'` backend) fall back to the `onnx`
|
|
6607
|
-
* floor so the registry lookup never throws.
|
|
6608
|
-
*/
|
|
6609
|
-
function toRuntimeId(backend) {
|
|
6610
|
-
return ALL_RUNTIME_IDS.find((id) => id === backend) ?? "onnx";
|
|
6611
|
-
}
|
|
6612
|
-
/** Static universe of backend options (every runtime). Labels from the registry. */
|
|
6613
|
-
var STATIC_BACKEND_OPTIONS = ALL_RUNTIME_IDS.map((id) => ({
|
|
6614
|
-
value: id,
|
|
6615
|
-
label: runtimeLabel(id)
|
|
6616
|
-
}));
|
|
6617
|
-
/**
|
|
6618
|
-
* Static device options for the schema's default backend. The live device
|
|
6619
|
-
* list per the selected backend + probed hardware is injected by
|
|
6620
|
-
* `getGlobalSettings()` via `runtimeDevices(backend, hardware)`.
|
|
6621
|
-
*/
|
|
6622
|
-
var STATIC_DEFAULT_DEVICE_OPTIONS = runtimeDevices(ALL_RUNTIME_IDS[0], null).map((d) => ({
|
|
6623
|
-
value: d.value,
|
|
6624
|
-
label: d.label
|
|
6625
|
-
}));
|
|
6626
6447
|
var DEFAULT_CONFIG = {
|
|
6627
6448
|
concurrency: 0,
|
|
6628
6449
|
batchMode: "",
|
|
@@ -6631,17 +6452,8 @@ var DEFAULT_CONFIG = {
|
|
|
6631
6452
|
numStreams: 0,
|
|
6632
6453
|
intraOpThreads: 0,
|
|
6633
6454
|
numWorkers: 0,
|
|
6634
|
-
engineRuntime: "python",
|
|
6635
|
-
engineBackend: "auto",
|
|
6636
|
-
engineDevice: "cpu",
|
|
6637
|
-
probedBestEngine: "",
|
|
6638
|
-
activeEngine: "",
|
|
6639
6455
|
activeModelSubstitutions: ""
|
|
6640
6456
|
};
|
|
6641
|
-
/** Derive the model-format from a backend value. Called by the provider. */
|
|
6642
|
-
function backendToFormat(backend) {
|
|
6643
|
-
return BACKEND_TO_FORMAT[backend] ?? "onnx";
|
|
6644
|
-
}
|
|
6645
6457
|
/**
|
|
6646
6458
|
* Cadence for the per-node engine-inventory snapshot. Engines change
|
|
6647
6459
|
* slowly (manual spin/kill, idle eviction, attach/detach). 5 s strikes
|
|
@@ -6734,63 +6546,18 @@ var DetectionPipelineAddon = class DetectionPipelineAddon extends BaseAddon {
|
|
|
6734
6546
|
tab: "engine",
|
|
6735
6547
|
order: 0,
|
|
6736
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.",
|
|
6737
|
-
fields: [
|
|
6738
|
-
|
|
6739
|
-
|
|
6740
|
-
|
|
6741
|
-
|
|
6742
|
-
|
|
6743
|
-
|
|
6744
|
-
|
|
6745
|
-
|
|
6746
|
-
|
|
6747
|
-
|
|
6748
|
-
|
|
6749
|
-
tooltip: "Re-probe engine"
|
|
6750
|
-
}]
|
|
6751
|
-
}),
|
|
6752
|
-
this.field({
|
|
6753
|
-
type: "text",
|
|
6754
|
-
key: "activeEngine",
|
|
6755
|
-
label: "Active engine",
|
|
6756
|
-
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.",
|
|
6757
|
-
readonlyField: true,
|
|
6758
|
-
default: ""
|
|
6759
|
-
}),
|
|
6760
|
-
this.field({
|
|
6761
|
-
type: "text",
|
|
6762
|
-
key: "activeModelSubstitutions",
|
|
6763
|
-
label: "Model substitutions",
|
|
6764
|
-
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.",
|
|
6765
|
-
readonlyField: true,
|
|
6766
|
-
default: "",
|
|
6767
|
-
showWhen: {
|
|
6768
|
-
field: "activeModelSubstitutions",
|
|
6769
|
-
notEquals: ""
|
|
6770
|
-
}
|
|
6771
|
-
}),
|
|
6772
|
-
this.field({
|
|
6773
|
-
type: "select",
|
|
6774
|
-
key: "engineBackend",
|
|
6775
|
-
label: "Execution provider",
|
|
6776
|
-
options: [{
|
|
6777
|
-
value: "auto",
|
|
6778
|
-
label: "Auto (best for this node)"
|
|
6779
|
-
}, ...STATIC_BACKEND_OPTIONS],
|
|
6780
|
-
default: DEFAULT_CONFIG.engineBackend,
|
|
6781
|
-
immediate: true,
|
|
6782
|
-
perNode: true
|
|
6783
|
-
}),
|
|
6784
|
-
this.field({
|
|
6785
|
-
type: "select",
|
|
6786
|
-
key: "engineDevice",
|
|
6787
|
-
label: "Hardware device",
|
|
6788
|
-
options: [...STATIC_DEFAULT_DEVICE_OPTIONS],
|
|
6789
|
-
default: DEFAULT_CONFIG.engineDevice,
|
|
6790
|
-
immediate: true,
|
|
6791
|
-
perNode: true
|
|
6792
|
-
})
|
|
6793
|
-
]
|
|
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
|
+
})]
|
|
6794
6561
|
}, {
|
|
6795
6562
|
id: "inference-executor",
|
|
6796
6563
|
title: "Inference executor",
|
|
@@ -6819,15 +6586,7 @@ var DetectionPipelineAddon = class DetectionPipelineAddon extends BaseAddon {
|
|
|
6819
6586
|
label: "Window (cross-camera accumulator)"
|
|
6820
6587
|
}
|
|
6821
6588
|
],
|
|
6822
|
-
default: ""
|
|
6823
|
-
showWhen: {
|
|
6824
|
-
field: "engineBackend",
|
|
6825
|
-
in: [
|
|
6826
|
-
"coreml",
|
|
6827
|
-
"onnx",
|
|
6828
|
-
"cpu"
|
|
6829
|
-
]
|
|
6830
|
-
}
|
|
6589
|
+
default: ""
|
|
6831
6590
|
}),
|
|
6832
6591
|
this.field({
|
|
6833
6592
|
type: "slider",
|
|
@@ -6893,59 +6652,31 @@ var DetectionPipelineAddon = class DetectionPipelineAddon extends BaseAddon {
|
|
|
6893
6652
|
}] });
|
|
6894
6653
|
}
|
|
6895
6654
|
/**
|
|
6896
|
-
* Override to inject the
|
|
6655
|
+
* Override to inject the read-only model-substitution status + the
|
|
6656
|
+
* per-backend tuning-slider defaults.
|
|
6897
6657
|
*
|
|
6898
|
-
*
|
|
6899
|
-
*
|
|
6900
|
-
*
|
|
6901
|
-
*
|
|
6902
|
-
*
|
|
6903
|
-
*
|
|
6904
|
-
*
|
|
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.
|
|
6905
6666
|
*/
|
|
6906
6667
|
async getGlobalSettings(overlay, _cap, nodeId) {
|
|
6907
6668
|
const targetNode = nodeId ? normalizeEngineNodeId(nodeId) : this.localNodeId();
|
|
6908
6669
|
const rawStored = await this.resolveUiSettingsStore();
|
|
6909
6670
|
if (rawStored === null) return null;
|
|
6910
|
-
const stored = projectNodeEngine(rawStored, targetNode);
|
|
6911
|
-
const storedBackendRaw = stored["engineBackend"];
|
|
6912
|
-
if (typeof storedBackendRaw !== "string" || storedBackendRaw === "") return null;
|
|
6913
6671
|
const merged = overlay ? {
|
|
6914
|
-
...
|
|
6672
|
+
...rawStored,
|
|
6915
6673
|
...overlay
|
|
6916
|
-
} :
|
|
6917
|
-
const
|
|
6918
|
-
const hardware = env.hardware;
|
|
6919
|
-
const offered = supportedRuntimes(env);
|
|
6920
|
-
const runtimeBackends = [{
|
|
6921
|
-
value: "auto",
|
|
6922
|
-
label: "Auto (best for this node)"
|
|
6923
|
-
}, ...offered.map((id) => ({
|
|
6924
|
-
value: id,
|
|
6925
|
-
label: runtimeLabel(id)
|
|
6926
|
-
}))];
|
|
6927
|
-
const storedBackend = typeof merged.engineBackend === "string" ? merged.engineBackend : "";
|
|
6928
|
-
const isAuto = storedBackend === "auto";
|
|
6929
|
-
const backend = (() => {
|
|
6930
|
-
if (isAuto) return pickBestRuntime(env, null).runtimeId;
|
|
6931
|
-
const rid = toRuntimeId(storedBackend);
|
|
6932
|
-
if (rid === "onnx") return "onnx";
|
|
6933
|
-
if (offered.includes(rid)) return rid;
|
|
6934
|
-
return rid === "openvino" && env.platform !== "darwin" && env.arch === "x64" || rid === "coreml" && env.platform === "darwin" ? rid : offered[0] ?? "onnx";
|
|
6935
|
-
})();
|
|
6936
|
-
const deviceOptions = runtimeDevices(backend, hardware);
|
|
6937
|
-
const storedDevice = typeof merged.engineDevice === "string" ? merged.engineDevice : "";
|
|
6938
|
-
const device = deviceOptions.find((d) => d.value === storedDevice)?.value ?? defaultDeviceFor(backend);
|
|
6939
|
-
const prov = targetNode === this.localNodeId() ? this.provider?.getEngineProvisioning() : void 0;
|
|
6940
|
-
const activeEngine = prov && prov.runtimeId ? `${prov.runtimeId}/${prov.device ?? "default"} (${prov.state})` : "";
|
|
6674
|
+
} : rawStored;
|
|
6675
|
+
const backend = pickBestRuntime(await this.probeHardwareEnv(targetNode), null).runtimeId;
|
|
6941
6676
|
const substitutions = this.provider?.getActiveModelSubstitutions() ?? [];
|
|
6942
6677
|
const activeModelSubstitutions = substitutions.length === 0 ? "" : substitutions.map((s) => `${s.addonId}: ${s.chosen}→${s.running}`).join(", ");
|
|
6943
|
-
const displayBackend = isAuto ? "auto" : backend;
|
|
6944
6678
|
const raw = {
|
|
6945
6679
|
...merged,
|
|
6946
|
-
engineBackend: displayBackend,
|
|
6947
|
-
engineDevice: device,
|
|
6948
|
-
activeEngine,
|
|
6949
6680
|
activeModelSubstitutions
|
|
6950
6681
|
};
|
|
6951
6682
|
const schema = this.globalSettingsSchema();
|
|
@@ -6957,18 +6688,6 @@ var DetectionPipelineAddon = class DetectionPipelineAddon extends BaseAddon {
|
|
|
6957
6688
|
...section,
|
|
6958
6689
|
fields: section.fields.map((field) => {
|
|
6959
6690
|
if (field.type === "select") {
|
|
6960
|
-
if (field.key === "engineBackend") return {
|
|
6961
|
-
...field,
|
|
6962
|
-
options: [...runtimeBackends]
|
|
6963
|
-
};
|
|
6964
|
-
if (field.key === "engineDevice") return {
|
|
6965
|
-
...field,
|
|
6966
|
-
options: deviceOptions.map((d) => ({
|
|
6967
|
-
value: d.value,
|
|
6968
|
-
label: d.label
|
|
6969
|
-
})),
|
|
6970
|
-
default: defaultDeviceFor(backend)
|
|
6971
|
-
};
|
|
6972
6691
|
if (field.key === "batchMode" && tuning["batchMode"] !== void 0) return {
|
|
6973
6692
|
...field,
|
|
6974
6693
|
default: tuning["batchMode"]
|
|
@@ -7101,7 +6820,7 @@ var DetectionPipelineAddon = class DetectionPipelineAddon extends BaseAddon {
|
|
|
7101
6820
|
* a reason to disagree.
|
|
7102
6821
|
*/
|
|
7103
6822
|
resolveBackendTuning() {
|
|
7104
|
-
const t = tuningFor(this.
|
|
6823
|
+
const t = tuningFor(this.provider?.resolvedEngineBackend() ?? "onnx");
|
|
7105
6824
|
const num = (v, dflt) => typeof v === "number" && v > 0 ? v : dflt;
|
|
7106
6825
|
const batch = (v, dflt) => v === "none" || v === "list" || v === "window" ? v : dflt;
|
|
7107
6826
|
return {
|
|
@@ -7164,9 +6883,6 @@ var DetectionPipelineAddon = class DetectionPipelineAddon extends BaseAddon {
|
|
|
7164
6883
|
await this.provider.init();
|
|
7165
6884
|
await this.provider.setApi(this.ctx);
|
|
7166
6885
|
}
|
|
7167
|
-
if (!this.config.probedBestEngine) await this.provider.reprobeEngine().catch((err) => {
|
|
7168
|
-
this.ctx.logger.warn("auto-reprobe engine failed", { meta: { error: err instanceof Error ? err.message : String(err) } });
|
|
7169
|
-
});
|
|
7170
6886
|
await this.provider.warmPool();
|
|
7171
6887
|
this.engineMetricsTimer = setInterval(() => this.emitEngineMetricsSnapshot(), ENGINE_METRICS_SNAPSHOT_INTERVAL_MS);
|
|
7172
6888
|
this.lastAppliedPoolConfig = this.snapshotPoolConfig();
|
|
@@ -7280,21 +6996,18 @@ var DetectionPipelineAddon = class DetectionPipelineAddon extends BaseAddon {
|
|
|
7280
6996
|
}
|
|
7281
6997
|
/**
|
|
7282
6998
|
* BaseAddon calls `onConfigChanged` after every settings write. This is the
|
|
7283
|
-
* SOLE apply path for
|
|
6999
|
+
* SOLE apply path for tuning changes — none of those fields declare
|
|
7284
7000
|
* `requiresRestart` anymore (an addon restart re-probes hardware + reloads
|
|
7285
|
-
* every model on every set, which is exactly what we want to avoid)
|
|
7286
|
-
*
|
|
7287
|
-
*
|
|
7288
|
-
*
|
|
7289
|
-
*
|
|
7290
|
-
*
|
|
7291
|
-
*
|
|
7292
|
-
*
|
|
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.
|
|
7293
7009
|
*/
|
|
7294
7010
|
async onConfigChanged() {
|
|
7295
|
-
if (this.provider) await this.provider.onEngineSelectionChanged().catch((err) => {
|
|
7296
|
-
this.ctx.logger.warn("engine provisioning re-select failed on config change", { meta: { error: err instanceof Error ? err.message : String(err) } });
|
|
7297
|
-
});
|
|
7298
7011
|
if (!this.poolConfigChanged()) return;
|
|
7299
7012
|
if (!this.provider) {
|
|
7300
7013
|
this.lastAppliedPoolConfig = this.snapshotPoolConfig();
|
|
@@ -7319,4 +7032,4 @@ var DetectionPipelineAddon = class DetectionPipelineAddon extends BaseAddon {
|
|
|
7319
7032
|
}
|
|
7320
7033
|
};
|
|
7321
7034
|
//#endregion
|
|
7322
|
-
export { ALL_STEPS, DetectionPipelineProvider,
|
|
7035
|
+
export { ALL_STEPS, DetectionPipelineProvider, DetectionPipelineAddon as default, getDefaultModelForFormat, getStepDefinition, shouldInstallOpenvino };
|