1use std::fmt;
2
3use itertools::Itertools;
4use rand::distr::Uniform;
5use rand::Rng;
6use rustc_hash::FxHashMap;
7
8use crate::contractionpath::contraction_cost::communication_path_cost;
9use crate::contractionpath::paths::cotengrust::{Cotengrust, OptMethod};
10use crate::contractionpath::paths::weighted_branchbound::WeightedBranchBound;
11use crate::contractionpath::paths::{ContractionPathResult, CostType, Pathfinder};
12use crate::contractionpath::SimplePath;
13use crate::tensornetwork::partitioning::{communication_partitioning, PartitioningStrategy};
14use crate::tensornetwork::tensor::{CompositeTensor, LeafTensor};
15
16#[derive(Debug, Copy, Clone)]
19pub enum CommunicationScheme {
20 Greedy,
22 RandomGreedy,
24 Bipartition,
26 BipartitionSweep,
28 WeightedBranchBound,
30 BranchBound,
32}
33
34impl fmt::Display for CommunicationScheme {
35 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
36 let comm_str = match self {
37 Self::Greedy => "greedy",
38 Self::RandomGreedy => "random_greedy",
39 Self::Bipartition => "bipartition",
40 Self::BipartitionSweep => "bipartition_sweep",
41 Self::WeightedBranchBound => "weightedbranchbound",
42 Self::BranchBound => "branchbound",
43 };
44 f.write_str(comm_str)
45 }
46}
47
48impl CommunicationScheme {
49 pub(crate) fn communication_path<R>(
50 &self,
51 children_tensors: &[LeafTensor],
52 latency_map: &FxHashMap<usize, f64>,
53 rng: Option<&mut R>,
54 ) -> SimplePath
55 where
56 R: Rng,
57 {
58 match self {
59 Self::Greedy => greedy(children_tensors, latency_map),
60 Self::RandomGreedy => random_greedy(children_tensors),
61 Self::Bipartition => bipartition(children_tensors, latency_map),
62 Self::BipartitionSweep => {
63 let Some(rng) = rng else {
64 panic!("BipartitionSweep requires a random number generator")
65 };
66 bipartition_sweep(children_tensors, latency_map, rng)
67 }
68
69 Self::WeightedBranchBound => weighted_branchbound(children_tensors, latency_map),
70 Self::BranchBound => branchbound(children_tensors),
71 }
72 }
73}
74
75fn greedy(children_tensors: &[LeafTensor], _latency_map: &FxHashMap<usize, f64>) -> SimplePath {
76 let communication_tensors = CompositeTensor::new(children_tensors.to_vec());
77 let mut opt = Cotengrust::new(OptMethod::Greedy);
78 let result = opt.find_path(&communication_tensors);
79 result.replace_path().into_simple()
80}
81
82fn bipartition(
83 children_tensors: &[LeafTensor],
84 _latency_map: &FxHashMap<usize, f64>,
85) -> SimplePath {
86 let children_tensors = children_tensors.iter().cloned().enumerate().collect_vec();
87 let imbalance = 0.03;
88 tensor_bipartition(&children_tensors, imbalance)
89}
90
91fn bipartition_sweep<R>(
92 children_tensors: &[LeafTensor],
93 latency_map: &FxHashMap<usize, f64>,
94 rng: &mut R,
95) -> SimplePath
96where
97 R: Rng,
98{
99 let tensors = children_tensors.iter().cloned().enumerate().collect_vec();
100 let mut best_flops = f64::INFINITY;
101 let mut best_path = vec![];
102 let partition_latencies = latency_map
103 .iter()
104 .sorted_by_key(|(k, _)| **k)
105 .map(|(_, v)| *v)
106 .collect::<Vec<_>>();
107 for _ in 0..20 {
108 let imbalance = rng.sample(Uniform::new(0.01, 0.5).unwrap());
109 let path = tensor_bipartition(&tensors, imbalance);
110 let (flops, _) = communication_path_cost(
111 children_tensors,
112 &path,
113 true,
114 true,
115 Some(&partition_latencies),
116 );
117 if flops < best_flops {
118 best_flops = flops;
119 best_path = path;
120 }
121 }
122 best_path
123}
124
125fn weighted_branchbound(
126 children_tensors: &[LeafTensor],
127 latency_map: &FxHashMap<usize, f64>,
128) -> SimplePath {
129 let communication_tensors = CompositeTensor::new(children_tensors.to_vec());
130
131 let mut opt = WeightedBranchBound::new(Some(10), 5., latency_map.clone(), CostType::Flops);
132 let result = opt.find_path(&communication_tensors);
133 result.replace_path().into_simple()
134}
135
136fn branchbound(children_tensors: &[LeafTensor]) -> SimplePath {
137 let communication_tensors = CompositeTensor::new(children_tensors.to_vec());
138 let latency_map = (0..children_tensors.len()).map(|i| (i, 0.0)).collect();
139
140 let mut opt = WeightedBranchBound::new(Some(10), 5., latency_map, CostType::Flops);
141 let result = opt.find_path(&communication_tensors);
142 result.replace_path().into_simple()
143}
144
145fn tensor_bipartition_recursive(
148 children_tensor: &[(usize, LeafTensor)],
149 imbalance: f64,
150) -> (usize, LeafTensor, SimplePath) {
151 let k = 2;
152 let min = true;
153
154 if children_tensor.len() == 1 {
156 return (
157 children_tensor[0].0,
158 children_tensor[0].1.clone(),
159 Vec::new(),
160 );
161 }
162
163 if children_tensor.len() == 2 {
165 let (t1, t2) = if children_tensor[1].1.size() > children_tensor[0].1.size() {
167 (children_tensor[1].0, children_tensor[0].0)
168 } else {
169 (children_tensor[0].0, children_tensor[1].0)
170 };
171 let tensor = &children_tensor[0].1 ^ &children_tensor[1].1;
172
173 return (t1, tensor, vec![(t1, t2)]);
174 }
175
176 let partitioning = communication_partitioning(
177 children_tensor,
178 k,
179 imbalance,
180 PartitioningStrategy::MinCut,
181 min,
182 );
183
184 let mut partition_iter = partitioning.iter();
185 let (children_1, children_2): (Vec<_>, Vec<_>) = children_tensor
186 .iter()
187 .cloned()
188 .partition(|_| partition_iter.next() == Some(&0));
189
190 let (id_1, t1, mut contraction_1) = tensor_bipartition_recursive(&children_1, imbalance);
191
192 let (id_2, t2, mut contraction_2) = tensor_bipartition_recursive(&children_2, imbalance);
193
194 let tensor = &t1 ^ &t2;
195
196 contraction_1.append(&mut contraction_2);
197 let (id_1, id_2) = if t2.size() > t1.size() {
198 (id_2, id_1)
199 } else {
200 (id_1, id_2)
201 };
202
203 contraction_1.push((id_1, id_2));
204 (id_1, tensor, contraction_1)
205}
206
207fn tensor_bipartition(children_tensor: &[(usize, LeafTensor)], imbalance: f64) -> SimplePath {
210 let (_, _, contraction_path) = tensor_bipartition_recursive(children_tensor, imbalance);
211 contraction_path
212}
213
214fn random_greedy(children_tensors: &[LeafTensor]) -> SimplePath {
215 let communication_tensors = CompositeTensor::new(children_tensors.to_vec());
216
217 let mut opt = Cotengrust::new(OptMethod::RandomGreedy(100));
218 let result = opt.find_path(&communication_tensors);
219 result.replace_path().into_simple()
220}
221
222#[cfg(test)]
223mod tests {
224 use super::*;
225
226 use itertools::Itertools;
227 use rustc_hash::FxHashMap;
228
229 use crate::contractionpath::contraction_cost::communication_path_cost;
230
231 fn setup_simple_partition_data() -> FxHashMap<usize, f64> {
232 FxHashMap::from_iter([(0, 40.), (1, 30.), (2, 50.)])
233 }
234
235 fn setup_simple() -> Vec<LeafTensor> {
239 let bond_dims =
240 FxHashMap::from_iter([(0, 2), (1, 2), (2, 2), (3, 2), (4, 2), (5, 2), (6, 2)]);
241
242 let tensor0 = LeafTensor::new_from_map(vec![3, 4, 5], &bond_dims);
243 let tensor1 = LeafTensor::new_from_map(vec![0, 1, 3, 4], &bond_dims);
244 let tensor2 = LeafTensor::new_from_map(vec![0, 1, 2, 5, 6], &bond_dims);
245 vec![tensor0, tensor1, tensor2]
246 }
247
248 #[test]
249 fn test_greedy_communication() {
250 let tensors = setup_simple();
251 let latency_map = setup_simple_partition_data();
252 let communication_scheme = greedy(&tensors, &latency_map);
253
254 assert_eq!(&communication_scheme, &[(0, 1), (0, 2)]);
255 let tensor_costs = (0..tensors.len()).map(|i| latency_map[&i]).collect_vec();
256 let (flop_cost, mem_cost) = communication_path_cost(
257 &tensors,
258 &communication_scheme,
259 true,
260 true,
261 Some(&tensor_costs),
262 );
263 assert_eq!(flop_cost, 104.);
264 assert_eq!(mem_cost, 44.);
265 }
266
267 #[test]
268 fn test_weighted_communication() {
269 let tensors = setup_simple();
270 let latency_map = setup_simple_partition_data();
271
272 let communication_scheme = weighted_branchbound(&tensors, &latency_map);
273
274 assert_eq!(&communication_scheme, &[(1, 0), (2, 1)]);
275 let tensor_costs = (0..tensors.len()).map(|i| latency_map[&i]).collect_vec();
280 let (flop_cost, mem_cost) = communication_path_cost(
281 &tensors,
282 &communication_scheme,
283 true,
284 true,
285 Some(&tensor_costs),
286 );
287
288 assert_eq!(flop_cost, 104.);
289 assert_eq!(mem_cost, 44.);
290 }
291
292 #[test]
293 fn test_bi_partition_communication() {
294 let tensors = setup_simple();
295 let latency_map = setup_simple_partition_data();
296
297 let communication_scheme = bipartition(&tensors, &latency_map);
298
299 assert_eq!(&communication_scheme, &[(2, 1), (2, 0)]);
300
301 let tensor_costs = (0..tensors.len()).map(|i| latency_map[&i]).collect_vec();
306 let (flop_cost, mem_cost) = communication_path_cost(
307 &tensors,
308 &communication_scheme,
309 true,
310 true,
311 Some(&tensor_costs),
312 );
313
314 assert_eq!(flop_cost, 210.);
315 assert_eq!(mem_cost, 80.);
316 }
317}