monarch_rdma/backend.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 backend implementations.
10
11#[cfg(any(test, feature = "test-utils"))]
12pub mod cuda_test_utils;
13pub mod ibverbs;
14pub mod tcp;
15
16use std::fmt::Debug;
17use std::time::Duration;
18
19use anyhow::Result;
20use async_trait::async_trait;
21use hyperactor::context;
22use serde::Deserialize;
23use serde::Serialize;
24use serde::de::DeserializeOwned;
25use typeuri::Named;
26
27use crate::RdmaOp;
28use crate::RdmaTransportLevel;
29use crate::backend::ibverbs::efa_device::EfaDevice;
30use crate::backend::ibverbs::manager_actor::IbvBackend;
31use crate::backend::ibverbs::mlx_device::MlxDevice;
32use crate::backend::ibverbs::primitives::IbvConfig;
33use crate::backend::tcp::manager_actor::TcpBackend;
34use crate::local_memory::KeepaliveLocalMemory;
35use crate::rdma_components::RdmaRemoteBuffer;
36
37/// Configuration for spawning RDMA backends.
38#[derive(Debug, Clone, Default)]
39pub struct RdmaConfig {
40 /// Configuration for ibverbs-based backends.
41 pub(crate) ibv: Option<IbvConfig>,
42}
43
44/// A transport backend for RDMA operations.
45///
46/// Implementors (e.g. [`IbvBackend<I>`], [`TcpBackend`]) are assembled
47/// into the [`RdmaBackendHandle`] and [`RdmaRemoteBackends`] registries
48/// by [`register_rdma_backends!`].
49#[async_trait]
50pub trait RdmaBackend: Clone + Debug + Send + Sync + 'static {
51 /// Serializable per-buffer context carried on the wire.
52 type RemoteBackendContext: Clone + Debug + Serialize + DeserializeOwned + Send + Sync + 'static;
53
54 /// Backend-specific transport details (e.g. a cffi struct with raw
55 /// ibverbs handles for GPU-initiated RDMA).
56 type TransportInfo;
57
58 /// Whether this backend is available on this host in the current proc.
59 /// Returns false if the backend is supported on the host but is
60 /// disabled by a config.
61 fn available() -> bool;
62
63 /// The transport level this backend provides.
64 fn transport_level(&self) -> RdmaTransportLevel;
65
66 /// Low-level transport details for direct control over RDMA
67 /// operations (e.g. from a GPU kernel).
68 fn transport_info(&self) -> Option<Self::TransportInfo>;
69
70 /// Spawn the backend's actor(s) as children of `cx` and return its handle.
71 async fn spawn(cx: &(impl context::Actor + Send + Sync), config: &RdmaConfig) -> Result<Self>
72 where
73 Self: Sized;
74
75 /// Register `local` for remote access and return its wire context.
76 async fn register_remote_buffer(
77 &self,
78 cx: &(impl context::Actor + Send + Sync),
79 remote_buf_id: usize,
80 local: KeepaliveLocalMemory,
81 ) -> Result<Self::RemoteBackendContext>;
82
83 /// Release a buffer registration by id.
84 async fn release_buffer(
85 &self,
86 cx: &(impl context::Actor + Send + Sync),
87 remote_buf_id: usize,
88 ) -> Result<()>;
89
90 /// Submit a batch of ops to this backend.
91 async fn submit(
92 &self,
93 cx: &(impl context::Actor + Send + Sync),
94 ops: Vec<RdmaOp>,
95 timeout: Duration,
96 ) -> Result<()>;
97}
98
99/// Resolves backend `B`'s context from a buffer's advertised backends.
100/// One impl per backend is generated by [`register_rdma_backends!`].
101pub(crate) trait ResolveRemoteBackendContext<B: RdmaBackend> {
102 fn resolve(&self) -> Option<B::RemoteBackendContext>;
103}
104
105/// Derives the per-process RDMA backend registry from a list of
106/// `Variant: Handle` pairs, where each `Handle` implements
107/// [`RdmaBackend`].
108///
109/// The expansion defines [`RdmaRemoteBackends`] (a buffer's per-backend
110/// wire contexts) with its [`ResolveRemoteBackendContext`] impls and
111/// `RdmaRemoteBuffer::resolve_<name>` accessors, [`RdmaBackendHandle`]
112/// and its `submit` dispatch, and [`RdmaBackends`] (the proc's spawned
113/// backends). The list order is the routing priority.
114macro_rules! register_rdma_backends {
115 ($($variant:ident: $handle:ty),+ $(,)?) => {
116 paste::paste! {
117 /// The backends a buffer is reachable through, one slot per backend.
118 #[derive(Debug, Clone, Serialize, Deserialize, Named, Default)]
119 pub(crate) struct RdmaRemoteBackends {
120 $(pub(crate) [<$variant:lower>]: Option<<$handle as RdmaBackend>::RemoteBackendContext>,)+
121 }
122
123 $(
124 impl ResolveRemoteBackendContext<$handle> for RdmaRemoteBuffer {
125 fn resolve(&self) -> Option<<$handle as RdmaBackend>::RemoteBackendContext> {
126 self.[<resolve_ $variant:lower>]()
127 }
128 }
129 )+
130
131 impl RdmaRemoteBuffer {
132 $(
133 /// Context for this backend, if the buffer advertises it.
134 pub fn [<resolve_ $variant:lower>](
135 &self,
136 ) -> Option<<$handle as RdmaBackend>::RemoteBackendContext> {
137 self.backends.[<$variant:lower>].clone()
138 }
139 )+
140
141 /// Whether this buffer advertises a backend compatible with `handle`.
142 pub(crate) fn is_compatible_with(&self, handle: &RdmaBackendHandle) -> bool {
143 match handle {
144 $(
145 RdmaBackendHandle::$variant(_) => {
146 self.backends.[<$variant:lower>].is_some()
147 }
148 )+
149 }
150 }
151 }
152
153 /// The backends spawned on this proc.
154 #[derive(Debug, Default)]
155 pub(crate) struct RdmaBackends {
156 $([<$variant:lower>]: Option<$handle>,)+
157 }
158
159 impl RdmaBackends {
160 /// Spawn every [`available`](RdmaBackend::available) backend.
161 /// A backend that fails to spawn is skipped; bails only if no
162 /// backend spawns.
163 pub(crate) async fn spawn_available(
164 cx: &(impl context::Actor + Send + Sync),
165 config: &RdmaConfig,
166 ) -> Result<Self> {
167 let mut backends = Self::default();
168 let mut errors: Vec<String> = Vec::new();
169 $(
170 if <$handle as RdmaBackend>::available() {
171 match <$handle as RdmaBackend>::spawn(cx, config).await {
172 Ok(handle) => backends.[<$variant:lower>] = Some(handle),
173 Err(e) => errors.push(format!("{}: {e}", stringify!($variant))),
174 }
175 }
176 )+
177 if backends.is_empty() {
178 if errors.is_empty() {
179 anyhow::bail!("no RDMA backend available");
180 }
181 anyhow::bail!(
182 "all available RDMA backends failed to initialize: {}",
183 errors.join("; ")
184 );
185 }
186 if !errors.is_empty() {
187 tracing::warn!("some RDMA backends failed to initialize: {}", errors.join("; "));
188 }
189 Ok(backends)
190 }
191
192 fn is_empty(&self) -> bool {
193 $(self.[<$variant:lower>].is_none() &&)+ true
194 }
195
196 /// Handles for all spawned backends, in priority order.
197 pub(crate) fn handles(&self) -> Vec<RdmaBackendHandle> {
198 let mut handles = Vec::new();
199 $(
200 if let Some(handle) = &self.[<$variant:lower>] {
201 handles.push(RdmaBackendHandle::$variant(handle.clone()));
202 }
203 )+
204 handles
205 }
206
207 /// Register `local` with every spawned backend. On the first
208 /// failure, release the backends that already registered and
209 /// return that error.
210 pub(crate) async fn register_all(
211 &self,
212 cx: &(impl context::Actor + Send + Sync),
213 remote_buf_id: usize,
214 local: KeepaliveLocalMemory,
215 ) -> Result<RdmaRemoteBackends> {
216 let mut remotes = RdmaRemoteBackends::default();
217 $(
218 if let Some(handle) = &self.[<$variant:lower>] {
219 match <$handle as RdmaBackend>::register_remote_buffer(
220 handle,
221 cx,
222 remote_buf_id,
223 local.clone(),
224 )
225 .await
226 {
227 Ok(context) => remotes.[<$variant:lower>] = Some(context),
228 Err(e) => {
229 // Release only the backends that already
230 // registered: this one failed and later
231 // ones were never reached.
232 if let Err(release_err) =
233 self.release_registered(cx, remote_buf_id, &remotes).await
234 {
235 tracing::warn!("failed to release remote buffers after registration failure: {release_err}");
236 }
237 return Err(e);
238 }
239 }
240 }
241 )+
242 Ok(remotes)
243 }
244
245 /// Release `remote_buf_id` from only the backends that
246 /// successfully registered (those present in `registered`),
247 /// accumulating any failures.
248 async fn release_registered(
249 &self,
250 cx: &(impl context::Actor + Send + Sync),
251 remote_buf_id: usize,
252 registered: &RdmaRemoteBackends,
253 ) -> Result<()> {
254 let mut errors: Vec<String> = Vec::new();
255 $(
256 if let Some(handle) = &self.[<$variant:lower>]
257 && registered.[<$variant:lower>].is_some()
258 {
259 if let Err(e) =
260 <$handle as RdmaBackend>::release_buffer(handle, cx, remote_buf_id).await
261 {
262 errors.push(format!("({}) {e}", stringify!($variant)));
263 }
264 }
265 )+
266 if errors.is_empty() {
267 Ok(())
268 } else {
269 anyhow::bail!(
270 "RDMA release failed on {} backend(s):\n{}",
271 errors.len(),
272 errors.join("\n")
273 )
274 }
275 }
276
277 /// Release `remote_buf_id` from every spawned backend,
278 /// accumulating any failures.
279 pub(crate) async fn release_all(
280 &self,
281 cx: &(impl context::Actor + Send + Sync),
282 remote_buf_id: usize,
283 ) -> Result<()> {
284 let mut errors: Vec<String> = Vec::new();
285 $(
286 if let Some(handle) = &self.[<$variant:lower>] {
287 if let Err(e) =
288 <$handle as RdmaBackend>::release_buffer(handle, cx, remote_buf_id).await
289 {
290 errors.push(format!("({}) {e}", stringify!($variant)));
291 }
292 }
293 )+
294 if errors.is_empty() {
295 Ok(())
296 } else {
297 anyhow::bail!(
298 "RDMA release failed on {} backend(s):\n{}",
299 errors.len(),
300 errors.join("\n")
301 )
302 }
303 }
304 }
305 }
306
307 wirevalue::register_type!(RdmaRemoteBackends);
308
309 /// Handle to a spawned backend.
310 #[derive(Debug, Clone)]
311 pub enum RdmaBackendHandle {
312 $($variant($handle),)+
313 }
314
315 impl RdmaBackendHandle {
316 /// The backend's name, for diagnostics.
317 pub(crate) fn backend_name(&self) -> &'static str {
318 match self {
319 $(RdmaBackendHandle::$variant(_) => stringify!($variant),)+
320 }
321 }
322
323 /// Submit `ops` to this backend.
324 pub(crate) async fn submit(
325 &self,
326 cx: &(impl context::Actor + Send + Sync),
327 ops: Vec<RdmaOp>,
328 timeout: Duration,
329 ) -> Result<()> {
330 match self {
331 $(
332 RdmaBackendHandle::$variant(handle) => {
333 <$handle as RdmaBackend>::submit(handle, cx, ops, timeout).await
334 }
335 )+
336 }
337 }
338 }
339 };
340}
341
342register_rdma_backends! {
343 Mlx: IbvBackend<MlxDevice>,
344 Efa: IbvBackend<EfaDevice>,
345 Tcp: TcpBackend,
346}