1use std::{
2 fmt,
3 marker::PhantomData,
4 ptr,
5 sync::{
6 Arc,
7 atomic::{AtomicU64, AtomicUsize, Ordering},
8 },
9};
10
11use cudf::{
12 RawCurrentResourceRouteLease, RawStreamReservationLease, Runtime, StreamReservationLease,
13 runtime::{BindingExtensionInitError, BindingExtensionLease, DeviceEntryGuard, ExecutionBindingId},
14};
15
16use crate::error::{Result, check_cuvs, from_cudf, from_interop, invalid_state, unsupported};
17
18const CREATE_OPERATION: &str = "CuvsResources::for_runtime";
19static NEXT_POOL_MEMBER_ID: AtomicU64 = AtomicU64::new(1);
20
21struct NativeResources {
22 raw: AtomicUsize,
23 device: cudf::DeviceId,
24 stream: usize,
25 memory_resource: usize,
26}
27
28impl NativeResources {
29 fn create(device: cudf::DeviceId, stream: usize, memory_resource: usize) -> Result<Self> {
30 cuvs_sys::validate_runtime_version()
31 .map_err(|error| invalid_state(CREATE_OPERATION, error.to_string()).set_source(error))?;
32 let _device =
33 DeviceEntryGuard::enter(device, CREATE_OPERATION).map_err(|error| from_cudf(CREATE_OPERATION, error))?;
34 let mut raw = 0;
35 unsafe { check_cuvs(cuvs_sys::cuvsResourcesCreate(&mut raw), CREATE_OPERATION)? };
36 if raw == 0 {
37 return Err(invalid_state(CREATE_OPERATION, "cuVS returned a null resources handle"));
38 }
39 let set_stream =
40 unsafe { cuvs_sys::cuvsStreamSet(raw, (stream as *mut std::ffi::c_void).cast::<cuvs_sys::CUstream_st>()) };
41 let set_pool = check_cuvs(set_stream, CREATE_OPERATION)
42 .and_then(|()| unsafe { check_cuvs(cuvs_sys::cuvsResourcesSetStreamPool(raw, 1), CREATE_OPERATION) });
43 if let Err(error) = set_pool {
44 unsafe {
45 let _ = cuvs_sys::cuvsResourcesDestroy(raw);
46 }
47 return Err(error);
48 }
49 Ok(Self { raw: AtomicUsize::new(raw), device, stream, memory_resource })
50 }
51
52 fn raw(&self, operation: &'static str) -> Result<cuvs_sys::cuvsResources_t> {
53 let raw = self.raw.load(Ordering::Acquire);
54 if raw == 0 {
55 return Err(invalid_state(operation, "the runtime-bound cuVS resources have been closed"));
56 }
57 Ok(raw)
58 }
59}
60
61impl Drop for NativeResources {
62 fn drop(&mut self) {
63 let raw = self.raw.swap(0, Ordering::AcqRel);
64 if raw == 0 {
65 return;
66 }
67 if let Ok(_device) = DeviceEntryGuard::enter(self.device, "CuvsResources::drop") {
68 unsafe {
69 let _ = cuvs_sys::cuvsResourcesDestroy(raw);
70 }
71 }
72 }
73}
74
75pub struct CuvsResources {
82 runtime: Arc<Runtime>,
83 native: BindingExtensionLease<NativeResources>,
84 _not_send_sync: PhantomData<*mut ()>,
85}
86
87#[must_use = "dropping pool coverage removes its allocation routes"]
90pub struct StreamPoolCoverage {
91 members: Vec<RawStreamReservationLease>,
92 routes: Vec<RawCurrentResourceRouteLease>,
93 _resources: CuvsResources,
94}
95
96impl StreamPoolCoverage {
97 pub fn usage(&self) -> Result<Vec<cudf::ReservationUsage>> {
99 self.members
100 .iter()
101 .map(|member| member.usage().map_err(|error| from_cudf("StreamPoolCoverage::usage", error)))
102 .collect()
103 }
104}
105
106impl CuvsResources {
107 pub fn for_runtime(runtime: Arc<Runtime>) -> Result<Self> {
108 let binding = runtime.execution_binding().map_err(|error| from_interop(CREATE_OPERATION, error))?;
109 let stream = binding.cuda_stream().as_raw() as usize;
110 let memory_resource = binding.memory_resource().as_raw() as usize;
111
112 let manager = runtime.device_memory_manager().map_err(|error| from_cudf(CREATE_OPERATION, error))?;
113 let manager_runtime = Runtime::builder()
114 .with_device(runtime.device())
115 .with_stream(runtime.stream().clone())
116 .with_memory_resource(manager.resource().clone())
117 .build()
118 .map_err(|error| from_cudf(CREATE_OPERATION, error))?;
119 let manager_binding =
120 manager_runtime.execution_binding().map_err(|error| from_interop(CREATE_OPERATION, error))?;
121 let current_memory_resource = manager_binding.memory_resource().as_raw() as usize;
122 if memory_resource != current_memory_resource {
123 return Err(unsupported(
124 CREATE_OPERATION,
125 "cuVS 26.08 cannot bind an arbitrary runtime memory resource; use the device memory manager resource and stream reservations",
126 )
127 .with_context("runtime_memory_resource", format!("0x{memory_resource:x}"))
128 .with_context("current_memory_resource", format!("0x{current_memory_resource:x}")));
129 }
130
131 let native = runtime
132 .get_or_try_init_binding_extension(CREATE_OPERATION, || {
133 NativeResources::create(runtime.device(), stream, memory_resource)
134 })
135 .map_err(|error| match error {
136 BindingExtensionInitError::Lifecycle(error) => from_cudf(CREATE_OPERATION, error),
137 BindingExtensionInitError::Factory(error) => error,
138 })?;
139 let resources = Self { runtime, native, _not_send_sync: PhantomData };
140 resources.validate_exact_binding(CREATE_OPERATION)?;
141 Ok(resources)
142 }
143
144 pub fn stream_pool_coverage(&self, reservation: Option<&StreamReservationLease>) -> Result<StreamPoolCoverage> {
148 let operation = "CuvsResources::stream_pool_coverage";
149 self.validate_exact_binding(operation)?;
150 if reservation.is_some_and(|lease| {
151 lease.stream().identity() != self.runtime.stream().identity() || lease.manager().device() != self.device()
152 }) {
153 return Err(invalid_state(operation, "reservation must cover the runtime stream on the same device"));
154 }
155 let manager = self.runtime.device_memory_manager().map_err(|error| from_cudf(operation, error))?;
156 let limit = reservation
157 .map(|lease| {
158 usize::try_from(lease.usage().map_err(|error| from_cudf(operation, error))?.allocation_limit_bytes)
159 .map_err(|_| invalid_state(operation, "reservation limit exceeds usize"))
160 })
161 .transpose()?;
162 let count = self.with_native(operation, |native| {
163 let mut count = 0;
164 unsafe {
165 check_cuvs(cuvs_sys::cuvsResourcesGetStreamPoolSize(native, &mut count), operation)?;
166 }
167 Ok(count)
168 })?;
169 if count == 0 {
170 return Err(invalid_state(operation, "cuVS resource has no RAFT stream pool"));
171 }
172 let mut coverage = StreamPoolCoverage {
173 members: Vec::with_capacity(count),
174 routes: Vec::with_capacity(count),
175 _resources: self.retain(),
176 };
177 for index in 0..count {
178 let raw = self.with_native(operation, |native| {
179 let mut raw = ptr::null_mut();
180 unsafe {
181 check_cuvs(cuvs_sys::cuvsResourcesGetStreamPoolStream(native, index, &mut raw), operation)?;
182 }
183 if raw.is_null() {
184 return Err(invalid_state(operation, "cuVS returned a null RAFT pool stream"));
185 }
186 Ok(raw)
187 })?;
188 if let Some(reservation) = reservation {
189 let id = NEXT_POOL_MEMBER_ID.fetch_add(1, Ordering::Relaxed);
190 let member = unsafe { reservation.attach_sibling_raw_stream(raw.cast(), id, limit.expect("limit")) }
192 .map_err(|error| from_cudf(operation, error))?;
193 coverage.members.push(member);
194 } else {
195 let route = unsafe { manager.route_current_resource_raw(raw.cast(), manager.resource()) }
197 .map_err(|error| from_cudf(operation, error))?;
198 coverage.routes.push(route);
199 }
200 }
201 Ok(coverage)
202 }
203
204 pub fn runtime(&self) -> &Arc<Runtime> {
205 &self.runtime
206 }
207
208 pub fn device(&self) -> cudf::DeviceId {
209 self.runtime.device()
210 }
211
212 pub fn binding_id(&self) -> ExecutionBindingId {
213 self.runtime.binding_id()
214 }
215
216 pub(crate) fn validate_exact_binding(&self, operation: &'static str) -> Result<()> {
217 let binding = self.runtime.execution_binding().map_err(|error| from_interop(operation, error))?;
218 let guard = self.native.enter(operation).map_err(|error| from_cudf(operation, error))?;
219 let native = guard.value();
220 let actual_device = binding.device();
221 let actual_stream = binding.cuda_stream().as_raw() as usize;
222 let actual_memory_resource = binding.memory_resource().as_raw() as usize;
223 if actual_device != native.device.ordinal() as i32
224 || actual_stream != native.stream
225 || actual_memory_resource != native.memory_resource
226 {
227 return Err(invalid_state(operation, "the cudf runtime no longer matches the cuVS resource binding")
228 .with_context("expected_device", native.device.ordinal())
229 .with_context("actual_device", actual_device)
230 .with_context("expected_stream", format!("0x{:x}", native.stream))
231 .with_context("actual_stream", format!("0x{actual_stream:x}"))
232 .with_context("expected_memory_resource", format!("0x{:x}", native.memory_resource))
233 .with_context("actual_memory_resource", format!("0x{actual_memory_resource:x}")));
234 }
235 native.raw(operation).map(|_| ())
236 }
237
238 pub(crate) fn retain(&self) -> Self {
239 Self { runtime: Arc::clone(&self.runtime), native: self.native.clone(), _not_send_sync: PhantomData }
240 }
241
242 pub(crate) fn with_native<T>(
243 &self,
244 operation: &'static str,
245 call: impl FnOnce(cuvs_sys::cuvsResources_t) -> Result<T>,
246 ) -> Result<T> {
247 let binding = self.runtime.execution_binding().map_err(|error| from_interop(operation, error))?;
248 let _device = DeviceEntryGuard::enter(self.device(), operation).map_err(|error| from_cudf(operation, error))?;
249 let guard = self.native.enter(operation).map_err(|error| from_cudf(operation, error))?;
250 let native = guard.value();
251 if binding.device() != native.device.ordinal() as i32
252 || binding.cuda_stream().as_raw() as usize != native.stream
253 || binding.memory_resource().as_raw() as usize != native.memory_resource
254 {
255 return Err(invalid_state(operation, "cuVS call rejected an execution-binding mismatch"));
256 }
257 call(native.raw(operation)?)
258 }
259}
260
261impl fmt::Debug for CuvsResources {
262 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
263 formatter
264 .debug_struct("CuvsResources")
265 .field("device", &self.device())
266 .field("binding_id", &self.binding_id())
267 .finish_non_exhaustive()
268 }
269}