use std::collections::HashMap;
use onnx_runtime_eager::{EagerContext, EagerError, Tensor};
fn assert_close(got: &[f32], want: &[f32], tol: f32) {
assert_eq!(
got.len(),
want.len(),
"length mismatch: {got:?} vs {want:?}"
);
for (g, w) in got.iter().zip(want.iter()) {
assert!((g - w).abs() <= tol, "‖{g} - {w}‖ > {tol}");
}
}
#[test]
fn dispatch_add_f32() {
let ctx = EagerContext::new().unwrap();
let a = Tensor::from_f32(&[2, 2], &[1.0, 2.0, 3.0, 4.0]).unwrap();
let b = Tensor::from_f32(&[2, 2], &[10.0, 20.0, 30.0, 40.0]).unwrap();
let out = ctx
.dispatch("Add", "", &[&a, &b], &HashMap::new(), None)
.unwrap();
assert_eq!(out.len(), 1);
assert_eq!(out[0].shape(), &[2, 2]);
assert_close(&out[0].to_vec_f32(), &[11.0, 22.0, 33.0, 44.0], 1e-6);
}
#[test]
fn dispatch_matmul_f32() {
let ctx = EagerContext::new().unwrap();
let a = Tensor::from_f32(&[2, 2], &[1.0, 2.0, 3.0, 4.0]).unwrap();
let b = Tensor::from_f32(&[2, 2], &[5.0, 6.0, 7.0, 8.0]).unwrap();
let out = ctx
.dispatch("MatMul", "", &[&a, &b], &HashMap::new(), None)
.unwrap();
assert_eq!(out[0].shape(), &[2, 2]);
assert_close(&out[0].to_vec_f32(), &[19.0, 22.0, 43.0, 50.0], 1e-6);
}
#[test]
fn dispatch_relu_f32() {
let ctx = EagerContext::new().unwrap();
let x = Tensor::from_f32(&[2, 2], &[-1.0, 0.0, 2.0, -3.0]).unwrap();
let out = ctx
.dispatch("Relu", "", &[&x], &HashMap::new(), None)
.unwrap();
assert_eq!(out[0].shape(), &[2, 2]);
assert_close(&out[0].to_vec_f32(), &[0.0, 0.0, 2.0, 0.0], 1e-6);
}
#[test]
fn dispatch_custom_domain_gelu() {
let ctx = EagerContext::new().unwrap();
let x = Tensor::from_f32(&[4], &[0.0, 1.0, -1.0, 2.0]).unwrap();
let out = ctx
.dispatch("Gelu", "com.microsoft", &[&x], &HashMap::new(), None)
.unwrap();
assert_eq!(out[0].shape(), &[4]);
assert_close(
&out[0].to_vec_f32(),
&[0.0, 0.841_344_7, -0.158_655_3, 1.954_499_7],
1e-4,
);
}
#[test]
fn dispatch_unknown_op_is_no_kernel() {
let ctx = EagerContext::new().unwrap();
let x = Tensor::from_f32(&[2], &[1.0, 2.0]).unwrap();
let err = ctx
.dispatch("Conv", "", &[&x], &HashMap::new(), None)
.unwrap_err();
match err {
EagerError::NoKernel {
op_type, domain, ..
} => {
assert_eq!(op_type, "Conv");
assert_eq!(domain, "");
}
other => panic!("expected NoKernel, got {other:?}"),
}
}
#[test]
fn kernel_cache_reuses_same_op_and_shape() {
let ctx = EagerContext::new().unwrap();
let a = Tensor::from_f32(&[2, 2], &[1.0, 2.0, 3.0, 4.0]).unwrap();
let b = Tensor::from_f32(&[2, 2], &[5.0, 6.0, 7.0, 8.0]).unwrap();
let before = ctx.cache_stats();
assert_eq!(before.entries, 0);
let _ = ctx
.dispatch("Add", "", &[&a, &b], &HashMap::new(), None)
.unwrap();
let after_first = ctx.cache_stats();
assert_eq!(after_first.misses, 1, "first dispatch compiles a kernel");
assert_eq!(after_first.hits, 0);
assert_eq!(after_first.entries, 1);
let _ = ctx
.dispatch("Add", "", &[&a, &b], &HashMap::new(), None)
.unwrap();
let after_second = ctx.cache_stats();
assert_eq!(
after_second.misses, 1,
"second dispatch reuses the cached kernel"
);
assert_eq!(after_second.hits, 1);
assert_eq!(after_second.entries, 1);
let c = Tensor::from_f32(&[3], &[1.0, 2.0, 3.0]).unwrap();
let d = Tensor::from_f32(&[3], &[4.0, 5.0, 6.0]).unwrap();
let _ = ctx
.dispatch("Add", "", &[&c, &d], &HashMap::new(), None)
.unwrap();
let after_third = ctx.cache_stats();
assert_eq!(after_third.misses, 2);
assert_eq!(after_third.entries, 2);
}
#[test]
fn explicit_opset_is_accepted() {
let ctx = EagerContext::new().unwrap();
let a = Tensor::from_f32(&[2], &[1.0, 2.0]).unwrap();
let b = Tensor::from_f32(&[2], &[3.0, 4.0]).unwrap();
let out = ctx
.dispatch("Add", "", &[&a, &b], &HashMap::new(), Some(17))
.unwrap();
assert_close(&out[0].to_vec_f32(), &[4.0, 6.0], 1e-6);
}