1mod pinned_staging;
4mod pool;
5mod ranges;
6
7pub(crate) use self::pinned_staging::PinnedUploadStagingPool;
8pub use self::pinned_staging::{
9 CudaPinnedUploadOperationGuard, CudaPinnedUploadStagingCheckout,
10 CudaPinnedUploadStagingPoolDiagnostics, CudaPinnedUploadStagingPoolLimits,
11};
12#[cfg(test)]
13pub(crate) use self::pool::copy_pooled_bytes_to_vec_uninit;
14#[cfg(test)]
15pub(crate) use self::pool::pool_fit_buffer_index_by_len;
16pub(crate) use self::pool::{
17 copy_pooled_bytes_to_vec_uninit_with_budget, pooled_device_buffer, CudaBufferPoolReuseGuard,
18};
19pub use self::pool::{
20 CudaBufferPool, CudaBufferPoolDiagnostics, CudaBufferPoolLimits, CudaBufferPoolTakeTrace,
21 CudaPooledDeviceBuffer,
22};
23pub(crate) use self::ranges::CheckedDeviceBufferRanges;
24
25#[cfg(test)]
26use crate::context::validate_non_null_pinned_host_allocation;
27use crate::{
28 bytes::f32_slice_as_bytes, context::CudaContext, driver::CuDevicePtr, error::CudaError,
29};
30use std::ffi::c_void;
31
32impl CudaContext {
33 pub fn upload(&self, bytes: &[u8]) -> Result<CudaDeviceBuffer, CudaError> {
35 let mut ptr = 0;
36 let buffer = if bytes.is_empty() {
37 self.inner.set_current()?;
38 CudaDeviceBuffer {
39 context: self.clone(),
40 ptr,
41 len: bytes.len(),
42 }
43 } else {
44 self.inner.with_current_stateful_operation(|| {
45 self.inner.driver.check("cuMemAlloc_v2", unsafe {
48 (self.inner.driver.cu_mem_alloc)(&raw mut ptr, bytes.len())
49 })?;
50 crate::context::validate_device_allocation(ptr, bytes.len())
51 })?;
52 self.record_device_allocation(bytes.len());
53
54 CudaDeviceBuffer {
55 context: self.clone(),
56 ptr,
57 len: bytes.len(),
58 }
59 };
60
61 if !bytes.is_empty() {
62 self.inner.with_current_resource_operation(|| {
63 self.inner.driver.check("cuMemcpyHtoD_v2", unsafe {
66 (self.inner.driver.cu_memcpy_htod)(
67 ptr,
68 bytes.as_ptr().cast::<c_void>(),
69 bytes.len(),
70 )
71 })
72 })?;
73 self.record_host_to_device_copy(bytes.len());
74 }
75
76 Ok(buffer)
77 }
78
79 pub fn upload_f32(&self, samples: &[f32]) -> Result<CudaDeviceBuffer, CudaError> {
81 self.upload(f32_slice_as_bytes(samples))
82 }
83
84 pub fn allocate(&self, len: usize) -> Result<CudaDeviceBuffer, CudaError> {
86 let mut ptr = 0;
87 if len != 0 {
88 self.inner.with_current_stateful_operation(|| {
89 self.inner.driver.check("cuMemAlloc_v2", unsafe {
92 (self.inner.driver.cu_mem_alloc)(&raw mut ptr, len)
93 })?;
94 crate::context::validate_device_allocation(ptr, len)
95 })?;
96 self.record_device_allocation(len);
97 } else {
98 self.inner.set_current()?;
99 }
100 Ok(CudaDeviceBuffer {
101 context: self.clone(),
102 ptr,
103 len,
104 })
105 }
106
107 #[cfg(test)]
109 pub(crate) fn pinned_host_buffer(&self, len: usize) -> Result<CudaPinnedHostBuffer, CudaError> {
110 let mut ptr = std::ptr::null_mut();
111 if len != 0 {
112 self.inner.with_current_stateful_operation(|| {
113 self.inner.driver.check("cuMemHostAlloc", unsafe {
116 (self.inner.driver.cu_mem_host_alloc)(&raw mut ptr, len, 0)
117 })?;
118 validate_non_null_pinned_host_allocation(ptr.cast::<u8>(), len).map(|_| ())
119 })?;
120 } else {
121 self.inner.set_current()?;
122 }
123 Ok(CudaPinnedHostBuffer {
124 context: self.clone(),
125 ptr: ptr.cast::<u8>(),
126 len,
127 })
128 }
129
130 pub fn buffer_pool(&self) -> CudaBufferPool {
132 CudaBufferPool::new(self.clone())
133 }
134
135 pub fn best_fit_buffer_pool(&self) -> CudaBufferPool {
138 CudaBufferPool::new_size_buckets(self.clone())
139 }
140}
141
142#[cfg(test)]
144#[derive(Debug)]
145pub(crate) struct CudaPinnedHostBuffer {
146 pub(crate) context: CudaContext,
147 pub(crate) ptr: *mut u8,
148 pub(crate) len: usize,
149}
150
151#[cfg(test)]
152impl CudaPinnedHostBuffer {
153 pub(crate) fn as_slice(&self) -> &[u8] {
155 if self.len == 0 {
156 &[]
157 } else {
158 unsafe { std::slice::from_raw_parts(self.ptr.cast_const(), self.len) }
160 }
161 }
162
163 pub(crate) fn as_mut_slice(&mut self) -> &mut [u8] {
165 if self.len == 0 {
166 &mut []
167 } else {
168 unsafe { std::slice::from_raw_parts_mut(self.ptr, self.len) }
171 }
172 }
173}
174
175#[cfg(test)]
176impl Drop for CudaPinnedHostBuffer {
177 fn drop(&mut self) {
178 if !self.ptr.is_null() {
179 let free_result = self.context.inner.with_current_stateful_operation(|| {
180 self.context.inner.driver.check("cuMemFreeHost", unsafe {
183 (self.context.inner.driver.cu_mem_free_host)(self.ptr.cast())
184 })
185 });
186 if free_result.is_err() {
187 std::mem::forget(self.context.clone());
188 }
189 }
190 }
191}
192
193#[cfg(test)]
196unsafe impl Send for CudaPinnedHostBuffer {}
197
198#[derive(Debug)]
200pub struct CudaDeviceBuffer {
201 pub(crate) context: CudaContext,
202 pub(crate) ptr: CuDevicePtr,
203 pub(crate) len: usize,
204}
205
206#[doc(hidden)]
207#[derive(Clone, Copy, Debug)]
209pub struct CudaDeviceBufferView<'a, T> {
210 pub(crate) ptr: CuDevicePtr,
211 pub(crate) len: usize,
212 pub(crate) _marker: std::marker::PhantomData<&'a T>,
213}
214
215impl<T> CudaDeviceBufferView<'_, T> {
216 pub fn device_ptr(&self) -> u64 {
218 self.ptr
219 }
220
221 pub fn len(&self) -> usize {
223 self.len
224 }
225
226 pub fn is_empty(&self) -> bool {
228 self.len == 0
229 }
230}
231
232#[doc(hidden)]
233#[derive(Debug)]
235pub struct CudaDeviceBufferViewMut<'a, T> {
236 pub(crate) ptr: CuDevicePtr,
237 pub(crate) len: usize,
238 pub(crate) _marker: std::marker::PhantomData<&'a mut T>,
239}
240
241#[doc(hidden)]
246#[derive(Debug)]
247pub struct CudaExternalDeviceBufferViewMut<'a> {
248 context: CudaContext,
249 ptr: CuDevicePtr,
250 len: usize,
251 _exclusive: std::marker::PhantomData<&'a mut ()>,
252}
253
254impl<'a> CudaExternalDeviceBufferViewMut<'a> {
255 pub unsafe fn from_raw_parts<Owner>(
267 context: &CudaContext,
268 ptr: u64,
269 len: usize,
270 required_alignment: usize,
271 _managed_owner: &'a mut Owner,
272 ) -> Result<Self, CudaError> {
273 if len == 0 {
274 return Err(CudaError::InvalidArgument {
275 message: "external CUDA buffer must not be empty".to_string(),
276 });
277 }
278 if ptr == 0 {
279 return Err(CudaError::InvalidArgument {
280 message: "external CUDA buffer pointer must not be null".to_string(),
281 });
282 }
283 if required_alignment == 0 || !required_alignment.is_power_of_two() {
284 return Err(CudaError::InvalidArgument {
285 message: "external CUDA buffer alignment must be a nonzero power of two"
286 .to_string(),
287 });
288 }
289 let len_u64 = u64::try_from(len).map_err(|_| CudaError::LengthTooLarge { len })?;
290 ptr.checked_add(len_u64)
291 .ok_or(CudaError::LengthTooLarge { len })?;
292 let ptr = context.inner.resolve_pointer_for_context(ptr)?;
293 if !ptr.is_multiple_of(required_alignment as u64) {
294 return Err(CudaError::InvalidArgument {
295 message: format!(
296 "external CUDA buffer pointer {ptr:#x} is not aligned to {required_alignment} bytes"
297 ),
298 });
299 }
300 ptr.checked_add(len_u64)
301 .ok_or(CudaError::LengthTooLarge { len })?;
302 Ok(Self {
303 context: context.clone(),
304 ptr,
305 len,
306 _exclusive: std::marker::PhantomData,
307 })
308 }
309
310 pub fn context(&self) -> &CudaContext {
312 &self.context
313 }
314
315 pub fn device_ptr(&self) -> u64 {
317 self.ptr
318 }
319
320 pub fn byte_len(&self) -> usize {
322 self.len
323 }
324}
325
326impl<T> CudaDeviceBufferViewMut<'_, T> {
327 pub fn device_ptr(&self) -> u64 {
329 self.ptr
330 }
331
332 pub fn len(&self) -> usize {
334 self.len
335 }
336
337 pub fn is_empty(&self) -> bool {
339 self.len == 0
340 }
341}
342
343#[doc(hidden)]
344#[derive(Clone, Copy, Debug, Eq, PartialEq)]
346pub struct CudaDeviceBufferRange {
347 pub offset: usize,
349 pub len: usize,
351}
352
353impl CudaDeviceBuffer {
354 pub(crate) fn is_owned_by(&self, context: &CudaContext) -> bool {
355 self.context.is_same_context(context)
356 }
357
358 pub fn context(&self) -> CudaContext {
360 self.context.clone()
361 }
362
363 pub fn device_ptr(&self) -> u64 {
365 self.ptr
366 }
367
368 pub fn byte_len(&self) -> usize {
370 self.len
371 }
372
373 #[doc(hidden)]
374 pub fn typed_view<T>(&self) -> Result<CudaDeviceBufferView<'_, T>, CudaError> {
376 let element_size = std::mem::size_of::<T>();
377 if element_size == 0 || !self.len.is_multiple_of(element_size) {
378 return Err(CudaError::LengthNotElementAligned {
379 bytes: self.len,
380 element_size,
381 });
382 }
383 Ok(CudaDeviceBufferView {
384 ptr: self.ptr,
385 len: self.len / element_size,
386 _marker: std::marker::PhantomData,
387 })
388 }
389
390 #[doc(hidden)]
391 pub fn typed_view_mut<T>(&mut self) -> Result<CudaDeviceBufferViewMut<'_, T>, CudaError> {
393 let element_size = std::mem::size_of::<T>();
394 if element_size == 0 || !self.len.is_multiple_of(element_size) {
395 return Err(CudaError::LengthNotElementAligned {
396 bytes: self.len,
397 element_size,
398 });
399 }
400 Ok(CudaDeviceBufferViewMut {
401 ptr: self.ptr,
402 len: self.len / element_size,
403 _marker: std::marker::PhantomData,
404 })
405 }
406
407 pub fn copy_to_host(&self, out: &mut [u8]) -> Result<(), CudaError> {
409 if out.len() < self.len {
410 return Err(CudaError::OutputTooSmall {
411 required: self.len,
412 have: out.len(),
413 });
414 }
415 if self.len == 0 {
416 return Ok(());
417 }
418
419 self.context.inner.with_current_resource_operation(|| {
420 self.context.inner.driver.check("cuMemcpyDtoH_v2", unsafe {
423 (self.context.inner.driver.cu_memcpy_dtoh)(
424 out.as_mut_ptr().cast::<c_void>(),
425 self.ptr,
426 self.len,
427 )
428 })
429 })?;
430 self.context.record_device_to_host_copy(self.len);
431 Ok(())
432 }
433
434 pub fn copy_range_to_host(&self, offset: usize, out: &mut [u8]) -> Result<(), CudaError> {
436 self.copy_byte_range_to_host_elements(offset, out)
437 }
438
439 pub fn copy_range_to_host_uninit(
441 &self,
442 offset: usize,
443 out: &mut [std::mem::MaybeUninit<u8>],
444 ) -> Result<(), CudaError> {
445 self.copy_byte_range_to_host_elements(offset, out)
446 }
447
448 fn copy_byte_range_to_host_elements<T>(
449 &self,
450 offset: usize,
451 out: &mut [T],
452 ) -> Result<(), CudaError> {
453 let byte_len = out
454 .len()
455 .checked_mul(std::mem::size_of::<T>())
456 .ok_or(CudaError::LengthTooLarge { len: usize::MAX })?;
457 let end = offset
458 .checked_add(byte_len)
459 .ok_or(CudaError::LengthTooLarge { len: usize::MAX })?;
460 if end > self.len {
461 return Err(CudaError::OutputTooSmall {
462 required: end,
463 have: self.len,
464 });
465 }
466 if byte_len == 0 {
467 return Ok(());
468 }
469
470 let source = self
471 .ptr
472 .checked_add(
473 u64::try_from(offset).map_err(|_| CudaError::LengthTooLarge { len: offset })?,
474 )
475 .ok_or(CudaError::LengthTooLarge { len: usize::MAX })?;
476 self.context.inner.with_current_resource_operation(|| {
477 self.context.inner.driver.check("cuMemcpyDtoH_v2", unsafe {
480 (self.context.inner.driver.cu_memcpy_dtoh)(
481 out.as_mut_ptr().cast::<c_void>(),
482 source,
483 byte_len,
484 )
485 })
486 })?;
487 self.context.record_device_to_host_copy(byte_len);
488 Ok(())
489 }
490}
491
492impl Drop for CudaDeviceBuffer {
493 fn drop(&mut self) {
494 if self.ptr != 0 {
495 let free_result = self.context.inner.with_current_stateful_operation(|| {
496 let status = unsafe { (self.context.inner.driver.cu_mem_free)(self.ptr) };
499 self.context.inner.driver.check("cuMemFree_v2", status)
500 });
501 if free_result.is_ok() {
502 self.context.record_device_free(self.len);
503 } else {
504 std::mem::forget(self.context.clone());
508 }
509 }
510 }
511}
512
513pub(crate) fn checked_image_words(
514 width: u32,
515 height: u32,
516 channels: usize,
517) -> Result<usize, CudaError> {
518 width
519 .try_into()
520 .ok()
521 .and_then(|width: usize| width.checked_mul(height as usize))
522 .and_then(|pixels| pixels.checked_mul(channels))
523 .ok_or(CudaError::ImageTooLarge {
524 width,
525 height,
526 channels,
527 })
528}