1use crate::symmetric::{CollectiveError, Rank, SymmetricBuffer, SymmetricTransport};
45use crate::transport::{Transport, default_barrier};
46use std::collections::{HashMap, VecDeque};
47use std::io::{self, Read, Write};
48use std::net::{IpAddr, Shutdown, SocketAddr, TcpListener, TcpStream};
49use std::sync::atomic::{AtomicBool, Ordering};
50use std::sync::{Arc, Condvar, Mutex, RwLock};
51use std::thread::{self, JoinHandle};
52use std::time::Duration;
53
54const HELLO: u8 = 1; const SEND: u8 = 2; const PUT: u8 = 3; const GETREQ: u8 = 4; const GETRESP: u8 = 5; const HEADER_LEN: usize = 13;
62
63fn write_frame<W: Write>(w: &mut W, kind: u8, a: u32, b: u32, payload: &[u8]) -> io::Result<()> {
64 let mut hdr = [0u8; HEADER_LEN];
65 hdr[0] = kind;
66 hdr[1..5].copy_from_slice(&a.to_le_bytes());
67 hdr[5..9].copy_from_slice(&b.to_le_bytes());
68 hdr[9..13].copy_from_slice(&(payload.len() as u32).to_le_bytes());
69 w.write_all(&hdr)?;
70 if !payload.is_empty() {
71 w.write_all(payload)?;
72 }
73 w.flush()
74}
75
76fn read_frame<R: Read>(r: &mut R) -> io::Result<(u8, u32, u32, Vec<u8>)> {
77 let mut hdr = [0u8; HEADER_LEN];
78 r.read_exact(&mut hdr)?;
79 let kind = hdr[0];
80 let a = u32::from_le_bytes(hdr[1..5].try_into().unwrap());
81 let b = u32::from_le_bytes(hdr[5..9].try_into().unwrap());
82 let len = u32::from_le_bytes(hdr[9..13].try_into().unwrap()) as usize;
83 let mut payload = vec![0u8; len];
84 if len > 0 {
85 r.read_exact(&mut payload)?;
86 }
87 Ok((kind, a, b, payload))
88}
89
90fn connect_retry(addr: SocketAddr) -> io::Result<TcpStream> {
91 let mut last: Option<io::Error> = None;
92 for _ in 0..400 {
93 match TcpStream::connect(addr) {
94 Ok(s) => return Ok(s),
95 Err(e) => {
96 last = Some(e);
97 thread::sleep(Duration::from_millis(25));
98 }
99 }
100 }
101 Err(last.unwrap_or_else(|| io::Error::new(io::ErrorKind::TimedOut, "connect retry exhausted")))
102}
103
104struct NetInner {
105 rank: u32,
106 world: u32,
107 heap_size: usize,
108 writers: Vec<Mutex<Option<TcpStream>>>,
111 inbox: Mutex<HashMap<(u32, u32), VecDeque<Vec<u8>>>>,
113 inbox_cv: Condvar,
114 heap: RwLock<Vec<u8>>,
116 getresp: Mutex<HashMap<u32, VecDeque<Vec<u8>>>>,
118 getresp_cv: Condvar,
119 shutdown: AtomicBool,
120}
121
122impl NetInner {
123 fn send_frame(
124 &self,
125 peer: u32,
126 kind: u8,
127 a: u32,
128 b: u32,
129 payload: &[u8],
130 ) -> Result<(), CollectiveError> {
131 let mut guard = self.writers[peer as usize].lock().unwrap();
132 match guard.as_mut() {
133 Some(s) => {
134 write_frame(s, kind, a, b, payload).map_err(|e| CollectiveError::TransportError {
135 reason: format!("send to rank {peer}: {e}"),
136 })
137 }
138 None => Err(CollectiveError::TransportError {
139 reason: format!("no connection to rank {peer}"),
140 }),
141 }
142 }
143
144 fn recv_inbox(&self, from: u32, tag: u32) -> Vec<u8> {
145 let mut guard = self.inbox.lock().unwrap();
146 loop {
147 if let Some(q) = guard.get_mut(&(from, tag))
148 && let Some(v) = q.pop_front()
149 {
150 return v;
151 }
152 guard = self.inbox_cv.wait(guard).unwrap();
153 }
154 }
155
156 fn wait_getresp(&self, peer: u32) -> Vec<u8> {
157 let mut guard = self.getresp.lock().unwrap();
158 loop {
159 if let Some(q) = guard.get_mut(&peer)
160 && let Some(v) = q.pop_front()
161 {
162 return v;
163 }
164 guard = self.getresp_cv.wait(guard).unwrap();
165 }
166 }
167
168 fn check_buf(&self, buf: SymmetricBuffer) -> Result<(), CollectiveError> {
169 if buf.rank.0 >= self.world {
170 return Err(CollectiveError::UnknownRank {
171 rank: buf.rank,
172 num_ranks: self.world,
173 });
174 }
175 if buf.offset + buf.len > self.heap_size {
176 return Err(CollectiveError::OutOfBounds {
177 rank: buf.rank,
178 offset: buf.offset,
179 len: buf.len,
180 heap_size: self.heap_size,
181 });
182 }
183 Ok(())
184 }
185
186 fn dispatch(&self, kind: u8, a: u32, b: u32, payload: Vec<u8>) {
187 match kind {
188 SEND => {
189 self.inbox
190 .lock()
191 .unwrap()
192 .entry((a, b))
193 .or_default()
194 .push_back(payload);
195 self.inbox_cv.notify_all();
196 }
197 PUT => {
198 let off = b as usize;
199 if off + payload.len() <= self.heap_size {
200 let mut h = self.heap.write().unwrap();
201 h[off..off + payload.len()].copy_from_slice(&payload);
202 }
203 }
204 GETREQ if payload.len() >= 8 => {
205 let off = u32::from_le_bytes(payload[0..4].try_into().unwrap()) as usize;
206 let rlen = u32::from_le_bytes(payload[4..8].try_into().unwrap()) as usize;
207 let data = {
208 let h = self.heap.read().unwrap();
209 if off + rlen <= self.heap_size {
210 h[off..off + rlen].to_vec()
211 } else {
212 Vec::new()
213 }
214 };
215 let _ = self.send_frame(a, GETRESP, self.rank, off as u32, &data);
217 }
218 GETRESP => {
219 self.getresp
220 .lock()
221 .unwrap()
222 .entry(a)
223 .or_default()
224 .push_back(payload);
225 self.getresp_cv.notify_all();
226 }
227 _ => {}
228 }
229 }
230}
231
232fn reader_loop(mut stream: TcpStream, inner: Arc<NetInner>) {
233 while let Ok((kind, a, b, payload)) = read_frame(&mut stream) {
235 inner.dispatch(kind, a, b, payload);
236 if inner.shutdown.load(Ordering::Relaxed) {
237 break;
238 }
239 }
240}
241
242pub struct NetTransport {
246 inner: Arc<NetInner>,
247 readers: Mutex<Vec<JoinHandle<()>>>,
248}
249
250impl NetTransport {
251 pub fn from_listener(
261 rank: u32,
262 world: u32,
263 listener: TcpListener,
264 peers: Vec<SocketAddr>,
265 heap_size: usize,
266 ) -> io::Result<Self> {
267 assert_eq!(
268 peers.len(),
269 world as usize,
270 "peers must have world_size entries"
271 );
272 assert!(rank < world, "rank out of range");
273
274 let inner = Arc::new(NetInner {
275 rank,
276 world,
277 heap_size,
278 writers: (0..world).map(|_| Mutex::new(None)).collect(),
279 inbox: Mutex::new(HashMap::new()),
280 inbox_cv: Condvar::new(),
281 heap: RwLock::new(vec![0u8; heap_size]),
282 getresp: Mutex::new(HashMap::new()),
283 getresp_cv: Condvar::new(),
284 shutdown: AtomicBool::new(false),
285 });
286 let mut readers = Vec::new();
287
288 for p in (rank + 1)..world {
290 let stream = connect_retry(peers[p as usize])?;
291 stream.set_nodelay(true).ok();
292 let mut wr = stream.try_clone()?;
293 write_frame(&mut wr, HELLO, rank, 0, &[])?;
294 let rd = stream.try_clone()?;
295 let inner2 = inner.clone();
296 readers.push(thread::spawn(move || reader_loop(rd, inner2)));
297 *inner.writers[p as usize].lock().unwrap() = Some(wr);
298 }
299
300 for _ in 0..rank {
302 let (stream, _addr) = listener.accept()?;
303 stream.set_nodelay(true).ok();
304 let mut rd = stream.try_clone()?;
305 let (kind, peer, _b, _payload) = read_frame(&mut rd)?;
306 if kind != HELLO || peer >= world {
307 return Err(io::Error::new(
308 io::ErrorKind::InvalidData,
309 format!("expected HELLO with valid rank, got kind={kind} rank={peer}"),
310 ));
311 }
312 let wr = stream.try_clone()?;
313 let inner2 = inner.clone();
314 readers.push(thread::spawn(move || reader_loop(rd, inner2)));
315 *inner.writers[peer as usize].lock().unwrap() = Some(wr);
316 }
317
318 Ok(Self {
319 inner,
320 readers: Mutex::new(readers),
321 })
322 }
323}
324
325impl Drop for NetTransport {
326 fn drop(&mut self) {
327 self.inner.shutdown.store(true, Ordering::SeqCst);
328 for w in &self.inner.writers {
330 if let Some(s) = w.lock().unwrap().as_ref() {
331 let _ = s.shutdown(Shutdown::Both);
332 }
333 }
334 self.inner.inbox_cv.notify_all();
335 self.inner.getresp_cv.notify_all();
336 if let Ok(mut hs) = self.readers.lock() {
337 for h in hs.drain(..) {
338 let _ = h.join();
339 }
340 }
341 }
342}
343
344impl Transport for NetTransport {
345 fn rank(&self) -> u32 {
346 self.inner.rank
347 }
348 fn world_size(&self) -> u32 {
349 self.inner.world
350 }
351 fn send_bytes(&self, to: u32, tag: u32, bytes: &[u8]) -> Result<(), CollectiveError> {
352 if to == self.inner.rank {
353 self.inner
355 .inbox
356 .lock()
357 .unwrap()
358 .entry((to, tag))
359 .or_default()
360 .push_back(bytes.to_vec());
361 self.inner.inbox_cv.notify_all();
362 return Ok(());
363 }
364 self.inner.send_frame(to, SEND, self.inner.rank, tag, bytes)
365 }
366 fn recv_bytes(&self, from: u32, tag: u32) -> Result<Vec<u8>, CollectiveError> {
367 Ok(self.inner.recv_inbox(from, tag))
368 }
369 fn barrier(&self) -> Result<(), CollectiveError> {
370 default_barrier(self)
371 }
372}
373
374impl SymmetricTransport for NetTransport {
375 fn num_ranks(&self) -> u32 {
376 self.inner.world
377 }
378 fn this_rank(&self) -> Rank {
379 Rank(self.inner.rank)
380 }
381 fn put(&self, buf: SymmetricBuffer, src: &[u8]) -> Result<(), CollectiveError> {
382 self.inner.check_buf(buf)?;
383 if src.len() != buf.len {
384 return Err(CollectiveError::LengthMismatch {
385 expected: buf.len,
386 got: src.len(),
387 });
388 }
389 if buf.rank.0 == self.inner.rank {
390 let mut h = self.inner.heap.write().unwrap();
391 h[buf.offset..buf.offset + buf.len].copy_from_slice(src);
392 Ok(())
393 } else {
394 self.inner
395 .send_frame(buf.rank.0, PUT, self.inner.rank, buf.offset as u32, src)
396 }
397 }
398 fn get(&self, buf: SymmetricBuffer, dst: &mut [u8]) -> Result<(), CollectiveError> {
399 self.inner.check_buf(buf)?;
400 if dst.len() != buf.len {
401 return Err(CollectiveError::LengthMismatch {
402 expected: buf.len,
403 got: dst.len(),
404 });
405 }
406 if buf.rank.0 == self.inner.rank {
407 let h = self.inner.heap.read().unwrap();
408 dst.copy_from_slice(&h[buf.offset..buf.offset + buf.len]);
409 Ok(())
410 } else {
411 let mut payload = [0u8; 8];
412 payload[0..4].copy_from_slice(&(buf.offset as u32).to_le_bytes());
413 payload[4..8].copy_from_slice(&(buf.len as u32).to_le_bytes());
414 self.inner
415 .send_frame(buf.rank.0, GETREQ, self.inner.rank, 0, &payload)?;
416 let data = self.inner.wait_getresp(buf.rank.0);
417 if data.len() != buf.len {
418 return Err(CollectiveError::LengthMismatch {
419 expected: buf.len,
420 got: data.len(),
421 });
422 }
423 dst.copy_from_slice(&data);
424 Ok(())
425 }
426 }
427 fn barrier(&self) -> Result<(), CollectiveError> {
428 default_barrier(self)
429 }
430}
431
432pub const DEFAULT_HEAP_BYTES: usize = 64 * 1024 * 1024;
436
437pub struct TcpTransport;
441
442impl TcpTransport {
443 pub fn bind(
446 rank: u32,
447 world: u32,
448 peers: Vec<SocketAddr>,
449 heap_size: usize,
450 ) -> io::Result<NetTransport> {
451 let listener = TcpListener::bind(peers[rank as usize])?;
452 NetTransport::from_listener(rank, world, listener, peers, heap_size)
453 }
454}
455
456pub struct ThunderboltTransport;
462
463impl ThunderboltTransport {
464 pub fn bind(
468 rank: u32,
469 world: u32,
470 peers: Vec<SocketAddr>,
471 heap_size: usize,
472 ) -> io::Result<NetTransport> {
473 TcpTransport::bind(rank, world, peers, heap_size)
474 }
475
476 pub fn looks_like_thunderbolt(ip: IpAddr) -> bool {
481 match ip {
482 IpAddr::V4(v4) => {
483 let o = v4.octets();
484 o[0] == 169 && o[1] == 254 || o[0] == 10
485 }
486 IpAddr::V6(_) => false,
487 }
488 }
489}
490
491#[cfg(test)]
492mod tests {
493 use super::*;
494 use crate::collective::{ReduceKind, all_reduce};
495 use crate::transport::ProcessGroup;
496
497 fn run_net<F>(n: u32, heap: usize, body: F)
500 where
501 F: Fn(u32, Arc<NetTransport>) + Send + Sync + 'static,
502 {
503 let listeners: Vec<TcpListener> = (0..n)
504 .map(|_| TcpListener::bind((std::net::Ipv4Addr::LOCALHOST, 0)).unwrap())
505 .collect();
506 let addrs: Vec<SocketAddr> = listeners.iter().map(|l| l.local_addr().unwrap()).collect();
507 let body = Arc::new(body);
508 let handles: Vec<_> = listeners
509 .into_iter()
510 .enumerate()
511 .map(|(rank, listener)| {
512 let addrs = addrs.clone();
513 let body = body.clone();
514 thread::spawn(move || {
515 let t = Arc::new(
516 NetTransport::from_listener(rank as u32, n, listener, addrs, heap)
517 .expect("transport build"),
518 );
519 body(rank as u32, t.clone());
520 <NetTransport as Transport>::barrier(&*t).unwrap();
524 })
525 })
526 .collect();
527 for h in handles {
528 h.join().unwrap();
529 }
530 }
531
532 #[test]
533 fn two_sided_pipeline_handoff() {
534 run_net(3, 4096, |rank, t| {
537 let g = ProcessGroup::new(t);
538 let n = g.world_size();
539 if rank + 1 < n {
540 let got = g.recv_f32(rank + 1, 7).unwrap();
541 assert_eq!(got, vec![(rank as f32 + 1.0) * 10.0, 1.0]);
542 }
543 if rank > 0 {
544 g.send_f32(rank - 1, 7, &[rank as f32 * 10.0, 1.0]).unwrap();
545 }
546 });
547 }
548
549 #[test]
550 fn process_group_all_reduce_over_tcp() {
551 run_net(4, 4096, |rank, t| {
552 let g = ProcessGroup::new(t);
553 let mut data = vec![rank as f32 + 1.0; 5];
554 g.all_reduce(&mut data, ReduceKind::Sum).unwrap();
555 assert_eq!(data, vec![10.0; 5], "rank {rank}");
556 });
557 }
558
559 #[test]
560 fn process_group_barrier_and_broadcast() {
561 run_net(3, 4096, |rank, t| {
562 let g = ProcessGroup::new(t);
563 g.barrier().unwrap();
564 let mut data = if rank == 0 {
565 vec![1.0, 2.0]
566 } else {
567 vec![0.0, 0.0]
568 };
569 g.broadcast(0, &mut data).unwrap();
570 assert_eq!(data, vec![1.0, 2.0], "rank {rank}");
571 });
572 }
573
574 #[test]
575 fn symmetric_remote_put_get() {
576 run_net(2, 4096, |rank, t| {
579 let off = 128;
580 let payload = [5u8, 6, 7, 8];
581 if rank == 0 {
582 t.put(
584 SymmetricBuffer {
585 rank: Rank(0),
586 offset: off,
587 len: 4,
588 },
589 &[1u8, 2, 3, 4],
590 )
591 .unwrap();
592 t.put(
594 SymmetricBuffer {
595 rank: Rank(1),
596 offset: off,
597 len: 4,
598 },
599 &payload,
600 )
601 .unwrap();
602 }
603 <NetTransport as Transport>::barrier(&t).unwrap();
605 if rank == 1 {
607 let mut got = [0u8; 4];
608 t.get(
609 SymmetricBuffer {
610 rank: Rank(1),
611 offset: off,
612 len: 4,
613 },
614 &mut got,
615 )
616 .unwrap();
617 assert_eq!(got, payload, "local read of remote PUT");
618 let mut remote = [0u8; 4];
619 t.get(
620 SymmetricBuffer {
621 rank: Rank(0),
622 offset: off,
623 len: 4,
624 },
625 &mut remote,
626 )
627 .unwrap();
628 assert_eq!(remote, [1u8, 2, 3, 4], "remote GET from rank 0");
629 }
630 });
631 }
632
633 #[test]
634 fn symmetric_collective_all_reduce_over_net() {
635 run_net(4, 4096, |rank, t| {
638 let elems = 3usize;
639 let bytes = elems * 4;
640 let buf = SymmetricBuffer {
641 rank: Rank(rank),
642 offset: 0,
643 len: bytes,
644 };
645 let mut local = vec![rank as f32 + 1.0; elems];
646 all_reduce(&*t, buf, &mut local, ReduceKind::Sum).unwrap();
647 assert_eq!(local, vec![10.0; elems], "rank {rank}");
648 });
649 }
650
651 #[test]
652 fn thunderbolt_addr_heuristic() {
653 use std::net::Ipv4Addr;
654 assert!(ThunderboltTransport::looks_like_thunderbolt(
655 Ipv4Addr::new(169, 254, 3, 1).into()
656 ));
657 assert!(ThunderboltTransport::looks_like_thunderbolt(
658 Ipv4Addr::new(10, 0, 0, 2).into()
659 ));
660 assert!(!ThunderboltTransport::looks_like_thunderbolt(
661 Ipv4Addr::new(192, 168, 1, 5).into()
662 ));
663 }
664}