@camstack/addon-pipeline 1.2.294 → 1.2.295
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/THIRD_PARTY_MODELS.md +233 -0
- package/dist/audio-analyzer/index.js +2 -2
- package/dist/audio-analyzer/index.mjs +2 -2
- package/dist/{default-detection-model-0dPKRKUD.mjs → default-detection-model-CAUBVbgK.mjs} +107 -54
- package/dist/{default-detection-model-D24AJOTn.js → default-detection-model-wNEISVA9.js} +107 -54
- package/dist/detection-pipeline/index.js +284 -457
- package/dist/detection-pipeline/index.mjs +284 -457
- package/dist/{dist-CJR259Xf.js → dist-BsVcf5wO.js} +1310 -832
- package/dist/{dist-RXbmRAwP.mjs → dist-CuxSNLKW.mjs} +1305 -833
- package/dist/motion-wasm/index.js +1 -1
- package/dist/motion-wasm/index.mjs +1 -1
- package/dist/{node-atmRSHPk.mjs → node-Cqb1QiXk.mjs} +1 -1
- package/dist/{node-DWg9zbY1.js → node-UFk6I2f6.js} +1 -1
- package/dist/pipeline-runner/index.js +189 -70
- package/dist/pipeline-runner/index.mjs +189 -70
- package/dist/{process-memory-BJUXvTjd.js → process-memory-CJV29sPf.js} +1 -1
- package/dist/{process-memory-BgFOHFnx.mjs → process-memory-D0Nvs9rr.mjs} +1 -1
- package/dist/recorder/index.js +12 -2
- package/dist/recorder/index.mjs +12 -2
- package/dist/{segment-demux-js-C_fPJub3.js → segment-demux-js-DGwmf5hu.js} +1 -1
- package/dist/{segment-demux-js-G7wFpHzn.mjs → segment-demux-js-DIDWw1iE.mjs} +1 -1
- package/dist/stream-broker/_stub.js +2 -2
- package/dist/stream-broker/{_virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-6IyM-BIn.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-B7nBFqva.mjs} +2 -2
- package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-CCIyBvRa.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-DoyA71_q.mjs → _virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-DeD_UFdb.mjs} +1 -1
- package/dist/stream-broker/demux-worker-child.js +1 -1
- package/dist/stream-broker/demux-worker-child.mjs +1 -1
- package/dist/stream-broker/{hostInit-BBYHWS3M.mjs → hostInit-BKlb3qac.mjs} +2 -2
- package/dist/stream-broker/index.js +3 -3
- package/dist/stream-broker/index.mjs +3 -3
- package/dist/stream-broker/remoteEntry.js +1 -1
- package/package.json +3 -2
- package/python/postprocessors/__init__.py +2 -0
- package/python/postprocessors/ssd.py +73 -17
- package/python/postprocessors/test_ssd.py +205 -0
- package/python/postprocessors/test_yunet.py +292 -0
- package/python/postprocessors/testdata/ssdlite_mobiledet_outputs.json +1 -0
- package/python/postprocessors/yunet.py +275 -0
- package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-6IHzlLJ_.mjs +0 -26
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { n as __require } from "../chunk-DnnnRqeS.mjs";
|
|
2
|
-
import {
|
|
3
|
-
import { s as readProcessCost } from "../node-
|
|
4
|
-
import { a as localFrameRegistry, c as ALL_STEPS, d as getStepDefinition, f as resolveModelForFormat, l as getDefaultModelForFormat, m as landmarkPrecisionVerdict, n as resolveDefaultDetectionModel, r as macroGateVerdict, s as ALL_PIPELINE_STEPS, u as getStep } from "../default-detection-model-
|
|
2
|
+
import { At as pickClusterStepSettings, Bt as resolveClusterStepModelId, Dn as array, Dt as overlayClusterStepSettings, H as YAMNET_TO_MACRO, In as string, Kt as runtimeDevices$1, Ln as union, Nn as object, Nt as pipelineExecutorCapability, Rn as EventCategory, Sn as parseJsonUnknown, Tn as sleep, Wt as resolvePoolMemoryPolicy, Zt as supportedRuntimes$1, _n as hydrateSchema, en as errMsg, et as defaultDeviceFor$1, gt as inferModelProvider, it as detectionPipelineCapability, j as PoolMemoryWatchdog, kt as pickClusterStepModels, lt as evaluateZoneRules, m as DEFAULT_CLUSTER_STEP_SETTINGS, p as DEFAULT_CLUSTER_STEP_MODELS, pn as createEvent, sn as BaseAddon, st as enumerateInferenceDevices, t as APPLE_SA_TO_MACRO, vt as loadContributionCapability, xn as nodePin, y as DEVICE_BACKEND_TO_FORMAT } from "../dist-CuxSNLKW.mjs";
|
|
3
|
+
import { s as readProcessCost } from "../node-Cqb1QiXk.mjs";
|
|
4
|
+
import { a as localFrameRegistry, c as ALL_STEPS, d as getStepDefinition, f as resolveModelForFormat, l as getDefaultModelForFormat, m as landmarkPrecisionVerdict, n as resolveDefaultDetectionModel, r as macroGateVerdict, s as ALL_PIPELINE_STEPS, u as getStep } from "../default-detection-model-CAUBVbgK.mjs";
|
|
5
5
|
import { t as getSharp } from "../lazy-sharp-DXsqwpph.mjs";
|
|
6
|
-
import { n as pickNodePlatformArch, t as readProcessMemory } from "../process-memory-
|
|
6
|
+
import { n as pickNodePlatformArch, t as readProcessMemory } from "../process-memory-D0Nvs9rr.mjs";
|
|
7
7
|
import * as os from "node:os";
|
|
8
8
|
import * as fs from "node:fs";
|
|
9
9
|
import * as path$1 from "node:path";
|
|
@@ -1696,12 +1696,13 @@ function resolveEffectivePostprocessorForEntry(definition, entry) {
|
|
|
1696
1696
|
//#region src/detection-pipeline/registry/model-knob-applicability.ts
|
|
1697
1697
|
/** Decodes that run NMS in the pipeline (and hence consult `nmsIouThreshold`).
|
|
1698
1698
|
* Mirrors the Python postprocessors that read the knob — yolo.py,
|
|
1699
|
-
* yolo_seg.py, rfdetr.py, scrfd.py. */
|
|
1699
|
+
* yolo_seg.py, rfdetr.py, scrfd.py, yunet.py. */
|
|
1700
1700
|
var NMS_CONSUMING_POSTPROCESSORS = new Set([
|
|
1701
1701
|
"yolo",
|
|
1702
1702
|
"yolo-seg",
|
|
1703
1703
|
"rfdetr",
|
|
1704
|
-
"scrfd"
|
|
1704
|
+
"scrfd",
|
|
1705
|
+
"yunet"
|
|
1705
1706
|
]);
|
|
1706
1707
|
/**
|
|
1707
1708
|
* Does this decode consult `nmsIouThreshold`?
|
|
@@ -3158,6 +3159,52 @@ function createInferenceTimeoutGuard(deps) {
|
|
|
3158
3159
|
};
|
|
3159
3160
|
}
|
|
3160
3161
|
//#endregion
|
|
3162
|
+
//#region src/detection-pipeline/model-pin.ts
|
|
3163
|
+
function requestedModels(steps) {
|
|
3164
|
+
const out = /* @__PURE__ */ new Map();
|
|
3165
|
+
const walk = (nodes) => {
|
|
3166
|
+
for (const step of nodes) {
|
|
3167
|
+
if (!step.enabled) continue;
|
|
3168
|
+
out.set(step.addonId, step.modelId);
|
|
3169
|
+
if (step.children !== void 0) walk(step.children);
|
|
3170
|
+
}
|
|
3171
|
+
};
|
|
3172
|
+
walk(steps);
|
|
3173
|
+
return out;
|
|
3174
|
+
}
|
|
3175
|
+
function checkReplayPin(input) {
|
|
3176
|
+
if (input.requestedDeviceKey !== void 0 && input.dispatchDeviceKey !== input.requestedDeviceKey) return {
|
|
3177
|
+
kind: "refused",
|
|
3178
|
+
reason: "device-not-servable",
|
|
3179
|
+
detail: `device pool ${input.requestedDeviceKey} cannot run this tree (the capability gate would fall back to ${input.dispatchDeviceKey ?? "the node default"})`
|
|
3180
|
+
};
|
|
3181
|
+
const asked = requestedModels(input.requested);
|
|
3182
|
+
for (const step of input.executing) {
|
|
3183
|
+
const want = asked.get(step.addonId);
|
|
3184
|
+
if (want === void 0 || want.length === 0) return {
|
|
3185
|
+
kind: "refused",
|
|
3186
|
+
reason: "model-not-servable",
|
|
3187
|
+
detail: `${step.addonId}: no model pinned by the caller (would run ${step.modelId}, ${input.format})`
|
|
3188
|
+
};
|
|
3189
|
+
if (want !== step.modelId) return {
|
|
3190
|
+
kind: "refused",
|
|
3191
|
+
reason: "model-not-servable",
|
|
3192
|
+
detail: `${step.addonId}: asked for ${want}, this pool would run ${step.modelId} (${input.format})`
|
|
3193
|
+
};
|
|
3194
|
+
}
|
|
3195
|
+
return { kind: "pinned" };
|
|
3196
|
+
}
|
|
3197
|
+
var PRIMARY_ROOT_STEP = "object-detection";
|
|
3198
|
+
/**
|
|
3199
|
+
* The root model a frame result reports: `object-detection` when it runs (it
|
|
3200
|
+
* is the detector the tracks come from), else the first enabled video root.
|
|
3201
|
+
* `undefined` when no root runs — never a fabricated id.
|
|
3202
|
+
*/
|
|
3203
|
+
function resolveRootModelId(roots) {
|
|
3204
|
+
const enabled = roots.filter((s) => s.enabled && s.slot !== "audio-classifier");
|
|
3205
|
+
return (enabled.find((s) => s.addonId === PRIMARY_ROOT_STEP) ?? enabled[0])?.modelId;
|
|
3206
|
+
}
|
|
3207
|
+
//#endregion
|
|
3161
3208
|
//#region src/detection-pipeline/engine-provisioner.ts
|
|
3162
3209
|
/** Incremental backoff growing to a ~5 min cap; retries indefinitely at cap. */
|
|
3163
3210
|
var BACKOFF_SCHEDULE_MS = [
|
|
@@ -3428,6 +3475,7 @@ var KNOWN_POSTPROCESSORS = new Set([
|
|
|
3428
3475
|
"rfdetr",
|
|
3429
3476
|
"yolo-seg",
|
|
3430
3477
|
"scrfd",
|
|
3478
|
+
"yunet",
|
|
3431
3479
|
"arcface",
|
|
3432
3480
|
"clip",
|
|
3433
3481
|
"softmax",
|
|
@@ -3529,9 +3577,12 @@ var DropSampler = class {
|
|
|
3529
3577
|
* accumulates and a summary is released once per interval — carrying the
|
|
3530
3578
|
* number suppressed, so nothing about the RATE is lost even though the lines
|
|
3531
3579
|
* are.
|
|
3580
|
+
*
|
|
3581
|
+
* `channel` separates occasions that share a device — a replay's drops are
|
|
3582
|
+
* sampled apart from the live ones.
|
|
3532
3583
|
*/
|
|
3533
|
-
record(deviceId, macroClass) {
|
|
3534
|
-
const key = `${deviceId}:${macroClass}`;
|
|
3584
|
+
record(deviceId, macroClass, channel = "live") {
|
|
3585
|
+
const key = `${channel}:${deviceId}:${macroClass}`;
|
|
3535
3586
|
const at = this.now();
|
|
3536
3587
|
const prev = this.seen.get(key);
|
|
3537
3588
|
if (prev === void 0) {
|
|
@@ -5317,6 +5368,11 @@ var PipelineExecutor = class {
|
|
|
5317
5368
|
async run(tree, rootInput, fullFrameJpegProvider, imageWidth, imageHeight, deviceId, runOpts, nativeCropProvider, cropZoneBbox, rootInputViewProvider) {
|
|
5318
5369
|
const startMs = Date.now();
|
|
5319
5370
|
const verbosity = runOpts?.traceVerbosity ?? "off";
|
|
5371
|
+
const occasion = runOpts?.occasion ?? "live";
|
|
5372
|
+
const logTags = occasion === "replay" ? {
|
|
5373
|
+
deviceId,
|
|
5374
|
+
occasion
|
|
5375
|
+
} : { deviceId };
|
|
5320
5376
|
const traceBuilder = new ExecutionTraceBuilder(verbosity, deviceId, imageWidth, imageHeight, this.opts.engineRuntime);
|
|
5321
5377
|
const firstLevel = [];
|
|
5322
5378
|
const details = [];
|
|
@@ -5332,9 +5388,9 @@ var PipelineExecutor = class {
|
|
|
5332
5388
|
idGen,
|
|
5333
5389
|
details,
|
|
5334
5390
|
onClassifierClassDropped: (drop) => {
|
|
5335
|
-
const sample = this.classFilterDropSampler.record(deviceId, `${drop.stepId}:${drop.droppedClass}
|
|
5391
|
+
const sample = this.classFilterDropSampler.record(deviceId, `${drop.stepId}:${drop.droppedClass}`, occasion);
|
|
5336
5392
|
if (sample.logExample) this.opts.logger?.info("classifier winner excluded by class filter — label dropped", {
|
|
5337
|
-
tags:
|
|
5393
|
+
tags: logTags,
|
|
5338
5394
|
meta: {
|
|
5339
5395
|
step: drop.stepId,
|
|
5340
5396
|
model: drop.modelId,
|
|
@@ -5345,7 +5401,7 @@ var PipelineExecutor = class {
|
|
|
5345
5401
|
}
|
|
5346
5402
|
});
|
|
5347
5403
|
if (sample.summaryCount !== null) this.opts.logger?.info("classifier class filter: still dropping", {
|
|
5348
|
-
tags:
|
|
5404
|
+
tags: logTags,
|
|
5349
5405
|
meta: {
|
|
5350
5406
|
step: drop.stepId,
|
|
5351
5407
|
droppedClass: drop.droppedClass,
|
|
@@ -5378,7 +5434,7 @@ var PipelineExecutor = class {
|
|
|
5378
5434
|
await this.executeChildren(rootStep.children, mutable, rootView?.jpegProvider ?? fullFrameJpegProvider, imageWidth, imageHeight, traceBuilder, stepTimings, ctx, poolAgg, runOpts?.plane, deviceId, nativeCropProvider);
|
|
5379
5435
|
} catch (err) {
|
|
5380
5436
|
this.opts.logger?.warn("Pipeline child execution failed — keeping parent detection", {
|
|
5381
|
-
tags:
|
|
5437
|
+
tags: logTags,
|
|
5382
5438
|
meta: {
|
|
5383
5439
|
rootStepId: rootStep.stepId,
|
|
5384
5440
|
parentClass: mutable.macroClass,
|
|
@@ -5432,9 +5488,9 @@ var PipelineExecutor = class {
|
|
|
5432
5488
|
const fullFrameBar = rootStep.settings?.["fullFrameGuardMaxConfidence"];
|
|
5433
5489
|
const fullFrameHardArea = rootStep.settings?.["fullFrameGuardHardAreaRatio"];
|
|
5434
5490
|
if (isFullFramePhantomDetection(mutable.bbox, imageWidth, imageHeight, mutable.score, typeof fullFrameBar === "number" ? fullFrameBar : void 0, typeof fullFrameHardArea === "number" ? fullFrameHardArea : void 0)) {
|
|
5435
|
-
const sample = this.dropSampler.record(deviceId, String(mutable.macroClass));
|
|
5491
|
+
const sample = this.dropSampler.record(deviceId, String(mutable.macroClass), occasion);
|
|
5436
5492
|
if (sample.summaryCount !== null) this.opts.logger?.info("full-frame phantom guard: still dropping", {
|
|
5437
|
-
tags:
|
|
5493
|
+
tags: logTags,
|
|
5438
5494
|
meta: {
|
|
5439
5495
|
macroClass: mutable.macroClass,
|
|
5440
5496
|
suppressed: sample.summaryCount,
|
|
@@ -5443,7 +5499,7 @@ var PipelineExecutor = class {
|
|
|
5443
5499
|
}
|
|
5444
5500
|
});
|
|
5445
5501
|
if (sample.logExample) this.opts.logger?.info("full-frame phantom guard: detection dropped", {
|
|
5446
|
-
tags:
|
|
5502
|
+
tags: logTags,
|
|
5447
5503
|
meta: {
|
|
5448
5504
|
macroClass: mutable.macroClass,
|
|
5449
5505
|
score: mutable.score,
|
|
@@ -5469,7 +5525,7 @@ var PipelineExecutor = class {
|
|
|
5469
5525
|
continue;
|
|
5470
5526
|
}
|
|
5471
5527
|
if (isFullFrameSurvivor(mutable.bbox, imageWidth, imageHeight, mutable.score, typeof fullFrameBar === "number" ? fullFrameBar : void 0)) this.opts.logger?.info("full-frame box SURVIVED the guard on score", {
|
|
5472
|
-
tags:
|
|
5528
|
+
tags: logTags,
|
|
5473
5529
|
meta: {
|
|
5474
5530
|
macroClass: mutable.macroClass,
|
|
5475
5531
|
score: mutable.score,
|
|
@@ -5483,7 +5539,7 @@ var PipelineExecutor = class {
|
|
|
5483
5539
|
await this.executeChildren(rootStep.children, mutable, rootView?.jpegProvider ?? fullFrameJpegProvider, imageWidth, imageHeight, traceBuilder, stepTimings, ctx, poolAgg, runOpts?.plane, deviceId, nativeCropProvider);
|
|
5484
5540
|
} catch (err) {
|
|
5485
5541
|
this.opts.logger?.warn("Pipeline child execution failed — keeping parent detection", {
|
|
5486
|
-
tags:
|
|
5542
|
+
tags: logTags,
|
|
5487
5543
|
meta: {
|
|
5488
5544
|
rootStepId: rootStep.stepId,
|
|
5489
5545
|
parentClass: mutable.macroClass,
|
|
@@ -6541,25 +6597,6 @@ var PROBE_READY_TIMEOUT_MS = 12e4;
|
|
|
6541
6597
|
* race bound keeps a wedged transport from wedging `setApi`.
|
|
6542
6598
|
*/
|
|
6543
6599
|
var PROBE_DIRECT_RPC_TIMEOUT_MS = 6e4;
|
|
6544
|
-
/**
|
|
6545
|
-
* Build the onnx-cpu floor pick using `pickBestRuntime` with a null hardware
|
|
6546
|
-
* env. Used wherever the old `detectBestEngine()` sync probe fell back — the
|
|
6547
|
-
* result is identical (onnx / cpu) but is now derived through the shared rules
|
|
6548
|
-
* instead of duplicated inline.
|
|
6549
|
-
*/
|
|
6550
|
-
/**
|
|
6551
|
-
* Is `backend` even POSSIBLE on this node's OS/arch (ignoring gpu detail)?
|
|
6552
|
-
* coreml ⇒ darwin only; openvino ⇒ x64 non-darwin only; onnx ⇒ anywhere. Used to
|
|
6553
|
-
* reject a persisted/global engine choice that the node's PLATFORM fundamentally
|
|
6554
|
-
* cannot run (e.g. the cluster's OpenVINO default landing on a Mac) — distinct
|
|
6555
|
-
* from the gpu-dependent support (linux without a probed Intel iGPU still keeps
|
|
6556
|
-
* openvino as a valid platform choice; the device falls back to cpu).
|
|
6557
|
-
*/
|
|
6558
|
-
function backendPossibleOnPlatform(backend) {
|
|
6559
|
-
if (backend === "coreml") return process.platform === "darwin";
|
|
6560
|
-
if (backend === "openvino") return process.arch === "x64" && process.platform !== "darwin";
|
|
6561
|
-
return true;
|
|
6562
|
-
}
|
|
6563
6600
|
var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
6564
6601
|
modelsDir;
|
|
6565
6602
|
eventBus;
|
|
@@ -6699,22 +6736,6 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
6699
6736
|
*/
|
|
6700
6737
|
needsAutoPick = false;
|
|
6701
6738
|
/**
|
|
6702
|
-
* Warm cache for benchmark engine-override runs.
|
|
6703
|
-
*
|
|
6704
|
-
* Each override rebuild costs a full Python pool spin-up (~300-500ms)
|
|
6705
|
-
* plus the per-model load. Benchmark tabs iterate up to 800× against
|
|
6706
|
-
* the same override (e.g. yolov9s / coreml / all) — paying that spin-up
|
|
6707
|
-
* each iteration dwarfs actual inference time.
|
|
6708
|
-
*
|
|
6709
|
-
* When the same override engine is requested within `OVERRIDE_CACHE_TTL_MS`,
|
|
6710
|
-
* we reuse the prior transient factory. Hitting a different override
|
|
6711
|
-
* or exceeding the TTL disposes the cache and spins a new factory.
|
|
6712
|
-
* The prior "main" factory (`priorFactory`) is still restored so
|
|
6713
|
-
* runtime dispatch (camera frames) keeps its own resident engine.
|
|
6714
|
-
*/
|
|
6715
|
-
overrideCache = null;
|
|
6716
|
-
overrideCacheTimer = null;
|
|
6717
|
-
/**
|
|
6718
6739
|
* Idle-TTL for a per-device inference pool (multi-device C3). A device pool
|
|
6719
6740
|
* with no dispatch for this long is disposed and recreated on next use — the
|
|
6720
6741
|
* memory guardrail that keeps `factoriesByDevice` from accumulating warm pools
|
|
@@ -6747,7 +6768,6 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
6747
6768
|
error: err instanceof Error ? err.message : String(err)
|
|
6748
6769
|
} })
|
|
6749
6770
|
});
|
|
6750
|
-
static OVERRIDE_CACHE_TTL_MS = 6e4;
|
|
6751
6771
|
/**
|
|
6752
6772
|
* RSS bound + periodic memory telemetry for the Python inference pools
|
|
6753
6773
|
* (2026-08-18 OOM audit: the pools, not the recorder, were the host's OOM
|
|
@@ -6884,15 +6904,14 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
6884
6904
|
/**
|
|
6885
6905
|
* Lazy-install the pip requirements file matching `engine.backend` into
|
|
6886
6906
|
* the embedded Python before the inference pool spawns. No-op for
|
|
6887
|
-
*
|
|
6888
|
-
*
|
|
6907
|
+
* backends with no requirements file on disk, or before the addon
|
|
6908
|
+
* context has been wired (warm-pool race window).
|
|
6889
6909
|
*
|
|
6890
6910
|
* Idempotent — `installPythonRequirements` short-circuits on a hash
|
|
6891
6911
|
* marker, so back-to-back EngineFactory rebuilds (benchmark overrides,
|
|
6892
6912
|
* tuning respawns) skip the pip subprocess after the first install.
|
|
6893
6913
|
*/
|
|
6894
6914
|
async ensureBackendDeps(engine) {
|
|
6895
|
-
if (engine.runtime !== "python") return;
|
|
6896
6915
|
if (!this.addonCtx) return;
|
|
6897
6916
|
const pythonAddonDir = this.executorOptions.pythonAddonDir;
|
|
6898
6917
|
if (!pythonAddonDir) return;
|
|
@@ -7457,8 +7476,22 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
7457
7476
|
* result with a warning logged, same posture as `recomputeConfigIssues`.
|
|
7458
7477
|
*/
|
|
7459
7478
|
async validatePipeline(input) {
|
|
7460
|
-
|
|
7479
|
+
let format = this.currentEngine.format;
|
|
7461
7480
|
try {
|
|
7481
|
+
if (input.deviceKey !== void 0) try {
|
|
7482
|
+
format = resolveDeviceEngine(input.deviceKey).format;
|
|
7483
|
+
} catch (err) {
|
|
7484
|
+
return {
|
|
7485
|
+
ok: false,
|
|
7486
|
+
issues: [{
|
|
7487
|
+
addonId: "*",
|
|
7488
|
+
kind: "unknown-device",
|
|
7489
|
+
detail: errMsg(err)
|
|
7490
|
+
}],
|
|
7491
|
+
substitutions: [],
|
|
7492
|
+
format
|
|
7493
|
+
};
|
|
7494
|
+
}
|
|
7462
7495
|
const steps = input.steps;
|
|
7463
7496
|
const enabledSteps = this.pruneToEnabledSteps(steps);
|
|
7464
7497
|
const substitutions = collectSubstitutions(enabledSteps, format);
|
|
@@ -7783,7 +7816,7 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
7783
7816
|
const nodeId = this.addonCtx?.kernel?.localNodeId ?? "hub";
|
|
7784
7817
|
const sessionId = input.sessionId ?? `run-${Date.now()}-${Math.random().toString(36).slice(2, 10)}`;
|
|
7785
7818
|
const isRuntime = Boolean(input.frame || input.frameRef);
|
|
7786
|
-
const progressToBus = !isRuntime || input.sessionId !== void 0;
|
|
7819
|
+
const progressToBus = !isRuntime && input.replay !== true || input.sessionId !== void 0;
|
|
7787
7820
|
const emit = (message, extra) => {
|
|
7788
7821
|
onProgress?.(message);
|
|
7789
7822
|
if (!progressToBus) return;
|
|
@@ -7925,7 +7958,6 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
7925
7958
|
const dispatchResolution = this.resolveStepsForDispatch({
|
|
7926
7959
|
steps: input.steps,
|
|
7927
7960
|
deviceKey: input.deviceKey,
|
|
7928
|
-
engineOverride: input.engine,
|
|
7929
7961
|
deviceId: input.deviceId,
|
|
7930
7962
|
plane: input.plane
|
|
7931
7963
|
});
|
|
@@ -7945,322 +7977,150 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
7945
7977
|
}
|
|
7946
7978
|
const enabledSteps = flattenSteps(benchmarkSteps);
|
|
7947
7979
|
emit(`Pipeline: ${enabledSteps.length} step(s) — ${enabledSteps.map((s) => s.addonId).join(" → ")}`);
|
|
7948
|
-
|
|
7949
|
-
const
|
|
7950
|
-
|
|
7951
|
-
|
|
7952
|
-
|
|
7953
|
-
|
|
7954
|
-
|
|
7955
|
-
|
|
7956
|
-
|
|
7957
|
-
|
|
7958
|
-
|
|
7959
|
-
|
|
7960
|
-
const cached = this.overrideCache;
|
|
7961
|
-
if (cached.initPromise) await cached.initPromise;
|
|
7962
|
-
this.log.debug("Benchmark: reusing warm override factory", { meta: { engine: `${runEngine.runtime}/${runEngine.backend}/${runEngine.device ?? "default"}` } });
|
|
7963
|
-
this.currentEngine = runEngine;
|
|
7964
|
-
this.engineFactory = cached.factory;
|
|
7965
|
-
this.executor = cached.executor;
|
|
7966
|
-
} else {
|
|
7967
|
-
if (this.overrideCache) {
|
|
7968
|
-
try {
|
|
7969
|
-
await this.overrideCache.factory.dispose();
|
|
7970
|
-
} catch {}
|
|
7971
|
-
this.overrideCache = null;
|
|
7972
|
-
}
|
|
7973
|
-
this.log.info("Benchmark: engine override", { meta: {
|
|
7974
|
-
from: `${priorEngine.runtime}/${priorEngine.backend}`,
|
|
7975
|
-
to: `${runEngine.runtime}/${runEngine.backend}`
|
|
7976
|
-
} });
|
|
7977
|
-
await this.ensureBackendDeps(runEngine);
|
|
7978
|
-
const newFactory = new EngineFactory({
|
|
7979
|
-
engine: runEngine,
|
|
7980
|
-
modelsDir: this.modelsDir,
|
|
7981
|
-
logger: this.log.child("engine-override"),
|
|
7982
|
-
pythonPath: this.executorOptions.pythonPath ?? "",
|
|
7983
|
-
provisioning: this.executorOptions.provisioning,
|
|
7984
|
-
resolveCustomModel: this.customModelResolver
|
|
7985
|
-
});
|
|
7986
|
-
const initPromise = newFactory.initialize([]);
|
|
7987
|
-
this.overrideCache = {
|
|
7988
|
-
engine: runEngine,
|
|
7989
|
-
factory: newFactory,
|
|
7990
|
-
executor: null,
|
|
7991
|
-
lastUsedMs: Date.now(),
|
|
7992
|
-
initPromise
|
|
7993
|
-
};
|
|
7994
|
-
this.scheduleOverrideCacheEviction();
|
|
7995
|
-
try {
|
|
7996
|
-
await initPromise;
|
|
7997
|
-
this.overrideCache.initPromise = null;
|
|
7998
|
-
} catch (err) {
|
|
7999
|
-
this.overrideCache = null;
|
|
8000
|
-
if (this.overrideCacheTimer) {
|
|
8001
|
-
clearTimeout(this.overrideCacheTimer);
|
|
8002
|
-
this.overrideCacheTimer = null;
|
|
8003
|
-
}
|
|
8004
|
-
throw err;
|
|
8005
|
-
}
|
|
8006
|
-
this.currentEngine = runEngine;
|
|
8007
|
-
this.engineFactory = newFactory;
|
|
8008
|
-
this.executor = null;
|
|
8009
|
-
}
|
|
8010
|
-
try {
|
|
8011
|
-
const deviceLabel = runEngine.device ?? "default";
|
|
8012
|
-
emit(`Engine: ${runEngine.runtime}/${runEngine.backend}/${deviceLabel}${input.engine ? " (override)" : ""}`, {
|
|
8013
|
-
step: "engine",
|
|
8014
|
-
engine: {
|
|
8015
|
-
runtime: runEngine.runtime,
|
|
8016
|
-
backend: runEngine.backend,
|
|
8017
|
-
device: deviceLabel
|
|
8018
|
-
}
|
|
8019
|
-
});
|
|
8020
|
-
const runtimeStr = `${runEngine.runtime}+${runEngine.backend}`;
|
|
8021
|
-
const executor = this.executor ?? new PipelineExecutor({
|
|
8022
|
-
engineRuntime: runtimeStr,
|
|
8023
|
-
logger: this.log
|
|
7980
|
+
const rootModelId = resolveRootModelId(benchmarkSteps);
|
|
7981
|
+
const stampRoot = (r) => rootModelId === void 0 ? r : {
|
|
7982
|
+
...r,
|
|
7983
|
+
rootModelId
|
|
7984
|
+
};
|
|
7985
|
+
if (input.replay === true) {
|
|
7986
|
+
const verdict = checkReplayPin({
|
|
7987
|
+
requested: input.steps,
|
|
7988
|
+
executing: input.plane === "frame" ? collectFramePlaneSteps(benchmarkSteps, isDetailPlaneStep) : enabledSteps,
|
|
7989
|
+
requestedDeviceKey: input.deviceKey,
|
|
7990
|
+
dispatchDeviceKey,
|
|
7991
|
+
format: dispatchDeviceKey ? resolveDeviceEngine(dispatchDeviceKey).format : this.currentEngine.format
|
|
8024
7992
|
});
|
|
8025
|
-
|
|
8026
|
-
|
|
8027
|
-
|
|
8028
|
-
|
|
8029
|
-
|
|
8030
|
-
|
|
8031
|
-
|
|
8032
|
-
|
|
8033
|
-
} });
|
|
8034
|
-
for (const s of needed) emit(`Loading model: ${s.addonName} (${s.modelId})...`, {
|
|
8035
|
-
step: s.addonId,
|
|
8036
|
-
addonId: s.addonId,
|
|
8037
|
-
modelId: s.modelId
|
|
7993
|
+
if (verdict.kind === "refused") {
|
|
7994
|
+
this.log.warn("replay refused — the node cannot run exactly what was pinned", {
|
|
7995
|
+
...input.deviceId !== void 0 ? { tags: { deviceId: input.deviceId } } : {},
|
|
7996
|
+
meta: {
|
|
7997
|
+
reason: verdict.reason,
|
|
7998
|
+
detail: verdict.detail,
|
|
7999
|
+
deviceKey: input.deviceKey
|
|
8000
|
+
}
|
|
8038
8001
|
});
|
|
8039
|
-
|
|
8040
|
-
emit(`All models loaded`);
|
|
8041
|
-
}
|
|
8042
|
-
emit("Running inference...");
|
|
8043
|
-
const tree = buildExecutableTree(benchmarkSteps, (stepId) => dispatchFactory.getEngine(stepId), this.customModelResolver);
|
|
8044
|
-
setupMs = performance.now() - wallT0 - decodeMs;
|
|
8045
|
-
const effectiveDeviceId = input.deviceId ?? 0;
|
|
8046
|
-
const deviceOverrides = effectiveDeviceId > 0 ? await this.deviceOverrides.resolve(effectiveDeviceId) : {};
|
|
8047
|
-
const effectiveTree = Object.keys(deviceOverrides).length > 0 ? applyDeviceOverridesToTree(tree, "object-detection", deviceOverrides) : tree;
|
|
8048
|
-
const nativeCropProvider = this.buildNativeCropProviderFromRef(input.nativeCropRef) ?? this.buildNativeFaceCropProvider(input.frameHandle);
|
|
8049
|
-
let cropZoneBbox;
|
|
8050
|
-
if (isRuntime && effectiveDeviceId > 0 && effectiveTree.roots.some((r) => r.definition.extractMode === "crop-zone")) {
|
|
8051
|
-
await this.ensureDeviceProxy(effectiveDeviceId);
|
|
8052
|
-
const proxy = this.deviceProxies.get(effectiveDeviceId);
|
|
8053
|
-
if (proxy) cropZoneBbox = resolvePackageCropBbox(proxy.state.zones.value?.zones ?? [], proxy.state.zoneRules.value?.package ?? [], imageWidth, imageHeight) ?? void 0;
|
|
8054
|
-
}
|
|
8055
|
-
let frameViewResolver;
|
|
8056
|
-
let rootInputViewProvider;
|
|
8057
|
-
if (runtimeFrameRef) {
|
|
8058
|
-
frameViewResolver = createRootFrameViewResolver(localFrameRegistry, runtimeFrameRef);
|
|
8059
|
-
rootInputViewProvider = async (root, crop) => {
|
|
8060
|
-
const view = await frameViewResolver(root, crop ? {
|
|
8061
|
-
left: crop[0],
|
|
8062
|
-
top: crop[1],
|
|
8063
|
-
width: crop[2] - crop[0],
|
|
8064
|
-
height: crop[3] - crop[1]
|
|
8065
|
-
} : void 0);
|
|
8066
|
-
const data = Buffer.from(view.data.buffer, view.data.byteOffset, view.data.byteLength);
|
|
8067
|
-
return {
|
|
8068
|
-
input: {
|
|
8069
|
-
kind: "jpeg",
|
|
8070
|
-
data
|
|
8071
|
-
},
|
|
8072
|
-
jpegProvider: async () => data,
|
|
8073
|
-
width: view.width,
|
|
8074
|
-
height: view.height,
|
|
8075
|
-
geometry: view.geometry
|
|
8076
|
-
};
|
|
8077
|
-
};
|
|
8078
|
-
}
|
|
8079
|
-
let executionSucceeded = false;
|
|
8080
|
-
let execution;
|
|
8081
|
-
try {
|
|
8082
|
-
execution = await executor.run(effectiveTree, rootInput, jpegProvider, imageWidth, imageHeight, effectiveDeviceId, {
|
|
8083
|
-
traceVerbosity: isRuntime ? this.eventBus ? "summary" : "off" : "full",
|
|
8084
|
-
plane: input.plane
|
|
8085
|
-
}, nativeCropProvider, cropZoneBbox, rootInputViewProvider);
|
|
8086
|
-
executionSucceeded = true;
|
|
8087
|
-
} finally {
|
|
8088
|
-
frameViewResolver?.release(executionSucceeded ? "success" : "error");
|
|
8089
|
-
}
|
|
8090
|
-
const { result, trace } = execution;
|
|
8091
|
-
if (isRuntime) {
|
|
8092
|
-
if (trace && this.eventBus) this.eventBus.emit(createEvent(EventCategory.PipelineTrace, {
|
|
8093
|
-
type: "device",
|
|
8094
|
-
id: trace.deviceId,
|
|
8095
|
-
nodeId: "hub"
|
|
8096
|
-
}, trace));
|
|
8097
|
-
if (effectiveDeviceId > 0) {
|
|
8098
|
-
await this.ensureDeviceProxy(effectiveDeviceId);
|
|
8099
|
-
return this.gateDetectionsByZoneRules(effectiveDeviceId, result);
|
|
8100
|
-
}
|
|
8101
|
-
return result;
|
|
8102
|
-
}
|
|
8103
|
-
for (const t of result.debug?.stepTimings ?? []) emit(`${t.source}${t.modelId ? ` (${t.modelId})` : ""} → ${t.ms}ms`, {
|
|
8104
|
-
step: t.source,
|
|
8105
|
-
addonId: t.source,
|
|
8106
|
-
modelId: t.modelId ?? void 0,
|
|
8107
|
-
ms: t.ms
|
|
8108
|
-
});
|
|
8109
|
-
emit(`Done — ${result.detections.length} detection(s) in ${result.debug?.totalInferenceMs ?? 0}ms`, { ms: result.debug?.totalInferenceMs });
|
|
8110
|
-
const wallMs = performance.now() - wallT0;
|
|
8111
|
-
const inferMs = result.debug?.totalInferenceMs ?? 0;
|
|
8112
|
-
const overheadMs = Math.max(0, wallMs - decodeMs - setupMs - inferMs);
|
|
8113
|
-
return {
|
|
8114
|
-
...result,
|
|
8115
|
-
debug: {
|
|
8116
|
-
...result.debug,
|
|
8117
|
-
decodeMs: Math.round(decodeMs * 100) / 100,
|
|
8118
|
-
setupMs: Math.round(setupMs * 100) / 100,
|
|
8119
|
-
wallMs: Math.round(wallMs * 100) / 100,
|
|
8120
|
-
overheadMs: Math.round(overheadMs * 100) / 100
|
|
8121
|
-
}
|
|
8122
|
-
};
|
|
8123
|
-
} finally {
|
|
8124
|
-
if (differs && this.overrideCache) {
|
|
8125
|
-
this.overrideCache.executor = this.executor;
|
|
8126
|
-
this.overrideCache.lastUsedMs = Date.now();
|
|
8127
|
-
this.scheduleOverrideCacheEviction();
|
|
8128
|
-
}
|
|
8129
|
-
if (differs) {
|
|
8130
|
-
this.currentEngine = priorEngine;
|
|
8131
|
-
this.engineFactory = priorFactory;
|
|
8132
|
-
this.executor = priorExecutor;
|
|
8002
|
+
throw new Error(`${verdict.reason}: ${verdict.detail}`);
|
|
8133
8003
|
}
|
|
8134
8004
|
}
|
|
8135
|
-
|
|
8136
|
-
|
|
8137
|
-
|
|
8138
|
-
|
|
8139
|
-
|
|
8140
|
-
|
|
8141
|
-
|
|
8142
|
-
|
|
8143
|
-
|
|
8144
|
-
|
|
8145
|
-
* `runPipelineBatch` (batched fast path). Without this, the batch
|
|
8146
|
-
* path silently ignored `input.engine` and ran the user's override
|
|
8147
|
-
* against the addon's saved engine — making the override matrix in
|
|
8148
|
-
* the bench UI a no-op for batchSize > 1.
|
|
8149
|
-
*/
|
|
8150
|
-
async applyEngineOverride(override) {
|
|
8151
|
-
const priorEngine = this.currentEngine;
|
|
8152
|
-
const priorFactory = this.engineFactory;
|
|
8153
|
-
const priorExecutor = this.executor;
|
|
8154
|
-
if (!override) return () => {};
|
|
8155
|
-
if (!backendPossibleOnPlatform(override.backend)) {
|
|
8156
|
-
this.log.warn("Engine override impossible on this platform — running on own engine", { meta: {
|
|
8157
|
-
override: `${override.runtime}/${override.backend}/${override.device ?? "default"}`,
|
|
8158
|
-
platform: process.platform,
|
|
8159
|
-
arch: process.arch,
|
|
8160
|
-
engine: `${priorEngine.runtime}/${priorEngine.backend}`
|
|
8161
|
-
} });
|
|
8162
|
-
return () => {};
|
|
8163
|
-
}
|
|
8164
|
-
const runEngine = {
|
|
8165
|
-
runtime: override.runtime,
|
|
8166
|
-
backend: override.backend,
|
|
8167
|
-
format: override.format,
|
|
8168
|
-
...override.device || priorEngine.device ? { device: override.device ?? priorEngine.device } : {}
|
|
8169
|
-
};
|
|
8170
|
-
if (!(priorEngine.runtime !== runEngine.runtime || priorEngine.backend !== runEngine.backend || priorEngine.format !== runEngine.format || (priorEngine.device ?? "") !== (runEngine.device ?? ""))) return () => {};
|
|
8171
|
-
if (this.overrideCache && enginesEqual(this.overrideCache.engine, runEngine)) {
|
|
8172
|
-
const cached = this.overrideCache;
|
|
8173
|
-
if (cached.initPromise) await cached.initPromise;
|
|
8174
|
-
this.log.debug("Benchmark: reusing warm override factory", { meta: { engine: `${runEngine.runtime}/${runEngine.backend}/${runEngine.device ?? "default"}` } });
|
|
8175
|
-
this.currentEngine = runEngine;
|
|
8176
|
-
this.engineFactory = cached.factory;
|
|
8177
|
-
this.executor = cached.executor;
|
|
8178
|
-
} else {
|
|
8179
|
-
if (this.overrideCache) {
|
|
8180
|
-
try {
|
|
8181
|
-
await this.overrideCache.factory.dispose();
|
|
8182
|
-
} catch {}
|
|
8183
|
-
this.overrideCache = null;
|
|
8005
|
+
const appliesCameraGates = isRuntime || input.replay === true;
|
|
8006
|
+
await this.ensureEngineFactory();
|
|
8007
|
+
const runEngine = this.currentEngine;
|
|
8008
|
+
const deviceLabel = runEngine.device ?? "default";
|
|
8009
|
+
emit(`Engine: ${runEngine.runtime}/${runEngine.backend}/${deviceLabel}`, {
|
|
8010
|
+
step: "engine",
|
|
8011
|
+
engine: {
|
|
8012
|
+
runtime: runEngine.runtime,
|
|
8013
|
+
backend: runEngine.backend,
|
|
8014
|
+
device: deviceLabel
|
|
8184
8015
|
}
|
|
8185
|
-
|
|
8186
|
-
|
|
8187
|
-
|
|
8016
|
+
});
|
|
8017
|
+
const runtimeStr = `${runEngine.runtime}+${runEngine.backend}`;
|
|
8018
|
+
const executor = this.executor ?? new PipelineExecutor({
|
|
8019
|
+
engineRuntime: runtimeStr,
|
|
8020
|
+
logger: this.log
|
|
8021
|
+
});
|
|
8022
|
+
this.executor = executor;
|
|
8023
|
+
const dispatchEngine = dispatchDeviceKey ? resolveDeviceEngine(dispatchDeviceKey) : runEngine;
|
|
8024
|
+
const dispatchFactory = dispatchDeviceKey ? await this.resolveDeviceFactory(dispatchDeviceKey) : this.engineFactory;
|
|
8025
|
+
const needed = enabledSteps.filter((s) => dispatchFactory.needsPoolUpdate(s));
|
|
8026
|
+
if (needed.length > 0) {
|
|
8027
|
+
this.log.info("Benchmark: models to load", { meta: {
|
|
8028
|
+
count: needed.length,
|
|
8029
|
+
models: needed.map((s) => `${s.addonId}/${s.modelId}`)
|
|
8188
8030
|
} });
|
|
8189
|
-
|
|
8190
|
-
|
|
8191
|
-
|
|
8192
|
-
|
|
8193
|
-
logger: this.log.child("engine-override"),
|
|
8194
|
-
pythonPath: this.executorOptions.pythonPath ?? "",
|
|
8195
|
-
provisioning: this.executorOptions.provisioning,
|
|
8196
|
-
resolveCustomModel: this.customModelResolver
|
|
8031
|
+
for (const s of needed) emit(`Loading model: ${s.addonName} (${s.modelId})...`, {
|
|
8032
|
+
step: s.addonId,
|
|
8033
|
+
addonId: s.addonId,
|
|
8034
|
+
modelId: s.modelId
|
|
8197
8035
|
});
|
|
8198
|
-
|
|
8199
|
-
|
|
8200
|
-
|
|
8201
|
-
|
|
8202
|
-
|
|
8203
|
-
|
|
8204
|
-
|
|
8036
|
+
await this.ensureModelsForSteps(benchmarkSteps, dispatchFactory, dispatchEngine.format);
|
|
8037
|
+
emit(`All models loaded`);
|
|
8038
|
+
}
|
|
8039
|
+
emit("Running inference...");
|
|
8040
|
+
const tree = buildExecutableTree(benchmarkSteps, (stepId) => dispatchFactory.getEngine(stepId), this.customModelResolver);
|
|
8041
|
+
setupMs = performance.now() - wallT0 - decodeMs;
|
|
8042
|
+
const effectiveDeviceId = input.deviceId ?? 0;
|
|
8043
|
+
const deviceOverrides = effectiveDeviceId > 0 ? await this.deviceOverrides.resolve(effectiveDeviceId) : {};
|
|
8044
|
+
const effectiveTree = Object.keys(deviceOverrides).length > 0 ? applyDeviceOverridesToTree(tree, "object-detection", deviceOverrides) : tree;
|
|
8045
|
+
const nativeCropProvider = this.buildNativeCropProviderFromRef(input.nativeCropRef) ?? this.buildNativeFaceCropProvider(input.frameHandle);
|
|
8046
|
+
let cropZoneBbox;
|
|
8047
|
+
if (appliesCameraGates && effectiveDeviceId > 0 && effectiveTree.roots.some((r) => r.definition.extractMode === "crop-zone")) {
|
|
8048
|
+
await this.ensureDeviceProxy(effectiveDeviceId);
|
|
8049
|
+
const proxy = this.deviceProxies.get(effectiveDeviceId);
|
|
8050
|
+
if (proxy) cropZoneBbox = resolvePackageCropBbox(proxy.state.zones.value?.zones ?? [], proxy.state.zoneRules.value?.package ?? [], imageWidth, imageHeight) ?? void 0;
|
|
8051
|
+
}
|
|
8052
|
+
let frameViewResolver;
|
|
8053
|
+
let rootInputViewProvider;
|
|
8054
|
+
if (runtimeFrameRef) {
|
|
8055
|
+
frameViewResolver = createRootFrameViewResolver(localFrameRegistry, runtimeFrameRef);
|
|
8056
|
+
rootInputViewProvider = async (root, crop) => {
|
|
8057
|
+
const view = await frameViewResolver(root, crop ? {
|
|
8058
|
+
left: crop[0],
|
|
8059
|
+
top: crop[1],
|
|
8060
|
+
width: crop[2] - crop[0],
|
|
8061
|
+
height: crop[3] - crop[1]
|
|
8062
|
+
} : void 0);
|
|
8063
|
+
const data = Buffer.from(view.data.buffer, view.data.byteOffset, view.data.byteLength);
|
|
8064
|
+
return {
|
|
8065
|
+
input: {
|
|
8066
|
+
kind: "jpeg",
|
|
8067
|
+
data
|
|
8068
|
+
},
|
|
8069
|
+
jpegProvider: async () => data,
|
|
8070
|
+
width: view.width,
|
|
8071
|
+
height: view.height,
|
|
8072
|
+
geometry: view.geometry
|
|
8073
|
+
};
|
|
8205
8074
|
};
|
|
8206
|
-
|
|
8207
|
-
|
|
8208
|
-
|
|
8209
|
-
|
|
8210
|
-
|
|
8211
|
-
this.
|
|
8212
|
-
|
|
8213
|
-
|
|
8214
|
-
|
|
8215
|
-
|
|
8216
|
-
|
|
8075
|
+
}
|
|
8076
|
+
let executionSucceeded = false;
|
|
8077
|
+
let execution;
|
|
8078
|
+
try {
|
|
8079
|
+
execution = await executor.run(effectiveTree, rootInput, jpegProvider, imageWidth, imageHeight, effectiveDeviceId, {
|
|
8080
|
+
traceVerbosity: isRuntime ? this.eventBus ? "summary" : "off" : "full",
|
|
8081
|
+
plane: input.plane,
|
|
8082
|
+
occasion: input.replay === true ? "replay" : "live"
|
|
8083
|
+
}, nativeCropProvider, cropZoneBbox, rootInputViewProvider);
|
|
8084
|
+
executionSucceeded = true;
|
|
8085
|
+
} finally {
|
|
8086
|
+
frameViewResolver?.release(executionSucceeded ? "success" : "error");
|
|
8087
|
+
}
|
|
8088
|
+
const { result, trace } = execution;
|
|
8089
|
+
if (isRuntime) {
|
|
8090
|
+
if (trace && this.eventBus) this.eventBus.emit(createEvent(EventCategory.PipelineTrace, {
|
|
8091
|
+
type: "device",
|
|
8092
|
+
id: trace.deviceId,
|
|
8093
|
+
nodeId: "hub"
|
|
8094
|
+
}, trace));
|
|
8095
|
+
if (effectiveDeviceId > 0) {
|
|
8096
|
+
await this.ensureDeviceProxy(effectiveDeviceId);
|
|
8097
|
+
return stampRoot(this.gateDetectionsByZoneRules(effectiveDeviceId, result));
|
|
8217
8098
|
}
|
|
8218
|
-
|
|
8219
|
-
this.engineFactory = newFactory;
|
|
8220
|
-
this.executor = null;
|
|
8099
|
+
return stampRoot(result);
|
|
8221
8100
|
}
|
|
8222
|
-
|
|
8223
|
-
|
|
8224
|
-
|
|
8225
|
-
|
|
8226
|
-
|
|
8101
|
+
for (const t of result.debug?.stepTimings ?? []) emit(`${t.source}${t.modelId ? ` (${t.modelId})` : ""} → ${t.ms}ms`, {
|
|
8102
|
+
step: t.source,
|
|
8103
|
+
addonId: t.source,
|
|
8104
|
+
modelId: t.modelId ?? void 0,
|
|
8105
|
+
ms: t.ms
|
|
8106
|
+
});
|
|
8107
|
+
emit(`Done — ${result.detections.length} detection(s) in ${result.debug?.totalInferenceMs ?? 0}ms`, { ms: result.debug?.totalInferenceMs });
|
|
8108
|
+
const wallMs = performance.now() - wallT0;
|
|
8109
|
+
const inferMs = result.debug?.totalInferenceMs ?? 0;
|
|
8110
|
+
const overheadMs = Math.max(0, wallMs - decodeMs - setupMs - inferMs);
|
|
8111
|
+
const gated = input.replay === true && effectiveDeviceId > 0 ? await this.ensureDeviceProxy(effectiveDeviceId).then(() => this.gateDetectionsByZoneRules(effectiveDeviceId, result)) : result;
|
|
8112
|
+
return {
|
|
8113
|
+
...stampRoot(gated),
|
|
8114
|
+
debug: {
|
|
8115
|
+
...gated.debug,
|
|
8116
|
+
decodeMs: Math.round(decodeMs * 100) / 100,
|
|
8117
|
+
setupMs: Math.round(setupMs * 100) / 100,
|
|
8118
|
+
wallMs: Math.round(wallMs * 100) / 100,
|
|
8119
|
+
overheadMs: Math.round(overheadMs * 100) / 100
|
|
8227
8120
|
}
|
|
8228
|
-
this.currentEngine = priorEngine;
|
|
8229
|
-
this.engineFactory = priorFactory;
|
|
8230
|
-
this.executor = priorExecutor;
|
|
8231
8121
|
};
|
|
8232
8122
|
}
|
|
8233
8123
|
/**
|
|
8234
|
-
* Schedule eviction of the override cache after `OVERRIDE_CACHE_TTL_MS`
|
|
8235
|
-
* of idleness. Rescheduling resets the timer so an active benchmark
|
|
8236
|
-
* keeps its warm factory alive until iterations stop arriving.
|
|
8237
|
-
*/
|
|
8238
|
-
scheduleOverrideCacheEviction() {
|
|
8239
|
-
if (this.overrideCacheTimer) clearTimeout(this.overrideCacheTimer);
|
|
8240
|
-
this.overrideCacheTimer = setTimeout(() => {
|
|
8241
|
-
this.evictOverrideCache("idle TTL");
|
|
8242
|
-
}, DetectionPipelineProvider.OVERRIDE_CACHE_TTL_MS);
|
|
8243
|
-
}
|
|
8244
|
-
async evictOverrideCache(reason) {
|
|
8245
|
-
if (!this.overrideCache) return;
|
|
8246
|
-
const entry = this.overrideCache;
|
|
8247
|
-
this.overrideCache = null;
|
|
8248
|
-
if (this.overrideCacheTimer) {
|
|
8249
|
-
clearTimeout(this.overrideCacheTimer);
|
|
8250
|
-
this.overrideCacheTimer = null;
|
|
8251
|
-
}
|
|
8252
|
-
try {
|
|
8253
|
-
await entry.factory.dispose();
|
|
8254
|
-
this.log.info("Override cache evicted", { meta: {
|
|
8255
|
-
reason,
|
|
8256
|
-
engine: `${entry.engine.runtime}/${entry.engine.backend}/${entry.engine.device ?? "default"}`,
|
|
8257
|
-
ageMs: Date.now() - entry.lastUsedMs
|
|
8258
|
-
} });
|
|
8259
|
-
} catch (err) {
|
|
8260
|
-
this.log.warn("Override cache dispose failed", { meta: { error: err instanceof Error ? err.message : String(err) } });
|
|
8261
|
-
}
|
|
8262
|
-
}
|
|
8263
|
-
/**
|
|
8264
8124
|
* Convert PipelineStepInput[] → PipelineDefaultStep[] by enriching from
|
|
8265
8125
|
* StepDefinitions. This is the LIVE per-camera dispatch path — it runs
|
|
8266
8126
|
* once per decoded frame (up to detectionFps × N cameras/sec) — so it
|
|
@@ -8276,13 +8136,8 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
8276
8136
|
* `resolveStepModels` (see its doc comment); a second writer here would
|
|
8277
8137
|
* corrupt it.
|
|
8278
8138
|
*
|
|
8279
|
-
* `format` defaults to `this.currentEngine.format` (the
|
|
8280
|
-
*
|
|
8281
|
-
* per-run engine override (`input.engine`) MUST pass the override's format
|
|
8282
|
-
* explicitly — `this.currentEngine` only gets swapped to the override
|
|
8283
|
-
* LATER in `runPipeline`/`runPipelineBatch`, so relying on the default here
|
|
8284
|
-
* would resolve models against the node's persisted format instead of the
|
|
8285
|
-
* format actually being benchmarked.
|
|
8139
|
+
* `format` defaults to `this.currentEngine.format` (the node-default
|
|
8140
|
+
* pool); a `deviceKey` dispatch passes that device's format explicitly.
|
|
8286
8141
|
*/
|
|
8287
8142
|
inputStepsToPipelineSteps(steps, format = this.currentEngine.format, engine = {
|
|
8288
8143
|
backend: this.currentEngine.backend,
|
|
@@ -8364,8 +8219,7 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
8364
8219
|
* devices — so the whole-tree gate stays.
|
|
8365
8220
|
*
|
|
8366
8221
|
* Fallback is strictly NODE-LOCAL and ordered: the node's own selected
|
|
8367
|
-
* engine
|
|
8368
|
-
* fallback — frames never migrate to another node from here (that is the
|
|
8222
|
+
* engine is the one and only fallback — frames never migrate to another node from here (that is the
|
|
8369
8223
|
* orchestrator's tier). The fallback is logged ONCE per distinct
|
|
8370
8224
|
* (deviceKey, steps, format) signature, with `tags: { deviceId }` when the
|
|
8371
8225
|
* dispatch is camera-scoped.
|
|
@@ -8376,12 +8230,9 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
8376
8230
|
* configuration must stay loud, not vanish into a silent fallback chain.
|
|
8377
8231
|
*/
|
|
8378
8232
|
resolveStepsForDispatch(args) {
|
|
8379
|
-
const { deviceKey,
|
|
8380
|
-
const nodeFormat =
|
|
8381
|
-
const nodeEngine =
|
|
8382
|
-
backend: engineOverride.backend,
|
|
8383
|
-
device: engineOverride.device ?? null
|
|
8384
|
-
} : {
|
|
8233
|
+
const { deviceKey, deviceId } = args;
|
|
8234
|
+
const nodeFormat = this.currentEngine.format;
|
|
8235
|
+
const nodeEngine = {
|
|
8385
8236
|
backend: this.currentEngine.backend,
|
|
8386
8237
|
device: this.currentEngine.device ?? null
|
|
8387
8238
|
};
|
|
@@ -8626,12 +8477,11 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
8626
8477
|
const dispatchResolution = this.resolveStepsForDispatch({
|
|
8627
8478
|
steps: input.steps,
|
|
8628
8479
|
deviceKey: input.deviceKey,
|
|
8629
|
-
engineOverride: input.engine,
|
|
8630
8480
|
deviceId: input.deviceId,
|
|
8631
8481
|
plane: void 0
|
|
8632
8482
|
});
|
|
8633
8483
|
const dispatchDeviceKey = dispatchResolution.deviceKey;
|
|
8634
|
-
const resolveFormat = dispatchDeviceKey ? resolveDeviceEngine(dispatchDeviceKey).format :
|
|
8484
|
+
const resolveFormat = dispatchDeviceKey ? resolveDeviceEngine(dispatchDeviceKey).format : this.currentEngine.format;
|
|
8635
8485
|
const benchmarkSteps = dispatchResolution.steps;
|
|
8636
8486
|
const enabledSteps = flattenEnabledVideoSteps(benchmarkSteps);
|
|
8637
8487
|
if (enabledSteps.length === 0) throw new Error("runPipelineBatch: no enabled steps");
|
|
@@ -8652,53 +8502,48 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
8652
8502
|
const uniformDims = input.frames.every((f) => f.width === firstFrame.width && f.height === firstFrame.height);
|
|
8653
8503
|
const singleRoot = enabledSteps.length === 1;
|
|
8654
8504
|
await this.ensureEngineFactory();
|
|
8655
|
-
const
|
|
8656
|
-
|
|
8657
|
-
|
|
8658
|
-
|
|
8659
|
-
|
|
8660
|
-
|
|
8661
|
-
|
|
8662
|
-
|
|
8663
|
-
|
|
8664
|
-
|
|
8665
|
-
|
|
8666
|
-
|
|
8667
|
-
|
|
8668
|
-
|
|
8669
|
-
|
|
8670
|
-
|
|
8671
|
-
|
|
8672
|
-
|
|
8673
|
-
|
|
8674
|
-
|
|
8675
|
-
|
|
8676
|
-
|
|
8677
|
-
|
|
8678
|
-
|
|
8679
|
-
|
|
8680
|
-
|
|
8681
|
-
|
|
8682
|
-
|
|
8683
|
-
|
|
8684
|
-
|
|
8685
|
-
|
|
8686
|
-
|
|
8687
|
-
|
|
8688
|
-
|
|
8689
|
-
|
|
8690
|
-
|
|
8691
|
-
|
|
8692
|
-
|
|
8693
|
-
|
|
8694
|
-
|
|
8695
|
-
|
|
8696
|
-
|
|
8697
|
-
const perFrameMs = totalMs / rawResults.length;
|
|
8698
|
-
return { results: rawResults.map((raw, i) => assembleBatchedFrameResult(raw, input.frames[i], rootStep.addonId, input.deviceId ?? 0, perFrameMs, perFrameMs)) };
|
|
8699
|
-
} finally {
|
|
8700
|
-
restoreEngine();
|
|
8701
|
-
}
|
|
8505
|
+
const factory = dispatchDeviceKey ? await this.resolveDeviceFactory(dispatchDeviceKey) : this.engineFactory;
|
|
8506
|
+
if (!factory) throw new Error("runPipelineBatch: factory not initialised");
|
|
8507
|
+
if (enabledSteps.filter((s) => factory.needsPoolUpdate(s)).length > 0) await this.ensureModelsForSteps(benchmarkSteps, factory, resolveFormat);
|
|
8508
|
+
const canFastPath = singleRoot && allRaw && uniformDims && factory.supportsBatch();
|
|
8509
|
+
this.log.info("runPipelineBatch path decision", { meta: {
|
|
8510
|
+
phase: "batch",
|
|
8511
|
+
canFastPath,
|
|
8512
|
+
singleRoot,
|
|
8513
|
+
allRaw,
|
|
8514
|
+
uniformDims,
|
|
8515
|
+
supportsBatch: factory.supportsBatch()
|
|
8516
|
+
} });
|
|
8517
|
+
if (!canFastPath) return { results: await Promise.all(input.frames.map((frame) => this.runPipeline({
|
|
8518
|
+
steps: input.steps,
|
|
8519
|
+
frame,
|
|
8520
|
+
deviceId: input.deviceId,
|
|
8521
|
+
sessionId: input.sessionId,
|
|
8522
|
+
deviceKey: dispatchDeviceKey
|
|
8523
|
+
}))) };
|
|
8524
|
+
const items = input.frames.map((frame) => ({
|
|
8525
|
+
raw: Buffer.from(frame.data),
|
|
8526
|
+
width: frame.width,
|
|
8527
|
+
height: frame.height,
|
|
8528
|
+
format: frame.format
|
|
8529
|
+
}));
|
|
8530
|
+
this.log.info("runPipelineBatch fast path dispatch", { meta: {
|
|
8531
|
+
phase: "batch",
|
|
8532
|
+
stepId: rootStep.addonId,
|
|
8533
|
+
modelId: rootStep.modelId,
|
|
8534
|
+
items: items.length,
|
|
8535
|
+
totalRawBytes: items.reduce((s, it) => s + it.raw.length, 0)
|
|
8536
|
+
} });
|
|
8537
|
+
const start = performance.now();
|
|
8538
|
+
const rawResults = await factory.batchInferRaw(rootStep.addonId, items, rootStep.modelId, input.frameId ?? 0);
|
|
8539
|
+
const totalMs = performance.now() - start;
|
|
8540
|
+
this.log.info("runPipelineBatch fast path returned", { meta: {
|
|
8541
|
+
phase: "batch",
|
|
8542
|
+
resultCount: rawResults.length,
|
|
8543
|
+
ms: Math.round(totalMs)
|
|
8544
|
+
} });
|
|
8545
|
+
const perFrameMs = totalMs / rawResults.length;
|
|
8546
|
+
return { results: rawResults.map((raw, i) => assembleBatchedFrameResult(raw, input.frames[i], rootStep.addonId, input.deviceId ?? 0, perFrameMs, perFrameMs)) };
|
|
8702
8547
|
}
|
|
8703
8548
|
async listReferenceImages() {
|
|
8704
8549
|
const dir = this.resolveRefImagesDir();
|
|
@@ -8803,7 +8648,6 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
8803
8648
|
this.poolMemoryGuard = null;
|
|
8804
8649
|
this.inferenceTimeoutGuard?.stop();
|
|
8805
8650
|
this.inferenceTimeoutGuard = null;
|
|
8806
|
-
await this.evictOverrideCache("shutdown");
|
|
8807
8651
|
if (this.engineFactory) {
|
|
8808
8652
|
await this.engineFactory.dispose();
|
|
8809
8653
|
this.engineFactory = null;
|
|
@@ -8885,13 +8729,22 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
8885
8729
|
* own runtime's row, so this is not "the node's tuning" — the per-pool truth
|
|
8886
8730
|
* is on each pool's `Python inference pool` log line and in `listLoadedEngines`.
|
|
8887
8731
|
*/
|
|
8888
|
-
|
|
8889
|
-
|
|
8732
|
+
/**
|
|
8733
|
+
* One pool's provisioning — the device pool named by `deviceKey`, else the
|
|
8734
|
+
* node default. Resolved with the SAME override `resolveDeviceFactory` and
|
|
8735
|
+
* `ensureEngineFactory` build the pool with, so the answer describes the
|
|
8736
|
+
* pool that ran the work (D646).
|
|
8737
|
+
*/
|
|
8738
|
+
async getEffectiveTuning(input = {}) {
|
|
8739
|
+
const p = resolvePoolProvisioning(input.deviceKey !== void 0 ? resolveDeviceEngine(input.deviceKey) : this.currentEngine, this.executorOptions.provisioning);
|
|
8890
8740
|
return {
|
|
8741
|
+
deviceKey: input.deviceKey ?? null,
|
|
8742
|
+
runtime: p.runtime,
|
|
8891
8743
|
batchMode: p.batchMode,
|
|
8892
8744
|
windowMs: p.windowMs,
|
|
8893
8745
|
maxBatchSize: p.maxBatchSize,
|
|
8894
|
-
concurrency: p.concurrency
|
|
8746
|
+
concurrency: p.concurrency,
|
|
8747
|
+
numWorkers: p.numWorkers
|
|
8895
8748
|
};
|
|
8896
8749
|
}
|
|
8897
8750
|
/**
|
|
@@ -9279,9 +9132,7 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
9279
9132
|
}
|
|
9280
9133
|
async listLoadedEngines() {
|
|
9281
9134
|
const out = [];
|
|
9282
|
-
|
|
9283
|
-
const runtimeFactoryIsOverride = this.engineFactory !== null && this.engineFactory === overrideFactory;
|
|
9284
|
-
if (this.engineFactory && !runtimeFactoryIsOverride) {
|
|
9135
|
+
if (this.engineFactory) {
|
|
9285
9136
|
const eng = this.currentEngine;
|
|
9286
9137
|
const engineKey = `${eng.runtime}/${eng.backend}/${eng.device ?? "default"}`;
|
|
9287
9138
|
const loaded = this.engineFactory.listLoaded();
|
|
@@ -9296,22 +9147,6 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
9296
9147
|
idleTtlMs: null
|
|
9297
9148
|
});
|
|
9298
9149
|
}
|
|
9299
|
-
if (this.overrideCache) {
|
|
9300
|
-
const eng = this.overrideCache.engine;
|
|
9301
|
-
const engineKey = `${eng.runtime}/${eng.backend}/${eng.device ?? "default"} (warm)`;
|
|
9302
|
-
const loaded = this.overrideCache.factory.listLoaded();
|
|
9303
|
-
const idleMs = Math.max(0, Date.now() - this.overrideCache.lastUsedMs);
|
|
9304
|
-
if (idleMs <= DetectionPipelineProvider.OVERRIDE_CACHE_TTL_MS) out.push({
|
|
9305
|
-
engineKey,
|
|
9306
|
-
engine: eng,
|
|
9307
|
-
modelsLoaded: loaded.map((l) => `${l.stepId}/${l.modelId}`),
|
|
9308
|
-
inUseByCameras: [],
|
|
9309
|
-
kind: "warm-override",
|
|
9310
|
-
poolPid: this.overrideCache.factory.getPoolPid(),
|
|
9311
|
-
idleMs,
|
|
9312
|
-
idleTtlMs: DetectionPipelineProvider.OVERRIDE_CACHE_TTL_MS
|
|
9313
|
-
});
|
|
9314
|
-
}
|
|
9315
9150
|
for (const [deviceKey, factory] of this.factoriesByDevice) {
|
|
9316
9151
|
const eng = resolveDeviceEngine(deviceKey);
|
|
9317
9152
|
const loaded = factory.listLoaded();
|
|
@@ -9340,14 +9175,6 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
9340
9175
|
return { success: true };
|
|
9341
9176
|
}
|
|
9342
9177
|
async killEngine(input) {
|
|
9343
|
-
if (this.overrideCache && enginesEqual(this.overrideCache.engine, input.engine)) {
|
|
9344
|
-
await this.evictOverrideCache("killEngine");
|
|
9345
|
-
this.log.info("Override engine killed", { meta: {
|
|
9346
|
-
...input.engine,
|
|
9347
|
-
force: input.force ?? false
|
|
9348
|
-
} });
|
|
9349
|
-
return { success: true };
|
|
9350
|
-
}
|
|
9351
9178
|
if (!this.engineFactory) return {
|
|
9352
9179
|
success: false,
|
|
9353
9180
|
reason: "not loaded"
|
|
@@ -9865,7 +9692,7 @@ var DetectionPipelineAddon = class extends BaseAddon {
|
|
|
9865
9692
|
/**
|
|
9866
9693
|
* Embedded Python path resolved once at boot via
|
|
9867
9694
|
* `ctx.deps.ensurePython()`. Empty string means the download failed
|
|
9868
|
-
*
|
|
9695
|
+
* — the provider's
|
|
9869
9696
|
* `ensureBackendDeps` and EngineFactory's `initPythonPool` raise a
|
|
9870
9697
|
* clear error in that case.
|
|
9871
9698
|
*/
|