#[cfg(target_os = "linux")]
use std::cell::Cell;
#[cfg(target_os = "linux")]
use std::collections::HashMap;
#[cfg(target_os = "linux")]
use std::panic::{self, AssertUnwindSafe, catch_unwind};
use std::sync::OnceLock;
use std::sync::atomic::{AtomicU64, Ordering};
#[cfg(target_os = "linux")]
use std::sync::{Arc, Mutex};
use super::device::GpuDeviceInfo;
use super::gpu_error::GpuError;
use super::policy::GpuDispatchPolicy;
#[cfg(target_os = "linux")]
use cudarc::driver::{CudaContext, result, sys};
#[path = "runtime_diagnostics.rs"]
pub(crate) mod diagnostics;
#[derive(Clone, Debug)]
#[must_use]
pub struct GpuRuntime {
pub device: GpuDeviceInfo,
pub devices: Vec<GpuDeviceInfo>,
pub policy: GpuDispatchPolicy,
pub memory_budget_bytes: usize,
}
static CPU_REASON: OnceLock<String> = OnceLock::new();
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum GpuAbsence {
UnsupportedPlatform,
DriverUnavailable { reason: String },
NoDevice { reason: String },
}
impl std::fmt::Display for GpuAbsence {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::UnsupportedPlatform => {
f.write_str("CUDA support is unavailable on this platform")
}
Self::DriverUnavailable { reason } | Self::NoDevice { reason } => f.write_str(reason),
}
}
}
#[derive(Debug)]
pub enum GpuAvailability {
Available(GpuRuntime),
Absent(GpuAbsence),
}
#[derive(Clone, Copy, Debug)]
pub enum GpuAvailabilityRef<'a> {
Available(&'a GpuRuntime),
Absent(&'a GpuAbsence),
}
static RESOLUTION_CALLS: AtomicU64 = AtomicU64::new(0);
#[cfg(target_os = "linux")]
thread_local! {
static CUDARC_RECOVERY_ACTIVE: Cell<bool> = const { Cell::new(false) };
}
#[cfg(target_os = "linux")]
fn panic_message(payload: &(dyn std::any::Any + Send)) -> Option<&str> {
payload
.downcast_ref::<&'static str>()
.copied()
.or_else(|| payload.downcast_ref::<String>().map(String::as_str))
}
#[cfg(target_os = "linux")]
fn install_cudarc_panic_filter() {
static HOOK_INSTALLED: OnceLock<()> = OnceLock::new();
HOOK_INSTALLED.get_or_init(|| {
let prior = panic::take_hook();
panic::set_hook(Box::new(move |info| {
if cfg!(panic = "unwind")
&& CUDARC_RECOVERY_ACTIVE.with(Cell::get)
&& panic_message(info.payload())
.is_some_and(|message| message.starts_with("Unable to dynamically load"))
{
return;
}
prior(info);
}));
});
}
#[cfg(target_os = "linux")]
fn catch_cudarc<T>(call: impl FnOnce() -> T) -> Result<T, String> {
install_cudarc_panic_filter();
struct RecoveryScope(bool);
impl Drop for RecoveryScope {
fn drop(&mut self) {
CUDARC_RECOVERY_ACTIVE.with(|active| active.set(self.0));
}
}
let scope = RecoveryScope(CUDARC_RECOVERY_ACTIVE.with(|active| active.replace(true)));
let outcome = catch_unwind(AssertUnwindSafe(call));
drop(scope);
match outcome {
Ok(value) => Ok(value),
Err(payload) => match panic_message(payload.as_ref()) {
Some(message) if message.starts_with("Unable to dynamically load") => {
Err(message.to_owned())
}
_ => panic::resume_unwind(payload),
},
}
}
impl GpuRuntime {
pub fn probe() -> Result<GpuAvailability, GpuError> {
#[cfg(target_os = "linux")]
{
catch_cudarc(Self::probe_devices)
.map_err(|reason| GpuError::RuntimeDependencyUnavailable { reason })?
}
#[cfg(not(target_os = "linux"))]
Self::probe_devices()
}
fn probe_devices() -> Result<GpuAvailability, GpuError> {
#[cfg(not(target_os = "linux"))]
{
let reason = "CUDA support not compiled into this build";
Self::record_cpu_reason(reason);
diagnostics::log_cuda_disabled(reason);
return Ok(GpuAvailability::Absent(GpuAbsence::UnsupportedPlatform));
}
#[cfg(target_os = "linux")]
{
let primary_ready = cuda_context_for(0).is_some();
log::trace!("[GPU] probe pre-init primary context + runtime: {primary_ready}");
match crate::driver::preload_cuda_driver() {
Ok(()) => {}
Err(GpuError::DriverLibraryUnavailable { reason }) => {
Self::record_cpu_reason(reason.clone());
log::info!("[GPU] CUDA acceleration disabled: {reason}");
diagnostics::log_cuda_disabled(&reason);
return Ok(GpuAvailability::Absent(GpuAbsence::DriverUnavailable {
reason,
}));
}
Err(error) => return Err(error),
}
for stem in ["cublas", "cusolver", "cusparse"] {
if let Err(error) = crate::driver::require_cuda_compute_library(stem) {
let reason = format!("lib{stem} unavailable: {error}");
Self::record_cpu_reason(reason.clone());
log::info!("[GPU] CUDA acceleration disabled: {reason}");
diagnostics::log_cuda_disabled(&reason);
return Err(GpuError::RuntimeDependencyUnavailable { reason });
}
}
let device_count = match catch_cudarc(CudaContext::device_count) {
Err(_) => {
return Err(GpuError::DriverCallFailed {
reason: "cudarc failed after the CUDA driver preflight succeeded"
.to_string(),
});
}
Ok(Ok(count)) => count,
Ok(Err(error)) => {
if let Some(absence) = absence_from_driver_init_error(&error) {
let reason = absence.to_string();
Self::record_cpu_reason(reason.clone());
log::info!("[GPU] CUDA acceleration disabled: {reason}");
diagnostics::log_cuda_disabled(&reason);
return Ok(GpuAvailability::Absent(absence));
}
return Err(GpuError::DriverCallFailed {
reason: error.to_string(),
});
}
};
if device_count <= 0 {
let reason = "CUDA driver reported no devices";
Self::record_cpu_reason(reason);
diagnostics::log_cuda_disabled(reason);
return Ok(GpuAvailability::Absent(GpuAbsence::NoDevice {
reason: reason.to_string(),
}));
}
let mut devices = Vec::new();
for ordinal in
0..usize::try_from(device_count).map_err(|_| GpuError::DriverCallFailed {
reason: "negative CUDA device count".into(),
})?
{
let ctx = cuda_context_for(ordinal).ok_or_else(|| {
gpu_err!("failed to create CUDA context for device {ordinal}")
})?;
catch_cudarc(|| ctx.bind_to_thread())
.map_err(|_| GpuError::DriverCallFailed {
reason: "CUDA context binding panicked after driver discovery".to_string(),
})?
.map_err(|err| GpuError::DriverCallFailed {
reason: err.to_string(),
})?;
devices.push(catch_cudarc(|| cuda_device_info(ordinal, &ctx)).map_err(
|_| GpuError::DriverCallFailed {
reason:
"CUDA device inspection panicked after driver discovery".to_string(),
},
)??);
}
devices.sort_by(|a, b| b.score().total_cmp(&a.score()));
let Some(device) = devices.first().cloned() else {
Self::record_cpu_reason("CUDA driver reported no usable devices");
diagnostics::log_cuda_disabled("CUDA driver reported no usable devices");
return Ok(GpuAvailability::Absent(GpuAbsence::NoDevice {
reason: "CUDA driver reported no usable devices".to_string(),
}));
};
let policy = crate::calibration::calibrated_policy_for_device(&device);
let memory_budget_bytes = device.memory_budget_bytes();
diagnostics::log_cuda_enabled(&device, &policy);
diagnostics::log_cuda_pool(&devices);
Ok(GpuAvailability::Available(Self {
device,
devices,
policy,
memory_budget_bytes,
}))
}
}
pub fn availability() -> Result<GpuAvailabilityRef<'static>, GpuError> {
RESOLUTION_CALLS.fetch_add(1, Ordering::Relaxed);
static RUNTIME: OnceLock<Result<GpuAvailability, GpuError>> = OnceLock::new();
let cached = RUNTIME.get_or_init(|| {
let outcome = Self::probe();
if let Err(error) = &outcome {
let reason = error.to_string();
Self::record_cpu_reason(reason.clone());
diagnostics::log_cuda_disabled(&reason);
}
if matches!(&outcome, Ok(GpuAvailability::Available(_))) {
gam_linalg::gpu_hook::register_gpu_dispatch(Box::new(
super::linalg_dispatch::CudaGemmDispatch,
));
}
outcome
});
match cached {
Ok(GpuAvailability::Available(runtime)) => Ok(GpuAvailabilityRef::Available(runtime)),
Ok(GpuAvailability::Absent(reason)) => Ok(GpuAvailabilityRef::Absent(reason)),
Err(error) => Err(error.clone()),
}
}
pub fn resolve(policy: super::GpuPolicy) -> Result<Option<&'static Self>, GpuError> {
if policy == super::GpuPolicy::Off {
return Ok(None);
}
Self::resolve_availability(policy, Self::availability())
}
fn resolve_availability<'a>(
policy: super::GpuPolicy,
availability: Result<GpuAvailabilityRef<'a>, GpuError>,
) -> Result<Option<&'a Self>, GpuError> {
match availability? {
GpuAvailabilityRef::Available(runtime) => Ok(Some(runtime)),
GpuAvailabilityRef::Absent(_reason) if policy == super::GpuPolicy::Auto => Ok(None),
GpuAvailabilityRef::Absent(reason) => Err(GpuError::RequiredDeviceUnavailable {
reason: reason.to_string(),
}),
}
}
pub fn require() -> Result<&'static Self, GpuError> {
Self::resolve(super::GpuPolicy::Required)?.ok_or_else(|| {
GpuError::RequiredDeviceUnavailable {
reason: "required CUDA runtime resolved to an absent state".to_string(),
}
})
}
pub fn resolve_if_fused_batch_exceeds_floor(
policy: super::GpuPolicy,
rows: usize,
) -> Result<Option<&'static Self>, GpuError> {
if rows < GpuDispatchPolicy::MIN_CALIBRATABLE_FUSED_KERNEL_N {
return Ok(None);
}
Self::resolve(policy)
}
#[must_use]
pub fn policy(&self) -> &GpuDispatchPolicy {
&self.policy
}
#[must_use]
pub fn selected_device(&self) -> &GpuDeviceInfo {
&self.device
}
#[must_use]
pub(crate) fn cpu_reason() -> Option<&'static str> {
CPU_REASON.get().map(String::as_str)
}
fn record_cpu_reason(reason: impl Into<String>) {
if let Err(dropped) = CPU_REASON.set(reason.into()) {
log::debug!(
"CPU fallback reason already recorded as {:?}; keeping it and dropping '{dropped}'",
CPU_REASON.get().map(String::as_str)
);
}
}
}
#[cfg(target_os = "linux")]
fn absence_from_driver_init_error(error: &result::DriverError) -> Option<GpuAbsence> {
use sys::cudaError_enum as CudaErrorCode;
let code = error.0;
let classification = match code {
CudaErrorCode::CUDA_ERROR_NO_DEVICE => {
return Some(GpuAbsence::NoDevice {
reason: format!(
"CUDA driver initialized but reports no attached device ({code:?})"
),
});
}
CudaErrorCode::CUDA_ERROR_STUB_LIBRARY => {
"the loaded libcuda is a linker stub, not a real driver"
}
CudaErrorCode::CUDA_ERROR_SYSTEM_NOT_READY => {
"the CUDA system is not ready (kernel driver or fabric daemon not running)"
}
CudaErrorCode::CUDA_ERROR_SYSTEM_DRIVER_MISMATCH => {
"the CUDA userland libraries do not match the host kernel driver"
}
CudaErrorCode::CUDA_ERROR_COMPAT_NOT_SUPPORTED_ON_DEVICE => {
"CUDA forward-compatibility mode is not supported on the visible device"
}
_ => return None,
};
Some(GpuAbsence::DriverUnavailable {
reason: format!("CUDA initialization refused: {classification} ({code:?})"),
})
}
#[cfg(target_os = "linux")]
fn ensure_cuda_runtime_device(ordinal: usize) {
let Ok(o) = i32::try_from(ordinal) else {
return;
};
let set_rc = unsafe { cudarc::runtime::sys::cudaSetDevice(o) };
log::trace!("[GPU] runtime cudaSetDevice({o}) -> {set_rc:?}");
let mut p: *mut core::ffi::c_void = core::ptr::null_mut();
let malloc_rc = unsafe { cudarc::runtime::sys::cudaMalloc(&mut p as *mut _ as *mut _, 256) };
log::trace!("[GPU] runtime cudaMalloc -> {malloc_rc:?}");
if !p.is_null() {
let free_rc = unsafe { cudarc::runtime::sys::cudaFree(p) };
log::trace!("[GPU] runtime cudaFree -> {free_rc:?}");
}
}
#[cfg(target_os = "linux")]
thread_local! {
static BOUND_RUNTIME_ORDINAL: Cell<Option<usize>> = const { Cell::new(None) };
}
#[cfg(target_os = "linux")]
fn bind_and_touch_runtime(ordinal: usize, ctx: &Arc<CudaContext>) {
if BOUND_RUNTIME_ORDINAL.with(Cell::get) == Some(ordinal) {
return;
}
let bound = catch_cudarc(|| ctx.bind_to_thread());
log::trace!(
"[GPU] cuda_context_for bind ok={} ordinal={ordinal}",
matches!(bound, Ok(Ok(())))
);
ensure_cuda_runtime_device(ordinal);
if matches!(bound, Ok(Ok(()))) {
BOUND_RUNTIME_ORDINAL.with(|c| c.set(Some(ordinal)));
}
}
#[cfg(target_os = "linux")]
pub fn cuda_context_for(ordinal: usize) -> Option<Arc<CudaContext>> {
static CONTEXTS: OnceLock<Mutex<HashMap<usize, Arc<CudaContext>>>> = OnceLock::new();
let contexts = CONTEXTS.get_or_init(|| Mutex::new(HashMap::new()));
if let Some(ctx) = contexts.lock().ok()?.get(&ordinal).cloned() {
bind_and_touch_runtime(ordinal, &ctx);
return Some(ctx);
}
let ctx = catch_cudarc(|| CudaContext::new(ordinal)).ok()?.ok()?;
let out = {
let mut guard = contexts.lock().ok()?;
guard.entry(ordinal).or_insert_with(|| ctx.clone()).clone()
};
bind_and_touch_runtime(ordinal, &out);
Some(out)
}
#[cfg(target_os = "linux")]
fn cuda_device_info(ordinal: usize, ctx: &CudaContext) -> Result<GpuDeviceInfo, GpuError> {
result::init().map_err(|err| GpuError::DriverCallFailed {
reason: err.to_string(),
})?;
let device =
result::device::get(
i32::try_from(ordinal).map_err(|_| GpuError::DriverCallFailed {
reason: "device ordinal overflow".into(),
})?,
)
.map_err(|err| GpuError::DriverCallFailed {
reason: err.to_string(),
})?;
let attr = |attribute| -> Result<i32, GpuError> {
unsafe { result::device::get_attribute(device, attribute) }.map_err(|err| {
GpuError::DriverCallFailed {
reason: err.to_string(),
}
})
};
let (free_mem_bytes, total_mem_bytes) =
ctx.mem_get_info()
.map_err(|err| GpuError::DriverCallFailed {
reason: err.to_string(),
})?;
let major = attr(sys::CUdevice_attribute_enum::CU_DEVICE_ATTRIBUTE_COMPUTE_CAPABILITY_MAJOR)?;
let minor = attr(sys::CUdevice_attribute_enum::CU_DEVICE_ATTRIBUTE_COMPUTE_CAPABILITY_MINOR)?;
Ok(GpuDeviceInfo {
ordinal,
name: result::device::get_name(device).unwrap_or_else(|err| {
log::debug!(
"CUDA device {ordinal}: name query failed ({err}); using a positional label"
);
format!("CUDA device {ordinal}")
}),
capability: super::device::GpuCapability::from_compute_capability(major, minor),
sm_count: attr(sys::CUdevice_attribute_enum::CU_DEVICE_ATTRIBUTE_MULTIPROCESSOR_COUNT)?,
max_threads_per_sm: attr(
sys::CUdevice_attribute_enum::CU_DEVICE_ATTRIBUTE_MAX_THREADS_PER_MULTIPROCESSOR,
)?,
max_shared_mem_per_block: attr(
sys::CUdevice_attribute_enum::CU_DEVICE_ATTRIBUTE_MAX_SHARED_MEMORY_PER_BLOCK,
)
.unwrap_or(0) as usize,
l2_cache_bytes: attr(sys::CUdevice_attribute_enum::CU_DEVICE_ATTRIBUTE_L2_CACHE_SIZE)
.unwrap_or(0) as usize,
total_mem_bytes,
free_mem_bytes,
ecc_enabled: attr(sys::CUdevice_attribute_enum::CU_DEVICE_ATTRIBUTE_ECC_ENABLED)
.unwrap_or(0)
!= 0,
integrated: attr(sys::CUdevice_attribute_enum::CU_DEVICE_ATTRIBUTE_INTEGRATED).unwrap_or(0)
!= 0,
mig_mode: false,
})
}
#[cfg(test)]
mod policy_resolution_contract_tests {
use super::*;
use crate::GpuPolicy;
#[cfg(target_os = "linux")]
#[test]
fn cudarc_loader_panic_diagnostics_follow_recovery_scope() {
const CHILD_MODE_PREFIX: &str = "__gam_cudarc_child_";
const LOADER_PANIC: &str = "Unable to dynamically load synthetic CUDA library";
if let Some(mode) = std::env::args()
.find_map(|argument| argument.strip_prefix(CHILD_MODE_PREFIX).map(str::to_owned))
{
install_cudarc_panic_filter();
match mode.as_str() {
"caught" => {
assert_eq!(
catch_cudarc::<()>(|| panic!("{LOADER_PANIC}")),
Err(LOADER_PANIC.into()),
);
assert!(!CUDARC_RECOVERY_ACTIVE.with(Cell::get));
}
"nested" => {
let outer = catch_cudarc::<()>(|| {
assert!(catch_cudarc::<()>(|| panic!("{LOADER_PANIC}")).is_err());
assert!(CUDARC_RECOVERY_ACTIVE.with(Cell::get));
panic!("{LOADER_PANIC}");
});
assert_eq!(outer, Err(LOADER_PANIC.into()));
assert!(!CUDARC_RECOVERY_ACTIVE.with(Cell::get));
}
"after" => {
assert!(catch_cudarc::<()>(|| panic!("{LOADER_PANIC}")).is_err());
panic!("{LOADER_PANIC}");
}
"other_thread" => {
catch_cudarc(|| {
assert!(
std::thread::spawn(|| panic!("{LOADER_PANIC}"))
.join()
.is_err()
);
})
.expect("a different thread's panic must not enter this recovery");
}
"unrelated" => {
catch_cudarc::<()>(|| panic!("unrelated failure"))
.expect("unrelated panics must unwind");
}
"unguarded" => panic!("{LOADER_PANIC}"),
_ => panic!("unknown subprocess mode: {mode}"),
}
return;
}
for (mode, succeeds, diagnostic) in [
("caught", true, None),
("nested", true, None),
("after", false, Some(LOADER_PANIC)),
("other_thread", true, Some(LOADER_PANIC)),
("unrelated", false, Some("unrelated failure")),
("unguarded", false, Some(LOADER_PANIC)),
] {
let output = std::process::Command::new(std::env::current_exe().expect("test binary"))
.args([
"--exact",
"device_runtime::policy_resolution_contract_tests::cudarc_loader_panic_diagnostics_follow_recovery_scope",
"--nocapture",
])
.args(["--skip", &format!("{CHILD_MODE_PREFIX}{mode}")])
.output()
.expect("run hook regression subprocess");
let stderr = String::from_utf8_lossy(&output.stderr);
assert_eq!(output.status.success(), succeeds, "mode={mode}: {stderr}");
assert!(String::from_utf8_lossy(&output.stdout).contains("running 1 test"));
match diagnostic {
Some(message) => assert!(stderr.contains(message), "mode={mode}: {stderr}"),
None => assert!(stderr.is_empty(), "mode={mode}: {stderr}"),
}
}
}
#[test]
fn auto_maps_only_typed_absence_to_none() {
let absence = GpuAbsence::NoDevice {
reason: "synthetic device-free absence".to_string(),
};
let resolved = GpuRuntime::resolve_availability(
GpuPolicy::Auto,
Ok(GpuAvailabilityRef::Absent(&absence)),
)
.expect("typed absence is expected under Auto");
assert!(resolved.is_none());
}
#[test]
fn exhausted_memory_does_not_erase_a_present_runtime_932() {
let device = GpuDeviceInfo {
ordinal: 0,
name: "memory-exhausted fixture".to_string(),
capability: crate::device::GpuCapability::from_compute_capability(8, 0),
sm_count: 1,
max_threads_per_sm: 2048,
max_shared_mem_per_block: 48 * 1024,
l2_cache_bytes: 1024 * 1024,
total_mem_bytes: 1024 * 1024 * 1024,
free_mem_bytes: 0,
ecc_enabled: false,
integrated: false,
mig_mode: false,
};
let runtime = GpuRuntime {
memory_budget_bytes: device.memory_budget_bytes(),
devices: vec![device.clone()],
device,
policy: GpuDispatchPolicy::default(),
};
assert_eq!(runtime.memory_budget_bytes, 0);
for policy in [GpuPolicy::Auto, GpuPolicy::Required] {
let resolved = GpuRuntime::resolve_availability(
policy,
Ok(GpuAvailabilityRef::Available(&runtime)),
)
.expect("memory pressure is not missing hardware")
.expect("the present runtime must survive resolution");
assert!(std::ptr::eq(resolved, &runtime));
let error = GpuRuntime::resolve_availability(
policy,
Err(GpuError::DriverCallFailed {
reason: "CUDA_ERROR_OUT_OF_MEMORY".to_string(),
}),
)
.expect_err("allocation faults must retain their diagnosis");
assert!(matches!(
error,
GpuError::DriverCallFailed { ref reason }
if reason == "CUDA_ERROR_OUT_OF_MEMORY"
));
}
}
#[test]
fn required_turns_only_typed_absence_into_required_unavailable() {
let absence = GpuAbsence::DriverUnavailable {
reason: "synthetic missing driver".to_string(),
};
let error = GpuRuntime::resolve_availability(
GpuPolicy::Required,
Ok(GpuAvailabilityRef::Absent(&absence)),
)
.expect_err("Required must reject typed absence");
assert!(matches!(
error,
GpuError::RequiredDeviceUnavailable { ref reason }
if reason == "synthetic missing driver"
));
}
#[cfg(target_os = "linux")]
#[test]
fn driver_mismatch_at_init_is_typed_absence_not_a_fault() {
for code in [
sys::cudaError_enum::CUDA_ERROR_SYSTEM_DRIVER_MISMATCH,
sys::cudaError_enum::CUDA_ERROR_STUB_LIBRARY,
sys::cudaError_enum::CUDA_ERROR_SYSTEM_NOT_READY,
sys::cudaError_enum::CUDA_ERROR_COMPAT_NOT_SUPPORTED_ON_DEVICE,
] {
let absence = absence_from_driver_init_error(&result::DriverError(code))
.unwrap_or_else(|| panic!("{code:?} is an environment fact, not a device fault"));
assert!(
matches!(absence, GpuAbsence::DriverUnavailable { .. }),
"{code:?} must classify as an unavailable driver"
);
let resolved = GpuRuntime::resolve_availability(
GpuPolicy::Auto,
Ok(GpuAvailabilityRef::Absent(&absence)),
)
.expect("Auto must accept driver-environment absence");
assert!(resolved.is_none(), "Auto must fall back to CPU on {code:?}");
let required_error = GpuRuntime::resolve_availability(
GpuPolicy::Required,
Ok(GpuAvailabilityRef::Absent(&absence)),
)
.expect_err("Required must refuse driver-environment absence");
assert!(
matches!(required_error, GpuError::RequiredDeviceUnavailable { .. }),
"Required must carry the environment diagnosis for {code:?}"
);
}
let no_device = absence_from_driver_init_error(&result::DriverError(
sys::cudaError_enum::CUDA_ERROR_NO_DEVICE,
))
.expect("no attached device is an environment fact");
assert!(matches!(no_device, GpuAbsence::NoDevice { .. }));
}
#[cfg(target_os = "linux")]
#[test]
fn present_device_faults_never_classify_as_absence() {
for code in [
sys::cudaError_enum::CUDA_ERROR_ILLEGAL_ADDRESS,
sys::cudaError_enum::CUDA_ERROR_OUT_OF_MEMORY,
sys::cudaError_enum::CUDA_ERROR_NOT_INITIALIZED,
sys::cudaError_enum::CUDA_ERROR_ECC_UNCORRECTABLE,
sys::cudaError_enum::CUDA_ERROR_UNKNOWN,
] {
assert!(
absence_from_driver_init_error(&result::DriverError(code)).is_none(),
"{code:?} is a fault of present hardware and must stay a probe fault"
);
}
}
#[test]
fn auto_and_required_preserve_probe_fault_variants() {
for policy in [GpuPolicy::Auto, GpuPolicy::Required] {
let error = GpuRuntime::resolve_availability(
policy,
Err(GpuError::RuntimeDependencyUnavailable {
reason: "synthetic missing cuBLAS".to_string(),
}),
)
.expect_err("probe faults must never project to absence");
assert!(matches!(
error,
GpuError::RuntimeDependencyUnavailable { ref reason }
if reason == "synthetic missing cuBLAS"
));
}
}
}