1use std::fmt;
5
6use nuvec::tree::{LeafKind, Leaves, Visit, Visitor};
7use nuvec::{Array, DType};
8
9#[derive(Debug, Clone, PartialEq, Eq)]
11pub struct SummaryRow {
12 pub path: String,
14 pub shape: Vec<i32>,
16 pub dtype: DType,
18 pub kind: LeafKind,
20 pub trainable: bool,
22 pub bytes: usize,
24}
25
26#[derive(Debug, Clone, PartialEq, Eq)]
29pub struct Summary {
30 pub rows: Vec<SummaryRow>,
32}
33
34impl Summary {
35 pub(crate) fn of<T: Leaves + ?Sized>(tree: &T) -> Self {
36 struct Rows(Vec<SummaryRow>);
37 impl Visitor for Rows {
38 fn leaf(&mut self, path: &str, a: &Array, kind: LeafKind, trainable: bool) {
39 self.0.push(SummaryRow {
40 path: path.to_owned(),
41 shape: a.shape().to_vec(),
42 dtype: a.dtype(),
43 kind,
44 trainable,
45 bytes: a.nbytes(),
46 });
47 }
48 }
49 let mut rows = Rows(Vec::new());
50 tree.visit(&mut Visit::new(&mut rows));
51 Self { rows: rows.0 }
52 }
53
54 pub fn num_params(&self) -> usize {
56 self.rows.iter().map(|r| elements(&r.shape)).sum()
57 }
58
59 pub fn num_trainable(&self) -> usize {
61 self.rows
62 .iter()
63 .filter(|r| r.trainable)
64 .map(|r| elements(&r.shape))
65 .sum()
66 }
67
68 pub fn bytes(&self) -> usize {
70 self.rows.iter().map(|r| r.bytes).sum()
71 }
72}
73
74fn elements(shape: &[i32]) -> usize {
75 shape.iter().map(|&d| d.max(0) as usize).product()
76}
77
78pub(crate) fn grouped(n: usize) -> String {
80 let digits = n.to_string();
81 let mut out = String::with_capacity(digits.len() + digits.len() / 3);
82 for (i, c) in digits.chars().enumerate() {
83 if i > 0 && (digits.len() - i).is_multiple_of(3) {
84 out.push(',');
85 }
86 out.push(c);
87 }
88 out
89}
90
91impl fmt::Display for Summary {
92 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
93 let shape = |s: &[i32]| {
94 let dims: Vec<String> = s.iter().map(i32::to_string).collect();
95 format!("[{}]", dims.join(", "))
96 };
97 let header = ["path", "shape", "dtype", "trainable", "bytes"];
98 let cells: Vec<[String; 5]> = self
99 .rows
100 .iter()
101 .map(|r| {
102 [
103 r.path.clone(),
104 shape(&r.shape),
105 r.dtype.to_string(),
106 if r.trainable { "yes" } else { "no" }.to_owned(),
107 grouped(r.bytes),
108 ]
109 })
110 .collect();
111 let mut widths = header.map(str::len);
112 for row in &cells {
113 for (w, c) in widths.iter_mut().zip(row) {
114 *w = (*w).max(c.chars().count());
115 }
116 }
117 let line = |f: &mut fmt::Formatter<'_>, row: [&str; 5]| -> fmt::Result {
118 let [p, s, d, t, b] = row;
119 writeln!(
120 f,
121 "{p:<w0$} {s:<w1$} {d:<w2$} {t:<w3$} {b:>w4$}",
122 w0 = widths[0],
123 w1 = widths[1],
124 w2 = widths[2],
125 w3 = widths[3],
126 w4 = widths[4]
127 )
128 };
129 line(f, header)?;
130 for row in &cells {
131 line(f, [&row[0], &row[1], &row[2], &row[3], &row[4]])?;
132 }
133 write!(
134 f,
135 "{} params ({} trainable), {} bytes",
136 grouped(self.num_params()),
137 grouped(self.num_trainable()),
138 grouped(self.bytes())
139 )
140 }
141}
142
143pub struct ModuleDisplay<'a, M: ?Sized> {
152 module: &'a M,
153}
154
155impl<'a, M: ?Sized> ModuleDisplay<'a, M> {
156 pub(crate) fn new(module: &'a M) -> Self {
157 Self { module }
158 }
159}
160
161struct Node {
163 path: String,
164 type_name: &'static str,
165 params: usize,
166 trainable: usize,
167 children: Vec<usize>,
168 hidden: bool,
170}
171
172fn short(type_name: &'static str) -> &'static str {
174 let base = type_name.split('<').next().unwrap_or(type_name);
175 base.rsplit("::").next().unwrap_or(base)
176}
177
178fn hidden(type_name: &str) -> bool {
180 let base = type_name.split('<').next().unwrap_or(type_name);
181 base.starts_with('(')
182 || base.starts_with('[')
183 || base.starts_with('&')
184 || [
185 "alloc::vec::Vec",
186 "core::option::Option",
187 "alloc::boxed::Box",
188 "alloc::collections::btree::map::BTreeMap",
189 "std::collections::hash::map::HashMap",
190 "nuvec_core::array::Array",
191 "nuvec_core::tree::Param",
192 "core::marker::PhantomData",
193 ]
194 .contains(&base)
195}
196
197struct Builder {
198 nodes: Vec<Node>,
199 stack: Vec<usize>,
200}
201
202impl Visitor for Builder {
203 fn leaf(&mut self, _: &str, a: &Array, _: LeafKind, trainable: bool) {
204 let n = a.size();
205 for &i in &self.stack {
206 self.nodes[i].params += n;
207 if trainable {
208 self.nodes[i].trainable += n;
209 }
210 }
211 }
212
213 fn enter(&mut self, path: &str, name: &str, type_name: &'static str) {
214 let parent = *self.stack.last().expect("the root is open");
215 if name.is_empty() && !hidden(type_name) {
216 let mut at = parent;
219 while self.nodes[at].hidden && self.nodes[at].path == path {
220 match self.stack.iter().rev().nth(1) {
221 Some(&up) if self.nodes[up].path == path => at = up,
222 _ => break,
223 }
224 }
225 if self.nodes[at].path == path {
226 self.nodes[at].type_name = type_name;
227 self.nodes[at].hidden = false;
228 }
229 }
230 self.nodes.push(Node {
231 path: path.to_owned(),
232 type_name,
233 params: 0,
234 trainable: 0,
235 children: Vec::new(),
236 hidden: name.is_empty() || hidden(type_name),
237 });
238 let id = self.nodes.len() - 1;
239 self.nodes[parent].children.push(id);
240 self.stack.push(id);
241 }
242
243 fn exit(&mut self, _: &str) {
244 self.stack.pop();
245 }
246}
247
248impl<M: Leaves> fmt::Display for ModuleDisplay<'_, M> {
249 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
250 let mut b = Builder {
251 nodes: vec![Node {
252 path: String::new(),
253 type_name: std::any::type_name::<M>(),
254 params: 0,
255 trainable: 0,
256 children: Vec::new(),
257 hidden: false,
258 }],
259 stack: vec![0],
260 };
261 self.module.visit(&mut Visit::new(&mut b));
262 let root = &b.nodes[0];
263 write!(
264 f,
265 "{} ({} params, {} trainable)",
266 short(root.type_name),
267 grouped(root.params),
268 grouped(root.trainable)
269 )?;
270 fn print(
271 f: &mut fmt::Formatter<'_>,
272 nodes: &[Node],
273 at: usize,
274 depth: usize,
275 ) -> fmt::Result {
276 for &c in &nodes[at].children {
277 let node = &nodes[c];
278 let mut next = depth;
279 if !node.hidden && node.params > 0 {
280 let frozen = if node.trainable == 0 { ", frozen" } else { "" };
281 write!(
282 f,
283 "\n{:indent$}{}: {} ({}{frozen})",
284 "",
285 node.path,
286 short(node.type_name),
287 grouped(node.params),
288 indent = 2 * depth
289 )?;
290 next = depth + 1;
291 }
292 print(f, nodes, c, next)?;
293 }
294 Ok(())
295 }
296 print(f, &b.nodes, 0, 1)
297 }
298}