use crate::cuda_transfer::CudaStream;
use crate::{DType, ModelInfo, Result, TensorData, TensorDataMut, TensorView, TensorViewMut};
use anyhow::{Context, ensure};
use ort::{
memory::{AllocationDevice, Allocator, AllocatorType, MemoryInfo, MemoryType},
session::{IoBinding, Session},
value::{DynTensor, DynTensorValueType, TensorElementType},
};
pub(crate) struct TensorSlot {
pub name: String,
pub shape: Vec<usize>,
dtype: DType,
host_tensor: DynTensor,
device_tensor: Option<DynTensor>,
}
pub(crate) struct InferenceBuffers {
pub input_tensors: Vec<TensorSlot>,
pub output_tensors: Vec<TensorSlot>,
io_binding: IoBinding,
rebind_inputs_each_run: bool,
input_bindings_initialized: bool,
pub outputs_valid: bool,
_host_allocator_guard: Allocator,
_device_allocator_guard: Option<Allocator>,
}
pub(crate) fn eligible(info: &ModelInfo) -> bool {
!info.inputs.is_empty()
&& !info.outputs.is_empty()
&& info.inputs.iter().chain(&info.outputs).all(|s| {
s.shape
.as_ref()
.is_some_and(|shape| shape.iter().all(|d| d.is_some_and(|n| n > 0)))
})
}
fn ort_dtype(dtype: DType) -> TensorElementType {
match dtype {
DType::F32 => TensorElementType::Float32,
DType::F64 => TensorElementType::Float64,
DType::F16 => TensorElementType::Float16,
DType::BF16 => TensorElementType::Bfloat16,
DType::I64 => TensorElementType::Int64,
DType::I32 => TensorElementType::Int32,
DType::I16 => TensorElementType::Int16,
DType::I8 => TensorElementType::Int8,
DType::U64 => TensorElementType::Uint64,
DType::U32 => TensorElementType::Uint32,
DType::U16 => TensorElementType::Uint16,
DType::U8 => TensorElementType::Uint8,
DType::Bool => TensorElementType::Bool,
}
}
macro_rules! dispatch_dtype {
($dtype:expr, $callback:ident, $($arg:expr),*) => {
match $dtype {
DType::F32 => $callback!($($arg,)* F32, f32),
DType::F64 => $callback!($($arg,)* F64, f64),
DType::F16 => $callback!($($arg,)* F16, half::f16),
DType::BF16 => $callback!($($arg,)* BF16, half::bf16),
DType::I64 => $callback!($($arg,)* I64, i64),
DType::I32 => $callback!($($arg,)* I32, i32),
DType::I16 => $callback!($($arg,)* I16, i16),
DType::I8 => $callback!($($arg,)* I8, i8),
DType::U64 => $callback!($($arg,)* U64, u64),
DType::U32 => $callback!($($arg,)* U32, u32),
DType::U16 => $callback!($($arg,)* U16, u16),
DType::U8 => $callback!($($arg,)* U8, u8),
DType::Bool => $callback!($($arg,)* Bool, bool),
}
};
}
macro_rules! view_data {
($slot:expr, $variant:ident, $ty:ty) => {
Ok::<TensorData<'_>, anyhow::Error>(TensorData::$variant(
$slot.host_tensor.try_extract_tensor::<$ty>()?.1,
))
};
}
macro_rules! view_data_mut {
($slot:expr, $variant:ident, $ty:ty) => {
Ok::<TensorDataMut<'_>, anyhow::Error>(TensorDataMut::$variant(
$slot.host_tensor.try_extract_tensor_mut::<$ty>()?.1,
))
};
}
impl TensorSlot {
pub fn view(&self) -> Result<TensorView<'_>> {
let data = dispatch_dtype!(self.dtype, view_data, self)?;
Ok(TensorView {
name: &self.name,
shape: &self.shape,
data,
})
}
pub fn view_mut(&mut self) -> Result<TensorViewMut<'_>> {
let data = dispatch_dtype!(self.dtype, view_data_mut, self)?;
Ok(TensorViewMut {
name: &self.name,
shape: &self.shape,
data,
})
}
}
impl InferenceBuffers {
pub fn new(
session: &Session,
info: &ModelInfo,
device_id: Option<i32>,
rebind_inputs_each_run: bool,
) -> Result<Self> {
ensure!(
eligible(info),
"Prepared buffers require fixed, positive tensor shapes"
);
let cuda = device_id.is_some();
let allocator_device_id = device_id.unwrap_or_default();
let host_allocator = if cuda {
Allocator::new(
session,
MemoryInfo::new(
AllocationDevice::CUDA_PINNED,
allocator_device_id,
AllocatorType::Device,
MemoryType::CPUOutput,
)?,
)?
} else {
Allocator::default()
};
let device_allocator = if cuda {
Some(Allocator::new(
session,
MemoryInfo::new(
AllocationDevice::CUDA,
allocator_device_id,
AllocatorType::Device,
MemoryType::Default,
)?,
)?)
} else {
None
};
let mut io_binding = session.create_binding()?;
let mut input_tensors = Vec::new();
let mut output_tensors = Vec::new();
for spec in &info.inputs {
let shape = spec.fixed_shape().expect("validated fixed shape");
let host_tensor =
DynTensor::new(&host_allocator, ort_dtype(spec.dtype), shape.as_slice())?;
let device_tensor = device_allocator
.as_ref()
.map(|allocator| DynTensor::new(allocator, ort_dtype(spec.dtype), shape.as_slice()))
.transpose()?;
input_tensors.push(TensorSlot {
name: spec.name.clone(),
shape,
dtype: spec.dtype,
host_tensor,
device_tensor,
});
}
for spec in &info.outputs {
let shape = spec.fixed_shape().expect("validated fixed shape");
let host_tensor =
DynTensor::new(&host_allocator, ort_dtype(spec.dtype), shape.as_slice())?;
if let Some(allocator) = &device_allocator {
io_binding.bind_output(
&spec.name,
DynTensor::new(allocator, ort_dtype(spec.dtype), shape.as_slice())?,
)?;
} else {
io_binding.bind_output(
&spec.name,
host_tensor
.view()
.try_upgrade()
.map_err(|_| anyhow::anyhow!("Cannot share owned output"))?,
)?;
}
output_tensors.push(TensorSlot {
name: spec.name.clone(),
shape,
dtype: spec.dtype,
host_tensor,
device_tensor: None,
});
}
Ok(Self {
input_tensors,
output_tensors,
io_binding,
rebind_inputs_each_run,
input_bindings_initialized: false,
outputs_valid: false,
_host_allocator_guard: host_allocator,
_device_allocator_guard: device_allocator,
})
}
pub fn run(&mut self, session: &mut Session, stream: Option<&CudaStream>) -> Result<()> {
self.outputs_valid = false;
if let Some(stream) = stream {
stream
.upload(self.input_tensors.iter_mut().map(|input| {
(
&input.host_tensor,
input.device_tensor.as_mut().expect("CUDA input"),
)
}))
.context("Pinned H2D transfers")?;
}
if !self.input_bindings_initialized || self.rebind_inputs_each_run {
for input in &self.input_tensors {
self.io_binding.bind_input(
&input.name,
input.device_tensor.as_ref().unwrap_or(&input.host_tensor),
)?;
}
if let Some(stream) = stream {
stream.synchronize()?;
}
self.input_bindings_initialized = true;
}
let values = session.run_binding(&self.io_binding).context("Inference")?;
if let Some(stream) = stream {
let device_outputs = self
.output_tensors
.iter()
.map(|output| values[output.name.as_str()].downcast_ref::<DynTensorValueType>())
.collect::<ort::Result<Vec<_>>>()?;
stream
.download(
device_outputs
.iter()
.zip(&mut self.output_tensors)
.map(|(value, output)| (&**value, &mut output.host_tensor)),
)
.context("Pinned D2H transfers")?;
}
self.outputs_valid = true;
Ok(())
}
}