use rlx_ir::{DType, Dim, Graph, GraphExt, NodeId, Shape};
use crate::Tensor;
use crate::handle::GraphHandle;
use crate::scalar::promote_scalar;
#[derive(Debug)]
pub struct GraphScope {
handle: GraphHandle,
next_dyn: u32,
}
impl GraphScope {
pub fn new(name: impl Into<String>) -> Self {
Self {
handle: GraphHandle::new(Graph::new(name)),
next_dyn: 0,
}
}
pub fn graph(&self) -> Graph {
self.handle.with_graph(|g| g.clone())
}
pub fn fresh_symbol(&mut self) -> u32 {
let s = self.next_dyn;
self.next_dyn += 1;
s
}
pub fn input(&mut self, name: impl Into<String>, shape: impl Into<Shape>) -> Tensor {
let id = self.handle.with_graph(|g| g.input(name, shape.into()));
Tensor::new(self.handle.clone(), id)
}
pub fn input_dyn(&mut self, name: impl Into<String>, dims: &[Dim], dtype: DType) -> Tensor {
let shape = Shape::from_dims(dims, dtype);
self.input(name, shape)
}
pub fn param(&mut self, name: impl Into<String>, shape: impl Into<Shape>) -> Tensor {
let id = self.handle.with_graph(|g| g.param(name, shape.into()));
Tensor::new(self.handle.clone(), id)
}
pub fn scalar(&mut self, value: f64) -> Tensor {
let id = self
.handle
.with_graph(|g| promote_scalar(g, value.into(), DType::F32));
Tensor::new(self.handle.clone(), id)
}
pub fn constant(&mut self, value: f64, dtype: DType) -> Tensor {
let id = self.handle.with_graph(|g| g.constant(value, dtype));
Tensor::new(self.handle.clone(), id)
}
pub fn constant_nd(&mut self, data: Vec<f64>, dims: Vec<usize>, dtype: DType) -> Tensor {
let n: usize = dims.iter().product();
assert_eq!(
data.len(),
n,
"constant_nd: {} values for shape {:?} ({n} expected)",
data.len(),
dims
);
let bytes: Vec<u8> = match dtype {
DType::F32 => data
.iter()
.flat_map(|v| (*v as f32).to_le_bytes())
.collect(),
DType::F64 => data.iter().flat_map(|v| v.to_le_bytes()).collect(),
DType::I32 => data
.iter()
.flat_map(|v| (*v as i32).to_le_bytes())
.collect(),
DType::I64 => data
.iter()
.flat_map(|v| (*v as i64).to_le_bytes())
.collect(),
DType::U8 => data.iter().map(|v| *v as u8).collect(),
other => panic!("constant_nd: unsupported dtype {other:?} (use F32/F64/I32/I64/U8)"),
};
let shape = Shape::new(&dims, dtype);
let id = self
.handle
.with_graph(|g| g.add_node(rlx_ir::Op::Constant { data: bytes }, vec![], shape));
Tensor::new(self.handle.clone(), id)
}
pub fn scan_block(
&mut self,
init: &Tensor,
bcasts: &[&Tensor],
length: u32,
body: impl FnOnce(&Tensor, &[Tensor]) -> Tensor,
) -> Tensor {
let init_id = self.handle.adopt(&init.handle, init.id);
let bcast_ids: Vec<NodeId> = bcasts
.iter()
.map(|b| self.handle.adopt(&b.handle, b.id))
.collect();
let mut inner = GraphScope::new("scan_body");
let carry = inner.input("carry", init.shape());
let bcast_ins: Vec<Tensor> = bcasts
.iter()
.enumerate()
.map(|(i, b)| inner.input(format!("bcast_{i}"), b.shape()))
.collect();
let next = body(&carry, &bcast_ins);
inner.set_outputs([next.id]);
let body_graph = inner.finish();
let id = self.handle.with_graph(|g| {
g.scan_with_bcasts_and_xs(init_id, &bcast_ids, &[], body_graph, length)
});
Tensor::new(self.handle.clone(), id)
}
pub fn cat(&mut self, tensors: &[&Tensor], axis: usize) -> Tensor {
let ids: Vec<NodeId> = tensors.iter().map(|t| t.id).collect();
let id = self.handle.with_graph(|g| g.concat_(ids, axis));
Tensor::new(self.handle.clone(), id)
}
pub fn set_outputs(&mut self, outputs: impl IntoIterator<Item = impl Into<NodeId>>) {
let ids: Vec<NodeId> = outputs.into_iter().map(Into::into).collect();
self.handle.with_graph(|g| g.set_outputs(ids));
}
pub fn finish(self) -> Graph {
self.handle.with_graph(|g| g.clone())
}
}
pub fn graph<F>(name: impl Into<String>, build: F) -> Graph
where
F: FnOnce(&mut GraphScope) -> Tensor,
{
let mut scope = GraphScope::new(name);
let out = build(&mut scope);
scope.set_outputs([out]);
scope.finish()
}
pub fn graph_with<F, R>(name: impl Into<String>, build: F) -> (Graph, R)
where
F: FnOnce(&mut GraphScope) -> R,
{
let mut scope = GraphScope::new(name);
let result = build(&mut scope);
(scope.finish(), result)
}