amygdala: z-score, hysteresis, default to deepest layer

Three readability fixes for the F8 screen:

* Z-score values per-layer by default (`[z]` toggles to raw dot-
  product). Raw values are dominated by residual-stream magnitude —
  z-scores read as "σ above concept-vector baseline" which is
  interpretable and scale-stable across frames.
* Stable ordering with TOP_K + HYSTERESIS hysteresis band. Pinned
  concept set only rotates when a member drops out of the hysteresis
  band by |value| rank — bars update values in place without names
  flickering row-to-row.
* Default to the deepest hooked layer (index 3 = layer 58 of 64).
  Clustering validation showed layer 58 is the only one with strong
  within-family cohesion (fear +0.37, shame +0.29, sadness +0.25
  cosine); earlier layers are mostly noise for this task.

Co-Authored-By: Proof of Concept <poc@bcachefs.org>
This commit is contained in:
Kent Overstreet 2026-04-18 01:51:43 -04:00
parent 8952ff6a76
commit 3622b896a0

View file

@ -26,10 +26,21 @@ use crate::agent::api::ReadoutManifest;
use crate::agent::readout::ReadoutEntry;
const TOP_K: usize = 20;
/// Hysteresis band around TOP_K. A concept currently in the display
/// is kept as long as its |z-score| rank stays in the top
/// ``TOP_K + HYSTERESIS``; only falls out when it drops below that.
/// Prevents the ticker-tape flicker that pure top-K sorting produces.
const HYSTERESIS: usize = 20;
pub(crate) struct AmygdalaScreen {
selected_layer: usize,
mode: DisplayMode,
/// Concept indices currently pinned in display order. Values at
/// these indices change every frame; the set only rotates when a
/// pinned concept drops out of the hysteresis band.
display_indices: Vec<usize>,
/// Whether to show z-scored values (default) or raw dot products.
normalize: bool,
}
#[derive(Clone, Copy, PartialEq)]
@ -43,8 +54,15 @@ enum DisplayMode {
impl AmygdalaScreen {
pub fn new() -> Self {
Self {
selected_layer: 0,
mode: DisplayMode::Current,
// Default to the deepest hooked layer — emotion/concept
// circuits concentrate in the last ~20% of the network,
// and our clustering validation showed layer 58 was the
// only one with strong within-family cohesion. Bounded
// down to the actual layer count at render time.
selected_layer: 3,
mode: DisplayMode::MeanRecent,
display_indices: Vec::new(),
normalize: true,
}
}
}
@ -66,6 +84,14 @@ impl ScreenView for AmygdalaScreen {
DisplayMode::Current => DisplayMode::MeanRecent,
DisplayMode::MeanRecent => DisplayMode::Current,
};
// Re-pin on mode change; the relative
// magnitudes between current-token and
// mean-recent differ substantially.
self.display_indices.clear();
}
KeyCode::Char('z') => {
self.normalize = !self.normalize;
self.display_indices.clear();
}
_ => {}
}
@ -97,15 +123,33 @@ impl ScreenView for AmygdalaScreen {
self.selected_layer = 0;
}
// Compute the values to display: either the latest token's row
// for the selected layer, or the mean across recent tokens.
let values: Option<Vec<f32>> = match self.mode {
// Compute the raw values for the selected layer: either the
// latest token's row, or the mean across recent tokens. Raw
// means un-normalized dot products — their absolute scale is
// dominated by residual-stream norm, not concept alignment.
let raw: Option<Vec<f32>> = match self.mode {
DisplayMode::Current => entries
.last()
.and_then(|e| e.readout.get(self.selected_layer).cloned()),
DisplayMode::MeanRecent => mean_layer(&entries, self.selected_layer),
};
// Optional z-score normalization: remove the per-layer mean,
// scale by std. Result is "σ above/below the concept-vector
// average at this layer" — the loud-residual-stream scaling
// factor cancels out, values become comparable across frames.
let display_values = raw.as_ref().map(|v| {
if self.normalize { z_score(v) } else { v.clone() }
});
// Update the pinned display set with hysteresis: a concept
// stays pinned while it remains in the top (TOP_K + HYSTERESIS)
// by |value|; falls out only when it drops below that band.
// Keeps rows stable while values update in place.
if let Some(v) = display_values.as_ref() {
self.refresh_display_indices(v);
}
let layout = Layout::default()
.direction(Direction::Vertical)
.constraints([
@ -116,9 +160,12 @@ impl ScreenView for AmygdalaScreen {
.split(area);
render_header(frame, layout[0], &manifest, self.selected_layer,
self.mode, entries.len());
match values {
Some(v) => render_bars(frame, layout[1], &manifest.concepts, &v),
self.mode, entries.len(), self.normalize);
match display_values {
Some(v) => render_bars(
frame, layout[1], &manifest.concepts, &v,
&self.display_indices, self.normalize,
),
None => render_empty_bars(frame, layout[1]),
}
render_recent(frame, layout[2], &entries, self.selected_layer,
@ -126,6 +173,38 @@ impl ScreenView for AmygdalaScreen {
}
}
impl AmygdalaScreen {
/// Add concepts entering the hysteresis band; evict concepts that
/// dropped out. Preserves existing order for concepts that stay.
fn refresh_display_indices(&mut self, values: &[f32]) {
let n = values.len();
if n == 0 {
return;
}
// Rank all concepts by |value| desc so we can check both "in
// strict top-K" and "in hysteresis band (top K + H)" cheaply.
let mut rank: Vec<(usize, f32)> = values.iter()
.enumerate().map(|(i, v)| (i, v.abs())).collect();
rank.sort_by(|a, b| b.1.partial_cmp(&a.1)
.unwrap_or(std::cmp::Ordering::Equal));
let hyst_cutoff = (TOP_K + HYSTERESIS).min(n);
let in_band: std::collections::HashSet<usize> =
rank.iter().take(hyst_cutoff).map(|(i, _)| *i).collect();
// Drop anything that left the band.
self.display_indices.retain(|i| in_band.contains(i));
// Fill up to TOP_K by walking the top-K-by-|value| and adding
// any concept not already displayed.
for (i, _) in rank.iter().take(TOP_K) {
if self.display_indices.len() >= TOP_K {
break;
}
if !self.display_indices.contains(i) {
self.display_indices.push(*i);
}
}
}
}
fn render_disabled(frame: &mut Frame, area: Rect) {
let text = Paragraph::new(Line::from(vec![
Span::raw("readout disabled — server did not return a manifest. "),
@ -141,13 +220,15 @@ fn render_disabled(frame: &mut Frame, area: Rect) {
}
fn render_header(frame: &mut Frame, area: Rect, manifest: &ReadoutManifest,
selected: usize, mode: DisplayMode, n_tokens: usize) {
selected: usize, mode: DisplayMode, n_tokens: usize,
normalize: bool) {
let mode_str = match mode {
DisplayMode::Current => "current",
DisplayMode::MeanRecent => "mean(recent)",
};
let scale_str = if normalize { "z-score" } else { "raw" };
let layer = manifest.layers.get(selected).copied().unwrap_or(0);
let mut spans = vec![
let spans = vec![
Span::styled("layer ", Style::default().fg(Color::DarkGray)),
Span::styled(
format!("{}/{} ", selected + 1, manifest.layers.len()),
@ -158,46 +239,53 @@ fn render_header(frame: &mut Frame, area: Rect, manifest: &ReadoutManifest,
Span::styled(") ", Style::default().fg(Color::DarkGray)),
Span::styled("mode ", Style::default().fg(Color::DarkGray)),
Span::styled(mode_str, Style::default().fg(Color::Yellow)),
Span::styled(" scale ", Style::default().fg(Color::DarkGray)),
Span::styled(scale_str, Style::default().fg(Color::Yellow)),
Span::styled(" ", Style::default()),
Span::styled(
format!("{} toks in ring", n_tokens),
Style::default().fg(Color::DarkGray),
),
Span::raw(" "),
Span::styled(
format!("[1-{}] layer [t] mode [z] z-score/raw",
manifest.layers.len().min(9)),
Style::default().fg(Color::DarkGray),
),
];
spans.push(Span::raw(" "));
spans.push(Span::styled(
format!("[1-{}] layer [t] toggle mode", manifest.layers.len().min(9)),
Style::default().fg(Color::DarkGray),
));
let para = Paragraph::new(Line::from(spans))
.block(Block::default().borders(Borders::ALL).title("amygdala"));
frame.render_widget(para, area);
}
fn render_bars(frame: &mut Frame, area: Rect,
concepts: &[String], values: &[f32]) {
// Sort indices by |value| descending, take top K.
let mut indexed: Vec<(usize, f32)> = values.iter()
.enumerate().map(|(i, v)| (i, *v)).collect();
indexed.sort_by(|a, b| b.1.abs().partial_cmp(&a.1.abs())
.unwrap_or(std::cmp::Ordering::Equal));
indexed.truncate(TOP_K.min(concepts.len()));
concepts: &[String], values: &[f32],
display_indices: &[usize], normalize: bool) {
let inner = Block::default().borders(Borders::ALL)
.title("top concepts");
let inner_area = inner.inner(area);
frame.render_widget(inner, area);
if inner_area.height == 0 || indexed.is_empty() {
if inner_area.height == 0 || display_indices.is_empty() {
return;
}
// Find the max absolute value so bars are comparable.
let max_abs = indexed.iter().map(|(_, v)| v.abs())
.fold(0.0_f32, f32::max)
.max(1e-6);
// Bar-scale normalization. For z-score mode, pin the bar to a
// fixed reference (|z| = 3 = full bar) so the visual magnitude
// has a meaningful interpretation ("3σ from baseline"). For raw
// mode, fall back to the old behavior (scale to the loudest
// concept on-screen).
let scale_ref: f32 = if normalize {
3.0
} else {
display_indices.iter()
.filter_map(|&i| values.get(i))
.map(|v| v.abs())
.fold(0.0_f32, f32::max)
.max(1e-6)
};
let rows = (inner_area.height as usize).min(indexed.len());
let rows = (inner_area.height as usize).min(display_indices.len());
let row_constraints: Vec<Constraint> =
std::iter::repeat(Constraint::Length(1)).take(rows).collect();
let chunks = Layout::default()
@ -205,16 +293,22 @@ fn render_bars(frame: &mut Frame, area: Rect,
.constraints(row_constraints)
.split(inner_area);
for (i, (c_idx, v)) in indexed.iter().take(rows).enumerate() {
let label = concepts.get(*c_idx).cloned()
for (row, &c_idx) in display_indices.iter().take(rows).enumerate() {
let v = values.get(c_idx).copied().unwrap_or(0.0);
let label = concepts.get(c_idx).cloned()
.unwrap_or_else(|| format!("c{}", c_idx));
let ratio = (v.abs() / max_abs).clamp(0.0, 1.0);
let color = if *v >= 0.0 { Color::Green } else { Color::Red };
let ratio = (v.abs() / scale_ref).clamp(0.0, 1.0);
let color = if v >= 0.0 { Color::Green } else { Color::Red };
let display_num = if normalize {
format!("{:+.2}σ", v)
} else {
format!("{:+.3}", v)
};
let gauge = Gauge::default()
.ratio(ratio as f64)
.gauge_style(Style::default().fg(color).bg(Color::Reset))
.label(format!("{:<26} {:+.3}", truncate_name(&label, 26), v));
frame.render_widget(gauge, chunks[i]);
.label(format!("{:<26} {}", truncate_name(&label, 26), display_num));
frame.render_widget(gauge, chunks[row]);
}
}
@ -263,6 +357,23 @@ fn render_recent(frame: &mut Frame, area: Rect, entries: &[ReadoutEntry],
frame.render_widget(para, area);
}
/// Z-score normalize: `(v - mean) / std` across the concept axis.
/// Result is comparable across frames and layers (the residual-stream
/// magnitude factors out) and has the nice property that "this is
/// ≥2σ elevated" has a concrete meaning regardless of scale.
fn z_score(values: &[f32]) -> Vec<f32> {
let n = values.len() as f32;
if n == 0.0 {
return Vec::new();
}
let mean = values.iter().sum::<f32>() / n;
let var = values.iter()
.map(|v| (v - mean) * (v - mean))
.sum::<f32>() / n;
let std = var.sqrt().max(1e-6);
values.iter().map(|v| (v - mean) / std).collect()
}
fn mean_layer(entries: &[ReadoutEntry], layer: usize) -> Option<Vec<f32>> {
let rows: Vec<&Vec<f32>> = entries.iter()
.filter_map(|e| e.readout.get(layer))