studio: unify Windows installer/setup logging style, verbosity controls, and startup messaging (#4651)

* refactor(studio): unify setup terminal output style and add verbose setup mode

* studio(windows): align setup.ps1 banner/steps with setup.sh (ANSI, verbose)

* studio(setup): revert nvcc path reordering to match main

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* studio(setup): restore fail-fast llama.cpp setup flow

* studio(banner): use IPv6 loopback URL when binding :: or ::1

* Fix IPv6 URL bracketing, try_quiet stderr, _step label clamp

- Bracket IPv6 display_host in external_url to produce clickable URLs
- Redirect try_quiet failure log to stderr instead of stdout
- Clamp _step label to column width to prevent negative padding

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Add sandbox integration tests for PR #4494 UX fixes

Simulation harness (tests/simulate_pr4494.py) creates an isolated uv
venv, copies the real source files into it, and runs subprocess tests
for all three fixes with visual before/after demos and edge cases.

Standalone bash test (tests/test_try_quiet.sh) validates try_quiet
stderr redirect across 8 scenarios including broken-version contrast.

39 integration tests total (14 IPv6 + 15 try_quiet + 10 _step), all
existing 75 unit tests still pass.

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Truncate step() labels in setup.sh to match PS1 and Python

The %-15s printf format pads short labels but does not truncate long
ones.  Change to %-15.15s so labels wider than 15 chars are clipped,
matching the PowerShell .Substring(0,15) and Python label[:15] logic.

* Remove sandbox integration tests from PR

These test files are not part of the styling fix and should not
ship with this PR.

* Show error output on failure instead of suppressing it

- install_python_stack.py: restore _red for patch_package_file
  warnings (was downgraded to _dim)
- setup.ps1: capture winget output and show on failure for CUDA,
  Node, Python, and OpenSSL installs (was piped to Out-Null)
- setup.ps1: always show git pull failure warning, not just in
  verbose mode

* Show winget error output for Git and CMake installs on failure

Same capture-and-print-on-failure pattern already used for
Node, Python, CUDA, and OpenSSL winget installs.

* fix: preserve stderr for _run_quiet error messages in setup.sh

The step() helper writes to stdout, but _run_quiet's error header
was originally sent to stderr (>&2). Without the redirect, callers
that separate stdout/stderr would miss the failure headline while
still seeing the log body on stderr. Add >&2 to both step calls
inside _run_quiet to match main's behavior.

* feat: add --verbose flag to setup and update commands

Wire UNSLOTH_VERBOSE=1 through _run_setup_script() so that
'unsloth studio update --verbose' (and the deprecated 'setup')
passes the flag to setup.sh / setup.ps1 / install_python_stack.py.

* fix(studio): honor verbose logging and keep llama.cpp failures non-blocking

* fix(studio): switch installer to 'studio update' and normalize Windows setup logs

* chore(studio): refine localhost tip and remove skip-base setup nois

* fix(studio): align Windows setup logs with Linux style and improve startup tips

* fix(studio): align Windows setup logs with Linux style

* refactor(windows-installer): align install/setup logs with Linux style and silence auto-launch output

* refactor(windows): align installer/setup output with Linux style and reduce default verbosity

* refactor(windows): match install.ps1 output style/colors to setup and quiet default logs

* fix(studio-banner): update personal-computer localhost tip

* fix(setup.sh): restore verbose llama.cpp build output while keeping default quiet mode

* fix(install.sh): align installer logging with setup style and restore POSIX-safe color output

* fix(install.sh): preserve installer reliability and launch visibility

Export verbose mode for child setup processes, harden install command handling under set -e, and keep first-run studio launch non-silent so users can always see URL and port fallback output.

* fix(windows installer): keep exit semantics and degrade status accurate

Use quiet command redirection that preserves native exit codes, keep startup output visible on first launch, and report limited install status when llama.cpp is unavailable.

* fix(setup.sh): improve log clarity and enforce GGUF degraded signaling

Restore clean default setup output, add verbose-only diagnostics, fail fast on Colab dependency install errors, and return non-zero when GGUF prerequisites or llama.cpp artifacts are unavailable.

* fix(installer): harden bash preflight and PowerShell GPU checks

Fail fast when bash is unavailable before invoking setup.sh, and replace remaining nvidia-smi pipeline checks with stream redirection patterns that preserve reliable native exit-code handling.

* fix(windows): keep verbose output visible while preserving exit codes

Ensure PowerShell wrapper helpers in install/update stream native command output to host without returning it as function output, so npm logs no longer corrupt exit-code checks in verbose mode.

* fix(windows): avoid sticky UNSLOTH_VERBOSE and gate studio update verbosity

* Fix degraded llama.cpp exit code, PS verbose stderr, banner URLs, npm verbose

- setup.sh: Do not exit non-zero when llama.cpp is unavailable; the footer
  already reports the limitation, and install.sh runs under set -e so a
  non-zero exit aborts the entire install including PATH/shortcuts/launch.
- setup.ps1: Remove $? check in Invoke-SetupCommand verbose path; PS 5.1
  sets $? = $false when native commands write to stderr even with exit 0.
  Merge stderr into stdout with 2>&1 and rely solely on $LASTEXITCODE.
- startup_banner.py: Show the actual bound address when Studio is bound to
  a non-loopback interface instead of always showing 127.0.0.1/localhost.
- setup.sh: Use run_quiet_no_exit instead of run_quiet_no_exit_always for
  npm install steps so --verbose correctly surfaces npm output.

* Fix install.ps1 verbose stderr, propagate UNSLOTH_VERBOSE, fix git clone verbose

- install.ps1: Apply same Invoke-InstallCommand fix as setup.ps1 -- merge
  stderr into stdout with 2>&1 and drop the $? check that misclassifies
  successful native commands on PS 5.1.
- install.ps1 + setup.ps1: Export UNSLOTH_VERBOSE=1 to the process env
  when --verbose is passed so child processes like install_python_stack.py
  also run in verbose mode.
- setup.sh: Use run_quiet_no_exit for git clone llama.cpp so --verbose
  correctly surfaces clone diagnostics during source-build fallback.

* Surface prebuilt llama.cpp output in verbose mode, remove dead code, fix banner

- setup.sh: Use tee in verbose mode for prebuilt llama.cpp installer so
  users can see download/validation progress while still capturing the log
  for structured error reporting on failure.
- setup.ps1: Same fix for Windows -- use Tee-Object in verbose mode.
- setup.sh: Remove run_quiet_no_exit_always() which has no remaining callers.
- startup_banner.py: Avoid printing the same URL twice when Studio is
  bound to a specific non-loopback address that matches the display host.

* Fix run_install_cmd exit code after failed if-statement

The previous pattern 'if "$@"; then return 0; fi; _rc=$?' always captured
$? = 0 because $? reflects the if-statement result, not the command's exit
code. Switch to '"$@" && return 0; _rc=$?' which preserves the actual
command exit code on failure. Applies to both verbose and quiet branches.

* Fix _run_quiet exit code, double uv install, missing --local flag

- setup.sh: Fix _run_quiet verbose path that always captured exit code 0
  due to $? resetting after if-then-fi with no else. Switch to the same
  '"$@" && return 0; exit_code=$?' pattern used in install.sh.
- setup.sh: Consolidate the two uv install branches (verbose + quiet)
  into a single attempt with conditional output. Previously, when verbose
  mode was on and the install failed, a second silent attempt was made.
- install.ps1: Pass --local flag to 'unsloth studio update' when
  $StudioLocalInstall is true. Without this, studio.py's update() command
  overwrites STUDIO_LOCAL_INSTALL to "0", which could cause issues if
  setup.ps1 or install_python_stack.py later checks that variable.

* Revert SKIP_STUDIO_BASE change for --no-torch, restore install banners

- Revert SKIP_STUDIO_BASE from 0 to 1 for --no-torch. install.sh already
  installs unsloth+unsloth-zoo and no-torch-runtime.txt before calling
  setup.sh, so letting install_python_stack.py redo it was redundant and
  slowed down --no-torch installs for no benefit.
- Restore the "Unsloth Studio installed!" success banner and "starting
  Unsloth Studio..." launch message so users get clear install completion
  feedback before the server starts.

* Make llama.cpp build failure a hard error with proper cleanup

- setup.sh: Restore exit 1 when _LLAMA_CPP_DEGRADED is true. GGUF
  inference requires a working llama.cpp build, so this should be a
  hard failure, not a silent degradation.
- install.sh: Catch setup.sh's non-zero exit with '|| _SETUP_EXIT=$?'
  instead of letting set -e abort immediately. This ensures PATH setup,
  symlinks, and shortcuts still get created so the user can fix the
  build deps and retry with 'unsloth studio update'. After post-install
  steps, propagate the failure with a clear error message.

* Revert install.ps1 to 'studio setup' to preserve SKIP_STUDIO_BASE

'studio update' pops SKIP_STUDIO_BASE from the environment, which
defeats the fast-path version check added in PR #4667. When called
from install.ps1 (which already installed packages), SKIP_STUDIO_BASE=1
must survive into setup.ps1 so it skips the redundant PyPI check and
package reinstallation. 'studio setup' does not modify env vars.

* Remove deprecation message from 'studio setup' command

install.ps1 uses 'studio setup' (not 'studio update') to preserve
SKIP_STUDIO_BASE. The deprecation message was confusing during first
install since the user never typed the command.

* Fix stale env vars, scope degraded exit, generic error message for PR #4651

- install.ps1: Always set STUDIO_LOCAL_INSTALL and clear STUDIO_LOCAL_REPO
  when not using --local, to prevent stale values from a previous --local
  run in the same PowerShell session. Fix log messages to say 'setup' not
  'update' since we call 'studio setup'.
- setup.sh: Only exit non-zero for degraded llama.cpp when called from the
  installer (SKIP_STUDIO_BASE=1). Direct 'unsloth studio update' keeps
  degraded installs successful since Studio is still usable for non-GGUF
  workflows and the footer already reports the limitation.
- install.sh: Make the setup failure error message generic instead of
  GGUF-specific, so unrelated failures (npm, Python deps) do not show
  misleading cmake/git recovery advice.

* Show captured output on failure in quiet mode for PR #4651

Both Invoke-InstallCommand (install.ps1) and Invoke-SetupCommand
(setup.ps1) now capture command output in quiet mode and display it
in red when the command fails. This matches the behavior of
run_install_cmd in install.sh where failure output is surfaced even
in quiet mode, making cross-platform error debugging consistent.

* Match degraded llama.cpp exit on Windows, fix --local recovery hint for PR #4651

- setup.ps1: Exit non-zero for degraded llama.cpp when called from
  install.ps1 (SKIP_STUDIO_BASE=1), matching setup.sh behavior. Direct
  'unsloth studio update' keeps degraded installs successful.
- install.sh: Show 'unsloth studio update --local' in the recovery
  message when the install was run with --local, so users retry with
  the correct flag instead of losing local checkout context.

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Daniel Han <danielhanchen@gmail.com>
This commit is contained in:
Lee Jackson 2026-03-30 08:53:23 +01:00 committed by GitHub
parent 5bbfabb151
commit 5557e1fd27
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 824 additions and 340 deletions

View file

@ -6,6 +6,7 @@
function Install-UnslothStudio {
$ErrorActionPreference = "Stop"
$script:UnslothVerbose = ($env:UNSLOTH_VERBOSE -eq "1")
# ── Parse flags ──
$StudioLocalInstall = $false
@ -17,6 +18,8 @@ function Install-UnslothStudio {
switch ($argList[$i]) {
"--local" { $StudioLocalInstall = $true }
"--no-torch" { $SkipTorch = $true }
"--verbose" { $script:UnslothVerbose = $true }
"-v" { $script:UnslothVerbose = $true }
"--package" {
$i++
if ($i -ge $argList.Count) {
@ -27,6 +30,12 @@ function Install-UnslothStudio {
}
}
}
# Propagate to child processes so they also respect verbose mode.
# Process-scoped -- does not persist.
if ($script:UnslothVerbose) {
$env:UNSLOTH_VERBOSE = '1'
}
if ($StudioLocalInstall) {
$RepoRoot = (Resolve-Path (Split-Path -Parent $PSCommandPath)).Path
if (-not (Test-Path (Join-Path $RepoRoot "pyproject.toml"))) {
@ -39,10 +48,55 @@ function Install-UnslothStudio {
$StudioHome = Join-Path $env:USERPROFILE ".unsloth\studio"
$VenvDir = Join-Path $StudioHome "unsloth_studio"
$Rule = [string]::new([char]0x2500, 52)
$Sloth = [char]::ConvertFromUtf32(0x1F9A5)
function Enable-StudioVirtualTerminal {
if ($env:NO_COLOR) { return $false }
try {
if (-not ("StudioVT.Native" -as [type])) {
Add-Type -Namespace StudioVT -Name Native -MemberDefinition @'
[DllImport("kernel32.dll")] public static extern IntPtr GetStdHandle(int nStdHandle);
[DllImport("kernel32.dll")] public static extern bool GetConsoleMode(IntPtr h, out uint m);
[DllImport("kernel32.dll")] public static extern bool SetConsoleMode(IntPtr h, uint m);
'@ -ErrorAction Stop
}
$h = [StudioVT.Native]::GetStdHandle(-11)
[uint32]$mode = 0
if (-not [StudioVT.Native]::GetConsoleMode($h, [ref]$mode)) { return $false }
$mode = $mode -bor 0x0004
return [StudioVT.Native]::SetConsoleMode($h, $mode)
} catch {
return $false
}
}
$script:StudioVtOk = Enable-StudioVirtualTerminal
function Get-StudioAnsi {
param(
[Parameter(Mandatory = $true)]
[ValidateSet('Title', 'Dim', 'Ok', 'Warn', 'Err', 'Reset')]
[string]$Kind
)
$e = [char]27
switch ($Kind) {
'Title' { return "${e}[38;5;150m" }
'Dim' { return "${e}[38;5;245m" }
'Ok' { return "${e}[38;5;108m" }
'Warn' { return "${e}[38;5;136m" }
'Err' { return "${e}[91m" }
'Reset' { return "${e}[0m" }
}
}
Write-Host ""
Write-Host "========================================="
Write-Host " Unsloth Studio Installer (Windows)"
Write-Host "========================================="
if ($script:StudioVtOk -and -not $env:NO_COLOR) {
Write-Host (" " + (Get-StudioAnsi Title) + $Sloth + " Unsloth Studio Installer (Windows)" + (Get-StudioAnsi Reset))
Write-Host (" {0}{1}{2}" -f (Get-StudioAnsi Dim), $Rule, (Get-StudioAnsi Reset))
} else {
Write-Host (" {0} Unsloth Studio Installer (Windows)" -f $Sloth) -ForegroundColor DarkGreen
Write-Host " $Rule" -ForegroundColor DarkGray
}
Write-Host ""
# ── Helper: refresh PATH from registry (deduplicating entries) ──
@ -62,13 +116,96 @@ function Install-UnslothStudio {
$env:Path = $unique -join ";"
}
function step {
param(
[Parameter(Mandatory = $true)][string]$Label,
[Parameter(Mandatory = $true)][string]$Value,
[string]$Color = "Green"
)
if ($script:StudioVtOk -and -not $env:NO_COLOR) {
$dim = Get-StudioAnsi Dim
$rst = Get-StudioAnsi Reset
$val = switch ($Color) {
'Green' { Get-StudioAnsi Ok }
'Yellow' { Get-StudioAnsi Warn }
'Red' { Get-StudioAnsi Err }
'DarkGray' { Get-StudioAnsi Dim }
default { Get-StudioAnsi Ok }
}
$padded = if ($Label.Length -ge 15) { $Label.Substring(0, 15) } else { $Label.PadRight(15) }
Write-Host (" {0}{1}{2}{3}{4}{2}" -f $dim, $padded, $rst, $val, $Value)
} else {
$padded = if ($Label.Length -ge 15) { $Label.Substring(0, 15) } else { $Label.PadRight(15) }
Write-Host (" {0}" -f $padded) -NoNewline -ForegroundColor DarkGray
$fc = switch ($Color) {
'Green' { 'DarkGreen' }
'Yellow' { 'Yellow' }
'Red' { 'Red' }
'DarkGray' { 'DarkGray' }
default { 'DarkGreen' }
}
Write-Host $Value -ForegroundColor $fc
}
}
function substep {
param(
[Parameter(Mandatory = $true)][string]$Message,
[string]$Color = "DarkGray"
)
if ($script:StudioVtOk -and -not $env:NO_COLOR) {
$msgCol = switch ($Color) {
'Yellow' { (Get-StudioAnsi Warn) }
'Red' { (Get-StudioAnsi Err) }
default { (Get-StudioAnsi Dim) }
}
$pad = "".PadRight(15)
Write-Host (" {0}{1}{2}{3}" -f $msgCol, $pad, $Message, (Get-StudioAnsi Reset))
} else {
$fc = switch ($Color) {
'Yellow' { 'Yellow' }
'Red' { 'Red' }
default { 'DarkGray' }
}
Write-Host (" {0,-15}{1}" -f "", $Message) -ForegroundColor $fc
}
}
# Run native commands quietly by default to match install.sh behavior.
# Full command output is shown only when --verbose / UNSLOTH_VERBOSE=1.
function Invoke-InstallCommand {
param(
[Parameter(Mandatory = $true)][ScriptBlock]$Command
)
$prevEap = $ErrorActionPreference
$ErrorActionPreference = "Continue"
try {
# Reset to avoid stale values from prior native commands.
$global:LASTEXITCODE = 0
if ($script:UnslothVerbose) {
# Merge stderr into stdout so progress/warning output stays visible
# without flipping $? on successful native commands (PS 5.1 treats
# stderr records as errors that set $? = $false even on exit code 0).
& $Command 2>&1 | Out-Host
} else {
$output = & $Command 2>&1 | Out-String
if ($LASTEXITCODE -ne 0) {
Write-Host $output -ForegroundColor Red
}
}
return [int]$LASTEXITCODE
} finally {
$ErrorActionPreference = $prevEap
}
}
function New-StudioShortcuts {
param(
[Parameter(Mandatory = $true)][string]$UnslothExePath
)
if (-not (Test-Path $UnslothExePath)) {
Write-Host "[WARN] Cannot create shortcuts: unsloth.exe not found at $UnslothExePath" -ForegroundColor Yellow
substep "cannot create shortcuts, unsloth.exe not found at $UnslothExePath" "Yellow"
return
}
try {
@ -81,7 +218,7 @@ function Install-UnslothStudio {
$localAppDataDir = $env:LOCALAPPDATA
if (-not $localAppDataDir -or [string]::IsNullOrWhiteSpace($localAppDataDir)) {
Write-Host "[WARN] LOCALAPPDATA path unavailable; skipped shortcut creation" -ForegroundColor Yellow
substep "LOCALAPPDATA path unavailable; skipped shortcut creation" "Yellow"
return
}
$appDir = Join-Path $localAppDataDir "Unsloth Studio"
@ -104,10 +241,10 @@ function Install-UnslothStudio {
$null
}
if (-not $desktopLink) {
Write-Host "[WARN] Desktop path unavailable; skipped desktop shortcut creation" -ForegroundColor Yellow
substep "Desktop path unavailable; skipped desktop shortcut creation" "Yellow"
}
if (-not $startMenuLink) {
Write-Host "[WARN] APPDATA/Start Menu path unavailable; skipped Start menu shortcut creation" -ForegroundColor Yellow
substep "APPDATA/Start Menu path unavailable; skipped Start menu shortcut creation" "Yellow"
}
$iconPath = Join-Path $appDir "unsloth.ico"
$bundledIcon = $null
@ -362,27 +499,27 @@ shell.Run cmd, 0, False
$shortcut.Save()
$createdShortcutCount++
} catch {
Write-Host "[WARN] Could not create shortcut at ${linkPath}: $($_.Exception.Message)" -ForegroundColor Yellow
substep "could not create shortcut at ${linkPath}: $($_.Exception.Message)" "Yellow"
}
}
if ($createdShortcutCount -gt 0) {
Write-Host "[OK] Created Unsloth Studio shortcut(s): $createdShortcutCount" -ForegroundColor Green
substep "Created Unsloth Studio shortcut"
} else {
Write-Host "[WARN] No Unsloth Studio shortcuts were created" -ForegroundColor Yellow
substep "no Unsloth Studio shortcuts were created" "Yellow"
}
} catch {
Write-Host "[WARN] Shortcut creation unavailable: $($_.Exception.Message)" -ForegroundColor Yellow
substep "shortcut creation unavailable: $($_.Exception.Message)" "Yellow"
}
} catch {
Write-Host "[WARN] Shortcut setup failed; skipping shortcuts: $($_.Exception.Message)" -ForegroundColor Yellow
substep "shortcut setup failed; skipping shortcuts: $($_.Exception.Message)" "Yellow"
}
}
# ── 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
step "winget" "not available" "Red"
substep "Install it from https://aka.ms/getwinget" "Yellow"
substep "or install Python $PythonVersion and uv manually, then re-run." "Yellow"
return
}
@ -460,10 +597,10 @@ shell.Run cmd, 0, False
# Find-CompatiblePython returns @{ Version = "3.13"; Path = "C:\...\python.exe" } or $null.
$DetectedPython = Find-CompatiblePython
if ($DetectedPython) {
Write-Host "==> Python already installed: Python $($DetectedPython.Version)"
step "python" "Python $($DetectedPython.Version) already installed"
}
if (-not $DetectedPython) {
Write-Host "==> Installing Python ${PythonVersion}..."
substep "installing Python ${PythonVersion}..."
$pythonPackageId = "Python.Python.$PythonVersion"
# Temporarily lower ErrorActionPreference so that winget stderr
# (progress bars, warnings) does not become a terminating error
@ -485,7 +622,7 @@ shell.Run cmd, 0, False
# This handles both real failures AND "already installed" codes where
# winget thinks Python is present but it's not actually on PATH
# (e.g. user partially uninstalled, or installed via a different method).
Write-Host " Python not found on PATH after winget. Retrying with --force..."
substep "Python not found on PATH after winget. Retrying with --force..." "Yellow"
$ErrorActionPreference = "Continue"
try {
winget install -e --id $pythonPackageId --accept-package-agreements --accept-source-agreements --force
@ -507,7 +644,7 @@ shell.Run cmd, 0, False
# ── Install uv if not present ──
if (-not (Get-Command uv -ErrorAction SilentlyContinue)) {
Write-Host "==> Installing uv package manager..."
substep "installing uv package manager..."
$prevEAP = $ErrorActionPreference
$ErrorActionPreference = "Continue"
try { winget install --id=astral-sh.uv -e --accept-package-agreements --accept-source-agreements } catch {}
@ -515,15 +652,15 @@ shell.Run cmd, 0, False
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..."
substep "trying alternative uv installer..." "Yellow"
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
step "uv" "could not be installed" "Red"
substep "Install it from https://docs.astral.sh/uv/" "Yellow"
return
}
@ -539,13 +676,13 @@ shell.Run cmd, 0, False
if (Test-Path $VenvPython) {
# New layout already exists -- nuke for fresh install
Write-Host "==> Removing existing environment for fresh install..."
substep "removing existing environment for fresh install..."
Remove-Item -Recurse -Force $VenvDir
} elseif (Test-Path (Join-Path $StudioHome ".venv\Scripts\python.exe")) {
# Old layout (~/.unsloth/studio/.venv) exists -- validate before migrating
$OldVenv = Join-Path $StudioHome ".venv"
$OldPy = Join-Path $OldVenv "Scripts\python.exe"
Write-Host "==> Found legacy Studio environment, validating..."
substep "found legacy Studio environment, validating..."
$prevEAP2 = $ErrorActionPreference
$ErrorActionPreference = "Continue"
try {
@ -554,32 +691,34 @@ shell.Run cmd, 0, False
} catch { $torchOk = $false }
$ErrorActionPreference = $prevEAP2
if ($torchOk) {
Write-Host " Legacy environment is healthy -- migrating..."
substep "legacy environment is healthy -- migrating..."
Move-Item -Path $OldVenv -Destination $VenvDir -Force
Write-Host " Moved .venv -> unsloth_studio"
substep "moved .venv -> unsloth_studio"
$_Migrated = $true
} else {
Write-Host " Legacy environment failed validation -- creating fresh environment"
substep "legacy environment failed validation -- creating fresh environment" "Yellow"
Remove-Item -Recurse -Force $OldVenv -ErrorAction SilentlyContinue
}
} elseif (Test-Path (Join-Path $env:USERPROFILE "unsloth_studio\Scripts\python.exe")) {
# CWD-relative venv from old install.ps1 -- migrate to absolute path
$CwdVenv = Join-Path $env:USERPROFILE "unsloth_studio"
Write-Host "==> Found CWD-relative Studio environment, migrating to $VenvDir..."
substep "found CWD-relative Studio environment, migrating to $VenvDir..."
Move-Item -Path $CwdVenv -Destination $VenvDir -Force
Write-Host " Moved ~/unsloth_studio -> ~/.unsloth/studio/unsloth_studio"
substep "moved ~/unsloth_studio -> ~/.unsloth/studio/unsloth_studio"
$_Migrated = $true
}
if (-not (Test-Path $VenvPython)) {
Write-Host "==> Creating Python $($DetectedPython.Version) virtual environment ($VenvDir)..."
uv venv $VenvDir --python "$($DetectedPython.Path)"
if ($LASTEXITCODE -ne 0) {
Write-Host "[ERROR] Failed to create virtual environment (exit code $LASTEXITCODE)" -ForegroundColor Red
step "venv" "creating Python $($DetectedPython.Version) virtual environment"
substep "$VenvDir"
$venvExit = Invoke-InstallCommand { uv venv $VenvDir --python "$($DetectedPython.Path)" }
if ($venvExit -ne 0) {
Write-Host "[ERROR] Failed to create virtual environment (exit code $venvExit)" -ForegroundColor Red
return
}
} else {
Write-Host "==> Using migrated environment at $VenvDir"
step "venv" "using migrated environment"
substep "$VenvDir"
}
# ── Detect GPU (robust: PATH + hardcoded fallback paths, mirrors setup.ps1) ──
@ -588,7 +727,7 @@ shell.Run cmd, 0, False
try {
$nvSmiCmd = Get-Command nvidia-smi -ErrorAction SilentlyContinue
if ($nvSmiCmd) {
& $nvSmiCmd.Source 2>&1 | Out-Null
& $nvSmiCmd.Source *> $null
if ($LASTEXITCODE -eq 0) { $HasNvidiaSmi = $true; $NvidiaSmiExe = $nvSmiCmd.Source }
}
} catch {}
@ -599,18 +738,18 @@ shell.Run cmd, 0, False
)) {
if (Test-Path $p) {
try {
& $p 2>&1 | Out-Null
& $p *> $null
if ($LASTEXITCODE -eq 0) { $HasNvidiaSmi = $true; $NvidiaSmiExe = $p; break }
} catch {}
}
}
}
if ($HasNvidiaSmi) {
Write-Host "[OK] NVIDIA GPU detected" -ForegroundColor Green
step "gpu" "NVIDIA GPU detected"
} else {
Write-Host "[WARN] No NVIDIA GPU detected. Studio will run in chat-only (GGUF) mode." -ForegroundColor Yellow
Write-Host " Training and GPU inference require an NVIDIA GPU with drivers installed." -ForegroundColor Yellow
Write-Host " https://www.nvidia.com/Download/index.aspx" -ForegroundColor Yellow
step "gpu" "none (chat-only / GGUF)" "Yellow"
substep "Training and GPU inference require an NVIDIA GPU with drivers installed." "Yellow"
substep "https://www.nvidia.com/Download/index.aspx" "Yellow"
}
# ── Choose the correct PyTorch index URL based on driver CUDA version ──
@ -630,7 +769,7 @@ shell.Run cmd, 0, False
return "$baseUrl/cpu"
}
} catch {}
Write-Host "[WARN] Could not determine CUDA version from nvidia-smi, defaulting to cu126" -ForegroundColor Yellow
substep "could not determine CUDA version from nvidia-smi, defaulting to cu126" "Yellow"
return "$baseUrl/cu126"
}
$TorchIndexUrl = Get-TorchIndexUrl
@ -677,74 +816,101 @@ shell.Run cmd, 0, False
if ($_Migrated) {
# Migrated env: force-reinstall unsloth+unsloth-zoo to ensure clean state
# in the new venv location, while preserving existing torch/CUDA
Write-Host "==> Upgrading unsloth in migrated environment..."
substep "upgrading unsloth in migrated environment..."
if ($SkipTorch) {
# No-torch: install unsloth + unsloth-zoo with --no-deps, then
# runtime deps (typer, safetensors, transformers, etc.) with --no-deps.
uv pip install --python $VenvPython --no-deps --reinstall-package unsloth --reinstall-package unsloth-zoo "unsloth>=2026.3.16" unsloth-zoo
$NoTorchReq = Find-NoTorchRuntimeFile
if ($NoTorchReq) {
uv pip install --python $VenvPython --no-deps -r $NoTorchReq
$baseInstallExit = Invoke-InstallCommand { uv pip install --python $VenvPython --no-deps --reinstall-package unsloth --reinstall-package unsloth-zoo "unsloth>=2026.3.16" unsloth-zoo }
if ($baseInstallExit -eq 0) {
$NoTorchReq = Find-NoTorchRuntimeFile
if ($NoTorchReq) {
$baseInstallExit = Invoke-InstallCommand { uv pip install --python $VenvPython --no-deps -r $NoTorchReq }
}
}
} else {
uv pip install --python $VenvPython --reinstall-package unsloth --reinstall-package unsloth-zoo "unsloth>=2026.3.16" unsloth-zoo
$baseInstallExit = Invoke-InstallCommand { uv pip install --python $VenvPython --reinstall-package unsloth --reinstall-package unsloth-zoo "unsloth>=2026.3.16" unsloth-zoo }
}
if ($baseInstallExit -ne 0) {
Write-Host "[ERROR] Failed to install unsloth (exit code $baseInstallExit)" -ForegroundColor Red
return
}
if ($StudioLocalInstall) {
Write-Host "==> Overlaying local repo (editable)..."
uv pip install --python $VenvPython -e $RepoRoot --no-deps
substep "overlaying local repo (editable)..."
$overlayExit = Invoke-InstallCommand { uv pip install --python $VenvPython -e $RepoRoot --no-deps }
if ($overlayExit -ne 0) {
Write-Host "[ERROR] Failed to overlay local repo (exit code $overlayExit)" -ForegroundColor Red
return
}
}
} elseif ($TorchIndexUrl) {
if ($SkipTorch) {
Write-Host "==> Skipping PyTorch (--no-torch flag set)."
substep "skipping PyTorch (--no-torch flag set)." "Yellow"
} else {
Write-Host "==> Installing PyTorch ($TorchIndexUrl)..."
uv pip install --python $VenvPython "torch>=2.4,<2.11.0" torchvision torchaudio --index-url $TorchIndexUrl
if ($LASTEXITCODE -ne 0) {
Write-Host "[ERROR] Failed to install PyTorch (exit code $LASTEXITCODE)" -ForegroundColor Red
substep "installing PyTorch ($TorchIndexUrl)..."
$torchInstallExit = Invoke-InstallCommand { uv pip install --python $VenvPython "torch>=2.4,<2.11.0" torchvision torchaudio --index-url $TorchIndexUrl }
if ($torchInstallExit -ne 0) {
Write-Host "[ERROR] Failed to install PyTorch (exit code $torchInstallExit)" -ForegroundColor Red
return
}
}
Write-Host "==> Installing unsloth (this may take a few minutes)..."
substep "installing unsloth (this may take a few minutes)..."
if ($SkipTorch) {
# No-torch: install unsloth + unsloth-zoo with --no-deps, then
# runtime deps (typer, safetensors, transformers, etc.) with --no-deps.
uv pip install --python $VenvPython --no-deps --upgrade-package unsloth --upgrade-package unsloth-zoo "unsloth>=2026.3.16" unsloth-zoo
$NoTorchReq = Find-NoTorchRuntimeFile
if ($NoTorchReq) {
uv pip install --python $VenvPython --no-deps -r $NoTorchReq
}
if ($StudioLocalInstall) {
Write-Host "==> Overlaying local repo (editable)..."
uv pip install --python $VenvPython -e $RepoRoot --no-deps
$baseInstallExit = Invoke-InstallCommand { uv pip install --python $VenvPython --no-deps --upgrade-package unsloth --upgrade-package unsloth-zoo "unsloth>=2026.3.16" unsloth-zoo }
if ($baseInstallExit -eq 0) {
$NoTorchReq = Find-NoTorchRuntimeFile
if ($NoTorchReq) {
$baseInstallExit = Invoke-InstallCommand { uv pip install --python $VenvPython --no-deps -r $NoTorchReq }
}
}
} elseif ($StudioLocalInstall) {
uv pip install --python $VenvPython --upgrade-package unsloth "unsloth>=2026.3.16" unsloth-zoo
Write-Host "==> Overlaying local repo (editable)..."
uv pip install --python $VenvPython -e $RepoRoot --no-deps
$baseInstallExit = Invoke-InstallCommand { uv pip install --python $VenvPython --upgrade-package unsloth "unsloth>=2026.3.16" unsloth-zoo }
} else {
uv pip install --python $VenvPython --upgrade-package unsloth "$PackageName"
$baseInstallExit = Invoke-InstallCommand { uv pip install --python $VenvPython --upgrade-package unsloth "$PackageName" }
}
if ($baseInstallExit -ne 0) {
Write-Host "[ERROR] Failed to install unsloth (exit code $baseInstallExit)" -ForegroundColor Red
return
}
if ($StudioLocalInstall) {
substep "overlaying local repo (editable)..."
$overlayExit = Invoke-InstallCommand { uv pip install --python $VenvPython -e $RepoRoot --no-deps }
if ($overlayExit -ne 0) {
Write-Host "[ERROR] Failed to overlay local repo (exit code $overlayExit)" -ForegroundColor Red
return
}
}
} else {
# Fallback: GPU detection failed to produce a URL -- let uv resolve torch
Write-Host "==> Installing unsloth (this may take a few minutes)..."
substep "installing unsloth (this may take a few minutes)..."
if ($StudioLocalInstall) {
uv pip install --python $VenvPython unsloth-zoo "unsloth>=2026.3.16" --torch-backend=auto
Write-Host "==> Overlaying local repo (editable)..."
uv pip install --python $VenvPython -e $RepoRoot --no-deps
$baseInstallExit = Invoke-InstallCommand { uv pip install --python $VenvPython unsloth-zoo "unsloth>=2026.3.16" --torch-backend=auto }
if ($baseInstallExit -ne 0) {
Write-Host "[ERROR] Failed to install unsloth (exit code $baseInstallExit)" -ForegroundColor Red
return
}
substep "overlaying local repo (editable)..."
$overlayExit = Invoke-InstallCommand { uv pip install --python $VenvPython -e $RepoRoot --no-deps }
if ($overlayExit -ne 0) {
Write-Host "[ERROR] Failed to overlay local repo (exit code $overlayExit)" -ForegroundColor Red
return
}
} else {
uv pip install --python $VenvPython "$PackageName" --torch-backend=auto
$baseInstallExit = Invoke-InstallCommand { uv pip install --python $VenvPython "$PackageName" --torch-backend=auto }
if ($baseInstallExit -ne 0) {
Write-Host "[ERROR] Failed to install unsloth (exit code $baseInstallExit)" -ForegroundColor Red
return
}
}
}
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..."
step "setup" "running unsloth studio setup..."
$UnslothExe = Join-Path $VenvDir "Scripts\unsloth.exe"
if (-not (Test-Path $UnslothExe)) {
Write-Host "[ERROR] unsloth CLI was not installed correctly." -ForegroundColor Red
@ -754,17 +920,27 @@ shell.Run cmd, 0, False
return
}
# Tell setup.ps1 to skip base package installation (install.ps1 already did it)
# Tell setup.ps1 to skip base package installation (install.ps1 already did it)
$env:SKIP_STUDIO_BASE = "1"
$env:STUDIO_PACKAGE_NAME = $PackageName
$env:UNSLOTH_NO_TORCH = if ($SkipTorch) { "true" } else { "false" }
# Always set STUDIO_LOCAL_INSTALL explicitly to avoid stale values from
# a previous --local run in the same PowerShell session.
if ($StudioLocalInstall) {
$env:STUDIO_LOCAL_INSTALL = "1"
$env:STUDIO_LOCAL_REPO = $RepoRoot
} else {
$env:STUDIO_LOCAL_INSTALL = "0"
Remove-Item Env:STUDIO_LOCAL_REPO -ErrorAction SilentlyContinue
}
& $UnslothExe studio setup
if ($LASTEXITCODE -ne 0) {
Write-Host "[ERROR] unsloth studio setup failed (exit code $LASTEXITCODE)" -ForegroundColor Red
# Use 'studio setup' (not 'studio update') because 'update' pops
# SKIP_STUDIO_BASE, which would cause redundant package reinstallation
# and bypass the fast-path version check from PR #4667.
$studioArgs = @('studio', 'setup')
if ($script:UnslothVerbose) { $studioArgs += '--verbose' }
& $UnslothExe @studioArgs
$setupExit = $LASTEXITCODE
if ($setupExit -ne 0) {
Write-Host "[ERROR] unsloth studio setup failed (exit code $setupExit)" -ForegroundColor Red
return
}
@ -780,27 +956,18 @@ shell.Run cmd, 0, False
[System.Environment]::SetEnvironmentVariable("Path", "$ScriptsDir", "User")
}
Refresh-SessionPath
Write-Host "[OK] Added unsloth to PATH" -ForegroundColor Green
step "path" "added unsloth to PATH"
}
Write-Host ""
Write-Host "========================================="
Write-Host " Unsloth Studio installed!"
Write-Host "========================================="
Write-Host ""
# Launch studio automatically in interactive terminals;
# in non-interactive environments (CI, Docker) just print instructions.
$IsInteractive = [Environment]::UserInteractive -and (-not [Console]::IsInputRedirected)
if ($IsInteractive) {
Write-Host "==> Launching Unsloth Studio..."
Write-Host ""
& $UnslothExe studio -H 0.0.0.0 -p 8888
} else {
Write-Host " To launch, run:"
Write-Host ""
Write-Host " & `"$VenvDir\Scripts\Activate.ps1`""
Write-Host " unsloth studio -H 0.0.0.0 -p 8888"
step "launch" "manual commands:"
substep "& `"$VenvDir\Scripts\Activate.ps1`""
substep "unsloth studio -H 0.0.0.0 -p 8888"
Write-Host ""
}
}

View file

@ -8,11 +8,36 @@
# Usage (py): ./install.sh --python 3.12 (override auto-detected Python version)
set -e
# ── Output style (aligned with studio/setup.sh) ──
RULE=""
_rule_i=0
while [ "$_rule_i" -lt 52 ]; do
RULE="${RULE}"
_rule_i=$((_rule_i + 1))
done
if [ -n "${NO_COLOR:-}" ]; then
C_TITLE= C_DIM= C_OK= C_WARN= C_ERR= C_RST=
elif [ -t 1 ] || [ -n "${FORCE_COLOR:-}" ]; then
_ESC="$(printf '\033')"
C_TITLE="${_ESC}[38;5;150m"
C_DIM="${_ESC}[38;5;245m"
C_OK="${_ESC}[38;5;108m"
C_WARN="${_ESC}[38;5;136m"
C_ERR="${_ESC}[91m"
C_RST="${_ESC}[0m"
else
C_TITLE= C_DIM= C_OK= C_WARN= C_ERR= C_RST=
fi
step() { printf " ${C_DIM}%-15.15s${C_RST}${3:-$C_OK}%s${C_RST}\n" "$1" "$2"; }
substep() { printf " ${C_DIM}%-15s${2:-$C_DIM}%s${C_RST}\n" "" "$1"; }
# ── Parse flags ──
STUDIO_LOCAL_INSTALL=false
PACKAGE_NAME="unsloth"
_USER_PYTHON=""
_NO_TORCH_FLAG=false
_VERBOSE=false
_next_is_package=false
_next_is_python=false
for arg in "$@"; do
@ -31,9 +56,44 @@ for arg in "$@"; do
--package) _next_is_package=true ;;
--python) _next_is_python=true ;;
--no-torch) _NO_TORCH_FLAG=true ;;
--verbose|-v) _VERBOSE=true ;;
esac
done
if [ "$_VERBOSE" = true ]; then
export UNSLOTH_VERBOSE=1
fi
_is_verbose() {
[ "${UNSLOTH_VERBOSE:-0}" = "1" ]
}
run_maybe_quiet() {
if _is_verbose; then
"$@"
else
"$@" > /dev/null 2>&1
fi
}
run_install_cmd() {
_label="$1"
shift
if _is_verbose; then
"$@" && return 0
_rc=$?
step "error" "$_label failed (exit code $_rc)" "$C_ERR" >&2
return "$_rc"
fi
_log=$(mktemp)
"$@" >"$_log" 2>&1 && { rm -f "$_log"; return 0; }
_rc=$?
step "error" "$_label failed (exit code $_rc)" "$C_ERR" >&2
cat "$_log" >&2
rm -f "$_log"
return $_rc
}
if [ "$_next_is_package" = true ]; then
echo "❌ ERROR: --package requires an argument." >&2
exit 1
@ -643,14 +703,13 @@ WSLPS1_EOF
fi
if [ "$_css_created" -eq 1 ]; then
echo "[OK] Created Unsloth Studio shortcut(s)"
substep "Created Unsloth Studio shortcut"
fi
}
echo ""
echo "========================================="
echo " Unsloth Studio Installer"
echo "========================================="
printf " ${C_TITLE}%s${C_RST}\n" "🦥 Unsloth Studio Installer"
printf " ${C_DIM}%s${C_RST}\n" "$RULE"
echo ""
# ── Detect platform ──
@ -660,7 +719,7 @@ if [ "$(uname)" = "Darwin" ]; then
elif grep -qi microsoft /proc/version 2>/dev/null; then
OS="wsl"
fi
echo "==> Platform: $OS"
step "platform" "$OS"
# ── Architecture detection & Python version ──
_ARCH=$(uname -m)
@ -740,8 +799,8 @@ 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."
step "deps" "missing: $MISSING" "$C_WARN"
substep "These are needed to build the GGUF inference engine."
case "$OS" in
macos)
@ -766,7 +825,7 @@ if [ -n "$MISSING" ]; then
esac
echo ""
else
echo "==> All system dependencies found."
step "deps" "all system dependencies found"
fi
# ── Install uv ──
@ -812,10 +871,10 @@ _uv_version_ok() {
}
if ! command -v uv >/dev/null 2>&1 || ! _uv_version_ok uv; then
echo "==> Installing uv package manager..."
substep "installing uv package manager..."
_uv_tmp=$(mktemp)
download "https://astral.sh/uv/install.sh" "$_uv_tmp"
sh "$_uv_tmp" </dev/null
run_maybe_quiet sh "$_uv_tmp" </dev/null
rm -f "$_uv_tmp"
if [ -f "$HOME/.local/bin/env" ]; then
. "$HOME/.local/bin/env"
@ -833,7 +892,7 @@ if [ -x "$VENV_DIR/bin/python" ]; then
rm -rf "$VENV_DIR"
elif [ -x "$STUDIO_HOME/.venv/bin/python" ]; then
# Old layout exists — validate before migrating
echo "==> Found legacy Studio environment, validating..."
substep "found legacy Studio environment, validating..."
if "$STUDIO_HOME/.venv/bin/python" -c "
import torch
device = 'cuda' if torch.cuda.is_available() else 'cpu'
@ -866,8 +925,9 @@ if [ "$SKIP_TORCH" = true ] && [ "$MAC_INTEL" = true ] && [ -z "$_USER_PYTHON" ]
fi
if [ ! -x "$VENV_DIR/bin/python" ]; then
echo "==> Creating Python ${PYTHON_VERSION} virtual environment (${VENV_DIR})..."
uv venv "$VENV_DIR" --python "$PYTHON_VERSION"
step "venv" "creating Python ${PYTHON_VERSION} virtual environment"
substep "$VENV_DIR"
run_install_cmd "create venv" uv venv "$VENV_DIR" --python "$PYTHON_VERSION"
fi
# Guard against Python 3.13.8 torch import bug on Apple Silicon
@ -880,12 +940,13 @@ if [ -z "$_USER_PYTHON" ] && [ "$OS" = "macos" ] && [ "$_ARCH" = "arm64" ]; then
echo " Recreating venv with Python 3.12..."
rm -rf "$VENV_DIR"
PYTHON_VERSION="3.12"
uv venv "$VENV_DIR" --python "$PYTHON_VERSION"
run_install_cmd "recreate venv" uv venv "$VENV_DIR" --python "$PYTHON_VERSION"
fi
fi
if [ -x "$VENV_DIR/bin/python" ]; then
echo "==> Using environment at ${VENV_DIR}"
step "venv" "using environment"
substep "${VENV_DIR}"
fi
# ── Resolve repo root (for --local installs) ──
@ -960,71 +1021,71 @@ _VENV_PY="$VENV_DIR/bin/python"
if [ "$_MIGRATED" = true ]; then
# Migrated env: force-reinstall unsloth+unsloth-zoo to ensure clean state
# in the new venv location, while preserving existing torch/CUDA
echo "==> Upgrading unsloth in migrated environment..."
substep "upgrading unsloth in migrated environment..."
if [ "$SKIP_TORCH" = true ]; then
# No-torch: install unsloth + unsloth-zoo with --no-deps (current
# PyPI metadata still declares torch as a hard dep), then install
# runtime deps (typer, safetensors, transformers, etc.) with --no-deps
# to prevent transitive torch resolution.
uv pip install --python "$_VENV_PY" --no-deps \
run_install_cmd "install unsloth (migrated no-torch)" uv pip install --python "$_VENV_PY" --no-deps \
--reinstall-package unsloth --reinstall-package unsloth-zoo \
"unsloth>=2026.3.16" unsloth-zoo
_NO_TORCH_RT="$(_find_no_torch_runtime)"
if [ -n "$_NO_TORCH_RT" ]; then
uv pip install --python "$_VENV_PY" --no-deps -r "$_NO_TORCH_RT"
run_install_cmd "install no-torch runtime deps" uv pip install --python "$_VENV_PY" --no-deps -r "$_NO_TORCH_RT"
fi
else
uv pip install --python "$_VENV_PY" \
run_install_cmd "install unsloth (migrated)" uv pip install --python "$_VENV_PY" \
--reinstall-package unsloth --reinstall-package unsloth-zoo \
"unsloth>=2026.3.16" unsloth-zoo
fi
if [ "$STUDIO_LOCAL_INSTALL" = true ]; then
echo "==> Overlaying local repo (editable)..."
uv pip install --python "$_VENV_PY" -e "$_REPO_ROOT" --no-deps
substep "overlaying local repo (editable)..."
run_install_cmd "overlay local repo" uv pip install --python "$_VENV_PY" -e "$_REPO_ROOT" --no-deps
fi
elif [ -n "$TORCH_INDEX_URL" ]; then
# Fresh: Step 1 - install torch from explicit index (skip when --no-torch or Intel Mac)
if [ "$SKIP_TORCH" = true ]; then
echo "==> Skipping PyTorch (--no-torch or Intel Mac x86_64)."
substep "skipping PyTorch (--no-torch or Intel Mac x86_64)." "$C_WARN"
else
echo "==> Installing PyTorch ($TORCH_INDEX_URL)..."
uv pip install --python "$_VENV_PY" "torch>=2.4,<2.11.0" torchvision torchaudio \
substep "installing PyTorch ($TORCH_INDEX_URL)..."
run_install_cmd "install PyTorch" uv pip install --python "$_VENV_PY" "torch>=2.4,<2.11.0" torchvision torchaudio \
--index-url "$TORCH_INDEX_URL"
fi
# Fresh: Step 2 - install unsloth, preserving pre-installed torch
echo "==> Installing unsloth (this may take a few minutes)..."
substep "installing unsloth (this may take a few minutes)..."
if [ "$SKIP_TORCH" = true ]; then
# No-torch: install unsloth + unsloth-zoo with --no-deps, then
# runtime deps (typer, safetensors, transformers, etc.) with --no-deps.
uv pip install --python "$_VENV_PY" --no-deps \
run_install_cmd "install unsloth (no-torch)" uv pip install --python "$_VENV_PY" --no-deps \
--upgrade-package unsloth --upgrade-package unsloth-zoo \
"unsloth>=2026.3.16" unsloth-zoo
_NO_TORCH_RT="$(_find_no_torch_runtime)"
if [ -n "$_NO_TORCH_RT" ]; then
uv pip install --python "$_VENV_PY" --no-deps -r "$_NO_TORCH_RT"
run_install_cmd "install no-torch runtime deps" uv pip install --python "$_VENV_PY" --no-deps -r "$_NO_TORCH_RT"
fi
if [ "$STUDIO_LOCAL_INSTALL" = true ]; then
echo "==> Overlaying local repo (editable)..."
uv pip install --python "$_VENV_PY" -e "$_REPO_ROOT" --no-deps
substep "overlaying local repo (editable)..."
run_install_cmd "overlay local repo" uv pip install --python "$_VENV_PY" -e "$_REPO_ROOT" --no-deps
fi
elif [ "$STUDIO_LOCAL_INSTALL" = true ]; then
uv pip install --python "$_VENV_PY" \
run_install_cmd "install unsloth (local)" uv pip install --python "$_VENV_PY" \
--upgrade-package unsloth "unsloth>=2026.3.16" unsloth-zoo
echo "==> Overlaying local repo (editable)..."
uv pip install --python "$_VENV_PY" -e "$_REPO_ROOT" --no-deps
substep "overlaying local repo (editable)..."
run_install_cmd "overlay local repo" uv pip install --python "$_VENV_PY" -e "$_REPO_ROOT" --no-deps
else
uv pip install --python "$_VENV_PY" \
run_install_cmd "install unsloth" uv pip install --python "$_VENV_PY" \
--upgrade-package unsloth "$PACKAGE_NAME"
fi
else
# Fallback: GPU detection failed to produce a URL -- let uv resolve torch
echo "==> Installing unsloth (this may take a few minutes)..."
substep "installing unsloth (this may take a few minutes)..."
if [ "$STUDIO_LOCAL_INSTALL" = true ]; then
uv pip install --python "$_VENV_PY" unsloth-zoo "unsloth>=2026.3.16" --torch-backend=auto
echo "==> Overlaying local repo (editable)..."
uv pip install --python "$_VENV_PY" -e "$_REPO_ROOT" --no-deps
run_install_cmd "install unsloth (auto torch backend)" uv pip install --python "$_VENV_PY" unsloth-zoo "unsloth>=2026.3.16" --torch-backend=auto
substep "overlaying local repo (editable)..."
run_install_cmd "overlay local repo" uv pip install --python "$_VENV_PY" -e "$_REPO_ROOT" --no-deps
else
uv pip install --python "$_VENV_PY" "$PACKAGE_NAME" --torch-backend=auto
run_install_cmd "install unsloth (auto torch backend)" uv pip install --python "$_VENV_PY" "$PACKAGE_NAME" --torch-backend=auto
fi
fi
@ -1059,19 +1120,33 @@ if [ -n "$VENV_ABS_BIN" ]; then
export PATH="$VENV_ABS_BIN:$PATH"
fi
echo "==> Running unsloth setup..."
if ! command -v bash >/dev/null 2>&1; then
step "setup" "bash is required to run studio setup" "$C_ERR"
substep "Please install bash and re-run install.sh"
exit 1
fi
step "setup" "running unsloth studio update..."
# install.sh already installs base packages (unsloth + unsloth-zoo) and
# no-torch-runtime.txt above, so tell install_python_stack.py to skip
# the base step to avoid redundant reinstallation.
_SKIP_BASE=1
# Run setup.sh outside set -e so that a llama.cpp build failure (exit 1)
# does not skip PATH setup, shortcuts, and launch below. We capture the
# exit code and propagate it after post-install steps finish.
_SETUP_EXIT=0
if [ "$STUDIO_LOCAL_INSTALL" = true ]; then
SKIP_STUDIO_BASE=1 \
SKIP_STUDIO_BASE="$_SKIP_BASE" \
STUDIO_PACKAGE_NAME="$PACKAGE_NAME" \
STUDIO_LOCAL_INSTALL=1 \
STUDIO_LOCAL_REPO="$_REPO_ROOT" \
UNSLOTH_NO_TORCH="$SKIP_TORCH" \
bash "$SETUP_SH" </dev/null
bash "$SETUP_SH" </dev/null || _SETUP_EXIT=$?
else
SKIP_STUDIO_BASE=1 \
SKIP_STUDIO_BASE="$_SKIP_BASE" \
STUDIO_PACKAGE_NAME="$PACKAGE_NAME" \
UNSLOTH_NO_TORCH="$SKIP_TORCH" \
bash "$SETUP_SH" </dev/null
bash "$SETUP_SH" </dev/null || _SETUP_EXIT=$?
fi
# ── Make 'unsloth' available globally via ~/.local/bin ──
@ -1096,7 +1171,7 @@ case ":$PATH:" in
echo '' >> "$_SHELL_PROFILE"
echo '# Added by Unsloth installer' >> "$_SHELL_PROFILE"
echo 'export PATH="$HOME/.local/bin:$PATH"' >> "$_SHELL_PROFILE"
echo "==> Added ~/.local/bin to PATH in $_SHELL_PROFILE"
step "path" "added ~/.local/bin to PATH in $_SHELL_PROFILE"
fi
fi
export PATH="$_LOCAL_BIN:$PATH"
@ -1105,17 +1180,30 @@ esac
create_studio_shortcuts "$VENV_ABS_BIN/unsloth" "$OS"
# If setup.sh failed, report and exit now.
# PATH and shortcuts are already set up so the user can fix and retry.
if [ "$_SETUP_EXIT" -ne 0 ]; then
echo ""
step "error" "studio setup failed (exit code $_SETUP_EXIT)" "$C_ERR"
substep "Check the output above for details, then re-run:"
if [ "$STUDIO_LOCAL_INSTALL" = true ]; then
substep " unsloth studio update --local"
else
substep " unsloth studio update"
fi
echo ""
exit "$_SETUP_EXIT"
fi
echo ""
echo "========================================="
echo " Unsloth Studio installed!"
echo "========================================="
printf " ${C_TITLE}%s${C_RST}\n" "Unsloth Studio installed!"
printf " ${C_DIM}%s${C_RST}\n" "$RULE"
echo ""
# Launch studio automatically in interactive terminals;
# in non-interactive environments (Docker, CI, cloud-init) just print instructions.
if [ -t 1 ]; then
echo "==> Launching Unsloth Studio..."
echo ""
step "launch" "starting Unsloth Studio..."
"$VENV_DIR/bin/unsloth" studio -H 0.0.0.0 -p 8888
_LAUNCH_EXIT=$?
if [ "$_LAUNCH_EXIT" -ne 0 ] && [ "$_MIGRATED" = true ]; then
@ -1130,13 +1218,10 @@ if [ -t 1 ]; then
fi
exit "$_LAUNCH_EXIT"
else
echo " To launch, run:"
echo ""
echo " unsloth studio -H 0.0.0.0 -p 8888"
echo ""
echo " Or activate the environment first:"
echo ""
echo " source ${VENV_DIR}/bin/activate"
echo " unsloth studio -H 0.0.0.0 -p 8888"
step "launch" "manual commands:"
substep "unsloth studio -H 0.0.0.0 -p 8888"
substep "or activate env first:"
substep "source ${VENV_DIR}/bin/activate"
substep "unsloth studio -H 0.0.0.0 -p 8888"
echo ""
fi

View file

@ -20,7 +20,7 @@ def stdout_supports_color() -> bool:
return True
try:
return sys.stdout.isatty()
except Exception:
except (AttributeError, OSError, ValueError):
return False
@ -52,28 +52,36 @@ def print_studio_access_banner(
ipv6_bind = bind_host in ("::", "::1")
if ipv6_bind:
local_url = f"http://[::1]:{port}"
loopback_url = f"http://[::1]:{port}"
alt_local = f"http://localhost:{port}"
else:
local_url = f"http://127.0.0.1:{port}"
loopback_url = f"http://127.0.0.1:{port}"
alt_local = f"http://localhost:{port}"
if ":" in display_host:
external_url = f"http://[{display_host}]:{port}"
else:
external_url = f"http://{display_host}:{port}"
listen_all = bind_host in ("0.0.0.0", "::")
loopback_bind = bind_host in ("127.0.0.1", "localhost", "::1")
api_base = local_url if listen_all or loopback_bind else external_url
# Use loopback URL only when the server is reachable on loopback;
# otherwise show the actual bound address.
primary_url = loopback_url if listen_all or loopback_bind else external_url
tip_url = alt_local if listen_all or loopback_bind else external_url
api_base = primary_url
lines: list[str] = [
"",
style("🦥 Unsloth Studio is running", title),
style("" * 52, dim),
style(" On this machine — open this in your browser:", dim),
style(f" {local_url}", local_url_style),
style(f" (same as {alt_local})", dim),
style(" On this machine -- open this in your browser:", dim),
style(f" {primary_url}", local_url_style),
]
if (listen_all or loopback_bind) and primary_url != alt_local:
lines.append(style(f" (same as {alt_local})", dim))
if listen_all and display_host not in (
"127.0.0.1",
"localhost",
@ -88,7 +96,7 @@ def print_studio_access_banner(
style(f" {external_url}", secondary),
]
)
elif not listen_all and bind_host not in ("127.0.0.1", "localhost", "::1"):
elif not listen_all and not loopback_bind and external_url != primary_url:
lines.extend(
[
"",
@ -105,7 +113,7 @@ def print_studio_access_banner(
style(f" {api_base}/api/health", secondary),
style("" * 52, dim),
style(
" Tip: if you are on the same computer, use the Local link above.",
f" Tip: if you are on this computer, open {tip_url}/ in your browser.",
dim,
),
"",

View file

@ -110,7 +110,7 @@ def _stdout_supports_color() -> bool:
try:
if not sys.stdout.isatty():
return False
except Exception:
except (AttributeError, OSError, ValueError):
return False
if IS_WINDOWS:
try:
@ -121,7 +121,7 @@ def _stdout_supports_color() -> bool:
mode = ctypes.c_ulong()
kernel32.GetConsoleMode(handle, ctypes.byref(mode))
kernel32.SetConsoleMode(handle, mode.value | 0x0004)
except Exception:
except (ImportError, AttributeError, OSError):
return False
return True
@ -460,7 +460,7 @@ def install_python_stack() -> int:
# 3. Core packages: unsloth-zoo + unsloth (or custom package name)
if skip_base:
print(_green(f"{package_name} already installed — skipping base packages"))
pass
elif NO_TORCH:
# No-torch update path: install unsloth + unsloth-zoo with --no-deps
# (current PyPI metadata still declares torch as a hard dep), then

View file

@ -12,9 +12,8 @@
.NOTES
Default output is minimal (step/substep), aligned with studio/setup.sh.
FULL / LEGACY LOGGING (defensible audit trail, multi-line [OK]/[WARN]/paths):
FULL / LEGACY LOGGING (defensible audit trail, detailed multi-line output):
unsloth studio setup --verbose
(sets UNSLOTH_VERBOSE=1; same as install_python_stack.py)
Or: $env:UNSLOTH_VERBOSE='1'; powershell -File .\studio\setup.ps1
Or: .\setup.ps1 --verbose
#>
@ -23,14 +22,20 @@ $ErrorActionPreference = "Stop"
$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
$PackageDir = Split-Path -Parent $ScriptDir
# Same as: unsloth studio setup --verbose (see unsloth_cli/commands/studio.py)
# Verbose can be enabled either by CLI flag or by UNSLOTH_VERBOSE=1.
$script:UnslothVerbose = ($env:UNSLOTH_VERBOSE -eq '1')
foreach ($a in $args) {
if ($a -eq '--verbose' -or $a -eq '-v') {
$env:UNSLOTH_VERBOSE = '1'
$script:UnslothVerbose = $true
break
}
}
$script:UnslothVerbose = ($env:UNSLOTH_VERBOSE -eq '1')
# Propagate to child processes (e.g. install_python_stack.py) so they
# also respect verbose mode. Process-scoped -- does not persist.
if ($script:UnslothVerbose) {
$env:UNSLOTH_VERBOSE = '1'
}
$script:LlamaCppDegraded = $false
# Detect if running from pip install (no frontend/ dir in studio)
$FrontendDir = Join-Path $ScriptDir "frontend"
@ -331,6 +336,51 @@ function Write-SetupVerboseDetail {
}
}
function Invoke-SetupCommand {
param(
[Parameter(Mandatory = $true)][scriptblock]$Command,
[switch]$AlwaysQuiet
)
$prevEap = $ErrorActionPreference
$ErrorActionPreference = "Continue"
try {
# Reset to avoid stale values from prior native commands.
$global:LASTEXITCODE = 0
if ($script:UnslothVerbose -and -not $AlwaysQuiet) {
# Merge stderr into stdout so progress/warning output stays visible
# without flipping $? on successful native commands (PS 5.1 treats
# stderr records as errors that set $? = $false even on exit code 0).
& $Command 2>&1 | Out-Host
} else {
$output = & $Command 2>&1 | Out-String
if ($LASTEXITCODE -ne 0) {
Write-Host $output -ForegroundColor Red
}
}
return [int]$LASTEXITCODE
} finally {
$ErrorActionPreference = $prevEap
}
}
function Write-LlamaFailureLog {
param(
[string]$Output,
[int]$MaxLines = 120
)
if (-not $Output) { return }
$lines = @(
($Output -split "`r?`n") | Where-Object { -not [string]::IsNullOrWhiteSpace($_) }
)
if ($lines.Count -eq 0) { return }
if ($lines.Count -gt $MaxLines) {
Write-Host " Showing last $MaxLines lines:" -ForegroundColor DarkGray
$lines = $lines | Select-Object -Last $MaxLines
}
foreach ($line in $lines) {
Write-Host " | $line" -ForegroundColor DarkGray
}
}
function step {
param(
[Parameter(Mandatory = $true)][string]$Label,
@ -409,7 +459,7 @@ $NvidiaSmiExe = $null # Absolute path -- survives Refresh-Environment
try {
$nvSmiCmd = Get-Command nvidia-smi -ErrorAction SilentlyContinue
if ($nvSmiCmd) {
& $nvSmiCmd.Source 2>&1 | Out-Null
& $nvSmiCmd.Source *> $null
if ($LASTEXITCODE -eq 0) {
$HasNvidiaSmi = $true
$NvidiaSmiExe = $nvSmiCmd.Source
@ -426,7 +476,7 @@ if (-not $HasNvidiaSmi) {
foreach ($p in $nvSmiDefaults) {
if (Test-Path $p) {
try {
& $p 2>&1 | Out-Null
& $p *> $null
if ($LASTEXITCODE -eq 0) {
$HasNvidiaSmi = $true
$NvidiaSmiExe = $p
@ -459,7 +509,7 @@ try {
} catch {}
if ($LongPathsEnabled) {
Write-Host "[OK] Windows Long Paths enabled" -ForegroundColor Green
step "long paths" "enabled"
} else {
Write-Host "Windows Long Paths not enabled (required for Triton compilation and deep dependency paths)." -ForegroundColor Yellow
Write-Host " Requesting admin access to fix..." -ForegroundColor Yellow
@ -470,12 +520,12 @@ if ($LongPathsEnabled) {
-Verb RunAs -Wait -PassThru -ErrorAction Stop
if ($proc.ExitCode -eq 0) {
$LongPathsEnabled = $true
Write-Host "[OK] Windows Long Paths enabled (via UAC)" -ForegroundColor Green
step "long paths" "enabled (via UAC)"
} else {
Write-Host "[WARN] Failed to enable Long Paths (exit code: $($proc.ExitCode))" -ForegroundColor Yellow
step "long paths" "failed to enable (exit code: $($proc.ExitCode))" "Yellow"
}
} catch {
Write-Host "[WARN] Could not enable Long Paths (UAC was declined or not available)" -ForegroundColor Yellow
step "long paths" "could not enable (UAC declined/unavailable)" "Yellow"
Write-Host " Run this manually in an Admin terminal:" -ForegroundColor Yellow
Write-Host ' reg add "HKLM\SYSTEM\CurrentControlSet\Control\FileSystem" /v LongPathsEnabled /t REG_DWORD /d 1 /f' -ForegroundColor Cyan
}
@ -490,7 +540,7 @@ if (-not $HasGit) {
$HasWinget = $null -ne (Get-Command winget -ErrorAction SilentlyContinue)
if ($HasWinget) {
try {
winget install Git.Git --source winget --accept-package-agreements --accept-source-agreements 2>&1 | Out-Null
Invoke-SetupCommand { winget install Git.Git --source winget --accept-package-agreements --accept-source-agreements } | Out-Null
Refresh-Environment
$HasGit = $null -ne (Get-Command git -ErrorAction SilentlyContinue)
} catch { }
@ -514,7 +564,7 @@ if (-not $HasCmake) {
$HasWinget = $null -ne (Get-Command winget -ErrorAction SilentlyContinue)
if ($HasWinget) {
try {
winget install Kitware.CMake --source winget --accept-package-agreements --accept-source-agreements 2>&1 | Out-Null
Invoke-SetupCommand { winget install Kitware.CMake --source winget --accept-package-agreements --accept-source-agreements } | Out-Null
Refresh-Environment
$HasCmake = $null -ne (Get-Command cmake -ErrorAction SilentlyContinue)
} catch { }
@ -579,7 +629,7 @@ if ($vsResult) {
$CmakeGenerator = $vsResult.Generator
$VsInstallPath = $vsResult.InstallPath
step "vs" "$CmakeGenerator ($($vsResult.Source))"
if ($vsResult.ClExe) { Write-Host " cl.exe: $($vsResult.ClExe)" -ForegroundColor Gray }
if ($vsResult.ClExe) { substep "cl.exe: $($vsResult.ClExe)" }
} else {
Write-Host "[ERROR] Visual Studio Build Tools could not be found or installed." -ForegroundColor Red
Write-Host " Manual install:" -ForegroundColor Red
@ -603,14 +653,14 @@ try {
$smiOut = & $NvidiaSmiExe 2>&1 | Out-String
if ($smiOut -match "CUDA Version:\s+([\d]+)\.([\d]+)") {
$DriverMaxCuda = "$($Matches[1]).$($Matches[2])"
Write-Host " Driver supports up to CUDA $DriverMaxCuda" -ForegroundColor Gray
substep "driver supports up to CUDA $DriverMaxCuda"
}
} catch {}
# Detect compute capability early so we can validate toolkit support
$CudaArch = Get-CudaComputeCapability
if ($CudaArch) {
Write-Host " GPU Compute Capability = $($CudaArch.Insert($CudaArch.Length-1, '.')) (sm_$CudaArch)" -ForegroundColor Gray
substep "GPU Compute Capability = $($CudaArch.Insert($CudaArch.Length-1, '.')) (sm_$CudaArch)"
}
# -- Find a toolkit that's compatible with the driver AND the GPU --
@ -643,16 +693,16 @@ if ($DriverMaxCuda) {
if ($CudaArch) {
$archOk = Test-NvccArchSupport -NvccExe $candidateNvcc -Arch $CudaArch
if (-not $archOk) {
Write-Host " [INFO] CUDA_PATH toolkit (CUDA $tkMaj.$tkMin) does not support GPU arch sm_$CudaArch" -ForegroundColor Yellow
Write-Host " Looking for a newer toolkit..." -ForegroundColor Yellow
substep "CUDA_PATH toolkit (CUDA $tkMaj.$tkMin) does not support GPU arch sm_$CudaArch" "Yellow"
substep "Looking for a newer toolkit..." "Yellow"
}
}
if ($archOk) {
$NvccPath = $candidateNvcc
Write-Host " [OK] Using existing CUDA Toolkit at CUDA_PATH (nvcc: $NvccPath)" -ForegroundColor Green
substep "using existing CUDA Toolkit at CUDA_PATH (nvcc: $NvccPath)"
}
} else {
Write-Host " [INFO] CUDA_PATH ($existingCudaPath) has CUDA $tkMaj.$tkMin which exceeds driver max $DriverMaxCuda" -ForegroundColor Yellow
substep "CUDA_PATH ($existingCudaPath) has CUDA $tkMaj.$tkMin which exceeds driver max $DriverMaxCuda" "Yellow"
}
}
}
@ -661,11 +711,11 @@ if ($DriverMaxCuda) {
if (-not $NvccPath) {
$NvccPath = Find-Nvcc -MaxVersion $DriverMaxCuda
if ($NvccPath) {
Write-Host " [OK] Found compatible CUDA Toolkit (nvcc: $NvccPath)" -ForegroundColor Green
substep "found compatible CUDA Toolkit (nvcc: $NvccPath)"
if ($existingCudaPath) {
$selectedRoot = Split-Path (Split-Path $NvccPath -Parent) -Parent
if ($existingCudaPath.TrimEnd('\') -ne $selectedRoot.TrimEnd('\')) {
Write-Host " [INFO] Overriding CUDA_PATH from $existingCudaPath to $selectedRoot" -ForegroundColor Yellow
substep "overriding CUDA_PATH from $existingCudaPath to $selectedRoot" "Yellow"
}
}
} else {
@ -736,26 +786,26 @@ if (-not $NvccPath) {
}
if ($BestVersion) {
Write-Host " Installing CUDA Toolkit $BestVersion via winget... " -ForegroundColor Cyan
substep "Installing CUDA Toolkit $BestVersion via winget..."
$prevEAPCuda = $ErrorActionPreference
$ErrorActionPreference = "Continue"
winget install --id=Nvidia.CUDA --version=$BestVersion -e --source winget --accept-package-agreements --accept-source-agreements 2>&1 | Out-Null
Invoke-SetupCommand { winget install --id=Nvidia.CUDA --version=$BestVersion -e --source winget --accept-package-agreements --accept-source-agreements } | Out-Null
$ErrorActionPreference = $prevEAPCuda
Refresh-Environment
$NvccPath = Find-Nvcc -MaxVersion $DriverMaxCuda
if ($NvccPath) {
Write-Host " [OK] CUDA Toolkit $BestVersion installed (nvcc: $NvccPath)" -ForegroundColor Green
substep "CUDA Toolkit $BestVersion installed (nvcc: $NvccPath)"
}
} else {
Write-Host " [WARN] No compatible CUDA Toolkit version found in winget (need <= $DriverMaxCuda)" -ForegroundColor Yellow
substep "no compatible CUDA Toolkit version found in winget (need <= $DriverMaxCuda)" "Yellow"
}
} else {
Write-Host " Installing CUDA Toolkit (latest) via winget..." -ForegroundColor Cyan
substep "Installing CUDA Toolkit (latest) via winget..."
winget install --id=Nvidia.CUDA -e --source winget --accept-package-agreements --accept-source-agreements
Refresh-Environment
$NvccPath = Find-Nvcc
if ($NvccPath) {
Write-Host " [OK] CUDA Toolkit installed (nvcc: $NvccPath)" -ForegroundColor Green
substep "CUDA Toolkit installed (nvcc: $NvccPath)"
}
}
}
@ -781,7 +831,7 @@ $CudaToolkitRoot = Split-Path (Split-Path $NvccPath -Parent) -Parent
# Always persist CUDA_PATH to User registry so the compatible toolkit is used
# in future sessions (overwrites any existing value pointing to a newer, incompatible version)
[Environment]::SetEnvironmentVariable('CUDA_PATH', $CudaToolkitRoot, 'User')
Write-Host " Persisted CUDA_PATH=$CudaToolkitRoot to user environment" -ForegroundColor Gray
substep "Persisted CUDA_PATH=$CudaToolkitRoot to user environment"
# Clear all versioned CUDA_PATH_V* env vars in this process to prevent
# cmake/MSBuild from discovering a conflicting CUDA installation.
$cudaPathVars = @([Environment]::GetEnvironmentVariables('Process').Keys | Where-Object { $_ -match '^CUDA_PATH_V' })
@ -793,7 +843,7 @@ $tkDirName = Split-Path $CudaToolkitRoot -Leaf
if ($tkDirName -match '^v(\d+)\.(\d+)') {
$cudaPathVerVar = "CUDA_PATH_V$($Matches[1])_$($Matches[2])"
[Environment]::SetEnvironmentVariable($cudaPathVerVar, $CudaToolkitRoot, 'Process')
Write-Host " Set $cudaPathVerVar (cleared other CUDA_PATH_V* vars)" -ForegroundColor Gray
substep "Set $cudaPathVerVar (cleared other CUDA_PATH_V* vars)"
}
# Ensure nvcc's bin dir is on PATH for this process
$nvccBinDir = Split-Path $NvccPath -Parent
@ -808,7 +858,7 @@ if (-not $userPath -or $userPath -notlike "*$nvccBinDir*") {
} else {
[Environment]::SetEnvironmentVariable('Path', "$nvccBinDir", 'User')
}
Write-Host " Persisted CUDA bin dir to user PATH" -ForegroundColor Gray
substep "Persisted CUDA bin dir to user PATH"
}
# -- Ensure CUDA ↔ Visual Studio integration files exist --
@ -821,10 +871,10 @@ if ($VsInstallPath -and $CudaToolkitRoot) {
if ((Test-Path $cudaExtras) -and (Test-Path $vsCustomizations)) {
$hasTargets = Get-ChildItem $vsCustomizations -Filter "CUDA *.targets" -ErrorAction SilentlyContinue
if (-not $hasTargets) {
Write-Host " [INFO] CUDA VS integration missing -- copying .targets files..." -ForegroundColor Yellow
substep "CUDA VS integration missing -- copying .targets files..." "Yellow"
try {
Copy-Item "$cudaExtras\*" $vsCustomizations -Force -ErrorAction Stop
Write-Host " [OK] CUDA VS integration files installed" -ForegroundColor Green
substep "CUDA VS integration files installed"
} catch {
# Direct copy failed (needs admin). Try elevated copy via Start-Process.
try {
@ -832,17 +882,17 @@ if ($VsInstallPath -and $CudaToolkitRoot) {
Start-Process powershell -ArgumentList "-NoProfile -Command $copyCmd" -Verb RunAs -Wait -ErrorAction Stop
$hasTargetsRetry = Get-ChildItem $vsCustomizations -Filter "CUDA *.targets" -ErrorAction SilentlyContinue
if ($hasTargetsRetry) {
Write-Host " [OK] CUDA VS integration files installed (elevated)" -ForegroundColor Green
substep "CUDA VS integration files installed (elevated)"
} else {
throw "Copy did not produce .targets files"
}
} catch {
Write-Host " [WARN] Could not copy CUDA VS integration files" -ForegroundColor Yellow
Write-Host " The llama.cpp build may fail with 'No CUDA toolset found'." -ForegroundColor Yellow
Write-Host " Manual fix: copy contents of" -ForegroundColor Yellow
Write-Host " $cudaExtras" -ForegroundColor Cyan
Write-Host " into:" -ForegroundColor Yellow
Write-Host " $vsCustomizations" -ForegroundColor Cyan
substep "could not copy CUDA VS integration files" "Yellow"
substep "The llama.cpp build may fail with 'No CUDA toolset found'." "Yellow"
substep "Manual fix: copy contents of" "Yellow"
substep "$cudaExtras"
substep "into:" "Yellow"
substep "$vsCustomizations"
}
}
}
@ -850,16 +900,16 @@ if ($VsInstallPath -and $CudaToolkitRoot) {
}
step "cuda" $NvccPath
Write-Host " CUDA_PATH = $CudaToolkitRoot" -ForegroundColor Gray
Write-Host " CudaToolkitDir = $CudaToolkitRoot\" -ForegroundColor Gray
substep "CUDA_PATH = $CudaToolkitRoot"
substep "CudaToolkitDir = $CudaToolkitRoot\"
# $CudaArch was detected earlier (before toolkit selection) so it could
# influence which toolkit we picked. Just log the final state here.
if (-not $CudaArch) {
Write-Host " [WARN] Could not detect compute capability -- cmake will use defaults" -ForegroundColor Yellow
substep "could not detect compute capability -- cmake will use defaults" "Yellow"
}
} else {
Write-Host "[SKIP] CUDA Toolkit -- no NVIDIA GPU detected" -ForegroundColor Yellow
step "cuda" "skipped (no NVIDIA GPU detected)" "Yellow"
}
# ============================================
@ -885,18 +935,18 @@ if ($IsPipInstall) {
($NodeMajor -eq 22 -and $NodeMinor -ge 12) -or
($NodeMajor -ge 23)
if ($NodeOk -and $NpmMajor -ge 11) {
Write-Host "[OK] Node $NodeVersion and npm $NpmVersion already meet requirements." -ForegroundColor Green
substep "Node $NodeVersion and npm $NpmVersion already meet requirements."
$NeedNode = $false
} else {
Write-Host "[WARN] Node $NodeVersion / npm $NpmVersion too old." -ForegroundColor Yellow
substep "Node $NodeVersion / npm $NpmVersion too old." "Yellow"
}
}
} catch {
Write-Host "[WARN] Node/npm not found." -ForegroundColor Yellow
substep "Node/npm not found." "Yellow"
}
if ($NeedNode) {
Write-Host "Installing Node.js LTS via winget..." -ForegroundColor Cyan
substep "installing Node.js LTS via winget..."
try {
winget install OpenJS.NodeJS.LTS --source winget --accept-package-agreements --accept-source-agreements
Refresh-Environment
@ -912,19 +962,19 @@ if ($IsPipInstall) {
# ── bun (optional, faster package installs) ──
# Installed via npm — Node is already guaranteed above. Works on all platforms.
if (-not (Get-Command bun -ErrorAction SilentlyContinue)) {
Write-Host " Installing bun (faster frontend package installs)..." -ForegroundColor DarkGray
substep "installing bun (faster frontend package installs)..."
$prevEAP_bun = $ErrorActionPreference
$ErrorActionPreference = "Continue"
npm install -g bun 2>&1 | Out-Null
Invoke-SetupCommand { npm install -g bun } | Out-Null
$ErrorActionPreference = $prevEAP_bun
Refresh-Environment
if (Get-Command bun -ErrorAction SilentlyContinue) {
Write-Host "[OK] bun installed ($(bun --version))" -ForegroundColor Green
substep "bun installed ($(bun --version))"
} else {
Write-Host "[OK] bun install skipped (npm will be used instead)" -ForegroundColor DarkGray
substep "bun install skipped (npm will be used instead)"
}
} else {
Write-Host "[OK] bun already installed ($(bun --version))" -ForegroundColor Green
substep "bun already installed ($(bun --version))"
}
}
@ -939,7 +989,7 @@ if ($HasPython) {
if ($PyVer -match "(\d+)\.(\d+)") {
$PyMajor = [int]$Matches[1]; $PyMinor = [int]$Matches[2]
if ($PyMajor -eq 3 -and $PyMinor -ge 11 -and $PyMinor -lt 14) {
Write-Host "[OK] Python $PyVer" -ForegroundColor Green
substep "Python $PyVer"
$PythonOk = $true
} else {
Write-Host "[ERROR] Python $PyVer is outside supported range (need >= 3.11 and < 3.14)." -ForegroundColor Red
@ -979,12 +1029,12 @@ if ($LASTEXITCODE -eq 0 -and $ScriptsDir -and (Test-Path $ScriptsDir)) {
if (-not ($ProcessPathEntries | Where-Object { $_.TrimEnd('\') -eq $ScriptsDir })) {
$env:PATH = "$ScriptsDir;$env:PATH"
}
Write-Host " Persisted Python Scripts dir to user PATH: $ScriptsDir" -ForegroundColor Gray
substep "Persisted Python Scripts dir to user PATH: $ScriptsDir"
}
}
Write-Host ""
Write-Host "--- System prerequisites ready ---" -ForegroundColor Green
step "system" "prerequisites ready"
Write-Host ""
# ==========================================================================
@ -1019,12 +1069,12 @@ if ($IsPipInstall) {
$NeedFrontendBuild = $false
step "frontend" "up to date"
} else {
Write-Host "[INFO] Frontend source changed since last build -- rebuilding..." -ForegroundColor Yellow
substep "Frontend source changed since last build -- rebuilding..." "Yellow"
}
}
if ($NeedFrontendBuild -and -not $IsPipInstall) {
Write-Host ""
Write-Host "Building frontend..." -ForegroundColor Cyan
substep "building frontend..."
# ── Tailwind v4 .gitignore workaround ──
# Tailwind v4's oxide scanner respects .gitignore in parent directories.
@ -1041,7 +1091,7 @@ if ($NeedFrontendBuild -and -not $IsPipInstall) {
$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
substep "Temporarily hiding $gi (venv .gitignore blocks Tailwind scanner)"
}
}
$WalkDir = Split-Path $WalkDir -Parent
@ -1061,8 +1111,7 @@ if ($NeedFrontendBuild -and -not $IsPipInstall) {
# the cache + retry once before falling back to npm.
if ($UseBun) {
Write-Host " Using bun for package install (faster)" -ForegroundColor DarkGray
& bun install *> $null
$bunExit = $LASTEXITCODE
$bunExit = Invoke-SetupCommand { bun install }
# On Windows, .bin/ entries can be tsc, tsc.cmd, or tsc.ps1
$hasTsc = (Test-Path "node_modules\.bin\tsc") -or (Test-Path "node_modules\.bin\tsc.cmd")
$hasVite = (Test-Path "node_modules\.bin\vite") -or (Test-Path "node_modules\.bin\vite.cmd")
@ -1073,9 +1122,8 @@ if ($NeedFrontendBuild -and -not $IsPipInstall) {
if (Test-Path "node_modules") {
Remove-Item "node_modules" -Recurse -Force -ErrorAction SilentlyContinue
}
& bun pm cache rm *> $null
& bun install *> $null
$bunExit = $LASTEXITCODE
Invoke-SetupCommand { bun pm cache rm } | Out-Null
$bunExit = Invoke-SetupCommand { bun install }
$hasTsc = (Test-Path "node_modules\.bin\tsc") -or (Test-Path "node_modules\.bin\tsc.cmd")
$hasVite = (Test-Path "node_modules\.bin\vite") -or (Test-Path "node_modules\.bin\vite.cmd")
if ($bunExit -ne 0 -or -not $hasTsc -or -not $hasVite) {
@ -1086,7 +1134,7 @@ if ($NeedFrontendBuild -and -not $IsPipInstall) {
$UseBun = $false
}
} else {
Write-Host " [WARN] bun install failed (exit $bunExit), falling back to npm" -ForegroundColor Yellow
substep "bun install failed (exit $bunExit), falling back to npm" "Yellow"
if (Test-Path "node_modules") {
Remove-Item "node_modules" -Recurse -Force -ErrorAction SilentlyContinue
}
@ -1094,8 +1142,7 @@ if ($NeedFrontendBuild -and -not $IsPipInstall) {
}
}
if (-not $UseBun) {
& npm install *> $null
$npmExit = $LASTEXITCODE
$npmExit = Invoke-SetupCommand { npm install }
if ($npmExit -ne 0) {
Pop-Location
$ErrorActionPreference = $prevEAP_npm
@ -1107,8 +1154,7 @@ if ($NeedFrontendBuild -and -not $IsPipInstall) {
}
# Always use npm to run the build (Node runtime — avoids bun Windows runtime issues)
& npm run build *> $null
$buildExit = $LASTEXITCODE
$buildExit = Invoke-SetupCommand { npm run build }
if ($buildExit -ne 0) {
Pop-Location
$ErrorActionPreference = $prevEAP_npm
@ -1135,27 +1181,27 @@ if ($NeedFrontendBuild -and -not $IsPipInstall) {
}
if (Test-Path $OxcValidatorDir) {
Write-Host "Installing OXC validator runtime..." -ForegroundColor Cyan
substep "installing OXC validator runtime..."
$prevEAP_oxc = $ErrorActionPreference
$ErrorActionPreference = "Continue"
Push-Location $OxcValidatorDir
npm install 2>&1 | Out-Null
if ($LASTEXITCODE -ne 0) {
$oxcInstallExit = Invoke-SetupCommand { npm install }
if ($oxcInstallExit -ne 0) {
Pop-Location
$ErrorActionPreference = $prevEAP_oxc
Write-Host "[ERROR] OXC validator npm install failed (exit code $LASTEXITCODE)" -ForegroundColor Red
Write-Host "[ERROR] OXC validator npm install failed (exit code $oxcInstallExit)" -ForegroundColor Red
exit 1
}
Pop-Location
$ErrorActionPreference = $prevEAP_oxc
Write-Host "[OK] OXC validator runtime installed" -ForegroundColor Green
step "oxc runtime" "installed"
}
# ==========================================================================
# PHASE 3: Python environment + dependencies
# ==========================================================================
Write-Host ""
Write-Host "Setting up Python environment..." -ForegroundColor Cyan
substep "setting up Python environment..."
# Find Python -- skip Anaconda/Miniconda distributions.
# Conda-bundled CPython ships modified DLL search paths that break
@ -1215,7 +1261,7 @@ if (-not $PythonCmd) {
if (-not $cmdInfo.Source) { continue }
if ($cmdInfo.Source -like "*\WindowsApps\*") { continue }
if (Test-IsConda $cmdInfo.Source) {
Write-Host " [SKIP] $($cmdInfo.Source) (conda Python breaks torch DLL loading)" -ForegroundColor Yellow
substep "skipping $($cmdInfo.Source) (conda Python breaks torch DLL loading)" "Yellow"
continue
}
$ver = & $cmdInfo.Source --version 2>&1
@ -1239,7 +1285,7 @@ if (-not $PythonCmd) {
exit 1
}
Write-Host "[OK] Using $PythonCmd ($(& $PythonCmd --version 2>&1))" -ForegroundColor Green
substep "Using $PythonCmd ($(& $PythonCmd --version 2>&1))"
# The venv must already exist (created by install.ps1).
# This script (setup.ps1 / "unsloth studio update") only updates packages.
@ -1294,7 +1340,7 @@ if (Test-Path $VenvDir -PathType Container) {
if ($shouldRebuild) {
$reason = if ($installedTorchTag) { "torch $installedTorchTag != required $expectedTorchTag" } else { "torch could not be imported" }
Write-Host " [INFO] Stale venv detected ($reason) -- rebuilding..." -ForegroundColor Yellow
substep "Stale venv detected ($reason) -- rebuilding..." "Yellow"
try {
Remove-Item $VenvDir -Recurse -Force -ErrorAction Stop
} catch {
@ -1311,7 +1357,7 @@ if (-not (Test-Path $VenvDir)) {
Write-Host " irm https://unsloth.ai/install.ps1 | iex" -ForegroundColor Yellow
exit 1
} else {
Write-Host " Reusing existing virtual environment at $VenvDir" -ForegroundColor Green
substep "reusing existing virtual environment at $VenvDir"
}
# pip and python write to stderr even on success (progress bars, warnings).
@ -1329,9 +1375,9 @@ $UseUv = $false
if (Get-Command uv -ErrorAction SilentlyContinue) {
$UseUv = $true
} else {
Write-Host " Installing uv package manager..." -ForegroundColor Cyan
substep "installing uv package manager..."
try {
powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex" 2>&1 | Out-Null
Invoke-SetupCommand { powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex" } | Out-Null
Refresh-Environment
# Re-activate venv since Refresh-Environment rebuilds PATH from
# registry and drops the venv's Scripts directory
@ -1396,7 +1442,11 @@ if ($env:SKIP_STUDIO_BASE -ne "1" -and $env:STUDIO_LOCAL_INSTALL -ne "1") {
if (-not $SkipPythonDeps) {
Fast-Install --upgrade pip | Out-Null
if ($script:UnslothVerbose) {
Fast-Install --upgrade pip
} else {
Fast-Install --upgrade pip | Out-Null
}
# Pre-install PyTorch with CUDA support.
# On Windows, the default PyPI torch wheel is CPU-only.
@ -1411,7 +1461,7 @@ $TorchCacheDir = "C:\tc"
if (-not (Test-Path $TorchCacheDir)) { New-Item -ItemType Directory -Path $TorchCacheDir -Force | Out-Null }
$env:TORCHINDUCTOR_CACHE_DIR = $TorchCacheDir
[Environment]::SetEnvironmentVariable('TORCHINDUCTOR_CACHE_DIR', $TorchCacheDir, 'User')
Write-Host "[OK] TORCHINDUCTOR_CACHE_DIR set to $TorchCacheDir (avoids MAX_PATH issues)" -ForegroundColor Green
substep "TORCHINDUCTOR_CACHE_DIR set to $TorchCacheDir (avoids MAX_PATH issues)"
if ($HasNvidiaSmi) {
$CuTag = Get-PytorchCudaTag
@ -1420,36 +1470,57 @@ if ($HasNvidiaSmi) {
}
if ($CuTag -eq "cpu") {
Write-Host " Installing PyTorch (CPU-only)..." -ForegroundColor Cyan
$output = Fast-Install torch torchvision torchaudio --index-url "https://download.pytorch.org/whl/cpu" | Out-String
if ($LASTEXITCODE -ne 0) {
Write-Host "[FAILED] PyTorch install failed (exit code $LASTEXITCODE)" -ForegroundColor Red
substep "installing PyTorch (CPU-only)..."
if ($script:UnslothVerbose) {
Fast-Install torch torchvision torchaudio --index-url "https://download.pytorch.org/whl/cpu"
$torchInstallExit = $LASTEXITCODE
$output = ""
} else {
$output = Fast-Install torch torchvision torchaudio --index-url "https://download.pytorch.org/whl/cpu" | Out-String
$torchInstallExit = $LASTEXITCODE
}
if ($torchInstallExit -ne 0) {
Write-Host "[FAILED] PyTorch install failed (exit code $torchInstallExit)" -ForegroundColor Red
Write-Host $output -ForegroundColor Red
exit 1
}
} else {
Write-Host " Installing PyTorch with CUDA support ($CuTag)..." -ForegroundColor Cyan
Write-Host " (This download is ~2.8 GB -- may take a few minutes)" -ForegroundColor Gray
$output = Fast-Install torch torchvision torchaudio --index-url "https://download.pytorch.org/whl/$CuTag" | Out-String
if ($LASTEXITCODE -ne 0) {
Write-Host "[FAILED] PyTorch CUDA install failed (exit code $LASTEXITCODE)" -ForegroundColor Red
substep "installing PyTorch with CUDA support ($CuTag)..."
substep "(This download is ~2.8 GB -- may take a few minutes)"
if ($script:UnslothVerbose) {
Fast-Install torch torchvision torchaudio --index-url "https://download.pytorch.org/whl/$CuTag"
$torchInstallExit = $LASTEXITCODE
$output = ""
} else {
$output = Fast-Install torch torchvision torchaudio --index-url "https://download.pytorch.org/whl/$CuTag" | Out-String
$torchInstallExit = $LASTEXITCODE
}
if ($torchInstallExit -ne 0) {
Write-Host "[FAILED] PyTorch CUDA install failed (exit code $torchInstallExit)" -ForegroundColor Red
Write-Host $output -ForegroundColor Red
exit 1
}
# Install Triton for Windows (enables torch.compile -- without it training can hang)
Write-Host " Installing Triton for Windows..." -ForegroundColor Cyan
$output = Fast-Install "triton-windows<3.7" | Out-String
if ($LASTEXITCODE -ne 0) {
Write-Host "[WARN] Triton install failed -- torch.compile may not work" -ForegroundColor Yellow
substep "installing Triton for Windows..."
if ($script:UnslothVerbose) {
Fast-Install "triton-windows<3.7"
$tritonInstallExit = $LASTEXITCODE
$output = ""
} else {
$output = Fast-Install "triton-windows<3.7" | Out-String
$tritonInstallExit = $LASTEXITCODE
}
if ($tritonInstallExit -ne 0) {
substep "Triton install failed -- torch.compile may not work" "Yellow"
Write-Host $output -ForegroundColor Yellow
} else {
Write-Host "[OK] Triton for Windows installed (enables torch.compile)" -ForegroundColor Green
substep "Triton for Windows installed (enables torch.compile)"
}
}
# Ordered heavy dependency installation -- shared cross-platform script
Write-Host " Running ordered dependency installation..." -ForegroundColor Cyan
substep "running ordered dependency installation..."
python "$PSScriptRoot\install_python_stack.py"
# Restore ErrorActionPreference after pip/python work
$ErrorActionPreference = $prevEAP
@ -1459,15 +1530,22 @@ $ErrorActionPreference = $prevEAP
# at runtime (slow, ~10-15s), we pre-install into a separate directory.
# The training subprocess just prepends .venv_t5/ to sys.path -- instant switch.
Write-Host ""
Write-Host " Pre-installing transformers 5.x for newer model support..." -ForegroundColor Cyan
substep "pre-installing transformers 5.x for newer model support..."
$VenvT5Dir = Join-Path $env:USERPROFILE ".unsloth\studio\.venv_t5"
if (Test-Path $VenvT5Dir) { Remove-Item -Recurse -Force $VenvT5Dir }
New-Item -ItemType Directory -Path $VenvT5Dir -Force | Out-Null
$prevEAP_t5 = $ErrorActionPreference
$ErrorActionPreference = "Continue"
foreach ($pkg in @("transformers==5.3.0", "huggingface_hub==1.7.1", "hf_xet==1.4.2")) {
$output = Fast-Install --target $VenvT5Dir --no-deps $pkg | Out-String
if ($LASTEXITCODE -ne 0) {
if ($script:UnslothVerbose) {
Fast-Install --target $VenvT5Dir --no-deps $pkg
$t5PkgExit = $LASTEXITCODE
$output = ""
} else {
$output = Fast-Install --target $VenvT5Dir --no-deps $pkg | Out-String
$t5PkgExit = $LASTEXITCODE
}
if ($t5PkgExit -ne 0) {
Write-Host "[FAIL] Could not install $pkg into .venv_t5/" -ForegroundColor Red
Write-Host $output -ForegroundColor Red
$ErrorActionPreference = $prevEAP_t5
@ -1476,9 +1554,16 @@ foreach ($pkg in @("transformers==5.3.0", "huggingface_hub==1.7.1", "hf_xet==1.4
}
# tiktoken is needed by Qwen-family tokenizers -- install with deps since
# regex/requests may be missing on Windows
$output = Fast-Install --target $VenvT5Dir tiktoken | Out-String
if ($LASTEXITCODE -ne 0) {
Write-Host "[WARN] Could not install tiktoken into .venv_t5/ -- Qwen tokenizers may fail" -ForegroundColor Yellow
if ($script:UnslothVerbose) {
Fast-Install --target $VenvT5Dir tiktoken
$tiktokenInstallExit = $LASTEXITCODE
$output = ""
} else {
$output = Fast-Install --target $VenvT5Dir tiktoken | Out-String
$tiktokenInstallExit = $LASTEXITCODE
}
if ($tiktokenInstallExit -ne 0) {
substep "Could not install tiktoken into .venv_t5/ -- Qwen tokenizers may fail" "Yellow"
}
$ErrorActionPreference = $prevEAP_t5
step "transformers" "5.x pre-installed"
@ -1504,10 +1589,8 @@ $resolveExit = $LASTEXITCODE
$ResolvedLlamaTag = if ($resolveOutput) { ($resolveOutput | Select-Object -Last 1).ToString().Trim() } else { "" }
if ($resolveExit -ne 0 -or [string]::IsNullOrWhiteSpace($ResolvedLlamaTag)) {
Write-Host ""
Write-Host "[WARN] Failed to resolve an installable prebuilt llama.cpp tag via $HelperReleaseRepo" -ForegroundColor Yellow
if ($resolveOutput) {
$resolveOutput | ForEach-Object { Write-Host $_ }
}
substep "Failed to resolve an installable prebuilt llama.cpp tag via $HelperReleaseRepo" "Yellow"
Write-LlamaFailureLog -Output ($resolveOutput | Out-String)
# Resolve the llama.cpp tag for source-build fallback. Pass --published-repo
# so the resolver prefers Unsloth's tested tag (e.g. b8508) over the upstream
# bleeding-edge tag (e.g. b8514) from ggml-org/llama.cpp.
@ -1537,20 +1620,20 @@ if ($resolveExit -ne 0 -or [string]::IsNullOrWhiteSpace($ResolvedLlamaTag)) {
}
Write-Host ""
Write-Host "Resolved llama.cpp release tag: $ResolvedLlamaTag" -ForegroundColor Gray
substep "Resolved llama.cpp release tag: $ResolvedLlamaTag"
if ($env:UNSLOTH_LLAMA_FORCE_COMPILE -eq "1") {
Write-Host ""
Write-Host "[WARN] UNSLOTH_LLAMA_FORCE_COMPILE=1 -- skipping prebuilt llama.cpp install" -ForegroundColor Yellow
substep "UNSLOTH_LLAMA_FORCE_COMPILE=1 -- skipping prebuilt llama.cpp install" "Yellow"
$NeedLlamaSourceBuild = $true
} else {
Write-Host ""
Write-Host "Installing prebuilt llama.cpp bundle (preferred path)..." -ForegroundColor Cyan
substep "installing prebuilt llama.cpp bundle (preferred path)..."
if (Test-Path $LlamaCppDir) {
Write-Host "Existing llama.cpp install detected -- validating staged prebuilt update before replacement" -ForegroundColor Gray
substep "Existing llama.cpp install detected -- validating staged prebuilt update before replacement"
}
if ($SkipPrebuiltInstall) {
Write-Host "[WARN] Skipping prebuilt install because prebuilt tag resolution failed -- falling back to source build" -ForegroundColor Yellow
substep "Skipping prebuilt install because prebuilt tag resolution failed -- falling back to source build" "Yellow"
} else {
$prebuiltArgs = @(
"$PSScriptRoot\install_llama_prebuilt.py",
@ -1563,17 +1646,28 @@ if ($env:UNSLOTH_LLAMA_FORCE_COMPILE -eq "1") {
}
$prevEAPPrebuilt = $ErrorActionPreference
$ErrorActionPreference = "Continue"
& python @prebuiltArgs
$prebuiltExit = $LASTEXITCODE
if ($script:UnslothVerbose) {
# Show live output in verbose mode while still capturing for error log
$prebuiltLog = Join-Path $env:TEMP "unsloth-prebuilt-$PID.log"
& python @prebuiltArgs 2>&1 | Tee-Object -FilePath $prebuiltLog | Out-Host
$prebuiltExit = $LASTEXITCODE
$prebuiltOutput = if (Test-Path $prebuiltLog) { Get-Content $prebuiltLog -Raw } else { "" }
Remove-Item $prebuiltLog -ErrorAction SilentlyContinue
} else {
$prebuiltOutput = & python @prebuiltArgs 2>&1 | Out-String
$prebuiltExit = $LASTEXITCODE
}
$ErrorActionPreference = $prevEAPPrebuilt
if ($prebuiltExit -eq 0) {
step "llama.cpp" "prebuilt installed and validated"
} else {
step "llama.cpp" "prebuilt install failed (continuing)" "Yellow"
Write-LlamaFailureLog -Output $prebuiltOutput
if (Test-Path $LlamaCppDir) {
Write-Host "[WARN] Prebuilt update failed; existing install was restored or cleaned before source build fallback" -ForegroundColor Yellow
substep "Prebuilt update failed; existing install was restored or cleaned before source build fallback" "Yellow"
}
Write-Host "[WARN] Prebuilt llama.cpp path unavailable or failed validation -- falling back to source build" -ForegroundColor Yellow
substep "Prebuilt llama.cpp path unavailable or failed validation -- falling back to source build" "Yellow"
$NeedLlamaSourceBuild = $true
}
}
@ -1603,10 +1697,10 @@ if ($NeedLlamaSourceBuild) {
if ($OpenSslRoot) {
$OpenSslAvailable = $true
Write-Host "[OK] OpenSSL dev found at $OpenSslRoot" -ForegroundColor Green
substep "OpenSSL dev found at $OpenSslRoot"
} else {
Write-Host ""
Write-Host "Installing OpenSSL dev (for HTTPS in llama-server)..." -ForegroundColor Cyan
Write-Host ""
substep "installing OpenSSL dev (for HTTPS in llama-server)..."
$HasWinget = $null -ne (Get-Command winget -ErrorAction SilentlyContinue)
if ($HasWinget) {
winget install -e --id ShiningLight.OpenSSL.Dev --accept-package-agreements --accept-source-agreements
@ -1615,17 +1709,17 @@ if ($NeedLlamaSourceBuild) {
if (Test-Path (Join-Path $root 'include\openssl\ssl.h')) {
$OpenSslRoot = $root
$OpenSslAvailable = $true
Write-Host "[OK] OpenSSL dev installed at $OpenSslRoot" -ForegroundColor Green
substep "OpenSSL dev installed at $OpenSslRoot"
break
}
}
}
if (-not $OpenSslAvailable) {
Write-Host "[WARN] OpenSSL dev not available -- llama-server will be built without HTTPS" -ForegroundColor Yellow
substep "OpenSSL dev not available -- llama-server will be built without HTTPS" "Yellow"
}
}
} else {
Write-Host "[SKIP] OpenSSL dev install -- prebuilt llama.cpp already validated" -ForegroundColor Yellow
substep "OpenSSL dev install skipped -- prebuilt llama.cpp already validated" "Yellow"
}
# ==========================================================================
@ -1671,21 +1765,22 @@ if (-not $NeedLlamaSourceBuild) {
Write-Host ""
if (-not $HasNvidiaSmi) {
# CPU-only machines depend entirely on llama-server for GGUF chat -- cmake is required
Write-Host "[ERROR] CMake is required to build llama-server for GGUF chat mode." -ForegroundColor Red
Write-Host " Install CMake from https://cmake.org/download/ and re-run setup." -ForegroundColor Yellow
exit 1
substep "CMake is required to build llama-server for GGUF chat mode." "Yellow"
substep "Continuing setup without llama.cpp build." "Yellow"
substep "Install CMake from https://cmake.org/download/ and re-run setup." "Yellow"
}
Write-Host "[SKIP] llama-server build -- cmake not available" -ForegroundColor Yellow
Write-Host " GGUF inference and export will not be available." -ForegroundColor Yellow
Write-Host " Install CMake from https://cmake.org/download/ and re-run setup." -ForegroundColor Yellow
step "llama.cpp" "build skipped (cmake not available)" "Yellow"
substep "GGUF inference and export will not be available." "Yellow"
substep "Install CMake from https://cmake.org/download/ and re-run setup." "Yellow"
$script:LlamaCppDegraded = $true
} else {
Write-Host ""
if ($HasNvidiaSmi) {
Write-Host "Building llama.cpp with CUDA support..." -ForegroundColor Cyan
substep "building llama.cpp with CUDA support..."
} else {
Write-Host "Building llama.cpp (CPU-only, no NVIDIA GPU detected)..." -ForegroundColor Cyan
substep "building llama.cpp (CPU-only, no NVIDIA GPU detected)..."
}
Write-Host " This typically takes 5-10 minutes on first build." -ForegroundColor Gray
substep "This typically takes 5-10 minutes on first build."
Write-Host ""
# Start total build timer
@ -1725,19 +1820,19 @@ if (-not $NeedLlamaSourceBuild) {
if (Test-Path (Join-Path $LlamaCppDir ".git")) {
Write-Host " Syncing llama.cpp to $ResolvedLlamaTag..." -ForegroundColor Gray
if ($UseConcreteRef) {
git -C $LlamaCppDir fetch --depth 1 origin $ResolvedLlamaTag 2>&1 | Out-Null
$gitFetchExit = Invoke-SetupCommand -AlwaysQuiet { git -C $LlamaCppDir fetch --depth 1 origin $ResolvedLlamaTag }
} else {
git -C $LlamaCppDir fetch --depth 1 origin 2>&1 | Out-Null
$gitFetchExit = Invoke-SetupCommand -AlwaysQuiet { git -C $LlamaCppDir fetch --depth 1 origin }
}
if ($LASTEXITCODE -ne 0) {
Write-Host " [WARN] git fetch failed -- using existing source" -ForegroundColor Yellow
if ($gitFetchExit -ne 0) {
substep "git fetch failed -- using existing source" "Yellow"
} else {
git -C $LlamaCppDir checkout -B unsloth-llama-build FETCH_HEAD 2>&1 | Out-Null
if ($LASTEXITCODE -ne 0) {
$gitCheckoutExit = Invoke-SetupCommand -AlwaysQuiet { git -C $LlamaCppDir checkout -B unsloth-llama-build FETCH_HEAD }
if ($gitCheckoutExit -ne 0) {
$BuildOk = $false
$FailedStep = "git checkout"
} else {
git -C $LlamaCppDir clean -fdx 2>&1 | Out-Null
Invoke-SetupCommand -AlwaysQuiet { git -C $LlamaCppDir clean -fdx } | Out-Null
}
}
} else {
@ -1749,8 +1844,8 @@ if (-not $NeedLlamaSourceBuild) {
$cloneArgs += @("--branch", $ResolvedLlamaTag)
}
$cloneArgs += @("https://github.com/ggml-org/llama.cpp.git", $buildTmp)
git @cloneArgs 2>&1 | Out-Null
if ($LASTEXITCODE -ne 0) {
$cloneExit = Invoke-SetupCommand -AlwaysQuiet { git @cloneArgs }
if ($cloneExit -ne 0) {
$BuildOk = $false
$FailedStep = "git clone"
if (Test-Path $buildTmp) { Remove-Item -Recurse -Force $buildTmp }
@ -1808,8 +1903,8 @@ if (-not $NeedLlamaSourceBuild) {
$maxArch = Get-NvccMaxArch -NvccExe $NvccPath
if ($maxArch) {
$CmakeArgs += "-DCMAKE_CUDA_ARCHITECTURES=$maxArch"
Write-Host " [WARN] GPU is sm_$CudaArch but nvcc only supports up to sm_$maxArch" -ForegroundColor Yellow
Write-Host " Building with sm_$maxArch (PTX will JIT for your GPU at runtime)" -ForegroundColor Yellow
substep "GPU is sm_$CudaArch but nvcc only supports up to sm_$maxArch" "Yellow"
substep "Building with sm_$maxArch (PTX will JIT for your GPU at runtime)" "Yellow"
}
# else: omit flag entirely, let cmake pick defaults
}
@ -1819,10 +1914,11 @@ if (-not $NeedLlamaSourceBuild) {
}
$cmakeOutput = cmake @CmakeArgs 2>&1 | Out-String
if ($LASTEXITCODE -ne 0) {
$cmakeConfigureExit = $LASTEXITCODE
if ($cmakeConfigureExit -ne 0) {
$BuildOk = $false
$FailedStep = "cmake configure"
Write-Host $cmakeOutput -ForegroundColor Red
Write-LlamaFailureLog -Output $cmakeOutput
if ($cmakeOutput -match 'No CUDA toolset found|CUDA_TOOLKIT_ROOT_DIR|nvcc') {
Write-Host ""
Write-Host " Hint: CUDA VS integration may be missing. Try running as admin:" -ForegroundColor Yellow
@ -1845,10 +1941,11 @@ if (-not $NeedLlamaSourceBuild) {
Write-Host ""
$output = cmake --build $BuildDir --config Release --target llama-server -j $NumCpu 2>&1 | Out-String
if ($LASTEXITCODE -ne 0) {
$cmakeBuildServerExit = $LASTEXITCODE
if ($cmakeBuildServerExit -ne 0) {
$BuildOk = $false
$FailedStep = "cmake build (llama-server)"
Write-Host $output -ForegroundColor Red
Write-LlamaFailureLog -Output $output
}
}
@ -1857,9 +1954,10 @@ if (-not $NeedLlamaSourceBuild) {
Write-Host ""
Write-Host "--- cmake build (llama-quantize) ---" -ForegroundColor Cyan
$output = cmake --build $BuildDir --config Release --target llama-quantize -j $NumCpu 2>&1 | Out-String
if ($LASTEXITCODE -ne 0) {
Write-Host " [WARN] llama-quantize build failed (GGUF export may be unavailable)" -ForegroundColor Yellow
Write-Host $output -ForegroundColor Yellow
$cmakeBuildQuantizeExit = $LASTEXITCODE
if ($cmakeBuildQuantizeExit -ne 0) {
substep "llama-quantize build failed (GGUF export may be unavailable)" "Yellow"
Write-LlamaFailureLog -Output $output
}
}
@ -1900,9 +1998,9 @@ if (-not $NeedLlamaSourceBuild) {
step "llama.cpp" "built"
step "build time" "${totalMin}m ${totalSec}s" "DarkGray"
} else {
step "llama.cpp" "build failed at: $FailedStep (${totalMin}m ${totalSec}s)" "Red"
step "llama.cpp" "build failed at: $FailedStep (${totalMin}m ${totalSec}s); continuing" "Yellow"
substep "To retry: delete $LlamaCppDir and re-run setup." "Yellow"
exit 1
$script:LlamaCppDegraded = $true
}
}
}
@ -1913,12 +2011,28 @@ if (-not $NeedLlamaSourceBuild) {
$DoneLabel = if ($env:SKIP_STUDIO_BASE -eq "1") { "Unsloth Studio Setup Complete" } else { "Unsloth Studio Updated" }
if ($script:StudioVtOk -and -not $env:NO_COLOR) {
Write-Host (" {0}{1}{2}" -f (Get-StudioAnsi Dim), $Rule, (Get-StudioAnsi Reset))
Write-Host (" " + (Get-StudioAnsi Title) + $DoneLabel + (Get-StudioAnsi Reset))
if ($script:LlamaCppDegraded) {
Write-Host (" " + (Get-StudioAnsi Warn) + "$DoneLabel (limited: llama.cpp unavailable)" + (Get-StudioAnsi Reset))
} else {
Write-Host (" " + (Get-StudioAnsi Title) + $DoneLabel + (Get-StudioAnsi Reset))
}
Write-Host (" {0}{1}{2}" -f (Get-StudioAnsi Dim), $Rule, (Get-StudioAnsi Reset))
} else {
Write-Host " $Rule" -ForegroundColor DarkGray
Write-Host " $DoneLabel" -ForegroundColor Green
if ($script:LlamaCppDegraded) {
Write-Host " $DoneLabel (limited: llama.cpp unavailable)" -ForegroundColor Yellow
} else {
Write-Host " $DoneLabel" -ForegroundColor Green
}
Write-Host " $Rule" -ForegroundColor DarkGray
}
step "launch" "unsloth studio -H 0.0.0.0 -p 8888"
Write-Host ""
# Match studio/setup.sh: exit non-zero for degraded llama.cpp when called
# from install.ps1 (SKIP_STUDIO_BASE=1) so the installer can detect the
# failure. Direct 'unsloth studio update' does not set SKIP_STUDIO_BASE,
# so it keeps degraded installs successful.
if ($script:LlamaCppDegraded -and $env:SKIP_STUDIO_BASE -eq "1") {
exit 1
}

View file

@ -28,12 +28,43 @@ fi
step() { printf " ${C_DIM}%-15.15s${C_RST}${3:-$C_OK}%s${C_RST}\n" "$1" "$2"; }
substep() { printf " ${C_DIM}%-15s%s${C_RST}\n" "" "$1"; }
_is_verbose() {
[ "${UNSLOTH_VERBOSE:-0}" = "1" ]
}
verbose_substep() {
if _is_verbose; then
substep "$1"
fi
return 0
}
run_maybe_quiet() {
if _is_verbose; then
"$@"
else
"$@" > /dev/null 2>&1
fi
}
# ── Helper: run command quietly, show output only on failure ──
_run_quiet() {
local on_fail=$1
local label=$2
shift 2
if _is_verbose; then
local exit_code
"$@" && return 0
exit_code=$?
step "error" "$label failed (exit code $exit_code)" "$C_ERR" >&2
if [ "$on_fail" = "exit" ]; then
exit "$exit_code"
else
return "$exit_code"
fi
fi
local tmplog
tmplog=$(mktemp) || {
step "error" "Failed to create temporary file" "$C_ERR" >&2
@ -65,11 +96,18 @@ run_quiet_no_exit() {
_run_quiet return "$@"
}
print_llama_error_log() {
local log_file=$1
[ -s "$log_file" ] || return 0
substep "llama.cpp diagnostics (last 120 lines):"
tail -n 120 "$log_file" | sed 's/^/ | /' >&2
}
# ── Banner ──
echo ""
printf " ${C_TITLE}%s${C_RST}\n" "🦥 Unsloth Studio Setup"
printf " ${C_DIM}%s${C_RST}\n" "$RULE"
verbose_substep "verbose diagnostics enabled"
# ── Clean up stale caches ──
rm -rf "$REPO_ROOT/unsloth_compiled_cache"
rm -rf "$SCRIPT_DIR/backend/unsloth_compiled_cache"
@ -97,6 +135,7 @@ fi
if [ "$_NEED_FRONTEND_BUILD" = false ]; then
step "frontend" "up to date"
verbose_substep "frontend dist is newer than source inputs"
else
# ── Node ──
@ -117,7 +156,7 @@ if command -v node &>/dev/null && command -v npm &>/dev/null; then
# In Colab, just upgrade npm directly - nvm doesn't work well
if [ "$NPM_MAJOR" -lt 11 ]; then
substep "upgrading npm..."
npm install -g npm@latest > /dev/null 2>&1
run_maybe_quiet npm install -g npm@latest
fi
NEED_NODE=false
fi
@ -127,7 +166,11 @@ fi
if [ "$NEED_NODE" = true ]; then
substep "installing nvm..."
export NODE_OPTIONS=--dns-result-order=ipv4first
curl -so- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash > /dev/null 2>&1
if _is_verbose; then
curl -so- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash
else
curl -so- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash > /dev/null 2>&1
fi
export NVM_DIR="$HOME/.nvm"
set +u
@ -141,7 +184,11 @@ if [ "$NEED_NODE" = true ]; then
substep "installing Node LTS..."
run_quiet "nvm install" nvm install --lts
nvm use --lts > /dev/null 2>&1
if _is_verbose; then
nvm use --lts
else
nvm use --lts > /dev/null 2>&1
fi
set -u
NODE_MAJOR=$(node -v | sed 's/v//' | cut -d. -f1)
@ -158,13 +205,14 @@ if [ "$NEED_NODE" = true ]; then
fi
step "node" "$(node -v) | npm $(npm -v)"
verbose_substep "node check: NEED_NODE=$NEED_NODE NODE_OK=${NODE_OK:-unknown} NPM_MAJOR=${NPM_MAJOR:-unknown}"
# ── Install bun (optional, faster package installs) ──
# Uses npm to install bun globally -- Node is already guaranteed above,
# avoids platform-specific installers, PATH issues, and admin requirements.
if ! command -v bun &>/dev/null; then
substep "installing bun..."
if npm install -g bun > /dev/null 2>&1 && command -v bun &>/dev/null; then
if run_maybe_quiet npm install -g bun && command -v bun &>/dev/null; then
substep "bun installed ($(bun --version))"
else
substep "bun install skipped (npm will be used instead)"
@ -228,21 +276,25 @@ _try_bun_install() {
_bun_install_ok=false
if command -v bun &>/dev/null; then
echo " Using bun for package install (faster)"
substep "using bun for package install (faster)"
if _try_bun_install; then
_bun_install_ok=true
else
# First attempt failed, likely due to corrupt cache entries.
# Clear the cache and retry once.
echo " Clearing bun cache and retrying..."
bun pm cache rm > /dev/null 2>&1 || true
run_maybe_quiet bun pm cache rm || true
if _try_bun_install; then
_bun_install_ok=true
fi
fi
fi
if [ "$_bun_install_ok" = false ]; then
run_quiet "npm install" npm install
run_quiet_no_exit "npm install" npm install --no-fund --no-audit --loglevel=error
_npm_install_rc=$?
if [ "$_npm_install_rc" -ne 0 ]; then
exit "$_npm_install_rc"
fi
fi
run_quiet "npm run build" npm run build
@ -265,7 +317,11 @@ fi # end frontend build check
# ── oxc-validator runtime ──
if [ -d "$SCRIPT_DIR/backend/core/data_recipe/oxc-validator" ] && command -v npm &>/dev/null; then
cd "$SCRIPT_DIR/backend/core/data_recipe/oxc-validator"
run_quiet "npm install (oxc validator runtime)" npm install
run_quiet_no_exit "npm install (oxc validator runtime)" npm install --no-fund --no-audit --loglevel=error
_oxc_install_rc=$?
if [ "$_oxc_install_rc" -ne 0 ]; then
exit "$_oxc_install_rc"
fi
cd "$SCRIPT_DIR"
fi
@ -287,9 +343,19 @@ if [ ! -x "$VENV_DIR/bin/python" ]; then
# packages (huggingface-hub, datasets, transformers) and only pulls
# in genuinely missing ones (structlog, fastapi, etc.).
substep "Colab detected, installing Studio backend dependencies..."
_COLAB_REQS_TMP="$(mktemp)"
sed 's/[><=!~;].*//' "$SCRIPT_DIR/backend/requirements/studio.txt" \
| grep -v '^#' | grep -v '^$' \
| pip install -q -r /dev/stdin 2>/dev/null || true
| grep -v '^#' | grep -v '^$' > "$_COLAB_REQS_TMP"
if [ -s "$_COLAB_REQS_TMP" ]; then
if ! run_quiet_no_exit "install Colab backend deps" pip install -q -r "$_COLAB_REQS_TMP"; then
rm -f "$_COLAB_REQS_TMP"
step "python" "Colab backend dependency install failed" "$C_ERR"
exit 1
fi
else
step "python" "no Colab backend dependencies resolved from requirements file" "$C_WARN"
fi
rm -f "$_COLAB_REQS_TMP"
_COLAB_NO_VENV=true
else
step "python" "venv not found at $VENV_DIR" "$C_ERR"
@ -308,7 +374,13 @@ install_python_stack() {
USE_UV=false
if command -v uv &>/dev/null; then
USE_UV=true
elif curl -LsSf https://astral.sh/uv/install.sh | sh > /dev/null 2>&1; then
elif {
if _is_verbose; then
curl -LsSf https://astral.sh/uv/install.sh | sh
else
curl -LsSf https://astral.sh/uv/install.sh | sh > /dev/null 2>&1
fi
}; then
export PATH="$HOME/.local/bin:$PATH"
command -v uv &>/dev/null && USE_UV=true
fi
@ -325,7 +397,8 @@ cd "$SCRIPT_DIR"
# On Colab without a venv, skip venv-dependent Python deps sections but
# continue to llama.cpp install so GGUF inference is available.
if [ "$_COLAB_NO_VENV" = true ]; then
echo "✅ Studio backend dependencies installed into system Python"
step "python" "backend deps installed into system Python"
substep "continuing to llama.cpp install for GGUF inference support"
fi
# ── Check if Python deps need updating ──
@ -375,6 +448,7 @@ if [ "$_SKIP_PYTHON_DEPS" = false ]; then
step "transformers" "5.x pre-installed"
else
step "python" "dependencies up to date"
verbose_substep "python deps check: installed=$_PKG_NAME@${INSTALLED_VER:-unknown} latest=${LATEST_VER:-unknown}"
fi
# ── 7. Prefer prebuilt llama.cpp bundles before any source build path ──
@ -383,6 +457,7 @@ mkdir -p "$UNSLOTH_HOME"
LLAMA_CPP_DIR="$UNSLOTH_HOME/llama.cpp"
LLAMA_SERVER_BIN="$LLAMA_CPP_DIR/build/bin/llama-server"
_NEED_LLAMA_SOURCE_BUILD=false
_LLAMA_CPP_DEGRADED=false
_LLAMA_FORCE_COMPILE="${UNSLOTH_LLAMA_FORCE_COMPILE:-0}"
_REQUESTED_LLAMA_TAG="${UNSLOTH_LLAMA_TAG:-latest}"
_HELPER_RELEASE_REPO="${UNSLOTH_LLAMA_RELEASE_REPO:-unslothai/llama.cpp}"
@ -400,7 +475,7 @@ else
fi
if [ -z "$_RESOLVED_LLAMA_TAG" ]; then
step "llama.cpp" "failed to resolve prebuilt tag via $_HELPER_RELEASE_REPO" "$C_WARN"
cat "$_RESOLVE_LLAMA_LOG" >&2 || true
print_llama_error_log "$_RESOLVE_LLAMA_LOG"
set +e
# Resolve the llama.cpp tag for source-build fallback. Pass --published-repo
# so the resolver prefers Unsloth's tested tag (e.g. b8508) over the upstream
@ -426,6 +501,7 @@ fi
rm -f "$_RESOLVE_LLAMA_LOG"
substep "resolved llama.cpp tag: $_RESOLVED_LLAMA_TAG"
verbose_substep "requested llama.cpp tag: $_REQUESTED_LLAMA_TAG (repo: $_HELPER_RELEASE_REPO)"
if [ "$_LLAMA_FORCE_COMPILE" = "1" ]; then
step "llama.cpp" "UNSLOTH_LLAMA_FORCE_COMPILE=1 -- skipping prebuilt" "$C_WARN"
@ -447,14 +523,25 @@ else
if [ -n "${UNSLOTH_LLAMA_RELEASE_TAG:-}" ]; then
_PREBUILT_CMD+=(--published-release-tag "$UNSLOTH_LLAMA_RELEASE_TAG")
fi
_PREBUILT_LOG="$(mktemp)"
set +e
"${_PREBUILT_CMD[@]}"
_PREBUILT_STATUS=$?
if _is_verbose; then
"${_PREBUILT_CMD[@]}" 2>&1 | tee "$_PREBUILT_LOG"
_PREBUILT_STATUS=${PIPESTATUS[0]}
else
"${_PREBUILT_CMD[@]}" >"$_PREBUILT_LOG" 2>&1
_PREBUILT_STATUS=$?
fi
set -e
if [ "$_PREBUILT_STATUS" -eq 0 ]; then
step "llama.cpp" "prebuilt installed and validated"
verbose_substep "llama.cpp install dir: $LLAMA_CPP_DIR"
rm -f "$_PREBUILT_LOG"
else
step "llama.cpp" "prebuilt install failed (continuing)" "$C_WARN"
print_llama_error_log "$_PREBUILT_LOG"
rm -f "$_PREBUILT_LOG"
if [ -d "$LLAMA_CPP_DIR" ]; then
substep "prebuilt update failed; existing install restored"
fi
@ -523,12 +610,15 @@ if [ "$_NEED_LLAMA_SOURCE_BUILD" = false ]; then
:
elif [ "${_SKIP_GGUF_BUILD:-}" = true ]; then
step "llama.cpp" "skipped (missing build deps)" "$C_WARN"
[ -f "$LLAMA_SERVER_BIN" ] || _LLAMA_CPP_DEGRADED=true
else
{
if ! command -v cmake &>/dev/null; then
step "llama.cpp" "skipped (cmake not found)" "$C_WARN"
[ -f "$LLAMA_SERVER_BIN" ] || _LLAMA_CPP_DEGRADED=true
elif ! command -v git &>/dev/null; then
step "llama.cpp" "skipped (git not found)" "$C_WARN"
[ -f "$LLAMA_SERVER_BIN" ] || _LLAMA_CPP_DEGRADED=true
else
BUILD_OK=true
_CLONE_BRANCH_ARGS=()
@ -691,8 +781,10 @@ else
[ -f "$LLAMA_CPP_DIR/llama-quantize" ] && step "llama-quantize" "built"
elif [ "$BUILD_OK" = true ]; then
step "llama.cpp" "binary not found after build" "$C_WARN"
_LLAMA_CPP_DEGRADED=true
else
step "llama.cpp" "build failed" "$C_ERR"
[ -f "$LLAMA_SERVER_BIN" ] || _LLAMA_CPP_DEGRADED=true
fi
fi
}
@ -702,14 +794,35 @@ fi # end _SKIP_GGUF_BUILD check
if [ "$IS_COLAB" = true ]; then
echo ""
printf " ${C_DIM}%s${C_RST}\n" "$RULE"
printf " ${C_TITLE}%s${C_RST}\n" "Unsloth Studio Setup Complete"
if [ "$_LLAMA_CPP_DEGRADED" = true ]; then
printf " ${C_WARN}%s${C_RST}\n" "Unsloth Studio Setup Complete (limited: llama.cpp unavailable)"
else
printf " ${C_TITLE}%s${C_RST}\n" "Unsloth Studio Setup Complete"
fi
printf " ${C_DIM}%s${C_RST}\n" "$RULE"
substep "from colab import start"
substep "start()"
else
printf " ${C_DIM}%s${C_RST}\n" "$RULE"
printf " ${C_TITLE}%s${C_RST}\n" "Unsloth Studio Installed"
if [ "$_LLAMA_CPP_DEGRADED" = true ]; then
printf " ${C_WARN}%s${C_RST}\n" "Unsloth Studio Installed (limited: llama.cpp unavailable)"
else
printf " ${C_TITLE}%s${C_RST}\n" "Unsloth Studio Installed"
fi
printf " ${C_DIM}%s${C_RST}\n" "$RULE"
printf " ${C_DIM}%-15s${C_OK}%s${C_RST}\n" "launch" "unsloth studio -H 0.0.0.0 -p 8888"
if [ "$_LLAMA_CPP_DEGRADED" = true ]; then
printf " ${C_DIM}%-15s${C_WARN}%s${C_RST}\n" "launch" "unsloth studio -H 0.0.0.0 -p 8888"
else
printf " ${C_DIM}%-15s${C_OK}%s${C_RST}\n" "launch" "unsloth studio -H 0.0.0.0 -p 8888"
fi
fi
echo ""
# When called from install.sh (SKIP_STUDIO_BASE=1), exit non-zero so the
# installer can report the GGUF failure after finishing PATH/shortcut setup.
# When called directly via 'unsloth studio update', keep the install
# successful -- the footer above already reports the limitation and Studio
# is still usable for non-GGUF workflows.
if [ "$_LLAMA_CPP_DEGRADED" = true ] && [ "${SKIP_STUDIO_BASE:-0}" = "1" ]; then
exit 1
fi

View file

@ -267,10 +267,7 @@ def setup(
help = "Full pip/build output during setup for troubleshooting.",
),
):
"""Deprecated: use 'unsloth studio update' or re-run install.sh."""
typer.echo(
"Note: 'unsloth studio setup' is deprecated. Use 'unsloth studio update' or re-run install.sh."
)
"""Run Studio setup (called by install.ps1 / install.sh)."""
_run_setup_script(verbose = verbose)