use std::sync::atomic::Ordering;
use numr::dtype::DType;
use numr::runtime::Runtime;
use numr::tensor::Tensor;
use boostr::model::audio::voxcpm::model::{
GenerateOptions, GenerateOutcome, GenerateState, PrefillRow, StepOutcome, VoxCpm2Model,
};
use crate::error::{Error, Result};
use crate::voxcpm::engine::batch::{Job, JobVoice, batch_width, row_options};
use crate::voxcpm::engine::types::{EngineInner, VoxCpm2RenderClient};
pub(super) struct Row<R: Runtime> {
pub(super) job: Job<R>,
pub(super) live: bool,
pub(super) emitted: usize,
}
impl<R: Runtime> Row<R> {
pub(super) fn retries(&self, patches: usize) -> bool {
self.live && self.job.retries(patches)
}
}
impl<R> EngineInner<R>
where
R: Runtime<DType = DType> + 'static,
R::Client: VoxCpm2RenderClient<R>,
VoxCpm2Model<R>: Send + Sync,
Tensor<R>: Send + Sync,
{
pub(super) fn run_batch(&self, jobs: Vec<Job<R>>) -> Vec<Job<R>> {
let mut rows = Vec::with_capacity(jobs.len());
for job in jobs {
let known = self.reference(&job.voice).map(|_| ());
match known {
Ok(()) => rows.push(Row {
job,
live: true,
emitted: 0,
}),
Err(err) => job.reply.fail(err),
}
}
if rows.is_empty() {
return Vec::new();
}
match self.generate_batch(&mut rows) {
Ok(retry) => rows
.into_iter()
.zip(retry)
.filter_map(|(row, again)| again.then(|| row.job.retried()))
.collect(),
Err(err) => {
fail_all(&rows, err);
Vec::new()
}
}
}
pub(super) fn reference<'a>(&'a self, voice: &'a JobVoice<R>) -> Result<Option<&'a Tensor<R>>> {
match voice {
JobVoice::ZeroShot => Ok(None),
JobVoice::Reference(feat) => Ok(Some(feat.as_ref())),
JobVoice::Named(voice_id) => {
let voice = self
.voices
.get(voice_id)
.ok_or_else(|| Error::InvalidArgument {
arg: "voice",
reason: format!(
"voice {voice_id:?} passed the request check but is not in the \
engine's voice table; this is an engine bug, report it with \
the voice id"
),
})?;
Ok(Some(&voice.ref_feat))
}
}
}
fn generate_batch(&self, rows: &mut [Row<R>]) -> Result<Vec<bool>> {
let width = batch_width(rows.len());
let real: Vec<_> = rows.iter().map(|row| row.job.row_options()).collect();
let max_len = real.iter().map(|row| row.max_len).max().unwrap_or(1);
let s_max = rows.iter().map(|row| row.job.seq_len).max().unwrap_or(1);
let capacity = s_max + max_len;
let mut options = GenerateOptions::new(max_len, self.options.seed);
if let Some(row) = rows.first() {
row.job.sampler.apply(&mut options);
}
options.min_len = self.options.min_len;
options.rows = row_options(&real, width);
let client = self.client.as_ref();
let mut state = GenerateState::open(&self.model, width, capacity)?;
for (b, row) in rows.iter().enumerate() {
let prefilled = self.model.prefill_batch(
client,
&[PrefillRow {
ref_feat: self.reference(&row.job.voice)?,
text_token_ids: &row.job.text_token_ids,
prompt_feat: row.job.prompt.as_deref(),
}],
capacity,
)?;
state.admit(client, b, &prefilled, options.row(b))?;
}
self.drive(&mut state, &options, rows)?;
Ok(self.deliver(&state, rows))
}
fn drive(
&self,
state: &mut GenerateState<R>,
options: &GenerateOptions,
rows: &mut [Row<R>],
) -> Result<()> {
let chunk = self.options.stream_chunk_patches.max(1);
let client = self.client.as_ref();
let generator = self.model.patch_generator();
while !state.all_finished() {
let outcome = generator.step(client, state, options)?;
for (b, row) in rows.iter_mut().enumerate() {
self.pump_row(state, b, row, chunk);
}
if outcome == StepOutcome::Stopped {
break;
}
}
Ok(())
}
pub(super) fn pump_row(
&self,
state: &mut GenerateState<R>,
b: usize,
row: &mut Row<R>,
chunk: usize,
) {
if !row.live {
return;
}
let Some((_, cancelled)) = row.job.reply.stream() else {
return;
};
if cancelled.load(Ordering::Relaxed) {
retire(state, b);
row.live = false;
return;
}
if state.patch_len[b] - row.emitted < chunk {
return;
}
let client = self.client.as_ref();
match host_samples(self.model.decode_row_from(client, state, b, row.emitted)) {
Ok(samples) => {
row.emitted = state.patch_len[b];
if !row.job.reply.send_chunk(samples) {
retire(state, b);
row.live = false;
}
}
Err(err) => fail_row(state, b, row, err),
}
}
fn deliver(&self, state: &GenerateState<R>, rows: &mut [Row<R>]) -> Vec<bool> {
rows.iter_mut()
.enumerate()
.map(|(b, row)| {
if row.retries(state.patch_len[b]) {
return true;
}
self.deliver_row(state, b, row);
false
})
.collect()
}
pub(super) fn deliver_row(&self, state: &GenerateState<R>, b: usize, row: &mut Row<R>) {
if !row.live {
return;
}
row.live = false;
let client = self.client.as_ref();
let streaming = row.job.reply.stream().is_some();
let decoded = if streaming {
if row.emitted >= state.patch_len[b] {
return;
}
self.model.decode_row_from(client, state, b, row.emitted)
} else {
self.model.decode_row(client, state, b)
};
match host_samples(decoded) {
Ok(samples) if streaming => {
row.emitted = state.patch_len[b];
row.job.reply.send_chunk(samples);
}
Ok(samples) => row.job.reply.send_samples(samples, row.job.attempt),
Err(err) => row.job.reply.fail(err),
}
}
}
pub(super) fn host_samples<R: Runtime<DType = DType>>(
decoded: std::result::Result<Tensor<R>, boostr::error::Error>,
) -> Result<Vec<f32>> {
let decoded = decoded?;
Ok(decoded.contiguous()?.to_vec())
}
pub(super) fn retire<R: Runtime>(state: &mut GenerateState<R>, b: usize) {
if let Some(outcome) = state.outcomes.get_mut(b) {
outcome.get_or_insert(GenerateOutcome::MaxLen);
}
}
fn fail_row<R: Runtime>(state: &mut GenerateState<R>, b: usize, row: &mut Row<R>, err: Error) {
row.job.reply.fail(err);
retire(state, b);
row.live = false;
}
pub(super) fn fail_all<R: Runtime>(rows: &[Row<R>], err: Error) {
let text = err.to_string();
let mut original = Some(err);
for row in rows.iter().filter(|row| row.live) {
match original.take() {
Some(err) => row.job.reply.fail(err),
None => row.job.reply.fail(Error::ModelError {
reason: text.clone(),
}),
}
}
}