use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering};
use anyhow::Result;
use cudarc::driver::{CudaContext, CudaEvent, CudaStream};
use derive_builder::Builder;
use tokio::sync::mpsc;
use uuid::Uuid;
use dynamo_memory::CudaMemPool;
use dynamo_memory::nixl::{NixlAgent, NixlBackendConfig, XferRequest};
use velo::EventManager;
use crate::manager::TransferManager;
use super::TransferCapabilities;
use notifications::RegisterPollingNotification;
pub(crate) use super::notifications;
pub use super::notifications::TransferCompleteNotification;
#[derive(Clone, Builder)]
#[builder(pattern = "owned", build_fn(private, name = "build_internal"), public)]
#[allow(dead_code)] pub struct TransferConfig {
#[builder(default = "Arc::new(EventManager::local())")]
event_system: Arc<EventManager>,
#[builder(default = "None", setter(strip_option))]
nixl_agent_name: Option<String>,
#[builder(default = "NixlBackendConfig::default()")]
nixl_backend_config: NixlBackendConfig,
#[builder(default = "0")]
cuda_device_id: usize,
#[builder(default = "get_tokio_runtime()")]
tokio_runtime: TokioRuntime,
#[builder(default = "TransferCapabilities::default()")]
capabilities: TransferCapabilities,
#[builder(default = "64 * 1024 * 1024")]
cuda_pool_reserve_size: usize,
#[builder(default = "Some(64 * 1024 * 1024)")]
cuda_pool_release_threshold: Option<u64>,
}
impl TransferConfigBuilder {
pub fn from_event_system_and_handle(
self,
event_system: Arc<EventManager>,
handle: tokio::runtime::Handle,
) -> Self {
self.event_system(event_system)
.tokio_runtime(TokioRuntime::Handle(handle))
}
pub fn nixl_agent(self, agent: NixlAgent) -> TransferConfigBuilderWithAgent {
TransferConfigBuilderWithAgent {
builder: self,
agent,
}
}
pub fn nixl_backend(mut self, backend: impl Into<String>) -> Self {
let config = self
.nixl_backend_config
.get_or_insert_with(NixlBackendConfig::default);
*config = config.clone().with_backend(backend);
self
}
pub fn with_env_backends(mut self) -> Result<Self> {
let env_config = NixlBackendConfig::from_env()?;
let config = self
.nixl_backend_config
.get_or_insert_with(NixlBackendConfig::default);
*config = config.clone().merge(env_config);
Ok(self)
}
pub fn build(self) -> Result<TransferManager> {
let mut config = self.build_internal()?;
let worker_id = config.event_system.system_id();
if config.nixl_backend_config.backends().is_empty() {
config.nixl_backend_config = NixlBackendConfig::from_env()?;
}
let agent_name = config
.nixl_agent_name
.unwrap_or_else(|| format!("worker-{}", worker_id));
let nixl_agent =
NixlAgent::from_nixl_backend_config(&agent_name, config.nixl_backend_config)?;
let cuda_context = CudaContext::new(config.cuda_device_id)?;
let context = TransferContext::new(
nixl_agent,
config.event_system,
cuda_context,
config.tokio_runtime,
config.capabilities,
config.cuda_pool_reserve_size,
config.cuda_pool_release_threshold,
)?;
Ok(TransferManager::from_context(context))
}
}
pub struct TransferConfigBuilderWithAgent {
builder: TransferConfigBuilder,
agent: NixlAgent,
}
impl TransferConfigBuilderWithAgent {
pub fn build(self) -> Result<TransferManager> {
let config = self.builder.build_internal()?;
let cuda_context = CudaContext::new(config.cuda_device_id)?;
let context = TransferContext::new(
self.agent,
config.event_system,
cuda_context,
config.tokio_runtime,
config.capabilities,
config.cuda_pool_reserve_size,
config.cuda_pool_release_threshold,
)?;
Ok(TransferManager::from_context(context))
}
pub fn cuda_device_id(mut self, cuda_device_id: usize) -> Self {
self.builder = self.builder.cuda_device_id(cuda_device_id);
self
}
}
fn get_tokio_runtime() -> TokioRuntime {
match tokio::runtime::Handle::try_current() {
Ok(handle) => TokioRuntime::Handle(handle),
Err(_) => {
let rt = tokio::runtime::Builder::new_multi_thread()
.enable_all()
.max_blocking_threads(4)
.worker_threads(2)
.build()
.expect("failed to build tokio runtime");
TokioRuntime::Shared(Arc::new(rt))
}
}
}
#[derive(Debug, Clone)]
#[doc(hidden)]
pub enum TokioRuntime {
Handle(tokio::runtime::Handle),
Shared(Arc<tokio::runtime::Runtime>),
}
impl TokioRuntime {
pub fn handle(&self) -> &tokio::runtime::Handle {
match self {
TokioRuntime::Handle(handle) => handle,
TokioRuntime::Shared(runtime) => runtime.handle(),
}
}
}
#[derive(Clone)]
#[doc(hidden)]
pub struct TransferContext {
worker_id: u64,
nixl_agent: NixlAgent,
#[allow(dead_code)]
cuda_context: Arc<CudaContext>,
d2h_stream: Arc<CudaStream>,
h2d_stream: Arc<CudaStream>,
d2h_streams: Vec<Arc<CudaStream>>,
h2d_streams: Vec<Arc<CudaStream>>,
current_d2h_stream: Arc<AtomicUsize>,
current_h2d_stream: Arc<AtomicUsize>,
#[allow(dead_code)]
tokio_runtime: TokioRuntime,
capabilities: TransferCapabilities,
event_system: Arc<EventManager>,
cuda_pool: Arc<CudaMemPool>,
tx_nixl_status: mpsc::Sender<RegisterPollingNotification<notifications::NixlStatusChecker>>,
tx_cuda_event: mpsc::Sender<RegisterPollingNotification<notifications::CudaEventChecker>>,
#[allow(dead_code)]
tx_nixl_events: mpsc::Sender<notifications::RegisterNixlNotification>,
}
impl TransferContext {
pub fn builder() -> TransferConfigBuilder {
TransferConfigBuilder::default()
}
pub(crate) fn new(
nixl_agent: NixlAgent,
event_system: Arc<EventManager>,
cuda_context: Arc<CudaContext>,
tokio_runtime: TokioRuntime,
capabilities: TransferCapabilities,
cuda_pool_reserve_size: usize,
cuda_pool_release_threshold: Option<u64>,
) -> Result<Self> {
unsafe { cuda_context.disable_event_tracking() };
let mut pool_builder = CudaMemPool::builder(cuda_context.clone(), cuda_pool_reserve_size);
if let Some(threshold) = cuda_pool_release_threshold {
pool_builder = pool_builder.release_threshold(threshold);
}
let cuda_pool = Arc::new(pool_builder.build()?);
let (tx_nixl_status, rx_nixl_status) = mpsc::channel(64);
let (tx_cuda_event, rx_cuda_event) = mpsc::channel(64);
let (tx_nixl_events, rx_nixl_events) = mpsc::channel(64);
let handle = tokio_runtime.handle();
handle.spawn(notifications::process_polling_notifications(
rx_nixl_status,
event_system.clone(),
));
handle.spawn(notifications::process_polling_notifications(
rx_cuda_event,
event_system.clone(),
));
handle.spawn(notifications::process_nixl_notification_events(
nixl_agent.raw_agent().clone(),
rx_nixl_events,
event_system.clone(),
));
let d2h_streams: Vec<Arc<CudaStream>> = (0..4)
.map(|_| cuda_context.new_stream())
.collect::<Result<Vec<_>, _>>()?;
let h2d_streams: Vec<Arc<CudaStream>> = (0..4)
.map(|_| cuda_context.new_stream())
.collect::<Result<Vec<_>, _>>()?;
let d2h_stream = d2h_streams[0].clone();
let h2d_stream = h2d_streams[0].clone();
let current_d2h_stream = Arc::new(AtomicUsize::new(0));
let current_h2d_stream = Arc::new(AtomicUsize::new(0));
Ok(Self {
worker_id: event_system.system_id(),
nixl_agent,
cuda_context: cuda_context.clone(),
d2h_stream,
h2d_stream,
d2h_streams,
h2d_streams,
current_d2h_stream,
current_h2d_stream,
tokio_runtime,
capabilities,
event_system,
cuda_pool,
tx_nixl_status,
tx_cuda_event,
tx_nixl_events,
})
}
pub(crate) fn nixl_agent(&self) -> &NixlAgent {
&self.nixl_agent
}
#[allow(dead_code)]
pub(crate) fn cuda_context(&self) -> &Arc<CudaContext> {
&self.cuda_context
}
#[allow(dead_code)]
pub(crate) fn d2h_stream(&self) -> &Arc<CudaStream> {
&self.d2h_stream
}
#[allow(dead_code)]
pub(crate) fn h2d_stream(&self) -> &Arc<CudaStream> {
&self.h2d_stream
}
pub(crate) fn next_d2h_streams(&self) -> Arc<CudaStream> {
let current_d2h_stream = self.current_d2h_stream.fetch_add(1, Ordering::Relaxed);
self.d2h_streams[current_d2h_stream % self.d2h_streams.len()].clone()
}
pub(crate) fn next_h2d_streams(&self) -> Arc<CudaStream> {
let current_h2d_stream = self.current_h2d_stream.fetch_add(1, Ordering::Relaxed);
self.h2d_streams[current_h2d_stream % self.h2d_streams.len()].clone()
}
pub fn acquire_h2d_stream(&self) -> Arc<CudaStream> {
self.next_h2d_streams()
}
pub fn acquire_d2h_stream(&self) -> Arc<CudaStream> {
self.next_d2h_streams()
}
#[allow(dead_code)]
#[doc(hidden)]
pub fn tokio(&self) -> &tokio::runtime::Handle {
self.tokio_runtime.handle()
}
pub(crate) fn capabilities(&self) -> &TransferCapabilities {
&self.capabilities
}
#[doc(hidden)]
pub fn event_system(&self) -> &Arc<EventManager> {
&self.event_system
}
pub(crate) fn cuda_pool(&self) -> &Arc<CudaMemPool> {
&self.cuda_pool
}
pub(crate) fn register_nixl_status(
&self,
xfer_req: XferRequest,
) -> TransferCompleteNotification {
let event = self
.event_system
.new_event()
.expect("Failed to allocate event");
let handle = event.into_handle();
let awaiter = self
.event_system
.awaiter(handle)
.expect("Failed to get awaiter");
let notification = notifications::RegisterPollingNotification {
uuid: Uuid::new_v4(),
checker: notifications::NixlStatusChecker::new(
self.nixl_agent.raw_agent().clone(),
xfer_req,
),
event_handle: handle,
};
if let Err(e) = self.tx_nixl_status.try_send(notification) {
tracing::error!(
"Failed to enqueue NIXL status notification: channel full or closed: {}",
e
);
}
TransferCompleteNotification::from_awaiter(awaiter)
}
pub(crate) fn register_cuda_event(&self, event: CudaEvent) -> TransferCompleteNotification {
let new_event = self
.event_system
.new_event()
.expect("Failed to allocate event");
let handle = new_event.into_handle();
let awaiter = self
.event_system
.awaiter(handle)
.expect("Failed to get awaiter");
let notification = notifications::RegisterPollingNotification {
uuid: Uuid::new_v4(),
checker: notifications::CudaEventChecker::new(event),
event_handle: handle,
};
if let Err(e) = self.tx_cuda_event.try_send(notification) {
tracing::error!(
"Failed to enqueue CUDA event notification: channel full or closed: {}",
e
);
}
TransferCompleteNotification::from_awaiter(awaiter)
}
#[allow(dead_code)]
pub(crate) fn register_nixl_event(
&self,
xfer_req: XferRequest,
) -> TransferCompleteNotification {
let event = self
.event_system
.new_event()
.expect("Failed to allocate event");
let handle = event.into_handle();
let awaiter = self
.event_system
.awaiter(handle)
.expect("Failed to get awaiter");
let notification = notifications::RegisterNixlNotification {
uuid: Uuid::new_v4(),
xfer_req,
event_handle: handle,
};
if let Err(e) = self.tx_nixl_events.try_send(notification) {
tracing::error!(
"Failed to enqueue NIXL event notification: channel full or closed: {}",
e
);
}
TransferCompleteNotification::from_awaiter(awaiter)
}
pub(crate) fn worker_id(&self) -> u64 {
self.worker_id
}
}