diff --git a/studio/frontend/src/features/studio/sections/training-section.tsx b/studio/frontend/src/features/studio/sections/training-section.tsx index 6f0297b03f..43cc2f889e 100644 --- a/studio/frontend/src/features/studio/sections/training-section.tsx +++ b/studio/frontend/src/features/studio/sections/training-section.tsx @@ -12,6 +12,7 @@ import { serializeConfigToYaml, useTrainingActions, useTrainingConfigStore, + validateTrainingConfig, } from "@/features/training"; import { Archive04Icon, @@ -43,7 +44,8 @@ export function TrainingSection() { const { isStarting, startError, startTrainingRun } = useTrainingActions(); const isIncompatible = !store.isVisionModel && store.isDatasetImage === true; - const fileInputRef = useRef(null); + const configValidation = validateTrainingConfig(store); + const fileInputRef = useRef(null); const handleFileUpload = (e: React.ChangeEvent) => { const file = e.target.files?.[0]; @@ -150,7 +152,7 @@ export function TrainingSection() { data-tour="studio-start" className="w-full cursor-pointer bg-gradient-to-r from-emerald-500 to-teal-500 text-white hover:from-emerald-600 hover:to-teal-600" onClick={() => void startTrainingRun()} - disabled={isStarting || isIncompatible} + disabled={isStarting || isIncompatible || !configValidation.ok} > {isStarting ? "Starting..." : "Start Training"} @@ -163,6 +165,9 @@ export function TrainingSection() { Text model is not compatible with a multimodal dataset. Switch to a vision model or choose a text-only dataset.

)} + {!configValidation.ok && configValidation.message && !isIncompatible && ( +

{configValidation.message}

+ )} {/* Upload / Save / Reset */}

Training Config

diff --git a/studio/frontend/src/features/training/index.ts b/studio/frontend/src/features/training/index.ts index d2fd4f2e43..9da25f88bb 100644 --- a/studio/frontend/src/features/training/index.ts +++ b/studio/frontend/src/features/training/index.ts @@ -11,3 +11,4 @@ export { listLocalModels } from "./api/models-api"; export type { LocalModelInfo } from "./api/models-api"; export type { TrainingPhase } from "./types/runtime"; export { parseYamlConfig, serializeConfigToYaml } from "./lib/yaml-config"; +export { validateTrainingConfig } from "./lib/validation"; diff --git a/studio/frontend/src/features/training/lib/validation.ts b/studio/frontend/src/features/training/lib/validation.ts index ae89cdaf51..6ca95c4b02 100644 --- a/studio/frontend/src/features/training/lib/validation.ts +++ b/studio/frontend/src/features/training/lib/validation.ts @@ -16,18 +16,22 @@ export function validateTrainingConfig( if (!config.dataset) { return { ok: false, message: "Select a Hugging Face dataset first." }; } - return { ok: true, message: null }; - } - - if (config.datasetSource === "upload") { + } else if (config.datasetSource === "upload") { if (!config.uploadedFile) { return { ok: false, message: "Select a local dataset first." }; } - return { ok: true, message: null }; + } else { + return { ok: false, message: "Unsupported dataset source." }; } - return { - ok: false, - message: "Unsupported dataset source.", - }; + // Eval steps requires an eval split to be selected + if (config.evalSteps > 0 && !config.datasetEvalSplit) { + return { + ok: false, + message: + "Eval Steps is set but no Eval Split is selected. Choose an Eval Split or set Eval Steps to 0.", + }; + } + + return { ok: true, message: null }; }