Skip to main content

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}