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