Coverage Report

Created: 2026-01-25 15:05

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/home/noah/src/realizar/src/bench_viz.rs
Line
Count
Source
1
//! Benchmark Visualization Module (PAR-040)
2
//!
3
//! Creates 2×3 grid visualizations for inference benchmark comparisons
4
//! and generates profiling logs suitable for chat paste debugging.
5
//!
6
//! ## Layout
7
//!
8
//! ```text
9
//! ┌─────────────────────────────────────────────────────────────────────┐
10
//! │              GGUF Inference Comparison (tok/s GPU)                  │
11
//! ├─────────────────────┬─────────────────────┬─────────────────────────┤
12
//! │   APR serve GGUF    │      Ollama         │      llama.cpp          │
13
//! ├─────────────────────┴─────────────────────┴─────────────────────────┤
14
//! │              APR Server Format Comparison (tok/s GPU)               │
15
//! ├─────────────────────┬─────────────────────┬─────────────────────────┤
16
//! │   APR serve .apr    │  APR serve GGUF     │ Ollama / llama.cpp      │
17
//! └─────────────────────┴─────────────────────┴─────────────────────────┘
18
//! ```
19
20
use std::fmt::Write as FmtWrite;
21
use std::time::{Duration, Instant};
22
23
// ============================================================================
24
// Benchmark Result Types
25
// ============================================================================
26
27
/// Single benchmark measurement
28
#[derive(Debug, Clone)]
29
pub struct BenchMeasurement {
30
    /// Engine name (APR, Ollama, llama.cpp)
31
    pub engine: String,
32
    /// Format (GGUF, APR)
33
    pub format: String,
34
    /// Throughput in tokens/second
35
    pub tokens_per_sec: f64,
36
    /// Time to first token in milliseconds
37
    pub ttft_ms: f64,
38
    /// Number of tokens generated
39
    pub tokens_generated: usize,
40
    /// Total duration
41
    pub duration: Duration,
42
    /// GPU utilization percentage (if available)
43
    pub gpu_util: Option<f64>,
44
    /// GPU memory used in MB (if available)
45
    pub gpu_mem_mb: Option<f64>,
46
}
47
48
impl BenchMeasurement {
49
    /// Create a new benchmark measurement
50
18
    pub fn new(engine: &str, format: &str) -> Self {
51
18
        Self {
52
18
            engine: engine.to_string(),
53
18
            format: format.to_string(),
54
18
            tokens_per_sec: 0.0,
55
18
            ttft_ms: 0.0,
56
18
            tokens_generated: 0,
57
18
            duration: Duration::ZERO,
58
18
            gpu_util: None,
59
18
            gpu_mem_mb: None,
60
18
        }
61
18
    }
62
63
    /// Set throughput
64
    #[must_use]
65
13
    pub fn with_throughput(mut self, tps: f64) -> Self {
66
13
        self.tokens_per_sec = tps;
67
13
        self
68
13
    }
69
70
    /// Set TTFT
71
    #[must_use]
72
8
    pub fn with_ttft(mut self, ttft_ms: f64) -> Self {
73
8
        self.ttft_ms = ttft_ms;
74
8
        self
75
8
    }
76
77
    /// Set tokens generated
78
    #[must_use]
79
2
    pub fn with_tokens(mut self, count: usize, duration: Duration) -> Self {
80
2
        self.tokens_generated = count;
81
2
        self.duration = duration;
82
2
        if duration.as_secs_f64() > 0.0 {
83
1
            self.tokens_per_sec = count as f64 / duration.as_secs_f64();
84
1
        }
85
2
        self
86
2
    }
87
88
    /// Set GPU metrics
89
    #[must_use]
90
3
    pub fn with_gpu(mut self, util: f64, mem_mb: f64) -> Self {
91
3
        self.gpu_util = Some(util);
92
3
        self.gpu_mem_mb = Some(mem_mb);
93
3
        self
94
3
    }
95
}
96
97
/// Profiling hotspot for debugging
98
#[derive(Debug, Clone)]
99
pub struct ProfilingHotspot {
100
    /// Component name
101
    pub component: String,
102
    /// Time spent
103
    pub time: Duration,
104
    /// Percentage of total
105
    pub percentage: f64,
106
    /// Call count
107
    pub call_count: u64,
108
    /// Average time per call
109
    pub avg_per_call: Duration,
110
    /// Explanation/recommendation
111
    pub explanation: String,
112
    /// Is this expected for inference?
113
    pub is_expected: bool,
114
}
115
116
impl ProfilingHotspot {
117
    /// Format as single-line report
118
1
    pub fn to_line(&self) -> String {
119
1
        let marker = if self.is_expected { "✓" } else { 
"⚠"0
};
120
1
        format!(
121
1
            "{} {:20} {:>6.1}% {:>8.2}ms ({:>6} calls, {:>6.2}µs/call)",
122
            marker,
123
            self.component,
124
            self.percentage,
125
1
            self.time.as_secs_f64() * 1000.0,
126
            self.call_count,
127
1
            self.avg_per_call.as_secs_f64() * 1_000_000.0
128
        )
129
1
    }
130
}
131
132
// ============================================================================
133
// Benchmark Grid (2×3)
134
// ============================================================================
135
136
/// 2×3 Benchmark comparison grid
137
#[derive(Debug, Clone, Default)]
138
pub struct BenchmarkGrid {
139
    /// Row 1, Col 1: APR server serving GGUF format
140
    pub gguf_apr: Option<BenchMeasurement>,
141
    /// Row 1, Col 2: Ollama serving GGUF format
142
    pub gguf_ollama: Option<BenchMeasurement>,
143
    /// Row 1, Col 3: llama.cpp serving GGUF format
144
    pub gguf_llamacpp: Option<BenchMeasurement>,
145
146
    /// Row 2, Col 1: APR server serving native .apr format
147
    pub apr_native: Option<BenchMeasurement>,
148
    /// Row 2, Col 2: APR server serving GGUF (for comparison)
149
    pub apr_gguf: Option<BenchMeasurement>,
150
    /// Row 2, Col 3: Baseline measurement (Ollama/llama.cpp)
151
    pub apr_baseline: Option<BenchMeasurement>,
152
153
    /// Profiling hotspots
154
    pub hotspots: Vec<ProfilingHotspot>,
155
156
    /// Model name
157
    pub model_name: String,
158
    /// Model parameters (e.g., "0.5B")
159
    pub model_params: String,
160
    /// Quantization type (e.g., "Q4_K_M")
161
    pub quantization: String,
162
163
    /// GPU name
164
    pub gpu_name: String,
165
    /// GPU VRAM in GB
166
    pub gpu_vram_gb: f64,
167
}
168
169
impl BenchmarkGrid {
170
    /// Create new benchmark grid
171
8
    pub fn new() -> Self {
172
8
        Self::default()
173
8
    }
174
175
    /// Set model info
176
    #[must_use]
177
3
    pub fn with_model(mut self, name: &str, params: &str, quant: &str) -> Self {
178
3
        self.model_name = name.to_string();
179
3
        self.model_params = params.to_string();
180
3
        self.quantization = quant.to_string();
181
3
        self
182
3
    }
183
184
    /// Set GPU info
185
    #[must_use]
186
3
    pub fn with_gpu(mut self, name: &str, vram_gb: f64) -> Self {
187
3
        self.gpu_name = name.to_string();
188
3
        self.gpu_vram_gb = vram_gb;
189
3
        self
190
3
    }
191
192
    /// Add GGUF row measurements
193
1
    pub fn set_gguf_row(
194
1
        &mut self,
195
1
        apr: BenchMeasurement,
196
1
        ollama: BenchMeasurement,
197
1
        llamacpp: BenchMeasurement,
198
1
    ) {
199
1
        self.gguf_apr = Some(apr);
200
1
        self.gguf_ollama = Some(ollama);
201
1
        self.gguf_llamacpp = Some(llamacpp);
202
1
    }
203
204
    /// Add APR row measurements
205
1
    pub fn set_apr_row(
206
1
        &mut self,
207
1
        native: BenchMeasurement,
208
1
        gguf: BenchMeasurement,
209
1
        baseline: BenchMeasurement,
210
1
    ) {
211
1
        self.apr_native = Some(native);
212
1
        self.apr_gguf = Some(gguf);
213
1
        self.apr_baseline = Some(baseline);
214
1
    }
215
216
    /// Add profiling hotspot
217
5
    pub fn add_hotspot(&mut self, hotspot: ProfilingHotspot) {
218
5
        self.hotspots.push(hotspot);
219
5
    }
220
221
    // ========================================================================
222
    // Terminal Visualization (ASCII)
223
    // ========================================================================
224
225
    /// Render as ASCII grid for terminal
226
1
    pub fn render_ascii(&self) -> String {
227
1
        let mut out = String::new();
228
229
        // Header
230
1
        writeln!(
231
1
            out,
232
1
            "╔═══════════════════════════════════════════════════════════════════════╗"
233
        )
234
1
        .expect("failed to write benchmark output");
235
1
        writeln!(
236
1
            out,
237
1
            "║           INFERENCE BENCHMARK COMPARISON (tok/s GPU)                  ║"
238
        )
239
1
        .expect("failed to write benchmark output");
240
1
        writeln!(
241
1
            out,
242
1
            "║  Model: {:30} Quant: {:10}         ║",
243
1
            truncate(&self.model_name, 30),
244
1
            truncate(&self.quantization, 10)
245
        )
246
1
        .expect("failed to write benchmark output");
247
1
        writeln!(
248
1
            out,
249
1
            "║  GPU: {:35} VRAM: {:5.1}GB              ║",
250
1
            truncate(&self.gpu_name, 35),
251
            self.gpu_vram_gb
252
        )
253
1
        .expect("failed to write benchmark output");
254
1
        writeln!(
255
1
            out,
256
1
            "╠═══════════════════════════════════════════════════════════════════════╣"
257
        )
258
1
        .expect("failed to write benchmark output");
259
260
        // Row 1: GGUF comparison
261
1
        writeln!(
262
1
            out,
263
1
            "║                    GGUF Format Inference                              ║"
264
        )
265
1
        .expect("failed to write benchmark output");
266
1
        writeln!(
267
1
            out,
268
1
            "╠═══════════════════════╦═══════════════════════╦═══════════════════════╣"
269
        )
270
1
        .expect("failed to write benchmark output");
271
1
        writeln!(
272
1
            out,
273
1
            "║    APR serve GGUF     ║       Ollama          ║      llama.cpp        ║"
274
        )
275
1
        .expect("failed to write benchmark output");
276
1
        writeln!(
277
1
            out,
278
1
            "╠═══════════════════════╬═══════════════════════╬═══════════════════════╣"
279
        )
280
1
        .expect("failed to write benchmark output");
281
282
1
        let gguf_apr_tps = self.gguf_apr.as_ref().map_or(0.0, |m| m.tokens_per_sec);
283
1
        let gguf_ollama_tps = self.gguf_ollama.as_ref().map_or(0.0, |m| m.tokens_per_sec);
284
1
        let gguf_llamacpp_tps = self
285
1
            .gguf_llamacpp
286
1
            .as_ref()
287
1
            .map_or(0.0, |m| m.tokens_per_sec);
288
289
1
        writeln!(
290
1
            out,
291
1
            "║  {:>8.1} tok/s      ║  {:>8.1} tok/s      ║  {:>8.1} tok/s      ║",
292
            gguf_apr_tps, gguf_ollama_tps, gguf_llamacpp_tps
293
        )
294
1
        .expect("failed to write benchmark output");
295
296
        // Bar visualization
297
1
        let max_tps = [gguf_apr_tps, gguf_ollama_tps, gguf_llamacpp_tps]
298
1
            .iter()
299
1
            .cloned()
300
1
            .fold(1.0, f64::max);
301
302
1
        writeln!(
303
1
            out,
304
1
            "║  {}  ║  {}  ║  {}  ║",
305
1
            render_bar(gguf_apr_tps, max_tps, 17),
306
1
            render_bar(gguf_ollama_tps, max_tps, 17),
307
1
            render_bar(gguf_llamacpp_tps, max_tps, 17)
308
        )
309
1
        .expect("failed to write benchmark output");
310
311
        // TTFT
312
1
        let gguf_apr_ttft = self.gguf_apr.as_ref().map_or(0.0, |m| m.ttft_ms);
313
1
        let gguf_ollama_ttft = self.gguf_ollama.as_ref().map_or(0.0, |m| m.ttft_ms);
314
1
        let gguf_llamacpp_ttft = self.gguf_llamacpp.as_ref().map_or(0.0, |m| m.ttft_ms);
315
316
1
        writeln!(
317
1
            out,
318
1
            "║  TTFT: {:>6.1}ms      ║  TTFT: {:>6.1}ms      ║  TTFT: {:>6.1}ms      ║",
319
            gguf_apr_ttft, gguf_ollama_ttft, gguf_llamacpp_ttft
320
        )
321
1
        .expect("failed to write benchmark output");
322
323
        // Row 2: APR server comparison
324
1
        writeln!(
325
1
            out,
326
1
            "╠═══════════════════════╩═══════════════════════╩═══════════════════════╣"
327
        )
328
1
        .expect("failed to write benchmark output");
329
1
        writeln!(
330
1
            out,
331
1
            "║                   APR Server Format Comparison                        ║"
332
        )
333
1
        .expect("failed to write benchmark output");
334
1
        writeln!(
335
1
            out,
336
1
            "╠═══════════════════════╦═══════════════════════╦═══════════════════════╣"
337
        )
338
1
        .expect("failed to write benchmark output");
339
1
        writeln!(
340
1
            out,
341
1
            "║   APR serve .apr      ║   APR serve GGUF      ║  Ollama (baseline)    ║"
342
        )
343
1
        .expect("failed to write benchmark output");
344
1
        writeln!(
345
1
            out,
346
1
            "╠═══════════════════════╬═══════════════════════╬═══════════════════════╣"
347
        )
348
1
        .expect("failed to write benchmark output");
349
350
1
        let apr_native_tps = self.apr_native.as_ref().map_or(0.0, |m| m.tokens_per_sec);
351
1
        let apr_gguf_tps = self.apr_gguf.as_ref().map_or(0.0, |m| m.tokens_per_sec);
352
1
        let apr_baseline_tps = self.apr_baseline.as_ref().map_or(0.0, |m| m.tokens_per_sec);
353
354
1
        writeln!(
355
1
            out,
356
1
            "║  {:>8.1} tok/s      ║  {:>8.1} tok/s      ║  {:>8.1} tok/s      ║",
357
            apr_native_tps, apr_gguf_tps, apr_baseline_tps
358
        )
359
1
        .expect("failed to write benchmark output");
360
361
1
        let max_tps2 = [apr_native_tps, apr_gguf_tps, apr_baseline_tps]
362
1
            .iter()
363
1
            .cloned()
364
1
            .fold(1.0, f64::max);
365
366
1
        writeln!(
367
1
            out,
368
1
            "║  {}  ║  {}  ║  {}  ║",
369
1
            render_bar(apr_native_tps, max_tps2, 17),
370
1
            render_bar(apr_gguf_tps, max_tps2, 17),
371
1
            render_bar(apr_baseline_tps, max_tps2, 17)
372
        )
373
1
        .expect("failed to write benchmark output");
374
375
        // Speedup vs baseline
376
1
        let speedup_native = if apr_baseline_tps > 0.0 {
377
1
            apr_native_tps / apr_baseline_tps
378
        } else {
379
0
            0.0
380
        };
381
1
        let speedup_gguf = if apr_baseline_tps > 0.0 {
382
1
            apr_gguf_tps / apr_baseline_tps
383
        } else {
384
0
            0.0
385
        };
386
387
1
        writeln!(
388
1
            out,
389
1
            "║  vs Ollama: {:>5.2}x   ║  vs Ollama: {:>5.2}x   ║  (baseline)           ║",
390
            speedup_native, speedup_gguf
391
        )
392
1
        .expect("failed to write benchmark output");
393
394
1
        writeln!(
395
1
            out,
396
1
            "╚═══════════════════════╩═══════════════════════╩═══════════════════════╝"
397
        )
398
1
        .expect("failed to write benchmark output");
399
400
1
        out
401
1
    }
402
403
    // ========================================================================
404
    // Profiling Log for Chat Paste
405
    // ========================================================================
406
407
    /// Generate profiling log suitable for chat paste
408
1
    pub fn render_profiling_log(&self) -> String {
409
1
        let mut out = String::new();
410
411
1
        writeln!(out, "```").expect("failed to write benchmark output");
412
1
        writeln!(
413
1
            out,
414
1
            "═══════════════════════════════════════════════════════════════════════"
415
        )
416
1
        .expect("failed to write benchmark output");
417
1
        writeln!(out, "INFERENCE PROFILING REPORT").expect("failed to write benchmark output");
418
1
        writeln!(
419
1
            out,
420
1
            "═══════════════════════════════════════════════════════════════════════"
421
        )
422
1
        .expect("failed to write benchmark output");
423
1
        writeln!(out).expect("failed to write benchmark output");
424
425
        // Model & Hardware
426
1
        writeln!(out, "MODEL: {} ({})", self.model_name, self.model_params)
427
1
            .expect("failed to write benchmark output");
428
1
        writeln!(out, "QUANT: {}", self.quantization).expect("failed to write benchmark output");
429
1
        writeln!(
430
1
            out,
431
1
            "GPU:   {} ({:.1}GB VRAM)",
432
            self.gpu_name, self.gpu_vram_gb
433
        )
434
1
        .expect("failed to write benchmark output");
435
1
        writeln!(out).expect("failed to write benchmark output");
436
437
        // Performance Summary
438
1
        writeln!(
439
1
            out,
440
1
            "───────────────────────────────────────────────────────────────────────"
441
        )
442
1
        .expect("failed to write benchmark output");
443
1
        writeln!(out, "THROUGHPUT COMPARISON (tok/s)").expect("failed to write benchmark output");
444
1
        writeln!(
445
1
            out,
446
1
            "───────────────────────────────────────────────────────────────────────"
447
        )
448
1
        .expect("failed to write benchmark output");
449
450
1
        if let Some(ref m) = self.gguf_apr {
451
1
            writeln!(
452
1
                out,
453
1
                "APR GGUF:      {:>8.1} tok/s  (TTFT: {:>6.1}ms)",
454
1
                m.tokens_per_sec, m.ttft_ms
455
1
            )
456
1
            .expect("failed to write benchmark output");
457
1
        
}0
458
1
        if let Some(
ref m0
) = self.apr_native {
459
0
            writeln!(
460
0
                out,
461
0
                "APR .apr:      {:>8.1} tok/s  (TTFT: {:>6.1}ms)",
462
0
                m.tokens_per_sec, m.ttft_ms
463
0
            )
464
0
            .expect("failed to write benchmark output");
465
1
        }
466
1
        if let Some(
ref m0
) = self.gguf_ollama {
467
0
            writeln!(
468
0
                out,
469
0
                "Ollama:        {:>8.1} tok/s  (TTFT: {:>6.1}ms)",
470
0
                m.tokens_per_sec, m.ttft_ms
471
0
            )
472
0
            .expect("failed to write benchmark output");
473
1
        }
474
1
        if let Some(
ref m0
) = self.gguf_llamacpp {
475
0
            writeln!(
476
0
                out,
477
0
                "llama.cpp:     {:>8.1} tok/s  (TTFT: {:>6.1}ms)",
478
0
                m.tokens_per_sec, m.ttft_ms
479
0
            )
480
0
            .expect("failed to write benchmark output");
481
1
        }
482
1
        writeln!(out).expect("failed to write benchmark output");
483
484
        // Speedup Analysis
485
1
        writeln!(
486
1
            out,
487
1
            "───────────────────────────────────────────────────────────────────────"
488
        )
489
1
        .expect("failed to write benchmark output");
490
1
        writeln!(out, "SPEEDUP ANALYSIS").expect("failed to write benchmark output");
491
1
        writeln!(
492
1
            out,
493
1
            "───────────────────────────────────────────────────────────────────────"
494
        )
495
1
        .expect("failed to write benchmark output");
496
497
1
        let ollama_tps = self
498
1
            .gguf_ollama
499
1
            .as_ref()
500
1
            .map_or(318.0, |m| m.tokens_per_sec);
501
1
        let llamacpp_tps = self
502
1
            .gguf_llamacpp
503
1
            .as_ref()
504
1
            .map_or(200.0, |m| m.tokens_per_sec);
505
506
1
        if let Some(ref m) = self.gguf_apr {
507
1
            let vs_ollama = m.tokens_per_sec / ollama_tps;
508
1
            let vs_llamacpp = m.tokens_per_sec / llamacpp_tps;
509
1
            writeln!(
510
1
                out,
511
1
                "APR GGUF vs Ollama:     {:>5.2}x  {}",
512
                vs_ollama,
513
1
                if vs_ollama >= 1.0 { "✓" } else { 
"⚠"0
}
514
            )
515
1
            .expect("failed to write benchmark output");
516
1
            writeln!(
517
1
                out,
518
1
                "APR GGUF vs llama.cpp:  {:>5.2}x  {}",
519
                vs_llamacpp,
520
1
                if vs_llamacpp >= 1.25 {
521
1
                    "✓ Point 41 PASS"
522
                } else {
523
0
                    "⚠ Point 41 FAIL"
524
                }
525
            )
526
1
            .expect("failed to write benchmark output");
527
0
        }
528
529
1
        if let Some(
ref m0
) = self.apr_native {
530
0
            let vs_ollama = m.tokens_per_sec / ollama_tps;
531
0
            writeln!(
532
0
                out,
533
0
                "APR .apr vs Ollama:     {:>5.2}x  {}",
534
                vs_ollama,
535
0
                if vs_ollama >= 2.0 {
536
0
                    "✓ 2x target"
537
                } else {
538
0
                    ""
539
                }
540
            )
541
0
            .expect("failed to write benchmark output");
542
1
        }
543
1
        writeln!(out).expect("failed to write benchmark output");
544
545
        // Profiling Hotspots
546
1
        if !self.hotspots.is_empty() {
547
1
            writeln!(
548
1
                out,
549
1
                "───────────────────────────────────────────────────────────────────────"
550
            )
551
1
            .expect("failed to write benchmark output");
552
1
            writeln!(out, "PROFILING HOTSPOTS (>5% of execution time)")
553
1
                .expect("failed to write benchmark output");
554
1
            writeln!(
555
1
                out,
556
1
                "───────────────────────────────────────────────────────────────────────"
557
            )
558
1
            .expect("failed to write benchmark output");
559
560
2
            for 
hotspot1
in &self.hotspots {
561
1
                writeln!(out, "{}", hotspot.to_line()).expect("failed to write benchmark output");
562
1
                if !hotspot.explanation.is_empty() {
563
1
                    writeln!(out, "   └─ {}", hotspot.explanation)
564
1
                        .expect("failed to write benchmark output");
565
1
                
}0
566
            }
567
1
            writeln!(out).expect("failed to write benchmark output");
568
0
        }
569
570
        // GPU Metrics
571
1
        writeln!(
572
1
            out,
573
1
            "───────────────────────────────────────────────────────────────────────"
574
        )
575
1
        .expect("failed to write benchmark output");
576
1
        writeln!(out, "GPU METRICS").expect("failed to write benchmark output");
577
1
        writeln!(
578
1
            out,
579
1
            "───────────────────────────────────────────────────────────────────────"
580
        )
581
1
        .expect("failed to write benchmark output");
582
583
1
        if let Some(ref m) = self.gguf_apr {
584
1
            if let (Some(util), Some(mem)) = (m.gpu_util, m.gpu_mem_mb) {
585
1
                writeln!(
586
1
                    out,
587
1
                    "APR GGUF:   GPU Util: {:>5.1}%  VRAM: {:>6.0}MB",
588
1
                    util, mem
589
1
                )
590
1
                .expect("failed to write benchmark output");
591
1
            
}0
592
0
        }
593
1
        if let Some(
ref m0
) = self.apr_native {
594
0
            if let (Some(util), Some(mem)) = (m.gpu_util, m.gpu_mem_mb) {
595
0
                writeln!(
596
0
                    out,
597
0
                    "APR .apr:   GPU Util: {:>5.1}%  VRAM: {:>6.0}MB",
598
0
                    util, mem
599
0
                )
600
0
                .expect("failed to write benchmark output");
601
0
            }
602
1
        }
603
1
        writeln!(out).expect("failed to write benchmark output");
604
605
        // Recommendations
606
1
        writeln!(
607
1
            out,
608
1
            "───────────────────────────────────────────────────────────────────────"
609
        )
610
1
        .expect("failed to write benchmark output");
611
1
        writeln!(out, "OPTIMIZATION RECOMMENDATIONS").expect("failed to write benchmark output");
612
1
        writeln!(
613
1
            out,
614
1
            "───────────────────────────────────────────────────────────────────────"
615
        )
616
1
        .expect("failed to write benchmark output");
617
618
1
        let unexpected: Vec<_> = self.hotspots.iter().filter(|h| !h.is_expected).collect();
619
1
        if unexpected.is_empty() {
620
1
            writeln!(out, "✓ No unexpected hotspots detected")
621
1
                .expect("failed to write benchmark output");
622
1
        } else {
623
0
            for h in unexpected {
624
0
                writeln!(out, "⚠ {}: {}", h.component, h.explanation)
625
0
                    .expect("failed to write benchmark output");
626
0
            }
627
        }
628
629
        // Phase 2 status
630
1
        let apr_tps = self.gguf_apr.as_ref().map_or(0.0, |m| m.tokens_per_sec);
631
1
        if apr_tps < 500.0 {
632
0
            writeln!(out).expect("failed to write benchmark output");
633
0
            writeln!(out, "Phase 2 Optimizations (projected 3.28x improvement):")
634
0
                .expect("failed to write benchmark output");
635
0
            writeln!(out, "  PAR-036: Persistent threads      (1.3x)")
636
0
                .expect("failed to write benchmark output");
637
0
            writeln!(out, "  PAR-037: CUDA graph capture      (1.5x)")
638
0
                .expect("failed to write benchmark output");
639
0
            writeln!(out, "  PAR-038: Multi-stream pipeline   (1.2x)")
640
0
                .expect("failed to write benchmark output");
641
0
            writeln!(out, "  PAR-039: Megakernel fusion       (1.4x)")
642
0
                .expect("failed to write benchmark output");
643
0
            writeln!(
644
0
                out,
645
0
                "  Projected: {:.1} × 3.28 = {:.1} tok/s",
646
0
                apr_tps,
647
0
                apr_tps * 3.28
648
0
            )
649
0
            .expect("failed to write benchmark output");
650
1
        }
651
652
1
        writeln!(
653
1
            out,
654
1
            "═══════════════════════════════════════════════════════════════════════"
655
        )
656
1
        .expect("failed to write benchmark output");
657
1
        writeln!(out, "```").expect("failed to write benchmark output");
658
659
1
        out
660
1
    }
661
662
    /// Generate compact one-liner for quick comparison
663
1
    pub fn render_compact(&self) -> String {
664
1
        let apr_tps = self.gguf_apr.as_ref().map_or(0.0, |m| m.tokens_per_sec);
665
1
        let ollama_tps = self.gguf_ollama.as_ref().map_or(0.0, |m| m.tokens_per_sec);
666
1
        let llamacpp_tps = self
667
1
            .gguf_llamacpp
668
1
            .as_ref()
669
1
            .map_or(0.0, |m| m.tokens_per_sec);
670
671
1
        format!(
672
1
            "APR:{:.0} Ollama:{:.0} llama.cpp:{:.0} tok/s | APR vs Ollama:{:.2}x vs llama.cpp:{:.2}x",
673
            apr_tps, ollama_tps, llamacpp_tps,
674
1
            apr_tps / ollama_tps.max(1.0),
675
1
            apr_tps / llamacpp_tps.max(1.0)
676
        )
677
1
    }
678
}
679
680
// ============================================================================
681
// Benchmark Runner
682
// ============================================================================
683
684
/// Benchmark runner with profiling
685
#[derive(Debug)]
686
pub struct BenchmarkRunner {
687
    /// Results grid
688
    pub grid: BenchmarkGrid,
689
    /// Profiling start time
690
    start_time: Option<Instant>,
691
    /// Component timings
692
    component_times: Vec<(String, Duration, u64)>,
693
}
694
695
impl Default for BenchmarkRunner {
696
0
    fn default() -> Self {
697
0
        Self::new()
698
0
    }
699
}
700
701
impl BenchmarkRunner {
702
    /// Create new benchmark runner
703
1
    pub fn new() -> Self {
704
1
        Self {
705
1
            grid: BenchmarkGrid::new(),
706
1
            start_time: None,
707
1
            component_times: Vec::new(),
708
1
        }
709
1
    }
710
711
    /// Start profiling
712
1
    pub fn start(&mut self) {
713
1
        self.start_time = Some(Instant::now());
714
1
    }
715
716
    /// Record component timing
717
3
    pub fn record_component(&mut self, name: &str, duration: Duration, calls: u64) {
718
3
        self.component_times
719
3
            .push((name.to_string(), duration, calls));
720
3
    }
721
722
    /// Measure a component
723
0
    pub fn measure<F, R>(&mut self, name: &str, f: F) -> R
724
0
    where
725
0
        F: FnOnce() -> R,
726
    {
727
0
        let start = Instant::now();
728
0
        let result = f();
729
0
        self.record_component(name, start.elapsed(), 1);
730
0
        result
731
0
    }
732
733
    /// Finalize and compute hotspots
734
1
    pub fn finalize(&mut self) {
735
1
        let total_time: Duration = self.component_times.iter().map(|(_, d, _)| *d).sum();
736
1
        let total_nanos = total_time.as_nanos() as f64;
737
738
1
        if total_nanos == 0.0 {
739
0
            return;
740
1
        }
741
742
4
        for (
name3
,
duration3
,
calls3
) in &self.component_times {
743
3
            let percentage = (duration.as_nanos() as f64 / total_nanos) * 100.0;
744
745
3
            if percentage > 5.0 {
746
3
                let avg_per_call = if *calls > 0 {
747
3
                    Duration::from_nanos((duration.as_nanos() / *calls as u128) as u64)
748
                } else {
749
0
                    Duration::ZERO
750
                };
751
752
3
                let (explanation, is_expected) = explain_inference_hotspot(name, percentage);
753
754
3
                self.grid.add_hotspot(ProfilingHotspot {
755
3
                    component: name.clone(),
756
3
                    time: *duration,
757
3
                    percentage,
758
3
                    call_count: *calls,
759
3
                    avg_per_call,
760
3
                    explanation,
761
3
                    is_expected,
762
3
                });
763
0
            }
764
        }
765
766
        // Sort by percentage descending
767
2
        
self.grid.hotspots1
.
sort_by1
(|a, b| {
768
2
            b.percentage
769
2
                .partial_cmp(&a.percentage)
770
2
                .unwrap_or(std::cmp::Ordering::Equal)
771
2
        });
772
1
    }
773
}
774
775
// ============================================================================
776
// Helper Functions
777
// ============================================================================
778
779
/// Render ASCII bar
780
10
fn render_bar(value: f64, max: f64, width: usize) -> String {
781
10
    let ratio = if max > 0.0 { value / max } else { 
0.00
};
782
10
    let filled = ((ratio * width as f64) as usize).min(width);
783
10
    let empty = width - filled;
784
785
10
    format!("{}{}", "█".repeat(filled), "░".repeat(empty))
786
10
}
787
788
/// Truncate string to max length
789
6
fn truncate(s: &str, max_len: usize) -> &str {
790
6
    if s.len() <= max_len {
791
5
        s
792
    } else {
793
1
        &s[..max_len]
794
    }
795
6
}
796
797
/// Explain inference hotspot
798
7
fn explain_inference_hotspot(component: &str, percentage: f64) -> (String, bool) {
799
7
    match component {
800
7
        "Q4K_GEMV" | 
"MatMul"5
|
"GEMM"5
=> (
801
2
            format!(
802
2
                "Matrix ops dominate ({:.1}%) - expected for transformer inference",
803
2
                percentage
804
2
            ),
805
2
            true,
806
2
        ),
807
5
        "Attention" | 
"FlashAttention"3
=> (
808
2
            format!(
809
2
                "Attention at {:.1}% - normal for autoregressive decoding",
810
2
                percentage
811
2
            ),
812
2
            true,
813
2
        ),
814
3
        "KV_Cache" | "KVCache" => {
815
0
            if percentage > 20.0 {
816
0
                (
817
0
                    "KV cache overhead high - consider FP16 cache or graph capture".to_string(),
818
0
                    false,
819
0
                )
820
            } else {
821
0
                ("KV cache within normal range".to_string(), true)
822
            }
823
        },
824
3
        "Softmax" => {
825
0
            if percentage > 10.0 {
826
0
                (
827
0
                    "Softmax unusually high - check for redundant computations".to_string(),
828
0
                    false,
829
0
                )
830
            } else {
831
0
                ("Softmax within normal range".to_string(), true)
832
            }
833
        },
834
3
        "RMSNorm" | "LayerNorm" => {
835
0
            if percentage > 15.0 {
836
0
                (
837
0
                    "Normalization overhead high - consider fused kernels".to_string(),
838
0
                    false,
839
0
                )
840
            } else {
841
0
                ("Normalization within normal range".to_string(), true)
842
            }
843
        },
844
3
        "MemcpyH2D" | "MemcpyD2H" | "Transfer" => (
845
0
            "Memory transfer - consider persistent GPU buffers".to_string(),
846
0
            false,
847
0
        ),
848
3
        "KernelLaunch" => (
849
0
            "Kernel launch overhead - consider CUDA graphs or megakernels".to_string(),
850
0
            false,
851
0
        ),
852
3
        "Embedding" => (
853
0
            "Embedding lookup - expected at start of inference".to_string(),
854
0
            true,
855
0
        ),
856
3
        "Sampling" | "TopK" | "TopP" => (
857
0
            "Sampling overhead - expected for token generation".to_string(),
858
0
            true,
859
0
        ),
860
        _ => {
861
3
            if percentage > 20.0 {
862
1
                (
863
1
                    format!("Unknown component at {:.1}% - investigate", percentage),
864
1
                    false,
865
1
                )
866
            } else {
867
2
                (String::new(), true)
868
            }
869
        },
870
    }
871
7
}
872
873
// ============================================================================
874
// Tests
875
// ============================================================================
876
877
#[cfg(test)]
878
mod tests {
879
    use super::*;
880
881
    #[test]
882
1
    fn test_benchmark_grid_ascii() {
883
1
        let mut grid = BenchmarkGrid::new()
884
1
            .with_model("Qwen2.5-Coder-0.5B", "0.5B", "Q4_K_M")
885
1
            .with_gpu("RTX 4090", 24.0);
886
887
1
        grid.set_gguf_row(
888
1
            BenchMeasurement::new("APR", "GGUF")
889
1
                .with_throughput(500.0)
890
1
                .with_ttft(7.0),
891
1
            BenchMeasurement::new("Ollama", "GGUF")
892
1
                .with_throughput(318.0)
893
1
                .with_ttft(50.0),
894
1
            BenchMeasurement::new("llama.cpp", "GGUF")
895
1
                .with_throughput(200.0)
896
1
                .with_ttft(30.0),
897
        );
898
899
1
        grid.set_apr_row(
900
1
            BenchMeasurement::new("APR", ".apr")
901
1
                .with_throughput(600.0)
902
1
                .with_ttft(5.0),
903
1
            BenchMeasurement::new("APR", "GGUF")
904
1
                .with_throughput(500.0)
905
1
                .with_ttft(7.0),
906
1
            BenchMeasurement::new("Ollama", "GGUF")
907
1
                .with_throughput(318.0)
908
1
                .with_ttft(50.0),
909
        );
910
911
1
        let ascii = grid.render_ascii();
912
1
        assert!(ascii.contains("APR serve GGUF"));
913
1
        assert!(ascii.contains("Ollama"));
914
1
        assert!(ascii.contains("llama.cpp"));
915
1
        assert!(ascii.contains("500.0 tok/s"));
916
1
    }
917
918
    #[test]
919
1
    fn test_profiling_log() {
920
1
        let mut grid = BenchmarkGrid::new()
921
1
            .with_model("Qwen2.5-Coder-0.5B", "0.5B", "Q4_K_M")
922
1
            .with_gpu("RTX 4090", 24.0);
923
924
1
        grid.gguf_apr = Some(
925
1
            BenchMeasurement::new("APR", "GGUF")
926
1
                .with_throughput(500.0)
927
1
                .with_ttft(7.0)
928
1
                .with_gpu(95.0, 2048.0),
929
1
        );
930
931
1
        grid.add_hotspot(ProfilingHotspot {
932
1
            component: "Q4K_GEMV".to_string(),
933
1
            time: Duration::from_millis(150),
934
1
            percentage: 45.0,
935
1
            call_count: 1000,
936
1
            avg_per_call: Duration::from_micros(150),
937
1
            explanation: "Matrix ops dominate - expected".to_string(),
938
1
            is_expected: true,
939
1
        });
940
941
1
        let log = grid.render_profiling_log();
942
1
        assert!(log.contains("PROFILING REPORT"));
943
1
        assert!(log.contains("Q4K_GEMV"));
944
1
        assert!(log.contains("45.0%"));
945
1
    }
946
947
    #[test]
948
1
    fn test_compact_output() {
949
1
        let mut grid = BenchmarkGrid::new();
950
1
        grid.gguf_apr = Some(BenchMeasurement::new("APR", "GGUF").with_throughput(500.0));
951
1
        grid.gguf_ollama = Some(BenchMeasurement::new("Ollama", "GGUF").with_throughput(318.0));
952
1
        grid.gguf_llamacpp =
953
1
            Some(BenchMeasurement::new("llama.cpp", "GGUF").with_throughput(200.0));
954
955
1
        let compact = grid.render_compact();
956
1
        assert!(compact.contains("APR:500"));
957
1
        assert!(compact.contains("vs llama.cpp:2.50x"));
958
1
    }
959
960
    #[test]
961
1
    fn test_runner_profiling() {
962
1
        let mut runner = BenchmarkRunner::new();
963
1
        runner.start();
964
965
1
        runner.record_component("Q4K_GEMV", Duration::from_millis(100), 500);
966
1
        runner.record_component("Attention", Duration::from_millis(50), 500);
967
1
        runner.record_component("Other", Duration::from_millis(10), 100);
968
969
1
        runner.finalize();
970
971
1
        assert!(!runner.grid.hotspots.is_empty());
972
1
        assert_eq!(runner.grid.hotspots[0].component, "Q4K_GEMV");
973
1
    }
974
975
    #[test]
976
1
    fn test_render_bar() {
977
1
        let bar = render_bar(50.0, 100.0, 10);
978
10
        
assert_eq!1
(
bar.chars()1
.
filter1
(|c| *c == '█').
count1
(), 5);
979
10
        
assert_eq!1
(
bar.chars()1
.
filter1
(|c| *c == '░').
count1
(), 5);
980
1
    }
981
982
    // =========================================================================
983
    // Coverage Tests: BenchMeasurement
984
    // =========================================================================
985
986
    #[test]
987
1
    fn test_bench_measurement_new() {
988
1
        let m = BenchMeasurement::new("TestEngine", "TestFormat");
989
1
        assert_eq!(m.engine, "TestEngine");
990
1
        assert_eq!(m.format, "TestFormat");
991
1
        assert_eq!(m.tokens_per_sec, 0.0);
992
1
        assert_eq!(m.ttft_ms, 0.0);
993
1
        assert_eq!(m.tokens_generated, 0);
994
1
        assert!(m.gpu_util.is_none());
995
1
        assert!(m.gpu_mem_mb.is_none());
996
1
    }
997
998
    #[test]
999
1
    fn test_bench_measurement_with_throughput() {
1000
1
        let m = BenchMeasurement::new("APR", "GGUF").with_throughput(100.0);
1001
1
        assert_eq!(m.tokens_per_sec, 100.0);
1002
1
    }
1003
1004
    #[test]
1005
1
    fn test_bench_measurement_with_ttft() {
1006
1
        let m = BenchMeasurement::new("APR", "GGUF").with_ttft(25.5);
1007
1
        assert_eq!(m.ttft_ms, 25.5);
1008
1
    }
1009
1010
    #[test]
1011
1
    fn test_bench_measurement_with_tokens() {
1012
1
        let duration = Duration::from_secs(2);
1013
1
        let m = BenchMeasurement::new("APR", "GGUF").with_tokens(200, duration);
1014
1
        assert_eq!(m.tokens_generated, 200);
1015
1
        assert_eq!(m.duration, duration);
1016
1
        assert!((m.tokens_per_sec - 100.0).abs() < 0.1);
1017
1
    }
1018
1019
    #[test]
1020
1
    fn test_bench_measurement_with_tokens_zero_duration() {
1021
1
        let m = BenchMeasurement::new("APR", "GGUF").with_tokens(100, Duration::ZERO);
1022
1
        assert_eq!(m.tokens_generated, 100);
1023
        // Zero duration means no TPS calculation
1024
1
    }
1025
1026
    #[test]
1027
1
    fn test_bench_measurement_with_gpu() {
1028
1
        let m = BenchMeasurement::new("APR", "GGUF").with_gpu(95.0, 4096.0);
1029
1
        assert_eq!(m.gpu_util, Some(95.0));
1030
1
        assert_eq!(m.gpu_mem_mb, Some(4096.0));
1031
1
    }
1032
1033
    #[test]
1034
1
    fn test_bench_measurement_debug() {
1035
1
        let m = BenchMeasurement::new("APR", "GGUF").with_throughput(100.0);
1036
1
        let debug_str = format!("{:?}", m);
1037
1
        assert!(debug_str.contains("BenchMeasurement"));
1038
1
        assert!(debug_str.contains("APR"));
1039
1
    }
1040
1041
    #[test]
1042
1
    fn test_bench_measurement_clone() {
1043
1
        let m = BenchMeasurement::new("APR", "GGUF")
1044
1
            .with_throughput(100.0)
1045
1
            .with_gpu(90.0, 1024.0);
1046
1
        let cloned = m.clone();
1047
1
        assert_eq!(cloned.engine, m.engine);
1048
1
        assert_eq!(cloned.tokens_per_sec, m.tokens_per_sec);
1049
1
        assert_eq!(cloned.gpu_util, m.gpu_util);
1050
1
    }
1051
1052
    // =========================================================================
1053
    // Coverage Tests: ProfilingHotspot
1054
    // =========================================================================
1055
1056
    #[test]
1057
1
    fn test_profiling_hotspot_debug() {
1058
1
        let hotspot = ProfilingHotspot {
1059
1
            component: "Attention".to_string(),
1060
1
            time: Duration::from_millis(100),
1061
1
            percentage: 50.0,
1062
1
            call_count: 1000,
1063
1
            avg_per_call: Duration::from_micros(100),
1064
1
            explanation: "Expected".to_string(),
1065
1
            is_expected: true,
1066
1
        };
1067
1
        let debug_str = format!("{:?}", hotspot);
1068
1
        assert!(debug_str.contains("ProfilingHotspot"));
1069
1
        assert!(debug_str.contains("Attention"));
1070
1
    }
1071
1072
    #[test]
1073
1
    fn test_profiling_hotspot_clone() {
1074
1
        let hotspot = ProfilingHotspot {
1075
1
            component: "GEMM".to_string(),
1076
1
            time: Duration::from_millis(200),
1077
1
            percentage: 75.0,
1078
1
            call_count: 500,
1079
1
            avg_per_call: Duration::from_micros(400),
1080
1
            explanation: "Matrix multiplication".to_string(),
1081
1
            is_expected: true,
1082
1
        };
1083
1
        let cloned = hotspot.clone();
1084
1
        assert_eq!(cloned.component, hotspot.component);
1085
1
        assert_eq!(cloned.percentage, hotspot.percentage);
1086
1
    }
1087
1088
    // =========================================================================
1089
    // Coverage Tests: BenchmarkGrid
1090
    // =========================================================================
1091
1092
    #[test]
1093
1
    fn test_benchmark_grid_new() {
1094
1
        let grid = BenchmarkGrid::new();
1095
1
        assert!(grid.gguf_apr.is_none());
1096
1
        assert!(grid.gguf_ollama.is_none());
1097
1
        assert!(grid.gguf_llamacpp.is_none());
1098
1
        assert!(grid.hotspots.is_empty());
1099
1
    }
1100
1101
    #[test]
1102
1
    fn test_benchmark_grid_with_model() {
1103
1
        let grid = BenchmarkGrid::new().with_model("Llama-7B", "7B", "Q4_K_M");
1104
1
        assert_eq!(grid.model_name, "Llama-7B");
1105
1
        assert_eq!(grid.model_params, "7B");
1106
1
        assert_eq!(grid.quantization, "Q4_K_M");
1107
1
    }
1108
1109
    #[test]
1110
1
    fn test_benchmark_grid_with_gpu() {
1111
1
        let grid = BenchmarkGrid::new().with_gpu("RTX 3090", 24.0);
1112
1
        assert_eq!(grid.gpu_name, "RTX 3090");
1113
1
        assert_eq!(grid.gpu_vram_gb, 24.0);
1114
1
    }
1115
1116
    #[test]
1117
1
    fn test_benchmark_grid_add_hotspot() {
1118
1
        let mut grid = BenchmarkGrid::new();
1119
1
        grid.add_hotspot(ProfilingHotspot {
1120
1
            component: "Test".to_string(),
1121
1
            time: Duration::from_millis(50),
1122
1
            percentage: 25.0,
1123
1
            call_count: 100,
1124
1
            avg_per_call: Duration::from_micros(500),
1125
1
            explanation: "Test hotspot".to_string(),
1126
1
            is_expected: true,
1127
1
        });
1128
1
        assert_eq!(grid.hotspots.len(), 1);
1129
1
        assert_eq!(grid.hotspots[0].component, "Test");
1130
1
    }
1131
1132
    // =========================================================================
1133
    // Coverage Tests: render_bar edge cases
1134
    // =========================================================================
1135
1136
    #[test]
1137
1
    fn test_render_bar_zero() {
1138
1
        let bar = render_bar(0.0, 100.0, 10);
1139
10
        
assert_eq!1
(
bar.chars()1
.
filter1
(|c| *c == '█').
count1
(), 0);
1140
10
        
assert_eq!1
(
bar.chars()1
.
filter1
(|c| *c == '░').
count1
(), 10);
1141
1
    }
1142
1143
    #[test]
1144
1
    fn test_render_bar_full() {
1145
1
        let bar = render_bar(100.0, 100.0, 10);
1146
10
        
assert_eq!1
(
bar.chars()1
.
filter1
(|c| *c == '█').
count1
(), 10);
1147
10
        
assert_eq!1
(
bar.chars()1
.
filter1
(|c| *c == '░').
count1
(), 0);
1148
1
    }
1149
1150
    #[test]
1151
1
    fn test_render_bar_over_max() {
1152
1
        let bar = render_bar(150.0, 100.0, 10);
1153
        // Should clamp to max
1154
10
        
assert_eq!1
(
bar.chars()1
.
filter1
(|c| *c == '█').
count1
(), 10);
1155
1
    }
1156
1157
    // =========================================================================
1158
    // Coverage Tests: truncate
1159
    // =========================================================================
1160
1161
    #[test]
1162
1
    fn test_truncate_short_string() {
1163
1
        let result = truncate("short", 10);
1164
1
        assert_eq!(result, "short");
1165
1
    }
1166
1167
    #[test]
1168
1
    fn test_truncate_exact_length() {
1169
1
        let result = truncate("exactly10c", 10);
1170
1
        assert_eq!(result, "exactly10c");
1171
1
    }
1172
1173
    #[test]
1174
1
    fn test_truncate_long_string() {
1175
1
        let result = truncate("this is a very long string", 10);
1176
1
        assert_eq!(result.len(), 10);
1177
1
    }
1178
1179
    // =========================================================================
1180
    // Coverage Tests: explain_inference_hotspot
1181
    // =========================================================================
1182
1183
    #[test]
1184
1
    fn test_explain_inference_hotspot_gemv() {
1185
1
        let (explanation, is_expected) = explain_inference_hotspot("Q4K_GEMV", 50.0);
1186
1
        assert!(is_expected);
1187
1
        assert!(!explanation.is_empty());
1188
1
    }
1189
1190
    #[test]
1191
1
    fn test_explain_inference_hotspot_attention() {
1192
1
        let (explanation, is_expected) = explain_inference_hotspot("Attention", 30.0);
1193
1
        assert!(is_expected);
1194
1
        assert!(!explanation.is_empty());
1195
1
    }
1196
1197
    #[test]
1198
1
    fn test_explain_inference_hotspot_unknown() {
1199
1
        let (explanation, is_expected) = explain_inference_hotspot("UnknownComponent", 60.0);
1200
        // High percentage for unknown component is unexpected
1201
1
        assert!(!is_expected);
1202
1
        assert!(!explanation.is_empty());
1203
1
    }
1204
1205
    #[test]
1206
1
    fn test_explain_inference_hotspot_low_percentage() {
1207
1
        let (explanation, is_expected) = explain_inference_hotspot("SomeComponent", 5.0);
1208
        // Low percentage unknown component returns empty string and is expected
1209
1
        assert!(is_expected);
1210
        // Note: The function returns empty string for low percentage unknown components
1211
        // which means "nothing to report" - this is valid behavior
1212
1
        let _ = explanation;
1213
1
    }
1214
}