1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
// SPDX-License-Identifier: MIT OR Apache-2.0
use crate::{
build_flags::cuda_stage_timings_disabled, context::CudaContext, driver::CudaNvtxRange,
error::CudaError,
};
mod handles;
pub(crate) use handles::CudaEvent;
#[cfg(test)]
use handles::CudaStream;
impl CudaContext {
fn complete_default_stream_work<T>(
&self,
mut work: impl FnMut() -> Result<T, CudaError>,
) -> Result<T, CudaError> {
self.inner.set_current()?;
let output = match work() {
Ok(output) => output,
Err(error) => return self.synchronize_then_error(error),
};
self.synchronize()?;
Ok(output)
}
/// Create a CUDA stream owned by this context.
#[cfg(test)]
pub(crate) fn create_stream(&self) -> Result<CudaStream, CudaError> {
let mut stream = std::ptr::null_mut();
self.inner.with_current_stateful_operation(|| {
// SAFETY: CUDA writes a new stream handle while the context
// lifecycle gate is held. CudaStream destroys the handle.
self.inner.driver.check("cuStreamCreate", unsafe {
(self.inner.driver.cu_stream_create)(&raw mut stream, 0)
})?;
crate::context::validate_resource_handle(
stream,
"CUDA returned a null stream after successful creation",
)
})?;
Ok(CudaStream {
context: self.clone(),
stream,
})
}
/// Create a CUDA timing event owned by this context.
pub(crate) fn create_event(&self) -> Result<CudaEvent, CudaError> {
let mut event = std::ptr::null_mut();
self.inner.with_current_stateful_operation(|| {
// SAFETY: CUDA writes a new event handle while the context
// lifecycle gate is held. CudaEvent destroys the handle.
self.inner.driver.check("cuEventCreate", unsafe {
(self.inner.driver.cu_event_create)(&raw mut event, 0)
})?;
crate::context::validate_resource_handle(
event,
"CUDA returned a null event after successful creation",
)
})?;
Ok(CudaEvent {
context: self.clone(),
event,
})
}
/// Time work submitted to the default CUDA stream and return elapsed microseconds.
///
/// `FnMut` keeps the closure environment owned by this frame until error
/// synchronization completes, so captured CUDA resources cannot be
/// dropped while submitted work may still reference them. Resources
/// created inside `work` must still be returned or protected by the
/// asynchronous operation's own completion guard.
pub(crate) fn time_default_stream_us<T>(
&self,
mut work: impl FnMut() -> Result<T, CudaError>,
) -> Result<(T, u128), CudaError> {
if cuda_stage_timings_disabled() {
// Disabling event collection must not weaken the helper's
// completion contract. Safe callers may return or recycle CUDA
// resources immediately after this method succeeds.
return self
.complete_default_stream_work(&mut work)
.map(|output| (output, 0));
}
self.inner.set_current()?;
let start = self.create_event()?;
let end = self.create_event()?;
start.record_default_stream()?;
let output = match work() {
Ok(output) => output,
Err(error) => {
// Timed closures may submit asynchronous default-stream work.
// On a later host-side error, wait before dropping any device
// buffers captured by the closure.
return self.synchronize_then_error(error);
}
};
// The gated event operations already either establish context-wide
// completion or quarantine the context before returning an error.
end.record_default_stream()?;
end.synchronize()?;
Ok((output, elapsed_event_us_ceil(&start, &end)?))
}
#[doc(hidden)]
/// Run work inside an optional NVTX profiling range.
///
/// The range is a no-op unless the crate is built with `cuda-profiling`
/// and an NVTX runtime library can be loaded dynamically.
pub fn with_nvtx_range<T>(
&self,
name: &str,
work: impl FnOnce() -> Result<T, CudaError>,
) -> Result<T, CudaError> {
let _range = CudaNvtxRange::push(name);
work()
}
#[doc(hidden)]
/// Time work submitted to the default CUDA stream inside an optional NVTX range.
///
/// The NVTX range is a no-op unless the crate is built with
/// `cuda-profiling` and an NVTX runtime library can be loaded dynamically.
pub fn time_default_stream_named_us<T>(
&self,
name: &str,
mut work: impl FnMut() -> Result<T, CudaError>,
) -> Result<(T, u128), CudaError> {
self.with_nvtx_range(name, || self.time_default_stream_us(&mut work))
}
#[doc(hidden)]
/// Optionally time work submitted to the default CUDA stream inside an NVTX range.
pub fn time_default_stream_named_us_if<T>(
&self,
collect_stage_timings: bool,
name: &str,
mut work: impl FnMut() -> Result<T, CudaError>,
) -> Result<(T, u128), CudaError> {
if collect_stage_timings {
self.time_default_stream_named_us(name, &mut work)
} else {
self.with_nvtx_range(name, || self.complete_default_stream_work(&mut work))
.map(|output| (output, 0))
}
}
#[doc(hidden)]
/// Submit default-stream work without establishing completion on success.
///
/// Errors are synchronized before return so captured resources can be
/// released safely. A successful return proves only submission.
///
/// # Safety
///
/// The caller must retain every resource reachable by submitted work and
/// establish context completion before any resource is mutated, reused,
/// or released. Prefer a typed `#[must_use]` queued guard.
pub unsafe fn submit_default_stream_named<T>(
&self,
name: &str,
mut work: impl FnMut() -> Result<T, CudaError>,
) -> Result<T, CudaError> {
self.inner.set_current()?;
self.with_nvtx_range(name, || match work() {
Ok(output) => Ok(output),
Err(error) => self.synchronize_then_error(error),
})
}
}
#[expect(
clippy::cast_possible_truncation,
clippy::cast_sign_loss,
reason = "rounded normalized samples are clamped to the complete u8 output range"
)]
pub(crate) fn elapsed_event_us_ceil(start: &CudaEvent, end: &CudaEvent) -> Result<u128, CudaError> {
let elapsed = CudaEvent::elapsed_time_us(start, end)?;
if elapsed <= 0.0 {
return Ok(1);
}
Ok(elapsed.ceil() as u128)
}