1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
//! In-process CPU op timers (`CMF_CPU_PROF=1`).
//!
//! Wall-clock A/B on a shared box is not evidence (see the perf notes):
//! the same change measured 23%, 0% and 22% across whole runs. These
//! counters accumulate nanoseconds inside the process around each CPU
//! stage of the layer loop, so contention inflates every slot alike and
//! the SPLIT survives it. Off by default: one cached bool per call site.
//!
//! `report(tokens)` prints ms per token for every slot that ran.
use std::sync::OnceLock;
use std::sync::atomic::{AtomicU64, Ordering};
#[derive(Clone, Copy)]
#[repr(usize)]
pub enum Slot {
/// Q/K/V projections of one decode position.
Qkv,
/// RoPE + append + attend (the attention core, one position).
AttnCore,
/// Output projection.
AttnO,
/// Fused gate/up (+SiLU·mul).
FfnGateUp,
/// Down projection.
FfnDown,
/// RMSNorms + residual adds of the layer loop.
Norms,
/// Final norm + lm_head matvec.
Head,
/// Sampler (argmax / top-k / penalties).
Sampler,
/// Whole layer stack of one forward (decode or prefill chunk).
Layers,
/// Batched prefill projections (every matmat).
Matmat,
/// Batched prefill attention core (per-position or batched attend).
PrefillAttend,
/// Activation quantization (split_act) inside the kernels.
SplitAct,
/// One pool dispatch, entry to exit (caller thread).
DispTotal,
/// Pool dispatch bookkeeping: descriptor, tickets, unparks.
DispSetup,
/// The caller participant's share of the dispatched closure.
DispWork,
/// Barrier drain: waiting for the other participants.
DispWait,
/// Lane driver (gate+up): per-batch scale/ladder decode.
GuScales,
/// Lane driver (gate+up): the interleaved group-dot loop.
GuDots,
/// Lane driver (gate+up): outliers + silu + store.
GuPost,
/// The dense-FFN activation pass (Spark's exact-erf GELU combine).
FfnCombine,
}
const N: usize = 20;
const NAMES: [&str; N] = [
"qkv",
"attn_core",
"attn_o",
"ffn_gate_up",
"ffn_down",
"norms",
"head",
"sampler",
"layers(total)",
"matmat(prefill)",
"attend(prefill)",
"split_act",
"disp(total)",
"disp(setup)",
"disp(work)",
"disp(wait)",
"ln.scales",
"ln.dots",
"ln.post",
"ffn_combine",
];
static NS: [AtomicU64; N] = [const { AtomicU64::new(0) }; N];
static CALLS: [AtomicU64; N] = [const { AtomicU64::new(0) }; N];
/// Total elements marked as activation outliers by `split_act` (diagnostic
/// for the per-row outlier-correction cost). Reset by [`reset`], printed by
/// [`report`].
static OUTLIERS: AtomicU64 = AtomicU64::new(0);
/// Rows routed through a lane driver (diagnostic: is the lane path live?).
static LANE_ROWS: AtomicU64 = AtomicU64::new(0);
/// Rows routed through the plain per-row path (diagnostic).
static PLAIN_ROWS: AtomicU64 = AtomicU64::new(0);
/// Record `n` outliers found in one activation split.
pub fn count_outliers(n: u64) {
OUTLIERS.fetch_add(n, Ordering::Relaxed);
}
/// Record `n` rows about to run through a lane-interleaved driver.
pub fn count_lane_rows(n: u64) {
LANE_ROWS.fetch_add(n, Ordering::Relaxed);
}
/// Record `n` rows about to run through the plain per-row path.
pub fn count_plain_rows(n: u64) {
PLAIN_ROWS.fetch_add(n, Ordering::Relaxed);
}
#[inline]
pub fn on() -> bool {
static ON: OnceLock<bool> = OnceLock::new();
*ON.get_or_init(|| std::env::var("CMF_CPU_PROF").is_ok_and(|v| v != "0"))
}
/// Scope timer: records on drop. `None` when profiling is off.
pub struct Timer(Option<(Slot, std::time::Instant)>);
impl Drop for Timer {
#[inline]
fn drop(&mut self) {
if let Some((s, t0)) = self.0.take() {
NS[s as usize].fetch_add(t0.elapsed().as_nanos() as u64, Ordering::Relaxed);
CALLS[s as usize].fetch_add(1, Ordering::Relaxed);
}
}
}
#[inline]
pub fn time(s: Slot) -> Timer {
if on() {
Timer(Some((s, std::time::Instant::now())))
} else {
Timer(None)
}
}
/// Zero every slot (bench calls it right before the measured window).
pub fn reset() {
for i in 0..N {
NS[i].store(0, Ordering::Relaxed);
CALLS[i].store(0, Ordering::Relaxed);
}
OUTLIERS.store(0, Ordering::Relaxed);
LANE_ROWS.store(0, Ordering::Relaxed);
PLAIN_ROWS.store(0, Ordering::Relaxed);
}
/// Snapshot of (name, total ms, calls) for every slot that ran.
pub fn snapshot() -> Vec<(&'static str, f64, u64)> {
(0..N)
.filter(|&i| CALLS[i].load(Ordering::Relaxed) > 0)
.map(|i| {
(
NAMES[i],
NS[i].load(Ordering::Relaxed) as f64 / 1e6,
CALLS[i].load(Ordering::Relaxed),
)
})
.collect()
}
/// Labeled stage spans (`SAM3_PROF=1`) — the generic sibling of [`time`]:
/// a process-wide map label → (ms, calls), accumulated by RAII spans and
/// printed (then cleared) by a `dump_on_drop` guard. The accumulator is a
/// shared Mutex so spans taken ON POOL WORKER THREADS inside run_rows
/// closures land in the caller's dump too. Inclusive: a child span's ms
/// are also inside its parent's. Off by default: one cached bool per
/// `span()` call site.
pub mod spans {
use std::collections::BTreeMap;
use std::sync::{Mutex, OnceLock};
use std::time::Instant;
type Acc = Mutex<BTreeMap<&'static str, (f64, u64)>>;
#[inline]
fn acc() -> &'static Acc {
static ACC: OnceLock<Acc> = OnceLock::new();
ACC.get_or_init(|| Mutex::new(BTreeMap::new()))
}
#[inline]
pub fn on() -> bool {
static ON: OnceLock<bool> = OnceLock::new();
*ON.get_or_init(|| std::env::var("SAM3_PROF").is_ok_and(|v| v != "0"))
}
/// Records wall time from creation to drop under `label`.
pub struct Span(Option<(&'static str, Instant)>);
impl Drop for Span {
#[inline]
fn drop(&mut self) {
if let Some((label, t0)) = self.0.take() {
add(label, t0.elapsed());
}
}
}
/// Manually accumulate `d` under `label` (worker-thread friendly).
#[inline]
pub fn add(label: &'static str, d: std::time::Duration) {
let mut m = match acc().lock() {
Ok(g) => g,
Err(poisoned) => poisoned.into_inner(),
};
let e = m.entry(label).or_insert((0.0, 0));
e.0 += d.as_secs_f64() * 1e3;
e.1 += 1;
}
/// Start a span; a no-op when `SAM3_PROF` is off.
#[inline]
pub fn span(label: &'static str) -> Span {
if on() {
Span(Some((label, Instant::now())))
} else {
Span(None)
}
}
/// Print the accumulated spans under `prefix`, then clear.
pub fn dump(prefix: &str) {
if !on() {
return;
}
let mut m = match acc().lock() {
Ok(g) => g,
Err(poisoned) => poisoned.into_inner(),
};
if m.is_empty() {
return;
}
eprintln!("[sam3-prof] {prefix}:");
let rows: Vec<(&'static str, f64, u64)> =
m.iter().map(|(k, v)| (*k, v.0, v.1)).collect();
for (k, ms, n) in rows {
eprintln!("[sam3-prof] {k:<18} {ms:>10.1} ms ({n} calls)");
}
m.clear();
}
/// Prints (and clears) the accumulator when dropped — one dump per
/// forward even through its early-return paths.
pub struct Dump(String);
impl Drop for Dump {
fn drop(&mut self) {
dump(&self.0);
}
}
pub fn dump_on_drop(prefix: impl Into<String>) -> Dump {
Dump(prefix.into())
}
}
/// Print ms/token per slot to stderr.
pub fn report(label: &str, tokens: usize) {
if !on() {
return;
}
let t = tokens.max(1) as f64;
eprintln!("cpu-prof [{label}] per token over {tokens} tokens:");
for (name, ms, calls) in snapshot() {
eprintln!(
" {name:<16} {:>8.3} ms/tok ({calls} calls, {:.1} us/call)",
ms / t,
ms * 1e3 / calls.max(1) as f64
);
}
let ol = OUTLIERS.load(Ordering::Relaxed);
if ol > 0 {
eprintln!(
" outliers {:>8.1} /split ({} total)",
ol as f64 / t,
ol
);
}
let lr = LANE_ROWS.load(Ordering::Relaxed);
let pr = PLAIN_ROWS.load(Ordering::Relaxed);
if lr > 0 || pr > 0 {
eprintln!(
" lane_rows {:>8.1} /tok plain_rows {:>8.1} /tok",
lr as f64 / t,
pr as f64 / t
);
}
}