1use std::ffi::{c_char, c_int};
26
27use crate::error::CudaResult;
28use crate::ffi::{CUdevice, CUdevice_attribute};
29use crate::loader::try_driver;
30
31#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
55pub struct Device {
56 raw: CUdevice,
58 ordinal: i32,
60}
61
62impl Device {
63 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 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 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 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 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 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 pub fn max_threads_per_block(&self) -> CudaResult<i32> {
160 self.attribute(CUdevice_attribute::MaxThreadsPerBlock)
161 }
162
163 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 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 pub fn max_threads_per_multiprocessor(&self) -> CudaResult<i32> {
183 self.attribute(CUdevice_attribute::MaxThreadsPerMultiprocessor)
184 }
185
186 pub fn max_blocks_per_multiprocessor(&self) -> CudaResult<i32> {
188 self.attribute(CUdevice_attribute::MaxBlocksPerMultiprocessor)
189 }
190
191 pub fn multiprocessor_count(&self) -> CudaResult<i32> {
195 self.attribute(CUdevice_attribute::MultiprocessorCount)
196 }
197
198 pub fn warp_size(&self) -> CudaResult<i32> {
200 self.attribute(CUdevice_attribute::WarpSize)
201 }
202
203 pub fn max_shared_memory_per_block(&self) -> CudaResult<i32> {
207 self.attribute(CUdevice_attribute::MaxSharedMemoryPerBlock)
208 }
209
210 pub fn max_shared_memory_per_multiprocessor(&self) -> CudaResult<i32> {
212 self.attribute(CUdevice_attribute::MaxSharedMemoryPerMultiprocessor)
213 }
214
215 pub fn max_shared_memory_per_block_optin(&self) -> CudaResult<i32> {
220 self.attribute(CUdevice_attribute::MaxSharedMemoryPerBlockOptin)
221 }
222
223 pub fn max_registers_per_block(&self) -> CudaResult<i32> {
225 self.attribute(CUdevice_attribute::MaxRegistersPerBlock)
226 }
227
228 pub fn max_registers_per_multiprocessor(&self) -> CudaResult<i32> {
230 self.attribute(CUdevice_attribute::MaxRegistersPerMultiprocessor)
231 }
232
233 pub fn l2_cache_size(&self) -> CudaResult<i32> {
235 self.attribute(CUdevice_attribute::L2CacheSize)
236 }
237
238 pub fn total_constant_memory(&self) -> CudaResult<i32> {
240 self.attribute(CUdevice_attribute::TotalConstantMemory)
241 }
242
243 pub fn clock_rate_khz(&self) -> CudaResult<i32> {
247 self.attribute(CUdevice_attribute::ClockRate)
248 }
249
250 pub fn memory_clock_rate_khz(&self) -> CudaResult<i32> {
252 self.attribute(CUdevice_attribute::MemoryClockRate)
253 }
254
255 pub fn memory_bus_width(&self) -> CudaResult<i32> {
257 self.attribute(CUdevice_attribute::GlobalMemoryBusWidth)
258 }
259
260 pub fn pci_bus_id(&self) -> CudaResult<i32> {
264 self.attribute(CUdevice_attribute::PciBusId)
265 }
266
267 pub fn pci_device_id(&self) -> CudaResult<i32> {
269 self.attribute(CUdevice_attribute::PciDeviceId)
270 }
271
272 pub fn pci_domain_id(&self) -> CudaResult<i32> {
274 self.attribute(CUdevice_attribute::PciDomainId)
275 }
276
277 pub fn supports_managed_memory(&self) -> CudaResult<bool> {
281 Ok(self.attribute(CUdevice_attribute::ManagedMemory)? != 0)
282 }
283
284 pub fn supports_concurrent_managed_access(&self) -> CudaResult<bool> {
286 Ok(self.attribute(CUdevice_attribute::ConcurrentManagedAccess)? != 0)
287 }
288
289 pub fn supports_concurrent_kernels(&self) -> CudaResult<bool> {
291 Ok(self.attribute(CUdevice_attribute::ConcurrentKernels)? != 0)
292 }
293
294 pub fn supports_cooperative_launch(&self) -> CudaResult<bool> {
296 Ok(self.attribute(CUdevice_attribute::CooperativeLaunch)? != 0)
297 }
298
299 pub fn ecc_enabled(&self) -> CudaResult<bool> {
301 Ok(self.attribute(CUdevice_attribute::EccEnabled)? != 0)
302 }
303
304 pub fn is_integrated(&self) -> CudaResult<bool> {
306 Ok(self.attribute(CUdevice_attribute::Integrated)? != 0)
307 }
308
309 pub fn can_map_host_memory(&self) -> CudaResult<bool> {
311 Ok(self.attribute(CUdevice_attribute::CanMapHostMemory)? != 0)
312 }
313
314 pub fn supports_unified_addressing(&self) -> CudaResult<bool> {
316 Ok(self.attribute(CUdevice_attribute::UnifiedAddressing)? != 0)
317 }
318
319 pub fn supports_stream_priorities(&self) -> CudaResult<bool> {
321 Ok(self.attribute(CUdevice_attribute::StreamPrioritiesSupported)? != 0)
322 }
323
324 pub fn supports_compute_preemption(&self) -> CudaResult<bool> {
326 Ok(self.attribute(CUdevice_attribute::ComputePreemptionSupported)? != 0)
327 }
328
329 pub fn async_engine_count(&self) -> CudaResult<i32> {
331 self.attribute(CUdevice_attribute::AsyncEngineCount)
332 }
333
334 pub fn is_multi_gpu_board(&self) -> CudaResult<bool> {
336 Ok(self.attribute(CUdevice_attribute::IsMultiGpuBoard)? != 0)
337 }
338
339 pub fn has_kernel_exec_timeout(&self) -> CudaResult<bool> {
341 Ok(self.attribute(CUdevice_attribute::KernelExecTimeout)? != 0)
342 }
343
344 pub fn compute_mode(&self) -> CudaResult<i32> {
349 self.attribute(CUdevice_attribute::ComputeMode)
350 }
351
352 pub fn tcc_driver(&self) -> CudaResult<bool> {
357 Ok(self.attribute(CUdevice_attribute::TccDriver)? != 0)
358 }
359
360 pub fn multi_gpu_board_group_id(&self) -> CudaResult<i32> {
364 self.attribute(CUdevice_attribute::MultiGpuBoardGroupId)
365 }
366
367 pub fn max_persisting_l2_cache_size(&self) -> CudaResult<i32> {
371 self.attribute(CUdevice_attribute::MaxPersistingL2CacheSize)
372 }
373
374 pub fn supports_generic_compression(&self) -> CudaResult<bool> {
376 Ok(self.attribute(CUdevice_attribute::GenericCompressionSupported)? != 0)
377 }
378
379 pub fn supports_pageable_memory_access(&self) -> CudaResult<bool> {
381 Ok(self.attribute(CUdevice_attribute::PageableMemoryAccess)? != 0)
382 }
383
384 pub fn pageable_memory_uses_host_page_tables(&self) -> CudaResult<bool> {
386 Ok(self.attribute(CUdevice_attribute::PageableMemoryAccessUsesHostPageTables)? != 0)
387 }
388
389 pub fn supports_direct_managed_mem_from_host(&self) -> CudaResult<bool> {
391 Ok(self.attribute(CUdevice_attribute::DirectManagedMemAccessFromHost)? != 0)
392 }
393
394 pub fn memory_pool_supported_handle_types(&self) -> CudaResult<i32> {
396 self.attribute(CUdevice_attribute::MemoryPoolSupportedHandleTypes)
397 }
398
399 pub fn supports_host_native_atomics(&self) -> CudaResult<bool> {
403 Ok(self.attribute(CUdevice_attribute::HostNativeAtomicSupported)? != 0)
404 }
405
406 pub fn single_to_double_perf_ratio(&self) -> CudaResult<i32> {
410 self.attribute(CUdevice_attribute::SingleToDoublePrecisionPerfRatio)
411 }
412
413 pub fn supports_cooperative_multi_device_launch(&self) -> CudaResult<bool> {
415 Ok(self.attribute(CUdevice_attribute::CooperativeMultiDeviceLaunch)? != 0)
416 }
417
418 pub fn supports_flush_remote_writes(&self) -> CudaResult<bool> {
420 Ok(self.attribute(CUdevice_attribute::CanFlushRemoteWrites)? != 0)
421 }
422
423 pub fn supports_host_register(&self) -> CudaResult<bool> {
425 Ok(self.attribute(CUdevice_attribute::HostRegisterSupported)? != 0)
426 }
427
428 pub fn can_use_host_pointer_for_registered_mem(&self) -> CudaResult<bool> {
430 Ok(self.attribute(CUdevice_attribute::CanUseHostPointerForRegisteredMem)? != 0)
431 }
432
433 pub fn supports_gpu_direct_rdma(&self) -> CudaResult<bool> {
435 Ok(self.attribute(CUdevice_attribute::GpuDirectRdmaSupported)? != 0)
436 }
437
438 pub fn supports_tensor_map_access(&self) -> CudaResult<bool> {
440 Ok(self.attribute(CUdevice_attribute::TensorMapAccessSupported)? != 0)
441 }
442
443 pub fn supports_multicast(&self) -> CudaResult<bool> {
445 Ok(self.attribute(CUdevice_attribute::MulticastSupported)? != 0)
446 }
447
448 pub fn mps_enabled(&self) -> CudaResult<bool> {
450 Ok(self.attribute(CUdevice_attribute::MpsEnabled)? != 0)
451 }
452
453 pub fn max_texture_1d_width(&self) -> CudaResult<i32> {
457 self.attribute(CUdevice_attribute::MaxTexture1DWidth)
458 }
459
460 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 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 pub fn gpu_overlap(&self) -> CudaResult<bool> {
479 Ok(self.attribute(CUdevice_attribute::GpuOverlap)? != 0)
480 }
481
482 pub fn max_pitch(&self) -> CudaResult<i32> {
484 self.attribute(CUdevice_attribute::MaxPitch)
485 }
486
487 pub fn texture_alignment(&self) -> CudaResult<i32> {
489 self.attribute(CUdevice_attribute::TextureAlignment)
490 }
491
492 pub fn surface_alignment(&self) -> CudaResult<i32> {
494 self.attribute(CUdevice_attribute::SurfaceAlignment)
495 }
496
497 pub fn supports_deferred_mapping(&self) -> CudaResult<bool> {
499 Ok(self.attribute(CUdevice_attribute::DeferredMappingCudaArraySupported)? != 0)
500 }
501
502 pub fn supports_memory_pools(&self) -> CudaResult<bool> {
506 Ok(self.attribute(CUdevice_attribute::MemoryPoolsSupported)? != 0)
507 }
508
509 pub fn supports_cluster_launch(&self) -> CudaResult<bool> {
511 Ok(self.attribute(CUdevice_attribute::ClusterLaunch)? != 0)
512 }
513
514 pub fn supports_virtual_memory_management(&self) -> CudaResult<bool> {
516 Ok(self.attribute(CUdevice_attribute::VirtualMemoryManagementSupported)? != 0)
517 }
518
519 pub fn supports_handle_type_posix_fd(&self) -> CudaResult<bool> {
521 Ok(self.attribute(CUdevice_attribute::HandleTypePosixFileDescriptorSupported)? != 0)
522 }
523
524 pub fn supports_handle_type_win32(&self) -> CudaResult<bool> {
526 Ok(self.attribute(CUdevice_attribute::HandleTypeWin32HandleSupported)? != 0)
527 }
528
529 pub fn supports_handle_type_win32_kmt(&self) -> CudaResult<bool> {
531 Ok(self.attribute(CUdevice_attribute::HandleTypeWin32KmtHandleSupported)? != 0)
532 }
533
534 pub fn supports_gpu_direct_rdma_vmm(&self) -> CudaResult<bool> {
536 Ok(self.attribute(CUdevice_attribute::GpuDirectRdmaWithCudaVmmSupported)? != 0)
537 }
538
539 pub fn gpu_direct_rdma_flush_writes_options(&self) -> CudaResult<i32> {
541 self.attribute(CUdevice_attribute::GpuDirectRdmaFlushWritesOptions)
542 }
543
544 pub fn gpu_direct_rdma_writes_ordering(&self) -> CudaResult<i32> {
546 self.attribute(CUdevice_attribute::GpuDirectRdmaWritesOrdering)
547 }
548
549 pub fn max_access_policy_window_size(&self) -> CudaResult<i32> {
551 self.attribute(CUdevice_attribute::MaxAccessPolicyWindowSize)
552 }
553
554 pub fn reserved_shared_memory_per_block(&self) -> CudaResult<i32> {
556 self.attribute(CUdevice_attribute::ReservedSharedMemoryPerBlock)
557 }
558
559 pub fn supports_timeline_semaphore_interop(&self) -> CudaResult<bool> {
561 Ok(self.attribute(CUdevice_attribute::TimelineSemaphoreInteropSupported)? != 0)
562 }
563
564 pub fn supports_mem_sync_domain(&self) -> CudaResult<bool> {
570 Ok(self.attribute(CUdevice_attribute::MemSyncDomainCount)? > 0)
571 }
572
573 pub fn mem_sync_domain_count(&self) -> CudaResult<i32> {
575 self.attribute(CUdevice_attribute::MemSyncDomainCount)
576 }
577
578 pub fn supports_gpu_direct_rdma_fabric(&self) -> CudaResult<bool> {
580 Ok(self.attribute(CUdevice_attribute::GpuDirectRdmaFabricSupported)? != 0)
581 }
582
583 pub fn supports_unified_function_pointers(&self) -> CudaResult<bool> {
585 Ok(self.attribute(CUdevice_attribute::UnifiedFunctionPointers)? != 0)
586 }
587
588 pub fn supports_ipc_events(&self) -> CudaResult<bool> {
590 Ok(self.attribute(CUdevice_attribute::IpcEventSupported)? != 0)
591 }
592
593 pub fn numa_config(&self) -> CudaResult<i32> {
595 self.attribute(CUdevice_attribute::NumaConfig)
596 }
597
598 pub fn numa_id(&self) -> CudaResult<i32> {
600 self.attribute(CUdevice_attribute::NumaId)
601 }
602
603 pub fn host_numa_id(&self) -> CudaResult<i32> {
605 self.attribute(CUdevice_attribute::HostNumaId)
606 }
607
608 pub fn texture_pitch_alignment(&self) -> CudaResult<i32> {
610 self.attribute(CUdevice_attribute::TexturePitchAlignment)
611 }
612
613 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 #[inline]
667 pub fn raw(&self) -> CUdevice {
668 self.raw
669 }
670
671 #[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#[derive(Debug, Clone)]
693pub struct DeviceInfo {
694 pub name: String,
696 pub ordinal: i32,
698 pub compute_capability: (i32, i32),
700 pub total_memory_bytes: usize,
702 pub multiprocessor_count: i32,
704 pub max_threads_per_block: i32,
706 pub max_threads_per_sm: i32,
708 pub warp_size: i32,
710 pub clock_rate_mhz: f64,
712 pub memory_clock_rate_mhz: f64,
714 pub memory_bus_width_bits: i32,
716 pub l2_cache_bytes: i32,
718 pub max_shared_memory_per_block: i32,
720 pub max_shared_memory_per_sm: i32,
722 pub max_registers_per_block: i32,
724 pub ecc_enabled: bool,
726 pub tcc_driver: bool,
728 pub compute_mode: i32,
730 pub supports_cooperative_launch: bool,
732 pub supports_managed_memory: bool,
734 pub max_persisting_l2_cache_bytes: i32,
736 pub async_engine_count: i32,
738 pub supports_memory_pools: bool,
740 pub supports_gpu_direct_rdma: bool,
742 pub supports_cluster_launch: bool,
744 pub supports_concurrent_kernels: bool,
746 pub supports_unified_addressing: bool,
748 pub max_blocks_per_sm: i32,
750 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
833pub 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
866pub 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
881pub 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
905pub 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 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 #[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 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 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 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}