qualia_core_db/inference/lab/
timeline.rs1use std::path::Path;
4use std::time::Instant;
5
6use crate::hardware_passport::measure_decode_proxy_tok_s;
7use crate::llm_bench::{
8 phase_snapshot, reset_phase_metrics, reset_resident_path_counts, resident_path_counts,
9};
10use crate::llm_gpu_profiler::{self, Phase};
11
12#[derive(Debug, Clone)]
13pub struct DecodeTimeline {
14 pub model: String,
15 pub tokens: u32,
16 pub tok_s: Option<f64>,
17 pub wall_ms: f64,
18 pub resident_hits: u64,
19 pub resident_fallbacks: u64,
20 pub host_phase_ns: String,
21 pub gpu_phase_ns: String,
22 pub notes: String,
23}
24
25pub fn run_decode_timeline(model: &Path, tokens: u32) -> DecodeTimeline {
27 let tokens = tokens.max(1).min(64);
28 reset_phase_metrics();
29 reset_resident_path_counts();
30 llm_gpu_profiler::set_enabled(true);
31 llm_gpu_profiler::reset();
32
33 let t0 = Instant::now();
34 let tok_s = measure_decode_proxy_tok_s(model, tokens);
35 let wall_ms = t0.elapsed().as_secs_f64() * 1e3;
36 let (hits, falls) = resident_path_counts();
37 let snap = phase_snapshot();
38 let host_phase_ns = format!(
39 "{{\"load_ns\":{},\"prefill_ns\":{},\"prefill_tokens\":{},\"decode_ns\":{},\"decode_tokens\":{},\"decode_forward_ns\":{},\"decode_output_ns\":{}}}",
40 snap.load_ns,
41 snap.prefill_ns,
42 snap.prefill_tokens,
43 snap.decode_ns,
44 snap.decode_tokens,
45 snap.decode_forward_ns,
46 snap.decode_output_ns
47 );
48
49 let mut gpu_parts = Vec::new();
50 for pt in llm_gpu_profiler::snapshot() {
51 if pt.calls > 0 || pt.total_ns > 0 {
52 gpu_parts.push(format!(
53 "\"{}\":{{\"ns\":{},\"calls\":{}}}",
54 pt.phase.label(),
55 pt.total_ns,
56 pt.calls
57 ));
58 }
59 }
60 let gpu_phase_ns = format!("{{{}}}", gpu_parts.join(","));
61
62 let mut notes = Vec::new();
63 if !llm_gpu_profiler::enabled() {
64 notes.push("GPU timestamps not active (device or flag)".into());
65 }
66 if falls > 0 {
67 notes.push(format!("resident fallbacks={falls}"));
68 }
69 if hits == 0 && tok_s.is_some() {
70 notes.push("no resident hits counted - check path".into());
71 }
72 let _ = Phase::COUNT;
73
74 llm_gpu_profiler::set_enabled(false);
75
76 DecodeTimeline {
77 model: model.display().to_string(),
78 tokens,
79 tok_s,
80 wall_ms,
81 resident_hits: hits,
82 resident_fallbacks: falls,
83 host_phase_ns,
84 gpu_phase_ns,
85 notes: notes.join("; "),
86 }
87}
88
89impl DecodeTimeline {
90 pub fn format_report(&self) -> String {
91 format!(
92 "Decode timeline\n model: {}\n tokens: {}\n tok_s: {}\n wall_ms: {:.2}\n resident: hits={} fallbacks={}\n host_ns: {}\n gpu_ns: {}\n notes: {}\n",
93 self.model,
94 self.tokens,
95 self.tok_s
96 .map(|t| format!("{t:.4}"))
97 .unwrap_or_else(|| "fail".into()),
98 self.wall_ms,
99 self.resident_hits,
100 self.resident_fallbacks,
101 self.host_phase_ns,
102 self.gpu_phase_ns,
103 self.notes
104 )
105 }
106}