#![allow(unused_imports)]
use std::collections::{HashMap, HashSet};
use rlx_ir::RegionPrologue;
use rlx_ir::op::{
Activation, AdaNormKind, BinaryOp, ChainOperand, ChainStep, CmpOp, MaskKind, ReduceOp,
RopeStyle, ScaleMode, SteKind, TransformStep,
};
use rlx_ir::shape::{Dim, DimBinding, Shape};
use rlx_ir::{DType, Graph, NodeId, Op};
use crate::array::{Array, MlxError, async_eval, eval};
use crate::ffi::{MlxMask, MlxReduce, MlxUnary};
use crate::ops;
use super::helpers::*;
use super::*;
pub fn expand_leaf_env(
graph: &Graph,
mut env: HashMap<NodeId, Array>,
) -> Result<HashMap<NodeId, Array>, MlxError> {
let mut canon_input: HashMap<String, NodeId> = HashMap::new();
let mut canon_param: HashMap<String, NodeId> = HashMap::new();
for (id, key) in compile_leaf_order(graph) {
match key {
LeafKey::Input(name) => {
canon_input.insert(name, id);
}
LeafKey::Param(name) => {
canon_param.insert(name, id);
}
LeafKey::Constant => {}
}
}
for node in graph.nodes() {
if env.contains_key(&node.id) {
continue;
}
match &node.op {
Op::Input { name } => {
let canon = *canon_input.get(name).ok_or_else(|| {
MlxError(format!("expand_leaf_env: missing canonical input '{name}'"))
})?;
let arr = env.get(&canon).ok_or_else(|| {
MlxError(format!("expand_leaf_env: canonical input '{name}' unbound"))
})?;
env.insert(node.id, arr.clone_handle()?);
}
Op::Param { name } => {
let canon = *canon_param.get(name).ok_or_else(|| {
MlxError(format!("expand_leaf_env: missing canonical param '{name}'"))
})?;
let arr = env.get(&canon).ok_or_else(|| {
MlxError(format!("expand_leaf_env: canonical param '{name}' unbound"))
})?;
env.insert(node.id, arr.clone_handle()?);
}
Op::Constant { .. } => {
return Err(MlxError(format!(
"expand_leaf_env: constant leaf {:?} not bound",
node.id
)));
}
_ => {}
}
}
Ok(env)
}
pub(crate) fn broadcast_leaf_data(
name: &str,
data: &[f32],
shape: &[usize],
) -> Result<Vec<f32>, MlxError> {
let product: usize = shape.iter().product();
if data.len() == product {
return Ok(data.to_vec());
}
if data.len() == 1 && product > 1 {
return Ok(vec![data[0]; product]);
}
Err(MlxError(format!(
"leaf '{name}': host len {} != shape {shape:?} product {product}",
data.len()
)))
}
pub fn build_leaf_for(
graph: &Graph,
id: NodeId,
params: &HashMap<String, Vec<f32>>,
inputs: &HashMap<String, Vec<f32>>,
params_typed: &HashMap<String, (Vec<u8>, DType)>,
inputs_typed: &HashMap<String, (Vec<u8>, DType)>,
gpu_inputs: Option<&HashMap<String, Array>>,
) -> Result<Array, MlxError> {
let node = graph.node(id);
let shape: Vec<usize> = node
.shape
.dims()
.iter()
.map(|d| d.unwrap_static())
.collect();
let dtype = node.shape.dtype();
match &node.op {
Op::Input { name } => {
if let Some(map) = gpu_inputs {
if let Some(arr) = map.get(name) {
return arr.clone_handle();
}
}
if let Some((bytes, dt)) = inputs_typed.get(name) {
if *dt != dtype {
return Err(MlxError(format!(
"typed input '{name}' dtype {dt:?} doesn't match graph's {dtype:?}"
)));
}
return Array::from_bytes(bytes, &shape, dtype);
}
let data = inputs
.get(name)
.ok_or_else(|| MlxError(format!("missing input '{name}'")))?;
let data = broadcast_leaf_data(name, data, &shape)?;
Array::from_f32_slice(&data, &shape, dtype)
}
Op::Param { name } => {
if let Some((bytes, dt)) = params_typed.get(name) {
if *dt != dtype {
return Err(MlxError(format!(
"typed param '{name}' dtype {dt:?} doesn't match graph's {dtype:?}"
)));
}
return Array::from_bytes(bytes, &shape, dtype);
}
let data = params
.get(name)
.ok_or_else(|| MlxError(format!("missing param '{name}'")))?;
if dtype == DType::F32
&& data.len() == shape.iter().product::<usize>()
&& std::env::var("RLX_MLX_PARAM_VIEW").as_deref() == Ok("1")
{
return unsafe { Array::from_f32_slice_view(data, &shape) };
}
let data = broadcast_leaf_data(name, data, &shape)?;
Array::from_f32_slice(&data, &shape, dtype)
}
Op::Constant { data } => {
match dtype {
DType::F32 => {
let n = data.len() / 4;
let mut buf = Vec::with_capacity(n);
for i in 0..n {
let bytes = [
data[i * 4],
data[i * 4 + 1],
data[i * 4 + 2],
data[i * 4 + 3],
];
buf.push(f32::from_le_bytes(bytes));
}
Array::from_f32_slice(&buf, &shape, dtype)
}
_ => Array::from_bytes(data, &shape, dtype),
}
}
other => Err(MlxError(format!("build_leaf called on non-leaf {other:?}"))),
}
}
pub fn lower_subgraph(
sub: &Graph,
captures: &[&Array],
parent_params: &HashMap<String, Vec<f32>>,
parent_params_typed: &HashMap<String, (Vec<u8>, DType)>,
rng: rlx_ir::RngOptions,
) -> Result<Vec<Array>, MlxError> {
let mut sub_env: HashMap<NodeId, Array> = HashMap::with_capacity(sub.nodes().len());
let mut canon_param: HashMap<String, NodeId> = HashMap::new();
let mut input_idx = 0;
for node in sub.nodes() {
match &node.op {
Op::Input { name } => {
if input_idx >= captures.len() {
return Err(MlxError(format!(
"sub-graph has more Op::Input nodes than parent supplied \
captures (input #{input_idx} = {name:?})"
)));
}
sub_env.insert(node.id, captures[input_idx].clone_handle()?);
input_idx += 1;
}
Op::Param { name } => {
if let Some(&canon_id) = canon_param.get(name) {
let arr = sub_env.get(&canon_id).ok_or_else(|| {
MlxError(format!("sub-graph canonical param '{name}' missing"))
})?;
sub_env.insert(node.id, arr.clone_handle()?);
continue;
}
let leaf = if let Some((bytes, dt)) = parent_params_typed.get(name) {
let shape: Vec<usize> = node
.shape
.dims()
.iter()
.map(|d| d.unwrap_static())
.collect();
Array::from_bytes(bytes, &shape, *dt)?
} else if let Some(data) = parent_params.get(name) {
let shape: Vec<usize> = node
.shape
.dims()
.iter()
.map(|d| d.unwrap_static())
.collect();
let dtype = node.shape.dtype();
Array::from_f32_slice(data, &shape, dtype)?
} else {
return Err(MlxError(format!(
"sub-graph param '{name}' not found in parent's param maps"
)));
};
canon_param.insert(name.clone(), node.id);
sub_env.insert(node.id, leaf);
}
Op::Constant { data } => {
let shape: Vec<usize> = node
.shape
.dims()
.iter()
.map(|d| d.unwrap_static())
.collect();
let dtype = node.shape.dtype();
let leaf = match dtype {
DType::F32 => {
let n = data.len() / 4;
let mut buf = Vec::with_capacity(n);
for i in 0..n {
let bytes = [
data[i * 4],
data[i * 4 + 1],
data[i * 4 + 2],
data[i * 4 + 3],
];
buf.push(f32::from_le_bytes(bytes));
}
Array::from_f32_slice(&buf, &shape, dtype)?
}
_ => Array::from_bytes(data, &shape, dtype)?,
};
sub_env.insert(node.id, leaf);
}
_ => {} }
}
if input_idx < captures.len() {
}
lower_with_env(sub, sub_env, parent_params, parent_params_typed, rng, false)
}