Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@ entry.

### Added

- The `pwm-tui` dashboard layout: a horizontal pipeline flow strip, a dominant
always-visible overview pane (live download gauges with a throughput
sparkline, exporter substage progress, a KPI grid that fills in as the
exporter reports facts, op-mix bars, the commitment table, and the verdict
banner), and a secondary per-stage detail pane with ←/→ navigation.
- **`pwm-tui`: a ratatui demo that proves the REAL pretrained checkpoint end to
end locally, with no Docker** (#200): downloads and sha256-pin-verifies the
`quentinll/lewm-pusht` checkpoint and a real `lerobot/pusht` episode into a
Expand Down
94 changes: 92 additions & 2 deletions crates/pwm-tui/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
//! TUI and `--headless` can never disagree about what happened.

use std::collections::VecDeque;
use std::time::Instant;

use pwm_testkit::report::PredictorReport;

Expand Down Expand Up @@ -32,6 +33,53 @@ pub(crate) struct ExportSubRow {
pub(crate) ms: Option<f64>,
}

/// Typed key facts parsed out of the exporter's kv stream, so the dashboard
/// can show live numbers before the stage-2 report exists.
#[derive(Default)]
pub(crate) struct Dash {
/// Checkpoint size in bytes (load stage).
pub(crate) checkpoint_bytes: Option<u64>,
/// Checkpoint tensor count (load stage).
pub(crate) tensors: Option<u64>,
/// Checkpoint float parameter count (load stage).
pub(crate) float_params: Option<u64>,
/// Quantized matrices in the exported manifest (quantize stage).
pub(crate) matrices: Option<u64>,
/// Exported int8 parameter count (quantize stage; wider scope than the
/// proven relation, which the report narrows to its own count).
pub(crate) int8_params: Option<u64>,
/// Measured max |int - float| (calibrate stage).
pub(crate) error: Option<f64>,
/// Bundle-carried float tolerance (calibrate stage).
pub(crate) tolerance: Option<f64>,
/// Predictor-scoped weights root carried in the bundle (bundle stage).
pub(crate) weights_root: Option<String>,
/// Predictor bundle size in bytes (bundle stage).
pub(crate) bundle_bytes: Option<u64>,
/// Input provenance string (encode stage).
pub(crate) source: Option<String>,
}

impl Dash {
fn absorb(&mut self, stage: &str, key: &str, value: &str) {
let u = || value.parse::<u64>().ok();
let f = || value.parse::<f64>().ok();
match (stage, key) {
("load", "checkpoint_bytes") => self.checkpoint_bytes = u(),
("load", "tensors") => self.tensors = u(),
("load", "float_params") => self.float_params = u(),
("quantize", "matrices") => self.matrices = u(),
("quantize", "int8_params") => self.int8_params = u(),
("calibrate", "error") => self.error = f(),
("calibrate", "tolerance") => self.tolerance = f(),
("bundle", "weights_root") => self.weights_root = Some(value.to_string()),
("bundle", "predictor_bundle_bytes") => self.bundle_bytes = u(),
("encode", "source") => self.source = Some(value.to_string()),
_ => {}
}
}
}

/// Everything the front ends render.
pub(crate) struct App {
/// Per-stage lifecycle, indexed by [`Stage::idx`].
Expand All @@ -58,6 +106,16 @@ pub(crate) struct App {
pub(crate) cache_root: String,
/// Spinner frame counter (advanced by the draw loop).
pub(crate) spin: usize,
/// Typed dashboard facts parsed from the exporter kv stream.
pub(crate) dash: Dash,
/// Wall-clock start of the run.
pub(crate) started: Instant,
/// Wall-clock end of the run (freezes the elapsed display).
pub(crate) finished_at: Option<Instant>,
/// Per-tick downloaded-bytes deltas, newest last (throughput sparkline).
pub(crate) net_history: Vec<u64>,
/// Total fetched bytes at the previous tick.
last_net: u64,
}

impl App {
Expand All @@ -84,7 +142,33 @@ impl App {
show_logs: false,
cache_root,
spin: 0,
dash: Dash::default(),
started: Instant::now(),
finished_at: None,
net_history: Vec::new(),
last_net: 0,
}
}

/// Advance animation state and sample the download throughput. Called once
/// per draw tick.
pub(crate) fn on_tick(&mut self) {
self.spin = self.spin.wrapping_add(1);
let total: u64 = self.fetch.iter().map(|r| r.done).sum();
let fetching = matches!(self.stages[Stage::Fetch.idx()], StageState::Running);
if fetching {
self.net_history.push(total.saturating_sub(self.last_net));
if self.net_history.len() > 72 {
self.net_history.remove(0);
}
}
self.last_net = total;
}

/// Elapsed wall time, frozen once the pipeline finishes.
pub(crate) fn elapsed_secs(&self) -> f64 {
let end = self.finished_at.unwrap_or_else(Instant::now);
end.duration_since(self.started).as_secs_f64()
}

/// Fold one pipeline event into the state.
Expand Down Expand Up @@ -119,15 +203,21 @@ impl App {
}
}
Event::ExportProgress { done, total } => self.export_progress = Some((done, total)),
Event::Kv { stage, key, value } => self.kvs.push((stage, key, value)),
Event::Kv { stage, key, value } => {
self.dash.absorb(&stage, &key, &value);
self.kvs.push((stage, key, value));
}
Event::Log(line) => {
self.logs.push_back(line);
while self.logs.len() > 500 {
self.logs.pop_front();
}
}
Event::Report(r) => self.report = Some(*r),
Event::Finished { ok } => self.finished = Some(ok),
Event::Finished { ok } => {
self.finished = Some(ok);
self.finished_at = Some(Instant::now());
}
}
}

Expand Down
6 changes: 3 additions & 3 deletions crates/pwm-tui/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,7 @@ fn run_tui(opts: &Options, json: bool) -> ExitCode {
while let Ok(ev) = rx.try_recv() {
app.handle(ev);
}
app.spin = app.spin.wrapping_add(1);
app.on_tick();
if terminal.draw(|f| ui::render(f, &app)).is_err() {
break;
}
Expand All @@ -247,8 +247,8 @@ fn run_tui(opts: &Options, json: bool) -> ExitCode {
break 'outer;
}
(KeyCode::Char('l'), _) => app.show_logs = !app.show_logs,
(KeyCode::Up, _) => app.select(-1),
(KeyCode::Down, _) => app.select(1),
(KeyCode::Up | KeyCode::Left | KeyCode::Char('k'), _) => app.select(-1),
(KeyCode::Down | KeyCode::Right | KeyCode::Char('j'), _) => app.select(1),
(KeyCode::Esc, _) => app.selected = None,
_ => {}
}
Expand Down
16 changes: 7 additions & 9 deletions crates/pwm-tui/src/pipeline.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,15 +67,13 @@ impl Stage {
/// One-line description for the detail panel header.
pub(crate) fn blurb(self) -> &'static str {
match self {
Stage::Fetch => "pin-verified checkpoint + real episode files (cache-first)",
Stage::Export => {
"PyTorch exporter: extract V0, quantize int8, commit, encode, calibrate"
}
Stage::Load => "parse the bundle, bind the export weights root, build the circuit",
Stage::Infer => "exact integer forward pass (the world model runs; no float, no GPU)",
Stage::Commit => "bind execution to the Fiat-Shamir transcript and commitments",
Stage::Verify => "no_std float-free audit: commitments, Freivalds, exact recompute",
Stage::Tamper => "forge one matmul output; the audit must reject it",
Stage::Fetch => "fetch the checkpoint and episode assets, sha256-pinned, cache-first",
Stage::Export => "extract the V0 subgraph, quantize to int8, commit, encode, calibrate",
Stage::Load => "parse the bundle, bind the weights root, build the circuit",
Stage::Infer => "exact integer forward pass, no float, no GPU",
Stage::Commit => "bind the execution to the Fiat-Shamir transcript",
Stage::Verify => "audit: commitments, Freivalds checks, exact recompute",
Stage::Tamper => "forge one matmul output, the audit must reject it",
}
}

Expand Down
Loading
Loading