Merge pull request #259 from unslothai/feat/dataset-subsets-split
Feat/dataset subsets split
This commit is contained in:
commit
cb3e4f2c26
10 changed files with 442 additions and 393 deletions
|
|
@ -357,48 +357,25 @@ def format_dataset(
|
|||
conversations = []
|
||||
num_examples = len(examples[list(examples.keys())[0]])
|
||||
|
||||
# NEW: Check if this is user-provided or auto-detected
|
||||
is_user_provided = custom_format_mapping is not None # Passed explicitly
|
||||
|
||||
# Preserve non-mapped columns ONLY if auto-detected
|
||||
preserved_columns = {}
|
||||
if not is_user_provided: # Only preserve for auto-detection
|
||||
all_columns = set(examples.keys())
|
||||
mapped_columns = set(custom_mapping.keys())
|
||||
non_mapped_columns = all_columns - mapped_columns
|
||||
|
||||
for col in non_mapped_columns:
|
||||
preserved_columns[col] = examples[col]
|
||||
# Preserve non-mapped columns
|
||||
all_columns = set(examples.keys())
|
||||
mapped_columns = set(custom_mapping.keys())
|
||||
preserved_columns = {
|
||||
col: examples[col]
|
||||
for col in all_columns - mapped_columns
|
||||
}
|
||||
|
||||
for i in range(num_examples):
|
||||
convo = []
|
||||
|
||||
# Enforce standard role order
|
||||
role_order = ['system', 'user', 'assistant']
|
||||
|
||||
for target_role in role_order:
|
||||
for target_role in ['system', 'user', 'assistant']:
|
||||
for col_name, role in custom_mapping.items():
|
||||
if role == target_role and col_name in examples:
|
||||
content = examples[col_name][i]
|
||||
|
||||
# NEW: Different behavior based on mapping source
|
||||
if is_user_provided:
|
||||
# User explicitly mapped this - always include even if empty
|
||||
convo.append({"role": role, "content": str(content) if content else ""})
|
||||
else:
|
||||
# Auto-detected - skip empty (original behavior)
|
||||
if content and str(content).strip():
|
||||
convo.append({"role": role, "content": str(content)})
|
||||
|
||||
if content and str(content).strip():
|
||||
convo.append({"role": role, "content": str(content)})
|
||||
conversations.append(convo)
|
||||
|
||||
result = {"conversations": conversations}
|
||||
|
||||
# Only add preserved columns if auto-detected
|
||||
if not is_user_provided:
|
||||
result.update(preserved_columns)
|
||||
|
||||
return result
|
||||
return {"conversations": conversations, **preserved_columns}
|
||||
|
||||
|
||||
try:
|
||||
|
|
@ -507,7 +484,7 @@ def format_dataset(
|
|||
}
|
||||
|
||||
# CHATML MODE: Convert to ChatML
|
||||
elif format_type in ["chatml", "conversational"]:
|
||||
elif format_type in ["chatml", "conversational", "sharegpt"]:
|
||||
|
||||
if detected["format"] == "alpaca":
|
||||
converted = convert_alpaca_to_chatml(dataset, batch_size, num_proc)
|
||||
|
|
@ -556,36 +533,38 @@ def format_dataset(
|
|||
|
||||
else:
|
||||
warnings.append(f"Unknown format, attempting standardization")
|
||||
try:
|
||||
standardized = standardize_chat_format(
|
||||
dataset, tokenizer, aliases_for_system,
|
||||
aliases_for_user, aliases_for_assistant,
|
||||
batch_size, num_proc
|
||||
)
|
||||
return {
|
||||
"dataset": standardized,
|
||||
"detected_format": "unknown",
|
||||
"final_format": f"chatml_{detected['chat_column']}",
|
||||
"chat_column": detected["chat_column"],
|
||||
"is_standardized": True,
|
||||
"requires_manual_mapping": False,
|
||||
"is_multimodal": multimodal_info["is_multimodal"],
|
||||
"multimodal_info": multimodal_info,
|
||||
"warnings": warnings
|
||||
}
|
||||
except Exception as e:
|
||||
warnings.append(f"Standardization failed: {e}")
|
||||
return {
|
||||
"dataset": dataset,
|
||||
"detected_format": "unknown",
|
||||
"final_format": "unknown",
|
||||
"chat_column": detected["chat_column"],
|
||||
"is_standardized": False,
|
||||
"requires_manual_mapping": True,
|
||||
"is_multimodal": multimodal_info["is_multimodal"],
|
||||
"multimodal_info": multimodal_info,
|
||||
"warnings": warnings
|
||||
}
|
||||
if detected["chat_column"]:
|
||||
try:
|
||||
standardized = standardize_chat_format(
|
||||
dataset, tokenizer, aliases_for_system,
|
||||
aliases_for_user, aliases_for_assistant,
|
||||
batch_size, num_proc
|
||||
)
|
||||
return {
|
||||
"dataset": standardized,
|
||||
"detected_format": "unknown",
|
||||
"final_format": f"chatml_{detected['chat_column']}",
|
||||
"chat_column": detected["chat_column"],
|
||||
"is_standardized": True,
|
||||
"requires_manual_mapping": False,
|
||||
"is_multimodal": multimodal_info["is_multimodal"],
|
||||
"multimodal_info": multimodal_info,
|
||||
"warnings": warnings
|
||||
}
|
||||
except Exception as e:
|
||||
warnings.append(f"Standardization failed: {e}")
|
||||
|
||||
return {
|
||||
"dataset": dataset,
|
||||
"detected_format": "unknown",
|
||||
"final_format": "unknown",
|
||||
"chat_column": detected["chat_column"],
|
||||
"is_standardized": False,
|
||||
"requires_manual_mapping": True,
|
||||
"is_multimodal": multimodal_info["is_multimodal"],
|
||||
"multimodal_info": multimodal_info,
|
||||
"warnings": warnings
|
||||
}
|
||||
|
||||
else:
|
||||
raise ValueError(f"Unknown format_type: {format_type}")
|
||||
|
|
@ -816,8 +795,10 @@ def format_and_template_dataset(
|
|||
)
|
||||
|
||||
# Step 2: Apply chat template
|
||||
if "gemma" in model_name.lower() and not dataset_info["is_multimodal"] and (format_type != "alpaca" or (format_type == "auto" and dataset_info["detected_format"] != "alpaca")):
|
||||
print("remove_bos_prefix is true")
|
||||
# Gemma emits a leading <bos> that must be stripped for text-only chatml/sharegpt.
|
||||
is_alpaca = format_type == "alpaca" or (format_type == "auto" and dataset_info["detected_format"] == "alpaca")
|
||||
is_gemma = "gemma" in model_name.lower()
|
||||
if is_gemma and not dataset_info["is_multimodal"] and not is_alpaca:
|
||||
remove_bos_prefix = True
|
||||
template_result = apply_chat_template_to_dataset(
|
||||
dataset_info=dataset_info,
|
||||
|
|
@ -839,14 +820,24 @@ def format_and_template_dataset(
|
|||
all_warnings = dataset_info.get("warnings", []) + template_result.get("warnings", [])
|
||||
all_errors = template_result.get("errors", [])
|
||||
|
||||
# If format_dataset returned "unknown" but apply_chat_template rescued
|
||||
# it via heuristic detection, update final_format to reflect reality.
|
||||
final_format = dataset_info["final_format"]
|
||||
requires_manual = dataset_info.get("requires_manual_mapping", False)
|
||||
if final_format == "unknown" and template_result["success"]:
|
||||
out_ds = template_result["dataset"]
|
||||
if hasattr(out_ds, "column_names") and "text" in out_ds.column_names:
|
||||
final_format = "chatml_conversations"
|
||||
requires_manual = False
|
||||
|
||||
return {
|
||||
"dataset": template_result["dataset"],
|
||||
"detected_format": dataset_info["detected_format"],
|
||||
"final_format": dataset_info["final_format"],
|
||||
"final_format": final_format,
|
||||
"chat_column": dataset_info.get("chat_column"),
|
||||
"is_vlm": False, # This is LLM flow
|
||||
"success": template_result["success"],
|
||||
"requires_manual_mapping": dataset_info.get("requires_manual_mapping", False),
|
||||
"requires_manual_mapping": requires_manual,
|
||||
"warnings": all_warnings,
|
||||
"errors": all_errors,
|
||||
"summary": summary,
|
||||
|
|
|
|||
|
|
@ -75,6 +75,8 @@ export function DatasetStep() {
|
|||
setDatasetSubset,
|
||||
datasetSplit,
|
||||
setDatasetSplit,
|
||||
datasetEvalSplit,
|
||||
setDatasetEvalSplit,
|
||||
uploadedFile,
|
||||
setUploadedFile,
|
||||
} = useTrainingConfigStore(
|
||||
|
|
@ -91,6 +93,8 @@ export function DatasetStep() {
|
|||
setDatasetSubset: s.setDatasetSubset,
|
||||
datasetSplit: s.datasetSplit,
|
||||
setDatasetSplit: s.setDatasetSplit,
|
||||
datasetEvalSplit: s.datasetEvalSplit,
|
||||
setDatasetEvalSplit: s.setDatasetEvalSplit,
|
||||
uploadedFile: s.uploadedFile,
|
||||
setUploadedFile: s.setUploadedFile,
|
||||
})),
|
||||
|
|
@ -304,6 +308,8 @@ export function DatasetStep() {
|
|||
setDatasetSubset={setDatasetSubset}
|
||||
datasetSplit={datasetSplit}
|
||||
setDatasetSplit={setDatasetSplit}
|
||||
datasetEvalSplit={datasetEvalSplit}
|
||||
setDatasetEvalSplit={setDatasetEvalSplit}
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
|
|
|
|||
|
|
@ -1,5 +1,10 @@
|
|||
import { SectionCard } from "@/components/section-card";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Collapsible,
|
||||
CollapsibleContent,
|
||||
CollapsibleTrigger,
|
||||
} from "@/components/ui/collapsible";
|
||||
import {
|
||||
Combobox,
|
||||
ComboboxContent,
|
||||
|
|
@ -35,6 +40,7 @@ import {
|
|||
useTrainingConfigStore,
|
||||
} from "@/features/training";
|
||||
import {
|
||||
ArrowDown01Icon,
|
||||
CloudUploadIcon,
|
||||
Database02Icon,
|
||||
FileAttachmentIcon,
|
||||
|
|
@ -66,6 +72,8 @@ export function DatasetSection() {
|
|||
setDatasetSubset,
|
||||
datasetSplit,
|
||||
setDatasetSplit,
|
||||
datasetEvalSplit,
|
||||
setDatasetEvalSplit,
|
||||
hfToken,
|
||||
} = useTrainingConfigStore(
|
||||
useShallow((s) => ({
|
||||
|
|
@ -77,11 +85,14 @@ export function DatasetSection() {
|
|||
setDatasetSubset: s.setDatasetSubset,
|
||||
datasetSplit: s.datasetSplit,
|
||||
setDatasetSplit: s.setDatasetSplit,
|
||||
datasetEvalSplit: s.datasetEvalSplit,
|
||||
setDatasetEvalSplit: s.setDatasetEvalSplit,
|
||||
hfToken: s.hfToken,
|
||||
})),
|
||||
);
|
||||
|
||||
const [inputValue, setInputValue] = useState("");
|
||||
const [advancedOpen, setAdvancedOpen] = useState(false);
|
||||
const openPreview = useDatasetPreviewDialogStore((s) => s.openPreview);
|
||||
const selectingRef = useRef(false);
|
||||
const debouncedQuery = useDebouncedValue(inputValue);
|
||||
|
|
@ -132,79 +143,82 @@ export function DatasetSection() {
|
|||
title="Dataset"
|
||||
description="Select or upload training data"
|
||||
accent="indigo"
|
||||
className="md:min-h-[450px] dark:shadow-border"
|
||||
className="md:min-h-[470px] dark:shadow-border"
|
||||
>
|
||||
<div className="flex flex-col gap-4">
|
||||
<div className="flex flex-col gap-2">
|
||||
<span className="flex items-center gap-1.5 text-xs font-medium text-muted-foreground">
|
||||
Load from Hub
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild={true}>
|
||||
<button
|
||||
type="button"
|
||||
className="text-foreground/70 hover:text-foreground"
|
||||
>
|
||||
<HugeiconsIcon
|
||||
icon={InformationCircleIcon}
|
||||
className="size-3"
|
||||
/>
|
||||
</button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
Search Hugging Face datasets or enter a path like
|
||||
'username/dataset-name'.{" "}
|
||||
<a
|
||||
href="https://unsloth.ai/docs/get-started/fine-tuning-llms-guide/datasets-guide"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-primary underline"
|
||||
>
|
||||
Read more
|
||||
</a>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</span>
|
||||
<div
|
||||
ref={comboboxAnchorRef}
|
||||
onKeyDown={(event) => {
|
||||
if (event.key !== "Enter") return;
|
||||
if (!(event.target instanceof HTMLInputElement)) return;
|
||||
event.preventDefault();
|
||||
if (hfResults.length > 0) {
|
||||
handleDatasetSelect(hfResults[0].id);
|
||||
} else {
|
||||
const text = event.target.value.trim();
|
||||
if (text) handleDatasetSelect(text);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Combobox
|
||||
items={resultIds}
|
||||
filteredItems={resultIds}
|
||||
filter={null}
|
||||
value={dataset}
|
||||
onValueChange={handleDatasetSelect}
|
||||
onInputValueChange={handleInputChange}
|
||||
itemToStringValue={(id) => id}
|
||||
autoHighlight={true}
|
||||
<div className="flex flex-col gap-2">
|
||||
<span className="flex items-center gap-1.5 text-xs font-medium text-muted-foreground">
|
||||
Load from Hub
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild={true}>
|
||||
<button
|
||||
type="button"
|
||||
className="text-foreground/70 hover:text-foreground"
|
||||
>
|
||||
<HugeiconsIcon
|
||||
icon={InformationCircleIcon}
|
||||
className="size-3"
|
||||
/>
|
||||
</button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
Search Hugging Face datasets or enter a path like
|
||||
'username/dataset-name'.{" "}
|
||||
<a
|
||||
href="https://unsloth.ai/docs/get-started/fine-tuning-llms-guide/datasets-guide"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-primary underline"
|
||||
>
|
||||
Read more
|
||||
</a>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</span>
|
||||
<div
|
||||
ref={comboboxAnchorRef}
|
||||
onKeyDown={(event) => {
|
||||
if (event.key !== "Enter") return;
|
||||
if (!(event.target instanceof HTMLInputElement)) return;
|
||||
event.preventDefault();
|
||||
if (hfResults.length > 0) {
|
||||
handleDatasetSelect(hfResults[0].id);
|
||||
} else {
|
||||
const text = event.target.value.trim();
|
||||
if (text) handleDatasetSelect(text);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<ComboboxInput placeholder="Search datasets..." className="w-full">
|
||||
<InputGroupAddon>
|
||||
<HugeiconsIcon icon={Search01Icon} className="size-4" />
|
||||
</InputGroupAddon>
|
||||
</ComboboxInput>
|
||||
<ComboboxContent anchor={comboboxAnchorRef}>
|
||||
{isLoading ? (
|
||||
<div className="flex items-center justify-center py-4 gap-2 text-xs text-muted-foreground">
|
||||
<Spinner className="size-4" /> Searching...
|
||||
</div>
|
||||
) : (
|
||||
<ComboboxEmpty>No datasets found</ComboboxEmpty>
|
||||
)}
|
||||
<div
|
||||
ref={scrollRef}
|
||||
className="max-h-64 overflow-y-auto overscroll-contain [scrollbar-width:thin]"
|
||||
<Combobox
|
||||
items={resultIds}
|
||||
filteredItems={resultIds}
|
||||
filter={null}
|
||||
value={dataset}
|
||||
onValueChange={handleDatasetSelect}
|
||||
onInputValueChange={handleInputChange}
|
||||
itemToStringValue={(id) => id}
|
||||
autoHighlight={true}
|
||||
>
|
||||
<ComboboxInput
|
||||
placeholder="Search datasets..."
|
||||
className="w-full"
|
||||
>
|
||||
<InputGroupAddon>
|
||||
<HugeiconsIcon icon={Search01Icon} className="size-4" />
|
||||
</InputGroupAddon>
|
||||
</ComboboxInput>
|
||||
<ComboboxContent anchor={comboboxAnchorRef}>
|
||||
{isLoading ? (
|
||||
<div className="flex items-center justify-center py-4 gap-2 text-xs text-muted-foreground">
|
||||
<Spinner className="size-4" /> Searching...
|
||||
</div>
|
||||
) : (
|
||||
<ComboboxEmpty>No datasets found</ComboboxEmpty>
|
||||
)}
|
||||
<div
|
||||
ref={scrollRef}
|
||||
className="max-h-64 overflow-y-auto overscroll-contain [scrollbar-width:thin]"
|
||||
>
|
||||
<ComboboxList className="p-1 !max-h-none !overflow-visible">
|
||||
{(id: string) => {
|
||||
const r = hfResults.find((ds) => ds.id === id);
|
||||
|
|
@ -220,58 +234,58 @@ export function DatasetSection() {
|
|||
<ComboboxItem
|
||||
key={id}
|
||||
value={id}
|
||||
className="justify-between"
|
||||
>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild={true}>
|
||||
<span className="min-w-0 flex-1 truncate">
|
||||
className="justify-between"
|
||||
>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild={true}>
|
||||
<span className="min-w-0 flex-1 truncate">
|
||||
{id}
|
||||
</span>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent
|
||||
side="left"
|
||||
className="max-w-xs break-all"
|
||||
>
|
||||
{id}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
{detail && (
|
||||
<span className="text-[10px] text-muted-foreground shrink-0">
|
||||
{detail}
|
||||
</span>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent
|
||||
side="left"
|
||||
className="max-w-xs break-all"
|
||||
>
|
||||
{id}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
{detail && (
|
||||
<span className="text-[10px] text-muted-foreground shrink-0">
|
||||
{detail}
|
||||
</span>
|
||||
)}
|
||||
</ComboboxItem>
|
||||
);
|
||||
}}
|
||||
</ComboboxList>
|
||||
<div ref={sentinelRef} className="h-px" />
|
||||
{isLoadingMore && (
|
||||
<div className="flex items-center justify-center py-2">
|
||||
<Spinner className="size-3.5 text-muted-foreground" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</ComboboxContent>
|
||||
</Combobox>
|
||||
)}
|
||||
</ComboboxItem>
|
||||
);
|
||||
}}
|
||||
</ComboboxList>
|
||||
<div ref={sentinelRef} className="h-px" />
|
||||
{isLoadingMore && (
|
||||
<div className="flex items-center justify-center py-2">
|
||||
<Spinner className="size-3.5 text-muted-foreground" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</ComboboxContent>
|
||||
</Combobox>
|
||||
</div>
|
||||
{(tokenValidationError ?? hfSearchError) && (
|
||||
<p className="text-xs text-destructive">
|
||||
{tokenValidationError ?? hfSearchError}
|
||||
{" — "}
|
||||
<a
|
||||
href="https://huggingface.co/settings/tokens"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="underline"
|
||||
>
|
||||
Get or update token
|
||||
</a>
|
||||
</p>
|
||||
)}
|
||||
{isCheckingToken && (
|
||||
<p className="text-xs text-muted-foreground">Checking token…</p>
|
||||
)}
|
||||
</div>
|
||||
{(tokenValidationError ?? hfSearchError) && (
|
||||
<p className="text-xs text-destructive">
|
||||
{tokenValidationError ?? hfSearchError}
|
||||
{" — "}
|
||||
<a
|
||||
href="https://huggingface.co/settings/tokens"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="underline"
|
||||
>
|
||||
Get or update token
|
||||
</a>
|
||||
</p>
|
||||
)}
|
||||
{isCheckingToken && (
|
||||
<p className="text-xs text-muted-foreground">Checking token…</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<HfDatasetSubsetSplitSelectors
|
||||
variant="studio"
|
||||
|
|
@ -282,52 +296,67 @@ export function DatasetSection() {
|
|||
setDatasetSubset={setDatasetSubset}
|
||||
datasetSplit={datasetSplit}
|
||||
setDatasetSplit={setDatasetSplit}
|
||||
datasetEvalSplit={datasetEvalSplit}
|
||||
setDatasetEvalSplit={setDatasetEvalSplit}
|
||||
/>
|
||||
|
||||
<div className="flex flex-col gap-2">
|
||||
<span className="flex items-center gap-1.5 text-xs font-medium text-muted-foreground">
|
||||
Target Format
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild={true}>
|
||||
<button
|
||||
type="button"
|
||||
className="text-foreground/70 hover:text-foreground"
|
||||
>
|
||||
<HugeiconsIcon
|
||||
icon={InformationCircleIcon}
|
||||
className="size-3"
|
||||
/>
|
||||
</button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
Format of your training data. Auto-detect works for most
|
||||
datasets.{" "}
|
||||
<a
|
||||
href="https://unsloth.ai/docs/get-started/fine-tuning-llms-guide/datasets-guide"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-primary underline"
|
||||
>
|
||||
Read more
|
||||
</a>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</span>
|
||||
<Select
|
||||
value={datasetFormat}
|
||||
onValueChange={(v) => setDatasetFormat(v as typeof datasetFormat)}
|
||||
>
|
||||
<SelectTrigger className="w-full">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="auto">Auto</SelectItem>
|
||||
<SelectItem value="alpaca">Alpaca</SelectItem>
|
||||
<SelectItem value="chatml">ChatML</SelectItem>
|
||||
<SelectItem value="sharegpt">ShareGPT</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<Collapsible open={advancedOpen} onOpenChange={setAdvancedOpen}>
|
||||
<CollapsibleTrigger className="flex w-full cursor-pointer items-center gap-1.5 text-xs text-muted-foreground">
|
||||
<HugeiconsIcon
|
||||
icon={ArrowDown01Icon}
|
||||
className={`size-3.5 transition-transform ${advancedOpen ? "rotate-180" : ""}`}
|
||||
/>
|
||||
Advanced
|
||||
</CollapsibleTrigger>
|
||||
<CollapsibleContent className="mt-3">
|
||||
<div className="flex flex-col gap-2">
|
||||
<span className="flex items-center gap-1.5 text-xs font-medium text-muted-foreground">
|
||||
Target Format
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild={true}>
|
||||
<button
|
||||
type="button"
|
||||
className="text-foreground/70 hover:text-foreground"
|
||||
>
|
||||
<HugeiconsIcon
|
||||
icon={InformationCircleIcon}
|
||||
className="size-3"
|
||||
/>
|
||||
</button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
Format of your training data. Auto-detect works for most
|
||||
datasets.{" "}
|
||||
<a
|
||||
href="https://unsloth.ai/docs/get-started/fine-tuning-llms-guide/datasets-guide"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-primary underline"
|
||||
>
|
||||
Read more
|
||||
</a>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</span>
|
||||
<Select
|
||||
value={datasetFormat}
|
||||
onValueChange={(v) =>
|
||||
setDatasetFormat(v as typeof datasetFormat)
|
||||
}
|
||||
>
|
||||
<SelectTrigger className="w-full">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="auto">Auto</SelectItem>
|
||||
<SelectItem value="alpaca">Alpaca</SelectItem>
|
||||
<SelectItem value="chatml">ChatML</SelectItem>
|
||||
<SelectItem value="sharegpt">ShareGPT</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</CollapsibleContent>
|
||||
</Collapsible>
|
||||
|
||||
{dataset ? (
|
||||
<div className="flex items-center gap-3 rounded-lg border bg-muted/40 px-3.5 py-3">
|
||||
|
|
|
|||
|
|
@ -127,7 +127,7 @@ export function ParamsSection(): ReactElement {
|
|||
title="Parameters"
|
||||
description="Configure training hyperparameters"
|
||||
accent="orange"
|
||||
className="md:min-h-[450px]"
|
||||
className="md:min-h-[470px]"
|
||||
>
|
||||
<div className="flex flex-col gap-4">
|
||||
{/* Max Steps */}
|
||||
|
|
|
|||
|
|
@ -98,7 +98,7 @@ export function TrainingSection() {
|
|||
title="Training"
|
||||
description="Monitor and control training"
|
||||
accent="blue"
|
||||
className="md:min-h-[450px]"
|
||||
className="md:min-h-[470px]"
|
||||
>
|
||||
<div className="flex flex-col gap-4">
|
||||
{/* Loss chart */}
|
||||
|
|
|
|||
|
|
@ -24,8 +24,9 @@ export function buildTrainingStartPayload(
|
|||
load_in_4bit: adapterMethod ? isQlorMethod : false,
|
||||
max_seq_length: config.contextLength,
|
||||
hf_dataset: hfDataset,
|
||||
hf_dataset_config: hfDataset ? config.datasetSubset : null,
|
||||
hf_dataset_split: hfDataset ? config.datasetSplit : null,
|
||||
subset: hfDataset ? config.datasetSubset : null,
|
||||
train_split: hfDataset ? config.datasetSplit : null,
|
||||
eval_split: hfDataset ? config.datasetEvalSplit : null,
|
||||
local_datasets: [],
|
||||
format_type: config.datasetFormat,
|
||||
custom_format_mapping: customFormatMapping,
|
||||
|
|
|
|||
|
|
@ -29,6 +29,8 @@ type Props = {
|
|||
setDatasetSubset: (v: string | null) => void;
|
||||
datasetSplit: string | null;
|
||||
setDatasetSplit: (v: string | null) => void;
|
||||
datasetEvalSplit: string | null;
|
||||
setDatasetEvalSplit: (v: string | null) => void;
|
||||
};
|
||||
|
||||
export function HfDatasetSubsetSplitSelectors({
|
||||
|
|
@ -40,12 +42,13 @@ export function HfDatasetSubsetSplitSelectors({
|
|||
setDatasetSubset,
|
||||
datasetSplit,
|
||||
setDatasetSplit,
|
||||
datasetEvalSplit,
|
||||
setDatasetEvalSplit,
|
||||
}: Props) {
|
||||
const {
|
||||
subsets: hfSubsets,
|
||||
splits: hfSplits,
|
||||
hasMultipleSubsets,
|
||||
hasMultipleSplits,
|
||||
isLoading,
|
||||
error,
|
||||
} = useHfDatasetSplits(enabled ? datasetName : null, datasetSubset, {
|
||||
|
|
@ -78,6 +81,8 @@ export function HfDatasetSubsetSplitSelectors({
|
|||
|
||||
if (!enabled || !datasetName) return null;
|
||||
|
||||
const showDropdowns = !isLoading && !error && hfSubsets.length > 0;
|
||||
|
||||
return (
|
||||
<>
|
||||
{isLoading && (
|
||||
|
|
@ -105,167 +110,169 @@ export function HfDatasetSubsetSplitSelectors({
|
|||
</div>
|
||||
)}
|
||||
|
||||
{!isLoading && !error && hasMultipleSubsets && (
|
||||
{showDropdowns && (
|
||||
<>
|
||||
{variant === "wizard" ? (
|
||||
<Field>
|
||||
<FieldLabel className="flex items-center gap-1.5">
|
||||
Subset
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild={true}>
|
||||
<button
|
||||
type="button"
|
||||
className="text-muted-foreground/50 hover:text-muted-foreground"
|
||||
>
|
||||
<HugeiconsIcon
|
||||
icon={InformationCircleIcon}
|
||||
className="size-3.5"
|
||||
/>
|
||||
</button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent className="max-w-xs">
|
||||
This dataset has multiple subsets. Select which one to use
|
||||
for training.
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</FieldLabel>
|
||||
<Select
|
||||
value={datasetSubset ?? ""}
|
||||
onValueChange={(v) => setDatasetSubset(v || null)}
|
||||
>
|
||||
<SelectTrigger className="w-full">
|
||||
<SelectValue placeholder="Select a subset..." />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{hfSubsets.map((subset) => (
|
||||
<SelectItem key={subset} value={subset}>
|
||||
{subset}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</Field>
|
||||
) : (
|
||||
<div className="flex flex-col gap-1.5">
|
||||
<span className="flex items-center gap-1.5 text-xs font-medium text-muted-foreground">
|
||||
Subset
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild={true}>
|
||||
<button
|
||||
type="button"
|
||||
className="text-foreground/70 hover:text-foreground"
|
||||
>
|
||||
<HugeiconsIcon
|
||||
icon={InformationCircleIcon}
|
||||
className="size-3"
|
||||
/>
|
||||
</button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
This dataset has multiple subsets. Select which one to use
|
||||
for training.
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</span>
|
||||
<Select
|
||||
value={datasetSubset ?? ""}
|
||||
onValueChange={(v) => setDatasetSubset(v || null)}
|
||||
>
|
||||
<SelectTrigger className="w-full">
|
||||
<SelectValue placeholder="Select a subset..." />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{hfSubsets.map((subset) => (
|
||||
<SelectItem key={subset} value={subset}>
|
||||
{subset}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
{variant === "studio" ? (
|
||||
<div className="grid gap-3 sm:grid-cols-2">
|
||||
<SelectorDropdown
|
||||
variant={variant}
|
||||
label="Subset"
|
||||
tooltip="Select which subset (config) of the dataset to use."
|
||||
value={datasetSubset}
|
||||
onChange={setDatasetSubset}
|
||||
options={hfSubsets}
|
||||
placeholder="Select a subset..."
|
||||
/>
|
||||
<SelectorDropdown
|
||||
variant={variant}
|
||||
label="Train Split"
|
||||
tooltip="Select which split to use for training."
|
||||
value={datasetSplit}
|
||||
onChange={setDatasetSplit}
|
||||
options={hfSplits}
|
||||
placeholder="Select a split..."
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
{!isLoading && !error && hasMultipleSplits && (
|
||||
<>
|
||||
{variant === "wizard" ? (
|
||||
<Field>
|
||||
<FieldLabel className="flex items-center gap-1.5">
|
||||
Split
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild={true}>
|
||||
<button
|
||||
type="button"
|
||||
className="text-muted-foreground/50 hover:text-muted-foreground"
|
||||
>
|
||||
<HugeiconsIcon
|
||||
icon={InformationCircleIcon}
|
||||
className="size-3.5"
|
||||
/>
|
||||
</button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent className="max-w-xs">
|
||||
Select which split of the dataset to use for training.
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</FieldLabel>
|
||||
<Select
|
||||
value={datasetSplit ?? ""}
|
||||
onValueChange={(v) => setDatasetSplit(v || null)}
|
||||
>
|
||||
<SelectTrigger className="w-full">
|
||||
<SelectValue placeholder="Select a split..." />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{hfSplits.map((split) => (
|
||||
<SelectItem key={split} value={split}>
|
||||
{split}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</Field>
|
||||
) : (
|
||||
<div className="flex flex-col gap-1.5">
|
||||
<span className="flex items-center gap-1.5 text-xs font-medium text-muted-foreground">
|
||||
Split
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild={true}>
|
||||
<button
|
||||
type="button"
|
||||
className="text-foreground/70 hover:text-foreground"
|
||||
>
|
||||
<HugeiconsIcon
|
||||
icon={InformationCircleIcon}
|
||||
className="size-3"
|
||||
/>
|
||||
</button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
Select which split of the dataset to use for training.
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</span>
|
||||
<Select
|
||||
value={datasetSplit ?? ""}
|
||||
onValueChange={(v) => setDatasetSplit(v || null)}
|
||||
>
|
||||
<SelectTrigger className="w-full">
|
||||
<SelectValue placeholder="Select a split..." />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{hfSplits.map((split) => (
|
||||
<SelectItem key={split} value={split}>
|
||||
{split}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<>
|
||||
<SelectorDropdown
|
||||
variant={variant}
|
||||
label="Subset"
|
||||
tooltip="Select which subset (config) of the dataset to use."
|
||||
value={datasetSubset}
|
||||
onChange={setDatasetSubset}
|
||||
options={hfSubsets}
|
||||
placeholder="Select a subset..."
|
||||
/>
|
||||
<SelectorDropdown
|
||||
variant={variant}
|
||||
label="Train Split"
|
||||
tooltip="Select which split to use for training."
|
||||
value={datasetSplit}
|
||||
onChange={setDatasetSplit}
|
||||
options={hfSplits}
|
||||
placeholder="Select a split..."
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
<SelectorDropdown
|
||||
variant={variant}
|
||||
label="Eval Split"
|
||||
tooltip="Select which split to use for evaluation. None means no evaluation during training."
|
||||
value={datasetEvalSplit}
|
||||
onChange={setDatasetEvalSplit}
|
||||
options={hfSplits}
|
||||
placeholder="None"
|
||||
allowNone
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function SelectorDropdown({
|
||||
variant,
|
||||
label,
|
||||
tooltip,
|
||||
value,
|
||||
onChange,
|
||||
options,
|
||||
placeholder,
|
||||
allowNone = false,
|
||||
}: {
|
||||
variant: "wizard" | "studio";
|
||||
label: string;
|
||||
tooltip: string;
|
||||
value: string | null;
|
||||
onChange: (v: string | null) => void;
|
||||
options: string[];
|
||||
placeholder: string;
|
||||
allowNone?: boolean;
|
||||
}) {
|
||||
if (variant === "wizard") {
|
||||
return (
|
||||
<Field>
|
||||
<FieldLabel className="flex items-center gap-1.5">
|
||||
{label}
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild={true}>
|
||||
<button
|
||||
type="button"
|
||||
className="text-muted-foreground/50 hover:text-muted-foreground"
|
||||
>
|
||||
<HugeiconsIcon
|
||||
icon={InformationCircleIcon}
|
||||
className="size-3.5"
|
||||
/>
|
||||
</button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent className="max-w-xs">
|
||||
{tooltip}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</FieldLabel>
|
||||
<Select
|
||||
value={value ?? "_none"}
|
||||
onValueChange={(v) => onChange(v === "_none" ? null : v)}
|
||||
>
|
||||
<SelectTrigger className="w-full">
|
||||
<SelectValue placeholder={placeholder} />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{allowNone && (
|
||||
<SelectItem value="_none">None</SelectItem>
|
||||
)}
|
||||
{options.map((opt) => (
|
||||
<SelectItem key={opt} value={opt}>
|
||||
{opt}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</Field>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-1.5">
|
||||
<span className="flex items-center gap-1.5 text-xs font-medium text-muted-foreground">
|
||||
{label}
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild={true}>
|
||||
<button
|
||||
type="button"
|
||||
className="text-foreground/70 hover:text-foreground"
|
||||
>
|
||||
<HugeiconsIcon
|
||||
icon={InformationCircleIcon}
|
||||
className="size-3"
|
||||
/>
|
||||
</button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
{tooltip}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</span>
|
||||
<Select
|
||||
value={value ?? "_none"}
|
||||
onValueChange={(v) => onChange(v === "_none" ? null : v)}
|
||||
>
|
||||
<SelectTrigger className="w-full">
|
||||
<SelectValue placeholder={placeholder} />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{allowNone && (
|
||||
<SelectItem value="_none">None</SelectItem>
|
||||
)}
|
||||
{options.map((opt) => (
|
||||
<SelectItem key={opt} value={opt}>
|
||||
{opt}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ const initialState: TrainingConfigState = {
|
|||
dataset: null,
|
||||
datasetSubset: null,
|
||||
datasetSplit: null,
|
||||
datasetEvalSplit: null,
|
||||
datasetManualMapping: emptyManualMapping(),
|
||||
uploadedFile: null,
|
||||
isCheckingVision: false,
|
||||
|
|
@ -252,6 +253,7 @@ export const useTrainingConfigStore = create<TrainingConfigStore>()(
|
|||
dataset,
|
||||
datasetSubset: null,
|
||||
datasetSplit: null,
|
||||
datasetEvalSplit: null,
|
||||
datasetManualMapping: emptyManualMapping(),
|
||||
isDatasetMultimodal: null,
|
||||
isCheckingDataset: false,
|
||||
|
|
@ -264,6 +266,7 @@ export const useTrainingConfigStore = create<TrainingConfigStore>()(
|
|||
set({
|
||||
datasetSubset,
|
||||
datasetSplit: null,
|
||||
datasetEvalSplit: null,
|
||||
datasetManualMapping: emptyManualMapping(),
|
||||
isDatasetMultimodal: null,
|
||||
isCheckingDataset: false,
|
||||
|
|
@ -300,6 +303,12 @@ export const useTrainingConfigStore = create<TrainingConfigStore>()(
|
|||
const split = state.datasetSplit || "train";
|
||||
runDatasetCheck(datasetName, split);
|
||||
},
|
||||
setDatasetEvalSplit: (datasetEvalSplit) => {
|
||||
set({
|
||||
datasetEvalSplit,
|
||||
evalSteps: datasetEvalSplit ? 0.1 : 0,
|
||||
});
|
||||
},
|
||||
setDatasetManualMapping: (datasetManualMapping) =>
|
||||
set({ datasetManualMapping }),
|
||||
setUploadedFile: (uploadedFile) => set({ uploadedFile }),
|
||||
|
|
@ -359,7 +368,7 @@ export const useTrainingConfigStore = create<TrainingConfigStore>()(
|
|||
},
|
||||
{
|
||||
name: "unsloth_training_config_v1",
|
||||
version: 5,
|
||||
version: 6,
|
||||
migrate: (persisted, version) => {
|
||||
const s = persisted as Record<string, unknown>;
|
||||
if (version < 2 && s.datasetSubset == null && s.datasetConfig != null) {
|
||||
|
|
@ -375,6 +384,9 @@ export const useTrainingConfigStore = create<TrainingConfigStore>()(
|
|||
if (version < 5 && s.lrSchedulerType == null) {
|
||||
s.lrSchedulerType = DEFAULT_HYPERPARAMS.lrSchedulerType;
|
||||
}
|
||||
if (version < 6 && s.datasetEvalSplit == null) {
|
||||
s.datasetEvalSplit = null;
|
||||
}
|
||||
return s as unknown as TrainingConfigStore;
|
||||
},
|
||||
partialize: partializePersistedState,
|
||||
|
|
|
|||
|
|
@ -5,8 +5,9 @@ export interface TrainingStartRequest {
|
|||
load_in_4bit: boolean;
|
||||
max_seq_length: number;
|
||||
hf_dataset: string | null;
|
||||
hf_dataset_config: string | null;
|
||||
hf_dataset_split: string | null;
|
||||
subset: string | null;
|
||||
train_split: string | null;
|
||||
eval_split: string | null;
|
||||
local_datasets: string[];
|
||||
format_type: string;
|
||||
custom_format_mapping?: Record<string, string> | null;
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ export interface TrainingConfigState {
|
|||
dataset: string | null;
|
||||
datasetSubset: string | null;
|
||||
datasetSplit: string | null;
|
||||
datasetEvalSplit: string | null;
|
||||
datasetManualMapping: DatasetManualMapping;
|
||||
uploadedFile: string | null;
|
||||
epochs: number;
|
||||
|
|
@ -81,6 +82,7 @@ export interface TrainingConfigActions {
|
|||
setDataset: (dataset: string | null) => void;
|
||||
setDatasetSubset: (subset: string | null) => void;
|
||||
setDatasetSplit: (split: string | null) => void;
|
||||
setDatasetEvalSplit: (split: string | null) => void;
|
||||
setDatasetManualMapping: (mapping: DatasetManualMapping) => void;
|
||||
setUploadedFile: (file: string | null) => void;
|
||||
setEpochs: (epochs: number) => void;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue