@camstack/addon-pipeline 1.0.7 → 1.0.8
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 +2 -3
- package/dist/audio-analyzer/index.mjs +2 -3
- package/dist/audio-codec-nodeav/index.js +2 -2
- package/dist/audio-codec-nodeav/index.mjs +2 -2
- package/dist/decoder-nodeav/index.js +1 -1
- package/dist/decoder-nodeav/index.mjs +1 -1
- package/dist/detection-pipeline/index.js +373 -184
- package/dist/detection-pipeline/index.mjs +373 -184
- package/dist/{dist-CjrjeaDd.mjs → dist-BA6DR_jV.mjs} +128 -5
- package/dist/{dist-G45MVm6i.js → dist-BLcTVvol.js} +151 -4
- package/dist/motion-wasm/index.js +1 -1
- package/dist/motion-wasm/index.mjs +1 -1
- package/dist/pipeline-runner/index.js +1 -1
- package/dist/pipeline-runner/index.mjs +1 -1
- package/dist/recorder/index.js +3 -3
- package/dist/recorder/index.mjs +3 -3
- package/dist/stream-broker/_stub.js +1 -1
- package/dist/stream-broker/{_virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-Tbqpu0v3.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-BFy9iszl.mjs} +3 -3
- package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-COa17XL2.mjs +26 -0
- package/dist/stream-broker/{hostInit-tIev5Gd9.mjs → hostInit-zRy9SzlX.mjs} +3 -3
- package/dist/stream-broker/index.js +19 -19
- package/dist/stream-broker/index.mjs +19 -19
- package/dist/stream-broker/remoteEntry.js +1 -1
- package/embed-dist/assets/{MaskShapeCanvas-DI4BY7W2-C0kKwNX_.js → MaskShapeCanvas-DI4BY7W2-DJ7ztnFv.js} +1 -1
- package/embed-dist/assets/MotionZonesSettings-NcxxQN8r-CQzEnQoq.js +1 -0
- package/embed-dist/assets/{PrivacyMaskSettings-APgPLF7p-C2SRtNe6.js → PrivacyMaskSettings-APgPLF7p-Cl0eOy_U.js} +1 -1
- package/embed-dist/assets/{index-B2LRyXWh.js → index-CSuLwWK-.js} +3 -3
- package/embed-dist/index.html +1 -1
- package/package.json +1 -1
- package/python/__pycache__/inference_pool.cpython-313.pyc +0 -0
- package/python/postprocessors/__pycache__/__init__.cpython-312.pyc +0 -0
- package/python/postprocessors/__pycache__/__init__.cpython-313.pyc +0 -0
- package/python/postprocessors/__pycache__/_safety.cpython-313.pyc +0 -0
- package/python/postprocessors/__pycache__/arcface.cpython-312.pyc +0 -0
- package/python/postprocessors/__pycache__/arcface.cpython-313.pyc +0 -0
- package/python/postprocessors/__pycache__/ctc.cpython-312.pyc +0 -0
- package/python/postprocessors/__pycache__/ctc.cpython-313.pyc +0 -0
- package/python/postprocessors/__pycache__/saliency.cpython-312.pyc +0 -0
- package/python/postprocessors/__pycache__/saliency.cpython-313.pyc +0 -0
- package/python/postprocessors/__pycache__/scrfd.cpython-312.pyc +0 -0
- package/python/postprocessors/__pycache__/scrfd.cpython-313.pyc +0 -0
- package/python/postprocessors/__pycache__/softmax.cpython-312.pyc +0 -0
- package/python/postprocessors/__pycache__/softmax.cpython-313.pyc +0 -0
- package/python/postprocessors/__pycache__/yamnet.cpython-312.pyc +0 -0
- package/python/postprocessors/__pycache__/yamnet.cpython-313.pyc +0 -0
- package/python/postprocessors/__pycache__/yolo.cpython-312.pyc +0 -0
- package/python/postprocessors/__pycache__/yolo.cpython-313.pyc +0 -0
- package/python/postprocessors/__pycache__/yolo_seg.cpython-312.pyc +0 -0
- package/python/postprocessors/__pycache__/yolo_seg.cpython-313.pyc +0 -0
- package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-DCsgcqTa.mjs +0 -26
- package/embed-dist/assets/MotionZonesSettings-C1EEbk2V-CYtJc892.js +0 -1
|
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
|
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
5
|
const require_model_download_service_C7AjBsX9 = require("../model-download-service-C7AjBsX9-rXY-VFDk.js");
|
|
6
|
-
const require_dist = require("../dist-
|
|
6
|
+
const require_dist = require("../dist-BLcTVvol.js");
|
|
7
7
|
let node_fs = require("node:fs");
|
|
8
8
|
node_fs = require_model_download_service_C7AjBsX9.__toESM(node_fs);
|
|
9
9
|
let node_path = require("node:path");
|
|
@@ -2165,6 +2165,78 @@ var EngineFactory = class {
|
|
|
2165
2165
|
}
|
|
2166
2166
|
};
|
|
2167
2167
|
//#endregion
|
|
2168
|
+
//#region src/detection-pipeline/engine-store-keys.ts
|
|
2169
|
+
/**
|
|
2170
|
+
* Per-node scoping for the detection-pipeline engine cascade.
|
|
2171
|
+
*
|
|
2172
|
+
* The detection addon's settings store is a single CLUSTER-SHARED blob (the
|
|
2173
|
+
* settings-store cap is hub-resident; every node's detection instance reads and
|
|
2174
|
+
* writes the same keys). That is correct for node-agnostic settings (pipeline
|
|
2175
|
+
* steps, tuning) but WRONG for the engine cascade — `engineBackend` /
|
|
2176
|
+
* `engineDevice` / `probedBestEngine` are hardware-specific, so the hub (NPU)
|
|
2177
|
+
* and a remote agent (iGPU, or no accelerator at all) must hold INDEPENDENT
|
|
2178
|
+
* selections. Sharing them lets one node's pick (e.g. `openvino/npu`) override
|
|
2179
|
+
* another node that has no NPU.
|
|
2180
|
+
*
|
|
2181
|
+
* These three keys are therefore persisted node-scoped as `<key>@<nodeId>`.
|
|
2182
|
+
* Everything else in the store stays shared. A legacy un-scoped value (written
|
|
2183
|
+
* before this change, or by an older build) is read as a migration fallback and
|
|
2184
|
+
* re-persisted under the node-scoped key on the next write.
|
|
2185
|
+
*/
|
|
2186
|
+
var ENGINE_CASCADE_KEYS = [
|
|
2187
|
+
"engineBackend",
|
|
2188
|
+
"engineDevice",
|
|
2189
|
+
"probedBestEngine"
|
|
2190
|
+
];
|
|
2191
|
+
function isEngineCascadeKey(key) {
|
|
2192
|
+
return ENGINE_CASCADE_KEYS.includes(key);
|
|
2193
|
+
}
|
|
2194
|
+
/**
|
|
2195
|
+
* Normalise a raw kernel node id to the bare node id used for scoping.
|
|
2196
|
+
* `localNodeId` can carry a `<node>/<addon>` suffix; the engine selection is
|
|
2197
|
+
* per-NODE, so strip the addon segment. Falls back to `hub`.
|
|
2198
|
+
*/
|
|
2199
|
+
function normalizeEngineNodeId(rawNodeId) {
|
|
2200
|
+
const raw = rawNodeId ?? "hub";
|
|
2201
|
+
return raw.includes("/") ? raw.split("/")[0] ?? "hub" : raw;
|
|
2202
|
+
}
|
|
2203
|
+
/** The node-scoped store key for an engine cascade field. */
|
|
2204
|
+
function nodeEngineKey(base, nodeId) {
|
|
2205
|
+
return `${base}@${normalizeEngineNodeId(nodeId)}`;
|
|
2206
|
+
}
|
|
2207
|
+
/**
|
|
2208
|
+
* Read an engine cascade value for a node: the node-scoped key if present,
|
|
2209
|
+
* otherwise the legacy un-scoped value (migration), otherwise undefined.
|
|
2210
|
+
*/
|
|
2211
|
+
function readNodeEngineValue(store, base, nodeId) {
|
|
2212
|
+
const scoped = store[nodeEngineKey(base, nodeId)];
|
|
2213
|
+
return scoped !== void 0 ? scoped : store[base];
|
|
2214
|
+
}
|
|
2215
|
+
/**
|
|
2216
|
+
* Project a raw store onto the plain engine cascade keys for THIS node, so the
|
|
2217
|
+
* UI schema (whose field keys are the bare `engineBackend` etc.) hydrates from
|
|
2218
|
+
* the node's own selection. Non-engine keys are left untouched. Node-scoped
|
|
2219
|
+
* keys for OTHER nodes are dropped from the projection (not relevant to this
|
|
2220
|
+
* node's form).
|
|
2221
|
+
*/
|
|
2222
|
+
function projectNodeEngine(store, nodeId) {
|
|
2223
|
+
const out = {};
|
|
2224
|
+
const scopedForAnyNode = /* @__PURE__ */ new Set();
|
|
2225
|
+
for (const key of Object.keys(store)) {
|
|
2226
|
+
const atIdx = key.indexOf("@");
|
|
2227
|
+
if (isEngineCascadeKey(atIdx >= 0 ? key.slice(0, atIdx) : key)) {
|
|
2228
|
+
scopedForAnyNode.add(key);
|
|
2229
|
+
continue;
|
|
2230
|
+
}
|
|
2231
|
+
out[key] = store[key];
|
|
2232
|
+
}
|
|
2233
|
+
for (const base of ENGINE_CASCADE_KEYS) {
|
|
2234
|
+
const value = readNodeEngineValue(store, base, nodeId);
|
|
2235
|
+
if (value !== void 0) out[base] = value;
|
|
2236
|
+
}
|
|
2237
|
+
return out;
|
|
2238
|
+
}
|
|
2239
|
+
//#endregion
|
|
2168
2240
|
//#region src/detection-pipeline/postprocess/dispatch.ts
|
|
2169
2241
|
var VALID_KINDS = new Set([
|
|
2170
2242
|
"detections",
|
|
@@ -2204,7 +2276,7 @@ function postprocessYolo(output, stepDef) {
|
|
|
2204
2276
|
const letterbox = output.letterbox;
|
|
2205
2277
|
const dets = [];
|
|
2206
2278
|
for (let i = 0; i < numBoxes; i++) {
|
|
2207
|
-
const cx = tensor[
|
|
2279
|
+
const cx = tensor[i];
|
|
2208
2280
|
const cy = tensor[1 * numBoxes + i];
|
|
2209
2281
|
const w = tensor[2 * numBoxes + i];
|
|
2210
2282
|
const h = tensor[3 * numBoxes + i];
|
|
@@ -2360,7 +2432,7 @@ function postprocessArcface(output, _stepDef) {
|
|
|
2360
2432
|
let sumSq = 0;
|
|
2361
2433
|
for (let i = 0; i < tensor.length; i++) sumSq += tensor[i] * tensor[i];
|
|
2362
2434
|
const norm = Math.sqrt(sumSq);
|
|
2363
|
-
const normalized =
|
|
2435
|
+
const normalized = Array.from({ length: tensor.length });
|
|
2364
2436
|
for (let i = 0; i < tensor.length; i++) normalized[i] = norm === 0 ? 0 : tensor[i] / norm;
|
|
2365
2437
|
return {
|
|
2366
2438
|
kind: "embedding",
|
|
@@ -3535,34 +3607,69 @@ function walkFieldsForDefaults(fields, out) {
|
|
|
3535
3607
|
}
|
|
3536
3608
|
//#endregion
|
|
3537
3609
|
//#region src/detection-pipeline/runtimes.ts
|
|
3538
|
-
var
|
|
3539
|
-
|
|
3540
|
-
|
|
3541
|
-
|
|
3542
|
-
|
|
3543
|
-
|
|
3544
|
-
|
|
3545
|
-
|
|
3546
|
-
|
|
3547
|
-
|
|
3548
|
-
|
|
3610
|
+
var KNOWN_PLATFORMS = [
|
|
3611
|
+
"darwin",
|
|
3612
|
+
"linux",
|
|
3613
|
+
"win32"
|
|
3614
|
+
];
|
|
3615
|
+
var KNOWN_ARCHES = ["arm64", "x64"];
|
|
3616
|
+
var KNOWN_GPU_TYPES = [
|
|
3617
|
+
"nvidia",
|
|
3618
|
+
"amd",
|
|
3619
|
+
"intel",
|
|
3620
|
+
"apple"
|
|
3621
|
+
];
|
|
3622
|
+
var KNOWN_NPU_TYPES = ["apple-ane", "intel-npu"];
|
|
3623
|
+
function toKnownPlatform(p) {
|
|
3624
|
+
return KNOWN_PLATFORMS.find((v) => v === p) ?? "linux";
|
|
3625
|
+
}
|
|
3626
|
+
function toKnownArch(a) {
|
|
3627
|
+
return KNOWN_ARCHES.find((v) => v === a) ?? "x64";
|
|
3628
|
+
}
|
|
3629
|
+
function gpuInfoFrom(hw) {
|
|
3630
|
+
if (!hw.gpu) return null;
|
|
3631
|
+
const type = KNOWN_GPU_TYPES.find((v) => v === hw.gpu?.type);
|
|
3632
|
+
if (!type) return null;
|
|
3633
|
+
return {
|
|
3634
|
+
type,
|
|
3635
|
+
name: ""
|
|
3636
|
+
};
|
|
3637
|
+
}
|
|
3638
|
+
function npuInfoFrom(hw) {
|
|
3639
|
+
if (!hw.npu) return null;
|
|
3640
|
+
const type = KNOWN_NPU_TYPES.find((v) => v === hw.npu?.type);
|
|
3641
|
+
if (!type) return null;
|
|
3642
|
+
return { type };
|
|
3643
|
+
}
|
|
3644
|
+
function envToHardwareInfo(env) {
|
|
3645
|
+
if (!env.hardware) return null;
|
|
3646
|
+
return {
|
|
3647
|
+
platform: toKnownPlatform(env.platform),
|
|
3648
|
+
arch: toKnownArch(env.arch),
|
|
3649
|
+
cpuModel: "",
|
|
3650
|
+
cpuCores: 0,
|
|
3651
|
+
totalRAM_MB: 0,
|
|
3652
|
+
availableRAM_MB: 0,
|
|
3653
|
+
gpu: gpuInfoFrom(env.hardware),
|
|
3654
|
+
npu: npuInfoFrom(env.hardware)
|
|
3655
|
+
};
|
|
3656
|
+
}
|
|
3657
|
+
function probedToHardwareInfo(hw) {
|
|
3658
|
+
if (!hw) return null;
|
|
3659
|
+
return {
|
|
3660
|
+
platform: toKnownPlatform(process.platform),
|
|
3661
|
+
arch: toKnownArch(process.arch),
|
|
3662
|
+
cpuModel: "",
|
|
3663
|
+
cpuCores: 0,
|
|
3664
|
+
totalRAM_MB: 0,
|
|
3665
|
+
availableRAM_MB: 0,
|
|
3666
|
+
gpu: gpuInfoFrom(hw),
|
|
3667
|
+
npu: npuInfoFrom(hw)
|
|
3668
|
+
};
|
|
3669
|
+
}
|
|
3670
|
+
var RUNTIME_DETAIL = {
|
|
3671
|
+
onnx: {
|
|
3549
3672
|
label: "ONNX Runtime",
|
|
3550
|
-
supports: () => true,
|
|
3551
|
-
devices: (hw) => {
|
|
3552
|
-
if (!hw) return [CPU];
|
|
3553
|
-
const out = [CPU];
|
|
3554
|
-
if (hw.gpu?.type === "nvidia") out.push({
|
|
3555
|
-
value: "cuda",
|
|
3556
|
-
label: "CUDA"
|
|
3557
|
-
});
|
|
3558
|
-
if (hw.npu?.type === "apple-ane") out.push({
|
|
3559
|
-
value: "coreml",
|
|
3560
|
-
label: "CoreML EP"
|
|
3561
|
-
});
|
|
3562
|
-
return out;
|
|
3563
|
-
},
|
|
3564
|
-
defaultDevice: "cpu",
|
|
3565
|
-
modelFormat: "onnx",
|
|
3566
3673
|
pythonRequirements: ["requirements.txt", "requirements-onnxruntime.txt"],
|
|
3567
3674
|
tuning: {
|
|
3568
3675
|
concurrency: 4,
|
|
@@ -3571,25 +3678,8 @@ var RUNTIMES = [
|
|
|
3571
3678
|
intraOpThreads: 0
|
|
3572
3679
|
}
|
|
3573
3680
|
},
|
|
3574
|
-
{
|
|
3575
|
-
id: "openvino",
|
|
3681
|
+
openvino: {
|
|
3576
3682
|
label: "OpenVINO",
|
|
3577
|
-
supports: (env) => env.arch === "x64" && env.platform !== "darwin" && env.hardware?.gpu?.type === "intel",
|
|
3578
|
-
devices: (hw) => {
|
|
3579
|
-
if (!hw) return [AUTO];
|
|
3580
|
-
const out = [AUTO, CPU];
|
|
3581
|
-
if (hw.gpu?.type === "intel") out.push({
|
|
3582
|
-
value: "gpu",
|
|
3583
|
-
label: "GPU"
|
|
3584
|
-
});
|
|
3585
|
-
if (hw.npu?.type === "intel-npu") out.push({
|
|
3586
|
-
value: "npu",
|
|
3587
|
-
label: "NPU"
|
|
3588
|
-
});
|
|
3589
|
-
return out;
|
|
3590
|
-
},
|
|
3591
|
-
defaultDevice: "auto",
|
|
3592
|
-
modelFormat: "openvino",
|
|
3593
3683
|
pythonRequirements: ["requirements.txt", "requirements-openvino.txt"],
|
|
3594
3684
|
tuning: {
|
|
3595
3685
|
concurrency: 1,
|
|
@@ -3597,29 +3687,8 @@ var RUNTIMES = [
|
|
|
3597
3687
|
numStreams: 0
|
|
3598
3688
|
}
|
|
3599
3689
|
},
|
|
3600
|
-
{
|
|
3601
|
-
id: "coreml",
|
|
3690
|
+
coreml: {
|
|
3602
3691
|
label: "CoreML",
|
|
3603
|
-
supports: (env) => env.platform === "darwin",
|
|
3604
|
-
devices: (hw) => {
|
|
3605
|
-
const all = {
|
|
3606
|
-
value: "all",
|
|
3607
|
-
label: "All (ANE + GPU + CPU)"
|
|
3608
|
-
};
|
|
3609
|
-
if (!hw) return [all];
|
|
3610
|
-
const out = [all];
|
|
3611
|
-
if (hw.npu?.type === "apple-ane") out.push({
|
|
3612
|
-
value: "ane",
|
|
3613
|
-
label: "Apple Neural Engine"
|
|
3614
|
-
});
|
|
3615
|
-
out.push({
|
|
3616
|
-
value: "gpu",
|
|
3617
|
-
label: "GPU"
|
|
3618
|
-
}, CPU);
|
|
3619
|
-
return out;
|
|
3620
|
-
},
|
|
3621
|
-
defaultDevice: "all",
|
|
3622
|
-
modelFormat: "coreml",
|
|
3623
3692
|
pythonRequirements: ["requirements.txt", "requirements-coreml.txt"],
|
|
3624
3693
|
tuning: {
|
|
3625
3694
|
concurrency: 1,
|
|
@@ -3629,32 +3698,50 @@ var RUNTIMES = [
|
|
|
3629
3698
|
numWorkers: 1
|
|
3630
3699
|
}
|
|
3631
3700
|
}
|
|
3632
|
-
|
|
3633
|
-
|
|
3634
|
-
|
|
3635
|
-
|
|
3636
|
-
|
|
3637
|
-
}
|
|
3701
|
+
};
|
|
3702
|
+
/**
|
|
3703
|
+
* Returns the list of supported runtime IDs for the given hardware env.
|
|
3704
|
+
* Delegates to `@camstack/types` `supportedRuntimes`.
|
|
3705
|
+
*/
|
|
3638
3706
|
function supportedRuntimes(env) {
|
|
3639
|
-
return
|
|
3707
|
+
return require_dist.supportedRuntimes(envToHardwareInfo(env));
|
|
3640
3708
|
}
|
|
3709
|
+
/**
|
|
3710
|
+
* Returns the device options for a given runtime and probed hardware.
|
|
3711
|
+
* Delegates to `@camstack/types` `runtimeDevices`.
|
|
3712
|
+
*/
|
|
3641
3713
|
function runtimeDevices(id, hardware) {
|
|
3642
|
-
return
|
|
3714
|
+
return require_dist.runtimeDevices(id, probedToHardwareInfo(hardware));
|
|
3643
3715
|
}
|
|
3716
|
+
/**
|
|
3717
|
+
* Returns the default device string for a given runtime.
|
|
3718
|
+
* Delegates to `@camstack/types` `defaultDeviceFor`.
|
|
3719
|
+
*/
|
|
3644
3720
|
function defaultDeviceFor(id) {
|
|
3645
|
-
return
|
|
3646
|
-
}
|
|
3647
|
-
function pythonRequirementsFor(id) {
|
|
3648
|
-
return def(id).pythonRequirements;
|
|
3721
|
+
return require_dist.defaultDeviceFor(id);
|
|
3649
3722
|
}
|
|
3723
|
+
/** Model format for each inference runtime supported by the detection pipeline. */
|
|
3724
|
+
var RUNTIME_FORMAT = {
|
|
3725
|
+
onnx: "onnx",
|
|
3726
|
+
openvino: "openvino",
|
|
3727
|
+
coreml: "coreml"
|
|
3728
|
+
};
|
|
3729
|
+
/**
|
|
3730
|
+
* Returns the model format required for a given runtime.
|
|
3731
|
+
* Returns the locally-typed format string ('onnx' | 'openvino' | 'coreml')
|
|
3732
|
+
* matching the engine-provisioner's own ModelFormat type.
|
|
3733
|
+
*/
|
|
3650
3734
|
function modelFormatFor(id) {
|
|
3651
|
-
return
|
|
3735
|
+
return RUNTIME_FORMAT[id];
|
|
3736
|
+
}
|
|
3737
|
+
function pythonRequirementsFor(id) {
|
|
3738
|
+
return RUNTIME_DETAIL[id].pythonRequirements;
|
|
3652
3739
|
}
|
|
3653
3740
|
function tuningFor(id) {
|
|
3654
|
-
return
|
|
3741
|
+
return RUNTIME_DETAIL[id].tuning;
|
|
3655
3742
|
}
|
|
3656
3743
|
function runtimeLabel(id) {
|
|
3657
|
-
return
|
|
3744
|
+
return RUNTIME_DETAIL[id].label;
|
|
3658
3745
|
}
|
|
3659
3746
|
/**
|
|
3660
3747
|
* Proactive-install hint kept for back-compat (re-exported from index.ts).
|
|
@@ -3995,6 +4082,27 @@ function toProbedHardware(hw) {
|
|
|
3995
4082
|
gpu: hw.gpu ? { type: hw.gpu.type } : null
|
|
3996
4083
|
};
|
|
3997
4084
|
}
|
|
4085
|
+
var ONNX_FLOOR = {
|
|
4086
|
+
runtime: "python",
|
|
4087
|
+
backend: "onnx",
|
|
4088
|
+
format: "onnx",
|
|
4089
|
+
device: "cpu"
|
|
4090
|
+
};
|
|
4091
|
+
/**
|
|
4092
|
+
* Build the onnx-cpu floor pick using `pickBestRuntime` with a null hardware
|
|
4093
|
+
* env. Used wherever the old `detectBestEngine()` sync probe fell back — the
|
|
4094
|
+
* result is identical (onnx / cpu) but is now derived through the shared rules
|
|
4095
|
+
* instead of duplicated inline.
|
|
4096
|
+
*/
|
|
4097
|
+
function onnxFloorPick() {
|
|
4098
|
+
const pick = pickBestRuntime(runtimeEnvFromProcess(null), null);
|
|
4099
|
+
return {
|
|
4100
|
+
runtime: "python",
|
|
4101
|
+
backend: pick.runtimeId,
|
|
4102
|
+
format: modelFormatFor(pick.runtimeId),
|
|
4103
|
+
device: pick.device
|
|
4104
|
+
};
|
|
4105
|
+
}
|
|
3998
4106
|
var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
3999
4107
|
modelsDir;
|
|
4000
4108
|
eventBus;
|
|
@@ -4084,7 +4192,7 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
4084
4192
|
this.readStore = () => settings.readAddonStore();
|
|
4085
4193
|
this.writeStore = (patch) => settings.writeAddonStore(patch);
|
|
4086
4194
|
this.readDeviceStore = settings.readDeviceStore ?? (async () => ({}));
|
|
4087
|
-
this.currentEngine =
|
|
4195
|
+
this.currentEngine = ONNX_FLOOR;
|
|
4088
4196
|
this.log.info("Engine selected (default)", { meta: {
|
|
4089
4197
|
runtime: this.currentEngine.runtime,
|
|
4090
4198
|
backend: this.currentEngine.backend,
|
|
@@ -4150,62 +4258,27 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
4150
4258
|
isReady() {
|
|
4151
4259
|
return this.ready;
|
|
4152
4260
|
}
|
|
4153
|
-
/** Detect the best inference engine for this platform. */
|
|
4154
|
-
static detectBestEngine() {
|
|
4155
|
-
const platform = process.platform;
|
|
4156
|
-
const arch = process.arch;
|
|
4157
|
-
if (platform === "darwin" && arch === "arm64") return {
|
|
4158
|
-
runtime: "python",
|
|
4159
|
-
backend: "coreml",
|
|
4160
|
-
format: "coreml",
|
|
4161
|
-
device: "all"
|
|
4162
|
-
};
|
|
4163
|
-
if (platform === "darwin") return {
|
|
4164
|
-
runtime: "python",
|
|
4165
|
-
backend: "coreml",
|
|
4166
|
-
format: "coreml",
|
|
4167
|
-
device: "gpu"
|
|
4168
|
-
};
|
|
4169
|
-
try {
|
|
4170
|
-
const { execFileSync } = require("node:child_process");
|
|
4171
|
-
execFileSync("nvidia-smi", ["--query-gpu=name", "--format=csv,noheader"], {
|
|
4172
|
-
timeout: 3e3,
|
|
4173
|
-
stdio: "pipe"
|
|
4174
|
-
});
|
|
4175
|
-
return {
|
|
4176
|
-
runtime: "python",
|
|
4177
|
-
backend: "onnx",
|
|
4178
|
-
format: "onnx",
|
|
4179
|
-
device: "cuda"
|
|
4180
|
-
};
|
|
4181
|
-
} catch {}
|
|
4182
|
-
try {
|
|
4183
|
-
const fsmod = require("node:fs");
|
|
4184
|
-
const isIntel = require("node:os").cpus()[0]?.model?.includes("Intel") ?? false;
|
|
4185
|
-
const hasIgpu = fsmod.existsSync("/dev/dri/renderD128");
|
|
4186
|
-
const hasNpu = fsmod.existsSync("/dev/accel/accel0") || fsmod.existsSync("/dev/accel");
|
|
4187
|
-
if (isIntel && (hasIgpu || hasNpu)) return {
|
|
4188
|
-
runtime: "python",
|
|
4189
|
-
backend: "openvino",
|
|
4190
|
-
format: "openvino",
|
|
4191
|
-
device: "auto"
|
|
4192
|
-
};
|
|
4193
|
-
} catch {}
|
|
4194
|
-
return {
|
|
4195
|
-
runtime: "python",
|
|
4196
|
-
backend: "onnx",
|
|
4197
|
-
format: "onnx",
|
|
4198
|
-
device: "cpu"
|
|
4199
|
-
};
|
|
4200
|
-
}
|
|
4201
4261
|
/** Store the addon context. ctx.api is a lazy getter resolved at call time. */
|
|
4202
4262
|
async setApi(addonCtx) {
|
|
4203
4263
|
this.addonCtx = addonCtx;
|
|
4204
|
-
if (this.needsAutoPick) {
|
|
4264
|
+
if (this.needsAutoPick) if (this.addonCtx.useCapability("platform-probe").isReady) {
|
|
4205
4265
|
await this.autoPickAndPersist();
|
|
4206
4266
|
this.needsAutoPick = false;
|
|
4267
|
+
this.startProvisioningForCurrentEngine();
|
|
4268
|
+
} else {
|
|
4269
|
+
this.startProvisioningForCurrentEngine();
|
|
4270
|
+
const unsubscribe = this.addonCtx.onCapabilityStateChange("platform-probe", { type: "global" }, (state) => {
|
|
4271
|
+
if (state !== "ready") return;
|
|
4272
|
+
unsubscribe();
|
|
4273
|
+
if (!this.needsAutoPick) return;
|
|
4274
|
+
this.autoPickAndPersist().then(() => {
|
|
4275
|
+
this.needsAutoPick = false;
|
|
4276
|
+
this.startProvisioningForCurrentEngine();
|
|
4277
|
+
});
|
|
4278
|
+
});
|
|
4279
|
+
this.addonCtx.addDisposer(unsubscribe);
|
|
4207
4280
|
}
|
|
4208
|
-
this.startProvisioningForCurrentEngine();
|
|
4281
|
+
else this.startProvisioningForCurrentEngine();
|
|
4209
4282
|
}
|
|
4210
4283
|
/**
|
|
4211
4284
|
* Auto-pick the best supported runtime at first boot (no stored engine).
|
|
@@ -4220,7 +4293,7 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
4220
4293
|
try {
|
|
4221
4294
|
const api = this.addonCtx?.api;
|
|
4222
4295
|
if (api) {
|
|
4223
|
-
const caps = await api.platformProbe.getCapabilities.query();
|
|
4296
|
+
const caps = await api.platformProbe.getCapabilities.query({ nodeId: this.localProbeNodeId() });
|
|
4224
4297
|
hardware = caps?.hardware ?? null;
|
|
4225
4298
|
const bs = caps?.bestScore;
|
|
4226
4299
|
if (bs && bs.runtime === "python") bestBackendHint = bs.backend;
|
|
@@ -4234,9 +4307,10 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
4234
4307
|
device: pick.device
|
|
4235
4308
|
};
|
|
4236
4309
|
this.currentEngine = engine;
|
|
4310
|
+
const apNode = this.localProbeNodeId();
|
|
4237
4311
|
await this.writeStore({
|
|
4238
|
-
engineBackend: pick.runtimeId,
|
|
4239
|
-
engineDevice: pick.device
|
|
4312
|
+
[nodeEngineKey("engineBackend", apNode)]: pick.runtimeId,
|
|
4313
|
+
[nodeEngineKey("engineDevice", apNode)]: pick.device
|
|
4240
4314
|
});
|
|
4241
4315
|
this.log.info("Auto-picked engine at first boot", { meta: {
|
|
4242
4316
|
backend: pick.runtimeId,
|
|
@@ -4409,11 +4483,22 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
4409
4483
|
* callers fall back to the registry's safe minimum. The engine OFFER derives
|
|
4410
4484
|
* from hardware ONLY — install state (probe `scores`) never gates it.
|
|
4411
4485
|
*/
|
|
4486
|
+
/**
|
|
4487
|
+
* The local Moleculer node id (child-suffix stripped). MUST be passed to every
|
|
4488
|
+
* `platformProbe.getCapabilities` query: the cap is a singleton and a query
|
|
4489
|
+
* with no nodeId resolves to the HUB's probe — so on a remote agent the engine
|
|
4490
|
+
* decision would use the HUB's hardware (e.g. an Intel NPU the agent doesn't
|
|
4491
|
+
* have) and pin a device the node can't run, breaking provisioning.
|
|
4492
|
+
*/
|
|
4493
|
+
localProbeNodeId() {
|
|
4494
|
+
const raw = this.addonCtx?.kernel?.localNodeId ?? "hub";
|
|
4495
|
+
return raw.includes("/") ? raw.split("/")[0] : raw;
|
|
4496
|
+
}
|
|
4412
4497
|
async fetchProbeGatingData() {
|
|
4413
4498
|
try {
|
|
4414
4499
|
const api = this.addonCtx?.api;
|
|
4415
4500
|
if (!api) return { hardware: null };
|
|
4416
|
-
return { hardware: (await api.platformProbe.getCapabilities.query())?.hardware ?? null };
|
|
4501
|
+
return { hardware: (await api.platformProbe.getCapabilities.query({ nodeId: this.localProbeNodeId() }))?.hardware ?? null };
|
|
4417
4502
|
} catch {
|
|
4418
4503
|
return { hardware: null };
|
|
4419
4504
|
}
|
|
@@ -5478,7 +5563,7 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
5478
5563
|
this.engineFactory = null;
|
|
5479
5564
|
this.executor = null;
|
|
5480
5565
|
}
|
|
5481
|
-
for (const id of
|
|
5566
|
+
for (const id of Array.from(this.deviceProxies.keys())) this.releaseDeviceProxy(id);
|
|
5482
5567
|
}
|
|
5483
5568
|
/**
|
|
5484
5569
|
* Resolve and cache a {@link DeviceProxy} for the given camera. Pins
|
|
@@ -5662,25 +5747,27 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
5662
5747
|
* fields (`engineRuntime`, `engineBackend`, `engineDevice`). Fallback
|
|
5663
5748
|
* to the legacy `KEY_ENGINE` JSON blob for pre-migration stores.
|
|
5664
5749
|
* Returns null when neither source has anything; the caller keeps
|
|
5665
|
-
*
|
|
5750
|
+
* the onnx floor set at construction until autoPickAndPersist() runs.
|
|
5666
5751
|
*/
|
|
5667
5752
|
async loadEngine() {
|
|
5668
5753
|
const store = await this.readStore();
|
|
5669
5754
|
const storedRuntime = store["engineRuntime"];
|
|
5670
|
-
const
|
|
5755
|
+
const node = this.localProbeNodeId();
|
|
5756
|
+
const storedBackend = readNodeEngineValue(store, "engineBackend", node);
|
|
5671
5757
|
if (typeof storedBackend === "string" && storedBackend.length > 0) {
|
|
5672
5758
|
const backend = storedBackend;
|
|
5673
|
-
const
|
|
5674
|
-
const
|
|
5759
|
+
const storedDeviceRaw = readNodeEngineValue(store, "engineDevice", node);
|
|
5760
|
+
const storedDevice = typeof storedDeviceRaw === "string" ? storedDeviceRaw : "";
|
|
5761
|
+
const floor = onnxFloorPick();
|
|
5675
5762
|
const migratedBackend = typeof storedRuntime === "string" && storedRuntime === "node" && backend === "cpu" ? "onnx" : backend;
|
|
5676
5763
|
if (!DetectionPipelineProvider.isPythonBackendAvailable(migratedBackend, this.executorOptions.pythonPath ?? "")) {
|
|
5677
|
-
this.log.warn("Stored engine backend unavailable on this node — falling back to
|
|
5764
|
+
this.log.warn("Stored engine backend unavailable on this node — falling back to onnx floor", { meta: {
|
|
5678
5765
|
stored: migratedBackend,
|
|
5679
|
-
fallback: `${
|
|
5766
|
+
fallback: `${floor.backend}`
|
|
5680
5767
|
} });
|
|
5681
|
-
return
|
|
5768
|
+
return floor;
|
|
5682
5769
|
}
|
|
5683
|
-
const device = storedDevice ||
|
|
5770
|
+
const device = storedDevice || floor.device;
|
|
5684
5771
|
return {
|
|
5685
5772
|
runtime: "python",
|
|
5686
5773
|
backend: migratedBackend,
|
|
@@ -5852,18 +5939,14 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
5852
5939
|
const api = this.addonCtx?.api;
|
|
5853
5940
|
let best;
|
|
5854
5941
|
if (api) try {
|
|
5855
|
-
const caps = await api.platformProbe.getCapabilities.query();
|
|
5942
|
+
const caps = await api.platformProbe.getCapabilities.query({ nodeId: this.localProbeNodeId() });
|
|
5856
5943
|
const bs = caps?.bestScore;
|
|
5857
5944
|
if (bs && bs.runtime === "python") {
|
|
5858
5945
|
const probeBackend = bs.backend;
|
|
5859
5946
|
const probeDevice = (() => {
|
|
5860
5947
|
const hw = caps.hardware;
|
|
5861
|
-
if (probeBackend === "
|
|
5862
|
-
if (probeBackend === "
|
|
5863
|
-
if (hw?.npu?.type === "intel-npu") return "npu";
|
|
5864
|
-
if (hw?.gpu?.type === "intel") return "gpu";
|
|
5865
|
-
return "auto";
|
|
5866
|
-
}
|
|
5948
|
+
if (probeBackend === "openvino") return defaultDeviceFor("openvino");
|
|
5949
|
+
if (probeBackend === "coreml") return defaultDeviceFor("coreml");
|
|
5867
5950
|
if (probeBackend === "onnx") return hw?.gpu?.type === "nvidia" ? "cuda" : "cpu";
|
|
5868
5951
|
return "cpu";
|
|
5869
5952
|
})();
|
|
@@ -5873,16 +5956,17 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
5873
5956
|
format: backendToFormat(probeBackend),
|
|
5874
5957
|
device: probeDevice
|
|
5875
5958
|
};
|
|
5876
|
-
} else best =
|
|
5959
|
+
} else best = onnxFloorPick();
|
|
5877
5960
|
} catch {
|
|
5878
|
-
best =
|
|
5961
|
+
best = onnxFloorPick();
|
|
5879
5962
|
}
|
|
5880
|
-
else best =
|
|
5963
|
+
else best = onnxFloorPick();
|
|
5881
5964
|
const probedLabel = `${best.backend}/${best.device ?? "default"}`;
|
|
5965
|
+
const rpNode = this.localProbeNodeId();
|
|
5882
5966
|
await this.writeStore({
|
|
5883
|
-
probedBestEngine: probedLabel,
|
|
5884
|
-
engineBackend: best.backend,
|
|
5885
|
-
engineDevice: best.device ?? "cpu"
|
|
5967
|
+
[nodeEngineKey("probedBestEngine", rpNode)]: probedLabel,
|
|
5968
|
+
[nodeEngineKey("engineBackend", rpNode)]: best.backend,
|
|
5969
|
+
[nodeEngineKey("engineDevice", rpNode)]: best.device ?? "cpu"
|
|
5886
5970
|
});
|
|
5887
5971
|
this.log.info("Re-probed engine — wrote back engineBackend + engineDevice", { meta: {
|
|
5888
5972
|
backend: best.backend,
|
|
@@ -6305,7 +6389,8 @@ var DEFAULT_CONFIG = {
|
|
|
6305
6389
|
engineRuntime: "python",
|
|
6306
6390
|
engineBackend: "onnx",
|
|
6307
6391
|
engineDevice: "cpu",
|
|
6308
|
-
probedBestEngine: ""
|
|
6392
|
+
probedBestEngine: "",
|
|
6393
|
+
activeEngine: ""
|
|
6309
6394
|
};
|
|
6310
6395
|
/** Derive the model-format from a backend value. Called by the provider. */
|
|
6311
6396
|
function backendToFormat(backend) {
|
|
@@ -6348,6 +6433,15 @@ var POOL_BOUND_KEYS = [
|
|
|
6348
6433
|
];
|
|
6349
6434
|
var DetectionPipelineAddon = class extends require_dist.BaseAddon {
|
|
6350
6435
|
provider = null;
|
|
6436
|
+
/** Last non-null probed hardware PER NODE — reused when the probe transiently
|
|
6437
|
+
* returns null so offered backends / device lists don't collapse. Keyed by
|
|
6438
|
+
* node because the hub addon probes other nodes when serving their config. */
|
|
6439
|
+
lastGoodHardwareByNode = /* @__PURE__ */ new Map();
|
|
6440
|
+
/** This node's effective engine selection, cached from the node-scoped store
|
|
6441
|
+
* so the synchronous `resolveBackendTuning` and the reprobe gate don't read
|
|
6442
|
+
* the cluster-shared bare keys. Refreshed at init + on every config change. */
|
|
6443
|
+
nodeEngineBackend = DEFAULT_CONFIG.engineBackend;
|
|
6444
|
+
nodeProbedBestEngine = "";
|
|
6351
6445
|
engineMetricsTimer = null;
|
|
6352
6446
|
/** Snapshot-equality cache for engine-metrics emit. Most ticks
|
|
6353
6447
|
* the engine inventory is unchanged (no model load/unload), so
|
|
@@ -6402,6 +6496,14 @@ var DetectionPipelineAddon = class extends require_dist.BaseAddon {
|
|
|
6402
6496
|
tooltip: "Re-probe engine"
|
|
6403
6497
|
}]
|
|
6404
6498
|
}),
|
|
6499
|
+
this.field({
|
|
6500
|
+
type: "text",
|
|
6501
|
+
key: "activeEngine",
|
|
6502
|
+
label: "Active engine",
|
|
6503
|
+
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.",
|
|
6504
|
+
readonlyField: true,
|
|
6505
|
+
default: ""
|
|
6506
|
+
}),
|
|
6405
6507
|
this.field({
|
|
6406
6508
|
type: "select",
|
|
6407
6509
|
key: "engineBackend",
|
|
@@ -6538,14 +6640,18 @@ var DetectionPipelineAddon = class extends require_dist.BaseAddon {
|
|
|
6538
6640
|
* probe is unreachable). Stored backend / device snap back to the registry
|
|
6539
6641
|
* floor / default when they fall outside the offered set.
|
|
6540
6642
|
*/
|
|
6541
|
-
async getGlobalSettings(overlay) {
|
|
6542
|
-
const
|
|
6543
|
-
const
|
|
6643
|
+
async getGlobalSettings(overlay, _cap, nodeId) {
|
|
6644
|
+
const targetNode = nodeId ? normalizeEngineNodeId(nodeId) : this.localNodeId();
|
|
6645
|
+
const rawStored = await this.resolveUiSettingsStore();
|
|
6646
|
+
if (rawStored === null) return null;
|
|
6647
|
+
const stored = projectNodeEngine(rawStored, targetNode);
|
|
6648
|
+
const storedBackendRaw = stored["engineBackend"];
|
|
6649
|
+
if (typeof storedBackendRaw !== "string" || storedBackendRaw === "") return null;
|
|
6544
6650
|
const merged = overlay ? {
|
|
6545
6651
|
...stored,
|
|
6546
6652
|
...overlay
|
|
6547
6653
|
} : stored;
|
|
6548
|
-
const env = await this.probeHardwareEnv();
|
|
6654
|
+
const env = await this.probeHardwareEnv(targetNode);
|
|
6549
6655
|
const hardware = env.hardware;
|
|
6550
6656
|
const offered = supportedRuntimes(env);
|
|
6551
6657
|
const runtimeBackends = offered.map((id) => ({
|
|
@@ -6553,14 +6659,22 @@ var DetectionPipelineAddon = class extends require_dist.BaseAddon {
|
|
|
6553
6659
|
label: runtimeLabel(id)
|
|
6554
6660
|
}));
|
|
6555
6661
|
const storedBackend = typeof merged.engineBackend === "string" ? merged.engineBackend : "";
|
|
6556
|
-
const backend =
|
|
6662
|
+
const backend = (() => {
|
|
6663
|
+
const rid = toRuntimeId(storedBackend);
|
|
6664
|
+
if (rid === "onnx") return "onnx";
|
|
6665
|
+
if (offered.includes(rid)) return rid;
|
|
6666
|
+
return rid === "openvino" && env.platform !== "darwin" && env.arch === "x64" || rid === "coreml" && env.platform === "darwin" ? rid : offered[0] ?? "onnx";
|
|
6667
|
+
})();
|
|
6557
6668
|
const deviceOptions = runtimeDevices(backend, hardware);
|
|
6558
6669
|
const storedDevice = typeof merged.engineDevice === "string" ? merged.engineDevice : "";
|
|
6559
6670
|
const device = deviceOptions.find((d) => d.value === storedDevice)?.value ?? defaultDeviceFor(backend);
|
|
6671
|
+
const prov = targetNode === this.localNodeId() ? this.provider?.getEngineProvisioning() : void 0;
|
|
6672
|
+
const activeEngine = prov && prov.runtimeId ? `${prov.runtimeId}/${prov.device ?? "default"} (${prov.state})` : "";
|
|
6560
6673
|
const raw = {
|
|
6561
6674
|
...merged,
|
|
6562
6675
|
engineBackend: backend,
|
|
6563
|
-
engineDevice: device
|
|
6676
|
+
engineDevice: device,
|
|
6677
|
+
activeEngine
|
|
6564
6678
|
};
|
|
6565
6679
|
const schema = this.globalSettingsSchema();
|
|
6566
6680
|
if (!schema) return { sections: [] };
|
|
@@ -6591,15 +6705,15 @@ var DetectionPipelineAddon = class extends require_dist.BaseAddon {
|
|
|
6591
6705
|
if (field.type === "slider" && "key" in field) {
|
|
6592
6706
|
const tuned = tuning[field.key];
|
|
6593
6707
|
const sliderField = field;
|
|
6594
|
-
let
|
|
6708
|
+
let patchedField = typeof tuned === "number" ? {
|
|
6595
6709
|
...sliderField,
|
|
6596
6710
|
default: tuned
|
|
6597
6711
|
} : sliderField;
|
|
6598
|
-
if (sliderField.key === "concurrency" && backend === "coreml")
|
|
6599
|
-
...
|
|
6712
|
+
if (sliderField.key === "concurrency" && backend === "coreml") patchedField = {
|
|
6713
|
+
...patchedField,
|
|
6600
6714
|
max: 4
|
|
6601
6715
|
};
|
|
6602
|
-
return
|
|
6716
|
+
return patchedField;
|
|
6603
6717
|
}
|
|
6604
6718
|
return field;
|
|
6605
6719
|
})
|
|
@@ -6616,23 +6730,41 @@ var DetectionPipelineAddon = class extends require_dist.BaseAddon {
|
|
|
6616
6730
|
* Protected seam — overridable by tests (canned hardware) and reused by the
|
|
6617
6731
|
* Phase 2 auto-pick path. NEVER reads install state.
|
|
6618
6732
|
*/
|
|
6619
|
-
|
|
6620
|
-
|
|
6733
|
+
/**
|
|
6734
|
+
* Resolve the persisted UI settings store, or `null` when it can't be read
|
|
6735
|
+
* right now (ctx/settings not wired — mid-restart). A readable-but-empty
|
|
6736
|
+
* store (genuine first boot) returns `{}`, never null. `getGlobalSettings`
|
|
6737
|
+
* uses the null signal to avoid serving fabricated empty-store defaults
|
|
6738
|
+
* during the restart window. Seam kept protected so tests can drive the
|
|
6739
|
+
* unreadable vs empty vs populated cases without faking a full AddonContext.
|
|
6740
|
+
*/
|
|
6741
|
+
async resolveUiSettingsStore() {
|
|
6742
|
+
const settings = this.ctxIfReady?.settings;
|
|
6743
|
+
if (!settings) return null;
|
|
6744
|
+
return await settings.readAddonStore() ?? {};
|
|
6745
|
+
}
|
|
6746
|
+
async probeHardwareEnv(nodeId) {
|
|
6747
|
+
const node = nodeId ?? this.localNodeId();
|
|
6748
|
+
const hardware = await this.resolveProbeHardware(node);
|
|
6749
|
+
if (hardware) this.lastGoodHardwareByNode.set(node, hardware);
|
|
6750
|
+
const effective = hardware ?? this.lastGoodHardwareByNode.get(node) ?? null;
|
|
6621
6751
|
return {
|
|
6622
6752
|
platform: process.platform,
|
|
6623
6753
|
arch: process.arch,
|
|
6624
|
-
hardware
|
|
6754
|
+
hardware: effective
|
|
6625
6755
|
};
|
|
6626
6756
|
}
|
|
6627
6757
|
/**
|
|
6628
|
-
* Fetch the probed hardware from the platform-probe cap
|
|
6629
|
-
* the cap is not reachable (caller falls back to
|
|
6758
|
+
* Fetch the probed hardware from the platform-probe cap for `nodeId` (default
|
|
6759
|
+
* = self). Returns null when the cap is not reachable (caller falls back to
|
|
6760
|
+
* the registry's safe minimum).
|
|
6630
6761
|
*/
|
|
6631
|
-
async resolveProbeHardware() {
|
|
6762
|
+
async resolveProbeHardware(nodeId) {
|
|
6632
6763
|
try {
|
|
6633
6764
|
const api = this.ctxIfReady?.api;
|
|
6634
6765
|
if (!api) return null;
|
|
6635
|
-
const
|
|
6766
|
+
const node = nodeId ?? this.localNodeId();
|
|
6767
|
+
const hw = (node === this.localNodeId() ? await api.platformProbe.getCapabilities.query() : await api.platformProbe.getCapabilities.query(void 0, require_dist.nodePin(node)))?.hardware;
|
|
6636
6768
|
if (!hw) return null;
|
|
6637
6769
|
return {
|
|
6638
6770
|
npu: hw.npu ? { type: hw.npu.type } : null,
|
|
@@ -6643,6 +6775,61 @@ var DetectionPipelineAddon = class extends require_dist.BaseAddon {
|
|
|
6643
6775
|
}
|
|
6644
6776
|
}
|
|
6645
6777
|
/**
|
|
6778
|
+
* Bare node id used to scope the engine cascade in the shared store. MUST
|
|
6779
|
+
* match the provider's `localProbeNodeId()` (kernel.localNodeId, default
|
|
6780
|
+
* 'hub') so the UI write path (this) and the provider read/write path
|
|
6781
|
+
* (loadEngine / autoPick / reprobe) target the SAME `<key>@<nodeId>`. The old
|
|
6782
|
+
* `?? this.ctx.id` fallback resolved to the ADDON id ('detection-pipeline')
|
|
6783
|
+
* when kernel.localNodeId was absent, so UI saves landed on a key the
|
|
6784
|
+
* provider never read — silently losing the per-node selection.
|
|
6785
|
+
*/
|
|
6786
|
+
localNodeId() {
|
|
6787
|
+
return normalizeEngineNodeId(this.ctxIfReady?.kernel?.localNodeId ?? "hub");
|
|
6788
|
+
}
|
|
6789
|
+
/**
|
|
6790
|
+
* Refresh this node's cached engine selection from the node-scoped store.
|
|
6791
|
+
* `resolveBackendTuning` is synchronous and the reprobe gate runs before the
|
|
6792
|
+
* provider exists, so both read these cached fields instead of the
|
|
6793
|
+
* cluster-shared bare keys (which belong to no single node). Best-effort: a
|
|
6794
|
+
* transiently-unreadable store leaves the last cached values in place.
|
|
6795
|
+
*/
|
|
6796
|
+
async refreshNodeEngineFromStore() {
|
|
6797
|
+
const store = await this.resolveUiSettingsStore();
|
|
6798
|
+
if (store === null) return;
|
|
6799
|
+
const node = this.localNodeId();
|
|
6800
|
+
const backend = readNodeEngineValue(store, "engineBackend", node);
|
|
6801
|
+
if (typeof backend === "string" && backend !== "") this.nodeEngineBackend = backend;
|
|
6802
|
+
const probed = readNodeEngineValue(store, "probedBestEngine", node);
|
|
6803
|
+
this.nodeProbedBestEngine = typeof probed === "string" ? probed : "";
|
|
6804
|
+
}
|
|
6805
|
+
/**
|
|
6806
|
+
* Persist a settings patch, mirroring the engine cascade fields to the TARGET
|
|
6807
|
+
* node's scoped keys so each node keeps an INDEPENDENT engine selection in the
|
|
6808
|
+
* cluster-central store. The hub addon serves writes for every node, so it
|
|
6809
|
+
* scopes by the requested `nodeId` (default self).
|
|
6810
|
+
*
|
|
6811
|
+
* When the target IS this node, `super.updateGlobalSettings` drives the normal
|
|
6812
|
+
* apply path (`resolveConfig` / `onConfigChanged` / `requiresRestart` restart),
|
|
6813
|
+
* and the bare engine keys it writes are shadowed by the node-scoped keys on
|
|
6814
|
+
* read. When the target is a SIBLING node, we persist the scoped engine keys +
|
|
6815
|
+
* the non-engine bare keys but DON'T run this node's restart/reprovision — the
|
|
6816
|
+
* owning node applies its own engine selection on its next (re)start.
|
|
6817
|
+
*/
|
|
6818
|
+
async updateGlobalSettings(patch, nodeId) {
|
|
6819
|
+
const targetNode = nodeId ? normalizeEngineNodeId(nodeId) : this.localNodeId();
|
|
6820
|
+
const patchRecord = patch;
|
|
6821
|
+
const scopedEngine = {};
|
|
6822
|
+
for (const key of ENGINE_CASCADE_KEYS) if (key in patchRecord) scopedEngine[nodeEngineKey(key, targetNode)] = patchRecord[key];
|
|
6823
|
+
if (Object.keys(scopedEngine).length > 0) await this.ctxIfReady?.settings?.writeAddonStore(scopedEngine);
|
|
6824
|
+
if (targetNode === this.localNodeId()) {
|
|
6825
|
+
await super.updateGlobalSettings(patch, nodeId);
|
|
6826
|
+
return;
|
|
6827
|
+
}
|
|
6828
|
+
const sharedPatch = {};
|
|
6829
|
+
for (const [k, v] of Object.entries(patchRecord)) if (!ENGINE_CASCADE_KEYS.includes(k)) sharedPatch[k] = v;
|
|
6830
|
+
if (Object.keys(sharedPatch).length > 0) await this.ctxIfReady?.settings?.writeAddonStore(sharedPatch);
|
|
6831
|
+
}
|
|
6832
|
+
/**
|
|
6646
6833
|
* Resolve the effective pool tuning for the configured backend.
|
|
6647
6834
|
*
|
|
6648
6835
|
* Reads the registry's `tuningFor(backend)` and ignores any persisted
|
|
@@ -6656,7 +6843,7 @@ var DetectionPipelineAddon = class extends require_dist.BaseAddon {
|
|
|
6656
6843
|
* a reason to disagree.
|
|
6657
6844
|
*/
|
|
6658
6845
|
resolveBackendTuning() {
|
|
6659
|
-
const t = tuningFor(toRuntimeId(this.
|
|
6846
|
+
const t = tuningFor(toRuntimeId(this.nodeEngineBackend ?? DEFAULT_CONFIG.engineBackend));
|
|
6660
6847
|
const num = (v, dflt) => typeof v === "number" && v > 0 ? v : dflt;
|
|
6661
6848
|
const batch = (v, dflt) => v === "none" || v === "list" || v === "window" ? v : dflt;
|
|
6662
6849
|
return {
|
|
@@ -6675,6 +6862,7 @@ var DetectionPipelineAddon = class extends require_dist.BaseAddon {
|
|
|
6675
6862
|
relativePath: ""
|
|
6676
6863
|
}).catch(() => "camstack-data/models");
|
|
6677
6864
|
if (!this.ctx.settings) throw new Error("DetectionPipelineAddon: ctx.settings not available");
|
|
6865
|
+
await this.refreshNodeEngineFromStore();
|
|
6678
6866
|
this.pythonAddonDir = resolveAddonPythonDir();
|
|
6679
6867
|
const py = await ensurePythonReady(this.ctx.deps, this.ctx.logger);
|
|
6680
6868
|
if (py.ok && py.pythonPath) this.pythonPath = py.pythonPath;
|
|
@@ -6696,7 +6884,7 @@ var DetectionPipelineAddon = class extends require_dist.BaseAddon {
|
|
|
6696
6884
|
});
|
|
6697
6885
|
await this.provider.init();
|
|
6698
6886
|
await this.provider.setApi(this.ctx);
|
|
6699
|
-
if (!this.
|
|
6887
|
+
if (!this.nodeProbedBestEngine) await this.provider.reprobeEngine().catch((err) => {
|
|
6700
6888
|
this.ctx.logger.warn("auto-reprobe engine failed", { meta: { error: err instanceof Error ? err.message : String(err) } });
|
|
6701
6889
|
});
|
|
6702
6890
|
await this.provider.warmPool();
|
|
@@ -6748,8 +6936,8 @@ var DetectionPipelineAddon = class extends require_dist.BaseAddon {
|
|
|
6748
6936
|
/**
|
|
6749
6937
|
* Proactively install the OpenVINO Python package when Intel hardware
|
|
6750
6938
|
* (iGPU or NPU) is detected. Called once from `onInitialize`, before the
|
|
6751
|
-
* provider is constructed, so the module is
|
|
6752
|
-
*
|
|
6939
|
+
* provider is constructed, so the module is RUNNABLE when the engine is
|
|
6940
|
+
* first loaded (gated by `loadEngine`'s `isPythonBackendAvailable` check).
|
|
6753
6941
|
*
|
|
6754
6942
|
* Failure is non-fatal: a warning is logged and the addon continues with
|
|
6755
6943
|
* the onnx-cpu baseline. The hardware query itself is also best-effort —
|
|
@@ -6818,6 +7006,7 @@ var DetectionPipelineAddon = class extends require_dist.BaseAddon {
|
|
|
6818
7006
|
* lifecycle (engineFactory rebuild on next runPipeline).
|
|
6819
7007
|
*/
|
|
6820
7008
|
async onConfigChanged() {
|
|
7009
|
+
await this.refreshNodeEngineFromStore();
|
|
6821
7010
|
if (this.provider) await this.provider.onEngineSelectionChanged().catch((err) => {
|
|
6822
7011
|
this.ctx.logger.warn("engine provisioning re-select failed on config change", { meta: { error: err instanceof Error ? err.message : String(err) } });
|
|
6823
7012
|
});
|