Skip to content

Commit 9f88ba3

Browse files
authored
Merge pull request #12 from EliasVahlberg/pascal-compat-fixes
fix(cuda): compile-time gate for green_ctx, device-aware PTX target for pre-Turing GPUs
2 parents 6c1ecbd + fa08c18 commit 9f88ba3

5 files changed

Lines changed: 229 additions & 19 deletions

File tree

crates/ringkernel-cuda/Cargo.toml

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,3 +73,28 @@ multi-gpu = ["cuda", "nvml-wrapper"]
7373
# When enabled, a build-time linker search path is added; users whose
7474
# NVSHMEM install lives outside /usr can set NVSHMEM_LIB_DIR.
7575
nvshmem = ["cuda", "multi-gpu"]
76+
77+
# `cuda-1xxxx` feature names below are cudarc's own version features
78+
# (propagated transitively via `cuda-version-from-build-system`), not
79+
# declared here directly — silences rustc's `unexpected_cfgs` lint for
80+
# `#[cfg(feature = "cuda-12040")]`-style checks against them in hopper/mod.rs.
81+
[lints.rust]
82+
unexpected_cfgs = { level = "allow", check-cfg = [
83+
'cfg(feature, values("cuda-11040"))',
84+
'cfg(feature, values("cuda-11050"))',
85+
'cfg(feature, values("cuda-11060"))',
86+
'cfg(feature, values("cuda-11070"))',
87+
'cfg(feature, values("cuda-11080"))',
88+
'cfg(feature, values("cuda-12000"))',
89+
'cfg(feature, values("cuda-12010"))',
90+
'cfg(feature, values("cuda-12020"))',
91+
'cfg(feature, values("cuda-12030"))',
92+
'cfg(feature, values("cuda-12040"))',
93+
'cfg(feature, values("cuda-12050"))',
94+
'cfg(feature, values("cuda-12060"))',
95+
'cfg(feature, values("cuda-12080"))',
96+
'cfg(feature, values("cuda-12090"))',
97+
'cfg(feature, values("cuda-13000"))',
98+
'cfg(feature, values("cuda-13010"))',
99+
'cfg(feature, values("cuda-13020"))',
100+
] }

crates/ringkernel-cuda/README.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,11 @@ managing persistent GPU kernels.
1010

1111
## Requirements
1212

13-
- NVIDIA GPU with Compute Capability 7.0 or higher (Volta, Turing, Ampere, Ada,
14-
Hopper, Blackwell)
13+
- NVIDIA GPU with Compute Capability 6.0 or higher (Pascal and newer) for the
14+
core persistent-actor/cooperative-groups path. Some features have higher
15+
floors — Thread Block Clusters, DSMEM, TMA, and Green Contexts all require
16+
Hopper (CC 9.0+). See the top-level README's "Feature to minimum compute
17+
capability" table for the full per-feature breakdown.
1518
- CUDA Toolkit 12.x or later
1619
- cudarc 0.19.3 (pinned via workspace)
1720
- Linux (native) or Windows (WSL2, with cooperative-group limitations)

crates/ringkernel-cuda/src/hopper/mod.rs

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,22 @@
1212
pub mod async_mem;
1313
pub mod cluster;
1414
pub mod dsmem;
15+
// Green Contexts (CUDA 12.4+) — cudarc only exposes `CUgreenCtx` and its FFI
16+
// functions behind these version features (matching NVIDIA's own
17+
// introduction of the API in CUDA 12.4). Gating this at compile time, not
18+
// just at runtime via `check_hopper_support()` below, so the crate still
19+
// builds on any CUDA Toolkit older than 12.4 — which otherwise fails to
20+
// compile at all, on any GPU, regardless of architecture.
21+
#[cfg(any(
22+
feature = "cuda-12040",
23+
feature = "cuda-12050",
24+
feature = "cuda-12060",
25+
feature = "cuda-12080",
26+
feature = "cuda-12090",
27+
feature = "cuda-13000",
28+
feature = "cuda-13010",
29+
feature = "cuda-13020"
30+
))]
1531
pub mod green_ctx;
1632
pub mod lifecycle;
1733
pub mod tma;
@@ -43,3 +59,97 @@ pub const MAX_PORTABLE_CLUSTER_SIZE: u32 = 8;
4359

4460
/// Maximum cluster size on Blackwell (B200).
4561
pub const MAX_BLACKWELL_CLUSTER_SIZE: u32 = 16;
62+
63+
#[cfg(test)]
64+
mod pascal_compat_tests {
65+
//! Confirms Hopper-only feature checks degrade gracefully (return an
66+
//! `Err`/`false`, not a panic or UB) on pre-Hopper hardware, now that the
67+
//! compile-time gating fix above lets this module build at all on CUDA
68+
//! Toolkits older than 12.4. Requires real GPU hardware — run with:
69+
//! cargo test -p ringkernel-cuda --features cuda -- --ignored pascal_compat
70+
use super::*;
71+
use crate::device::CudaDevice;
72+
73+
#[test]
74+
#[ignore] // Requires CUDA hardware
75+
fn pascal_compat_check_hopper_support_degrades_gracefully() {
76+
let device = CudaDevice::new(0).expect("Failed to create device");
77+
let (major, minor) = device.compute_capability();
78+
println!("Device compute capability: {major}.{minor}");
79+
80+
let result = check_hopper_support(&device);
81+
if major < 9 {
82+
assert!(
83+
result.is_err(),
84+
"check_hopper_support returned Ok on pre-Hopper hardware"
85+
);
86+
} else {
87+
assert!(result.is_ok());
88+
}
89+
}
90+
91+
#[test]
92+
#[ignore] // Requires CUDA hardware
93+
fn pascal_compat_supports_cluster_launch_degrades_gracefully() {
94+
let device = CudaDevice::new(0).expect("Failed to create device");
95+
let (major, _) = device.compute_capability();
96+
97+
let supported = supports_cluster_launch(&device);
98+
if major < 9 {
99+
assert!(
100+
!supported,
101+
"supports_cluster_launch returned true on pre-Hopper hardware"
102+
);
103+
}
104+
}
105+
106+
#[test]
107+
#[ignore] // Requires CUDA hardware
108+
fn pascal_compat_dsmem_degrades_gracefully() {
109+
let device = CudaDevice::new(0).expect("Failed to create device");
110+
let (major, _) = device.compute_capability();
111+
112+
let available = dsmem::is_dsmem_available(&device, 4);
113+
if major < 9 {
114+
assert!(
115+
!available,
116+
"is_dsmem_available returned true on pre-Hopper hardware"
117+
);
118+
}
119+
}
120+
121+
#[test]
122+
#[ignore] // Requires CUDA hardware
123+
fn pascal_compat_cluster_size_degrades_to_one() {
124+
let device = CudaDevice::new(0).expect("Failed to create device");
125+
let (major, _) = device.compute_capability();
126+
127+
let max_cluster = cluster::query_max_cluster_size(&device, std::ptr::null_mut())
128+
.expect("query_max_cluster_size should not error");
129+
if major < 9 {
130+
assert_eq!(
131+
max_cluster, 1,
132+
"query_max_cluster_size did not degrade to 1 on pre-Hopper hardware"
133+
);
134+
}
135+
}
136+
137+
#[test]
138+
#[ignore] // Requires CUDA hardware
139+
fn pascal_compat_cooperative_groups_still_available() {
140+
// Cooperative groups (grid.sync()) have a lower floor (CC 6.0+,
141+
// Pascal) than Hopper-specific features (CC 9.0+) — confirms the
142+
// gating logic distinguishes per-feature floors correctly, rather
143+
// than treating "not Hopper" as "nothing works".
144+
let device = CudaDevice::new(0).expect("Failed to create device");
145+
let (major, _) = device.compute_capability();
146+
147+
let coop = device.supports_cooperative_groups();
148+
if major >= 6 {
149+
assert!(
150+
coop,
151+
"supports_cooperative_groups returned false on CC 6.0+ hardware"
152+
);
153+
}
154+
}
155+
}

crates/ringkernel-cuda/src/lib.rs

Lines changed: 75 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,11 @@
1111
//!
1212
//! # Requirements
1313
//!
14-
//! - NVIDIA GPU with Compute Capability 7.0+
14+
//! - NVIDIA GPU with Compute Capability 6.0+ (Pascal and newer) for the core
15+
//! persistent-actor/cooperative-groups path. Some features have higher
16+
//! floors (e.g. Thread Block Clusters/DSMEM/TMA/Green Contexts require
17+
//! Hopper, CC 9.0+) — see the top-level README's "Feature to minimum
18+
//! compute capability" table for the full breakdown.
1519
//! - CUDA Toolkit 11.0+
1620
//! - Native Linux (persistent kernels) or WSL2 (event-driven fallback)
1721
//!
@@ -227,12 +231,78 @@ pub fn compile_ptx(_cuda_source: &str) -> ringkernel_core::error::Result<String>
227231
))
228232
}
229233

230-
/// PTX kernel source template for persistent ring kernel.
234+
/// PTX kernel source template for persistent ring kernel, generic over the
235+
/// target compute capability (major, minor).
231236
///
232237
/// This is a minimal kernel that immediately marks itself as terminated.
233-
/// Uses PTX 8.0 / sm_75 as the lowest common denominator that supports
234-
/// cooperative groups. PTX is forward-compatible, so sm_75 PTX runs on
235-
/// sm_89/sm_90/sm_100 and newer GPUs.
238+
///
239+
/// PTX `.target` directives are only forward-compatible (PTX built for
240+
/// `sm_X` runs on `sm_X` and newer, never older) — so this must be
241+
/// generated per-device rather than hardcoded to a single value. Maps the
242+
/// device's actual compute capability to the corresponding PTX ISA
243+
/// version, matching the minimum ISA version that introduced each `sm_XX`
244+
/// target (per NVIDIA's PTX ISA documentation).
245+
pub fn ring_kernel_ptx_template_for(major: u32, minor: u32) -> String {
246+
let ptx_version = match (major, minor) {
247+
// Pascal
248+
(6, 0) | (6, 1) | (6, 2) => "5.0",
249+
// Volta / Turing
250+
(7, 0) | (7, 2) => "6.0",
251+
(7, 5) => "6.3",
252+
// Ampere
253+
(8, 0) => "7.0",
254+
(8, 6) | (8, 7) => "7.1",
255+
(8, 9) => "8.0",
256+
// Hopper
257+
(9, 0) => "8.0",
258+
// Blackwell and newer — fall through to the newest known ISA
259+
(major, _) if major >= 10 => "8.5",
260+
// Below Pascal (Maxwell/Kepler) or anything unrecognized: fall back
261+
// to the oldest ISA version this template's instructions need.
262+
_ => "5.0",
263+
};
264+
format!(
265+
r#"
266+
.version {ptx_version}
267+
.target sm_{major}{minor}
268+
.address_size 64
269+
270+
.visible .entry ring_kernel_main(
271+
.param .u64 control_block_ptr,
272+
.param .u64 input_queue_ptr,
273+
.param .u64 output_queue_ptr,
274+
.param .u64 shared_state_ptr
275+
) {{
276+
.reg .u64 %cb_ptr;
277+
.reg .u32 %one;
278+
279+
// Load control block pointer
280+
ld.param.u64 %cb_ptr, [control_block_ptr];
281+
282+
// Mark as terminated immediately (offset 8)
283+
mov.u32 %one, 1;
284+
st.global.u32 [%cb_ptr + 8], %one;
285+
286+
ret;
287+
}}
288+
"#
289+
)
290+
}
291+
292+
/// PTX kernel source template for persistent ring kernel — **deprecated**
293+
/// fixed-`sm_75` fallback, kept only for API compatibility with existing
294+
/// callers that don't have a device handle available.
295+
///
296+
/// PTX built for `sm_75` will fail to load (`CUDA_ERROR_INVALID_PTX`) on any
297+
/// GPU with a compute capability below 7.5 (e.g. Pascal, sm_61) — PTX
298+
/// forward-compatibility only extends to equal-or-newer architectures.
299+
/// Prefer [`ring_kernel_ptx_template_for`] with the actual target device's
300+
/// compute capability (`CudaDevice::compute_capability`, not part of this
301+
/// crate's public API surface).
302+
#[deprecated(
303+
since = "1.1.1",
304+
note = "hardcodes sm_75, breaking on Pascal and older GPUs — use ring_kernel_ptx_template_for(major, minor) with the real device's compute capability instead"
305+
)]
236306
pub const RING_KERNEL_PTX_TEMPLATE: &str = r#"
237307
.version 8.0
238308
.target sm_75

crates/ringkernel-cuda/src/runtime.rs

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ use crate::device::CudaDevice;
2020
use crate::kernel::CudaKernel;
2121
use crate::memory::CudaMemoryPool;
2222
use crate::persistent::{PersistentSimulation, PersistentSimulationConfig};
23-
use crate::RING_KERNEL_PTX_TEMPLATE;
23+
use crate::ring_kernel_ptx_template_for;
2424

2525
/// CUDA runtime for RingKernel.
2626
pub struct CudaRuntime {
@@ -263,6 +263,13 @@ impl RingKernelRuntime for CudaRuntime {
263263

264264
let sim = PersistentSimulation::new(&self.device, sim_config)?;
265265

266+
// Generate PTX targeting the actual device's compute capability
267+
// — PTX .target is forward-compatible only (never backward), so
268+
// this must match (or be older than) the real device, not a
269+
// hardcoded value.
270+
let (cc_major, cc_minor) = self.device.compute_capability();
271+
let template_ptx = ring_kernel_ptx_template_for(cc_major, cc_minor);
272+
266273
// Determine PTX and function name for the persistent kernel.
267274
// Use cooperative kernel PTX from build.rs if available.
268275
#[cfg(feature = "cooperative")]
@@ -274,10 +281,7 @@ impl RingKernelRuntime for CudaRuntime {
274281
"Cooperative PTX not available (nvcc not found at build time), \
275282
falling back to template PTX"
276283
);
277-
(
278-
RING_KERNEL_PTX_TEMPLATE.to_string(),
279-
"ring_kernel_main".to_string(),
280-
)
284+
(template_ptx.clone(), "ring_kernel_main".to_string())
281285
} else {
282286
(coop_ptx.to_string(), "coop_persistent_fdtd".to_string())
283287
}
@@ -288,15 +292,12 @@ impl RingKernelRuntime for CudaRuntime {
288292
tracing::warn!(
289293
"Cooperative feature not enabled, persistent simulation will use template PTX"
290294
);
291-
(
292-
RING_KERNEL_PTX_TEMPLATE.to_string(),
293-
"ring_kernel_main".to_string(),
294-
)
295+
(template_ptx.clone(), "ring_kernel_main".to_string())
295296
};
296297

297298
// Still load PTX for the CudaKernel's module/function fields
298299
// (needed for state transition to Launched)
299-
kernel.load_ptx(RING_KERNEL_PTX_TEMPLATE)?;
300+
kernel.load_ptx(&template_ptx)?;
300301

301302
let kernel = Arc::new(kernel);
302303

@@ -321,8 +322,9 @@ impl RingKernelRuntime for CudaRuntime {
321322

322323
Ok(KernelHandle::new(id, kernel))
323324
} else {
324-
// Standard path: load template PTX
325-
kernel.load_ptx(RING_KERNEL_PTX_TEMPLATE)?;
325+
// Standard path: load template PTX, targeting the actual device.
326+
let (cc_major, cc_minor) = self.device.compute_capability();
327+
kernel.load_ptx(&ring_kernel_ptx_template_for(cc_major, cc_minor))?;
326328

327329
let kernel = Arc::new(kernel);
328330
self.kernels.write().insert(id.clone(), Arc::clone(&kernel));

0 commit comments

Comments
 (0)