@camstack/addon-training 0.1.6 → 0.1.7
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/MaskShapeCanvas-Kfg9dl6B.mjs +686 -0
- package/dist/{MotionZonesSettings-DPtcv80q.mjs → MotionZonesSettings-DKtYdHUO.mjs} +101 -119
- package/dist/{PrivacyMaskSettings-Wsn5QL0_.mjs → PrivacyMaskSettings-BvLUVz86.mjs} +45 -70
- package/dist/ReactKonva-DGuYUN0L.mjs +5416 -0
- package/dist/{SceneMonitorEditor-BlMwyiH8.mjs → SceneMonitorEditor-rFYGkGHV.mjs} +82 -82
- package/dist/_stub.js +28146 -12781
- package/dist/{_virtual_mf-localSharedImportMap___mfe_internal__addon_training_page-Cbrdn9ib.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_training_page-B3xXUcaU.mjs} +3 -3
- package/dist/_virtual_mf___mfe_internal__addon_training_page__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-C7dks3FG.mjs +26 -0
- package/dist/_virtual_mf___mfe_internal__addon_training_page__loadShare___mf_0_tanstack_mf_1_react_mf_2_query__loadShare__.js-DpjirGmB.mjs +89 -0
- package/dist/_virtual_mf___mfe_internal__addon_training_page__loadShare___mf_0_trpc_mf_1_client__loadShare__.js-CFCVuRAs.mjs +58 -0
- package/dist/_virtual_mf___mfe_internal__addon_training_page__loadShare___mf_0_trpc_mf_1_react_mf_2_query__loadShare__.js-BkvDudqY.mjs +62 -0
- package/dist/{_virtual_mf___mfe_internal__addon_training_page__loadShare__react__loadShare__.js-BT_wL8s3.mjs → _virtual_mf___mfe_internal__addon_training_page__loadShare__react__loadShare__.js-DeDbF7NI.mjs} +1 -1
- package/dist/_virtual_mf___mfe_internal__addon_training_page__loadShare__react_mf_1_jsx_mf_2_runtime__loadShare__.js-DnrbBvrw.mjs +34 -0
- package/dist/_virtual_mf___mfe_internal__addon_training_page__loadShare__react_mf_2_dom__loadShare__.js-DAZkHxxU.mjs +45 -0
- package/dist/_virtual_mf___mfe_internal__addon_training_page__loadShare__react_mf_2_dom_mf_1_client__loadShare__.js-CHqN9NLt.mjs +34 -0
- package/dist/addon-training.css +1 -1
- package/dist/{dist-BhCRZEE4.js → dist-DVDBSrjw.js} +270 -146
- package/dist/{dist-Cqa2fHNk.mjs → dist-Phot2UwD.mjs} +271 -141
- package/dist/grid-2x2-Bl3xw1w7.mjs +21 -0
- package/dist/{hostInit-BI98RYIm.mjs → hostInit-BAGO4XYA.mjs} +2 -2
- package/dist/index.js +100 -39
- package/dist/index.mjs +99 -38
- package/dist/lib-CVGFTwe0.mjs +7770 -0
- package/dist/orchestrator/training.addon.js +376 -222
- package/dist/orchestrator/training.addon.mjs +376 -222
- package/dist/{player-overlays-BX3obOMu.mjs → player-overlays-BT-6fK9N.mjs} +26 -7
- package/dist/providers/modal/training-modal.addon.js +361 -155
- package/dist/providers/modal/training-modal.addon.mjs +364 -158
- package/dist/remoteEntry.js +1 -1
- package/dist/{responsive-CdDIJCpI.mjs → responsive-Dy5RAD1s.mjs} +42 -38
- package/dist/{scene-monitor-copy-5u5w3K-Y.mjs → scene-monitor-copy-7HImRbA5.mjs} +4 -4
- package/dist/{square-C-P1-89Q.mjs → square-DoPBhbZN.mjs} +29 -4
- package/dist/{trash-2-BikGYiWf.mjs → trash-2-BwunNgLf.mjs} +1 -1
- package/dist/{virtual_mf-REMOTE_ENTRY_ID___mfe_internal__addon_training_page__remoteEntry_js-DGLUs0o6.mjs → virtual_mf-REMOTE_ENTRY_ID___mfe_internal__addon_training_page__remoteEntry_js-gU49KvnZ.mjs} +399 -390
- package/package.json +1 -1
- package/dist/MaskShapeCanvas-BokARvOj.mjs +0 -13806
- package/dist/_virtual_mf___mfe_internal__addon_training_page__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-BkKuzhOJ.mjs +0 -26
- package/dist/_virtual_mf___mfe_internal__addon_training_page__loadShare___mf_0_tanstack_mf_1_react_mf_2_query__loadShare__.js-CaB1jnkt.mjs +0 -25
- package/dist/_virtual_mf___mfe_internal__addon_training_page__loadShare___mf_0_trpc_mf_1_client__loadShare__.js-B3rryujf.mjs +0 -25
- package/dist/_virtual_mf___mfe_internal__addon_training_page__loadShare___mf_0_trpc_mf_1_react_mf_2_query__loadShare__.js-nL4MGFRP.mjs +0 -26
- package/dist/_virtual_mf___mfe_internal__addon_training_page__loadShare__react_mf_1_jsx_mf_2_runtime__loadShare__.js-DDBEvLdo.mjs +0 -26
- package/dist/_virtual_mf___mfe_internal__addon_training_page__loadShare__react_mf_2_dom__loadShare__.js-i_pnGDxd.mjs +0 -25
- package/dist/_virtual_mf___mfe_internal__addon_training_page__loadShare__react_mf_2_dom_mf_1_client__loadShare__.js-gTIQrhKs.mjs +0 -25
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
-
const require_dist = require("../dist-
|
|
2
|
+
const require_dist = require("../dist-DVDBSrjw.js");
|
|
3
3
|
let node_path = require("node:path");
|
|
4
4
|
node_path = require_dist.__toESM(node_path);
|
|
5
5
|
let node_crypto = require("node:crypto");
|
|
@@ -8,22 +8,123 @@ let node_fs_promises = require("node:fs/promises");
|
|
|
8
8
|
//#region src/orchestrator/api-ports.ts
|
|
9
9
|
var DETECTOR_STEP = "object-detection";
|
|
10
10
|
var REGISTRY_CAP = "custom-model-registry";
|
|
11
|
-
/** The provider cap,
|
|
11
|
+
/** The provider cap, with entryId → owning addon resolution for collection pins. */
|
|
12
12
|
function providerPort(api) {
|
|
13
13
|
const p = api.trainingProvider;
|
|
14
|
+
const addonForEntry = async (entryId) => {
|
|
15
|
+
const entry = (await p.listEntries.query({})).find((e) => e.entryId === entryId);
|
|
16
|
+
if (entry === void 0) throw new Error(`unknown training provider entry "${entryId}"`);
|
|
17
|
+
return entry.addonId;
|
|
18
|
+
};
|
|
19
|
+
const addonForType = async (typeId) => {
|
|
20
|
+
const type = (await p.describeTypes.query({})).find((t) => t.typeId === typeId);
|
|
21
|
+
if (type === void 0) throw new Error(`unknown training provider type "${typeId}"`);
|
|
22
|
+
return type.addonId;
|
|
23
|
+
};
|
|
14
24
|
return {
|
|
15
25
|
describe: () => p.describe.query({}),
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
26
|
+
describeTypes: () => p.describeTypes.query({}),
|
|
27
|
+
listEntries: () => p.listEntries.query({}),
|
|
28
|
+
createEntry: async (i) => {
|
|
29
|
+
const addonId = await addonForType(i.typeId);
|
|
30
|
+
return p.createEntry.mutate({
|
|
31
|
+
...i,
|
|
32
|
+
addonId
|
|
33
|
+
});
|
|
34
|
+
},
|
|
35
|
+
deleteEntry: async (i) => {
|
|
36
|
+
const addonId = await addonForEntry(i.entryId);
|
|
37
|
+
return p.deleteEntry.mutate({
|
|
38
|
+
...i,
|
|
39
|
+
addonId
|
|
40
|
+
});
|
|
41
|
+
},
|
|
42
|
+
validateCredentials: async (i) => {
|
|
43
|
+
const addonId = await addonForEntry(i.entryId);
|
|
44
|
+
return p.validateCredentials.mutate({
|
|
45
|
+
...i,
|
|
46
|
+
addonId
|
|
47
|
+
});
|
|
48
|
+
},
|
|
49
|
+
estimateJob: async (i) => {
|
|
50
|
+
const addonId = await addonForEntry(i.entryId);
|
|
51
|
+
return p.estimateJob.query({
|
|
52
|
+
...i,
|
|
53
|
+
addonId
|
|
54
|
+
});
|
|
55
|
+
},
|
|
56
|
+
uploadDataset: async (i) => {
|
|
57
|
+
const addonId = await addonForEntry(i.entryId);
|
|
58
|
+
return p.uploadDataset.mutate({
|
|
59
|
+
...i,
|
|
60
|
+
addonId
|
|
61
|
+
});
|
|
62
|
+
},
|
|
63
|
+
submitJob: async (i) => {
|
|
64
|
+
const addonId = await addonForEntry(i.entryId);
|
|
65
|
+
return p.submitJob.mutate({
|
|
66
|
+
...i,
|
|
67
|
+
addonId
|
|
68
|
+
});
|
|
69
|
+
},
|
|
70
|
+
getJob: async (i) => {
|
|
71
|
+
const addonId = await addonForEntry(i.entryId);
|
|
72
|
+
return p.getJob.query({
|
|
73
|
+
...i,
|
|
74
|
+
addonId
|
|
75
|
+
});
|
|
76
|
+
},
|
|
77
|
+
listJobEvents: async (i) => {
|
|
78
|
+
const addonId = await addonForEntry(i.entryId);
|
|
79
|
+
return p.listJobEvents.query({
|
|
80
|
+
...i,
|
|
81
|
+
addonId
|
|
82
|
+
});
|
|
83
|
+
},
|
|
84
|
+
cancelJob: async (i) => {
|
|
85
|
+
const addonId = await addonForEntry(i.entryId);
|
|
86
|
+
return p.cancelJob.mutate({
|
|
87
|
+
...i,
|
|
88
|
+
addonId
|
|
89
|
+
});
|
|
90
|
+
},
|
|
91
|
+
readResult: async (i) => {
|
|
92
|
+
const addonId = await addonForEntry(i.entryId);
|
|
93
|
+
return p.readResult.mutate({
|
|
94
|
+
...i,
|
|
95
|
+
addonId
|
|
96
|
+
});
|
|
97
|
+
},
|
|
98
|
+
offerArtifact: async (i) => {
|
|
99
|
+
const addonId = await addonForEntry(i.entryId);
|
|
100
|
+
return p.offerArtifact.mutate({
|
|
101
|
+
...i,
|
|
102
|
+
addonId
|
|
103
|
+
});
|
|
104
|
+
},
|
|
105
|
+
cleanupJob: async (i) => {
|
|
106
|
+
const addonId = await addonForEntry(i.entryId);
|
|
107
|
+
return p.cleanupJob.mutate({
|
|
108
|
+
...i,
|
|
109
|
+
addonId
|
|
110
|
+
});
|
|
111
|
+
},
|
|
112
|
+
listJobs: async (i) => {
|
|
113
|
+
if (i.entryId !== void 0) {
|
|
114
|
+
const addonId = await addonForEntry(i.entryId);
|
|
115
|
+
return p.listJobs.query({
|
|
116
|
+
...i,
|
|
117
|
+
addonId
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
const types = await p.describeTypes.query({});
|
|
121
|
+
const seen = /* @__PURE__ */ new Set();
|
|
122
|
+
return (await Promise.all(types.map(async (t) => {
|
|
123
|
+
if (seen.has(t.addonId)) return [];
|
|
124
|
+
seen.add(t.addonId);
|
|
125
|
+
return p.listJobs.query({ addonId: t.addonId });
|
|
126
|
+
}))).flat();
|
|
127
|
+
}
|
|
27
128
|
};
|
|
28
129
|
}
|
|
29
130
|
function datasetPort(api) {
|
|
@@ -38,10 +139,13 @@ function datasetPort(api) {
|
|
|
38
139
|
};
|
|
39
140
|
} };
|
|
40
141
|
}
|
|
142
|
+
function corpusPort(api) {
|
|
143
|
+
return { resolve: (selection) => api.pipelineAnalytics.resolveRetrainTrainingCorpus.query(selection) };
|
|
144
|
+
}
|
|
41
145
|
/** Model Studio's registry: the hub's provider of `custom-model-registry`. */
|
|
42
146
|
function registryPort(api) {
|
|
43
147
|
return { importBundle: async (input) => {
|
|
44
|
-
const hub = (await api.addons.listCapabilityProviders.query({ capName: REGISTRY_CAP })).find((
|
|
148
|
+
const hub = (await api.addons.listCapabilityProviders.query({ capName: REGISTRY_CAP })).find((prov) => !prov.addonId.includes("@"));
|
|
45
149
|
if (hub === void 0) throw new Error("no custom-model registry on the hub — is Model Studio installed?");
|
|
46
150
|
return api.customModelRegistry.importModelBundle.mutate({
|
|
47
151
|
...input,
|
|
@@ -540,27 +644,36 @@ function dataNoticeFor(provider, region, frames) {
|
|
|
540
644
|
return `${String(frames)} frames and their boxes are uploaded to YOUR ${provider.label} account, ${where}. ${provider.dataResidencyNote} They are deleted there as soon as the result is fetched (${String(grace)} h after a failure, so it can be inspected), and they are never put in a log, an environment variable or the job file. The result stays on this hub for ${String(14)} days; nothing is registered until you click Register.`;
|
|
541
645
|
}
|
|
542
646
|
async function planJob(request, deps) {
|
|
543
|
-
const
|
|
544
|
-
if (
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
647
|
+
const resolved = await deps.corpus.resolve(request.selection);
|
|
648
|
+
if (resolved.kind === "refused") {
|
|
649
|
+
if (resolved.reason === "unknown-collection") return refused("unknown-dataset", resolved.detail);
|
|
650
|
+
return refused("no-training-subjects", resolved.detail);
|
|
651
|
+
}
|
|
652
|
+
const corpus = {
|
|
653
|
+
frameIds: resolved.frameIds,
|
|
654
|
+
stats: resolved.stats,
|
|
655
|
+
annotationHash: resolved.annotationHash,
|
|
656
|
+
layout: resolved.layout
|
|
657
|
+
};
|
|
658
|
+
const provider = (await deps.provider.describe({})).find((p) => p.entryId === request.entryId);
|
|
659
|
+
if (provider === void 0) return refused("unknown-provider", `no training provider entry "${request.entryId}"`);
|
|
660
|
+
const notice = dataNoticeFor(provider, request.region, corpus.frameIds.length);
|
|
661
|
+
if (!provider.configured) return refused("provider-not-configured", `${provider.name} (${provider.label}) has no credentials yet — set them on the entry`, notice);
|
|
549
662
|
const base = findBaseModel(request.baseModelId);
|
|
550
663
|
if (base === null) return refused("unknown-base-model", `"${request.baseModelId}" is not a trainable base`, notice);
|
|
551
664
|
if (!knownGpu(provider.gpuTypes, request.gpuType)) return refused("unknown-gpu", `${provider.label} offers no "${request.gpuType}"`, notice);
|
|
552
|
-
const datasetBytes = datasetBytesEstimate(
|
|
665
|
+
const datasetBytes = datasetBytesEstimate(corpus.frameIds.length);
|
|
553
666
|
if (datasetBytes > provider.maxDatasetBytes) return refused("dataset-too-large", `about ${String(Math.round(datasetBytes / 1e6))} MB is over ${provider.label}'s limit`, notice);
|
|
554
|
-
const { split, warnings } = planSplit(
|
|
667
|
+
const { split, warnings } = planSplit(corpus.stats, request.holdoutCameras);
|
|
555
668
|
if (split.trainSubjects <= 0) return refused("no-training-subjects", "no boxed subject is left to train on once the holdout is set aside", notice);
|
|
556
669
|
const gpu = estimateGpuSeconds({
|
|
557
|
-
frames:
|
|
670
|
+
frames: corpus.frameIds.length,
|
|
558
671
|
base,
|
|
559
672
|
preset: request.preset,
|
|
560
673
|
gpuType: request.gpuType
|
|
561
674
|
});
|
|
562
675
|
const usd = await deps.provider.estimateJob({
|
|
563
|
-
|
|
676
|
+
entryId: request.entryId,
|
|
564
677
|
gpuType: request.gpuType,
|
|
565
678
|
region: request.region,
|
|
566
679
|
datasetBytes,
|
|
@@ -570,7 +683,7 @@ async function planJob(request, deps) {
|
|
|
570
683
|
const maxCostUsd = defaultCostCap(usd.usdHigh, provider.defaults.costCapUsd);
|
|
571
684
|
const allWarnings = [...warnings];
|
|
572
685
|
if (usd.usdHigh > maxCostUsd) allWarnings.push(`The high estimate ($${usd.usdHigh.toFixed(2)}) is above the cost cap ($${maxCostUsd.toFixed(2)}): the job may be stopped before it finishes.`);
|
|
573
|
-
if (
|
|
686
|
+
if (corpus.stats.completeness < .5) allWarnings.push("Fewer than half of the frames are marked complete: evaluation uses complete frames only, so the comparison rests on few frames.");
|
|
574
687
|
return {
|
|
575
688
|
plan: {
|
|
576
689
|
refusal: null,
|
|
@@ -592,7 +705,7 @@ async function planJob(request, deps) {
|
|
|
592
705
|
dataNotice: notice
|
|
593
706
|
},
|
|
594
707
|
accepted: {
|
|
595
|
-
|
|
708
|
+
corpus,
|
|
596
709
|
provider,
|
|
597
710
|
base,
|
|
598
711
|
split
|
|
@@ -670,14 +783,14 @@ var TrainingEngine = class {
|
|
|
670
783
|
kind: "started",
|
|
671
784
|
job: existing
|
|
672
785
|
};
|
|
673
|
-
const
|
|
674
|
-
if (
|
|
786
|
+
const busy = this.busyJob(await this.ports.jobs.list());
|
|
787
|
+
if (busy !== void 0) return {
|
|
675
788
|
kind: "refused",
|
|
676
789
|
reason: "job-already-running",
|
|
677
|
-
detail: `"${
|
|
790
|
+
detail: `"${busy.name}" is still ${busy.state}`
|
|
678
791
|
};
|
|
679
792
|
const planned = await planJob(input, {
|
|
680
|
-
|
|
793
|
+
corpus: this.ports.corpus,
|
|
681
794
|
provider: this.ports.provider
|
|
682
795
|
});
|
|
683
796
|
if (planned.plan.refusal !== null || planned.accepted === null) {
|
|
@@ -691,55 +804,24 @@ var TrainingEngine = class {
|
|
|
691
804
|
detail: refusal.detail
|
|
692
805
|
};
|
|
693
806
|
}
|
|
694
|
-
const { version, split } = planned.accepted;
|
|
695
|
-
const now = this.ports.now();
|
|
696
807
|
const { name, maxCostUsd, maxRuntimeSec, idempotencyKey, ...request } = input;
|
|
697
|
-
const job = {
|
|
698
|
-
id: this.ports.newId(),
|
|
699
|
-
idempotencyKey,
|
|
700
|
-
name,
|
|
701
|
-
createdAt: now,
|
|
702
|
-
updatedAt: now,
|
|
703
|
-
state: "uploading",
|
|
704
|
-
stage: null,
|
|
705
|
-
failure: null,
|
|
808
|
+
const job = this.newJobRow({
|
|
706
809
|
request,
|
|
810
|
+
name,
|
|
707
811
|
maxCostUsd,
|
|
708
812
|
maxRuntimeSec,
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
state: null,
|
|
715
|
-
providerJobId: null,
|
|
716
|
-
startedAt: null,
|
|
717
|
-
endedAt: null,
|
|
718
|
-
billedSeconds: 0,
|
|
719
|
-
costUsd: 0,
|
|
720
|
-
costIsEstimate: true,
|
|
721
|
-
lastPolledAt: null
|
|
722
|
-
},
|
|
723
|
-
progress: {
|
|
724
|
-
epoch: null,
|
|
725
|
-
epochs: null,
|
|
726
|
-
etaSec: null,
|
|
727
|
-
series: []
|
|
728
|
-
},
|
|
729
|
-
logTail: [],
|
|
730
|
-
logCursor: null,
|
|
731
|
-
artifacts: null,
|
|
732
|
-
cloudDatasetDeletedAt: null,
|
|
733
|
-
cloudCleanupDueAt: null,
|
|
734
|
-
registeredModelIds: [],
|
|
735
|
-
canary: null
|
|
736
|
-
};
|
|
813
|
+
idempotencyKey,
|
|
814
|
+
batchId: null,
|
|
815
|
+
state: "uploading",
|
|
816
|
+
accepted: planned.accepted
|
|
817
|
+
});
|
|
737
818
|
await this.ports.jobs.put(job);
|
|
738
819
|
this.ports.logger.info("training job started", { meta: {
|
|
739
820
|
jobId: job.id,
|
|
740
|
-
provider: request.
|
|
821
|
+
provider: request.entryId,
|
|
741
822
|
gpu: request.gpuType,
|
|
742
|
-
frames:
|
|
823
|
+
frames: job.frameCount,
|
|
824
|
+
collections: request.selection.collectionIds.length
|
|
743
825
|
} });
|
|
744
826
|
this.runTask(job.id, () => this.uploadAndSubmit(job.id));
|
|
745
827
|
return {
|
|
@@ -747,9 +829,109 @@ var TrainingEngine = class {
|
|
|
747
829
|
job
|
|
748
830
|
};
|
|
749
831
|
}
|
|
832
|
+
/**
|
|
833
|
+
* One job per base model, same live selection snapshot. The first uploads;
|
|
834
|
+
* siblings stay `queued` until the prior job in the batch settles.
|
|
835
|
+
*/
|
|
836
|
+
async startJobBatch(input) {
|
|
837
|
+
const existing = await this.ports.jobs.findByIdempotencyKey(input.idempotencyKey);
|
|
838
|
+
if (existing !== null) return {
|
|
839
|
+
kind: "started",
|
|
840
|
+
batchId: existing.batchId ?? existing.id,
|
|
841
|
+
jobs: existing.batchId === null ? [existing] : (await this.ports.jobs.list()).filter((j) => j.batchId === existing.batchId).toSorted((a, b) => a.createdAt - b.createdAt)
|
|
842
|
+
};
|
|
843
|
+
const busy = this.busyJob(await this.ports.jobs.list());
|
|
844
|
+
if (busy !== void 0) return {
|
|
845
|
+
kind: "refused",
|
|
846
|
+
reason: "job-already-running",
|
|
847
|
+
detail: `"${busy.name}" is still ${busy.state}`
|
|
848
|
+
};
|
|
849
|
+
const models = uniqueBaseModelIds(input.baseModelIds);
|
|
850
|
+
for (const modelId of models) if (findBaseModel(modelId) === null) return {
|
|
851
|
+
kind: "refused",
|
|
852
|
+
reason: "unknown-base-model",
|
|
853
|
+
detail: `"${modelId}" is not a trainable base`
|
|
854
|
+
};
|
|
855
|
+
const headModel = models[0];
|
|
856
|
+
if (headModel === void 0) return {
|
|
857
|
+
kind: "refused",
|
|
858
|
+
reason: "unknown-base-model",
|
|
859
|
+
detail: "no base models given"
|
|
860
|
+
};
|
|
861
|
+
const shared = {
|
|
862
|
+
selection: input.selection,
|
|
863
|
+
entryId: input.entryId,
|
|
864
|
+
gpuType: input.gpuType,
|
|
865
|
+
region: input.region,
|
|
866
|
+
preset: input.preset,
|
|
867
|
+
freeze: input.freeze,
|
|
868
|
+
holdoutCameras: input.holdoutCameras
|
|
869
|
+
};
|
|
870
|
+
const planned = await planJob({
|
|
871
|
+
...shared,
|
|
872
|
+
baseModelId: headModel
|
|
873
|
+
}, {
|
|
874
|
+
corpus: this.ports.corpus,
|
|
875
|
+
provider: this.ports.provider
|
|
876
|
+
});
|
|
877
|
+
if (planned.plan.refusal !== null || planned.accepted === null) {
|
|
878
|
+
const refusal = planned.plan.refusal ?? {
|
|
879
|
+
reason: "unknown-dataset",
|
|
880
|
+
detail: "no plan"
|
|
881
|
+
};
|
|
882
|
+
return {
|
|
883
|
+
kind: "refused",
|
|
884
|
+
reason: refusal.reason,
|
|
885
|
+
detail: refusal.detail
|
|
886
|
+
};
|
|
887
|
+
}
|
|
888
|
+
const batchId = this.ports.newId();
|
|
889
|
+
const jobs = [];
|
|
890
|
+
for (let i = 0; i < models.length; i += 1) {
|
|
891
|
+
const modelId = models[i];
|
|
892
|
+
if (modelId === void 0) continue;
|
|
893
|
+
const job = this.newJobRow({
|
|
894
|
+
request: {
|
|
895
|
+
...shared,
|
|
896
|
+
baseModelId: modelId
|
|
897
|
+
},
|
|
898
|
+
name: input.name,
|
|
899
|
+
maxCostUsd: input.maxCostUsd,
|
|
900
|
+
maxRuntimeSec: input.maxRuntimeSec,
|
|
901
|
+
idempotencyKey: i === 0 ? input.idempotencyKey : `${input.idempotencyKey}:${modelId}`,
|
|
902
|
+
batchId,
|
|
903
|
+
state: i === 0 ? "uploading" : "queued",
|
|
904
|
+
accepted: planned.accepted
|
|
905
|
+
});
|
|
906
|
+
await this.ports.jobs.put(job);
|
|
907
|
+
jobs.push(job);
|
|
908
|
+
}
|
|
909
|
+
const head = jobs[0];
|
|
910
|
+
if (head === void 0) return {
|
|
911
|
+
kind: "refused",
|
|
912
|
+
reason: "unknown-base-model",
|
|
913
|
+
detail: "no base models given"
|
|
914
|
+
};
|
|
915
|
+
this.ports.logger.info("training job batch started", { meta: {
|
|
916
|
+
batchId,
|
|
917
|
+
jobIds: jobs.map((j) => j.id),
|
|
918
|
+
models: [...models],
|
|
919
|
+
frames: head.frameCount
|
|
920
|
+
} });
|
|
921
|
+
this.runTask(head.id, () => this.uploadAndSubmit(head.id));
|
|
922
|
+
return {
|
|
923
|
+
kind: "started",
|
|
924
|
+
batchId,
|
|
925
|
+
jobs
|
|
926
|
+
};
|
|
927
|
+
}
|
|
750
928
|
async cancelJob(id) {
|
|
751
929
|
const job = await this.ports.jobs.get(id);
|
|
752
930
|
if (job === null) throw new Error(`training job "${id}" does not exist`);
|
|
931
|
+
if (job.state === "queued") return await this.mutator.update(id, (j) => ({
|
|
932
|
+
...j,
|
|
933
|
+
state: "cancelled"
|
|
934
|
+
})) ?? job;
|
|
753
935
|
if (!ACTIVE.has(job.state) || job.state === "cancelling") return job;
|
|
754
936
|
const moved = await this.mutator.update(id, (j) => ({
|
|
755
937
|
...j,
|
|
@@ -757,7 +939,7 @@ var TrainingEngine = class {
|
|
|
757
939
|
}));
|
|
758
940
|
if (job.state !== "uploading") try {
|
|
759
941
|
await this.ports.provider.cancelJob({
|
|
760
|
-
|
|
942
|
+
entryId: job.request.entryId,
|
|
761
943
|
jobKey: id
|
|
762
944
|
});
|
|
763
945
|
} catch (err) {
|
|
@@ -788,7 +970,7 @@ var TrainingEngine = class {
|
|
|
788
970
|
const swept = [];
|
|
789
971
|
for (const provider of await this.ports.provider.describe({})) {
|
|
790
972
|
if (!provider.configured) continue;
|
|
791
|
-
for (const status of await this.ports.provider.listJobs({
|
|
973
|
+
for (const status of await this.ports.provider.listJobs({ entryId: provider.entryId })) {
|
|
792
974
|
const record = known.get(status.jobKey);
|
|
793
975
|
const live = record !== void 0 && !SETTLED.has(record.state);
|
|
794
976
|
const stillRunning = status.state === "pending" || status.state === "starting" || status.state === "running";
|
|
@@ -799,11 +981,11 @@ var TrainingEngine = class {
|
|
|
799
981
|
state: status.state
|
|
800
982
|
} });
|
|
801
983
|
await this.ports.provider.cancelJob({
|
|
802
|
-
|
|
984
|
+
entryId: provider.entryId,
|
|
803
985
|
jobKey: status.jobKey
|
|
804
986
|
});
|
|
805
987
|
await this.ports.provider.cleanupJob({
|
|
806
|
-
|
|
988
|
+
entryId: provider.entryId,
|
|
807
989
|
jobKey: status.jobKey
|
|
808
990
|
});
|
|
809
991
|
swept.push(status.jobKey);
|
|
@@ -824,23 +1006,19 @@ var TrainingEngine = class {
|
|
|
824
1006
|
async uploadAndSubmit(id) {
|
|
825
1007
|
const job = await this.ports.jobs.get(id);
|
|
826
1008
|
if (job === null) return;
|
|
827
|
-
const version = await this.ports.versions.get(job.request.datasetVersionId);
|
|
828
1009
|
const base = findBaseModel(job.request.baseModelId);
|
|
829
|
-
if (
|
|
830
|
-
await this.fail(id, "upload-failed", null, "the
|
|
1010
|
+
if (base === null || job.frameIds.length === 0) {
|
|
1011
|
+
await this.fail(id, "upload-failed", null, "the corpus snapshot or base model disappeared");
|
|
831
1012
|
return;
|
|
832
1013
|
}
|
|
833
1014
|
let uploaded;
|
|
834
1015
|
try {
|
|
835
1016
|
const offer = await this.ports.datasets.offerArchive({
|
|
836
|
-
filter: {
|
|
837
|
-
|
|
838
|
-
...version.completeOnly ? { completeOnly: true } : {}
|
|
839
|
-
},
|
|
840
|
-
layout: version.layout
|
|
1017
|
+
filter: { frameIds: [...job.frameIds] },
|
|
1018
|
+
layout: job.datasetLayout
|
|
841
1019
|
});
|
|
842
1020
|
uploaded = await this.ports.provider.uploadDataset({
|
|
843
|
-
|
|
1021
|
+
entryId: job.request.entryId,
|
|
844
1022
|
jobKey: id,
|
|
845
1023
|
ticket: offer.ticket
|
|
846
1024
|
});
|
|
@@ -865,12 +1043,12 @@ var TrainingEngine = class {
|
|
|
865
1043
|
const spec = buildJobSpec({
|
|
866
1044
|
job: afterUpload ?? job,
|
|
867
1045
|
base,
|
|
868
|
-
versionId:
|
|
869
|
-
annotationHash:
|
|
1046
|
+
versionId: `live-${job.annotationHash.slice(0, 12)}`,
|
|
1047
|
+
annotationHash: job.annotationHash,
|
|
870
1048
|
datasetSha256: uploaded.sha256
|
|
871
1049
|
});
|
|
872
1050
|
const status = await this.ports.provider.submitJob({
|
|
873
|
-
|
|
1051
|
+
entryId: job.request.entryId,
|
|
874
1052
|
jobKey: id,
|
|
875
1053
|
trainer: job.trainer,
|
|
876
1054
|
jobSpecJson: JSON.stringify(spec),
|
|
@@ -919,7 +1097,7 @@ var TrainingEngine = class {
|
|
|
919
1097
|
}
|
|
920
1098
|
async poll(job, now) {
|
|
921
1099
|
const ref = {
|
|
922
|
-
|
|
1100
|
+
entryId: job.request.entryId,
|
|
923
1101
|
jobKey: job.id
|
|
924
1102
|
};
|
|
925
1103
|
const status = await this.ports.provider.getJob(ref);
|
|
@@ -949,7 +1127,7 @@ var TrainingEngine = class {
|
|
|
949
1127
|
}
|
|
950
1128
|
async afterPoll(job, status, now) {
|
|
951
1129
|
const ref = {
|
|
952
|
-
|
|
1130
|
+
entryId: job.request.entryId,
|
|
953
1131
|
jobKey: job.id
|
|
954
1132
|
};
|
|
955
1133
|
const running = status.state === "pending" || status.state === "starting" || status.state === "running";
|
|
@@ -985,15 +1163,18 @@ var TrainingEngine = class {
|
|
|
985
1163
|
await this.fail(job.id, "provider-error", job.stage, "the provider stopped the job");
|
|
986
1164
|
return;
|
|
987
1165
|
}
|
|
988
|
-
if (status.state === "lost")
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
1166
|
+
if (status.state === "lost") {
|
|
1167
|
+
await this.mutator.update(job.id, (j) => ({
|
|
1168
|
+
...j,
|
|
1169
|
+
state: "lost"
|
|
1170
|
+
}));
|
|
1171
|
+
await this.advanceBatchQueue(job.id);
|
|
1172
|
+
}
|
|
992
1173
|
}
|
|
993
1174
|
async failureDetail(job) {
|
|
994
1175
|
try {
|
|
995
1176
|
const raw = await this.ports.provider.readResult({
|
|
996
|
-
|
|
1177
|
+
entryId: job.request.entryId,
|
|
997
1178
|
jobKey: job.id
|
|
998
1179
|
});
|
|
999
1180
|
if (!raw.found || raw.text === void 0) return null;
|
|
@@ -1008,7 +1189,7 @@ var TrainingEngine = class {
|
|
|
1008
1189
|
const job = await this.ports.jobs.get(id);
|
|
1009
1190
|
if (job === null || job.state !== "collecting") return;
|
|
1010
1191
|
const ref = {
|
|
1011
|
-
|
|
1192
|
+
entryId: job.request.entryId,
|
|
1012
1193
|
jobKey: id
|
|
1013
1194
|
};
|
|
1014
1195
|
try {
|
|
@@ -1055,6 +1236,7 @@ var TrainingEngine = class {
|
|
|
1055
1236
|
bytes: got.bytes
|
|
1056
1237
|
} });
|
|
1057
1238
|
await this.cleanupCloud(id);
|
|
1239
|
+
await this.advanceBatchQueue(id);
|
|
1058
1240
|
} catch (err) {
|
|
1059
1241
|
await this.fail(id, "fetch-failed", "package", errText(err));
|
|
1060
1242
|
}
|
|
@@ -1065,6 +1247,7 @@ var TrainingEngine = class {
|
|
|
1065
1247
|
state: "cancelled"
|
|
1066
1248
|
}));
|
|
1067
1249
|
await this.cleanupCloud(id);
|
|
1250
|
+
await this.advanceBatchQueue(id);
|
|
1068
1251
|
}
|
|
1069
1252
|
/** Delete the cloud copy (dataset first). A failure is retried by the next tick. */
|
|
1070
1253
|
async cleanupCloud(id) {
|
|
@@ -1072,7 +1255,7 @@ var TrainingEngine = class {
|
|
|
1072
1255
|
if (job === null || job.cloudDatasetDeletedAt !== null) return;
|
|
1073
1256
|
try {
|
|
1074
1257
|
const { deleted } = await this.ports.provider.cleanupJob({
|
|
1075
|
-
|
|
1258
|
+
entryId: job.request.entryId,
|
|
1076
1259
|
jobKey: id
|
|
1077
1260
|
});
|
|
1078
1261
|
await this.mutator.update(id, (j) => ({
|
|
@@ -1094,7 +1277,7 @@ var TrainingEngine = class {
|
|
|
1094
1277
|
async fail(id, reason, stage, detail) {
|
|
1095
1278
|
let grace = 0;
|
|
1096
1279
|
const job = await this.ports.jobs.get(id);
|
|
1097
|
-
if (job !== null && reason === "trainer-failed") grace = ((await this.ports.provider.describe({})).find((p) => p.
|
|
1280
|
+
if (job !== null && reason === "trainer-failed") grace = ((await this.ports.provider.describe({})).find((p) => p.entryId === job.request.entryId)?.defaults.failedDatasetGraceHours ?? 0) * 60 * 6e4;
|
|
1098
1281
|
await this.mutator.update(id, (j) => ({
|
|
1099
1282
|
...j,
|
|
1100
1283
|
state: "failed",
|
|
@@ -1111,6 +1294,7 @@ var TrainingEngine = class {
|
|
|
1111
1294
|
stage,
|
|
1112
1295
|
detail
|
|
1113
1296
|
} });
|
|
1297
|
+
await this.advanceBatchQueue(id);
|
|
1114
1298
|
}
|
|
1115
1299
|
withStatus(job, status) {
|
|
1116
1300
|
const mapped = job.state === "cancelling" ? "cancelling" : status.state === "pending" ? "submitted" : status.state === "starting" ? "starting" : status.state === "running" ? "running" : job.state;
|
|
@@ -1129,7 +1313,96 @@ var TrainingEngine = class {
|
|
|
1129
1313
|
}
|
|
1130
1314
|
};
|
|
1131
1315
|
}
|
|
1316
|
+
/**
|
|
1317
|
+
* Blocks a new start from outside while any non-settled job is in flight —
|
|
1318
|
+
* GPU-holding (`ACTIVE`) or a batch sibling still `queued`. Poll/cancel keep
|
|
1319
|
+
* using `ACTIVE` alone (queued holds no GPU).
|
|
1320
|
+
*/
|
|
1321
|
+
busyJob(jobs) {
|
|
1322
|
+
return jobs.find((j) => ACTIVE.has(j.state) || j.state === "queued");
|
|
1323
|
+
}
|
|
1324
|
+
newJobRow(args) {
|
|
1325
|
+
const now = this.ports.now();
|
|
1326
|
+
const { corpus, split } = args.accepted;
|
|
1327
|
+
return {
|
|
1328
|
+
id: this.ports.newId(),
|
|
1329
|
+
idempotencyKey: args.idempotencyKey,
|
|
1330
|
+
name: args.name,
|
|
1331
|
+
createdAt: now,
|
|
1332
|
+
updatedAt: now,
|
|
1333
|
+
state: args.state,
|
|
1334
|
+
batchId: args.batchId,
|
|
1335
|
+
stage: null,
|
|
1336
|
+
failure: null,
|
|
1337
|
+
request: args.request,
|
|
1338
|
+
maxCostUsd: args.maxCostUsd,
|
|
1339
|
+
maxRuntimeSec: args.maxRuntimeSec,
|
|
1340
|
+
trainer: this.trainer,
|
|
1341
|
+
datasetLayout: corpus.layout,
|
|
1342
|
+
frameCount: corpus.frameIds.length,
|
|
1343
|
+
frameIds: [...corpus.frameIds],
|
|
1344
|
+
annotationHash: corpus.annotationHash,
|
|
1345
|
+
dataset: null,
|
|
1346
|
+
split,
|
|
1347
|
+
provider: {
|
|
1348
|
+
state: null,
|
|
1349
|
+
providerJobId: null,
|
|
1350
|
+
startedAt: null,
|
|
1351
|
+
endedAt: null,
|
|
1352
|
+
billedSeconds: 0,
|
|
1353
|
+
costUsd: 0,
|
|
1354
|
+
costIsEstimate: true,
|
|
1355
|
+
lastPolledAt: null
|
|
1356
|
+
},
|
|
1357
|
+
progress: {
|
|
1358
|
+
epoch: null,
|
|
1359
|
+
epochs: null,
|
|
1360
|
+
etaSec: null,
|
|
1361
|
+
series: []
|
|
1362
|
+
},
|
|
1363
|
+
logTail: [],
|
|
1364
|
+
logCursor: null,
|
|
1365
|
+
artifacts: null,
|
|
1366
|
+
cloudDatasetDeletedAt: null,
|
|
1367
|
+
cloudCleanupDueAt: null,
|
|
1368
|
+
registeredModelIds: [],
|
|
1369
|
+
canary: null
|
|
1370
|
+
};
|
|
1371
|
+
}
|
|
1372
|
+
/**
|
|
1373
|
+
* When a batch member settles and nothing else is ACTIVE, promote the next
|
|
1374
|
+
* `queued` sibling (same `batchId`, oldest first) into `uploading`.
|
|
1375
|
+
*/
|
|
1376
|
+
async advanceBatchQueue(settledId) {
|
|
1377
|
+
const settled = await this.ports.jobs.get(settledId);
|
|
1378
|
+
if (settled === null || settled.batchId === null || !SETTLED.has(settled.state)) return;
|
|
1379
|
+
const jobs = await this.ports.jobs.list();
|
|
1380
|
+
if (jobs.some((j) => ACTIVE.has(j.state))) return;
|
|
1381
|
+
const next = jobs.filter((j) => j.batchId === settled.batchId && j.state === "queued").toSorted((a, b) => a.createdAt - b.createdAt)[0];
|
|
1382
|
+
if (next === void 0) return;
|
|
1383
|
+
const moved = await this.mutator.update(next.id, (j) => j.state === "queued" ? {
|
|
1384
|
+
...j,
|
|
1385
|
+
state: "uploading"
|
|
1386
|
+
} : j);
|
|
1387
|
+
if (moved === null || moved.state !== "uploading") return;
|
|
1388
|
+
this.ports.logger.info("training batch advanced to next model", { meta: {
|
|
1389
|
+
batchId: settled.batchId,
|
|
1390
|
+
jobId: moved.id,
|
|
1391
|
+
baseModelId: moved.request.baseModelId
|
|
1392
|
+
} });
|
|
1393
|
+
this.runTask(moved.id, () => this.uploadAndSubmit(moved.id));
|
|
1394
|
+
}
|
|
1132
1395
|
};
|
|
1396
|
+
function uniqueBaseModelIds(ids) {
|
|
1397
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1398
|
+
const out = [];
|
|
1399
|
+
for (const id of ids) {
|
|
1400
|
+
if (seen.has(id)) continue;
|
|
1401
|
+
seen.add(id);
|
|
1402
|
+
out.push(id);
|
|
1403
|
+
}
|
|
1404
|
+
return out;
|
|
1405
|
+
}
|
|
1133
1406
|
//#endregion
|
|
1134
1407
|
//#region src/orchestrator/return-flow.ts
|
|
1135
1408
|
/**
|
|
@@ -1172,7 +1445,7 @@ function catalogEntryFromDraft(input) {
|
|
|
1172
1445
|
return {
|
|
1173
1446
|
id: draft.id,
|
|
1174
1447
|
name: draft.name,
|
|
1175
|
-
description: `${draft.description} Fine-tuned from ${base.modelId} by CamStack Training (job ${job.id},
|
|
1448
|
+
description: `${draft.description} Fine-tuned from ${base.modelId} by CamStack Training (job ${job.id}, ${String(job.frameCount)} frames from ${String(job.request.selection.collectionIds.length)} collection(s)).`,
|
|
1176
1449
|
inputSize: { ...draft.inputSize },
|
|
1177
1450
|
labels: [],
|
|
1178
1451
|
preprocessMode: "letterbox",
|
|
@@ -1186,7 +1459,7 @@ function catalogEntryFromDraft(input) {
|
|
|
1186
1459
|
url: "https://github.com/ultralytics/ultralytics"
|
|
1187
1460
|
},
|
|
1188
1461
|
attribution: `© Ultralytics, AGPL-3.0 (YOLOv9 weights); ${result.licence}. Corresponding source: ${TRAINER_SOURCE_URL} at revision ${result.trainer.revision}.`,
|
|
1189
|
-
modifications: `fine-tuned on the operator's own frames (
|
|
1462
|
+
modifications: `fine-tuned on the operator's own frames (${String(job.frameCount)} frames; collections ${job.request.selection.collectionIds.join(", ")}) and exported by CamStack Training`
|
|
1190
1463
|
},
|
|
1191
1464
|
formats
|
|
1192
1465
|
};
|
|
@@ -1343,7 +1616,7 @@ var ReturnFlow = class {
|
|
|
1343
1616
|
//#endregion
|
|
1344
1617
|
//#region src/orchestrator/stores.ts
|
|
1345
1618
|
/**
|
|
1346
|
-
* The orchestrator's
|
|
1619
|
+
* The orchestrator's durable job table in the settings store.
|
|
1347
1620
|
*
|
|
1348
1621
|
* Rows are validated on the way OUT (a row a newer build wrote, or a hand
|
|
1349
1622
|
* edit, never reaches the state machine as a half-shaped job) and dropped
|
|
@@ -1359,13 +1632,6 @@ var ReturnFlow = class {
|
|
|
1359
1632
|
* separately (14 days after ready/registration) and the row says so."
|
|
1360
1633
|
*/
|
|
1361
1634
|
var TRAINING_JOBS_COLLECTION = "training:jobs";
|
|
1362
|
-
/**
|
|
1363
|
-
* @durable class=ledger owner=training
|
|
1364
|
-
* write="one row per dataset version, inserted once (cut by pipeline-analytics, or moved
|
|
1365
|
-
* from its local store on first sight of this capability) and never updated."
|
|
1366
|
-
* retention="none — rows leave only when the operator deletes the version."
|
|
1367
|
-
*/
|
|
1368
|
-
var TRAINING_DATASET_VERSIONS_COLLECTION = "training:dataset-versions";
|
|
1369
1635
|
var JOB_COLUMNS = [
|
|
1370
1636
|
{
|
|
1371
1637
|
name: "id",
|
|
@@ -1402,37 +1668,6 @@ var JOB_INDEXES = [{
|
|
|
1402
1668
|
name: "idx_training_jobs_created",
|
|
1403
1669
|
columns: ["createdAt"]
|
|
1404
1670
|
}];
|
|
1405
|
-
var VERSION_COLUMNS = [
|
|
1406
|
-
{
|
|
1407
|
-
name: "id",
|
|
1408
|
-
type: "TEXT",
|
|
1409
|
-
primaryKey: true,
|
|
1410
|
-
notNull: true
|
|
1411
|
-
},
|
|
1412
|
-
{
|
|
1413
|
-
name: "nameKey",
|
|
1414
|
-
type: "TEXT",
|
|
1415
|
-
notNull: true
|
|
1416
|
-
},
|
|
1417
|
-
{
|
|
1418
|
-
name: "createdAt",
|
|
1419
|
-
type: "INTEGER",
|
|
1420
|
-
notNull: true
|
|
1421
|
-
},
|
|
1422
|
-
{
|
|
1423
|
-
name: "row",
|
|
1424
|
-
type: "JSON",
|
|
1425
|
-
notNull: true
|
|
1426
|
-
}
|
|
1427
|
-
];
|
|
1428
|
-
var VERSION_INDEXES = [{
|
|
1429
|
-
name: "idx_training_versions_name_key",
|
|
1430
|
-
columns: ["nameKey"],
|
|
1431
|
-
unique: true
|
|
1432
|
-
}, {
|
|
1433
|
-
name: "idx_training_versions_created",
|
|
1434
|
-
columns: ["createdAt"]
|
|
1435
|
-
}];
|
|
1436
1671
|
function parseJson(value) {
|
|
1437
1672
|
if (typeof value !== "string") return value;
|
|
1438
1673
|
try {
|
|
@@ -1450,11 +1685,6 @@ async function declareTrainingCollections(store) {
|
|
|
1450
1685
|
columns: [...JOB_COLUMNS],
|
|
1451
1686
|
indexes: [...JOB_INDEXES]
|
|
1452
1687
|
});
|
|
1453
|
-
await store.declareCollection.mutate({
|
|
1454
|
-
collection: TRAINING_DATASET_VERSIONS_COLLECTION,
|
|
1455
|
-
columns: [...VERSION_COLUMNS],
|
|
1456
|
-
indexes: [...VERSION_INDEXES]
|
|
1457
|
-
});
|
|
1458
1688
|
}
|
|
1459
1689
|
var SettingsJobStore = class {
|
|
1460
1690
|
store;
|
|
@@ -1514,74 +1744,6 @@ var SettingsJobStore = class {
|
|
|
1514
1744
|
return null;
|
|
1515
1745
|
}
|
|
1516
1746
|
};
|
|
1517
|
-
var nameKeyOf = (name) => name.trim().toLowerCase();
|
|
1518
|
-
var SettingsVersionStore = class {
|
|
1519
|
-
store;
|
|
1520
|
-
logger;
|
|
1521
|
-
constructor(store, logger) {
|
|
1522
|
-
this.store = store;
|
|
1523
|
-
this.logger = logger;
|
|
1524
|
-
}
|
|
1525
|
-
/** Insert once; an existing id is left as it is (a version is immutable). */
|
|
1526
|
-
put = async (row) => {
|
|
1527
|
-
if (await this.get(row.id) !== null) return false;
|
|
1528
|
-
await this.store.set.mutate({
|
|
1529
|
-
collection: TRAINING_DATASET_VERSIONS_COLLECTION,
|
|
1530
|
-
key: row.id,
|
|
1531
|
-
value: {
|
|
1532
|
-
nameKey: nameKeyOf(row.name),
|
|
1533
|
-
createdAt: row.createdAt,
|
|
1534
|
-
row
|
|
1535
|
-
}
|
|
1536
|
-
});
|
|
1537
|
-
return true;
|
|
1538
|
-
};
|
|
1539
|
-
get = async (id) => {
|
|
1540
|
-
const row = await this.store.get.query({
|
|
1541
|
-
collection: TRAINING_DATASET_VERSIONS_COLLECTION,
|
|
1542
|
-
key: id
|
|
1543
|
-
});
|
|
1544
|
-
return row === null || row === void 0 ? null : this.parse(id, recordOf(row));
|
|
1545
|
-
};
|
|
1546
|
-
list = async () => {
|
|
1547
|
-
return (await this.store.query.query({
|
|
1548
|
-
collection: TRAINING_DATASET_VERSIONS_COLLECTION,
|
|
1549
|
-
filter: { orderBy: {
|
|
1550
|
-
field: "createdAt",
|
|
1551
|
-
direction: "desc"
|
|
1552
|
-
} }
|
|
1553
|
-
})).flatMap((r) => {
|
|
1554
|
-
const parsed = this.parse(r.id, recordOf(r.data));
|
|
1555
|
-
return parsed === null ? [] : [parsed];
|
|
1556
|
-
});
|
|
1557
|
-
};
|
|
1558
|
-
findIdByName = async (name) => {
|
|
1559
|
-
return (await this.store.query.query({
|
|
1560
|
-
collection: "training:dataset-versions",
|
|
1561
|
-
filter: {
|
|
1562
|
-
where: { nameKey: nameKeyOf(name) },
|
|
1563
|
-
limit: 1
|
|
1564
|
-
}
|
|
1565
|
-
}))[0]?.id ?? null;
|
|
1566
|
-
};
|
|
1567
|
-
remove = async (id) => {
|
|
1568
|
-
if (await this.get(id) === null) return false;
|
|
1569
|
-
await this.store.delete.mutate({
|
|
1570
|
-
collection: TRAINING_DATASET_VERSIONS_COLLECTION,
|
|
1571
|
-
key: id
|
|
1572
|
-
});
|
|
1573
|
-
return true;
|
|
1574
|
-
};
|
|
1575
|
-
parse(id, data) {
|
|
1576
|
-
const parsed = require_dist.TrainingDatasetVersionRowSchema.safeParse(parseJson(data["row"]));
|
|
1577
|
-
if (parsed.success) return parsed.data;
|
|
1578
|
-
this.logger.warn("training dataset version row does not parse — skipped", { meta: {
|
|
1579
|
-
versionId: id,
|
|
1580
|
-
issue: parsed.error.issues[0]?.message ?? "unknown"
|
|
1581
|
-
} });
|
|
1582
|
-
return null;
|
|
1583
|
-
}
|
|
1584
|
-
};
|
|
1585
1747
|
//#endregion
|
|
1586
1748
|
//#region src/orchestrator/trainer-source.ts
|
|
1587
1749
|
var TRAINER_SOURCE = {
|
|
@@ -1622,11 +1784,10 @@ var TrainingAddon = class extends require_dist.BaseAddon {
|
|
|
1622
1784
|
if (peer === void 0) throw new Error("training needs the peer-bytes facility");
|
|
1623
1785
|
await declareTrainingCollections(api.settingsStore);
|
|
1624
1786
|
const logger = this.ctx.logger;
|
|
1625
|
-
const versions = new SettingsVersionStore(api.settingsStore, logger);
|
|
1626
1787
|
const ports = {
|
|
1627
1788
|
provider: providerPort(api),
|
|
1628
1789
|
datasets: datasetPort(api),
|
|
1629
|
-
|
|
1790
|
+
corpus: corpusPort(api),
|
|
1630
1791
|
jobs: new SettingsJobStore(api.settingsStore, logger),
|
|
1631
1792
|
artifacts: createDiskArtifacts(node_path.default.join(this.ctx.dataDir, "training", "jobs"), peer),
|
|
1632
1793
|
registry: registryPort(api),
|
|
@@ -1639,20 +1800,13 @@ var TrainingAddon = class extends require_dist.BaseAddon {
|
|
|
1639
1800
|
const flow = new ReturnFlow(ports, engine.mutator);
|
|
1640
1801
|
this.engine = engine;
|
|
1641
1802
|
const provider = {
|
|
1642
|
-
putDatasetVersion: async (row) => ({ stored: await versions.put(row) }),
|
|
1643
|
-
getDatasetVersion: async ({ id }) => versions.get(id),
|
|
1644
|
-
listDatasetVersions: async () => (await versions.list()).map(({ frameIds, ...rest }) => ({
|
|
1645
|
-
...rest,
|
|
1646
|
-
frameCount: frameIds.length
|
|
1647
|
-
})),
|
|
1648
|
-
findDatasetVersionIdByName: async ({ name }) => ({ id: await versions.findIdByName(name) }),
|
|
1649
|
-
removeDatasetVersion: async ({ id }) => ({ removed: await versions.remove(id) }),
|
|
1650
1803
|
listBaseModels: async () => BASE_MODELS.map(publicBaseModel),
|
|
1651
1804
|
planJob: async (request) => (await planJob(request, {
|
|
1652
|
-
|
|
1805
|
+
corpus: ports.corpus,
|
|
1653
1806
|
provider: ports.provider
|
|
1654
1807
|
})).plan,
|
|
1655
1808
|
startJob: async (input) => engine.startJob(input),
|
|
1809
|
+
startJobBatch: async (input) => engine.startJobBatch(input),
|
|
1656
1810
|
listJobs: async () => ports.jobs.list(),
|
|
1657
1811
|
getJob: async ({ id }) => ports.jobs.get(id),
|
|
1658
1812
|
cancelJob: async ({ id }) => engine.cancelJob(id),
|