qualia_core_db/inference/inference_bench/
runner.rs1use std::sync::{Arc, Mutex};
7use std::time::{Duration, Instant};
8
9use crate::llm_agent::{AgentBackend, LocalLlmAgent};
10
11use super::metrics::{ms, ns_to_ms, tok_per_s};
12use super::*;
13
14struct RunTiming {
17 ttft: Duration,
18 total: Duration,
19 output_tokens: u64,
20}
21
22fn timed_infer(agent: &LocalLlmAgent, prompt: &str) -> RunTiming {
24 let stamps: Arc<Mutex<Vec<Instant>>> = Arc::new(Mutex::new(Vec::with_capacity(64)));
25 let stamps_cb = Arc::clone(&stamps);
26 let start = Instant::now();
27 let cb = move |_delta: String| {
28 if let Ok(mut v) = stamps_cb.lock() {
29 v.push(Instant::now());
30 }
31 };
32 let (_text, _prov, tokens, _quin) = agent.infer_local_model_streaming(prompt, "", Some(cb));
33 let total = start.elapsed();
34 let v = stamps.lock().map(|g| g.clone()).unwrap_or_default();
35 let ttft = v.first().map(|f| f.duration_since(start)).unwrap_or(total);
36 RunTiming {
37 ttft,
38 total,
39 output_tokens: tokens as u64,
40 }
41}
42
43pub fn run_bench(cfg: &BenchConfig) -> Result<BenchResult, String> {
50 if !std::path::Path::new(&cfg.model_path).exists() {
51 return Err(format!("model not found: {}", cfg.model_path));
52 }
53
54 let agent = LocalLlmAgent::with_local_backend(
55 "did:qualia:bench",
56 AgentBackend::Local {
57 model_path: cfg.model_path.clone(),
58 context_window: 4096,
59 quantization: cfg.quantization.clone(),
60 vision_projector_path: None,
61 modality: "text".into(),
62 architecture: None,
63 },
64 );
65
66 set_decode_budget_override(cfg.decode_tokens);
68
69 crate::resident_model::clear_resident_model();
71 reset_phase_metrics();
72 let cold = timed_infer(&agent, &cfg.prompt);
73
74 let model_id = crate::q_hash(&cfg.model_path);
76 let mut meta = ModelMeta::default();
77 if let Ok(report) = crate::resident_model::mount_resident_gguf(model_id, &cfg.model_path, false)
78 {
79 meta = ModelMeta {
80 n_layer: report.n_layer,
81 n_head: report.n_head,
82 n_kv_head: report.n_kv_head,
83 mapped_bytes: report.mapped_bytes,
84 kv_cache_bytes: report.kv_cache_bytes,
85 directml_enabled: report.directml_enabled,
86 };
87 }
88
89 let repeats = cfg.warm_repeats.max(1);
91 let mut warm_ttft = Duration::ZERO;
92 let mut warm_total = Duration::ZERO;
93 let mut acc_load = 0.0f64;
94 let mut acc_prefill_ns = 0u64;
95 let mut acc_prefill_tok = 0u64;
96 let mut acc_decode_ns = 0u64;
97 let mut acc_decode_tok = 0u64;
98 let mut last_warm = RunTiming {
99 ttft: Duration::ZERO,
100 total: Duration::ZERO,
101 output_tokens: 0,
102 };
103 for _ in 0..repeats {
104 reset_phase_metrics();
105 let w = timed_infer(&agent, &cfg.prompt);
106 let snap = phase_snapshot();
107 warm_ttft += w.ttft;
108 warm_total += w.total;
109 acc_load += ns_to_ms(snap.load_ns);
110 acc_prefill_ns += snap.prefill_ns;
111 acc_prefill_tok += snap.prefill_tokens;
112 acc_decode_ns += snap.decode_ns;
113 acc_decode_tok += snap.decode_tokens;
114 last_warm = w;
115 }
116 let n = repeats as u32;
117
118 crate::resident_model::clear_resident_model();
119 set_decode_budget_override(0); let prompt_tokens = if repeats > 0 {
122 acc_prefill_tok / repeats as u64 + 1 } else {
124 0
125 };
126
127 let shared_gpu = crate::gpu_context::shared_gpu();
128
129 Ok(BenchResult {
130 label: cfg.label.clone(),
131 model_path: cfg.model_path.clone(),
132 quantization: cfg.quantization.clone(),
133 model: meta,
134 gpu: BenchGpuMeta::from_shared_context(shared_gpu),
135 prompt_tokens,
136 output_tokens: last_warm.output_tokens,
137 cold_ttft_ms: ms(cold.ttft),
138 cold_total_ms: ms(cold.total),
139 warm_ttft_ms: ms(warm_ttft) / n as f64,
140 warm_total_ms: ms(warm_total) / n as f64,
141 load_ms: acc_load / n as f64,
142 prefill_ms: ns_to_ms(acc_prefill_ns) / n as f64,
143 prefill_tok_s: tok_per_s(acc_prefill_tok, acc_prefill_ns),
144 decode_ms: ns_to_ms(acc_decode_ns) / n as f64,
145 decode_tok_s: tok_per_s(acc_decode_tok, acc_decode_ns),
146 gpu_timestamp_supported: shared_gpu.timestamps_supported,
150 note: String::new(),
151 })
152}
153
154pub fn run_suite(cfgs: &[BenchConfig]) -> Vec<BenchResult> {
156 let mut out = Vec::new();
157 for c in cfgs {
158 match run_bench(c) {
159 Ok(r) => out.push(r),
160 Err(e) => log::warn!("llm_bench|skip|{}|{}", c.label, e),
161 }
162 }
163 out
164}
165
166pub fn run_suite_blocking(cfgs: &[BenchConfig]) -> Vec<BenchResult> {
172 let rt = tokio::runtime::Builder::new_multi_thread()
173 .worker_threads(2)
174 .enable_all()
175 .build()
176 .expect("tokio multi-thread runtime for llm_bench");
177 rt.block_on(async { run_suite(cfgs) })
178}