Skip to main content

hanzo_ml/
display.rs

1//! Pretty printing of tensors
2//!
3//! This implementation should be in line with the [PyTorch version](https://github.com/pytorch/pytorch/blob/7b419e8513a024e172eae767e24ec1b849976b13/torch/_tensor_str.py).
4//!
5use crate::{DType, Result, Tensor, WithDType};
6use half::{bf16, f16};
7
8impl Tensor {
9    fn fmt_dt<T: WithDType + std::fmt::Display>(
10        &self,
11        f: &mut std::fmt::Formatter,
12    ) -> std::fmt::Result {
13        let device_str = match self.device().location() {
14            crate::DeviceLocation::Cpu => "".to_owned(),
15            crate::DeviceLocation::Cuda { gpu_id } => {
16                format!(", cuda:{gpu_id}")
17            }
18            crate::DeviceLocation::Metal { gpu_id } => {
19                format!(", metal:{gpu_id}")
20            }
21            #[cfg(feature = "rocm")]
22            crate::DeviceLocation::Rocm { gpu_id } => {
23                format!(", rocm:{gpu_id}")
24            }
25            #[cfg(feature = "vulkan")]
26            crate::DeviceLocation::Vulkan { gpu_id } => {
27                format!(", vulkan:{gpu_id}")
28            }
29            #[cfg(feature = "wgpu")]
30            crate::DeviceLocation::Wgpu { gpu_id } => {
31                format!(", wgpu:{gpu_id}")
32            }
33        };
34
35        write!(f, "Tensor[")?;
36        match self.dims() {
37            [] => {
38                if let Ok(v) = self.to_scalar::<T>() {
39                    write!(f, "{v}")?
40                }
41            }
42            [s] if *s < 10 => {
43                if let Ok(vs) = self.to_vec1::<T>() {
44                    for (i, v) in vs.iter().enumerate() {
45                        if i > 0 {
46                            write!(f, ", ")?;
47                        }
48                        write!(f, "{v}")?;
49                    }
50                }
51            }
52            dims => {
53                write!(f, "dims ")?;
54                for (i, d) in dims.iter().enumerate() {
55                    if i > 0 {
56                        write!(f, ", ")?;
57                    }
58                    write!(f, "{d}")?;
59                }
60            }
61        }
62        write!(f, "; {}{}]", self.dtype().as_str(), device_str)
63    }
64}
65
66impl std::fmt::Debug for Tensor {
67    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
68        match self.dtype() {
69            DType::U8 => self.fmt_dt::<u8>(f),
70            DType::U32 => self.fmt_dt::<u32>(f),
71            DType::I16 => self.fmt_dt::<i16>(f),
72            DType::I32 => self.fmt_dt::<i32>(f),
73            DType::I64 => self.fmt_dt::<i64>(f),
74            DType::BF16 => self.fmt_dt::<bf16>(f),
75            DType::F16 => self.fmt_dt::<f16>(f),
76            DType::F32 => self.fmt_dt::<f32>(f),
77            DType::F64 => self.fmt_dt::<f64>(f),
78            DType::F8E4M3 => self.fmt_dt::<float8::F8E4M3>(f),
79            DType::F6E2M3 | DType::F6E3M2 | DType::F4 | DType::F8E8M0 => {
80                write!(
81                    f,
82                    "Tensor[{:?}; dtype={}, unsupported dummy type]",
83                    self.shape(),
84                    self.dtype().as_str()
85                )
86            }
87        }
88    }
89}
90
91/// Options for Tensor pretty printing
92#[derive(Debug, Clone)]
93pub struct PrinterOptions {
94    pub precision: usize,
95    pub threshold: usize,
96    pub edge_items: usize,
97    pub line_width: usize,
98    pub sci_mode: Option<bool>,
99}
100
101static PRINT_OPTS: std::sync::Mutex<PrinterOptions> =
102    std::sync::Mutex::new(PrinterOptions::const_default());
103
104impl PrinterOptions {
105    // We cannot use the default trait as it's not const.
106    const fn const_default() -> Self {
107        Self {
108            precision: 4,
109            threshold: 1000,
110            edge_items: 3,
111            line_width: 80,
112            sci_mode: None,
113        }
114    }
115}
116
117pub fn print_options() -> &'static std::sync::Mutex<PrinterOptions> {
118    &PRINT_OPTS
119}
120
121pub fn set_print_options(options: PrinterOptions) {
122    *PRINT_OPTS.lock().unwrap() = options
123}
124
125pub fn set_print_options_default() {
126    *PRINT_OPTS.lock().unwrap() = PrinterOptions::const_default()
127}
128
129pub fn set_print_options_short() {
130    *PRINT_OPTS.lock().unwrap() = PrinterOptions {
131        precision: 2,
132        threshold: 1000,
133        edge_items: 2,
134        line_width: 80,
135        sci_mode: None,
136    }
137}
138
139pub fn set_print_options_full() {
140    *PRINT_OPTS.lock().unwrap() = PrinterOptions {
141        precision: 4,
142        threshold: usize::MAX,
143        edge_items: 3,
144        line_width: 80,
145        sci_mode: None,
146    }
147}
148
149pub fn set_line_width(line_width: usize) {
150    PRINT_OPTS.lock().unwrap().line_width = line_width
151}
152
153pub fn set_precision(precision: usize) {
154    PRINT_OPTS.lock().unwrap().precision = precision
155}
156
157pub fn set_edge_items(edge_items: usize) {
158    PRINT_OPTS.lock().unwrap().edge_items = edge_items
159}
160
161pub fn set_threshold(threshold: usize) {
162    PRINT_OPTS.lock().unwrap().threshold = threshold
163}
164
165pub fn set_sci_mode(sci_mode: Option<bool>) {
166    PRINT_OPTS.lock().unwrap().sci_mode = sci_mode
167}
168
169struct FmtSize {
170    current_size: usize,
171}
172
173impl FmtSize {
174    fn new() -> Self {
175        Self { current_size: 0 }
176    }
177
178    fn final_size(self) -> usize {
179        self.current_size
180    }
181}
182
183impl std::fmt::Write for FmtSize {
184    fn write_str(&mut self, s: &str) -> std::fmt::Result {
185        self.current_size += s.len();
186        Ok(())
187    }
188}
189
190trait TensorFormatter {
191    type Elem: WithDType;
192
193    fn fmt<T: std::fmt::Write>(&self, v: Self::Elem, max_w: usize, f: &mut T) -> std::fmt::Result;
194
195    fn max_width(&self, to_display: &Tensor) -> usize {
196        let mut max_width = 1;
197        if let Ok(vs) = to_display.flatten_all().and_then(|t| t.to_vec1()) {
198            for &v in vs.iter() {
199                let mut fmt_size = FmtSize::new();
200                let _res = self.fmt(v, 1, &mut fmt_size);
201                max_width = usize::max(max_width, fmt_size.final_size())
202            }
203        }
204        max_width
205    }
206
207    fn write_newline_indent(i: usize, f: &mut std::fmt::Formatter) -> std::fmt::Result {
208        writeln!(f)?;
209        for _ in 0..i {
210            write!(f, " ")?
211        }
212        Ok(())
213    }
214
215    fn fmt_tensor(
216        &self,
217        t: &Tensor,
218        indent: usize,
219        max_w: usize,
220        summarize: bool,
221        po: &PrinterOptions,
222        f: &mut std::fmt::Formatter,
223    ) -> std::fmt::Result {
224        let dims = t.dims();
225        let edge_items = po.edge_items;
226        write!(f, "[")?;
227        match dims {
228            [] => {
229                if let Ok(v) = t.to_scalar::<Self::Elem>() {
230                    self.fmt(v, max_w, f)?
231                }
232            }
233            [v] if summarize && *v > 2 * edge_items => {
234                if let Ok(vs) = t
235                    .narrow(0, 0, edge_items)
236                    .and_then(|t| t.to_vec1::<Self::Elem>())
237                {
238                    for v in vs.into_iter() {
239                        self.fmt(v, max_w, f)?;
240                        write!(f, ", ")?;
241                    }
242                }
243                write!(f, "...")?;
244                if let Ok(vs) = t
245                    .narrow(0, v - edge_items, edge_items)
246                    .and_then(|t| t.to_vec1::<Self::Elem>())
247                {
248                    for v in vs.into_iter() {
249                        write!(f, ", ")?;
250                        self.fmt(v, max_w, f)?;
251                    }
252                }
253            }
254            [_] => {
255                let elements_per_line = usize::max(1, po.line_width / (max_w + 2));
256                if let Ok(vs) = t.to_vec1::<Self::Elem>() {
257                    for (i, v) in vs.into_iter().enumerate() {
258                        if i > 0 {
259                            if i % elements_per_line == 0 {
260                                write!(f, ",")?;
261                                Self::write_newline_indent(indent, f)?
262                            } else {
263                                write!(f, ", ")?;
264                            }
265                        }
266                        self.fmt(v, max_w, f)?
267                    }
268                }
269            }
270            _ => {
271                if summarize && dims[0] > 2 * edge_items {
272                    for i in 0..edge_items {
273                        match t.get(i) {
274                            Ok(t) => self.fmt_tensor(&t, indent + 1, max_w, summarize, po, f)?,
275                            Err(e) => write!(f, "{e:?}")?,
276                        }
277                        write!(f, ",")?;
278                        Self::write_newline_indent(indent, f)?
279                    }
280                    write!(f, "...")?;
281                    Self::write_newline_indent(indent, f)?;
282                    for i in dims[0] - edge_items..dims[0] {
283                        match t.get(i) {
284                            Ok(t) => self.fmt_tensor(&t, indent + 1, max_w, summarize, po, f)?,
285                            Err(e) => write!(f, "{e:?}")?,
286                        }
287                        if i + 1 != dims[0] {
288                            write!(f, ",")?;
289                            Self::write_newline_indent(indent, f)?
290                        }
291                    }
292                } else {
293                    for i in 0..dims[0] {
294                        match t.get(i) {
295                            Ok(t) => self.fmt_tensor(&t, indent + 1, max_w, summarize, po, f)?,
296                            Err(e) => write!(f, "{e:?}")?,
297                        }
298                        if i + 1 != dims[0] {
299                            write!(f, ",")?;
300                            Self::write_newline_indent(indent, f)?
301                        }
302                    }
303                }
304            }
305        }
306        write!(f, "]")?;
307        Ok(())
308    }
309}
310
311struct FloatFormatter<S: WithDType> {
312    int_mode: bool,
313    sci_mode: bool,
314    precision: usize,
315    _phantom: std::marker::PhantomData<S>,
316}
317
318impl<S> FloatFormatter<S>
319where
320    S: WithDType + num_traits::Float + std::fmt::Display,
321{
322    fn new(t: &Tensor, po: &PrinterOptions) -> Result<Self> {
323        let mut int_mode = true;
324        let mut sci_mode = false;
325
326        // Rather than containing all values, this should only include
327        // values that end up being displayed according to [threshold].
328        let values = t
329            .flatten_all()?
330            .to_vec1()?
331            .into_iter()
332            .filter(|v: &S| v.is_finite() && !v.is_zero())
333            .collect::<Vec<_>>();
334        if !values.is_empty() {
335            let mut nonzero_finite_min = S::max_value();
336            let mut nonzero_finite_max = S::min_value();
337            for &v in values.iter() {
338                let v = v.abs();
339                if v < nonzero_finite_min {
340                    nonzero_finite_min = v
341                }
342                if v > nonzero_finite_max {
343                    nonzero_finite_max = v
344                }
345            }
346
347            for &value in values.iter() {
348                if value.ceil() != value {
349                    int_mode = false;
350                    break;
351                }
352            }
353            if let Some(v1) = S::from(1000.) {
354                if let Some(v2) = S::from(1e8) {
355                    if let Some(v3) = S::from(1e-4) {
356                        sci_mode = nonzero_finite_max / nonzero_finite_min > v1
357                            || nonzero_finite_max > v2
358                            || nonzero_finite_min < v3
359                    }
360                }
361            }
362        }
363
364        match po.sci_mode {
365            None => {}
366            Some(v) => sci_mode = v,
367        }
368        Ok(Self {
369            int_mode,
370            sci_mode,
371            precision: po.precision,
372            _phantom: std::marker::PhantomData,
373        })
374    }
375}
376
377impl<S> TensorFormatter for FloatFormatter<S>
378where
379    S: WithDType + num_traits::Float + std::fmt::Display + std::fmt::LowerExp,
380{
381    type Elem = S;
382
383    fn fmt<T: std::fmt::Write>(&self, v: Self::Elem, max_w: usize, f: &mut T) -> std::fmt::Result {
384        if self.sci_mode {
385            write!(
386                f,
387                "{v:width$.prec$e}",
388                v = v,
389                width = max_w,
390                prec = self.precision
391            )
392        } else if self.int_mode {
393            if v.is_finite() {
394                write!(f, "{v:width$.0}.", v = v, width = max_w - 1)
395            } else {
396                write!(f, "{v:max_w$.0}")
397            }
398        } else {
399            write!(
400                f,
401                "{v:width$.prec$}",
402                v = v,
403                width = max_w,
404                prec = self.precision
405            )
406        }
407    }
408}
409
410struct IntFormatter<S: WithDType> {
411    _phantom: std::marker::PhantomData<S>,
412}
413
414impl<S: WithDType> IntFormatter<S> {
415    fn new() -> Self {
416        Self {
417            _phantom: std::marker::PhantomData,
418        }
419    }
420}
421
422impl<S> TensorFormatter for IntFormatter<S>
423where
424    S: WithDType + std::fmt::Display,
425{
426    type Elem = S;
427
428    fn fmt<T: std::fmt::Write>(&self, v: Self::Elem, max_w: usize, f: &mut T) -> std::fmt::Result {
429        write!(f, "{v:max_w$}")
430    }
431}
432
433fn get_summarized_data(t: &Tensor, edge_items: usize) -> Result<Tensor> {
434    let dims = t.dims();
435    if dims.is_empty() {
436        Ok(t.clone())
437    } else if dims.len() == 1 {
438        if dims[0] > 2 * edge_items {
439            Tensor::cat(
440                &[
441                    t.narrow(0, 0, edge_items)?,
442                    t.narrow(0, dims[0] - edge_items, edge_items)?,
443                ],
444                0,
445            )
446        } else {
447            Ok(t.clone())
448        }
449    } else if dims[0] > 2 * edge_items {
450        let mut vs: Vec<_> = (0..edge_items)
451            .map(|i| get_summarized_data(&t.get(i)?, edge_items))
452            .collect::<Result<Vec<_>>>()?;
453        for i in (dims[0] - edge_items)..dims[0] {
454            vs.push(get_summarized_data(&t.get(i)?, edge_items)?)
455        }
456        Tensor::cat(&vs, 0)
457    } else {
458        let vs: Vec<_> = (0..dims[0])
459            .map(|i| get_summarized_data(&t.get(i)?, edge_items))
460            .collect::<Result<Vec<_>>>()?;
461        Tensor::cat(&vs, 0)
462    }
463}
464
465impl std::fmt::Display for Tensor {
466    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
467        let po = PRINT_OPTS.lock().unwrap();
468        let summarize = self.elem_count() > po.threshold;
469        let to_display = if summarize {
470            match get_summarized_data(self, po.edge_items) {
471                Ok(v) => v,
472                Err(err) => return write!(f, "{err:?}"),
473            }
474        } else {
475            self.clone()
476        };
477        match self.dtype() {
478            DType::U8 => {
479                let tf: IntFormatter<u8> = IntFormatter::new();
480                let max_w = tf.max_width(&to_display);
481                tf.fmt_tensor(self, 1, max_w, summarize, &po, f)?;
482                writeln!(f)?;
483            }
484            DType::U32 => {
485                let tf: IntFormatter<u32> = IntFormatter::new();
486                let max_w = tf.max_width(&to_display);
487                tf.fmt_tensor(self, 1, max_w, summarize, &po, f)?;
488                writeln!(f)?;
489            }
490            DType::I16 => {
491                let tf: IntFormatter<i16> = IntFormatter::new();
492                let max_w = tf.max_width(&to_display);
493                tf.fmt_tensor(self, 1, max_w, summarize, &po, f)?;
494                writeln!(f)?;
495            }
496            DType::I32 => {
497                let tf: IntFormatter<i32> = IntFormatter::new();
498                let max_w = tf.max_width(&to_display);
499                tf.fmt_tensor(self, 1, max_w, summarize, &po, f)?;
500                writeln!(f)?;
501            }
502            DType::I64 => {
503                let tf: IntFormatter<i64> = IntFormatter::new();
504                let max_w = tf.max_width(&to_display);
505                tf.fmt_tensor(self, 1, max_w, summarize, &po, f)?;
506                writeln!(f)?;
507            }
508            DType::BF16 => {
509                if let Ok(tf) = FloatFormatter::<bf16>::new(&to_display, &po) {
510                    let max_w = tf.max_width(&to_display);
511                    tf.fmt_tensor(self, 1, max_w, summarize, &po, f)?;
512                    writeln!(f)?;
513                }
514            }
515            DType::F16 => {
516                if let Ok(tf) = FloatFormatter::<f16>::new(&to_display, &po) {
517                    let max_w = tf.max_width(&to_display);
518                    tf.fmt_tensor(self, 1, max_w, summarize, &po, f)?;
519                    writeln!(f)?;
520                }
521            }
522            DType::F64 => {
523                if let Ok(tf) = FloatFormatter::<f64>::new(&to_display, &po) {
524                    let max_w = tf.max_width(&to_display);
525                    tf.fmt_tensor(self, 1, max_w, summarize, &po, f)?;
526                    writeln!(f)?;
527                }
528            }
529            DType::F32 => {
530                if let Ok(tf) = FloatFormatter::<f32>::new(&to_display, &po) {
531                    let max_w = tf.max_width(&to_display);
532                    tf.fmt_tensor(self, 1, max_w, summarize, &po, f)?;
533                    writeln!(f)?;
534                }
535            }
536            DType::F8E4M3 => {
537                if let Ok(tf) = FloatFormatter::<float8::F8E4M3>::new(&to_display, &po) {
538                    let max_w = tf.max_width(&to_display);
539                    tf.fmt_tensor(self, 1, max_w, summarize, &po, f)?;
540                    writeln!(f)?;
541                }
542            }
543            DType::F6E2M3 | DType::F6E3M2 | DType::F4 | DType::F8E8M0 => {
544                writeln!(
545                    f,
546                    "Dummy type {} (not supported for display)",
547                    self.dtype().as_str()
548                )?;
549            }
550        };
551
552        let device_str = match self.device().location() {
553            crate::DeviceLocation::Cpu => "".to_owned(),
554            crate::DeviceLocation::Cuda { gpu_id } => {
555                format!(", cuda:{gpu_id}")
556            }
557            crate::DeviceLocation::Metal { gpu_id } => {
558                format!(", metal:{gpu_id}")
559            }
560            #[cfg(feature = "rocm")]
561            crate::DeviceLocation::Rocm { gpu_id } => {
562                format!(", rocm:{gpu_id}")
563            }
564            #[cfg(feature = "vulkan")]
565            crate::DeviceLocation::Vulkan { gpu_id } => {
566                format!(", vulkan:{gpu_id}")
567            }
568            #[cfg(feature = "wgpu")]
569            crate::DeviceLocation::Wgpu { gpu_id } => {
570                format!(", wgpu:{gpu_id}")
571            }
572        };
573
574        write!(
575            f,
576            "Tensor[{:?}, {}{}]",
577            self.dims(),
578            self.dtype().as_str(),
579            device_str
580        )
581    }
582}