Skip to main content

oxicuda_driver/
device.rs

1//! CUDA device enumeration and attribute queries.
2//!
3//! This module provides the [`Device`] type, which wraps a `CUdevice` handle
4//! obtained from the CUDA driver. Devices are identified by ordinal (0, 1, 2,
5//! ...) and expose a rich set of convenience methods for querying hardware
6//! capabilities such as compute capability, memory size, multiprocessor count,
7//! and various limits.
8//!
9//! # Examples
10//!
11//! ```no_run
12//! use oxicuda_driver::device::{Device, list_devices};
13//!
14//! oxicuda_driver::init()?;
15//! for dev in list_devices()? {
16//!     println!("{}: compute {}.{}",
17//!         dev.name()?,
18//!         dev.compute_capability()?.0,
19//!         dev.compute_capability()?.1,
20//!     );
21//! }
22//! # Ok::<(), oxicuda_driver::error::CudaError>(())
23//! ```
24
25use std::ffi::{c_char, c_int};
26
27use crate::error::CudaResult;
28use crate::ffi::{CUdevice, CUdevice_attribute};
29use crate::loader::try_driver;
30
31// ---------------------------------------------------------------------------
32// Device
33// ---------------------------------------------------------------------------
34
35/// Represents a CUDA-capable GPU device.
36///
37/// Wraps a `CUdevice` handle obtained from the driver API. Devices are
38/// identified by a zero-based ordinal index. The handle is a lightweight
39/// integer that can be freely copied.
40///
41/// # Examples
42///
43/// ```no_run
44/// use oxicuda_driver::device::Device;
45///
46/// oxicuda_driver::init()?;
47/// let device = Device::get(0)?;
48/// println!("GPU: {}", device.name()?);
49/// println!("Memory: {} MB", device.total_memory()? / (1024 * 1024));
50/// let (major, minor) = device.compute_capability()?;
51/// println!("Compute: {major}.{minor}");
52/// # Ok::<(), oxicuda_driver::error::CudaError>(())
53/// ```
54#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
55pub struct Device {
56    /// Raw CUDA device handle (integer ordinal internally).
57    raw: CUdevice,
58    /// The ordinal used to obtain this device.
59    ordinal: i32,
60}
61
62impl Device {
63    // -- Construction --------------------------------------------------------
64
65    /// Get a device handle by ordinal (0-indexed).
66    ///
67    /// # Errors
68    ///
69    /// Returns [`CudaError::InvalidDevice`](crate::error::CudaError::InvalidDevice) if the ordinal is out of range,
70    /// or [`CudaError::NotInitialized`](crate::error::CudaError::NotInitialized) if the driver has not been loaded.
71    pub fn get(ordinal: i32) -> CudaResult<Self> {
72        let driver = try_driver()?;
73        let mut raw: CUdevice = 0;
74        crate::error::check(unsafe { (driver.cu_device_get)(&mut raw, ordinal) })?;
75        Ok(Self { raw, ordinal })
76    }
77
78    /// Get the number of CUDA-capable devices in the system.
79    ///
80    /// # Errors
81    ///
82    /// Returns an error if the driver cannot enumerate devices.
83    pub fn count() -> CudaResult<i32> {
84        let driver = try_driver()?;
85        let mut count: std::ffi::c_int = 0;
86        crate::error::check(unsafe { (driver.cu_device_get_count)(&mut count) })?;
87        Ok(count)
88    }
89
90    // -- Identity ------------------------------------------------------------
91
92    /// Get the device name (e.g., `"NVIDIA A100-SXM4-80GB"`).
93    ///
94    /// The returned string is an ASCII identifier provided by the driver.
95    ///
96    /// # Errors
97    ///
98    /// Returns an error if the driver call fails.
99    pub fn name(&self) -> CudaResult<String> {
100        let driver = try_driver()?;
101        let mut buf = [0u8; 256];
102        crate::error::check(unsafe {
103            (driver.cu_device_get_name)(buf.as_mut_ptr() as *mut c_char, 256, self.raw)
104        })?;
105        let len = buf.iter().position(|&b| b == 0).unwrap_or(buf.len());
106        Ok(String::from_utf8_lossy(&buf[..len]).into_owned())
107    }
108
109    /// Get total device memory in bytes.
110    ///
111    /// # Errors
112    ///
113    /// Returns an error if the driver call fails.
114    pub fn total_memory(&self) -> CudaResult<usize> {
115        let driver = try_driver()?;
116        let mut bytes: usize = 0;
117        crate::error::check(unsafe { (driver.cu_device_total_mem_v2)(&mut bytes, self.raw) })?;
118        Ok(bytes)
119    }
120
121    // -- Generic attribute query --------------------------------------------
122
123    /// Query an arbitrary device attribute.
124    ///
125    /// This is the low-level building block for all the convenience methods
126    /// below. Callers can use any [`CUdevice_attribute`] variant directly.
127    ///
128    /// # Errors
129    ///
130    /// Returns an error if the attribute is not supported or the driver call
131    /// fails.
132    pub fn attribute(&self, attr: CUdevice_attribute) -> CudaResult<i32> {
133        let driver = try_driver()?;
134        let mut value: std::ffi::c_int = 0;
135        crate::error::check(unsafe {
136            (driver.cu_device_get_attribute)(&mut value, attr, self.raw)
137        })?;
138        Ok(value)
139    }
140
141    // -- Compute capability -------------------------------------------------
142
143    /// Get compute capability as `(major, minor)`.
144    ///
145    /// For example, an A100 returns `(8, 0)` and an RTX 4090 returns `(8, 9)`.
146    ///
147    /// # Errors
148    ///
149    /// Returns an error if the driver call fails.
150    pub fn compute_capability(&self) -> CudaResult<(i32, i32)> {
151        let major = self.attribute(CUdevice_attribute::ComputeCapabilityMajor)?;
152        let minor = self.attribute(CUdevice_attribute::ComputeCapabilityMinor)?;
153        Ok((major, minor))
154    }
155
156    // -- Thread / block / grid limits ---------------------------------------
157
158    /// Get the maximum number of threads per block.
159    pub fn max_threads_per_block(&self) -> CudaResult<i32> {
160        self.attribute(CUdevice_attribute::MaxThreadsPerBlock)
161    }
162
163    /// Get the maximum block dimensions as `(x, y, z)`.
164    pub fn max_block_dim(&self) -> CudaResult<(i32, i32, i32)> {
165        Ok((
166            self.attribute(CUdevice_attribute::MaxBlockDimX)?,
167            self.attribute(CUdevice_attribute::MaxBlockDimY)?,
168            self.attribute(CUdevice_attribute::MaxBlockDimZ)?,
169        ))
170    }
171
172    /// Get the maximum grid dimensions as `(x, y, z)`.
173    pub fn max_grid_dim(&self) -> CudaResult<(i32, i32, i32)> {
174        Ok((
175            self.attribute(CUdevice_attribute::MaxGridDimX)?,
176            self.attribute(CUdevice_attribute::MaxGridDimY)?,
177            self.attribute(CUdevice_attribute::MaxGridDimZ)?,
178        ))
179    }
180
181    /// Get the maximum number of threads per multiprocessor.
182    pub fn max_threads_per_multiprocessor(&self) -> CudaResult<i32> {
183        self.attribute(CUdevice_attribute::MaxThreadsPerMultiprocessor)
184    }
185
186    /// Get the maximum number of blocks per multiprocessor.
187    pub fn max_blocks_per_multiprocessor(&self) -> CudaResult<i32> {
188        self.attribute(CUdevice_attribute::MaxBlocksPerMultiprocessor)
189    }
190
191    // -- Multiprocessor / warp ----------------------------------------------
192
193    /// Get the number of streaming multiprocessors (SMs) on the device.
194    pub fn multiprocessor_count(&self) -> CudaResult<i32> {
195        self.attribute(CUdevice_attribute::MultiprocessorCount)
196    }
197
198    /// Get the warp size in threads (typically 32 for all NVIDIA GPUs).
199    pub fn warp_size(&self) -> CudaResult<i32> {
200        self.attribute(CUdevice_attribute::WarpSize)
201    }
202
203    // -- Memory hierarchy ---------------------------------------------------
204
205    /// Get the maximum shared memory per block in bytes.
206    pub fn max_shared_memory_per_block(&self) -> CudaResult<i32> {
207        self.attribute(CUdevice_attribute::MaxSharedMemoryPerBlock)
208    }
209
210    /// Get the maximum shared memory per multiprocessor in bytes.
211    pub fn max_shared_memory_per_multiprocessor(&self) -> CudaResult<i32> {
212        self.attribute(CUdevice_attribute::MaxSharedMemoryPerMultiprocessor)
213    }
214
215    /// Get the maximum opt-in shared memory per block in bytes.
216    ///
217    /// This is the upper bound achievable via
218    /// `cuFuncSetAttribute(CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES)`.
219    pub fn max_shared_memory_per_block_optin(&self) -> CudaResult<i32> {
220        self.attribute(CUdevice_attribute::MaxSharedMemoryPerBlockOptin)
221    }
222
223    /// Get the maximum number of 32-bit registers per block.
224    pub fn max_registers_per_block(&self) -> CudaResult<i32> {
225        self.attribute(CUdevice_attribute::MaxRegistersPerBlock)
226    }
227
228    /// Get the maximum number of 32-bit registers per multiprocessor.
229    pub fn max_registers_per_multiprocessor(&self) -> CudaResult<i32> {
230        self.attribute(CUdevice_attribute::MaxRegistersPerMultiprocessor)
231    }
232
233    /// Get the L2 cache size in bytes.
234    pub fn l2_cache_size(&self) -> CudaResult<i32> {
235        self.attribute(CUdevice_attribute::L2CacheSize)
236    }
237
238    /// Get the total constant memory on the device in bytes.
239    pub fn total_constant_memory(&self) -> CudaResult<i32> {
240        self.attribute(CUdevice_attribute::TotalConstantMemory)
241    }
242
243    // -- Clock / bus --------------------------------------------------------
244
245    /// Get the core clock rate in kHz.
246    pub fn clock_rate_khz(&self) -> CudaResult<i32> {
247        self.attribute(CUdevice_attribute::ClockRate)
248    }
249
250    /// Get the memory clock rate in kHz.
251    pub fn memory_clock_rate_khz(&self) -> CudaResult<i32> {
252        self.attribute(CUdevice_attribute::MemoryClockRate)
253    }
254
255    /// Get the global memory bus width in bits.
256    pub fn memory_bus_width(&self) -> CudaResult<i32> {
257        self.attribute(CUdevice_attribute::GlobalMemoryBusWidth)
258    }
259
260    // -- PCI topology -------------------------------------------------------
261
262    /// Get the PCI bus ID of the device.
263    pub fn pci_bus_id(&self) -> CudaResult<i32> {
264        self.attribute(CUdevice_attribute::PciBusId)
265    }
266
267    /// Get the PCI device ID.
268    pub fn pci_device_id(&self) -> CudaResult<i32> {
269        self.attribute(CUdevice_attribute::PciDeviceId)
270    }
271
272    /// Get the PCI domain ID.
273    pub fn pci_domain_id(&self) -> CudaResult<i32> {
274        self.attribute(CUdevice_attribute::PciDomainId)
275    }
276
277    // -- Feature / capability flags -----------------------------------------
278
279    /// Check if the device supports managed (unified) memory.
280    pub fn supports_managed_memory(&self) -> CudaResult<bool> {
281        Ok(self.attribute(CUdevice_attribute::ManagedMemory)? != 0)
282    }
283
284    /// Check if the device supports concurrent managed memory access.
285    pub fn supports_concurrent_managed_access(&self) -> CudaResult<bool> {
286        Ok(self.attribute(CUdevice_attribute::ConcurrentManagedAccess)? != 0)
287    }
288
289    /// Check if the device supports concurrent kernel execution.
290    pub fn supports_concurrent_kernels(&self) -> CudaResult<bool> {
291        Ok(self.attribute(CUdevice_attribute::ConcurrentKernels)? != 0)
292    }
293
294    /// Check if the device supports cooperative kernel launches.
295    pub fn supports_cooperative_launch(&self) -> CudaResult<bool> {
296        Ok(self.attribute(CUdevice_attribute::CooperativeLaunch)? != 0)
297    }
298
299    /// Check if ECC memory is enabled on the device.
300    pub fn ecc_enabled(&self) -> CudaResult<bool> {
301        Ok(self.attribute(CUdevice_attribute::EccEnabled)? != 0)
302    }
303
304    /// Check if the device is integrated (shares memory with the host).
305    pub fn is_integrated(&self) -> CudaResult<bool> {
306        Ok(self.attribute(CUdevice_attribute::Integrated)? != 0)
307    }
308
309    /// Check if the device can map host memory into its address space.
310    pub fn can_map_host_memory(&self) -> CudaResult<bool> {
311        Ok(self.attribute(CUdevice_attribute::CanMapHostMemory)? != 0)
312    }
313
314    /// Check if the device uses a unified address space with the host.
315    pub fn supports_unified_addressing(&self) -> CudaResult<bool> {
316        Ok(self.attribute(CUdevice_attribute::UnifiedAddressing)? != 0)
317    }
318
319    /// Check if the device supports stream priorities.
320    pub fn supports_stream_priorities(&self) -> CudaResult<bool> {
321        Ok(self.attribute(CUdevice_attribute::StreamPrioritiesSupported)? != 0)
322    }
323
324    /// Check if the device supports compute preemption.
325    pub fn supports_compute_preemption(&self) -> CudaResult<bool> {
326        Ok(self.attribute(CUdevice_attribute::ComputePreemptionSupported)? != 0)
327    }
328
329    /// Get the number of asynchronous engines (copy engines).
330    pub fn async_engine_count(&self) -> CudaResult<i32> {
331        self.attribute(CUdevice_attribute::AsyncEngineCount)
332    }
333
334    /// Check if the device is on a multi-GPU board.
335    pub fn is_multi_gpu_board(&self) -> CudaResult<bool> {
336        Ok(self.attribute(CUdevice_attribute::IsMultiGpuBoard)? != 0)
337    }
338
339    /// Check if there is a kernel execution timeout enforced by the OS.
340    pub fn has_kernel_exec_timeout(&self) -> CudaResult<bool> {
341        Ok(self.attribute(CUdevice_attribute::KernelExecTimeout)? != 0)
342    }
343
344    // -- Compute mode / driver model ----------------------------------------
345
346    /// Get the compute mode (0=default, 1=exclusive-thread, 2=prohibited,
347    /// 3=exclusive-process).
348    pub fn compute_mode(&self) -> CudaResult<i32> {
349        self.attribute(CUdevice_attribute::ComputeMode)
350    }
351
352    /// Check if the device uses the TCC (Tesla Compute Cluster) driver model.
353    ///
354    /// TCC mode disables the display driver, giving full GPU resources to
355    /// compute workloads.
356    pub fn tcc_driver(&self) -> CudaResult<bool> {
357        Ok(self.attribute(CUdevice_attribute::TccDriver)? != 0)
358    }
359
360    /// Get the multi-GPU board group identifier.
361    ///
362    /// Devices on the same board share the same group ID.
363    pub fn multi_gpu_board_group_id(&self) -> CudaResult<i32> {
364        self.attribute(CUdevice_attribute::MultiGpuBoardGroupId)
365    }
366
367    // -- Memory features (extended) -----------------------------------------
368
369    /// Get the maximum persisting L2 cache size in bytes (Ampere+).
370    pub fn max_persisting_l2_cache_size(&self) -> CudaResult<i32> {
371        self.attribute(CUdevice_attribute::MaxPersistingL2CacheSize)
372    }
373
374    /// Check if the device supports generic memory compression.
375    pub fn supports_generic_compression(&self) -> CudaResult<bool> {
376        Ok(self.attribute(CUdevice_attribute::GenericCompressionSupported)? != 0)
377    }
378
379    /// Check if the device supports pageable memory access.
380    pub fn supports_pageable_memory_access(&self) -> CudaResult<bool> {
381        Ok(self.attribute(CUdevice_attribute::PageableMemoryAccess)? != 0)
382    }
383
384    /// Check if pageable memory access uses host page tables.
385    pub fn pageable_memory_uses_host_page_tables(&self) -> CudaResult<bool> {
386        Ok(self.attribute(CUdevice_attribute::PageableMemoryAccessUsesHostPageTables)? != 0)
387    }
388
389    /// Check if the device supports direct managed memory access from the host.
390    pub fn supports_direct_managed_mem_from_host(&self) -> CudaResult<bool> {
391        Ok(self.attribute(CUdevice_attribute::DirectManagedMemAccessFromHost)? != 0)
392    }
393
394    /// Get memory pool supported handle types as a bitmask.
395    pub fn memory_pool_supported_handle_types(&self) -> CudaResult<i32> {
396        self.attribute(CUdevice_attribute::MemoryPoolSupportedHandleTypes)
397    }
398
399    // -- Advanced features --------------------------------------------------
400
401    /// Check if the device supports host-visible native atomic operations.
402    pub fn supports_host_native_atomics(&self) -> CudaResult<bool> {
403        Ok(self.attribute(CUdevice_attribute::HostNativeAtomicSupported)? != 0)
404    }
405
406    /// Get the ratio of single-precision to double-precision performance.
407    ///
408    /// A higher value means the GPU is relatively faster at FP32 than FP64.
409    pub fn single_to_double_perf_ratio(&self) -> CudaResult<i32> {
410        self.attribute(CUdevice_attribute::SingleToDoublePrecisionPerfRatio)
411    }
412
413    /// Check if the device supports cooperative multi-device kernel launches.
414    pub fn supports_cooperative_multi_device_launch(&self) -> CudaResult<bool> {
415        Ok(self.attribute(CUdevice_attribute::CooperativeMultiDeviceLaunch)? != 0)
416    }
417
418    /// Check if the device supports flushing outstanding remote writes.
419    pub fn supports_flush_remote_writes(&self) -> CudaResult<bool> {
420        Ok(self.attribute(CUdevice_attribute::CanFlushRemoteWrites)? != 0)
421    }
422
423    /// Check if the device supports host-side memory register functions.
424    pub fn supports_host_register(&self) -> CudaResult<bool> {
425        Ok(self.attribute(CUdevice_attribute::HostRegisterSupported)? != 0)
426    }
427
428    /// Check if the device can use host pointers for registered memory.
429    pub fn can_use_host_pointer_for_registered_mem(&self) -> CudaResult<bool> {
430        Ok(self.attribute(CUdevice_attribute::CanUseHostPointerForRegisteredMem)? != 0)
431    }
432
433    /// Check if the device supports GPU Direct RDMA.
434    pub fn supports_gpu_direct_rdma(&self) -> CudaResult<bool> {
435        Ok(self.attribute(CUdevice_attribute::GpuDirectRdmaSupported)? != 0)
436    }
437
438    /// Check if the device supports tensor-map access (Hopper+).
439    pub fn supports_tensor_map_access(&self) -> CudaResult<bool> {
440        Ok(self.attribute(CUdevice_attribute::TensorMapAccessSupported)? != 0)
441    }
442
443    /// Check if the device supports multicast operations.
444    pub fn supports_multicast(&self) -> CudaResult<bool> {
445        Ok(self.attribute(CUdevice_attribute::MulticastSupported)? != 0)
446    }
447
448    /// Check if Multi-Process Service (MPS) is enabled on the device.
449    pub fn mps_enabled(&self) -> CudaResult<bool> {
450        Ok(self.attribute(CUdevice_attribute::MpsEnabled)? != 0)
451    }
452
453    // -- Texture / surface limits -------------------------------------------
454
455    /// Get the maximum 1D texture width.
456    pub fn max_texture_1d_width(&self) -> CudaResult<i32> {
457        self.attribute(CUdevice_attribute::MaxTexture1DWidth)
458    }
459
460    /// Get the maximum 2D texture dimensions as `(width, height)`.
461    pub fn max_texture_2d_dims(&self) -> CudaResult<(i32, i32)> {
462        Ok((
463            self.attribute(CUdevice_attribute::MaxTexture2DWidth)?,
464            self.attribute(CUdevice_attribute::MaxTexture2DHeight)?,
465        ))
466    }
467
468    /// Get the maximum 3D texture dimensions as `(width, height, depth)`.
469    pub fn max_texture_3d_dims(&self) -> CudaResult<(i32, i32, i32)> {
470        Ok((
471            self.attribute(CUdevice_attribute::MaxTexture3DWidth)?,
472            self.attribute(CUdevice_attribute::MaxTexture3DHeight)?,
473            self.attribute(CUdevice_attribute::MaxTexture3DDepth)?,
474        ))
475    }
476
477    /// Check if the device can copy memory and execute a kernel concurrently.
478    pub fn gpu_overlap(&self) -> CudaResult<bool> {
479        Ok(self.attribute(CUdevice_attribute::GpuOverlap)? != 0)
480    }
481
482    /// Get the maximum pitch for memory copies in bytes.
483    pub fn max_pitch(&self) -> CudaResult<i32> {
484        self.attribute(CUdevice_attribute::MaxPitch)
485    }
486
487    /// Get the texture alignment requirement in bytes.
488    pub fn texture_alignment(&self) -> CudaResult<i32> {
489        self.attribute(CUdevice_attribute::TextureAlignment)
490    }
491
492    /// Get the surface alignment requirement in bytes.
493    pub fn surface_alignment(&self) -> CudaResult<i32> {
494        self.attribute(CUdevice_attribute::SurfaceAlignment)
495    }
496
497    /// Check if the device supports deferred mapping of CUDA arrays.
498    pub fn supports_deferred_mapping(&self) -> CudaResult<bool> {
499        Ok(self.attribute(CUdevice_attribute::DeferredMappingCudaArraySupported)? != 0)
500    }
501
502    // -- Memory pool / async features ---------------------------------------
503
504    /// Check if the device supports memory pools (`cudaMallocAsync`).
505    pub fn supports_memory_pools(&self) -> CudaResult<bool> {
506        Ok(self.attribute(CUdevice_attribute::MemoryPoolsSupported)? != 0)
507    }
508
509    /// Check if the device supports cluster launch (Hopper+).
510    pub fn supports_cluster_launch(&self) -> CudaResult<bool> {
511        Ok(self.attribute(CUdevice_attribute::ClusterLaunch)? != 0)
512    }
513
514    /// Check if the device supports virtual memory management APIs.
515    pub fn supports_virtual_memory_management(&self) -> CudaResult<bool> {
516        Ok(self.attribute(CUdevice_attribute::VirtualMemoryManagementSupported)? != 0)
517    }
518
519    /// Check if the device supports POSIX file descriptor handles for IPC.
520    pub fn supports_handle_type_posix_fd(&self) -> CudaResult<bool> {
521        Ok(self.attribute(CUdevice_attribute::HandleTypePosixFileDescriptorSupported)? != 0)
522    }
523
524    /// Check if the device supports Win32 handles for IPC.
525    pub fn supports_handle_type_win32(&self) -> CudaResult<bool> {
526        Ok(self.attribute(CUdevice_attribute::HandleTypeWin32HandleSupported)? != 0)
527    }
528
529    /// Check if the device supports Win32 KMT handles for IPC.
530    pub fn supports_handle_type_win32_kmt(&self) -> CudaResult<bool> {
531        Ok(self.attribute(CUdevice_attribute::HandleTypeWin32KmtHandleSupported)? != 0)
532    }
533
534    /// Check if the device supports GPU Direct RDMA with CUDA VMM.
535    pub fn supports_gpu_direct_rdma_vmm(&self) -> CudaResult<bool> {
536        Ok(self.attribute(CUdevice_attribute::GpuDirectRdmaWithCudaVmmSupported)? != 0)
537    }
538
539    /// Get the GPU Direct RDMA flush-writes options bitmask.
540    pub fn gpu_direct_rdma_flush_writes_options(&self) -> CudaResult<i32> {
541        self.attribute(CUdevice_attribute::GpuDirectRdmaFlushWritesOptions)
542    }
543
544    /// Get the GPU Direct RDMA writes ordering.
545    pub fn gpu_direct_rdma_writes_ordering(&self) -> CudaResult<i32> {
546        self.attribute(CUdevice_attribute::GpuDirectRdmaWritesOrdering)
547    }
548
549    /// Get the maximum access-policy window size for L2 cache.
550    pub fn max_access_policy_window_size(&self) -> CudaResult<i32> {
551        self.attribute(CUdevice_attribute::MaxAccessPolicyWindowSize)
552    }
553
554    /// Get the reserved shared memory per block in bytes.
555    pub fn reserved_shared_memory_per_block(&self) -> CudaResult<i32> {
556        self.attribute(CUdevice_attribute::ReservedSharedMemoryPerBlock)
557    }
558
559    /// Check if timeline semaphore interop is supported.
560    pub fn supports_timeline_semaphore_interop(&self) -> CudaResult<bool> {
561        Ok(self.attribute(CUdevice_attribute::TimelineSemaphoreInteropSupported)? != 0)
562    }
563
564    /// Check if memory sync domain operations are supported.
565    ///
566    /// There is no dedicated `MEM_SYNC_DOMAIN_SUPPORTED` attribute in `cuda.h`;
567    /// support is indicated by a positive
568    /// [`MemSyncDomainCount`](CUdevice_attribute::MemSyncDomainCount).
569    pub fn supports_mem_sync_domain(&self) -> CudaResult<bool> {
570        Ok(self.attribute(CUdevice_attribute::MemSyncDomainCount)? > 0)
571    }
572
573    /// Get the number of memory sync domains.
574    pub fn mem_sync_domain_count(&self) -> CudaResult<i32> {
575        self.attribute(CUdevice_attribute::MemSyncDomainCount)
576    }
577
578    /// Check if GPU-Direct Fabric (RDMA) is supported.
579    pub fn supports_gpu_direct_rdma_fabric(&self) -> CudaResult<bool> {
580        Ok(self.attribute(CUdevice_attribute::GpuDirectRdmaFabricSupported)? != 0)
581    }
582
583    /// Check if unified function pointers are supported.
584    pub fn supports_unified_function_pointers(&self) -> CudaResult<bool> {
585        Ok(self.attribute(CUdevice_attribute::UnifiedFunctionPointers)? != 0)
586    }
587
588    /// Check if IPC event handles are supported.
589    pub fn supports_ipc_events(&self) -> CudaResult<bool> {
590        Ok(self.attribute(CUdevice_attribute::IpcEventSupported)? != 0)
591    }
592
593    /// Get the NUMA configuration of the device.
594    pub fn numa_config(&self) -> CudaResult<i32> {
595        self.attribute(CUdevice_attribute::NumaConfig)
596    }
597
598    /// Get the NUMA ID of the device.
599    pub fn numa_id(&self) -> CudaResult<i32> {
600        self.attribute(CUdevice_attribute::NumaId)
601    }
602
603    /// Get the host NUMA ID of the device.
604    pub fn host_numa_id(&self) -> CudaResult<i32> {
605        self.attribute(CUdevice_attribute::HostNumaId)
606    }
607
608    /// Get the texture pitch alignment requirement in bytes.
609    pub fn texture_pitch_alignment(&self) -> CudaResult<i32> {
610        self.attribute(CUdevice_attribute::TexturePitchAlignment)
611    }
612
613    // -- Structured info ----------------------------------------------------
614
615    /// Gather comprehensive device information in a single call.
616    ///
617    /// Returns a [`DeviceInfo`] with all key properties. Individual attribute
618    /// query failures are silently replaced with default values (`0` / `false`)
619    /// so that the call succeeds even on older drivers that lack some attributes.
620    ///
621    /// # Errors
622    ///
623    /// Returns an error only if the device name or total memory cannot be
624    /// queried (fundamental properties).
625    pub fn info(&self) -> CudaResult<DeviceInfo> {
626        let name = self.name()?;
627        let total_memory_bytes = self.total_memory()?;
628        let (cc_major, cc_minor) = self.compute_capability().unwrap_or((0, 0));
629
630        Ok(DeviceInfo {
631            name,
632            ordinal: self.ordinal,
633            compute_capability: (cc_major, cc_minor),
634            total_memory_bytes,
635            multiprocessor_count: self.multiprocessor_count().unwrap_or(0),
636            max_threads_per_block: self.max_threads_per_block().unwrap_or(0),
637            max_threads_per_sm: self.max_threads_per_multiprocessor().unwrap_or(0),
638            warp_size: self.warp_size().unwrap_or(0),
639            clock_rate_mhz: self.clock_rate_khz().unwrap_or(0) as f64 / 1000.0,
640            memory_clock_rate_mhz: self.memory_clock_rate_khz().unwrap_or(0) as f64 / 1000.0,
641            memory_bus_width_bits: self.memory_bus_width().unwrap_or(0),
642            l2_cache_bytes: self.l2_cache_size().unwrap_or(0),
643            max_shared_memory_per_block: self.max_shared_memory_per_block().unwrap_or(0),
644            max_shared_memory_per_sm: self.max_shared_memory_per_multiprocessor().unwrap_or(0),
645            max_registers_per_block: self.max_registers_per_block().unwrap_or(0),
646            ecc_enabled: self.ecc_enabled().unwrap_or(false),
647            tcc_driver: self.tcc_driver().unwrap_or(false),
648            compute_mode: self.compute_mode().unwrap_or(0),
649            supports_cooperative_launch: self.supports_cooperative_launch().unwrap_or(false),
650            supports_managed_memory: self.supports_managed_memory().unwrap_or(false),
651            max_persisting_l2_cache_bytes: self.max_persisting_l2_cache_size().unwrap_or(0),
652            async_engine_count: self.async_engine_count().unwrap_or(0),
653            supports_memory_pools: self.supports_memory_pools().unwrap_or(false),
654            supports_gpu_direct_rdma: self.supports_gpu_direct_rdma().unwrap_or(false),
655            supports_cluster_launch: self.supports_cluster_launch().unwrap_or(false),
656            supports_concurrent_kernels: self.supports_concurrent_kernels().unwrap_or(false),
657            supports_unified_addressing: self.supports_unified_addressing().unwrap_or(false),
658            max_blocks_per_sm: self.max_blocks_per_multiprocessor().unwrap_or(0),
659            single_to_double_perf_ratio: self.single_to_double_perf_ratio().unwrap_or(0),
660        })
661    }
662
663    // -- Raw access ---------------------------------------------------------
664
665    /// Get the raw `CUdevice` handle for use with FFI calls.
666    #[inline]
667    pub fn raw(&self) -> CUdevice {
668        self.raw
669    }
670
671    /// Get the device ordinal that was used to obtain this handle.
672    #[inline]
673    pub fn ordinal(&self) -> i32 {
674        self.ordinal
675    }
676}
677
678impl std::fmt::Display for Device {
679    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
680        write!(f, "Device({})", self.ordinal)
681    }
682}
683
684// ---------------------------------------------------------------------------
685// DeviceInfo — structured summary
686// ---------------------------------------------------------------------------
687
688/// Comprehensive device information gathered in a single call.
689///
690/// All fields are populated via convenience methods on [`Device`]. Fields that
691/// fail to query (e.g. on an older driver) default to `0` / `false`.
692#[derive(Debug, Clone)]
693pub struct DeviceInfo {
694    /// Human-readable device name.
695    pub name: String,
696    /// Zero-based device ordinal.
697    pub ordinal: i32,
698    /// Compute capability `(major, minor)`.
699    pub compute_capability: (i32, i32),
700    /// Total device memory in bytes.
701    pub total_memory_bytes: usize,
702    /// Number of streaming multiprocessors.
703    pub multiprocessor_count: i32,
704    /// Maximum threads per block.
705    pub max_threads_per_block: i32,
706    /// Maximum threads per streaming multiprocessor.
707    pub max_threads_per_sm: i32,
708    /// Warp size in threads.
709    pub warp_size: i32,
710    /// Core clock rate in MHz.
711    pub clock_rate_mhz: f64,
712    /// Memory clock rate in MHz.
713    pub memory_clock_rate_mhz: f64,
714    /// Memory bus width in bits.
715    pub memory_bus_width_bits: i32,
716    /// L2 cache size in bytes.
717    pub l2_cache_bytes: i32,
718    /// Maximum shared memory per block in bytes.
719    pub max_shared_memory_per_block: i32,
720    /// Maximum shared memory per SM in bytes.
721    pub max_shared_memory_per_sm: i32,
722    /// Maximum 32-bit registers per block.
723    pub max_registers_per_block: i32,
724    /// ECC memory enabled.
725    pub ecc_enabled: bool,
726    /// TCC driver mode.
727    pub tcc_driver: bool,
728    /// Compute mode (0=default, 1=exclusive-thread, 2=prohibited, 3=exclusive-process).
729    pub compute_mode: i32,
730    /// Cooperative kernel launch support.
731    pub supports_cooperative_launch: bool,
732    /// Managed (unified) memory support.
733    pub supports_managed_memory: bool,
734    /// Maximum persisting L2 cache size in bytes (Ampere+).
735    pub max_persisting_l2_cache_bytes: i32,
736    /// Number of async copy engines.
737    pub async_engine_count: i32,
738    /// Supports memory pools (`cudaMallocAsync`).
739    pub supports_memory_pools: bool,
740    /// Supports GPU Direct RDMA.
741    pub supports_gpu_direct_rdma: bool,
742    /// Supports cluster launch (Hopper+).
743    pub supports_cluster_launch: bool,
744    /// Concurrent kernel execution supported.
745    pub supports_concurrent_kernels: bool,
746    /// Unified addressing supported.
747    pub supports_unified_addressing: bool,
748    /// Maximum blocks per SM.
749    pub max_blocks_per_sm: i32,
750    /// Single-to-double precision performance ratio.
751    pub single_to_double_perf_ratio: i32,
752}
753
754impl std::fmt::Display for DeviceInfo {
755    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
756        let mem_mb = self.total_memory_bytes / (1024 * 1024);
757        let (major, minor) = self.compute_capability;
758        writeln!(f, "Device {}: {}", self.ordinal, self.name)?;
759        writeln!(f, "  Compute capability : {major}.{minor}")?;
760        writeln!(f, "  Total memory       : {mem_mb} MB")?;
761        writeln!(f, "  SMs                : {}", self.multiprocessor_count)?;
762        writeln!(f, "  Max threads/block  : {}", self.max_threads_per_block)?;
763        writeln!(f, "  Max threads/SM     : {}", self.max_threads_per_sm)?;
764        writeln!(f, "  Warp size          : {}", self.warp_size)?;
765        writeln!(f, "  Core clock         : {:.1} MHz", self.clock_rate_mhz)?;
766        writeln!(
767            f,
768            "  Memory clock       : {:.1} MHz",
769            self.memory_clock_rate_mhz
770        )?;
771        writeln!(
772            f,
773            "  Memory bus         : {} bits",
774            self.memory_bus_width_bits
775        )?;
776        writeln!(
777            f,
778            "  L2 cache           : {} KB",
779            self.l2_cache_bytes / 1024
780        )?;
781        writeln!(
782            f,
783            "  Shared mem/block   : {} KB",
784            self.max_shared_memory_per_block / 1024
785        )?;
786        writeln!(
787            f,
788            "  Shared mem/SM      : {} KB",
789            self.max_shared_memory_per_sm / 1024
790        )?;
791        writeln!(f, "  Registers/block    : {}", self.max_registers_per_block)?;
792        writeln!(f, "  ECC                : {}", self.ecc_enabled)?;
793        writeln!(f, "  TCC driver         : {}", self.tcc_driver)?;
794        writeln!(f, "  Compute mode       : {}", self.compute_mode)?;
795        writeln!(
796            f,
797            "  Cooperative launch : {}",
798            self.supports_cooperative_launch
799        )?;
800        writeln!(f, "  Managed memory     : {}", self.supports_managed_memory)?;
801        writeln!(
802            f,
803            "  Persist L2 cache   : {} KB",
804            self.max_persisting_l2_cache_bytes / 1024
805        )?;
806        writeln!(f, "  Async engines      : {}", self.async_engine_count)?;
807        writeln!(f, "  Memory pools       : {}", self.supports_memory_pools)?;
808        writeln!(
809            f,
810            "  GPU Direct RDMA    : {}",
811            self.supports_gpu_direct_rdma
812        )?;
813        writeln!(f, "  Cluster launch     : {}", self.supports_cluster_launch)?;
814        writeln!(
815            f,
816            "  Concurrent kernels : {}",
817            self.supports_concurrent_kernels
818        )?;
819        writeln!(
820            f,
821            "  Unified addressing : {}",
822            self.supports_unified_addressing
823        )?;
824        writeln!(f, "  Max blocks/SM      : {}", self.max_blocks_per_sm)?;
825        write!(
826            f,
827            "  FP32/FP64 ratio    : {}",
828            self.single_to_double_perf_ratio
829        )
830    }
831}
832
833// ---------------------------------------------------------------------------
834// Free functions
835// ---------------------------------------------------------------------------
836
837/// List all available CUDA devices.
838///
839/// Returns a vector of [`Device`] handles for every GPU visible to the driver.
840/// The vector is empty if no CUDA-capable devices are present.
841///
842/// # Errors
843///
844/// Returns an error if the driver cannot be loaded or device enumeration fails.
845///
846/// # Examples
847///
848/// ```no_run
849/// use oxicuda_driver::device::list_devices;
850///
851/// oxicuda_driver::init()?;
852/// for dev in list_devices()? {
853///     println!("{}: {} MB", dev.name()?, dev.total_memory()? / (1024 * 1024));
854/// }
855/// # Ok::<(), oxicuda_driver::error::CudaError>(())
856/// ```
857pub fn list_devices() -> CudaResult<Vec<Device>> {
858    let count = Device::count()?;
859    let mut devices = Vec::with_capacity(count as usize);
860    for i in 0..count {
861        devices.push(Device::get(i)?);
862    }
863    Ok(devices)
864}
865
866/// Query the CUDA driver version.
867///
868/// Returns the version as `major * 1000 + minor * 10`, e.g. 12060 for CUDA 12.6.
869/// This is a system-wide property that does not depend on a specific device.
870///
871/// # Errors
872///
873/// Returns an error if the driver cannot be loaded or `cuDriverGetVersion` fails.
874pub fn driver_version() -> CudaResult<i32> {
875    let driver = try_driver()?;
876    let mut version: c_int = 0;
877    crate::error::check(unsafe { (driver.cu_driver_get_version)(&mut version) })?;
878    Ok(version)
879}
880
881/// Query whether peer access is possible between two devices.
882///
883/// Peer access allows one GPU to directly access another GPU's memory
884/// without going through the host. This requires both devices to support
885/// peer access and be connected via a suitable interconnect (e.g., NVLink
886/// or PCIe peer-to-peer).
887///
888/// # Parameters
889///
890/// * `device` — the device that would initiate the access.
891/// * `peer` — the device whose memory would be accessed.
892///
893/// # Errors
894///
895/// Returns an error if the driver cannot be loaded or the query fails.
896pub fn can_access_peer(device: &Device, peer: &Device) -> CudaResult<bool> {
897    let driver = try_driver()?;
898    let mut can_access: c_int = 0;
899    crate::error::check(unsafe {
900        (driver.cu_device_can_access_peer)(&mut can_access, device.raw(), peer.raw())
901    })?;
902    Ok(can_access != 0)
903}
904
905/// Find the device with the most total memory.
906///
907/// Returns `None` if no CUDA devices are available.
908///
909/// # Errors
910///
911/// Returns an error if device enumeration or memory queries fail.
912///
913/// # Examples
914///
915/// ```no_run
916/// use oxicuda_driver::device::best_device;
917///
918/// oxicuda_driver::init()?;
919/// if let Some(dev) = best_device()? {
920///     println!("Best GPU: {} ({} MB)", dev.name()?, dev.total_memory()? / (1024 * 1024));
921/// }
922/// # Ok::<(), oxicuda_driver::error::CudaError>(())
923/// ```
924pub fn best_device() -> CudaResult<Option<Device>> {
925    let devices = list_devices()?;
926    if devices.is_empty() {
927        return Ok(None);
928    }
929    let mut best = devices[0];
930    let mut best_mem = best.total_memory()?;
931    for dev in devices.iter().skip(1) {
932        let mem = dev.total_memory()?;
933        if mem > best_mem {
934            best = *dev;
935            best_mem = mem;
936        }
937    }
938    Ok(Some(best))
939}
940
941#[cfg(test)]
942mod managed_memory_tests {
943    use super::*;
944    use crate::context::Context;
945    use crate::ffi::{CU_MEM_ATTACH_GLOBAL, CUdeviceptr};
946    use crate::module::Module;
947    use crate::stream::Stream;
948    use std::ffi::c_void;
949
950    /// Grid-stride in-place doubling kernel, arch-portable (`.target sm_70`).
951    const DOUBLE_PTX: &str = "\
952.version 7.0
953.target sm_70
954.address_size 64
955.visible .entry dbl(
956    .param .u64 ptr,
957    .param .u32 n
958)
959{
960    .reg .b32 %r<8>;
961    .reg .b64 %rd<8>;
962    .reg .f32 %f<2>;
963    .reg .pred %p<2>;
964    ld.param.u64 %rd0, [ptr];
965    ld.param.u32 %r0, [n];
966    mov.u32 %r1, %ctaid.x;
967    mov.u32 %r2, %ntid.x;
968    mov.u32 %r3, %tid.x;
969    mad.lo.u32 %r4, %r1, %r2, %r3;
970    mov.u32 %r5, %nctaid.x;
971    mul.lo.u32 %r6, %r5, %r2;
972$LOOP:
973    setp.ge.u32 %p0, %r4, %r0;
974    @%p0 bra $DONE;
975    mul.wide.u32 %rd1, %r4, 4;
976    add.u64 %rd2, %rd0, %rd1;
977    ld.global.f32 %f0, [%rd2];
978    add.f32 %f0, %f0, %f0;
979    st.global.f32 [%rd2], %f0;
980    add.u32 %r4, %r4, %r6;
981    bra $LOOP;
982$DONE:
983    ret;
984}
985";
986
987    /// Real-hardware unified (managed) memory round-trip: allocate with
988    /// `cuMemAllocManaged`, write the inputs from the HOST directly into the
989    /// managed pointer (no `cuMemcpyHtoD`), run a GPU kernel that doubles them
990    /// in place, then read the results back from the HOST directly (no
991    /// `cuMemcpyDtoH`). This exercises the page-migration path end-to-end.
992    /// No-op when no GPU is present or managed memory is unsupported.
993    #[test]
994    fn managed_memory_round_trip_on_real_device() {
995        let Ok(dev) = Device::get(0) else {
996            return;
997        };
998        if !matches!(dev.supports_managed_memory(), Ok(true)) {
999            return;
1000        }
1001        let ctx = match Context::new(&dev) {
1002            Ok(c) => std::sync::Arc::new(c),
1003            Err(_) => return,
1004        };
1005        let stream = match Stream::new(&ctx) {
1006            Ok(s) => s,
1007            Err(_) => return,
1008        };
1009        let api = crate::loader::try_driver().expect("driver present");
1010
1011        let module = match Module::from_ptx(DOUBLE_PTX) {
1012            Ok(m) => m,
1013            Err(_) => return,
1014        };
1015        let func = module.get_function("dbl").expect("dbl");
1016
1017        const N: usize = 1024;
1018        let bytes = N * std::mem::size_of::<f32>();
1019        let mut dptr: CUdeviceptr = 0;
1020        crate::error::check(unsafe {
1021            (api.cu_mem_alloc_managed)(&mut dptr, bytes, CU_MEM_ATTACH_GLOBAL)
1022        })
1023        .expect("managed alloc");
1024
1025        let result = (|| -> CudaResult<bool> {
1026            // HOST writes directly into managed memory (no explicit copy).
1027            // SAFETY: managed memory is host-accessible; `dptr` addresses N f32.
1028            let host_in = unsafe { std::slice::from_raw_parts_mut(dptr as *mut f32, N) };
1029            for (i, v) in host_in.iter_mut().enumerate() {
1030                *v = i as f32;
1031            }
1032
1033            // GPU kernel doubles the buffer in place.
1034            let mut dptr_arg = dptr;
1035            let mut n_arg: u32 = N as u32;
1036            let mut params: [*mut c_void; 2] = [
1037                (&mut dptr_arg as *mut CUdeviceptr).cast(),
1038                (&mut n_arg as *mut u32).cast(),
1039            ];
1040            crate::error::check(unsafe {
1041                (api.cu_launch_kernel)(
1042                    func.raw(),
1043                    8,
1044                    1,
1045                    1,
1046                    128,
1047                    1,
1048                    1,
1049                    0,
1050                    stream.raw(),
1051                    params.as_mut_ptr(),
1052                    std::ptr::null_mut(),
1053                )
1054            })?;
1055            crate::error::check(unsafe { (api.cu_stream_synchronize)(stream.raw()) })?;
1056
1057            // HOST reads results back directly (no explicit copy).
1058            // SAFETY: same managed allocation, still live until freed below.
1059            let host_out = unsafe { std::slice::from_raw_parts(dptr as *const f32, N) };
1060            Ok(host_out
1061                .iter()
1062                .enumerate()
1063                .all(|(i, &v)| (v - 2.0 * i as f32).abs() <= 1e-5))
1064        })();
1065
1066        let _ = unsafe { (api.cu_mem_free_v2)(dptr) };
1067        assert!(
1068            result.expect("managed round-trip"),
1069            "managed memory: GPU did not double the host-written values"
1070        );
1071    }
1072}