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

    Line
    Count
    Source
    1
    //! Speculative Decoding
    2
    //!
    3
    //! Per spec §8.3: Speculative decoding with draft model for up to 3x speedup.
    4
    //! Reference: [6] Zheng et al. (2024) "SGLang: Efficient Execution of Structured LM Programs"
    5
    //!
    6
    //! ## Algorithm
    7
    //!
    8
    //! 1. Generate K speculative tokens with fast draft model
    9
    //! 2. Verify all K tokens with single forward pass of target model
    10
    //! 3. Accept tokens matching probability distribution
    11
    //! 4. Resample at first rejection point
    12
    //!
    13
    //! ## Benefits
    14
    //!
    15
    //! - Up to 3x speedup for greedy decoding
    16
    //! - Maintains output quality (mathematically equivalent)
    17
    //! - Reduces memory-bound bottleneck of autoregressive decoding
    18
    19
    // Module-level clippy allows
    20
    #![allow(clippy::must_use_candidate)]
    21
    #![allow(clippy::return_self_not_must_use)]
    22
    #![allow(clippy::missing_errors_doc)]
    23
    24
    use serde::{Deserialize, Serialize};
    25
    use std::time::Instant;
    26
    use thiserror::Error;
    27
    28
    /// Error type for speculative decoding
    29
    #[derive(Debug, Error)]
    30
    pub enum SpeculativeError {
    31
        /// Draft model error
    32
        #[error("Draft model error: {0}")]
    33
        DraftModelError(String),
    34
    35
        /// Target model error
    36
        #[error("Target model error: {0}")]
    37
        TargetModelError(String),
    38
    39
        /// Invalid speculation length
    40
        #[error("Invalid speculation length: {0}")]
    41
        InvalidSpecLength(usize),
    42
    43
        /// Verification failed
    44
        #[error("Verification failed at position {position}")]
    45
        VerificationFailed {
    46
            /// Position where verification failed
    47
            position: usize,
    48
        },
    49
    }
    50
    51
    /// Speculative decoding statistics
    52
    #[derive(Debug, Clone, Default, Serialize, Deserialize)]
    53
    pub struct SpeculativeStats {
    54
        /// Total speculative iterations
    55
        pub iterations: u64,
    56
        /// Total tokens speculated
    57
        pub tokens_speculated: u64,
    58
        /// Total tokens accepted
    59
        pub tokens_accepted: u64,
    60
        /// Average acceptance rate
    61
        pub acceptance_rate: f32,
    62
        /// Average speculation length
    63
        pub avg_spec_length: f32,
    64
        /// Total time saved (estimated)
    65
        pub time_saved_ms: f64,
    66
        /// Draft model time (ms)
    67
        pub draft_time_ms: f64,
    68
        /// Target model time (ms)
    69
        pub target_time_ms: f64,
    70
    }
    71
    72
    impl SpeculativeStats {
    73
        /// Update stats after an iteration
    74
    5
        pub fn record_iteration(
    75
    5
            &mut self,
    76
    5
            speculated: usize,
    77
    5
            accepted: usize,
    78
    5
            draft_ms: f64,
    79
    5
            target_ms: f64,
    80
    5
        ) {
    81
    5
            self.iterations += 1;
    82
    5
            self.tokens_speculated += speculated as u64;
    83
    5
            self.tokens_accepted += accepted as u64;
    84
    5
            self.draft_time_ms += draft_ms;
    85
    5
            self.target_time_ms += target_ms;
    86
    87
            // Update running averages
    88
    5
            if self.tokens_speculated > 0 {
    89
    5
                self.acceptance_rate = self.tokens_accepted as f32 / self.tokens_speculated as f32;
    90
    5
            }0
    91
    5
            if self.iterations > 0 {
    92
    5
                self.avg_spec_length = self.tokens_speculated as f32 / self.iterations as f32;
    93
    5
            }0
    94
    95
            // Estimate time saved (accepted tokens would have required sequential target calls)
    96
            // Each accepted token saves ~target_time/batch_size
    97
    5
            let time_per_token = target_ms / speculated.max(1) as f64;
    98
    5
            self.time_saved_ms += (accepted.saturating_sub(1)) as f64 * time_per_token;
    99
    5
        }
    100
    101
        /// Get speedup ratio
    102
    1
        pub fn speedup(&self) -> f32 {
    103
    1
            if self.tokens_accepted == 0 {
    104
    0
                return 1.0;
    105
    1
            }
    106
            // Speedup = (tokens * time_per_token) / actual_time
    107
            // Assuming draft is ~10x faster than target
    108
    1
            let draft_tokens_equivalent = self.tokens_speculated as f64 * 0.1;
    109
    1
            let baseline_time = self.tokens_accepted as f64;
    110
    1
            let actual_time = draft_tokens_equivalent + self.iterations as f64;
    111
    112
    1
            if actual_time > 0.0 {
    113
    1
                (baseline_time / actual_time) as f32
    114
            } else {
    115
    0
                1.0
    116
            }
    117
    1
        }
    118
    }
    119
    120
    /// Token with probability
    121
    #[derive(Debug, Clone)]
    122
    pub struct TokenProb {
    123
        /// Token ID
    124
        pub token: u32,
    125
        /// Log probability
    126
        pub log_prob: f32,
    127
    }
    128
    129
    impl TokenProb {
    130
        /// Create a new token with probability
    131
    27
        pub fn new(token: u32, log_prob: f32) -> Self {
    132
    27
            Self { token, log_prob }
    133
    27
        }
    134
    135
        /// Get probability (exp of log_prob)
    136
    2
        pub fn prob(&self) -> f32 {
    137
    2
            self.log_prob.exp()
    138
    2
        }
    139
    }
    140
    141
    /// Speculative decoding result
    142
    #[derive(Debug, Clone)]
    143
    pub struct SpeculativeResult {
    144
        /// Accepted tokens
    145
        pub accepted_tokens: Vec<u32>,
    146
        /// Number of tokens speculated
    147
        pub num_speculated: usize,
    148
        /// Number of tokens accepted
    149
        pub num_accepted: usize,
    150
        /// Token that was resampled (if any)
    151
        pub resampled_token: Option<u32>,
    152
        /// Time taken for draft model (ms)
    153
        pub draft_time_ms: f64,
    154
        /// Time taken for target model (ms)
    155
        pub target_time_ms: f64,
    156
    }
    157
    158
    impl SpeculativeResult {
    159
        /// Get acceptance rate for this iteration
    160
    1
        pub fn acceptance_rate(&self) -> f32 {
    161
    1
            if self.num_speculated == 0 {
    162
    0
                return 0.0;
    163
    1
            }
    164
    1
            self.num_accepted as f32 / self.num_speculated as f32
    165
    1
        }
    166
    167
        /// Check if all tokens were accepted
    168
    2
        pub fn all_accepted(&self) -> bool {
    169
    2
            self.num_accepted == self.num_speculated
    170
    2
        }
    171
    }
    172
    173
    /// Trait for models that can be used in speculative decoding
    174
    pub trait SpeculativeModel {
    175
        /// Generate next token logits
    176
        fn forward(&self, input_ids: &[u32]) -> Result<Vec<f32>, SpeculativeError>;
    177
    178
        /// Sample from logits
    179
        fn sample(&self, logits: &[f32]) -> Result<TokenProb, SpeculativeError>;
    180
    181
        /// Get vocabulary size
    182
        fn vocab_size(&self) -> usize;
    183
    184
        /// Get EOS token
    185
        fn eos_token(&self) -> u32;
    186
    }
    187
    188
    /// Speculative decoder with draft and target models
    189
    pub struct SpeculativeDecoder<D: SpeculativeModel, T: SpeculativeModel> {
    190
        /// Draft model (smaller, faster)
    191
        draft: D,
    192
        /// Target model (larger, slower)
    193
        target: T,
    194
        /// Number of tokens to speculate per iteration
    195
        spec_length: usize,
    196
        /// Statistics
    197
        stats: SpeculativeStats,
    198
    }
    199
    200
    impl<D: SpeculativeModel, T: SpeculativeModel> SpeculativeDecoder<D, T> {
    201
        /// Create a new speculative decoder
    202
    6
        pub fn new(draft: D, target: T, spec_length: usize) -> Result<Self, SpeculativeError> {
    203
    6
            if spec_length == 0 || spec_length > 325 {
    204
    1
                return Err(SpeculativeError::InvalidSpecLength(spec_length));
    205
    5
            }
    206
    207
    5
            Ok(Self {
    208
    5
                draft,
    209
    5
                target,
    210
    5
                spec_length,
    211
    5
                stats: SpeculativeStats::default(),
    212
    5
            })
    213
    6
        }
    214
    215
        /// Get speculation length
    216
    2
        pub fn spec_length(&self) -> usize {
    217
    2
            self.spec_length
    218
    2
        }
    219
    220
        /// Set speculation length
    221
    2
        pub fn set_spec_length(&mut self, spec_length: usize) -> Result<(), SpeculativeError> {
    222
    2
            if spec_length == 0 || spec_length > 321 {
    223
    1
                return Err(SpeculativeError::InvalidSpecLength(spec_length));
    224
    1
            }
    225
    1
            self.spec_length = spec_length;
    226
    1
            Ok(())
    227
    2
        }
    228
    229
        /// Generate one iteration of speculative decoding
    230
    3
        pub fn decode_iteration(
    231
    3
            &mut self,
    232
    3
            context: &[u32],
    233
    3
        ) -> Result<SpeculativeResult, SpeculativeError> {
    234
    3
            let mut accepted_tokens = Vec::new();
    235
    3
            let mut draft_tokens = Vec::new();
    236
    3
            let mut draft_probs = Vec::new();
    237
    238
            // 1. Generate speculative tokens with draft model
    239
    3
            let draft_start = Instant::now();
    240
    3
            let mut current_context = context.to_vec();
    241
    242
    3
            for _ in 0..self.spec_length {
    243
    12
                let logits = self
    244
    12
                    .draft
    245
    12
                    .forward(&current_context)
    246
    12
                    .map_err(|e| SpeculativeError::DraftModelError(e0.to_string0()))?0;
    247
    12
                let token_prob = self
    248
    12
                    .draft
    249
    12
                    .sample(&logits)
    250
    12
                    .map_err(|e| SpeculativeError::DraftModelError(e0.to_string0()))?0;
    251
    252
    12
                let token = token_prob.token;
    253
    12
                draft_tokens.push(token);
    254
    12
                draft_probs.push(token_prob);
    255
    12
                current_context.push(token);
    256
    257
                // Stop if EOS
    258
    12
                if token == self.draft.eos_token() {
    259
    0
                    break;
    260
    12
                }
    261
            }
    262
    3
            let draft_time = draft_start.elapsed();
    263
    264
            // 2. Verify with target model (single forward pass for all positions)
    265
    3
            let target_start = Instant::now();
    266
    3
            let mut verify_context = context.to_vec();
    267
    3
            verify_context.extend(&draft_tokens);
    268
    269
            // Get target logits for verification
    270
    3
            let target_logits = self
    271
    3
                .target
    272
    3
                .forward(&verify_context)
    273
    3
                .map_err(|e| SpeculativeError::TargetModelError(e0.to_string0()))?0;
    274
    275
    3
            let target_time = target_start.elapsed();
    276
    277
            // 3. Accept/reject based on probability matching
    278
    3
            let mut resampled_token = None;
    279
    280
    15
            for draft_prob12 in &draft_probs {
    281
                // Simple acceptance: compare draft and target probabilities
    282
                // In practice, use more sophisticated rejection sampling
    283
    12
                let target_token = self
    284
    12
                    .target
    285
    12
                    .sample(&target_logits)
    286
    12
                    .map_err(|e| SpeculativeError::TargetModelError(e0.to_string0()))?0;
    287
    288
                // Accept if draft token matches target distribution
    289
                // Simplified: accept if tokens match or random acceptance
    290
    12
                if self.should_accept(draft_prob, &target_token) {
    291
    12
                    accepted_tokens.push(draft_prob.token);
    292
    12
                } else {
    293
                    // Resample from target distribution
    294
    0
                    resampled_token = Some(target_token.token);
    295
    0
                    accepted_tokens.push(target_token.token);
    296
    0
                    break;
    297
                }
    298
            }
    299
    300
    3
            let num_speculated = draft_tokens.len();
    301
    3
            let num_accepted = accepted_tokens.len();
    302
    303
            // Update stats
    304
    3
            self.stats.record_iteration(
    305
    3
                num_speculated,
    306
    3
                num_accepted,
    307
    3
                draft_time.as_secs_f64() * 1000.0,
    308
    3
                target_time.as_secs_f64() * 1000.0,
    309
            );
    310
    311
    3
            Ok(SpeculativeResult {
    312
    3
                accepted_tokens,
    313
    3
                num_speculated,
    314
    3
                num_accepted,
    315
    3
                resampled_token,
    316
    3
                draft_time_ms: draft_time.as_secs_f64() * 1000.0,
    317
    3
                target_time_ms: target_time.as_secs_f64() * 1000.0,
    318
    3
            })
    319
    3
        }
    320
    321
        /// Acceptance criterion for speculative decoding
    322
        #[allow(clippy::unused_self)] // Will use self for config-based thresholds
    323
    12
        fn should_accept(&self, draft: &TokenProb, target: &TokenProb) -> bool {
    324
            // Simple acceptance: tokens must match
    325
            // More sophisticated: use probability ratio for rejection sampling
    326
    12
            if draft.token == target.token {
    327
    12
                return true;
    328
    0
            }
    329
    330
            // Probabilistic acceptance based on ratio
    331
    0
            let ratio = target.prob() / draft.prob().max(1e-10);
    332
    0
            ratio >= 1.0 || ratio > 0.5 // Simplified threshold
    333
    12
        }
    334
    335
        /// Get statistics
    336
    2
        pub fn stats(&self) -> &SpeculativeStats {
    337
    2
            &self.stats
    338
    2
        }
    339
    340
        /// Reset statistics
    341
    1
        pub fn reset_stats(&mut self) {
    342
    1
            self.stats = SpeculativeStats::default();
    343
    1
        }
    344
    }
    345
    346
    /// Configuration for speculative decoding
    347
    #[derive(Debug, Clone, Serialize, Deserialize)]
    348
    pub struct SpeculativeConfig {
    349
        /// Number of tokens to speculate
    350
        pub spec_length: usize,
    351
        /// Minimum acceptance rate before adapting spec_length
    352
        pub min_acceptance_rate: f32,
    353
        /// Enable adaptive speculation length
    354
        pub adaptive: bool,
    355
        /// Maximum speculation length for adaptive mode
    356
        pub max_spec_length: usize,
    357
    }
    358
    359
    impl Default for SpeculativeConfig {
    360
    5
        fn default() -> Self {
    361
    5
            Self {
    362
    5
                spec_length: 4,
    363
    5
                min_acceptance_rate: 0.5,
    364
    5
                adaptive: true,
    365
    5
                max_spec_length: 8,
    366
    5
            }
    367
    5
        }
    368
    }
    369
    370
    impl SpeculativeConfig {
    371
        /// Create a new config with default values
    372
    4
        pub fn new() -> Self {
    373
    4
            Self::default()
    374
    4
        }
    375
    376
        /// Set speculation length
    377
    4
        pub fn with_spec_length(mut self, spec_length: usize) -> Self {
    378
    4
            self.spec_length = spec_length;
    379
    4
            self
    380
    4
        }
    381
    382
        /// Enable/disable adaptive mode
    383
    2
        pub fn with_adaptive(mut self, adaptive: bool) -> Self {
    384
    2
            self.adaptive = adaptive;
    385
    2
            self
    386
    2
        }
    387
    388
        /// Adapt speculation length based on acceptance rate
    389
    3
        pub fn adapt_spec_length(&mut self, acceptance_rate: f32) {
    390
    3
            if !self.adaptive {
    391
    1
                return;
    392
    2
            }
    393
    394
    2
            if acceptance_rate > 0.8 && self.spec_length < self.max_spec_length1 {
    395
    1
                // High acceptance: increase speculation
    396
    1
                self.spec_length = (self.spec_length + 1).min(self.max_spec_length);
    397
    1
            } else if acceptance_rate < self.min_acceptance_rate && self.spec_length > 1 {
    398
    1
                // Low acceptance: decrease speculation
    399
    1
                self.spec_length = (self.spec_length - 1).max(1);
    400
    1
            }0
    401
    3
        }
    402
    }
    403
    404
    // ============================================================================
    405
    // Tests
    406
    // ============================================================================
    407
    408
    #[cfg(test)]
    409
    mod tests {
    410
        use super::*;
    411
    412
        /// Mock model for testing
    413
        struct MockModel {
    414
            vocab_size: usize,
    415
            eos_token: u32,
    416
            /// Fixed token to return
    417
            fixed_token: u32,
    418
        }
    419
    420
        impl MockModel {
    421
    12
            fn new(vocab_size: usize, fixed_token: u32) -> Self {
    422
    12
                Self {
    423
    12
                    vocab_size,
    424
    12
                    eos_token: 0,
    425
    12
                    fixed_token,
    426
    12
                }
    427
    12
            }
    428
        }
    429
    430
        impl SpeculativeModel for MockModel {
    431
    15
            fn forward(&self, _input_ids: &[u32]) -> Result<Vec<f32>, SpeculativeError> {
    432
                // Return uniform logits
    433
    15
                Ok(vec![0.0; self.vocab_size])
    434
    15
            }
    435
    436
    24
            fn sample(&self, _logits: &[f32]) -> Result<TokenProb, SpeculativeError> {
    437
    24
                Ok(TokenProb::new(self.fixed_token, -1.0))
    438
    24
            }
    439
    440
    0
            fn vocab_size(&self) -> usize {
    441
    0
                self.vocab_size
    442
    0
            }
    443
    444
    12
            fn eos_token(&self) -> u32 {
    445
    12
                self.eos_token
    446
    12
            }
    447
        }
    448
    449
        // === TokenProb Tests ===
    450
    451
        #[test]
    452
    1
        fn test_token_prob_new() {
    453
    1
            let tp = TokenProb::new(42, -1.0);
    454
    1
            assert_eq!(tp.token, 42);
    455
    1
            assert_eq!(tp.log_prob, -1.0);
    456
    1
        }
    457
    458
        #[test]
    459
    1
        fn test_token_prob_prob() {
    460
    1
            let tp = TokenProb::new(42, 0.0);
    461
    1
            assert!((tp.prob() - 1.0).abs() < 0.001);
    462
    463
    1
            let tp2 = TokenProb::new(42, -1.0);
    464
    1
            assert!((tp2.prob() - 0.368).abs() < 0.01);
    465
    1
        }
    466
    467
        // === SpeculativeStats Tests ===
    468
    469
        #[test]
    470
    1
        fn test_speculative_stats_default() {
    471
    1
            let stats = SpeculativeStats::default();
    472
    1
            assert_eq!(stats.iterations, 0);
    473
    1
            assert_eq!(stats.tokens_speculated, 0);
    474
    1
            assert_eq!(stats.acceptance_rate, 0.0);
    475
    1
        }
    476
    477
        #[test]
    478
    1
        fn test_speculative_stats_record() {
    479
    1
            let mut stats = SpeculativeStats::default();
    480
    1
            stats.record_iteration(4, 3, 1.0, 10.0);
    481
    482
    1
            assert_eq!(stats.iterations, 1);
    483
    1
            assert_eq!(stats.tokens_speculated, 4);
    484
    1
            assert_eq!(stats.tokens_accepted, 3);
    485
    1
            assert_eq!(stats.acceptance_rate, 0.75);
    486
    1
        }
    487
    488
        #[test]
    489
    1
        fn test_speculative_stats_speedup() {
    490
    1
            let mut stats = SpeculativeStats::default();
    491
    1
            stats.record_iteration(4, 4, 1.0, 10.0);
    492
    493
    1
            let speedup = stats.speedup();
    494
    1
            assert!(speedup > 1.0);
    495
    1
        }
    496
    497
        #[test]
    498
    1
        fn test_speculative_stats_serialization() {
    499
    1
            let stats = SpeculativeStats {
    500
    1
                iterations: 10,
    501
    1
                tokens_speculated: 40,
    502
    1
                tokens_accepted: 30,
    503
    1
                acceptance_rate: 0.75,
    504
    1
                avg_spec_length: 4.0,
    505
    1
                time_saved_ms: 100.0,
    506
    1
                draft_time_ms: 10.0,
    507
    1
                target_time_ms: 100.0,
    508
    1
            };
    509
    510
    1
            let json = serde_json::to_string(&stats).expect("test");
    511
    1
            let parsed: SpeculativeStats = serde_json::from_str(&json).expect("test");
    512
    513
    1
            assert_eq!(parsed.iterations, stats.iterations);
    514
    1
            assert_eq!(parsed.acceptance_rate, stats.acceptance_rate);
    515
    1
        }
    516
    517
        // === SpeculativeResult Tests ===
    518
    519
        #[test]
    520
    1
        fn test_speculative_result_acceptance_rate() {
    521
    1
            let result = SpeculativeResult {
    522
    1
                accepted_tokens: vec![1, 2, 3],
    523
    1
                num_speculated: 4,
    524
    1
                num_accepted: 3,
    525
    1
                resampled_token: Some(4),
    526
    1
                draft_time_ms: 1.0,
    527
    1
                target_time_ms: 10.0,
    528
    1
            };
    529
    530
    1
            assert_eq!(result.acceptance_rate(), 0.75);
    531
    1
            assert!(!result.all_accepted());
    532
    1
        }
    533
    534
        #[test]
    535
    1
        fn test_speculative_result_all_accepted() {
    536
    1
            let result = SpeculativeResult {
    537
    1
                accepted_tokens: vec![1, 2, 3, 4],
    538
    1
                num_speculated: 4,
    539
    1
                num_accepted: 4,
    540
    1
                resampled_token: None,
    541
    1
                draft_time_ms: 1.0,
    542
    1
                target_time_ms: 10.0,
    543
    1
            };
    544
    545
    1
            assert!(result.all_accepted());
    546
    1
        }
    547
    548
        // === SpeculativeConfig Tests ===
    549
    550
        #[test]
    551
    1
        fn test_speculative_config_default() {
    552
    1
            let config = SpeculativeConfig::default();
    553
    1
            assert_eq!(config.spec_length, 4);
    554
    1
            assert!(config.adaptive);
    555
    1
        }
    556
    557
        #[test]
    558
    1
        fn test_speculative_config_builder() {
    559
    1
            let config = SpeculativeConfig::new()
    560
    1
                .with_spec_length(6)
    561
    1
                .with_adaptive(false);
    562
    563
    1
            assert_eq!(config.spec_length, 6);
    564
    1
            assert!(!config.adaptive);
    565
    1
        }
    566
    567
        #[test]
    568
    1
        fn test_speculative_config_adapt_increase() {
    569
    1
            let mut config = SpeculativeConfig::new().with_spec_length(4);
    570
    1
            config.adapt_spec_length(0.9); // High acceptance
    571
    572
    1
            assert!(config.spec_length >= 4);
    573
    1
        }
    574
    575
        #[test]
    576
    1
        fn test_speculative_config_adapt_decrease() {
    577
    1
            let mut config = SpeculativeConfig::new().with_spec_length(4);
    578
    1
            config.adapt_spec_length(0.3); // Low acceptance
    579
    580
    1
            assert!(config.spec_length <= 4);
    581
    1
        }
    582
    583
        #[test]
    584
    1
        fn test_speculative_config_no_adapt_when_disabled() {
    585
    1
            let mut config = SpeculativeConfig::new()
    586
    1
                .with_spec_length(4)
    587
    1
                .with_adaptive(false);
    588
    589
    1
            config.adapt_spec_length(0.1);
    590
    1
            assert_eq!(config.spec_length, 4); // Should not change
    591
    1
        }
    592
    593
        // === SpeculativeDecoder Tests ===
    594
    595
        #[test]
    596
    1
        fn test_speculative_decoder_new() {
    597
    1
            let draft = MockModel::new(100, 1);
    598
    1
            let target = MockModel::new(100, 1);
    599
    600
    1
            let decoder = SpeculativeDecoder::new(draft, target, 4).expect("test");
    601
    1
            assert_eq!(decoder.spec_length(), 4);
    602
    1
        }
    603
    604
        #[test]
    605
    1
        fn test_speculative_decoder_invalid_spec_length() {
    606
    1
            let draft = MockModel::new(100, 1);
    607
    1
            let target = MockModel::new(100, 1);
    608
    609
    1
            let result = SpeculativeDecoder::new(draft, target, 0);
    610
    1
            assert!(matches!0(
    611
    1
                result,
    612
                Err(SpeculativeError::InvalidSpecLength(0))
    613
            ));
    614
    1
        }
    615
    616
        #[test]
    617
    1
        fn test_speculative_decoder_decode_iteration() {
    618
    1
            let draft = MockModel::new(100, 1);
    619
    1
            let target = MockModel::new(100, 1);
    620
    621
    1
            let mut decoder = SpeculativeDecoder::new(draft, target, 4).expect("test");
    622
    1
            let result = decoder.decode_iteration(&[10, 20, 30]).expect("test");
    623
    624
    1
            assert!(!result.accepted_tokens.is_empty());
    625
    1
            assert!(result.num_speculated > 0);
    626
    1
        }
    627
    628
        #[test]
    629
    1
        fn test_speculative_decoder_set_spec_length() {
    630
    1
            let draft = MockModel::new(100, 1);
    631
    1
            let target = MockModel::new(100, 1);
    632
    633
    1
            let mut decoder = SpeculativeDecoder::new(draft, target, 4).expect("test");
    634
    1
            decoder.set_spec_length(8).expect("test");
    635
    1
            assert_eq!(decoder.spec_length(), 8);
    636
    637
    1
            let err = decoder.set_spec_length(0);
    638
    1
            assert!(err.is_err());
    639
    1
        }
    640
    641
        #[test]
    642
    1
        fn test_speculative_decoder_stats() {
    643
    1
            let draft = MockModel::new(100, 1);
    644
    1
            let target = MockModel::new(100, 1);
    645
    646
    1
            let mut decoder = SpeculativeDecoder::new(draft, target, 4).expect("test");
    647
    1
            let _ = decoder.decode_iteration(&[10]).expect("test");
    648
    649
    1
            let stats = decoder.stats();
    650
    1
            assert_eq!(stats.iterations, 1);
    651
    1
        }
    652
    653
        #[test]
    654
    1
        fn test_speculative_decoder_reset_stats() {
    655
    1
            let draft = MockModel::new(100, 1);
    656
    1
            let target = MockModel::new(100, 1);
    657
    658
    1
            let mut decoder = SpeculativeDecoder::new(draft, target, 4).expect("test");
    659
    1
            let _ = decoder.decode_iteration(&[10]).expect("test");
    660
    661
    1
            decoder.reset_stats();
    662
    1
            assert_eq!(decoder.stats().iterations, 0);
    663
    1
        }
    664
    665
        // === Error Tests ===
    666
    667
        #[test]
    668
    1
        fn test_speculative_error_display() {
    669
    1
            let err = SpeculativeError::DraftModelError("test".to_string());
    670
    1
            assert!(err.to_string().contains("Draft"));
    671
    672
    1
            let err = SpeculativeError::TargetModelError("test".to_string());
    673
    1
            assert!(err.to_string().contains("Target"));
    674
    675
    1
            let err = SpeculativeError::InvalidSpecLength(0);
    676
    1
            assert!(err.to_string().contains("0"));
    677
    678
    1
            let err = SpeculativeError::VerificationFailed { position: 3 };
    679
    1
            assert!(err.to_string().contains("3"));
    680
    1
        }
    681
    }