Skip to main content

tcontract_exec/
host.rs

1//! Host class and the serial GEMM rate of each class.
2//!
3//! The rate the width and batch rules use is keyed by the kernel dispatch the
4//! machine takes, not by the CPU model. The class is `threadteam::HostClass`,
5//! re-exported here: one enum and one detection for the dispatch costs and the
6//! rates below.
7//!
8//! | class | ns per flop | multiply-adds per ns | source |
9//! | --- | --- | --- | --- |
10//! | `x86_64-avx512` | 0.032 | 15.6 | AMD Ryzen AI 9 HX 470 at 2.0 GHz (boost off): GEMM f64 0.0641 ns per MAC (`faer-benchmark` `data/probes/20261026-awake-team-linux/tables-consts-exec3.md`, `gemm_per_mac`; the `tc_matmul` seq times of `tables-confirm-exec3.md` agree: 0.0678, 0.0657, 0.0644 ns per MAC at n = 48, 64, 96), halved to a flop (a MAC is two flops), then 1 / (2 * 0.032) = 15.6 |
11//! | `aarch64-apple` | 0.0185 | 27 | Apple M5 Max, the serial GEMM rate (about 54 GFLOP/s, threadteam `docs/design/thresholds.md` section 3.2) |
12//! | every other class | 0.0185 | 27 | the `generic` row: the fastest known value of each column (the minimum of the ns per flop, the maximum of the multiply-adds per ns over the measured classes); it keeps work serial |
13//!
14//! The rate of a class and the dispatch costs of the same class in `threadteam`
15//! come from the same machine in the same clock state: boost on or off moves both
16//! by about 1.66x and leaves their ratio stable within a few percent
17//! (`raw-exec3-boost`, `tables-*-exec3-boost.md`).
18
19pub use threadteam::HostClass;
20
21/// Estimated serial time per real flop of a GEMM item on a host class (the
22/// table in the module documentation).
23pub const fn ns_per_flop_of(class: HostClass) -> f64 {
24    match class {
25        HostClass::X86_64Avx512 => 0.032,
26        HostClass::Aarch64Apple
27        | HostClass::X86_64Avx2
28        | HostClass::X86_64Scalar
29        | HostClass::Aarch64
30        | HostClass::Generic => 0.0185,
31    }
32}
33
34/// Serial multiply-adds per nanosecond of one GEMM item on a host class: the
35/// same rate as [`ns_per_flop_of`] (`1 / ns_per_flop / 2`), rounded as the
36/// table in the module documentation states it.
37pub const fn muladds_per_ns_of(class: HostClass) -> f64 {
38    match class {
39        HostClass::X86_64Avx512 => 15.6,
40        HostClass::Aarch64Apple
41        | HostClass::X86_64Avx2
42        | HostClass::X86_64Scalar
43        | HostClass::Aarch64
44        | HostClass::Generic => 27.0,
45    }
46}
47
48/// Estimated serial time per real flop on this host (`ns_per_flop_of` of
49/// [`HostClass::detect`]); the one default the width choice uses everywhere. The
50/// width itself is the token's decision (`Exec::width_for` over this estimate).
51pub fn ns_per_flop() -> f64 {
52    ns_per_flop_of(HostClass::detect())
53}
54
55/// Serial multiply-adds per nanosecond on this host (`muladds_per_ns_of` of
56/// [`HostClass::detect`]).
57pub fn muladds_per_ns() -> f64 {
58    muladds_per_ns_of(HostClass::detect())
59}
60
61#[cfg(test)]
62mod tests {
63    use super::*;
64
65    #[test]
66    fn the_two_rates_of_a_class_agree() {
67        for c in [
68            HostClass::X86_64Avx512,
69            HostClass::X86_64Avx2,
70            HostClass::X86_64Scalar,
71            HostClass::Aarch64Apple,
72            HostClass::Aarch64,
73            HostClass::Generic,
74        ] {
75            let exact = 1.0 / ns_per_flop_of(c) / 2.0;
76            assert!(
77                (muladds_per_ns_of(c) / exact - 1.0).abs() < 0.01,
78                "{}",
79                c.name()
80            );
81        }
82    }
83
84    #[test]
85    fn rows_per_class() {
86        assert_eq!(ns_per_flop_of(HostClass::X86_64Avx512), 0.032);
87        assert_eq!(muladds_per_ns_of(HostClass::X86_64Avx512), 15.6);
88        for c in [
89            HostClass::Aarch64Apple,
90            HostClass::X86_64Avx2,
91            HostClass::X86_64Scalar,
92            HostClass::Aarch64,
93            HostClass::Generic,
94        ] {
95            assert_eq!(ns_per_flop_of(c), 0.0185, "{}", c.name());
96            assert_eq!(muladds_per_ns_of(c), 27.0, "{}", c.name());
97        }
98        // `generic` is the fastest known value of each column.
99        let g = HostClass::Generic;
100        assert!(ns_per_flop_of(g) <= ns_per_flop_of(HostClass::X86_64Avx512));
101        assert!(muladds_per_ns_of(g) >= muladds_per_ns_of(HostClass::X86_64Avx512));
102        assert_eq!(HostClass::detect(), HostClass::detect());
103        assert_eq!(ns_per_flop(), ns_per_flop_of(HostClass::detect()));
104        assert_eq!(muladds_per_ns(), muladds_per_ns_of(HostClass::detect()));
105    }
106}