use crate::backend::{Backend, ExecutableGraph};
use rlx_driver::Device;
use rlx_ir::dynamic::{
bind_graph, infer_bindings_from_f32_inputs, infer_bindings_from_inputs, same_binding,
sync_graph_shapes,
};
use rlx_ir::{DimBinding, Graph, GraphModule};
pub(crate) struct DeferredExecutable {
graph: Graph,
backend: Box<dyn Backend>,
options: crate::CompileOptions,
device: Device,
params: Vec<(String, Vec<f32>)>,
typed_params: Vec<(String, Vec<u8>, rlx_ir::DType)>,
rng: rlx_ir::RngOptions,
current: Option<(DimBinding, Box<dyn ExecutableGraph>)>,
}
impl DeferredExecutable {
pub(crate) fn new(
graph: Graph,
backend: Box<dyn Backend>,
options: crate::CompileOptions,
device: Device,
) -> Self {
Self {
graph,
backend,
options,
device,
params: Vec::new(),
typed_params: Vec::new(),
rng: rlx_ir::RngOptions::default(),
current: None,
}
}
fn ensure(&mut self, binding: DimBinding) {
if let Some((prev, _)) = &self.current
&& same_binding(prev, &binding)
{
return;
}
let mut resolved = bind_graph(&self.graph, &binding);
sync_graph_shapes(&mut resolved);
let module = GraphModule::from_graph(resolved);
let mut inner = self
.backend
.compile_module(module, self.device, &self.options)
.expect("deferred dynamic-shape compile: backend failed on specialized graph");
inner.set_rng(self.rng);
for (name, data) in &self.params {
inner.set_param(name, data);
}
for (name, data, dtype) in &self.typed_params {
inner.set_param_typed(name, data, *dtype);
}
if !self.params.is_empty() || !self.typed_params.is_empty() {
inner.finalize_params();
}
self.current = Some((binding, inner));
}
fn ensure_from_f32(&mut self, inputs: &[(&str, &[f32])]) {
let binding = infer_bindings_from_f32_inputs(&self.graph, inputs)
.expect("deferred dynamic-shape compile: could not infer shape from f32 inputs");
self.ensure(binding);
}
fn ensure_from_typed(&mut self, inputs: &[(&str, &[u8], rlx_ir::DType)]) {
let lens: Vec<(&str, usize)> = inputs
.iter()
.map(|(n, b, dt)| (*n, b.len() / dt.size_bytes().max(1)))
.collect();
let binding = infer_bindings_from_inputs(&self.graph, &lens)
.expect("deferred dynamic-shape compile: could not infer shape from typed inputs");
self.ensure(binding);
}
fn active(&mut self) -> &mut Box<dyn ExecutableGraph> {
&mut self
.current
.as_mut()
.expect("deferred executable: run before a specialization was compiled")
.1
}
}
impl ExecutableGraph for DeferredExecutable {
fn set_param(&mut self, name: &str, data: &[f32]) {
match self.params.iter_mut().find(|(n, _)| n == name) {
Some(slot) => slot.1 = data.to_vec(),
None => self.params.push((name.to_string(), data.to_vec())),
}
if let Some((_, inner)) = &mut self.current {
inner.set_param(name, data);
}
}
fn set_param_typed(&mut self, name: &str, data: &[u8], dtype: rlx_ir::DType) {
match self.typed_params.iter_mut().find(|(n, _, _)| n == name) {
Some(slot) => {
slot.1 = data.to_vec();
slot.2 = dtype;
}
None => self
.typed_params
.push((name.to_string(), data.to_vec(), dtype)),
}
if let Some((_, inner)) = &mut self.current {
inner.set_param_typed(name, data, dtype);
}
}
fn finalize_params(&mut self) {
if let Some((_, inner)) = &mut self.current {
inner.finalize_params();
}
}
fn run(&mut self, inputs: &[(&str, &[f32])]) -> Vec<Vec<f32>> {
self.ensure_from_f32(inputs);
self.active().run(inputs)
}
fn run_read_outputs(
&mut self,
inputs: &[(&str, &[f32])],
read_indices: Option<&[usize]>,
) -> Vec<Vec<f32>> {
self.ensure_from_f32(inputs);
self.active().run_read_outputs(inputs, read_indices)
}
fn run_raw(&mut self, inputs: &[(&str, &[f32])]) -> Vec<(*const f32, usize)> {
self.ensure_from_f32(inputs);
self.active().run_raw(inputs)
}
fn run_typed(
&mut self,
inputs: &[(&str, &[u8], rlx_ir::DType)],
) -> Vec<(Vec<u8>, rlx_ir::DType)> {
self.ensure_from_typed(inputs);
self.active().run_typed(inputs)
}
fn set_rng(&mut self, rng: rlx_ir::RngOptions) {
self.rng = rng;
if let Some((_, inner)) = &mut self.current {
inner.set_rng(rng);
}
}
fn rng(&self) -> rlx_ir::RngOptions {
self.rng
}
fn clone_box(&self) -> Box<dyn ExecutableGraph> {
panic!("DeferredExecutable (dynamic-shape graph) does not support clone_box");
}
}