From 99a396964488d6f03c202e5f1ca068d69fc60daf Mon Sep 17 00:00:00 2001 From: Thales <> Date: Mon, 21 Sep 2026 09:13:48 +0100 Subject: [PATCH] Install a CUDA wheel that exists on a pre-12.4 driver An RTX 4060 on Windows reported the NVIDIA build falling back to CPU with "No matching distribution found for torch==2.6.0+cu121" (#644). The tag comes from the driver and the torch version from a table, and nothing held the two together. cuda_tag sent a driver reporting CUDA 12.0-12.3 to cu121, torch_version_for_tag answers 2.6.0 for every non-cu128 tag, and the cu121 index stops at 2.5.1 -- for torch, torchaudio and torchvision alike, on every platform. So setup asked pip for a wheel that has never been published, the install failed, CPU torch was restored, and a working GPU sat unused. Every CUDA 12 driver now gets cu124. CUDA 12 is minor-version compatible, so a cu124 build runs on a 12.0 driver, and cu124 publishes the same 2.6.0 line the rest of the app is pinned to. cu121 is no longer reachable at all. cu118 follows it as a second candidate. wheel_candidates has returned a list since #502 precisely so a tag that fails falls through to the next, but every non-Blackwell branch returned one element, so the fallthrough had nowhere to go: cu124 failing meant CPU. cu118 runs on every 12.x driver and publishes the same torch line, which makes it a real second chance. The new test is the guard the original bug lacked: it walks every driver and compute-capability combination setup can see and asserts each tag offered appears in a table of tag/version pairs confirmed against download.pytorch.org. A tag whose index does not publish what setup installs now fails locally instead of on a user's machine. Re-running it with the cu121 branch restored reproduces #644 as a test failure. Verified: cargo fmt, clippy --tests -D warnings and test on Windows (99) and on the Linux target via WSL (109), plus pytest (1060 passing), ruff, bandit and the JS unit tests. Wheel availability checked live against download.pytorch.org/whl// for torch, torchaudio and torchvision, cp312, win_amd64 and linux_x86_64. Co-Authored-By: Claude Opus 5 --- desktop/src-tauri/src/main.rs | 106 ++++++++++++++++++++++++++++++---- 1 file changed, 94 insertions(+), 12 deletions(-) diff --git a/desktop/src-tauri/src/main.rs b/desktop/src-tauri/src/main.rs index e77872f5..731860d2 100644 --- a/desktop/src-tauri/src/main.rs +++ b/desktop/src-tauri/src/main.rs @@ -2241,8 +2241,14 @@ fn cuda_tag(cuda_version: &str) -> &'static str { // not just Blackwell (#502). CUDA is backward compatible, so cu128 is // the right floor for a 13.x driver. [major, _] if *major >= 13 => "cu128", - [12, minor] if *minor >= 4 => "cu124", - [12, _] => "cu121", + // Every CUDA 12 driver, not just 12.4 and newer. A 12.0-12.3 driver + // used to be handed cu121, whose index stops at torch 2.5.1 -- so the + // 2.6.0 line this app installs was asked for at a tag that has never + // published it, pip answered "No matching distribution found", and an + // RTX 4060 dropped to CPU with a GPU sitting right there (#644). + // cu124 is the right answer for all of them: CUDA 12 is minor-version + // compatible, so a cu124 build runs on any 12.x driver. + [12, _] => "cu124", [11, _] => "cu118", _ => "cu124", } @@ -2281,9 +2287,17 @@ fn wheel_candidates(compute_cap: Option<&str>, cuda_version: &str) -> Vec<&'stat .and_then(|cap| cap.split('.').next()?.parse::().ok()) .is_some_and(|major| major >= 10); if blackwell { - vec!["cu128"] - } else { - vec![cuda_tag(cuda_version)] + return vec!["cu128"]; + } + match cuda_tag(cuda_version) { + // cu118 as a second chance for a CUDA 12 driver. cu124 is the right + // first answer for all of them (see cuda_tag), but minor-version + // compatibility is the thing being relied on there, and when it does + // not hold the alternative used to be CPU. cu118 runs on every 12.x + // driver and publishes the same torch 2.6.0 line, so the fallthrough + // this list was built for (#502) finally has somewhere to go (#644). + "cu124" => vec!["cu124", "cu118"], + tag => vec![tag], } } @@ -5896,7 +5910,7 @@ mod tests { // torchaudio 2.9 removed the soundfile backend. Any tag this maps to // 2.9+ ships a torchaudio whose save() needs torchcodec, which is not // a StemDeck dependency, so demucs' ta.save() would fail at runtime. - for tag in ["cu128", "cu124", "cu121", "cu118"] { + for tag in ["cu128", "cu124", "cu118"] { let v = super::torch_version_for_tag(tag); let minor: u32 = v.split('.').nth(1).unwrap().parse().unwrap(); assert!(minor < 9, "{tag} maps to torch {v}, which is 2.9+"); @@ -5909,18 +5923,86 @@ mod tests { // catch-all and got cu124 -- two major versions behind, on every card. assert_eq!(super::cuda_tag("13.0"), "cu128"); assert_eq!(super::cuda_tag("14.2"), "cu128"); - // Older drivers keep their existing mapping. + // A CUDA 12 driver gets cu124 whatever its minor version: 12.1 used + // to get cu121, whose index has no torch 2.6.0 to install (#644). assert_eq!(super::cuda_tag("12.8"), "cu124"); - assert_eq!(super::cuda_tag("12.1"), "cu121"); + assert_eq!(super::cuda_tag("12.1"), "cu124"); + // An 11.x driver keeps its existing mapping. assert_eq!(super::cuda_tag("11.8"), "cu118"); } #[test] fn a_non_blackwell_card_still_follows_the_driver() { - assert_eq!(super::wheel_candidates(Some("8.9"), "12.4"), vec!["cu124"]); + assert_eq!( + super::wheel_candidates(Some("8.9"), "12.4"), + vec!["cu124", "cu118"] + ); assert_eq!(super::wheel_candidates(None, "11.8"), vec!["cu118"]); } + /// Every wheel tag setup can offer publishes the torch line it installs. + /// + /// The tag comes from the driver and the version from a table, and nothing + /// held the two together. A CUDA 12.0-12.3 driver was handed cu121, whose + /// index stops at torch 2.5.1, so setup asked pip for torch==2.6.0+cu121 -- + /// never published, on any platform -- and an RTX 4060 dropped to CPU with + /// "No matching distribution found" (#644). + #[cfg(not(target_os = "macos"))] + #[test] + fn every_wheel_tag_offered_has_a_published_torch_wheel() { + // Checked against download.pytorch.org/whl// on 2026-09-21, for + // torch, torchaudio and torchvision, cp312, win_amd64 and + // linux_x86_64. A tag with no row here is one nobody confirmed + // publishes what setup is about to ask it for, so add the row before + // offering the tag. + const PUBLISHED: [(&str, &str); 3] = + [("cu128", "2.8.0"), ("cu124", "2.6.0"), ("cu118", "2.6.0")]; + + let drivers = [ + "11.8", "12.0", "12.1", "12.3", "12.4", "12.8", "13.0", "14.2", "unknown", + ]; + let caps = [ + None, + Some("7.5"), + Some("8.6"), + Some("8.9"), + Some("10.0"), + Some("12.0"), + Some("N/A"), + ]; + for driver in drivers { + for cap in caps { + for tag in super::wheel_candidates(cap, driver) { + let published = PUBLISHED + .iter() + .find(|(t, _)| *t == tag) + .map(|(_, version)| *version); + let Some(published) = published else { + panic!("{tag} (driver {driver}, cap {cap:?}) has no verified torch line"); + }; + assert_eq!( + super::torch_version_for_tag(tag), + published, + "{tag} installs a torch its index does not publish", + ); + } + } + } + } + + /// The reporter's machine: an RTX 4060 behind a pre-12.4 driver (#644). + #[cfg(not(target_os = "macos"))] + #[test] + fn a_pre_12_4_driver_gets_a_gpu_wheel_and_a_second_chance() { + for driver in ["12.0", "12.1", "12.2", "12.3"] { + assert_eq!( + super::wheel_candidates(Some("8.9"), driver), + vec!["cu124", "cu118"], + "driver {driver}", + ); + } + } + #[test] fn legacy_migration_preserves_user_settings_when_data_dir_already_exists() { // setup() creates the destination before ensure_workspace() invokes @@ -6642,10 +6724,10 @@ b6052160df96b31c9b1e33854a4dcda3d4b57641b880270f31736fb9f445d384 ffmpeg-n7.1-la assert_eq!(super::wheel_tag(Some("10.0"), "12.4"), "cu128"); // Non-Blackwell cards fall back to the CUDA-version heuristic. assert_eq!(super::wheel_tag(Some("8.9"), "12.4"), "cu124"); - assert_eq!(super::wheel_tag(Some("8.6"), "12.1"), "cu121"); + assert_eq!(super::wheel_tag(Some("8.6"), "12.1"), "cu124"); assert_eq!(super::wheel_tag(Some("7.5"), "11.8"), "cu118"); // Missing / unparseable compute capability also falls back. - assert_eq!(super::wheel_tag(None, "12.1"), "cu121"); + assert_eq!(super::wheel_tag(None, "12.1"), "cu124"); assert_eq!(super::wheel_tag(Some("N/A"), "12.4"), "cu124"); } @@ -6662,7 +6744,7 @@ b6052160df96b31c9b1e33854a4dcda3d4b57641b880270f31736fb9f445d384 ffmpeg-n7.1-la assert_eq!(super::torch_version_for_tag("cu128"), "2.8.0"); assert_eq!(super::torchvision_version_for_tag("cu128"), "0.23.0"); - for tag in ["cu124", "cu121", "cu118"] { + for tag in ["cu124", "cu118"] { assert_eq!(super::torch_version_for_tag(tag), super::CPU_TORCH_VERSION); assert_eq!( super::torchvision_version_for_tag(tag),