use crate::engine::{EngineConfig, InferenceEngine};
use crate::error::InferenceResult;
use scirs2_core::ndarray::Array1;
use std::collections::VecDeque;
use std::time::{Duration, Instant};
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct BatchConfig {
pub max_batch_size: usize,
pub max_wait_ms: u64,
pub min_batch_size: usize,
pub enable_priority: bool,
pub max_seq_len: usize,
}
impl Default for BatchConfig {
fn default() -> Self {
Self {
max_batch_size: 32,
max_wait_ms: 10,
min_batch_size: 1,
enable_priority: false,
max_seq_len: 2048,
}
}
}
impl BatchConfig {
pub fn new() -> Self {
Self::default()
}
pub fn max_batch_size(mut self, size: usize) -> Self {
self.max_batch_size = size;
self
}
pub fn max_wait_ms(mut self, ms: u64) -> Self {
self.max_wait_ms = ms;
self
}
pub fn min_batch_size(mut self, size: usize) -> Self {
self.min_batch_size = size;
self
}
pub fn with_priority(mut self) -> Self {
self.enable_priority = true;
self
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum Priority {
Low = 0,
Normal = 1,
High = 2,
Critical = 3,
}
#[derive(Debug, Clone)]
pub struct BatchRequest {
pub id: u64,
pub input: Array1<f32>,
pub max_steps: usize,
pub priority: Priority,
pub received_at: Instant,
pub current_step: usize,
}
impl BatchRequest {
pub fn new(id: u64, input: Array1<f32>, max_steps: usize) -> Self {
Self {
id,
input,
max_steps,
priority: Priority::Normal,
received_at: Instant::now(),
current_step: 0,
}
}
pub fn with_priority(mut self, priority: Priority) -> Self {
self.priority = priority;
self
}
pub fn is_complete(&self) -> bool {
self.current_step >= self.max_steps
}
pub fn wait_time_ms(&self) -> u64 {
self.received_at.elapsed().as_millis() as u64
}
}
#[derive(Debug, Clone)]
pub struct BatchResponse {
pub request_id: u64,
pub outputs: Vec<Array1<f32>>,
pub steps_completed: usize,
pub is_complete: bool,
pub inference_time_us: u64,
}
pub struct BatchScheduler {
config: BatchConfig,
engine: InferenceEngine,
pending: VecDeque<BatchRequest>,
active: Vec<BatchRequest>,
completed: Vec<BatchResponse>,
next_id: u64,
last_batch_time: Instant,
}
impl BatchScheduler {
pub fn new(config: BatchConfig, engine_config: EngineConfig) -> InferenceResult<Self> {
let engine = InferenceEngine::new(engine_config);
Ok(Self {
config,
engine,
pending: VecDeque::new(),
active: Vec::new(),
completed: Vec::new(),
next_id: 0,
last_batch_time: Instant::now(),
})
}
pub fn submit(&mut self, input: Array1<f32>, max_steps: usize) -> u64 {
let id = self.next_id;
self.next_id += 1;
let request = BatchRequest::new(id, input, max_steps);
self.pending.push_back(request);
id
}
pub fn submit_with_priority(
&mut self,
input: Array1<f32>,
max_steps: usize,
priority: Priority,
) -> u64 {
let id = self.next_id;
self.next_id += 1;
let request = BatchRequest::new(id, input, max_steps).with_priority(priority);
if self.config.enable_priority {
let insert_pos = self
.pending
.iter()
.position(|r| r.priority < priority)
.unwrap_or(self.pending.len());
self.pending.insert(insert_pos, request);
} else {
self.pending.push_back(request);
}
id
}
fn should_form_batch(&self) -> bool {
if self.pending.is_empty() {
return false;
}
if self.pending.len() >= self.config.min_batch_size {
return true;
}
let wait_time = self.last_batch_time.elapsed();
wait_time >= Duration::from_millis(self.config.max_wait_ms)
}
fn form_batch(&mut self) {
let batch_size = self
.config
.max_batch_size
.min(self.pending.len())
.min(self.config.max_batch_size - self.active.len());
for _ in 0..batch_size {
if let Some(request) = self.pending.pop_front() {
self.active.push(request);
}
}
self.last_batch_time = Instant::now();
}
pub fn step(&mut self) -> InferenceResult<Vec<BatchResponse>> {
if self.should_form_batch() {
self.form_batch();
}
if self.active.is_empty() {
return Ok(Vec::new());
}
let start = Instant::now();
let mut responses = Vec::new();
let mut i = 0;
while i < self.active.len() {
let request = &mut self.active[i];
let output = self.engine.step(&request.input)?;
request.current_step += 1;
request.input = output.clone();
if request.is_complete() {
let completed_request = self.active.remove(i);
let inference_time = start.elapsed().as_micros() as u64;
responses.push(BatchResponse {
request_id: completed_request.id,
outputs: vec![output],
steps_completed: completed_request.current_step,
is_complete: true,
inference_time_us: inference_time,
});
} else {
i += 1;
}
}
Ok(responses)
}
pub fn process_all(&mut self) -> InferenceResult<Vec<BatchResponse>> {
let mut all_responses = Vec::new();
while !self.pending.is_empty() || !self.active.is_empty() {
let responses = self.step()?;
all_responses.extend(responses);
}
Ok(all_responses)
}
pub fn stats(&self) -> SchedulerStats {
SchedulerStats {
pending_requests: self.pending.len(),
active_requests: self.active.len(),
completed_requests: self.completed.len(),
total_submitted: self.next_id,
}
}
pub fn reset(&mut self) {
self.pending.clear();
self.active.clear();
self.completed.clear();
self.engine.reset();
}
}
#[derive(Debug, Clone)]
pub struct SchedulerStats {
pub pending_requests: usize,
pub active_requests: usize,
pub completed_requests: usize,
pub total_submitted: u64,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_batch_config() {
let config = BatchConfig::new()
.max_batch_size(16)
.max_wait_ms(5)
.min_batch_size(4)
.with_priority();
assert_eq!(config.max_batch_size, 16);
assert_eq!(config.max_wait_ms, 5);
assert_eq!(config.min_batch_size, 4);
assert!(config.enable_priority);
}
#[test]
fn test_batch_request() {
let input = Array1::from_vec(vec![1.0, 2.0, 3.0]);
let request = BatchRequest::new(1, input, 10);
assert_eq!(request.id, 1);
assert_eq!(request.max_steps, 10);
assert_eq!(request.current_step, 0);
assert!(!request.is_complete());
}
#[test]
fn test_priority_ordering() {
assert!(Priority::Critical > Priority::High);
assert!(Priority::High > Priority::Normal);
assert!(Priority::Normal > Priority::Low);
}
#[test]
fn test_scheduler_creation() {
let batch_config = BatchConfig::new();
let engine_config = EngineConfig::new(3, 3);
let scheduler = BatchScheduler::new(batch_config, engine_config);
assert!(scheduler.is_ok());
}
#[test]
fn test_scheduler_submit() {
let batch_config = BatchConfig::new();
let engine_config = EngineConfig::new(3, 3);
let mut scheduler = BatchScheduler::new(batch_config, engine_config).unwrap();
let input = Array1::from_vec(vec![1.0, 2.0, 3.0]);
let id = scheduler.submit(input, 5);
assert_eq!(id, 0);
assert_eq!(scheduler.stats().pending_requests, 1);
}
#[test]
fn test_scheduler_priority() {
let batch_config = BatchConfig::new().with_priority();
let engine_config = EngineConfig::new(3, 3);
let mut scheduler = BatchScheduler::new(batch_config, engine_config).unwrap();
let input = Array1::from_vec(vec![1.0, 2.0, 3.0]);
let _id1 = scheduler.submit_with_priority(input.clone(), 5, Priority::Low);
let _id2 = scheduler.submit_with_priority(input.clone(), 5, Priority::High);
let _id3 = scheduler.submit_with_priority(input.clone(), 5, Priority::Normal);
assert_eq!(scheduler.pending[0].priority, Priority::High);
assert_eq!(scheduler.stats().pending_requests, 3);
}
#[test]
fn test_scheduler_stats() {
let batch_config = BatchConfig::new();
let engine_config = EngineConfig::new(3, 3);
let mut scheduler = BatchScheduler::new(batch_config, engine_config).unwrap();
let input = Array1::from_vec(vec![1.0, 2.0, 3.0]);
scheduler.submit(input.clone(), 5);
scheduler.submit(input.clone(), 5);
let stats = scheduler.stats();
assert_eq!(stats.pending_requests, 2);
assert_eq!(stats.total_submitted, 2);
}
}