monarch_rdma/rdma_components.rs
1/*
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 * All rights reserved.
4 *
5 * This source code is licensed under the BSD-style license found in the
6 * LICENSE file in the root directory of this source tree.
7 */
8
9//! # RDMA Components
10//!
11//! This module provides the core RDMA building blocks for establishing and managing RDMA connections.
12//!
13//! ## Core Components
14//!
15//! * `IbvDomain` - Manages RDMA resources including context, protection domain, and memory region
16//! * `IbvQueuePair` - Handles communication between endpoints via queue pairs and completion queues
17//!
18//! ## RDMA Overview
19//!
20//! Remote Direct Memory Access (RDMA) allows direct memory access from the memory of one computer
21//! into the memory of another without involving either computer's operating system. This permits
22//! high-throughput, low-latency networking with minimal CPU overhead.
23//!
24//! ## Connection Architecture
25//!
26//! The module manages the following ibverbs primitives:
27//!
28//! 1. **Queue Pairs (QP)**: Each connection has a send queue and a receive queue
29//! 2. **Completion Queues (CQ)**: Events are reported when operations complete
30//! 3. **Memory Regions (MR)**: Memory must be registered with the RDMA device before use
31//! 4. **Protection Domains (PD)**: Provide isolation between different connections
32//!
33//! ## Connection Lifecycle
34//!
35//! 1. Create an `IbvDomain` with `new()`
36//! 2. Create an `IbvQueuePair` from the domain
37//! 3. Exchange connection info with remote peer (application must handle this)
38//! 4. Connect to remote endpoint with `connect()`
39//! 5. Perform RDMA operations (read/write)
40//! 6. Poll for completions
41//! 7. Resources are cleaned up when dropped
42
43/// Maximum size for a single RDMA operation in bytes (1 GiB)
44use std::fs;
45use std::result::Result;
46use std::time::Duration;
47
48use hyperactor::ActorRef;
49use hyperactor::context;
50use serde::Deserialize;
51use serde::Serialize;
52use typeuri::Named;
53
54use crate::RdmaAction;
55use crate::RdmaManagerActor;
56use crate::ReleaseBufferClient;
57use crate::backend::RdmaRemoteBackends;
58use crate::local_memory::KeepaliveLocalMemory;
59
60/// Lightweight handle representing a registered RDMA buffer.
61///
62/// Contains an id for the buffer registration, the buffer size, a reference
63/// to the owning [`RdmaManagerActor`], and backend-specific contexts for
64/// performing RDMA operations.
65#[derive(Debug, Named, Clone, Serialize, Deserialize)]
66pub struct RdmaRemoteBuffer {
67 pub id: usize,
68 pub size: usize,
69 pub owner: ActorRef<RdmaManagerActor>,
70 pub(crate) backends: RdmaRemoteBackends,
71}
72wirevalue::register_type!(RdmaRemoteBuffer);
73
74impl RdmaRemoteBuffer {
75 /// Push data from local memory into this remote buffer (local->remote).
76 pub async fn write_from_local(
77 &self,
78 client: &(impl context::Actor + Send + Sync),
79 local: KeepaliveLocalMemory,
80 timeout: u64,
81 ) -> Result<bool, anyhow::Error> {
82 let mut action = RdmaAction::new();
83 action.add_write_from_local(self.clone(), local)?;
84 action.submit(client, Duration::from_secs(timeout)).await?;
85 Ok(true)
86 }
87
88 /// Pull data from this remote buffer into local memory (remote->local).
89 pub async fn read_into_local(
90 &self,
91 client: &(impl context::Actor + Send + Sync),
92 local: KeepaliveLocalMemory,
93 timeout: u64,
94 ) -> Result<bool, anyhow::Error> {
95 let mut action = RdmaAction::new();
96 action.add_read_into_local(self.clone(), local)?;
97 action.submit(client, Duration::from_secs(timeout)).await?;
98 Ok(true)
99 }
100
101 /// Drop the buffer and release remote handles.
102 pub async fn drop_buffer(&self, client: &impl context::Actor) -> Result<(), anyhow::Error> {
103 tracing::debug!("[buffer] dropping buffer id={}", self.id);
104 self.owner.release_buffer(client, self.id).await?;
105 Ok(())
106 }
107}
108
109/// Utility to validate execution context.
110///
111/// Remote Execution environments do not always have access to the nvidia_peermem module
112/// and/or set the PeerMappingOverride parameter due to security. This function can be
113/// used to validate that the execution context when running operations that need this
114/// functionality (ie. cudaHostRegisterIoMemory).
115///
116/// # Returns
117///
118/// * `Ok(())` if the execution context is valid
119/// * `Err(anyhow::Error)` if the execution context is invalid
120pub async fn validate_execution_context() -> Result<(), anyhow::Error> {
121 // Check for nvidia peermem
122 match fs::read_to_string("/proc/modules") {
123 Ok(contents) => {
124 if !contents.contains("nvidia_peermem") {
125 return Err(anyhow::anyhow!(
126 "nvidia_peermem module not found in /proc/modules"
127 ));
128 }
129 }
130 Err(e) => {
131 return Err(anyhow::anyhow!(e));
132 }
133 }
134
135 // Test file access to nvidia params
136 match fs::read_to_string("/proc/driver/nvidia/params") {
137 Ok(contents) => {
138 if !contents.contains("PeerMappingOverride=1") {
139 return Err(anyhow::anyhow!(
140 "PeerMappingOverride=1 not found in /proc/driver/nvidia/params"
141 ));
142 }
143 }
144 Err(e) => {
145 return Err(anyhow::anyhow!(e));
146 }
147 }
148 Ok(())
149}
150
151/// Get all segments that have been registered with MRs for the given PD.
152///
153/// Each protection domain maintains independent segment registrations, so
154/// callers must pass the PD whose lkeys they intend to use.
155pub fn get_registered_cuda_segments(
156 pd: *mut rdmaxcel_sys::ibv_pd,
157) -> Vec<rdmaxcel_sys::rdma_segment_info_t> {
158 unsafe {
159 let segment_count = rdmaxcel_sys::rdma_get_active_segment_count(pd);
160 if segment_count <= 0 {
161 return Vec::new();
162 }
163
164 let mut segments = vec![
165 std::mem::MaybeUninit::<rdmaxcel_sys::rdma_segment_info_t>::zeroed()
166 .assume_init();
167 segment_count as usize
168 ];
169 let actual_count = rdmaxcel_sys::rdma_get_all_registered_segment_info(
170 pd,
171 segments.as_mut_ptr(),
172 segment_count,
173 );
174
175 if actual_count > 0 {
176 segments.truncate(actual_count as usize);
177 segments
178 } else {
179 Vec::new()
180 }
181 }
182}
183
184/// Segment scanner callback type alias for convenience.
185pub type SegmentScannerFn = rdmaxcel_sys::RdmaxcelSegmentScannerFn;
186
187/// Register a segment scanner callback.
188///
189/// The scanner callback is called during RDMA segment registration to discover
190/// CUDA memory segments. The callback should fill the provided buffer with
191/// segment information and return the total count of segments found.
192///
193/// If the returned count exceeds the buffer size, the caller will allocate
194/// a larger buffer and retry.
195///
196/// Pass `None` to unregister the scanner.
197///
198/// # Safety
199///
200/// The provided callback function must be safe to call from C code and must
201/// properly handle the segment buffer.
202pub fn register_segment_scanner(scanner: SegmentScannerFn) {
203 // SAFETY: We are registering a callback function pointer with rdmaxcel.
204 unsafe { rdmaxcel_sys::rdmaxcel_register_segment_scanner(scanner) }
205}