openvm_cuda_common/
d_buffer.rs1use std::{ffi::c_void, fmt::Debug, ptr};
2
3use crate::{
4 error::{check, CudaError},
5 memory_manager::{d_free, d_malloc_on},
6 stream::{cudaStream_t, GpuDeviceCtx},
7};
8
9#[link(name = "cudart")]
10extern "C" {
11 pub fn cudaMemsetAsync(dst: *mut c_void, value: i32, count: usize, stream: cudaStream_t)
12 -> i32;
13}
14
15#[repr(C)]
18pub struct DeviceBuffer<T> {
19 ptr: *mut T,
20 len: usize,
21 #[cfg(feature = "debug-cuda-stream")]
22 alloc_stream: cudaStream_t,
23}
24
25#[repr(C)]
31#[derive(Debug, Copy, Clone)]
32pub struct DeviceBufferView {
33 pub ptr: *const c_void,
34 pub size: usize,
35}
36
37unsafe impl<T> Send for DeviceBuffer<T> {}
38unsafe impl<T> Sync for DeviceBuffer<T> {}
39
40impl<T> DeviceBuffer<T> {
41 #[allow(clippy::new_without_default)]
43 pub fn new() -> Self {
44 DeviceBuffer {
45 ptr: ptr::null_mut(),
46 len: 0,
47 #[cfg(feature = "debug-cuda-stream")]
48 alloc_stream: std::ptr::null_mut(),
49 }
50 }
51
52 pub unsafe fn from_raw_parts(ptr: *mut T, len: usize) -> Self {
59 DeviceBuffer {
60 ptr,
61 len,
62 #[cfg(feature = "debug-cuda-stream")]
63 alloc_stream: std::ptr::null_mut(),
64 }
65 }
66
67 pub fn with_capacity_on(len: usize, device_ctx: &GpuDeviceCtx) -> Self {
69 tracing::debug!(
70 "Creating device buffer of size {} (sizeof type = {}) on stream {:?}",
71 len,
72 size_of::<T>(),
73 device_ctx.stream
74 );
75 assert_ne!(len, 0, "Zero capacity request is wrong");
76 let size_bytes = std::mem::size_of::<T>() * len;
77 let raw_ptr = d_malloc_on(size_bytes, &device_ctx.stream).expect("GPU allocation failed");
78 #[cfg(feature = "touchemall")]
79 {
80 unsafe {
81 cudaMemsetAsync(raw_ptr, 0xff, size_bytes, device_ctx.stream.as_raw());
82 }
83 }
84 let typed_ptr = raw_ptr as *mut T;
85
86 DeviceBuffer {
87 ptr: typed_ptr,
88 len,
89 #[cfg(feature = "debug-cuda-stream")]
90 alloc_stream: device_ctx.stream.as_raw(),
91 }
92 }
93
94 pub fn fill_zero_on(&self, device_ctx: &GpuDeviceCtx) -> Result<(), CudaError> {
99 if self.len == 0 {
100 return Ok(());
101 }
102 #[cfg(feature = "debug-cuda-stream")]
103 debug_assert_eq!(
104 device_ctx.stream.as_raw(),
105 self.alloc_stream,
106 "fill_zero_on: stream mismatch"
107 );
108 let size_bytes = std::mem::size_of::<T>() * self.len;
109 check(unsafe {
110 cudaMemsetAsync(
111 self.as_mut_raw_ptr(),
112 0,
113 size_bytes,
114 device_ctx.stream.as_raw(),
115 )
116 })
117 }
118
119 pub fn fill_zero_suffix_on(
121 &self,
122 start_idx: usize,
123 device_ctx: &GpuDeviceCtx,
124 ) -> Result<(), CudaError> {
125 assert!(
126 start_idx <= self.len,
127 "start index has to be smaller than or equal to length"
128 );
129 if start_idx == self.len {
130 return Ok(());
131 }
132 #[cfg(feature = "debug-cuda-stream")]
133 debug_assert_eq!(
134 device_ctx.stream.as_raw(),
135 self.alloc_stream,
136 "fill_zero_suffix_on: stream mismatch"
137 );
138 let size_bytes = std::mem::size_of::<T>() * (self.len - start_idx);
139 check(unsafe {
140 cudaMemsetAsync(
141 self.as_mut_ptr().add(start_idx) as *mut c_void,
142 0,
143 size_bytes,
144 device_ctx.stream.as_raw(),
145 )
146 })
147 }
148
149 pub fn len(&self) -> usize {
151 self.len
152 }
153
154 pub fn is_empty(&self) -> bool {
156 self.len == 0 || self.ptr.is_null()
157 }
158
159 pub fn as_mut_ptr(&self) -> *mut T {
161 self.ptr
162 }
163
164 pub fn as_ptr(&self) -> *const T {
166 self.ptr as *const T
167 }
168
169 pub fn as_mut_raw_ptr(&self) -> *mut c_void {
171 self.ptr as *mut c_void
172 }
173
174 pub fn as_raw_ptr(&self) -> *const c_void {
176 self.ptr as *const c_void
177 }
178
179 pub fn as_buffer<U>(mut self) -> DeviceBuffer<U> {
182 assert_eq!(
183 size_of::<T>() % size_of::<U>(),
184 0,
185 "the underlying type size must divide the former one"
186 );
187 assert_eq!(
188 align_of::<T>() % align_of::<U>(),
189 0,
190 "the underlying type alignment must divide the former one"
191 );
192 let res = DeviceBuffer {
193 ptr: self.ptr as *mut U,
194 len: self.len * (size_of::<T>() / size_of::<U>()),
195 #[cfg(feature = "debug-cuda-stream")]
196 alloc_stream: self.alloc_stream,
197 };
198 self.ptr = ptr::null_mut(); self.len = 0;
200 res
201 }
202
203 pub fn view(&self) -> DeviceBufferView {
204 DeviceBufferView {
205 ptr: self.ptr as *const c_void,
206 size: self.len * size_of::<T>(),
207 }
208 }
209}
210
211impl<T> Drop for DeviceBuffer<T> {
212 fn drop(&mut self) {
213 if !self.ptr.is_null() {
214 tracing::debug!(
215 "Freeing device buffer of size {} (sizeof type = {})",
216 self.len,
217 size_of::<T>()
218 );
219 unsafe {
224 d_free(self.ptr as *mut c_void).expect("GPU free failed");
225 }
226 self.ptr = ptr::null_mut();
227 self.len = 0;
228 }
229 }
230}
231
232impl<T: Debug> Debug for DeviceBuffer<T> {
233 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
234 write!(
235 f,
236 "DeviceBuffer(len = {}, ptr = {:?})",
237 self.len(),
238 self.ptr
239 )
240 }
241}
242
243#[cfg(test)]
244mod tests {
245 use super::*;
246 use crate::copy::{MemCopyD2H, MemCopyH2D};
247
248 fn test_ctx() -> GpuDeviceCtx {
249 GpuDeviceCtx::for_current_device().unwrap()
250 }
251
252 #[test]
253 fn test_device_buffer_float() {
254 let device_ctx = test_ctx();
255 let db = DeviceBuffer::<f32>::with_capacity_on(10, &device_ctx);
257
258 assert_eq!(db.len(), 10);
259 assert!(!db.as_ptr().is_null());
260 assert!(!db.is_empty());
261
262 }
264
265 #[test]
266 fn test_device_buffer_fill_zero() {
267 let device_ctx = test_ctx();
268 let v: Vec<u64> = (0..10).collect();
269 let d_array = v.to_device_on(&device_ctx).unwrap();
270 d_array.fill_zero_on(&device_ctx).unwrap();
271 assert_eq!(d_array.to_host_on(&device_ctx).unwrap(), vec![0; v.len()]);
272 }
273}