use std::sync::Arc;
use std::time::Instant;
use crossbeam_channel::{Receiver, Sender, bounded};
use ndarray::Array2;
use super::batch::BatchPostProcessor;
use super::{BatchStreamError, CONCURRENT_WINDOW_QUEUE, PipelineRunner};
use crate::inference::segmentation::SegmentationError;
use crate::pipeline::PipelineError;
struct SegmentationJob<A> {
audio: Arc<A>,
windows: Sender<Array2<f32>>,
done: Sender<Result<usize, SegmentationError>>,
}
struct QueuedFile<S, A> {
file_id: S,
audio: Arc<A>,
windows: Receiver<Array2<f32>>,
done: Receiver<Result<usize, SegmentationError>>,
}
const WORKER: &str = "batch segmentation";
fn worker_panic() -> PipelineError {
PipelineError::WorkerPanic {
worker: WORKER.to_owned(),
}
}
impl PipelineRunner<'_> {
pub(super) fn run_prefetched_stream<E, A, S>(
&mut self,
files: impl IntoIterator<Item = Result<(S, A), E>>,
post: &mut BatchPostProcessor<'_>,
) -> Result<(), BatchStreamError<E>>
where
A: AsRef<[f32]> + Send + Sync,
S: AsRef<str>,
{
let plan = self.concurrent_plan();
let Self {
seg_model,
emb_model,
powerset,
..
} = self;
std::thread::scope(|scope| {
let (jobs, queue) = bounded::<SegmentationJob<A>>(1);
let worker = std::thread::Builder::new()
.name(WORKER.to_owned())
.spawn_scoped(scope, move || {
for job in queue {
let result =
seg_model.run_streaming(job.audio.as_ref().as_ref(), job.windows);
let _ = job.done.send(result);
}
})
.map_err(|source| PipelineError::WorkerSpawn {
worker: WORKER,
source,
})?;
let run = {
let jobs = jobs;
let enqueue = |(file_id, audio): (S, A)| {
let audio = Arc::new(audio);
let (windows_tx, windows) = bounded(CONCURRENT_WINDOW_QUEUE);
let (done_tx, done) = bounded(1);
jobs.send(SegmentationJob {
audio: Arc::clone(&audio),
windows: windows_tx,
done: done_tx,
})
.map_err(|_| worker_panic())?;
Ok::<_, PipelineError>(QueuedFile {
file_id,
audio,
windows,
done,
})
};
(|| {
let mut files = files.into_iter();
let Some(first) = files.next() else {
return Ok(());
};
let mut current = enqueue(first.map_err(BatchStreamError::Input)?)?;
loop {
post.collect_ready()?;
let upcoming = match files.next() {
Some(Ok(file)) => Some(Ok(enqueue(file)?)),
Some(Err(error)) => Some(Err(error)),
None => None,
};
let QueuedFile {
file_id,
audio,
windows,
done,
} = current;
let file_id = file_id.as_ref().to_owned();
let start = Instant::now();
let span = tracing::debug_span!("batch_inference", %file_id);
let artifacts = span.in_scope(|| {
let audio = audio.as_ref().as_ref();
let embedded = plan.embed(powerset, emb_model, audio, windows);
done.recv().map_err(|_| worker_panic())??;
plan.artifacts(embedded?, start.elapsed())
})?;
let inference_ms = start.elapsed().as_millis();
let audio_secs = audio.as_ref().as_ref().len() as f64 / 16_000.0;
drop(audio);
post.submit(artifacts, file_id, audio_secs, start, inference_ms)?;
match upcoming {
Some(Ok(next)) => current = next,
Some(Err(error)) => return Err(BatchStreamError::Input(error)),
None => return Ok(()),
}
}
})()
};
let joined = worker.join().map_err(|_| worker_panic());
run?;
joined?;
Ok(())
})
}
}