1use std::fmt;
2use std::fmt::Debug;
3use tract_core::internal::num_integer::Integer;
4use tract_core::internal::*;
5
6use crate::fact::DeviceTypedFactExt;
7use crate::sync::{DeviceSync, DeviceSyncKind};
8
9#[derive(Debug, Clone, PartialEq, Eq, Hash)]
11pub struct NodeMemReq {
12 pub outlet_id: OutletId,
13 pub lifetime: Lifetime,
14 pub mem_size: TDim,
15}
16
17#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
18pub struct Lifetime {
19 pub start: usize,
20 pub end: usize,
21}
22
23impl Lifetime {
24 pub fn is_disjoint(&self, other: &Lifetime) -> bool {
25 self.start >= other.end || other.start >= self.end
26 }
27
28 pub fn is_alive_at_step(&self, step: usize) -> bool {
29 self.start <= step && step < self.end
30 }
31
32 pub fn is_empty(&self) -> bool {
33 self.len() == 0
34 }
35
36 pub fn len(&self) -> usize {
37 self.end - self.start
38 }
39}
40
41fn next_nodes<'a>(model: &'a TypedModel, node: &TypedNode) -> Option<TVec<&'a TypedNode>> {
42 if node.outputs.is_empty() {
43 return None;
44 };
45
46 Some(
47 node.outputs
48 .iter()
49 .flat_map(|o| {
50 o.successors.iter().map(|succ| &model.nodes()[succ.node]).collect::<Vec<_>>()
51 })
52 .collect(),
53 )
54}
55
56pub fn eval_device_mem_req_for_nodes(
57 model: &TypedModel,
58 order: &[usize],
59) -> TractResult<TVec<NodeMemReq>> {
60 let outputs = model.output_outlets()?.to_vec();
61 let flush_lists = order::build_flush_list(model, order, &outputs, |node| {
62 let Ok(facts) = model.node_output_facts(node.id) else { return false };
63
64 let cpu_sync_in_next_nodes = next_nodes(model, node).is_some_and(|nodes| {
65 nodes.iter().any(|it| {
66 it.op_as::<DeviceSync>().is_some_and(|op| op.kind == DeviceSyncKind::ToHost)
67 || it.outputs.iter().any(|o| o.fact.as_device_fact().is_none())
68 })
69 });
70
71 !cpu_sync_in_next_nodes
72 && facts.iter().any(|it| {
73 it.as_device_fact()
74 .map(|it| it.is_from_device() && !it.is_state_owned())
75 .unwrap_or(false)
76 })
77 });
78 let mut scoped_nodes = tvec![];
79
80 for (step, n) in order.iter().enumerate() {
81 let lifetime_start = step;
82
83 let lifetime_end = flush_lists
84 .iter()
85 .enumerate()
86 .find(|(_step, flush_list)| flush_list.contains(n))
87 .map(|it| usize::min(it.0 + 1, order.len()));
88 let Some(lifetime_end) = lifetime_end else {
90 continue;
91 };
92
93 let out_device_tmp_facts = model
94 .node_output_facts(*n)?
95 .into_iter()
96 .flat_map(|it| it.as_device_fact())
97 .filter(|it| it.is_from_device())
98 .collect::<TVec<_>>();
99
100 if out_device_tmp_facts.is_empty() {
101 continue;
102 }
103
104 for (slot, fact) in out_device_tmp_facts.iter().enumerate() {
105 let outlet_id = OutletId { node: *n, slot };
106 for buff_size in fact.buffer_sizes() {
107 scoped_nodes.push(NodeMemReq {
108 outlet_id,
109 lifetime: Lifetime { start: lifetime_start, end: lifetime_end },
110 mem_size: buff_size,
111 })
112 }
113 }
114 }
115
116 for n in order.iter().rev() {
119 let Some(ix) = model.node(*n).op.forwards_input() else { continue };
120 let Some(end) = scoped_nodes
121 .iter()
122 .filter(|req| req.outlet_id.node == *n)
123 .map(|req| req.lifetime.end)
124 .max()
125 else {
126 continue;
127 };
128 let src = model.node(*n).inputs[ix];
129 for req in scoped_nodes.iter_mut().filter(|req| req.outlet_id == src) {
130 req.lifetime.end = req.lifetime.end.max(end);
131 }
132 }
133
134 Ok(scoped_nodes)
135}
136
137fn collect_exotic_facts(model: &TypedModel) -> TractResult<Vec<NodeExoticFacts>> {
138 let mut res: Vec<TVec<Option<Box<dyn ExoticFact>>>> = vec![];
139 for node in model.nodes() {
140 let mut tmp: TVec<Option<Box<dyn ExoticFact>>> = tvec![];
141 for fact in model.node_output_facts(node.id)? {
142 if let Some(dev_fact) = fact.as_device_fact() {
143 tmp.push(dev_fact.exotic_fact.clone());
144 }
145 }
146 res.push(tmp);
147 }
148 Ok(res)
149}
150
151#[derive(Debug, Clone, PartialEq, Eq, Hash)]
154pub struct Partition {
155 pub nodes: Vec<NodeMemReq>,
156}
157
158impl Partition {
159 pub fn eval_size_to_i64(&self, symbols: &SymbolValues) -> TractResult<i64> {
160 let mut max_size = self
161 .nodes
162 .iter()
163 .map(|it| it.mem_size.eval_to_i64(symbols))
164 .collect::<TractResult<Vec<_>>>()?
165 .into_iter()
166 .max()
167 .unwrap_or(0);
168 max_size = Integer::next_multiple_of(&max_size, &(vector_size() as i64));
169 Ok(max_size)
170 }
171
172 pub fn size(&self) -> TDim {
173 TDim::Max(self.nodes.iter().map(|s| s.mem_size.clone()).collect()).simplify()
174 }
175
176 pub fn has_no_conflict_with_lifetime(&self, lifetime: &Lifetime) -> bool {
177 self.nodes.iter().all(|n| n.lifetime.is_disjoint(lifetime))
178 }
179
180 pub fn find_node_alive_at_step(&self, step: usize) -> Option<&NodeMemReq> {
181 self.nodes.iter().find(|it| it.lifetime.is_alive_at_step(step))
182 }
183}
184
185type NodeExoticFacts = TVec<Option<Box<dyn ExoticFact>>>;
186#[derive(Debug, Clone, PartialEq, Eq)]
189pub struct DeviceResolvedMemSchema {
190 pub offsets_by_node: Vec<Option<TVec<TVec<usize>>>>,
191 pub memory_size: usize,
192}
193
194#[derive(Debug, Clone, PartialEq, Eq, Hash)]
197pub struct DeviceMemSchema {
198 pub model_num_nodes: usize,
200 pub by_partition: Vec<Partition>,
201 pub by_steps: Vec<Vec<Option<NodeMemReq>>>,
203 pub exotic_facts: Vec<NodeExoticFacts>,
204}
205
206impl DeviceMemSchema {
207 pub fn size_by_partition(&self) -> Vec<TDim> {
209 self.by_partition.iter().map(|it| it.size()).collect()
210 }
211
212 pub fn eval_size_by_partition(&self, symbols: &SymbolValues) -> TractResult<Vec<i64>> {
214 self.by_partition.iter().map(|it| it.eval_size_to_i64(symbols)).collect()
215 }
216
217 pub fn memory_size(&self) -> TDim {
219 self.by_partition.iter().map(|it| it.size()).sum()
220 }
221
222 pub fn eval_memory_size(&self, symbols: &SymbolValues) -> TractResult<i64> {
224 self.by_partition.iter().map(|it| it.eval_size_to_i64(symbols)).sum()
225 }
226
227 pub fn compute_offset_by_node(
230 &self,
231 symbols: &SymbolValues,
232 ) -> TractResult<Vec<Option<TVec<TVec<usize>>>>> {
233 let mut cursor = 0;
234 let mut offset_by_outlet: Vec<Option<TVec<TVec<usize>>>> = vec![None; self.model_num_nodes];
235
236 for partition in &self.by_partition {
237 for node_mem in &partition.nodes {
238 let node = node_mem.outlet_id.node;
239 let slot = node_mem.outlet_id.slot;
240
241 let slots: &mut TVec<TVec<usize>> =
242 offset_by_outlet[node].get_or_insert_with(|| tvec![tvec!()]);
243
244 if slot < 1 {
245 slots[slot].push(cursor);
246 } else {
247 if slots.len() <= slot {
248 slots.resize_with(slot + 1, TVec::<usize>::new);
249 }
250 slots[slot].push(cursor);
251 }
252 }
253 cursor += partition.eval_size_to_i64(symbols)? as usize;
254 }
255
256 Ok(offset_by_outlet)
257 }
258
259 pub fn peak_memory_terms(&self) -> Vec<TDim> {
267 self.by_steps
268 .iter()
269 .map(|active_nodes| active_nodes.iter().flatten().map(|it| it.mem_size.clone()).sum())
270 .collect()
271 }
272
273 pub fn eval_peak_memory_size(&self, symbols: &SymbolValues) -> TractResult<i64> {
274 Ok(self
275 .peak_memory_terms()
276 .iter()
277 .map(|term| term.eval_to_i64(symbols))
278 .collect::<TractResult<Vec<_>>>()?
279 .into_iter()
280 .max()
281 .unwrap_or(0))
282 }
283
284 pub fn eval_usage(&self, symbols: &SymbolValues) -> TractResult<f32> {
288 let memory_size = self.eval_memory_size(symbols)? as f32;
289 let peak_memory_size = self.eval_peak_memory_size(symbols)? as f32;
290 Ok(peak_memory_size / memory_size)
291 }
292}
293
294impl fmt::Display for DeviceMemSchema {
295 fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
296 for (step, mem_step) in self.by_steps.iter().enumerate() {
297 writeln!(
298 fmt,
299 "step: {:5} => |{}|",
300 step,
301 mem_step
302 .iter()
303 .map(|n| -> String {
304 n.as_ref()
305 .map(|it| format!("{:^7}/{:^7}", it.outlet_id.node, it.outlet_id.slot))
306 .unwrap_or(format!("{:^7}", "*"))
307 })
308 .collect::<Vec<String>>()
309 .join("|")
310 )?;
311 }
312 writeln!(fmt, "memory_size: {}", self.memory_size())?;
313 Ok(())
314 }
315}
316
317impl DeviceMemSchema {
318 pub fn resolve(&self, symbols: &SymbolValues) -> TractResult<DeviceResolvedMemSchema> {
320 Ok(DeviceResolvedMemSchema {
321 offsets_by_node: self.compute_offset_by_node(symbols)?,
322 memory_size: self.eval_memory_size(symbols)?.try_into()?,
323 })
324 }
325
326 pub fn build(
330 model: &TypedModel,
331 order: &[usize],
332 hint: &SymbolValues,
333 ) -> TractResult<DeviceMemSchema> {
334 let mut nodes_mem_req = eval_device_mem_req_for_nodes(model, order)?;
335
336 let exotic_facts = collect_exotic_facts(model)?;
337 let hinted_mem_size = nodes_mem_req
338 .iter()
339 .map(|node_mem| Ok((node_mem.outlet_id, node_mem.mem_size.eval_to_i64(hint)?)))
340 .collect::<TractResult<HashMap<OutletId, i64>>>()?;
341
342 nodes_mem_req.sort_by(|lhs, rhs| {
343 let lhs_hint_mem_size = hinted_mem_size.get(&lhs.outlet_id);
344 let rhs_hint_mem_size = hinted_mem_size.get(&rhs.outlet_id);
345 lhs_hint_mem_size.cmp(&rhs_hint_mem_size).reverse()
346 });
347
348 let mut partitions: Vec<Partition> = vec![];
349 for node_mem in nodes_mem_req {
350 let mut available = partitions
352 .iter_mut()
353 .filter(|it| it.has_no_conflict_with_lifetime(&node_mem.lifetime))
354 .collect::<Vec<_>>();
355
356 available.sort_by_cached_key(|n| {
357 -n.nodes.iter().flat_map(|it| hinted_mem_size.get(&it.outlet_id)).sum::<i64>()
358 });
359
360 match available.first_mut() {
361 Some(available) => {
362 available.nodes.push(node_mem);
363 }
364 None => partitions.push(Partition { nodes: vec![node_mem] }),
365 }
366 }
367
368 let by_steps: Vec<Vec<Option<NodeMemReq>>> = (0..order.len())
369 .map(|step| {
370 let mem_step: Vec<_> =
371 partitions.iter().map(|p| p.find_node_alive_at_step(step).cloned()).collect();
372 ensure!(mem_step.len() <= partitions.len());
373 Ok(mem_step)
374 })
375 .collect::<TractResult<Vec<_>>>()?;
376
377 Ok(DeviceMemSchema {
378 model_num_nodes: model.nodes().len(),
379 by_partition: partitions,
380 by_steps,
381 exotic_facts,
382 })
383 }
384}
385
386#[cfg(test)]
387mod tests {
388 use super::*;
389
390 #[test]
391 fn test_lifetime_is_disjoint() {
392 let l1 = Lifetime { start: 0, end: 5 };
393 let l2 = Lifetime { start: 5, end: 10 };
394 let l3 = Lifetime { start: 3, end: 7 };
395
396 assert!(l1.is_disjoint(&l2));
397 assert!(l2.is_disjoint(&l1));
398 assert!(!l1.is_disjoint(&l3));
399 assert!(!l3.is_disjoint(&l2));
400 }
401
402 #[test]
403 fn test_lifetime_is_alive_at_step() {
404 let lifetime = Lifetime { start: 5, end: 10 };
405
406 assert!(!lifetime.is_alive_at_step(4));
407 assert!(lifetime.is_alive_at_step(5));
408 assert!(lifetime.is_alive_at_step(7));
409 assert!(lifetime.is_alive_at_step(9));
410 assert!(!lifetime.is_alive_at_step(10));
411 }
412
413 #[test]
414 fn test_empty_lifetime() {
415 let lifetime = Lifetime { start: 5, end: 5 };
416 assert!(lifetime.is_empty());
417 assert_eq!(lifetime.len(), 0);
418 }
419
420 #[test]
421 fn test_node_mem_req_basic() {
422 let outlet_id = OutletId { node: 1, slot: 0 };
423 let req = NodeMemReq {
424 outlet_id,
425 lifetime: Lifetime { start: 0, end: 5 },
426 mem_size: 1000.into(),
427 };
428
429 assert_eq!(req.outlet_id.node, 1);
430 assert_eq!(req.lifetime.start, 0);
431 assert_eq!(req.lifetime.end, 5);
432 assert_eq!(req.mem_size.to_i64().unwrap(), 1000);
433 }
434
435 #[test]
436 fn test_partition_has_no_conflict() {
437 let outlet_id = OutletId { node: 1, slot: 0 };
438 let node1 = NodeMemReq {
439 outlet_id,
440 lifetime: Lifetime { start: 0, end: 5 },
441 mem_size: 1000.into(),
442 };
443
444 let partition = Partition { nodes: vec![node1] };
445
446 assert!(partition.has_no_conflict_with_lifetime(&Lifetime { start: 5, end: 10 }));
447 assert!(!partition.has_no_conflict_with_lifetime(&Lifetime { start: 3, end: 7 }));
448 }
449
450 #[test]
451 fn test_partition_find_node() {
452 let outlet_id = OutletId { node: 1, slot: 0 };
453 let node1 = NodeMemReq {
454 outlet_id,
455 lifetime: Lifetime { start: 0, end: 5 },
456 mem_size: 1000.into(),
457 };
458
459 let outlet_id = OutletId { node: 2, slot: 0 };
460 let node2 = NodeMemReq {
461 outlet_id,
462 lifetime: Lifetime { start: 5, end: 10 },
463 mem_size: 2000.into(),
464 };
465
466 let partition = Partition { nodes: vec![node1.clone(), node2.clone()] };
467
468 assert_eq!(partition.find_node_alive_at_step(3), Some(&node1));
469 assert_eq!(partition.find_node_alive_at_step(7), Some(&node2));
470 assert_eq!(partition.find_node_alive_at_step(10), None);
471 }
472}