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<'a> Device<'a> for CudaDevice<'a> {
277 type Buffer<N: Num> = CudaBuffer<'a, N>;
278 type Program<K: teeny_core::device::program::Kernel> = CudaProgram<'a, K>;
279 type LaunchConfig = CudaLaunchConfig;
280
281 fn buffer<N: Num>(&self, count: usize) -> teeny_core::errors::Result<Self::Buffer<N>> {
282 CudaBuffer::try_new(count)
283 }
284
285 fn launch<K: Kernel>(
286 &self,
287 program: &Self::Program<K>,
288 cfg: &Self::LaunchConfig,
289 args: K::Args<'a>,
290 ) -> teeny_core::errors::Result<()> {
291 let num_ctas = (cfg.grid[0] * cfg.grid[1] * cfg.grid[2]) as u64;
294 let scratch_total = program.metadata.global_scratch_size * num_ctas;
295 let mut scratch_ptr: cuda::CUdeviceptr = 0;
296 if scratch_total > 0 {
297 let alloc_status =
300 unsafe { cuda::cuMemAlloc_v2(&mut scratch_ptr, scratch_total as usize) };
301 if alloc_status != cuda::cudaError_enum_CUDA_SUCCESS {
302 return Err(Error::from_cuda_error(alloc_status).into());
303 }
304 unsafe { cuda::cuMemsetD8_v2(scratch_ptr, 0, scratch_total as usize) };
306 }
307
308 let mut packer = CudaArgPacker::new();
309 args.visit_args(&mut packer);
310 packer.visit_ptr(scratch_ptr as *mut std::ffi::c_void); packer.visit_ptr(std::ptr::null_mut()); let mut ptrs = packer.as_ptrs();
316
317 let status = unsafe {
318 cuda::cuLaunchKernel(
319 program.function,
320 cfg.grid[0],
321 cfg.grid[1],
322 cfg.grid[2],
323 cfg.block[0],
324 cfg.block[1],
325 cfg.block[2],
326 program.metadata.shared, std::ptr::null_mut(), ptrs.as_mut_ptr(),
329 std::ptr::null_mut(), )
331 };
332
333 if status != cuda::cudaError_enum_CUDA_SUCCESS {
334 return Err(Error::from_cuda_error(status).into());
335 }
336
337 let sync_status = unsafe { cuda::cuCtxSynchronize() };
341
342 if sync_status != cuda::cudaError_enum_CUDA_SUCCESS {
343 if scratch_ptr != 0 {
344 unsafe { cuda::cuMemFree_v2(scratch_ptr) };
345 }
346 return Err(Error::from_cuda_error(sync_status).into());
347 }
348
349 if scratch_ptr != 0 {
350 unsafe { cuda::cuMemFree_v2(scratch_ptr) };
351 }
352
353 Ok(())
354 }
355}
356
357impl<'a> CudaDevice<'a> {
358 pub(crate) fn launch_on_stream<K: Kernel>(
363 &self,
364 program: &CudaProgram<'_, K>,
365 cfg: &CudaLaunchConfig,
366 packer: &mut CudaArgPacker,
367 stream: cuda::CUstream,
368 ) -> Result<()> {
369 let mut ptrs = packer.as_ptrs();
370 let status = unsafe {
371 cuda::cuLaunchKernel(
372 program.function,
373 cfg.grid[0],
374 cfg.grid[1],
375 cfg.grid[2],
376 cfg.block[0],
377 cfg.block[1],
378 cfg.block[2],
379 program.metadata.shared,
380 stream,
381 ptrs.as_mut_ptr(),
382 std::ptr::null_mut(),
383 )
384 };
385 if status != cuda::cudaError_enum_CUDA_SUCCESS {
386 Err(Error::from_cuda_error(status).into())
387 } else {
388 Ok(())
389 }
390 }
391
392 pub fn launch_with_packer<K: Kernel>(
398 &self,
399 program: &CudaProgram<'_, K>,
400 cfg: &CudaLaunchConfig,
401 packer: &mut CudaArgPacker,
402 ) -> Result<()> {
403 let num_ctas = (cfg.grid[0] * cfg.grid[1] * cfg.grid[2]) as u64;
404 let scratch_total = program.metadata.global_scratch_size * num_ctas;
405 let mut scratch_ptr: cuda::CUdeviceptr = 0;
406 if scratch_total > 0 {
407 let alloc_status =
408 unsafe { cuda::cuMemAlloc_v2(&mut scratch_ptr, scratch_total as usize) };
409 if alloc_status != cuda::cudaError_enum_CUDA_SUCCESS {
410 return Err(Error::from_cuda_error(alloc_status).into());
411 }
412 unsafe { cuda::cuMemsetD8_v2(scratch_ptr, 0, scratch_total as usize) };
413 }
414
415 packer.visit_ptr(scratch_ptr as *mut std::ffi::c_void);
416 packer.visit_ptr(std::ptr::null_mut());
417 let mut ptrs = packer.as_ptrs();
418
419 let status = unsafe {
420 cuda::cuLaunchKernel(
421 program.function,
422 cfg.grid[0],
423 cfg.grid[1],
424 cfg.grid[2],
425 cfg.block[0],
426 cfg.block[1],
427 cfg.block[2],
428 program.metadata.shared,
429 std::ptr::null_mut(),
430 ptrs.as_mut_ptr(),
431 std::ptr::null_mut(),
432 )
433 };
434
435 if status != cuda::cudaError_enum_CUDA_SUCCESS {
436 if scratch_ptr != 0 {
437 unsafe { cuda::cuMemFree_v2(scratch_ptr) };
438 }
439 return Err(Error::from_cuda_error(status).into());
440 }
441
442 let sync_status = unsafe { cuda::cuCtxSynchronize() };
443 if scratch_ptr != 0 {
444 unsafe { cuda::cuMemFree_v2(scratch_ptr) };
445 }
446
447 if sync_status != cuda::cudaError_enum_CUDA_SUCCESS {
448 return Err(Error::from_cuda_error(sync_status).into());
449 }
450 Ok(())
451 }
452}