Compare commits

...
Sign in to create a new pull request.

26 commits

Author SHA1 Message Date
Daniel Han
cfc93b7ecf Merge remote-tracking branch 'origin/main' into add-cu128-torch2110-extra 2026-07-27 13:18:25 +00:00
Daniel Han
c433254893 Record why torch 2.12 on Blackwell needs the cu130 leaf
torch 2.12 ships on the cu126 and cu130 indexes only, and the gate added
here already restricts it to those two. What the gate cannot express is
that the two are not interchangeable on current hardware.

Measured on torch 2.12.1:

    cu126 arch list: sm_50 sm_60 sm_70 sm_75 sm_80 sm_86 sm_90
    cu130 arch list: sm_75 sm_80 sm_86 sm_90 sm_100 sm_120

A B200 is sm_100, so a cu126 build of 2.12 fails a plain matmul there
with "CUDA error: no kernel image is available for execution on the
device". Nothing in this repo causes that and nothing here can repair
it; the wheel simply carries no code for the arch.

The gate keys off the detected CUDA version rather than the GPU, which
stays correct: cu126 is the right leaf for a pre-Blackwell host on CUDA
12.6, and a Blackwell host needs CUDA 13 regardless. The comment records
the measurement so the cu126 leaf is not later mistaken for a
Blackwell-capable option.
2026-07-27 10:37:56 +00:00
pre-commit-ci[bot]
d7e137ee79 [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
2026-07-26 12:54:51 +00:00
Daniel Han
fb177fa867 Add the CUDA torch 2.12 extras alongside the 2.11 ones
torch 2.12.0 and 2.12.1 are published on the cu126 and cu130 indexes, so
_auto_install.py raised "too new" for anyone already on them. The new
cuNNNonlytorch2120 and cuNNNonlytorch2121 leaves follow the 2110 shape:
the trio is pinned to the matching +cuNNN local build because xformers
0.0.35 depends on torch without pinning it, so an unpinned trio walks
torch up to the newest release the index serves.

torchvision exact-pins torch, so 2.12.0 takes 0.27.0 and 2.12.1 takes
0.27.1. torchaudio has no 2.12 release at all; 2.11.0 dropped its exact
torch pin, so it stays as the audio member of both trios.

There is no cu128 leaf: that index tops out at torch 2.11.0, so a cu128
2.12 extra would be unresolvable. _auto_install.py now rejects CUDA 12.8
on torch 2.12 with a message naming the two flavors that exist instead
of printing a command for an extra that does not.

Verified in throwaway uv venvs on a real GPU: each candidate trio plus
the pinned xformers wheel and bitsandbytes installed, then torch bf16
matmul, sdpa, torchvision nms, torchaudio resample, xformers
memory_efficient_attention with its registered op set, the swiglu symbol
and a bitsandbytes 4bit forward all ran. cu130 2.12.0 and 2.12.1 pass
end to end, with the shipping 2.11.0 trio as the control. The cu126
wheels build sm_50 through sm_90 so they cannot launch kernels on the
sm_100 test box; those two cases were checked for extension loading and
CPU execution instead. Every new leaf also resolves cleanly through
uv pip compile for both linux and windows targets.
2026-07-26 12:53:41 +00:00
Daniel Han
d9efdafaa7 Merge remote-tracking branch 'origin/main' into r6955 2026-07-26 12:52:57 +00:00
Daniel Han
ffa28dc34b Merge remote-tracking branch 'origin/main' into r6955
# Conflicts:
#	studio/backend/tests/test_gguf_load_cache_reuse.py
2026-07-21 02:18:48 +00:00
Daniel Han
5be994aa1a pyproject: tighten extras comments 2026-07-20 05:11:18 +00:00
Daniel Han
96fe03011d tests: align the gguf order test with main
Main fixed the stale ordering assertion in PR 7252; adopting its
version verbatim removes this file from the branch diff entirely and
avoids a conflict on the next main merge. 32 tests pass.
2026-07-20 00:21:23 +00:00
Daniel Han
6a3db3b9e0 tests: anchor the inheritance order check on the call, not the definition
source.index("_resolve_inherited_extra_args(") matched the function
definition, which always precedes the endpoint, so the ordering
assertion was vacuously true. Anchoring on "= _resolve_inherited_
extra_args(" pins the first call site inside the load endpoint (line
4505), which is the statement whose position relative to the GGUF
branch the test is meant to guard. 32 tests pass.
2026-07-19 16:21:19 +00:00
Daniel Han
fd306b0362 tests: track the moved pass-through inheritance in the gguf order check
Main moved the llama_extra_args pass-through inheritance out of the
GGUF branch into _resolve_inherited_extra_args, which runs before it,
so the source-order assertion's "if request.llama_extra_args is None"
anchor no longer exists inside the branch and the check failed after
the main merge. The test now asserts the same property in the current
shape: inheritance before the GGUF branch (a carried --no-mmproj still
shapes the hub guard's companion requirement), and marker, hub guard,
unload in order within the branch. Full file passes (32 tests).
2026-07-19 15:34:44 +00:00
Daniel Han
b63182683b Merge remote-tracking branch 'origin/main' into r6955 2026-07-19 13:20:39 +00:00
Daniel Han
1086bab371 Tighten comments in torch2110 CUDA extras 2026-07-18 08:17:15 +00:00
Daniel Han
8a2389c5dc install: tighten comments 2026-07-14 13:44:40 +00:00
Daniel Han
8997bf020a torch2100 extras: pin torch explicitly now that xformers can be skipped
The torch2100 leaves relied on the xformers 0.0.34 wheel's transitive
torch==2.10.0 pin as their only torch constraint. With the new ARM64
platform_machine markers those leaves contributed no requirement at all
off x86-64, so a cu-torch2100 wrapper install on Linux aarch64 or Windows
ARM64 proceeded unpinned and resolved a newer torch. Pin torch==2.10.0
explicitly (identical to the transitive pin, so x86-64 resolution is
unchanged): Linux aarch64 installs the real 2.10.0 aarch64 wheels and
Windows ARM64 fails loudly instead of silently drifting.
2026-07-13 02:52:16 +00:00
pre-commit-ci[bot]
7e9ab42d30 [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
2026-07-13 02:40:34 +00:00
Daniel Han
0167b5d72b torch2110 extras: gate x86-64 xformers wheels on platform_machine
The cuNNN torch2110/torch2100 extras pinned direct xformers wheel URLs that
are x86_64/win_amd64-only while their markers checked only the operating
system, so on Linux AArch64 (DGX Spark/GB10) or Windows ARM64 the marker
matched and pip aborted on an unsupported wheel even though the torch
2.11/2.10 aarch64 CUDA wheels themselves exist. Append the same
platform_machine guard the file already uses for its other direct-URL
wheels (bitsandbytes, triton), so ARM64 installs resolve the trio and
simply skip xformers.

Also in _auto_install.py: torch 2.10.1 fell into the new <2.11.1 branch
and emitted the torch2110 extra, whose exact 2.11.0 pins would replace the
detected runtime; restore the pre-PR loud RuntimeError for the
2.10.1..<2.11.0 gap, and report the actual torch version in the CUDA
requirement error instead of a hard-coded 2.10.

Tests: _reqs() now keeps both platform wheel entries per extra (the
Windows entry previously shadowed Linux), asserts the ARM64 exclusions,
and covers all six public wrapper extras.
2026-07-13 02:39:54 +00:00
pre-commit-ci[bot]
c446f8edac [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
2026-07-12 11:37:53 +00:00
Daniel Han
ed275cbd25 cu130onlytorch2110: pin the trio to +cu130 like the cu126/cu128 extras
A bare torch==2.11.0 range lets a configured CUDA-12 extra index win the
resolve: PEP 440 ranks the local build 2.11.0+cu126 above the unlabelled
2.11.0, so the cu130 extra could silently install cu126 wheels. An
===2.11.0 arbitrary-equality pin has the opposite problem: it rejects the
+cu130 local builds that an install from the official cu130 index
produces. Pinning ==2.11.0+cu130 matches the cu126/cu128 sibling extras
exactly: it accepts only the cu130 builds (all three wheels exist on
download.pytorch.org/whl/cu130) and fails the resolve loudly when no
cu130 index is configured instead of installing the wrong flavor.

_auto_install now appends the cu130 extra index for CUDA 13.0 the same
way it does for 12.6/12.8, and the extras test parametrizes cu130
alongside its siblings.
2026-07-12 11:36:04 +00:00
Daniel Han
ac830337c1 Pin the cu130 torch2110 trio with arbitrary equality to exclude CUDA-12 locals
PEP 440 ranks a local build above the unlabelled release of the same
version, so cu130onlytorch2110's range specs let a configured CUDA-12
extra index win: pip verified that torch>=2.11.0,<2.12.0 with
--extra-index-url .../cu126 installs torch 2.11.0+cu126, silently pairing
a CUDA-12 trio with the cu130 xformers wheel. Arbitrary equality
(torch===2.11.0) matches only the unlabelled PyPI default, which is the
CUDA 13.0 build: pip resolves it correctly next to a CUDA-12 extra index,
and uv's first-index strategy fails loudly instead of mismatching.
Regression test updated to lock in the === pins.
2026-07-12 10:53:22 +00:00
Daniel Han
dacbf893ba scripts: re-baseline sentencepiece's stdout-redirect dup2 after release drift
The security audit's hf-stack shard fails on one CRITICAL finding: the
reverse-shell regex matches os.dup2 in sentencepiece/__init__.py. This is
the package's well-known _redirect_wrapper stdout/stderr suppression
helper, verified verbatim against google/sentencepiece's official python
wrapper (python/src/sentencepiece/__init__.py L772/L777). The baseline
already carries this same benign finding for an earlier release; the new
release's matched-code window differs, producing a new evidence_hash, so
the old entry no longer suppresses it. Add the refreshed entry alongside
the old one (older resolves can still occur from cache). One entry added;
no scanner or code changes.
2026-07-12 10:26:43 +00:00
Daniel Han
ea33734214 Merge remote-tracking branch 'origin/main' into add-cu128-torch2110-extra 2026-07-12 07:46:11 +00:00
Daniel Han
c216d27ac4 Add CUDA index to torch2110 auto-install and a tomli test fallback
The cu126/cu128 torch2110 extras pin torch==2.11.0+cuNNN, which only resolves
from the matching PyTorch CUDA index (torch 2.11's default PyPI wheel is CUDA
13.0). _auto_install.py now appends --extra-index-url download.pytorch.org/whl/
cuNNN for exactly the CUDA-12 torch 2.11 selections so the printed install
command resolves; cu130 and non-torch2110 selections are unchanged.

tests/test_torch2110_cuda_extras.py falls back to the tomli backport when
tomllib is unavailable (Python 3.9 / 3.10).
2026-07-08 08:45:42 +00:00
pre-commit-ci[bot]
f757e68dac [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
2026-07-08 08:31:22 +00:00
Daniel Han
39af326e1a Pin CUDA-12 torch2110 extras to the matching +cuNNN torch build
torch 2.11's default PyPI wheel is CUDA 13.0 (torch 2.10 defaulted to CUDA
12.x), so the bare torch>=2.11 constraints in cu126onlytorch2110 /
cu128onlytorch2110 would resolve a cu130 torch from PyPI alongside the cu126/
cu128 xformers wheel and break at import. Pin the torch/torchvision/torchaudio
trio to the +cu126 / +cu128 local build so it only resolves from the matching
download.pytorch.org index the installer configures (or fails loudly if that
index is absent) instead of silently mismatching. cu130onlytorch2110 stays bare
because torch 2.11's PyPI default already lands on cu130.

Adds tests/test_torch2110_cuda_extras.py to guard the pins.
2026-07-08 08:28:17 +00:00
Daniel Han
bf9bca08e0 Route torch 2.11.x to the torch2110 extra family in auto-install selector
unsloth/_auto_install.py fell through to RuntimeError("Torch = {v} too
new!") for any installed torch >= 2.10.1, so an environment running torch
2.11.x could not resolve the new cu{cuda}-torch2110 extras. Add a 2.11.x
branch right after the 2.10.1 case. The existing CUDA guard already permits
12.6/12.8/13.0 for torch >= 2.10, matching the cu126/cu128/cu130 torch2110
extras.
2026-07-08 06:31:18 +00:00
Daniel Han
ae319b11b0 Add cu128/cu126/cu130 torch 2.11.0 extras (torch2110) with xformers 0.0.35
The cu*-ampere-torch2100 / cu*-torch2100 extras cap the CUDA training stack at
torch 2.10.0 because their xformers 0.0.34 wheel hard-requires torch==2.10.0.
There was no torch 2.11.0 counterpart for the CUDA indexes, so anything that
wants torch 2.11.0 (the Docker image, torchao 0.17.0, the rocm7.2 studio path)
could not select a matching unsloth extra: torch==2.11.0 plus
unsloth[cu128-ampere-torch2100] is unsatisfiable (xformers 0.0.34 -> torch 2.10.0).

Add the torch2110 family mirroring the torch2100 layout for cu126/cu128/cu130:
- cu{126,128,130}onlytorch2110: xformers 0.0.35 (linux + win32) plus an explicit
  torch>=2.11.0,<2.12.0 trio. xformers 0.0.35 does not hard-pin torch the way
  0.0.34 pinned 2.10.0, so without the trio a bare resolve floats torch to 2.12.x;
  the pin keeps the "torch2110" name faithful and matches studio's
  torch>=2.11.0,<2.12.0 convention and the existing xpu torch2110 extra.
- cu{126,128,130}-torch2110 and cu{126,128,130}-ampere-torch2110: huggingface +
  bitsandbytes + the matching onlytorch2110, same shape as the torch2100 wrappers.

Resolves cleanly: unsloth[cu128-ampere-torch2110] -> torch 2.11.0+cu128 /
torchvision 0.26.0 / torchaudio 2.11.0 / xformers 0.0.35.
2026-07-08 01:45:18 +00:00
3 changed files with 342 additions and 8 deletions

View file

@ -338,16 +338,83 @@ cu130onlytorch291 = [
"xformers @ https://download.pytorch.org/whl/cu130/xformers-0.0.33.post2-cp39-abi3-win_amd64.whl ; (sys_platform == 'win32')",
]
cu126onlytorch2100 = [
"xformers @ https://download.pytorch.org/whl/cu126/xformers-0.0.34-cp39-abi3-manylinux_2_28_x86_64.whl ; ('linux' in sys_platform)",
"xformers @ https://download.pytorch.org/whl/cu126/xformers-0.0.34-cp39-abi3-win_amd64.whl ; (sys_platform == 'win32')",
# Pin torch so ARM64 (x86-64-only xformers wheel skipped) stays on 2.10.
"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')",
]
cu128onlytorch2100 = [
"xformers @ https://download.pytorch.org/whl/cu128/xformers-0.0.34-cp39-abi3-manylinux_2_28_x86_64.whl ; ('linux' in sys_platform)",
"xformers @ https://download.pytorch.org/whl/cu128/xformers-0.0.34-cp39-abi3-win_amd64.whl ; (sys_platform == 'win32')",
# Same torch pin as cu126onlytorch2100.
"torch==2.10.0",
"xformers @ https://download.pytorch.org/whl/cu128/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/cu128/xformers-0.0.34-cp39-abi3-win_amd64.whl ; (sys_platform == 'win32') and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
]
cu130onlytorch2100 = [
"xformers @ https://download.pytorch.org/whl/cu130/xformers-0.0.34-cp39-abi3-manylinux_2_28_x86_64.whl ; ('linux' in sys_platform)",
"xformers @ https://download.pytorch.org/whl/cu130/xformers-0.0.34-cp39-abi3-win_amd64.whl ; (sys_platform == 'win32')",
# Same torch pin as cu126onlytorch2100.
"torch==2.10.0",
"xformers @ https://download.pytorch.org/whl/cu130/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/cu130/xformers-0.0.34-cp39-abi3-win_amd64.whl ; (sys_platform == 'win32') and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
]
cu126onlytorch2110 = [
# Pin trio to +cu126 so it resolves from the cu126 index (torch 2.11 defaults
# to a CUDA-13 wheel; xformers 0.0.35 does not pin torch).
"torch==2.11.0+cu126",
"torchvision==0.26.0+cu126",
"torchaudio==2.11.0+cu126",
"xformers @ https://download.pytorch.org/whl/cu126/xformers-0.0.35-py39-none-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.35-py39-none-win_amd64.whl ; (sys_platform == 'win32') and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
]
cu128onlytorch2110 = [
# Same +cuNNN pin as cu126onlytorch2110, on the cu128 index.
"torch==2.11.0+cu128",
"torchvision==0.26.0+cu128",
"torchaudio==2.11.0+cu128",
"xformers @ https://download.pytorch.org/whl/cu128/xformers-0.0.35-py39-none-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/cu128/xformers-0.0.35-py39-none-win_amd64.whl ; (sys_platform == 'win32') and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
]
cu130onlytorch2110 = [
# Same +cuNNN pin as cu126/cu128: a bare range lets +cu126 win (PEP 440) and
# === would force-replace cu130-index installs. _auto_install.py adds the index.
"torch==2.11.0+cu130",
"torchvision==0.26.0+cu130",
"torchaudio==2.11.0+cu130",
"xformers @ https://download.pytorch.org/whl/cu130/xformers-0.0.35-py39-none-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/cu130/xformers-0.0.35-py39-none-win_amd64.whl ; (sys_platform == 'win32') and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
]
# torch 2.12 ships on the cu126 and cu130 indexes only, so there is no cu128 leaf.
# torchaudio has no 2.12 release; 2.11.0 carries no torch pin and pairs with 2.12.
cu126onlytorch2120 = [
# Same +cuNNN pin as cu126onlytorch2110: xformers 0.0.35 depends on torch without
# pinning it, so an unpinned trio walks up to the newest release on the index.
"torch==2.12.0+cu126",
"torchvision==0.27.0+cu126",
"torchaudio==2.11.0+cu126",
"xformers @ https://download.pytorch.org/whl/cu126/xformers-0.0.35-py39-none-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.35-py39-none-win_amd64.whl ; (sys_platform == 'win32') and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
]
cu130onlytorch2120 = [
# Same +cuNNN pin as cu126onlytorch2120, on the cu130 index.
"torch==2.12.0+cu130",
"torchvision==0.27.0+cu130",
"torchaudio==2.11.0+cu130",
"xformers @ https://download.pytorch.org/whl/cu130/xformers-0.0.35-py39-none-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/cu130/xformers-0.0.35-py39-none-win_amd64.whl ; (sys_platform == 'win32') and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
]
cu126onlytorch2121 = [
# torchvision exact-pins torch, so the 2.12.1 patch takes 0.27.1.
"torch==2.12.1+cu126",
"torchvision==0.27.1+cu126",
"torchaudio==2.11.0+cu126",
"xformers @ https://download.pytorch.org/whl/cu126/xformers-0.0.35-py39-none-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.35-py39-none-win_amd64.whl ; (sys_platform == 'win32') and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
]
cu130onlytorch2121 = [
# Same +cuNNN pin as cu126onlytorch2121, on the cu130 index.
"torch==2.12.1+cu130",
"torchvision==0.27.1+cu130",
"torchaudio==2.11.0+cu130",
"xformers @ https://download.pytorch.org/whl/cu130/xformers-0.0.35-py39-none-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/cu130/xformers-0.0.35-py39-none-win_amd64.whl ; (sys_platform == 'win32') and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
]
cu118 = [
"unsloth[huggingface]",
@ -557,6 +624,41 @@ cu130-torch2100 = [
"unsloth[cu130onlytorch2100]",
"unsloth[audio-torch210]",
]
cu126-torch2110 = [
"unsloth[huggingface]",
"bitsandbytes>=0.45.5,!=0.46.0,!=0.48.0",
"unsloth[cu126onlytorch2110]",
]
cu128-torch2110 = [
"unsloth[huggingface]",
"bitsandbytes>=0.45.5,!=0.46.0,!=0.48.0",
"unsloth[cu128onlytorch2110]",
]
cu130-torch2110 = [
"unsloth[huggingface]",
"bitsandbytes>=0.45.5,!=0.46.0,!=0.48.0",
"unsloth[cu130onlytorch2110]",
]
cu126-torch2120 = [
"unsloth[huggingface]",
"bitsandbytes>=0.45.5,!=0.46.0,!=0.48.0",
"unsloth[cu126onlytorch2120]",
]
cu130-torch2120 = [
"unsloth[huggingface]",
"bitsandbytes>=0.45.5,!=0.46.0,!=0.48.0",
"unsloth[cu130onlytorch2120]",
]
cu126-torch2121 = [
"unsloth[huggingface]",
"bitsandbytes>=0.45.5,!=0.46.0,!=0.48.0",
"unsloth[cu126onlytorch2121]",
]
cu130-torch2121 = [
"unsloth[huggingface]",
"bitsandbytes>=0.45.5,!=0.46.0,!=0.48.0",
"unsloth[cu130onlytorch2121]",
]
kaggle = [
"unsloth[huggingface]",
]
@ -859,6 +961,41 @@ cu130-ampere-torch2100 = [
"unsloth[cu130onlytorch2100]",
"unsloth[audio-torch210]",
]
cu126-ampere-torch2110 = [
"unsloth[huggingface]",
"bitsandbytes>=0.45.5,!=0.46.0,!=0.48.0",
"unsloth[cu126onlytorch2110]",
]
cu128-ampere-torch2110 = [
"unsloth[huggingface]",
"bitsandbytes>=0.45.5,!=0.46.0,!=0.48.0",
"unsloth[cu128onlytorch2110]",
]
cu130-ampere-torch2110 = [
"unsloth[huggingface]",
"bitsandbytes>=0.45.5,!=0.46.0,!=0.48.0",
"unsloth[cu130onlytorch2110]",
]
cu126-ampere-torch2120 = [
"unsloth[huggingface]",
"bitsandbytes>=0.45.5,!=0.46.0,!=0.48.0",
"unsloth[cu126onlytorch2120]",
]
cu130-ampere-torch2120 = [
"unsloth[huggingface]",
"bitsandbytes>=0.45.5,!=0.46.0,!=0.48.0",
"unsloth[cu130onlytorch2120]",
]
cu126-ampere-torch2121 = [
"unsloth[huggingface]",
"bitsandbytes>=0.45.5,!=0.46.0,!=0.48.0",
"unsloth[cu126onlytorch2121]",
]
cu130-ampere-torch2121 = [
"unsloth[huggingface]",
"bitsandbytes>=0.45.5,!=0.46.0,!=0.48.0",
"unsloth[cu130onlytorch2121]",
]
flashattentiontorch260abiFALSEcu12x = [
"flash-attn @ https://github.com/Dao-AILab/flash-attention/releases/download/v2.7.4.post1/flash_attn-2.7.4.post1+cu12torch2.6cxx11abiFALSE-cp39-cp39-linux_x86_64.whl ; ('linux' in sys_platform) and python_version == '3.9'",
"flash-attn @ https://github.com/Dao-AILab/flash-attention/releases/download/v2.7.4.post1/flash_attn-2.7.4.post1+cu12torch2.6cxx11abiFALSE-cp310-cp310-linux_x86_64.whl ; ('linux' in sys_platform) and python_version == '3.10'",

View file

@ -0,0 +1,183 @@
# 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 <https://www.gnu.org/licenses/>.
"""Regression guard for the CUDA torch2110 and torch212x optional-dependency extras.
The cuXXXonlytorch2110 / cuXXXonlytorch212X extras must pin the torch trio to the
matching +cuXXX local build (these releases default to a CUDA-13 PyPI wheel, and
xformers 0.0.35 depends on torch without pinning it), or resolution walks torch up
to the newest release on the index and mismatches the xformers wheel. Hermetic:
only parses pyproject.toml and _auto_install.py, no network or install.
"""
from __future__ import annotations
from pathlib import Path
import pytest
from packaging.requirements import Requirement
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")
REPO = Path(__file__).resolve().parents[1]
PYPROJECT = REPO / "pyproject.toml"
AUTO_INSTALL = REPO / "unsloth" / "_auto_install.py"
_TORCH_TRIO = ("torch", "torchvision", "torchaudio")
# torchaudio has no 2.12 release, so the 2.12 leaves keep the unpinned 2.11.0 audio wheel.
_TORCH212_TRIO = {
"torch2120": {"torch": "2.12.0", "torchvision": "0.27.0", "torchaudio": "2.11.0"},
"torch2121": {"torch": "2.12.1", "torchvision": "0.27.1", "torchaudio": "2.11.0"},
}
# torch 2.12 is absent from the cu128 index, so only these two flavors get 2.12 extras.
_TORCH212_CUDA = ("cu126", "cu130")
def _extras() -> dict[str, list[str]]:
with open(PYPROJECT, "rb") as f:
data = tomllib.load(f)
return data["project"]["optional-dependencies"]
def _extra(name: str) -> list[str]:
return _extras()[name]
def _reqs(specs: list[str]) -> dict[str, list[Requirement]]:
# name -> reqs (one Linux + one Windows xformers per extra)
out: dict[str, list[Requirement]] = {}
for spec in specs:
r = Requirement(spec)
out.setdefault(r.name.lower(), []).append(r)
return out
@pytest.mark.parametrize("cuda", ["cu126", "cu128", "cu130"])
def test_cuda12_torch2110_pins_matching_local_build(cuda: str):
reqs = _reqs(_extra(f"{cuda}onlytorch2110"))
for pkg in _TORCH_TRIO:
(req,) = reqs[pkg]
spec = str(req.specifier)
assert (
spec == f"=={('2.11.0' if pkg != 'torchvision' else '0.26.0')}+{cuda}"
), f"{cuda}onlytorch2110: {pkg} pinned as '{spec}', expected the +{cuda} local build"
xformers = reqs["xformers"]
assert len(xformers) == 2, f"expected Linux + Windows xformers wheels, got {xformers}"
linux = [r for r in xformers if r.url and r.url.endswith("manylinux_2_28_x86_64.whl")]
windows = [r for r in xformers if r.url and r.url.endswith("win_amd64.whl")]
assert len(linux) == 1 and len(windows) == 1, f"unexpected xformers wheels: {xformers}"
for r in linux + windows:
assert (
f"/whl/{cuda}/xformers-0.0.35-" in r.url
), f"xformers not on the {cuda} index: {r.url}"
# 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"})
assert linux[0].marker.evaluate({"sys_platform": "linux", "platform_machine": "x86_64"})
assert windows[0].marker.evaluate({"sys_platform": "win32", "platform_machine": "AMD64"})
@pytest.mark.parametrize("cuda", ["cu126", "cu128", "cu130"])
@pytest.mark.parametrize("variant", ["", "ampere-"])
def test_torch2110_wrapper_references_matching_leaf(cuda: str, variant: str):
specs = _extra(f"{cuda}-{variant}torch2110")
assert specs == [
"unsloth[huggingface]",
"bitsandbytes>=0.45.5,!=0.46.0,!=0.48.0",
f"unsloth[{cuda}onlytorch2110]",
]
@pytest.mark.parametrize("cuda", _TORCH212_CUDA)
@pytest.mark.parametrize("series", sorted(_TORCH212_TRIO))
def test_cuda12_torch212_pins_matching_local_build(cuda: str, series: str):
reqs = _reqs(_extra(f"{cuda}only{series}"))
for pkg, want in _TORCH212_TRIO[series].items():
(req,) = reqs[pkg]
spec = str(req.specifier)
assert spec == f"=={want}+{cuda}", (
f"{cuda}only{series}: {pkg} pinned as '{spec}', "
f"expected the =={want}+{cuda} local build"
)
assert req.marker is None, f"the {pkg} pin must apply on every machine"
xformers = reqs["xformers"]
linux = [r for r in xformers if r.url and r.url.endswith("manylinux_2_28_x86_64.whl")]
windows = [r for r in xformers if r.url and r.url.endswith("win_amd64.whl")]
assert len(linux) == 1 and len(windows) == 1, f"unexpected xformers wheels: {xformers}"
for r in linux + windows:
assert (
f"/whl/{cuda}/xformers-0.0.35-" in r.url
), f"xformers not on the {cuda} index: {r.url}"
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"})
assert linux[0].marker.evaluate({"sys_platform": "linux", "platform_machine": "x86_64"})
assert windows[0].marker.evaluate({"sys_platform": "win32", "platform_machine": "AMD64"})
@pytest.mark.parametrize("cuda", _TORCH212_CUDA)
@pytest.mark.parametrize("series", sorted(_TORCH212_TRIO))
@pytest.mark.parametrize("variant", ["", "ampere-"])
def test_torch212_wrapper_references_matching_leaf(cuda: str, series: str, variant: str):
specs = _extra(f"{cuda}-{variant}{series}")
assert specs == [
"unsloth[huggingface]",
"bitsandbytes>=0.45.5,!=0.46.0,!=0.48.0",
f"unsloth[{cuda}only{series}]",
]
@pytest.mark.parametrize("series", sorted(_TORCH212_TRIO))
def test_no_cu128_torch212_extras(series: str):
# torch 2.12 is not published on the cu128 index; a cu128 leaf would be unresolvable.
names = _extras()
for name in (f"cu128only{series}", f"cu128-{series}", f"cu128-ampere-{series}"):
assert name not in names, f"{name} cannot resolve: no torch 2.12 on the cu128 index"
@pytest.mark.parametrize("series", sorted(_TORCH212_TRIO))
def test_auto_install_maps_torch212_to_defined_extras(series: str):
# The printed command must name extras that exist, and must add the index that
# serves the +cuNNN local builds those extras pin.
source = AUTO_INSTALL.read_text()
assert f"'cu{{}}{{}}-{series}'" in source, f"_auto_install.py never selects {series}"
assert f"'-{series}'" in source, f"{series} missing from the extra-index-url gate"
names = _extras()
for cuda in _TORCH212_CUDA:
for variant in ("", "-ampere"):
assert f"cu{cuda[2:]}{variant}-{series}" in names
def test_auto_install_rejects_cuda128_on_torch212():
# cu128 tops out at torch 2.11, so 2.12 on that flavor must fail loudly rather
# than print an install command for an extra that does not exist.
source = AUTO_INSTALL.read_text()
assert 'if v >= V(\'2.12.0\') and cuda not in ("12.6", "13.0")' in source
@pytest.mark.parametrize("cuda", ["cu126", "cu128", "cu130"])
def test_cuda12_torch2100_keeps_torch_pinned_off_x86(cuda: str):
# xformers wheels now carry x86-64 markers, so the leaf must pin torch for ARM64.
reqs = _reqs(_extra(f"{cuda}onlytorch2100"))
(torch_req,) = reqs["torch"]
assert str(torch_req.specifier) == "==2.10.0", (
f"{cuda}onlytorch2100 must pin torch==2.10.0 for machines where the "
f"x86-64-only xformers wheel (and its transitive pin) is skipped"
)
assert torch_req.marker is None, "the torch pin must apply on every machine"

View file

@ -36,8 +36,22 @@ elif v < V('2.8.9'): x = 'cu{}{}-torch280'
elif v < V('2.9.1'): x = 'cu{}{}-torch290'
elif v < V('2.9.2'): x = 'cu{}{}-torch291'
elif v < V('2.10.1'): x = 'cu{}{}-torch2100'
elif v < V('2.11.0'): raise RuntimeError(f"Torch = {v} not supported!")
elif v < V('2.11.1'): x = 'cu{}{}-torch2110'
elif v < V('2.12.0'): raise RuntimeError(f"Torch = {v} not supported!")
elif v < V('2.12.1'): x = 'cu{}{}-torch2120'
elif v < V('2.12.2'): x = 'cu{}{}-torch2121'
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 2.10 requires CUDA 12.6, 12.8, or 13.0! Got CUDA = {cuda}")
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}")
# torch 2.12 is published on the cu126 and cu130 indexes only, so there is no cu128 extra.
# Of those two, only cu130 covers Blackwell: measured on 2.12.1, the cu126 build's
# arch list ends at sm_90 while cu130 carries sm_100 and sm_120, so on a B200 a cu126
# 2.12 fails even a plain matmul with "no kernel image is available for execution on
# the device". This gate keys off the detected CUDA, not the GPU, so cu126 stays valid
# for pre-Blackwell; a Blackwell host needs CUDA 13.
if v >= V('2.12.0') and cuda not in ("12.6", "13.0"): raise RuntimeError(f"Torch = {v} requires CUDA 12.6 or 13.0! Got CUDA = {cuda}")
x = x.format(cuda.replace(".", ""), "-ampere" if False else "") # is_ampere is broken due to flash-attn
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')
# torch2110 and later extras pin +cuNNN local builds that only resolve from the matching index.
extra_index = f' --extra-index-url https://download.pytorch.org/whl/cu{cuda.replace(".", "")}' if (x.endswith(('-torch2110', '-torch2120', '-torch2121')) 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}')