1#[cfg(feature = "rerun")]
8mod inner {
9 use std::path::PathBuf;
10 use std::sync::atomic::{AtomicBool, Ordering};
11 use std::sync::Arc;
12 use std::thread::{self, JoinHandle};
13 use std::time::Duration;
14
15 use crossbeam::channel::{self, Receiver, Sender};
16 use rerun::{RecordingStream, RecordingStreamBuilder};
17
18 use crate::events::{LayerKind, LogLevel, ProofEvent};
19 use crate::sink::ProofEventSink;
20
21 fn layer_node_path(layer_idx: usize) -> String {
24 format!("gkr/walk/layer_{layer_idx}/node")
25 }
26
27 fn layer_poly_path(layer_idx: usize) -> String {
28 format!("gkr/walk/layer_{layer_idx}/poly")
29 }
30
31 fn layer_claim_path(layer_idx: usize) -> String {
32 format!("gkr/walk/layer_{layer_idx}/claim")
33 }
34
35 fn inference_output_path(layer_idx: usize) -> String {
36 format!("inference/layer_{layer_idx}/output")
37 }
38
39 fn inference_mean_path(layer_idx: usize) -> String {
40 format!("inference/stats/layer_{layer_idx}/mean")
41 }
42
43 fn inference_std_path(layer_idx: usize) -> String {
44 format!("inference/stats/layer_{layer_idx}/std")
45 }
46
47 fn inference_attn_path(layer_idx: usize, head_idx: usize) -> String {
48 format!("inference/layer_{layer_idx}/attn_head_{head_idx}")
49 }
50
51 fn gpu_util_path(device_id: usize) -> String {
52 format!("gpu/device_{device_id}/util")
53 }
54
55 fn gpu_free_path(device_id: usize) -> String {
56 format!("gpu/device_{device_id}/free_gb")
57 }
58
59 fn kind_color(kind: LayerKind) -> rerun::Color {
62 let [r, g, b] = kind.color_rgb();
63 rerun::Color::from_rgb(r, g, b)
64 }
65
66 fn kind_y(kind: LayerKind) -> f32 {
68 match kind {
69 LayerKind::MatMul => 0.0,
70 LayerKind::Activation => 1.8,
71 LayerKind::LayerNorm | LayerKind::RMSNorm => -1.2,
72 LayerKind::Attention => 3.0,
73 LayerKind::Add => 0.8,
74 LayerKind::Mul => 1.2,
75 LayerKind::Embedding => -2.5,
76 LayerKind::Dequantize | LayerKind::Quantize => -1.8,
77 _ => 0.0,
78 }
79 }
80
81 const X_STEP: f32 = 3.0;
83
84 fn layer_x(layer_idx: usize) -> f32 {
85 layer_idx as f32 * X_STEP
86 }
87
88 fn helix_pos(layer_idx: usize) -> [f32; 3] {
91 let angle = layer_idx as f32 * 0.9_f32;
92 let r = 1.8_f32;
93 [layer_x(layer_idx), angle.sin() * r, angle.cos() * r]
94 }
95
96 pub enum RerunConnection {
100 Tcp { addr: String },
102 File { path: PathBuf },
104 Spawn,
106 }
107
108 impl RerunConnection {
109 pub fn from_str(s: &str) -> Self {
114 if s == "spawn" {
115 RerunConnection::Spawn
116 } else if let Some(path) = s.strip_prefix("file:") {
117 RerunConnection::File {
118 path: PathBuf::from(path),
119 }
120 } else {
121 RerunConnection::Tcp { addr: s.to_owned() }
122 }
123 }
124 }
125
126 enum Msg {
129 Event(ProofEvent),
130 Flush,
131 Stop,
132 }
133
134 fn build_recording_stream(
135 conn: RerunConnection,
136 app_id: &str,
137 ) -> Result<RecordingStream, Box<dyn std::error::Error + Send + Sync>> {
138 let builder = RecordingStreamBuilder::new(app_id);
139 match conn {
140 RerunConnection::Tcp { addr } => {
141 let addr_str = addr
143 .strip_prefix("tcp://")
144 .unwrap_or(addr.as_str());
145 let url = if addr_str.starts_with("rerun+") {
147 addr_str.to_owned()
148 } else {
149 format!("rerun+http://{addr_str}/proxy")
150 };
151 let stream = builder.connect_grpc_opts(url)?;
152 Ok(stream)
153 }
154 RerunConnection::File { path } => {
155 let stream = builder.save(path)?;
156 Ok(stream)
157 }
158 RerunConnection::Spawn => {
159 let stream = builder.spawn()?;
160 Ok(stream)
161 }
162 }
163 }
164
165 fn dispatch_event(rec: &RecordingStream, event: &ProofEvent) {
166 match event {
167 ProofEvent::CircuitCompiled { nodes, .. } => {
168 let positions: Vec<[f32; 3]> = nodes
169 .iter()
170 .map(|n| [layer_x(n.layer_idx), kind_y(n.kind), 0.0])
171 .collect();
172 let colors: Vec<rerun::Color> = nodes.iter().map(|n| kind_color(n.kind)).collect();
173 let radii: Vec<rerun::Radius> = nodes
174 .iter()
175 .map(|n| {
176 rerun::Radius::new_ui_points(4.0 + (n.trace_cost as f32).sqrt() * 0.4)
177 })
178 .collect();
179 let labels: Vec<String> = nodes
180 .iter()
181 .map(|n| format!("{:?} [{}]", n.kind, n.layer_idx))
182 .collect();
183 let _ = rec.log_static(
184 "circuit/nodes",
185 &rerun::Points3D::new(positions.clone())
186 .with_colors(colors)
187 .with_radii(radii)
188 .with_labels(labels),
189 );
190
191 let mut edge_strips: Vec<Vec<[f32; 3]>> = Vec::new();
193 for n in nodes {
194 let to = [layer_x(n.layer_idx), kind_y(n.kind), 0.0];
195 for &from_idx in &n.input_layers {
196 if let Some(m) = nodes.iter().find(|m| m.layer_idx == from_idx) {
197 edge_strips.push(vec![
198 [layer_x(m.layer_idx), kind_y(m.kind), 0.0],
199 to,
200 ]);
201 }
202 }
203 }
204 if !edge_strips.is_empty() {
205 let _ = rec.log_static(
206 "circuit/edges",
207 &rerun::LineStrips3D::new(edge_strips)
208 .with_colors(vec![rerun::Color::from_unmultiplied_rgba(
209 0x44, 0x88, 0xcc, 0xaa,
210 )])
211 .with_radii(vec![rerun::Radius::new_ui_points(1.2)]),
212 );
213 }
214 }
215
216 ProofEvent::LayerActivation {
217 layer_idx,
218 output_sample,
219 stats,
220 ..
221 } => {
222 let vals: Vec<f32> = output_sample
223 .iter()
224 .map(|&v| v as f32 / 0x7fff_ffff_u32 as f32)
225 .collect();
226 let n = vals.len();
227 let _ = rec.log(
228 inference_output_path(*layer_idx).as_str(),
229 &rerun::Tensor::new(rerun::TensorData::new(
230 vec![n as u64],
231 rerun::TensorBuffer::F32(vals.into()),
232 )),
233 );
234 let _ = rec.log(
235 inference_mean_path(*layer_idx).as_str(),
236 &rerun::Scalars::single(stats.mean as f64),
237 );
238 let _ = rec.log(
239 inference_std_path(*layer_idx).as_str(),
240 &rerun::Scalars::single(stats.std_dev as f64),
241 );
242 }
243
244 ProofEvent::AttentionHeatmap {
245 layer_idx,
246 head_idx,
247 scores,
248 ..
249 } => {
250 let n = (scores.len() as f64).sqrt() as usize;
251 let _ = rec.log(
252 inference_attn_path(*layer_idx, *head_idx).as_str(),
253 &rerun::Tensor::new(rerun::TensorData::new(
254 vec![n as u64, n as u64],
255 rerun::TensorBuffer::F32(scores.clone().into()),
256 )),
257 );
258 }
259
260 ProofEvent::ProofStart {
261 num_layers,
262 input_shape,
263 output_shape,
264 backend,
265 model_name,
266 } => {
267 let n = *num_layers;
270 let positions: Vec<[f32; 3]> = (0..n).map(helix_pos).collect();
271 let dim = rerun::Color::from_unmultiplied_rgba(72, 74, 92, 200);
272 let labels: Vec<String> = (0..n).map(|i| format!("L{i}")).collect();
273 let _ = rec.log_static(
274 "circuit/nodes",
275 &rerun::Points3D::new(positions.clone())
276 .with_colors(vec![dim; n])
277 .with_radii(vec![rerun::Radius::new_ui_points(5.0); n])
278 .with_labels(labels),
279 );
280 let _ = rec.log_static(
282 "circuit/backbone",
283 &rerun::LineStrips3D::new(vec![positions])
284 .with_colors(vec![rerun::Color::from_unmultiplied_rgba(
285 60, 62, 90, 120,
286 )])
287 .with_radii(vec![rerun::Radius::new_ui_points(1.0)]),
288 );
289
290 let _ = rec.log("proof/progress", &rerun::Scalars::single(0.0_f64));
291 let _ = rec.log(
292 "logs/proof",
293 &rerun::TextLog::new(format!(
294 "ProofStart: {backend}, {n} layers, \
295 input={input_shape:?} output={output_shape:?}, model={model_name:?}"
296 )),
297 );
298 }
299
300 ProofEvent::LayerStart {
301 layer_idx,
302 kind,
303 claim_value_approx,
304 gpu_device,
305 trace_cost,
306 ..
307 } => {
308 let [hx, hy, hz] = helix_pos(*layer_idx);
309 let ky = kind_y(*kind);
310 let [r, g, b] = kind.color_rgb();
311 let radius =
312 rerun::Radius::new_ui_points(6.0 + (*trace_cost as f32).sqrt() * 0.2);
313
314 let _ = rec.log(
316 layer_node_path(*layer_idx).as_str(),
317 &rerun::Points3D::new(vec![[hx, hy + ky, hz]])
318 .with_colors(vec![rerun::Color::from_rgb(r, g, b)])
319 .with_radii(vec![radius])
320 .with_labels(vec![format!("{kind:?} [{layer_idx}]")]),
321 );
322 let _ = rec.log(
324 "gkr/walk/cursor",
325 &rerun::Points3D::new(vec![[hx, hy + ky + 0.7, hz]])
326 .with_colors(vec![rerun::Color::from_rgb(0x00, 0xe5, 0xff)])
327 .with_radii(vec![rerun::Radius::new_ui_points(6.0)])
328 .with_labels(vec![format!("proving {kind:?}...")]),
329 );
330 let _ = rec.log("gkr/claim", &rerun::Scalars::single(*claim_value_approx as f64));
331 let _ = rec.log(
332 "logs/gkr",
333 &rerun::TextLog::new(format!(
334 "-> [{layer_idx}] {kind:?} claim={claim_value_approx:.6} gpu={gpu_device:?}"
335 )),
336 );
337 }
338
339 ProofEvent::SumcheckRound {
340 layer_idx,
341 round,
342 total_rounds,
343 poly_deg2,
344 claim_value_approx,
345 ..
346 } => {
347 let _ = rec.log(
349 layer_claim_path(*layer_idx).as_str(),
350 &rerun::Scalars::single(*claim_value_approx as f64),
351 );
352
353 {
357 let [hx, hy, hz] = helix_pos(*layer_idx);
358 let frac = *round as f32 / (*total_rounds).max(1) as f32;
359
360 let bar_h = (claim_value_approx.abs() * 4.0).clamp(0.1, 4.0);
362
363 let spread = *total_rounds as f32 * 0.38;
365 let x_off = (frac - 0.5) * spread;
366
367 let base = [hx + x_off, hy, hz];
369 let top = [hx + x_off, hy + bar_h, hz];
370
371 let cap_w = 0.18_f32;
373 let cap_l = [hx + x_off - cap_w, hy + bar_h, hz];
374 let cap_r = [hx + x_off + cap_w, hy + bar_h, hz];
375
376 let r_col = (frac * 210.0) as u8 + 45;
378 let b_col = ((1.0 - frac) * 210.0) as u8 + 45;
379 let color = rerun::Color::from_rgb(r_col, 0x55, b_col);
380
381 let _ = rec.log(
382 format!("gkr/walk/layer_{layer_idx}/bar_{round}").as_str(),
383 &rerun::LineStrips3D::new(vec![
384 vec![base, top], vec![cap_l, cap_r], ])
387 .with_colors(vec![color])
388 .with_radii(vec![rerun::Radius::new_ui_points(2.2)]),
389 );
390 }
391
392 if *round == 0 || round + 1 == *total_rounds {
393 let _ = rec.log(
394 "logs/gkr",
395 &rerun::TextLog::new(format!(
396 " [{layer_idx}] round {}/{total_rounds} claim={claim_value_approx:.6}",
397 round + 1
398 )),
399 );
400 }
401 }
402
403 ProofEvent::LayerEnd {
404 layer_idx,
405 kind,
406 final_claim_value_approx,
407 duration_ms,
408 ..
409 } => {
410 let [hx, hy, hz] = helix_pos(*layer_idx);
411 let _ = rec.log(
413 layer_node_path(*layer_idx).as_str(),
414 &rerun::Points3D::new(vec![[hx, hy, hz]])
415 .with_colors(vec![rerun::Color::from_rgb(0x00, 0xe6, 0x76)])
416 .with_radii(vec![rerun::Radius::new_ui_points(6.0)])
417 .with_labels(vec![format!("{kind:?}")]),
418 );
419 let trail: Vec<[f32; 3]> = (0..=*layer_idx).map(helix_pos).collect();
421 let _ = rec.log(
422 "gkr/walk/progress_trail",
423 &rerun::LineStrips3D::new(vec![trail])
424 .with_colors(vec![rerun::Color::from_unmultiplied_rgba(
425 0x00, 0xe6, 0x76, 0xcc,
426 )])
427 .with_radii(vec![rerun::Radius::new_ui_points(2.5)]),
428 );
429 let _ = rec.log(
430 "logs/gkr",
431 &rerun::TextLog::new(format!(
432 "[{layer_idx}] proved {kind:?} in {duration_ms}ms claim={final_claim_value_approx:.6}"
433 )),
434 );
435 }
436
437 ProofEvent::ProofComplete {
438 duration_ms,
439 num_layer_proofs,
440 weight_binding_mode,
441 ..
442 } => {
443 let _ = rec.log("proof/progress", &rerun::Scalars::single(1.0_f64));
444 let _ = rec.log(
446 "gkr/walk/cursor",
447 &rerun::Points3D::new(vec![[0.0_f32, -30.0, -30.0]])
448 .with_colors(vec![rerun::Color::from_unmultiplied_rgba(0, 0, 0, 0)])
449 .with_radii(vec![rerun::Radius::new_ui_points(0.1)]),
450 );
451 let _ = rec.log(
452 "logs/proof",
453 &rerun::TextLog::new(format!(
454 "PROOF COMPLETE {duration_ms}ms {num_layer_proofs} layers {weight_binding_mode}"
455 )),
456 );
457 }
458
459 ProofEvent::WeightOpeningStart {
460 weight_node_id,
461 eval_point_len,
462 } => {
463 let _ = rec.log(
464 "logs/weights",
465 &rerun::TextLog::new(format!(
466 "WeightOpening node={weight_node_id}, eval_dim={eval_point_len}"
467 )),
468 );
469 }
470
471 ProofEvent::WeightOpeningEnd {
472 weight_node_id,
473 duration_ms,
474 commitment_hex,
475 } => {
476 let _ = rec.log(
477 "logs/weights",
478 &rerun::TextLog::new(format!(
479 "WeightOpening done node={weight_node_id}, {duration_ms}ms, commit={commitment_hex}"
480 )),
481 );
482 }
483
484 ProofEvent::AggregatedBindingStart {
485 num_claims,
486 num_matrices,
487 } => {
488 let _ = rec.log(
489 "logs/proof",
490 &rerun::TextLog::new(format!(
491 "AggregatedBinding: {num_claims} claims, {num_matrices} matrices"
492 )),
493 );
494 }
495
496 ProofEvent::AggregatedBindingEnd {
497 duration_ms,
498 estimated_calldata_felts,
499 } => {
500 let _ = rec.log("proof/binding_ms", &rerun::Scalars::single(*duration_ms as f64));
501 let _ = rec.log(
502 "logs/proof",
503 &rerun::TextLog::new(format!(
504 "AggregatedBinding done: {duration_ms}ms, ~{estimated_calldata_felts} felts"
505 )),
506 );
507 }
508
509 ProofEvent::GpuStatus {
510 devices,
511 matmul_done,
512 matmul_total,
513 layers_done,
514 layers_total,
515 } => {
516 let positions: Vec<[f32; 3]> = devices
517 .iter()
518 .enumerate()
519 .map(|(i, _)| [i as f32 * 2.0, 0.0, 2.0])
520 .collect();
521 let colors: Vec<rerun::Color> = devices
522 .iter()
523 .map(|d| {
524 let r = (d.utilization * 255.0) as u8;
525 let g = ((1.0 - d.utilization) * 200.0) as u8;
526 rerun::Color::from_rgb(r, g, 0x40)
527 })
528 .collect();
529 let _ = rec.log(
530 "gpu/cluster",
531 &rerun::Points3D::new(positions)
532 .with_colors(colors)
533 .with_radii(vec![rerun::Radius::new_ui_points(10.0)]),
534 );
535 for d in devices {
536 let _ = rec.log(
537 gpu_util_path(d.device_id).as_str(),
538 &rerun::Scalars::single(d.utilization as f64),
539 );
540 if let Some(free) = d.free_memory_bytes {
541 let _ = rec.log(
542 gpu_free_path(d.device_id).as_str(),
543 &rerun::Scalars::single(free as f64 / 1e9),
544 );
545 }
546 }
547 if *layers_total > 0 {
548 let _ = rec.log(
549 "proof/progress",
550 &rerun::Scalars::single(*layers_done as f64 / *layers_total as f64),
551 );
552 }
553 let _ = rec.log(
554 "logs/proof",
555 &rerun::TextLog::new(format!(
556 "GPU: matmul {matmul_done}/{matmul_total}, layers {layers_done}/{layers_total}"
557 )),
558 );
559 }
560
561 ProofEvent::StarkProofStart {
562 num_activation_layers,
563 num_add_layers,
564 num_layernorm_layers,
565 } => {
566 let _ = rec.log(
567 "logs/stark",
568 &rerun::TextLog::new(format!(
569 "StarkProofStart: activations={num_activation_layers}, add={num_add_layers}, layernorm={num_layernorm_layers}"
570 )),
571 );
572 }
573
574 ProofEvent::StarkProofEnd { duration_ms } => {
575 let _ = rec.log(
576 "logs/stark",
577 &rerun::TextLog::new(format!("StarkProofEnd: {duration_ms}ms")),
578 );
579 }
580
581 ProofEvent::Log { level, message } => {
582 let level_str = match level {
583 LogLevel::Trace | LogLevel::Debug => rerun::TextLogLevel::TRACE,
584 LogLevel::Info => rerun::TextLogLevel::INFO,
585 LogLevel::Warn => rerun::TextLogLevel::WARN,
586 LogLevel::Error => rerun::TextLogLevel::ERROR,
587 };
588 let _ = rec.log(
589 "logs/proof",
590 &rerun::TextLog::new(message.as_str()).with_level(level_str),
591 );
592 }
593 }
594 }
595
596 fn background_worker(
597 rx: Receiver<Msg>,
598 conn: RerunConnection,
599 app_id: String,
600 ready: Arc<AtomicBool>,
601 ) {
602 let rec = match build_recording_stream(conn, &app_id) {
603 Ok(r) => r,
604 Err(e) => {
605 eprintln!("[proof-stream] Failed to connect to Rerun: {e}");
606 ready.store(true, Ordering::SeqCst);
607 return;
608 }
609 };
610
611 crate::blueprint::send_blueprint(&rec);
613
614 ready.store(true, Ordering::SeqCst);
615
616 for msg in &rx {
617 match msg {
618 Msg::Event(event) => dispatch_event(&rec, &event),
619 Msg::Flush => {
620 let _ = rec.flush_blocking();
621 }
622 Msg::Stop => break,
623 }
624 }
625
626 let _ = rec.flush_blocking();
627 }
628
629 pub struct RerunSink {
633 tx: Sender<Msg>,
634 _handle: Option<JoinHandle<()>>,
635 }
636
637 impl RerunSink {
638 pub fn connect(
640 conn: RerunConnection,
641 app_id: impl Into<String>,
642 ) -> Result<Self, Box<dyn std::error::Error + Send + Sync>> {
643 let (tx, rx) = channel::bounded::<Msg>(8192);
644 let app_id = app_id.into();
645 let ready = Arc::new(AtomicBool::new(false));
646 let ready2 = Arc::clone(&ready);
647
648 let handle = thread::Builder::new()
649 .name("proof-stream-rerun".into())
650 .spawn(move || background_worker(rx, conn, app_id, ready2))?;
651
652 let deadline = std::time::Instant::now() + Duration::from_secs(10);
654 while !ready.load(Ordering::SeqCst) && std::time::Instant::now() < deadline {
655 thread::sleep(Duration::from_millis(20));
656 }
657
658 Ok(Self {
659 tx,
660 _handle: Some(handle),
661 })
662 }
663 }
664
665 impl ProofEventSink for RerunSink {
666 #[inline]
667 fn emit(&self, event: ProofEvent) {
668 let _ = self.tx.try_send(Msg::Event(event));
669 }
670
671 fn flush(&self) {
672 let _ = self.tx.send(Msg::Flush);
673 }
674 }
675
676 impl Drop for RerunSink {
677 fn drop(&mut self) {
678 let _ = self.tx.send(Msg::Stop);
679 if let Some(h) = self._handle.take() {
680 let _ = h.join();
681 }
682 }
683 }
684}
685
686#[cfg(feature = "rerun")]
689pub use inner::{RerunConnection, RerunSink};
690
691#[cfg(not(feature = "rerun"))]
694pub struct RerunSink;
695
696#[cfg(not(feature = "rerun"))]
697pub enum RerunConnection {
698 Tcp { addr: String },
699 File { path: std::path::PathBuf },
700 Spawn,
701}
702
703#[cfg(not(feature = "rerun"))]
704impl RerunConnection {
705 pub fn from_str(s: &str) -> Self {
706 if s == "spawn" {
707 RerunConnection::Spawn
708 } else if let Some(path) = s.strip_prefix("file:") {
709 RerunConnection::File {
710 path: std::path::PathBuf::from(path),
711 }
712 } else {
713 RerunConnection::Tcp { addr: s.to_owned() }
714 }
715 }
716}