mirror of
https://github.com/unslothai/unsloth
synced 2026-04-21 13:37:39 +00:00
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:
parent
5bbfabb151
commit
5557e1fd27
7 changed files with 824 additions and 340 deletions
355
install.ps1
355
install.ps1
|
|
@ -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 ""
|
||||
}
|
||||
}
|
||||
|
|
|
|||
199
install.sh
199
install.sh
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
),
|
||||
"",
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
422
studio/setup.ps1
422
studio/setup.ps1
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
151
studio/setup.sh
151
studio/setup.sh
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue