1use std::sync::Arc;
7use std::sync::atomic::{AtomicUsize, Ordering};
8
9use anyhow::Result;
10use cudarc::driver::{CudaContext, CudaEvent, CudaStream};
11use derive_builder::Builder;
12use tokio::sync::mpsc;
13use uuid::Uuid;
14
15use dynamo_memory::CudaMemPool;
16use dynamo_memory::nixl::{NixlAgent, NixlBackendConfig, XferRequest};
17use velo::EventManager;
18
19use crate::manager::TransferManager;
20
21use super::TransferCapabilities;
24use notifications::RegisterPollingNotification;
25
26pub(crate) use super::notifications;
27pub use super::notifications::TransferCompleteNotification;
28
29#[derive(Clone, Builder)]
30#[builder(pattern = "owned", build_fn(private, name = "build_internal"), public)]
31#[allow(dead_code)] pub struct TransferConfig {
33 #[builder(default = "Arc::new(EventManager::local())")]
34 event_system: Arc<EventManager>,
35
36 #[builder(default = "None", setter(strip_option))]
38 nixl_agent_name: Option<String>,
39
40 #[builder(default = "NixlBackendConfig::default()")]
42 nixl_backend_config: NixlBackendConfig,
43
44 #[builder(default = "0")]
45 cuda_device_id: usize,
46
47 #[builder(default = "get_tokio_runtime()")]
48 tokio_runtime: TokioRuntime,
49
50 #[builder(default = "TransferCapabilities::default()")]
51 capabilities: TransferCapabilities,
52
53 #[builder(default = "64 * 1024 * 1024")]
55 cuda_pool_reserve_size: usize,
56
57 #[builder(default = "Some(64 * 1024 * 1024)")]
61 cuda_pool_release_threshold: Option<u64>,
62}
63
64impl TransferConfigBuilder {
65 pub fn from_event_system_and_handle(
72 self,
73 event_system: Arc<EventManager>,
74 handle: tokio::runtime::Handle,
75 ) -> Self {
76 self.event_system(event_system)
77 .tokio_runtime(TokioRuntime::Handle(handle))
78 }
79
80 pub fn nixl_agent(self, agent: NixlAgent) -> TransferConfigBuilderWithAgent {
86 TransferConfigBuilderWithAgent {
87 builder: self,
88 agent,
89 }
90 }
91
92 pub fn nixl_backend(mut self, backend: impl Into<String>) -> Self {
94 let config = self
95 .nixl_backend_config
96 .get_or_insert_with(NixlBackendConfig::default);
97 *config = config.clone().with_backend(backend);
98 self
99 }
100
101 pub fn with_env_backends(mut self) -> Result<Self> {
106 let env_config = NixlBackendConfig::from_env()?;
107 let config = self
108 .nixl_backend_config
109 .get_or_insert_with(NixlBackendConfig::default);
110 *config = config.clone().merge(env_config);
111 Ok(self)
112 }
113
114 pub fn build(self) -> Result<TransferManager> {
115 let mut config = self.build_internal()?;
116
117 let worker_id = config.event_system.system_id();
118
119 if config.nixl_backend_config.backends().is_empty() {
121 config.nixl_backend_config = NixlBackendConfig::from_env()?;
122 }
123
124 let agent_name = config
126 .nixl_agent_name
127 .unwrap_or_else(|| format!("worker-{}", worker_id));
128
129 let nixl_agent =
130 NixlAgent::from_nixl_backend_config(&agent_name, config.nixl_backend_config)?;
131
132 let cuda_context = CudaContext::new(config.cuda_device_id)?;
133 let context = TransferContext::new(
134 nixl_agent,
135 config.event_system,
136 cuda_context,
137 config.tokio_runtime,
138 config.capabilities,
139 config.cuda_pool_reserve_size,
140 config.cuda_pool_release_threshold,
141 )?;
142 Ok(TransferManager::from_context(context))
143 }
144}
145
146pub struct TransferConfigBuilderWithAgent {
151 builder: TransferConfigBuilder,
152 agent: NixlAgent,
153}
154
155impl TransferConfigBuilderWithAgent {
156 pub fn build(self) -> Result<TransferManager> {
158 let config = self.builder.build_internal()?;
159 let cuda_context = CudaContext::new(config.cuda_device_id)?;
160 let context = TransferContext::new(
161 self.agent,
162 config.event_system,
163 cuda_context,
164 config.tokio_runtime,
165 config.capabilities,
166 config.cuda_pool_reserve_size,
167 config.cuda_pool_release_threshold,
168 )?;
169 Ok(TransferManager::from_context(context))
170 }
171
172 pub fn cuda_device_id(mut self, cuda_device_id: usize) -> Self {
173 self.builder = self.builder.cuda_device_id(cuda_device_id);
174 self
175 }
176}
177
178fn get_tokio_runtime() -> TokioRuntime {
179 match tokio::runtime::Handle::try_current() {
180 Ok(handle) => TokioRuntime::Handle(handle),
181 Err(_) => {
182 let rt = tokio::runtime::Builder::new_multi_thread()
183 .enable_all()
184 .max_blocking_threads(4)
185 .worker_threads(2)
186 .build()
187 .expect("failed to build tokio runtime");
188
189 TokioRuntime::Shared(Arc::new(rt))
190 }
191 }
192}
193
194#[derive(Debug, Clone)]
195#[doc(hidden)]
196pub enum TokioRuntime {
197 Handle(tokio::runtime::Handle),
198 Shared(Arc<tokio::runtime::Runtime>),
199}
200
201impl TokioRuntime {
202 pub fn handle(&self) -> &tokio::runtime::Handle {
203 match self {
204 TokioRuntime::Handle(handle) => handle,
205 TokioRuntime::Shared(runtime) => runtime.handle(),
206 }
207 }
208}
209
210#[derive(Clone)]
211#[doc(hidden)]
212pub struct TransferContext {
213 worker_id: u64,
214 nixl_agent: NixlAgent,
215 #[allow(dead_code)]
216 cuda_context: Arc<CudaContext>,
217 d2h_stream: Arc<CudaStream>,
218 h2d_stream: Arc<CudaStream>,
219 d2h_streams: Vec<Arc<CudaStream>>,
220 h2d_streams: Vec<Arc<CudaStream>>,
221 current_d2h_stream: Arc<AtomicUsize>,
222 current_h2d_stream: Arc<AtomicUsize>,
223 #[allow(dead_code)]
224 tokio_runtime: TokioRuntime,
225 capabilities: TransferCapabilities,
226 event_system: Arc<EventManager>,
227 cuda_pool: Arc<CudaMemPool>,
229 tx_nixl_status: mpsc::Sender<RegisterPollingNotification<notifications::NixlStatusChecker>>,
231 tx_cuda_event: mpsc::Sender<RegisterPollingNotification<notifications::CudaEventChecker>>,
232 #[allow(dead_code)]
233 tx_nixl_events: mpsc::Sender<notifications::RegisterNixlNotification>,
234}
235
236impl TransferContext {
237 pub fn builder() -> TransferConfigBuilder {
238 TransferConfigBuilder::default()
239 }
240
241 pub(crate) fn new(
242 nixl_agent: NixlAgent,
243 event_system: Arc<EventManager>,
244 cuda_context: Arc<CudaContext>,
245 tokio_runtime: TokioRuntime,
246 capabilities: TransferCapabilities,
247 cuda_pool_reserve_size: usize,
248 cuda_pool_release_threshold: Option<u64>,
249 ) -> Result<Self> {
250 unsafe { cuda_context.disable_event_tracking() };
251
252 let mut pool_builder = CudaMemPool::builder(cuda_context.clone(), cuda_pool_reserve_size);
254 if let Some(threshold) = cuda_pool_release_threshold {
255 pool_builder = pool_builder.release_threshold(threshold);
256 }
257 let cuda_pool = Arc::new(pool_builder.build()?);
258
259 let (tx_nixl_status, rx_nixl_status) = mpsc::channel(64);
261 let (tx_cuda_event, rx_cuda_event) = mpsc::channel(64);
262 let (tx_nixl_events, rx_nixl_events) = mpsc::channel(64);
263
264 let handle = tokio_runtime.handle();
266
267 handle.spawn(notifications::process_polling_notifications(
269 rx_nixl_status,
270 event_system.clone(),
271 ));
272
273 handle.spawn(notifications::process_polling_notifications(
275 rx_cuda_event,
276 event_system.clone(),
277 ));
278
279 handle.spawn(notifications::process_nixl_notification_events(
281 nixl_agent.raw_agent().clone(),
282 rx_nixl_events,
283 event_system.clone(),
284 ));
285
286 let d2h_streams: Vec<Arc<CudaStream>> = (0..4)
287 .map(|_| cuda_context.new_stream())
288 .collect::<Result<Vec<_>, _>>()?;
289
290 let h2d_streams: Vec<Arc<CudaStream>> = (0..4)
291 .map(|_| cuda_context.new_stream())
292 .collect::<Result<Vec<_>, _>>()?;
293
294 let d2h_stream = d2h_streams[0].clone();
295 let h2d_stream = h2d_streams[0].clone();
296
297 let current_d2h_stream = Arc::new(AtomicUsize::new(0));
298 let current_h2d_stream = Arc::new(AtomicUsize::new(0));
299
300 Ok(Self {
301 worker_id: event_system.system_id(),
302 nixl_agent,
303 cuda_context: cuda_context.clone(),
304 d2h_stream,
305 h2d_stream,
306 d2h_streams,
307 h2d_streams,
308 current_d2h_stream,
309 current_h2d_stream,
310 tokio_runtime,
311 capabilities,
312 event_system,
313 cuda_pool,
314 tx_nixl_status,
315 tx_cuda_event,
316 tx_nixl_events,
317 })
318 }
319
320 pub(crate) fn nixl_agent(&self) -> &NixlAgent {
321 &self.nixl_agent
322 }
323
324 #[allow(dead_code)]
325 pub(crate) fn cuda_context(&self) -> &Arc<CudaContext> {
326 &self.cuda_context
327 }
328
329 #[allow(dead_code)]
331 pub(crate) fn d2h_stream(&self) -> &Arc<CudaStream> {
332 &self.d2h_stream
333 }
334
335 #[allow(dead_code)]
337 pub(crate) fn h2d_stream(&self) -> &Arc<CudaStream> {
338 &self.h2d_stream
339 }
340
341 pub(crate) fn next_d2h_streams(&self) -> Arc<CudaStream> {
343 let current_d2h_stream = self.current_d2h_stream.fetch_add(1, Ordering::Relaxed);
344 self.d2h_streams[current_d2h_stream % self.d2h_streams.len()].clone()
345 }
346
347 pub(crate) fn next_h2d_streams(&self) -> Arc<CudaStream> {
349 let current_h2d_stream = self.current_h2d_stream.fetch_add(1, Ordering::Relaxed);
350 self.h2d_streams[current_h2d_stream % self.h2d_streams.len()].clone()
351 }
352
353 pub fn acquire_h2d_stream(&self) -> Arc<CudaStream> {
361 self.next_h2d_streams()
362 }
363
364 pub fn acquire_d2h_stream(&self) -> Arc<CudaStream> {
372 self.next_d2h_streams()
373 }
374
375 #[allow(dead_code)]
376 #[doc(hidden)]
377 pub fn tokio(&self) -> &tokio::runtime::Handle {
378 self.tokio_runtime.handle()
379 }
380
381 pub(crate) fn capabilities(&self) -> &TransferCapabilities {
382 &self.capabilities
383 }
384
385 #[doc(hidden)]
386 pub fn event_system(&self) -> &Arc<EventManager> {
387 &self.event_system
388 }
389
390 pub(crate) fn cuda_pool(&self) -> &Arc<CudaMemPool> {
392 &self.cuda_pool
393 }
394
395 pub(crate) fn register_nixl_status(
401 &self,
402 xfer_req: XferRequest,
403 ) -> TransferCompleteNotification {
404 let event = self
405 .event_system
406 .new_event()
407 .expect("Failed to allocate event");
408 let handle = event.into_handle();
409 let awaiter = self
410 .event_system
411 .awaiter(handle)
412 .expect("Failed to get awaiter");
413
414 let notification = notifications::RegisterPollingNotification {
415 uuid: Uuid::new_v4(),
416 checker: notifications::NixlStatusChecker::new(
417 self.nixl_agent.raw_agent().clone(),
418 xfer_req,
419 ),
420 event_handle: handle,
421 };
422
423 if let Err(e) = self.tx_nixl_status.try_send(notification) {
425 tracing::error!(
426 "Failed to enqueue NIXL status notification: channel full or closed: {}",
427 e
428 );
429 }
430
431 TransferCompleteNotification::from_awaiter(awaiter)
432 }
433
434 pub(crate) fn register_cuda_event(&self, event: CudaEvent) -> TransferCompleteNotification {
439 let new_event = self
440 .event_system
441 .new_event()
442 .expect("Failed to allocate event");
443 let handle = new_event.into_handle();
444 let awaiter = self
445 .event_system
446 .awaiter(handle)
447 .expect("Failed to get awaiter");
448
449 let notification = notifications::RegisterPollingNotification {
450 uuid: Uuid::new_v4(),
451 checker: notifications::CudaEventChecker::new(event),
452 event_handle: handle,
453 };
454
455 if let Err(e) = self.tx_cuda_event.try_send(notification) {
457 tracing::error!(
458 "Failed to enqueue CUDA event notification: channel full or closed: {}",
459 e
460 );
461 }
462
463 TransferCompleteNotification::from_awaiter(awaiter)
464 }
465
466 #[allow(dead_code)]
472 pub(crate) fn register_nixl_event(
473 &self,
474 xfer_req: XferRequest,
475 ) -> TransferCompleteNotification {
476 let event = self
477 .event_system
478 .new_event()
479 .expect("Failed to allocate event");
480 let handle = event.into_handle();
481 let awaiter = self
482 .event_system
483 .awaiter(handle)
484 .expect("Failed to get awaiter");
485
486 let notification = notifications::RegisterNixlNotification {
487 uuid: Uuid::new_v4(),
488 xfer_req,
489 event_handle: handle,
490 };
491
492 if let Err(e) = self.tx_nixl_events.try_send(notification) {
494 tracing::error!(
495 "Failed to enqueue NIXL event notification: channel full or closed: {}",
496 e
497 );
498 }
499
500 TransferCompleteNotification::from_awaiter(awaiter)
501 }
502
503 pub(crate) fn worker_id(&self) -> u64 {
505 self.worker_id
506 }
507}