Merge branch 'main' into pip
This commit is contained in:
commit
9d1e3c38bc
21 changed files with 638 additions and 76 deletions
54
README.md
54
README.md
|
|
@ -19,7 +19,7 @@ Run and train AI models with a unified local interface.
|
|||
<a href="https://unsloth.ai/docs/new/studio">
|
||||
<img alt="unsloth studio ui homepage" src="https://raw.githubusercontent.com/unslothai/unsloth/main/studio/frontend/public/studio%20github%20landscape%20colab%20display.png" style="max-width: 100%; margin-bottom: 0;"></a>
|
||||
|
||||
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
|
||||
|
|
|
|||
44
build.sh
44
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
|
||||
|
|
|
|||
119
install.ps1
Normal file
119
install.ps1
Normal file
|
|
@ -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
|
||||
212
install.sh
Executable file
212
install.sh
Executable file
|
|
@ -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/tty || REPLY="y"
|
||||
else
|
||||
REPLY="y"
|
||||
fi
|
||||
case "$REPLY" in
|
||||
[nN]*)
|
||||
echo ""
|
||||
echo " Please install these packages first, then re-run Unsloth Studio setup:"
|
||||
echo " sudo apt-get update -y && sudo apt-get install -y $_STILL_MISSING"
|
||||
exit 1
|
||||
;;
|
||||
*)
|
||||
sudo apt-get update -y
|
||||
sudo apt-get install -y $_STILL_MISSING
|
||||
;;
|
||||
esac
|
||||
else
|
||||
echo ""
|
||||
echo " sudo is not available on this system."
|
||||
echo " Please install these packages as root, then re-run Unsloth Studio setup:"
|
||||
echo " apt-get update -y && apt-get install -y $_STILL_MISSING"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
echo ""
|
||||
echo "========================================="
|
||||
echo " Unsloth Studio Installer"
|
||||
echo "========================================="
|
||||
echo ""
|
||||
|
||||
# ── Detect platform ──
|
||||
OS="linux"
|
||||
if [ "$(uname)" = "Darwin" ]; then
|
||||
OS="macos"
|
||||
elif grep -qi microsoft /proc/version 2>/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 </dev/null
|
||||
|
||||
echo ""
|
||||
echo "========================================="
|
||||
echo " Unsloth Studio installed!"
|
||||
echo "========================================="
|
||||
echo ""
|
||||
echo " To launch, run:"
|
||||
echo ""
|
||||
echo " source ${VENV_NAME}/bin/activate"
|
||||
echo " unsloth studio -H 0.0.0.0 -p 8888"
|
||||
echo ""
|
||||
|
|
@ -45,7 +45,7 @@ from loggers import get_logger
|
|||
logger = get_logger(__name__)
|
||||
|
||||
|
||||
def get_colab_url(port: int = 8000) -> 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.
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -630,7 +630,10 @@ _AUDIO_TOKEN_PATTERNS = {
|
|||
"whisper": lambda tokens: "<|startoftranscript|>" in tokens,
|
||||
"audio_vlm": lambda tokens: "<audio_soft_token>" in tokens,
|
||||
"bicodec": lambda tokens: any(t.startswith("<|bicodec_") for t in tokens),
|
||||
"dac": lambda tokens: "<|audio_start|>" in tokens and "<|audio_end|>" in tokens,
|
||||
"dac": lambda tokens: "<|audio_start|>" in tokens
|
||||
and "<|audio_end|>" in tokens
|
||||
and "<|text_start|>" in tokens
|
||||
and "<|text_end|>" in tokens,
|
||||
"snac": lambda tokens: sum(1 for t in tokens if t.startswith("<custom_token_"))
|
||||
> 10000,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,10 +15,16 @@ import { AppProvider } from "../provider";
|
|||
|
||||
const CHAT_ONLY_ALLOWED = new Set(["/", "/chat", "/login", "/signup", "/change-password"]);
|
||||
|
||||
function isChatOnlyAllowed(pathname: string): boolean {
|
||||
if (CHAT_ONLY_ALLOWED.has(pathname)) return true;
|
||||
if (pathname === "/data-recipes" || pathname.startsWith("/data-recipes/")) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
export const Route = createRootRoute({
|
||||
beforeLoad: ({ location }) => {
|
||||
const chatOnly = usePlatformStore.getState().isChatOnly();
|
||||
if (chatOnly && !CHAT_ONLY_ALLOWED.has(location.pathname)) {
|
||||
if (chatOnly && !isChatOnlyAllowed(location.pathname)) {
|
||||
throw redirect({ to: "/chat" });
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -93,7 +93,7 @@ export function Navbar() {
|
|||
const disabledByTraining =
|
||||
isTrainingRunning && item.href !== "/studio";
|
||||
const disabledByDevice =
|
||||
chatOnly && item.href !== "/chat";
|
||||
chatOnly && item.href !== "/chat" && item.href !== "/data-recipes";
|
||||
if (!item.enabled || disabledByTraining || disabledByDevice) {
|
||||
return (
|
||||
<span
|
||||
|
|
@ -209,7 +209,7 @@ export function Navbar() {
|
|||
</div>
|
||||
|
||||
{/* Right: mobile */}
|
||||
<div className="flex items-center gap-2 md:hidden">
|
||||
<div className="col-start-3 flex items-center gap-2 justify-self-end md:hidden">
|
||||
{tourId ? (
|
||||
<button
|
||||
type="button"
|
||||
|
|
@ -240,13 +240,14 @@ export function Navbar() {
|
|||
const disabledByTraining =
|
||||
isTrainingRunning && item.href !== "/studio";
|
||||
const disabledByDevice =
|
||||
chatOnly && item.href !== "/chat";
|
||||
chatOnly && item.href !== "/chat" && item.href !== "/data-recipes";
|
||||
if (disabledByTraining || disabledByDevice) {
|
||||
return (
|
||||
<span
|
||||
key={item.href}
|
||||
className="rounded-md border border-border px-3 py-2 text-sm font-medium text-muted-foreground/40 cursor-not-allowed"
|
||||
className="flex items-center gap-2 rounded-md border border-border px-3 py-2 text-sm font-medium text-muted-foreground/40 cursor-not-allowed"
|
||||
>
|
||||
<HugeiconsIcon icon={item.icon} className="size-4" />
|
||||
{item.label}
|
||||
</span>
|
||||
);
|
||||
|
|
@ -257,12 +258,13 @@ export function Navbar() {
|
|||
to={item.href}
|
||||
onClick={() => setMobileOpen(false)}
|
||||
className={cn(
|
||||
"rounded-md border px-3 py-2 text-sm font-medium",
|
||||
"flex items-center gap-2 rounded-md border px-3 py-2 text-sm font-medium",
|
||||
active
|
||||
? "border-foreground bg-foreground text-background"
|
||||
: "border-border text-foreground hover:bg-accent",
|
||||
)}
|
||||
>
|
||||
<HugeiconsIcon icon={item.icon} className="size-4" />
|
||||
{item.label}
|
||||
</Link>
|
||||
);
|
||||
|
|
@ -271,23 +273,33 @@ export function Navbar() {
|
|||
href="https://unsloth.ai/docs"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="mt-2 rounded-md border border-border px-3 py-2 text-sm font-medium text-foreground hover:bg-accent"
|
||||
className="mt-2 flex items-center gap-2 rounded-md border border-border px-3 py-2 text-sm font-medium text-foreground hover:bg-accent"
|
||||
onClick={() => setMobileOpen(false)}
|
||||
>
|
||||
<HugeiconsIcon icon={Book03Icon} className="size-4" />
|
||||
Learn more (Docs)
|
||||
</a>
|
||||
{tourId ? (
|
||||
<button
|
||||
type="button"
|
||||
className="rounded-md border border-border px-3 py-2 text-left text-sm font-medium text-foreground hover:bg-accent"
|
||||
className="flex items-center gap-2 rounded-md border border-border px-3 py-2 text-left text-sm font-medium text-foreground hover:bg-accent"
|
||||
onClick={() => {
|
||||
openTour();
|
||||
setMobileOpen(false);
|
||||
}}
|
||||
>
|
||||
<HugeiconsIcon icon={CursorInfo02Icon} className="size-4" />
|
||||
Start tour
|
||||
</button>
|
||||
) : null}
|
||||
<div className="mt-2 flex items-center justify-between rounded-md border border-border px-3 py-2">
|
||||
<span className="text-sm font-medium text-foreground">Theme</span>
|
||||
<AnimatedThemeToggler
|
||||
className="flex h-8 w-8 items-center justify-center rounded-md text-muted-foreground transition-colors hover:bg-accent hover:text-foreground [&_svg]:size-4"
|
||||
title="Toggle theme"
|
||||
aria-label="Toggle theme"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</SheetContent>
|
||||
</Sheet>
|
||||
|
|
|
|||
|
|
@ -189,6 +189,7 @@ export {
|
|||
DialogHeader,
|
||||
DialogOverlay,
|
||||
DialogPortal,
|
||||
DialogPortalContainerContext,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -3,8 +3,10 @@
|
|||
|
||||
import { Dialog as SheetPrimitive } from "radix-ui";
|
||||
import type * as React from "react";
|
||||
import { useState } from "react";
|
||||
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { DialogPortalContainerContext } from "@/components/ui/dialog";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { Cancel01Icon } from "@hugeicons/core-free-icons";
|
||||
import { HugeiconsIcon } from "@hugeicons/react";
|
||||
|
|
@ -69,6 +71,7 @@ function SheetContent({
|
|||
overlayClassName?: string;
|
||||
overlayPosition?: "fixed" | "absolute";
|
||||
}) {
|
||||
const [contentEl, setContentEl] = useState<HTMLDivElement | null>(null);
|
||||
return (
|
||||
<SheetPortal container={container ?? undefined}>
|
||||
<SheetOverlay
|
||||
|
|
@ -76,6 +79,7 @@ function SheetContent({
|
|||
position={overlayPosition ?? position}
|
||||
/>
|
||||
<SheetPrimitive.Content
|
||||
ref={setContentEl}
|
||||
data-slot="sheet-content"
|
||||
data-side={side}
|
||||
className={cn(
|
||||
|
|
@ -85,7 +89,9 @@ function SheetContent({
|
|||
)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
<DialogPortalContainerContext.Provider value={contentEl}>
|
||||
{children}
|
||||
</DialogPortalContainerContext.Provider>
|
||||
{showCloseButton && (
|
||||
<SheetPrimitive.Close data-slot="sheet-close" asChild>
|
||||
<Button
|
||||
|
|
|
|||
|
|
@ -768,6 +768,7 @@ export function ChatPage(): ReactElement {
|
|||
|
||||
<ChatSettingsPanel
|
||||
open={settingsOpen}
|
||||
onOpenChange={setSettingsOpen}
|
||||
params={inferenceParams}
|
||||
onParamsChange={setInferenceParams}
|
||||
autoTitle={autoTitle}
|
||||
|
|
|
|||
|
|
@ -21,6 +21,14 @@ import {
|
|||
} from "@hugeicons/core-free-icons";
|
||||
import { HugeiconsIcon } from "@hugeicons/react";
|
||||
import { AnimatePresence, motion } from "motion/react";
|
||||
import {
|
||||
Sheet,
|
||||
SheetContent,
|
||||
SheetDescription,
|
||||
SheetHeader,
|
||||
SheetTitle,
|
||||
} from "@/components/ui/sheet";
|
||||
import { useIsMobile } from "@/hooks/use-mobile";
|
||||
import type { ReactNode } from "react";
|
||||
import { useState } from "react";
|
||||
import {
|
||||
|
|
@ -151,6 +159,7 @@ function CollapsibleSection({
|
|||
|
||||
interface ChatSettingsPanelProps {
|
||||
open: boolean;
|
||||
onOpenChange?: (open: boolean) => void;
|
||||
params: InferenceParams;
|
||||
onParamsChange: (params: InferenceParams) => void;
|
||||
autoTitle: boolean;
|
||||
|
|
@ -160,12 +169,14 @@ interface ChatSettingsPanelProps {
|
|||
|
||||
export function ChatSettingsPanel({
|
||||
open,
|
||||
onOpenChange,
|
||||
params,
|
||||
onParamsChange,
|
||||
autoTitle,
|
||||
onAutoTitleChange,
|
||||
onReloadModel,
|
||||
}: ChatSettingsPanelProps) {
|
||||
const isMobile = useIsMobile();
|
||||
const isGguf = useChatRuntimeStore((s) => s.activeGgufVariant) != null;
|
||||
const ggufContextLength = useChatRuntimeStore((s) => s.ggufContextLength);
|
||||
const kvCacheDtype = useChatRuntimeStore((s) => s.kvCacheDtype);
|
||||
|
|
@ -214,24 +225,21 @@ export function ChatSettingsPanel({
|
|||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<aside
|
||||
className={`shrink-0 self-start h-[calc(100%-0.875rem)] overflow-hidden bg-muted/70 rounded-2xl corner-squircle transition-[width] duration-200 ease-linear ${open ? "w-[17rem] border-l border-sidebar-border/70" : "w-0"}`}
|
||||
>
|
||||
<div className="flex h-full w-[17rem] flex-col">
|
||||
<div className="flex items-center gap-2 px-4 py-3">
|
||||
<HugeiconsIcon
|
||||
icon={PencilEdit01Icon}
|
||||
className="size-4 text-muted-foreground/70"
|
||||
/>
|
||||
<span className="flex-1 text-base font-semibold tracking-tight">
|
||||
Configuration
|
||||
</span>
|
||||
</div>
|
||||
const settingsContent = (
|
||||
<>
|
||||
<div className="flex items-center gap-2 px-4 py-3">
|
||||
<HugeiconsIcon
|
||||
icon={PencilEdit01Icon}
|
||||
className="size-4 text-muted-foreground/70"
|
||||
/>
|
||||
<span className="flex-1 text-base font-semibold tracking-tight">
|
||||
Configuration
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="flex-1 overflow-y-auto px-1.5">
|
||||
{/* mt-4 matches the Playground sidebar gap (SidebarHeader py-3 + SidebarGroup pt-1) */}
|
||||
<div className="mt-4 px-2 pb-3">
|
||||
<div className="flex-1 overflow-y-auto px-1.5">
|
||||
{/* mt-4 matches the Playground sidebar gap (SidebarHeader py-3 + SidebarGroup pt-1) */}
|
||||
<div className="mt-4 px-2 pb-3">
|
||||
<div className="flex items-center gap-2">
|
||||
<Select value={activePreset} onValueChange={applyPreset}>
|
||||
<SelectTrigger className="h-8 flex-1 corner-squircle text-xs">
|
||||
|
|
@ -434,7 +442,28 @@ export function ChatSettingsPanel({
|
|||
|
||||
<ChatTemplateSection onReloadModel={onReloadModel} />
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
||||
if (isMobile) {
|
||||
return (
|
||||
<Sheet open={open} onOpenChange={onOpenChange}>
|
||||
<SheetContent side="right" className="w-[18rem] p-0">
|
||||
<SheetHeader className="sr-only">
|
||||
<SheetTitle>Configuration</SheetTitle>
|
||||
<SheetDescription>Chat inference settings</SheetDescription>
|
||||
</SheetHeader>
|
||||
<div className="flex h-full flex-col">{settingsContent}</div>
|
||||
</SheetContent>
|
||||
</Sheet>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<aside
|
||||
className={`shrink-0 self-start h-[calc(100%-0.875rem)] overflow-hidden bg-muted/70 rounded-2xl corner-squircle transition-[width] duration-200 ease-linear ${open ? "w-[17rem] border-l border-sidebar-border/70" : "w-0"}`}
|
||||
>
|
||||
<div className="flex h-full w-[17rem] flex-col">{settingsContent}</div>
|
||||
</aside>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,11 +17,11 @@ export default defineConfig({
|
|||
allowedHosts: true,
|
||||
proxy: {
|
||||
"/api": {
|
||||
target: "http://127.0.0.1:8000",
|
||||
target: "http://127.0.0.1:8888",
|
||||
changeOrigin: true,
|
||||
},
|
||||
"/v1": {
|
||||
target: "http://127.0.0.1:8000",
|
||||
target: "http://127.0.0.1:8888",
|
||||
changeOrigin: true,
|
||||
},
|
||||
"/seed/inspect": {
|
||||
|
|
|
|||
|
|
@ -855,10 +855,31 @@ if ($IsPipInstall) {
|
|||
Write-Host "[INFO] Frontend source changed since last build -- rebuilding..." -ForegroundColor Yellow
|
||||
}
|
||||
}
|
||||
$NeedFrontendBuild = $true
|
||||
if ($NeedFrontendBuild -and -not $IsPipInstall) {
|
||||
Write-Host ""
|
||||
Write-Host "Building frontend..." -ForegroundColor Cyan
|
||||
|
||||
# ── Tailwind v4 .gitignore workaround ──
|
||||
# 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.
|
||||
$HiddenGitignores = @()
|
||||
$WalkDir = (Get-Item $FrontendDir).Parent.FullName
|
||||
while ($WalkDir -and $WalkDir -ne [System.IO.Path]::GetPathRoot($WalkDir)) {
|
||||
$gi = Join-Path $WalkDir ".gitignore"
|
||||
if (Test-Path $gi) {
|
||||
$content = Get-Content $gi -Raw -ErrorAction SilentlyContinue
|
||||
if ($content -and ($content.Trim() -match '^\*$')) {
|
||||
$hidden = "$gi._twbuild"
|
||||
Rename-Item -Path $gi -NewName (Split-Path $hidden -Leaf) -Force
|
||||
$HiddenGitignores += $gi
|
||||
Write-Host " [INFO] Temporarily hiding $gi (venv .gitignore blocks Tailwind scanner)" -ForegroundColor DarkGray
|
||||
}
|
||||
}
|
||||
$WalkDir = Split-Path $WalkDir -Parent
|
||||
}
|
||||
|
||||
# npm writes warnings to stderr; lower ErrorActionPreference so PS doesn't
|
||||
# treat them as terminating errors (same pattern as the pip section below).
|
||||
$prevEAP_npm = $ErrorActionPreference
|
||||
|
|
@ -868,6 +889,7 @@ if ($NeedFrontendBuild -and -not $IsPipInstall) {
|
|||
if ($LASTEXITCODE -ne 0) {
|
||||
Pop-Location
|
||||
$ErrorActionPreference = $prevEAP_npm
|
||||
foreach ($gi in $HiddenGitignores) { Rename-Item -Path "$gi._twbuild" -NewName (Split-Path $gi -Leaf) -Force -ErrorAction SilentlyContinue }
|
||||
Write-Host "[ERROR] npm install failed (exit code $LASTEXITCODE)" -ForegroundColor Red
|
||||
Write-Host " Try running 'npm install' manually in frontend/ to see errors" -ForegroundColor Yellow
|
||||
exit 1
|
||||
|
|
@ -876,12 +898,27 @@ if ($NeedFrontendBuild -and -not $IsPipInstall) {
|
|||
if ($LASTEXITCODE -ne 0) {
|
||||
Pop-Location
|
||||
$ErrorActionPreference = $prevEAP_npm
|
||||
foreach ($gi in $HiddenGitignores) { Rename-Item -Path "$gi._twbuild" -NewName (Split-Path $gi -Leaf) -Force -ErrorAction SilentlyContinue }
|
||||
Write-Host "[ERROR] npm run build failed (exit code $LASTEXITCODE)" -ForegroundColor Red
|
||||
exit 1
|
||||
}
|
||||
Pop-Location
|
||||
$ErrorActionPreference = $prevEAP_npm
|
||||
Write-Host "[OK] Frontend built to frontend/dist" -ForegroundColor Green
|
||||
|
||||
# ── Restore hidden .gitignore files ──
|
||||
foreach ($gi in $HiddenGitignores) {
|
||||
Rename-Item -Path "$gi._twbuild" -NewName (Split-Path $gi -Leaf) -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
|
||||
# ── Validate CSS output ──
|
||||
$CssFiles = Get-ChildItem (Join-Path $DistDir "assets") -Filter "*.css" -ErrorAction SilentlyContinue
|
||||
$MaxCssSize = ($CssFiles | Measure-Object -Property Length -Maximum).Maximum
|
||||
if ($MaxCssSize -lt 100000) {
|
||||
Write-Host "[WARN] Largest CSS file is only $([math]::Round($MaxCssSize / 1024))KB -- Tailwind may not have scanned all source files." -ForegroundColor Yellow
|
||||
Write-Host " Expected >100KB. Check for .gitignore files blocking the Tailwind oxide scanner." -ForegroundColor Yellow
|
||||
} else {
|
||||
Write-Host "[OK] Frontend built to frontend/dist (CSS: $([math]::Round($MaxCssSize / 1024))KB)" -ForegroundColor Green
|
||||
}
|
||||
}
|
||||
|
||||
if (Test-Path $OxcValidatorDir) {
|
||||
|
|
@ -1374,6 +1411,6 @@ Write-Host "+===============================================+" -ForegroundColor
|
|||
Write-Host "| Setup Complete! |" -ForegroundColor Green
|
||||
Write-Host "| |" -ForegroundColor Green
|
||||
Write-Host "| Launch with: |" -ForegroundColor Green
|
||||
Write-Host "| unsloth studio -H 0.0.0.0 -p 8000 |" -ForegroundColor Green
|
||||
Write-Host "| unsloth studio -H 0.0.0.0 -p 8888 |" -ForegroundColor Green
|
||||
Write-Host "| |" -ForegroundColor Green
|
||||
Write-Host "+===============================================+" -ForegroundColor Green
|
||||
|
|
|
|||
|
|
@ -59,7 +59,6 @@ if [ -d "$SCRIPT_DIR/frontend/dist" ]; then
|
|||
_NEED_FRONTEND_BUILD=false
|
||||
fi
|
||||
fi
|
||||
_NEED_FRONTEND_BUILD=true
|
||||
if [ "$_NEED_FRONTEND_BUILD" = false ]; then
|
||||
echo "✅ Frontend already built and up to date -- skipping Node/npm check."
|
||||
else
|
||||
|
|
@ -160,6 +159,16 @@ run_quiet "npm run build" npm run build
|
|||
|
||||
_restore_gitignores
|
||||
trap - EXIT
|
||||
|
||||
# Validate CSS output -- catch truncated Tailwind builds
|
||||
_MAX_CSS=$(find "$SCRIPT_DIR/frontend/dist/assets" -name '*.css' -exec wc -c {} + 2>/dev/null | sort -n | tail -1 | awk '{print $1}')
|
||||
if [ -z "$_MAX_CSS" ]; then
|
||||
echo "⚠️ WARNING: No CSS files were emitted. The frontend build may have failed."
|
||||
elif [ "$_MAX_CSS" -lt 100000 ]; then
|
||||
echo "⚠️ WARNING: Largest CSS file is only $((_MAX_CSS / 1024))KB (expected >100KB)."
|
||||
echo " Tailwind may not have scanned all source files. Check for .gitignore interference."
|
||||
fi
|
||||
|
||||
cd "$SCRIPT_DIR"
|
||||
echo "✅ Frontend built to frontend/dist"
|
||||
|
||||
|
|
@ -308,10 +317,59 @@ echo "✅ Transformers 5.x pre-installed to $VENV_T5_DIR/"
|
|||
if grep -qi microsoft /proc/version 2>/dev/null; then
|
||||
echo ""
|
||||
echo "⚠️ WSL detected -- installing build dependencies for GGUF export..."
|
||||
echo " You may be prompted for your password."
|
||||
sudo apt-get update -y
|
||||
sudo apt-get install -y build-essential cmake curl git libcurl4-openssl-dev
|
||||
echo "✅ GGUF build dependencies installed"
|
||||
_GGUF_DEPS="pciutils build-essential cmake curl git libcurl4-openssl-dev"
|
||||
|
||||
# Try without sudo first (works when already root)
|
||||
apt-get update -y >/dev/null 2>&1 || true
|
||||
apt-get install -y $_GGUF_DEPS >/dev/null 2>&1 || true
|
||||
|
||||
# Check which packages are still missing
|
||||
_STILL_MISSING=""
|
||||
for _pkg in $_GGUF_DEPS; do
|
||||
case "$_pkg" in
|
||||
build-essential) command -v gcc >/dev/null 2>&1 || _STILL_MISSING="$_STILL_MISSING $_pkg" ;;
|
||||
pciutils) command -v lspci >/dev/null 2>&1 || _STILL_MISSING="$_STILL_MISSING $_pkg" ;;
|
||||
libcurl4-openssl-dev) dpkg -s "$_pkg" >/dev/null 2>&1 || _STILL_MISSING="$_STILL_MISSING $_pkg" ;;
|
||||
*) command -v "$_pkg" >/dev/null 2>&1 || _STILL_MISSING="$_STILL_MISSING $_pkg" ;;
|
||||
esac
|
||||
done
|
||||
_STILL_MISSING=$(echo "$_STILL_MISSING" | sed 's/^ *//')
|
||||
|
||||
if [ -z "$_STILL_MISSING" ]; then
|
||||
echo "✅ GGUF build dependencies installed"
|
||||
elif 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/tty || REPLY="y"
|
||||
else
|
||||
REPLY="y"
|
||||
fi
|
||||
case "$REPLY" in
|
||||
[nN]*)
|
||||
echo ""
|
||||
echo " Please install these packages first, then re-run Unsloth Studio setup:"
|
||||
echo " sudo apt-get update -y && sudo apt-get install -y $_STILL_MISSING"
|
||||
_SKIP_GGUF_BUILD=true
|
||||
;;
|
||||
*)
|
||||
sudo apt-get update -y
|
||||
sudo apt-get install -y $_STILL_MISSING
|
||||
echo "✅ GGUF build dependencies installed"
|
||||
;;
|
||||
esac
|
||||
else
|
||||
echo " sudo is not available on this system."
|
||||
echo " Please install as root, then re-run setup:"
|
||||
echo " apt-get install -y $_STILL_MISSING"
|
||||
_SKIP_GGUF_BUILD=true
|
||||
fi
|
||||
fi
|
||||
|
||||
# ── 8. Build llama.cpp binaries for GGUF inference + export ──
|
||||
|
|
@ -324,6 +382,11 @@ UNSLOTH_HOME="$HOME/.unsloth"
|
|||
mkdir -p "$UNSLOTH_HOME"
|
||||
LLAMA_CPP_DIR="$UNSLOTH_HOME/llama.cpp"
|
||||
LLAMA_SERVER_BIN="$LLAMA_CPP_DIR/build/bin/llama-server"
|
||||
if [ "${_SKIP_GGUF_BUILD:-}" = true ]; then
|
||||
echo ""
|
||||
echo "Skipping llama-server build (missing dependencies)"
|
||||
echo " Install the missing packages and re-run setup to enable GGUF inference."
|
||||
else
|
||||
rm -rf "$LLAMA_CPP_DIR"
|
||||
{
|
||||
# Check prerequisites
|
||||
|
|
@ -442,6 +505,7 @@ rm -rf "$LLAMA_CPP_DIR"
|
|||
fi
|
||||
fi
|
||||
}
|
||||
fi # end _SKIP_GGUF_BUILD check
|
||||
|
||||
echo ""
|
||||
if [ "$IS_COLAB" = true ]; then
|
||||
|
|
@ -460,6 +524,6 @@ else
|
|||
echo "╠══════════════════════════════════════╣"
|
||||
echo "║ Launch with: ║"
|
||||
echo "║ ║"
|
||||
echo "║ unsloth studio -H 0.0.0.0 -p 8000 ║"
|
||||
echo "║ unsloth studio -H 0.0.0.0 -p 8888 ║"
|
||||
echo "╚══════════════════════════════════════╝"
|
||||
fi
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@
|
|||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
__version__ = "2026.3.7"
|
||||
__version__ = "2026.3.8"
|
||||
|
||||
__all__ = [
|
||||
"SUPPORTS_BFLOAT16",
|
||||
|
|
|
|||
|
|
@ -75,7 +75,7 @@ def _find_setup_script() -> Optional[Path]:
|
|||
@studio_app.callback(invoke_without_command = True)
|
||||
def studio_default(
|
||||
ctx: typer.Context,
|
||||
port: int = typer.Option(8000, "--port", "-p"),
|
||||
port: int = typer.Option(8888, "--port", "-p"),
|
||||
host: str = typer.Option("0.0.0.0", "--host", "-H"),
|
||||
frontend: Optional[Path] = typer.Option(None, "--frontend", "-f"),
|
||||
silent: bool = typer.Option(False, "--silent", "-q"),
|
||||
|
|
@ -93,7 +93,7 @@ def studio_default(
|
|||
run_py = _find_run_py()
|
||||
if studio_python and run_py:
|
||||
if not silent:
|
||||
typer.echo("Launching with studio venv...")
|
||||
typer.echo("Launching Unsloth Studio... Please wait...")
|
||||
args = [
|
||||
str(studio_python),
|
||||
str(run_py),
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ import typer
|
|||
|
||||
def ui(
|
||||
port: int = typer.Option(
|
||||
8000, "--port", "-p", help = "Port to run the UI server on."
|
||||
8888, "--port", "-p", help = "Port to run the UI server on."
|
||||
),
|
||||
host: str = typer.Option(
|
||||
"0.0.0.0", "--host", "-H", help = "Host address to bind to."
|
||||
|
|
@ -40,7 +40,7 @@ def ui(
|
|||
run_py = _find_run_py()
|
||||
if studio_python and run_py:
|
||||
if not silent:
|
||||
typer.echo("Launching with studio venv...")
|
||||
typer.echo("Launching Unsloth Studio... Please wait...")
|
||||
args = [
|
||||
str(studio_python),
|
||||
str(run_py),
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue