runtime/benchmark/
runner.rs1use std::path::Path;
4use std::time::Instant;
5
6use anyhow::Result;
7use prettytable::{row, Table};
8use sysinfo::{Pid, System};
9
10use super::{BenchmarkComparison, BenchmarkConfig, BenchmarkResults, GenerationResult, InferenceMetrics};
11
12pub trait InferenceBackend: Send + Sync {
14 fn name(&self) -> &str;
16
17 fn load_model(&mut self, path: &Path) -> Result<f64>;
19
20 fn generate(&mut self, prompt: &str, max_tokens: usize) -> Result<GenerationResult>;
22
23 fn memory_usage(&self) -> u64;
25
26 fn unload(&mut self);
28}
29
30pub fn get_process_memory() -> u64 {
32 let mut sys = System::new_all();
33 let pid = Pid::from_u32(std::process::id());
34 sys.refresh_all();
35
36 sys.process(pid).map(|p| p.memory()).unwrap_or(0)
37}
38
39pub struct BenchmarkRunner {
41 config: BenchmarkConfig,
42}
43
44impl BenchmarkRunner {
45 pub fn new(config: BenchmarkConfig) -> Self {
47 Self { config }
48 }
49
50 pub fn run<B: InferenceBackend>(&self, backend: &mut B) -> Result<BenchmarkResults> {
52 let mut runs = Vec::new();
53
54 let memory_before = get_process_memory();
56
57 println!(" Loading model...");
59 let load_time = backend.load_model(&self.config.model_path)?;
60 println!(" Model loaded in {:.2}ms", load_time);
61
62 let memory_after = get_process_memory();
64 let model_memory = memory_after.saturating_sub(memory_before);
65 println!(
66 " Model memory: {:.2} MB",
67 model_memory as f64 / (1024.0 * 1024.0)
68 );
69
70 println!(
72 " Running {} warmup iterations...",
73 self.config.warmup_iterations
74 );
75 for i in 0..self.config.warmup_iterations {
76 for prompt in &self.config.prompts {
77 let _ = backend.generate(prompt, self.config.max_new_tokens)?;
78 }
79 print!(" Warmup {}/{}\r", i + 1, self.config.warmup_iterations);
80 }
81 println!();
82
83 println!(
85 " Running {} measurement iterations...",
86 self.config.measurement_iterations
87 );
88 let mut peak_memory = memory_after;
89
90 for i in 0..self.config.measurement_iterations {
91 for prompt in &self.config.prompts {
92 let result = backend.generate(prompt, self.config.max_new_tokens)?;
93
94 let current_memory = get_process_memory();
96 peak_memory = peak_memory.max(current_memory);
97
98 let tokens_per_sec = if result.total_time_ms > 0.0 {
99 result.tokens_generated as f64 / (result.total_time_ms / 1000.0)
100 } else {
101 0.0
102 };
103
104 runs.push(InferenceMetrics {
105 load_time_ms: load_time,
106 time_to_first_token_ms: result.time_to_first_token_ms,
107 total_inference_time_ms: result.total_time_ms,
108 tokens_generated: result.tokens_generated,
109 tokens_per_second: tokens_per_sec,
110 prompt_tokens: result.prompt_tokens,
111 memory_before_load_bytes: memory_before,
112 memory_after_load_bytes: memory_after,
113 peak_memory_bytes: peak_memory,
114 });
115 }
116 print!(
117 " Iteration {}/{}\r",
118 i + 1,
119 self.config.measurement_iterations
120 );
121 }
122 println!();
123
124 Ok(BenchmarkResults::aggregate(
125 backend.name(),
126 runs,
127 load_time,
128 peak_memory,
129 ))
130 }
131}
132
133pub fn print_comparison_table(comparison: &BenchmarkComparison) {
135 let mut table = Table::new();
136
137 table.add_row(row![
139 "Metric",
140 "llama.cpp",
141 "UniLLM",
142 "Diff"
143 ]);
144
145 table.add_row(row![
147 "Load Time (ms)",
148 format!("{:.1}", comparison.llama_cpp.load_time_ms),
149 format!("{:.1}", comparison.unillm.load_time_ms),
150 BenchmarkComparison::format_diff(
151 comparison.llama_cpp.load_time_ms,
152 comparison.unillm.load_time_ms
153 )
154 ]);
155
156 table.add_row(row![
158 "TTFT (ms)",
159 format!("{:.1}", comparison.llama_cpp.avg_ttft_ms),
160 format!("{:.1}", comparison.unillm.avg_ttft_ms),
161 BenchmarkComparison::format_diff(
162 comparison.llama_cpp.avg_ttft_ms,
163 comparison.unillm.avg_ttft_ms
164 )
165 ]);
166
167 table.add_row(row![
169 "Tokens/sec (avg)",
170 format!("{:.1}", comparison.llama_cpp.avg_tokens_per_sec),
171 format!("{:.1}", comparison.unillm.avg_tokens_per_sec),
172 BenchmarkComparison::format_diff(
173 comparison.llama_cpp.avg_tokens_per_sec,
174 comparison.unillm.avg_tokens_per_sec
175 )
176 ]);
177
178 table.add_row(row![
180 "Tokens/sec (p50)",
181 format!("{:.1}", comparison.llama_cpp.p50_tokens_per_sec),
182 format!("{:.1}", comparison.unillm.p50_tokens_per_sec),
183 BenchmarkComparison::format_diff(
184 comparison.llama_cpp.p50_tokens_per_sec,
185 comparison.unillm.p50_tokens_per_sec
186 )
187 ]);
188
189 table.add_row(row![
191 "Memory (MB)",
192 format!("{:.1}", comparison.llama_cpp.avg_memory_mb),
193 format!("{:.1}", comparison.unillm.avg_memory_mb),
194 BenchmarkComparison::format_diff(
195 comparison.llama_cpp.avg_memory_mb,
196 comparison.unillm.avg_memory_mb
197 )
198 ]);
199
200 table.add_row(row![
202 "Peak Memory (MB)",
203 format!("{:.1}", comparison.llama_cpp.peak_memory_mb),
204 format!("{:.1}", comparison.unillm.peak_memory_mb),
205 BenchmarkComparison::format_diff(
206 comparison.llama_cpp.peak_memory_mb,
207 comparison.unillm.peak_memory_mb
208 )
209 ]);
210
211 println!("\n╔══════════════════════════════════════════════════════════════╗");
212 println!("║ UniLLM vs llama.cpp Benchmark Results ║");
213 println!("╚══════════════════════════════════════════════════════════════╝\n");
214
215 table.printstd();
216
217 println!("\n--- Notes ---");
219 println!("* UniLLM uses KV caching for efficient autoregressive generation");
220 println!("* UniLLM keeps weights in quantized format (Q4/Q8) for 4-5x memory savings");
221 println!("* UniLLM runs on CPU only (no GPU acceleration yet)");
222 println!("* llama.cpp uses quantized inference with optimized SIMD kernels");
223}
224
225pub fn print_single_results(results: &BenchmarkResults) {
227 println!("\n=== {} Results ===", results.backend_name);
228 println!("Load Time: {:.1} ms", results.load_time_ms);
229 println!("Avg TTFT: {:.1} ms", results.avg_ttft_ms);
230 println!("Avg Tokens/sec: {:.1}", results.avg_tokens_per_sec);
231 println!("P50 Tokens/sec: {:.1}", results.p50_tokens_per_sec);
232 println!("P99 Tokens/sec: {:.1}", results.p99_tokens_per_sec);
233 println!("Avg Memory: {:.1} MB", results.avg_memory_mb);
234 println!("Peak Memory: {:.1} MB", results.peak_memory_mb);
235}