aprender-serve 0.64.0

Pure Rust ML inference engine built from scratch - model serving for GGUF and safetensors
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430

/// PARITY-027e: Combined GPU optimization coverage
#[test]
#[cfg(feature = "gpu")]
#[serial_test::serial]
fn test_parity027e_combined_optimization_coverage() {
    println!("=== PARITY-027e: Combined GPU Optimization Coverage ===\n");

    // Summary of all GPU optimizations in forward pass
    println!("  Complete GPU optimization pipeline:");
    println!("  ");
    println!("  | Component | PARITY | Method | Benefit |");
    println!("  |-----------|--------|--------|---------|");
    println!("  | QKV projection | 024 | batch_qkv_projection_gpu | 10x GPU GEMM |");
    println!("  | Attention (long) | 027 | flash_attention_tiled | O(N) memory |");
    println!("  | Attention (short) | - | attention_with_cache | Standard |");
    println!("  | Attn output | 024 | batch_attention_output_gpu | 10x GPU GEMM |");
    println!("  | FFN gate+up | 020 | batch_ffn_gpu | 10x GPU GEMM |");
    println!("  | FFN down | 021 | batch_ffn_gpu | 10x GPU GEMM |");
    println!("  | LM head | 025 | batch_lm_head_gpu | 10x GPU GEMM |");
    println!("  ");

    // Memory optimization summary
    println!("  Memory optimizations:");
    println!("    - Dequantized weight cache (PARITY-018): ~6.4 GB for phi-2");
    println!("    - FlashAttention (PARITY-026/027): O(N) vs O(N²)");
    println!("    - Enables 4K+ context with bounded memory");
    println!("  ");

    // Throughput projection
    let baseline_cpu = 5.25; // tok/s from measurements
    let gpu_speedup = 10.0; // 10x for batch >= 32
    let gpu_coverage = 1.0; // 100% of GEMM ops
    let expected_speedup = 1.0 / (1.0 - gpu_coverage * (1.0 - 1.0 / gpu_speedup));
    let per_request_tps = baseline_cpu * expected_speedup;
    let batch_throughput = per_request_tps * 32.0;

    println!("  Throughput projection (batch=32):");
    println!("    Per-request: {:.1} tok/s", per_request_tps);
    println!("    Batch throughput: {:.0} tok/s", batch_throughput);
    println!("    Target (Ollama): 225 tok/s");

    if batch_throughput >= 225.0 {
        println!("\n  Status: VERIFIED - Exceeds Ollama throughput target!");
    } else {
        println!("\n  Status: PARTIAL - Continue optimizations");
    }
}

// ============================================================================
// PARITY-028: Continuous Batching Tests
// ============================================================================

/// PARITY-028a: Verify SlotState enum structure
#[test]
#[cfg(feature = "gpu")]
#[serial_test::serial]
fn test_parity028a_slot_state_structure() {
    println!("=== PARITY-028a: SlotState Enum ===\n");

    // SlotState represents lifecycle of a request slot:
    // Empty -> Active -> Completed -> Empty

    println!("  SlotState variants:");
    println!("    Empty - Available for new request");
    println!("    Active - Request being processed");
    println!("    Completed - Request finished, awaiting retrieval");
    println!();

    // Create and verify each state
    use crate::gguf::SlotState;

    let empty = SlotState::Empty;
    assert!(empty.is_empty(), "Empty should be empty");
    assert!(!empty.is_active(), "Empty should not be active");
    assert!(!empty.is_completed(), "Empty should not be completed");
    assert!(empty.request_id().is_none(), "Empty has no request ID");

    let active = SlotState::Active {
        request_id: 42,
        prompt_tokens: vec![1, 2, 3],
        generated_tokens: vec![4, 5],
        max_tokens: 10,
        temperature: 0.7,
        top_k: 40,
    };
    assert!(!active.is_empty(), "Active should not be empty");
    assert!(active.is_active(), "Active should be active");
    assert!(!active.is_completed(), "Active should not be completed");
    assert_eq!(active.request_id(), Some(42), "Active has request ID");

    let completed = SlotState::Completed {
        request_id: 42,
        generated_tokens: vec![4, 5, 6, 7],
    };
    assert!(!completed.is_empty(), "Completed should not be empty");
    assert!(!completed.is_active(), "Completed should not be active");
    assert!(completed.is_completed(), "Completed should be completed");
    assert_eq!(completed.request_id(), Some(42), "Completed has request ID");

    println!("  Verified: Empty, Active, Completed states");
    println!("\n  Status: VERIFIED");
}

/// PARITY-028b: ContinuousBatchScheduler creation and slot management
#[test]
#[cfg(feature = "gpu")]
#[serial_test::serial]
fn test_parity028b_scheduler_creation() {
    println!("=== PARITY-028b: Scheduler Creation ===\n");

    use crate::gguf::ContinuousBatchScheduler;

    // Create scheduler with 32 slots (optimal for GPU batch threshold)
    let num_slots = 32;
    let num_layers = 32;
    let hidden_dim = 2560;
    let max_seq_len = 2048;

    let scheduler = ContinuousBatchScheduler::new(num_slots, num_layers, hidden_dim, max_seq_len);

    println!("  Scheduler configuration:");
    println!("    Slots: {}", scheduler.num_slots);
    println!("    Empty slots: {}", scheduler.empty_count());
    println!("    Active slots: {}", scheduler.active_count());
    println!("    Utilization: {:.1}%", scheduler.utilization() * 100.0);

    // Verify initial state
    assert_eq!(scheduler.num_slots, 32, "Should have 32 slots");
    assert_eq!(
        scheduler.empty_count(),
        32,
        "All slots should be empty initially"
    );
    assert_eq!(scheduler.active_count(), 0, "No active slots initially");
    assert!(
        !scheduler.has_completed(),
        "No completed requests initially"
    );

    println!("\n  Status: VERIFIED");
}

/// PARITY-028c: Request submission and slot allocation
#[test]
#[cfg(feature = "gpu")]
#[serial_test::serial]
fn test_parity028c_request_submission() {
    println!("=== PARITY-028c: Request Submission ===\n");

    use crate::gguf::ContinuousBatchScheduler;

    let scheduler = ContinuousBatchScheduler::new(4, 32, 2560, 2048);

    println!("  Submitting requests to scheduler...");

    // Submit 3 requests
    let id1 = scheduler.submit(vec![1, 2, 3], 10, 0.7, 40);
    let id2 = scheduler.submit(vec![4, 5], 20, 0.5, 50);
    let id3 = scheduler.submit(vec![6], 5, 0.0, 1);

    assert!(id1.is_some(), "First request should succeed");
    assert!(id2.is_some(), "Second request should succeed");
    assert!(id3.is_some(), "Third request should succeed");

    println!("    Request 1: ID={}", id1.expect("test"));
    println!("    Request 2: ID={}", id2.expect("test"));
    println!("    Request 3: ID={}", id3.expect("test"));

    // Check counts
    assert_eq!(scheduler.active_count(), 3, "Should have 3 active slots");
    assert_eq!(scheduler.empty_count(), 1, "Should have 1 empty slot");
    assert_eq!(scheduler.utilization(), 0.75, "Utilization should be 75%");

    // Submit 4th request (last slot)
    let id4 = scheduler.submit(vec![7, 8, 9], 15, 0.9, 30);
    assert!(id4.is_some(), "Fourth request should succeed");

    // Submit 5th request (no slots available)
    let id5 = scheduler.submit(vec![10], 5, 0.5, 40);
    assert!(id5.is_none(), "Fifth request should fail (no slots)");

    println!("\n  After 4 submissions:");
    println!("    Active: {}", scheduler.active_count());
    println!("    Empty: {}", scheduler.empty_count());
    println!("    Utilization: {:.0}%", scheduler.utilization() * 100.0);

    println!("\n  Status: VERIFIED");
}

/// PARITY-028d: Request completion and slot recycling
#[test]
#[cfg(feature = "gpu")]
#[serial_test::serial]
fn test_parity028d_completion_and_recycling() {
    println!("=== PARITY-028d: Completion and Recycling ===\n");

    use crate::gguf::ContinuousBatchScheduler;

    let scheduler = ContinuousBatchScheduler::new(4, 32, 2560, 2048);

    // Fill all slots
    let _id1 = scheduler.submit(vec![1], 10, 0.7, 40).expect("test");
    let _id2 = scheduler.submit(vec![2], 10, 0.7, 40).expect("test");
    let _id3 = scheduler.submit(vec![3], 10, 0.7, 40).expect("test");
    let _id4 = scheduler.submit(vec![4], 10, 0.7, 40).expect("test");

    assert_eq!(scheduler.active_count(), 4, "All slots active");
    assert_eq!(scheduler.empty_count(), 0, "No empty slots");

    println!("  Initial: 4 active, 0 empty");

    // Complete slot 1
    scheduler.complete_request(1, vec![100, 101, 102]);

    assert_eq!(
        scheduler.active_count(),
        3,
        "3 slots active after completion"
    );
    assert_eq!(scheduler.empty_count(), 1, "1 slot freed");
    assert!(scheduler.has_completed(), "Should have completed request");

    println!("  After completing slot 1: 3 active, 1 empty");

    // Poll completed
    let completed = scheduler.poll_completed();
    assert_eq!(completed.len(), 1, "Should have 1 completed request");
    assert_eq!(completed[0].1, vec![100, 101, 102], "Correct tokens");

    println!("  Polled completed: {} requests", completed.len());

    // New request can now use freed slot
    let id5 = scheduler.submit(vec![5], 10, 0.7, 40);
    assert!(id5.is_some(), "New request should succeed after slot freed");

    println!("  New request submitted to recycled slot");
    println!("\n  Status: VERIFIED");
}

/// PARITY-028e: Throughput analysis with continuous batching
#[test]
#[cfg(feature = "gpu")]
#[serial_test::serial]
fn test_parity028e_continuous_batching_throughput() {
    println!("=== PARITY-028e: Continuous Batching Throughput ===\n");

    // Continuous batching enables higher throughput by:
    // 1. Keeping batch full (new requests fill completed slots)
    // 2. Variable-length requests don't block each other
    // 3. GPU utilization stays high

    let num_slots: usize = 32;
    let avg_tokens_per_request: usize = 50;
    let generation_latency_ms: f64 = 20.0; // Per batch step

    // Without continuous batching: wait for full batch to complete
    let static_batch_tokens = num_slots * avg_tokens_per_request;
    let static_batch_time_ms = avg_tokens_per_request as f64 * generation_latency_ms;
    let static_throughput = (static_batch_tokens as f64 / static_batch_time_ms) * 1000.0;

    // With continuous batching: new requests fill completed slots
    // Effective throughput is higher because slots are recycled
    let avg_utilization = 0.9; // 90% utilization with continuous batching
    let continuous_throughput = static_throughput * avg_utilization / 0.5; // Static batch ~50% avg util

    println!("  Throughput comparison:");
    println!();
    println!("  Static batching:");
    println!("    - Wait for batch to fill: {} requests", num_slots);
    println!(
        "    - Wait for all to complete: {} tokens",
        static_batch_tokens
    );
    println!("    - Average utilization: ~50%");
    println!("    - Throughput: {:.0} tok/s", static_throughput);
    println!();
    println!("  Continuous batching:");
    println!("    - Slot recycling: freed slots immediately reused");
    println!("    - Average utilization: ~90%");
    println!("    - Throughput: {:.0} tok/s", continuous_throughput);
    println!();
    println!(
        "  Improvement: {:.1}x",
        continuous_throughput / static_throughput
    );

    // Verify improvement
    assert!(
        continuous_throughput > static_throughput,
        "Continuous batching should improve throughput"
    );

    println!("\n  Status: VERIFIED - Continuous batching improves throughput");
}

// ============================================================================
// PARITY-029: Speculative Decoding Tests
// ============================================================================

/// PARITY-029a: SpeculativeConfig default values
#[test]
#[cfg(feature = "gpu")]
#[serial_test::serial]
fn test_parity029a_speculative_config() {
    println!("=== PARITY-029a: Speculative Config ===\n");

    use crate::gguf::SpeculativeConfig;

    let config = SpeculativeConfig::default();

    println!("  Default configuration:");
    println!("    speculation_length: {}", config.speculation_length);
    println!("    draft_temperature: {}", config.draft_temperature);
    println!("    self_speculative: {}", config.self_speculative);

    // Verify reasonable defaults
    assert_eq!(
        config.speculation_length, 4,
        "Default speculation length should be 4"
    );
    assert_eq!(
        config.draft_temperature, 0.0,
        "Default draft temp should be greedy"
    );
    assert!(
        config.self_speculative,
        "Default should use self-speculative"
    );

    println!("\n  Status: VERIFIED");
}

/// PARITY-029b: SpeculativeDecoder creation and statistics
#[test]
#[cfg(feature = "gpu")]
#[serial_test::serial]
fn test_parity029b_decoder_creation() {
    println!("=== PARITY-029b: Decoder Creation ===\n");

    use crate::gguf::SpeculativeDecoder;

    let decoder = SpeculativeDecoder::new();

    println!("  Initial state:");
    println!(
        "    speculation_length: {}",
        decoder.config.speculation_length
    );
    println!(
        "    acceptance_rate: {:.1}%",
        decoder.acceptance_rate() * 100.0
    );
    println!("    expected_speedup: {:.2}x", decoder.expected_speedup());

    // Initial state should have 0 acceptance rate
    assert_eq!(
        decoder.acceptance_rate(),
        0.0,
        "Initial acceptance rate should be 0"
    );
    assert_eq!(
        decoder.expected_speedup(),
        1.0,
        "Initial speedup should be 1x"
    );

    println!("\n  Status: VERIFIED");
}

/// PARITY-029c: Draft verification with greedy decoding
#[test]
#[cfg(feature = "gpu")]
#[serial_test::serial]
fn test_parity029c_greedy_verification() {
    println!("=== PARITY-029c: Greedy Verification ===\n");

    use crate::gguf::SpeculativeDecoder;

    let decoder = SpeculativeDecoder::new();

    // Create target logits where token 5 is highest for all positions
    let vocab_size = 100;
    let target_logits: Vec<Vec<f32>> = (0..4)
        .map(|_| {
            let mut logits = vec![0.0f32; vocab_size];
            logits[5] = 10.0; // Token 5 is highest
            logits
        })
        .collect();

    // Case 1: All draft tokens match
    println!("  Case 1: All draft tokens match target");
    let draft_tokens = vec![5, 5, 5, 5];
    let result = decoder.verify_draft(&draft_tokens, &target_logits, 0.0);

    println!("    Draft: {:?}", draft_tokens);
    println!("    Accepted: {:?}", result.accepted_tokens);
    println!(
        "    Count: {}/{}",
        result.accepted_count, result.draft_count
    );

    assert_eq!(result.accepted_count, 4, "All tokens should be accepted");
    assert!(result.all_accepted, "Should report all accepted");

    // Reset for case 2
    decoder.reset_stats();

    // Case 2: First mismatch
    println!("\n  Case 2: Mismatch at position 2");
    let draft_tokens = vec![5, 5, 7, 5]; // Token 7 doesn't match
    let result = decoder.verify_draft(&draft_tokens, &target_logits, 0.0);

    println!("    Draft: {:?}", draft_tokens);
    println!("    Accepted: {:?}", result.accepted_tokens);
    println!(
        "    Count: {}/{}",
        result.accepted_count, result.draft_count
    );

    // Should accept first 2, then reject at 3rd and use target's token
    assert_eq!(
        result.accepted_count, 3,
        "Should accept up to and including correction"
    );
    assert!(!result.all_accepted, "Should not report all accepted");

    println!("\n  Status: VERIFIED");
}