use std::collections::HashMap;
use rlx_ir::{DType, Graph, NodeId, Op};
use crate::array::{Array, MlxError};
use crate::ops;
use super::helpers::{lookup, mlx_c64_leaf_from_bytes, mlx_c128_leaf_from_bytes};
pub(crate) fn host_eval_op_typed(
graph: &Graph,
node: &rlx_ir::Node,
env: &HashMap<NodeId, Array>,
) -> Result<Array, MlxError> {
let mut g = Graph::new("mlx_host_eval");
let mut ids = Vec::with_capacity(node.inputs.len());
let mut staged: Vec<Vec<u8>> = Vec::with_capacity(node.inputs.len());
for (i, &in_id) in node.inputs.iter().enumerate() {
let sh = graph.node(in_id).shape.clone();
let arr = ops::contiguous(lookup(env, in_id)?)?;
let bytes = arr.to_bytes()?;
staged.push(bytes);
ids.push(g.append_node(
Op::Input {
name: format!("in{i}"),
},
vec![],
sh,
None,
));
}
let out = g.append_node(node.op.clone(), ids.clone(), node.shape.clone(), None);
g.set_outputs(vec![out]);
let plan = rlx_opt::memory::plan_memory_aligned(&g, 64);
let mut arena = rlx_cpu::arena::Arena::from_plan(plan);
for (i, bytes) in staged.iter().enumerate() {
let id = ids[i];
let off = arena.byte_offset(id);
let nbytes = graph_node_nbytes(&g, id);
let buf = arena.raw_buf_mut();
let n = nbytes.min(bytes.len()).min(buf.len().saturating_sub(off));
buf[off..off + n].copy_from_slice(&bytes[..n]);
}
let schedule = rlx_cpu::thunk::compile_thunks(&g, &arena);
rlx_cpu::thunk::execute_thunks(&schedule, arena.raw_buf_mut());
let off = arena.byte_offset(out);
let nbytes = graph_node_nbytes(&g, out);
let out_bytes = arena.raw_buf()[off..off + nbytes].to_vec();
let out_dims: Vec<usize> = node
.shape
.dims()
.iter()
.map(|d| d.unwrap_static())
.collect();
array_from_host_bytes(&out_bytes, &out_dims, node.shape.dtype())
}
fn graph_node_nbytes(g: &Graph, id: NodeId) -> usize {
g.node(id).shape.size_bytes().unwrap_or(0)
}
fn array_from_host_bytes(bytes: &[u8], dims: &[usize], dtype: DType) -> Result<Array, MlxError> {
match dtype {
DType::C64 => mlx_c64_leaf_from_bytes(bytes),
DType::C128 => mlx_c128_leaf_from_bytes(bytes),
DType::F32 | DType::F16 | DType::BF16 => {
let n = bytes.len() / 4;
let mut vals = Vec::with_capacity(n);
for chunk in bytes.chunks_exact(4) {
vals.push(f32::from_le_bytes([chunk[0], chunk[1], chunk[2], chunk[3]]));
}
Array::from_f32_slice(&vals, dims, DType::F32)
}
_ => Array::from_bytes(bytes, dims, dtype),
}
}
pub(crate) fn is_mlx_typed_host_op(op: &Op) -> bool {
match op {
Op::ScaledQuantize { .. } => true,
Op::ScaledMatMul {
lhs_format,
rhs_format,
scale_layout,
..
} => {
!super::helpers::scaled_fp8_mlx_ok(*lhs_format, *scale_layout)
|| !super::helpers::scaled_fp8_mlx_ok(*rhs_format, *scale_layout)
}
Op::ScaledQuantScale {
format,
scale_layout,
}
| Op::ScaledDequantize {
format,
scale_layout,
} => !super::helpers::scaled_fp8_mlx_ok(*format, *scale_layout),
Op::GaussianSplatPrepare { .. }
| Op::GaussianSplatRasterize { .. }
| Op::CustomFn { .. }
| Op::BiMap
| Op::ReEig { .. }
| Op::LogEig { .. }
| Op::SpdBatchNorm { .. }
| Op::SpdKarcherMean { .. }
| Op::ReEigBackward { .. }
| Op::LogEigBackward { .. }
| Op::SpdBatchNormBackwardX { .. }
| Op::SpdBatchNormBackwardG { .. }
| Op::SpdKarcherMeanWeighted { .. }
| Op::SpdLogMap
| Op::SpdExpMap
| Op::SpdParallelTransport
| Op::SpdMatrixFnBatch { .. }
| Op::SpdLogMapBackward
| Op::SpdExpMapBackward
| Op::SpdParallelTransportBackward
| Op::SpdMatrixFnBatchBackward { .. }
| Op::Eigh
| Op::EighBackward
| Op::EighBatch
| Op::EighBatchBackward
| Op::Cholesky
| Op::TriangularSolve { .. }
| Op::Det
| Op::LogDet
| Op::Sort { .. } | Op::Svd { .. } | Op::Qr { .. }
| Op::ArgSort { .. }
| Op::Interpolate3d { .. } => true,
_ => false,
}
}