@camstack/addon-pipeline 1.2.293 → 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-DCQlW9fx.mjs → default-detection-model-CAUBVbgK.mjs} +53 -19
- package/dist/{default-detection-model-DTKHLLcC.js → default-detection-model-wNEISVA9.js} +58 -18
- package/dist/detection-pipeline/index.js +286 -458
- package/dist/detection-pipeline/index.mjs +286 -458
- package/dist/{dist-Ce8s7XqV.js → dist-BsVcf5wO.js} +1321 -833
- package/dist/{dist-C0Fr4hM5.mjs → dist-CuxSNLKW.mjs} +1316 -834
- package/dist/motion-wasm/index.js +1 -1
- package/dist/motion-wasm/index.mjs +1 -1
- package/dist/{node-Xm7amG2e.mjs → node-Cqb1QiXk.mjs} +1 -1
- package/dist/{node-DpOrymbW.js → node-UFk6I2f6.js} +1 -1
- package/dist/pipeline-runner/index.js +266 -72
- package/dist/pipeline-runner/index.mjs +266 -72
- package/dist/{process-memory-CBRjuhyg.js → process-memory-CJV29sPf.js} +1 -1
- package/dist/{process-memory-BN5qYGsD.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-BhMlS_Ow.js → segment-demux-js-DGwmf5hu.js} +1 -1
- package/dist/{segment-demux-js-Bhwpj5Wl.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-DKd7oYba.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-CCMIHjff.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,
|
|
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) {
|
|
@@ -5055,7 +5106,8 @@ function buildFrameResult(input) {
|
|
|
5055
5106
|
...embeddingModelId !== void 0 ? { embeddingModelId } : {}
|
|
5056
5107
|
} : {},
|
|
5057
5108
|
...m.faceAlignedCrop !== void 0 ? { faceAlignedCrop: m.faceAlignedCrop } : {},
|
|
5058
|
-
...m.nativeFaceShortSidePx !== void 0 ? { nativeFaceShortSidePx: m.nativeFaceShortSidePx } : {}
|
|
5109
|
+
...m.nativeFaceShortSidePx !== void 0 ? { nativeFaceShortSidePx: m.nativeFaceShortSidePx } : {},
|
|
5110
|
+
...m.landmarks !== void 0 ? { landmarks: m.landmarks } : {}
|
|
5059
5111
|
};
|
|
5060
5112
|
if (input.debug) {
|
|
5061
5113
|
const cleanDebug = pruneUndefined({
|
|
@@ -5316,6 +5368,11 @@ var PipelineExecutor = class {
|
|
|
5316
5368
|
async run(tree, rootInput, fullFrameJpegProvider, imageWidth, imageHeight, deviceId, runOpts, nativeCropProvider, cropZoneBbox, rootInputViewProvider) {
|
|
5317
5369
|
const startMs = Date.now();
|
|
5318
5370
|
const verbosity = runOpts?.traceVerbosity ?? "off";
|
|
5371
|
+
const occasion = runOpts?.occasion ?? "live";
|
|
5372
|
+
const logTags = occasion === "replay" ? {
|
|
5373
|
+
deviceId,
|
|
5374
|
+
occasion
|
|
5375
|
+
} : { deviceId };
|
|
5319
5376
|
const traceBuilder = new ExecutionTraceBuilder(verbosity, deviceId, imageWidth, imageHeight, this.opts.engineRuntime);
|
|
5320
5377
|
const firstLevel = [];
|
|
5321
5378
|
const details = [];
|
|
@@ -5331,9 +5388,9 @@ var PipelineExecutor = class {
|
|
|
5331
5388
|
idGen,
|
|
5332
5389
|
details,
|
|
5333
5390
|
onClassifierClassDropped: (drop) => {
|
|
5334
|
-
const sample = this.classFilterDropSampler.record(deviceId, `${drop.stepId}:${drop.droppedClass}
|
|
5391
|
+
const sample = this.classFilterDropSampler.record(deviceId, `${drop.stepId}:${drop.droppedClass}`, occasion);
|
|
5335
5392
|
if (sample.logExample) this.opts.logger?.info("classifier winner excluded by class filter — label dropped", {
|
|
5336
|
-
tags:
|
|
5393
|
+
tags: logTags,
|
|
5337
5394
|
meta: {
|
|
5338
5395
|
step: drop.stepId,
|
|
5339
5396
|
model: drop.modelId,
|
|
@@ -5344,7 +5401,7 @@ var PipelineExecutor = class {
|
|
|
5344
5401
|
}
|
|
5345
5402
|
});
|
|
5346
5403
|
if (sample.summaryCount !== null) this.opts.logger?.info("classifier class filter: still dropping", {
|
|
5347
|
-
tags:
|
|
5404
|
+
tags: logTags,
|
|
5348
5405
|
meta: {
|
|
5349
5406
|
step: drop.stepId,
|
|
5350
5407
|
droppedClass: drop.droppedClass,
|
|
@@ -5377,7 +5434,7 @@ var PipelineExecutor = class {
|
|
|
5377
5434
|
await this.executeChildren(rootStep.children, mutable, rootView?.jpegProvider ?? fullFrameJpegProvider, imageWidth, imageHeight, traceBuilder, stepTimings, ctx, poolAgg, runOpts?.plane, deviceId, nativeCropProvider);
|
|
5378
5435
|
} catch (err) {
|
|
5379
5436
|
this.opts.logger?.warn("Pipeline child execution failed — keeping parent detection", {
|
|
5380
|
-
tags:
|
|
5437
|
+
tags: logTags,
|
|
5381
5438
|
meta: {
|
|
5382
5439
|
rootStepId: rootStep.stepId,
|
|
5383
5440
|
parentClass: mutable.macroClass,
|
|
@@ -5431,9 +5488,9 @@ var PipelineExecutor = class {
|
|
|
5431
5488
|
const fullFrameBar = rootStep.settings?.["fullFrameGuardMaxConfidence"];
|
|
5432
5489
|
const fullFrameHardArea = rootStep.settings?.["fullFrameGuardHardAreaRatio"];
|
|
5433
5490
|
if (isFullFramePhantomDetection(mutable.bbox, imageWidth, imageHeight, mutable.score, typeof fullFrameBar === "number" ? fullFrameBar : void 0, typeof fullFrameHardArea === "number" ? fullFrameHardArea : void 0)) {
|
|
5434
|
-
const sample = this.dropSampler.record(deviceId, String(mutable.macroClass));
|
|
5491
|
+
const sample = this.dropSampler.record(deviceId, String(mutable.macroClass), occasion);
|
|
5435
5492
|
if (sample.summaryCount !== null) this.opts.logger?.info("full-frame phantom guard: still dropping", {
|
|
5436
|
-
tags:
|
|
5493
|
+
tags: logTags,
|
|
5437
5494
|
meta: {
|
|
5438
5495
|
macroClass: mutable.macroClass,
|
|
5439
5496
|
suppressed: sample.summaryCount,
|
|
@@ -5442,7 +5499,7 @@ var PipelineExecutor = class {
|
|
|
5442
5499
|
}
|
|
5443
5500
|
});
|
|
5444
5501
|
if (sample.logExample) this.opts.logger?.info("full-frame phantom guard: detection dropped", {
|
|
5445
|
-
tags:
|
|
5502
|
+
tags: logTags,
|
|
5446
5503
|
meta: {
|
|
5447
5504
|
macroClass: mutable.macroClass,
|
|
5448
5505
|
score: mutable.score,
|
|
@@ -5468,7 +5525,7 @@ var PipelineExecutor = class {
|
|
|
5468
5525
|
continue;
|
|
5469
5526
|
}
|
|
5470
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", {
|
|
5471
|
-
tags:
|
|
5528
|
+
tags: logTags,
|
|
5472
5529
|
meta: {
|
|
5473
5530
|
macroClass: mutable.macroClass,
|
|
5474
5531
|
score: mutable.score,
|
|
@@ -5482,7 +5539,7 @@ var PipelineExecutor = class {
|
|
|
5482
5539
|
await this.executeChildren(rootStep.children, mutable, rootView?.jpegProvider ?? fullFrameJpegProvider, imageWidth, imageHeight, traceBuilder, stepTimings, ctx, poolAgg, runOpts?.plane, deviceId, nativeCropProvider);
|
|
5483
5540
|
} catch (err) {
|
|
5484
5541
|
this.opts.logger?.warn("Pipeline child execution failed — keeping parent detection", {
|
|
5485
|
-
tags:
|
|
5542
|
+
tags: logTags,
|
|
5486
5543
|
meta: {
|
|
5487
5544
|
rootStepId: rootStep.stepId,
|
|
5488
5545
|
parentClass: mutable.macroClass,
|
|
@@ -6540,25 +6597,6 @@ var PROBE_READY_TIMEOUT_MS = 12e4;
|
|
|
6540
6597
|
* race bound keeps a wedged transport from wedging `setApi`.
|
|
6541
6598
|
*/
|
|
6542
6599
|
var PROBE_DIRECT_RPC_TIMEOUT_MS = 6e4;
|
|
6543
|
-
/**
|
|
6544
|
-
* Build the onnx-cpu floor pick using `pickBestRuntime` with a null hardware
|
|
6545
|
-
* env. Used wherever the old `detectBestEngine()` sync probe fell back — the
|
|
6546
|
-
* result is identical (onnx / cpu) but is now derived through the shared rules
|
|
6547
|
-
* instead of duplicated inline.
|
|
6548
|
-
*/
|
|
6549
|
-
/**
|
|
6550
|
-
* Is `backend` even POSSIBLE on this node's OS/arch (ignoring gpu detail)?
|
|
6551
|
-
* coreml ⇒ darwin only; openvino ⇒ x64 non-darwin only; onnx ⇒ anywhere. Used to
|
|
6552
|
-
* reject a persisted/global engine choice that the node's PLATFORM fundamentally
|
|
6553
|
-
* cannot run (e.g. the cluster's OpenVINO default landing on a Mac) — distinct
|
|
6554
|
-
* from the gpu-dependent support (linux without a probed Intel iGPU still keeps
|
|
6555
|
-
* openvino as a valid platform choice; the device falls back to cpu).
|
|
6556
|
-
*/
|
|
6557
|
-
function backendPossibleOnPlatform(backend) {
|
|
6558
|
-
if (backend === "coreml") return process.platform === "darwin";
|
|
6559
|
-
if (backend === "openvino") return process.arch === "x64" && process.platform !== "darwin";
|
|
6560
|
-
return true;
|
|
6561
|
-
}
|
|
6562
6600
|
var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
6563
6601
|
modelsDir;
|
|
6564
6602
|
eventBus;
|
|
@@ -6698,22 +6736,6 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
6698
6736
|
*/
|
|
6699
6737
|
needsAutoPick = false;
|
|
6700
6738
|
/**
|
|
6701
|
-
* Warm cache for benchmark engine-override runs.
|
|
6702
|
-
*
|
|
6703
|
-
* Each override rebuild costs a full Python pool spin-up (~300-500ms)
|
|
6704
|
-
* plus the per-model load. Benchmark tabs iterate up to 800× against
|
|
6705
|
-
* the same override (e.g. yolov9s / coreml / all) — paying that spin-up
|
|
6706
|
-
* each iteration dwarfs actual inference time.
|
|
6707
|
-
*
|
|
6708
|
-
* When the same override engine is requested within `OVERRIDE_CACHE_TTL_MS`,
|
|
6709
|
-
* we reuse the prior transient factory. Hitting a different override
|
|
6710
|
-
* or exceeding the TTL disposes the cache and spins a new factory.
|
|
6711
|
-
* The prior "main" factory (`priorFactory`) is still restored so
|
|
6712
|
-
* runtime dispatch (camera frames) keeps its own resident engine.
|
|
6713
|
-
*/
|
|
6714
|
-
overrideCache = null;
|
|
6715
|
-
overrideCacheTimer = null;
|
|
6716
|
-
/**
|
|
6717
6739
|
* Idle-TTL for a per-device inference pool (multi-device C3). A device pool
|
|
6718
6740
|
* with no dispatch for this long is disposed and recreated on next use — the
|
|
6719
6741
|
* memory guardrail that keeps `factoriesByDevice` from accumulating warm pools
|
|
@@ -6746,7 +6768,6 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
6746
6768
|
error: err instanceof Error ? err.message : String(err)
|
|
6747
6769
|
} })
|
|
6748
6770
|
});
|
|
6749
|
-
static OVERRIDE_CACHE_TTL_MS = 6e4;
|
|
6750
6771
|
/**
|
|
6751
6772
|
* RSS bound + periodic memory telemetry for the Python inference pools
|
|
6752
6773
|
* (2026-08-18 OOM audit: the pools, not the recorder, were the host's OOM
|
|
@@ -6883,15 +6904,14 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
6883
6904
|
/**
|
|
6884
6905
|
* Lazy-install the pip requirements file matching `engine.backend` into
|
|
6885
6906
|
* the embedded Python before the inference pool spawns. No-op for
|
|
6886
|
-
*
|
|
6887
|
-
*
|
|
6907
|
+
* backends with no requirements file on disk, or before the addon
|
|
6908
|
+
* context has been wired (warm-pool race window).
|
|
6888
6909
|
*
|
|
6889
6910
|
* Idempotent — `installPythonRequirements` short-circuits on a hash
|
|
6890
6911
|
* marker, so back-to-back EngineFactory rebuilds (benchmark overrides,
|
|
6891
6912
|
* tuning respawns) skip the pip subprocess after the first install.
|
|
6892
6913
|
*/
|
|
6893
6914
|
async ensureBackendDeps(engine) {
|
|
6894
|
-
if (engine.runtime !== "python") return;
|
|
6895
6915
|
if (!this.addonCtx) return;
|
|
6896
6916
|
const pythonAddonDir = this.executorOptions.pythonAddonDir;
|
|
6897
6917
|
if (!pythonAddonDir) return;
|
|
@@ -7456,8 +7476,22 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
7456
7476
|
* result with a warning logged, same posture as `recomputeConfigIssues`.
|
|
7457
7477
|
*/
|
|
7458
7478
|
async validatePipeline(input) {
|
|
7459
|
-
|
|
7479
|
+
let format = this.currentEngine.format;
|
|
7460
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
|
+
}
|
|
7461
7495
|
const steps = input.steps;
|
|
7462
7496
|
const enabledSteps = this.pruneToEnabledSteps(steps);
|
|
7463
7497
|
const substitutions = collectSubstitutions(enabledSteps, format);
|
|
@@ -7782,7 +7816,7 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
7782
7816
|
const nodeId = this.addonCtx?.kernel?.localNodeId ?? "hub";
|
|
7783
7817
|
const sessionId = input.sessionId ?? `run-${Date.now()}-${Math.random().toString(36).slice(2, 10)}`;
|
|
7784
7818
|
const isRuntime = Boolean(input.frame || input.frameRef);
|
|
7785
|
-
const progressToBus = !isRuntime || input.sessionId !== void 0;
|
|
7819
|
+
const progressToBus = !isRuntime && input.replay !== true || input.sessionId !== void 0;
|
|
7786
7820
|
const emit = (message, extra) => {
|
|
7787
7821
|
onProgress?.(message);
|
|
7788
7822
|
if (!progressToBus) return;
|
|
@@ -7924,7 +7958,6 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
7924
7958
|
const dispatchResolution = this.resolveStepsForDispatch({
|
|
7925
7959
|
steps: input.steps,
|
|
7926
7960
|
deviceKey: input.deviceKey,
|
|
7927
|
-
engineOverride: input.engine,
|
|
7928
7961
|
deviceId: input.deviceId,
|
|
7929
7962
|
plane: input.plane
|
|
7930
7963
|
});
|
|
@@ -7944,322 +7977,150 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
7944
7977
|
}
|
|
7945
7978
|
const enabledSteps = flattenSteps(benchmarkSteps);
|
|
7946
7979
|
emit(`Pipeline: ${enabledSteps.length} step(s) — ${enabledSteps.map((s) => s.addonId).join(" → ")}`);
|
|
7947
|
-
|
|
7948
|
-
const
|
|
7949
|
-
|
|
7950
|
-
|
|
7951
|
-
|
|
7952
|
-
|
|
7953
|
-
|
|
7954
|
-
|
|
7955
|
-
|
|
7956
|
-
|
|
7957
|
-
|
|
7958
|
-
|
|
7959
|
-
const cached = this.overrideCache;
|
|
7960
|
-
if (cached.initPromise) await cached.initPromise;
|
|
7961
|
-
this.log.debug("Benchmark: reusing warm override factory", { meta: { engine: `${runEngine.runtime}/${runEngine.backend}/${runEngine.device ?? "default"}` } });
|
|
7962
|
-
this.currentEngine = runEngine;
|
|
7963
|
-
this.engineFactory = cached.factory;
|
|
7964
|
-
this.executor = cached.executor;
|
|
7965
|
-
} else {
|
|
7966
|
-
if (this.overrideCache) {
|
|
7967
|
-
try {
|
|
7968
|
-
await this.overrideCache.factory.dispose();
|
|
7969
|
-
} catch {}
|
|
7970
|
-
this.overrideCache = null;
|
|
7971
|
-
}
|
|
7972
|
-
this.log.info("Benchmark: engine override", { meta: {
|
|
7973
|
-
from: `${priorEngine.runtime}/${priorEngine.backend}`,
|
|
7974
|
-
to: `${runEngine.runtime}/${runEngine.backend}`
|
|
7975
|
-
} });
|
|
7976
|
-
await this.ensureBackendDeps(runEngine);
|
|
7977
|
-
const newFactory = new EngineFactory({
|
|
7978
|
-
engine: runEngine,
|
|
7979
|
-
modelsDir: this.modelsDir,
|
|
7980
|
-
logger: this.log.child("engine-override"),
|
|
7981
|
-
pythonPath: this.executorOptions.pythonPath ?? "",
|
|
7982
|
-
provisioning: this.executorOptions.provisioning,
|
|
7983
|
-
resolveCustomModel: this.customModelResolver
|
|
7984
|
-
});
|
|
7985
|
-
const initPromise = newFactory.initialize([]);
|
|
7986
|
-
this.overrideCache = {
|
|
7987
|
-
engine: runEngine,
|
|
7988
|
-
factory: newFactory,
|
|
7989
|
-
executor: null,
|
|
7990
|
-
lastUsedMs: Date.now(),
|
|
7991
|
-
initPromise
|
|
7992
|
-
};
|
|
7993
|
-
this.scheduleOverrideCacheEviction();
|
|
7994
|
-
try {
|
|
7995
|
-
await initPromise;
|
|
7996
|
-
this.overrideCache.initPromise = null;
|
|
7997
|
-
} catch (err) {
|
|
7998
|
-
this.overrideCache = null;
|
|
7999
|
-
if (this.overrideCacheTimer) {
|
|
8000
|
-
clearTimeout(this.overrideCacheTimer);
|
|
8001
|
-
this.overrideCacheTimer = null;
|
|
8002
|
-
}
|
|
8003
|
-
throw err;
|
|
8004
|
-
}
|
|
8005
|
-
this.currentEngine = runEngine;
|
|
8006
|
-
this.engineFactory = newFactory;
|
|
8007
|
-
this.executor = null;
|
|
8008
|
-
}
|
|
8009
|
-
try {
|
|
8010
|
-
const deviceLabel = runEngine.device ?? "default";
|
|
8011
|
-
emit(`Engine: ${runEngine.runtime}/${runEngine.backend}/${deviceLabel}${input.engine ? " (override)" : ""}`, {
|
|
8012
|
-
step: "engine",
|
|
8013
|
-
engine: {
|
|
8014
|
-
runtime: runEngine.runtime,
|
|
8015
|
-
backend: runEngine.backend,
|
|
8016
|
-
device: deviceLabel
|
|
8017
|
-
}
|
|
8018
|
-
});
|
|
8019
|
-
const runtimeStr = `${runEngine.runtime}+${runEngine.backend}`;
|
|
8020
|
-
const executor = this.executor ?? new PipelineExecutor({
|
|
8021
|
-
engineRuntime: runtimeStr,
|
|
8022
|
-
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
|
|
8023
7992
|
});
|
|
8024
|
-
|
|
8025
|
-
|
|
8026
|
-
|
|
8027
|
-
|
|
8028
|
-
|
|
8029
|
-
|
|
8030
|
-
|
|
8031
|
-
|
|
8032
|
-
} });
|
|
8033
|
-
for (const s of needed) emit(`Loading model: ${s.addonName} (${s.modelId})...`, {
|
|
8034
|
-
step: s.addonId,
|
|
8035
|
-
addonId: s.addonId,
|
|
8036
|
-
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
|
+
}
|
|
8037
8001
|
});
|
|
8038
|
-
|
|
8039
|
-
emit(`All models loaded`);
|
|
8040
|
-
}
|
|
8041
|
-
emit("Running inference...");
|
|
8042
|
-
const tree = buildExecutableTree(benchmarkSteps, (stepId) => dispatchFactory.getEngine(stepId), this.customModelResolver);
|
|
8043
|
-
setupMs = performance.now() - wallT0 - decodeMs;
|
|
8044
|
-
const effectiveDeviceId = input.deviceId ?? 0;
|
|
8045
|
-
const deviceOverrides = effectiveDeviceId > 0 ? await this.deviceOverrides.resolve(effectiveDeviceId) : {};
|
|
8046
|
-
const effectiveTree = Object.keys(deviceOverrides).length > 0 ? applyDeviceOverridesToTree(tree, "object-detection", deviceOverrides) : tree;
|
|
8047
|
-
const nativeCropProvider = this.buildNativeCropProviderFromRef(input.nativeCropRef) ?? this.buildNativeFaceCropProvider(input.frameHandle);
|
|
8048
|
-
let cropZoneBbox;
|
|
8049
|
-
if (isRuntime && effectiveDeviceId > 0 && effectiveTree.roots.some((r) => r.definition.extractMode === "crop-zone")) {
|
|
8050
|
-
await this.ensureDeviceProxy(effectiveDeviceId);
|
|
8051
|
-
const proxy = this.deviceProxies.get(effectiveDeviceId);
|
|
8052
|
-
if (proxy) cropZoneBbox = resolvePackageCropBbox(proxy.state.zones.value?.zones ?? [], proxy.state.zoneRules.value?.package ?? [], imageWidth, imageHeight) ?? void 0;
|
|
8053
|
-
}
|
|
8054
|
-
let frameViewResolver;
|
|
8055
|
-
let rootInputViewProvider;
|
|
8056
|
-
if (runtimeFrameRef) {
|
|
8057
|
-
frameViewResolver = createRootFrameViewResolver(localFrameRegistry, runtimeFrameRef);
|
|
8058
|
-
rootInputViewProvider = async (root, crop) => {
|
|
8059
|
-
const view = await frameViewResolver(root, crop ? {
|
|
8060
|
-
left: crop[0],
|
|
8061
|
-
top: crop[1],
|
|
8062
|
-
width: crop[2] - crop[0],
|
|
8063
|
-
height: crop[3] - crop[1]
|
|
8064
|
-
} : void 0);
|
|
8065
|
-
const data = Buffer.from(view.data.buffer, view.data.byteOffset, view.data.byteLength);
|
|
8066
|
-
return {
|
|
8067
|
-
input: {
|
|
8068
|
-
kind: "jpeg",
|
|
8069
|
-
data
|
|
8070
|
-
},
|
|
8071
|
-
jpegProvider: async () => data,
|
|
8072
|
-
width: view.width,
|
|
8073
|
-
height: view.height,
|
|
8074
|
-
geometry: view.geometry
|
|
8075
|
-
};
|
|
8076
|
-
};
|
|
8077
|
-
}
|
|
8078
|
-
let executionSucceeded = false;
|
|
8079
|
-
let execution;
|
|
8080
|
-
try {
|
|
8081
|
-
execution = await executor.run(effectiveTree, rootInput, jpegProvider, imageWidth, imageHeight, effectiveDeviceId, {
|
|
8082
|
-
traceVerbosity: isRuntime ? this.eventBus ? "summary" : "off" : "full",
|
|
8083
|
-
plane: input.plane
|
|
8084
|
-
}, nativeCropProvider, cropZoneBbox, rootInputViewProvider);
|
|
8085
|
-
executionSucceeded = true;
|
|
8086
|
-
} finally {
|
|
8087
|
-
frameViewResolver?.release(executionSucceeded ? "success" : "error");
|
|
8088
|
-
}
|
|
8089
|
-
const { result, trace } = execution;
|
|
8090
|
-
if (isRuntime) {
|
|
8091
|
-
if (trace && this.eventBus) this.eventBus.emit(createEvent(EventCategory.PipelineTrace, {
|
|
8092
|
-
type: "device",
|
|
8093
|
-
id: trace.deviceId,
|
|
8094
|
-
nodeId: "hub"
|
|
8095
|
-
}, trace));
|
|
8096
|
-
if (effectiveDeviceId > 0) {
|
|
8097
|
-
await this.ensureDeviceProxy(effectiveDeviceId);
|
|
8098
|
-
return this.gateDetectionsByZoneRules(effectiveDeviceId, result);
|
|
8099
|
-
}
|
|
8100
|
-
return result;
|
|
8101
|
-
}
|
|
8102
|
-
for (const t of result.debug?.stepTimings ?? []) emit(`${t.source}${t.modelId ? ` (${t.modelId})` : ""} → ${t.ms}ms`, {
|
|
8103
|
-
step: t.source,
|
|
8104
|
-
addonId: t.source,
|
|
8105
|
-
modelId: t.modelId ?? void 0,
|
|
8106
|
-
ms: t.ms
|
|
8107
|
-
});
|
|
8108
|
-
emit(`Done — ${result.detections.length} detection(s) in ${result.debug?.totalInferenceMs ?? 0}ms`, { ms: result.debug?.totalInferenceMs });
|
|
8109
|
-
const wallMs = performance.now() - wallT0;
|
|
8110
|
-
const inferMs = result.debug?.totalInferenceMs ?? 0;
|
|
8111
|
-
const overheadMs = Math.max(0, wallMs - decodeMs - setupMs - inferMs);
|
|
8112
|
-
return {
|
|
8113
|
-
...result,
|
|
8114
|
-
debug: {
|
|
8115
|
-
...result.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
|
|
8120
|
-
}
|
|
8121
|
-
};
|
|
8122
|
-
} finally {
|
|
8123
|
-
if (differs && this.overrideCache) {
|
|
8124
|
-
this.overrideCache.executor = this.executor;
|
|
8125
|
-
this.overrideCache.lastUsedMs = Date.now();
|
|
8126
|
-
this.scheduleOverrideCacheEviction();
|
|
8127
|
-
}
|
|
8128
|
-
if (differs) {
|
|
8129
|
-
this.currentEngine = priorEngine;
|
|
8130
|
-
this.engineFactory = priorFactory;
|
|
8131
|
-
this.executor = priorExecutor;
|
|
8002
|
+
throw new Error(`${verdict.reason}: ${verdict.detail}`);
|
|
8132
8003
|
}
|
|
8133
8004
|
}
|
|
8134
|
-
|
|
8135
|
-
|
|
8136
|
-
|
|
8137
|
-
|
|
8138
|
-
|
|
8139
|
-
|
|
8140
|
-
|
|
8141
|
-
|
|
8142
|
-
|
|
8143
|
-
|
|
8144
|
-
* `runPipelineBatch` (batched fast path). Without this, the batch
|
|
8145
|
-
* path silently ignored `input.engine` and ran the user's override
|
|
8146
|
-
* against the addon's saved engine — making the override matrix in
|
|
8147
|
-
* the bench UI a no-op for batchSize > 1.
|
|
8148
|
-
*/
|
|
8149
|
-
async applyEngineOverride(override) {
|
|
8150
|
-
const priorEngine = this.currentEngine;
|
|
8151
|
-
const priorFactory = this.engineFactory;
|
|
8152
|
-
const priorExecutor = this.executor;
|
|
8153
|
-
if (!override) return () => {};
|
|
8154
|
-
if (!backendPossibleOnPlatform(override.backend)) {
|
|
8155
|
-
this.log.warn("Engine override impossible on this platform — running on own engine", { meta: {
|
|
8156
|
-
override: `${override.runtime}/${override.backend}/${override.device ?? "default"}`,
|
|
8157
|
-
platform: process.platform,
|
|
8158
|
-
arch: process.arch,
|
|
8159
|
-
engine: `${priorEngine.runtime}/${priorEngine.backend}`
|
|
8160
|
-
} });
|
|
8161
|
-
return () => {};
|
|
8162
|
-
}
|
|
8163
|
-
const runEngine = {
|
|
8164
|
-
runtime: override.runtime,
|
|
8165
|
-
backend: override.backend,
|
|
8166
|
-
format: override.format,
|
|
8167
|
-
...override.device || priorEngine.device ? { device: override.device ?? priorEngine.device } : {}
|
|
8168
|
-
};
|
|
8169
|
-
if (!(priorEngine.runtime !== runEngine.runtime || priorEngine.backend !== runEngine.backend || priorEngine.format !== runEngine.format || (priorEngine.device ?? "") !== (runEngine.device ?? ""))) return () => {};
|
|
8170
|
-
if (this.overrideCache && enginesEqual(this.overrideCache.engine, runEngine)) {
|
|
8171
|
-
const cached = this.overrideCache;
|
|
8172
|
-
if (cached.initPromise) await cached.initPromise;
|
|
8173
|
-
this.log.debug("Benchmark: reusing warm override factory", { meta: { engine: `${runEngine.runtime}/${runEngine.backend}/${runEngine.device ?? "default"}` } });
|
|
8174
|
-
this.currentEngine = runEngine;
|
|
8175
|
-
this.engineFactory = cached.factory;
|
|
8176
|
-
this.executor = cached.executor;
|
|
8177
|
-
} else {
|
|
8178
|
-
if (this.overrideCache) {
|
|
8179
|
-
try {
|
|
8180
|
-
await this.overrideCache.factory.dispose();
|
|
8181
|
-
} catch {}
|
|
8182
|
-
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
|
|
8183
8015
|
}
|
|
8184
|
-
|
|
8185
|
-
|
|
8186
|
-
|
|
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}`)
|
|
8187
8030
|
} });
|
|
8188
|
-
|
|
8189
|
-
|
|
8190
|
-
|
|
8191
|
-
|
|
8192
|
-
logger: this.log.child("engine-override"),
|
|
8193
|
-
pythonPath: this.executorOptions.pythonPath ?? "",
|
|
8194
|
-
provisioning: this.executorOptions.provisioning,
|
|
8195
|
-
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
|
|
8196
8035
|
});
|
|
8197
|
-
|
|
8198
|
-
|
|
8199
|
-
|
|
8200
|
-
|
|
8201
|
-
|
|
8202
|
-
|
|
8203
|
-
|
|
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
|
+
};
|
|
8204
8074
|
};
|
|
8205
|
-
|
|
8206
|
-
|
|
8207
|
-
|
|
8208
|
-
|
|
8209
|
-
|
|
8210
|
-
this.
|
|
8211
|
-
|
|
8212
|
-
|
|
8213
|
-
|
|
8214
|
-
|
|
8215
|
-
|
|
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));
|
|
8216
8098
|
}
|
|
8217
|
-
|
|
8218
|
-
this.engineFactory = newFactory;
|
|
8219
|
-
this.executor = null;
|
|
8099
|
+
return stampRoot(result);
|
|
8220
8100
|
}
|
|
8221
|
-
|
|
8222
|
-
|
|
8223
|
-
|
|
8224
|
-
|
|
8225
|
-
|
|
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
|
|
8226
8120
|
}
|
|
8227
|
-
this.currentEngine = priorEngine;
|
|
8228
|
-
this.engineFactory = priorFactory;
|
|
8229
|
-
this.executor = priorExecutor;
|
|
8230
8121
|
};
|
|
8231
8122
|
}
|
|
8232
8123
|
/**
|
|
8233
|
-
* Schedule eviction of the override cache after `OVERRIDE_CACHE_TTL_MS`
|
|
8234
|
-
* of idleness. Rescheduling resets the timer so an active benchmark
|
|
8235
|
-
* keeps its warm factory alive until iterations stop arriving.
|
|
8236
|
-
*/
|
|
8237
|
-
scheduleOverrideCacheEviction() {
|
|
8238
|
-
if (this.overrideCacheTimer) clearTimeout(this.overrideCacheTimer);
|
|
8239
|
-
this.overrideCacheTimer = setTimeout(() => {
|
|
8240
|
-
this.evictOverrideCache("idle TTL");
|
|
8241
|
-
}, DetectionPipelineProvider.OVERRIDE_CACHE_TTL_MS);
|
|
8242
|
-
}
|
|
8243
|
-
async evictOverrideCache(reason) {
|
|
8244
|
-
if (!this.overrideCache) return;
|
|
8245
|
-
const entry = this.overrideCache;
|
|
8246
|
-
this.overrideCache = null;
|
|
8247
|
-
if (this.overrideCacheTimer) {
|
|
8248
|
-
clearTimeout(this.overrideCacheTimer);
|
|
8249
|
-
this.overrideCacheTimer = null;
|
|
8250
|
-
}
|
|
8251
|
-
try {
|
|
8252
|
-
await entry.factory.dispose();
|
|
8253
|
-
this.log.info("Override cache evicted", { meta: {
|
|
8254
|
-
reason,
|
|
8255
|
-
engine: `${entry.engine.runtime}/${entry.engine.backend}/${entry.engine.device ?? "default"}`,
|
|
8256
|
-
ageMs: Date.now() - entry.lastUsedMs
|
|
8257
|
-
} });
|
|
8258
|
-
} catch (err) {
|
|
8259
|
-
this.log.warn("Override cache dispose failed", { meta: { error: err instanceof Error ? err.message : String(err) } });
|
|
8260
|
-
}
|
|
8261
|
-
}
|
|
8262
|
-
/**
|
|
8263
8124
|
* Convert PipelineStepInput[] → PipelineDefaultStep[] by enriching from
|
|
8264
8125
|
* StepDefinitions. This is the LIVE per-camera dispatch path — it runs
|
|
8265
8126
|
* once per decoded frame (up to detectionFps × N cameras/sec) — so it
|
|
@@ -8275,13 +8136,8 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
8275
8136
|
* `resolveStepModels` (see its doc comment); a second writer here would
|
|
8276
8137
|
* corrupt it.
|
|
8277
8138
|
*
|
|
8278
|
-
* `format` defaults to `this.currentEngine.format` (the
|
|
8279
|
-
*
|
|
8280
|
-
* per-run engine override (`input.engine`) MUST pass the override's format
|
|
8281
|
-
* explicitly — `this.currentEngine` only gets swapped to the override
|
|
8282
|
-
* LATER in `runPipeline`/`runPipelineBatch`, so relying on the default here
|
|
8283
|
-
* would resolve models against the node's persisted format instead of the
|
|
8284
|
-
* 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.
|
|
8285
8141
|
*/
|
|
8286
8142
|
inputStepsToPipelineSteps(steps, format = this.currentEngine.format, engine = {
|
|
8287
8143
|
backend: this.currentEngine.backend,
|
|
@@ -8363,8 +8219,7 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
8363
8219
|
* devices — so the whole-tree gate stays.
|
|
8364
8220
|
*
|
|
8365
8221
|
* Fallback is strictly NODE-LOCAL and ordered: the node's own selected
|
|
8366
|
-
* engine
|
|
8367
|
-
* 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
|
|
8368
8223
|
* orchestrator's tier). The fallback is logged ONCE per distinct
|
|
8369
8224
|
* (deviceKey, steps, format) signature, with `tags: { deviceId }` when the
|
|
8370
8225
|
* dispatch is camera-scoped.
|
|
@@ -8375,12 +8230,9 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
8375
8230
|
* configuration must stay loud, not vanish into a silent fallback chain.
|
|
8376
8231
|
*/
|
|
8377
8232
|
resolveStepsForDispatch(args) {
|
|
8378
|
-
const { deviceKey,
|
|
8379
|
-
const nodeFormat =
|
|
8380
|
-
const nodeEngine =
|
|
8381
|
-
backend: engineOverride.backend,
|
|
8382
|
-
device: engineOverride.device ?? null
|
|
8383
|
-
} : {
|
|
8233
|
+
const { deviceKey, deviceId } = args;
|
|
8234
|
+
const nodeFormat = this.currentEngine.format;
|
|
8235
|
+
const nodeEngine = {
|
|
8384
8236
|
backend: this.currentEngine.backend,
|
|
8385
8237
|
device: this.currentEngine.device ?? null
|
|
8386
8238
|
};
|
|
@@ -8625,12 +8477,11 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
8625
8477
|
const dispatchResolution = this.resolveStepsForDispatch({
|
|
8626
8478
|
steps: input.steps,
|
|
8627
8479
|
deviceKey: input.deviceKey,
|
|
8628
|
-
engineOverride: input.engine,
|
|
8629
8480
|
deviceId: input.deviceId,
|
|
8630
8481
|
plane: void 0
|
|
8631
8482
|
});
|
|
8632
8483
|
const dispatchDeviceKey = dispatchResolution.deviceKey;
|
|
8633
|
-
const resolveFormat = dispatchDeviceKey ? resolveDeviceEngine(dispatchDeviceKey).format :
|
|
8484
|
+
const resolveFormat = dispatchDeviceKey ? resolveDeviceEngine(dispatchDeviceKey).format : this.currentEngine.format;
|
|
8634
8485
|
const benchmarkSteps = dispatchResolution.steps;
|
|
8635
8486
|
const enabledSteps = flattenEnabledVideoSteps(benchmarkSteps);
|
|
8636
8487
|
if (enabledSteps.length === 0) throw new Error("runPipelineBatch: no enabled steps");
|
|
@@ -8651,53 +8502,48 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
8651
8502
|
const uniformDims = input.frames.every((f) => f.width === firstFrame.width && f.height === firstFrame.height);
|
|
8652
8503
|
const singleRoot = enabledSteps.length === 1;
|
|
8653
8504
|
await this.ensureEngineFactory();
|
|
8654
|
-
const
|
|
8655
|
-
|
|
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
|
-
const perFrameMs = totalMs / rawResults.length;
|
|
8697
|
-
return { results: rawResults.map((raw, i) => assembleBatchedFrameResult(raw, input.frames[i], rootStep.addonId, input.deviceId ?? 0, perFrameMs, perFrameMs)) };
|
|
8698
|
-
} finally {
|
|
8699
|
-
restoreEngine();
|
|
8700
|
-
}
|
|
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)) };
|
|
8701
8547
|
}
|
|
8702
8548
|
async listReferenceImages() {
|
|
8703
8549
|
const dir = this.resolveRefImagesDir();
|
|
@@ -8802,7 +8648,6 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
8802
8648
|
this.poolMemoryGuard = null;
|
|
8803
8649
|
this.inferenceTimeoutGuard?.stop();
|
|
8804
8650
|
this.inferenceTimeoutGuard = null;
|
|
8805
|
-
await this.evictOverrideCache("shutdown");
|
|
8806
8651
|
if (this.engineFactory) {
|
|
8807
8652
|
await this.engineFactory.dispose();
|
|
8808
8653
|
this.engineFactory = null;
|
|
@@ -8884,13 +8729,22 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
8884
8729
|
* own runtime's row, so this is not "the node's tuning" — the per-pool truth
|
|
8885
8730
|
* is on each pool's `Python inference pool` log line and in `listLoadedEngines`.
|
|
8886
8731
|
*/
|
|
8887
|
-
|
|
8888
|
-
|
|
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);
|
|
8889
8740
|
return {
|
|
8741
|
+
deviceKey: input.deviceKey ?? null,
|
|
8742
|
+
runtime: p.runtime,
|
|
8890
8743
|
batchMode: p.batchMode,
|
|
8891
8744
|
windowMs: p.windowMs,
|
|
8892
8745
|
maxBatchSize: p.maxBatchSize,
|
|
8893
|
-
concurrency: p.concurrency
|
|
8746
|
+
concurrency: p.concurrency,
|
|
8747
|
+
numWorkers: p.numWorkers
|
|
8894
8748
|
};
|
|
8895
8749
|
}
|
|
8896
8750
|
/**
|
|
@@ -9278,9 +9132,7 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
9278
9132
|
}
|
|
9279
9133
|
async listLoadedEngines() {
|
|
9280
9134
|
const out = [];
|
|
9281
|
-
|
|
9282
|
-
const runtimeFactoryIsOverride = this.engineFactory !== null && this.engineFactory === overrideFactory;
|
|
9283
|
-
if (this.engineFactory && !runtimeFactoryIsOverride) {
|
|
9135
|
+
if (this.engineFactory) {
|
|
9284
9136
|
const eng = this.currentEngine;
|
|
9285
9137
|
const engineKey = `${eng.runtime}/${eng.backend}/${eng.device ?? "default"}`;
|
|
9286
9138
|
const loaded = this.engineFactory.listLoaded();
|
|
@@ -9295,22 +9147,6 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
9295
9147
|
idleTtlMs: null
|
|
9296
9148
|
});
|
|
9297
9149
|
}
|
|
9298
|
-
if (this.overrideCache) {
|
|
9299
|
-
const eng = this.overrideCache.engine;
|
|
9300
|
-
const engineKey = `${eng.runtime}/${eng.backend}/${eng.device ?? "default"} (warm)`;
|
|
9301
|
-
const loaded = this.overrideCache.factory.listLoaded();
|
|
9302
|
-
const idleMs = Math.max(0, Date.now() - this.overrideCache.lastUsedMs);
|
|
9303
|
-
if (idleMs <= DetectionPipelineProvider.OVERRIDE_CACHE_TTL_MS) out.push({
|
|
9304
|
-
engineKey,
|
|
9305
|
-
engine: eng,
|
|
9306
|
-
modelsLoaded: loaded.map((l) => `${l.stepId}/${l.modelId}`),
|
|
9307
|
-
inUseByCameras: [],
|
|
9308
|
-
kind: "warm-override",
|
|
9309
|
-
poolPid: this.overrideCache.factory.getPoolPid(),
|
|
9310
|
-
idleMs,
|
|
9311
|
-
idleTtlMs: DetectionPipelineProvider.OVERRIDE_CACHE_TTL_MS
|
|
9312
|
-
});
|
|
9313
|
-
}
|
|
9314
9150
|
for (const [deviceKey, factory] of this.factoriesByDevice) {
|
|
9315
9151
|
const eng = resolveDeviceEngine(deviceKey);
|
|
9316
9152
|
const loaded = factory.listLoaded();
|
|
@@ -9339,14 +9175,6 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
9339
9175
|
return { success: true };
|
|
9340
9176
|
}
|
|
9341
9177
|
async killEngine(input) {
|
|
9342
|
-
if (this.overrideCache && enginesEqual(this.overrideCache.engine, input.engine)) {
|
|
9343
|
-
await this.evictOverrideCache("killEngine");
|
|
9344
|
-
this.log.info("Override engine killed", { meta: {
|
|
9345
|
-
...input.engine,
|
|
9346
|
-
force: input.force ?? false
|
|
9347
|
-
} });
|
|
9348
|
-
return { success: true };
|
|
9349
|
-
}
|
|
9350
9178
|
if (!this.engineFactory) return {
|
|
9351
9179
|
success: false,
|
|
9352
9180
|
reason: "not loaded"
|
|
@@ -9864,7 +9692,7 @@ var DetectionPipelineAddon = class extends BaseAddon {
|
|
|
9864
9692
|
/**
|
|
9865
9693
|
* Embedded Python path resolved once at boot via
|
|
9866
9694
|
* `ctx.deps.ensurePython()`. Empty string means the download failed
|
|
9867
|
-
*
|
|
9695
|
+
* — the provider's
|
|
9868
9696
|
* `ensureBackendDeps` and EngineFactory's `initPythonPool` raise a
|
|
9869
9697
|
* clear error in that case.
|
|
9870
9698
|
*/
|