Removed across 66 files:
- 49 instances of "// use crate::safe_operations; // DISABLED"
- 11 instances of "// use error_handling::{...}; // crate doesn't exist"
- 2 instances of "// use crate::Optimizer; // not available"
- 5 disabled test placeholder blocks (/* ... */) in ensemble/
- 1 disabled From impl in lib.rs (38 lines)
- 1 disabled test module in model.rs (113 lines)
- 1 disabled code block in integration/distillation.rs (41 lines)
- Various other disabled imports with explanation comments
All of this code references modules/crates that were removed during
prior refactoring waves and is preserved in git history. Removing it
reduces noise and makes the codebase easier to navigate.
1922 lib tests passing, compilation clean.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
481 lines
14 KiB
Rust
481 lines
14 KiB
Rust
#![allow(unsafe_code)] // Intentional unsafe for SIMD vectorization performance
|
|
|
|
//! Ultra-Low Latency Performance Optimizations for HFT ML Models
|
|
//!
|
|
//! Provides SIMD vectorization, cache-optimal data layouts, and memory-mapped
|
|
//! operations to achieve sub-100μs inference latency for high-frequency trading.
|
|
|
|
use std::arch::x86_64::*;
|
|
use std::time::Instant;
|
|
|
|
// SIMD operations for high-performance ML computations
|
|
|
|
use crate::MLError;
|
|
|
|
/// Aligned buffer for SIMD operations
|
|
#[derive(Debug)]
|
|
pub struct AlignedBuffer<T> {
|
|
data: Vec<T>,
|
|
size: usize,
|
|
}
|
|
|
|
impl<T: Default + Clone> AlignedBuffer<T> {
|
|
pub fn new(size: usize) -> Result<Self, MLError> {
|
|
Ok(Self {
|
|
data: vec![T::default(); size],
|
|
size,
|
|
})
|
|
}
|
|
|
|
pub fn resize(&mut self, new_size: usize) -> Result<(), MLError> {
|
|
self.data.resize(new_size, T::default());
|
|
self.size = new_size;
|
|
Ok(())
|
|
}
|
|
|
|
pub fn len(&self) -> usize {
|
|
self.size
|
|
}
|
|
|
|
pub fn capacity(&self) -> usize {
|
|
self.data.capacity()
|
|
}
|
|
|
|
pub fn as_slice(&self) -> &[T] {
|
|
&self.data[..self.size]
|
|
}
|
|
}
|
|
|
|
/// Performance profiler for ML inference
|
|
#[derive(Debug)]
|
|
pub struct LatencyProfiler {
|
|
violations: usize,
|
|
total: usize,
|
|
min_latency: u64,
|
|
max_latency: u64,
|
|
}
|
|
|
|
impl LatencyProfiler {
|
|
pub fn new() -> Self {
|
|
Self {
|
|
violations: 0,
|
|
total: 0,
|
|
min_latency: u64::MAX,
|
|
max_latency: 0,
|
|
}
|
|
}
|
|
|
|
pub fn record_inference(&mut self, latency_us: u64) {
|
|
self.total += 1;
|
|
if latency_us > 100 {
|
|
// 100μs threshold
|
|
self.violations += 1;
|
|
}
|
|
self.min_latency = self.min_latency.min(latency_us);
|
|
self.max_latency = self.max_latency.max(latency_us);
|
|
}
|
|
|
|
pub fn get_stats(&self) -> LatencyStats {
|
|
LatencyStats {
|
|
total_inferences: self.total,
|
|
violation_rate: if self.total > 0 {
|
|
self.violations as f64 / self.total as f64
|
|
} else {
|
|
0.0
|
|
},
|
|
min_latency_us: self.min_latency,
|
|
max_latency_us: self.max_latency,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub struct LatencyStats {
|
|
pub total_inferences: usize,
|
|
pub violation_rate: f64,
|
|
pub min_latency_us: u64,
|
|
pub max_latency_us: u64,
|
|
}
|
|
|
|
/// Performance benchmark utilities
|
|
#[derive(Debug)]
|
|
pub struct PerformanceBenchmark;
|
|
|
|
impl PerformanceBenchmark {
|
|
/// Benchmark SIMD dot product performance
|
|
///
|
|
/// # Errors
|
|
///
|
|
/// Returns `MLError` if:
|
|
/// - Vector allocation fails
|
|
/// - Timing measurement fails
|
|
/// - Arithmetic overflow occurs
|
|
pub fn benchmark_simd_dot_product(size: usize, iterations: usize) -> Result<f64, MLError> {
|
|
let a: Vec<f32> = (0..size).map(|i| i as f32).collect();
|
|
let b: Vec<f32> = (0..size).map(|i| (i + 1) as f32).collect();
|
|
|
|
let start = Instant::now();
|
|
for _ in 0..iterations {
|
|
let _result: f32 = a.iter().zip(b.iter()).map(|(x, y)| x * y).sum();
|
|
}
|
|
let elapsed = start.elapsed();
|
|
|
|
Ok(elapsed.as_micros() as f64 / iterations as f64)
|
|
}
|
|
}
|
|
|
|
/// SIMD operations
|
|
pub mod simd_ops {
|
|
pub fn simd_dot_product(a: &[f32], b: &[f32]) -> f32 {
|
|
a.iter().zip(b.iter()).map(|(x, y)| x * y).sum()
|
|
}
|
|
|
|
pub fn simd_sigmoid_batch(input: &[f32]) -> Vec<f32> {
|
|
input.iter().map(|&x| 1.0 / (1.0 + (-x).exp())).collect()
|
|
}
|
|
}
|
|
|
|
/// High-performance SIMD operations for ML computations
|
|
#[derive(Debug)]
|
|
pub struct SimdOptimizedOps;
|
|
|
|
impl SimdOptimizedOps {
|
|
/// Vectorized dot product using AVX2 instructions
|
|
///
|
|
/// # Errors
|
|
///
|
|
/// Returns `MLError` if:
|
|
/// - Input vectors have different lengths
|
|
/// - Vectors are empty (returns 0.0)
|
|
/// - SIMD operations fail
|
|
/// - Memory alignment is invalid
|
|
///
|
|
#[cfg(target_arch = "x86_64")]
|
|
pub fn dot_product_f32(a: &[f32], b: &[f32]) -> Result<f32, MLError> {
|
|
if a.len() != b.len() {
|
|
return Err(MLError::ValidationError {
|
|
message: format!(
|
|
"dot_product: Dimension mismatch: expected {}, got {}",
|
|
a.len(),
|
|
b.len()
|
|
),
|
|
});
|
|
}
|
|
|
|
if !is_x86_feature_detected!("avx2") {
|
|
// Fallback to standard implementation
|
|
return Ok(a.iter().zip(b.iter()).map(|(x, y)| x * y).sum());
|
|
}
|
|
|
|
// SECURITY: Added bounds checking before unsafe SIMD operations
|
|
if a.len() != b.len() {
|
|
return Err(MLError::InvalidInput(
|
|
"Vector lengths must match for dot product".to_string(),
|
|
));
|
|
}
|
|
|
|
if a.is_empty() {
|
|
return Ok(0.0);
|
|
}
|
|
|
|
// SAFETY: AVX2 SIMD dot product delegation
|
|
// - Invariant 1: Length equality verified above (a.len() == b.len())
|
|
// - Invariant 2: Empty case handled, ensures at least 1 element
|
|
// - Invariant 3: AVX2 support implied by #[cfg] and target_feature
|
|
// - Verified: Bounds checking in caller, avx2_dot_product has debug_asserts
|
|
// - Risk: MEDIUM - SIMD function requires AVX2 CPU support
|
|
unsafe { Self::avx2_dot_product(a, b) }
|
|
}
|
|
|
|
#[cfg(target_arch = "x86_64")]
|
|
#[target_feature(enable = "avx2")]
|
|
unsafe fn avx2_dot_product(a: &[f32], b: &[f32]) -> Result<f32, MLError> {
|
|
// SECURITY: Additional bounds checking in unsafe function
|
|
debug_assert_eq!(a.len(), b.len(), "Vector lengths must match");
|
|
debug_assert!(!a.is_empty(), "Vectors must not be empty");
|
|
|
|
let len = a.len();
|
|
let mut sum = _mm256_setzero_ps();
|
|
|
|
// Process 8 elements at a time (AVX2 width)
|
|
let chunks = len / 8;
|
|
for i in 0..chunks {
|
|
let offset = i * 8;
|
|
|
|
let va = _mm256_loadu_ps(a.as_ptr().add(offset));
|
|
let vb = _mm256_loadu_ps(b.as_ptr().add(offset));
|
|
let vmul = _mm256_mul_ps(va, vb);
|
|
sum = _mm256_add_ps(sum, vmul);
|
|
}
|
|
|
|
// Sum the 8 components of the result
|
|
let sum_array: [f32; 8] = std::mem::transmute(sum);
|
|
let mut result = sum_array.iter().sum::<f32>();
|
|
|
|
// Handle remaining elements
|
|
for i in (chunks * 8)..len {
|
|
result += a[i] * b[i];
|
|
}
|
|
|
|
Ok(result)
|
|
}
|
|
|
|
/// Vectorized matrix-vector multiplication
|
|
///
|
|
/// # Errors
|
|
///
|
|
/// Returns `MLError` if:
|
|
/// - Matrix is empty (returns empty vector)
|
|
/// - Matrix dimensions don't match vector length
|
|
/// - Row-vector multiplication fails
|
|
/// - Memory allocation fails
|
|
///
|
|
#[cfg(target_arch = "x86_64")]
|
|
pub fn matrix_vector_mul(matrix: &[Vec<f32>], vector: &[f32]) -> Result<Vec<f32>, MLError> {
|
|
if matrix.is_empty() {
|
|
return Ok(Vec::new());
|
|
}
|
|
|
|
if matrix[0].len() != vector.len() {
|
|
return Err(MLError::ValidationError {
|
|
message: format!(
|
|
"matrix_vector_multiply: Dimension mismatch: expected {}, got {}",
|
|
matrix[0].len(),
|
|
vector.len()
|
|
),
|
|
});
|
|
}
|
|
|
|
let mut result = Vec::with_capacity(matrix.len());
|
|
|
|
for row in matrix {
|
|
let dot_product = Self::dot_product_f32(row, vector)?;
|
|
result.push(dot_product);
|
|
}
|
|
|
|
Ok(result)
|
|
}
|
|
|
|
/// Vectorized ReLU activation with SIMD
|
|
#[cfg(target_arch = "x86_64")]
|
|
pub fn relu_batch(input: &[f32]) -> Vec<f32> {
|
|
if !is_x86_feature_detected!("avx2") {
|
|
return input.iter().map(|&x| x.max(0.0)).collect();
|
|
}
|
|
|
|
// SAFETY: AVX2 SIMD ReLU batch processing
|
|
// - Invariant 1: Empty case handled above, input has elements
|
|
// - Invariant 2: Output vector pre-allocated with correct size
|
|
// - Invariant 3: AVX2 _mm256_max_ps correctly implements ReLU (max(0, x))
|
|
// - Verified: Target feature enable="avx2" ensures CPU support
|
|
// - Risk: LOW - Simple SIMD operation, well-tested pattern
|
|
unsafe { Self::avx2_relu_batch(input) }
|
|
}
|
|
|
|
#[cfg(target_arch = "x86_64")]
|
|
#[target_feature(enable = "avx2")]
|
|
unsafe fn avx2_relu_batch(input: &[f32]) -> Vec<f32> {
|
|
let mut output = vec![0.0_f32; input.len()];
|
|
let zero = _mm256_setzero_ps();
|
|
|
|
// Process 8 elements at a time
|
|
let chunks = input.len() / 8;
|
|
for i in 0..chunks {
|
|
let offset = i * 8;
|
|
let data = _mm256_loadu_ps(input.as_ptr().add(offset));
|
|
let result = _mm256_max_ps(data, zero);
|
|
_mm256_storeu_ps(output.as_mut_ptr().add(offset), result);
|
|
}
|
|
|
|
// Handle remaining elements
|
|
for i in (chunks * 8)..input.len() {
|
|
output[i] = input[i].max(0.0);
|
|
}
|
|
|
|
output
|
|
}
|
|
|
|
/// Optimized softmax with numerical stability
|
|
///
|
|
/// # Errors
|
|
///
|
|
/// Returns `MLError` if:
|
|
/// - Input is empty
|
|
/// - Exponential calculation overflows
|
|
/// - Sum is zero (numerical instability)
|
|
///
|
|
/// High-performance softmax with SIMD optimization
|
|
pub fn softmax_batch(input: &[f32]) -> Result<Vec<f32>, MLError> {
|
|
if input.is_empty() {
|
|
return Ok(Vec::new());
|
|
}
|
|
|
|
// Find maximum for numerical stability
|
|
let max_val = input.iter().fold(f32::NEG_INFINITY, |a, &b| a.max(b));
|
|
|
|
// Compute exp(x - max) for all elements
|
|
let exp_vals: Vec<f32> = input.iter().map(|&x| (x - max_val).exp()).collect();
|
|
|
|
// Compute sum of exponentials
|
|
let sum_exp: f32 = exp_vals.iter().sum();
|
|
|
|
if sum_exp == 0.0 {
|
|
return Err(MLError::ValidationError {
|
|
message: "softmax: Softmax sum is zero".to_string(),
|
|
});
|
|
}
|
|
|
|
// Normalize
|
|
let result = exp_vals.iter().map(|&x| x / sum_exp).collect();
|
|
|
|
Ok(result)
|
|
}
|
|
|
|
/// Optimized batch normalization
|
|
pub fn batch_norm(
|
|
input: &[f32],
|
|
mean: f32,
|
|
variance: f32,
|
|
gamma: f32,
|
|
beta: f32,
|
|
epsilon: f32,
|
|
) -> Result<Vec<f32>, MLError> {
|
|
if variance < 0.0 {
|
|
return Err(MLError::ValidationError {
|
|
message: "batch_norm: Variance cannot be negative".to_string(),
|
|
});
|
|
}
|
|
|
|
let std_dev = (variance + epsilon).sqrt();
|
|
let result = input
|
|
.iter()
|
|
.map(|&x| gamma * (x - mean) / std_dev + beta)
|
|
.collect();
|
|
|
|
Ok(result)
|
|
}
|
|
}
|
|
|
|
/// Memory-optimized operations with zero-copy where possible
|
|
#[derive(Debug)]
|
|
pub struct ZeroCopyOps;
|
|
|
|
impl ZeroCopyOps {
|
|
/// In-place ReLU operation to avoid allocations
|
|
pub fn relu_inplace(data: &mut [f32]) {
|
|
for value in data.iter_mut() {
|
|
if *value < 0.0 {
|
|
*value = 0.0;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// In-place sigmoid operation
|
|
pub fn sigmoid_inplace(data: &mut [f32]) {
|
|
for value in data.iter_mut() {
|
|
*value = 1.0 / (1.0 + (-*value).exp());
|
|
}
|
|
}
|
|
|
|
/// In-place batch normalization
|
|
pub fn batch_norm_inplace(
|
|
data: &mut [f32],
|
|
mean: f32,
|
|
variance: f32,
|
|
gamma: f32,
|
|
beta: f32,
|
|
epsilon: f32,
|
|
) -> Result<(), MLError> {
|
|
if variance < 0.0 {
|
|
return Err(MLError::ValidationError {
|
|
message: "batch_norm: Variance cannot be negative".to_string(),
|
|
});
|
|
}
|
|
|
|
let std_dev = (variance + epsilon).sqrt();
|
|
for value in data.iter_mut() {
|
|
*value = gamma * (*value - mean) / std_dev + beta;
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use std::mem::align_of;
|
|
|
|
|
|
#[test]
|
|
fn test_aligned_buffer() -> Result<(), MLError> {
|
|
let mut buffer: AlignedBuffer<f32> = AlignedBuffer::new(1024)?;
|
|
buffer.resize(512)?;
|
|
|
|
assert_eq!(buffer.capacity(), 1024);
|
|
assert_eq!(buffer.len(), 512);
|
|
|
|
// Test memory alignment (best effort - Vec doesn't guarantee specific alignment)
|
|
let ptr = buffer.as_slice().as_ptr() as usize;
|
|
// Note: Rust's Vec uses system allocator which may not guarantee 64-byte alignment
|
|
// This is acceptable for testing; production code would use aligned allocators
|
|
assert_eq!(ptr % align_of::<f32>(), 0); // At least naturally aligned
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn test_simd_dot_product() {
|
|
let a = vec![1.0, 2.0, 3.0, 4.0];
|
|
let b = vec![2.0, 3.0, 4.0, 5.0];
|
|
|
|
let result = simd_ops::simd_dot_product(&a, &b);
|
|
let expected = 1.0 * 2.0 + 2.0 * 3.0 + 3.0 * 4.0 + 4.0 * 5.0; // 40.0
|
|
|
|
assert!((result - expected).abs() < 1e-6);
|
|
}
|
|
|
|
#[test]
|
|
fn test_simd_activations() {
|
|
let input = vec![-2.0, -1.0, 0.0, 1.0, 2.0];
|
|
|
|
// Test ReLU
|
|
let relu_output = SimdOptimizedOps::relu_batch(&input);
|
|
assert_eq!(relu_output, vec![0.0, 0.0, 0.0, 1.0, 2.0]);
|
|
|
|
// Test sigmoid
|
|
let sigmoid_output = simd_ops::simd_sigmoid_batch(&input);
|
|
assert_eq!(sigmoid_output.len(), input.len());
|
|
// All sigmoid values should be between 0 and 1
|
|
for &val in &sigmoid_output {
|
|
assert!(val > 0.0 && val < 1.0);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_performance_profiler() {
|
|
let mut profiler = LatencyProfiler::new(); // Use actual LatencyProfiler
|
|
|
|
// Record some measurements
|
|
profiler.record_inference(50);
|
|
profiler.record_inference(75);
|
|
profiler.record_inference(120); // Violation
|
|
profiler.record_inference(90);
|
|
|
|
let stats = profiler.get_stats();
|
|
assert_eq!(stats.total_inferences, 4);
|
|
assert_eq!(stats.violation_rate, 0.25); // 1 out of 4 violated
|
|
assert_eq!(stats.min_latency_us, 50);
|
|
assert_eq!(stats.max_latency_us, 120);
|
|
}
|
|
|
|
#[test]
|
|
fn test_benchmark_simd_performance() -> Result<(), MLError> {
|
|
// Benchmark should complete without errors
|
|
let avg_time = PerformanceBenchmark::benchmark_simd_dot_product(1024, 100)?;
|
|
|
|
// Should be very fast (sub-microsecond for 1024-element dot product)
|
|
assert!(avg_time < 10.0); // Less than 10 microseconds average
|
|
Ok(())
|
|
}
|
|
}
|