metrale_model_layers/layers/ops/model_stats.rs
1// SPDX-License-Identifier: MIT OR Apache-2.0
2
3//! 2026-09-25: [`ModelStats`]: diagnostic counters and one-shot latches owned by one model.
4//!
5//! Owner: model-layers ops.
6//! Invariants:
7//! - A new `ModelStats` starts with zero counters and every latch unfired, so a second model
8//! neither inherits the first one's counts nor finds its dumps already spent.
9//!
10//! `TransformerModel` owns one next to its [`ModelLevers`](super::ModelLevers) and lends it to
11//! each `ForwardContext` as `stats`. The counters are atomics because they are updated through that
12//! shared reference.
13
14use std::sync::atomic::AtomicU64;
15
16/// 2026-09-25: Per-model diagnostic state.
17#[derive(Debug, Default)]
18pub struct ModelStats {
19 /// 2026-09-25: MoE expert-union sampling under `ModelLevers::moe_union_stats`
20 /// (`moe/union_stats.rs`).
21 pub moe_union: MoeUnionStats,
22 /// 2026-09-25: One-shot latches for dumps and once-per-model log lines.
23 pub dumped: DumpLatches,
24}
25
26/// 2026-09-25: Expert-union sampling counters for one model: calls seen, calls sampled, and the
27/// running unique-expert and slot totals.
28#[derive(Debug, Default)]
29pub struct MoeUnionStats {
30 pub calls: AtomicU64,
31 pub samples: AtomicU64,
32 pub unique_sum: AtomicU64,
33 pub slots_sum: AtomicU64,
34}
35
36/// 2026-09-25: One-shot latches for one model, keyed by a `&'static str`
37/// ([`keyed`](DumpLatches::keyed)).
38#[derive(Debug, Default)]
39pub struct DumpLatches {
40 /// 2026-09-25: The keys that have fired.
41 fired: std::sync::Mutex<std::collections::BTreeSet<&'static str>>,
42}
43
44impl ModelStats {
45 pub fn new() -> Self {
46 Self::default()
47 }
48
49 /// 2026-09-25: `true` the first time this model reaches `key`, `false` after; the same latch
50 /// set as [`DumpLatches::keyed`]. Prefix keys by purpose (`"log:..."`, `"dump:..."`) so two
51 /// unrelated sites cannot share one.
52 pub fn once(&self, key: &'static str) -> bool {
53 self.dumped.keyed(key)
54 }
55}
56
57impl DumpLatches {
58 /// 2026-09-25: `true` exactly once per model for `key`. Calling it consumes the shot, so test
59 /// the dump's lever first; a disabled dump then does not spend it.
60 pub fn keyed(&self, key: &'static str) -> bool {
61 self.fired
62 .lock()
63 .expect("dump latches poisoned")
64 .insert(key)
65 }
66}
67
68#[cfg(test)]
69mod tests {
70 use super::*;
71 use std::sync::atomic::Ordering;
72
73 #[test]
74 fn a_keyed_latch_fires_once_per_key_per_model() {
75 let a = ModelStats::new();
76 assert!(a.dumped.keyed("dflash_block"));
77 assert!(!a.dumped.keyed("dflash_block"), "and only once");
78 assert!(a.dumped.keyed("dflash_ctx"), "a different dump is separate");
79 assert!(
80 ModelStats::new().dumped.keyed("dflash_block"),
81 "and a new model re-arms every key"
82 );
83 }
84
85 #[test]
86 fn two_models_count_expert_unions_independently() {
87 let a = ModelStats::new();
88 let b = ModelStats::new();
89 a.moe_union.calls.fetch_add(9, Ordering::Relaxed);
90 assert_eq!(a.moe_union.calls.load(Ordering::Relaxed), 9);
91 assert_eq!(
92 b.moe_union.calls.load(Ordering::Relaxed),
93 0,
94 "a second model starts clean rather than inheriting a mean"
95 );
96 }
97}