use crate::Engine;
use std::sync::atomic::Ordering;
pub fn spec_trace_on() -> bool {
spec_trace_level() >= 1
}
pub fn spec_trace_level() -> u8 {
use std::sync::OnceLock;
static L: OnceLock<u8> = OnceLock::new();
*L.get_or_init(|| {
spec_trace_level_from(
std::env::var("MEMRA_SPEC_TRACE").ok().as_deref(),
std::env::var("MEMRA_GLM5_SPEC_TRACE").ok().as_deref(),
)
})
}
fn parse_level(v: Option<&str>) -> Option<u8> {
match v {
Some("1") => Some(1),
Some("2") => Some(2),
_ => None,
}
}
fn spec_trace_level_from(general: Option<&str>, glm5_alias: Option<&str>) -> u8 {
let g = parse_level(general);
let a = parse_level(glm5_alias);
if let (Some(gv), Some(av)) = (g, a)
&& gv != av
{
eprintln!(
"[spec-trace] MEMRA_SPEC_TRACE={gv} overrides MEMRA_GLM5_SPEC_TRACE={av} \
(the general flag wins; unset one to silence this)"
);
}
g.or(a).unwrap_or(0)
}
pub(crate) static V_KDA_NS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
pub(crate) static V_KDA_SCAN_NS: std::sync::atomic::AtomicU64 =
std::sync::atomic::AtomicU64::new(0);
pub(crate) static V_MLA_NS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
pub(crate) static V_FFN_NS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
#[derive(Default)]
pub(crate) struct SpecPhaseNs {
pub(crate) draft: u64,
pub(crate) verify: u64,
pub(crate) accept: u64,
pub(crate) roll: u64,
pub(crate) maint: u64,
pub(crate) rounds: u64,
}
impl SpecPhaseNs {
pub(crate) fn add(&mut self, o: &SpecPhaseNs) {
self.draft += o.draft;
self.verify += o.verify;
self.accept += o.accept;
self.roll += o.roll;
self.maint += o.maint;
self.rounds += o.rounds;
}
pub(crate) fn clock(e: &Engine, eh: &Engine) -> std::time::Instant {
let _ = e.stream().synchronize();
if !std::ptr::eq(e, eh) {
let _ = eh.stream().synchronize();
}
std::time::Instant::now()
}
pub(crate) fn emit(&self, tag: &str, tag_v: &str, k: usize) {
if self.rounds == 0 {
return;
}
let ms = |ns: u64| ns as f64 / 1e6;
let per = |ns: u64| ns as f64 / 1e6 / self.rounds as f64;
let total = self.draft + self.verify + self.accept + self.roll + self.maint;
eprintln!(
"[{tag}] rounds={} k={k} total={:.2}ms | draft={:.2} verify={:.2} \
accept={:.2} roll={:.2} maint={:.2} | per-round ms: draft={:.3} verify={:.3} \
accept={:.3} roll={:.3} maint={:.3} total={:.3}",
self.rounds,
ms(total),
ms(self.draft),
ms(self.verify),
ms(self.accept),
ms(self.roll),
ms(self.maint),
per(self.draft),
per(self.verify),
per(self.accept),
per(self.roll),
per(self.maint),
per(total),
);
if spec_trace_level() >= 2 {
let vkda = V_KDA_NS.swap(0, Ordering::Relaxed);
let scan = V_KDA_SCAN_NS.swap(0, Ordering::Relaxed);
let vmla = V_MLA_NS.swap(0, Ordering::Relaxed);
let vffn = V_FFN_NS.swap(0, Ordering::Relaxed);
let vrest = self.verify.saturating_sub(vkda + vmla);
eprintln!(
"[{tag_v}] rounds={} k={k} | per-round ms: vkda={:.3} (scan={:.3}) \
vmla={:.3} vrest={:.3} (vffn={:.3})",
self.rounds,
per(vkda),
per(scan),
per(vmla),
per(vrest),
per(vffn),
);
}
}
}
pub fn spec_prof_on() -> bool {
use std::sync::OnceLock;
static ON: OnceLock<bool> = OnceLock::new();
*ON.get_or_init(|| std::env::var("MEMRA_SPEC_PROF").as_deref() == Ok("1"))
}
#[derive(Default, Debug, Clone, PartialEq)]
pub struct SpecFirstTokenProf {
pub cache_alloc_ms: f64,
pub prime_ms: f64,
pub capture_ms: f64,
pub anchor_ms: f64,
pub draft_alloc_ms: f64,
pub draft_prime_ms: f64,
pub first_draft_ms: f64,
pub first_verify_ms: f64,
pub first_accept_ms: f64,
pub first_roll_ms: f64,
pub first_maint_ms: f64,
pub first_round_tokens: usize,
pub first_burst_ms: f64,
pub first_burst_hook_ms: f64,
pub first_burst_rounds: usize,
pub first_burst_tokens: usize,
pub sink_alloc_ms: f64,
pub prime_tap_dtoh_ms: f64,
pub draft_prime_h2d_ms: f64,
pub draft_prime_feat_ms: f64,
pub draft_prime_kv_ms: f64,
pub draft_prime_rows: usize,
pub draft_prime_chunks: usize,
pub draft_prime_arm: &'static str,
pub draft_kv_mb: f64,
pub free_mb_before: Vec<(usize, u64)>,
pub free_mb_after: Vec<(usize, u64)>,
}
pub const SPEC_PROF_ROUNDS: usize = 64;
#[derive(Default, Debug, Clone, Copy, PartialEq)]
pub struct SpecRoundProf {
pub wall_ms: f32,
pub draft_ms: f32,
pub verify_ms: f32,
pub accept_ms: f32,
pub rest_ms: f32,
pub k: u16,
pub j: u16,
pub ctx: u32,
pub seq_rows: u32,
}
#[derive(Default, Debug)]
pub struct SpecRoundsLog {
pub rounds: Vec<SpecRoundProf>,
pub printed: usize,
pub summarized: bool,
}
impl SpecRoundsLog {
pub fn wants_more(&self) -> bool {
self.rounds.len() < SPEC_PROF_ROUNDS
}
pub fn push(&mut self, r: SpecRoundProf) {
if self.wants_more() {
self.rounds.push(r);
}
}
pub fn fresh(&mut self) -> &[SpecRoundProf] {
let from = self.printed;
self.printed = self.rounds.len();
&self.rounds[from..]
}
pub fn summary(&self) -> String {
let n = self.rounds.len();
if n == 0 {
return "rounds=0".to_string();
}
let nf = n as f64;
let k: f64 = self.rounds.iter().map(|r| r.k as f64).sum::<f64>();
let j: f64 = self.rounds.iter().map(|r| r.j as f64).sum::<f64>();
let mut walls: Vec<f32> = self.rounds.iter().map(|r| r.wall_ms).collect();
walls.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
let med = walls[n / 2];
let mean = walls.iter().map(|&w| w as f64).sum::<f64>() / nf;
let slow = walls.iter().filter(|&&w| w > 1.5 * med).count();
let verify_mean = self.rounds.iter().map(|r| r.verify_ms as f64).sum::<f64>() / nf;
let draft_mean = self.rounds.iter().map(|r| r.draft_ms as f64).sum::<f64>() / nf;
let seq: u64 = self.rounds.iter().map(|r| r.seq_rows as u64).sum();
format!(
"rounds={n} k_mean={:.2} j_mean={:.2} accept={:.3} tok_per_round={:.2} \
wall_ms mean={:.1} min={:.1} med={:.1} max={:.1} slow_rounds(>1.5x med)={slow} \
draft_mean={:.1} verify_mean={:.1} seq_rows_total={seq} ctx_first={} ctx_last={}",
k / nf,
j / nf,
if k > 0.0 { j / k } else { 0.0 },
(j + nf) / nf,
mean,
walls[0],
med,
walls[n - 1],
draft_mean,
verify_mean,
self.rounds[0].ctx,
self.rounds[n - 1].ctx,
)
}
}
pub(crate) static V_SEQ_ROWS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
pub(crate) struct ProfClock {
t: std::time::Instant,
}
impl ProfClock {
pub(crate) fn start(e: &Engine, eh: &Engine) -> Self {
Self {
t: SpecPhaseNs::clock(e, eh),
}
}
pub(crate) fn lap(&mut self, e: &Engine, eh: &Engine) -> f64 {
let now = SpecPhaseNs::clock(e, eh);
let ms = now.duration_since(self.t).as_secs_f64() * 1e3;
self.t = now;
ms
}
}
#[cfg(test)]
mod tests {
use super::{parse_level, spec_trace_level_from};
#[test]
fn level_resolution_honors_both_names_general_wins() {
assert_eq!(spec_trace_level_from(None, None), 0);
assert_eq!(spec_trace_level_from(Some("x"), None), 0);
assert_eq!(spec_trace_level_from(Some("1"), None), 1);
assert_eq!(spec_trace_level_from(Some("2"), None), 2);
assert_eq!(spec_trace_level_from(None, Some("1")), 1);
assert_eq!(spec_trace_level_from(None, Some("2")), 2);
assert_eq!(spec_trace_level_from(Some("2"), Some("2")), 2);
assert_eq!(spec_trace_level_from(Some("1"), Some("2")), 1);
assert_eq!(spec_trace_level_from(Some("2"), Some("1")), 2);
assert_eq!(spec_trace_level_from(Some("x"), Some("1")), 1);
assert_eq!(parse_level(Some("0")), None);
}
}