1use crate::collective::ReduceKind;
44use crate::symmetric::CollectiveError;
45use std::sync::Arc;
46
47pub const TAG_RESERVED_BASE: u32 = 0xFFF0_0000;
51const TAG_BARRIER: u32 = TAG_RESERVED_BASE;
52const TAG_ALL_REDUCE: u32 = TAG_RESERVED_BASE + 1;
53const TAG_ALL_GATHER: u32 = TAG_RESERVED_BASE + 2;
54const TAG_BROADCAST: u32 = TAG_RESERVED_BASE + 3;
55
56pub trait Transport: Send + Sync {
65 fn rank(&self) -> u32;
67 fn world_size(&self) -> u32;
69
70 fn send_bytes(&self, to: u32, tag: u32, bytes: &[u8]) -> Result<(), CollectiveError>;
73
74 fn recv_bytes(&self, from: u32, tag: u32) -> Result<Vec<u8>, CollectiveError>;
77
78 fn barrier(&self) -> Result<(), CollectiveError> {
86 default_barrier(self)
87 }
88}
89
90pub fn default_barrier<T: Transport + ?Sized>(t: &T) -> Result<(), CollectiveError> {
94 let n = t.world_size();
95 if n <= 1 {
96 return Ok(());
97 }
98 let me = t.rank();
99 if me == 0 {
100 for r in 1..n {
101 t.recv_bytes(r, TAG_BARRIER)?;
102 }
103 for r in 1..n {
104 t.send_bytes(r, TAG_BARRIER, &[1u8])?;
105 }
106 } else {
107 t.send_bytes(0, TAG_BARRIER, &[1u8])?;
108 t.recv_bytes(0, TAG_BARRIER)?;
109 }
110 Ok(())
111}
112
113fn f32_to_le_bytes(data: &[f32]) -> Vec<u8> {
117 let mut out = Vec::with_capacity(data.len() * 4);
118 for &x in data {
119 out.extend_from_slice(&x.to_le_bytes());
120 }
121 out
122}
123
124fn le_bytes_to_f32(bytes: &[u8]) -> Result<Vec<f32>, CollectiveError> {
127 if !bytes.len().is_multiple_of(4) {
128 return Err(CollectiveError::TransportError {
129 reason: format!("recv payload {} bytes is not a multiple of 4", bytes.len()),
130 });
131 }
132 Ok(bytes
133 .chunks_exact(4)
134 .map(|c| f32::from_le_bytes([c[0], c[1], c[2], c[3]]))
135 .collect())
136}
137
138fn combine(op: ReduceKind, a: f32, b: f32) -> f32 {
139 match op {
140 ReduceKind::Sum | ReduceKind::Mean => a + b,
141 ReduceKind::Max => a.max(b),
142 ReduceKind::Min => a.min(b),
143 }
144}
145
146fn finalize(op: ReduceKind, acc: f32, n: u32) -> f32 {
147 match op {
148 ReduceKind::Mean => acc / (n as f32),
149 _ => acc,
150 }
151}
152
153#[derive(Clone)]
160pub struct ProcessGroup {
161 transport: Arc<dyn Transport>,
162}
163
164impl ProcessGroup {
165 pub fn new(transport: Arc<dyn Transport>) -> Self {
166 Self { transport }
167 }
168
169 pub fn rank(&self) -> u32 {
170 self.transport.rank()
171 }
172
173 pub fn world_size(&self) -> u32 {
174 self.transport.world_size()
175 }
176
177 pub fn is_leader(&self) -> bool {
180 self.rank() == 0
181 }
182
183 pub fn transport(&self) -> &Arc<dyn Transport> {
184 &self.transport
185 }
186
187 pub fn barrier(&self) -> Result<(), CollectiveError> {
188 self.transport.barrier()
189 }
190
191 pub fn send_f32(&self, to: u32, tag: u32, data: &[f32]) -> Result<(), CollectiveError> {
196 debug_assert!(tag < TAG_RESERVED_BASE, "tag collides with collective tags");
197 self.send_f32_tagged(to, tag, data)
198 }
199
200 pub fn recv_f32(&self, from: u32, tag: u32) -> Result<Vec<f32>, CollectiveError> {
202 debug_assert!(tag < TAG_RESERVED_BASE, "tag collides with collective tags");
203 self.recv_f32_tagged(from, tag)
204 }
205
206 fn send_f32_tagged(&self, to: u32, tag: u32, data: &[f32]) -> Result<(), CollectiveError> {
209 self.transport.send_bytes(to, tag, &f32_to_le_bytes(data))
210 }
211
212 fn recv_f32_tagged(&self, from: u32, tag: u32) -> Result<Vec<f32>, CollectiveError> {
213 le_bytes_to_f32(&self.transport.recv_bytes(from, tag)?)
214 }
215
216 pub fn all_reduce(&self, data: &mut [f32], op: ReduceKind) -> Result<(), CollectiveError> {
226 let n = self.world_size();
227 if n <= 1 {
228 for v in data.iter_mut() {
229 *v = finalize(op, *v, n.max(1));
230 }
231 return Ok(());
232 }
233 let elems = data.len();
234 if self.rank() == 0 {
235 let mut acc = data.to_vec();
236 for r in 1..n {
237 let other = self.recv_f32_tagged(r, TAG_ALL_REDUCE)?;
238 if other.len() != elems {
239 return Err(CollectiveError::LengthMismatch {
240 expected: elems,
241 got: other.len(),
242 });
243 }
244 for i in 0..elems {
245 acc[i] = combine(op, acc[i], other[i]);
246 }
247 }
248 for v in acc.iter_mut() {
249 *v = finalize(op, *v, n);
250 }
251 data.copy_from_slice(&acc);
252 for r in 1..n {
253 self.send_f32_tagged(r, TAG_ALL_REDUCE, &acc)?;
254 }
255 } else {
256 self.send_f32_tagged(0, TAG_ALL_REDUCE, data)?;
257 let res = self.recv_f32_tagged(0, TAG_ALL_REDUCE)?;
258 if res.len() != elems {
259 return Err(CollectiveError::LengthMismatch {
260 expected: elems,
261 got: res.len(),
262 });
263 }
264 data.copy_from_slice(&res);
265 }
266 Ok(())
267 }
268
269 pub fn all_gather(&self, local: &[f32]) -> Result<Vec<f32>, CollectiveError> {
273 let n = self.world_size();
274 let len = local.len();
275 if n <= 1 {
276 return Ok(local.to_vec());
277 }
278 if self.rank() == 0 {
279 let mut out = vec![0f32; n as usize * len];
280 out[..len].copy_from_slice(local);
281 for r in 1..n {
282 let chunk = self.recv_f32_tagged(r, TAG_ALL_GATHER)?;
283 if chunk.len() != len {
284 return Err(CollectiveError::LengthMismatch {
285 expected: len,
286 got: chunk.len(),
287 });
288 }
289 let start = r as usize * len;
290 out[start..start + len].copy_from_slice(&chunk);
291 }
292 for r in 1..n {
293 self.send_f32_tagged(r, TAG_ALL_GATHER, &out)?;
294 }
295 Ok(out)
296 } else {
297 self.send_f32_tagged(0, TAG_ALL_GATHER, local)?;
298 let out = self.recv_f32_tagged(0, TAG_ALL_GATHER)?;
299 if out.len() != n as usize * len {
300 return Err(CollectiveError::LengthMismatch {
301 expected: n as usize * len,
302 got: out.len(),
303 });
304 }
305 Ok(out)
306 }
307 }
308
309 pub fn broadcast(&self, root: u32, data: &mut [f32]) -> Result<(), CollectiveError> {
312 let n = self.world_size();
313 if n <= 1 {
314 return Ok(());
315 }
316 if self.rank() == root {
317 for r in 0..n {
318 if r != root {
319 self.send_f32_tagged(r, TAG_BROADCAST, data)?;
320 }
321 }
322 } else {
323 let res = self.recv_f32_tagged(root, TAG_BROADCAST)?;
324 if res.len() != data.len() {
325 return Err(CollectiveError::LengthMismatch {
326 expected: data.len(),
327 got: res.len(),
328 });
329 }
330 data.copy_from_slice(&res);
331 }
332 Ok(())
333 }
334}
335
336#[cfg(test)]
337mod tests {
338 use super::*;
339 use std::collections::{HashMap, VecDeque};
340 use std::sync::{Condvar, Mutex};
341
342 struct ChannelTransport {
346 rank: u32,
347 world: u32,
348 mailbox: Arc<(Mutex<HashMap<(u32, u32, u32), VecDeque<Vec<u8>>>>, Condvar)>,
349 }
350
351 impl ChannelTransport {
352 fn fan_out(world: u32) -> Vec<Arc<ChannelTransport>> {
353 let mailbox = Arc::new((Mutex::new(HashMap::new()), Condvar::new()));
354 (0..world)
355 .map(|r| {
356 Arc::new(ChannelTransport {
357 rank: r,
358 world,
359 mailbox: mailbox.clone(),
360 })
361 })
362 .collect()
363 }
364 }
365
366 impl Transport for ChannelTransport {
367 fn rank(&self) -> u32 {
368 self.rank
369 }
370 fn world_size(&self) -> u32 {
371 self.world
372 }
373 fn send_bytes(&self, to: u32, tag: u32, bytes: &[u8]) -> Result<(), CollectiveError> {
374 let (m, cv) = &*self.mailbox;
375 m.lock()
376 .unwrap()
377 .entry((to, self.rank, tag))
378 .or_default()
379 .push_back(bytes.to_vec());
380 cv.notify_all();
381 Ok(())
382 }
383 fn recv_bytes(&self, from: u32, tag: u32) -> Result<Vec<u8>, CollectiveError> {
384 let (m, cv) = &*self.mailbox;
385 let mut guard = m.lock().unwrap();
386 loop {
387 if let Some(q) = guard.get_mut(&(self.rank, from, tag))
388 && let Some(v) = q.pop_front()
389 {
390 return Ok(v);
391 }
392 guard = cv.wait(guard).unwrap();
393 }
394 }
395 }
396
397 fn run_ranks<F>(world: u32, body: F) -> Vec<()>
398 where
399 F: Fn(ProcessGroup) + Send + Sync + 'static,
400 {
401 let ts = ChannelTransport::fan_out(world);
402 let body = Arc::new(body);
403 let handles: Vec<_> = ts
404 .into_iter()
405 .map(|t| {
406 let body = body.clone();
407 std::thread::spawn(move || body(ProcessGroup::new(t)))
408 })
409 .collect();
410 handles.into_iter().map(|h| h.join().unwrap()).collect()
411 }
412
413 #[test]
414 fn all_reduce_sum_matches_serial() {
415 run_ranks(4, |g| {
416 let r = g.rank() as f32;
417 let mut data = vec![r + 1.0; 3]; g.all_reduce(&mut data, ReduceKind::Sum).unwrap();
419 assert_eq!(data, vec![10.0; 3], "rank {}", g.rank());
420 });
421 }
422
423 #[test]
424 fn all_gather_concatenates_in_rank_order() {
425 run_ranks(3, |g| {
426 let r = g.rank() as f32;
427 let out = g.all_gather(&[10.0 * r, 10.0 * r + 1.0]).unwrap();
428 assert_eq!(
429 out,
430 vec![0.0, 1.0, 10.0, 11.0, 20.0, 21.0],
431 "rank {}",
432 g.rank()
433 );
434 });
435 }
436
437 #[test]
438 fn broadcast_from_root_overwrites() {
439 run_ranks(4, |g| {
440 let mut data = if g.is_leader() {
441 vec![7.0, 8.0, 9.0]
442 } else {
443 vec![0.0, 0.0, 0.0]
444 };
445 g.broadcast(0, &mut data).unwrap();
446 assert_eq!(data, vec![7.0, 8.0, 9.0], "rank {}", g.rank());
447 });
448 }
449
450 #[test]
451 fn barrier_round_trips() {
452 run_ranks(4, |g| {
453 g.barrier().unwrap();
454 g.barrier().unwrap(); });
456 }
457
458 #[test]
459 fn point_to_point_ring_handoff() {
460 run_ranks(3, |g| {
463 let n = g.world_size();
464 let me = g.rank();
465 let next = (me + 1) % n;
466 let prev = (me + n - 1) % n;
467 if me % 2 == 0 {
470 g.send_f32(next, 1, &[me as f32]).unwrap();
471 let got = g.recv_f32(prev, 1).unwrap();
472 assert_eq!(got, vec![prev as f32]);
473 } else {
474 let got = g.recv_f32(prev, 1).unwrap();
475 assert_eq!(got, vec![prev as f32]);
476 g.send_f32(next, 1, &[me as f32]).unwrap();
477 }
478 });
479 }
480}