-
-
Notifications
You must be signed in to change notification settings - Fork 6.1k
install: let UNSLOTH_TORCH_INDEX_FAMILY / _URL override CUDA wheel detection #6692
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 4 commits
138c094
b02a609
997155d
0c0e2cb
5d69193
5736d5d
5a017ec
d2d5f90
4a5baba
f6c5e46
c290cac
4018a69
37ce507
c1e956c
7845276
00781b7
ffd11cc
7814e2c
881ec95
e1f05c2
0275432
3b17c9b
1213135
753ef2f
35c62da
6bbc692
257ca41
5c93ffd
0d48584
5ed0737
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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. | ||
|
|
@@ -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 | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Useful? React with 👍 / 👎. |
||
| fi | ||
| [ "$_torch_index_pinned" = true ] || _maybe_bootstrap_rocm_wsl || true | ||
|
|
||
| TORCH_INDEX_URL=$(get_torch_index_url) | ||
|
|
||
|
|
@@ -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" ;; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
This pattern checks the whole URL, so a custom mirror whose base path contains a Useful? React with 👍 / 👎.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed. install.sh's torch>=2.11 constraint case now switches on the index leaf ( There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
With a full Useful? React with 👍 / 👎. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
For a newly supported pinned gfx override such as Useful? React with 👍 / 👎. |
||
| 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 | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When a caller pins an AMD per-arch index, for example Useful? React with 👍 / 👎. |
||
| case "$TORCH_INDEX_URL" in | ||
| */rocm*) | ||
| if _has_amd_rocm_gpu && command -v rocminfo >/dev/null 2>&1 && \ | ||
|
|
@@ -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" | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -933,11 +933,20 @@ def _detect_cuda_torch_index_url() -> str: | |
|
|
||
| Mirrors install.sh::get_torch_index_url's CUDA ladder so `studio update` | ||
| repairs to the same wheel family a fresh `curl | sh` install would pick. | ||
| Probes nvidia-smi (PATH, then /usr/bin/nvidia-smi) and parses both the | ||
| legacy "CUDA Version:" and the newer "CUDA UMD Version:" spellings. | ||
| Defaults to cu126 when nvidia-smi is missing or the version is unreadable | ||
| (e.g. NVIDIA detected only via the /proc/driver/nvidia/gpus fallback). | ||
| Honours the same explicit overrides first (UNSLOTH_TORCH_INDEX_URL / | ||
| UNSLOTH_TORCH_INDEX_FAMILY) so a headless / container / CI install never lets | ||
| the host GPU decide the wheel family. Otherwise probes nvidia-smi (PATH, then | ||
| /usr/bin/nvidia-smi) and parses both the legacy "CUDA Version:" and the newer | ||
| "CUDA UMD Version:" spellings. Defaults to cu126 when nvidia-smi is missing or | ||
| the version is unreadable (e.g. NVIDIA detected only via the | ||
| /proc/driver/nvidia/gpus fallback). | ||
| """ | ||
| _override_url = os.environ.get("UNSLOTH_TORCH_INDEX_URL", "").strip() | ||
| if _override_url: | ||
| return _override_url.rstrip("/") | ||
| _override_family = os.environ.get("UNSLOTH_TORCH_INDEX_FAMILY", "").strip() | ||
| if _override_family: | ||
| return f"{_PYTORCH_WHL_BASE}/{_override_family.strip('/')}" | ||
|
Comment on lines
+1176
to
+1178
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
In standalone Useful? React with 👍 / 👎. |
||
| exe = shutil.which("nvidia-smi") | ||
| if not exe and os.path.isfile("/usr/bin/nvidia-smi"): | ||
| exe = "/usr/bin/nvidia-smi" | ||
|
|
@@ -972,6 +981,30 @@ def _detect_cuda_torch_index_url() -> str: | |
| return f"{_PYTORCH_WHL_BASE}/{tag}" | ||
|
|
||
|
|
||
| def _explicit_torch_index_url() -> "str | None": | ||
| """The wheel index URL pinned via UNSLOTH_TORCH_INDEX_URL / _FAMILY, else None. | ||
|
|
||
| Lets the CUDA/ROCm repair helpers honour the exact pinned family/URL instead | ||
| of re-probing the GPU. Mirrors install.sh::get_torch_index_url's override. | ||
| """ | ||
| url = os.environ.get("UNSLOTH_TORCH_INDEX_URL", "").strip() | ||
| if url: | ||
| return url.rstrip("/") | ||
| family = os.environ.get("UNSLOTH_TORCH_INDEX_FAMILY", "").strip() | ||
| if family: | ||
| return f"{_PYTORCH_WHL_BASE}/{family.strip('/')}" | ||
| return None | ||
|
|
||
|
|
||
| def _explicit_rocm_torch_index_url() -> "str | None": | ||
| """The pinned wheel index URL when it names a ROCm family (rocm*/gfx*), else None.""" | ||
| url = _explicit_torch_index_url() | ||
| if url is None: | ||
| return None | ||
| leaf = url.rstrip("/").rsplit("/", 1)[-1].lower() | ||
| return url if leaf.startswith(("rocm", "gfx")) else None | ||
|
Comment on lines
+1231
to
+1234
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
For an explicit Useful? React with 👍 / 👎.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed. Added _ensure_verbatim_torch_index: for an explicit UNSLOTH_TORCH_INDEX_URL whose leaf is not rocm/gfx/cu/cpu (a private mirror ending in /simple, /current, ...), when the marker differs from the pin it reinstalls torch/vision/audio verbatim from that URL (exclusive --index-url, bare specs). With no marker it is a no-op so an old venv is not blindly force-reinstalled. This honors "URL wins verbatim" instead of returning None and re-probing the GPU. |
||
|
|
||
|
|
||
| def _ensure_cuda_torch() -> None: | ||
| """Repair a venv whose torch is a ROCm build on an NVIDIA host. | ||
|
|
||
|
|
@@ -1004,7 +1037,10 @@ def _ensure_cuda_torch() -> None: | |
| return | ||
| # Only NVIDIA hosts should carry CUDA torch. _has_usable_nvidia_gpu() | ||
| # covers the /proc/driver/nvidia/gpus fallback when nvidia-smi is absent. | ||
| if not _has_usable_nvidia_gpu(): | ||
| # An explicit CUDA wheel-index pin (headless / container / CI cross-install) | ||
| # commits to CUDA wheels regardless of whether a GPU is visible here, so it | ||
| # overrides the GPU-presence gate. | ||
| if not _has_usable_nvidia_gpu() and _explicit_torch_index_url() is None: | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
In standalone Useful? React with 👍 / 👎. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When Useful? React with 👍 / 👎. |
||
| return | ||
|
|
||
| # Classify the installed torch: "hip" (ROCm build -- the poisoning | ||
|
|
@@ -1247,7 +1283,9 @@ def _ensure_rocm_torch() -> None: | |
| # an incompatible wheel. Use HIP_VISIBLE_DEVICES for the runtime target. | ||
| _strix_override_url: "str | None" = None | ||
| _strix_override_pkgs: "tuple[str, str, str] | None" = None | ||
| if ver < (7, 2): | ||
| # An explicit ROCm wheel-index pin is authoritative: never auto-reroute it to | ||
| # the AMD per-gfx index (the caller already chose the family/URL). | ||
| if ver < (7, 2) and _explicit_rocm_torch_index_url() is None: | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When a ROCm/GFX index is pinned, this guard suppresses the Strix auto-reroute, but the pinned index is only installed later inside the Useful? React with 👍 / 👎. |
||
| gfx_codes = _detect_amd_gfx_codes() | ||
| _strix_gfx = {"gfx1151", "gfx1150"} | ||
| _detected_strix = _strix_gfx.intersection(gfx_codes) | ||
|
|
@@ -1310,23 +1348,35 @@ def _ensure_rocm_torch() -> None: | |
| ) | ||
| rocm_torch_ready = True | ||
| elif not has_hip_torch: | ||
| # Select best matching wheel tag (newest ROCm version <= installed) | ||
| tag = next( | ||
| ( | ||
| t | ||
| for (maj, mn), t in sorted(_ROCM_TORCH_INDEX.items(), reverse = True) | ||
| if ver >= (maj, mn) | ||
| ), | ||
| None, | ||
| ) | ||
| # Honour an explicit ROCm wheel-index pin verbatim instead of re-detecting | ||
| # the host ROCm version; otherwise select the best wheel tag (newest ROCm | ||
| # version <= installed). gfx*/rocm7.2 indexes serve torch 2.11+, so match | ||
| # the constraints to the index leaf when overridden. | ||
| _override_idx = _explicit_rocm_torch_index_url() | ||
| if _override_idx is not None: | ||
| index_url = _override_idx | ||
|
Comment on lines
+1960
to
+1962
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
The explicit ROCm index is only consulted here, after Useful? React with 👍 / 👎. |
||
| tag = index_url.rstrip("/").rsplit("/", 1)[-1].lower() | ||
|
Comment on lines
+1960
to
+1963
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
In standalone Useful? React with 👍 / 👎.
Comment on lines
+1960
to
+1963
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
The pinned ROCm index is only consulted inside the Useful? React with 👍 / 👎. |
||
| else: | ||
| tag = next( | ||
| ( | ||
| t | ||
| for (maj, mn), t in sorted(_ROCM_TORCH_INDEX.items(), reverse = True) | ||
| if ver >= (maj, mn) | ||
| ), | ||
| None, | ||
| ) | ||
| if tag is None: | ||
| print(f" No PyTorch wheel for ROCm {ver[0]}.{ver[1]} -- " f"skipping torch reinstall") | ||
| else: | ||
| index_url = f"{_PYTORCH_WHL_BASE}/{tag}" | ||
| print(f" ROCm {ver[0]}.{ver[1]} -- installing torch from {index_url}") | ||
| _torch_pkg, _vision_pkg, _audio_pkg = _ROCM_TORCH_PKG_SPECS.get( | ||
| tag, _ROCM_TORCH_PKG_SPECS["_default"] | ||
| ) | ||
| if _override_idx is None: | ||
| index_url = f"{_PYTORCH_WHL_BASE}/{tag}" | ||
| print(f" ROCm torch -- installing from {index_url}") | ||
| if tag.startswith("gfx"): | ||
| _torch_pkg, _vision_pkg, _audio_pkg = _ROCM_TORCH_PKG_SPECS["rocm7.2"] | ||
| else: | ||
| _torch_pkg, _vision_pkg, _audio_pkg = _ROCM_TORCH_PKG_SPECS.get( | ||
| tag, _ROCM_TORCH_PKG_SPECS["_default"] | ||
| ) | ||
| pip_install( | ||
| f"ROCm torch ({tag})", | ||
| "--force-reinstall", | ||
|
|
@@ -1420,6 +1470,23 @@ def _infer_no_torch() -> bool: | |
| # 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" | ||
|
Comment on lines
+2097
to
+2098
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
The new override handling classifies Useful? React with 👍 / 👎.
Comment on lines
+2097
to
+2098
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When Useful? React with 👍 / 👎. |
||
| elif _idx_leaf == "cpu": | ||
| _TORCH_BACKEND = "cpu" | ||
|
Comment on lines
+2099
to
+2100
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When standalone update runs with Useful? React with 👍 / 👎. |
||
| elif _idx_leaf.startswith("cu"): | ||
| _TORCH_BACKEND = "cuda" | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
For a standalone Useful? React with 👍 / 👎. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When Useful? React with 👍 / 👎.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed. The _TORCH_BACKEND derivation now uses _is_cuda_family_leaf (^cu[0-9]), so current/custom fall through to backend "" and the GPU is probed. Also aligned install.sh to brand cuda only on cu[0-9]* (unset otherwise). Added tests. |
||
|
|
||
|
|
||
| def _torch_step_label(suffix: str) -> str: | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When
UNSLOTH_TORCH_INDEX_URLpoints at an AMD per-gfx index such ashttps://repo.amd.com/rocm/whl/gfx1151, this return makesTORCH_INDEX_URLend ingfx1151without taking the Strix reroute path that normally also raisesTORCH_CONSTRAINTtotorch>=2.11.0,<2.12.0. The only generic constraint adjustment below handles*/rocm7.2, so the fresh install still asks that per-gfx index fortorch>=2.4,<2.11.0even though the existing Strix path documents that this index serves the torch 2.11+ ROCm build, causing the pinned full-URL install to fail to resolve.Useful? React with 👍 / 👎.