Files
foxhunt/ml/benches/inference_bench.rs
jgrusewski 8b9abcc3c1 fix: resolve all clippy errors across 37+ workspace crates
Eliminate ~4,260 clippy deny-level errors that blocked workspace-wide
clippy runs. Errors cascaded: upstream crate failures (ctrader-openapi,
risk-data) hid thousands of downstream errors in ml, tli, backtesting.

Key changes:
- ctrader-openapi: fix shadow_unrelated/shadow_reuse (renamed vars)
- risk-data/risk: replace non-ASCII em dashes with ASCII equivalents
- tli: allow deny lints on prost-generated proto code, fix shadows
- trading_engine: fix let_underscore_must_use, wildcard matches, shadows
- broker_gateway_service: allow dead_code on unused redis_client field
- ml (4030 errors): remove local deny overrides for unwrap/expect/indexing
  (workspace warn level sufficient), add crate-level allows for non-safety
  mass-violation lints (non_ascii_literal, shadow_*, str_to_string, etc.),
  batch-fix em dashes, unseparated literal suffixes, format_push_string,
  wildcard matches, impl_trait_in_params, mutex_atomic, and more
- backtesting: replace unwrap() on first()/last() with match destructure
- tests: simplify loop-that-never-loops, fix mutex unwrap

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 12:44:10 +01:00

302 lines
8.8 KiB
Rust

//! 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<f64>,
volumes: Vec<f64>,
order_book_depth: Vec<(f64, f64)>, // (bid, ask) pairs
timestamp_features: Vec<f64>,
}
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::<f64>(); 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<f64> = 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<MarketFeatures> = (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::<Vec<_>>()
},
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<MarketFeatures> = (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::<Vec<_>>()
},
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<MarketFeatures> = (0..10).map(|_| MarketFeatures::new()).collect();
let iterations = 10;
let start = Instant::now();
for _ in 0..iterations {
let results: Vec<f64> = 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
);
}
}