@camstack/addon-pipeline 1.2.296 → 1.2.297
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/audio-analyzer/index.js +2 -2
- package/dist/audio-analyzer/index.mjs +2 -2
- package/dist/{default-detection-model-D1daTtqT.js → default-detection-model-B2n_SFXt.js} +368 -5
- package/dist/{default-detection-model-Co578D8C.mjs → default-detection-model-D6DypCkI.mjs} +339 -6
- package/dist/detection-pipeline/index.js +521 -217
- package/dist/detection-pipeline/index.mjs +520 -216
- package/dist/{dist-CCd0Q3nr.mjs → dist-CxHIpsQs.mjs} +53 -123
- package/dist/{dist-8up-f2TX.js → dist-DUqr1zyq.js} +64 -140
- package/dist/motion-wasm/index.js +1 -1
- package/dist/motion-wasm/index.mjs +1 -1
- package/dist/{node-lpQgHes9.js → node-Dx6DLQ1j.js} +1 -1
- package/dist/{node-DgMSXSWP.mjs → node-DyeWu78a.mjs} +1 -1
- package/dist/pipeline-runner/index.js +213 -203
- package/dist/pipeline-runner/index.mjs +213 -203
- package/dist/{process-memory-DFC_O5zE.mjs → process-memory-BVR4592X.mjs} +1 -1
- package/dist/{process-memory-CX_92V_r.js → process-memory-CMJ3NY-s.js} +1 -1
- package/dist/recorder/index.js +2 -2
- package/dist/recorder/index.mjs +2 -2
- package/dist/{segment-demux-js-DzBx6NN2.js → segment-demux-js-CmGIF_uK.js} +1 -1
- package/dist/{segment-demux-js-FZbBuk3F.mjs → segment-demux-js-Dzga-XgE.mjs} +1 -1
- package/dist/stream-broker/_stub.js +2 -2
- package/dist/stream-broker/{_virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-C_i7oFBl.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-P71ze8cu.mjs} +2 -2
- package/dist/stream-broker/{_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-DUGQKsKL.mjs → _virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-CZkpFU-J.mjs} +1 -1
- package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-Dti8Ex88.mjs +26 -0
- 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-BPtppL3W.mjs → hostInit-CRlStbz4.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 +1 -1
- package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-CkbplMHA.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
|
|
2
|
+
import { Dn as array, Dt as overlayClusterStepSettings, Gt as runtimeDevices$1, H as YAMNET_TO_MACRO, In as string, Kt as stepModelRunsOnFormat, Ln as union, Mt as pipelineExecutorCapability, Nn as object, Rn as EventCategory, Sn as parseJsonUnknown, Tn as sleep, Ut as resolvePoolMemoryPolicy, Zt as supportedRuntimes$1, _n as hydrateSchema, en as errMsg, et as defaultDeviceFor$1, it as detectionPipelineCapability, j as PoolMemoryWatchdog, kt as pickClusterStepSettings, lt as evaluateZoneRules, m as DEFAULT_CLUSTER_STEP_SETTINGS, pn as createEvent, sn as BaseAddon, st as enumerateInferenceDevices, t as APPLE_SA_TO_MACRO, u as CLUSTER_MODEL_SCOPED_STEPS, vt as loadContributionCapability, xn as nodePin, y as DEVICE_BACKEND_TO_FORMAT, zt as resolveClusterStepModelId } from "../dist-CxHIpsQs.mjs";
|
|
3
|
+
import { s as readProcessCost } from "../node-DyeWu78a.mjs";
|
|
4
|
+
import { _ as resolveModelForFormat, a as buildSubtreeCanRun, c as macroGateVerdict, f as ALL_PIPELINE_STEPS, g as getStepDefinition, h as getStep, i as mergeCustomModels, m as getDefaultModelForFormat, n as resolveDefaultDetectionModel, p as ALL_STEPS, r as CustomModelCatalog, u as localFrameRegistry, y as landmarkPrecisionVerdict } from "../default-detection-model-D6DypCkI.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-BVR4592X.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";
|
|
@@ -360,12 +360,14 @@ function normalizeEngineNodeId(rawNodeId) {
|
|
|
360
360
|
* builders eventually reaches ten of them, and the cameras attached by the
|
|
361
361
|
* forgotten one quietly write a foreign feature space into the shared index.
|
|
362
362
|
*
|
|
363
|
-
* ## Why the failure mode keeps the last value
|
|
363
|
+
* ## Why the failure mode keeps the last value — and refuses before the first
|
|
364
364
|
*
|
|
365
365
|
* A failed read must never revert to the registry default MID-PASS: that would
|
|
366
366
|
* silently change what every remaining vector of that dispatch means, which is
|
|
367
367
|
* the exact class of harm the scope exists to prevent. So a failure keeps the
|
|
368
|
-
* last known row and says so,
|
|
368
|
+
* last known row and says so, once per change. Before the FIRST answer there is
|
|
369
|
+
* no row to keep: the step is refused by name (D658) — a default run then made
|
|
370
|
+
* the default model the pool's active variant for the process lifetime.
|
|
369
371
|
*/
|
|
370
372
|
/**
|
|
371
373
|
* The addon whose GLOBAL settings own the cluster row. Same owner as the crop
|
|
@@ -374,35 +376,58 @@ function normalizeEngineNodeId(rawNodeId) {
|
|
|
374
376
|
* enforces it live — neither should own a value the other depends on).
|
|
375
377
|
*/
|
|
376
378
|
var CLUSTER_MODEL_OWNER_ADDON_ID = "pipeline-orchestrator";
|
|
377
|
-
/**
|
|
379
|
+
/**
|
|
380
|
+
* TTL-cached read of the cluster model row.
|
|
381
|
+
*
|
|
382
|
+
* A step's model is KNOWN only once the owner has answered for it (`stored` or
|
|
383
|
+
* `absent`). Until then it is UNKNOWN, and a cluster-scoped step is refused by
|
|
384
|
+
* name rather than run with the registry default (D49, D658): a default run
|
|
385
|
+
* during the owner's boot window made that model the pool's active variant for
|
|
386
|
+
* the whole process, and the face default was ArcFace until D651. Once known,
|
|
387
|
+
* an unanswered read keeps the LAST KNOWN model — never reverts to a default.
|
|
388
|
+
*/
|
|
378
389
|
var ClusterModelSource = class {
|
|
379
|
-
models =
|
|
390
|
+
models = {};
|
|
391
|
+
unknown = new Map(CLUSTER_MODEL_SCOPED_STEPS.map((s) => [s.stepId, "not-yet-asked"]));
|
|
380
392
|
settings = DEFAULT_CLUSTER_STEP_SETTINGS;
|
|
381
393
|
lastReadAtMs = Number.NEGATIVE_INFINITY;
|
|
382
394
|
inFlight = null;
|
|
383
395
|
logger;
|
|
384
396
|
now;
|
|
385
397
|
ttlMs;
|
|
398
|
+
unknownRetryMs;
|
|
399
|
+
/** The last "not known" state logged, so it is said once per change. */
|
|
400
|
+
lastUnknownLogKey = "";
|
|
386
401
|
constructor(options) {
|
|
387
402
|
this.logger = options.logger;
|
|
388
403
|
this.now = options.now ?? (() => Date.now());
|
|
389
404
|
this.ttlMs = options.ttlMs ?? 6e4;
|
|
405
|
+
this.unknownRetryMs = options.unknownRetryMs ?? 5e3;
|
|
390
406
|
}
|
|
391
|
-
/**
|
|
407
|
+
/**
|
|
408
|
+
* The KNOWN models, `stepId → modelId`. A step absent here is unknown (see
|
|
409
|
+
* {@link unknownSteps}) and must be refused, never defaulted.
|
|
410
|
+
*/
|
|
392
411
|
current() {
|
|
393
412
|
return this.models;
|
|
394
413
|
}
|
|
414
|
+
/** Cluster-scoped steps whose model the owner has not answered yet, with why. */
|
|
415
|
+
unknownSteps() {
|
|
416
|
+
return this.unknown;
|
|
417
|
+
}
|
|
395
418
|
/** Cluster-scoped step knobs in force right now (admission floors, …). */
|
|
396
419
|
currentSettings() {
|
|
397
420
|
return this.settings;
|
|
398
421
|
}
|
|
399
422
|
/**
|
|
400
|
-
* Re-read if the cached row has expired
|
|
401
|
-
* in-flight read; a failure keeps the LAST
|
|
423
|
+
* Re-read if the cached row has expired (sooner while a step is unknown).
|
|
424
|
+
* Concurrent callers share one in-flight read; a failure keeps the LAST
|
|
425
|
+
* KNOWN row.
|
|
402
426
|
*/
|
|
403
427
|
async refresh(api) {
|
|
404
428
|
if (api === void 0) return this.models;
|
|
405
|
-
|
|
429
|
+
const ttl = this.unknown.size > 0 ? Math.min(this.ttlMs, this.unknownRetryMs) : this.ttlMs;
|
|
430
|
+
if (this.now() - this.lastReadAtMs < ttl) return this.models;
|
|
406
431
|
this.inFlight ??= this.read(api).finally(() => {
|
|
407
432
|
this.inFlight = null;
|
|
408
433
|
});
|
|
@@ -410,44 +435,186 @@ var ClusterModelSource = class {
|
|
|
410
435
|
return this.models;
|
|
411
436
|
}
|
|
412
437
|
async read(api) {
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
return [...new Set([...Object.keys(from), ...Object.keys(to)])].some((key) => from[key] !== to[key]);
|
|
429
|
-
});
|
|
430
|
-
if (changed.length > 0) {
|
|
431
|
-
this.logger.info("cluster step model changed — vectors already stored by the previous model need a re-embed pass", { meta: { changed: changed.map((stepId) => ({
|
|
432
|
-
stepId,
|
|
433
|
-
from: this.models[stepId],
|
|
434
|
-
to: next[stepId]
|
|
435
|
-
})) } });
|
|
436
|
-
this.models = next;
|
|
437
|
-
}
|
|
438
|
-
if (settingsChanged) {
|
|
439
|
-
this.logger.info("cluster step settings changed", { meta: {
|
|
440
|
-
from: { ...this.settings },
|
|
441
|
-
to: { ...nextSettings }
|
|
442
|
-
} });
|
|
443
|
-
this.settings = nextSettings;
|
|
438
|
+
this.lastReadAtMs = this.now();
|
|
439
|
+
await Promise.all([this.readModels(api), this.readSettings(api)]);
|
|
440
|
+
}
|
|
441
|
+
async readModels(api) {
|
|
442
|
+
const answers = await Promise.all(CLUSTER_MODEL_SCOPED_STEPS.map(async (step) => ({
|
|
443
|
+
stepId: step.stepId,
|
|
444
|
+
answer: await askOwner(api, step.stepId)
|
|
445
|
+
})));
|
|
446
|
+
const nextModels = { ...this.models };
|
|
447
|
+
const nextUnknown = /* @__PURE__ */ new Map();
|
|
448
|
+
const unanswered = [];
|
|
449
|
+
for (const { stepId, answer } of answers) {
|
|
450
|
+
if (answer.kind === "model") {
|
|
451
|
+
nextModels[stepId] = answer.modelId;
|
|
452
|
+
continue;
|
|
444
453
|
}
|
|
454
|
+
const keeping = this.models[stepId] ?? null;
|
|
455
|
+
unanswered.push({
|
|
456
|
+
stepId,
|
|
457
|
+
reason: answer.reason,
|
|
458
|
+
keeping
|
|
459
|
+
});
|
|
460
|
+
if (keeping === null) nextUnknown.set(stepId, answer.reason);
|
|
461
|
+
}
|
|
462
|
+
const changed = Object.keys(nextModels).filter((stepId) => this.models[stepId] !== void 0 && nextModels[stepId] !== this.models[stepId]);
|
|
463
|
+
if (changed.length > 0) this.logger.info("cluster step model changed — vectors already stored by the previous model need a re-embed pass", { meta: { changed: changed.map((stepId) => ({
|
|
464
|
+
stepId,
|
|
465
|
+
from: this.models[stepId],
|
|
466
|
+
to: nextModels[stepId]
|
|
467
|
+
})) } });
|
|
468
|
+
const learned = Object.keys(nextModels).filter((stepId) => this.models[stepId] === void 0);
|
|
469
|
+
if (learned.length > 0) this.logger.info("cluster step model known — cluster-scoped steps run it", { meta: { known: Object.fromEntries(learned.map((id) => [id, nextModels[id]])) } });
|
|
470
|
+
this.models = nextModels;
|
|
471
|
+
this.unknown = nextUnknown;
|
|
472
|
+
this.reportUnanswered(unanswered);
|
|
473
|
+
}
|
|
474
|
+
/** Once per change of the unanswered set — never once per retry. */
|
|
475
|
+
reportUnanswered(unanswered) {
|
|
476
|
+
const key = JSON.stringify(unanswered);
|
|
477
|
+
if (key === this.lastUnknownLogKey) return;
|
|
478
|
+
this.lastUnknownLogKey = key;
|
|
479
|
+
if (unanswered.length === 0) return;
|
|
480
|
+
this.logger.warn("cluster step model not answered by its owner — a step never known is REFUSED, a known one keeps its last model", { meta: {
|
|
481
|
+
owner: CLUSTER_MODEL_OWNER_ADDON_ID,
|
|
482
|
+
unanswered: unanswered.map((u) => ({
|
|
483
|
+
stepId: u.stepId,
|
|
484
|
+
reason: u.reason,
|
|
485
|
+
...u.keeping !== null ? { keeping: u.keeping } : { refused: true }
|
|
486
|
+
}))
|
|
487
|
+
} });
|
|
488
|
+
}
|
|
489
|
+
async readSettings(api) {
|
|
490
|
+
let view;
|
|
491
|
+
try {
|
|
492
|
+
view = await api.addonSettings.getGlobalSettings.query({ addonId: CLUSTER_MODEL_OWNER_ADDON_ID });
|
|
445
493
|
} catch (err) {
|
|
446
|
-
this.logger.warn("cluster step
|
|
494
|
+
this.logger.warn("cluster step settings read failed — keeping the last known knobs", { meta: {
|
|
447
495
|
owner: CLUSTER_MODEL_OWNER_ADDON_ID,
|
|
448
|
-
inUse: { ...this.models },
|
|
449
496
|
error: err instanceof Error ? err.message : String(err)
|
|
450
497
|
} });
|
|
498
|
+
return;
|
|
499
|
+
}
|
|
500
|
+
if (view === null) return;
|
|
501
|
+
const nextSettings = pickClusterStepSettings(view);
|
|
502
|
+
if (Object.keys(nextSettings).some((stepId) => {
|
|
503
|
+
const from = this.settings[stepId] ?? {};
|
|
504
|
+
const to = nextSettings[stepId] ?? {};
|
|
505
|
+
return [...new Set([...Object.keys(from), ...Object.keys(to)])].some((key) => from[key] !== to[key]);
|
|
506
|
+
})) {
|
|
507
|
+
this.logger.info("cluster step settings changed", { meta: {
|
|
508
|
+
from: { ...this.settings },
|
|
509
|
+
to: { ...nextSettings }
|
|
510
|
+
} });
|
|
511
|
+
this.settings = nextSettings;
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
};
|
|
515
|
+
/** One step's model from the owner's three-way answer; a throw is no answer. */
|
|
516
|
+
async function askOwner(api, stepId) {
|
|
517
|
+
try {
|
|
518
|
+
const choice = await api.pipelineOrchestrator.getClusterModelChoice.query({ stepId });
|
|
519
|
+
switch (choice.kind) {
|
|
520
|
+
case "stored": return {
|
|
521
|
+
kind: "model",
|
|
522
|
+
modelId: choice.modelId
|
|
523
|
+
};
|
|
524
|
+
case "absent": return {
|
|
525
|
+
kind: "model",
|
|
526
|
+
modelId: choice.defaultModelId
|
|
527
|
+
};
|
|
528
|
+
case "unreadable": return {
|
|
529
|
+
kind: "none",
|
|
530
|
+
reason: choice.reason
|
|
531
|
+
};
|
|
532
|
+
}
|
|
533
|
+
} catch (err) {
|
|
534
|
+
return {
|
|
535
|
+
kind: "none",
|
|
536
|
+
reason: `read-failed: ${err instanceof Error ? err.message : String(err)}`
|
|
537
|
+
};
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
//#endregion
|
|
541
|
+
//#region src/detection-pipeline/cluster-model-switch-log.ts
|
|
542
|
+
function* clusterScopedSteps(steps) {
|
|
543
|
+
for (const step of steps) {
|
|
544
|
+
if (step.definition.modelScope === "cluster") yield step;
|
|
545
|
+
yield* clusterScopedSteps(step.children);
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
var ClusterModelSwitchLog = class {
|
|
549
|
+
logger;
|
|
550
|
+
/** `deviceId|stepId` → the model that step last ran for that camera. */
|
|
551
|
+
lastRun = /* @__PURE__ */ new Map();
|
|
552
|
+
constructor(logger) {
|
|
553
|
+
this.logger = logger;
|
|
554
|
+
}
|
|
555
|
+
/** Record what `tree` runs for `deviceId`; say so when a cluster step changed model. */
|
|
556
|
+
note(deviceId, tree) {
|
|
557
|
+
if (deviceId <= 0) return;
|
|
558
|
+
for (const step of clusterScopedSteps(tree.roots)) {
|
|
559
|
+
const key = `${String(deviceId)}|${step.stepId}`;
|
|
560
|
+
const previous = this.lastRun.get(key);
|
|
561
|
+
if (previous === step.modelId) continue;
|
|
562
|
+
this.lastRun.set(key, step.modelId);
|
|
563
|
+
if (previous === void 0) continue;
|
|
564
|
+
this.logger.info("cluster-scoped step re-provisioned for this camera — now runs the new model", {
|
|
565
|
+
tags: { deviceId },
|
|
566
|
+
meta: {
|
|
567
|
+
stepId: step.stepId,
|
|
568
|
+
from: previous,
|
|
569
|
+
to: step.modelId
|
|
570
|
+
}
|
|
571
|
+
});
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
};
|
|
575
|
+
function* enabledStepIds(steps) {
|
|
576
|
+
for (const step of steps) {
|
|
577
|
+
if (!step.enabled) continue;
|
|
578
|
+
yield step.addonId;
|
|
579
|
+
if (step.children) yield* enabledStepIds(step.children);
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
/** The enabled steps of `steps` (any depth) whose cluster model is unknown. */
|
|
583
|
+
function unknownStepsNamed(steps, unknown) {
|
|
584
|
+
if (unknown.size === 0) return [];
|
|
585
|
+
return [...enabledStepIds(steps)].filter((id) => unknown.has(id));
|
|
586
|
+
}
|
|
587
|
+
/**
|
|
588
|
+
* One line per (camera, step) while a cluster-scoped step is REFUSED because
|
|
589
|
+
* the owner has not answered which model the cluster runs (D658). Said once per
|
|
590
|
+
* episode: when every step becomes known the memory is cleared, so the next
|
|
591
|
+
* episode is said again.
|
|
592
|
+
*/
|
|
593
|
+
var ClusterUnknownRefusalLog = class {
|
|
594
|
+
logger;
|
|
595
|
+
said = /* @__PURE__ */ new Set();
|
|
596
|
+
constructor(logger) {
|
|
597
|
+
this.logger = logger;
|
|
598
|
+
}
|
|
599
|
+
note(deviceId, steps, unknown) {
|
|
600
|
+
if (unknown.size === 0) {
|
|
601
|
+
if (this.said.size > 0) this.said = /* @__PURE__ */ new Set();
|
|
602
|
+
return;
|
|
603
|
+
}
|
|
604
|
+
if (deviceId <= 0) return;
|
|
605
|
+
for (const stepId of enabledStepIds(steps)) {
|
|
606
|
+
const reason = unknown.get(stepId);
|
|
607
|
+
if (reason === void 0) continue;
|
|
608
|
+
const key = `${String(deviceId)}|${stepId}`;
|
|
609
|
+
if (this.said.has(key)) continue;
|
|
610
|
+
this.said = new Set(this.said).add(key);
|
|
611
|
+
this.logger.warn("cluster-scoped step refused for this camera — the owner has not answered which model the cluster runs", {
|
|
612
|
+
tags: { deviceId },
|
|
613
|
+
meta: {
|
|
614
|
+
stepId,
|
|
615
|
+
reason
|
|
616
|
+
}
|
|
617
|
+
});
|
|
451
618
|
}
|
|
452
619
|
}
|
|
453
620
|
};
|
|
@@ -2147,7 +2314,8 @@ function collectFramePlaneSteps(steps, isDetailStep) {
|
|
|
2147
2314
|
}
|
|
2148
2315
|
/**
|
|
2149
2316
|
* Immutably remove from a frame-plane dispatch tree every enabled DETAIL
|
|
2150
|
-
* subtree (at any depth below the top level)
|
|
2317
|
+
* subtree (at any depth below the top level) that `isUnrunnable` — judged
|
|
2318
|
+
* WHOLE at its top, because its children ride its call (D657).
|
|
2151
2319
|
* Those steps can never load into the dispatch device's pool — leaving them
|
|
2152
2320
|
* in the tree makes `ensureModelsForSteps` fail the whole call on a format
|
|
2153
2321
|
* build that does not exist. They are NOT dropped work: each runs later as
|
|
@@ -2159,6 +2327,11 @@ function collectFramePlaneSteps(steps, isDetailStep) {
|
|
|
2159
2327
|
*/
|
|
2160
2328
|
function pruneUnrunnableDetailSteps(steps, isDetailStep, isUnrunnable) {
|
|
2161
2329
|
const prunedAddonIds = [];
|
|
2330
|
+
const removedAddonIds = [];
|
|
2331
|
+
const collect = (step) => {
|
|
2332
|
+
removedAddonIds.push(step.addonId);
|
|
2333
|
+
for (const child of step.children ?? []) collect(child);
|
|
2334
|
+
};
|
|
2162
2335
|
const walk = (nodes, topLevel) => {
|
|
2163
2336
|
const kept = [];
|
|
2164
2337
|
for (const step of nodes) {
|
|
@@ -2166,8 +2339,9 @@ function pruneUnrunnableDetailSteps(steps, isDetailStep, isUnrunnable) {
|
|
|
2166
2339
|
kept.push(step);
|
|
2167
2340
|
continue;
|
|
2168
2341
|
}
|
|
2169
|
-
if (!topLevel && isDetailStep(step.addonId) && isUnrunnable(step
|
|
2342
|
+
if (!topLevel && isDetailStep(step.addonId) && isUnrunnable(step)) {
|
|
2170
2343
|
prunedAddonIds.push(step.addonId);
|
|
2344
|
+
collect(step);
|
|
2171
2345
|
continue;
|
|
2172
2346
|
}
|
|
2173
2347
|
kept.push(step.children?.length ? {
|
|
@@ -2179,7 +2353,8 @@ function pruneUnrunnableDetailSteps(steps, isDetailStep, isUnrunnable) {
|
|
|
2179
2353
|
};
|
|
2180
2354
|
return {
|
|
2181
2355
|
steps: walk(steps, true),
|
|
2182
|
-
prunedAddonIds
|
|
2356
|
+
prunedAddonIds,
|
|
2357
|
+
removedAddonIds
|
|
2183
2358
|
};
|
|
2184
2359
|
}
|
|
2185
2360
|
//#endregion
|
|
@@ -2452,16 +2627,26 @@ var PipelineModelManager = class {
|
|
|
2452
2627
|
for (const { entry, step } of diff.unchanged) await this.reconcileDecode(entry, step.settings);
|
|
2453
2628
|
}
|
|
2454
2629
|
/**
|
|
2455
|
-
*
|
|
2456
|
-
*
|
|
2457
|
-
*
|
|
2458
|
-
*
|
|
2459
|
-
*
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
|
|
2630
|
+
* The warm variant that runs `(stepId, modelId)` — its handle AND the model
|
|
2631
|
+
* loaded at that pool index, which is the only honest answer to "which model
|
|
2632
|
+
* produced this output" (D658).
|
|
2633
|
+
*
|
|
2634
|
+
* No active-variant fallback, deliberately. The step-only lookup this
|
|
2635
|
+
* replaces (`getHandle(stepId)`, deleted) answered the step's ACTIVE
|
|
2636
|
+
* variant — the first one loaded after boot — and a tree that paired that
|
|
2637
|
+
* handle with the step's REQUESTED id stamped SigLIP2 on MobileCLIP vectors
|
|
2638
|
+
* for twenty minutes on 2026-09-27. A pair that is not resident throws,
|
|
2639
|
+
* naming both.
|
|
2640
|
+
*/
|
|
2641
|
+
getVariant(stepId, modelId) {
|
|
2642
|
+
const entry = this.loaded.get(stepId)?.get(modelId);
|
|
2643
|
+
if (entry === void 0) throw new Error(`Step "${stepId}" has no warm variant for model "${modelId}" in the inference pool — refusing rather than running another variant`);
|
|
2463
2644
|
this.touch(entry);
|
|
2464
|
-
return
|
|
2645
|
+
return {
|
|
2646
|
+
engine: this.pool.getHandle(entry.poolIndex),
|
|
2647
|
+
modelId: entry.modelId,
|
|
2648
|
+
poolIndex: entry.poolIndex
|
|
2649
|
+
};
|
|
2465
2650
|
}
|
|
2466
2651
|
/** True iff the step has any model loaded. */
|
|
2467
2652
|
isLoaded(stepId) {
|
|
@@ -2492,13 +2677,14 @@ var PipelineModelManager = class {
|
|
|
2492
2677
|
return this.activeByStep.get(stepId);
|
|
2493
2678
|
}
|
|
2494
2679
|
/**
|
|
2495
|
-
* Pool index for
|
|
2496
|
-
*
|
|
2497
|
-
*
|
|
2680
|
+
* Pool index for exactly `(stepId, modelId)`, or `null` when that pair is not
|
|
2681
|
+
* warm. Used by the inference fast paths that bypass `getVariant` and call
|
|
2682
|
+
* `pool.inferBatch` directly. The model is REQUIRED: the active-variant
|
|
2683
|
+
* fallback this had timed a benchmark on whichever variant was active (D658).
|
|
2498
2684
|
*/
|
|
2499
2685
|
getPoolIndex(stepId, modelId) {
|
|
2500
|
-
const entry = this.
|
|
2501
|
-
if (
|
|
2686
|
+
const entry = this.loaded.get(stepId)?.get(modelId);
|
|
2687
|
+
if (entry === void 0) return null;
|
|
2502
2688
|
this.touch(entry);
|
|
2503
2689
|
return entry.poolIndex;
|
|
2504
2690
|
}
|
|
@@ -2718,18 +2904,6 @@ var PipelineModelManager = class {
|
|
|
2718
2904
|
await this.unloadEntry(evicted);
|
|
2719
2905
|
}
|
|
2720
2906
|
}
|
|
2721
|
-
resolve(stepId, modelId) {
|
|
2722
|
-
const perStep = this.loaded.get(stepId);
|
|
2723
|
-
if (!perStep) return null;
|
|
2724
|
-
const targetModelId = modelId ?? this.activeByStep.get(stepId);
|
|
2725
|
-
if (!targetModelId) return null;
|
|
2726
|
-
return perStep.get(targetModelId) ?? null;
|
|
2727
|
-
}
|
|
2728
|
-
resolveOrThrow(stepId, modelId) {
|
|
2729
|
-
const entry = this.resolve(stepId, modelId);
|
|
2730
|
-
if (!entry) throw new Error(`Step "${stepId}"${modelId ? ` (model "${modelId}")` : ""} is not loaded in the inference pool`);
|
|
2731
|
-
return entry;
|
|
2732
|
-
}
|
|
2733
2907
|
touch(entry) {
|
|
2734
2908
|
entry.lruTick = ++this.lruClock;
|
|
2735
2909
|
}
|
|
@@ -2925,16 +3099,15 @@ var EngineFactory = class {
|
|
|
2925
3099
|
await this.poolManager.applyConfig(steps);
|
|
2926
3100
|
}
|
|
2927
3101
|
/**
|
|
2928
|
-
*
|
|
2929
|
-
*
|
|
2930
|
-
*
|
|
2931
|
-
* paths picking a non-runtime model).
|
|
3102
|
+
* The resident variant for exactly `(stepId, modelId)`, with the model it
|
|
3103
|
+
* runs — never the step's active variant in its place (D658). The executable
|
|
3104
|
+
* tree is built from this, and a result's model stamp comes from it.
|
|
2932
3105
|
*
|
|
2933
|
-
* @throws if
|
|
3106
|
+
* @throws if that exact pair is not warm.
|
|
2934
3107
|
*/
|
|
2935
|
-
|
|
3108
|
+
getStepVariant(stepId, modelId) {
|
|
2936
3109
|
if (!this.poolManager) throw new Error("EngineFactory not initialized");
|
|
2937
|
-
return this.poolManager.
|
|
3110
|
+
return this.poolManager.getVariant(stepId, modelId);
|
|
2938
3111
|
}
|
|
2939
3112
|
/** Check if a step is loaded (any variant). */
|
|
2940
3113
|
isLoaded(stepId) {
|
|
@@ -3038,7 +3211,7 @@ var EngineFactory = class {
|
|
|
3038
3211
|
async batchInferRaw(stepId, items, modelId, frameId = 0) {
|
|
3039
3212
|
if (!this.poolManager) throw new Error("EngineFactory.batchInferRaw: pool not initialized");
|
|
3040
3213
|
const idx = this.poolManager.getPoolIndex(stepId, modelId);
|
|
3041
|
-
if (idx === null) throw new Error(`EngineFactory.batchInferRaw: step "${stepId}"
|
|
3214
|
+
if (idx === null) throw new Error(`EngineFactory.batchInferRaw: step "${stepId}" (model "${modelId}") is not loaded`);
|
|
3042
3215
|
return this.poolManager.getPool().inferBatch(idx, items, frameId);
|
|
3043
3216
|
}
|
|
3044
3217
|
async cacheFrameInPool(raw, width, height, format) {
|
|
@@ -3048,7 +3221,7 @@ var EngineFactory = class {
|
|
|
3048
3221
|
async inferCached(stepId, frameId, modelId) {
|
|
3049
3222
|
if (!this.poolManager) throw new Error("inferCached: pool not available");
|
|
3050
3223
|
const idx = this.poolManager.getPoolIndex(stepId, modelId);
|
|
3051
|
-
if (idx === null) throw new Error(`inferCached: step "${stepId}"
|
|
3224
|
+
if (idx === null) throw new Error(`inferCached: step "${stepId}" (model "${modelId}") not loaded`);
|
|
3052
3225
|
return this.poolManager.getPool().inferCached(idx, frameId);
|
|
3053
3226
|
}
|
|
3054
3227
|
async uncacheFrame(frameId) {
|
|
@@ -3943,13 +4116,16 @@ function checkReplayPin(input) {
|
|
|
3943
4116
|
}
|
|
3944
4117
|
var PRIMARY_ROOT_STEP = "object-detection";
|
|
3945
4118
|
/**
|
|
3946
|
-
* The
|
|
3947
|
-
*
|
|
3948
|
-
*
|
|
4119
|
+
* The same rule over an EXECUTABLE tree's roots (D658): their model ids are the
|
|
4120
|
+
* pool variants that will run — after resolution, per-step loads and drops —
|
|
4121
|
+
* so this is what a live `runPipeline` stamps. The tree holds only enabled
|
|
4122
|
+
* video roots already.
|
|
3949
4123
|
*/
|
|
3950
|
-
function
|
|
3951
|
-
|
|
3952
|
-
|
|
4124
|
+
function resolveTreeRootModelId(roots) {
|
|
4125
|
+
return primaryRootModelId(roots);
|
|
4126
|
+
}
|
|
4127
|
+
function primaryRootModelId(roots) {
|
|
4128
|
+
return (roots.find((s) => s.stepId === PRIMARY_ROOT_STEP) ?? roots[0])?.modelId;
|
|
3953
4129
|
}
|
|
3954
4130
|
//#endregion
|
|
3955
4131
|
//#region src/detection-pipeline/engine-provisioner.ts
|
|
@@ -6742,26 +6918,47 @@ function resolveEffectiveClassMap(definition, modelId, resolveModel) {
|
|
|
6742
6918
|
* Build an executable tree from user config.
|
|
6743
6919
|
*
|
|
6744
6920
|
* @param steps - User-configured pipeline steps (from PipelineDefaultStep[])
|
|
6745
|
-
* @param getEngine -
|
|
6746
|
-
* Throws if
|
|
6921
|
+
* @param getEngine - Returns the engine that runs exactly `(stepId, modelId)`
|
|
6922
|
+
* and the model it runs. Throws if that pair is not loaded.
|
|
6747
6923
|
*/
|
|
6748
|
-
function buildExecutableTree(steps, getEngine, resolveModel) {
|
|
6749
|
-
return { roots: steps.filter((s) => s.enabled).filter((s) => s.slot !== "audio-classifier").map((s) => buildNode(s, getEngine, resolveModel)) };
|
|
6924
|
+
function buildExecutableTree(steps, getEngine, resolveModel, onMissingVariant) {
|
|
6925
|
+
return { roots: steps.filter((s) => s.enabled).filter((s) => s.slot !== "audio-classifier").map((s) => buildNode(s, getEngine, resolveModel, onMissingVariant)) };
|
|
6926
|
+
}
|
|
6927
|
+
function subtreeIds(step) {
|
|
6928
|
+
return [step.addonId, ...(step.children ?? []).filter((c) => c.enabled).flatMap((c) => subtreeIds(c))];
|
|
6929
|
+
}
|
|
6930
|
+
function buildChild(child, parentStepId, getEngine, resolveModel, onMissingVariant) {
|
|
6931
|
+
if (onMissingVariant === void 0) return buildNode(child, getEngine, resolveModel, onMissingVariant);
|
|
6932
|
+
let variant;
|
|
6933
|
+
try {
|
|
6934
|
+
variant = getEngine(child.addonId, child.modelId);
|
|
6935
|
+
} catch (err) {
|
|
6936
|
+
onMissingVariant({
|
|
6937
|
+
step: child,
|
|
6938
|
+
stepId: child.addonId,
|
|
6939
|
+
modelId: child.modelId,
|
|
6940
|
+
parentStepId,
|
|
6941
|
+
droppedSubtree: subtreeIds(child),
|
|
6942
|
+
error: err instanceof Error ? err.message : String(err)
|
|
6943
|
+
});
|
|
6944
|
+
return null;
|
|
6945
|
+
}
|
|
6946
|
+
return buildNode(child, getEngine, resolveModel, onMissingVariant, variant);
|
|
6750
6947
|
}
|
|
6751
|
-
function buildNode(step, getEngine, resolveModel) {
|
|
6948
|
+
function buildNode(step, getEngine, resolveModel, onMissingVariant, resolved) {
|
|
6752
6949
|
const definition = getStepDefinition(step.addonId);
|
|
6753
|
-
const
|
|
6754
|
-
const children = (step.children ?? []).filter((c) => c.enabled).map((c) =>
|
|
6950
|
+
const variant = resolved ?? getEngine(step.addonId, step.modelId);
|
|
6951
|
+
const children = (step.children ?? []).filter((c) => c.enabled).map((c) => buildChild(c, step.addonId, getEngine, resolveModel, onMissingVariant)).filter((c) => c !== null);
|
|
6755
6952
|
const mergedSettings = {
|
|
6756
6953
|
...collectSchemaDefaults(step.addonId),
|
|
6757
6954
|
...step.settings
|
|
6758
6955
|
};
|
|
6759
|
-
const effectiveClassMap = resolveEffectiveClassMap(definition,
|
|
6956
|
+
const effectiveClassMap = resolveEffectiveClassMap(definition, variant.modelId, resolveModel);
|
|
6760
6957
|
return {
|
|
6761
6958
|
stepId: step.addonId,
|
|
6762
6959
|
definition,
|
|
6763
|
-
engine,
|
|
6764
|
-
modelId:
|
|
6960
|
+
engine: variant.engine,
|
|
6961
|
+
modelId: variant.modelId,
|
|
6765
6962
|
inputClasses: definition.inputClasses ?? [],
|
|
6766
6963
|
enabled: step.enabled,
|
|
6767
6964
|
children,
|
|
@@ -6791,43 +6988,6 @@ function walkFieldsForDefaults(fields, out) {
|
|
|
6791
6988
|
}
|
|
6792
6989
|
}
|
|
6793
6990
|
//#endregion
|
|
6794
|
-
//#region src/detection-pipeline/registry/custom-models.ts
|
|
6795
|
-
/**
|
|
6796
|
-
* Group a flat list of custom-model descriptors (as returned by the
|
|
6797
|
-
* `custom-model-registry` collection cap) into a `stepId → entries` map for
|
|
6798
|
-
* the picker / resolution union. Pure; order within a step preserved.
|
|
6799
|
-
*/
|
|
6800
|
-
function groupCustomModelsByStep(descriptors) {
|
|
6801
|
-
const byStep = /* @__PURE__ */ new Map();
|
|
6802
|
-
for (const d of descriptors) {
|
|
6803
|
-
const arr = byStep.get(d.stepId) ?? [];
|
|
6804
|
-
arr.push(d.entry);
|
|
6805
|
-
byStep.set(d.stepId, arr);
|
|
6806
|
-
}
|
|
6807
|
-
return byStep;
|
|
6808
|
-
}
|
|
6809
|
-
/**
|
|
6810
|
-
* Union a step's static catalog models with operator-registered custom
|
|
6811
|
-
* models. On an `id` collision the static catalog entry wins — a custom
|
|
6812
|
-
* model can never shadow a built-in one.
|
|
6813
|
-
*
|
|
6814
|
-
* Pure + side-effect-free so it can be unit-tested in isolation and called
|
|
6815
|
-
* from the (free) `buildSchemaSlots` builder without any addon context.
|
|
6816
|
-
*/
|
|
6817
|
-
function mergeCustomModels(staticModels, customModels) {
|
|
6818
|
-
const seen = new Set(staticModels.map((m) => m.id));
|
|
6819
|
-
const merged = [...staticModels];
|
|
6820
|
-
for (const m of customModels) {
|
|
6821
|
-
if (seen.has(m.id)) continue;
|
|
6822
|
-
seen.add(m.id);
|
|
6823
|
-
merged.push({
|
|
6824
|
-
...m,
|
|
6825
|
-
provider: inferModelProvider(m)
|
|
6826
|
-
});
|
|
6827
|
-
}
|
|
6828
|
-
return merged;
|
|
6829
|
-
}
|
|
6830
|
-
//#endregion
|
|
6831
6991
|
//#region src/detection-pipeline/cluster-model-resolution.ts
|
|
6832
6992
|
/**
|
|
6833
6993
|
* The cluster model rule, enforced on the node that has to obey it.
|
|
@@ -6932,8 +7092,10 @@ function collectSubstitutions(steps, format) {
|
|
|
6932
7092
|
return result;
|
|
6933
7093
|
}
|
|
6934
7094
|
/**
|
|
6935
|
-
* Return one {@link ZeroBuildIssue} per step in `steps`
|
|
6936
|
-
*
|
|
7095
|
+
* Return one {@link ZeroBuildIssue} per step in `steps` that a pool of `format`
|
|
7096
|
+
* cannot run: neither the model it names nor any NON-legacy model of its
|
|
7097
|
+
* catalog (via `getStepDef`) ships a build for `format`
|
|
7098
|
+
* (`stepModelRunsOnFormat`, D657). `steps` is
|
|
6937
7099
|
* expected to already be flattened + filtered to the set the caller cares
|
|
6938
7100
|
* about (e.g. enabled video steps) — this does not recurse into children or
|
|
6939
7101
|
* filter by `enabled`, unlike {@link collectSubstitutions}.
|
|
@@ -6954,7 +7116,7 @@ function collectZeroBuildIssues(steps, format, getStepDef = getStepDefinition) {
|
|
|
6954
7116
|
} catch {
|
|
6955
7117
|
continue;
|
|
6956
7118
|
}
|
|
6957
|
-
if (!def.models.
|
|
7119
|
+
if (!stepModelRunsOnFormat(def.models, step.modelId, format)) result.push({
|
|
6958
7120
|
addonId: step.addonId,
|
|
6959
7121
|
format
|
|
6960
7122
|
});
|
|
@@ -7004,11 +7166,20 @@ function collectUnknownAddonIssues(steps, getStepDef = getStepDefinition) {
|
|
|
7004
7166
|
* `diagnostics.substitutions` instead of happening silently. The caller
|
|
7005
7167
|
* (the provider) owns logging + dedup for both.
|
|
7006
7168
|
*/
|
|
7007
|
-
function resolveInputSteps(steps, format, engine, clusterModels, clusterSettings) {
|
|
7169
|
+
function resolveInputSteps(steps, format, engine, clusterModels, clusterSettings, unknownClusterSteps) {
|
|
7008
7170
|
const resolvedSteps = [];
|
|
7009
7171
|
const unknownAddonIds = [];
|
|
7010
7172
|
const substitutions = [];
|
|
7011
7173
|
const clusterRefusals = [];
|
|
7174
|
+
const clusterUnknown = [];
|
|
7175
|
+
const recurse = (children) => {
|
|
7176
|
+
const r = resolveInputSteps(children, format, engine, clusterModels, clusterSettings, unknownClusterSteps);
|
|
7177
|
+
unknownAddonIds.push(...r.diagnostics.unknownAddonIds);
|
|
7178
|
+
substitutions.push(...r.diagnostics.substitutions);
|
|
7179
|
+
clusterRefusals.push(...r.diagnostics.clusterRefusals);
|
|
7180
|
+
clusterUnknown.push(...r.diagnostics.clusterUnknown);
|
|
7181
|
+
return r;
|
|
7182
|
+
};
|
|
7012
7183
|
for (const s of steps) {
|
|
7013
7184
|
let def;
|
|
7014
7185
|
try {
|
|
@@ -7017,6 +7188,10 @@ function resolveInputSteps(steps, format, engine, clusterModels, clusterSettings
|
|
|
7017
7188
|
unknownAddonIds.push(s.addonId);
|
|
7018
7189
|
continue;
|
|
7019
7190
|
}
|
|
7191
|
+
if (def.modelScope === "cluster" && unknownClusterSteps?.has(s.addonId) === true) {
|
|
7192
|
+
clusterUnknown.push(s.addonId);
|
|
7193
|
+
continue;
|
|
7194
|
+
}
|
|
7020
7195
|
if (clusterModels !== void 0) {
|
|
7021
7196
|
const verdict = resolveClusterStepModel(s.addonId, clusterModels, format, () => def);
|
|
7022
7197
|
if (verdict.kind !== "node-scoped") {
|
|
@@ -7026,12 +7201,7 @@ function resolveInputSteps(steps, format, engine, clusterModels, clusterSettings
|
|
|
7026
7201
|
format: verdict.format,
|
|
7027
7202
|
formatsShipped: verdict.formatsShipped
|
|
7028
7203
|
});
|
|
7029
|
-
const childResult = s.children ?
|
|
7030
|
-
if (childResult) {
|
|
7031
|
-
unknownAddonIds.push(...childResult.diagnostics.unknownAddonIds);
|
|
7032
|
-
substitutions.push(...childResult.diagnostics.substitutions);
|
|
7033
|
-
clusterRefusals.push(...childResult.diagnostics.clusterRefusals);
|
|
7034
|
-
}
|
|
7204
|
+
const childResult = s.children ? recurse(s.children) : null;
|
|
7035
7205
|
const settings = overlayClusterStepSettings(s.addonId, s.settings, clusterSettings ?? {});
|
|
7036
7206
|
resolvedSteps.push({
|
|
7037
7207
|
addonId: s.addonId,
|
|
@@ -7055,12 +7225,7 @@ function resolveInputSteps(steps, format, engine, clusterModels, clusterSettings
|
|
|
7055
7225
|
running: runningModelId,
|
|
7056
7226
|
format
|
|
7057
7227
|
});
|
|
7058
|
-
const childResult = s.children ?
|
|
7059
|
-
if (childResult) {
|
|
7060
|
-
unknownAddonIds.push(...childResult.diagnostics.unknownAddonIds);
|
|
7061
|
-
substitutions.push(...childResult.diagnostics.substitutions);
|
|
7062
|
-
clusterRefusals.push(...childResult.diagnostics.clusterRefusals);
|
|
7063
|
-
}
|
|
7228
|
+
const childResult = s.children ? recurse(s.children) : null;
|
|
7064
7229
|
const settings = overlayClusterStepSettings(s.addonId, s.settings, clusterSettings ?? {});
|
|
7065
7230
|
resolvedSteps.push({
|
|
7066
7231
|
addonId: s.addonId,
|
|
@@ -7079,7 +7244,8 @@ function resolveInputSteps(steps, format, engine, clusterModels, clusterSettings
|
|
|
7079
7244
|
diagnostics: {
|
|
7080
7245
|
unknownAddonIds,
|
|
7081
7246
|
substitutions,
|
|
7082
|
-
clusterRefusals
|
|
7247
|
+
clusterRefusals,
|
|
7248
|
+
clusterUnknown
|
|
7083
7249
|
}
|
|
7084
7250
|
};
|
|
7085
7251
|
}
|
|
@@ -7322,6 +7488,62 @@ function stepModelKey(step) {
|
|
|
7322
7488
|
function stateOf(verdict) {
|
|
7323
7489
|
return verdict.kind === "refused" ? "refused" : `failed:${verdict.generation}`;
|
|
7324
7490
|
}
|
|
7491
|
+
/** The load policy of a `runPipeline` call — see {@link DispatchLoadPolicy}. */
|
|
7492
|
+
function dispatchLoadPolicyOf(input) {
|
|
7493
|
+
return input.deviceId !== void 0 && input.replay !== true ? "partial" : "all-or-nothing";
|
|
7494
|
+
}
|
|
7495
|
+
/**
|
|
7496
|
+
* An all-or-nothing call lost steps to failed loads (D657). Names every one,
|
|
7497
|
+
* so a benchmark never reports timings for a tree it did not run.
|
|
7498
|
+
*/
|
|
7499
|
+
var DispatchStepsNotLoadedError = class extends Error {
|
|
7500
|
+
failedSteps;
|
|
7501
|
+
constructor(failures) {
|
|
7502
|
+
const named = failures.map((f) => `${f.step.addonId}/${f.step.modelId} (${f.error instanceof Error ? f.error.message : String(f.error)})`);
|
|
7503
|
+
super(`pipeline steps failed to load — the call runs all of them or none: ${named.join("; ")}`, { cause: failures[0]?.error });
|
|
7504
|
+
this.name = "DispatchStepsNotLoadedError";
|
|
7505
|
+
this.failedSteps = failures.map((f) => `${f.step.addonId}/${f.step.modelId}`);
|
|
7506
|
+
}
|
|
7507
|
+
};
|
|
7508
|
+
/**
|
|
7509
|
+
* A step's model artifact cannot be made present for the pool's format — no
|
|
7510
|
+
* build for that format, a custom model missing from this node, a download
|
|
7511
|
+
* that failed. Named by step and model so the failure is charged to THAT
|
|
7512
|
+
* model only (D657): a plain `Error` was charged to every model of the set,
|
|
7513
|
+
* and one face model with no tflite build backed the Coral's root detector
|
|
7514
|
+
* off for minutes.
|
|
7515
|
+
*/
|
|
7516
|
+
var StepModelArtifactError = class extends Error {
|
|
7517
|
+
stepId;
|
|
7518
|
+
modelId;
|
|
7519
|
+
constructor(stepId, modelId, cause) {
|
|
7520
|
+
super(cause instanceof Error ? cause.message : String(cause), { cause });
|
|
7521
|
+
this.name = "StepModelArtifactError";
|
|
7522
|
+
this.stepId = stepId;
|
|
7523
|
+
this.modelId = modelId;
|
|
7524
|
+
}
|
|
7525
|
+
};
|
|
7526
|
+
/** The one model a failed load is charged to, when the error names it. */
|
|
7527
|
+
function failedModelOf(err) {
|
|
7528
|
+
if (err instanceof StepVariantLoadError || err instanceof StepModelArtifactError) return `${err.stepId}/${err.modelId}`;
|
|
7529
|
+
return null;
|
|
7530
|
+
}
|
|
7531
|
+
/**
|
|
7532
|
+
* `steps` without the subtrees of the steps whose model did not load (D657).
|
|
7533
|
+
* A step's children consume its output, so they leave with it. Matched by
|
|
7534
|
+
* INSTANCE: two steps of one addon in a tree are two steps.
|
|
7535
|
+
*/
|
|
7536
|
+
function withoutFailedSteps(steps, failed) {
|
|
7537
|
+
const kept = [];
|
|
7538
|
+
for (const step of steps) {
|
|
7539
|
+
if (failed.has(step)) continue;
|
|
7540
|
+
kept.push(step.children && step.children.length > 0 ? {
|
|
7541
|
+
...step,
|
|
7542
|
+
children: withoutFailedSteps(step.children, failed)
|
|
7543
|
+
} : step);
|
|
7544
|
+
}
|
|
7545
|
+
return kept;
|
|
7546
|
+
}
|
|
7325
7547
|
/**
|
|
7326
7548
|
* The reason a condemned pool is charged against its device's restart budget.
|
|
7327
7549
|
* A pool recycled because it HUNG (D653) names the hang — `inference device
|
|
@@ -7429,18 +7651,18 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
7429
7651
|
* paths don't issue a cap round-trip on every call. Empty map = no provider
|
|
7430
7652
|
* (or a query failure) → behaviour identical to the static-catalog-only path.
|
|
7431
7653
|
*/
|
|
7432
|
-
|
|
7654
|
+
customModels = new CustomModelCatalog({ warn: (message, extras) => this.log.warn(message, extras) });
|
|
7433
7655
|
/**
|
|
7434
7656
|
* SYNC custom-model lookup handed to every live {@link EngineFactory} so
|
|
7435
7657
|
* `buildPoolModelConfigForStep` can resolve an operator-registered model at
|
|
7436
|
-
* pool-load time. Reads the {@link
|
|
7658
|
+
* pool-load time. Reads the {@link customModels} snapshot — every load
|
|
7437
7659
|
* path (`ensureModelsForSteps`, `ensureModelsForCurrentSteps`) refreshes the
|
|
7438
7660
|
* cache via `resolveModelEntry` BEFORE `loadAdditional`/`applyConfig`, so
|
|
7439
7661
|
* the snapshot is fresh when the pool asks. Without this seam a selected
|
|
7440
7662
|
* custom model failed the WHOLE pool applyConfig with
|
|
7441
7663
|
* `Model "X" not found in step "Y" catalog`.
|
|
7442
7664
|
*/
|
|
7443
|
-
customModelResolver = (stepId, modelId) => this.
|
|
7665
|
+
customModelResolver = (stepId, modelId) => this.customModels.current().get(stepId)?.find((m) => m.id === modelId);
|
|
7444
7666
|
/**
|
|
7445
7667
|
* Per-device {@link DeviceProxy} cache used for zone gating at the
|
|
7446
7668
|
* runtime path. Reads `state.zones.value` + `state.zoneRules.value`
|
|
@@ -7463,6 +7685,10 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
7463
7685
|
info: (message, extras) => this.log.info(message, extras),
|
|
7464
7686
|
warn: (message, extras) => this.log.warn(message, extras)
|
|
7465
7687
|
} });
|
|
7688
|
+
/** One line per camera when a cluster-scoped step starts running another model (D658). */
|
|
7689
|
+
clusterModelSwitchLog = new ClusterModelSwitchLog({ info: (message, extras) => this.log.info(message, extras) });
|
|
7690
|
+
/** One line per (camera, step) while a cluster step is refused as unknown (D658). */
|
|
7691
|
+
clusterUnknownLog = new ClusterUnknownRefusalLog({ warn: (message, extras) => this.log.warn(message, extras) });
|
|
7466
7692
|
/**
|
|
7467
7693
|
* Last logged "step configured" signature — used to dedupe the
|
|
7468
7694
|
* per-step debug trail so it only fires on actual config CHANGE
|
|
@@ -7929,7 +8155,7 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
7929
8155
|
if (!steps) return;
|
|
7930
8156
|
const format = this.currentEngine.format;
|
|
7931
8157
|
const substitutionIssues = this.getActiveModelSubstitutions().map((s) => `${s.addonId}: chose ${s.chosen}, running ${s.running} (${s.format})`);
|
|
7932
|
-
const zeroBuildIssues = collectZeroBuildIssues(flattenSteps(steps), format).map((i) => `${i.addonId}: no model has a ${i.format} build`);
|
|
8158
|
+
const zeroBuildIssues = collectZeroBuildIssues(flattenSteps(steps), format, this.mergedStepDefOrThrow).map((i) => `${i.addonId}: no model has a ${i.format} build`);
|
|
7933
8159
|
this.configIssues = [...substitutionIssues, ...zeroBuildIssues];
|
|
7934
8160
|
} catch (err) {
|
|
7935
8161
|
this.configIssues = [];
|
|
@@ -8063,24 +8289,21 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
8063
8289
|
* a warning — callers then behave exactly like the static-catalog path.
|
|
8064
8290
|
*/
|
|
8065
8291
|
async getCustomModels() {
|
|
8066
|
-
|
|
8067
|
-
if (this.customModelsCache && now - this.customModelsCache.at < 5e3) return this.customModelsCache.byStep;
|
|
8068
|
-
let byStep = /* @__PURE__ */ new Map();
|
|
8069
|
-
try {
|
|
8070
|
-
const api = this.addonCtx?.api;
|
|
8071
|
-
if (api) {
|
|
8072
|
-
if ((await api.addons.listCapabilityProviders.query({ capName: "custom-model-registry" })).some((p) => p.isActive)) byStep = groupCustomModelsByStep(await api.customModelRegistry.listModels.query());
|
|
8073
|
-
}
|
|
8074
|
-
} catch (err) {
|
|
8075
|
-
this.log.warn("custom-model-registry query failed — using static catalog only", { meta: { error: errMsg(err) } });
|
|
8076
|
-
}
|
|
8077
|
-
this.customModelsCache = {
|
|
8078
|
-
at: now,
|
|
8079
|
-
byStep
|
|
8080
|
-
};
|
|
8081
|
-
return byStep;
|
|
8292
|
+
return this.customModels.refresh(this.addonCtx?.api);
|
|
8082
8293
|
}
|
|
8083
8294
|
/**
|
|
8295
|
+
* A step's definition MERGED with its custom models, synchronously, from
|
|
8296
|
+
* the last custom-model read (D657 round 2). Every placement gate here
|
|
8297
|
+
* judges this catalog — the one the orchestrator's schema carries.
|
|
8298
|
+
*/
|
|
8299
|
+
mergedStepDef = (addonId) => this.customModels.stepDefinition(addonId);
|
|
8300
|
+
/** {@link mergedStepDef} as the throwing lookup `collectZeroBuildIssues` takes. */
|
|
8301
|
+
mergedStepDefOrThrow = (addonId) => {
|
|
8302
|
+
const def = this.mergedStepDef(addonId);
|
|
8303
|
+
if (def === null) throw new Error(`Unknown pipeline step: "${addonId}"`);
|
|
8304
|
+
return def;
|
|
8305
|
+
};
|
|
8306
|
+
/**
|
|
8084
8307
|
* Resolve a model id within a step to a catalog entry — static catalog
|
|
8085
8308
|
* first, then the custom registry. Returns undefined if neither has it.
|
|
8086
8309
|
*/
|
|
@@ -8271,7 +8494,7 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
8271
8494
|
const enabledSteps = this.pruneToEnabledSteps(steps);
|
|
8272
8495
|
const substitutions = collectSubstitutions(enabledSteps, format);
|
|
8273
8496
|
const unknownAddonIssues = collectUnknownAddonIssues(enabledSteps);
|
|
8274
|
-
const zeroBuildIssues = collectZeroBuildIssues(this.flattenEnabledVideoStepInputs(steps), format);
|
|
8497
|
+
const zeroBuildIssues = collectZeroBuildIssues(this.flattenEnabledVideoStepInputs(steps), format, this.mergedStepDefOrThrow);
|
|
8275
8498
|
const issues = [...unknownAddonIssues.map((u) => ({
|
|
8276
8499
|
addonId: u.addonId,
|
|
8277
8500
|
kind: "unknown-addon",
|
|
@@ -8588,6 +8811,12 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
8588
8811
|
}
|
|
8589
8812
|
async runPipeline(input, onProgress) {
|
|
8590
8813
|
await this.clusterModels.refresh(this.addonCtx?.api);
|
|
8814
|
+
this.clusterUnknownLog.note(input.deviceId ?? 0, input.steps, this.clusterModels.unknownSteps());
|
|
8815
|
+
if (dispatchLoadPolicyOf(input) === "all-or-nothing") {
|
|
8816
|
+
const unknown = unknownStepsNamed(input.steps, this.clusterModels.unknownSteps());
|
|
8817
|
+
if (unknown.length > 0) throw new Error(`cluster-model-unknown: the owner has not answered which model the cluster runs for ${unknown.join(", ")} — the call runs all of its steps or none`);
|
|
8818
|
+
}
|
|
8819
|
+
await this.getCustomModels();
|
|
8591
8820
|
const nodeId = this.addonCtx?.kernel?.localNodeId ?? "hub";
|
|
8592
8821
|
const sessionId = input.sessionId ?? `run-${Date.now()}-${Math.random().toString(36).slice(2, 10)}`;
|
|
8593
8822
|
const isRuntime = Boolean(input.frame || input.frameRef);
|
|
@@ -8752,11 +8981,6 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
8752
8981
|
}
|
|
8753
8982
|
const enabledSteps = flattenSteps(benchmarkSteps);
|
|
8754
8983
|
emit(`Pipeline: ${enabledSteps.length} step(s) — ${enabledSteps.map((s) => s.addonId).join(" → ")}`);
|
|
8755
|
-
const rootModelId = resolveRootModelId(benchmarkSteps);
|
|
8756
|
-
const stampRoot = (r) => rootModelId === void 0 ? r : {
|
|
8757
|
-
...r,
|
|
8758
|
-
rootModelId
|
|
8759
|
-
};
|
|
8760
8984
|
if (input.replay === true) {
|
|
8761
8985
|
const verdict = checkReplayPin({
|
|
8762
8986
|
requested: input.steps,
|
|
@@ -8798,24 +9022,51 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
8798
9022
|
const dispatchEngine = dispatchDeviceKey ? resolveDeviceEngine(dispatchDeviceKey) : runEngine;
|
|
8799
9023
|
const dispatchFactory = dispatchDeviceKey ? await this.resolveDeviceFactory(dispatchDeviceKey) : this.engineFactory;
|
|
8800
9024
|
const needed = enabledSteps.filter((s) => dispatchFactory.needsPoolUpdate(s));
|
|
9025
|
+
let runSteps = benchmarkSteps;
|
|
9026
|
+
const loadPolicy = dispatchLoadPolicyOf(input);
|
|
8801
9027
|
if (needed.length > 0) {
|
|
8802
|
-
|
|
8803
|
-
|
|
8804
|
-
|
|
9028
|
+
const dispatchDeviceKeyLabel = deviceKeyOf(dispatchEngine);
|
|
9029
|
+
const toIssue = needed.filter((s) => this.loadGovernor.check(dispatchFactory, dispatchDeviceKeyLabel, [stepModelKey(s)]).kind === "go");
|
|
9030
|
+
if (toIssue.length > 0) this.log.info("Benchmark: models to load", { meta: {
|
|
9031
|
+
count: toIssue.length,
|
|
9032
|
+
models: toIssue.map((s) => `${s.addonId}/${s.modelId}`)
|
|
8805
9033
|
} });
|
|
8806
|
-
for (const s of
|
|
9034
|
+
for (const s of toIssue) emit(`Loading model: ${s.addonName} (${s.modelId})...`, {
|
|
8807
9035
|
step: s.addonId,
|
|
8808
9036
|
addonId: s.addonId,
|
|
8809
9037
|
modelId: s.modelId
|
|
8810
9038
|
});
|
|
8811
|
-
await this.
|
|
9039
|
+
const failures = await this.loadDispatchModels(benchmarkSteps, dispatchFactory, dispatchEngine.format, {
|
|
8812
9040
|
...input.deviceId !== void 0 ? { deviceId: input.deviceId } : {},
|
|
8813
|
-
deviceKey:
|
|
8814
|
-
});
|
|
8815
|
-
|
|
9041
|
+
deviceKey: dispatchDeviceKeyLabel
|
|
9042
|
+
}, loadPolicy);
|
|
9043
|
+
runSteps = stepsSurvivingLoad(benchmarkSteps, failures, loadPolicy);
|
|
9044
|
+
emit(failures.length === 0 ? `All models loaded` : `Models loaded (${failures.length} failed)`);
|
|
8816
9045
|
}
|
|
8817
9046
|
emit("Running inference...");
|
|
8818
|
-
const
|
|
9047
|
+
const notWarm = [];
|
|
9048
|
+
const tree = buildExecutableTree(runSteps, (stepId, modelId) => dispatchFactory.getStepVariant(stepId, modelId), this.customModelResolver, (drop) => notWarm.push({
|
|
9049
|
+
step: drop.step,
|
|
9050
|
+
error: new Error(drop.error)
|
|
9051
|
+
}));
|
|
9052
|
+
if (notWarm.length > 0) {
|
|
9053
|
+
if (loadPolicy === "all-or-nothing") throw new DispatchStepsNotLoadedError(notWarm);
|
|
9054
|
+
const dispatchDeviceKeyLabel = deviceKeyOf(dispatchEngine);
|
|
9055
|
+
for (const failure of notWarm) {
|
|
9056
|
+
const model = stepModelKey(failure.step);
|
|
9057
|
+
this.reportDispatchLoadLoss({
|
|
9058
|
+
...input.deviceId !== void 0 ? { deviceId: input.deviceId } : {},
|
|
9059
|
+
deviceKey: dispatchDeviceKeyLabel,
|
|
9060
|
+
outcome: "step-dropped"
|
|
9061
|
+
}, dispatchDeviceKeyLabel, [model], model, "not-warm", errMsg(failure.error));
|
|
9062
|
+
}
|
|
9063
|
+
}
|
|
9064
|
+
const rootModelId = resolveTreeRootModelId(tree.roots);
|
|
9065
|
+
const stampRoot = (r) => rootModelId === void 0 ? r : {
|
|
9066
|
+
...r,
|
|
9067
|
+
rootModelId
|
|
9068
|
+
};
|
|
9069
|
+
if (input.replay !== true) this.clusterModelSwitchLog.note(input.deviceId ?? 0, tree);
|
|
8819
9070
|
setupMs = performance.now() - wallT0 - decodeMs;
|
|
8820
9071
|
const effectiveDeviceId = input.deviceId ?? 0;
|
|
8821
9072
|
const deviceOverrides = effectiveDeviceId > 0 ? await this.deviceOverrides.resolve(effectiveDeviceId) : {};
|
|
@@ -8921,7 +9172,7 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
8921
9172
|
backend: this.currentEngine.backend,
|
|
8922
9173
|
device: this.currentEngine.device ?? null
|
|
8923
9174
|
}, clusterRefusalsOut) {
|
|
8924
|
-
const { steps: resolvedSteps, diagnostics } = resolveInputSteps(steps, format, engine, this.clusterModels.current(), this.clusterModels.currentSettings());
|
|
9175
|
+
const { steps: resolvedSteps, diagnostics } = resolveInputSteps(steps, format, engine, this.clusterModels.current(), this.clusterModels.currentSettings(), this.clusterModels.unknownSteps());
|
|
8925
9176
|
if (clusterRefusalsOut) clusterRefusalsOut.push(...diagnostics.clusterRefusals);
|
|
8926
9177
|
for (const addonId of diagnostics.unknownAddonIds) this.logLiveDispatchIssueOnce(`unknown:${addonId}`, () => this.log.warn("Live pipeline step references an unknown addon — dropping step", { meta: { addonId } }));
|
|
8927
9178
|
for (const sub of diagnostics.substitutions) this.logLiveDispatchIssueOnce(`${sub.addonId}|${sub.chosen}|${sub.running}|${sub.format}`, () => this.log.info("Live pipeline step model substituted for engine format (node-local)", { meta: {
|
|
@@ -9034,7 +9285,7 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
9034
9285
|
const deviceRefusals = [];
|
|
9035
9286
|
const steps = this.inputStepsToPipelineSteps(args.steps, deviceFormat, deviceStepEngine, deviceRefusals);
|
|
9036
9287
|
const framePlane = args.plane === "frame";
|
|
9037
|
-
const zeroBuild = collectZeroBuildIssues(framePlane ? collectFramePlaneSteps(steps, isDetailPlaneStep) : flattenSteps(steps), deviceFormat);
|
|
9288
|
+
const zeroBuild = collectZeroBuildIssues(framePlane ? collectFramePlaneSteps(steps, isDetailPlaneStep) : flattenSteps(steps), deviceFormat, this.mergedStepDefOrThrow);
|
|
9038
9289
|
if (zeroBuild.length === 0) {
|
|
9039
9290
|
if (!framePlane) {
|
|
9040
9291
|
this.logClusterRefusals(deviceRefusals, deviceId, `${deviceKey} (${deviceFormat})`);
|
|
@@ -9043,7 +9294,7 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
9043
9294
|
steps
|
|
9044
9295
|
};
|
|
9045
9296
|
}
|
|
9046
|
-
const pruneResult = pruneUnrunnableDetailSteps(steps, isDetailPlaneStep, (
|
|
9297
|
+
const pruneResult = pruneUnrunnableDetailSteps(steps, isDetailPlaneStep, (subtree) => !buildSubtreeCanRun(subtree, this.mergedStepDef)(deviceFormat));
|
|
9047
9298
|
if (pruneResult.prunedAddonIds.length > 0) {
|
|
9048
9299
|
const prunedList = pruneResult.prunedAddonIds.join(",");
|
|
9049
9300
|
this.logLiveDispatchIssueOnce(`detail-defer:${deviceKey}:${deviceFormat}:${prunedList}`, () => this.log.info("Detail steps have no model build for the dispatch device format — deferred to the detail-plane device jump", {
|
|
@@ -9055,7 +9306,7 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
9055
9306
|
}
|
|
9056
9307
|
}));
|
|
9057
9308
|
}
|
|
9058
|
-
this.logClusterRefusals(deviceRefusals.filter((r) => !pruneResult.
|
|
9309
|
+
this.logClusterRefusals(deviceRefusals.filter((r) => !pruneResult.removedAddonIds.includes(r.stepId)), deviceId, `${deviceKey} (${deviceFormat})`);
|
|
9059
9310
|
return {
|
|
9060
9311
|
deviceKey,
|
|
9061
9312
|
steps: pruneResult.steps
|
|
@@ -9089,7 +9340,7 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
9089
9340
|
* microseconds instead of a 3-retry download backoff).
|
|
9090
9341
|
*/
|
|
9091
9342
|
assertStepsRunnable(steps, format, deviceId, enginesTried) {
|
|
9092
|
-
const zeroBuild = collectZeroBuildIssues(flattenSteps(steps), format);
|
|
9343
|
+
const zeroBuild = collectZeroBuildIssues(flattenSteps(steps), format, this.mergedStepDefOrThrow);
|
|
9093
9344
|
if (zeroBuild.length === 0) return;
|
|
9094
9345
|
const detail = zeroBuild.map((issue) => {
|
|
9095
9346
|
const step = flattenSteps(steps).find((s) => s.addonId === issue.addonId);
|
|
@@ -9123,6 +9374,40 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
9123
9374
|
nullPoolKey = {};
|
|
9124
9375
|
/** Negative cache, per-model compile-timeout refusals, per-camera reporting (D653). */
|
|
9125
9376
|
loadGovernor = new ModelLoadGovernor();
|
|
9377
|
+
/**
|
|
9378
|
+
* Load a dispatch's models ONE STEP AT A TIME (D657), and answer which steps
|
|
9379
|
+
* did not load. Never throws: each step is its own load, joined, backed off
|
|
9380
|
+
* and charged on its own model, so a step whose model cannot load on this
|
|
9381
|
+
* pool — no build for its format, a compile that failed — costs that step,
|
|
9382
|
+
* never the root or a sibling. The caller decides what the dispatch runs
|
|
9383
|
+
* without it ({@link stepsSurvivingLoad}).
|
|
9384
|
+
*/
|
|
9385
|
+
async loadDispatchModels(steps, factory, format, context, policy) {
|
|
9386
|
+
const failures = [];
|
|
9387
|
+
const walk = async (nodes, root) => {
|
|
9388
|
+
for (const step of nodes) {
|
|
9389
|
+
if (!step.enabled || step.slot === "audio-classifier") continue;
|
|
9390
|
+
if (factory === null || factory.needsPoolUpdate(step)) try {
|
|
9391
|
+
await this.ensureModelsForSteps([{
|
|
9392
|
+
...step,
|
|
9393
|
+
children: []
|
|
9394
|
+
}], factory, format, {
|
|
9395
|
+
...context,
|
|
9396
|
+
outcome: policy === "all-or-nothing" ? "call-failed" : root ? "root-lost" : "step-dropped"
|
|
9397
|
+
});
|
|
9398
|
+
} catch (err) {
|
|
9399
|
+
failures.push({
|
|
9400
|
+
step,
|
|
9401
|
+
error: err
|
|
9402
|
+
});
|
|
9403
|
+
continue;
|
|
9404
|
+
}
|
|
9405
|
+
await walk(step.children ?? [], false);
|
|
9406
|
+
}
|
|
9407
|
+
};
|
|
9408
|
+
await walk(steps, true);
|
|
9409
|
+
return failures;
|
|
9410
|
+
}
|
|
9126
9411
|
/** Ensure all models needed by steps are downloaded and loaded in the engine pool.
|
|
9127
9412
|
* `factory`/`format` default to the node's engine; a per-device dispatch passes
|
|
9128
9413
|
* that device's factory + format (Phase 2 multi-device). `context` names the
|
|
@@ -9152,7 +9437,7 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
9152
9437
|
this.reportDispatchLoadLoss(context, deviceKey, models, err.model, err.state, err.message);
|
|
9153
9438
|
throw err;
|
|
9154
9439
|
}
|
|
9155
|
-
const failed = err
|
|
9440
|
+
const failed = failedModelOf(err) ?? models[0] ?? "";
|
|
9156
9441
|
this.reportDispatchLoadLoss(context, deviceKey, models, failed, `failed:${flight.generation ?? "unknown"}`, errMsg(err));
|
|
9157
9442
|
throw err;
|
|
9158
9443
|
}
|
|
@@ -9244,7 +9529,8 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
9244
9529
|
}
|
|
9245
9530
|
/** Charge a failed load to the model that failed; say so once if it is now refused. */
|
|
9246
9531
|
recordModelLoadFailure(pool, deviceKey, models, err) {
|
|
9247
|
-
const
|
|
9532
|
+
const named = failedModelOf(err);
|
|
9533
|
+
const failedModels = named !== null ? [named] : models;
|
|
9248
9534
|
const compileTimeout = err instanceof StepVariantLoadError && err.reason === "compile-timeout";
|
|
9249
9535
|
const poolModel = err instanceof StepVariantLoadError ? err.poolModel : null;
|
|
9250
9536
|
let generation = 0;
|
|
@@ -9277,24 +9563,31 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
9277
9563
|
models,
|
|
9278
9564
|
model,
|
|
9279
9565
|
state,
|
|
9280
|
-
error
|
|
9566
|
+
error,
|
|
9567
|
+
...context.outcome !== void 0 ? { outcome: context.outcome } : {}
|
|
9281
9568
|
}
|
|
9282
9569
|
});
|
|
9283
9570
|
}
|
|
9284
9571
|
/** Download any model of `needed` missing in `format` (the device's). */
|
|
9285
9572
|
async downloadNeededModels(needed, format) {
|
|
9286
|
-
for (const step of needed) {
|
|
9287
|
-
|
|
9288
|
-
|
|
9289
|
-
|
|
9290
|
-
|
|
9291
|
-
|
|
9292
|
-
|
|
9293
|
-
|
|
9294
|
-
|
|
9295
|
-
|
|
9296
|
-
|
|
9297
|
-
}
|
|
9573
|
+
for (const step of needed) try {
|
|
9574
|
+
await this.ensureStepModelArtifact(step, format);
|
|
9575
|
+
} catch (err) {
|
|
9576
|
+
throw new StepModelArtifactError(step.addonId, step.modelId, err);
|
|
9577
|
+
}
|
|
9578
|
+
}
|
|
9579
|
+
/** Make one step's model artifact present on disk for `format`, or say why it cannot be. */
|
|
9580
|
+
async ensureStepModelArtifact(step, format) {
|
|
9581
|
+
const modelEntry = await this.resolveModelEntry(step.addonId, step.modelId);
|
|
9582
|
+
if (modelEntry && !isModelDownloaded(this.modelsDir, modelEntry, format)) {
|
|
9583
|
+
if (modelEntry.formats[format] === void 0) throw new Error(`Model "${modelEntry.id}" has no ${format} format build (available: ${Object.keys(modelEntry.formats).join(", ") || "none"}) — not retrying a permanent format mismatch`);
|
|
9584
|
+
if (modelEntry.formats[format]?.url.startsWith("camstack-local://") === true) throw new Error(`Custom model "${modelEntry.id}" (${format}) is not present in this node's models directory — distribute it to this node from Model Studio before selecting it here`);
|
|
9585
|
+
this.log.info("Downloading model for step", { meta: {
|
|
9586
|
+
modelId: step.modelId,
|
|
9587
|
+
format,
|
|
9588
|
+
step: step.addonId
|
|
9589
|
+
} });
|
|
9590
|
+
await this.downloadWithRetry(modelEntry, format, 3);
|
|
9298
9591
|
}
|
|
9299
9592
|
}
|
|
9300
9593
|
/** Download a model with retry + exponential backoff */
|
|
@@ -9392,6 +9685,7 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
9392
9685
|
}
|
|
9393
9686
|
async runPipelineBatchImpl(input) {
|
|
9394
9687
|
if (input.frames.length === 0) return { results: [] };
|
|
9688
|
+
await this.getCustomModels();
|
|
9395
9689
|
const dispatchResolution = this.resolveStepsForDispatch({
|
|
9396
9690
|
steps: input.steps,
|
|
9397
9691
|
deviceKey: input.deviceKey,
|
|
@@ -9634,9 +9928,10 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
9634
9928
|
return this.engineFactory.cacheFrameInPool(buf, input.width, input.height, input.format);
|
|
9635
9929
|
}
|
|
9636
9930
|
async inferCached(input) {
|
|
9931
|
+
if (typeof input.modelId !== "string" || input.modelId === "") throw new Error(`inferCached: no modelId for step "${input.stepId}" — refusing rather than running the step's active variant`);
|
|
9637
9932
|
await this.ensureEngineFactory();
|
|
9638
9933
|
if (!this.engineFactory) throw new Error("inferCached: factory not initialized");
|
|
9639
|
-
return this.engineFactory.inferCached(input.stepId, input.frameId);
|
|
9934
|
+
return this.engineFactory.inferCached(input.stepId, input.frameId, input.modelId);
|
|
9640
9935
|
}
|
|
9641
9936
|
async uncacheFrame(input) {
|
|
9642
9937
|
if (!this.engineFactory) return;
|
|
@@ -10440,13 +10735,22 @@ function isDetailPlaneStep(addonId) {
|
|
|
10440
10735
|
}
|
|
10441
10736
|
}
|
|
10442
10737
|
/**
|
|
10443
|
-
*
|
|
10444
|
-
*
|
|
10445
|
-
*
|
|
10446
|
-
*
|
|
10447
|
-
|
|
10448
|
-
|
|
10449
|
-
|
|
10738
|
+
* What a dispatch runs after its per-step loads (D657).
|
|
10739
|
+
*
|
|
10740
|
+
* An ALL-OR-NOTHING call (a benchmark, a replay — D56 — a capacity ramp) fails
|
|
10741
|
+
* on any failure, naming every step it lost ({@link DispatchStepsNotLoadedError}):
|
|
10742
|
+
* it measures or reproduces exactly what it names. A camera's LIVE dispatch
|
|
10743
|
+
* runs the tree without the steps whose model did not load, and fails — with
|
|
10744
|
+
* the first root's own named error — only when no root survives: a dispatch
|
|
10745
|
+
* with no root has nothing to produce.
|
|
10746
|
+
*/
|
|
10747
|
+
function stepsSurvivingLoad(steps, failures, policy) {
|
|
10748
|
+
if (failures.length === 0) return steps;
|
|
10749
|
+
if (policy === "all-or-nothing") throw new DispatchStepsNotLoadedError(failures);
|
|
10750
|
+
const kept = withoutFailedSteps(steps, new Set(failures.map((f) => f.step)));
|
|
10751
|
+
if (kept.some((s) => s.enabled)) return kept;
|
|
10752
|
+
const roots = new Set(steps);
|
|
10753
|
+
throw (failures.find((f) => roots.has(f.step)) ?? failures[0]).error;
|
|
10450
10754
|
}
|
|
10451
10755
|
/**
|
|
10452
10756
|
* The union of model formats a step's catalog ships ANY build for — the
|