install: make the torch-index override authoritative across ROCm paths

Address review feedback on the override added in this PR so a pinned index is
honoured everywhere, not just in get_torch_index_url:

- Skip the WSL ROCm bootstrap (root privilege + large downloads, probes
  /dev/dxg) when UNSLOTH_TORCH_INDEX_URL / _FAMILY is set; it previously ran
  before the override was consulted.
- Skip the Radeon/Strix rerouting (which re-probes the GPU and overwrites the
  resolved URL with repo.radeon.com / repo.amd.com) when the index is pinned, so
  an explicit ROCm override (e.g. UNSLOTH_TORCH_INDEX_FAMILY=rocm6.4) is kept.
- install_python_stack.py: derive _TORCH_BACKEND from the override when
  UNSLOTH_TORCH_BACKEND is unset (standalone studio update), so _ensure_rocm_torch
  / _ensure_cuda_torch repair to the requested family instead of re-detecting.
- Strip ALL leading/trailing slashes in the shell override to match the Python
  side (avoids 404s on strict pip proxies).

Adds test cases for double-slash and leading/trailing-slash overrides.
This commit is contained in:
Daniel Han 2026-06-26 05:28:36 +00:00
commit b02a609af5
3 changed files with 50 additions and 4 deletions

View file

@ -1994,11 +1994,18 @@ get_torch_index_url() {
# 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
echo "${UNSLOTH_TORCH_INDEX_URL%/}"; return
# 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#/}"
echo "$_base/${_family%/}"; return
_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
@ -2396,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)
@ -2424,7 +2440,11 @@ 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 && \
@ -2505,6 +2525,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"

View file

@ -1429,6 +1429,23 @@ NO_TORCH = _infer_no_torch()
# GPU detection. Values: "cuda", "rocm", or "cpu". Empty means unknown
# (standalone `unsloth studio update` runs, where we re-detect normally).
_TORCH_BACKEND: str = os.environ.get("UNSLOTH_TORCH_BACKEND", "").lower()
# When install.sh did not run (standalone `unsloth studio update`) but the caller
# pinned the wheel index explicitly, derive the backend from that override so the
# CUDA/ROCm repair helpers honour it instead of re-probing the GPU and possibly
# reinstalling a different family. Classify on the final URL/family segment,
# mirroring install.sh's UNSLOTH_TORCH_BACKEND case.
if not _TORCH_BACKEND:
_idx_override = (
os.environ.get("UNSLOTH_TORCH_INDEX_URL", "").strip()
or os.environ.get("UNSLOTH_TORCH_INDEX_FAMILY", "").strip()
)
_idx_leaf = _idx_override.rstrip("/").rsplit("/", 1)[-1].lower()
if _idx_leaf.startswith(("rocm", "gfx")):
_TORCH_BACKEND = "rocm"
elif _idx_leaf == "cpu":
_TORCH_BACKEND = "cpu"
elif _idx_leaf.startswith("cu"):
_TORCH_BACKEND = "cuda"
def _torch_step_label(suffix: str) -> str:

View file

@ -415,6 +415,14 @@ assert_eq "url override beats family override -> url" "https://mirror.example.co
_result=$(UNSLOTH_TORCH_INDEX_FAMILY="" UNSLOTH_TORCH_INDEX_URL="" run_func "none")
assert_eq "empty overrides ignored -> detected cpu" "https://download.pytorch.org/whl/cpu" "$_result"
# 46) ALL trailing slashes are stripped from a URL override (not just one).
_result=$(UNSLOTH_TORCH_INDEX_URL="https://mirror.example.com/whl/cu128///" run_func "none")
assert_eq "url override double slash stripped" "https://mirror.example.com/whl/cu128" "$_result"
# 47) Leading and trailing slashes stripped from a family override.
_result=$(UNSLOTH_TORCH_INDEX_FAMILY="//cu128//" run_func "none")
assert_eq "family override slashes stripped" "https://download.pytorch.org/whl/cu128" "$_result"
rm -f "$_FUNC_FILE"
rm -rf "$_FAKE_SMI_DIR"
rm -rf "$_TOOLS_DIR"