Files
wifi-densepose/vendor/ruvector/examples/vibecast-7sense/benches/api_benchmark.rs

760 lines
23 KiB
Rust

//! API Benchmark Suite for 7sense
//!
//! Performance targets from ADR-004:
//! - Query latency: <100ms total (end-to-end)
//! - Neighbor search: <50ms p99
//! - Evidence pack generation: <200ms
use criterion::{
black_box, criterion_group, criterion_main, BenchmarkId, Criterion, Throughput,
};
use std::collections::HashMap;
use std::time::Duration;
mod utils;
use utils::*;
// ============================================================================
// Simulated API Types
// ============================================================================
/// Neighbor search request
#[derive(Clone, Debug)]
struct NeighborSearchRequest {
embedding: Vec<f32>,
k: usize,
filter: Option<SearchFilter>,
include_metadata: bool,
}
/// Search filter for neighbor queries
#[derive(Clone, Debug)]
struct SearchFilter {
species: Option<Vec<String>>,
location: Option<BoundingBox>,
time_range: Option<TimeRange>,
min_confidence: Option<f32>,
}
#[derive(Clone, Debug)]
struct BoundingBox {
min_lat: f32,
max_lat: f32,
min_lon: f32,
max_lon: f32,
}
#[derive(Clone, Debug)]
struct TimeRange {
start: i64,
end: i64,
}
/// Neighbor search response
#[derive(Clone, Debug)]
struct NeighborSearchResponse {
results: Vec<SearchResult>,
total_time_ms: u64,
cache_hit: bool,
}
#[derive(Clone, Debug)]
struct SearchResult {
id: String,
distance: f32,
metadata: Option<EmbeddingMetadata>,
}
/// Embedding metadata
#[derive(Clone, Debug)]
struct EmbeddingMetadata {
recording_id: String,
species: Option<String>,
call_type: Option<String>,
location: Option<Location>,
timestamp: i64,
confidence: f32,
audio_url: Option<String>,
}
#[derive(Clone, Debug)]
struct Location {
lat: f32,
lon: f32,
site_name: Option<String>,
}
/// Evidence pack for interpretability
#[derive(Clone, Debug)]
struct EvidencePack {
query_embedding: Vec<f32>,
neighbors: Vec<NeighborEvidence>,
cluster_info: ClusterInfo,
spectrogram_url: Option<String>,
attention_map: Option<Vec<Vec<f32>>>,
confidence_breakdown: ConfidenceBreakdown,
}
#[derive(Clone, Debug)]
struct NeighborEvidence {
result: SearchResult,
similarity_score: f32,
contributing_features: Vec<FeatureContribution>,
}
#[derive(Clone, Debug)]
struct FeatureContribution {
feature_name: String,
contribution: f32,
}
#[derive(Clone, Debug)]
struct ClusterInfo {
cluster_id: i32,
cluster_size: usize,
centroid_distance: f32,
typical_species: Vec<String>,
}
#[derive(Clone, Debug)]
struct ConfidenceBreakdown {
neighbor_agreement: f32,
cluster_membership: f32,
embedding_quality: f32,
overall: f32,
}
// ============================================================================
// Simulated API Service
// ============================================================================
/// Simulated API service for benchmarking
struct ApiService {
index: SimpleHnswIndex,
metadata_store: HashMap<usize, EmbeddingMetadata>,
cluster_centroids: Vec<Vec<f32>>,
cluster_assignments: Vec<i32>,
}
impl ApiService {
fn new(index: SimpleHnswIndex, num_clusters: usize) -> Self {
let size = index.len();
// Generate fake metadata
let mut metadata_store = HashMap::new();
let species = ["Robin", "Sparrow", "Blackbird", "Thrush", "Finch"];
let call_types = ["song", "call", "alarm", "contact"];
for i in 0..size {
metadata_store.insert(
i,
EmbeddingMetadata {
recording_id: format!("rec_{}", i),
species: Some(species[i % species.len()].to_string()),
call_type: Some(call_types[i % call_types.len()].to_string()),
location: Some(Location {
lat: 51.5 + (i as f32 * 0.001),
lon: -0.1 + (i as f32 * 0.001),
site_name: Some(format!("Site {}", i % 10)),
}),
timestamp: 1700000000 + (i as i64 * 300),
confidence: 0.7 + (i as f32 % 30) / 100.0,
audio_url: Some(format!("https://audio.example.com/{}.wav", i)),
},
);
}
// Generate cluster centroids and assignments
let cluster_centroids = generate_random_vectors(num_clusters, PERCH_EMBEDDING_DIM);
let cluster_assignments: Vec<i32> = (0..size).map(|i| (i % num_clusters) as i32).collect();
Self {
index,
metadata_store,
cluster_centroids,
cluster_assignments,
}
}
/// Execute neighbor search
fn neighbor_search(&self, request: &NeighborSearchRequest) -> NeighborSearchResponse {
let start = std::time::Instant::now();
// Perform HNSW search
let raw_results = self.index.search(&request.embedding, request.k * 2);
// Apply filters
let filtered_results: Vec<_> = raw_results
.into_iter()
.filter(|(idx, _)| self.apply_filter(*idx, &request.filter))
.take(request.k)
.collect();
// Build response with optional metadata
let results: Vec<SearchResult> = filtered_results
.into_iter()
.map(|(idx, distance)| SearchResult {
id: format!("emb_{}", idx),
distance,
metadata: if request.include_metadata {
self.metadata_store.get(&idx).cloned()
} else {
None
},
})
.collect();
NeighborSearchResponse {
results,
total_time_ms: start.elapsed().as_millis() as u64,
cache_hit: false,
}
}
fn apply_filter(&self, idx: usize, filter: &Option<SearchFilter>) -> bool {
match filter {
None => true,
Some(f) => {
if let Some(metadata) = self.metadata_store.get(&idx) {
// Species filter
if let Some(species_list) = &f.species {
if let Some(species) = &metadata.species {
if !species_list.contains(species) {
return false;
}
} else {
return false;
}
}
// Confidence filter
if let Some(min_conf) = f.min_confidence {
if metadata.confidence < min_conf {
return false;
}
}
// Time range filter
if let Some(time_range) = &f.time_range {
if metadata.timestamp < time_range.start
|| metadata.timestamp > time_range.end
{
return false;
}
}
// Location filter
if let Some(bbox) = &f.location {
if let Some(loc) = &metadata.location {
if loc.lat < bbox.min_lat
|| loc.lat > bbox.max_lat
|| loc.lon < bbox.min_lon
|| loc.lon > bbox.max_lon
{
return false;
}
} else {
return false;
}
}
true
} else {
false
}
}
}
}
/// Generate evidence pack for interpretability
fn generate_evidence_pack(&self, embedding: &[f32], k: usize) -> EvidencePack {
// Get neighbors
let raw_results = self.index.search(embedding, k);
let neighbors: Vec<NeighborEvidence> = raw_results
.iter()
.map(|(idx, distance)| {
let metadata = self.metadata_store.get(idx).cloned();
let similarity = 1.0 / (1.0 + distance);
// Generate feature contributions (mock)
let contributions: Vec<FeatureContribution> = (0..5)
.map(|i| FeatureContribution {
feature_name: format!("feature_{}", i),
contribution: similarity * (1.0 - i as f32 * 0.1),
})
.collect();
NeighborEvidence {
result: SearchResult {
id: format!("emb_{}", idx),
distance: *distance,
metadata,
},
similarity_score: similarity,
contributing_features: contributions,
}
})
.collect();
// Compute cluster info
let cluster_info = self.compute_cluster_info(embedding);
// Compute confidence breakdown
let confidence_breakdown = self.compute_confidence(embedding, &neighbors);
EvidencePack {
query_embedding: embedding.to_vec(),
neighbors,
cluster_info,
spectrogram_url: Some("https://spectrograms.example.com/query.png".to_string()),
attention_map: Some(self.generate_attention_map()),
confidence_breakdown,
}
}
fn compute_cluster_info(&self, embedding: &[f32]) -> ClusterInfo {
// Find nearest cluster
let mut best_cluster = 0;
let mut best_distance = f32::MAX;
for (i, centroid) in self.cluster_centroids.iter().enumerate() {
let dist = l2_distance(embedding, centroid);
if dist < best_distance {
best_distance = dist;
best_cluster = i;
}
}
// Count cluster members
let cluster_size = self
.cluster_assignments
.iter()
.filter(|&&c| c == best_cluster as i32)
.count();
ClusterInfo {
cluster_id: best_cluster as i32,
cluster_size,
centroid_distance: best_distance,
typical_species: vec!["Robin".to_string(), "Sparrow".to_string()],
}
}
fn compute_confidence(&self, _embedding: &[f32], neighbors: &[NeighborEvidence]) -> ConfidenceBreakdown {
// Compute neighbor agreement
let neighbor_agreement = if !neighbors.is_empty() {
let avg_sim: f32 = neighbors.iter().map(|n| n.similarity_score).sum::<f32>()
/ neighbors.len() as f32;
avg_sim
} else {
0.0
};
ConfidenceBreakdown {
neighbor_agreement,
cluster_membership: 0.85,
embedding_quality: 0.92,
overall: (neighbor_agreement + 0.85 + 0.92) / 3.0,
}
}
fn generate_attention_map(&self) -> Vec<Vec<f32>> {
// Generate a small mock attention map
(0..32)
.map(|i| (0..128).map(|j| ((i * j) % 100) as f32 / 100.0).collect())
.collect()
}
}
// ============================================================================
// Neighbor Search Benchmarks
// ============================================================================
/// Benchmark neighbor search endpoint
fn benchmark_neighbor_search_endpoint(c: &mut Criterion) {
let mut group = c.benchmark_group("neighbor_search_endpoint");
group.sample_size(50);
group.measurement_time(Duration::from_secs(15));
// Build test service
let index = setup_test_index(50_000);
let service = ApiService::new(index, 50);
let query = generate_random_vectors(1, PERCH_EMBEDDING_DIM).remove(0);
// Basic search without metadata
group.bench_function("basic_k10", |b| {
let request = NeighborSearchRequest {
embedding: query.clone(),
k: 10,
filter: None,
include_metadata: false,
};
b.iter(|| black_box(service.neighbor_search(&request)));
});
// Search with metadata
group.bench_function("with_metadata_k10", |b| {
let request = NeighborSearchRequest {
embedding: query.clone(),
k: 10,
filter: None,
include_metadata: true,
};
b.iter(|| black_box(service.neighbor_search(&request)));
});
// Search with filters
group.bench_function("filtered_k10", |b| {
let request = NeighborSearchRequest {
embedding: query.clone(),
k: 10,
filter: Some(SearchFilter {
species: Some(vec!["Robin".to_string(), "Sparrow".to_string()]),
location: None,
time_range: None,
min_confidence: Some(0.8),
}),
include_metadata: true,
};
b.iter(|| black_box(service.neighbor_search(&request)));
});
// Different k values
for &k in &[10, 50, 100] {
group.bench_with_input(BenchmarkId::new("k", k), &k, |b, &k| {
let request = NeighborSearchRequest {
embedding: query.clone(),
k,
filter: None,
include_metadata: true,
};
b.iter(|| black_box(service.neighbor_search(&request)));
});
}
group.finish();
}
/// Benchmark search throughput under concurrent load
fn benchmark_search_throughput(c: &mut Criterion) {
let mut group = c.benchmark_group("search_throughput");
group.sample_size(20);
group.measurement_time(Duration::from_secs(20));
let index = setup_test_index(50_000);
let service = ApiService::new(index, 50);
// Batch of queries
let queries = generate_random_vectors(100, PERCH_EMBEDDING_DIM);
let requests: Vec<NeighborSearchRequest> = queries
.into_iter()
.map(|embedding| NeighborSearchRequest {
embedding,
k: 10,
filter: None,
include_metadata: true,
})
.collect();
group.throughput(Throughput::Elements(requests.len() as u64));
group.bench_function("batch_100_queries", |b| {
b.iter(|| {
for request in &requests {
black_box(service.neighbor_search(request));
}
});
});
group.finish();
}
// ============================================================================
// Evidence Pack Benchmarks
// ============================================================================
/// Benchmark evidence pack generation
fn benchmark_evidence_pack_generation(c: &mut Criterion) {
let mut group = c.benchmark_group("evidence_pack_generation");
group.sample_size(30);
group.measurement_time(Duration::from_secs(15));
let index = setup_test_index(50_000);
let service = ApiService::new(index, 50);
let query = generate_random_vectors(1, PERCH_EMBEDDING_DIM).remove(0);
// Basic evidence pack
group.bench_function("basic", |b| {
b.iter(|| black_box(service.generate_evidence_pack(&query, 10)));
});
// Different neighbor counts
for &k in &[5, 10, 20, 50] {
group.bench_with_input(BenchmarkId::new("neighbors", k), &k, |b, &k| {
b.iter(|| black_box(service.generate_evidence_pack(&query, k)));
});
}
group.finish();
}
// ============================================================================
// Filter Performance Benchmarks
// ============================================================================
/// Benchmark filter application performance
fn benchmark_filter_performance(c: &mut Criterion) {
let mut group = c.benchmark_group("filter_performance");
group.sample_size(50);
group.measurement_time(Duration::from_secs(10));
let index = setup_test_index(50_000);
let service = ApiService::new(index, 50);
let query = generate_random_vectors(1, PERCH_EMBEDDING_DIM).remove(0);
// No filter
group.bench_function("no_filter", |b| {
let request = NeighborSearchRequest {
embedding: query.clone(),
k: 100,
filter: None,
include_metadata: false,
};
b.iter(|| black_box(service.neighbor_search(&request)));
});
// Species filter only
group.bench_function("species_filter", |b| {
let request = NeighborSearchRequest {
embedding: query.clone(),
k: 100,
filter: Some(SearchFilter {
species: Some(vec!["Robin".to_string()]),
location: None,
time_range: None,
min_confidence: None,
}),
include_metadata: false,
};
b.iter(|| black_box(service.neighbor_search(&request)));
});
// Confidence filter only
group.bench_function("confidence_filter", |b| {
let request = NeighborSearchRequest {
embedding: query.clone(),
k: 100,
filter: Some(SearchFilter {
species: None,
location: None,
time_range: None,
min_confidence: Some(0.9),
}),
include_metadata: false,
};
b.iter(|| black_box(service.neighbor_search(&request)));
});
// All filters combined
group.bench_function("all_filters", |b| {
let request = NeighborSearchRequest {
embedding: query.clone(),
k: 100,
filter: Some(SearchFilter {
species: Some(vec!["Robin".to_string(), "Sparrow".to_string()]),
location: Some(BoundingBox {
min_lat: 51.0,
max_lat: 52.0,
min_lon: -1.0,
max_lon: 1.0,
}),
time_range: Some(TimeRange {
start: 1700000000,
end: 1710000000,
}),
min_confidence: Some(0.8),
}),
include_metadata: false,
};
b.iter(|| black_box(service.neighbor_search(&request)));
});
group.finish();
}
// ============================================================================
// Latency Analysis
// ============================================================================
/// Analyze end-to-end latency against targets
fn analyze_api_latency() {
use std::time::Instant;
println!("\n=== API Latency Analysis ===\n");
// Build service
let index = setup_test_index(100_000);
let service = ApiService::new(index, 50);
let queries = generate_random_vectors(1000, PERCH_EMBEDDING_DIM);
// Neighbor search latency
let mut search_latencies = Vec::new();
for query in &queries {
let request = NeighborSearchRequest {
embedding: query.clone(),
k: 10,
filter: None,
include_metadata: true,
};
let start = Instant::now();
let _ = service.neighbor_search(&request);
search_latencies.push(start.elapsed());
}
let search_stats = PerformanceStats::from_latencies(search_latencies);
println!("Neighbor Search (k=10, with metadata):");
println!("{}", search_stats.report());
println!(
" p99 target: {}ms ({})",
targets::QUERY_LATENCY_P99_MS,
if search_stats.p99 <= Duration::from_millis(targets::QUERY_LATENCY_P99_MS) {
"PASS"
} else {
"FAIL"
}
);
println!(
" Total target: {}ms ({})",
targets::TOTAL_QUERY_LATENCY_MS,
if search_stats.p99 <= Duration::from_millis(targets::TOTAL_QUERY_LATENCY_MS) {
"PASS"
} else {
"FAIL"
}
);
println!();
// Evidence pack latency
let mut evidence_latencies = Vec::new();
for query in queries.iter().take(100) {
let start = Instant::now();
let _ = service.generate_evidence_pack(query, 10);
evidence_latencies.push(start.elapsed());
}
let evidence_stats = PerformanceStats::from_latencies(evidence_latencies);
println!("Evidence Pack Generation (10 neighbors):");
println!("{}", evidence_stats.report());
println!(
" p99 target: 200ms ({})",
if evidence_stats.p99 <= Duration::from_millis(200) {
"PASS"
} else {
"FAIL"
}
);
}
// ============================================================================
// Criterion Groups
// ============================================================================
criterion_group!(
name = search_benches;
config = Criterion::default().with_output_color(true);
targets = benchmark_neighbor_search_endpoint, benchmark_search_throughput
);
criterion_group!(
name = evidence_benches;
config = Criterion::default().with_output_color(true);
targets = benchmark_evidence_pack_generation
);
criterion_group!(
name = filter_benches;
config = Criterion::default().with_output_color(true);
targets = benchmark_filter_performance
);
criterion_main!(search_benches, evidence_benches, filter_benches);
// ============================================================================
// Tests
// ============================================================================
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_neighbor_search_basic() {
let index = setup_test_index(1000);
let service = ApiService::new(index, 10);
let query = generate_random_vectors(1, PERCH_EMBEDDING_DIM).remove(0);
let request = NeighborSearchRequest {
embedding: query,
k: 10,
filter: None,
include_metadata: true,
};
let response = service.neighbor_search(&request);
assert_eq!(response.results.len(), 10);
assert!(response.results.iter().all(|r| r.metadata.is_some()));
}
#[test]
fn test_neighbor_search_with_filter() {
let index = setup_test_index(1000);
let service = ApiService::new(index, 10);
let query = generate_random_vectors(1, PERCH_EMBEDDING_DIM).remove(0);
let request = NeighborSearchRequest {
embedding: query,
k: 10,
filter: Some(SearchFilter {
species: Some(vec!["Robin".to_string()]),
location: None,
time_range: None,
min_confidence: Some(0.7),
}),
include_metadata: true,
};
let response = service.neighbor_search(&request);
// All results should match filter
for result in &response.results {
if let Some(metadata) = &result.metadata {
assert_eq!(metadata.species, Some("Robin".to_string()));
assert!(metadata.confidence >= 0.7);
}
}
}
#[test]
fn test_evidence_pack_generation() {
let index = setup_test_index(1000);
let service = ApiService::new(index, 10);
let query = generate_random_vectors(1, PERCH_EMBEDDING_DIM).remove(0);
let evidence = service.generate_evidence_pack(&query, 10);
assert_eq!(evidence.neighbors.len(), 10);
assert!(evidence.confidence_breakdown.overall > 0.0);
assert!(evidence.cluster_info.cluster_size > 0);
}
#[test]
#[ignore] // Run with: cargo test --release -- --ignored --nocapture
fn run_api_latency_analysis() {
analyze_api_latency();
}
}