Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 52 additions & 6 deletions install.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -1939,6 +1939,17 @@ exit 0
# Mirrors Get-PytorchCudaTag in setup.ps1.
function Get-TorchIndexUrl {
$baseUrl = if ($env:UNSLOTH_PYTORCH_MIRROR) { $env:UNSLOTH_PYTORCH_MIRROR.TrimEnd('/') } else { "https://download.pytorch.org/whl" }
# Explicit pin -- skip ALL GPU probing when the caller names the wheel index
# (headless / CI / cross-install). Matches install.sh::get_torch_index_url and
# install_python_stack.py: UNSLOTH_TORCH_INDEX_URL wins (full URL, verbatim);
# UNSLOTH_TORCH_INDEX_FAMILY is the convenience leaf (cpu, cu128, rocm6.4, ...)
# appended to the mirror base so UNSLOTH_PYTORCH_MIRROR is still honoured.
if (-not [string]::IsNullOrWhiteSpace($env:UNSLOTH_TORCH_INDEX_URL)) {
return $env:UNSLOTH_TORCH_INDEX_URL.Trim().TrimEnd('/')
}
if (-not [string]::IsNullOrWhiteSpace($env:UNSLOTH_TORCH_INDEX_FAMILY)) {
return "$baseUrl/$($env:UNSLOTH_TORCH_INDEX_FAMILY.Trim().Trim('/'))"
}
if (-not $NvidiaSmiExe) { return "$baseUrl/cpu" }
try {
$output = Invoke-NvidiaSmiBounded $NvidiaSmiExe
Expand Down Expand Up @@ -2016,6 +2027,11 @@ exit 0
} catch { return $null }
}

# An explicit UNSLOTH_TORCH_INDEX_URL / _FAMILY pin is authoritative: the AMD
# ROCm reroute below must not rewrite it (e.g. a deliberate cpu pin on an AMD
# host, or a pinned ROCm family we already resolved in Get-TorchIndexUrl).
$TorchIndexPinned = (-not [string]::IsNullOrWhiteSpace($env:UNSLOTH_TORCH_INDEX_URL)) -or `
(-not [string]::IsNullOrWhiteSpace($env:UNSLOTH_TORCH_INDEX_FAMILY))
$TorchIndexUrl = Get-TorchIndexUrl

# ── GPU arch → newest compatible Windows ROCm wheel release ──
Expand All @@ -2027,7 +2043,9 @@ exit 0
# Override with UNSLOTH_ROCM_WINDOWS_MIRROR for air-gapped / mirror installs.
$ROCmIndexUrl = $null
$ROCmTorchFloor = $null
if (($HasROCm -or $ROCmGfxArch) -and $TorchIndexUrl -like "*/cpu" -and -not $SkipTorch) {
$PinnedRocmVisionSpec = $null
$PinnedRocmAudioSpec = $null
if (-not $TorchIndexPinned -and ($HasROCm -or $ROCmGfxArch) -and $TorchIndexUrl -like "*/cpu" -and -not $SkipTorch) {
$amdIndexBase = if ($env:UNSLOTH_ROCM_WINDOWS_MIRROR) { $env:UNSLOTH_ROCM_WINDOWS_MIRROR.TrimEnd('/') } else { "https://repo.amd.com/rocm/whl" }
$archFamilyMap = @{
"gfx1201" = "gfx120X-all"; "gfx1200" = "gfx120X-all" # RDNA 4
Expand Down Expand Up @@ -2077,6 +2095,30 @@ exit 0
}
}

# An explicit gfx*/rocm pin skips the auto-reroute above, but the generic
# CPU/CUDA install below would use torch>=2.4,<2.11 and pull a known-bad wheel
# on the gfx115x/gfx120x/rocm>=7.2 indexes (the torch._C._grouped_mm null-ptr
# bug). Route a pinned ROCm index through the ROCm install path with the same
# 2.11 floor/companions the unpinned reroute derives from the gfx arch.
if ($TorchIndexPinned -and -not $ROCmIndexUrl -and -not $SkipTorch) {
$_pinLeaf = ($TorchIndexUrl.TrimEnd('/') -split '/')[-1].ToLower()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Use .ToLowerInvariant() instead of .ToLower() to prevent potential locale-specific issues (such as the Turkish 'I' bug) when parsing the wheel index URL leaf. This also ensures consistency with the implementation of Get-TorchIndexLeaf in setup.ps1.

        $_pinLeaf = ($TorchIndexUrl.TrimEnd('/') -split '/')[-1].ToLowerInvariant()

$_pinRocm211 = $false
if ($_pinLeaf -match '^rocm(\d+)\.(\d+)') {
$_pinRocm211 = ([int]$Matches[1] -gt 7) -or ([int]$Matches[1] -eq 7 -and [int]$Matches[2] -ge 2)
}
if ($_pinLeaf -like 'gfx*' -or $_pinRocm211) {
$ROCmIndexUrl = $TorchIndexUrl
$ROCmTorchFloor = "torch>=2.11.0,<2.12.0"
$PinnedRocmVisionSpec = "torchvision>=0.26.0,<0.27.0"
$PinnedRocmAudioSpec = "torchaudio>=2.11.0,<2.12.0"
substep "pinned ROCm index ($_pinLeaf) -- enforcing $ROCmTorchFloor" "Cyan"
} elseif ($_pinLeaf -like 'rocm*') {
# Older rocm (<=7.1) ships torch <2.11; route via the ROCm path with the
# default floor so the pinned family resolves its own wheels.
$ROCmIndexUrl = $TorchIndexUrl
}
}

if ($ROCmIndexUrl) {
$TorchIndexFamily = "rocm"
} else {
Expand Down Expand Up @@ -2189,18 +2231,22 @@ exit 0
$torchSpec = if ($ROCmTorchFloor) { $ROCmTorchFloor } else { "torch" }
# Pin the companions to match $torchSpec; bare names can resolve an
# ABI-incompatible torchvision/torchaudio on AMD's per-arch index.
$visionSpec = if ($ROCmGfxArch -and $torchvisionFloorMap.ContainsKey($ROCmGfxArch)) { $torchvisionFloorMap[$ROCmGfxArch] } else { "torchvision" }
$audioSpec = if ($ROCmGfxArch -and $torchaudioFloorMap.ContainsKey($ROCmGfxArch)) { $torchaudioFloorMap[$ROCmGfxArch] } else { "torchaudio" }
$visionSpec = if ($PinnedRocmVisionSpec) { $PinnedRocmVisionSpec } elseif ($ROCmGfxArch -and $torchvisionFloorMap -and $torchvisionFloorMap.ContainsKey($ROCmGfxArch)) { $torchvisionFloorMap[$ROCmGfxArch] } else { "torchvision" }
$audioSpec = if ($PinnedRocmAudioSpec) { $PinnedRocmAudioSpec } elseif ($ROCmGfxArch -and $torchaudioFloorMap -and $torchaudioFloorMap.ContainsKey($ROCmGfxArch)) { $torchaudioFloorMap[$ROCmGfxArch] } else { "torchaudio" }
$torchInstallExit = Invoke-InstallCommandRetry -Label "install PyTorch (AMD ROCm)" { uv pip install --python $VenvPython --force-reinstall --index-url $ROCmIndexUrl $torchSpec $visionSpec $audioSpec }
if ($torchInstallExit -ne 0) {
# Transient AMD-index failure: fall back to a CPU base so the install
# still completes; Studio setup retries ROCm afterwards.
# Use an explicit CPU index: in the unpinned AMD path $TorchIndexUrl is
# already */cpu, but for a pinned ROCm index it IS the ROCm mirror, so
# reusing it here would just retry the failing ROCm index, not fall back.
$CpuFallbackIndexUrl = if ($env:UNSLOTH_PYTORCH_MIRROR) { "$($env:UNSLOTH_PYTORCH_MIRROR.TrimEnd('/'))/cpu" } else { "https://download.pytorch.org/whl/cpu" }
substep "ROCm PyTorch install failed (exit $torchInstallExit); using a CPU base, Studio setup retries ROCm." "Yellow"
# --force-reinstall: a failed ROCm install can leave an unpinned ROCm
# torch (e.g. 2.10.0+rocm on gfx110X/gfx90a) that still satisfies the CPU
# torch>= range, so without it uv would keep the ROCm build and only swap
# the companions -- a mismatched venv the flavor-repair block won't fix.
$torchInstallExit = Invoke-InstallCommandRetry -Label "install PyTorch (CPU fallback)" { uv pip install --python $VenvPython --force-reinstall "torch>=2.4,<2.11.0" torchvision torchaudio --index-url $TorchIndexUrl }
$torchInstallExit = Invoke-InstallCommandRetry -Label "install PyTorch (CPU fallback)" { uv pip install --python $VenvPython --force-reinstall "torch>=2.4,<2.11.0" torchvision torchaudio --index-url $CpuFallbackIndexUrl }
if ($torchInstallExit -ne 0) {
Write-Host "[ERROR] Failed to install PyTorch (ROCm and CPU base both failed, exit code $torchInstallExit)" -ForegroundColor Red
return (Exit-InstallFailure "Failed to install PyTorch (exit code $torchInstallExit)" $torchInstallExit)
Expand Down Expand Up @@ -2310,8 +2356,8 @@ exit 0
$rocmSpec = if ($ROCmTorchFloor) { $ROCmTorchFloor } else { "torch" }
# Pin companions like the fresh ROCm path (bare names can pull an
# ABI-incompatible torchvision/torchaudio from the per-arch index).
$visionSpec = if ($ROCmGfxArch -and $torchvisionFloorMap.ContainsKey($ROCmGfxArch)) { $torchvisionFloorMap[$ROCmGfxArch] } else { "torchvision" }
$audioSpec = if ($ROCmGfxArch -and $torchaudioFloorMap.ContainsKey($ROCmGfxArch)) { $torchaudioFloorMap[$ROCmGfxArch] } else { "torchaudio" }
$visionSpec = if ($PinnedRocmVisionSpec) { $PinnedRocmVisionSpec } elseif ($ROCmGfxArch -and $torchvisionFloorMap -and $torchvisionFloorMap.ContainsKey($ROCmGfxArch)) { $torchvisionFloorMap[$ROCmGfxArch] } else { "torchvision" }
$audioSpec = if ($PinnedRocmAudioSpec) { $PinnedRocmAudioSpec } elseif ($ROCmGfxArch -and $torchaudioFloorMap -and $torchaudioFloorMap.ContainsKey($ROCmGfxArch)) { $torchaudioFloorMap[$ROCmGfxArch] } else { "torchaudio" }
substep "PyTorch flavor mismatch (installed $installedTorchTag, need ROCm) -- reinstalling correct build..." "Yellow"
$torchFixExit = Invoke-InstallCommand { uv pip install --python $VenvPython --force-reinstall --index-url $ROCmIndexUrl $rocmSpec $visionSpec $audioSpec }
if ($torchFixExit -ne 0) {
Expand Down
45 changes: 42 additions & 3 deletions install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -1985,6 +1985,28 @@ _has_usable_nvidia_gpu() {
get_torch_index_url() {
_base="${UNSLOTH_PYTORCH_MIRROR:-https://download.pytorch.org/whl}"
_base="${_base%/}"
# Explicit override -- skip ALL GPU probing when the caller pins the wheel
# index. Headless / container / CI builds (and anyone cross-installing for a
# different target) must not let the build host's GPU -- or the lack of one --
# decide the wheel family. This is the same "tell the build, don't ask the
# hardware" approach the Docker base image and vLLM/SGLang's Dockerfiles take.
# UNSLOTH_TORCH_INDEX_URL wins (full URL, verbatim); UNSLOTH_TORCH_INDEX_FAMILY
# is the convenience form (cpu, cu124, cu126, cu128, cu130, rocm6.4, ...)
# appended to the mirror base so UNSLOTH_PYTORCH_MIRROR is still honoured.
if [ -n "${UNSLOTH_TORCH_INDEX_URL:-}" ]; then
# Strip ALL trailing slashes (match the Python side's .rstrip("/") and the
# Strix mirror handling below) -- a double/triple-slash URL 404s on strict
# pip proxies (artifactory, sonatype).
_url="${UNSLOTH_TORCH_INDEX_URL}"
while [ "${_url%/}" != "$_url" ]; do _url="${_url%/}"; done
echo "$_url"; return
fi
if [ -n "${UNSLOTH_TORCH_INDEX_FAMILY:-}" ]; then
_family="${UNSLOTH_TORCH_INDEX_FAMILY}"
while [ "${_family#/}" != "$_family" ]; do _family="${_family#/}"; done
while [ "${_family%/}" != "$_family" ]; do _family="${_family%/}"; done
echo "$_base/$_family"; return
fi
# macOS: always CPU (no CUDA support)
case "$(uname -s)" in Darwin) echo "$_base/cpu"; return ;; esac
# Try nvidia-smi -- require the binary to actually list a usable GPU.
Expand Down Expand Up @@ -2381,7 +2403,16 @@ _maybe_bootstrap_rocm_wsl() {
[ -n "$_rw_tmp" ] && rm -f "$_rw_tmp"
return 0
}
_maybe_bootstrap_rocm_wsl || true
# When the caller pins the wheel index (UNSLOTH_TORCH_INDEX_URL / _FAMILY),
# honour it everywhere downstream: skip the WSL ROCm bootstrap (which can run
# sudo + large downloads after probing /dev/dxg) and the Radeon/Strix rerouting
# below (which would re-probe the GPU and overwrite the pinned URL). A headless /
# container / CI build must get exactly the index it asked for.
_torch_index_pinned=false
if [ -n "${UNSLOTH_TORCH_INDEX_URL:-}" ] || [ -n "${UNSLOTH_TORCH_INDEX_FAMILY:-}" ]; then
_torch_index_pinned=true
fi
[ "$_torch_index_pinned" = true ] || _maybe_bootstrap_rocm_wsl || true

TORCH_INDEX_URL=$(get_torch_index_url)

Expand All @@ -2400,16 +2431,23 @@ case "$_torch_index_leaf" in
*) export UNSLOTH_TORCH_BACKEND="cuda" ;;
esac

# rocm7.2 ships torch 2.11.0 -- adjust the constraint to allow it.
# rocm7.2 and the AMD per-gfx indexes (repo.amd.com/.../gfxNNNN) ship torch
# 2.11.0 -- adjust the constraint to allow it. This also covers a pinned full-URL
# or family override (e.g. UNSLOTH_TORCH_INDEX_URL=.../gfx1151) that returns early
# above and so never hits the Strix reroute that otherwise raises this constraint.
# All other ROCm tags and CUDA stay within <2.11.0.
case "$TORCH_INDEX_URL" in
*/rocm7.2) TORCH_CONSTRAINT="torch>=2.11.0,<2.12.0" ;;
*/rocm7.2|*/gfx*) TORCH_CONSTRAINT="torch>=2.11.0,<2.12.0" ;;
esac

# Auto-detect GPU for AMD ROCm based
# get_torch_index_url must have chosen */rocm*
# (gfx in rocminfo or amd-smi list). Then require rocminfo "Marketing Name:.*Radeon".
# Skipped entirely when the index is pinned: an explicit override (even a ROCm
# one like UNSLOTH_TORCH_INDEX_FAMILY=rocm6.4) must not be rerouted to the
# Radeon/Strix repos by GPU probing.
_amd_gpu_radeon=false
if [ "$_torch_index_pinned" = false ]; then
case "$TORCH_INDEX_URL" in
*/rocm*)
if _has_amd_rocm_gpu && command -v rocminfo >/dev/null 2>&1 && \
Expand Down Expand Up @@ -2490,6 +2528,7 @@ case "$TORCH_INDEX_URL" in
fi
;;
esac
fi # _torch_index_pinned guard (Radeon + Strix reroute)
_TAURI_TORCH_INDEX_FAMILY=$(_tauri_torch_index_family "$TORCH_INDEX_URL")
if [ "$_amd_gpu_radeon" = true ] && [ "$SKIP_TORCH" = false ]; then
_TAURI_TORCH_INDEX_FAMILY="radeon"
Expand Down
Loading
Loading