Skip to main content

rlx_driver/
net.rs

1// RLX — versatile ML compiler + runtime.
2// Copyright (C) 2026 Eugene Hauptmann, Nataliya Kosmyna.
3//
4// This program is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, version 3.
7//
8// This program is distributed in the hope that it will be useful,
9// but WITHOUT ANY WARRANTY; without even the implied warranty of
10// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11// GNU General Public License for more details.
12//
13// You should have received a copy of the GNU General Public License
14// along with this program. If not, see <https://www.gnu.org/licenses/>.
15
16//! Networked multi-node transport (plan #12/#49, multi-node).
17//!
18//! [`NetTransport`] is one TCP connection per rank pair (full mesh)
19//! with a single reader thread per connection that *demultiplexes*
20//! every incoming frame into the right place:
21//!
22//!   - `SEND`    → a two-sided inbox keyed by `(src, tag)` that
23//!     [`Transport::recv_bytes`] pops (pipeline-parallel hidden states).
24//!   - `PUT`     → this rank's symmetric heap (one-sided write).
25//!   - `GETREQ`  → read this rank's heap, reply with `GETRESP`.
26//!   - `GETRESP` → a response inbox the blocked
27//!     [`SymmetricTransport::get`] pops.
28//!
29//! Because the reader thread services the socket, a blocked `recv`/`get`
30//! on the main thread never stalls the peer — so the gather-to-root
31//! collectives in [`crate::transport::ProcessGroup`] (and the
32//! one-sided collectives in [`crate::collective`]) are deadlock-free
33//! over any rank count.
34//!
35//! Two public constructors expose the same engine with different
36//! intent:
37//!   - [`TcpTransport::bind`] — portable; give it any reachable IPs
38//!     (Ethernet, or the macOS Thunderbolt Bridge link).
39//!   - [`ThunderboltTransport::bind`] — same wire protocol, intended
40//!     for IPs on the Thunderbolt interface, and the place a future
41//!     zero-copy DMA backend slots in behind the unchanged
42//!     [`Transport`] + [`SymmetricTransport`] traits.
43
44use 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, Instant};
53
54// Frame kinds. Header is fixed 13 bytes: [kind:u8][a:u32 le][b:u32 le][len:u32 le].
55const HELLO: u8 = 1; // a = connector rank, no payload
56const SEND: u8 = 2; // a = src rank, b = tag, payload = bytes
57const PUT: u8 = 3; // a = src rank, b = offset, payload = bytes to write
58const GETREQ: u8 = 4; // a = requester rank, payload = [offset:u32 le][len:u32 le]
59const GETRESP: u8 = 5; // a = responder rank, payload = bytes
60
61const 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    // Coalesce header + payload into ONE write for the small frames that
70    // dominate collective latency: with TCP_NODELAY set, two separate
71    // writes became two segments and an extra RTT-coupled syscall. For
72    // large frames the second write is negligible against the payload, so
73    // keep them separate and skip the copy.
74    const COALESCE_MAX: usize = 1 << 16;
75    if payload.len() <= COALESCE_MAX {
76        let mut buf = Vec::with_capacity(HEADER_LEN + payload.len());
77        buf.extend_from_slice(&hdr);
78        buf.extend_from_slice(payload);
79        w.write_all(&buf)?;
80    } else {
81        w.write_all(&hdr)?;
82        w.write_all(payload)?;
83    }
84    w.flush()
85}
86
87fn read_frame<R: Read>(r: &mut R) -> io::Result<(u8, u32, u32, Vec<u8>)> {
88    let mut hdr = [0u8; HEADER_LEN];
89    r.read_exact(&mut hdr)?;
90    let kind = hdr[0];
91    let a = u32::from_le_bytes(hdr[1..5].try_into().unwrap());
92    let b = u32::from_le_bytes(hdr[5..9].try_into().unwrap());
93    let len = u32::from_le_bytes(hdr[9..13].try_into().unwrap()) as usize;
94    let mut payload = vec![0u8; len];
95    if len > 0 {
96        r.read_exact(&mut payload)?;
97    }
98    Ok((kind, a, b, payload))
99}
100
101fn connect_retry(addr: SocketAddr) -> io::Result<TcpStream> {
102    let mut last: Option<io::Error> = None;
103    for _ in 0..400 {
104        match TcpStream::connect(addr) {
105            Ok(s) => return Ok(s),
106            Err(e) => {
107                last = Some(e);
108                thread::sleep(Duration::from_millis(25));
109            }
110        }
111    }
112    Err(last.unwrap_or_else(|| io::Error::new(io::ErrorKind::TimedOut, "connect retry exhausted")))
113}
114
115struct NetInner {
116    rank: u32,
117    world: u32,
118    heap_size: usize,
119    /// Write half of the connection to each peer (index = peer rank).
120    /// `None` at our own rank and until a peer connects.
121    writers: Vec<Mutex<Option<TcpStream>>>,
122    /// Two-sided inbox: `(src_rank, tag) -> queued payloads`.
123    inbox: Mutex<HashMap<(u32, u32), VecDeque<Vec<u8>>>>,
124    inbox_cv: Condvar,
125    /// This rank's symmetric heap (target of remote PUT / GETREQ).
126    heap: RwLock<Vec<u8>>,
127    /// Responses to our one-sided GETs: `responder_rank -> payloads`.
128    getresp: Mutex<HashMap<u32, VecDeque<Vec<u8>>>>,
129    getresp_cv: Condvar,
130    shutdown: AtomicBool,
131}
132
133impl NetInner {
134    fn send_frame(
135        &self,
136        peer: u32,
137        kind: u8,
138        a: u32,
139        b: u32,
140        payload: &[u8],
141    ) -> Result<(), CollectiveError> {
142        let mut guard = self.writers[peer as usize].lock().unwrap();
143        match guard.as_mut() {
144            Some(s) => {
145                write_frame(s, kind, a, b, payload).map_err(|e| CollectiveError::TransportError {
146                    reason: format!("send to rank {peer}: {e}"),
147                })
148            }
149            None => Err(CollectiveError::TransportError {
150                reason: format!("no connection to rank {peer}"),
151            }),
152        }
153    }
154
155    fn recv_inbox(&self, from: u32, tag: u32) -> Vec<u8> {
156        let mut guard = self.inbox.lock().unwrap();
157        loop {
158            if let Some(q) = guard.get_mut(&(from, tag))
159                && let Some(v) = q.pop_front()
160            {
161                return v;
162            }
163            guard = self.inbox_cv.wait(guard).unwrap();
164        }
165    }
166
167    /// Like [`Self::recv_inbox`] but returns `None` if nothing matching
168    /// `(from, tag)` arrives before `timeout` elapses. Lets a collective
169    /// bound how long it waits on a slow/offline peer (bounded staleness).
170    fn recv_inbox_timeout(&self, from: u32, tag: u32, timeout: Duration) -> Option<Vec<u8>> {
171        let deadline = Instant::now() + timeout;
172        let mut guard = self.inbox.lock().unwrap();
173        loop {
174            if let Some(q) = guard.get_mut(&(from, tag))
175                && let Some(v) = q.pop_front()
176            {
177                return Some(v);
178            }
179            let now = Instant::now();
180            if now >= deadline {
181                return None;
182            }
183            let (g, res) = self.inbox_cv.wait_timeout(guard, deadline - now).unwrap();
184            guard = g;
185            if res.timed_out()
186                && let Some(q) = guard.get_mut(&(from, tag))
187                && let Some(v) = q.pop_front()
188            {
189                return Some(v);
190            }
191        }
192    }
193
194    fn wait_getresp(&self, peer: u32) -> Vec<u8> {
195        let mut guard = self.getresp.lock().unwrap();
196        loop {
197            if let Some(q) = guard.get_mut(&peer)
198                && let Some(v) = q.pop_front()
199            {
200                return v;
201            }
202            guard = self.getresp_cv.wait(guard).unwrap();
203        }
204    }
205
206    fn check_buf(&self, buf: SymmetricBuffer) -> Result<(), CollectiveError> {
207        if buf.rank.0 >= self.world {
208            return Err(CollectiveError::UnknownRank {
209                rank: buf.rank,
210                num_ranks: self.world,
211            });
212        }
213        if buf.offset + buf.len > self.heap_size {
214            return Err(CollectiveError::OutOfBounds {
215                rank: buf.rank,
216                offset: buf.offset,
217                len: buf.len,
218                heap_size: self.heap_size,
219            });
220        }
221        Ok(())
222    }
223
224    fn dispatch(&self, kind: u8, a: u32, b: u32, payload: Vec<u8>) {
225        match kind {
226            SEND => {
227                self.inbox
228                    .lock()
229                    .unwrap()
230                    .entry((a, b))
231                    .or_default()
232                    .push_back(payload);
233                self.inbox_cv.notify_all();
234            }
235            PUT => {
236                let off = b as usize;
237                if off + payload.len() <= self.heap_size {
238                    let mut h = self.heap.write().unwrap();
239                    h[off..off + payload.len()].copy_from_slice(&payload);
240                }
241            }
242            GETREQ if payload.len() >= 8 => {
243                let off = u32::from_le_bytes(payload[0..4].try_into().unwrap()) as usize;
244                let rlen = u32::from_le_bytes(payload[4..8].try_into().unwrap()) as usize;
245                let data = {
246                    let h = self.heap.read().unwrap();
247                    if off + rlen <= self.heap_size {
248                        h[off..off + rlen].to_vec()
249                    } else {
250                        Vec::new()
251                    }
252                };
253                // a = requester rank; respond with our rank as sender.
254                let _ = self.send_frame(a, GETRESP, self.rank, off as u32, &data);
255            }
256            GETRESP => {
257                self.getresp
258                    .lock()
259                    .unwrap()
260                    .entry(a)
261                    .or_default()
262                    .push_back(payload);
263                self.getresp_cv.notify_all();
264            }
265            _ => {}
266        }
267    }
268}
269
270fn reader_loop(mut stream: TcpStream, inner: Arc<NetInner>) {
271    // Exits when read_frame errors (peer closed or we were shut down).
272    while let Ok((kind, a, b, payload)) = read_frame(&mut stream) {
273        inner.dispatch(kind, a, b, payload);
274        if inner.shutdown.load(Ordering::Relaxed) {
275            break;
276        }
277    }
278}
279
280/// Full-mesh TCP transport implementing both [`Transport`] (two-sided
281/// send/recv) and [`SymmetricTransport`] (one-sided put/get/barrier).
282/// Construct via [`TcpTransport::bind`] or [`ThunderboltTransport::bind`].
283pub struct NetTransport {
284    inner: Arc<NetInner>,
285    readers: Mutex<Vec<JoinHandle<()>>>,
286}
287
288impl NetTransport {
289    /// Build a transport from an already-bound listener. Ranks above us
290    /// we connect to; ranks below us we accept from (each connector
291    /// announces itself with a `HELLO`). `peers[r]` is rank `r`'s listen
292    /// address; `peers[rank]` must match `listener`.
293    ///
294    /// Public so callers (and multi-rank tests) can pre-bind ephemeral
295    /// ports, exchange the addresses out-of-band, then construct the
296    /// mesh — avoiding the fixed-port races [`TcpTransport::bind`] is
297    /// subject to.
298    pub fn from_listener(
299        rank: u32,
300        world: u32,
301        listener: TcpListener,
302        peers: Vec<SocketAddr>,
303        heap_size: usize,
304    ) -> io::Result<Self> {
305        assert_eq!(
306            peers.len(),
307            world as usize,
308            "peers must have world_size entries"
309        );
310        assert!(rank < world, "rank out of range");
311
312        let inner = Arc::new(NetInner {
313            rank,
314            world,
315            heap_size,
316            writers: (0..world).map(|_| Mutex::new(None)).collect(),
317            inbox: Mutex::new(HashMap::new()),
318            inbox_cv: Condvar::new(),
319            heap: RwLock::new(vec![0u8; heap_size]),
320            getresp: Mutex::new(HashMap::new()),
321            getresp_cv: Condvar::new(),
322            shutdown: AtomicBool::new(false),
323        });
324        let mut readers = Vec::new();
325
326        // Connect to every higher-ranked peer.
327        for p in (rank + 1)..world {
328            let stream = connect_retry(peers[p as usize])?;
329            stream.set_nodelay(true).ok();
330            let mut wr = stream.try_clone()?;
331            write_frame(&mut wr, HELLO, rank, 0, &[])?;
332            let rd = stream.try_clone()?;
333            let inner2 = inner.clone();
334            readers.push(thread::spawn(move || reader_loop(rd, inner2)));
335            *inner.writers[p as usize].lock().unwrap() = Some(wr);
336        }
337
338        // Accept exactly `rank` connections from lower-ranked peers.
339        for _ in 0..rank {
340            let (stream, _addr) = listener.accept()?;
341            stream.set_nodelay(true).ok();
342            let mut rd = stream.try_clone()?;
343            let (kind, peer, _b, _payload) = read_frame(&mut rd)?;
344            if kind != HELLO || peer >= world {
345                return Err(io::Error::new(
346                    io::ErrorKind::InvalidData,
347                    format!("expected HELLO with valid rank, got kind={kind} rank={peer}"),
348                ));
349            }
350            let wr = stream.try_clone()?;
351            let inner2 = inner.clone();
352            readers.push(thread::spawn(move || reader_loop(rd, inner2)));
353            *inner.writers[peer as usize].lock().unwrap() = Some(wr);
354        }
355
356        Ok(Self {
357            inner,
358            readers: Mutex::new(readers),
359        })
360    }
361}
362
363impl NetTransport {
364    fn empty_inner(rank: u32, world: u32, heap_size: usize) -> Arc<NetInner> {
365        Arc::new(NetInner {
366            rank,
367            world,
368            heap_size,
369            writers: (0..world).map(|_| Mutex::new(None)).collect(),
370            inbox: Mutex::new(HashMap::new()),
371            inbox_cv: Condvar::new(),
372            heap: RwLock::new(vec![0u8; heap_size]),
373            getresp: Mutex::new(HashMap::new()),
374            getresp_cv: Condvar::new(),
375            shutdown: AtomicBool::new(false),
376        })
377    }
378
379    /// Coordinator side of a **star** topology: bind `listener` (rank 0's
380    /// address) and accept `world - 1` worker connections, each announcing its
381    /// rank with a `HELLO`. Workers connect only to the coordinator (no
382    /// worker↔worker links), so a worker needs **no inbound port** — the
383    /// NAT/mobile-friendly shape for iOS, phones, and Pis behind a router.
384    pub fn coordinator_listen(
385        world: u32,
386        listener: TcpListener,
387        heap_size: usize,
388    ) -> io::Result<Self> {
389        let inner = Self::empty_inner(0, world, heap_size);
390        let mut readers = Vec::new();
391        for _ in 1..world {
392            let (stream, _addr) = listener.accept()?;
393            stream.set_nodelay(true).ok();
394            let mut rd = stream.try_clone()?;
395            let (kind, peer, _b, _payload) = read_frame(&mut rd)?;
396            if kind != HELLO || peer == 0 || peer >= world {
397                return Err(io::Error::new(
398                    io::ErrorKind::InvalidData,
399                    format!("expected HELLO from a worker rank, got kind={kind} rank={peer}"),
400                ));
401            }
402            let wr = stream.try_clone()?;
403            let inner2 = inner.clone();
404            readers.push(thread::spawn(move || reader_loop(rd, inner2)));
405            *inner.writers[peer as usize].lock().unwrap() = Some(wr);
406        }
407        Ok(Self {
408            inner,
409            readers: Mutex::new(readers),
410        })
411    }
412
413    /// Worker side of a **star** topology: dial the coordinator at `coord_addr`
414    /// and announce this `rank`. No listener — the worker only needs outbound
415    /// connectivity, so it works from behind NAT / on mobile.
416    pub fn worker_dial(
417        rank: u32,
418        world: u32,
419        coord_addr: SocketAddr,
420        heap_size: usize,
421    ) -> io::Result<Self> {
422        assert!(rank > 0 && rank < world, "worker rank must be in 1..world");
423        let inner = Self::empty_inner(rank, world, heap_size);
424        let stream = connect_retry(coord_addr)?;
425        stream.set_nodelay(true).ok();
426        let mut wr = stream.try_clone()?;
427        write_frame(&mut wr, HELLO, rank, 0, &[])?;
428        let rd = stream.try_clone()?;
429        let inner2 = inner.clone();
430        let readers = vec![thread::spawn(move || reader_loop(rd, inner2))];
431        *inner.writers[0].lock().unwrap() = Some(wr);
432        Ok(Self {
433            inner,
434            readers: Mutex::new(readers),
435        })
436    }
437}
438
439impl Drop for NetTransport {
440    fn drop(&mut self) {
441        self.inner.shutdown.store(true, Ordering::SeqCst);
442        // Closing each socket makes the reader threads' read_exact fail.
443        for w in &self.inner.writers {
444            if let Some(s) = w.lock().unwrap().as_ref() {
445                let _ = s.shutdown(Shutdown::Both);
446            }
447        }
448        self.inner.inbox_cv.notify_all();
449        self.inner.getresp_cv.notify_all();
450        if let Ok(mut hs) = self.readers.lock() {
451            for h in hs.drain(..) {
452                let _ = h.join();
453            }
454        }
455    }
456}
457
458impl Transport for NetTransport {
459    fn rank(&self) -> u32 {
460        self.inner.rank
461    }
462    fn world_size(&self) -> u32 {
463        self.inner.world
464    }
465    fn send_bytes(&self, to: u32, tag: u32, bytes: &[u8]) -> Result<(), CollectiveError> {
466        if to == self.inner.rank {
467            // Self-message: deliver straight to our own inbox.
468            self.inner
469                .inbox
470                .lock()
471                .unwrap()
472                .entry((to, tag))
473                .or_default()
474                .push_back(bytes.to_vec());
475            self.inner.inbox_cv.notify_all();
476            return Ok(());
477        }
478        self.inner.send_frame(to, SEND, self.inner.rank, tag, bytes)
479    }
480    fn recv_bytes(&self, from: u32, tag: u32) -> Result<Vec<u8>, CollectiveError> {
481        Ok(self.inner.recv_inbox(from, tag))
482    }
483    fn recv_bytes_timeout(
484        &self,
485        from: u32,
486        tag: u32,
487        timeout: Duration,
488    ) -> Result<Option<Vec<u8>>, CollectiveError> {
489        Ok(self.inner.recv_inbox_timeout(from, tag, timeout))
490    }
491    fn barrier(&self) -> Result<(), CollectiveError> {
492        default_barrier(self)
493    }
494}
495
496impl SymmetricTransport for NetTransport {
497    fn num_ranks(&self) -> u32 {
498        self.inner.world
499    }
500    fn this_rank(&self) -> Rank {
501        Rank(self.inner.rank)
502    }
503    fn put(&self, buf: SymmetricBuffer, src: &[u8]) -> Result<(), CollectiveError> {
504        self.inner.check_buf(buf)?;
505        if src.len() != buf.len {
506            return Err(CollectiveError::LengthMismatch {
507                expected: buf.len,
508                got: src.len(),
509            });
510        }
511        if buf.rank.0 == self.inner.rank {
512            let mut h = self.inner.heap.write().unwrap();
513            h[buf.offset..buf.offset + buf.len].copy_from_slice(src);
514            Ok(())
515        } else {
516            self.inner
517                .send_frame(buf.rank.0, PUT, self.inner.rank, buf.offset as u32, src)
518        }
519    }
520    fn get(&self, buf: SymmetricBuffer, dst: &mut [u8]) -> Result<(), CollectiveError> {
521        self.inner.check_buf(buf)?;
522        if dst.len() != buf.len {
523            return Err(CollectiveError::LengthMismatch {
524                expected: buf.len,
525                got: dst.len(),
526            });
527        }
528        if buf.rank.0 == self.inner.rank {
529            let h = self.inner.heap.read().unwrap();
530            dst.copy_from_slice(&h[buf.offset..buf.offset + buf.len]);
531            Ok(())
532        } else {
533            let mut payload = [0u8; 8];
534            payload[0..4].copy_from_slice(&(buf.offset as u32).to_le_bytes());
535            payload[4..8].copy_from_slice(&(buf.len as u32).to_le_bytes());
536            self.inner
537                .send_frame(buf.rank.0, GETREQ, self.inner.rank, 0, &payload)?;
538            let data = self.inner.wait_getresp(buf.rank.0);
539            if data.len() != buf.len {
540                return Err(CollectiveError::LengthMismatch {
541                    expected: buf.len,
542                    got: data.len(),
543                });
544            }
545            dst.copy_from_slice(&data);
546            Ok(())
547        }
548    }
549    fn barrier(&self) -> Result<(), CollectiveError> {
550        default_barrier(self)
551    }
552}
553
554/// Default symmetric-heap size per rank (bytes) when a constructor is
555/// not given one. 64 MiB comfortably holds a single transformer hidden
556/// state at typical batch×seq×d_model for collective scratch.
557pub const DEFAULT_HEAP_BYTES: usize = 64 * 1024 * 1024;
558
559/// Portable TCP transport. Works over any reachable IP — Ethernet or
560/// the macOS Thunderbolt Bridge link. This is the baseline that
561/// pipeline- and tensor-parallel inference run on before any fast path.
562pub struct TcpTransport;
563
564impl TcpTransport {
565    /// Bind this rank's listener at `peers[rank]` and establish the full
566    /// mesh. Every rank must pass the same `peers` list.
567    pub fn bind(
568        rank: u32,
569        world: u32,
570        peers: Vec<SocketAddr>,
571        heap_size: usize,
572    ) -> io::Result<NetTransport> {
573        let listener = TcpListener::bind(peers[rank as usize])?;
574        NetTransport::from_listener(rank, world, listener, peers, heap_size)
575    }
576}
577
578/// Thunderbolt transport. Same wire protocol as [`TcpTransport`] today
579/// (TCP over the Thunderbolt Bridge IP link), but a distinct type so
580/// the symmetric one-sided heap path is the intended interface and so a
581/// future zero-copy Thunderbolt DMA backend can replace the engine
582/// behind the unchanged [`Transport`] + [`SymmetricTransport`] traits.
583pub struct ThunderboltTransport;
584
585impl ThunderboltTransport {
586    /// Bind at `peers[rank]`. The addresses are expected to be on the
587    /// Thunderbolt interface (see [`ThunderboltTransport::looks_like_thunderbolt`]);
588    /// correctness does not depend on it, but bandwidth does.
589    pub fn bind(
590        rank: u32,
591        world: u32,
592        peers: Vec<SocketAddr>,
593        heap_size: usize,
594    ) -> io::Result<NetTransport> {
595        TcpTransport::bind(rank, world, peers, heap_size)
596    }
597
598    /// Heuristic: is `ip` plausibly a Thunderbolt Bridge address? macOS
599    /// auto-assigns link-local `169.254.0.0/16` to the bridge when no
600    /// static IP is configured; static setups commonly use a private
601    /// `10.0.0.0/8` block. This is advisory only.
602    pub fn looks_like_thunderbolt(ip: IpAddr) -> bool {
603        match ip {
604            IpAddr::V4(v4) => {
605                let o = v4.octets();
606                o[0] == 169 && o[1] == 254 || o[0] == 10
607            }
608            IpAddr::V6(_) => false,
609        }
610    }
611}
612
613#[cfg(test)]
614mod tests {
615    use super::*;
616    use crate::collective::{ReduceKind, all_reduce};
617    use crate::transport::ProcessGroup;
618
619    /// Pre-bind `n` loopback listeners and run `body(rank, transport)`
620    /// on a thread per rank. Returns once every rank's body returns.
621    fn run_net<F>(n: u32, heap: usize, body: F)
622    where
623        F: Fn(u32, Arc<NetTransport>) + Send + Sync + 'static,
624    {
625        let listeners: Vec<TcpListener> = (0..n)
626            .map(|_| TcpListener::bind((std::net::Ipv4Addr::LOCALHOST, 0)).unwrap())
627            .collect();
628        let addrs: Vec<SocketAddr> = listeners.iter().map(|l| l.local_addr().unwrap()).collect();
629        let body = Arc::new(body);
630        let handles: Vec<_> = listeners
631            .into_iter()
632            .enumerate()
633            .map(|(rank, listener)| {
634                let addrs = addrs.clone();
635                let body = body.clone();
636                thread::spawn(move || {
637                    let t = Arc::new(
638                        NetTransport::from_listener(rank as u32, n, listener, addrs, heap)
639                            .expect("transport build"),
640                    );
641                    body(rank as u32, t.clone());
642                    // Trailing barrier: keep every rank's reader threads alive
643                    // until all peers finish, so a late remote GET still gets a
644                    // response before any transport is dropped.
645                    <NetTransport as Transport>::barrier(&*t).unwrap();
646                })
647            })
648            .collect();
649        for h in handles {
650            h.join().unwrap();
651        }
652    }
653
654    /// Star variant of [`run_net`]: rank 0 listens (coordinator) and ranks
655    /// 1..n dial in (workers, no inbound port of their own).
656    fn run_star<F>(n: u32, heap: usize, body: F)
657    where
658        F: Fn(u32, Arc<NetTransport>) + Send + Sync + 'static,
659    {
660        let listener = TcpListener::bind((std::net::Ipv4Addr::LOCALHOST, 0)).unwrap();
661        let coord_addr = listener.local_addr().unwrap();
662        let body = Arc::new(body);
663        let mut handles = Vec::new();
664        {
665            let body = body.clone();
666            handles.push(thread::spawn(move || {
667                let t = Arc::new(NetTransport::coordinator_listen(n, listener, heap).unwrap());
668                body(0, t.clone());
669                <NetTransport as Transport>::barrier(&*t).unwrap();
670            }));
671        }
672        for rank in 1..n {
673            let body = body.clone();
674            handles.push(thread::spawn(move || {
675                let t = Arc::new(NetTransport::worker_dial(rank, n, coord_addr, heap).unwrap());
676                body(rank, t.clone());
677                <NetTransport as Transport>::barrier(&*t).unwrap();
678            }));
679        }
680        for h in handles {
681            h.join().unwrap();
682        }
683    }
684
685    #[test]
686    fn star_dial_out_coordinator_worker() {
687        // Workers dial the coordinator (no listener of their own); the
688        // coordinator sums their contributions and sends the result back.
689        run_star(3, 4096, |rank, t| {
690            let g = ProcessGroup::new(t);
691            if rank == 0 {
692                let a = g.recv_f32(1, 5).unwrap();
693                let b = g.recv_f32(2, 5).unwrap();
694                let sum: Vec<f32> = a.iter().zip(&b).map(|(x, y)| x + y).collect();
695                g.send_f32(1, 6, &sum).unwrap();
696                g.send_f32(2, 6, &sum).unwrap();
697            } else {
698                g.send_f32(0, 5, &[rank as f32]).unwrap();
699                assert_eq!(g.recv_f32(0, 6).unwrap(), vec![3.0]); // 1 + 2
700            }
701        });
702    }
703
704    #[test]
705    fn two_sided_pipeline_handoff() {
706        // rank r sends [r*10..] to r-1 (toward the leader), like a
707        // pipeline returning hidden states.
708        run_net(3, 4096, |rank, t| {
709            let g = ProcessGroup::new(t);
710            let n = g.world_size();
711            if rank + 1 < n {
712                let got = g.recv_f32(rank + 1, 7).unwrap();
713                assert_eq!(got, vec![(rank as f32 + 1.0) * 10.0, 1.0]);
714            }
715            if rank > 0 {
716                g.send_f32(rank - 1, 7, &[rank as f32 * 10.0, 1.0]).unwrap();
717            }
718        });
719    }
720
721    #[test]
722    fn process_group_all_reduce_over_tcp() {
723        run_net(4, 4096, |rank, t| {
724            let g = ProcessGroup::new(t);
725            let mut data = vec![rank as f32 + 1.0; 5];
726            g.all_reduce(&mut data, ReduceKind::Sum).unwrap();
727            assert_eq!(data, vec![10.0; 5], "rank {rank}");
728        });
729    }
730
731    #[test]
732    fn federated_average_skips_late_worker() {
733        // rank 2 behaves like an offline/slow edge client — it only sends
734        // after the deadline, so rank 0 drops it and averages ranks 0 and 1.
735        run_net(3, 4096, |rank, t| {
736            let g = ProcessGroup::new(t);
737            let mut data = vec![rank as f32 + 1.0; 3]; // 1, 2, 3
738            if rank == 2 {
739                std::thread::sleep(Duration::from_millis(300));
740            }
741            let present = g
742                .federated_average(&mut data, Duration::from_millis(100))
743                .unwrap();
744            if rank == 0 {
745                assert_eq!(present, 2, "late rank 2 must be dropped");
746                assert_eq!(data, vec![1.5; 3]); // mean of ranks 0 and 1
747            }
748        });
749    }
750
751    #[test]
752    fn process_group_barrier_and_broadcast() {
753        run_net(3, 4096, |rank, t| {
754            let g = ProcessGroup::new(t);
755            g.barrier().unwrap();
756            let mut data = if rank == 0 {
757                vec![1.0, 2.0]
758            } else {
759                vec![0.0, 0.0]
760            };
761            g.broadcast(0, &mut data).unwrap();
762            assert_eq!(data, vec![1.0, 2.0], "rank {rank}");
763        });
764    }
765
766    #[test]
767    fn symmetric_remote_put_get() {
768        // Rank 0 PUTs into rank 1's heap; rank 1 reads its own slot and
769        // sees it. Then rank 1 GETs from rank 0's heap.
770        run_net(2, 4096, |rank, t| {
771            let off = 128;
772            let payload = [5u8, 6, 7, 8];
773            if rank == 0 {
774                // seed our own heap for rank 1 to GET later
775                t.put(
776                    SymmetricBuffer {
777                        rank: Rank(0),
778                        offset: off,
779                        len: 4,
780                    },
781                    &[1u8, 2, 3, 4],
782                )
783                .unwrap();
784                // write into rank 1's heap
785                t.put(
786                    SymmetricBuffer {
787                        rank: Rank(1),
788                        offset: off,
789                        len: 4,
790                    },
791                    &payload,
792                )
793                .unwrap();
794            }
795            // Make sure the PUT landed before the reads.
796            <NetTransport as Transport>::barrier(&t).unwrap();
797            // ^ disambiguates Transport::barrier from SymmetricTransport::barrier
798            if rank == 1 {
799                let mut got = [0u8; 4];
800                t.get(
801                    SymmetricBuffer {
802                        rank: Rank(1),
803                        offset: off,
804                        len: 4,
805                    },
806                    &mut got,
807                )
808                .unwrap();
809                assert_eq!(got, payload, "local read of remote PUT");
810                let mut remote = [0u8; 4];
811                t.get(
812                    SymmetricBuffer {
813                        rank: Rank(0),
814                        offset: off,
815                        len: 4,
816                    },
817                    &mut remote,
818                )
819                .unwrap();
820                assert_eq!(remote, [1u8, 2, 3, 4], "remote GET from rank 0");
821            }
822        });
823    }
824
825    #[test]
826    fn symmetric_collective_all_reduce_over_net() {
827        // The existing one-sided collective (collective::all_reduce)
828        // runs unmodified over NetTransport.
829        run_net(4, 4096, |rank, t| {
830            let elems = 3usize;
831            let bytes = elems * 4;
832            let buf = SymmetricBuffer {
833                rank: Rank(rank),
834                offset: 0,
835                len: bytes,
836            };
837            let mut local = vec![rank as f32 + 1.0; elems];
838            all_reduce(&*t, buf, &mut local, ReduceKind::Sum).unwrap();
839            assert_eq!(local, vec![10.0; elems], "rank {rank}");
840        });
841    }
842
843    #[test]
844    fn thunderbolt_addr_heuristic() {
845        use std::net::Ipv4Addr;
846        assert!(ThunderboltTransport::looks_like_thunderbolt(
847            Ipv4Addr::new(169, 254, 3, 1).into()
848        ));
849        assert!(ThunderboltTransport::looks_like_thunderbolt(
850            Ipv4Addr::new(10, 0, 0, 2).into()
851        ));
852        assert!(!ThunderboltTransport::looks_like_thunderbolt(
853            Ipv4Addr::new(192, 168, 1, 5).into()
854        ));
855    }
856}