Compare commits
1 commit
main
...
cleanup/pr
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
95b50ec57a |
15 changed files with 246 additions and 405 deletions
116
install.ps1
116
install.ps1
|
|
@ -144,9 +144,8 @@ function Install-UnslothStudio {
|
|||
|
||||
# UNSLOTH_PYTHON pins the version (mirrors install.sh --python); default 3.13.
|
||||
$PythonVersion = if ($env:UNSLOTH_PYTHON) { $env:UNSLOTH_PYTHON } else { "3.13" }
|
||||
# python.org fallback patch, used only when winget is unavailable/broken AND
|
||||
# the live python.org listing can't be fetched. The installer URL scheme is
|
||||
# stable so an older patch still installs. Bump alongside $PythonVersion.
|
||||
# python.org fallback patch for when winget fails AND the live listing is
|
||||
# unreachable; the URL scheme is stable. Bump with $PythonVersion.
|
||||
$PythonFallbackFullVersion = "3.13.13"
|
||||
|
||||
# Resolve install destinations. Priority: UNSLOTH_STUDIO_HOME, then
|
||||
|
|
@ -881,13 +880,10 @@ shell.Run cmd, 0, False
|
|||
}
|
||||
if ($createdShortcutCount -gt 0) {
|
||||
substep "Created Unsloth Studio shortcut"
|
||||
# Force Explorer to re-read each new shortcut's icon so it renders
|
||||
# immediately instead of a stale/generic entry (a same-name .lnk
|
||||
# recreated across reinstalls keeps Explorer's cached per-item icon).
|
||||
# The reliable, non-disruptive fix (no explorer restart) is a per-item
|
||||
# SHChangeNotify SHCNE_UPDATEITEM + SHCNF_PATHW per .lnk; the global
|
||||
# SHCNE_ASSOCCHANGED broadcast alone does NOT recover a stale item.
|
||||
# Also clear the on-disk icon cache (covers heavier staleness).
|
||||
# A same-name .lnk recreated across reinstalls keeps Explorer's stale
|
||||
# per-item icon; per-item SHChangeNotify(SHCNE_UPDATEITEM, SHCNF_PATHW)
|
||||
# fixes it (global SHCNE_ASSOCCHANGED alone does not). Also clear the
|
||||
# on-disk icon cache.
|
||||
try { & "$env:SystemRoot\System32\ie4uinit.exe" -ClearIconCache 2>$null } catch {}
|
||||
try { & "$env:SystemRoot\System32\ie4uinit.exe" -show 2>$null } catch {}
|
||||
try {
|
||||
|
|
@ -896,15 +892,12 @@ shell.Run cmd, 0, False
|
|||
foreach ($scPath in $createdShortcutPaths) {
|
||||
try { [UnslothShell.IconRefresh]::SHChangeNotify(0x00002000, 0x0005, $scPath, [System.IntPtr]::Zero) } catch {}
|
||||
}
|
||||
# SHCNE_ASSOCCHANGED (0x08000000) global refresh (belt-and-suspenders)
|
||||
# SHCNE_ASSOCCHANGED (0x08000000) global refresh as backup
|
||||
[UnslothShell.IconRefresh]::SHChangeNotify(0x08000000, 0, $null, [System.IntPtr]::Zero)
|
||||
} catch {}
|
||||
# Win11's Start Menu (StartMenuExperienceHost) keeps its OWN
|
||||
# pre-rendered tile-icon cache that ie4uinit/explorer restart do NOT
|
||||
# invalidate, so a rewritten same-name shortcut shows the old tile
|
||||
# until the host restarts. Drop only the render caches (NEVER
|
||||
# start2.bin -- the pinned layout) and let the host rebuild.
|
||||
# Best-effort; Win10 has no such host (Test-Path skips it).
|
||||
# Win11 StartMenuExperienceHost keeps its own tile-icon cache that
|
||||
# ie4uinit cannot invalidate: drop the render caches (NEVER start2.bin,
|
||||
# the pinned layout) and restart the host. Win10: Test-Path skips it.
|
||||
try {
|
||||
$smehTemp = Join-Path $env:LOCALAPPDATA "Packages\Microsoft.Windows.StartMenuExperienceHost_cw5n1h2txyewy\TempState"
|
||||
if (Test-Path -LiteralPath $smehTemp) {
|
||||
|
|
@ -1030,13 +1023,10 @@ shell.Run cmd, 0, False
|
|||
}
|
||||
|
||||
# ── Fallback: install CPython directly from python.org ──
|
||||
# Used when winget is unavailable or fails (notably msstore cert-pinning error
|
||||
# 0x8a15005e, which aborts `winget install` unless --source winget is given).
|
||||
# Downloads the official installer and runs it silently as a per-user install
|
||||
# (no UAC), putting python.exe + the py launcher on PATH. Mirrors the uv ->
|
||||
# astral.sh fallback below. Returns @{ Version; Path } or $null.
|
||||
# For when winget is missing or fails (e.g. msstore cert-pinning error
|
||||
# 0x8a15005e). Silent per-user install, no UAC; puts python.exe + py
|
||||
# launcher on PATH. Returns @{ Version; Path } or $null.
|
||||
function Install-PythonFromPythonOrg {
|
||||
# python.org ships one installer per architecture.
|
||||
$archSuffix = switch (Get-TauriDiagArch) {
|
||||
"x86_64" { "-amd64" }
|
||||
"arm64" { "-arm64" }
|
||||
|
|
@ -1048,10 +1038,8 @@ shell.Run cmd, 0, False
|
|||
return $null
|
||||
}
|
||||
|
||||
# Resolve the latest $PythonVersion.x patch from the python.org listing,
|
||||
# falling back to a same-minor version if the listing cannot be fetched.
|
||||
# Use the pinned full version only when it matches the requested minor so a
|
||||
# non-default UNSLOTH_PYTHON (e.g. 3.12) doesn't silently install 3.13.
|
||||
# Latest $PythonVersion.x patch from the live listing; pinned fallback only
|
||||
# if it matches the requested minor (UNSLOTH_PYTHON=3.12 must not get 3.13).
|
||||
$full = if ($PythonFallbackFullVersion -like "$PythonVersion.*") { $PythonFallbackFullVersion } else { "$PythonVersion.0" }
|
||||
try {
|
||||
$listing = [string](Invoke-RestMethod -Uri "https://www.python.org/ftp/python/" -UseBasicParsing -TimeoutSec 20)
|
||||
|
|
@ -1071,16 +1059,14 @@ shell.Run cmd, 0, False
|
|||
return $null
|
||||
}
|
||||
|
||||
# Per-user install => no UAC. PrependPath puts python + py on PATH;
|
||||
# Include_launcher installs py.exe (preferred by Find-CompatiblePython).
|
||||
# Per-user => no UAC; PrependPath + Include_launcher put python and py.exe on PATH.
|
||||
substep "installing Python $full (silent, per-user)..."
|
||||
$installArgs = @(
|
||||
"/quiet",
|
||||
"InstallAllUsers=0",
|
||||
"PrependPath=1",
|
||||
"Include_launcher=1",
|
||||
# Launcher per-user too: Include_launcher defaults InstallLauncherAllUsers=1,
|
||||
# which needs admin and would break this non-admin per-user fallback.
|
||||
# Default InstallLauncherAllUsers=1 needs admin -- force per-user.
|
||||
"InstallLauncherAllUsers=0",
|
||||
"Include_pip=1",
|
||||
"AssociateFiles=0",
|
||||
|
|
@ -1116,13 +1102,9 @@ shell.Run cmd, 0, False
|
|||
$wingetExit = $null
|
||||
|
||||
if ($script:WingetAvailable) {
|
||||
# --source winget avoids the msstore source, which can fail with
|
||||
# cert-pinning error 0x8a15005e and abort the whole `winget install`
|
||||
# (winget then demands --source). Python and uv both live in the
|
||||
# winget source, so pinning it is correct and faster.
|
||||
#
|
||||
# Lower ErrorActionPreference so winget stderr (progress/warnings) is
|
||||
# not a terminating error on PS 5.1 (native stderr is ErrorRecord).
|
||||
# --source winget skips msstore, whose cert-pinning error 0x8a15005e
|
||||
# aborts the whole `winget install`; Python and uv both live in winget.
|
||||
# Lower EAP so winget stderr is not a terminating error on PS 5.1.
|
||||
$prevEAP = $ErrorActionPreference
|
||||
$ErrorActionPreference = "Continue"
|
||||
try {
|
||||
|
|
@ -1136,10 +1118,8 @@ shell.Run cmd, 0, False
|
|||
$DetectedPython = Find-CompatiblePython
|
||||
|
||||
if (-not $DetectedPython) {
|
||||
# Python still not functional after winget -- force reinstall.
|
||||
# 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).
|
||||
# Force reinstall: winget can report success/"already installed"
|
||||
# while Python still is not on PATH (partial uninstall, other installer).
|
||||
substep "Python not found on PATH after winget. Retrying with --force..." "Yellow"
|
||||
$ErrorActionPreference = "Continue"
|
||||
try {
|
||||
|
|
@ -1152,9 +1132,7 @@ shell.Run cmd, 0, False
|
|||
}
|
||||
}
|
||||
|
||||
# Fall back to python.org if winget is unavailable OR couldn't install a
|
||||
# working Python (missing/broken winget, msstore cert errors --source
|
||||
# winget can't fix). Keeps the install automatic instead of failing out.
|
||||
# python.org fallback when winget is missing or could not deliver a working Python.
|
||||
if (-not $DetectedPython) {
|
||||
if ($script:WingetAvailable) {
|
||||
substep "winget could not install Python -- falling back to python.org..." "Yellow"
|
||||
|
|
@ -1408,25 +1386,21 @@ shell.Run cmd, 0, False
|
|||
}
|
||||
|
||||
# ── Helper: run amd-smi without triggering a UAC elevation prompt ──
|
||||
# amd-smi on Windows auto-elevates to read GPU/APU memory, surfacing a confusing
|
||||
# DiskPart UAC prompt mid-install (Studio backend amd.py hits the same).
|
||||
# __COMPAT_LAYER=RunAsInvoker forces it (and helpers it spawns) to run
|
||||
# un-elevated; on failure the WMI name -> gfx fallback still resolves the arch.
|
||||
# amd-smi auto-elevates on Windows (confusing DiskPart UAC prompt mid-install);
|
||||
# __COMPAT_LAYER=RunAsInvoker runs it un-elevated (backend amd.py does the same).
|
||||
function Invoke-AmdSmiNoElevate {
|
||||
param(
|
||||
[Parameter(Mandatory = $true, Position = 0)][string]$Exe,
|
||||
[Parameter(Position = 1)][string[]]$SmiArgs = @(),
|
||||
[int]$TimeoutSec = 30
|
||||
)
|
||||
# RunAsInvoker blocks the auto-elevation/UAC prompt; the timeout bounds a
|
||||
# flaky amd-smi that can otherwise spin for minutes (30s mirrors amd.py).
|
||||
# Timeout bounds a flaky amd-smi that can spin for minutes (30s mirrors amd.py).
|
||||
$prevCompat = [Environment]::GetEnvironmentVariable('__COMPAT_LAYER', 'Process')
|
||||
$env:__COMPAT_LAYER = 'RunAsInvoker'
|
||||
try {
|
||||
# [Process]::Start, NOT Start-Process -PassThru: the latter leaves
|
||||
# .ExitCode $null after WaitForExit on PS 5.1, so $LASTEXITCODE (checked
|
||||
# by callers) reads non-zero and kills detection. Async reads drain the
|
||||
# pipes (no deadlock); amd-smi args have no spaces so a plain join is safe.
|
||||
# NOT Start-Process -PassThru: on PS 5.1 it leaves .ExitCode $null, breaking
|
||||
# callers' $LASTEXITCODE checks. Async reads avoid pipe deadlock; amd-smi
|
||||
# args have no spaces so a plain join is safe.
|
||||
$psi = New-Object System.Diagnostics.ProcessStartInfo
|
||||
$psi.FileName = $Exe
|
||||
$psi.Arguments = ($SmiArgs -join ' ')
|
||||
|
|
@ -1642,11 +1616,11 @@ shell.Run cmd, 0, False
|
|||
# (gfx120X/110X/1151/1150/103X); unknown names fall back to CPU.
|
||||
elseif ($ROCmGpuLabel) {
|
||||
$nameArchTable = @(
|
||||
@{ P = "9070 XT|9080"; A = "gfx1201" } # RDNA 4 (RX 9070 XT / 9080)
|
||||
@{ P = "9070|9060"; A = "gfx1200" } # RDNA 4 (RX 9070 / 9060)
|
||||
@{ P = "8060S|8050S|8040S|Strix Halo|Ryzen AI Max|AI Max"; A = "gfx1151" } # RDNA 3.5 (Strix Halo: Radeon 8060S/8050S/8040S iGPU, Ryzen AI Max+)
|
||||
@{ P = "890M|880M|860M|840M|Strix Point|Krackan|HX 37[05]|AI 9 HX|AI 9 36[05]|AI 7 35[05]|AI 5 34[05]|AI 7 PRO 35|AI 5 33"; A = "gfx1150" } # RDNA 3.5 (Strix/Krackan Point: Radeon 890M/880M iGPU, Ryzen AI 9 HX 370/375)
|
||||
@{ P = "RX 7900|RX 7800|RX 7700(?!S)|PRO W7900|PRO W7800|PRO W7700"; A = "gfx1100" } # RDNA 3 desktop/workstation (Navi 31)
|
||||
@{ P = "9070 XT|9080"; A = "gfx1201" } # RDNA 4
|
||||
@{ P = "9070|9060"; A = "gfx1200" } # RDNA 4
|
||||
@{ P = "8060S|8050S|8040S|Strix Halo|Ryzen AI Max|AI Max"; A = "gfx1151" } # RDNA 3.5 (Strix Halo)
|
||||
@{ P = "890M|880M|860M|840M|Strix Point|Krackan|HX 37[05]|AI 9 HX|AI 9 36[05]|AI 7 35[05]|AI 5 34[05]|AI 7 PRO 35|AI 5 33"; A = "gfx1150" } # RDNA 3.5 (Strix/Krackan Point)
|
||||
@{ P = "RX 7900|RX 7800|RX 7700(?!S)|PRO W7900|PRO W7800|PRO W7700"; A = "gfx1100" } # RDNA 3 (Navi 31)
|
||||
@{ P = "RX 7600|RX 7700S|RX 7650|PRO W7600|PRO W7500|PRO V710"; A = "gfx1102" } # RDNA 3 (Navi 33)
|
||||
@{ P = "780M|760M|740M|Phoenix|Hawk Point|Z1 Extreme|Z2 Extreme"; A = "gfx1103" } # RDNA 3 iGPU (Phoenix / Hawk Point)
|
||||
@{ P = "RX 6900|RX 6800|RX 6750|RX 6700|PRO W6800|PRO W6900"; A = "gfx1030" } # RDNA 2 (Navi 21) -- gfx103X family
|
||||
|
|
@ -1707,11 +1681,9 @@ shell.Run cmd, 0, False
|
|||
}
|
||||
|
||||
# ── Optional WSL-ROCm driver hint ────────────────────────────────────────
|
||||
# An AMD GPU can also be used inside WSL2, but only with Adrenalin >= 26.2.2
|
||||
# (first production ROCDXG/WSL release); native Windows GPU works with any
|
||||
# recent driver. We can't auto-install it (AMD referrer-gates downloads, no
|
||||
# winget package), so just point at AMD's page. Shown only when the installed
|
||||
# driver predates 26.2.2 (Feb 2026); suppress with UNSLOTH_SKIP_AMD_DRIVER_HINT=1.
|
||||
# WSL2 ROCm needs Adrenalin >= 26.2.2; we can't auto-install it (referrer-
|
||||
# gated download, no winget package), so hint when the driver is older.
|
||||
# Suppress with UNSLOTH_SKIP_AMD_DRIVER_HINT=1.
|
||||
function Show-AmdWslDriverHint {
|
||||
if ($env:UNSLOTH_SKIP_AMD_DRIVER_HINT) { return }
|
||||
try {
|
||||
|
|
@ -1728,15 +1700,13 @@ shell.Run cmd, 0, False
|
|||
$drvDate = [Management.ManagementDateTimeConverter]::ToDateTime([string]$amd.DriverDate)
|
||||
}
|
||||
} catch {}
|
||||
# Older than 26.2.2 (Feb 2026) => can't expose the GPU to WSL ROCm.
|
||||
# Unreadable date => still show the hint (informational, suppressible).
|
||||
# Pre-26.2.2 (Feb 2026) driver, or unreadable date => show the hint.
|
||||
if ($drvDate -and $drvDate -ge (Get-Date '2026-02-01')) { return }
|
||||
substep "Tip: to use this GPU inside WSL too, install AMD Adrenalin 26.2.2+ (for WSL2)." "Cyan"
|
||||
substep " Your current driver predates it; native Windows GPU is unaffected. Get it from AMD:" "Cyan"
|
||||
substep " https://www.amd.com/en/resources/support-articles/release-notes/RN-RAD-WIN-26-2-2.html" "Cyan"
|
||||
substep " Then reboot and run this installer inside an Ubuntu-24.04 WSL distro." "Cyan"
|
||||
# If WSL isn't installed yet, point at the command that provisions it
|
||||
# (best-effort; wsl.exe absent => no WSL).
|
||||
# No wsl.exe => suggest installing WSL.
|
||||
$hasWsl = $false
|
||||
try { $hasWsl = [bool](Get-Command wsl.exe -ErrorAction SilentlyContinue) } catch {}
|
||||
if (-not $hasWsl) {
|
||||
|
|
@ -1763,8 +1733,8 @@ shell.Run cmd, 0, False
|
|||
substep " Ensure the ROCm compute driver is installed alongside the display driver:" "Yellow"
|
||||
substep " https://rocm.docs.amd.com/en/latest/deploy/windows/index.html" "Yellow"
|
||||
} elseif ($ROCmGfxArch) {
|
||||
# Known arch: Studio setup installs AMD's bundled-runtime ROCm PyTorch wheels
|
||||
# (repo.amd.com), which ship their own runtime -- HIP SDK optional.
|
||||
# Known arch: Studio setup installs AMD's bundled-runtime ROCm wheels
|
||||
# (repo.amd.com) -- HIP SDK optional.
|
||||
step "gpu" "AMD ROCm ($ROCmGfxArch)" "Cyan"
|
||||
substep "Detected: $ROCmGpuLabel" "Cyan"
|
||||
substep "GPU PyTorch uses AMD's bundled-runtime ROCm wheels -- HIP SDK not required (optional)." "Cyan"
|
||||
|
|
@ -1778,7 +1748,6 @@ shell.Run cmd, 0, False
|
|||
step "gpu" "none (chat-only / GGUF)" "Yellow"
|
||||
substep "Training and GPU inference require an NVIDIA or AMD ROCm GPU." "Yellow"
|
||||
}
|
||||
# On an AMD GPU (no NVIDIA), surface the optional WSL-ROCm driver hint.
|
||||
if (-not $HasNvidiaSmi -and ($ROCmGfxArch -or $ROCmGpuLabel)) { Show-AmdWslDriverHint }
|
||||
|
||||
# ── Choose the correct PyTorch index URL based on driver CUDA version ──
|
||||
|
|
@ -1866,8 +1835,7 @@ shell.Run cmd, 0, False
|
|||
if (-not $SkipTorch -and -not $ROCmIndexUrl -and $TorchIndexUrl -like "*/cpu") {
|
||||
Write-Host ""
|
||||
if ($ROCmGfxArch) {
|
||||
# Known AMD arch: install.ps1 lays down CPU PyTorch as a base, then
|
||||
# setup.ps1 swaps in AMD's bundled-runtime GPU ROCm wheels (no HIP SDK).
|
||||
# CPU PyTorch is only a base; setup.ps1 swaps in GPU ROCm wheels next.
|
||||
substep "Installing CPU PyTorch as a base -- Studio setup installs GPU ROCm" "Cyan"
|
||||
substep "wheels for $ROCmGfxArch next (bundled runtime; HIP SDK not required)." "Cyan"
|
||||
} else {
|
||||
|
|
|
|||
28
install.sh
28
install.sh
|
|
@ -1207,8 +1207,7 @@ STUB_EOF
|
|||
# Escape single quotes for PowerShell single-quoted string embedding
|
||||
_css_sc_args_ps=$(printf '%s' "$_css_sc_args" | sed "s/'/''/g")
|
||||
|
||||
# DISTINCT shortcut name so the WSL launcher never clobbers a native
|
||||
# install's "Unsloth Studio.lnk" in the same folder. Per-distro suffix.
|
||||
# Per-distro name so the WSL launcher never clobbers a native install's "Unsloth Studio.lnk".
|
||||
if [ -n "$_css_distro" ]; then
|
||||
_css_lnk_name="Unsloth Studio (WSL - ${_css_distro}).lnk"
|
||||
else
|
||||
|
|
@ -1283,8 +1282,7 @@ WSLPS1_EOF
|
|||
fi
|
||||
rm -f "$_css_ps1_tmp"
|
||||
fi
|
||||
# If WSL interop is disabled (powershell.exe "Exec format error"), the
|
||||
# shortcut wasn't created; tell the user how to launch / re-enable it.
|
||||
# WSL interop disabled (powershell.exe "Exec format error") => no shortcut; tell the user.
|
||||
if [ "$_css_created" -ne 1 ]; then
|
||||
substep "Couldn't create the Windows shortcut (WSL interop may be disabled)." "$C_WARN"
|
||||
substep " Launch Studio from Windows: wsl -d \"$_css_distro\" -- bash -lc 'unsloth studio'" "$C_WARN"
|
||||
|
|
@ -1708,10 +1706,9 @@ _find_no_torch_runtime() {
|
|||
}
|
||||
|
||||
# ── AMD ROCm GPU detection helper ──
|
||||
# WSL2 ROCDXG: the system rocminfo enumerates the GPU over /dev/dxg only when
|
||||
# HSA_ENABLE_DXG_DETECTION=1 (a no-op on bare metal), and /opt/rocm/bin can be
|
||||
# off PATH outside login shells (the profile.d drop-in). Seed both before any
|
||||
# rocminfo probe or a ROCDXG WSL host is misdetected as CPU-only.
|
||||
# WSL2 ROCDXG: rocminfo only sees the GPU with HSA_ENABLE_DXG_DETECTION=1
|
||||
# (no-op on bare metal), and /opt/rocm/bin may be off PATH in non-login shells.
|
||||
# Seed both or a ROCDXG WSL host is misdetected as CPU-only.
|
||||
_ensure_rocm_probe_env() {
|
||||
export HSA_ENABLE_DXG_DETECTION="${HSA_ENABLE_DXG_DETECTION:-1}"
|
||||
if ! command -v rocminfo >/dev/null 2>&1 && [ -x /opt/rocm/bin/rocminfo ]; then
|
||||
|
|
@ -2084,10 +2081,9 @@ _maybe_bootstrap_rocm_wsl() {
|
|||
[ "${UNSLOTH_SKIP_ROCM_WSL_SETUP:-0}" = "1" ] && return 0
|
||||
# Leave any already-usable GPU completely alone (NVIDIA, or working ROCm).
|
||||
if _has_usable_nvidia_gpu; then return 0; fi
|
||||
# "Usable ROCm" here = rocminfo enumerates the gfx1151 agent. Don't use the
|
||||
# generic _has_amd_rocm_gpu: its broad gfx match accepts "gfx11-generic" and
|
||||
# would skip this bootstrap while the real GPU is still unusable. awk consumes
|
||||
# all input, so rocminfo isn't SIGPIPE'd like `grep -q` would under pipefail.
|
||||
# Usable = rocminfo enumerates gfx1151. Not _has_amd_rocm_gpu: its broad
|
||||
# match accepts "gfx11-generic" and would skip the bootstrap. awk consumes
|
||||
# all input, avoiding the pipefail SIGPIPE a `grep -q` would cause.
|
||||
_ensure_rocm_probe_env
|
||||
if command -v rocminfo >/dev/null 2>&1 && \
|
||||
rocminfo 2>/dev/null | awk '/Name:[[:space:]]*gfx1151/{found=1} END{exit !found}'; then
|
||||
|
|
@ -2098,15 +2094,13 @@ _maybe_bootstrap_rocm_wsl() {
|
|||
_persist_rocm_wsl_dropin
|
||||
return 0
|
||||
fi
|
||||
# WSL GPU passthrough device must exist (present on any WSL2 GPU host).
|
||||
# /dev/dxg exists on any WSL2 GPU host.
|
||||
[ -e /dev/dxg ] || return 0
|
||||
# Only Strix Halo (gfx1151): rocminfo can't tell us the arch yet, so match
|
||||
# the CPU model string WSL exposes (e.g. "AMD Ryzen AI Max+ ... Radeon 8060S").
|
||||
# Strix Halo only: rocminfo can't report the arch yet, so match the CPU model string.
|
||||
grep -qiE 'Ryzen AI Max|Radeon 80[0-9]0S|Strix Halo' /proc/cpuinfo 2>/dev/null || return 0
|
||||
command -v bash >/dev/null 2>&1 || return 0
|
||||
|
||||
# Fast path: already configured (librocdxg present) but launched from a
|
||||
# non-login shell so the persisted env wasn't loaded -- just load it.
|
||||
# Fast path: librocdxg present but env not loaded (non-login shell) -- load it.
|
||||
if [ -e /opt/rocm/lib/librocdxg.so ] || [ -e /opt/rocm/lib64/librocdxg.so ]; then
|
||||
if [ -r /etc/profile.d/unsloth-rocm-wsl.sh ]; then
|
||||
# shellcheck disable=SC1091
|
||||
|
|
|
|||
|
|
@ -5,30 +5,25 @@
|
|||
# ──────────────────────────────────────────────────────────────────────────────
|
||||
# Enable ROCm-on-WSL for AMD Strix Halo (Radeon 8060S / gfx1151)
|
||||
# ──────────────────────────────────────────────────────────────────────────────
|
||||
# install.sh already routes gfx1151 to the right ROCm wheels once a ROCm runtime
|
||||
# is present; what it does NOT do is install AMD's ROCm userspace + the WSL DXG
|
||||
# bridge. This helper automates that Linux-side prerequisite on Ubuntu 24.04
|
||||
# WSL2 and is invoked by install.sh when it sees a Strix Halo APU in WSL (via
|
||||
# /dev/dxg) but no ROCm runtime yet. Fully idempotent (re-run just re-verifies).
|
||||
# Installs AMD's ROCm userspace + the WSL DXG bridge (the one part install.sh
|
||||
# doesn't do) on Ubuntu 24.04 WSL2. Invoked by install.sh when it sees a Strix
|
||||
# Halo APU in WSL (/dev/dxg) but no ROCm runtime. Idempotent.
|
||||
#
|
||||
# Manual, admin-gated Windows prerequisite: an AMD Adrenalin driver with
|
||||
# production ROCDXG/WSL support (26.2.2+). install.ps1 offers to update it. Once
|
||||
# installed + rebooted, /dev/dxg is exposed to WSL and this script builds the rest.
|
||||
# Windows prerequisite (manual, admin): Adrenalin with production ROCDXG/WSL
|
||||
# support (26.2.2+) -- install.ps1 offers it; after reboot /dev/dxg appears.
|
||||
#
|
||||
# HOW ROCDXG WORKS (and why older /usr/lib/wsl/lib notes are wrong): librocdxg.so
|
||||
# is AMD's user-mode bridge between the Linux HSA runtime and the Windows driver
|
||||
# over /dev/dxg. The STANDARD hsa-rocr runtime (NOT the gone "roc4wsl" package)
|
||||
# loads it when HSA_ENABLE_DXG_DETECTION=1. No hsa/rocm libs need injecting into
|
||||
# /usr/lib/wsl/lib (it holds only d3d12/dxcore), yet rocminfo enumerates gfx1151
|
||||
# fine -- so we gate on /dev/dxg, not on WSL lib injection.
|
||||
# How ROCDXG works (older /usr/lib/wsl/lib notes are wrong): librocdxg.so
|
||||
# bridges the Linux HSA runtime to the Windows driver over /dev/dxg; the
|
||||
# STANDARD hsa-rocr loads it when HSA_ENABLE_DXG_DETECTION=1. Nothing needs
|
||||
# injecting into /usr/lib/wsl/lib (only d3d12/dxcore live there), so we gate
|
||||
# on /dev/dxg, not on WSL lib injection.
|
||||
#
|
||||
# KNOWN CAVEAT (ROCm/ROCm#6022): librocdxg can cap usable ROCm VRAM at the WSL
|
||||
# VM's RAM (.wslconfig [wsl2] memory=) on some BIOS UMA layouts, and amd-smi
|
||||
# doesn't work in WSL. On OOM below capacity, raise memory= (then wsl --shutdown)
|
||||
# and watch GPU use from Windows. Large-UMA BIOS exposes the full pool regardless.
|
||||
# Caveat (ROCm/ROCm#6022): librocdxg can cap usable VRAM at the WSL VM's RAM
|
||||
# (.wslconfig [wsl2] memory=) on some BIOS UMA layouts; amd-smi doesn't work
|
||||
# in WSL. On OOM below capacity, raise memory= then `wsl --shutdown`.
|
||||
#
|
||||
# Verified on Ryzen AI Max+ PRO 395 / Radeon 8060S (gfx1151) with ROCm 7.2.1 +
|
||||
# Ubuntu 24.04 + WSL2 + Adrenalin. These pins MOVE; bump + re-verify on newer ROCm.
|
||||
# Verified: Ryzen AI Max+ PRO 395 / Radeon 8060S, ROCm 7.2.1, Ubuntu 24.04,
|
||||
# WSL2. Pins MOVE; bump + re-verify on newer ROCm.
|
||||
# ──────────────────────────────────────────────────────────────────────────────
|
||||
set -euo pipefail
|
||||
|
||||
|
|
@ -38,11 +33,9 @@ GFX="gfx1151"
|
|||
LIBROCDXG_REF="${UNSLOTH_LIBROCDXG_REF:-develop}" # ROCm/librocdxg git ref to build
|
||||
# AMD's gfx1151 wheel index (same one install.sh uses); only for the smoke test.
|
||||
TORCH_INDEX="${UNSLOTH_AMD_ROCM_MIRROR:-https://repo.amd.com/rocm/whl}/${GFX}/"
|
||||
# Optional torch smoke test (throwaway venv). OFF by default: install.sh installs
|
||||
# torch itself into the real venv right after, so a duplicate download is wasteful.
|
||||
# Optional torch smoke test (throwaway venv); OFF by default since install.sh installs torch itself.
|
||||
SMOKE_TEST="${UNSLOTH_WSL_SMOKE_TEST:-0}"
|
||||
# REQUIRED constraint -- without it pip prefers PyPI's newer CUDA torch over the
|
||||
# gfx1151 ROCm wheel. 2.11 carries AMD's real gfx1151 fix (matches install.sh).
|
||||
# REQUIRED: without it pip prefers PyPI's newer CUDA torch. 2.11 carries the gfx1151 fix.
|
||||
TORCH_CONSTRAINT="${UNSLOTH_WSL_TORCH_CONSTRAINT:-torch>=2.11.0,<2.12.0}"
|
||||
ROCM_DIR="" # resolved after install
|
||||
|
||||
|
|
@ -58,8 +51,7 @@ if [ "$(id -u)" -ne 0 ]; then
|
|||
fi
|
||||
|
||||
# ── Windows 11 SDK (headers for the librocdxg build) ─────────────────────────
|
||||
# librocdxg's cmake build needs the Windows SDK 'shared' headers, which live on
|
||||
# the Windows HOST under C:\Program Files (x86)\Windows Kits\10\Include\<ver>\.
|
||||
# The cmake build needs the SDK 'shared' headers from the Windows host.
|
||||
_WIN_SDK_INC_BASE="/mnt/c/Program Files (x86)/Windows Kits/10/Include"
|
||||
|
||||
# Print the newest installed SDK include dir with 'shared' headers, or nothing.
|
||||
|
|
@ -73,15 +65,13 @@ _find_win_sdk() {
|
|||
return 0
|
||||
}
|
||||
|
||||
# Best-effort: install the Windows 11 SDK on the Windows HOST via winget so the
|
||||
# build has its headers with no manual step. Elevates -> ONE UAC prompt; headers
|
||||
# appear under /mnt/c immediately (no reboot). Never fatal -- failure falls
|
||||
# through to a manual-install message. Opt out: UNSLOTH_SKIP_WIN_SDK_INSTALL=1.
|
||||
# Best-effort winget install of the Win11 SDK on the Windows host (ONE UAC
|
||||
# prompt; headers appear under /mnt/c immediately, no reboot). Never fatal.
|
||||
# Opt out: UNSLOTH_SKIP_WIN_SDK_INSTALL=1.
|
||||
_install_windows_sdk_via_winget() {
|
||||
[ "${UNSLOTH_SKIP_WIN_SDK_INSTALL:-0}" = "1" ] && { note "Skipping Windows SDK auto-install (UNSLOTH_SKIP_WIN_SDK_INSTALL=1)."; return 0; }
|
||||
command -v powershell.exe >/dev/null 2>&1 || return 0
|
||||
# `command -v` succeeds even with WSL interop OFF (.exe on PATH but fails
|
||||
# with "Exec format error"); verify it actually executes.
|
||||
# command -v passes even with interop OFF ("Exec format error"); verify it runs.
|
||||
powershell.exe -NoProfile -Command "exit 0" >/dev/null 2>&1 || return 0
|
||||
if ! powershell.exe -NoProfile -Command "if (Get-Command winget -ErrorAction SilentlyContinue) { exit 0 } else { exit 1 }" >/dev/null 2>&1; then
|
||||
note "winget not available on the Windows host -- cannot auto-install the Windows SDK."
|
||||
|
|
@ -90,13 +80,11 @@ _install_windows_sdk_via_winget() {
|
|||
say "Installing the Windows 11 SDK on the Windows host via winget"
|
||||
note "librocdxg needs its headers. Approve the UAC prompt on the Windows desktop."
|
||||
note "One-time (~1-3 GB download); opt out with UNSLOTH_SKIP_WIN_SDK_INSTALL=1."
|
||||
# Newest SDK first, then a fallback. Header presence is the source of truth
|
||||
# (re-check each attempt), not winget's exit code. </dev/null so winget never
|
||||
# consumes a piped `curl | sh` stdin.
|
||||
# Newest SDK first. Header presence (not winget exit code) is the truth;
|
||||
# </dev/null keeps winget from eating a piped `curl | sh` stdin.
|
||||
for _sdk_id in Microsoft.WindowsSDK.10.0.26100 Microsoft.WindowsSDK.10.0.22621; do
|
||||
note "winget install ${_sdk_id} ..."
|
||||
# --source winget: pin the community source so a broken default msstore
|
||||
# source (the cert failure this PR fixes) can't abort SDK resolution.
|
||||
# --source winget: a broken msstore source (cert failure) can't abort resolution.
|
||||
powershell.exe -NoProfile -Command "winget install --id ${_sdk_id} -e --source winget --accept-source-agreements --accept-package-agreements --disable-interactivity" </dev/null || true
|
||||
if [ -n "$(_find_win_sdk)" ]; then
|
||||
note "Windows SDK headers present after install."
|
||||
|
|
@ -120,22 +108,19 @@ if [ ! -e /dev/dxg ]; then
|
|||
die "/dev/dxg missing -- WSL GPU paravirtualization not present. Ensure this is WSL2 (not WSL1) on a recent Windows build, and that an AMD GPU + ROCDXG-capable Adrenalin driver is installed on the Windows host (then reboot)."
|
||||
fi
|
||||
note "Ubuntu 24.04 + /dev/dxg present."
|
||||
# Don't block on hsa/rocm libs in /usr/lib/wsl/lib: a working ROCDXG setup
|
||||
# doesn't need them (only d3d12/dxcore). Real readiness is checked via rocminfo.
|
||||
# /usr/lib/wsl/lib needs no hsa/rocm libs (only d3d12/dxcore); readiness is checked via rocminfo.
|
||||
|
||||
# ── Step 1: build/runtime prerequisites ──────────────────────────────────────
|
||||
say "Installing build prerequisites"
|
||||
export DEBIAN_FRONTEND=noninteractive
|
||||
$SUDO apt-get update -y
|
||||
# `make` is explicit: cmake shells out to it but Ubuntu only *recommends* it, so
|
||||
# minimal images lack it and the librocdxg `make -j` build would fail.
|
||||
# `make` is explicit: Ubuntu only recommends it, so minimal images lack it.
|
||||
$SUDO apt-get install -y cmake make gcc g++ git wget gpg ca-certificates python3-venv python3-pip
|
||||
|
||||
# ── Step 2: ROCm ${ROCM_VER} userspace (no DKMS -- WSL uses the Windows driver) ─
|
||||
say "Installing ROCm ${ROCM_VER} userspace"
|
||||
if ! command -v rocminfo >/dev/null 2>&1 && [ ! -x /opt/rocm/bin/rocminfo ]; then
|
||||
# Direct apt-repo install (leaner than amdgpu-install; repo is indexed by
|
||||
# ROCm version, e.g. .../apt/7.2.1).
|
||||
# Direct apt-repo install (leaner than amdgpu-install); repo indexed by ROCm version.
|
||||
$SUDO mkdir -p /etc/apt/keyrings
|
||||
wget -qO- https://repo.radeon.com/rocm/rocm.gpg.key \
|
||||
| gpg --dearmor | $SUDO tee /etc/apt/keyrings/rocm.gpg >/dev/null
|
||||
|
|
@ -144,23 +129,20 @@ if ! command -v rocminfo >/dev/null 2>&1 && [ ! -x /opt/rocm/bin/rocminfo ]; the
|
|||
printf 'Package: *\nPin: release o=repo.radeon.com\nPin-Priority: 600\n' \
|
||||
| $SUDO tee /etc/apt/preferences.d/rocm-pin-600 >/dev/null
|
||||
$SUDO apt-get update -y
|
||||
# rocm-libs pulls everything torch links at runtime (rocblas, hipblas,
|
||||
# miopen-hip, rccl, ...); hsa-rocr + rocminfo come as deps. Large (~5 GB
|
||||
# download / ~23 GB installed).
|
||||
# rocm-libs pulls everything torch links at runtime; hsa-rocr + rocminfo
|
||||
# come as deps. Large (~5 GB download / ~23 GB installed).
|
||||
$SUDO apt-get install -y rocm-libs rocminfo hip-runtime-amd
|
||||
else
|
||||
note "ROCm already present -- skipping apt install."
|
||||
fi
|
||||
|
||||
# Resolve the real ROCm dir and ensure the canonical /opt/rocm symlink. apt lays
|
||||
# ROCm under /opt/rocm-<ver> and rocm-core symlinks /opt/rocm -> that; repair if
|
||||
# an earlier partial run left /opt/rocm as a real dir blocking the symlink.
|
||||
# Resolve the real ROCm dir and ensure the /opt/rocm symlink (apt installs to
|
||||
# /opt/rocm-<ver>); repair a partial run that left /opt/rocm as a real dir.
|
||||
_real="$(ls -d /opt/rocm-* 2>/dev/null | sort -V | tail -1 || true)"
|
||||
if [ -n "$_real" ] && [ ! -L /opt/rocm ] && [ -d /opt/rocm ]; then
|
||||
# /opt/rocm is a real dir blocking the symlink. Only treat it as a removable
|
||||
# stray stub if it's NOT a real ROCm install (a real one has bin/rocminfo /
|
||||
# bin/hipcc / .info/version) -- this protects a user's pre-existing ROCm. Even
|
||||
# then we MOVE IT ASIDE, never rm -rf, so a wrong guess can't lose data.
|
||||
# Treat /opt/rocm as a stray stub only if it lacks ROCm markers (bin/rocminfo,
|
||||
# bin/hipcc, .info/version) -- protects a pre-existing install. Even then,
|
||||
# MOVE it aside, never rm -rf, so a wrong guess can't lose data.
|
||||
if [ -e /opt/rocm/bin/rocminfo ] || [ -e /opt/rocm/bin/hipcc ] || [ -e /opt/rocm/.info/version ]; then
|
||||
note "/opt/rocm is a real ROCm install -- leaving it untouched (will install librocdxg into it)."
|
||||
else
|
||||
|
|
@ -181,9 +163,8 @@ say "Building librocdxg (${LIBROCDXG_REF})"
|
|||
if [ -e "${ROCM_DIR}/lib/librocdxg.so" ]; then
|
||||
note "librocdxg already installed -- skipping build."
|
||||
else
|
||||
# Discover the newest installed Win11 SDK (version differs per machine). If
|
||||
# absent, auto-install via winget (one UAC prompt) and re-discover; only if
|
||||
# that ALSO fails do we stop with manual instructions.
|
||||
# Find the newest installed Win11 SDK; if absent, winget-install and retry,
|
||||
# stopping with manual instructions only if that also fails.
|
||||
_win_sdk="$(_find_win_sdk)"
|
||||
if [ -z "$_win_sdk" ]; then
|
||||
note "Windows 11 SDK headers not found -- attempting automatic install..."
|
||||
|
|
@ -238,17 +219,15 @@ export LD_LIBRARY_PATH="${ROCM_DIR}/lib:${LD_LIBRARY_PATH:-}"
|
|||
|
||||
# ── Step 5: verify the runtime enumerates the GPU ────────────────────────────
|
||||
say "Verifying rocminfo sees ${GFX}"
|
||||
# Capture rocminfo into a var BEFORE grepping: piping into `grep -q` SIGPIPEs
|
||||
# rocminfo on first match, which under `set -o pipefail` turns a successful match
|
||||
# into a pipeline failure. Match the gfx1151 ISA "Name:" agent exactly (not a
|
||||
# broad gfx1[0-9]) so a generic fallback ISA or unrelated RDNA GPU can't pass.
|
||||
# Capture rocminfo BEFORE grepping: piping into `grep -q` SIGPIPEs it, which
|
||||
# pipefail turns into failure on a successful match. Match the gfx1151 "Name:"
|
||||
# agent exactly so a generic fallback ISA or unrelated RDNA GPU can't pass.
|
||||
_rocminfo_out="$(rocminfo 2>/dev/null || true)"
|
||||
if ! printf '%s\n' "$_rocminfo_out" | grep -qE "Name:[[:space:]]*${GFX}([^0-9]|$)"; then
|
||||
printf '%s\n' "$_rocminfo_out" | head -25 >&2 || true
|
||||
die "rocminfo did not enumerate a ${GFX} GPU agent. Most common cause: the Windows AMD driver predates production ROCDXG -- update Adrenalin (install.ps1 offers this), reboot, and re-run."
|
||||
fi
|
||||
# Display-only summary: best-effort (|| true) so head's early pipe-close under
|
||||
# `set -o pipefail` can't fail the bootstrap after verification already passed.
|
||||
# Display-only; || true so head's pipe-close under pipefail can't fail the bootstrap.
|
||||
printf '%s\n' "$_rocminfo_out" | grep -E 'Marketing Name|Device Type|Compute Unit' | grep -iE "Radeon|GPU|Compute" | head -3 || true
|
||||
note "ROCm-on-WSL runtime is live for ${GFX}."
|
||||
|
||||
|
|
@ -258,8 +237,7 @@ if [ "$SMOKE_TEST" = "1" ]; then
|
|||
_venv="${HOME}/.unsloth/rocm-smoketest"
|
||||
rm -rf "$_venv"; python3 -m venv "$_venv"
|
||||
"$_venv/bin/pip" install --quiet --upgrade pip
|
||||
# gfx1151 index is primary (torch + triton); PyPI only an extra for pure-py
|
||||
# deps. The constraint keeps pip on the ROCm wheel, not a newer PyPI CUDA torch.
|
||||
# gfx1151 index primary, PyPI only extra; the constraint keeps pip on the ROCm wheel.
|
||||
"$_venv/bin/pip" install --index-url "$TORCH_INDEX" \
|
||||
--extra-index-url https://pypi.org/simple "$TORCH_CONSTRAINT" || \
|
||||
die "torch install from ${TORCH_INDEX} failed."
|
||||
|
|
|
|||
|
|
@ -16,8 +16,7 @@ function Uninstall-UnslothStudio {
|
|||
function _Step { param([string]$Msg) Write-Host $Msg }
|
||||
function _Substep { param([string]$Msg, [string]$Color = "Gray") Write-Host " $Msg" -ForegroundColor $Color }
|
||||
|
||||
# Remove a file/dir/symlink if present. Idempotent; retries since a just-killed
|
||||
# process can briefly hold a handle (Windows refuses the delete until released).
|
||||
# Remove a file/dir/symlink if present. Retries: a just-killed process can briefly hold a handle.
|
||||
function _RemovePath {
|
||||
param([string]$Path)
|
||||
if ([string]::IsNullOrWhiteSpace($Path)) { return }
|
||||
|
|
@ -240,11 +239,9 @@ function Uninstall-UnslothStudio {
|
|||
} catch { }
|
||||
}
|
||||
|
||||
# Stop processes that would block deleting the paths we remove. Unlike
|
||||
# _StopStudioProcesses (venv exe only), this also catches llama-server/llama-cli,
|
||||
# the unsloth.exe shim, and orphaned mp workers under SYSTEM python holding a
|
||||
# venv DLL (an open DLL handle blocks the dir delete) -- found by scanning each
|
||||
# candidate's loaded modules, not just its image path.
|
||||
# Stop processes that would block the deletes. Unlike _StopStudioProcesses
|
||||
# (venv exe only), scans loaded modules too: catches llama-server/llama-cli,
|
||||
# the unsloth.exe shim, and orphaned mp workers holding a venv DLL.
|
||||
function _StopProcessesLockingRoots {
|
||||
param([string[]]$Roots)
|
||||
$clean = @($Roots | Where-Object { $_ } | ForEach-Object { $_.TrimEnd('\','/') })
|
||||
|
|
@ -263,8 +260,8 @@ function Uninstall-UnslothStudio {
|
|||
}
|
||||
}
|
||||
} catch { }
|
||||
# 2. A loaded module under a target root (orphaned mp-fork python holding a
|
||||
# venv DLL). Scoped to names that load our DLLs to keep the scan fast.
|
||||
# 2. Loaded module under a root (orphaned python holding a venv DLL);
|
||||
# scoped to known process names to keep the scan fast.
|
||||
try {
|
||||
$cands = Get-Process -Name python, pythonw, unsloth, llama-server, llama-cli -ErrorAction SilentlyContinue
|
||||
foreach ($proc in $cands) {
|
||||
|
|
@ -280,17 +277,14 @@ function Uninstall-UnslothStudio {
|
|||
# Default install root + default data dir.
|
||||
$defaultStudioHome = if ($env:USERPROFILE) { Join-Path $env:USERPROFILE ".unsloth\studio" } else { $null }
|
||||
$defaultDataDir = if ($env:LOCALAPPDATA) { Join-Path $env:LOCALAPPDATA "Unsloth Studio" } else { $null }
|
||||
# Default-mode ~/.unsloth holds a SHARED llama.cpp build + .cache that are
|
||||
# siblings of studio (not under it), so deleting <studio> misses them -- handle
|
||||
# explicitly. No-op in env/custom mode (nested under the custom root, removed
|
||||
# with it). A user-set UNSLOTH_LLAMA_CPP_PATH is left alone.
|
||||
# Default mode: shared llama.cpp + .cache are siblings of <studio> under
|
||||
# ~/.unsloth, so deleting <studio> misses them. No-op in env/custom mode;
|
||||
# a user-set UNSLOTH_LLAMA_CPP_PATH is left alone.
|
||||
$defaultUnslothHome = if ($env:USERPROFILE) { Join-Path $env:USERPROFILE ".unsloth" } else { $null }
|
||||
$defaultLlamaCpp = if ($defaultUnslothHome) { Join-Path $defaultUnslothHome "llama.cpp" } else { $null }
|
||||
$defaultCache = if ($defaultUnslothHome) { Join-Path $defaultUnslothHome ".cache" } else { $null }
|
||||
# llama.cpp atomic-install staging root (install_llama_prebuilt.py .staging,
|
||||
# sibling of the install dir). Usually pruned after activate, but an interrupted
|
||||
# build can leave a "<name>.staging-XXXX" tree; removing it lets the empty-dir
|
||||
# cleanup of ~/.unsloth below succeed. No-op in env/custom mode and when absent.
|
||||
# install_llama_prebuilt.py's .staging root: an interrupted build can leave
|
||||
# it behind, blocking the empty-dir cleanup of ~/.unsloth below.
|
||||
$defaultStaging = if ($defaultUnslothHome) { Join-Path $defaultUnslothHome ".staging" } else { $null }
|
||||
|
||||
# Build known-root list FIRST so the port-file kill can verify ownership.
|
||||
|
|
@ -308,8 +302,7 @@ function Uninstall-UnslothStudio {
|
|||
_StopByPortFile -PortFile (Join-Path $r "share\studio.port") -KnownRoots $knownRoots
|
||||
}
|
||||
_StopStudioProcesses -KnownRoots $knownRoots
|
||||
# Also stop anything holding a handle on the exact paths we delete (llama-server,
|
||||
# the CLI shim, an mp-fork python with a venv DLL) so the dir delete isn't refused.
|
||||
# Also stop anything holding a handle on the paths we delete (else the delete is refused).
|
||||
_StopProcessesLockingRoots -Roots (@($knownRoots) + @($defaultDataDir, $defaultLlamaCpp, $defaultCache))
|
||||
|
||||
# ── Remove custom-root install trees ──
|
||||
|
|
@ -329,8 +322,7 @@ function Uninstall-UnslothStudio {
|
|||
if ($defaultStudioHome) { _RemovePath $defaultStudioHome }
|
||||
# Default data dir.
|
||||
if ($defaultDataDir) { _RemovePath $defaultDataDir }
|
||||
# Default-mode shared llama.cpp build + cache (siblings of studio under
|
||||
# ~/.unsloth). No-op in env/custom mode and when absent.
|
||||
# Default-mode shared llama.cpp + cache (siblings of studio; no-op in env/custom mode).
|
||||
if ($defaultLlamaCpp) { _RemovePath $defaultLlamaCpp }
|
||||
if ($defaultCache) { _RemovePath $defaultCache }
|
||||
if ($defaultStaging) { _RemovePath $defaultStaging }
|
||||
|
|
@ -349,9 +341,8 @@ function Uninstall-UnslothStudio {
|
|||
if ($env:APPDATA) {
|
||||
_RemovePath (Join-Path $env:APPDATA "Microsoft\Windows\Start Menu\Programs\Unsloth Studio.lnk")
|
||||
}
|
||||
# Invalidate the Win11 Start Menu tile cache so the removed shortcut's tile
|
||||
# disappears promptly instead of lingering stale (mirrors install.ps1's
|
||||
# New-StudioShortcuts). Preserves start2.bin (the pin layout).
|
||||
# Invalidate the Win11 Start Menu tile cache so the removed tile doesn't
|
||||
# linger (mirrors install.ps1). Preserves start2.bin (the pin layout).
|
||||
try {
|
||||
$smehTemp = Join-Path $env:LOCALAPPDATA "Packages\Microsoft.Windows.StartMenuExperienceHost_cw5n1h2txyewy\TempState"
|
||||
if (Test-Path -LiteralPath $smehTemp) {
|
||||
|
|
|
|||
|
|
@ -212,17 +212,14 @@ _custom_studio_roots | while IFS= read -r _custom_root; do
|
|||
_remove_path "$_custom_root"
|
||||
done
|
||||
_remove_path "$HOME/.unsloth/studio"
|
||||
# Default-mode shared llama.cpp build + cache are siblings of studio (not removed
|
||||
# by deleting it). No-op in env/custom mode (they nest under the custom root) and
|
||||
# when absent. A user-set UNSLOTH_LLAMA_CPP_PATH is intentionally kept.
|
||||
# Default-mode shared llama.cpp + cache are siblings of studio. No-op in
|
||||
# env/custom mode; a user-set UNSLOTH_LLAMA_CPP_PATH is kept.
|
||||
_remove_path "$HOME/.unsloth/llama.cpp"
|
||||
_remove_path "$HOME/.unsloth/.cache"
|
||||
# llama.cpp atomic-install staging root (install_llama_prebuilt.py .staging).
|
||||
# Normally pruned after activate, but an interrupted build can leave it behind;
|
||||
# removing it lets the rmdir below succeed. No-op in env/custom mode and absent.
|
||||
# install_llama_prebuilt.py's .staging root: an interrupted build can leave it
|
||||
# behind, blocking the rmdir below.
|
||||
_remove_path "$HOME/.unsloth/.staging"
|
||||
# ROCm-on-WSL helper artifacts (librocdxg build clone + smoke-test venv). No-op
|
||||
# where they don't exist; removing them lets the rmdir below succeed.
|
||||
# ROCm-on-WSL helper artifacts (librocdxg clone + smoke-test venv).
|
||||
_remove_path "$HOME/.unsloth/librocdxg"
|
||||
_remove_path "$HOME/.unsloth/rocm-smoketest"
|
||||
# Drop ~/.unsloth only if now empty (rmdir refuses non-empty, so user content is kept).
|
||||
|
|
@ -259,21 +256,17 @@ case "$_os" in
|
|||
Linux)
|
||||
if [ "$_is_wsl" = "1" ]; then
|
||||
echo "Removing WSL Windows-side shortcuts..."
|
||||
# install.sh creates per-distro 'Unsloth Studio (WSL - <distro>).lnk'
|
||||
# on the Windows Desktop + Start Menu via powershell.exe. Scope removal
|
||||
# to THIS distro (passed as $args[0]) so a multi-distro install keeps the
|
||||
# other distros' launchers; the TARGET=wsl.exe check still spares a
|
||||
# native install's "Unsloth Studio.lnk". Prefer powershell.exe; test it
|
||||
# can EXECUTE (`command -v` succeeds even with interop OFF -- .exe then
|
||||
# fails "Exec format error", common on systemd-enabled distros).
|
||||
# Remove only THIS distro's 'Unsloth Studio (WSL - <distro>).lnk' so a
|
||||
# multi-distro install keeps other launchers; the wsl.exe target check
|
||||
# spares a native install's lnk. Verify powershell.exe actually EXECUTES
|
||||
# (`command -v` passes even with interop off -- "Exec format error").
|
||||
_wsl_distro="${WSL_DISTRO_NAME:-}"
|
||||
_ps_ran=0
|
||||
if command -v powershell.exe >/dev/null 2>&1 && \
|
||||
powershell.exe -NoProfile -Command "exit 0" >/dev/null 2>&1; then
|
||||
_ps_ran=1
|
||||
# Inject the distro into the command: a -Command string does not
|
||||
# receive trailing tokens as $args. WSL distro names are safe to
|
||||
# embed (no quotes/$/backtick).
|
||||
# Inject the distro: -Command strings get no $args. Distro names
|
||||
# are safe to embed (no quotes/$/backtick).
|
||||
# shellcheck disable=SC2016
|
||||
powershell.exe -NoProfile -Command '$distro = "'"$_wsl_distro"'";
|
||||
$dirs = @(
|
||||
|
|
@ -300,9 +293,8 @@ case "$_os" in
|
|||
}
|
||||
}' >/dev/null 2>&1 || true
|
||||
fi
|
||||
# Fallback when powershell.exe can't run (interop disabled): remove the
|
||||
# WSL .lnk files via drvfs. The "Unsloth Studio (WSL..." name is
|
||||
# WSL-specific, so a native install's "Unsloth Studio.lnk" never matches.
|
||||
# Interop disabled: remove the .lnk files via drvfs. The "(WSL..." name
|
||||
# is WSL-specific, so a native "Unsloth Studio.lnk" never matches.
|
||||
if [ "$_ps_ran" = "0" ]; then
|
||||
for _drive in /mnt/c /mnt/d /mnt/e; do
|
||||
[ -d "$_drive/Users" ] || continue
|
||||
|
|
@ -329,9 +321,8 @@ case "$_os" in
|
|||
done
|
||||
fi
|
||||
# ── ROCm-on-WSL config (install_rocm_wsl_strixhalo.sh) ──
|
||||
# Remove Unsloth's own ROCDXG config (the env it persisted). The system
|
||||
# ROCm userspace is a shared prereq (like CUDA) and is LEFT IN PLACE by
|
||||
# default; set UNSLOTH_UNINSTALL_ROCM=1 to remove it too.
|
||||
# Remove only Unsloth's persisted env; system ROCm is a shared prereq,
|
||||
# left in place unless UNSLOTH_UNINSTALL_ROCM=1.
|
||||
echo "Removing ROCm-on-WSL config..."
|
||||
_sudo=""
|
||||
if [ "$_uid" != "0" ] && command -v sudo >/dev/null 2>&1; then _sudo="sudo"; fi
|
||||
|
|
|
|||
|
|
@ -5178,14 +5178,11 @@ class LlamaCppBackend:
|
|||
|
||||
@staticmethod
|
||||
def _fit_off_retry_eligible(cmd: "list[str]", use_fit: bool) -> bool:
|
||||
"""Whether a llama-server startup crash may be retried with --fit off.
|
||||
|
||||
Only when Studio's own VRAM math placed the model (use_fit=False)
|
||||
and nothing on the command line set the fit mode explicitly
|
||||
(-fit / --fit, space- or equals-form). --fit-ctx / --fit-target /
|
||||
-fitc / -fitt tune the fit step but do not select the mode, so
|
||||
they do not block the retry.
|
||||
"""
|
||||
"""Whether a startup crash may be retried with --fit off: only when
|
||||
Studio placed the model (use_fit=False) and no explicit fit-mode flag
|
||||
(-fit/--fit, space- or equals-form) is on the command line. --fit-ctx,
|
||||
--fit-target, -fitc, -fitt tune the step, not the mode, so they don't
|
||||
block the retry."""
|
||||
if use_fit:
|
||||
return False
|
||||
for a in cmd:
|
||||
|
|
@ -5209,9 +5206,8 @@ class LlamaCppBackend:
|
|||
if self._stdout_thread is not None:
|
||||
self._stdout_thread.join(timeout = 2)
|
||||
output = "\n".join(self._stdout_lines[-50:])
|
||||
# Keep the TAIL: crash details (abort reason, ROCm/CUDA error
|
||||
# text) print last, after the long startup banner. Head
|
||||
# truncation has cut off exactly the diagnostic line before.
|
||||
# Keep the TAIL: crash details print last, after the startup
|
||||
# banner; head truncation has cut off the diagnostic line before.
|
||||
_log_hint = (
|
||||
f" Full log: {self._llama_log_path}"
|
||||
if getattr(self, "_llama_log_path", None)
|
||||
|
|
|
|||
|
|
@ -27,12 +27,9 @@ from pathlib import Path
|
|||
from typing import Any, Callable
|
||||
|
||||
# ── WSL AMD Strix Halo (gfx1151): enable ROCDXG before any torch import ──────
|
||||
# Mirrors main.py. In WSL the AMD GPU is reached via the ROCDXG bridge
|
||||
# (librocdxg.so over /dev/dxg), which HSA loads only when HSA_ENABLE_DXG_
|
||||
# DETECTION=1 is set before torch touches the GPU. A worker spawned outside a
|
||||
# login shell misses the installer's persisted env and falls back to CPU.
|
||||
# Gated to no-op unless BOTH /dev/dxg and librocdxg.so exist, so native Linux
|
||||
# ROCm, NVIDIA, macOS and Windows are unaffected.
|
||||
# Mirrors main.py: HSA loads librocdxg only with HSA_ENABLE_DXG_DETECTION=1 set
|
||||
# before torch touches the GPU; a worker spawned outside a login shell misses
|
||||
# the persisted env and falls back to CPU. No-op unless /dev/dxg + librocdxg exist.
|
||||
if sys.platform.startswith("linux") and "HSA_ENABLE_DXG_DETECTION" not in os.environ:
|
||||
try:
|
||||
if os.path.exists("/dev/dxg") and any(
|
||||
|
|
@ -711,13 +708,10 @@ def _rocm_classify_unified_memory(props: Any) -> tuple[str, bool]:
|
|||
gcn_arch = _v
|
||||
break
|
||||
|
||||
# Driver's own answer first: hipDeviceProp_t.integrated (exposed as
|
||||
# props.is_integrated; same gate PR #5988's UMA safetensors fast-load
|
||||
# uses). Strictly additive -- only a truthy value upgrades to unified;
|
||||
# 0/absent falls through to the arch/name logic below, so a wheel that
|
||||
# omits or zeroes the field can never downgrade the known APU set. This
|
||||
# covers unified APUs outside the hardcoded arches (gfx1103 Phoenix
|
||||
# iGPUs, future parts) with one universal signal.
|
||||
# Driver's own answer first: hipDeviceProp_t.integrated (same gate as
|
||||
# PR #5988's UMA fast-load). Strictly additive -- only truthy upgrades to
|
||||
# unified; 0/absent falls through, so a wheel omitting the field can't
|
||||
# downgrade the known APU set. Covers APUs beyond the hardcoded arches.
|
||||
if getattr(props, "is_integrated", 0):
|
||||
return gcn_arch, True
|
||||
|
||||
|
|
@ -2163,13 +2157,10 @@ def run_training_process(*, event_queue: Any, stop_queue: Any, config: dict) ->
|
|||
os.environ["TORCHDYNAMO_DISABLE"] = "1"
|
||||
logger.info("Windows ROCm: torch.compile (dynamo) disabled")
|
||||
|
||||
# bitsandbytes' import-time get_rocm_gpu_arch() probe runs
|
||||
# `hipinfo.exe` from PATH; the AMD torch wheel ships it in the venv
|
||||
# Scripts dir, which is on PATH only for activated venvs. Prepend
|
||||
# it so the probe succeeds instead of logging a scary (harmless)
|
||||
# "Could not detect ROCm GPU architecture" ERROR on every import.
|
||||
# Normally inherited from main.py's env, but workers can also be
|
||||
# spawned standalone (tests, CLI) -- keep the guard here too.
|
||||
# bitsandbytes' get_rocm_gpu_arch() runs hipinfo.exe via PATH; the
|
||||
# AMD wheel ships it in the venv Scripts dir (on PATH only when
|
||||
# activated). Prepend it to silence a scary-but-harmless ERROR.
|
||||
# Mirrors main.py for standalone-spawned workers (tests, CLI).
|
||||
_scripts_dir = os.path.dirname(sys.executable)
|
||||
if os.path.isfile(os.path.join(_scripts_dir, "hipInfo.exe")):
|
||||
import shutil as _shutil
|
||||
|
|
@ -2386,18 +2377,13 @@ def run_training_process(*, event_queue: Any, stop_queue: Any, config: dict) ->
|
|||
"unified memory from device name %r; applying unified cap",
|
||||
_dev_name,
|
||||
)
|
||||
# Unified hosts on native Windows: mem_get_info's total is the
|
||||
# WDDM budget the driver grants HIP (BIOS carve + ~half of the
|
||||
# remaining RAM) -- the OS share is already outside it, so the
|
||||
# Linux 0.80 starve-protection double-taxes (48.49 GiB budget →
|
||||
# 38.79 allowed) and blocks loads that fit in free memory.
|
||||
# 1.0 removes the double-tax. Current AMD Windows wheels only
|
||||
# enforce sub-1.0 fractions (measured on gfx1151: 0.5 caps,
|
||||
# 1.0 still allocates past the budget via WDDM overcommit), so
|
||||
# 1.0 behaves like torch's uncapped default, with WDDM
|
||||
# arbitrating residency; on wheels that do enforce it, it caps
|
||||
# at exactly the driver-granted budget. On Linux the total
|
||||
# spans nearly all RAM, so keep the 0.80 OS headroom there.
|
||||
# Native Windows unified: mem_get_info's total is the WDDM
|
||||
# budget (OS share already outside it), so the Linux 0.80 cap
|
||||
# double-taxes and blocks loads that fit. 1.0 removes that;
|
||||
# current AMD Windows wheels enforce only sub-1.0 fractions
|
||||
# (measured on gfx1151), so it acts like torch's uncapped
|
||||
# default with WDDM arbitrating residency. Linux totals span
|
||||
# nearly all RAM, so keep the 0.80 OS headroom there.
|
||||
if _is_unified:
|
||||
_mem_fraction = 1.0 if sys.platform == "win32" else 0.80
|
||||
else:
|
||||
|
|
@ -2411,10 +2397,8 @@ def run_training_process(*, event_queue: Any, stop_queue: Any, config: dict) ->
|
|||
_dev_name,
|
||||
_gcn_arch or "unknown arch",
|
||||
)
|
||||
# Unified Windows APUs: the WDDM budget is user-raisable, but
|
||||
# nothing on the box says so -- users see "48 GB VRAM" on a
|
||||
# 96 GB machine and assume a Studio bug. Say where the limit
|
||||
# comes from and how to raise it.
|
||||
# The WDDM budget is user-raisable but nothing on the box says
|
||||
# so; tell users where the limit comes from and how to raise it.
|
||||
if _is_unified and sys.platform == "win32":
|
||||
try:
|
||||
import psutil as _psutil
|
||||
|
|
|
|||
|
|
@ -64,14 +64,11 @@ if sys.platform == "win32":
|
|||
del _add_rocm_dll_dirs
|
||||
|
||||
# ── Windows AMD ROCm: make hipInfo.exe resolvable for subprocess probes ──
|
||||
# bitsandbytes' get_rocm_gpu_arch() runs `hipinfo.exe` via PATH at import
|
||||
# time; the AMD torch wheel ships it in the venv Scripts dir, which is on
|
||||
# PATH only when the venv is activated -- Studio launches python directly.
|
||||
# Without this, every bitsandbytes import logs a scary (but harmless)
|
||||
# "Could not detect ROCm GPU architecture: [WinError 2]" ERROR + WARNING.
|
||||
# Gated on the file existing: only AMD ROCm wheels ship hipInfo.exe, so
|
||||
# NVIDIA/CPU hosts are untouched. os.add_dll_directory above does not help
|
||||
# here -- subprocess PATH resolution ignores DLL search directories.
|
||||
# bitsandbytes' get_rocm_gpu_arch() runs hipinfo.exe via PATH; the AMD wheel
|
||||
# ships it in the venv Scripts dir, on PATH only when activated -- Studio
|
||||
# launches python directly, so every import logs a scary (harmless) ERROR.
|
||||
# Gated on the file existing (only AMD wheels ship it). add_dll_directory
|
||||
# doesn't help: subprocess PATH resolution ignores DLL search dirs.
|
||||
_scripts_dir = os.path.dirname(sys.executable)
|
||||
if os.path.isfile(os.path.join(_scripts_dir, "hipInfo.exe")):
|
||||
import shutil as _shutil
|
||||
|
|
@ -133,13 +130,10 @@ if sys.platform == "win32":
|
|||
)
|
||||
|
||||
# ── WSL AMD Strix Halo (gfx1151): enable ROCDXG before any torch import ──────
|
||||
# In WSL the AMD GPU is reached via the ROCDXG bridge (librocdxg.so over
|
||||
# /dev/dxg), which HSA loads only when HSA_ENABLE_DXG_DETECTION=1 is set BEFORE
|
||||
# torch touches the GPU. A worker launched outside a login shell (e.g.
|
||||
# `wsl.exe -d Ubuntu-24.04 python ...`) misses the installer's persisted env
|
||||
# and silently falls back to CPU. Set it here, gated to no-op unless BOTH
|
||||
# /dev/dxg AND librocdxg.so exist -- native Linux ROCm, NVIDIA, macOS and
|
||||
# Windows are unaffected.
|
||||
# HSA loads the librocdxg bridge only with HSA_ENABLE_DXG_DETECTION=1 set BEFORE
|
||||
# torch touches the GPU; a process launched outside a login shell misses the
|
||||
# installer's persisted env and silently falls back to CPU. No-op unless both
|
||||
# /dev/dxg and librocdxg.so exist.
|
||||
elif sys.platform.startswith("linux") and "HSA_ENABLE_DXG_DETECTION" not in os.environ:
|
||||
try:
|
||||
if os.path.exists("/dev/dxg") and any(
|
||||
|
|
|
|||
|
|
@ -726,10 +726,9 @@ def _resolve_frontend_path(frontend_path: Path) -> tuple[Optional[Path], list[Pa
|
|||
class _TeeStream:
|
||||
"""Mirror writes to the original stream and a session log file.
|
||||
|
||||
Console behavior is unchanged (writes/returns delegate to the original
|
||||
stream; Tauri's structured-stdout protocol and isatty probes see exactly
|
||||
what they saw before). The file copy is best-effort: a full disk or a
|
||||
closed handle must never break the console."""
|
||||
Console behavior is unchanged (Tauri's structured-stdout protocol and
|
||||
isatty probes are unaffected); the file copy is best-effort and must
|
||||
never break the console."""
|
||||
|
||||
def __init__(self, stream, log_fh):
|
||||
self._stream = stream
|
||||
|
|
@ -757,15 +756,11 @@ class _TeeStream:
|
|||
|
||||
|
||||
def _setup_server_disk_logging():
|
||||
"""Tee stdout/stderr to ~/.unsloth/studio/logs/server/ and aim
|
||||
faulthandler at the same file so hard crashes (access violations /
|
||||
SIGSEGV in the GPU runtime) leave a stack trace on disk.
|
||||
|
||||
Also exports PYTHONFAULTHANDLER=1 so child Python processes (training
|
||||
workers) dump native-crash stacks to their captured stderr. Keeps the
|
||||
newest 20 session logs. Opt out with UNSLOTH_STUDIO_NO_FILE_LOG=1.
|
||||
Returns the log path, or None when disabled/unavailable.
|
||||
"""
|
||||
"""Tee stdout/stderr to ~/.unsloth/studio/logs/server/ and point
|
||||
faulthandler there so native crashes leave a stack trace on disk.
|
||||
Exports PYTHONFAULTHANDLER=1 for child workers. Keeps the newest 20
|
||||
logs; opt out with UNSLOTH_STUDIO_NO_FILE_LOG=1. Returns the log path
|
||||
or None."""
|
||||
if os.environ.get("UNSLOTH_STUDIO_NO_FILE_LOG") == "1":
|
||||
return None
|
||||
try:
|
||||
|
|
@ -782,8 +777,7 @@ def _setup_server_disk_logging():
|
|||
log_dir.mkdir(parents = True, exist_ok = True)
|
||||
stamp = time.strftime("%Y%m%d-%H%M%S")
|
||||
log_path = log_dir / f"server-{stamp}-pid{os.getpid()}.log"
|
||||
# Line-buffered so the tail survives a hard kill; errors="replace"
|
||||
# so a console encoding quirk can never take the server down.
|
||||
# Line-buffered so the tail survives a hard kill; errors="replace" guards encoding quirks.
|
||||
log_fh = open(log_path, "w", encoding = "utf-8", errors = "replace", buffering = 1)
|
||||
except Exception:
|
||||
return None
|
||||
|
|
@ -794,8 +788,7 @@ def _setup_server_disk_logging():
|
|||
faulthandler.enable(file = log_fh, all_threads = True)
|
||||
except Exception:
|
||||
pass
|
||||
# Children (training workers) inherit: their native-crash stacks land on
|
||||
# the stderr the server already captures.
|
||||
# Children inherit: their native-crash stacks land on captured stderr.
|
||||
os.environ.setdefault("PYTHONFAULTHANDLER", "1")
|
||||
|
||||
sys.stdout = _TeeStream(sys.stdout, log_fh)
|
||||
|
|
@ -844,12 +837,9 @@ def run_server(
|
|||
except Exception:
|
||||
pass
|
||||
|
||||
# Persist a session log + native-crash stacks BEFORE importing main, so
|
||||
# even import-time failures leave evidence on disk. Field report: Studio
|
||||
# "terminates without a warning" -- a native crash in the GPU runtime
|
||||
# kills the process with no Python traceback, and a desktop-shortcut
|
||||
# console closes before anything can be read. Console-only logging made
|
||||
# that undiagnosable.
|
||||
# Arm disk logging BEFORE importing main so import-time failures leave
|
||||
# evidence. Field report: native GPU-runtime crashes kill the process with
|
||||
# no traceback, and the shortcut console closes before it can be read.
|
||||
_session_log = _setup_server_disk_logging()
|
||||
if _session_log is not None and not silent:
|
||||
print(f"Session log: {_session_log}")
|
||||
|
|
|
|||
|
|
@ -47,13 +47,9 @@ def _hip_sdk_present() -> bool:
|
|||
|
||||
|
||||
def _amd_smi_allowed() -> bool:
|
||||
"""Whether it is safe to spawn amd-smi here.
|
||||
|
||||
On Windows without a working HIP runtime, amd-smi elevates a child at
|
||||
runtime -- popping a UAC/DiskPart prompt that RunAsInvoker can't suppress
|
||||
(its manifest is asInvoker). So only call it on Windows with a HIP SDK
|
||||
present or UNSLOTH_ENABLE_AMD_SMI=1. Linux amd-smi never elevates.
|
||||
"""
|
||||
"""Safe to spawn amd-smi? On Windows without a working HIP runtime it
|
||||
elevates a child (UAC/DiskPart prompt RunAsInvoker can't suppress), so
|
||||
require a HIP SDK or UNSLOTH_ENABLE_AMD_SMI=1 there. Linux never elevates."""
|
||||
if platform.system() != "Windows":
|
||||
return True
|
||||
flag = os.environ.get("UNSLOTH_ENABLE_AMD_SMI", "").strip().lower()
|
||||
|
|
@ -70,10 +66,8 @@ def _run_amd_smi(*args: str, timeout: int = _AMD_SMI_DEFAULT_TIMEOUT) -> Optiona
|
|||
if _amd_smi_disabled:
|
||||
return None
|
||||
if not _amd_smi_allowed():
|
||||
# Permanently skip amd-smi on Windows w/o a HIP SDK: every call would
|
||||
# pop a UAC/DiskPart prompt (see _amd_smi_allowed). VRAM polling is then
|
||||
# unavailable, but that beats the prompt. Opt back in with
|
||||
# UNSLOTH_ENABLE_AMD_SMI=1.
|
||||
# Permanently skip: every call would pop the UAC/DiskPart prompt (see
|
||||
# _amd_smi_allowed). Opt back in with UNSLOTH_ENABLE_AMD_SMI=1.
|
||||
if not _amd_smi_disabled:
|
||||
logger.info(
|
||||
"amd-smi disabled on Windows (no HIP SDK detected) to avoid a "
|
||||
|
|
@ -83,11 +77,9 @@ def _run_amd_smi(*args: str, timeout: int = _AMD_SMI_DEFAULT_TIMEOUT) -> Optiona
|
|||
_amd_smi_disabled = True
|
||||
return None
|
||||
if shutil.which("amd-smi") is None:
|
||||
# amd-smi does not exist on Windows (neither Adrenalin nor the HIP SDK
|
||||
# ship a CLI) and can be absent on minimal Linux installs. Disable the
|
||||
# poller in one step instead of burning the 3-strike circuit breaker
|
||||
# on guaranteed FileNotFoundError spawns. Studio's VRAM display falls
|
||||
# back to torch mem_get_info.
|
||||
# amd-smi doesn't exist on Windows (no AMD product ships the CLI) and
|
||||
# can be absent on minimal Linux: disable in one step instead of burning
|
||||
# the 3-strike breaker; VRAM display falls back to torch mem_get_info.
|
||||
if not _amd_smi_disabled:
|
||||
logger.info(
|
||||
"amd-smi not found on PATH; GPU utilization polling via "
|
||||
|
|
@ -97,8 +89,7 @@ def _run_amd_smi(*args: str, timeout: int = _AMD_SMI_DEFAULT_TIMEOUT) -> Optiona
|
|||
return None
|
||||
_amd_env = child_env_without_native_path_secret()
|
||||
if platform.system() == "Windows":
|
||||
# RunAsInvoker belt-and-suspenders for any manifest-elevating helper;
|
||||
# the real guard is _amd_smi_allowed() above. Mirrors install scripts.
|
||||
# RunAsInvoker is belt-and-suspenders; the real guard is _amd_smi_allowed().
|
||||
_amd_env = {**_amd_env, "__COMPAT_LAYER": "RunAsInvoker"}
|
||||
try:
|
||||
result = subprocess.run(
|
||||
|
|
@ -111,8 +102,7 @@ def _run_amd_smi(*args: str, timeout: int = _AMD_SMI_DEFAULT_TIMEOUT) -> Optiona
|
|||
)
|
||||
except (OSError, subprocess.TimeoutExpired) as e:
|
||||
if isinstance(e, FileNotFoundError):
|
||||
# Raced a PATH change after the which() check above; absence is
|
||||
# expected on Windows (no AMD product ships an amd-smi CLI there).
|
||||
# Raced a PATH change after the which() check; expected absent on Windows.
|
||||
logger.debug("amd-smi not found (not in PATH): %s", e)
|
||||
else:
|
||||
logger.warning("amd-smi query failed: %s", e)
|
||||
|
|
|
|||
|
|
@ -46,23 +46,17 @@ EXIT_FALLBACK = 2
|
|||
EXIT_ERROR = 1
|
||||
EXIT_BUSY = 3
|
||||
|
||||
# DiskPart-prompt suppression. RunAsInvoker does NOT stop amd-smi's runtime
|
||||
# elevation (its manifest is asInvoker), so this is just harmless belt-and-
|
||||
# suspenders for manifest-elevating tools. The real guard is _amd_smi_allowed():
|
||||
# we don't spawn amd-smi on Windows w/o a HIP SDK (or opt-in).
|
||||
# RunAsInvoker can't stop amd-smi's runtime elevation -- harmless belt-and-
|
||||
# suspenders; the real guard is _amd_smi_allowed() (no spawn w/o HIP SDK/opt-in).
|
||||
if platform.system() == "Windows":
|
||||
os.environ.setdefault("__COMPAT_LAYER", "RunAsInvoker")
|
||||
|
||||
|
||||
def _amd_smi_allowed() -> bool:
|
||||
"""Whether it is safe to spawn amd-smi here.
|
||||
|
||||
On Windows w/o a working HIP runtime, amd-smi elevates a child and pops a
|
||||
UAC/DiskPart prompt RunAsInvoker can't suppress. Only call it on Windows
|
||||
when a HIP SDK is detectable (hipinfo present) or UNSLOTH_ENABLE_AMD_SMI=1;
|
||||
Linux/macOS always allowed. When skipped, the gfx arch still arrives via the
|
||||
forwarded --rocm-gfx, so prebuilt selection is unaffected.
|
||||
"""
|
||||
"""Safe to spawn amd-smi? On Windows without a HIP runtime it elevates a
|
||||
child (UAC/DiskPart prompt), so require a detectable HIP SDK or
|
||||
UNSLOTH_ENABLE_AMD_SMI=1 there; Linux/macOS always allowed. When skipped,
|
||||
--rocm-gfx still supplies the arch."""
|
||||
if platform.system() != "Windows":
|
||||
return True
|
||||
flag = os.environ.get("UNSLOTH_ENABLE_AMD_SMI", "").strip().lower()
|
||||
|
|
@ -2721,9 +2715,8 @@ def run_capture(
|
|||
check: bool = False,
|
||||
env: dict[str, str] | None = None,
|
||||
) -> subprocess.CompletedProcess[str]:
|
||||
# amd-smi on Windows auto-elevates and pops a UAC/DiskPart prompt mid-install;
|
||||
# RunAsInvoker forces it un-elevated. Callers already fall back to WMI/name
|
||||
# detection. Mirrors install.ps1's Invoke-AmdSmiNoElevate; Windows-only.
|
||||
# amd-smi auto-elevates on Windows (UAC/DiskPart prompt mid-install);
|
||||
# RunAsInvoker forces it un-elevated (mirrors install.ps1). Windows-only.
|
||||
if (
|
||||
command
|
||||
and platform.system() == "Windows"
|
||||
|
|
@ -2992,9 +2985,8 @@ def detect_host() -> HostInfo:
|
|||
_candidate = os.path.join(_root, "bin", f"{name}.exe")
|
||||
if os.path.isfile(_candidate):
|
||||
return _candidate
|
||||
# AMD torch wheels ship hipInfo.exe into the venv Scripts dir
|
||||
# (next to python.exe) -- resolvable on driver-only hosts where no
|
||||
# SDK dir exists, so a standalone rerun can still detect the GPU.
|
||||
# AMD torch wheels ship hipInfo.exe in the venv Scripts dir --
|
||||
# resolvable on driver-only hosts with no SDK dir.
|
||||
_venv_candidate = os.path.join(os.path.dirname(sys.executable), f"{name}.exe")
|
||||
if os.path.isfile(_venv_candidate):
|
||||
return _venv_candidate
|
||||
|
|
|
|||
|
|
@ -42,11 +42,9 @@ IS_MAC_INTEL = IS_MACOS and platform.machine() == "x86_64"
|
|||
IS_MAC_ARM = IS_MACOS and platform.machine() == "arm64"
|
||||
IS_LINUX = sys.platform.startswith("linux")
|
||||
|
||||
# DiskPart-prompt suppression: amd-smi auto-elevates on Windows, popping a
|
||||
# UAC/DiskPart prompt mid-install. This installer only spawns probes and pip/uv
|
||||
# (none need elevation), so set __COMPAT_LAYER=RunAsInvoker process-wide -- every
|
||||
# amd-smi subprocess then runs un-elevated, no per-call guard needed. setup.ps1
|
||||
# keeps per-call guards since it ALSO spawns winget installers that need elevation.
|
||||
# amd-smi auto-elevates on Windows (UAC/DiskPart prompt). This installer spawns
|
||||
# only probes and pip/uv, so RunAsInvoker process-wide is safe; setup.ps1 keeps
|
||||
# per-call guards because it also spawns winget installers that need elevation.
|
||||
if IS_WINDOWS:
|
||||
os.environ.setdefault("__COMPAT_LAYER", "RunAsInvoker")
|
||||
# torchcodec ships wheels only for manylinux_2_28_x86_64, macosx_12_0_arm64,
|
||||
|
|
@ -160,21 +158,17 @@ def _bnb_rocm_prerelease_url() -> str | None:
|
|||
|
||||
def _amd_smi_env() -> dict[str, str] | None:
|
||||
"""On Windows, env with __COMPAT_LAYER=RunAsInvoker; None elsewhere.
|
||||
NB: RunAsInvoker doesn't stop amd-smi's runtime elevation (its manifest is
|
||||
asInvoker -- it elevates a child via ShellExecute). The real guard is
|
||||
_amd_smi_allowed() below; this is harmless belt-and-suspenders."""
|
||||
Belt-and-suspenders only -- RunAsInvoker can't stop amd-smi's runtime
|
||||
elevation; the real guard is _amd_smi_allowed()."""
|
||||
if platform.system() != "Windows":
|
||||
return None
|
||||
return {**os.environ, "__COMPAT_LAYER": "RunAsInvoker"}
|
||||
|
||||
|
||||
def _amd_smi_allowed() -> bool:
|
||||
"""Whether it is safe to spawn amd-smi here.
|
||||
|
||||
On Windows w/o a working HIP runtime, amd-smi elevates a child and pops a
|
||||
UAC/DiskPart prompt RunAsInvoker can't suppress. Only call it on Windows with
|
||||
a HIP SDK (hipinfo present) or UNSLOTH_ENABLE_AMD_SMI=1; Linux/macOS always.
|
||||
"""
|
||||
"""Safe to spawn amd-smi? On Windows without a HIP runtime it elevates a
|
||||
child (UAC/DiskPart prompt), so require a HIP SDK or
|
||||
UNSLOTH_ENABLE_AMD_SMI=1 there; Linux/macOS always allowed."""
|
||||
if platform.system() != "Windows":
|
||||
return True
|
||||
flag = os.environ.get("UNSLOTH_ENABLE_AMD_SMI", "").strip().lower()
|
||||
|
|
@ -210,8 +204,7 @@ def _detect_rocm_version() -> tuple[int, int] | None:
|
|||
pass
|
||||
|
||||
# Try amd-smi version (outputs "... | ROCm version: X.Y.Z").
|
||||
# Gated off on Windows w/o a HIP SDK (avoids the UAC/DiskPart prompt);
|
||||
# hipconfig below covers that case.
|
||||
# Gated off on Windows w/o a HIP SDK (UAC prompt); hipconfig below covers it.
|
||||
amd_smi = shutil.which("amd-smi") if _amd_smi_allowed() else None
|
||||
if amd_smi:
|
||||
try:
|
||||
|
|
@ -339,10 +332,8 @@ def _detect_windows_gfx_arch() -> str | None:
|
|||
hipinfo = _candidate
|
||||
break
|
||||
if not hipinfo:
|
||||
# 2b. AMD torch wheels ship hipInfo.exe into the venv Scripts dir
|
||||
# (next to python.exe); resolvable even on driver-only hosts with no
|
||||
# SDK install at all. Lets `studio update` re-detect the arch on a
|
||||
# venv that already has the AMD wheel.
|
||||
# 2b. AMD torch wheels ship hipInfo.exe in the venv Scripts dir --
|
||||
# lets `studio update` re-detect the arch on driver-only hosts.
|
||||
_venv_hipinfo = os.path.join(os.path.dirname(sys.executable), "hipInfo.exe")
|
||||
if os.path.isfile(_venv_hipinfo):
|
||||
hipinfo = _venv_hipinfo
|
||||
|
|
@ -368,8 +359,7 @@ def _detect_windows_gfx_arch() -> str | None:
|
|||
pass
|
||||
|
||||
# 3. amd-smi fallback -- runtime-only Radeon installs ship amd-smi but no hipinfo.
|
||||
# Gated off on Windows w/o a HIP SDK (avoids the UAC/DiskPart prompt); the arch
|
||||
# arrives via --rocm-gfx / name inference there, so this is only needed when safe.
|
||||
# Gated off on Windows w/o a HIP SDK (UAC prompt); --rocm-gfx / name inference covers it.
|
||||
amd_smi = shutil.which("amd-smi") if _amd_smi_allowed() else None
|
||||
if amd_smi:
|
||||
for _args in (("static", "--asic"), ("list",)):
|
||||
|
|
@ -398,11 +388,9 @@ def _detect_windows_gfx_arch() -> str | None:
|
|||
except Exception:
|
||||
continue
|
||||
|
||||
# 4. Last resort: GPU marketing name via WMI → arch table. Driver-only
|
||||
# hosts (Adrenalin, no HIP SDK) have neither hipinfo nor amd-smi
|
||||
# (amd-smi does not exist on Windows at all), but the display driver
|
||||
# always knows the GPU name. Mirrors setup.ps1's $nameArchTable so a
|
||||
# standalone `studio update` can repair a CPU-only venv on such hosts.
|
||||
# 4. Last resort: GPU marketing name via WMI → arch table. Driver-only hosts
|
||||
# have neither hipinfo nor amd-smi, but the driver knows the GPU name;
|
||||
# mirrors setup.ps1's $nameArchTable so `studio update` can repair CPU venvs.
|
||||
try:
|
||||
result = subprocess.run(
|
||||
[
|
||||
|
|
@ -437,17 +425,17 @@ def _detect_windows_gfx_arch() -> str | None:
|
|||
# prebuilts / AMD Windows torch indexes support; unknown names return None
|
||||
# (callers then fall back cleanly to CPU).
|
||||
_WIN_GPU_NAME_ARCH_TABLE: "list[tuple[str, str]]" = [
|
||||
(r"9070 XT|9080", "gfx1201"), # RDNA 4 (Radeon RX 9070 XT / 9080)
|
||||
(r"9070|9060", "gfx1200"), # RDNA 4 (Radeon RX 9070 / 9060)
|
||||
# RDNA 3.5 (Strix Halo: Radeon 8060S/8050S/8040S iGPU, Ryzen AI Max+)
|
||||
(r"9070 XT|9080", "gfx1201"), # RDNA 4
|
||||
(r"9070|9060", "gfx1200"), # RDNA 4
|
||||
# RDNA 3.5 (Strix Halo)
|
||||
(r"8060S|8050S|8040S|Strix Halo|Ryzen AI Max|AI Max", "gfx1151"),
|
||||
# RDNA 3.5 (Strix/Krackan Point: Radeon 890M/880M iGPU, Ryzen AI 9 HX 370/375)
|
||||
# RDNA 3.5 (Strix/Krackan Point)
|
||||
(
|
||||
r"890M|880M|860M|840M|Strix Point|Krackan|HX 37[05]|AI 9 HX|AI 9 36[05]"
|
||||
r"|AI 7 35[05]|AI 5 34[05]|AI 7 PRO 35|AI 5 33",
|
||||
"gfx1150",
|
||||
),
|
||||
# RDNA 3 desktop / workstation (Navi 31)
|
||||
# RDNA 3 (Navi 31)
|
||||
(r"RX 7900|RX 7800|RX 7700(?!S)|PRO W7900|PRO W7800|PRO W7700", "gfx1100"),
|
||||
(r"RX 7600|RX 7700S|RX 7650|PRO W7600|PRO W7500|PRO V710", "gfx1102"), # Navi 33
|
||||
# RDNA 3 iGPU (Phoenix / Hawk Point)
|
||||
|
|
@ -596,8 +584,7 @@ def _has_rocm_gpu() -> bool:
|
|||
exe = shutil.which(cmd[0])
|
||||
if not exe:
|
||||
continue
|
||||
# Skip amd-smi on Windows w/o a HIP SDK (avoids the UAC/DiskPart prompt);
|
||||
# rely on rocminfo / the sysfs fallback there.
|
||||
# Skip amd-smi on Windows w/o a HIP SDK (UAC prompt); rocminfo/sysfs cover it.
|
||||
if cmd[0] == "amd-smi" and not _amd_smi_allowed():
|
||||
continue
|
||||
try:
|
||||
|
|
@ -2021,10 +2008,8 @@ def install_python_stack() -> int:
|
|||
_wexe = shutil.which(_wcmd[0])
|
||||
if not _wexe:
|
||||
continue
|
||||
# Skip amd-smi on Windows w/o a HIP SDK (avoids the UAC/DiskPart
|
||||
# prompt), as _has_rocm_gpu()/_detect_amd_gfx_codes do. The only loss
|
||||
# is the best-effort "AMD GPU detected" note; ROCm-torch state below
|
||||
# comes from the install itself.
|
||||
# Skip amd-smi w/o a HIP SDK (UAC prompt). Only loss: the best-effort
|
||||
# "AMD GPU detected" note; ROCm-torch state comes from the install.
|
||||
if _wcmd[0] == "amd-smi" and not _amd_smi_allowed():
|
||||
continue
|
||||
try:
|
||||
|
|
|
|||
|
|
@ -745,25 +745,21 @@ if (-not $HasNvidiaSmi) {
|
|||
}
|
||||
}
|
||||
# ── Helper: run amd-smi without triggering a UAC elevation prompt ──
|
||||
# amd-smi on Windows auto-elevates to read GPU/APU memory, surfacing a confusing
|
||||
# DiskPart UAC prompt mid-install (Studio backend amd.py hits the same). RunAsInvoker
|
||||
# forces it (and helpers it spawns) to run un-elevated; on failure the WMI name ->
|
||||
# gfx fallback still resolves the arch.
|
||||
# amd-smi auto-elevates on Windows (confusing DiskPart UAC prompt mid-install);
|
||||
# __COMPAT_LAYER=RunAsInvoker runs it un-elevated (backend amd.py does the same).
|
||||
function Invoke-AmdSmiNoElevate {
|
||||
param(
|
||||
[Parameter(Mandatory = $true, Position = 0)][string]$Exe,
|
||||
[Parameter(Position = 1)][string[]]$SmiArgs = @(),
|
||||
[int]$TimeoutSec = 30
|
||||
)
|
||||
# RunAsInvoker blocks the auto-elevation/UAC prompt; the timeout bounds a flaky
|
||||
# amd-smi that can otherwise spin for minutes (30s mirrors the backend amd.py).
|
||||
# Timeout bounds a flaky amd-smi that can spin for minutes (30s mirrors amd.py).
|
||||
$prevCompat = [Environment]::GetEnvironmentVariable('__COMPAT_LAYER', 'Process')
|
||||
$env:__COMPAT_LAYER = 'RunAsInvoker'
|
||||
try {
|
||||
# [Process]::Start, NOT Start-Process -PassThru: the latter leaves .ExitCode
|
||||
# $null after WaitForExit on PS 5.1, so $LASTEXITCODE (checked by callers)
|
||||
# reads non-zero and kills detection. Async reads drain the pipes (no
|
||||
# deadlock); amd-smi args have no spaces so a plain join is safe.
|
||||
# NOT Start-Process -PassThru: on PS 5.1 it leaves .ExitCode $null, breaking
|
||||
# callers' $LASTEXITCODE checks. Async reads avoid pipe deadlock; amd-smi
|
||||
# args have no spaces so a plain join is safe.
|
||||
$psi = New-Object System.Diagnostics.ProcessStartInfo
|
||||
$psi.FileName = $Exe
|
||||
$psi.Arguments = ($SmiArgs -join ' ')
|
||||
|
|
@ -940,11 +936,11 @@ if (-not $HasNvidiaSmi) {
|
|||
# (gfx120X/110X/1151/1150/103X); unknown names fall back cleanly to CPU.
|
||||
elseif ($ROCmGpuLabel) {
|
||||
$nameArchTable = @(
|
||||
@{ P = "9070 XT|9080"; A = "gfx1201" } # RDNA 4 (Radeon RX 9070 XT / 9080)
|
||||
@{ P = "9070|9060"; A = "gfx1200" } # RDNA 4 (Radeon RX 9070 / 9060)
|
||||
@{ P = "8060S|8050S|8040S|Strix Halo|Ryzen AI Max|AI Max"; A = "gfx1151" } # RDNA 3.5 (Strix Halo: Radeon 8060S/8050S/8040S iGPU, Ryzen AI Max+)
|
||||
@{ P = "890M|880M|860M|840M|Strix Point|Krackan|HX 37[05]|AI 9 HX|AI 9 36[05]|AI 7 35[05]|AI 5 34[05]|AI 7 PRO 35|AI 5 33"; A = "gfx1150" } # RDNA 3.5 (Strix/Krackan Point: Radeon 890M/880M iGPU, Ryzen AI 9 HX 370/375)
|
||||
@{ P = "RX 7900|RX 7800|RX 7700(?!S)|PRO W7900|PRO W7800|PRO W7700"; A = "gfx1100" } # RDNA 3 desktop / workstation (Navi 31)
|
||||
@{ P = "9070 XT|9080"; A = "gfx1201" } # RDNA 4
|
||||
@{ P = "9070|9060"; A = "gfx1200" } # RDNA 4
|
||||
@{ P = "8060S|8050S|8040S|Strix Halo|Ryzen AI Max|AI Max"; A = "gfx1151" } # RDNA 3.5 (Strix Halo)
|
||||
@{ P = "890M|880M|860M|840M|Strix Point|Krackan|HX 37[05]|AI 9 HX|AI 9 36[05]|AI 7 35[05]|AI 5 34[05]|AI 7 PRO 35|AI 5 33"; A = "gfx1150" } # RDNA 3.5 (Strix/Krackan Point)
|
||||
@{ P = "RX 7900|RX 7800|RX 7700(?!S)|PRO W7900|PRO W7800|PRO W7700"; A = "gfx1100" } # RDNA 3 (Navi 31)
|
||||
@{ P = "RX 7600|RX 7700S|RX 7650|PRO W7600|PRO W7500|PRO V710"; A = "gfx1102" } # RDNA 3 (Navi 33)
|
||||
@{ P = "780M|760M|740M|Phoenix|Hawk Point|Z1 Extreme|Z2 Extreme"; A = "gfx1103" } # RDNA 3 iGPU (Phoenix / Hawk Point)
|
||||
@{ P = "RX 6900|RX 6800|RX 6750|RX 6700|PRO W6800|PRO W6900"; A = "gfx1030" } # RDNA 2 (Navi 21) -- gfx103X family
|
||||
|
|
@ -1021,8 +1017,8 @@ if ($HasNvidiaSmi) {
|
|||
substep " Ensure the ROCm compute driver is installed alongside the display driver:" "Yellow"
|
||||
substep " https://rocm.docs.amd.com/en/latest/deploy/windows/index.html" "Yellow"
|
||||
} elseif ($script:ROCmGfxArch) {
|
||||
# Known arch: PyTorch comes from AMD's bundled-runtime ROCm wheels (repo.amd.com),
|
||||
# which ship their own runtime -- HIP SDK optional (only adds the system toolchain).
|
||||
# Known arch: PyTorch comes from AMD's bundled-runtime ROCm wheels
|
||||
# (repo.amd.com) -- HIP SDK optional.
|
||||
Write-Host ""
|
||||
step "gpu" "AMD ROCm ($script:ROCmGfxArch)" "Cyan"
|
||||
substep "Detected: $ROCmGpuLabel" "Cyan"
|
||||
|
|
@ -1474,8 +1470,7 @@ if ($HasROCm) {
|
|||
$rocmVerLabel = if ($script:ROCmVersionFull) { "ROCm $script:ROCmVersionFull" } elseif ($script:ROCmVersion) { "ROCm $script:ROCmVersion" } else { "ROCm (version unknown)" }
|
||||
step "rocm" $rocmVerLabel
|
||||
} elseif ($script:ROCmGfxArch) {
|
||||
# GPU training/inference works via AMD's bundled-runtime ROCm PyTorch wheels;
|
||||
# the HIP SDK is optional (only the system ROCm toolchain).
|
||||
# GPU works via AMD's bundled-runtime ROCm wheels; HIP SDK optional.
|
||||
step "rocm" "GPU via bundled ROCm wheels ($script:ROCmGfxArch) -- HIP SDK optional" "Cyan"
|
||||
} elseif ($ROCmGpuLabel) {
|
||||
step "rocm" "AMD GPU detected -- arch unknown; HIP SDK not found" "Yellow"
|
||||
|
|
@ -2204,10 +2199,9 @@ if ($env:SKIP_STUDIO_BASE -ne "1" -and $env:STUDIO_LOCAL_INSTALL -ne "1") {
|
|||
if ($InstalledVer -and $LatestVer -and ($InstalledVer -eq $LatestVer)) {
|
||||
step "python" "$_PkgName $InstalledVer is up to date"
|
||||
$SkipPythonDeps = $true
|
||||
# ...but not if an AMD GPU is present and installed PyTorch is CPU-only
|
||||
# (host predates ROCm-wheel support, or GPU added later): the fast "up to
|
||||
# date" path would leave the user on CPU torch with Train/Export disabled.
|
||||
# Force the dependency pass so the ROCm wheels get installed.
|
||||
# ...unless an AMD GPU is present but installed torch is CPU-only (host
|
||||
# predates ROCm-wheel support): force the dependency pass so the ROCm
|
||||
# wheels install instead of leaving Train/Export disabled.
|
||||
if ($script:ROCmGfxArch) {
|
||||
$_torchIsCpu = $true
|
||||
try {
|
||||
|
|
@ -2282,12 +2276,10 @@ if ($HasNvidiaSmi) {
|
|||
# Wheels bundle their own ROCm runtime; HIP SDK version is irrelevant.
|
||||
$ROCmGfxArch = $script:ROCmGfxArch
|
||||
$ROCmIndexUrl = $null
|
||||
# Install AMD ROCm PyTorch wheels when ROCm is confirmed OR a gfx arch is known
|
||||
# (name-inferred on Adrenalin-only hosts). The per-arch wheels bundle the runtime
|
||||
# (rocm-sdk-libraries-<gfx>), so torch.cuda.is_available() is True without a HIP
|
||||
# SDK -- which flips Studio out of chat-only (CHAT_ONLY) and enables Train/Export.
|
||||
# Gating on $HasROCm alone left Strix Halo / Radeon 8060S on CPU torch; a failed
|
||||
# ROCm install still falls back to CPU below, so this is safe.
|
||||
# Install ROCm PyTorch when ROCm is confirmed OR a gfx arch is known (name-
|
||||
# inferred, Adrenalin-only hosts): the wheels bundle the runtime, so GPU torch
|
||||
# works without a HIP SDK and Studio leaves chat-only mode. Gating on $HasROCm
|
||||
# alone left Strix Halo on CPU torch; a failed install still falls back to CPU.
|
||||
if (($HasROCm -or $ROCmGfxArch) -and $CuTag -eq "cpu") {
|
||||
$amdIndexBase = if ($env:UNSLOTH_ROCM_WINDOWS_MIRROR) { $env:UNSLOTH_ROCM_WINDOWS_MIRROR.TrimEnd('/') } else { "https://repo.amd.com/rocm/whl" }
|
||||
$archFamilyMap = @{
|
||||
|
|
|
|||
|
|
@ -854,10 +854,9 @@ fi
|
|||
fi
|
||||
|
||||
# ── GPU detection summary (mirrors setup.ps1 step "gpu" block) ──
|
||||
# WSL2 ROCDXG: the system rocminfo enumerates the GPU over /dev/dxg only when
|
||||
# HSA_ENABLE_DXG_DETECTION=1 (a no-op on bare metal), and /opt/rocm/bin can be
|
||||
# off PATH outside login shells (the profile.d drop-in). Seed both before the
|
||||
# probes or a ROCDXG WSL host is misdetected as CPU-only.
|
||||
# WSL2 ROCDXG: rocminfo only sees the GPU with HSA_ENABLE_DXG_DETECTION=1
|
||||
# (no-op on bare metal), and /opt/rocm/bin may be off PATH in non-login shells.
|
||||
# Seed both or a ROCDXG WSL host is misdetected as CPU-only.
|
||||
export HSA_ENABLE_DXG_DETECTION="${HSA_ENABLE_DXG_DETECTION:-1}"
|
||||
if ! command -v rocminfo >/dev/null 2>&1 && [ -x /opt/rocm/bin/rocminfo ]; then
|
||||
PATH="$PATH:/opt/rocm/bin"
|
||||
|
|
@ -918,9 +917,8 @@ elif [ "$_setup_amd_detected" = true ]; then
|
|||
substep "gfx arch from UNSLOTH_ROCM_GFX_ARCH env override: $_setup_gfx"
|
||||
# Name-based arch inference when tools don't report gfx (mirrors setup.ps1 nameArchTable)
|
||||
elif [ -z "$_setup_gfx" ] && [ -n "$_setup_mkt" ]; then
|
||||
# Kept in sync with the table in install.sh (and the PS nameArchTable).
|
||||
# gfx1102 matched BEFORE gfx1100 so the spaceless "RX 7700S" lands on
|
||||
# gfx1102 (bash case has no negative lookahead like the PS tables).
|
||||
# In sync with install.sh's table (and the PS nameArchTable). gfx1102 comes
|
||||
# BEFORE gfx1100 so "RX 7700S" matches it (bash case has no negative lookahead).
|
||||
case "$_setup_mkt" in
|
||||
*"9070 XT"*|*9080*) _setup_gfx="gfx1201" ;; # RDNA 4
|
||||
*9070*|*9060*) _setup_gfx="gfx1200" ;; # RDNA 4
|
||||
|
|
|
|||
|
|
@ -1252,11 +1252,9 @@ _PID_FILE = STUDIO_HOME / "studio.pid"
|
|||
|
||||
|
||||
def _pid_alive(pid: int) -> bool:
|
||||
"""Return True if a process with ``pid`` exists.
|
||||
|
||||
``os.kill(pid, 0)`` raises OSError (WinError 87) for every pid on Windows,
|
||||
so use ``tasklist`` there and the signal-0 probe elsewhere.
|
||||
"""
|
||||
"""True if a process with ``pid`` exists. ``os.kill(pid, 0)`` raises
|
||||
OSError (WinError 87) for every pid on Windows, so use ``tasklist``
|
||||
there and the signal-0 probe elsewhere."""
|
||||
if sys.platform == "win32":
|
||||
try:
|
||||
out = subprocess.run(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue