use crate::engine::{mlx_dtype_from_onnx, MlxError, NodeDesc, TranslationContext};
use crate::registry::{
is_int_index, is_mlx_float, is_mlx_numeric, is_signed_integer, is_unsigned_integer,
scalar_or_suffix_broadcast, K_ANY_OPSET, NodeView, OpRegistration, OpRegistry,
};
use crate::sys::mlx;
use crate::sys::ort;
fn add_op(ctx: &mut TranslationContext, n: &NodeDesc) -> Result<(), MlxError> {
let a = ctx.resolve(&n.inputs[0])?;
let b = ctx.resolve(&n.inputs[1])?;
let r = ctx.binary(mlx::mlx_add, a, b)?;
ctx.bind(&n.outputs[0], r);
Ok(())
}
fn mul_op(ctx: &mut TranslationContext, n: &NodeDesc) -> Result<(), MlxError> {
let a = ctx.resolve(&n.inputs[0])?;
let b = ctx.resolve(&n.inputs[1])?;
let r = ctx.binary(mlx::mlx_multiply, a, b)?;
ctx.bind(&n.outputs[0], r);
Ok(())
}
fn sub_op(ctx: &mut TranslationContext, n: &NodeDesc) -> Result<(), MlxError> {
let a = ctx.resolve(&n.inputs[0])?;
let b = ctx.resolve(&n.inputs[1])?;
let r = ctx.binary(mlx::mlx_subtract, a, b)?;
ctx.bind(&n.outputs[0], r);
Ok(())
}
fn sigmoid_op(ctx: &mut TranslationContext, n: &NodeDesc) -> Result<(), MlxError> {
let x = ctx.resolve(&n.inputs[0])?;
let r = ctx.unary(mlx::mlx_sigmoid, x)?;
ctx.bind(&n.outputs[0], r);
Ok(())
}
fn softmax_op(ctx: &mut TranslationContext, n: &NodeDesc) -> Result<(), MlxError> {
let x = ctx.resolve(&n.inputs[0])?;
let rank = ctx.ndim(x) as i64;
let axis_attr = n.ints.get("axis").copied().unwrap_or(-1);
let axis = if axis_attr < 0 { axis_attr + rank } else { axis_attr } as i32;
let r = ctx.softmax_axis(x, axis)?;
ctx.bind(&n.outputs[0], r);
Ok(())
}
fn cast_op(ctx: &mut TranslationContext, n: &NodeDesc) -> Result<(), MlxError> {
let x = ctx.resolve(&n.inputs[0])?;
let r = ctx.astype(x, mlx_dtype_from_onnx(n.outputs[0].otype))?;
ctx.bind(&n.outputs[0], r);
Ok(())
}
fn bind_as_out(
ctx: &mut TranslationContext,
n: &NodeDesc,
r: mlx::mlx_array,
) -> Result<(), MlxError> {
let r = ctx.astype(r, mlx_dtype_from_onnx(n.outputs[0].otype))?;
ctx.bind(&n.outputs[0], r);
Ok(())
}
fn fold_variadic(
ctx: &mut TranslationContext,
n: &NodeDesc,
op: unsafe extern "C" fn(
*mut mlx::mlx_array,
mlx::mlx_array,
mlx::mlx_array,
mlx::mlx_stream,
) -> i32,
) -> Result<mlx::mlx_array, MlxError> {
let mut acc = ctx.resolve(&n.inputs[0])?;
for i in 1..n.inputs.len() {
let next = ctx.resolve(&n.inputs[i])?;
acc = ctx.binary(op, acc, next)?;
}
Ok(acc)
}
fn max_op(ctx: &mut TranslationContext, n: &NodeDesc) -> Result<(), MlxError> {
let r = fold_variadic(ctx, n, mlx::mlx_maximum)?;
bind_as_out(ctx, n, r)
}
fn min_op(ctx: &mut TranslationContext, n: &NodeDesc) -> Result<(), MlxError> {
let r = fold_variadic(ctx, n, mlx::mlx_minimum)?;
bind_as_out(ctx, n, r)
}
fn sum_op(ctx: &mut TranslationContext, n: &NodeDesc) -> Result<(), MlxError> {
let r = fold_variadic(ctx, n, mlx::mlx_add)?;
bind_as_out(ctx, n, r)
}
fn mean_op(ctx: &mut TranslationContext, n: &NodeDesc) -> Result<(), MlxError> {
let acc = fold_variadic(ctx, n, mlx::mlx_add)?;
let dt = ctx.dtype_of(acc);
let count = ctx.scalar_f32(n.inputs.len() as f32);
let count = ctx.astype(count, dt)?;
let r = ctx.binary(mlx::mlx_divide, acc, count)?;
bind_as_out(ctx, n, r)
}
macro_rules! binary_bool_handler {
($name:ident, $mlx_op:expr) => {
fn $name(ctx: &mut TranslationContext, n: &NodeDesc) -> Result<(), MlxError> {
let a = ctx.resolve(&n.inputs[0])?;
let b = ctx.resolve(&n.inputs[1])?;
let r = ctx.binary($mlx_op, a, b)?;
ctx.bind(&n.outputs[0], r);
Ok(())
}
};
}
binary_bool_handler!(equal_op, mlx::mlx_equal);
binary_bool_handler!(greater_op, mlx::mlx_greater);
binary_bool_handler!(less_op, mlx::mlx_less);
binary_bool_handler!(greater_equal_op, mlx::mlx_greater_equal);
binary_bool_handler!(less_equal_op, mlx::mlx_less_equal);
binary_bool_handler!(and_op, mlx::mlx_logical_and);
binary_bool_handler!(or_op, mlx::mlx_logical_or);
binary_bool_handler!(xor_op, mlx::mlx_not_equal);
fn not_op(ctx: &mut TranslationContext, n: &NodeDesc) -> Result<(), MlxError> {
let x = ctx.resolve(&n.inputs[0])?;
let r = ctx.unary(mlx::mlx_logical_not, x)?;
ctx.bind(&n.outputs[0], r);
Ok(())
}
fn mod_op(ctx: &mut TranslationContext, n: &NodeDesc) -> Result<(), MlxError> {
let a = ctx.resolve(&n.inputs[0])?;
let b = ctx.resolve(&n.inputs[1])?;
let fmod = n.ints.get("fmod").copied().unwrap_or(0) != 0;
let r = if !fmod {
ctx.binary(mlx::mlx_remainder, a, b)?
} else {
let q = ctx.binary(mlx::mlx_divide, a, b)?;
let fl = ctx.unary(mlx::mlx_floor, q)?;
let cl = ctx.unary(mlx::mlx_ceil, q)?;
let dt = ctx.dtype_of(q);
let zero = ctx.scalar_f32(0.0);
let zero = ctx.astype(zero, dt)?;
let nonneg = ctx.binary(mlx::mlx_greater_equal, q, zero)?;
let trunc = ctx.where_(nonneg, fl, cl)?;
let prod = ctx.binary(mlx::mlx_multiply, trunc, b)?;
ctx.binary(mlx::mlx_subtract, a, prod)?
};
bind_as_out(ctx, n, r)
}
fn bitshift_op(ctx: &mut TranslationContext, n: &NodeDesc) -> Result<(), MlxError> {
let a = ctx.resolve(&n.inputs[0])?;
let b = ctx.resolve(&n.inputs[1])?;
let left = n
.strings
.get("direction")
.map(String::as_str)
.unwrap_or("LEFT")
== "LEFT";
let r = if left {
ctx.binary(mlx::mlx_left_shift, a, b)?
} else {
ctx.binary(mlx::mlx_right_shift, a, b)?
};
bind_as_out(ctx, n, r)
}
fn binary_same_type_claim(
node: &NodeView,
int_ok: fn(ort::ONNXTensorElementDataType) -> bool,
) -> bool {
if node.num_inputs() != 2 || node.num_outputs() != 1 {
return false;
}
let (a, b, out) = match (node.input_info(0), node.input_info(1), node.output_info(0)) {
(Some(a), Some(b), Some(o)) => (a, b, o),
_ => return false,
};
if a.dtype != b.dtype || b.dtype != out.dtype {
return false;
}
if !scalar_or_suffix_broadcast(&a.shape, &b.shape) {
return false;
}
is_mlx_float(a.dtype) || int_ok(a.dtype)
}
fn add_claim(node: &NodeView) -> bool {
binary_same_type_claim(node, is_int_index)
}
fn mul_claim(node: &NodeView) -> bool {
binary_same_type_claim(node, is_int_index)
}
fn sub_claim(node: &NodeView) -> bool {
binary_same_type_claim(node, is_signed_integer)
}
fn float_unary_claim(node: &NodeView) -> bool {
if node.num_inputs() != 1 || node.num_outputs() != 1 {
return false;
}
match (node.input_info(0), node.output_info(0)) {
(Some(i), Some(o)) => i.dtype == o.dtype && is_mlx_float(i.dtype),
_ => false,
}
}
fn sigmoid_claim(node: &NodeView) -> bool {
float_unary_claim(node)
}
fn softmax_claim(node: &NodeView) -> bool {
if node.num_inputs() != 1 || node.num_outputs() < 1 {
return false;
}
let (i, o) = match (node.input_info(0), node.output_info(0)) {
(Some(i), Some(o)) => (i, o),
_ => return false,
};
if !is_mlx_float(i.dtype) || i.dtype != o.dtype {
return false;
}
let rank = i.shape.len() as i64;
if rank == 0 {
return false;
}
let axis = node.int_attr("axis", -1);
let norm = if axis < 0 { axis + rank } else { axis };
if norm < 0 || norm >= rank {
return false;
}
norm == rank - 1 || node.since_version() >= 13
}
fn cast_pair_claimable(
src: ort::ONNXTensorElementDataType,
dst: ort::ONNXTensorElementDataType,
) -> bool {
if is_mlx_float(src) && is_mlx_float(dst) && src != dst {
return true;
}
if is_int_index(src) && is_int_index(dst) && src != dst {
return true;
}
if is_int_index(src) && is_cast_float(dst) {
return true;
}
if is_cast_float(src) && is_int_index(dst) {
return true;
}
false
}
fn is_cast_float(t: ort::ONNXTensorElementDataType) -> bool {
t == ort::ONNXTensorElementDataType_ONNX_TENSOR_ELEMENT_DATA_TYPE_FLOAT
|| t == ort::ONNXTensorElementDataType_ONNX_TENSOR_ELEMENT_DATA_TYPE_FLOAT16
}
fn cast_claim(node: &NodeView) -> bool {
if node.num_inputs() != 1 || node.num_outputs() != 1 {
return false;
}
let (i, o) = match (node.input_info(0), node.output_info(0)) {
(Some(i), Some(o)) => (i, o),
_ => return false,
};
cast_pair_claimable(i.dtype, o.dtype)
}
fn is_bool(t: ort::ONNXTensorElementDataType) -> bool {
t == ort::ONNXTensorElementDataType_ONNX_TENSOR_ELEMENT_DATA_TYPE_BOOL
}
fn variadic_claim(node: &NodeView, allow_int: bool) -> bool {
if node.num_inputs() < 1 || node.num_outputs() != 1 {
return false;
}
let out = match node.output_info(0) {
Some(o) => o,
None => return false,
};
let ok_dtype = is_mlx_float(out.dtype)
|| (allow_int && (is_signed_integer(out.dtype) || is_unsigned_integer(out.dtype)));
if !ok_dtype {
return false;
}
for i in 0..node.num_inputs() {
match node.input_info(i) {
Some(inf)
if inf.dtype == out.dtype
&& scalar_or_suffix_broadcast(&inf.shape, &out.shape) => {}
_ => return false,
}
}
true
}
fn float_variadic_claim(node: &NodeView) -> bool {
variadic_claim(node, false)
}
fn numeric_variadic_claim(node: &NodeView) -> bool {
variadic_claim(node, true)
}
fn comparison_claim(node: &NodeView, allow_bool: bool) -> bool {
if node.num_inputs() != 2 || node.num_outputs() != 1 {
return false;
}
let (a, b, out) = match (node.input_info(0), node.input_info(1), node.output_info(0)) {
(Some(a), Some(b), Some(o)) => (a, b, o),
_ => return false,
};
if a.dtype != b.dtype || !is_bool(out.dtype) {
return false;
}
(is_mlx_numeric(a.dtype) || (allow_bool && is_bool(a.dtype)))
&& scalar_or_suffix_broadcast(&a.shape, &b.shape)
}
fn ordered_comparison_claim(node: &NodeView) -> bool {
comparison_claim(node, false)
}
fn equal_claim(node: &NodeView) -> bool {
comparison_claim(node, true)
}
fn logical_binary_claim(node: &NodeView) -> bool {
if node.num_inputs() != 2 || node.num_outputs() != 1 {
return false;
}
let (a, b, out) = match (node.input_info(0), node.input_info(1), node.output_info(0)) {
(Some(a), Some(b), Some(o)) => (a, b, o),
_ => return false,
};
is_bool(a.dtype) && is_bool(b.dtype) && is_bool(out.dtype)
&& scalar_or_suffix_broadcast(&a.shape, &b.shape)
}
fn not_claim(node: &NodeView) -> bool {
if node.num_inputs() != 1 || node.num_outputs() != 1 {
return false;
}
match (node.input_info(0), node.output_info(0)) {
(Some(i), Some(o)) => is_bool(i.dtype) && is_bool(o.dtype),
_ => false,
}
}
fn mod_claim(node: &NodeView) -> bool {
if node.num_inputs() != 2 || node.num_outputs() != 1 {
return false;
}
let (a, b, out) = match (node.input_info(0), node.input_info(1), node.output_info(0)) {
(Some(a), Some(b), Some(o)) => (a, b, o),
_ => return false,
};
if a.dtype != b.dtype || b.dtype != out.dtype {
return false;
}
if !scalar_or_suffix_broadcast(&a.shape, &b.shape) {
return false;
}
let fmod = node.int_attr("fmod", 0) != 0;
if fmod {
is_mlx_float(a.dtype)
} else {
is_mlx_float(a.dtype) || is_signed_integer(a.dtype) || is_unsigned_integer(a.dtype)
}
}
fn bitshift_claim(node: &NodeView) -> bool {
if node.num_inputs() != 2 || node.num_outputs() != 1 {
return false;
}
let (a, b, out) = match (node.input_info(0), node.input_info(1), node.output_info(0)) {
(Some(a), Some(b), Some(o)) => (a, b, o),
_ => return false,
};
let u64_t = ort::ONNXTensorElementDataType_ONNX_TENSOR_ELEMENT_DATA_TYPE_UINT64;
a.dtype == b.dtype
&& b.dtype == out.dtype
&& is_unsigned_integer(a.dtype)
&& a.dtype != u64_t
&& scalar_or_suffix_broadcast(&a.shape, &b.shape)
}
fn reg(
registry: &mut OpRegistry,
op_type: &'static str,
handler: crate::registry::OpHandler,
claim: crate::registry::ClaimPredicate,
) {
registry.register(OpRegistration {
domain: "",
op_type,
min_opset: K_ANY_OPSET,
max_opset: K_ANY_OPSET,
handler,
claim,
});
}
pub fn register(registry: &mut OpRegistry) {
registry.register(OpRegistration {
domain: "",
op_type: "Add",
min_opset: K_ANY_OPSET,
max_opset: K_ANY_OPSET,
handler: add_op,
claim: add_claim,
});
registry.register(OpRegistration {
domain: "",
op_type: "Mul",
min_opset: K_ANY_OPSET,
max_opset: K_ANY_OPSET,
handler: mul_op,
claim: mul_claim,
});
registry.register(OpRegistration {
domain: "",
op_type: "Sub",
min_opset: K_ANY_OPSET,
max_opset: K_ANY_OPSET,
handler: sub_op,
claim: sub_claim,
});
registry.register(OpRegistration {
domain: "",
op_type: "Sigmoid",
min_opset: K_ANY_OPSET,
max_opset: K_ANY_OPSET,
handler: sigmoid_op,
claim: sigmoid_claim,
});
registry.register(OpRegistration {
domain: "",
op_type: "Softmax",
min_opset: K_ANY_OPSET,
max_opset: K_ANY_OPSET,
handler: softmax_op,
claim: softmax_claim,
});
registry.register(OpRegistration {
domain: "",
op_type: "Cast",
min_opset: K_ANY_OPSET,
max_opset: K_ANY_OPSET,
handler: cast_op,
claim: cast_claim,
});
registry.register(OpRegistration {
domain: "com.microsoft",
op_type: "Sigmoid",
min_opset: K_ANY_OPSET,
max_opset: K_ANY_OPSET,
handler: sigmoid_op,
claim: sigmoid_claim,
});
reg(registry, "Max", max_op, numeric_variadic_claim);
reg(registry, "Min", min_op, numeric_variadic_claim);
reg(registry, "Sum", sum_op, float_variadic_claim);
reg(registry, "Mean", mean_op, float_variadic_claim);
reg(registry, "Equal", equal_op, equal_claim);
reg(registry, "Greater", greater_op, ordered_comparison_claim);
reg(registry, "Less", less_op, ordered_comparison_claim);
reg(registry, "GreaterOrEqual", greater_equal_op, ordered_comparison_claim);
reg(registry, "LessOrEqual", less_equal_op, ordered_comparison_claim);
reg(registry, "And", and_op, logical_binary_claim);
reg(registry, "Or", or_op, logical_binary_claim);
reg(registry, "Xor", xor_op, logical_binary_claim);
reg(registry, "Not", not_op, not_claim);
reg(registry, "Mod", mod_op, mod_claim);
reg(registry, "BitShift", bitshift_op, bitshift_claim);
}