#[cfg(feature = "native")]
use std::collections::BTreeMap;
#[cfg(feature = "native")]
use std::sync::{Mutex, OnceLock};
use crate::error::FocrResult;
#[cfg(feature = "native")]
struct Acc {
rows: u64,
sum_sq: Vec<f64>,
}
#[cfg(feature = "native")]
struct Recorder {
path: std::path::PathBuf,
table: Mutex<BTreeMap<String, Acc>>,
}
#[cfg(feature = "native")]
fn recorder() -> Option<&'static Recorder> {
static STATE: OnceLock<Option<Recorder>> = OnceLock::new();
STATE
.get_or_init(|| {
std::env::var_os("FOCR_CALIB_OUT").map(|path| Recorder {
path: std::path::PathBuf::from(path),
table: Mutex::new(BTreeMap::new()),
})
})
.as_ref()
}
#[must_use]
#[inline]
pub fn enabled() -> bool {
#[cfg(feature = "native")]
{
recorder().is_some()
}
#[cfg(not(feature = "native"))]
{
false
}
}
#[inline]
pub fn record_rows(key: &str, data: &[f32], cols: usize) {
#[cfg(not(feature = "native"))]
{
let _ = (key, data, cols);
}
#[cfg(feature = "native")]
{
let Some(rec) = recorder() else { return };
if cols == 0 || !data.len().is_multiple_of(cols) {
return;
}
let n_rows = data.len() / cols;
if n_rows == 0 {
return;
}
let mut local = vec![0.0f64; cols];
for row in data.chunks_exact(cols) {
for (slot, &v) in local.iter_mut().zip(row.iter()) {
let x = f64::from(v);
*slot += x * x;
}
}
let Ok(mut table) = rec.table.lock() else {
return;
};
match table.get_mut(key) {
Some(acc) if acc.sum_sq.len() == cols => {
acc.rows = acc.rows.saturating_add(n_rows as u64);
for (slot, &v) in acc.sum_sq.iter_mut().zip(local.iter()) {
*slot += v;
}
}
Some(_) => {
}
None => {
table.insert(
key.to_string(),
Acc {
rows: n_rows as u64,
sum_sq: local,
},
);
}
}
}
}
#[inline]
pub fn record_row(key: &str, row: &[f32]) {
record_rows(key, row, row.len());
}
pub fn flush() -> FocrResult<()> {
#[cfg(feature = "native")]
{
let Some(rec) = recorder() else {
return Ok(());
};
let Ok(table) = rec.table.lock() else {
return Ok(());
};
let mut stats = crate::quant::calib::CalibStats::new();
for (key, acc) in table.iter() {
if acc.rows == 0 {
continue;
}
let denom = acc.rows as f64;
stats.insert(
key.clone(),
crate::quant::calib::ChannelStats {
rows: acc.rows,
mean_sq: acc.sum_sq.iter().map(|&s| s / denom).collect(),
},
);
}
std::fs::write(&rec.path, stats.to_json()).map_err(|e| {
crate::error::FocrError::Other(anyhow::anyhow!(
"writing FOCR_CALIB_OUT to {}: {e}",
rec.path.display()
))
})?;
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn record_and_flush_are_noops_when_disabled() {
assert!(!enabled());
record_rows("attn.0.in", &[1.0, 2.0, 3.0, 4.0], 2);
record_row("lm_head.in", &[1.0, 2.0]);
flush().expect("flush is a no-op when disabled");
}
#[test]
fn ragged_and_empty_blocks_are_ignored_not_panicked() {
record_rows("attn.0.in", &[1.0, 2.0, 3.0], 2);
record_rows("attn.0.in", &[], 2);
record_rows("attn.0.in", &[1.0], 0);
}
}