diff --git a/studio/backend/core/inference/llama_cpp.py b/studio/backend/core/inference/llama_cpp.py
index 37a57e4c39..dac8d380c3 100644
--- a/studio/backend/core/inference/llama_cpp.py
+++ b/studio/backend/core/inference/llama_cpp.py
@@ -249,17 +249,42 @@ class LlamaCppBackend:
)
# Determine the filename from the variant (e.g., "Q4_K_M" -> find matching file)
+ # For split GGUFs (e.g., *-00001-of-00003.gguf) we must download ALL shards.
gguf_filename = None
+ gguf_extra_shards: list[str] = []
if hf_variant:
# Try common naming patterns
try:
+ import re
from huggingface_hub import list_repo_files
files = list_repo_files(hf_repo, token=hf_token)
variant_lower = hf_variant.lower()
- for f in files:
- if f.endswith(".gguf") and variant_lower in f.lower():
- gguf_filename = f
- break
+ # Use word-boundary matching so "Q8_0" doesn't also
+ # match "IQ8_0" or other superset variant names.
+ boundary = re.compile(
+ r'(?= 0
+ and dataset_slice_end >= _slice_start):
+ # Manual slice — stream only the rows we need instead of
+ # downloading the entire dataset.
+ rows_to_stream = dataset_slice_end + 1
+ print(
+ f"[dataset-slice] Manual slice specified "
+ f"(start={dataset_slice_start}, end={dataset_slice_end}), "
+ f"streaming {rows_to_stream} rows\n"
+ )
+ stream = load_dataset(**load_kwargs, streaming=True)
+ dataset = Dataset.from_list(list(stream.take(rows_to_stream)))
+ print(
+ f"[dataset-slice] Downloaded {len(dataset)} rows "
+ f"(requested {rows_to_stream})\n"
+ )
+ self._update_progress(
+ status_message=f"Streamed {len(dataset)} rows from HuggingFace"
+ )
+ else:
+ dataset = load_dataset(**load_kwargs)
# Check if stopped during dataset loading
if self.should_stop:
@@ -1877,7 +1901,7 @@ class UnslothTrainer:
return None
self._update_progress(status_message=f"Loaded dataset from HuggingFace: {dataset_source}")
- print(f"Loaded dataset from Hugging Face: {dataset_source}\n")
+ print(f"Loaded dataset from Hugging Face: {dataset_source} ({len(dataset)} rows)\n")
# Resolve eval split from a separate HF split (explicit or auto-detected)
if eval_enabled:
diff --git a/studio/frontend/src/components/ui/tooltip.tsx b/studio/frontend/src/components/ui/tooltip.tsx
index 94fba54cc2..30eb1e7b80 100644
--- a/studio/frontend/src/components/ui/tooltip.tsx
+++ b/studio/frontend/src/components/ui/tooltip.tsx
@@ -1,6 +1,6 @@
-// SPDX-License-Identifier: AGPL-3.0-only - See /studio/LICENSE.AGPL-3.0
-// Copyright © 2025 Unsloth AI
-
+// SPDX-License-Identifier: AGPL-3.0-only - See /studio/LICENSE.AGPL-3.0
+// Copyright © 2025 Unsloth AI
+
import { Tooltip as TooltipPrimitive } from "radix-ui";
import type * as React from "react";
@@ -46,14 +46,14 @@ function TooltipContent({
{children}
-
+
);
diff --git a/studio/frontend/src/features/studio/sections/dataset-section.tsx b/studio/frontend/src/features/studio/sections/dataset-section.tsx
index c61565cbd9..4f20133b5c 100644
--- a/studio/frontend/src/features/studio/sections/dataset-section.tsx
+++ b/studio/frontend/src/features/studio/sections/dataset-section.tsx
@@ -89,6 +89,13 @@ function formatUpdatedDate(timestamp: number | null): string {
return new Date(timestamp * 1000).toLocaleDateString();
}
+function normalizeSliceInput(value: string): string | null {
+ const trimmed = value.trim();
+ if (!trimmed) return null;
+ if (!/^\d+$/.test(trimmed)) return null;
+ return trimmed;
+}
+
export function DatasetSection() {
const {
dataset,
@@ -641,6 +648,19 @@ export function DatasetSection() {
datasetEvalSplit={datasetEvalSplit}
setDatasetEvalSplit={setDatasetEvalSplit}
/>
+ ) : !selectedDatasetName ? (
+
) : datasetSource === "upload" && selectedLocalDataset ? (
diff --git a/studio/frontend/src/features/training/api/mappers.ts b/studio/frontend/src/features/training/api/mappers.ts
index f9fb07d6dc..b10c282113 100644
--- a/studio/frontend/src/features/training/api/mappers.ts
+++ b/studio/frontend/src/features/training/api/mappers.ts
@@ -12,7 +12,7 @@ function parseSliceValue(value: string | null): number | null {
const trimmed = value.trim();
if (!trimmed) return null;
const num = Number(trimmed);
- if (!Number.isFinite(num) || !Number.isInteger(num)) return null;
+ if (!Number.isFinite(num) || !Number.isInteger(num) || num < 0) return null;
return num;
}
diff --git a/studio/frontend/src/features/training/api/models-api.ts b/studio/frontend/src/features/training/api/models-api.ts
index 64ac3884e3..ca5bdc7bf7 100644
--- a/studio/frontend/src/features/training/api/models-api.ts
+++ b/studio/frontend/src/features/training/api/models-api.ts
@@ -67,8 +67,8 @@ export interface ModelConfigResponse {
config?: BackendModelConfig | null;
is_vision: boolean;
is_embedding?: boolean;
+ is_audio: boolean;
is_lora: boolean;
- is_audio?: boolean;
base_model?: string | null;
model_type?: "text" | "vision" | "audio" | "embeddings" | null;
}
diff --git a/studio/frontend/src/features/training/components/hf-dataset-subset-split-selectors.tsx b/studio/frontend/src/features/training/components/hf-dataset-subset-split-selectors.tsx
index 0dab4685be..b177a41999 100644
--- a/studio/frontend/src/features/training/components/hf-dataset-subset-split-selectors.tsx
+++ b/studio/frontend/src/features/training/components/hf-dataset-subset-split-selectors.tsx
@@ -56,6 +56,8 @@ export function HfDatasetSubsetSplitSelectors({
} = useHfDatasetSplits(enabled ? datasetName : null, datasetSubset, {
accessToken,
});
+ const showPlaceholderDropdowns =
+ variant === "studio" && !enabled && !datasetName;
// Auto-select subset and split in one pass to avoid racing effects
useEffect(() => {
@@ -83,12 +85,48 @@ export function HfDatasetSubsetSplitSelectors({
setDatasetSplit,
]);
- if (!enabled || !datasetName) return null;
-
const showDropdowns = !isLoading && !error && hfSubsets.length > 0;
return (
<>
+ {showPlaceholderDropdowns && (
+ <>
+
+
+
+
+
+ >
+ )}
+
{isLoading && (
@@ -217,8 +260,9 @@ function SelectorDropdown({