diff --git a/pyproject.toml b/pyproject.toml index 28ed380266..49bd9d74d0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -326,11 +326,8 @@ cu130onlytorch291 = [ "xformers @ https://download.pytorch.org/whl/cu130/xformers-0.0.33.post2-cp39-abi3-win_amd64.whl ; (sys_platform == 'win32')", ] cu126onlytorch2100 = [ - # The explicit torch pin replicates the xformers 0.0.34 wheel's transitive - # torch==2.10.0 pin on machines where the x86-64-only wheel is skipped - # (Linux aarch64 has real 2.10.0 wheels; Windows ARM64 fails loudly). - # Without it an ARM64 install of the cu126-torch2100 wrapper would proceed - # unpinned and resolve a newer torch. + # Pin torch so ARM64 installs (x86-64-only xformers wheel and its transitive + # torch pin skipped) stay on 2.10 instead of resolving newer. "torch==2.10.0", "xformers @ https://download.pytorch.org/whl/cu126/xformers-0.0.34-cp39-abi3-manylinux_2_28_x86_64.whl ; ('linux' in sys_platform) and (platform_machine == 'AMD64' or platform_machine == 'x86_64')", "xformers @ https://download.pytorch.org/whl/cu126/xformers-0.0.34-cp39-abi3-win_amd64.whl ; (sys_platform == 'win32') and (platform_machine == 'AMD64' or platform_machine == 'x86_64')", @@ -348,14 +345,9 @@ cu130onlytorch2100 = [ "xformers @ https://download.pytorch.org/whl/cu130/xformers-0.0.34-cp39-abi3-win_amd64.whl ; (sys_platform == 'win32') and (platform_machine == 'AMD64' or platform_machine == 'x86_64')", ] cu126onlytorch2110 = [ - # xformers 0.0.35 does not hard-pin torch the way 0.0.34 pinned torch==2.10.0, - # so the torch 2.11 trio is pinned explicitly. Use the +cu126 LOCAL build, not - # a bare torch>=2.11: torch 2.11's default PyPI wheel is CUDA 13.0 (2.10 was - # CUDA 12.x), so a bare resolve would pair a cu130 torch with the cu126 - # xformers below and fail at import. The +cu126 label only resolves from the - # matching download.pytorch.org/whl/cu126 index the installer configures, so - # torch stays on the same CUDA as xformers (or fails loudly if that index is - # absent) instead of silently mismatching. + # xformers 0.0.35 does not pin torch, so pin the trio to the +cu126 local + # build: torch 2.11 defaults to a CUDA-13 PyPI wheel, and only the +cu126 tag + # resolves from the cu126 index alongside the cu126 xformers below. "torch==2.11.0+cu126", "torchvision==0.26.0+cu126", "torchaudio==2.11.0+cu126", @@ -363,9 +355,8 @@ cu126onlytorch2110 = [ "xformers @ https://download.pytorch.org/whl/cu126/xformers-0.0.35-py39-none-win_amd64.whl ; (sys_platform == 'win32') and (platform_machine == 'AMD64' or platform_machine == 'x86_64')", ] cu128onlytorch2110 = [ - # +cu128 local build for the same reason as cu126onlytorch2110 above: torch - # 2.11's default PyPI wheel is CUDA 13.0, so pin to the cu128 index to keep the - # torch trio on the same CUDA as the cu128 xformers wheel. + # Same +cuNNN pin as cu126onlytorch2110: keeps the trio on the cu128 index + # instead of torch 2.11's CUDA-13 PyPI default. "torch==2.11.0+cu128", "torchvision==0.26.0+cu128", "torchaudio==2.11.0+cu128", @@ -373,13 +364,9 @@ cu128onlytorch2110 = [ "xformers @ https://download.pytorch.org/whl/cu128/xformers-0.0.35-py39-none-win_amd64.whl ; (sys_platform == 'win32') and (platform_machine == 'AMD64' or platform_machine == 'x86_64')", ] cu130onlytorch2110 = [ - # Pinned to the +cu130 local build like the cu126/cu128 extras: a bare range - # lets a configured CUDA-12 extra index win (PEP 440 ranks 2.11.0+cu126 above - # the unlabelled 2.11.0), and a ===2.11.0 pin would force-replace the trio on - # machines installed from the official cu130 index (whose wheels carry the - # +cu130 local tag). The exact +cu130 pins accept the official cu130-index - # install as-is and fail loudly anywhere the cu130 index is missing; - # _auto_install.py appends the matching index for every torch2110 CUDA extra. + # Same +cuNNN pin as the cu126/cu128 extras: a bare range lets a CUDA-12 index + # win (PEP 440 ranks +cu126 above unlabelled 2.11.0) and ===2.11.0 would + # force-replace official cu130-index installs. _auto_install.py adds the index. "torch==2.11.0+cu130", "torchvision==0.26.0+cu130", "torchaudio==2.11.0+cu130", diff --git a/tests/test_torch2110_cuda_extras.py b/tests/test_torch2110_cuda_extras.py index 19d40560f4..8e7ff04e09 100644 --- a/tests/test_torch2110_cuda_extras.py +++ b/tests/test_torch2110_cuda_extras.py @@ -1,13 +1,24 @@ +# Unsloth Zoo - Utilities for Unsloth +# Copyright 2023-present Daniel Han-Chen, Michael Han-Chen & the Unsloth team. All rights reserved. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + """Regression guard for the CUDA torch2110 optional-dependency extras. -torch 2.11's DEFAULT PyPI wheel is CUDA 13.0 (torch 2.10 defaulted to CUDA 12.x). -So the CUDA-12 `cuXXXonlytorch2110` extras must pin the torch trio to the matching -`+cuXXX` local build; a bare `torch>=2.11` there would resolve a cu130 torch from -PyPI alongside the cu126/cu128 xformers wheel and fail at import. The cu130 extra is -pinned to +cu130 too: a bare or ===-pinned spec either lets a foreign CUDA index -outrank the intended wheel or force-replaces official cu130-index installs. - -Hermetic: only parses pyproject.toml, no network or install. +The cuXXXonlytorch2110 extras must pin the torch trio to the matching +cuXXX local +build (torch 2.11 defaults to a CUDA-13 PyPI wheel), or resolution mismatches the +xformers wheel. Hermetic: only parses pyproject.toml, no network or install. """ from __future__ import annotations @@ -17,7 +28,7 @@ from pathlib import Path import pytest from packaging.requirements import Requirement -try: # tomllib is stdlib on Python 3.11+; older interpreters need the tomli backport. +try: # tomllib is stdlib on 3.11+; older interpreters need the tomli backport. import tomllib except ModuleNotFoundError: # pragma: no cover - Python 3.9 / 3.10 tomllib = pytest.importorskip("tomli") @@ -33,9 +44,7 @@ def _extra(name: str) -> list[str]: def _reqs(specs: list[str]) -> dict[str, list[Requirement]]: - # Keyed by name -> list: each extra carries one Linux and one Windows - # xformers requirement, so a plain name -> Requirement dict would silently - # drop the Linux entry. + # name -> list: each extra has one Linux and one Windows xformers requirement. out: dict[str, list[Requirement]] = {} for spec in specs: r = Requirement(spec) @@ -45,9 +54,7 @@ def _reqs(specs: list[str]) -> dict[str, list[Requirement]]: @pytest.mark.parametrize("cuda", ["cu126", "cu128", "cu130"]) def test_cuda12_torch2110_pins_matching_local_build(cuda: str): - # Each of torch/torchvision/torchaudio must pin the exact +cuXXX local build - # so it can only resolve from the matching PyTorch CUDA index, never the - # CUDA-13 default on PyPI. + # Each trio member must pin the exact +cuXXX local build. reqs = _reqs(_extra(f"{cuda}onlytorch2110")) for pkg in _TORCH_TRIO: (req,) = reqs[pkg] @@ -65,9 +72,7 @@ def test_cuda12_torch2110_pins_matching_local_build(cuda: str): assert ( f"/whl/{cuda}/xformers-0.0.35-" in r.url ), f"xformers not on the {cuda} index: {r.url}" - # The wheels are x86-64 only, so the markers must exclude other machines - # (e.g. Linux aarch64 such as GB200/DGX Spark, Windows ARM64) where the - # torch trio resolves fine but these wheels would abort the install. + # x86-64-only wheels: markers must exclude aarch64 / ARM64. assert r.marker is not None assert not r.marker.evaluate({"sys_platform": "linux", "platform_machine": "aarch64"}) assert not r.marker.evaluate({"sys_platform": "win32", "platform_machine": "ARM64"}) @@ -78,8 +83,7 @@ def test_cuda12_torch2110_pins_matching_local_build(cuda: str): @pytest.mark.parametrize("cuda", ["cu126", "cu128", "cu130"]) @pytest.mark.parametrize("variant", ["", "ampere-"]) def test_torch2110_wrapper_references_matching_leaf(cuda: str, variant: str): - # The six public wrappers must pull in the usual huggingface + bitsandbytes - # pair and reference the internal leaf of the SAME CUDA version. + # Wrappers pull huggingface + bitsandbytes and the leaf of the same CUDA version. specs = _extra(f"{cuda}-{variant}torch2110") assert specs == [ "unsloth[huggingface]", @@ -90,11 +94,8 @@ def test_torch2110_wrapper_references_matching_leaf(cuda: str, variant: str): @pytest.mark.parametrize("cuda", ["cu126", "cu128", "cu130"]) def test_cuda12_torch2100_keeps_torch_pinned_off_x86(cuda: str): - # The torch2100 leaves used to rely on the xformers 0.0.34 wheel's transitive - # torch==2.10.0 pin. Now that the x86-64-only wheels carry platform_machine - # markers, the leaf must pin torch explicitly so an ARM64 install stays on - # torch 2.10 (Linux aarch64 wheels exist) or fails loudly (Windows ARM64) - # instead of resolving an unpinned newer torch. + # Now the xformers wheels carry x86-64 markers, the leaf must pin torch + # explicitly so ARM64 installs stay on 2.10 instead of resolving newer. reqs = _reqs(_extra(f"{cuda}onlytorch2100")) (torch_req,) = reqs["torch"] assert str(torch_req.specifier) == "==2.10.0", ( diff --git a/unsloth/_auto_install.py b/unsloth/_auto_install.py index a7b29b4616..2b6ae43eb8 100644 --- a/unsloth/_auto_install.py +++ b/unsloth/_auto_install.py @@ -42,10 +42,7 @@ else: raise RuntimeError(f"Torch = {v} too new!") if v > V('2.6.9') and cuda not in ("11.8", "12.6", "12.8", "13.0"): raise RuntimeError(f"CUDA = {cuda} not supported!") if v >= V('2.10.0') and cuda not in ("12.6", "12.8", "13.0"): raise RuntimeError(f"Torch = {v} requires CUDA 12.6, 12.8, or 13.0! Got CUDA = {cuda}") x = x.format(cuda.replace(".", ""), "-ampere" if False else "") # is_ampere is broken due to flash-attn -# The CUDA torch2110 extras pin the torch trio to the exact +cuNNN local build, -# which only resolves from the matching PyTorch CUDA index (a bare spec would let -# a configured foreign CUDA index outrank the intended wheel, since PEP 440 ranks -# any local build above the unlabelled release), so add that index for every -# CUDA torch 2.11 environment. +# The torch2110 extras pin the trio to +cuNNN local builds, which only resolve +# from the matching PyTorch CUDA index, so add that index for torch 2.11. extra_index = f' --extra-index-url https://download.pytorch.org/whl/cu{cuda.replace(".", "")}' if (x.endswith('-torch2110') and cuda in ("12.6", "12.8", "13.0")) else '' print(f'pip install --upgrade pip setuptools wheel && pip install --no-deps git+https://github.com/unslothai/unsloth-zoo.git && pip install "unsloth[{x}] @ git+https://github.com/unslothai/unsloth.git" --no-build-isolation{extra_index}') \ No newline at end of file