1use std::marker::PhantomData;
18
19use teeny_core::{
20 device::{
21 context::DeviceInfo,
22 program::{ArgVisitor, Kernel, KernelArgs},
23 {Device, LaunchConfig},
24 },
25 dtype::Num,
26};
27
28use crate::{
29 cuda,
30 device::buffer::CudaBuffer,
31 device::program::CudaProgram,
32 errors::{Error, Result},
33};
34
35pub mod buffer;
37pub mod context;
39pub mod mem;
41pub mod program;
43
44pub struct CudaArgPacker {
50 values: Vec<Vec<u8>>,
51}
52
53impl Default for CudaArgPacker {
54 fn default() -> Self {
55 Self::new()
56 }
57}
58
59impl CudaArgPacker {
60 pub fn new() -> Self {
62 Self { values: Vec::new() }
63 }
64
65 fn push_bytes(&mut self, bytes: &[u8]) {
66 self.values.push(bytes.to_vec());
67 }
68
69 fn as_ptrs(&mut self) -> Vec<*mut core::ffi::c_void> {
73 self.values
74 .iter_mut()
75 .map(|v| v.as_mut_ptr().cast())
76 .collect()
77 }
78}
79
80impl ArgVisitor for CudaArgPacker {
81 fn visit_ptr(&mut self, ptr: *mut core::ffi::c_void) {
82 self.push_bytes(&(ptr as usize).to_ne_bytes());
83 }
84 fn visit_bool(&mut self, val: bool) {
85 self.push_bytes(&[val as u8]);
86 }
87 fn visit_i8(&mut self, val: i8) {
88 self.push_bytes(&val.to_ne_bytes());
89 }
90 fn visit_i16(&mut self, val: i16) {
91 self.push_bytes(&val.to_ne_bytes());
92 }
93 fn visit_i32(&mut self, val: i32) {
94 self.push_bytes(&val.to_ne_bytes());
95 }
96 fn visit_i64(&mut self, val: i64) {
97 self.push_bytes(&val.to_ne_bytes());
98 }
99 fn visit_u8(&mut self, val: u8) {
100 self.push_bytes(&[val]);
101 }
102 fn visit_u16(&mut self, val: u16) {
103 self.push_bytes(&val.to_ne_bytes());
104 }
105 fn visit_u32(&mut self, val: u32) {
106 self.push_bytes(&val.to_ne_bytes());
107 }
108 fn visit_u64(&mut self, val: u64) {
109 self.push_bytes(&val.to_ne_bytes());
110 }
111 fn visit_f32(&mut self, val: f32) {
112 self.push_bytes(&val.to_ne_bytes());
113 }
114 fn visit_f64(&mut self, val: f64) {
115 self.push_bytes(&val.to_ne_bytes());
116 }
117}
118
119pub struct CudaLaunchConfig {
121 pub grid: [u32; 3],
123 pub block: [u32; 3],
125 pub cluster: [u32; 3],
127}
128
129impl LaunchConfig for CudaLaunchConfig {}
130
131#[derive(Debug, Clone)]
134pub struct CudaDeviceInfo {
135 pub id: i32,
137 pub name: String,
139 pub major: i32,
141 pub minor: i32,
143 pub multi_processor_count: i32,
145 pub total_global_mem: usize,
147 pub shared_mem_per_block: usize,
149 pub regs_per_block: i32,
151 pub warp_size: i32,
153 pub max_threads_per_block: i32,
155 pub max_threads_per_multi_processor: i32,
157 pub max_blocks_per_multi_processor: i32,
159 pub max_threads_dim: [i32; 3],
161 pub max_grid_size: [i32; 3],
163 pub memory_bus_width: i32,
165 pub l2_cache_size: i32,
167 pub concurrent_kernels: i32,
169}
170
171impl CudaDeviceInfo {
172 pub fn new(id: i32, props: cuda::cudaDeviceProp) -> Self {
174 let name = unsafe { std::ffi::CStr::from_ptr(props.name.as_ptr()) };
175 let name = name.to_string_lossy().to_string();
176
177 CudaDeviceInfo {
178 id,
179 name,
180 major: props.major,
181 minor: props.minor,
182 multi_processor_count: props.multiProcessorCount,
183 total_global_mem: props.totalGlobalMem,
184 shared_mem_per_block: props.sharedMemPerBlock,
185 regs_per_block: props.regsPerBlock,
186 warp_size: props.warpSize,
187 max_threads_per_block: props.maxThreadsPerBlock,
188 max_threads_per_multi_processor: props.maxThreadsPerMultiProcessor,
189 max_blocks_per_multi_processor: props.maxBlocksPerMultiProcessor,
190 max_threads_dim: props.maxThreadsDim,
191 max_grid_size: props.maxGridSize,
192 memory_bus_width: props.memoryBusWidth,
193 l2_cache_size: props.l2CacheSize,
194 concurrent_kernels: props.concurrentKernels,
195 }
196 }
197}
198impl DeviceInfo for CudaDeviceInfo {
199 type Id = i32;
200
201 fn id(&self) -> Self::Id {
202 self.id
203 }
204
205 fn name(&self) -> &str {
206 &self.name
207 }
208}
209
210#[derive(Debug, Clone)]
212pub struct CudaDevice<'a> {
213 pub info: CudaDeviceInfo,
215 #[allow(dead_code)]
218 device: cuda::CUdevice,
219 context: cuda::CUcontext,
220 _unused: PhantomData<&'a ()>,
221}
222
223impl<'a> CudaDevice<'a> {
224 pub fn try_new(id: i32) -> Result<Self> {
226 let device_id = id;
227 let mut device = cuda::CUdevice::default();
228 let status = unsafe { cuda::cuDeviceGet(&mut device, device_id) };
229 if status != cuda::cudaError_enum_CUDA_SUCCESS {
230 return Err(Error::from_cuda_error(status).into());
231 }
232
233 let mut props = cuda::cudaDeviceProp::default();
234 #[cfg(cuda_props_v2)]
235 let status = unsafe { cuda::cudaGetDeviceProperties_v2(&mut props, device_id) };
236 #[cfg(not(cuda_props_v2))]
237 let status = unsafe { cuda::cudaGetDeviceProperties(&mut props, device_id) };
238 if status != cuda::cudaError_enum_CUDA_SUCCESS {
239 return Err(Error::from_cuda_error(status).into());
240 }
241
242 let info = CudaDeviceInfo::new(device_id, props);
243
244 let mut context = cuda::CUcontext::default();
245 let mut params = cuda::CUctxCreateParams::default();
246 let flags = 0;
247 let status = unsafe { cuda::cuCtxCreate_v4(&mut context, &mut params, flags, device) };
248 if status != cuda::cudaError_enum_CUDA_SUCCESS {
249 return Err(Error::from_cuda_error(status).into());
250 }
251
252 Ok(Self {
253 device,
254 context,
255 info,
256 _unused: PhantomData,
257 })
258 }
259
260 pub fn info(&self) -> &CudaDeviceInfo {
262 &self.info
263 }
264}
265
266impl<'a> Drop for CudaDevice<'a> {
267 fn drop(&mut self) {
268 let result = unsafe { cuda::cuCtxDestroy_v2(self.context) };
269 if result != cuda::cudaError_enum_CUDA_SUCCESS {
270 eprintln!("Failed to destroy CUDA context: {}", result);
272 }
273 }
274}
275
276impl CudaDevice<'_> {
277 pub(crate) fn ensure_dynamic_shared(
286 function: cuda::CUfunction,
287 shared_bytes: u32,
288 ) -> Result<()> {
289 if shared_bytes == 0 {
290 return Ok(());
291 }
292 let status = unsafe {
293 cuda::cuFuncSetAttribute(
294 function,
295 cuda::CUfunction_attribute_enum_CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
296 shared_bytes as i32,
297 )
298 };
299 if status != cuda::cudaError_enum_CUDA_SUCCESS {
300 return Err(Error::from_cuda_error(status).into());
301 }
302 Ok(())
303 }
304}
305
306impl<'a> Device<'a> for CudaDevice<'a> {
307 type Buffer<N: Num> = CudaBuffer<'a, N>;
308 type Program<K: teeny_core::device::program::Kernel> = CudaProgram<'a, K>;
309 type LaunchConfig = CudaLaunchConfig;
310
311 fn buffer<N: Num>(&self, count: usize) -> teeny_core::errors::Result<Self::Buffer<N>> {
312 CudaBuffer::try_new(count)
313 }
314
315 fn launch<K: Kernel>(
316 &self,
317 program: &Self::Program<K>,
318 cfg: &Self::LaunchConfig,
319 args: K::Args<'a>,
320 ) -> teeny_core::errors::Result<()> {
321 let num_ctas = (cfg.grid[0] * cfg.grid[1] * cfg.grid[2]) as u64;
324 let scratch_total = program.metadata.global_scratch_size * num_ctas;
325 let mut scratch_ptr: cuda::CUdeviceptr = 0;
326 if scratch_total > 0 {
327 let alloc_status =
330 unsafe { cuda::cuMemAlloc_v2(&mut scratch_ptr, scratch_total as usize) };
331 if alloc_status != cuda::cudaError_enum_CUDA_SUCCESS {
332 return Err(Error::from_cuda_error(alloc_status).into());
333 }
334 unsafe { cuda::cuMemsetD8_v2(scratch_ptr, 0, scratch_total as usize) };
336 }
337
338 let mut packer = CudaArgPacker::new();
339 args.visit_args(&mut packer);
340 packer.visit_ptr(scratch_ptr as *mut std::ffi::c_void); packer.visit_ptr(std::ptr::null_mut()); let mut ptrs = packer.as_ptrs();
346
347 Self::ensure_dynamic_shared(program.function, program.metadata.shared)?;
348
349 let status = unsafe {
350 cuda::cuLaunchKernel(
351 program.function,
352 cfg.grid[0],
353 cfg.grid[1],
354 cfg.grid[2],
355 cfg.block[0],
356 cfg.block[1],
357 cfg.block[2],
358 program.metadata.shared, std::ptr::null_mut(), ptrs.as_mut_ptr(),
361 std::ptr::null_mut(), )
363 };
364
365 if status != cuda::cudaError_enum_CUDA_SUCCESS {
366 return Err(Error::from_cuda_error(status).into());
367 }
368
369 let sync_status = unsafe { cuda::cuCtxSynchronize() };
373
374 if sync_status != cuda::cudaError_enum_CUDA_SUCCESS {
375 if scratch_ptr != 0 {
376 unsafe { cuda::cuMemFree_v2(scratch_ptr) };
377 }
378 return Err(Error::from_cuda_error(sync_status).into());
379 }
380
381 if scratch_ptr != 0 {
382 unsafe { cuda::cuMemFree_v2(scratch_ptr) };
383 }
384
385 Ok(())
386 }
387}
388
389impl<'a> CudaDevice<'a> {
390 pub(crate) fn launch_on_stream<K: Kernel>(
395 &self,
396 program: &CudaProgram<'_, K>,
397 cfg: &CudaLaunchConfig,
398 packer: &mut CudaArgPacker,
399 stream: cuda::CUstream,
400 ) -> Result<()> {
401 let mut ptrs = packer.as_ptrs();
402 Self::ensure_dynamic_shared(program.function, program.metadata.shared)?;
403 let status = unsafe {
404 cuda::cuLaunchKernel(
405 program.function,
406 cfg.grid[0],
407 cfg.grid[1],
408 cfg.grid[2],
409 cfg.block[0],
410 cfg.block[1],
411 cfg.block[2],
412 program.metadata.shared,
413 stream,
414 ptrs.as_mut_ptr(),
415 std::ptr::null_mut(),
416 )
417 };
418 if status != cuda::cudaError_enum_CUDA_SUCCESS {
419 Err(Error::from_cuda_error(status).into())
420 } else {
421 Ok(())
422 }
423 }
424
425 pub fn launch_with_packer<K: Kernel>(
431 &self,
432 program: &CudaProgram<'_, K>,
433 cfg: &CudaLaunchConfig,
434 packer: &mut CudaArgPacker,
435 ) -> Result<()> {
436 let num_ctas = (cfg.grid[0] * cfg.grid[1] * cfg.grid[2]) as u64;
437 let scratch_total = program.metadata.global_scratch_size * num_ctas;
438 let mut scratch_ptr: cuda::CUdeviceptr = 0;
439 if scratch_total > 0 {
440 let alloc_status =
441 unsafe { cuda::cuMemAlloc_v2(&mut scratch_ptr, scratch_total as usize) };
442 if alloc_status != cuda::cudaError_enum_CUDA_SUCCESS {
443 return Err(Error::from_cuda_error(alloc_status).into());
444 }
445 unsafe { cuda::cuMemsetD8_v2(scratch_ptr, 0, scratch_total as usize) };
446 }
447
448 packer.visit_ptr(scratch_ptr as *mut std::ffi::c_void);
449 packer.visit_ptr(std::ptr::null_mut());
450 let mut ptrs = packer.as_ptrs();
451
452 Self::ensure_dynamic_shared(program.function, program.metadata.shared)?;
453
454 let status = unsafe {
455 cuda::cuLaunchKernel(
456 program.function,
457 cfg.grid[0],
458 cfg.grid[1],
459 cfg.grid[2],
460 cfg.block[0],
461 cfg.block[1],
462 cfg.block[2],
463 program.metadata.shared,
464 std::ptr::null_mut(),
465 ptrs.as_mut_ptr(),
466 std::ptr::null_mut(),
467 )
468 };
469
470 if status != cuda::cudaError_enum_CUDA_SUCCESS {
471 if scratch_ptr != 0 {
472 unsafe { cuda::cuMemFree_v2(scratch_ptr) };
473 }
474 return Err(Error::from_cuda_error(status).into());
475 }
476
477 let sync_status = unsafe { cuda::cuCtxSynchronize() };
478 if scratch_ptr != 0 {
479 unsafe { cuda::cuMemFree_v2(scratch_ptr) };
480 }
481
482 if sync_status != cuda::cudaError_enum_CUDA_SUCCESS {
483 return Err(Error::from_cuda_error(sync_status).into());
484 }
485 Ok(())
486 }
487}