use crate::internal::*;
#[derive(Clone, Debug)]
pub struct UnbatchedRunnable {
inner: Arc<dyn Runnable>,
interface: Arc<Interface>,
}
#[derive(Debug)]
struct Interface {
inputs: Vec<TypedFact>,
outputs: Vec<TypedFact>,
properties: HashMap<String, Arc<Tensor>>,
batch_in: Vec<bool>,
batch_out: Vec<bool>,
}
impl UnbatchedRunnable {
pub fn hide(inner: Box<dyn Runnable>, original: &TypedModel) -> TractResult<Box<dyn Runnable>> {
match Interface::of(&*inner, original)? {
Some(interface) => Ok(Box::new(UnbatchedRunnable {
inner: inner.into(),
interface: Arc::new(interface),
})),
None => Ok(inner),
}
}
pub fn inner(&self) -> &Arc<dyn Runnable> {
&self.inner
}
}
impl Interface {
fn of(inner: &dyn Runnable, original: &TypedModel) -> TractResult<Option<Interface>> {
if original.inputs.len() != inner.input_count()
|| original.outputs.len() != inner.output_count()
{
return Ok(None);
}
let mut batch: Option<TDim> = None;
let mut unbatched = |fact: &TypedFact, before: &TypedFact| -> Option<(TypedFact, bool)> {
let dims = fact.shape.dims();
if dims == before.shape.dims() {
return Some((fact.clone(), false));
}
let (first, rest) = dims.split_first()?;
if !matches!(first, TDim::Sym(_)) || rest != before.shape.dims() {
return None;
}
if batch.get_or_insert_with(|| first.clone()) != first {
return None;
}
let mut fact = fact.clone();
fact.shape = rest.into();
Some((fact, true))
};
let (mut inputs, mut batch_in) = (vec![], vec![]);
for ix in 0..inner.input_count() {
let Some((fact, batched)) = unbatched(inner.input_fact(ix)?, original.input_fact(ix)?)
else {
return Ok(None);
};
inputs.push(fact);
batch_in.push(batched);
}
let (mut outputs, mut batch_out) = (vec![], vec![]);
for ix in 0..inner.output_count() {
let Some((fact, batched)) =
unbatched(inner.output_fact(ix)?, original.output_fact(ix)?)
else {
return Ok(None);
};
outputs.push(fact);
batch_out.push(batched);
}
if batch.is_none() {
return Ok(None);
}
let properties = original.properties.clone();
Ok(Some(Interface { inputs, outputs, properties, batch_in, batch_out }))
}
}
impl Runnable for UnbatchedRunnable {
fn spawn(&self) -> TractResult<Box<dyn State>> {
Ok(Box::new(UnbatchedState { inner: self.inner.spawn()?, runnable: self.clone() }))
}
fn input_fact(&self, ix: usize) -> TractResult<&TypedFact> {
self.interface.inputs.get(ix).with_context(|| format!("No input {ix}"))
}
fn output_fact(&self, ix: usize) -> TractResult<&TypedFact> {
self.interface.outputs.get(ix).with_context(|| format!("No output {ix}"))
}
fn properties(&self) -> &HashMap<String, Arc<Tensor>> {
&self.interface.properties
}
fn typed_plan(&self) -> Option<&Arc<TypedSimplePlan>> {
self.inner.typed_plan()
}
fn typed_model(&self) -> Option<&Arc<TypedModel>> {
self.inner.typed_model()
}
}
#[derive(Clone, Debug)]
struct UnbatchedState {
inner: Box<dyn State>,
runnable: UnbatchedRunnable,
}
fn reshape_batched(
values: TVec<TValue>,
batched: &[bool],
edit: fn(&mut Tensor, usize) -> TractResult<()>,
) -> TractResult<TVec<TValue>> {
values
.into_iter()
.enumerate()
.map(|(ix, value)| {
if !batched.get(ix).copied().unwrap_or(false) {
return Ok(value);
}
let mut tensor = value.into_tensor();
edit(&mut tensor, 0)?;
Ok(tensor.into_tvalue())
})
.collect()
}
impl State for UnbatchedState {
fn run(&mut self, inputs: TVec<TValue>) -> TractResult<TVec<TValue>> {
let interface = &self.runnable.interface;
let inputs = reshape_batched(inputs, &interface.batch_in, Tensor::insert_axis)?;
let outputs = self.inner.run(inputs)?;
reshape_batched(outputs, &interface.batch_out, Tensor::remove_axis)
}
fn resolve_symbol(&mut self, symbol: &Symbol, value: i64) -> TractResult<()> {
self.inner.resolve_symbol(symbol, value)
}
fn seat(&mut self, seating: Seating) -> TractResult<()> {
self.inner.seat(seating)
}
fn reset_lanes(&mut self, lanes: &[LaneId]) -> TractResult<()> {
self.inner.reset_lanes(lanes)
}
fn runnable(&self) -> &dyn Runnable {
&self.runnable
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::ops::math::mul;
fn doubler() -> TractResult<TypedModel> {
let mut model = TypedModel::default();
let input = model.add_source("input", f32::fact([3]))?;
let two = model.add_const("two", tensor1(&[2f32]))?;
let doubled = model.wire_node("doubled", mul(), &[input, two])?;
model.select_output_outlets(&doubled)?;
model.properties.insert("pulse.input_axes".into(), rctensor1(&[0i64]));
Ok(model)
}
#[test]
fn a_batchified_model_keeps_the_contract_it_was_handed_over_with() -> TractResult<()> {
let model = doubler()?;
let batch = model.symbols.sym("B");
let batched = crate::batchify::batchify(&model, &batch, &[])?;
assert_eq!(*batched.properties["pulse.input_axes"], tensor1(&[1i64]));
let inner = DefaultRuntime.prepare(batched)?;
let runnable = UnbatchedRunnable::hide(inner, &model)?;
assert!(runnable.is::<UnbatchedRunnable>());
assert_eq!(runnable.input_fact(0)?.shape.dims(), &[3.to_dim()]);
assert_eq!(runnable.output_fact(0)?.shape.dims(), &[3.to_dim()]);
assert_eq!(*runnable.properties()["pulse.input_axes"], tensor1(&[0i64]));
let output = runnable.run(tvec!(tensor1(&[1f32, 2., 3.]).into_tvalue()))?;
assert_eq!(*output[0], tensor1(&[2f32, 4., 6.]));
Ok(())
}
#[test]
fn a_model_left_as_it_was_is_not_hidden() -> TractResult<()> {
let model = doubler()?;
let inner = DefaultRuntime.prepare(model.clone())?;
assert!(!UnbatchedRunnable::hide(inner, &model)?.is::<UnbatchedRunnable>());
Ok(())
}
}