{dictationSupported && (
diff --git a/studio/frontend/src/features/chat/stores/chat-runtime-store.ts b/studio/frontend/src/features/chat/stores/chat-runtime-store.ts
index 7fc1998b35..5715705725 100644
--- a/studio/frontend/src/features/chat/stores/chat-runtime-store.ts
+++ b/studio/frontend/src/features/chat/stores/chat-runtime-store.ts
@@ -46,6 +46,11 @@ type ChatRuntimeStore = {
ggufContextLength: number | null;
supportsReasoning: boolean;
reasoningEnabled: boolean;
+ supportsTools: boolean;
+ toolsEnabled: boolean;
+ toolStatus: string | null;
+ generatingStatus: string | null;
+ kvCacheDtype: string | null;
defaultChatTemplate: string | null;
chatTemplateOverride: string | null;
activeThreadId: string | null;
@@ -63,6 +68,10 @@ type ChatRuntimeStore = {
setActiveThreadId: (threadId: string | null) => void;
clearCheckpoint: () => void;
setReasoningEnabled: (enabled: boolean) => void;
+ setToolsEnabled: (enabled: boolean) => void;
+ setToolStatus: (status: string | null) => void;
+ setGeneratingStatus: (status: string | null) => void;
+ setKvCacheDtype: (dtype: string | null) => void;
setChatTemplateOverride: (template: string | null) => void;
setPendingAudio: (base64: string, name: string) => void;
clearPendingAudio: () => void;
@@ -79,6 +88,11 @@ export const useChatRuntimeStore = create
((set) => ({
ggufContextLength: null,
supportsReasoning: false,
reasoningEnabled: true,
+ supportsTools: false,
+ toolsEnabled: false,
+ toolStatus: null,
+ generatingStatus: null,
+ kvCacheDtype: null,
defaultChatTemplate: null,
chatTemplateOverride: null,
activeThreadId: null,
@@ -124,10 +138,18 @@ export const useChatRuntimeStore = create((set) => ({
ggufContextLength: null,
supportsReasoning: false,
reasoningEnabled: true,
+ supportsTools: false,
+ toolsEnabled: false,
+ toolStatus: null,
+ kvCacheDtype: null,
defaultChatTemplate: null,
chatTemplateOverride: null,
})),
setReasoningEnabled: (reasoningEnabled) => set({ reasoningEnabled }),
+ setToolsEnabled: (toolsEnabled) => set({ toolsEnabled }),
+ setToolStatus: (toolStatus) => set({ toolStatus }),
+ setGeneratingStatus: (generatingStatus) => set({ generatingStatus }),
+ setKvCacheDtype: (kvCacheDtype) => set({ kvCacheDtype }),
setChatTemplateOverride: (chatTemplateOverride) => set({ chatTemplateOverride }),
setPendingAudio: (base64, name) =>
set({ pendingAudioBase64: base64, pendingAudioName: name }),
diff --git a/studio/frontend/src/features/chat/types/api.ts b/studio/frontend/src/features/chat/types/api.ts
index f9eca3455d..d40b5fa853 100644
--- a/studio/frontend/src/features/chat/types/api.ts
+++ b/studio/frontend/src/features/chat/types/api.ts
@@ -40,6 +40,7 @@ export interface LoadModelRequest {
/** Allow loading models with custom code (e.g. NVIDIA Nemotron). Only enable for repos you trust. */
trust_remote_code?: boolean;
chat_template_override?: string | null;
+ cache_type_kv?: string | null;
}
export interface ValidateModelResponse {
@@ -85,6 +86,8 @@ export interface LoadModelResponse {
};
context_length?: number | null;
supports_reasoning?: boolean;
+ supports_tools?: boolean;
+ cache_type_kv?: string | null;
chat_template?: string | null;
}
@@ -139,6 +142,7 @@ export interface OpenAIChatCompletionsRequest {
audio_base64?: string;
use_adapter?: boolean | string | null;
enable_thinking?: boolean | null;
+ enable_tools?: boolean | null;
}
export interface OpenAIChatDelta {
diff --git a/studio/frontend/src/features/onboarding/components/steps/model-selection-step.tsx b/studio/frontend/src/features/onboarding/components/steps/model-selection-step.tsx
index 5e981fef3b..f05643c092 100644
--- a/studio/frontend/src/features/onboarding/components/steps/model-selection-step.tsx
+++ b/studio/frontend/src/features/onboarding/components/steps/model-selection-step.tsx
@@ -33,7 +33,7 @@ import {
TooltipContent,
TooltipTrigger,
} from "@/components/ui/tooltip";
-import { MODEL_TYPE_TO_HF_TASK } from "@/config/training";
+import { MODEL_TYPE_TO_HF_TASK, PRIORITY_TRAINING_MODELS, applyPriorityOrdering } from "@/config/training";
import {
useDebouncedValue,
useGpuInfo,
@@ -96,12 +96,16 @@ export function ModelSelectionStep() {
task,
accessToken: hfToken || undefined,
excludeGguf: true,
+ priorityIds: PRIORITY_TRAINING_MODELS,
});
const { error: tokenValidationError, isChecking: isCheckingToken } =
useHfTokenValidation(hfToken);
- const resultIds = useMemo(() => hfResults.map((r) => r.id), [hfResults]);
+ const resultIds = useMemo(() => {
+ const ids = hfResults.map((r) => r.id);
+ return applyPriorityOrdering(ids);
+ }, [hfResults]);
// Match Studio behavior: only show exception signals (OOM/TIGHT) in training flows.
const vramMap = useMemo(() => {
diff --git a/studio/frontend/src/features/studio/sections/model-section.tsx b/studio/frontend/src/features/studio/sections/model-section.tsx
index 72d6abcf59..77edf68d24 100644
--- a/studio/frontend/src/features/studio/sections/model-section.tsx
+++ b/studio/frontend/src/features/studio/sections/model-section.tsx
@@ -28,7 +28,7 @@ import {
TooltipContent,
TooltipTrigger,
} from "@/components/ui/tooltip";
-import { MODEL_TYPE_TO_HF_TASK } from "@/config/training";
+import { MODEL_TYPE_TO_HF_TASK, PRIORITY_TRAINING_MODELS, applyPriorityOrdering } from "@/config/training";
import {
useDebouncedValue,
useGpuInfo,
@@ -162,6 +162,7 @@ export function ModelSection() {
task,
accessToken: hfToken || undefined,
excludeGguf: true,
+ priorityIds: PRIORITY_TRAINING_MODELS,
});
const { error: tokenValidationError, isChecking: isCheckingToken } =
@@ -172,7 +173,8 @@ export function ModelSection() {
if (selectedModel && !ids.includes(selectedModel)) {
ids.push(selectedModel);
}
- return ids;
+
+ return applyPriorityOrdering(ids);
}, [hfResults, selectedModel]);
// Filter out GGUF models — they can't be used for training
diff --git a/studio/frontend/src/features/studio/sections/training-section.tsx b/studio/frontend/src/features/studio/sections/training-section.tsx
index d1cec00e22..a47654d290 100644
--- a/studio/frontend/src/features/studio/sections/training-section.tsx
+++ b/studio/frontend/src/features/studio/sections/training-section.tsx
@@ -46,7 +46,8 @@ export function TrainingSection() {
const store = useTrainingConfigStore();
const { isStarting, startError, startTrainingRun } = useTrainingActions();
const isIncompatible =
- !store.isVisionModel && store.isDatasetImage === true;
+ (!store.isVisionModel && store.isDatasetImage === true) ||
+ (!store.isAudioModel && store.isDatasetAudio === true);
const configValidation = validateTrainingConfig(store);
const fileInputRef = useRef(null);
@@ -155,10 +156,10 @@ 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 || !configValidation.ok}
+ disabled={isStarting || isIncompatible || store.isCheckingDataset || !configValidation.ok}
>
- {isStarting ? "Starting..." : "Start Training"}
+ {isStarting ? "Starting..." : store.isCheckingDataset ? "Checking dataset..." : "Start Training"}
{startError && (
{startError}
diff --git a/studio/frontend/src/features/training/stores/training-config-store.ts b/studio/frontend/src/features/training/stores/training-config-store.ts
index 212dc78e6c..e1b8d4b1a2 100644
--- a/studio/frontend/src/features/training/stores/training-config-store.ts
+++ b/studio/frontend/src/features/training/stores/training-config-store.ts
@@ -210,6 +210,7 @@ export const useTrainingConfigStore = create()(
hfToken: state.hfToken.trim() || null,
subset: state.datasetSubset,
split,
+ isVlm: state.isVisionModel,
})
.then((res) => {
if (controller.signal.aborted) return;
diff --git a/studio/frontend/src/hooks/use-hf-model-search.ts b/studio/frontend/src/hooks/use-hf-model-search.ts
index f7b06ab65a..69ea4d3b83 100644
--- a/studio/frontend/src/hooks/use-hf-model-search.ts
+++ b/studio/frontend/src/hooks/use-hf-model-search.ts
@@ -2,7 +2,7 @@
// Copyright 2026-present the Unsloth AI Inc. team. All rights reserved. See /studio/LICENSE.AGPL-3.0
import type { PipelineType } from "@huggingface/hub";
-import { listModels } from "@huggingface/hub";
+import { listModels, modelInfo } from "@huggingface/hub";
import { useCallback, useMemo } from "react";
import { useHfPaginatedSearch } from "./use-hf-paginated-search";
@@ -148,17 +148,73 @@ async function* mergedModelIterator(
}
}
+/**
+ * Creates an async generator that yields priority models (fetched individually
+ * via modelInfo for full metadata), then the general unsloth listing.
+ */
+async function* priorityThenListingIterator(
+ priorityIds: readonly string[],
+ task?: PipelineType,
+ accessToken?: string,
+): AsyncGenerator {
+ const common = {
+ additionalFields: ["safetensors", "tags"] as ("safetensors" | "tags")[],
+ fetch: withPopularitySort,
+ ...(accessToken ? { credentials: { accessToken } } : {}),
+ };
+
+ // Phase 1: fetch priority models in parallel via modelInfo
+ const seen = new Set();
+ const settled = await Promise.allSettled(
+ priorityIds.map((id) =>
+ modelInfo({
+ name: id,
+ additionalFields: ["safetensors", "tags"],
+ ...(accessToken ? { credentials: { accessToken } } : {}),
+ }),
+ ),
+ );
+ for (const result of settled) {
+ if (result.status === "fulfilled") {
+ const m = result.value as { name?: string; pipeline_tag?: string };
+ // Skip models that don't match the selected task filter
+ if (task && m.pipeline_tag && m.pipeline_tag !== task) continue;
+ if (m.name) seen.add(m.name);
+ yield result.value;
+ }
+ }
+
+ // Phase 2: yield general unsloth listing, skipping already-seen
+ const generalIter = listModels({
+ search: { owner: "unsloth", ...(task ? { task } : {}) },
+ ...common,
+ });
+ for await (const model of generalIter) {
+ const m = model as { name?: string };
+ if (m.name && seen.has(m.name)) continue;
+ yield model;
+ }
+}
+
export function useHfModelSearch(
query: string,
- options?: { task?: PipelineType; accessToken?: string; excludeGguf?: boolean },
+ options?: {
+ task?: PipelineType;
+ accessToken?: string;
+ excludeGguf?: boolean;
+ priorityIds?: readonly string[];
+ },
) {
- const { task, accessToken, excludeGguf = false } = options ?? {};
+ const { task, accessToken, excludeGguf = false, priorityIds } = options ?? {};
const createIter = useCallback(
() => {
const trimmed = query.trim();
if (!trimmed) {
- // No query → show default unsloth models
+ // No query → show priority models first (with full metadata), then general unsloth listing
+ if (priorityIds && priorityIds.length > 0) {
+ return priorityThenListingIterator(priorityIds, task, accessToken) as AsyncGenerator;
+ }
return listModels({
search: { owner: "unsloth", ...(task ? { task } : {}) },
additionalFields: ["safetensors", "tags"],
@@ -169,7 +225,7 @@ export function useHfModelSearch(
// Typed query: disable task filter so explicitly searched models still appear even if HF task metadata is wrong/missing.
return mergedModelIterator(trimmed, undefined, accessToken) as AsyncGenerator;
},
- [query, task, accessToken],
+ [query, task, accessToken, priorityIds],
);
const mapModel = useMemo(() => makeMapModel(excludeGguf), [excludeGguf]);
diff --git a/studio/setup.ps1 b/studio/setup.ps1
index d9e7c5a43b..15b5d217b0 100644
--- a/studio/setup.ps1
+++ b/studio/setup.ps1
@@ -1054,6 +1054,10 @@ if (Test-Path $LlamaServerBin) {
}
# Common flags
$CmakeArgs += '-DBUILD_SHARED_LIBS=OFF'
+ $CmakeArgs += '-DLLAMA_BUILD_TESTS=OFF'
+ $CmakeArgs += '-DLLAMA_BUILD_EXAMPLES=OFF'
+ $CmakeArgs += '-DLLAMA_BUILD_SERVER=ON'
+ $CmakeArgs += '-DGGML_NATIVE=ON'
# HTTPS support via OpenSSL
if ($OpenSslAvailable -and $OpenSslRoot) {
$CmakeArgs += "-DOPENSSL_ROOT_DIR=$OpenSslRoot"
diff --git a/studio/setup.sh b/studio/setup.sh
index 5fbdce918f..7fe8dd7e51 100755
--- a/studio/setup.sh
+++ b/studio/setup.sh
@@ -168,7 +168,8 @@ for candidate in $(compgen -c python3 2>/dev/null | grep -E '^python3(\.[0-9]+)?
continue
fi
# Get version string, e.g. "Python 3.12.5"
- ver_str=$("$candidate" --version 2>&1 | awk '{print $2}')
+ ver_str=$("$candidate" --version 2>&1) || continue
+ ver_str=$(echo "$ver_str" | awk '{print $2}')
py_major=$(echo "$ver_str" | cut -d. -f1)
py_minor=$(echo "$ver_str" | cut -d. -f2)
@@ -194,7 +195,7 @@ for candidate in $(compgen -c python3 2>/dev/null | grep -E '^python3(\.[0-9]+)?
BEST_MINOR="$py_minor"
fi
done
-
+echo "finished finding best python"
if [ -z "$BEST_PY" ]; then
echo "❌ ERROR: No Python version between 3.${MIN_PY_MINOR} and 3.${MAX_PY_MINOR} found on this system."
echo " Detected Python 3 installations:"
@@ -296,7 +297,15 @@ rm -rf "$LLAMA_CPP_DIR"
run_quiet "clone llama.cpp" git clone --depth 1 https://github.com/ggml-org/llama.cpp.git "$LLAMA_CPP_DIR" || BUILD_OK=false
if [ "$BUILD_OK" = true ]; then
- CMAKE_ARGS=""
+ # Skip tests/examples we don't need (faster build)
+ CMAKE_ARGS="-DLLAMA_BUILD_TESTS=OFF -DLLAMA_BUILD_EXAMPLES=OFF -DLLAMA_BUILD_SERVER=ON -DGGML_NATIVE=ON"
+
+ # Use ccache if available (dramatically faster rebuilds)
+ if command -v ccache &>/dev/null; then
+ CMAKE_ARGS="$CMAKE_ARGS -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache -DCMAKE_CUDA_COMPILER_LAUNCHER=ccache"
+ echo " Using ccache for faster compilation"
+ fi
+
# Detect CUDA: check nvcc on PATH, then common install locations
NVCC_PATH=""
if command -v nvcc &>/dev/null; then
@@ -312,7 +321,7 @@ rm -rf "$LLAMA_CPP_DIR"
if [ -n "$NVCC_PATH" ]; then
echo " Building with CUDA support (nvcc: $NVCC_PATH)..."
- CMAKE_ARGS="-DGGML_CUDA=ON"
+ CMAKE_ARGS="$CMAKE_ARGS -DGGML_CUDA=ON"
# Detect GPU compute capability and limit CUDA architectures
# Without this, cmake builds for ALL default archs (very slow)
diff --git a/unsloth/models/rl.py b/unsloth/models/rl.py
index 5c61a6eae2..a05186eee5 100755
--- a/unsloth/models/rl.py
+++ b/unsloth/models/rl.py
@@ -1119,14 +1119,15 @@ def _patch_trl_rl_trainers(trainer_file = "grpo_trainer"):
if "dataset_num_proc" in call_args:
num_proc_check = (
"import multiprocessing as _mp\n"
- "if _mp.get_start_method() != 'fork':\n"
- " dataset_num_proc = None\n"
- "elif dataset_num_proc is None:\n"
- " import psutil\n"
- " dataset_num_proc = min(max((psutil.cpu_count() or 1)+4, 2), 64)\n"
- " memory_gb_left = psutil.virtual_memory().available / (1024**3)\n"
- " if memory_gb_left <= 2: dataset_num_proc = 1\n"
- " else: dataset_num_proc = min(dataset_num_proc, int(memory_gb_left))\n"
+ "if dataset_num_proc is None:\n"
+ " if _mp.get_start_method() != 'fork':\n"
+ " dataset_num_proc = None\n"
+ " else:\n"
+ " import psutil\n"
+ " dataset_num_proc = min(max((psutil.cpu_count() or 1)+4, 2), 64)\n"
+ " memory_gb_left = psutil.virtual_memory().available / (1024**3)\n"
+ " if memory_gb_left <= 2: dataset_num_proc = 1\n"
+ " else: dataset_num_proc = min(dataset_num_proc, int(memory_gb_left))\n"
)
extra_args += num_proc_check