ruccl/in_process/collective/
all_to_all.rs1use super::*;
2
3impl<T, D, E> InProcessCollective<'_, T, D, E>
4where
5 T: Copy + Send + Sync + 'static,
6 D: InProcessDevice<T>,
7 E: From<D::Error> + From<InProcessError>,
8{
9 pub fn all_to_all(
12 &self,
13 input: &DistributedBuffer<T, D>,
14 ) -> Result<(DistributedBuffer<T, D>, CollectiveStats), E> {
15 self.validate_buffer(input)?;
16 if !input.length_per_rank.is_multiple_of(self.world_size()) {
17 return Err(InProcessError::InvalidLength(
18 "all-to-all rank length must be divisible by world size",
19 )
20 .into());
21 }
22 let shard_length = input.length_per_rank / self.world_size();
23 let output = self.allocate(input.length_per_rank)?;
24 for source in 0..self.world_size() {
25 for destination in 0..self.world_size() {
26 let values = <D as InProcessDevice<T>>::copy_from_device_at(
27 &self.contexts[source],
28 &input.buffers[source],
29 destination * shard_length,
30 shard_length,
31 )?;
32 <D as InProcessDevice<T>>::copy_to_device_at(
33 &self.contexts[destination],
34 &output.buffers[destination],
35 source * shard_length,
36 &values,
37 )?;
38 }
39 }
40 let mut stats = CollectiveStats::new(CollectiveAlgorithm::Direct);
41 stats.steps = self.world_size().saturating_sub(1) as u32;
42 stats.transferred_bytes = transferred_bytes(
43 self.world_size()
44 .saturating_mul(self.world_size().saturating_sub(1)),
45 shard_length,
46 D::ELEMENT_SIZE,
47 )?;
48 Ok((output, stats))
49 }
50
51 pub fn all_to_all_v(
52 &self,
53 input: &DistributedBuffer<T, D>,
54 send_counts: &[Vec<usize>],
55 ) -> Result<(VariableDistributedBuffer<T, D>, CollectiveStats), E> {
56 self.validate_buffer(input)?;
57 if send_counts.len() != self.world_size()
58 || send_counts
59 .iter()
60 .any(|counts| counts.len() != self.world_size())
61 {
62 return Err(InProcessError::InvalidLength(
63 "all-to-all-v send_counts must be a world_size square matrix",
64 )
65 .into());
66 }
67 for counts in send_counts {
68 let total = counts
69 .iter()
70 .try_fold(0_usize, |sum, count| sum.checked_add(*count))
71 .ok_or(InProcessError::Overflow("all-to-all-v send count"))?;
72 if total != input.length_per_rank {
73 return Err(InProcessError::InvalidLength(
74 "each all-to-all-v send-count row must sum to the input rank length",
75 )
76 .into());
77 }
78 }
79 let receive_lengths = (0..self.world_size())
80 .map(|destination| {
81 send_counts.iter().try_fold(0_usize, |total, counts| {
82 total.checked_add(counts[destination])
83 })
84 })
85 .collect::<Option<Vec<_>>>()
86 .ok_or(InProcessError::Overflow("all-to-all-v receive count"))?;
87 let output = self.allocate_variable(&receive_lengths)?;
88 let mut receive_offsets = vec![0_usize; self.world_size()];
89 for (source, counts) in send_counts.iter().enumerate() {
90 let values = self.download_rank(input, source)?;
91 let mut send_offset = 0_usize;
92 for destination in 0..self.world_size() {
93 let count = counts[destination];
94 let send_end = send_offset
95 .checked_add(count)
96 .ok_or(InProcessError::Overflow("all-to-all-v source range"))?;
97 if count != 0 {
98 let destination_buffer = output.buffers[destination]
99 .as_ref()
100 .expect("non-empty all-to-all-v receive has a device buffer");
101 <D as InProcessDevice<T>>::copy_to_device_at(
102 &self.contexts[destination],
103 destination_buffer,
104 receive_offsets[destination],
105 &values[send_offset..send_end],
106 )?;
107 }
108 receive_offsets[destination] = receive_offsets[destination]
109 .checked_add(count)
110 .ok_or(InProcessError::Overflow("all-to-all-v destination range"))?;
111 send_offset = send_end;
112 }
113 }
114 let remote_elements = send_counts
115 .iter()
116 .enumerate()
117 .try_fold(0_usize, |total, (source, counts)| {
118 counts
119 .iter()
120 .enumerate()
121 .try_fold(total, |total, (destination, count)| {
122 if source == destination {
123 Some(total)
124 } else {
125 total.checked_add(*count)
126 }
127 })
128 })
129 .ok_or(InProcessError::Overflow(
130 "all-to-all-v transferred elements",
131 ))?;
132 let mut stats = CollectiveStats::new(CollectiveAlgorithm::Direct);
133 stats.steps = self.world_size().saturating_sub(1) as u32;
134 stats.transferred_bytes = transferred_bytes(1, remote_elements, D::ELEMENT_SIZE)?;
135 Ok((output, stats))
136 }
137}