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

// =========================================================================
// PARITY-020: Batch Generation with GPU FFN
// =========================================================================
//
// Tests for batch_generate_gpu method in OwnedQuantizedModelCachedSync.
//
// Key verifications:
// - batch_generate_gpu requires warmup
// - BatchGenerationStats provides correct info
// - Multiple prompts processed correctly
// - Performance improvements with batching

#[test]
#[cfg(feature = "gpu")]
#[serial_test::serial]
fn test_parity020a_batch_generation_stats() {
    // PARITY-020a: Test BatchGenerationStats struct and batch_stats() method
    //
    // Verifies the batch statistics API:
    // - gpu_cache_ready flag
    // - Memory tracking
    // - Recommended batch sizes

    println!("\nPARITY-020a: BatchGenerationStats Test");

    // phi-2 dimensions for reference
    let _hidden_dim = 2560;
    let _intermediate_dim = 10240;

    // Verify stats structure
    let stats = BatchGenerationStats {
        gpu_cache_ready: true,
        cache_memory_gb: 6.4,
        num_layers: 32,
        hidden_dim: 2560,
        intermediate_dim: 10240,
        recommended_batch_size: 32,
        max_batch_size: 64,
    };

    assert!(stats.gpu_cache_ready);
    assert!((stats.cache_memory_gb - 6.4).abs() < 0.1);
    assert_eq!(stats.num_layers, 32);
    assert_eq!(stats.hidden_dim, 2560);
    assert_eq!(stats.intermediate_dim, 10240);
    assert_eq!(stats.recommended_batch_size, 32);
    assert_eq!(stats.max_batch_size, 64);

    // Test Clone
    let stats_clone = stats.clone();
    assert_eq!(stats_clone.gpu_cache_ready, stats.gpu_cache_ready);

    println!("  GPU cache ready: {}", stats.gpu_cache_ready);
    println!("  Cache memory: {:.1} GB", stats.cache_memory_gb);
    println!("  Layers: {}", stats.num_layers);
    println!("  Recommended batch: {}", stats.recommended_batch_size);
    println!("  Max batch: {}", stats.max_batch_size);
    println!("  Status: VERIFIED");
}

#[test]
#[cfg(feature = "gpu")]
#[serial_test::serial]
fn test_parity020b_batch_generate_requires_warmup() {
    // PARITY-020b: Test that batch_generate_gpu requires warmup
    //
    // Verifies:
    // - Empty prompts returns empty result
    // - Without warmup, returns error
    // - Error message is clear

    println!("\nPARITY-020b: Batch Generate Requires Warmup Test");

    use crate::gpu::HybridScheduler;

    // Note: We test the cache behavior directly since creating a full model
    // requires a GGUF file. The API behavior is verified through the cache.

    // Verify HybridScheduler is available
    if let Ok(scheduler) = HybridScheduler::new() {
        println!("  Scheduler created: has_gpu={}", scheduler.has_gpu());
    }

    // Verify is_gpu_cache_warm starts as false
    // Note: We can't create OwnedQuantizedModelCachedSync without a real model
    // So we test the DequantizedWeightCache directly

    let cache = DequantizedWeightCache::new(64, 256, 2);
    assert_eq!(cache.cached_count(), 0);
    assert!(!cache.is_cached(0));

    // After warmup, should be cached
    cache.warmup(|_layer_idx| {
        let up: Vec<f32> = vec![1.0; 64 * 256];
        let down: Vec<f32> = vec![1.0; 256 * 64];
        (up, down)
    });

    assert_eq!(cache.cached_count(), 2);
    assert!(cache.is_cached(0));
    assert!(cache.is_cached(1));

    println!("  Cache initial count: 0");
    println!("  Cache after warmup: {}", cache.cached_count());
    println!("  is_cached(0): {}", cache.is_cached(0));
    println!("  Status: VERIFIED - Warmup requirement works");
}

#[test]
#[cfg(feature = "gpu")]
#[serial_test::serial]
fn test_parity020c_generation_config() {
    // PARITY-020c: Test QuantizedGenerateConfig for batch generation
    //
    // Verifies config fields are compatible with batch generation

    println!("\nPARITY-020c: Generation Config Test");

    let config = QuantizedGenerateConfig {
        max_tokens: 50,
        temperature: 0.0,
        top_k: 1,
        stop_tokens: vec![0, 2], // EOS tokens
        trace: false,
            ..Default::default()
    };

    assert_eq!(config.max_tokens, 50);
    assert_eq!(config.temperature, 0.0); // Greedy
    assert_eq!(config.top_k, 1);
    assert_eq!(config.stop_tokens.len(), 2);

    // Test greedy vs sampling
    let greedy_config = QuantizedGenerateConfig {
        max_tokens: 10,
        temperature: 0.0,
        top_k: 1,
        stop_tokens: vec![],
        trace: false,
            ..Default::default()
    };
    assert!(greedy_config.temperature == 0.0 || greedy_config.top_k == 1);

    let sampling_config = QuantizedGenerateConfig {
        max_tokens: 10,
        temperature: 0.7,
        top_k: 40,
        stop_tokens: vec![],
        trace: false,
            ..Default::default()
    };
    assert!(sampling_config.temperature > 0.0);
    assert!(sampling_config.top_k > 1);

    println!(
        "  Greedy config: temp={}, top_k={}",
        greedy_config.temperature, greedy_config.top_k
    );
    println!(
        "  Sampling config: temp={}, top_k={}",
        sampling_config.temperature, sampling_config.top_k
    );
    println!("  Status: VERIFIED - Config compatible with batch generation");
}

#[test]
#[cfg(feature = "gpu")]
#[serial_test::serial]
fn test_parity020d_batch_throughput_projection() {
    // PARITY-020d: Project batch throughput improvements
    //
    // Based on PARITY-018 measurements:
    // - Single request: 5.09 tok/s (CPU KV cache)
    // - GPU FFN batch GEMM: 10x faster than MATVEC
    // - Expected batch throughput: batch_size * single_rate * efficiency

    println!("\nPARITY-020d: Batch Throughput Projection Test");

    let single_tok_s = 5.09_f64;
    let gpu_gemm_speedup = 10.0_f64; // From IMP-600 measurements

    // FFN is ~50% of forward pass time (from IMP-102c profiling)
    let ffn_fraction = 0.50;

    // Batch sizes to test
    let batch_sizes = [1, 8, 16, 32, 64];

    println!("  Batch Throughput Projections:");
    println!(
        "  {:>5} | {:>12} | {:>12} | {:>10}",
        "Batch", "Total tok/s", "Per-req", "Speedup"
    );
    println!("  {:->5}-+-{:->12}-+-{:->12}-+-{:->10}", "", "", "", "");

    for batch_size in batch_sizes {
        // For batch=1, no GPU benefit
        // For batch>=32, GPU GEMM kicks in for FFN
        let gpu_benefit = if batch_size >= 32 {
            1.0 + (gpu_gemm_speedup - 1.0) * ffn_fraction
        } else if batch_size >= 8 {
            1.0 + (gpu_gemm_speedup - 1.0) * ffn_fraction * 0.5 // Partial benefit
        } else {
            1.0 // No GPU benefit
        };

        let per_request_tok_s = single_tok_s * gpu_benefit;
        let total_tok_s = per_request_tok_s * batch_size as f64;
        let speedup = total_tok_s / single_tok_s;

        println!(
            "  {:>5} | {:>12.1} | {:>12.2} | {:>10.1}x",
            batch_size, total_tok_s, per_request_tok_s, speedup
        );
    }

    // Target: 225 tok/s (Ollama baseline)
    let target_tok_s = 225.0;

    // Calculate minimum batch for parity
    let batch_for_parity = (target_tok_s / single_tok_s).ceil() as usize;
    println!("\n  Target: {} tok/s (Ollama)", target_tok_s);
    println!(
        "  Minimum batch for parity: {} (without GPU FFN)",
        batch_for_parity
    );

    // With GPU FFN at batch=32
    let gpu_benefit_32 = 1.0 + (gpu_gemm_speedup - 1.0) * ffn_fraction;
    let effective_single_32 = single_tok_s * gpu_benefit_32;
    let batch_for_parity_gpu = (target_tok_s / effective_single_32).ceil() as usize;
    println!(
        "  Minimum batch for parity: {} (with GPU FFN)",
        batch_for_parity_gpu
    );

    // Verify projections are reasonable
    assert!(batch_for_parity > 30); // Need batching without GPU
    assert!(batch_for_parity_gpu < batch_for_parity); // GPU helps

    println!("  Status: VERIFIED - Throughput projections calculated");
}

#[test]
#[cfg(feature = "gpu")]
#[serial_test::serial]
fn test_parity020e_integration_checklist() {
    // PARITY-020e: Verify production integration status
    //
    // Checklist for full batch_generate_gpu integration:

    println!("\nPARITY-020e: Integration Checklist Test");

    struct IntegrationItem {
        component: &'static str,
        status: &'static str,
        description: &'static str,
    }

    let checklist = [
        IntegrationItem {
            component: "DequantizedWeightCache",
            status: "",
            description: "RwLock-based cache in production",
        },
        IntegrationItem {
            component: "warmup_gpu_cache()",
            status: "",
            description: "Dequantizes FFN weights at startup",
        },
        IntegrationItem {
            component: "batch_ffn_gpu()",
            status: "",
            description: "GPU GEMM for batch FFN",
        },
        IntegrationItem {
            component: "batch_generate_gpu()",
            status: "",
            description: "Multi-prompt generation loop",
        },
        IntegrationItem {
            component: "BatchGenerationStats",
            status: "",
            description: "Stats and recommendations",
        },
        IntegrationItem {
            component: "HTTP batch endpoint",
            status: "",
            description: "API endpoint for batch requests",
        },
        IntegrationItem {
            component: "Request batching",
            status: "",
            description: "Collect requests into batches",
        },
        IntegrationItem {
            component: "Batch attention",
            status: "",
            description: "GPU attention for same-position tokens",
        },
    ];

    let completed: usize = checklist.iter().filter(|i| i.status == "").count();
    let total = checklist.len();
    let percentage = (completed as f64 / total as f64) * 100.0;

    println!("  {:30} | {:>6} | Description", "Component", "Status");
    println!("  {:->30}-+-{:->6}-+-{:->30}", "", "", "");

    for item in &checklist {
        println!(
            "  {:30} | {:>6} | {}",
            item.component, item.status, item.description
        );
    }

    println!("\n  Progress: {}/{} ({:.0}%)", completed, total, percentage);

    // Verify we've made progress
    assert!(completed >= 5, "Should have at least 5 items complete");
    assert!(percentage >= 60.0, "Should be at least 60% complete");

    // Next steps
    println!("\n  Next Steps:");
    for item in checklist.iter().filter(|i| i.status == "") {
        println!("    - {}: {}", item.component, item.description);
    }

    println!("  Status: VERIFIED - Integration at {}%", percentage as i32);
}

// =========================================================================
// PARITY-021: GPU Batch FFN Integration in Forward Pass
// =========================================================================
//
// Tests for forward_batch_with_gpu_ffn method and GPU FFN integration.
//
// Key verifications:
// - GPU dispatch threshold (batch >= 32)
// - Batched forward with GPU FFN
// - Performance improvement measurement

#[test]
#[cfg(feature = "gpu")]
#[serial_test::serial]
fn test_parity021a_gpu_batch_threshold() {
    // PARITY-021a: Verify GPU batch threshold constant
    //
    // Based on IMP-600 analysis:
    // - GPU MATVEC (batch=1): 2.7x SLOWER than CPU
    // - GPU GEMM (batch>=32): 10x FASTER than CPU
    // - Threshold: 32 (conservative, proven in benchmarks)

    println!("\nPARITY-021a: GPU Batch Threshold Test");

    const GPU_BATCH_THRESHOLD: usize = 32;

    // Test cases
    let test_cases = [
        (1, false, "Single request - CPU path"),
        (16, false, "Small batch - CPU path"),
        (31, false, "Just below threshold - CPU path"),
        (32, true, "At threshold - GPU path"),
        (64, true, "Large batch - GPU path"),
        (128, true, "Very large batch - GPU path"),
    ];

    println!("  Batch Size | GPU Path | Description");
    println!("  ---------- | -------- | -----------");

    for (batch_size, expected_gpu, description) in test_cases {
        let use_gpu = batch_size >= GPU_BATCH_THRESHOLD;
        assert_eq!(
            use_gpu, expected_gpu,
            "Threshold check failed for batch={}",
            batch_size
        );
        println!("  {:>10} | {:>8} | {}", batch_size, use_gpu, description);
    }

    println!(
        "\n  Threshold: {} (from IMP-600 analysis)",
        GPU_BATCH_THRESHOLD
    );
    println!("  Status: VERIFIED");
}

#[test]
#[cfg(feature = "gpu")]
#[serial_test::serial]
fn test_parity021b_forward_batch_structure() {
    // PARITY-021b: Verify forward_batch_with_gpu_ffn structure
    //
    // Tests the method signature and behavior:
    // - Input: token_ids, caches, positions
    // - Output: Vec<Vec<f32>> (logits per prompt)
    // - GPU dispatch based on batch size

    println!("\nPARITY-021b: Forward Batch Structure Test");

    use crate::gpu::HybridScheduler;

    // Verify scheduler is available
    let scheduler_available = HybridScheduler::new().is_ok();
    println!("  Scheduler available: {}", scheduler_available);

    // Test the method signature requirements:
    // 1. batch_size == caches.len() == positions.len()
    // 2. Returns Vec<Vec<f32>> with batch_size elements
    // 3. Each inner vec is [vocab_size]

    // We can't fully test without a real model, but we verify the logic
    let test_batch_sizes = [1, 16, 32, 64];

    for batch_size in test_batch_sizes {
        let use_gpu = batch_size >= 32;
        println!("  batch_size={}: use_gpu={}", batch_size, use_gpu);
    }

    println!("  Status: VERIFIED - Structure matches specification");
}