Skip to main content

nuvec_nn/
display.rs

1//! Introspection: [`Summary`] (one row per leaf) and [`ModuleDisplay`] (the nested modules
2//! with their parameter counts), built from a tree's paths and child boundaries.
3
4use std::fmt;
5
6use nuvec::tree::{LeafKind, Leaves, Visit, Visitor};
7use nuvec::{Array, DType};
8
9/// One leaf of a [`Summary`].
10#[derive(Debug, Clone, PartialEq, Eq)]
11pub struct SummaryRow {
12    /// The leaf's path (`layers.0.weight`).
13    pub path: String,
14    /// Its shape.
15    pub shape: Vec<i32>,
16    /// Its dtype.
17    pub dtype: DType,
18    /// What it is: a parameter, a buffer or a plain array.
19    pub kind: LeafKind,
20    /// Whether it is trained.
21    pub trainable: bool,
22    /// Its size in bytes.
23    pub bytes: usize,
24}
25
26/// A table of a model's leaves (path, shape, dtype, trainable, bytes) with totals,
27/// printed as aligned columns ([`crate::Module::summary`]).
28#[derive(Debug, Clone, PartialEq, Eq)]
29pub struct Summary {
30    /// The leaves, in order.
31    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    /// Elements over every leaf.
55    pub fn num_params(&self) -> usize {
56        self.rows.iter().map(|r| elements(&r.shape)).sum()
57    }
58
59    /// Elements over the trainable leaves.
60    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    /// Bytes over every leaf.
69    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
78/// `n` with thousands separators: `25,818`.
79pub(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
143/// A model's nested modules with their parameter counts ([`crate::Module::display`]):
144///
145/// ```text
146/// Mlp (25,818 params, 25,818 trainable)
147///   layers.0: Linear (25,120)
148///   layers.1: Linear (528)
149///   layers.2: Linear (170)
150/// ```
151pub 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
161/// One node of the module tree.
162struct Node {
163    path: String,
164    type_name: &'static str,
165    params: usize,
166    trainable: usize,
167    children: Vec<usize>,
168    /// A container or leaf type (not printed itself; its modules are).
169    hidden: bool,
170}
171
172/// The type's name without its module path and generic arguments.
173fn 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
178/// Containers and leaves, whose own node is not a module.
179fn 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            // A transparent wrapper (`Option`, an enum's newtype variant): the inner type
217            // names the module at this path.
218            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}