-Unsloth Studio lets you run and train text, [audio](https://unsloth.ai/docs/basics/text-to-speech-tts-fine-tuning), [embedding](https://unsloth.ai/docs/new/embedding-finetuning), [vision](https://unsloth.ai/docs/basics/vision-fine-tuning) models on Windows, Linux and macOS.
+Unsloth Studio (Beta) lets you run and train text, [audio](https://unsloth.ai/docs/basics/text-to-speech-tts-fine-tuning), [embedding](https://unsloth.ai/docs/new/embedding-finetuning), [vision](https://unsloth.ai/docs/basics/vision-fine-tuning) models on Windows, Linux and macOS.
## ⭐ Features
Unsloth provides several key features for both inference and training:
@@ -42,7 +42,7 @@ Unsloth provides several key features for both inference and training:
Unsloth can be used in two ways: through **[Unsloth Studio](https://unsloth.ai/docs/new/studio/)**, the web UI, or through **Unsloth Core**, the code-based version. Each has different requirements.
### Unsloth Studio (web UI)
-Unsloth Studio works on **Windows, Linux, WSL** and **macOS**.
+Unsloth Studio (Beta) works on **Windows, Linux, WSL** and **macOS**.
* **CPU:** Supported for **chat inference only**
* **NVIDIA:** Training works on RTX 30/40/50, Blackwell, DGX Spark, Station and more
@@ -51,7 +51,26 @@ Unsloth Studio works on **Windows, Linux, WSL** and **macOS**.
* **Coming soon:** Training support for Apple MLX, AMD, and Intel.
* **Multi-GPU:** Available now, with a major upgrade on the way
-#### MacOS, Linux or WSL Setup (One time):
+#### MacOS, Linux, WSL Setup:
+```bash
+curl -fsSL https://raw.githubusercontent.com/unslothai/unsloth/main/install.sh | sh
+```
+If you don't have `curl`, use `wget`. Then to launch after setup:
+```bash
+source unsloth_studio/bin/activate
+unsloth studio -H 0.0.0.0 -p 8888
+```
+
+#### Windows PowerShell Setup:
+```powershell
+irm https://raw.githubusercontent.com/unslothai/unsloth/main/install.ps1 | iex
+```
+Then to launch after setup:
+```powershell
+& .\unsloth_studio\Scripts\unsloth.exe studio -H 0.0.0.0 -p 8888
+```
+
+#### MacOS, Linux, WSL developer installs:
```bash
curl -LsSf https://astral.sh/uv/install.sh | sh
uv venv unsloth_studio --python 3.13
@@ -60,14 +79,9 @@ uv pip install unsloth --torch-backend=auto
unsloth studio setup
unsloth studio -H 0.0.0.0 -p 8888
```
-Then to launch every time:
-```bash
-source unsloth_studio/bin/activate
-unsloth studio -H 0.0.0.0 -p 8888
-```
-#### Windows PowerShell (One time):
-```bash
+#### Windows PowerShell developer installs:
+```powershell
winget install -e --id Python.Python.3.13
winget install --id=astral-sh.uv -e
uv venv unsloth_studio --python 3.13
@@ -76,15 +90,18 @@ uv pip install unsloth --torch-backend=auto
unsloth studio setup
unsloth studio -H 0.0.0.0 -p 8888
```
-Then to launch every time:
+
+#### Docker
+Use our [Docker image](https://hub.docker.com/r/unsloth/unsloth) ```unsloth/unsloth``` container. Run:
```bash
-.\unsloth_studio\Scripts\activate
-unsloth studio -H 0.0.0.0 -p 8888
-```
+docker run -d -e JUPYTER_PASSWORD="mypassword" \
+ -p 8888:8888 -p 8000:8000 -p 2222:22 \
+ -v $(pwd)/work:/workspace/work \
+ --gpus all \
+ unsloth/unsloth
+ ```
-Use our [Docker image](https://hub.docker.com/r/unsloth/unsloth) ```unsloth/unsloth``` container. Read our [Docker Guide](https://unsloth.ai/docs/get-started/install/docker).
-
-#### Nightly Installation - MacOS, Linux or WSL Setup (One time):
+#### Nightly Install - MacOS, Linux, WSL:
```bash
curl -LsSf https://astral.sh/uv/install.sh | sh
git clone --filter=blob:none https://github.com/unslothai/unsloth.git unsloth_studio
@@ -102,7 +119,8 @@ source .venv/bin/activate
unsloth studio -H 0.0.0.0 -p 8888
```
-#### Nightly Installation - Windows Powershell (One time):
+#### Nightly Install - Windows:
+Run in Windows Powershell:
```bash
winget install -e --id Python.Python.3.13
winget install --id=astral-sh.uv -e
diff --git a/build.sh b/build.sh
index 4948a73ae4..3118e8810a 100644
--- a/build.sh
+++ b/build.sh
@@ -4,8 +4,52 @@ set -euo pipefail
# 1. Build frontend (Vite outputs to dist/)
cd studio/frontend
+
+# Clean stale dist to force a full rebuild
+rm -rf dist
+
+# Tailwind v4's oxide scanner respects .gitignore in parent directories.
+# Python venvs create a .gitignore with "*" (ignore everything), which
+# prevents Tailwind from scanning .tsx source files for class names.
+# Temporarily hide any such .gitignore during the build, then restore it.
+_HIDDEN_GITIGNORES=()
+_dir="$(pwd)"
+while [ "$_dir" != "/" ]; do
+ _dir="$(dirname "$_dir")"
+ if [ -f "$_dir/.gitignore" ] && grep -qx '\*' "$_dir/.gitignore" 2>/dev/null; then
+ mv "$_dir/.gitignore" "$_dir/.gitignore._twbuild"
+ _HIDDEN_GITIGNORES+=("$_dir/.gitignore")
+ fi
+done
+
+_restore_gitignores() {
+ for _gi in "${_HIDDEN_GITIGNORES[@]+"${_HIDDEN_GITIGNORES[@]}"}"; do
+ mv "${_gi}._twbuild" "$_gi" 2>/dev/null || true
+ done
+}
+trap _restore_gitignores EXIT
+
npm install
npm run build # outputs to studio/frontend/dist/
+
+_restore_gitignores
+trap - EXIT
+
+# Validate CSS output -- catch truncated Tailwind builds before packaging
+MAX_CSS_SIZE=$(find dist/assets -name '*.css' -exec wc -c {} + 2>/dev/null | sort -n | tail -1 | awk '{print $1}')
+if [ -z "$MAX_CSS_SIZE" ]; then
+ echo "❌ ERROR: No CSS files were emitted into dist/assets."
+ echo " The frontend build may have failed silently."
+ exit 1
+fi
+if [ "$MAX_CSS_SIZE" -lt 100000 ]; then
+ echo "❌ ERROR: Largest CSS file is only $((MAX_CSS_SIZE / 1024))KB (expected >100KB)."
+ echo " Tailwind may not have scanned all source files."
+ echo " Check for .gitignore files blocking the Tailwind oxide scanner."
+ exit 1
+fi
+echo "✅ Frontend CSS validated (${MAX_CSS_SIZE} bytes)"
+
cd ../..
# 2. Clean old artifacts
diff --git a/install.ps1 b/install.ps1
new file mode 100644
index 0000000000..0d65ef4e06
--- /dev/null
+++ b/install.ps1
@@ -0,0 +1,119 @@
+# Unsloth Studio Installer for Windows PowerShell
+# Usage: irm https://raw.githubusercontent.com/unslothai/unsloth/main/install.ps1 | iex
+# Local: Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass; .\install.ps1
+
+function Install-UnslothStudio {
+ $ErrorActionPreference = "Stop"
+
+ $VenvName = "unsloth_studio"
+ $PythonVersion = "3.13"
+
+ Write-Host ""
+ Write-Host "========================================="
+ Write-Host " Unsloth Studio Installer (Windows)"
+ Write-Host "========================================="
+ Write-Host ""
+
+ # ── Helper: refresh PATH from registry (preserving current session entries) ──
+ function Refresh-SessionPath {
+ $machine = [System.Environment]::GetEnvironmentVariable("Path", "Machine")
+ $user = [System.Environment]::GetEnvironmentVariable("Path", "User")
+ $env:Path = "$machine;$user;$env:Path"
+ }
+
+ # ── Check winget ──
+ if (-not (Get-Command winget -ErrorAction SilentlyContinue)) {
+ Write-Host "Error: winget is not available." -ForegroundColor Red
+ Write-Host " Install it from https://aka.ms/getwinget" -ForegroundColor Yellow
+ Write-Host " or install Python $PythonVersion and uv manually, then re-run." -ForegroundColor Yellow
+ return
+ }
+
+ # ── Install Python if no compatible version (3.11-3.13) found ──
+ $DetectedPythonVersion = ""
+ if (Get-Command python -ErrorAction SilentlyContinue) {
+ $pyVer = python --version 2>&1
+ if ($pyVer -match "Python (3\.1[1-3])\.\d+") {
+ Write-Host "==> Python already installed: $pyVer"
+ $DetectedPythonVersion = $Matches[1]
+ }
+ }
+ if (-not $DetectedPythonVersion) {
+ Write-Host "==> Installing Python ${PythonVersion}..."
+ winget install -e --id Python.Python.3.13 --accept-package-agreements --accept-source-agreements
+ Refresh-SessionPath
+ if ($LASTEXITCODE -ne 0) {
+ # winget returns non-zero for "already installed" -- only fail if python is truly missing
+ if (-not (Get-Command python -ErrorAction SilentlyContinue)) {
+ Write-Host "[ERROR] Python installation failed (exit code $LASTEXITCODE)" -ForegroundColor Red
+ return
+ }
+ }
+ $DetectedPythonVersion = $PythonVersion
+ }
+
+ # ── Install uv if not present ──
+ if (-not (Get-Command uv -ErrorAction SilentlyContinue)) {
+ Write-Host "==> Installing uv package manager..."
+ winget install --id=astral-sh.uv -e --accept-package-agreements --accept-source-agreements
+ Refresh-SessionPath
+ # Fallback: if winget didn't put uv on PATH, try the PowerShell installer
+ if (-not (Get-Command uv -ErrorAction SilentlyContinue)) {
+ Write-Host " Trying alternative uv installer..."
+ powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"
+ Refresh-SessionPath
+ }
+ }
+
+ if (-not (Get-Command uv -ErrorAction SilentlyContinue)) {
+ Write-Host "Error: uv could not be installed." -ForegroundColor Red
+ Write-Host " Install it from https://docs.astral.sh/uv/" -ForegroundColor Yellow
+ return
+ }
+
+ # ── Create venv (skip if it already exists and has a valid interpreter) ──
+ $VenvPython = Join-Path $VenvName "Scripts\python.exe"
+ if (-not (Test-Path $VenvPython)) {
+ if (Test-Path $VenvName) { Remove-Item -Recurse -Force $VenvName }
+ Write-Host "==> Creating Python ${DetectedPythonVersion} virtual environment (${VenvName})..."
+ uv venv $VenvName --python $DetectedPythonVersion
+ if ($LASTEXITCODE -ne 0) {
+ Write-Host "[ERROR] Failed to create virtual environment (exit code $LASTEXITCODE)" -ForegroundColor Red
+ return
+ }
+ } else {
+ Write-Host "==> Virtual environment ${VenvName} already exists, skipping creation."
+ }
+
+ # ── Install unsloth directly into the venv (no activation needed) ──
+ Write-Host "==> Installing unsloth (this may take a few minutes)..."
+ uv pip install --python $VenvPython unsloth --torch-backend=auto
+ if ($LASTEXITCODE -ne 0) {
+ Write-Host "[ERROR] Failed to install unsloth (exit code $LASTEXITCODE)" -ForegroundColor Red
+ return
+ }
+
+ # ── Run studio setup ──
+ # setup.ps1 will handle installing Git, CMake, Visual Studio Build Tools,
+ # CUDA Toolkit, Node.js, and other dependencies automatically via winget.
+ Write-Host "==> Running unsloth studio setup..."
+ $UnslothExe = Join-Path $VenvName "Scripts\unsloth.exe"
+ & $UnslothExe studio setup
+ if ($LASTEXITCODE -ne 0) {
+ Write-Host "[ERROR] unsloth studio setup failed (exit code $LASTEXITCODE)" -ForegroundColor Red
+ return
+ }
+
+ Write-Host ""
+ Write-Host "========================================="
+ Write-Host " Unsloth Studio installed!"
+ Write-Host "========================================="
+ Write-Host ""
+ Write-Host " To launch, run:"
+ Write-Host ""
+ Write-Host " .\${VenvName}\Scripts\activate"
+ Write-Host " unsloth studio -H 0.0.0.0 -p 8888"
+ Write-Host ""
+}
+
+Install-UnslothStudio
diff --git a/install.sh b/install.sh
new file mode 100755
index 0000000000..5e846a2feb
--- /dev/null
+++ b/install.sh
@@ -0,0 +1,212 @@
+#!/bin/sh
+# Unsloth Studio Installer
+# Usage (curl): curl -fsSL https://raw.githubusercontent.com/unslothai/unsloth/main/install.sh | sh
+# Usage (wget): wget -qO- https://raw.githubusercontent.com/unslothai/unsloth/main/install.sh | sh
+set -e
+
+VENV_NAME="unsloth_studio"
+PYTHON_VERSION="3.13"
+
+# ── Helper: download a URL to a file (supports curl and wget) ──
+download() {
+ if command -v curl >/dev/null 2>&1; then
+ curl -LsSf "$1" -o "$2"
+ elif command -v wget >/dev/null 2>&1; then
+ wget -qO "$2" "$1"
+ else
+ echo "Error: neither curl nor wget found. Install one and re-run."
+ exit 1
+ fi
+}
+
+# ── Helper: check if a single package is available on the system ──
+_is_pkg_installed() {
+ case "$1" in
+ build-essential) command -v gcc >/dev/null 2>&1 ;;
+ libcurl4-openssl-dev)
+ command -v dpkg >/dev/null 2>&1 && dpkg -s "$1" >/dev/null 2>&1 ;;
+ pciutils)
+ command -v lspci >/dev/null 2>&1 ;;
+ *) command -v "$1" >/dev/null 2>&1 ;;
+ esac
+}
+
+# ── Helper: install packages via apt, escalating to sudo only if needed ──
+# Usage: _smart_apt_install pkg1 pkg2 pkg3 ...
+_smart_apt_install() {
+ _PKGS="$*"
+
+ # Step 1: Try installing without sudo (works when already root)
+ apt-get update -y >/dev/null 2>&1 || true
+ apt-get install -y $_PKGS >/dev/null 2>&1 || true
+
+ # Step 2: Check which packages are still missing
+ _STILL_MISSING=""
+ for _pkg in $_PKGS; do
+ if ! _is_pkg_installed "$_pkg"; then
+ _STILL_MISSING="$_STILL_MISSING $_pkg"
+ fi
+ done
+ _STILL_MISSING=$(echo "$_STILL_MISSING" | sed 's/^ *//')
+
+ if [ -z "$_STILL_MISSING" ]; then
+ return 0
+ fi
+
+ # Step 3: Escalate -- need elevated permissions for remaining packages
+ if command -v sudo >/dev/null 2>&1; then
+ echo ""
+ echo " !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"
+ echo " WARNING: We require sudo elevated permissions to install:"
+ echo " $_STILL_MISSING"
+ echo " If you accept, we'll run sudo now, and it'll prompt your password."
+ echo " !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"
+ echo ""
+ printf " Accept? [Y/n] "
+ if [ -r /dev/tty ]; then
+ read -r REPLY /dev/null; then
+ OS="wsl"
+fi
+echo "==> Platform: $OS"
+
+# ── Check system dependencies ──
+# cmake and git are needed by unsloth studio setup to build the GGUF inference
+# engine (llama.cpp). build-essential and libcurl-dev are also needed on Linux.
+MISSING=""
+
+command -v cmake >/dev/null 2>&1 || MISSING="$MISSING cmake"
+command -v git >/dev/null 2>&1 || MISSING="$MISSING git"
+
+case "$OS" in
+ macos)
+ # Xcode Command Line Tools provide the C/C++ compiler
+ if ! xcode-select -p >/dev/null 2>&1; then
+ echo ""
+ echo "==> Xcode Command Line Tools are required."
+ echo " Installing (a system dialog will appear)..."
+ xcode-select --install 2>/dev/null || true
+ echo " After the installation completes, please re-run this script."
+ exit 1
+ fi
+ ;;
+ linux|wsl)
+ # curl or wget is needed for downloads; check both
+ if ! command -v curl >/dev/null 2>&1 && ! command -v wget >/dev/null 2>&1; then
+ MISSING="$MISSING curl"
+ fi
+ command -v gcc >/dev/null 2>&1 || MISSING="$MISSING build-essential"
+ # libcurl dev headers for llama.cpp HTTPS support
+ if command -v dpkg >/dev/null 2>&1; then
+ dpkg -s libcurl4-openssl-dev >/dev/null 2>&1 || MISSING="$MISSING libcurl4-openssl-dev"
+ fi
+ ;;
+esac
+
+MISSING=$(echo "$MISSING" | sed 's/^ *//')
+
+if [ -n "$MISSING" ]; then
+ echo ""
+ echo "==> Unsloth Studio needs these packages: $MISSING"
+ echo " These are needed to build the GGUF inference engine."
+
+ case "$OS" in
+ macos)
+ if ! command -v brew >/dev/null 2>&1; then
+ echo ""
+ echo " Homebrew is required to install them."
+ echo " Install Homebrew from https://brew.sh then re-run this script."
+ exit 1
+ fi
+ brew install $MISSING
+ ;;
+ linux|wsl)
+ if command -v apt-get >/dev/null 2>&1; then
+ _smart_apt_install $MISSING
+ else
+ echo " apt-get is not available. Please install with your package manager:"
+ echo " $MISSING"
+ echo " Then re-run Unsloth Studio setup."
+ exit 1
+ fi
+ ;;
+ esac
+ echo ""
+else
+ echo "==> All system dependencies found."
+fi
+
+# ── Install uv ──
+if ! command -v uv >/dev/null 2>&1; then
+ echo "==> Installing uv package manager..."
+ _uv_tmp=$(mktemp)
+ download "https://astral.sh/uv/install.sh" "$_uv_tmp"
+ sh "$_uv_tmp"
+ rm -f "$_uv_tmp"
+ if [ -f "$HOME/.local/bin/env" ]; then
+ . "$HOME/.local/bin/env"
+ fi
+ export PATH="$HOME/.local/bin:$PATH"
+fi
+
+# ── Create venv (skip if it already exists and has a valid interpreter) ──
+if [ ! -x "$VENV_NAME/bin/python" ]; then
+ [ -e "$VENV_NAME" ] && rm -rf "$VENV_NAME"
+ echo "==> Creating Python ${PYTHON_VERSION} virtual environment (${VENV_NAME})..."
+ uv venv "$VENV_NAME" --python "$PYTHON_VERSION"
+else
+ echo "==> Virtual environment ${VENV_NAME} already exists, skipping creation."
+fi
+
+# ── Install unsloth directly into the venv (no activation needed) ──
+echo "==> Installing unsloth (this may take a few minutes)..."
+uv pip install --python "$VENV_NAME/bin/python" unsloth --torch-backend=auto
+
+# ── Run studio setup ──
+echo "==> Running unsloth studio setup..."
+"$VENV_NAME/bin/unsloth" studio setup str:
+def get_colab_url(port: int = 8888) -> str:
"""
Get the actual Colab proxy URL for a port.
"""
@@ -60,7 +60,7 @@ def get_colab_url(port: int = 8000) -> str:
return f"http://localhost:{port}"
-def show_link(port: int = 8000):
+def show_link(port: int = 8888):
"""Display a styled clickable link to the UI."""
from IPython.display import display, HTML
@@ -68,8 +68,8 @@ def show_link(port: int = 8000):
url = get_colab_url(port)
short_url = (
- url[: url.index("-", url.index("8000-") + 5) + 1] + "..."
- if "8000-" in url
+ url[: url.index("-", url.index(f"{port}-") + len(str(port)) + 1) + 1] + "..."
+ if f"{port}-" in url
else url
)
html = f"""
@@ -96,7 +96,7 @@ def show_link(port: int = 8000):
display(HTML(html))
-def start(port: int = 8000):
+def start(port: int = 8888):
"""
Start Unsloth Studio server in Colab and display the URL.
diff --git a/studio/backend/core/training/trainer.py b/studio/backend/core/training/trainer.py
index bc252d1fa5..ede213647d 100644
--- a/studio/backend/core/training/trainer.py
+++ b/studio/backend/core/training/trainer.py
@@ -772,6 +772,11 @@ class UnslothTrainer:
if self.should_stop:
return False
+ if full_finetuning:
+ # Enable training mode for full fine-tuning
+ # This ensures all model parameters are trainable; otherwise, they might be frozen.
+ self.model.for_training()
+
self._update_progress(status_message = "Model loaded successfully")
logger.info("Model loaded successfully")
return True
diff --git a/studio/backend/requirements/extras.txt b/studio/backend/requirements/extras.txt
index 170b1c9ec8..d783975a4f 100644
--- a/studio/backend/requirements/extras.txt
+++ b/studio/backend/requirements/extras.txt
@@ -26,8 +26,9 @@ omegaconf
einx
pyloudnorm
openai-whisper
-# uroman # 4.0 MB - romanization, no imports found
-# MeCab # 19.9 MB - Japanese tokenizer, no imports found
+uroman # 4.0 MB - used for Outetts.
+MeCab # 19.9 MB - used for Outetts.
+inflect # number-to-words, required by OuteTTS
loguru
flatten_dict
ffmpy
diff --git a/studio/backend/run.py b/studio/backend/run.py
index 2f064ab92e..5388d3148b 100644
--- a/studio/backend/run.py
+++ b/studio/backend/run.py
@@ -155,7 +155,7 @@ _shutdown_event = None
def run_server(
host: str = "0.0.0.0",
- port: int = 8000,
+ port: int = 8888,
frontend_path: Path = Path(__file__).resolve().parent.parent / "frontend" / "dist",
silent: bool = False,
):
@@ -224,13 +224,17 @@ def run_server(
if not silent:
display_host = _resolve_external_ip() if host == "0.0.0.0" else host
+ print("")
+ print("=" * 50)
+ print(f"🦥 Open your web browser, and enter http://localhost:{port}")
+ print("=" * 50)
print("")
print("=" * 50)
print(f"🦥 Unsloth Studio is running on port {port}")
- print(f" Local: http://localhost:{port}")
- print(f" External: http://{display_host}:{port}")
- print(f" API: http://{display_host}:{port}/api")
- print(f" Health: http://{display_host}:{port}/api/health")
+ print(f" Local Access: http://localhost:{port}")
+ print(f" Worldwide Web Address: http://{display_host}:{port}")
+ print(f" API: http://{display_host}:{port}/api")
+ print(f" Health: http://{display_host}:{port}/api/health")
print("=" * 50)
return app
@@ -243,7 +247,7 @@ if __name__ == "__main__":
parser = argparse.ArgumentParser(description = "Run Unsloth UI Backend server")
parser.add_argument("--host", default = "0.0.0.0", help = "Host to bind to")
- parser.add_argument("--port", type = int, default = 8000, help = "Port to bind to")
+ parser.add_argument("--port", type = int, default = 8888, help = "Port to bind to")
parser.add_argument(
"--frontend",
type = str,
diff --git a/studio/backend/utils/models/model_config.py b/studio/backend/utils/models/model_config.py
index aaf15994be..13f1b5febf 100644
--- a/studio/backend/utils/models/model_config.py
+++ b/studio/backend/utils/models/model_config.py
@@ -630,7 +630,10 @@ _AUDIO_TOKEN_PATTERNS = {
"whisper": lambda tokens: "<|startoftranscript|>" in tokens,
"audio_vlm": lambda tokens: "