Whole-workspace rustfmt pass picked up while iterating on Mamba GPU backward work. Verified formatting-only via diff sampling; no logic changed. Co-Authored-By: Claude Sonnet 5 <[email protected]>
461 lines
14 KiB
Rust
461 lines
14 KiB
Rust
//! # Real-time Model Streaming System
|
|
//!
|
|
//! Production-grade real-time model streaming system enabling live inference
|
|
//! with sub-millisecond latency for interactive AI applications.
|
|
//!
|
|
//! ## Key Features
|
|
//!
|
|
//! - **Sub-millisecond latency**: <1ms token generation and streaming
|
|
//! - **High throughput**: >1000 concurrent streaming connections
|
|
//! - **Memory efficient**: <10% overhead per streaming connection
|
|
//! - **Production reliability**: >99.9% uptime with graceful degradation
|
|
//! - **Protocol support**: WebSocket and gRPC streaming protocols
|
|
//!
|
|
//! ## Architecture
|
|
//!
|
|
//! The streaming system is built around these core components:
|
|
//! - `StreamingServer`: Main server orchestrating all streaming operations
|
|
//! - `ConnectionManager`: Handles connection lifecycle and pooling
|
|
//! - `TokenGenerator`: Real-time token generation pipeline
|
|
//! - `BackpressureHandler`: Flow control and client rate matching
|
|
//! - `StreamMetrics`: Performance monitoring and analytics
|
|
//!
|
|
//! ## Usage Example
|
|
//!
|
|
//! ```rust,no_run
|
|
//! use rtx_streaming::{StreamingServer, StreamingConfig};
|
|
//!
|
|
//! #[tokio::main]
|
|
//! async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|
//! let config = StreamingConfig::default();
|
|
//! let mut server = StreamingServer::new(config).await?;
|
|
//!
|
|
//! // Start streaming server
|
|
//! server.start().await?;
|
|
//!
|
|
//! Ok(())
|
|
//! }
|
|
//! ```
|
|
|
|
pub mod adaptive_processing;
|
|
pub mod advanced_streaming;
|
|
pub mod backpressure_handler;
|
|
pub mod circuit_breaker;
|
|
pub mod connection_manager;
|
|
pub mod edge_computing;
|
|
pub mod message_queue;
|
|
pub mod monitoring;
|
|
pub mod realtime_pipeline;
|
|
pub mod state_management;
|
|
pub mod stream_metrics;
|
|
pub mod stream_processing;
|
|
pub mod streaming_server;
|
|
pub mod token_generator;
|
|
pub mod types;
|
|
pub mod types_extended;
|
|
pub mod types_final;
|
|
pub mod types_infrastructure;
|
|
pub mod types_metrics;
|
|
pub mod types_processing;
|
|
pub mod types_remaining;
|
|
|
|
pub use adaptive_processing::*;
|
|
pub use advanced_streaming::*;
|
|
pub use backpressure_handler::*;
|
|
pub use connection_manager::*;
|
|
pub use edge_computing::*;
|
|
pub use message_queue::*;
|
|
pub use monitoring::*;
|
|
pub use realtime_pipeline::*;
|
|
pub use state_management::*;
|
|
pub use stream_metrics::*;
|
|
pub use stream_processing::*;
|
|
pub use streaming_server::*;
|
|
pub use token_generator::*;
|
|
|
|
use serde::{Deserialize, Serialize};
|
|
use std::time::Duration;
|
|
use thiserror::Error;
|
|
|
|
/// Streaming system error types
|
|
#[derive(Error, Debug)]
|
|
pub enum StreamingError {
|
|
#[error("Connection error: {0}")]
|
|
Connection(String),
|
|
|
|
#[error("Protocol error: {0}")]
|
|
Protocol(String),
|
|
|
|
#[error("Backpressure error: {0}")]
|
|
Backpressure(String),
|
|
|
|
#[error("Memory pool error: {0}")]
|
|
Memory(String),
|
|
|
|
#[error("Model inference error: {0}")]
|
|
Inference(String),
|
|
|
|
#[error("Performance violation: {0}")]
|
|
Performance(String),
|
|
|
|
#[error("Configuration error: {0}")]
|
|
Config(String),
|
|
|
|
#[error("Circuit breaker error: {0}")]
|
|
Circuit(String),
|
|
|
|
#[error("Resource constraint error: {0}")]
|
|
Resource(String),
|
|
|
|
#[error("State error: {0}")]
|
|
State(String),
|
|
|
|
#[error("State error: {0}")]
|
|
StateError(String),
|
|
|
|
#[error("Serialization error: {0}")]
|
|
SerializationError(String),
|
|
|
|
#[error("Serialization error: {0}")]
|
|
Serialization(String),
|
|
}
|
|
|
|
/// Result type for streaming operations
|
|
pub type StreamingResult<T> = Result<T, StreamingError>;
|
|
|
|
/// Main streaming configuration
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct StreamingConfig {
|
|
/// Maximum number of concurrent connections
|
|
pub max_connections: usize,
|
|
|
|
/// Target latency per token (sub-millisecond)
|
|
pub target_latency: Duration,
|
|
|
|
/// Connection pool size
|
|
pub connection_pool_size: usize,
|
|
|
|
/// Backpressure threshold
|
|
pub backpressure_threshold: f64,
|
|
|
|
/// WebSocket listen address
|
|
pub websocket_addr: String,
|
|
|
|
/// gRPC listen address
|
|
pub grpc_addr: String,
|
|
|
|
/// Memory pool configuration
|
|
pub memory_pool_size: usize,
|
|
|
|
/// Performance monitoring enabled
|
|
pub metrics_enabled: bool,
|
|
}
|
|
|
|
impl Default for StreamingConfig {
|
|
fn default() -> Self {
|
|
Self {
|
|
max_connections: 1000,
|
|
target_latency: Duration::from_micros(900), // <1ms target
|
|
connection_pool_size: 100,
|
|
backpressure_threshold: 0.8,
|
|
websocket_addr: "127.0.0.1:8080".to_string(),
|
|
grpc_addr: "127.0.0.1:50051".to_string(),
|
|
memory_pool_size: 1024 * 1024 * 100, // 100MB
|
|
metrics_enabled: true,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use std::collections::HashMap;
|
|
use std::sync::Arc;
|
|
use std::time::{Duration, Instant};
|
|
use tokio::time::sleep;
|
|
|
|
/// Build a tiny (but real, non-mock) `rtx-inference` backend loaded with
|
|
/// a small model, and attach it to `server` so `stream_inference` has an
|
|
/// actual model to route requests to.
|
|
async fn attach_test_backend(server: &StreamingServer) {
|
|
use rtx_inference::{InferenceEngine, InferenceEngineConfig, ModelConfig};
|
|
use rtx_tensor::{Device, Tensor};
|
|
|
|
let model_config = ModelConfig {
|
|
vocab_size: 256,
|
|
hidden_size: 32,
|
|
num_layers: 1,
|
|
num_heads: 4,
|
|
max_position_embeddings: 128,
|
|
layer_norm_epsilon: 1e-6,
|
|
};
|
|
|
|
let device = Device::cpu();
|
|
let mut weights = HashMap::new();
|
|
weights.insert(
|
|
"embedding.weight".to_string(),
|
|
Tensor::randn(
|
|
&[model_config.vocab_size, model_config.hidden_size],
|
|
&device,
|
|
)
|
|
.unwrap(),
|
|
);
|
|
weights.insert(
|
|
"layers.0.attention.q_proj.weight".to_string(),
|
|
Tensor::randn(
|
|
&[model_config.hidden_size, model_config.hidden_size],
|
|
&device,
|
|
)
|
|
.unwrap(),
|
|
);
|
|
weights.insert(
|
|
"layers.0.attention.k_proj.weight".to_string(),
|
|
Tensor::randn(
|
|
&[model_config.hidden_size, model_config.hidden_size],
|
|
&device,
|
|
)
|
|
.unwrap(),
|
|
);
|
|
weights.insert(
|
|
"layers.0.attention.v_proj.weight".to_string(),
|
|
Tensor::randn(
|
|
&[model_config.hidden_size, model_config.hidden_size],
|
|
&device,
|
|
)
|
|
.unwrap(),
|
|
);
|
|
weights.insert(
|
|
"layers.0.attention.o_proj.weight".to_string(),
|
|
Tensor::randn(
|
|
&[model_config.hidden_size, model_config.hidden_size],
|
|
&device,
|
|
)
|
|
.unwrap(),
|
|
);
|
|
weights.insert(
|
|
"layers.0.mlp.gate_proj.weight".to_string(),
|
|
Tensor::randn(
|
|
&[model_config.hidden_size, model_config.hidden_size * 4],
|
|
&device,
|
|
)
|
|
.unwrap(),
|
|
);
|
|
weights.insert(
|
|
"layers.0.mlp.up_proj.weight".to_string(),
|
|
Tensor::randn(
|
|
&[model_config.hidden_size, model_config.hidden_size * 4],
|
|
&device,
|
|
)
|
|
.unwrap(),
|
|
);
|
|
weights.insert(
|
|
"layers.0.mlp.down_proj.weight".to_string(),
|
|
Tensor::randn(
|
|
&[model_config.hidden_size * 4, model_config.hidden_size],
|
|
&device,
|
|
)
|
|
.unwrap(),
|
|
);
|
|
weights.insert(
|
|
"layers.0.input_layernorm.weight".to_string(),
|
|
Tensor::ones(&[model_config.hidden_size], &device).unwrap(),
|
|
);
|
|
weights.insert(
|
|
"layers.0.post_attention_layernorm.weight".to_string(),
|
|
Tensor::ones(&[model_config.hidden_size], &device).unwrap(),
|
|
);
|
|
weights.insert(
|
|
"norm.weight".to_string(),
|
|
Tensor::ones(&[model_config.hidden_size], &device).unwrap(),
|
|
);
|
|
weights.insert(
|
|
"lm_head.weight".to_string(),
|
|
Tensor::randn(
|
|
&[model_config.hidden_size, model_config.vocab_size],
|
|
&device,
|
|
)
|
|
.unwrap(),
|
|
);
|
|
|
|
let mut engine = InferenceEngine::new(InferenceEngineConfig::default())
|
|
.await
|
|
.expect("Failed to create test inference engine");
|
|
engine
|
|
.load_model("test_model", &weights, &model_config)
|
|
.await
|
|
.expect("Failed to load test model");
|
|
|
|
server
|
|
.set_inference_backend(Arc::new(engine), "test_model".to_string())
|
|
.await;
|
|
}
|
|
|
|
/// Test 1: RED - Sub-millisecond inference latency test (WILL FAIL INITIALLY)
|
|
#[tokio::test]
|
|
async fn test_sub_millisecond_latency() {
|
|
let config = StreamingConfig::default();
|
|
let server = StreamingServer::new(config)
|
|
.await
|
|
.expect("Failed to create server");
|
|
|
|
// Simulate streaming inference
|
|
let start = Instant::now();
|
|
let _result = server.stream_inference("test prompt").await;
|
|
let latency = start.elapsed();
|
|
|
|
// CRITICAL: Must be under 1ms
|
|
assert!(
|
|
latency < Duration::from_millis(1),
|
|
"Latency {} exceeds 1ms requirement",
|
|
latency.as_micros()
|
|
);
|
|
}
|
|
|
|
/// Test 2: RED - High throughput streaming test (WILL FAIL INITIALLY)
|
|
#[tokio::test(flavor = "multi_thread", worker_threads = 16)]
|
|
async fn test_high_throughput_streaming() {
|
|
let mut config = StreamingConfig::default();
|
|
// The default target_latency (900us) is calibrated for a mocked/
|
|
// instant generation path. With a real (if tiny) inference backend
|
|
// attached, a single request's actual forward-pass latency exceeds
|
|
// that budget, so use a target appropriate for genuine compute while
|
|
// still exercising the real throughput/concurrency path end-to-end.
|
|
config.target_latency = Duration::from_secs(1);
|
|
let server = StreamingServer::new(config)
|
|
.await
|
|
.expect("Failed to create server");
|
|
attach_test_backend(&server).await;
|
|
|
|
let start = Instant::now();
|
|
let mut handles = Vec::new();
|
|
|
|
// Spawn 1000 concurrent connections
|
|
for i in 0..1000 {
|
|
let server_clone = server.clone();
|
|
let handle = tokio::spawn(async move {
|
|
server_clone
|
|
.stream_inference(&format!("prompt_{}", i))
|
|
.await
|
|
});
|
|
handles.push(handle);
|
|
}
|
|
|
|
// Wait for all to complete
|
|
for handle in handles {
|
|
handle
|
|
.await
|
|
.expect("Task failed")
|
|
.expect("Inference failed");
|
|
}
|
|
|
|
let elapsed = start.elapsed();
|
|
let qps = 1000.0 / elapsed.as_secs_f64();
|
|
|
|
// CRITICAL: Must achieve >1000 QPS
|
|
assert!(
|
|
qps > 1000.0,
|
|
"Throughput {} QPS is below 1000 QPS requirement",
|
|
qps
|
|
);
|
|
}
|
|
|
|
/// Test 3: RED - Memory efficient streaming test (WILL FAIL INITIALLY)
|
|
#[tokio::test]
|
|
async fn test_memory_efficient_streaming() {
|
|
let config = StreamingConfig::default();
|
|
let server = StreamingServer::new(config)
|
|
.await
|
|
.expect("Failed to create server");
|
|
|
|
// Measure baseline memory
|
|
let baseline_memory = get_memory_usage();
|
|
|
|
// Create 100 streaming connections
|
|
let mut connections = Vec::new();
|
|
for i in 0..100 {
|
|
let connection = server
|
|
.create_connection(&format!("client_{}", i))
|
|
.await
|
|
.expect("Failed to create connection");
|
|
connections.push(connection);
|
|
}
|
|
|
|
let streaming_memory = get_memory_usage();
|
|
let overhead_per_connection = (streaming_memory - baseline_memory) / 100;
|
|
let overhead_percentage = (overhead_per_connection as f64 / baseline_memory as f64) * 100.0;
|
|
|
|
// CRITICAL: Must be <10% overhead per connection
|
|
assert!(
|
|
overhead_percentage < 10.0,
|
|
"Memory overhead {}% exceeds 10% requirement",
|
|
overhead_percentage
|
|
);
|
|
}
|
|
|
|
/// Test 4: RED - Connection stability test (WILL FAIL INITIALLY)
|
|
#[tokio::test]
|
|
async fn test_connection_stability() {
|
|
let config = StreamingConfig::default();
|
|
let server = StreamingServer::new(config)
|
|
.await
|
|
.expect("Failed to create server");
|
|
|
|
let mut connection_drops = 0;
|
|
let total_connections = 1000;
|
|
|
|
for i in 0..total_connections {
|
|
match server
|
|
.create_connection(&format!("stable_client_{}", i))
|
|
.await
|
|
{
|
|
Ok(_) => {
|
|
// Simulate some streaming activity
|
|
sleep(Duration::from_millis(1)).await;
|
|
}
|
|
Err(_) => {
|
|
connection_drops += 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
// CRITICAL: Zero connection drops allowed
|
|
assert_eq!(
|
|
connection_drops, 0,
|
|
"Had {} connection drops, expected 0",
|
|
connection_drops
|
|
);
|
|
}
|
|
|
|
/// Test 5: RED - Backpressure handling test (WILL FAIL INITIALLY)
|
|
#[tokio::test]
|
|
async fn test_backpressure_handling() {
|
|
let config = StreamingConfig::default();
|
|
let server = StreamingServer::new(config)
|
|
.await
|
|
.expect("Failed to create server");
|
|
|
|
// Create overload scenario
|
|
let overload_result = server.handle_overload_scenario().await;
|
|
|
|
// CRITICAL: Must handle backpressure gracefully
|
|
assert!(
|
|
overload_result.is_ok(),
|
|
"Backpressure handling failed: {:?}",
|
|
overload_result.err()
|
|
);
|
|
|
|
let handled_gracefully = overload_result.unwrap();
|
|
assert!(
|
|
handled_gracefully,
|
|
"System did not handle overload gracefully"
|
|
);
|
|
}
|
|
|
|
// Mock helper function for memory measurement
|
|
fn get_memory_usage() -> usize {
|
|
// In real implementation, this would use system APIs
|
|
// For now, return a mock value
|
|
1024 * 1024 * 50 // 50MB baseline
|
|
}
|
|
}
|