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

    Line
    Count
    Source
    1
    //! GPU parity benchmarks for comparing Realizar vs Ollama/llama.cpp
    2
    //!
    3
    //! Extracted from bench/mod.rs (PMAT-802) to reduce module size.
    4
    //! Contains:
    5
    //! - IMP-800: TRUE GPU Parity Benchmark (M2 Milestone)
    6
    //! - IMP-900: Closing the 18x Gap (M3/M4 Milestones)
    7
    8
    use serde::{Deserialize, Serialize};
    9
    10
    // ============================================================================
    11
    // IMP-800: TRUE GPU Parity Benchmark (M2 Milestone)
    12
    // ============================================================================
    13
    14
    /// GPU parity benchmark configuration (IMP-800b)
    15
    ///
    16
    /// Configures apples-to-apples throughput comparison on same GPU.
    17
    #[derive(Debug, Clone, Serialize, Deserialize)]
    18
    pub struct GpuParityBenchmark {
    19
        /// Model to benchmark (phi-2 Q4_K_M)
    20
        pub model_path: String,
    21
        /// Prompt for generation
    22
        pub prompt: String,
    23
        /// Number of tokens to generate
    24
        pub max_tokens: usize,
    25
        /// Ollama endpoint for comparison
    26
        pub ollama_endpoint: String,
    27
        /// Number of warmup iterations
    28
        pub warmup_iterations: usize,
    29
        /// Number of measurement iterations
    30
        pub measurement_iterations: usize,
    31
        /// Target CV for stable measurements
    32
        pub target_cv: f64,
    33
    }
    34
    35
    impl Default for GpuParityBenchmark {
    36
    2
        fn default() -> Self {
    37
    2
            Self {
    38
    2
                model_path: String::new(),
    39
    2
                prompt: "The quick brown fox".to_string(),
    40
    2
                max_tokens: 32,
    41
    2
                ollama_endpoint: "http://localhost:11434".to_string(),
    42
    2
                warmup_iterations: 3,
    43
    2
                measurement_iterations: 10,
    44
    2
                target_cv: 0.05,
    45
    2
            }
    46
    2
        }
    47
    }
    48
    49
    impl GpuParityBenchmark {
    50
        /// Create a new GPU parity benchmark with model path
    51
        #[must_use]
    52
    1
        pub fn new(model_path: impl Into<String>) -> Self {
    53
    1
            Self {
    54
    1
                model_path: model_path.into(),
    55
    1
                ..Default::default()
    56
    1
            }
    57
    1
        }
    58
    59
        /// Set the prompt for generation
    60
        #[must_use]
    61
    1
        pub fn with_prompt(mut self, prompt: impl Into<String>) -> Self {
    62
    1
            self.prompt = prompt.into();
    63
    1
            self
    64
    1
        }
    65
    66
        /// Set the number of tokens to generate
    67
        #[must_use]
    68
    1
        pub fn with_max_tokens(mut self, max_tokens: usize) -> Self {
    69
    1
            self.max_tokens = max_tokens;
    70
    1
            self
    71
    1
        }
    72
    73
        /// Set the Ollama endpoint
    74
        #[must_use]
    75
    1
        pub fn with_ollama_endpoint(mut self, endpoint: impl Into<String>) -> Self {
    76
    1
            self.ollama_endpoint = endpoint.into();
    77
    1
            self
    78
    1
        }
    79
    80
        /// Set the number of warmup iterations
    81
        #[must_use]
    82
    1
        pub fn with_warmup(mut self, warmup: usize) -> Self {
    83
    1
            self.warmup_iterations = warmup;
    84
    1
            self
    85
    1
        }
    86
    87
        /// Set the number of measurement iterations
    88
        #[must_use]
    89
    1
        pub fn with_iterations(mut self, iterations: usize) -> Self {
    90
    1
            self.measurement_iterations = iterations;
    91
    1
            self
    92
    1
        }
    93
    }
    94
    95
    /// Benchmark result with statistical analysis (IMP-800b)
    96
    #[derive(Debug, Clone, Serialize, Deserialize)]
    97
    pub struct GpuParityResult {
    98
        /// Realizar GPU throughput (tok/s)
    99
        pub realizar_gpu_tps: f64,
    100
        /// Ollama throughput (tok/s)
    101
        pub ollama_tps: f64,
    102
        /// Performance gap ratio (Ollama / Realizar)
    103
        pub gap_ratio: f64,
    104
        /// Coefficient of variation (measurement stability)
    105
        pub cv: f64,
    106
        /// GPU device name
    107
        pub gpu_device: String,
    108
        /// VRAM usage (MB)
    109
        pub vram_mb: u64,
    110
        /// Realizar latency p50 (ms)
    111
        pub realizar_p50_ms: f64,
    112
        /// Ollama latency p50 (ms)
    113
        pub ollama_p50_ms: f64,
    114
    }
    115
    116
    impl GpuParityResult {
    117
        /// Create a new GPU parity result
    118
        #[must_use]
    119
    8
        pub fn new(
    120
    8
            realizar_gpu_tps: f64,
    121
    8
            ollama_tps: f64,
    122
    8
            cv: f64,
    123
    8
            gpu_device: impl Into<String>,
    124
    8
            vram_mb: u64,
    125
    8
        ) -> Self {
    126
    8
            let gap_ratio = if realizar_gpu_tps > 0.0 {
    127
    8
                ollama_tps / realizar_gpu_tps
    128
            } else {
    129
    0
                f64::INFINITY
    130
            };
    131
    132
    8
            Self {
    133
    8
                realizar_gpu_tps,
    134
    8
                ollama_tps,
    135
    8
                gap_ratio,
    136
    8
                cv,
    137
    8
                gpu_device: gpu_device.into(),
    138
    8
                vram_mb,
    139
    8
                realizar_p50_ms: 0.0,
    140
    8
                ollama_p50_ms: 0.0,
    141
    8
            }
    142
    8
        }
    143
    144
        /// Returns true if within 2x of Ollama (M2 target)
    145
        #[must_use]
    146
    3
        pub fn achieves_m2_parity(&self) -> bool {
    147
    3
            self.gap_ratio <= 2.0
    148
    3
        }
    149
    150
        /// Returns true if within 1.25x of Ollama (M4 target)
    151
        #[must_use]
    152
    3
        pub fn achieves_m4_parity(&self) -> bool {
    153
    3
            self.gap_ratio <= 1.25
    154
    3
        }
    155
    156
        /// Returns true if GPU is faster than CPU SIMD baseline (5 tok/s)
    157
        #[must_use]
    158
    2
        pub fn gpu_faster_than_cpu(&self) -> bool {
    159
    2
            self.realizar_gpu_tps > 5.0
    160
    2
        }
    161
    162
        /// Returns true if measurements are stable (CV < 0.05)
    163
        #[must_use]
    164
    2
        pub fn measurements_stable(&self) -> bool {
    165
    2
            self.cv < 0.05
    166
    2
        }
    167
    168
        /// Get speedup over CPU SIMD baseline
    169
        #[must_use]
    170
    1
        pub fn cpu_speedup(&self) -> f64 {
    171
    1
            self.realizar_gpu_tps / 5.0 // CPU baseline ~5 tok/s
    172
    1
        }
    173
    }
    174
    175
    /// Gap analysis with falsifiable claims (IMP-800c)
    176
    #[derive(Debug, Clone, Serialize, Deserialize)]
    177
    pub struct GapAnalysis {
    178
        /// Claimed gap reduction
    179
        pub claimed_gap: f64,
    180
        /// Measured gap
    181
        pub measured_gap: f64,
    182
        /// Statistical significance (p-value)
    183
        pub p_value: f64,
    184
        /// Confidence interval lower bound (95%)
    185
        pub ci_95_lower: f64,
    186
        /// Confidence interval upper bound (95%)
    187
        pub ci_95_upper: f64,
    188
        /// Popper score (falsifiability, 0-100)
    189
        pub popper_score: f64,
    190
        /// Claim descriptions
    191
        pub claims: Vec<FalsifiableClaim>,
    192
    }
    193
    194
    /// A falsifiable claim for Popperian testing (IMP-800c)
    195
    #[derive(Debug, Clone, Serialize, Deserialize)]
    196
    pub struct FalsifiableClaim {
    197
        /// Claim identifier
    198
        pub id: String,
    199
        /// Claim description
    200
        pub description: String,
    201
        /// Expected value
    202
        pub expected: f64,
    203
        /// Threshold for verification
    204
        pub threshold: f64,
    205
        /// Measured value
    206
        pub measured: f64,
    207
        /// Whether claim is verified
    208
        pub verified: bool,
    209
    }
    210
    211
    impl FalsifiableClaim {
    212
        /// Create a new falsifiable claim
    213
        #[must_use]
    214
    6
        pub fn new(
    215
    6
            id: impl Into<String>,
    216
    6
            description: impl Into<String>,
    217
    6
            expected: f64,
    218
    6
            threshold: f64,
    219
    6
        ) -> Self {
    220
    6
            Self {
    221
    6
                id: id.into(),
    222
    6
                description: description.into(),
    223
    6
                expected,
    224
    6
                threshold,
    225
    6
                measured: 0.0,
    226
    6
                verified: false,
    227
    6
            }
    228
    6
        }
    229
    230
        /// Evaluate the claim against a measured value
    231
        #[must_use]
    232
    6
        pub fn evaluate(mut self, measured: f64) -> Self {
    233
    6
            self.measured = measured;
    234
    6
            self.verified = measured >= self.threshold;
    235
    6
            self
    236
    6
        }
    237
    }
    238
    239
    impl GapAnalysis {
    240
        /// Create a new gap analysis
    241
        #[must_use]
    242
    5
        pub fn new(claimed_gap: f64, measured_gap: f64) -> Self {
    243
    5
            Self {
    244
    5
                claimed_gap,
    245
    5
                measured_gap,
    246
    5
                p_value: 0.0,
    247
    5
                ci_95_lower: 0.0,
    248
    5
                ci_95_upper: 0.0,
    249
    5
                popper_score: 0.0,
    250
    5
                claims: Vec::new(),
    251
    5
            }
    252
    5
        }
    253
    254
        /// Add statistical bounds
    255
        #[must_use]
    256
    4
        pub fn with_statistics(mut self, p_value: f64, ci_lower: f64, ci_upper: f64) -> Self {
    257
    4
            self.p_value = p_value;
    258
    4
            self.ci_95_lower = ci_lower;
    259
    4
            self.ci_95_upper = ci_upper;
    260
    4
            self
    261
    4
        }
    262
    263
        /// Calculate and set Popper score based on claims
    264
    1
        pub fn calculate_popper_score(&mut self) {
    265
    1
            if self.claims.is_empty() {
    266
    0
                self.popper_score = 0.0;
    267
    0
                return;
    268
    1
            }
    269
    270
    1
            let verified_count = self.claims.iter().filter(|c| c.verified).count();
    271
    1
            self.popper_score = (verified_count as f64 / self.claims.len() as f64) * 100.0;
    272
    1
        }
    273
    274
        /// Add a falsifiable claim
    275
    0
        pub fn add_claim(&mut self, claim: FalsifiableClaim) {
    276
    0
            self.claims.push(claim);
    277
    0
        }
    278
    279
        /// Claim is verified if measured within CI
    280
        #[must_use]
    281
    2
        pub fn claim_verified(&self) -> bool {
    282
    2
            self.measured_gap >= self.ci_95_lower && self.measured_gap <= self.ci_95_upper1
    283
    2
        }
    284
    285
        /// Create default IMP-800c claims
    286
        #[must_use]
    287
    1
        pub fn with_default_claims(mut self, realizar_gpu_tps: f64) -> Self {
    288
            // IMP-800c-1: GPU faster than CPU SIMD (>5x, threshold 25 tok/s)
    289
    1
            self.claims.push(
    290
    1
                FalsifiableClaim::new("IMP-800c-1", "GPU faster than CPU SIMD (>5x)", 5.0, 25.0)
    291
    1
                    .evaluate(realizar_gpu_tps),
    292
            );
    293
    294
            // IMP-800c-2: GPU within 10x of Ollama (threshold 24 tok/s)
    295
    1
            self.claims.push(
    296
    1
                FalsifiableClaim::new("IMP-800c-2", "GPU within 10x of Ollama", 10.0, 24.0)
    297
    1
                    .evaluate(realizar_gpu_tps),
    298
            );
    299
    300
            // IMP-800c-3: GPU within 2x of Ollama - M2 (threshold 120 tok/s)
    301
    1
            self.claims.push(
    302
    1
                FalsifiableClaim::new("IMP-800c-3", "GPU within 2x of Ollama (M2)", 2.0, 120.0)
    303
    1
                    .evaluate(realizar_gpu_tps),
    304
            );
    305
    306
            // IMP-800c-4: GPU at parity with Ollama - M4 (threshold 192 tok/s)
    307
    1
            self.claims.push(
    308
    1
                FalsifiableClaim::new("IMP-800c-4", "GPU at parity with Ollama (M4)", 1.25, 192.0)
    309
    1
                    .evaluate(realizar_gpu_tps),
    310
            );
    311
    312
    1
            self.calculate_popper_score();
    313
    1
            self
    314
    1
        }
    315
    }
    316
    317
    // ============================================================================
    318
    // IMP-900: Closing the 18x Gap (M3/M4 Milestones)
    319
    // ============================================================================
    320
    321
    /// Optimized GEMM configuration (IMP-900a)
    322
    #[derive(Debug, Clone, Serialize, Deserialize)]
    323
    pub struct OptimizedGemmConfig {
    324
        /// Tile size for shared memory (typically 32 or 64)
    325
        pub tile_size: u32,
    326
        /// Register blocking factor (typically 4 or 8)
    327
        pub reg_block: u32,
    328
        /// Use tensor cores if available (SM 7.0+)
    329
        pub use_tensor_cores: bool,
    330
        /// Vectorized loads (float4 = 4)
    331
        pub vector_width: u32,
    332
        /// Unroll factor for K-loop
    333
        pub k_unroll: u32,
    334
        /// Use double buffering for tile prefetch
    335
        pub double_buffer: bool,
    336
    }
    337
    338
    impl Default for OptimizedGemmConfig {
    339
    6
        fn default() -> Self {
    340
    6
            Self {
    341
    6
                tile_size: 32,
    342
    6
                reg_block: 4,
    343
    6
                use_tensor_cores: false,
    344
    6
                vector_width: 4,
    345
    6
                k_unroll: 4,
    346
    6
                double_buffer: true,
    347
    6
            }
    348
    6
        }
    349
    }
    350
    351
    impl OptimizedGemmConfig {
    352
        /// Create configuration for small matrices (256x256)
    353
        #[must_use]
    354
    0
        pub fn small() -> Self {
    355
    0
            Self {
    356
    0
                tile_size: 16,
    357
    0
                reg_block: 2,
    358
    0
                use_tensor_cores: false,
    359
    0
                vector_width: 4,
    360
    0
                k_unroll: 4,
    361
    0
                double_buffer: false,
    362
    0
            }
    363
    0
        }
    364
    365
        /// Create configuration for large matrices (1024+)
    366
        #[must_use]
    367
    2
        pub fn large() -> Self {
    368
    2
            Self {
    369
    2
                tile_size: 64,
    370
    2
                reg_block: 8,
    371
    2
                use_tensor_cores: false,
    372
    2
                vector_width: 4,
    373
    2
                k_unroll: 8,
    374
    2
                double_buffer: true,
    375
    2
            }
    376
    2
        }
    377
    378
        /// Calculate shared memory requirement (bytes)
    379
        #[must_use]
    380
    2
        pub fn shared_memory_bytes(&self) -> u32 {
    381
            // Two tiles (A and B) in shared memory
    382
            // Each tile is tile_size × tile_size × sizeof(f32)
    383
    2
            let tile_bytes = self.tile_size * self.tile_size * 4;
    384
    2
            if self.double_buffer {
    385
    1
                tile_bytes * 4 // 2 tiles × 2 buffers
    386
            } else {
    387
    1
                tile_bytes * 2 // 2 tiles
    388
            }
    389
    2
        }
    390
    391
        /// Calculate threads per block
    392
        #[must_use]
    393
    2
        pub fn threads_per_block(&self) -> u32 {
    394
            // Each thread computes reg_block × reg_block elements
    395
    2
            let threads_per_dim = self.tile_size / self.reg_block;
    396
    2
            threads_per_dim * threads_per_dim
    397
    2
        }
    398
    399
        /// Calculate registers per thread (for accumulators)
    400
        #[must_use]
    401
    2
        pub fn registers_per_thread(&self) -> u32 {
    402
            // reg_block × reg_block accumulator values
    403
    2
            self.reg_block * self.reg_block
    404
    2
        }
    405
    }
    406
    407
    /// GEMM performance result (IMP-900a)
    408
    #[derive(Debug, Clone, Serialize, Deserialize)]
    409
    pub struct GemmPerformanceResult {
    410
        /// Matrix M dimension (rows of A, rows of C)
    411
        pub m: u32,
    412
        /// Matrix N dimension (cols of B, cols of C)
    413
        pub n: u32,
    414
        /// Matrix K dimension (cols of A, rows of B)
    415
        pub k: u32,
    416
        /// Time in milliseconds
    417
        pub time_ms: f64,
    418
        /// GFLOP/s achieved
    419
        pub gflops: f64,
    420
        /// Memory bandwidth achieved (GB/s)
    421
        pub bandwidth_gbs: f64,
    422
        /// Percentage of peak performance
    423
        pub efficiency: f64,
    424
    }
    425
    426
    impl GemmPerformanceResult {
    427
        /// Create a new GEMM performance result
    428
        #[must_use]
    429
    2
        pub fn new(m: u32, n: u32, k: u32, time_ms: f64) -> Self {
    430
            // GEMM operations: 2 * M * N * K (multiply-add)
    431
    2
            let ops = 2.0 * f64::from(m) * f64::from(n) * f64::from(k);
    432
    2
            let gflops = ops / (time_ms * 1e6);
    433
    434
            // Memory: read A (M*K), read B (K*N), write C (M*N)
    435
    2
            let bytes = (f64::from(m) * f64::from(k)
    436
    2
                + f64::from(k) * f64::from(n)
    437
    2
                + f64::from(m) * f64::from(n))
    438
    2
                * 4.0;
    439
    2
            let bandwidth_gbs = bytes / (time_ms * 1e6);
    440
    441
    2
            Self {
    442
    2
                m,
    443
    2
                n,
    444
    2
                k,
    445
    2
                time_ms,
    446
    2
                gflops,
    447
    2
                bandwidth_gbs,
    448
    2
                efficiency: 0.0, // Set by caller based on peak
    449
    2
            }
    450
    2
        }
    451
    452
        /// Set efficiency based on peak GFLOP/s
    453
        #[must_use]
    454
    1
        pub fn with_peak(mut self, peak_gflops: f64) -> Self {
    455
    1
            self.efficiency = (self.gflops / peak_gflops) * 100.0;
    456
    1
            self
    457
    1
        }
    458
    459
        /// Check if performance improved by at least the given factor
    460
        #[must_use]
    461
    2
        pub fn improved_by(&self, baseline_gflops: f64, factor: f64) -> bool {
    462
    2
            self.gflops >= baseline_gflops * factor
    463
    2
        }
    464
    }
    465
    466
    /// Optimized GEMM benchmark runner (IMP-900a)
    467
    #[derive(Debug)]
    468
    pub struct OptimizedGemmBenchmark {
    469
        /// Configuration
    470
        pub config: OptimizedGemmConfig,
    471
        /// Warmup iterations
    472
        pub warmup_iterations: usize,
    473
        /// Measurement iterations
    474
        pub measurement_iterations: usize,
    475
        /// Target coefficient of variation
    476
        pub target_cv: f64,
    477
    }
    478
    479
    impl Default for OptimizedGemmBenchmark {
    480
    1
        fn default() -> Self {
    481
    1
            Self {
    482
    1
                config: OptimizedGemmConfig::default(),
    483
    1
                warmup_iterations: 5,
    484
    1
                measurement_iterations: 20,
    485
    1
                target_cv: 0.05,
    486
    1
            }
    487
    1
        }
    488
    }
    489
    490
    impl OptimizedGemmBenchmark {
    491
        /// Create benchmark with custom config
    492
        #[must_use]
    493
    0
        pub fn with_config(config: OptimizedGemmConfig) -> Self {
    494
    0
            Self {
    495
    0
                config,
    496
    0
                ..Default::default()
    497
    0
            }
    498
    0
        }
    499
    500
        /// Calculate expected improvement over naive GEMM
    501
        #[must_use]
    502
    1
        pub fn expected_improvement(&self) -> f64 {
    503
    1
            let mut improvement = 1.0;
    504
    505
            // Shared memory tiling: ~2x for cache efficiency
    506
    1
            improvement *= 2.0;
    507
    508
            // Register blocking: ~1.5x for reduced memory traffic
    509
    1
            if self.config.reg_block >= 4 {
    510
    1
                improvement *= 1.5;
    511
    1
            }0
    512
    513
            // Vectorized loads: ~1.3x for coalesced access
    514
    1
            if self.config.vector_width >= 4 {
    515
    1
                improvement *= 1.3;
    516
    1
            }0
    517
    518
            // Double buffering: ~1.2x for latency hiding
    519
    1
            if self.config.double_buffer {
    520
    1
                improvement *= 1.2;
    521
    1
            }0
    522
    523
    1
            improvement
    524
    1
        }
    525
    }
    526
    527
    /// Kernel fusion configuration (IMP-900b)
    528
    #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
    529
    pub enum FusedOpType {
    530
        /// GEMM + bias + activation
    531
        GemmBiasActivation,
    532
        /// Layer normalization + linear projection
    533
        LayerNormLinear,
    534
        /// Fused attention (FlashAttention-style)
    535
        FusedAttention,
    536
        /// FFN: up projection + gate + down projection
    537
        FusedFfn,
    538
    }
    539
    540
    /// Fused operation specification (IMP-900b)
    541
    #[derive(Debug, Clone, Serialize, Deserialize)]
    542
    pub struct FusedOpSpec {
    543
        /// Type of fused operation
    544
        pub op_type: FusedOpType,
    545
        /// Input dimensions
    546
        pub input_dims: Vec<u32>,
    547
        /// Output dimensions
    548
        pub output_dims: Vec<u32>,
    549
        /// Activation function (if applicable)
    550
        pub activation: Option<String>,
    551
        /// Number of kernel launches when fused
    552
        pub fused_launches: u32,
    553
        /// Number of kernel launches when unfused
    554
        pub unfused_launches: u32,
    555
    }
    556
    557
    impl FusedOpSpec {
    558
        /// Calculate launch reduction factor
    559
        #[must_use]
    560
    5
        pub fn launch_reduction(&self) -> f64 {
    561
    5
            f64::from(self.unfused_launches) / f64::from(self.fused_launches)
    562
    5
        }
    563
    564
        /// Check if fusion reduces launches by at least 50%
    565
        #[must_use]
    566
    4
        pub fn achieves_target_reduction(&self) -> bool {
    567
    4
            self.launch_reduction() >= 2.0
    568
    4
        }
    569
    }
    570
    571
    /// FlashAttention configuration (IMP-900c)
    572
    #[derive(Debug, Clone, Serialize, Deserialize)]
    573
    pub struct FlashAttentionConfig {
    574
        /// Block size for Q tiling (Br)
    575
        pub block_size_q: u32,
    576
        /// Block size for K/V tiling (Bc)
    577
        pub block_size_kv: u32,
    578
        /// Head dimension
    579
        pub head_dim: u32,
    580
        /// Number of attention heads
    581
        pub num_heads: u32,
    582
        /// Use causal masking
    583
        pub causal: bool,
    584
        /// Softmax scale (default: 1/sqrt(head_dim))
    585
        pub scale: f32,
    586
    }
    587
    588
    impl FlashAttentionConfig {
    589
        /// Create configuration for phi-2 model
    590
        #[must_use]
    591
    3
        pub fn phi2() -> Self {
    592
    3
            Self {
    593
    3
                block_size_q: 64,
    594
    3
                block_size_kv: 64,
    595
    3
                head_dim: 80, // phi-2: 2560 / 32 heads
    596
    3
                num_heads: 32,
    597
    3
                causal: true,
    598
    3
                scale: 1.0 / (80.0_f32).sqrt(),
    599
    3
            }
    600
    3
        }
    601
    602
        /// Calculate memory required for attention (naive vs flash)
    603
        #[must_use]
    604
    4
        pub fn memory_comparison(&self, seq_len: u32) -> (u64, u64) {
    605
            // Naive: O(N²) attention matrix
    606
    4
            let naive_bytes = u64::from(seq_len) * u64::from(seq_len) * 4;
    607
    608
            // FlashAttention: O(N) working memory
    609
    4
            let flash_bytes = u64::from(self.block_size_q) * u64::from(self.block_size_kv) * 4 * 2; // S and P blocks
    610
    611
    4
            (naive_bytes, flash_bytes)
    612
    4
        }
    613
    614
        /// Calculate memory savings factor
    615
        #[must_use]
    616
    2
        pub fn memory_savings(&self, seq_len: u32) -> f64 {
    617
    2
            let (naive, flash) = self.memory_comparison(seq_len);
    618
    2
            naive as f64 / flash as f64
    619
    2
        }
    620
    }
    621
    622
    /// Memory pool configuration (IMP-900d)
    623
    #[derive(Debug, Clone, Serialize, Deserialize)]
    624
    pub struct MemoryPoolConfig {
    625
        /// Initial pool size (bytes)
    626
        pub initial_size: usize,
    627
        /// Maximum pool size (bytes)
    628
        pub max_size: usize,
    629
        /// Size classes for allocation (powers of 2)
    630
        pub size_classes: Vec<usize>,
    631
        /// Use pinned memory for host staging
    632
        pub use_pinned_memory: bool,
    633
        /// Enable async transfers
    634
        pub async_transfers: bool,
    635
    }
    636
    637
    impl Default for MemoryPoolConfig {
    638
    4
        fn default() -> Self {
    639
    4
            Self {
    640
    4
                initial_size: 256 * 1024 * 1024,  // 256 MB
    641
    4
                max_size: 2 * 1024 * 1024 * 1024, // 2 GB
    642
    4
                size_classes: vec![
    643
    4
                    4096,        // 4 KB
    644
    4
                    16384,       // 16 KB
    645
    4
                    65536,       // 64 KB
    646
    4
                    262_144,     // 256 KB
    647
    4
                    1_048_576,   // 1 MB
    648
    4
                    4_194_304,   // 4 MB
    649
    4
                    16_777_216,  // 16 MB
    650
    4
                    67_108_864,  // 64 MB
    651
    4
                    268_435_456, // 256 MB
    652
    4
                ],
    653
    4
                use_pinned_memory: true,
    654
    4
                async_transfers: true,
    655
    4
            }
    656
    4
        }
    657
    }
    658
    659
    impl MemoryPoolConfig {
    660
        /// Find the smallest size class that fits the requested size
    661
        #[must_use]
    662
    4
        pub fn find_size_class(&self, requested: usize) -> Option<usize> {
    663
    4
            self.size_classes
    664
    4
                .iter()
    665
    4
                .copied()
    666
    24
                .find4(|&size| size >= requested)
    667
    4
        }
    668
    669
        /// Calculate expected bandwidth improvement from pinned memory
    670
        #[must_use]
    671
    2
        pub fn expected_bandwidth_improvement(&self) -> f64 {
    672
    2
            if self.use_pinned_memory {
    673
    1
                2.4 // Pinned memory typically 2-3x faster
    674
            } else {
    675
    1
                1.0
    676
            }
    677
    2
        }
    678
    }
    679
    680
    /// IMP-900 combined result (M3/M4 targets)
    681
    #[derive(Debug, Clone, Serialize, Deserialize)]
    682
    pub struct Imp900Result {
    683
        /// Baseline throughput (13.1 tok/s from IMP-800)
    684
        pub baseline_tps: f64,
    685
        /// Throughput after optimizations
    686
        pub optimized_tps: f64,
    687
        /// GEMM optimization improvement factor
    688
        pub gemm_improvement: f64,
    689
        /// Kernel fusion improvement factor
    690
        pub fusion_improvement: f64,
    691
        /// FlashAttention improvement factor
    692
        pub flash_attention_improvement: f64,
    693
        /// Memory optimization improvement factor
    694
        pub memory_improvement: f64,
    695
        /// Gap to Ollama
    696
        pub gap_ratio: f64,
    697
        /// Target milestone achieved
    698
        pub milestone: Option<String>,
    699
    }
    700
    701
    impl Imp900Result {
    702
        /// Create result from baseline
    703
        #[must_use]
    704
    5
        pub fn from_baseline(baseline_tps: f64) -> Self {
    705
    5
            Self {
    706
    5
                baseline_tps,
    707
    5
                optimized_tps: baseline_tps,
    708
    5
                gemm_improvement: 1.0,
    709
    5
                fusion_improvement: 1.0,
    710
    5
                flash_attention_improvement: 1.0,
    711
    5
                memory_improvement: 1.0,
    712
    5
                gap_ratio: 240.0 / baseline_tps,
    713
    5
                milestone: None,
    714
    5
            }
    715
    5
        }
    716
    717
        /// Apply GEMM optimization
    718
        #[must_use]
    719
    4
        pub fn with_gemm_improvement(mut self, factor: f64) -> Self {
    720
    4
            self.gemm_improvement = factor;
    721
    4
            self.recalculate();
    722
    4
            self
    723
    4
        }
    724
    725
        /// Apply fusion optimization
    726
        #[must_use]
    727
    2
        pub fn with_fusion_improvement(mut self, factor: f64) -> Self {
    728
    2
            self.fusion_improvement = factor;
    729
    2
            self.recalculate();
    730
    2
            self
    731
    2
        }
    732
    733
        /// Apply FlashAttention optimization
    734
        #[must_use]
    735
    2
        pub fn with_flash_attention_improvement(mut self, factor: f64) -> Self {
    736
    2
            self.flash_attention_improvement = factor;
    737
    2
            self.recalculate();
    738
    2
            self
    739
    2
        }
    740
    741
        /// Apply memory optimization
    742
        #[must_use]
    743
    3
        pub fn with_memory_improvement(mut self, factor: f64) -> Self {
    744
    3
            self.memory_improvement = factor;
    745
    3
            self.recalculate();
    746
    3
            self
    747
    3
        }
    748
    749
        /// Recalculate throughput and milestone
    750
    11
        fn recalculate(&mut self) {
    751
    11
            let total_improvement = self.gemm_improvement
    752
    11
                * self.fusion_improvement
    753
    11
                * self.flash_attention_improvement
    754
    11
                * self.memory_improvement;
    755
    756
    11
            self.optimized_tps = self.baseline_tps * total_improvement;
    757
    11
            self.gap_ratio = 240.0 / self.optimized_tps;
    758
    759
    11
            self.milestone = if self.gap_ratio <= 1.25 {
    760
    2
                Some("M4".to_string()) // Full parity
    761
    9
            } else if self.gap_ratio <= 2.0 {
    762
    0
                Some("M3".to_string()) // Near parity
    763
    9
            } else if self.gap_ratio <= 5.0 {
    764
    4
                Some("M2".to_string()) // Within 5x
    765
            } else {
    766
    5
                None
    767
            };
    768
    11
        }
    769
    770
        /// Check if M3 target achieved (>48 tok/s, <5x gap)
    771
        #[must_use]
    772
    1
        pub fn achieves_m3(&self) -> bool {
    773
    1
            self.optimized_tps >= 48.0 && self.gap_ratio <= 5.0
    774
    1
        }
    775
    776
        /// Check if M4 target achieved (>192 tok/s, <1.25x gap)
    777
        #[must_use]
    778
    1
        pub fn achieves_m4(&self) -> bool {
    779
    1
            self.optimized_tps >= 192.0 && self.gap_ratio <= 1.25
    780
    1
        }
    781
    782
        /// Get combined improvement factor
    783
        #[must_use]
    784
    1
        pub fn total_improvement(&self) -> f64 {
    785
    1
            self.optimized_tps / self.baseline_tps
    786
    1
        }
    787
    }