From f4d2fa494037c381047d99fdc6a879691d760eae Mon Sep 17 00:00:00 2001 From: Guangting Yu Date: Wed, 11 Nov 2026 14:23:30 -0700 Subject: [PATCH] cuda: say that sm_6x and older are not supported, before compiling On a GTX 1080 Ti (sm_61) `--backend cuda` printed `sm_61 (Volta, m8n8k4)` and then failed inside NVRTC with `identifier "__nanosleep" is undefined`. `Gpu::new` now returns a "CUDA init" error that names the card and the sm_70 minimum before anything is compiled, so `backend::open` goes on to Vulkan and reports both errors, and the tests skip the backend. `device_log` no longer labels a pre-Volta arch as Volta. Co-Authored-By: Claude Fable 5.2 Claude-Session: https://claude.ai/code/session_014eNPMupWL2tifApEBfcCgi --- src/cuda/mod.rs | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/src/cuda/mod.rs b/src/cuda/mod.rs index 38928354..a13fe5ab 100644 --- a/src/cuda/mod.rs +++ b/src/cuda/mod.rs @@ -217,10 +217,21 @@ fn arch_support_error(cc: (i32, i32), nvrtc: (i32, i32)) -> Option { nvrtc.0, nvrtc.1, cc.0, cc.1)) } +/// The user-facing error for a device older than Volta, `None` for sm_70 and newer. The kernels need sm_70 +/// instructions (`__nanosleep`, `mma`): without this check NVRTC fails deep in the source on Pascal (sm_6x) and older +/// (`identifier "__nanosleep" is undefined`). Checked before anything is compiled. +fn min_arch_error(name: &str, cc: (i32, i32)) -> Option { + (cc < (7, 0)).then(|| format!( + "{name} is sm_{}{}: the CUDA backend requires sm_70 or newer (Volta, Turing, Ampere and later); \ + Pascal and older cards have no tensor cores and are not supported", + cc.0, cc.1)) +} + /// The startup line for the device: arch always, plus the sm_70 prefill GEMM path (tensor cores vs FMA fallback). /// `gemm_fma` is passed explicitly so the log reflects the cubin actually compiled, not the env at print time. fn device_log(device: usize, name: &str, arch: &str, cc: (i32, i32), gemm_fma: bool) -> String { let path = match (cc, gemm_fma) { + (c, _) if c < (7, 0) => " (pre-Volta, not supported)", // not reached from `Gpu::new`: `min_arch_error` (c, false) if c < (7, 5) => " (Volta, m8n8k4)", (c, true) if c < (7, 5) => " (Volta fallback)", (c, _) if c < (8, 0) => " (Turing, m16n8k8)", @@ -451,6 +462,10 @@ impl Gpu { let ctx = CudaContext::new(device).context("CUDA init")?; unsafe { ctx.disable_event_tracking() }; let (maj, min) = ctx.compute_capability()?; + // "CUDA init" so that `backend::open` falls back to Vulkan (naming this error) and the tests skip the backend. + if let Some(msg) = min_arch_error(&ctx.name().unwrap_or_default(), (maj, min)) { + return Err(anyhow::anyhow!("{msg}")).context("CUDA init"); + } let int8_mma = maj >= 8; let max_optin = ctx.attribute(cudarc::driver::sys::CUdevice_attribute::CU_DEVICE_ATTRIBUTE_MAX_SHARED_MEMORY_PER_BLOCK_OPTIN) .unwrap_or(48 << 10).max(48 << 10) as u32; @@ -1222,6 +1237,30 @@ mod tests { assert!(super::device_log(0, "x", "sm_70", (7, 0), false).contains("Volta")); } + #[test] + fn min_arch_error_rejects_pascal_and_older() { + let msg = super::min_arch_error("NVIDIA GeForce GTX 1080 Ti", (6, 1)).expect("sm_61 is not supported"); + assert!(msg.contains("GTX 1080 Ti") && msg.contains("sm_61") && msg.contains("sm_70"), "{msg}"); + for cc in [(3, 5), (5, 2), (6, 0), (6, 2)] { + assert!(super::min_arch_error("x", cc).is_some(), "sm_{}{}", cc.0, cc.1); + } + } + + #[test] + fn min_arch_error_allows_volta_and_newer() { + for cc in [(7, 0), (7, 2), (7, 5), (8, 0), (8, 6), (8, 9), (9, 0), (12, 0)] { + assert_eq!(super::min_arch_error("x", cc), None, "sm_{}{}", cc.0, cc.1); + } + } + + #[test] + fn device_log_does_not_call_pascal_volta() { + for fma in [false, true] { + let line = super::device_log(0, "NVIDIA GeForce GTX 1080 Ti", "sm_61", (6, 1), fma); + assert_eq!(line, "cuda device 0: NVIDIA GeForce GTX 1080 Ti, sm_61 (pre-Volta, not supported)"); + } + } + #[test] fn arch_support_error_allows_cuda12_on_sm70() { assert_eq!(super::arch_support_error((7, 0), (12, 9)), None);