## 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/cli/mod.rs

    Line
    Count
    Source
    1
    //! CLI command implementations
    2
    //!
    3
    //! This module contains all the business logic for CLI commands,
    4
    //! extracted from main.rs for testability.
    5
    6
    // CLI glue code - relaxed lint requirements
    7
    #![allow(clippy::missing_errors_doc)]
    8
    #![allow(clippy::needless_pass_by_value)]
    9
    #![allow(clippy::unreadable_literal)]
    10
    #![allow(clippy::case_sensitive_file_extension_comparisons)]
    11
    12
    use crate::error::{RealizarError, Result};
    13
    14
    // PMAT-802: Extracted inference runners
    15
    pub mod inference;
    16
    pub use inference::{run_gguf_inference, run_safetensors_inference, run_apr_inference};
    17
    #[cfg(feature = "cuda")]
    18
    pub use inference::run_gguf_inference_gpu;
    19
    20
    /// Available benchmark suites
    21
    pub const BENCHMARK_SUITES: &[(&str, &str)] = &[
    22
        (
    23
            "tensor_ops",
    24
            "Core tensor operations (add, mul, matmul, softmax)",
    25
        ),
    26
        ("inference", "End-to-end inference pipeline benchmarks"),
    27
        ("cache", "KV cache operations and memory management"),
    28
        ("tokenizer", "BPE and SentencePiece tokenization"),
    29
        ("quantize", "Quantization/dequantization (Q4_0, Q8_0)"),
    30
        ("lambda", "AWS Lambda cold start and warm invocation"),
    31
        (
    32
            "comparative",
    33
            "Framework comparison (MNIST, CIFAR-10, Iris)",
    34
        ),
    35
    ];
    36
    37
    /// Format file size in human-readable form
    38
    44
    pub fn format_size(bytes: u64) -> String {
    39
        const KB: u64 = 1024;
    40
        const MB: u64 = KB * 1024;
    41
        const GB: u64 = MB * 1024;
    42
    43
    44
        if bytes >= GB {
    44
    12
            format!("{:.1} GB", bytes as f64 / GB as f64)
    45
    32
        } else if bytes >= MB {
    46
    11
            format!("{:.1} MB", bytes as f64 / MB as f64)
    47
    21
        } else if bytes >= KB {
    48
    12
            format!("{:.1} KB", bytes as f64 / KB as f64)
    49
        } else {
    50
    9
            format!("{bytes} B")
    51
        }
    52
    44
    }
    53
    54
    /// Display model information based on file type
    55
    21
    pub fn display_model_info(model_ref: &str, file_data: &[u8]) -> Result<()> {
    56
        use crate::format::{APR_MAGIC, GGUF_MAGIC};
    57
    58
    21
        if model_ref.ends_with(".gguf") || file_data18.starts_with18(GGUF_MAGIC18) {
    59
            use crate::gguf::GGUFModel;
    60
    4
            let gguf0 = GGUFModel::from_bytes(file_data)?;
    61
    0
            println!("  Format: GGUF v{}", gguf.header.version);
    62
    0
            println!("  Tensors: {}", gguf.header.tensor_count);
    63
    17
        } else if model_ref.ends_with(".safetensors") {
    64
            use crate::safetensors::SafetensorsModel;
    65
    3
            let st0 = SafetensorsModel::from_bytes(file_data)?;
    66
    0
            println!("  Format: SafeTensors");
    67
    0
            println!("  Tensors: {}", st.tensors.len());
    68
    14
        } else if model_ref.ends_with(".apr") || file_data11.starts_with11(APR_MAGIC11) {
    69
            use crate::model_loader::read_apr_model_type;
    70
    5
            let model_type = read_apr_model_type(file_data).unwrap_or_else(|| "Unknown"3.to_string3());
    71
    5
            println!("  Format: APR (Aprender Native)");
    72
    5
            println!("  Model Type: {model_type}");
    73
    9
        } else {
    74
    9
            println!("  Format: Unknown ({} bytes)", file_data.len());
    75
    9
        }
    76
    14
        Ok(())
    77
    21
    }
    78
    79
    /// Run visualization demo
    80
    11
    pub fn run_visualization(use_color: bool, samples: usize) {
    81
        use crate::viz::{
    82
            print_benchmark_results, render_ascii_histogram, render_sparkline, BenchmarkData,
    83
        };
    84
    85
    11
        println!("Realizar Benchmark Visualization Demo");
    86
    11
        println!("=====================================");
    87
    11
        println!();
    88
    89
        // Generate test benchmark data (simulating inference latencies)
    90
    11
        let mut rng_state = 42u64;
    91
    11
        let latencies: Vec<f64> = (0..samples)
    92
    348
            .map11(|_| {
    93
                // Simple LCG for reproducible pseudo-random numbers
    94
    348
                rng_state = rng_state.wrapping_mul(6364136223846793005).wrapping_add(1);
    95
    348
                let uniform = (rng_state >> 33) as f64 / (1u64 << 31) as f64;
    96
                // Log-normal distribution (typical for latencies)
    97
    348
                let log_mean = 3.0; // ~20us median
    98
    348
                let log_std = 0.5;
    99
    348
                (log_mean + log_std * (2.0 * uniform - 1.0)).exp()
    100
    348
            })
    101
    11
            .collect();
    102
    103
        // Demo 1: Sparkline
    104
    11
        println!("1. Sparkline (latency trend)");
    105
    11
        println!("   {}", render_sparkline(&latencies, 60));
    106
    11
        println!();
    107
    108
        // Demo 2: ASCII histogram
    109
    11
        println!("2. ASCII Histogram (latency distribution)");
    110
    11
        let hist = render_ascii_histogram(&latencies, 12, 50);
    111
    120
        for line in hist11.lines11() {
    112
    120
            println!("   {line}");
    113
    120
        }
    114
    11
        println!();
    115
    116
        // Demo 3: Full benchmark report
    117
    11
        println!("3. Full Benchmark Report");
    118
    11
        let data = BenchmarkData::new("inference_latency", latencies);
    119
    11
        print_benchmark_results(&data, use_color);
    120
    11
        println!();
    121
    122
        // Demo 4: Multi-benchmark comparison
    123
    11
        println!("4. Multi-Benchmark Comparison");
    124
    11
        println!();
    125
    126
    11
        let benchmarks = [
    127
    11
            ("tensor_add", 15.2, 18.1),
    128
    11
            ("tensor_mul", 16.8, 20.3),
    129
    11
            ("matmul_128", 145.3, 172.1),
    130
    11
            ("softmax", 23.4, 28.9),
    131
    11
            ("attention", 892.1, 1024.5),
    132
    11
        ];
    133
    134
    11
        println!(
    135
    11
            "   {:.<20} {:>10} {:>10} {:>10}",
    136
            "Benchmark", "p50 (us)", "p99 (us)", "Trend"
    137
        );
    138
    11
        println!("   {}", "-".repeat(55));
    139
    140
    66
        for (name55, p5055, p9955) in benchmarks {
    141
            // Generate mini trend data
    142
    55
            let trend: Vec<f64> = (0..20)
    143
    1.10k
                .map55(|i| p50 + (i as f64 / 20.0) * (p99 - p50) * 0.3)
    144
    55
                .collect();
    145
    55
            let sparkline = render_sparkline(&trend, 10);
    146
    55
            println!("   {:.<20} {:>10.1} {:>10.1} {}", name, p50, p99, sparkline);
    147
        }
    148
    11
        println!();
    149
    150
    11
        println!("Visualization powered by trueno-viz");
    151
    11
    }
    152
    153
    /// Run benchmarks with cargo bench or real HTTP client
    154
    9
    pub fn run_benchmarks(
    155
    9
        suite: Option<String>,
    156
    9
        list: bool,
    157
    9
        runtime: Option<String>,
    158
    9
        model: Option<String>,
    159
    9
        url: Option<String>,
    160
    9
        output: Option<String>,
    161
    9
    ) -> Result<()> {
    162
    9
        if list {
    163
    9
            println!("Available benchmark suites:");
    164
    9
            println!();
    165
    72
            for (name63, description63) in BENCHMARK_SUITES {
    166
    63
                println!("  {name:<12} - {description}");
    167
    63
            }
    168
    9
            println!();
    169
    9
            println!("Usage:");
    170
    9
            println!("  realizar bench                        # Run all benchmarks");
    171
    9
            println!("  realizar bench tensor_ops             # Run specific suite");
    172
    9
            println!("  realizar bench --list                 # List available suites");
    173
    9
            println!("  realizar bench --runtime realizar     # Specify runtime");
    174
    9
            println!("  realizar bench --output results.json  # Save JSON results");
    175
    9
            println!();
    176
    9
            println!("External Runtime Benchmarking (REAL HTTP calls):");
    177
    9
            println!("  realizar bench --runtime ollama --url http://localhost:11434 --model llama3.2");
    178
    9
            println!("  realizar bench --runtime vllm --url http://localhost:8000 --model meta-llama/Llama-3.2-1B");
    179
    9
            println!("  realizar bench --runtime llama-cpp --url http://localhost:8080");
    180
    9
            return Ok(());
    181
    0
        }
    182
    183
    0
        let runtime_name = runtime.clone().unwrap_or_else(|| "realizar".to_string());
    184
    0
        println!("Benchmark Configuration:");
    185
    0
        println!("  Runtime: {runtime_name}");
    186
    0
        if let Some(ref m) = model {
    187
    0
            println!("  Model: {m}");
    188
    0
        }
    189
    0
        if let Some(ref u) = url {
    190
    0
            println!("  URL: {u}");
    191
    0
        }
    192
    0
        if let Some(ref o) = output {
    193
    0
            println!("  Output: {o}");
    194
    0
        }
    195
    0
        println!();
    196
    197
        // Check if this is an external runtime benchmark (requires bench-http feature)
    198
    0
        if let (Some(ref rt), Some(ref server_url)) = (&runtime, &url) {
    199
    0
            return run_external_benchmark(rt, server_url, model.as_deref(), output.as_deref());
    200
    0
        }
    201
    202
    0
        let mut cmd = std::process::Command::new("cargo");
    203
    0
        cmd.arg("bench");
    204
    205
    0
        if let Some(ref suite_name) = suite {
    206
            // Validate suite name
    207
    0
            if !BENCHMARK_SUITES.iter().any(|(name, _)| *name == suite_name) {
    208
    0
                eprintln!("Error: Unknown benchmark suite '{suite_name}'");
    209
    0
                eprintln!();
    210
    0
                eprintln!("Available suites:");
    211
    0
                for (name, _) in BENCHMARK_SUITES {
    212
    0
                    eprintln!("  {name}");
    213
    0
                }
    214
    0
                std::process::exit(1);
    215
    0
            }
    216
    0
            cmd.arg("--bench").arg(suite_name);
    217
    0
        }
    218
    219
    0
        println!("Running benchmarks...");
    220
    0
        println!();
    221
    222
        // Capture output if JSON output is requested
    223
    0
        let bench_output = if output.is_some() {
    224
    0
            cmd.output()
    225
    0
                .map_err(|e| RealizarError::UnsupportedOperation {
    226
    0
                    operation: "run_benchmarks".to_string(),
    227
    0
                    reason: format!("Failed to execute cargo bench: {e}"),
    228
    0
                })?
    229
        } else {
    230
            // Just run and show output directly
    231
    0
            let status = cmd
    232
    0
                .status()
    233
    0
                .map_err(|e| RealizarError::UnsupportedOperation {
    234
    0
                    operation: "run_benchmarks".to_string(),
    235
    0
                    reason: format!("Failed to execute cargo bench: {e}"),
    236
    0
                })?;
    237
    0
            if !status.success() {
    238
    0
                return Err(RealizarError::UnsupportedOperation {
    239
    0
                    operation: "run_benchmarks".to_string(),
    240
    0
                    reason: format!("Benchmarks failed with exit code: {:?}", status.code()),
    241
    0
                });
    242
    0
            }
    243
    0
            return Ok(());
    244
        };
    245
    246
    0
        if !bench_output.status.success() {
    247
    0
            eprintln!("{}", String::from_utf8_lossy(&bench_output.stderr));
    248
    0
            return Err(RealizarError::UnsupportedOperation {
    249
    0
                operation: "run_benchmarks".to_string(),
    250
    0
                reason: format!(
    251
    0
                    "Benchmarks failed with exit code: {:?}",
    252
    0
                    bench_output.status.code()
    253
    0
                ),
    254
    0
            });
    255
    0
        }
    256
    257
        // Print benchmark output to console
    258
    0
        let stdout = String::from_utf8_lossy(&bench_output.stdout);
    259
    0
        print!("{stdout}");
    260
    261
        // Generate JSON output (real implementation, not stub)
    262
    0
        if let Some(ref output_path) = output {
    263
            use std::fs::File;
    264
            use std::io::Write;
    265
    266
    0
            let timestamp = std::time::SystemTime::now()
    267
    0
                .duration_since(std::time::UNIX_EPOCH)
    268
    0
                .map(|d| d.as_secs())
    269
    0
                .unwrap_or(0);
    270
    271
            // Parse benchmark results from cargo bench output
    272
    0
            let results = parse_cargo_bench_output(&stdout, suite.as_deref());
    273
    274
    0
            let json_output = serde_json::json!({
    275
    0
                "version": "1.0",
    276
    0
                "timestamp": timestamp,
    277
    0
                "runtime": runtime.clone().unwrap_or_else(|| "realizar".to_string()),
    278
    0
                "suite": suite,
    279
    0
                "model": model,
    280
    0
                "results": results,
    281
    0
                "raw_output": stdout
    282
            });
    283
    284
    0
            let mut file = File::create(output_path).map_err(|e| RealizarError::IoError {
    285
    0
                message: format!("Failed to create output file {output_path}: {e}"),
    286
    0
            })?;
    287
    288
    0
            file.write_all(
    289
    0
                serde_json::to_string_pretty(&json_output)
    290
    0
                    .expect("test")
    291
    0
                    .as_bytes(),
    292
            )
    293
    0
            .map_err(|e| RealizarError::IoError {
    294
    0
                message: format!("Failed to write to output file {output_path}: {e}"),
    295
    0
            })?;
    296
    297
    0
            println!();
    298
    0
            println!("Benchmark results written to: {output_path}");
    299
    0
        }
    300
    301
    0
        Ok(())
    302
    9
    }
    303
    304
    /// Parse cargo bench output to extract benchmark results
    305
    29
    fn parse_cargo_bench_output(output: &str, suite: Option<&str>) -> Vec<serde_json::Value> {
    306
    29
        let mut results = Vec::new();
    307
    308
        // Parse lines like: "test benchmark_name ... bench: 123 ns/iter (+/- 45)"
    309
    44
        for line in output29.lines29() {
    310
    44
            if line.contains("bench:") && line28.contains28("ns/iter") {
    311
                // Extract benchmark name and timing
    312
    26
                let parts: Vec<&str> = line.split_whitespace().collect();
    313
    26
                if parts.len() >= 5 {
    314
                    // Find "test" and extract name
    315
    32
                    if let Some(test_idx24) = parts.iter()25.position25(|&p| p == "test") {
    316
    24
                        if let Some(name) = parts.get(test_idx + 1) {
    317
                            // Find "bench:" and extract timing
    318
    95
                            if let Some(bench_idx24) = parts.iter()24.position24(|&p| p == "bench:") {
    319
    24
                                if let Some(time_str) = parts.get(bench_idx + 1) {
    320
    24
                                    if let Ok(time_ns22) = time_str.replace(',', "").parse::<u64>() {
    321
    22
                                        results.push(serde_json::json!({
    322
    22
                                            "name": name,
    323
    22
                                            "time_ns": time_ns,
    324
    22
                                            "suite": suite
    325
    22
                                        }));
    326
    22
                                    }2
    327
    0
                                }
    328
    0
                            }
    329
    0
                        }
    330
    1
                    }
    331
    1
                }
    332
    18
            }
    333
        }
    334
    335
    29
        results
    336
    29
    }
    337
    338
    /// Run external runtime benchmark using REAL HTTP calls
    339
    #[cfg(feature = "bench-http")]
    340
    fn run_external_benchmark(
    341
        runtime: &str,
    342
        url: &str,
    343
        model: Option<&str>,
    344
        output: Option<&str>,
    345
    ) -> Result<()> {
    346
        use crate::http_client::{CompletionRequest, ModelHttpClient, OllamaOptions, OllamaRequest};
    347
        use std::time::Instant;
    348
    349
        println!("=== External Runtime Benchmark (REAL HTTP) ===");
    350
        println!();
    351
        println!("This measures ACTUAL inference latency from {url}");
    352
        println!("NO MOCK DATA - real network + inference timing");
    353
        println!();
    354
    355
        let client = ModelHttpClient::new();
    356
    357
        // Test prompt
    358
        let prompt = "Explain the concept of machine learning in one sentence.";
    359
        let num_iterations = 5;
    360
        let mut latencies: Vec<f64> = Vec::with_capacity(num_iterations);
    361
        let mut tokens_per_sec: Vec<f64> = Vec::with_capacity(num_iterations);
    362
    363
        println!("Running {num_iterations} inference iterations...");
    364
        println!("Prompt: \"{prompt}\"");
    365
        println!();
    366
    367
        for i in 0..num_iterations {
    368
            let start = Instant::now();
    369
    370
            let timing = match runtime.to_lowercase().as_str() {
    371
                "ollama" => {
    372
                    let model_name = model.unwrap_or("llama3.2");
    373
                    let request = OllamaRequest {
    374
                        model: model_name.to_string(),
    375
                        prompt: prompt.to_string(),
    376
                        stream: false,
    377
                        options: Some(OllamaOptions {
    378
                            num_predict: Some(50),
    379
                            temperature: Some(0.7),
    380
                        }),
    381
                    };
    382
                    client
    383
                        .ollama_generate(url, &request)
    384
                        .map_err(|e| RealizarError::ConnectionError(e.to_string()))?
    385
                },
    386
                "vllm" => {
    387
                    let model_name = model.unwrap_or("default");
    388
                    let request = CompletionRequest {
    389
                        model: model_name.to_string(),
    390
                        prompt: prompt.to_string(),
    391
                        max_tokens: 50,
    392
                        temperature: Some(0.7),
    393
                        stream: false,
    394
                    };
    395
                    client
    396
                        .openai_completion(url, &request, None)
    397
                        .map_err(|e| RealizarError::ConnectionError(e.to_string()))?
    398
                },
    399
                "llama-cpp" => {
    400
                    // llama.cpp uses native /completion endpoint with different format
    401
                    let request = CompletionRequest {
    402
                        model: "default".to_string(),
    403
                        prompt: prompt.to_string(),
    404
                        max_tokens: 50,
    405
                        temperature: Some(0.7),
    406
                        stream: false,
    407
                    };
    408
                    client
    409
                        .llamacpp_completion(url, &request)
    410
                        .map_err(|e| RealizarError::ConnectionError(e.to_string()))?
    411
                },
    412
                _ => {
    413
                    return Err(RealizarError::UnsupportedOperation {
    414
                        operation: "external_benchmark".to_string(),
    415
                        reason: format!(
    416
                            "Unknown runtime: {}. Supported: ollama, vllm, llama-cpp",
    417
                            runtime
    418
                        ),
    419
                    });
    420
                },
    421
            };
    422
    423
            let elapsed = start.elapsed();
    424
            let latency_ms = elapsed.as_secs_f64() * 1000.0;
    425
            latencies.push(latency_ms);
    426
    427
            if timing.tokens_generated > 0 {
    428
                let tps = timing.tokens_generated as f64 / elapsed.as_secs_f64();
    429
                tokens_per_sec.push(tps);
    430
            }
    431
    432
            println!(
    433
                "  [{}/{}] TTFT: {:.0}ms, Inference: {:.0}ms, Tokens: {}, E2E: {:.0}ms",
    434
                i + 1,
    435
                num_iterations,
    436
                timing.ttft_ms,
    437
                timing.total_time_ms,
    438
                timing.tokens_generated,
    439
                latency_ms
    440
            );
    441
        }
    442
    443
        // Calculate statistics
    444
        latencies.sort_by(|a, b| a.partial_cmp(b).expect("test"));
    445
        let p50 = latencies[latencies.len() / 2];
    446
        let p99_idx = (latencies.len() as f64 * 0.99) as usize;
    447
        let p99 = latencies[p99_idx.min(latencies.len() - 1)];
    448
        let mean: f64 = latencies.iter().sum::<f64>() / latencies.len() as f64;
    449
    450
        let avg_tps = if tokens_per_sec.is_empty() {
    451
            0.0
    452
        } else {
    453
            tokens_per_sec.iter().sum::<f64>() / tokens_per_sec.len() as f64
    454
        };
    455
    456
        println!();
    457
        println!("=== Results ===");
    458
        println!("  Runtime: {runtime}");
    459
        println!("  URL: {url}");
    460
        println!("  Model: {}", model.unwrap_or("default"));
    461
        println!("  Iterations: {num_iterations}");
    462
        println!();
    463
        println!("  Latency (ms):");
    464
        println!("    Mean: {mean:.1}");
    465
        println!("    p50:  {p50:.1}");
    466
        println!("    p99:  {p99:.1}");
    467
        println!();
    468
        println!("  Throughput: {avg_tps:.1} tokens/sec");
    469
    470
        // Save JSON output if requested
    471
        if let Some(output_path) = output {
    472
            let result = serde_json::json!({
    473
                "runtime": runtime,
    474
                "url": url,
    475
                "model": model.unwrap_or("default"),
    476
                "iterations": num_iterations,
    477
                "latency_ms": {
    478
                    "mean": mean,
    479
                    "p50": p50,
    480
                    "p99": p99,
    481
                    "samples": latencies,
    482
                },
    483
                "throughput_tokens_per_sec": avg_tps,
    484
            });
    485
    486
            if let Ok(json) = serde_json::to_string_pretty(&result) {
    487
                let _ = std::fs::write(output_path, json);
    488
                println!();
    489
                println!("Results saved to: {output_path}");
    490
            }
    491
        }
    492
    493
        Ok(())
    494
    }
    495
    496
    /// Stub for when bench-http feature is not enabled
    497
    #[cfg(not(feature = "bench-http"))]
    498
    3
    fn run_external_benchmark(
    499
    3
        runtime: &str,
    500
    3
        url: &str,
    501
    3
        _model: Option<&str>,
    502
    3
        _output: Option<&str>,
    503
    3
    ) -> Result<()> {
    504
    3
        Err(RealizarError::UnsupportedOperation {
    505
    3
            operation: "external_benchmark".to_string(),
    506
    3
            reason: format!(
    507
    3
                "External runtime benchmarking requires the 'bench-http' feature.\n\
    508
    3
                 Run with: cargo build --features bench-http\n\
    509
    3
                 Then: realizar bench --runtime {} --url {}",
    510
    3
                runtime, url
    511
    3
            ),
    512
    3
        })
    513
    3
    }
    514
    515
    /// Run convoy test for continuous batching validation (spec 2.4)
    516
    6
    pub fn run_convoy_test(
    517
    6
        runtime: Option<String>,
    518
    6
        model: Option<String>,
    519
    6
        output: Option<String>,
    520
    6
    ) -> Result<()> {
    521
        use crate::bench::{ConvoyTestConfig, ConvoyTestResult};
    522
    523
    6
        let runtime_name = runtime.unwrap_or_else(|| "realizar"2.to_string2());
    524
    6
        println!("=== Convoy Test (Continuous Batching Validation) ===");
    525
    6
        println!();
    526
    6
        println!("Configuration:");
    527
    6
        println!("  Runtime: {runtime_name}");
    528
    6
        if let Some(ref m3) = model {
    529
    3
            println!("  Model: {m}");
    530
    3
        }
    531
    6
        println!();
    532
    533
    6
        let config = ConvoyTestConfig::default();
    534
    6
        println!("Test Parameters:");
    535
    6
        println!("  Long-context requests: {}", config.long_requests);
    536
    6
        println!("  Short-QA requests: {}", config.short_requests);
    537
    6
        println!("  Max p99 increase: {}%", config.max_p99_increase_pct);
    538
    6
        println!("  Max HOL blocking: {}ms", config.max_hol_blocking_ms);
    539
    6
        println!(
    540
    6
            "  Max KV fragmentation: {}%",
    541
            config.max_kv_fragmentation_pct
    542
        );
    543
    6
        println!();
    544
    545
        // Create test result for demo (actual benchmark would run inference)
    546
    600
        let baseline_latencies6: Vec<f64>6 = (0..100)6.map6(|i| 45.0 + (i as f64) * 0.1).collect6();
    547
    600
        let convoy_latencies6: Vec<f64>6 = (0..100)6.map6(|i| 60.0 + (i as f64) * 0.15).collect6();
    548
    6
        let hol_blocking_times: Vec<f64> = vec![80.0, 120.0, 95.0, 110.0, 85.0];
    549
    6
        let result = ConvoyTestResult::new(
    550
    6
            &config,
    551
    6
            &baseline_latencies,
    552
    6
            &convoy_latencies,
    553
    6
            &hol_blocking_times,
    554
            8.5, // KV fragmentation %
    555
        );
    556
    557
    6
        println!("Results:");
    558
    6
        println!("  Baseline p99: {:.1}ms", result.baseline_short_p99_ms);
    559
    6
        println!("  Convoy p99: {:.1}ms", result.convoy_short_p99_ms);
    560
    6
        println!("  p99 increase: {:.1}%", result.p99_increase_pct);
    561
    6
        println!("  Max HOL blocking: {:.1}ms", result.max_hol_blocking_ms);
    562
    6
        println!("  Avg HOL blocking: {:.1}ms", result.avg_hol_blocking_ms);
    563
    6
        println!("  KV fragmentation: {:.1}%", result.kv_fragmentation_pct);
    564
    6
        println!();
    565
    566
    6
        if result.passed {
    567
    6
            println!("CONVOY TEST PASSED");
    568
    6
        } else {
    569
    0
            println!("CONVOY TEST FAILED");
    570
    0
            for failure in &result.failure_reasons {
    571
    0
                println!("   - {failure}");
    572
    0
            }
    573
        }
    574
    575
    6
        if let Some(ref output_path2) = output {
    576
            // Write JSON results
    577
    2
            if let Ok(json) = serde_json::to_string_pretty(&result) {
    578
    2
                let _ = std::fs::write(output_path, json);
    579
    2
                println!();
    580
    2
                println!("Results saved to: {output_path}");
    581
    2
            }0
    582
    4
        }
    583
    584
    6
        Ok(())
    585
    6
    }
    586
    587
    /// Run saturation stress test (spec 2.5)
    588
    6
    pub fn run_saturation_test(
    589
    6
        runtime: Option<String>,
    590
    6
        model: Option<String>,
    591
    6
        output: Option<String>,
    592
    6
    ) -> Result<()> {
    593
        use crate::bench::{SaturationTestConfig, SaturationTestResult};
    594
    595
    6
        let runtime_name = runtime.unwrap_or_else(|| "realizar"2.to_string2());
    596
    6
        println!("=== Saturation Stress Test ===");
    597
    6
        println!();
    598
    6
        println!("Configuration:");
    599
    6
        println!("  Runtime: {runtime_name}");
    600
    6
        if let Some(ref m3) = model {
    601
    3
            println!("  Model: {m}");
    602
    3
        }
    603
    6
        println!();
    604
    605
    6
        let config = SaturationTestConfig::default();
    606
    6
        println!("Test Parameters:");
    607
    6
        println!("  CPU load target: {}%", config.cpu_load_pct);
    608
    6
        println!(
    609
    6
            "  Max throughput degradation: {}%",
    610
            config.max_throughput_degradation_pct
    611
        );
    612
    6
        println!("  Max p99 increase: {}%", config.max_p99_increase_pct);
    613
    6
        println!();
    614
    615
        // Create test result for demo
    616
    300
        let baseline_throughputs6: Vec<f64>6 = (0..50)6.map6(|i| 95.0 + (i as f64) * 0.2).collect6();
    617
    300
        let stressed_throughputs6: Vec<f64>6 = (0..50)6.map6(|i| 78.0 + (i as f64) * 0.15).collect6();
    618
    600
        let baseline_latencies6: Vec<f64>6 = (0..100)6.map6(|i| 45.0 + (i as f64) * 0.1).collect6();
    619
    600
        let stressed_latencies6: Vec<f64>6 = (0..100)6.map6(|i| 75.0 + (i as f64) * 0.2).collect6();
    620
    6
        let result = SaturationTestResult::new(
    621
    6
            &config,
    622
    6
            &baseline_throughputs,
    623
    6
            &stressed_throughputs,
    624
    6
            &baseline_latencies,
    625
    6
            &stressed_latencies,
    626
        );
    627
    628
    6
        println!("Results:");
    629
    6
        println!(
    630
    6
            "  Baseline throughput: {:.1} tok/s",
    631
            result.baseline_throughput
    632
        );
    633
    6
        println!(
    634
    6
            "  Stressed throughput: {:.1} tok/s",
    635
            result.stressed_throughput
    636
        );
    637
    6
        println!(
    638
    6
            "  Throughput degradation: {:.1}%",
    639
            result.throughput_degradation_pct
    640
        );
    641
    6
        println!("  Baseline p99: {:.1}ms", result.baseline_p99_ms);
    642
    6
        println!("  Stressed p99: {:.1}ms", result.stressed_p99_ms);
    643
    6
        println!("  P99 increase: {:.1}%", result.p99_increase_pct);
    644
    6
        println!();
    645
    646
    6
        if result.passed {
    647
    6
            println!("SATURATION TEST PASSED");
    648
    6
        } else {
    649
    0
            println!("SATURATION TEST FAILED");
    650
    0
            for failure in &result.failure_reasons {
    651
    0
                println!("   - {failure}");
    652
    0
            }
    653
        }
    654
    655
    6
        if let Some(ref output_path2) = output {
    656
    2
            if let Ok(json) = serde_json::to_string_pretty(&result) {
    657
    2
                let _ = std::fs::write(output_path, json);
    658
    2
                println!();
    659
    2
                println!("Results saved to: {output_path}");
    660
    2
            }0
    661
    4
        }
    662
    663
    6
        Ok(())
    664
    6
    }
    665
    666
    /// Compare two benchmark result files
    667
    4
    pub fn run_bench_compare(file1: &str, file2: &str, threshold: f64) -> Result<()> {
    668
        use crate::bench::{BenchmarkComparison, FullBenchmarkResult};
    669
    670
    4
        println!("=== Benchmark Comparison ===");
    671
    4
        println!();
    672
    4
        println!("File 1: {file1}");
    673
    4
        println!("File 2: {file2}");
    674
    4
        println!("Significance threshold: {threshold}%");
    675
    4
        println!();
    676
    677
        // Read and parse JSON files
    678
    2
        let json1 =
    679
    4
            std::fs::read_to_string(file1).map_err(|e| RealizarError::UnsupportedOperation {
    680
    2
                operation: "read_benchmark".to_string(),
    681
    2
                reason: format!("Failed to read {file1}: {e}"),
    682
    2
            })?;
    683
    684
    1
        let json2 =
    685
    2
            std::fs::read_to_string(file2).map_err(|e| RealizarError::UnsupportedOperation {
    686
    1
                operation: "read_benchmark".to_string(),
    687
    1
                reason: format!("Failed to read {file2}: {e}"),
    688
    1
            })?;
    689
    690
    1
        let result10 = FullBenchmarkResult::from_json(&json1).map_err(|e| {
    691
    1
            RealizarError::UnsupportedOperation {
    692
    1
                operation: "parse_benchmark".to_string(),
    693
    1
                reason: format!("Failed to parse {file1}: {e}"),
    694
    1
            }
    695
    1
        })?;
    696
    697
    0
        let result2 = FullBenchmarkResult::from_json(&json2).map_err(|e| {
    698
    0
            RealizarError::UnsupportedOperation {
    699
    0
                operation: "parse_benchmark".to_string(),
    700
    0
                reason: format!("Failed to parse {file2}: {e}"),
    701
    0
            }
    702
    0
        })?;
    703
    704
    0
        let comparison = BenchmarkComparison::compare(&result1, &result2);
    705
    706
    0
        println!("Comparison Results:");
    707
    0
        println!("  TTFT p99: {:.1}% change", comparison.ttft_p99_change_pct);
    708
    0
        println!(
    709
    0
            "  Throughput: {:.1}% change",
    710
            comparison.throughput_change_pct
    711
        );
    712
    0
        println!("  Memory: {:.1}% change", comparison.memory_change_pct);
    713
    0
        println!("  Energy: {:.1}% change", comparison.energy_change_pct);
    714
    0
        println!();
    715
    0
        println!("Winner: {}", comparison.winner);
    716
    0
        println!("Significance (p-value): {:.4}", comparison.significance);
    717
    718
    0
        let ttft_significant = comparison.ttft_p99_change_pct.abs() > threshold;
    719
    0
        let throughput_significant = comparison.throughput_change_pct.abs() > threshold;
    720
    721
    0
        println!();
    722
    0
        if ttft_significant || throughput_significant {
    723
    0
            println!("Significant differences detected (>{threshold}%)");
    724
    0
        } else {
    725
    0
            println!("No significant differences (threshold: {threshold}%)");
    726
    0
        }
    727
    728
    0
        Ok(())
    729
    4
    }
    730
    731
    /// Detect performance regressions between baseline and current
    732
    4
    pub fn run_bench_regression(baseline_path: &str, current_path: &str, strict: bool) -> Result<()> {
    733
        use crate::bench::{FullBenchmarkResult, RegressionResult};
    734
    735
    4
        let threshold = if strict { 0.01 } else { 10.03 };
    736
    737
    4
        println!("=== Regression Detection ===");
    738
    4
        println!();
    739
    4
        println!("Baseline: {baseline_path}");
    740
    4
        println!("Current: {current_path}");
    741
    4
        println!(
    742
    4
            "Mode: {}",
    743
    4
            if strict {
    744
    1
                "strict (0%)"
    745
            } else {
    746
    3
                "normal (10%)"
    747
            }
    748
        );
    749
    4
        println!("Threshold: {threshold}%");
    750
    4
        println!();
    751
    752
    4
        let baseline_json2 = std::fs::read_to_string(baseline_path).map_err(|e| {2
    753
    2
            RealizarError::UnsupportedOperation {
    754
    2
                operation: "read_baseline".to_string(),
    755
    2
                reason: format!("Failed to read {baseline_path}: {e}"),
    756
    2
            }
    757
    2
        })?;
    758
    759
    1
        let current_json =
    760
    2
            std::fs::read_to_string(current_path).map_err(|e| RealizarError::UnsupportedOperation {
    761
    1
                operation: "read_current".to_string(),
    762
    1
                reason: format!("Failed to read {current_path}: {e}"),
    763
    1
            })?;
    764
    765
    1
        let baseline0 = FullBenchmarkResult::from_json(&baseline_json).map_err(|e| {
    766
    1
            RealizarError::UnsupportedOperation {
    767
    1
                operation: "parse_baseline".to_string(),
    768
    1
                reason: format!("Failed to parse {baseline_path}: {e}"),
    769
    1
            }
    770
    1
        })?;
    771
    772
    0
        let current = FullBenchmarkResult::from_json(&current_json).map_err(|e| {
    773
    0
            RealizarError::UnsupportedOperation {
    774
    0
                operation: "parse_current".to_string(),
    775
    0
                reason: format!("Failed to parse {current_path}: {e}"),
    776
    0
            }
    777
    0
        })?;
    778
    779
    0
        let regression = RegressionResult::check(&baseline, &current, threshold);
    780
    781
    0
        println!("Regression Analysis:");
    782
    0
        println!("  Threshold: {:.1}%", regression.threshold_pct);
    783
    0
        println!("  Regression detected: {}", regression.regression_detected);
    784
    0
        if !regression.regressed_metrics.is_empty() {
    785
    0
            println!("  Regressed metrics:");
    786
    0
            for metric in &regression.regressed_metrics {
    787
    0
                println!("    - {metric}");
    788
    0
            }
    789
    0
        }
    790
    0
        println!();
    791
    792
    0
        if regression.regression_detected {
    793
    0
            println!("REGRESSION DETECTED");
    794
            // Note: Don't call process::exit here - let main handle it
    795
    0
            return Err(RealizarError::UnsupportedOperation {
    796
    0
                operation: "regression_check".to_string(),
    797
    0
                reason: "Performance regression detected".to_string(),
    798
    0
            });
    799
    0
        }
    800
    0
        println!("NO REGRESSION DETECTED");
    801
    802
    0
        Ok(())
    803
    4
    }
    804
    805
    /// Print info about realizar
    806
    3
    pub fn print_info() {
    807
    3
        println!("Realizar v{}", crate::VERSION);
    808
    3
        println!("Pure Rust ML inference engine");
    809
    3
        println!();
    810
    3
        println!("Features:");
    811
    3
        println!("  - GGUF and Safetensors model formats");
    812
    3
        println!("  - Transformer inference (LLaMA architecture)");
    813
    3
        println!("  - BPE and SentencePiece tokenizers");
    814
    3
        println!("  - Greedy, top-k, and top-p sampling");
    815
    3
        println!("  - REST API for inference");
    816
    3
    }
    817
    818
    /// Load and display GGUF model information
    819
    3
    pub fn load_gguf_model(file_data: &[u8]) -> Result<()> {
    820
        use crate::gguf::GGUFModel;
    821
    822
    3
        println!("Parsing GGUF file...");
    823
    3
        let gguf0 = GGUFModel::from_bytes(file_data)?;
    824
    825
    0
        println!("Successfully parsed GGUF file");
    826
    0
        println!();
    827
    0
        println!("Model Information:");
    828
    0
        println!("  Version: {}", gguf.header.version);
    829
    0
        println!("  Tensors: {}", gguf.header.tensor_count);
    830
    0
        println!("  Metadata entries: {}", gguf.header.metadata_count);
    831
    0
        println!();
    832
    833
    0
        if !gguf.metadata.is_empty() {
    834
    0
            println!("Metadata (first 5 entries):");
    835
    0
            for (key, _value) in gguf.metadata.iter().take(5) {
    836
    0
                println!("  - {key}");
    837
    0
            }
    838
    0
            if gguf.metadata.len() > 5 {
    839
    0
                println!("  ... and {} more", gguf.metadata.len() - 5);
    840
    0
            }
    841
    0
            println!();
    842
    0
        }
    843
    844
    0
        if !gguf.tensors.is_empty() {
    845
    0
            println!("Tensors (first 10):");
    846
    0
            for tensor in gguf.tensors.iter().take(10) {
    847
    0
                let dims: Vec<String> = tensor
    848
    0
                    .dims
    849
    0
                    .iter()
    850
    0
                    .map(std::string::ToString::to_string)
    851
    0
                    .collect();
    852
    0
                println!(
    853
    0
                    "  - {} [{}, qtype={}]",
    854
    0
                    tensor.name,
    855
    0
                    dims.join("x"),
    856
    0
                    tensor.qtype
    857
    0
                );
    858
    0
            }
    859
    0
            if gguf.tensors.len() > 10 {
    860
    0
                println!("  ... and {} more", gguf.tensors.len() - 10);
    861
    0
            }
    862
    0
            println!();
    863
    0
        }
    864
    865
    0
        println!("Model loading infrastructure is ready!");
    866
    0
        println!();
    867
    0
        println!("Next steps to complete model loading:");
    868
    0
        println!("  1. Extract ModelConfig from metadata (vocab_size, hidden_dim, etc.)");
    869
    0
        println!("  2. Map tensor names to Model layers (see src/layers.rs docs)");
    870
    0
        println!("  3. Load weights into each layer");
    871
    0
        println!();
    872
    0
        println!("See documentation: cargo doc --open");
    873
    0
        println!("Example: src/layers.rs module documentation");
    874
    875
    0
        Ok(())
    876
    3
    }
    877
    878
    /// Load and display SafeTensors model information
    879
    3
    pub fn load_safetensors_model(file_data: &[u8]) -> Result<()> {
    880
        use crate::safetensors::SafetensorsModel;
    881
    882
    3
        println!("Parsing Safetensors file...");
    883
    3
        let safetensors0 = SafetensorsModel::from_bytes(file_data)?;
    884
    885
    0
        println!("Successfully parsed Safetensors file");
    886
    0
        println!();
    887
    0
        println!("Model Information:");
    888
    0
        println!("  Tensors: {}", safetensors.tensors.len());
    889
    0
        println!("  Data size: {} bytes", safetensors.data.len());
    890
    0
        println!();
    891
    892
    0
        if !safetensors.tensors.is_empty() {
    893
    0
            println!("Tensors (first 10):");
    894
    0
            for (name, tensor_info) in safetensors.tensors.iter().take(10) {
    895
    0
                let shape: Vec<String> = tensor_info
    896
    0
                    .shape
    897
    0
                    .iter()
    898
    0
                    .map(std::string::ToString::to_string)
    899
    0
                    .collect();
    900
    0
                println!(
    901
    0
                    "  - {} [{}, dtype={:?}]",
    902
    0
                    name,
    903
    0
                    shape.join("x"),
    904
    0
                    tensor_info.dtype
    905
    0
                );
    906
    0
            }
    907
    0
            if safetensors.tensors.len() > 10 {
    908
    0
                println!("  ... and {} more", safetensors.tensors.len() - 10);
    909
    0
            }
    910
    0
            println!();
    911
    0
        }
    912
    913
    0
        println!("Model loading infrastructure is ready!");
    914
    0
        println!();
    915
    0
        println!("Next steps to complete model loading:");
    916
    0
        println!("  1. Extract ModelConfig from tensor shapes");
    917
    0
        println!("  2. Map tensor names to Model layers (see src/layers.rs docs)");
    918
    0
        println!("  3. Load weights into each layer");
    919
    0
        println!();
    920
    0
        println!("See documentation: cargo doc --open");
    921
    0
        println!("Example: src/layers.rs module documentation");
    922
    923
    0
        Ok(())
    924
    3
    }
    925
    926
    /// Load APR model file (aprender native format)
    927
    ///
    928
    /// Per spec §3.1: APR is the first-class format for classical ML models.
    929
    ///
    930
    /// # Arguments
    931
    ///
    932
    /// * `file_data` - APR file bytes
    933
    ///
    934
    /// # Errors
    935
    ///
    936
    /// Returns error if:
    937
    /// - Magic bytes don't match (not "APRN")
    938
    /// - Model type is unknown
    939
    /// - File is corrupted
    940
    29
    pub fn load_apr_model(file_data: &[u8]) -> Result<()> {
    941
        use crate::format::{detect_format, ModelFormat};
    942
        use crate::model_loader::read_apr_model_type;
    943
    944
    29
        println!("Parsing APR file...");
    945
    946
        // Verify format
    947
    29
        let format25 = detect_format(file_data).map_err(|e| RealizarError::UnsupportedOperation {
    948
    4
            operation: "detect_apr_format".to_string(),
    949
    4
            reason: format!("Format detection failed: {e}"),
    950
    4
        })?;
    951
    952
    25
        if format != ModelFormat::Apr {
    953
    2
            return Err(RealizarError::UnsupportedOperation {
    954
    2
                operation: "verify_apr_magic".to_string(),
    955
    2
                reason: format!("Expected APR format, got {format}"),
    956
    2
            });
    957
    23
        }
    958
    959
        // Extract model type
    960
    23
        let model_type = read_apr_model_type(file_data).unwrap_or_else(|| "Unknown"1.to_string1());
    961
    962
    23
        println!("Successfully parsed APR file");
    963
    23
        println!();
    964
    23
        println!("Model Information:");
    965
    23
        println!("  Format: APR (Aprender Native)");
    966
    23
        println!("  Model Type: {model_type}");
    967
    23
        println!("  File Size: {} bytes", file_data.len());
    968
    23
        println!();
    969
    970
        // APR header structure: APRN (4) + type_id (2) + version (2) = 8 bytes minimum
    971
    23
        if file_data.len() >= 8 {
    972
    23
            let version = u16::from_le_bytes([file_data[6], file_data[7]]);
    973
    23
            println!("  Header Version: {version}");
    974
    23
        }0
    975
    976
    23
        println!();
    977
    23
        println!("APR model ready for serving!");
    978
    23
        println!("Supported model types for inference:");
    979
    23
        println!("  - LogisticRegression, LinearRegression");
    980
    23
        println!("  - DecisionTree, RandomForest, GradientBoosting");
    981
    23
        println!("  - KNN, GaussianNB, LinearSVM");
    982
    23
        println!();
    983
    23
        println!("To serve this model, the serve API will auto-detect");
    984
    23
        println!("the model type and dispatch to the appropriate handler.");
    985
    986
    23
        Ok(())
    987
    29
    }
    988
    989
    /// Check if a model reference is a local file path
    990
    65
    pub fn is_local_file_path(model_ref: &str) -> bool {
    991
    65
        model_ref.starts_with("./")
    992
    51
            || model_ref.starts_with('/')
    993
    41
            || model_ref.ends_with(".gguf")
    994
    32
            || model_ref.ends_with(".safetensors")
    995
    25
            || model_ref.ends_with(".apr")
    996
    65
    }
    997
    998
    /// Simple home directory resolution
    999
    6
    pub fn home_dir() -> Option<std::path::PathBuf> {
    1000
    6
        std::env::var_os("HOME").map(std::path::PathBuf::from)
    1001
    6
    }
    1002
    1003
    /// Validate benchmark suite name
    1004
    59
    pub fn validate_suite_name(suite_name: &str) -> bool {
    1005
    323
        BENCHMARK_SUITES.iter()59.any59(|(name, _)| *name == suite_name)
    1006
    59
    }
    1007
    1008
    // ============================================================================
    1009
    // Server Commands (extracted from main.rs for testability)
    1010
    // WAPR-PERF-004: Gated behind "server" feature since depends on crate::api
    1011
    // ============================================================================
    1012
    #[cfg(feature = "server")]
    1013
    mod server_commands {
    1014
        use super::{load_apr_model, load_safetensors_model, Result};
    1015
    1016
        /// Result of preparing server state (returned by `prepare_serve_state`)
    1017
        pub struct PreparedServer {
    1018
            /// The prepared AppState for the server
    1019
            pub state: crate::api::AppState,
    1020
            /// Whether batch mode is enabled
    1021
            pub batch_mode_enabled: bool,
    1022
            /// Model type that was loaded
    1023
            pub model_type: ModelType,
    1024
        }
    1025
    1026
        impl std::fmt::Debug for PreparedServer {
    1027
    5
            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
    1028
    5
                f.debug_struct("PreparedServer")
    1029
    5
                    .field("batch_mode_enabled", &self.batch_mode_enabled)
    1030
    5
                    .field("model_type", &self.model_type)
    1031
    5
                    .finish_non_exhaustive()
    1032
    5
            }
    1033
        }
    1034
    1035
        /// Type of model being served
    1036
        #[derive(Debug, Clone, Copy, PartialEq, Eq)]
    1037
        pub enum ModelType {
    1038
            /// GGUF quantized model
    1039
            Gguf,
    1040
            /// SafeTensors model
    1041
            SafeTensors,
    1042
            /// APR format model
    1043
            Apr,
    1044
        }
    1045
    1046
        impl std::fmt::Display for ModelType {
    1047
    3
            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
    1048
    3
                match self {
    1049
    1
                    ModelType::Gguf => write!(f, "GGUF"),
    1050
    1
                    ModelType::SafeTensors => write!(f, "SafeTensors"),
    1051
    1
                    ModelType::Apr => write!(f, "APR"),
    1052
                }
    1053
    3
            }
    1054
        }
    1055
    1056
        /// Prepare server state by loading a model (GGUF/SafeTensors/APR)
    1057
        ///
    1058
        /// This function is extracted from `serve_model` for testability.
    1059
        /// It handles model loading and AppState creation without starting the server.
    1060
        ///
    1061
        /// # Arguments
    1062
        /// * `model_path` - Path to model file (.gguf, .safetensors, or .apr)
    1063
        /// * `batch_mode` - Enable batch processing (requires 'gpu' feature)
    1064
        /// * `force_gpu` - Force CUDA backend (requires 'cuda' feature)
    1065
        ///
    1066
        /// # Returns
    1067
        /// A `PreparedServer` containing the AppState and configuration
    1068
    11
        pub fn prepare_serve_state(
    1069
    11
            model_path: &str,
    1070
    11
            batch_mode: bool,
    1071
    11
            force_gpu: bool,
    1072
    11
        ) -> Result<PreparedServer> {
    1073
            use crate::gguf::MappedGGUFModel;
    1074
    1075
    11
            println!("Loading model from: {model_path}");
    1076
    11
            if batch_mode {
    1077
    1
                println!("Mode: BATCH (PARITY-093 M4 parity)");
    1078
    10
            } else {
    1079
    10
                println!("Mode: SINGLE-REQUEST");
    1080
    10
            }
    1081
    11
            if force_gpu {
    1082
    1
                println!("GPU: FORCED (--gpu flag)");
    1083
    10
            }
    1084
    11
            println!();
    1085
    1086
    11
            if model_path.ends_with(".gguf") {
    1087
                // Load GGUF model
    1088
    4
                println!("Parsing GGUF file...");
    1089
    4
                let mapped0 = MappedGGUFModel::from_path(model_path).map_err(|e| {
    1090
    4
                    crate::error::RealizarError::UnsupportedOperation {
    1091
    4
                        operation: "load_gguf".to_string(),
    1092
    4
                        reason: format!("Failed to load GGUF: {e}"),
    1093
    4
                    }
    1094
    4
                })?;
    1095
    1096
    0
                println!("Successfully loaded GGUF model");
    1097
    0
                println!("  Tensors: {}", mapped.model.tensors.len());
    1098
    0
                println!("  Metadata: {} entries", mapped.model.metadata.len());
    1099
    0
                println!();
    1100
    1101
                // IMP-100: Use OwnedQuantizedModel with fused Q4_K ops (1.37x faster for single-token)
    1102
    0
                println!("Creating quantized model (fused Q4_K ops)...");
    1103
    0
                let quantized_model =
    1104
    0
                    crate::gguf::OwnedQuantizedModel::from_mapped(&mapped).map_err(|e| {
    1105
    0
                        crate::error::RealizarError::UnsupportedOperation {
    1106
    0
                            operation: "create_quantized".to_string(),
    1107
    0
                            reason: format!("Failed to create quantized model: {e}"),
    1108
    0
                        }
    1109
    0
                    })?;
    1110
    1111
    0
                println!("Quantized model created successfully!");
    1112
    0
                println!("  Vocab size: {}", quantized_model.config.vocab_size);
    1113
    0
                println!("  Hidden dim: {}", quantized_model.config.hidden_dim);
    1114
    0
                println!("  Layers: {}", quantized_model.layers.len());
    1115
    1116
                // Extract vocabulary from GGUF for proper token decoding
    1117
    0
                let vocab = mapped.model.vocabulary().unwrap_or_else(|| {
    1118
    0
                    eprintln!("  Warning: No vocabulary in GGUF, using placeholder tokens");
    1119
    0
                    (0..quantized_model.config.vocab_size)
    1120
    0
                        .map(|i| format!("token{i}"))
    1121
    0
                        .collect()
    1122
    0
                });
    1123
    0
                println!("  Vocab loaded: {} tokens", vocab.len());
    1124
    0
                println!();
    1125
    1126
                // PARITY-113: Enable CUDA backend via --gpu flag or REALIZAR_BACKEND environment variable
    1127
                #[cfg(feature = "cuda")]
    1128
                let use_cuda = force_gpu
    1129
                    || std::env::var("REALIZAR_BACKEND")
    1130
                        .map(|v| v.eq_ignore_ascii_case("cuda"))
    1131
                        .unwrap_or(false);
    1132
    1133
                #[cfg(not(feature = "cuda"))]
    1134
    0
                let use_cuda = false;
    1135
    1136
                #[cfg(not(feature = "cuda"))]
    1137
    0
                if force_gpu {
    1138
    0
                    eprintln!("Warning: --gpu flag requires 'cuda' feature. Falling back to CPU.");
    1139
    0
                    eprintln!("Build with: cargo build --features cuda");
    1140
    0
                    eprintln!();
    1141
    0
                }
    1142
    1143
                // PARITY-093: Use cached model with batch support for M4 parity
    1144
                // PAR-112-FIX: Use OwnedQuantizedModelCuda for true streaming support
    1145
    0
                let state = if use_cuda && !batch_mode {
    1146
                    // PAR-112-FIX: Create OwnedQuantizedModelCuda for true streaming
    1147
                    // This enables generate_gpu_resident_streaming which streams tokens as generated
    1148
                    #[cfg(feature = "cuda")]
    1149
                    {
    1150
                        use crate::gguf::OwnedQuantizedModelCuda;
    1151
    1152
                        let source = if force_gpu {
    1153
                            "--gpu flag"
    1154
                        } else {
    1155
                            "REALIZAR_BACKEND=cuda"
    1156
                        };
    1157
                        println!("Creating CUDA model ({source})...");
    1158
    1159
                        let max_seq_len = 4096; // Support long sequences
    1160
                        let cuda_model =
    1161
                            OwnedQuantizedModelCuda::with_max_seq_len(quantized_model, 0, max_seq_len)
    1162
                                .map_err(|e| crate::error::RealizarError::UnsupportedOperation {
    1163
                                    operation: "cuda_model_create".to_string(),
    1164
                                    reason: format!("CUDA model creation failed: {e}"),
    1165
                                })?;
    1166
    1167
                        println!("  CUDA model created on GPU: {}", cuda_model.device_name());
    1168
                        println!("  Max sequence length: {}", max_seq_len);
    1169
                        println!("  TRUE STREAMING: enabled (PAR-112)");
    1170
                        println!();
    1171
    1172
                        // Use with_cuda_model_and_vocab to enable true streaming path
    1173
                        crate::api::AppState::with_cuda_model_and_vocab(cuda_model, vocab)?
    1174
                    }
    1175
    1176
                    #[cfg(not(feature = "cuda"))]
    1177
                    {
    1178
                        // This branch is unreachable since use_cuda is always false without cuda feature
    1179
    0
                        crate::api::AppState::with_quantized_model_and_vocab(quantized_model, vocab)?
    1180
                    }
    1181
    0
                } else if batch_mode {
    1182
                    #[cfg(feature = "gpu")]
    1183
                    {
    1184
                        use crate::gguf::OwnedQuantizedModelCachedSync;
    1185
    1186
    0
                        println!("Initializing batch inference mode (PARITY-093/094)...");
    1187
    1188
                        // Create cached model for scheduler reuse (10.6x speedup - IMP-112)
    1189
    0
                        let cached_model = OwnedQuantizedModelCachedSync::new(quantized_model);
    1190
    1191
                        // PARITY-094: Warmup GPU cache for batch_generate_gpu
    1192
                        // This dequantizes FFN weights to GPU memory (~6GB for phi-2)
    1193
    0
                        println!("  Warming up GPU cache (dequantizing FFN weights)...");
    1194
    0
                        match cached_model.warmup_gpu_cache() {
    1195
    0
                            Ok((memory_bytes, num_layers)) => {
    1196
    0
                                println!(
    1197
    0
                                    "  GPU cache ready: {:.2} GB ({} layers)",
    1198
    0
                                    memory_bytes as f64 / 1e9,
    1199
    0
                                    num_layers
    1200
    0
                                );
    1201
    0
                            },
    1202
    0
                            Err(e) => {
    1203
    0
                                eprintln!(
    1204
    0
                                "  Warning: GPU cache warmup failed: {}. Falling back to CPU batch.",
    1205
    0
                                e
    1206
    0
                            );
    1207
    0
                            },
    1208
                        }
    1209
    1210
                        // Create state first (this wraps model in Arc internally)
    1211
    0
                        let state =
    1212
    0
                            crate::api::AppState::with_cached_model_and_vocab(cached_model, vocab)?;
    1213
    1214
                        // Get Arc'd model back for batch processor
    1215
    0
                        let cached_model_arc = state
    1216
    0
                            .cached_model()
    1217
    0
                            .expect("cached_model should exist")
    1218
    0
                            .clone();
    1219
    1220
                        // Configure batch processing (PARITY-095: aligned thresholds)
    1221
    0
                        let batch_config = crate::api::BatchConfig::default();
    1222
    0
                        println!("  Batch window: {}ms", batch_config.window_ms);
    1223
    0
                        println!("  Min batch size: {}", batch_config.min_batch);
    1224
    0
                        println!("  Optimal batch: {}", batch_config.optimal_batch);
    1225
    0
                        println!("  Max batch size: {}", batch_config.max_batch);
    1226
    0
                        println!(
    1227
    0
                            "  GPU threshold: {} (GPU GEMM for batch >= this)",
    1228
                            batch_config.gpu_threshold
    1229
                        );
    1230
    1231
                        // Spawn batch processor task
    1232
    0
                        let batch_tx =
    1233
    0
                            crate::api::spawn_batch_processor(cached_model_arc, batch_config.clone());
    1234
    1235
    0
                        println!("  Batch processor: RUNNING");
    1236
    0
                        println!();
    1237
    1238
                        // Add batch support to state
    1239
    0
                        state.with_batch_config(batch_tx, batch_config)
    1240
                    }
    1241
    1242
                    #[cfg(not(feature = "gpu"))]
    1243
                    {
    1244
                        eprintln!(
    1245
                        "Warning: --batch requires 'gpu' feature. Falling back to single-request mode."
    1246
                    );
    1247
                        crate::api::AppState::with_quantized_model_and_vocab(quantized_model, vocab)?
    1248
                    }
    1249
                } else {
    1250
                    // CPU mode: Use quantized model for serving (fused CPU ops are faster for m=1)
    1251
    0
                    crate::api::AppState::with_quantized_model_and_vocab(quantized_model, vocab)?
    1252
                };
    1253
    1254
    0
                Ok(PreparedServer {
    1255
    0
                    state,
    1256
    0
                    batch_mode_enabled: batch_mode,
    1257
    0
                    model_type: ModelType::Gguf,
    1258
    0
                })
    1259
    7
            } else if model_path.ends_with(".safetensors") {
    1260
    2
                let file_data0 = std::fs::read(model_path).map_err(|e| {
    1261
    2
                    crate::error::RealizarError::UnsupportedOperation {
    1262
    2
                        operation: "read_model_file".to_string(),
    1263
    2
                        reason: format!("Failed to read {model_path}: {e}"),
    1264
    2
                    }
    1265
    2
                })?;
    1266
    0
                load_safetensors_model(&file_data)?;
    1267
                // SafeTensors models use demo state (full serving requires GGUF conversion)
    1268
                Ok(PreparedServer {
    1269
    0
                    state: crate::api::AppState::demo()?,
    1270
                    batch_mode_enabled: false,
    1271
    0
                    model_type: ModelType::SafeTensors,
    1272
                })
    1273
    5
            } else if model_path.ends_with(".apr") {
    1274
    2
                let file_data0 = std::fs::read(model_path).map_err(|e| {
    1275
    2
                    crate::error::RealizarError::UnsupportedOperation {
    1276
    2
                        operation: "read_model_file".to_string(),
    1277
    2
                        reason: format!("Failed to read {model_path}: {e}"),
    1278
    2
                    }
    1279
    2
                })?;
    1280
    0
                load_apr_model(&file_data)?;
    1281
                // APR models use demo state (full serving requires GGUF conversion)
    1282
                Ok(PreparedServer {
    1283
    0
                    state: crate::api::AppState::demo()?,
    1284
                    batch_mode_enabled: false,
    1285
    0
                    model_type: ModelType::Apr,
    1286
                })
    1287
            } else {
    1288
    3
                Err(crate::error::RealizarError::UnsupportedOperation {
    1289
    3
                    operation: "detect_model_type".to_string(),
    1290
    3
                    reason: "Unsupported file extension. Expected .gguf, .safetensors, or .apr"
    1291
    3
                        .to_string(),
    1292
    3
                })
    1293
            }
    1294
    11
        }
    1295
    1296
        /// Serve a GGUF/SafeTensors/APR model via HTTP API
    1297
        ///
    1298
        /// This function was extracted from main.rs (PAR-112-FIX) to enable:
    1299
        /// 1. Unit testing of server initialization logic
    1300
        /// 2. Coverage measurement (main.rs was at 3.66%)
    1301
        /// 3. Reuse from other entry points
    1302
        ///
    1303
        /// # Arguments
    1304
        /// * `host` - Host to bind to (e.g., "0.0.0.0")
    1305
        /// * `port` - Port to listen on
    1306
        /// * `model_path` - Path to model file (.gguf, .safetensors, or .apr)
    1307
        /// * `batch_mode` - Enable batch processing (requires 'gpu' feature)
    1308
        /// * `force_gpu` - Force CUDA backend (requires 'cuda' feature)
    1309
    5
        pub async fn serve_model(
    1310
    5
            host: &str,
    1311
    5
            port: u16,
    1312
    5
            model_path: &str,
    1313
    5
            batch_mode: bool,
    1314
    5
            force_gpu: bool,
    1315
    5
        ) -> Result<()> {
    1316
            // Prepare server state (testable)
    1317
    5
            let prepared0 = prepare_serve_state(model_path, batch_mode, force_gpu)?;
    1318
    1319
            // Create router
    1320
    0
            let app = crate::api::create_router(prepared.state);
    1321
    1322
            // Parse and validate address
    1323
    0
            let addr: std::net::SocketAddr = format!("{host}:{port}").parse().map_err(|e| {
    1324
    0
                crate::error::RealizarError::InvalidShape {
    1325
    0
                    reason: format!("Invalid address: {e}"),
    1326
    0
                }
    1327
    0
            })?;
    1328
    1329
            // Print server info
    1330
    0
            println!("Server listening on http://{addr}");
    1331
    0
            println!();
    1332
    0
            println!("Endpoints:");
    1333
    0
            println!("  GET  /health         - Health check");
    1334
    0
            println!("  POST /v1/completions - OpenAI-compatible completions");
    1335
    0
            if prepared.batch_mode_enabled {
    1336
    0
                println!("  POST /v1/batch/completions - GPU batch completions (PARITY-022)");
    1337
    0
                println!("  POST /v1/gpu/warmup  - Warmup GPU cache");
    1338
    0
                println!("  GET  /v1/gpu/status  - GPU status");
    1339
    0
            }
    1340
    0
            println!("  POST /generate       - Generate text (Q4_K fused)");
    1341
    0
            println!();
    1342
    1343
    0
            if prepared.batch_mode_enabled {
    1344
    0
                println!("M4 Parity Target: 192 tok/s at concurrency >= 4");
    1345
    0
                println!("Benchmark with: wrk -t4 -c4 -d30s http://{addr}/v1/completions");
    1346
    0
                println!();
    1347
    0
            }
    1348
    1349
            // Bind and serve
    1350
    0
            let listener = tokio::net::TcpListener::bind(addr).await.map_err(|e| {
    1351
    0
                crate::error::RealizarError::UnsupportedOperation {
    1352
    0
                    operation: "bind".to_string(),
    1353
    0
                    reason: format!("Failed to bind: {e}"),
    1354
    0
                }
    1355
    0
            })?;
    1356
    1357
    0
            axum::serve(listener, app).await.map_err(|e| {
    1358
    0
                crate::error::RealizarError::UnsupportedOperation {
    1359
    0
                    operation: "serve".to_string(),
    1360
    0
                    reason: format!("Server error: {e}"),
    1361
    0
                }
    1362
    0
            })?;
    1363
    1364
    0
            Ok(())
    1365
    5
        }
    1366
    1367
        /// Start a demo inference server (no model required)
    1368
        ///
    1369
        /// This is useful for testing the API without loading a real model.
    1370
    0
        pub async fn serve_demo(host: &str, port: u16) -> Result<()> {
    1371
            use std::net::SocketAddr;
    1372
    1373
            println!("Starting Realizar inference server (demo mode)...");
    1374
    1375
            let state = crate::api::AppState::demo()?;
    1376
            let app = crate::api::create_router(state);
    1377
    1378
            let addr: SocketAddr = format!("{host}:{port}").parse().map_err(|e| {
    1379
                crate::error::RealizarError::InvalidShape {
    1380
                    reason: format!("Invalid address: {e}"),
    1381
                }
    1382
            })?;
    1383
    1384
            println!("Server listening on http://{addr}");
    1385
            println!();
    1386
            println!("Endpoints:");
    1387
            println!("  GET  /health   - Health check");
    1388
            println!("  POST /tokenize - Tokenize text");
    1389
            println!("  POST /generate - Generate text");
    1390
            println!();
    1391
            println!("Example:");
    1392
            println!("  curl http://{addr}/health");
    1393
            println!();
    1394
    1395
            let listener = tokio::net::TcpListener::bind(addr).await.map_err(|e| {
    1396
                crate::error::RealizarError::InvalidShape {
    1397
                    reason: format!("Failed to bind: {e}"),
    1398
                }
    1399
            })?;
    1400
    1401
            axum::serve(listener, app).await.map_err(|e| {
    1402
                crate::error::RealizarError::InvalidShape {
    1403
                    reason: format!("Server error: {e}"),
    1404
                }
    1405
            })?;
    1406
    1407
            Ok(())
    1408
        }
    1409
    } // mod server_commands
    1410
    1411
    #[cfg(feature = "server")]
    1412
    pub use server_commands::*;
    1413
    1414
    // Tests extracted to tests.rs (PMAT-802)
    1415
    #[cfg(test)]
    1416
    #[path = "tests.rs"]
    1417
    mod cli_tests;