//! ML Inference Performance Benchmarks //! //! Validates ML model inference latency targets for HFT trading system. //! //! Performance Targets: //! - Ensemble inference: <300ms //! - Single model inference: <100ms //! - Feature preparation: <10ms //! - Model switching: <50ms #![allow(unused_crate_dependencies)] use criterion::{black_box, criterion_group, criterion_main, BatchSize, Criterion}; use std::time::Duration; /// Simulated market features for inference #[derive(Clone)] struct MarketFeatures { prices: Vec, volumes: Vec, #[allow(dead_code)] order_book_depth: Vec<(f64, f64)>, // (bid, ask) pairs #[allow(dead_code)] timestamp_features: Vec, } impl MarketFeatures { fn new() -> Self { Self { prices: vec![150.0; 60], // 60 time steps volumes: vec![1000.0; 60], order_book_depth: vec![(149.9, 150.1); 10], timestamp_features: vec![0.0, 0.1, 0.2, 0.3, 0.4], } } } /// Mock ensemble inference struct MockEnsembleInference { model_count: usize, } impl MockEnsembleInference { fn new(model_count: usize) -> Self { Self { model_count } } fn predict(&self, features: &MarketFeatures) -> f64 { // Simulate ensemble inference with some computation let mut result = 0.0; for _ in 0..self.model_count { // Simulate model inference for &price in &features.prices { result += (price * 0.001).tanh(); } for &vol in &features.volumes { result += (vol * 0.0001).ln(); } } result / self.model_count as f64 } } /// Benchmark ensemble inference fn bench_ensemble_inference(c: &mut Criterion) { let ensemble = MockEnsembleInference::new(5); // 5 models in ensemble let features = MarketFeatures::new(); c.bench_function("ensemble_inference_5_models", |b| { b.iter_batched( || features.clone(), |features| black_box(ensemble.predict(&features)), BatchSize::SmallInput, ) }); // Test different ensemble sizes let mut group = c.benchmark_group("ensemble_size_comparison"); for size in [1, 3, 5, 7, 10] { let ensemble = MockEnsembleInference::new(size); group.bench_function(format!("{}_models", size), |b| { b.iter_batched( || features.clone(), |features| black_box(ensemble.predict(&features)), BatchSize::SmallInput, ) }); } group.finish(); } /// Benchmark feature preparation fn bench_feature_preparation(c: &mut Criterion) { let prices = vec![150.0 + (0..100).map(|i| i as f64 * 0.1).sum::(); 100]; let volumes = vec![1000.0; 100]; c.bench_function("feature_preparation", |b| { b.iter(|| { let features = MarketFeatures { prices: black_box(&prices).to_vec(), volumes: black_box(&volumes).to_vec(), order_book_depth: vec![(149.9, 150.1); 10], timestamp_features: vec![0.0; 5], }; black_box(features) }) }); // Test feature normalization c.bench_function("feature_normalization", |b| { b.iter(|| { let normalized: Vec = prices .iter() .map(|&p| { let mean = 150.0; let std = 10.0; (p - mean) / std }) .collect(); black_box(normalized) }) }); } /// Benchmark single model inference fn bench_single_model_inference(c: &mut Criterion) { let model = MockEnsembleInference::new(1); let features = MarketFeatures::new(); c.bench_function("single_model_inference", |b| { b.iter_batched( || features.clone(), |features| black_box(model.predict(&features)), BatchSize::SmallInput, ) }); } /// Benchmark batch inference fn bench_batch_inference(c: &mut Criterion) { let model = MockEnsembleInference::new(5); let batch: Vec = (0..10).map(|_| MarketFeatures::new()).collect(); c.bench_function("batch_inference_10_samples", |b| { b.iter_batched( || batch.clone(), |batch| { batch .iter() .map(|features| model.predict(features)) .collect::>() }, BatchSize::SmallInput, ) }); // Test different batch sizes let mut group = c.benchmark_group("batch_size_comparison"); for batch_size in [1, 5, 10, 20, 50] { let batch: Vec = (0..batch_size).map(|_| MarketFeatures::new()).collect(); group.bench_function(format!("batch_{}", batch_size), |b| { b.iter_batched( || batch.clone(), |batch| { batch .iter() .map(|features| model.predict(features)) .collect::>() }, BatchSize::SmallInput, ) }); } group.finish(); } criterion_group! { name = ml_inference_benchmarks; config = Criterion::default() .measurement_time(Duration::from_secs(10)) .sample_size(100) .warm_up_time(Duration::from_secs(3)); targets = bench_ensemble_inference, bench_single_model_inference, bench_feature_preparation, bench_batch_inference } criterion_main!(ml_inference_benchmarks); #[cfg(test)] mod performance_tests { use super::*; use std::time::Instant; #[test] fn test_ensemble_inference_latency() { let ensemble = MockEnsembleInference::new(5); let features = MarketFeatures::new(); let iterations = 100; let start = Instant::now(); for _ in 0..iterations { black_box(ensemble.predict(&features)); } let duration = start.elapsed(); let avg_duration_ms = duration.as_millis() / iterations; // Target: <300ms for ensemble inference assert!( avg_duration_ms < 300, "Ensemble inference too slow: {}ms average (target: <300ms)", avg_duration_ms ); println!("✓ Ensemble inference: {}ms average", avg_duration_ms); } #[test] fn test_single_model_latency() { let model = MockEnsembleInference::new(1); let features = MarketFeatures::new(); let iterations = 100; let start = Instant::now(); for _ in 0..iterations { black_box(model.predict(&features)); } let duration = start.elapsed(); let avg_duration_ms = duration.as_millis() / iterations; // Target: <100ms for single model assert!( avg_duration_ms < 100, "Single model inference too slow: {}ms average (target: <100ms)", avg_duration_ms ); println!("✓ Single model inference: {}ms average", avg_duration_ms); } #[test] fn test_feature_preparation_latency() { let prices = vec![150.0; 100]; let volumes = vec![1000.0; 100]; let iterations = 1000; let start = Instant::now(); for _ in 0..iterations { let features = MarketFeatures { prices: prices.clone(), volumes: volumes.clone(), order_book_depth: vec![(149.9, 150.1); 10], timestamp_features: vec![0.0; 5], }; black_box(features); } let duration = start.elapsed(); let avg_duration_us = duration.as_micros() / iterations; // Target: <10ms (10000μs) for feature preparation assert!( avg_duration_us < 10000, "Feature preparation too slow: {}μs average (target: <10000μs)", avg_duration_us ); println!("✓ Feature preparation: {}μs average", avg_duration_us); } #[test] fn test_batch_inference_throughput() { let model = MockEnsembleInference::new(5); let batch: Vec = (0..10).map(|_| MarketFeatures::new()).collect(); let iterations = 10; let start = Instant::now(); for _ in 0..iterations { let results: Vec = batch.iter().map(|f| model.predict(f)).collect(); black_box(results); } let duration = start.elapsed(); let total_predictions = iterations * 10; let avg_per_prediction_ms = duration.as_millis() / total_predictions; println!( "✓ Batch inference throughput: {}ms per prediction (batch size: 10)", avg_per_prediction_ms ); println!( " Total: {} predictions in {:?}", total_predictions, duration ); } }