1use std::{
2 borrow::Cow,
3 ffi::c_void,
4 ops::Deref,
5 sync::{Arc, Mutex},
6};
7
8use crate::error::{check, CudaError};
9
10#[link(name = "cudart")]
11extern "C" {
12 fn cudaDeviceSynchronize() -> i32;
13 fn cudaStreamCreateWithFlags(stream: *mut cudaStream_t, flags: u32) -> i32;
14 fn cudaStreamDestroy(stream: cudaStream_t) -> i32;
15 fn cudaStreamSynchronize(stream: cudaStream_t) -> i32;
16 fn cudaStreamWaitEvent(stream: cudaStream_t, event: cudaEvent_t, flags: u32) -> i32;
17 fn cudaEventCreate(event: *mut cudaEvent_t) -> i32;
18 fn cudaEventRecord(event: cudaEvent_t, stream: cudaStream_t) -> i32;
19 fn cudaEventSynchronize(event: cudaEvent_t) -> i32;
20 fn cudaEventQuery(event: cudaEvent_t) -> i32;
21 fn cudaEventDestroy(event: cudaEvent_t) -> i32;
22 fn cudaEventElapsedTime(ms: *mut f32, start: cudaEvent_t, end: cudaEvent_t) -> i32;
23}
24
25pub fn device_synchronize() -> Result<(), CudaError> {
26 check(unsafe { cudaDeviceSynchronize() })
27}
28
29#[allow(non_camel_case_types)]
30pub type cudaStream_t = *mut c_void;
31
32pub struct CudaStream {
33 stream: cudaStream_t,
34 host_event: Mutex<CudaEvent>,
35}
36
37impl std::fmt::Debug for CudaStream {
38 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
39 f.debug_struct("CudaStream")
40 .field("stream", &self.stream)
41 .finish()
42 }
43}
44
45unsafe impl Send for CudaStream {}
46unsafe impl Sync for CudaStream {}
47
48const CUDA_STREAM_NON_BLOCKING: u32 = 0x1;
51
52impl CudaStream {
53 pub fn new_non_blocking() -> Result<Self, CudaError> {
57 let mut stream: cudaStream_t = std::ptr::null_mut();
58 check(unsafe { cudaStreamCreateWithFlags(&mut stream, CUDA_STREAM_NON_BLOCKING) })?;
59 let host_event = Mutex::new(CudaEvent::new()?);
60 Ok(Self { stream, host_event })
61 }
62
63 #[inline]
65 pub fn as_raw(&self) -> cudaStream_t {
66 self.stream
67 }
68
69 pub fn synchronize(&self) -> Result<(), CudaError> {
71 check(unsafe { cudaStreamSynchronize(self.stream) })
72 }
73
74 pub fn wait(&self, event: &CudaEvent) -> Result<(), CudaError> {
76 check(unsafe { cudaStreamWaitEvent(self.stream, event.event, 0) })
77 }
78
79 pub fn to_host_sync(&self) -> Result<(), CudaError> {
84 let event = self.host_event.lock().unwrap();
85 unsafe { event.record(self.stream) }?;
86 event.synchronize()
87 }
88}
89
90impl Drop for CudaStream {
91 fn drop(&mut self) {
92 if !self.stream.is_null() {
93 let err = unsafe { cudaStreamDestroy(self.stream) };
95 debug_assert_eq!(err, 0, "cudaStreamDestroy failed with error code: {err}");
96 self.stream = std::ptr::null_mut();
97 }
98 }
99}
100
101#[derive(Clone, Debug)]
107pub struct StreamGuard(Arc<CudaStream>);
108
109impl StreamGuard {
110 pub fn new(stream: CudaStream) -> Self {
111 Self(Arc::new(stream))
112 }
113}
114
115impl PartialEq for StreamGuard {
116 fn eq(&self, other: &Self) -> bool {
117 Arc::ptr_eq(&self.0, &other.0)
118 }
119}
120
121impl Eq for StreamGuard {}
122
123impl Deref for StreamGuard {
124 type Target = CudaStream;
125 fn deref(&self) -> &CudaStream {
126 &self.0
127 }
128}
129
130#[derive(Clone, Debug)]
136pub struct GpuDeviceCtx {
137 pub device_id: u32,
138 pub stream: StreamGuard,
139}
140
141impl GpuDeviceCtx {
142 pub fn for_device(device_id: u32) -> Result<Self, CudaError> {
147 crate::common::set_device_by_id(device_id as i32)?;
148 Ok(Self {
149 device_id,
150 stream: StreamGuard::new(CudaStream::new_non_blocking()?),
151 })
152 }
153
154 pub fn for_current_device() -> Result<Self, CudaError> {
155 let device_id = crate::common::get_device()? as u32;
156 Self::for_device(device_id)
157 }
158}
159
160pub unsafe fn sync_stream(stream: cudaStream_t) -> Result<(), CudaError> {
166 check(cudaStreamSynchronize(stream))
167}
168
169#[allow(non_camel_case_types)]
174pub type cudaEvent_t = *mut c_void;
175
176#[derive(Debug)]
177pub enum CudaEventStatus {
178 Completed,
179 NotReady,
180 Error(CudaError),
181}
182
183impl PartialEq for CudaEventStatus {
184 fn eq(&self, other: &Self) -> bool {
185 use CudaEventStatus::*;
186 matches!((self, other), (Completed, Completed) | (NotReady, NotReady))
187 }
188}
189
190impl Eq for CudaEventStatus {}
191
192impl PartialOrd for CudaEventStatus {
193 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
194 Some(self.cmp(other))
195 }
196}
197
198impl Ord for CudaEventStatus {
200 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
201 use std::cmp::Ordering;
202
203 use CudaEventStatus::*;
204
205 match (self, other) {
206 (Completed, Completed) => Ordering::Equal,
207 (Completed, _) => Ordering::Less,
208 (_, Completed) => Ordering::Greater,
209 (NotReady, NotReady) => Ordering::Equal,
210 (NotReady, Error(_)) => Ordering::Less,
211 (Error(_), NotReady) => Ordering::Greater,
212 (Error(_), Error(_)) => Ordering::Equal,
213 }
214 }
215}
216
217#[derive(Debug)]
218pub struct CudaEvent {
219 event: cudaEvent_t,
220}
221
222unsafe impl Send for CudaEvent {}
223unsafe impl Sync for CudaEvent {}
224
225impl CudaEvent {
226 pub fn new() -> Result<Self, CudaError> {
227 let mut event: cudaEvent_t = std::ptr::null_mut();
228 check(unsafe { cudaEventCreate(&mut event) })?;
229 Ok(Self { event })
230 }
231
232 pub unsafe fn record(&self, stream: cudaStream_t) -> Result<(), CudaError> {
235 check(cudaEventRecord(self.event, stream))
236 }
237
238 pub fn record_on(&self, stream: &CudaStream) -> Result<(), CudaError> {
240 check(unsafe { cudaEventRecord(self.event, stream.as_raw()) })
241 }
242
243 pub fn synchronize(&self) -> Result<(), CudaError> {
244 check(unsafe { cudaEventSynchronize(self.event) })
245 }
246
247 pub unsafe fn record_and_wait(&self, stream: cudaStream_t) -> Result<(), CudaError> {
250 self.record(stream)?;
251 check(cudaEventSynchronize(self.event))
252 }
253
254 pub fn status(&self) -> CudaEventStatus {
255 let status = unsafe { cudaEventQuery(self.event) };
256 match status {
257 0 => CudaEventStatus::Completed, 600 => CudaEventStatus::NotReady, _ => CudaEventStatus::Error(CudaError::new(status)),
260 }
261 }
262
263 pub fn completed(&self) -> bool {
264 self.status() == CudaEventStatus::Completed
265 }
266}
267
268impl Drop for CudaEvent {
269 fn drop(&mut self) {
270 let err = unsafe { cudaEventDestroy(self.event) };
271 debug_assert_eq!(err, 0, "cudaEventDestroy failed with error code: {err}");
272 }
273}
274
275pub fn gpu_metrics_span_on<R, F: FnOnce() -> R>(
281 name: impl Into<Cow<'static, str>>,
282 stream: &CudaStream,
283 f: F,
284) -> Result<R, CudaError> {
285 let start = CudaEvent::new()?;
286 let stop = CudaEvent::new()?;
287 start.record_on(stream)?;
288 let res = f();
289 stop.record_on(stream)?;
290 stop.synchronize()?;
291
292 let mut elapsed_ms = 0f32;
293 unsafe {
294 check(cudaEventElapsedTime(
295 &mut elapsed_ms,
296 start.event,
297 stop.event,
298 ))?
299 };
300
301 metrics::gauge!(name.into()).set(elapsed_ms as f64);
302 Ok(res)
303}