1#[cfg(all(feature = "cuda", metrale_rdma_verbs))]
22pub use imp::RdmaSnapshotArena;
23#[cfg(not(all(feature = "cuda", metrale_rdma_verbs)))]
24pub use stub::RdmaSnapshotArena;
25
26#[cfg(not(all(feature = "cuda", metrale_rdma_verbs)))]
27mod stub {
28 use anyhow::{Result, bail};
29 pub struct RdmaSnapshotArena;
32 impl RdmaSnapshotArena {
33 pub fn connect(_addr: &str, _arena_bytes: u64, _blob_bytes: usize) -> Result<Self> {
34 bail!("RDMA snapshot tier not built (needs feature `cuda` + metrale_rdma_verbs)")
35 }
36 pub fn connect_paging(_addr: &str, _arena_bytes: u64, _blob_bytes: usize) -> Result<Self> {
37 bail!("RDMA snapshot tier not built (needs feature `cuda` + metrale_rdma_verbs)")
38 }
39 pub fn write(&self, _offset: u64, _bytes: &[u8]) -> Result<()> {
40 unreachable!("stub RdmaSnapshotArena is never constructed")
41 }
42 pub fn read(&self, _offset: u64, _out: &mut [u8]) -> Result<()> {
43 unreachable!("stub RdmaSnapshotArena is never constructed")
44 }
45 pub fn paging_put(&self, _key: u64, _bytes: &[u8]) -> Result<()> {
46 unreachable!("stub RdmaSnapshotArena is never constructed")
47 }
48 pub fn paging_get(&self, _key: u64, _out: &mut [u8]) -> Result<bool> {
49 unreachable!("stub RdmaSnapshotArena is never constructed")
50 }
51 pub fn paging_remove(&self, _key: u64) -> Result<()> {
52 unreachable!("stub RdmaSnapshotArena is never constructed")
53 }
54 }
55}
56
57#[cfg(all(feature = "cuda", metrale_rdma_verbs))]
58mod imp {
59 use std::io::Write;
60 use std::net::TcpStream;
61 use std::sync::Mutex;
62
63 use anyhow::{Result, bail};
64
65 use crate::cuda_min::PinnedBuffer;
66 use metrale_gpu_sys::env::{first_set, first_set_u32};
67 use metrale_gpu_sys::railset::{RailSet, RailSpec};
68 use metrale_gpu_sys::verbs::Verbs;
69
70 struct SnapRail {
72 verbs: Verbs,
73 bounce: PinnedBuffer,
74 lkey: u32,
75 remote_rkey: u32,
76 staging_lkey: u32,
80 }
81
82 struct ArenaInner {
85 rails: Vec<SnapRail>,
86 staging: Option<PinnedBuffer>,
90 rr: usize,
91 next_wr: u64,
92 stream: TcpStream,
95 }
96
97 pub struct RdmaSnapshotArena {
100 inner: Mutex<ArenaInner>,
101 remote_base: u64,
102 blob_bytes: usize,
103 }
104
105 unsafe impl Send for RdmaSnapshotArena {}
108 unsafe impl Sync for RdmaSnapshotArena {}
109
110 impl RdmaSnapshotArena {
111 pub fn connect(addr: &str, arena_bytes: u64, blob_bytes: usize) -> Result<Self> {
116 Self::connect_inner(addr, arena_bytes, blob_bytes, false)
117 }
118
119 pub fn connect_paging(addr: &str, arena_bytes: u64, blob_bytes: usize) -> Result<Self> {
124 Self::connect_inner(addr, arena_bytes, blob_bytes, true)
125 }
126
127 fn connect_inner(
128 addr: &str,
129 arena_bytes: u64,
130 blob_bytes: usize,
131 paging: bool,
132 ) -> Result<Self> {
133 let spec =
135 |dev: String, gid: u32| RailSpec::new(dev, gid, rand::random::<u32>() & 0xff_ffff);
136 let rail0 = spec(
137 first_set(&["METRALE_EXPERT_RDMA_DEV"], "roceP2p1s0f1"),
138 first_set_u32(&["METRALE_EXPERT_RDMA_GID"], 3),
139 );
140 let dual = std::env::var("METRALE_KV_DUAL_RAIL").ok().as_deref() == Some("1");
141 let specs: Vec<RailSpec> = if dual {
142 let rail1 = spec(
143 first_set(&["METRALE_KV_RAIL2_DEV"], "rocep1s0f1"),
144 first_set_u32(&["METRALE_KV_RAIL2_GID"], 3),
145 );
146 vec![rail0, rail1]
147 } else {
148 vec![rail0]
149 };
150 let n_rails = specs.len();
151
152 let mut stream = TcpStream::connect(addr)
153 .map_err(|e| anyhow::anyhow!("connect snapshot peer {addr}: {e}"))?;
154 stream.set_nodelay(true).ok();
155 stream.write_all(&crate::snapshot_swap::encode_paging_v2_header(
158 crate::snapshot_swap::PagingKind::SSM,
159 arena_bytes,
160 if paging { blob_bytes as u64 } else { 0 },
161 ))?;
162 let mut rs = RailSet::begin(&mut stream, &specs)?;
163
164 let staging_on = std::env::var("METRALE_SSM_STAGING").ok().as_deref() == Some("1");
166 let staging = if staging_on {
167 Some(PinnedBuffer::new(blob_bytes)?)
168 } else {
169 None
170 };
171
172 let mut parts: Vec<(PinnedBuffer, u32, u32)> = Vec::with_capacity(n_rails);
175 for rail in &mut rs.rails {
176 let bounce = PinnedBuffer::new(blob_bytes)?;
177 let keys = unsafe { rail.verbs.reg_mr(bounce.ptr, blob_bytes, false)? };
179 let staging_lkey = match &staging {
182 Some(s) => unsafe { rail.verbs.reg_mr(s.ptr, blob_bytes, false)?.lkey },
183 None => 0,
184 };
185 parts.push((bounce, keys.lkey, staging_lkey));
186 }
187
188 let server = rs.finish_rw(&mut stream, "snapshot peer")?;
190 let base = server.last().map(|sp| sp.base_addr).unwrap_or(0);
191 let rails: Vec<SnapRail> = rs
192 .into_verbs()
193 .into_iter()
194 .zip(parts)
195 .zip(&server)
196 .map(|((verbs, (bounce, lkey, staging_lkey)), sp)| SnapRail {
197 verbs,
198 bounce,
199 lkey,
200 remote_rkey: sp.rkey,
201 staging_lkey,
202 })
203 .collect();
204 tracing::info!(
205 "RdmaSnapshotArena connected to {addr}: {:.1} GiB arena, {n_rails} rail(s), blob {blob_bytes} B",
206 arena_bytes as f64 / (1024.0 * 1024.0 * 1024.0),
207 );
208 Ok(Self {
209 inner: Mutex::new(ArenaInner {
210 rails,
211 staging,
212 rr: 0,
213 next_wr: 1,
214 stream,
215 }),
216 remote_base: base,
217 blob_bytes,
218 })
219 }
220
221 #[inline]
222 pub fn blob_bytes(&self) -> usize {
223 self.blob_bytes
224 }
225
226 pub fn write(&self, offset: u64, bytes: &[u8]) -> Result<()> {
229 if bytes.len() != self.blob_bytes {
230 bail!(
231 "snapshot write: {} != blob_bytes {}",
232 bytes.len(),
233 self.blob_bytes
234 );
235 }
236 let mut g = self.inner.lock().expect("snapshot arena mutex");
237 self.rdma_write_locked(&mut g, self.remote_base + offset, bytes)
238 }
239
240 pub fn read(&self, offset: u64, out: &mut [u8]) -> Result<()> {
243 if out.len() != self.blob_bytes {
244 bail!(
245 "snapshot read: {} != blob_bytes {}",
246 out.len(),
247 self.blob_bytes
248 );
249 }
250 let mut g = self.inner.lock().expect("snapshot arena mutex");
251 self.rdma_read_locked(&mut g, self.remote_base + offset, out)
252 }
253
254 fn rail_and_wr(g: &mut ArenaInner) -> (usize, u64) {
256 let n = g.rails.len();
257 let ri = g.rr % n;
258 g.rr = g.rr.wrapping_add(1);
259 let wr = g.next_wr;
260 g.next_wr = g.next_wr.wrapping_add(1).max(1);
261 (ri, wr)
262 }
263
264 fn rdma_write_locked(&self, g: &mut ArenaInner, raddr: u64, bytes: &[u8]) -> Result<()> {
265 if g.staging.is_some() {
266 return self.rdma_staged(g, raddr, Some(bytes), None);
267 }
268 let (ri, wr) = Self::rail_and_wr(g);
269 let rail = &mut g.rails[ri];
270 unsafe {
273 std::ptr::copy_nonoverlapping(
274 bytes.as_ptr(),
275 rail.bounce.ptr as *mut u8,
276 self.blob_bytes,
277 );
278 rail.verbs.post_write(
279 rail.bounce.ptr,
280 rail.lkey,
281 raddr,
282 rail.remote_rkey,
283 self.blob_bytes as u32,
284 wr,
285 )?;
286 }
287 while rail.verbs.poll()? != wr {}
288 Ok(())
289 }
290
291 fn rdma_read_locked(&self, g: &mut ArenaInner, raddr: u64, out: &mut [u8]) -> Result<()> {
292 if g.staging.is_some() {
293 return self.rdma_staged(g, raddr, None, Some(out));
294 }
295 let (ri, wr) = Self::rail_and_wr(g);
296 let rail = &mut g.rails[ri];
297 unsafe {
300 rail.verbs.post_read(
301 rail.bounce.ptr,
302 rail.lkey,
303 raddr,
304 rail.remote_rkey,
305 self.blob_bytes as u32,
306 wr,
307 )?;
308 }
309 while rail.verbs.poll()? != wr {}
310 unsafe {
311 std::ptr::copy_nonoverlapping(
312 rail.bounce.ptr as *const u8,
313 out.as_mut_ptr(),
314 self.blob_bytes,
315 );
316 }
317 Ok(())
318 }
319
320 fn rdma_staged(
326 &self,
327 g: &mut ArenaInner,
328 raddr: u64,
329 write_src: Option<&[u8]>,
330 read_dst: Option<&mut [u8]>,
331 ) -> Result<()> {
332 let staging = g.staging.as_ref().expect("staging present").ptr as *mut u8;
333 let is_read = read_dst.is_some();
334 if let Some(src) = write_src {
335 unsafe { std::ptr::copy_nonoverlapping(src.as_ptr(), staging, self.blob_bytes) };
336 }
337 let n = g.rails.len();
338 let chunk = crate::snapshot_swap::staging_chunk_bytes();
339 let depth = crate::snapshot_swap::staging_depth();
340 let plan = crate::snapshot_swap::stripe_plan(self.blob_bytes, chunk, n);
341 let total: usize = plan.iter().map(|w| w.len()).sum();
342 let mut posted = vec![0usize; n];
343 let mut reaped = vec![0usize; n];
344 let mut done = 0usize;
345 while done < total {
346 for ri in 0..n {
347 while posted[ri] < plan[ri].len() && (posted[ri] - reaped[ri]) < depth {
348 let (off, len) = plan[ri][posted[ri]];
349 let wr = g.next_wr;
350 g.next_wr = g.next_wr.wrapping_add(1).max(1);
351 let rail = &mut g.rails[ri];
352 let local = unsafe { staging.add(off) } as *mut _;
353 let raddr_chunk = raddr + off as u64;
354 unsafe {
355 if is_read {
356 rail.verbs.post_read(
357 local,
358 rail.staging_lkey,
359 raddr_chunk,
360 rail.remote_rkey,
361 len as u32,
362 wr,
363 )?;
364 } else {
365 rail.verbs.post_write(
366 local,
367 rail.staging_lkey,
368 raddr_chunk,
369 rail.remote_rkey,
370 len as u32,
371 wr,
372 )?;
373 }
374 }
375 posted[ri] += 1;
376 }
377 }
378 for ri in 0..n {
379 if posted[ri] > reaped[ri] {
380 g.rails[ri].verbs.poll()?;
381 reaped[ri] += 1;
382 done += 1;
383 }
384 }
385 }
386 if let Some(dst) = read_dst {
387 unsafe {
388 std::ptr::copy_nonoverlapping(
389 staging as *const u8,
390 dst.as_mut_ptr(),
391 self.blob_bytes,
392 )
393 };
394 }
395 Ok(())
396 }
397
398 pub fn paging_put(&self, key: u64, bytes: &[u8]) -> Result<()> {
401 if bytes.len() != self.blob_bytes {
402 bail!(
403 "paging_put: {} != blob_bytes {}",
404 bytes.len(),
405 self.blob_bytes
406 );
407 }
408 let mut g = self.inner.lock().expect("snapshot arena mutex");
409 let off = crate::snapshot_swap::client_alloc(&mut g.stream, key)?;
410 self.rdma_write_locked(&mut g, self.remote_base + off, bytes)?;
411 crate::snapshot_swap::client_commit(&mut g.stream, key)
412 }
413
414 pub fn paging_get(&self, key: u64, out: &mut [u8]) -> Result<bool> {
417 if out.len() != self.blob_bytes {
418 bail!(
419 "paging_get: {} != blob_bytes {}",
420 out.len(),
421 self.blob_bytes
422 );
423 }
424 let mut g = self.inner.lock().expect("snapshot arena mutex");
425 match crate::snapshot_swap::client_get(&mut g.stream, key)? {
426 Some(off) => {
427 self.rdma_read_locked(&mut g, self.remote_base + off, out)?;
428 Ok(true)
429 }
430 None => Ok(false),
431 }
432 }
433
434 pub fn paging_remove(&self, key: u64) -> Result<()> {
436 let mut g = self.inner.lock().expect("snapshot arena mutex");
437 crate::snapshot_swap::client_remove(&mut g.stream, key)
438 }
439 }
440}