#![cfg(feature = "cpu")]
use rlx_ir::{DType, Graph, Shape};
use rlx_runtime::{Device, Session};
fn run(device: Device, dims: &[usize], axes: Vec<usize>, x: &[f32]) -> Vec<f32> {
let mut g = Graph::new("reverse");
let inp = g.input("x", Shape::new(dims, DType::F32));
let y = g.reverse(inp, axes);
g.set_outputs(vec![y]);
Session::new(device)
.compile(g)
.run(&[("x", x)])
.pop()
.unwrap()
}
fn reference(dims: &[usize], axes: &[usize], x: &[f32]) -> Vec<f32> {
let rank = dims.len();
let total: usize = dims.iter().product();
let mut strides = vec![1usize; rank];
for i in (0..rank.saturating_sub(1)).rev() {
strides[i] = strides[i + 1] * dims[i + 1];
}
let rev: Vec<bool> = (0..rank).map(|a| axes.contains(&a)).collect();
let mut out = vec![0f32; total];
for o in 0..total {
let mut rem = o;
let mut inf = 0usize;
for ax in 0..rank {
let idx = rem / strides[ax];
rem %= strides[ax];
let ii = if rev[ax] { dims[ax] - 1 - idx } else { idx };
inf += ii * strides[ax];
}
out[o] = x[inf];
}
out
}
#[test]
fn reverse_batch_general_seq_axis() {
let dims = [3usize, 4, 2];
let x: Vec<f32> = (0..3 * 4 * 2).map(|i| i as f32).collect();
let got = run(Device::Cpu, &dims, vec![1], &x);
let want = reference(&dims, &[1], &x);
assert_eq!(got, want, "seq-axis reverse must be batch-general");
let idx = |b: usize, s: usize, f: usize| (b * 4 + s) * 2 + f;
assert_eq!(got[idx(1, 3, 0)], 8.0);
assert_eq!(got[idx(1, 3, 1)], 9.0);
assert_eq!(got[idx(2, 0, 0)], x[idx(2, 3, 0)]);
}
#[test]
fn reverse_multi_axis_and_identity() {
let dims = [2usize, 3, 4];
let x: Vec<f32> = (0..2 * 3 * 4).map(|i| (i * 3 % 7) as f32).collect();
assert_eq!(
run(Device::Cpu, &dims, vec![1, 2], &x),
reference(&dims, &[1, 2], &x)
);
assert_eq!(run(Device::Cpu, &dims, vec![], &x), x);
assert_eq!(
run(Device::Cpu, &dims, vec![0, 1, 2], &x),
reference(&dims, &[0, 1, 2], &x)
);
}
#[test]
#[cfg(all(target_os = "macos", feature = "mlx"))]
fn reverse_mlx_matches_cpu() {
let dims = [3usize, 4, 5];
let x: Vec<f32> = (0..3 * 4 * 5).map(|i| (i % 11) as f32 * 0.5).collect();
for axes in [vec![1], vec![0, 2], vec![2]] {
assert_eq!(
run(Device::Mlx, &dims, axes.clone(), &x),
run(Device::Cpu, &dims, axes.clone(), &x),
"mlx reverse axes={axes:?}"
);
}
}
#[test]
#[cfg(all(target_os = "macos", feature = "metal"))]
fn reverse_metal_matches_cpu() {
let dims = [3usize, 4, 5];
let x: Vec<f32> = (0..3 * 4 * 5).map(|i| (i % 11) as f32 * 0.5).collect();
for axes in [vec![1], vec![0, 2], vec![2]] {
assert_eq!(
run(Device::Metal, &dims, axes.clone(), &x),
run(Device::Cpu, &dims, axes.clone(), &x),
"metal reverse axes={axes:?}"
);
}
}
#[test]
#[cfg(feature = "gpu")]
fn reverse_wgpu_matches_cpu() {
let dims = [3usize, 4, 5];
let x: Vec<f32> = (0..3 * 4 * 5).map(|i| (i % 11) as f32 * 0.5).collect();
for axes in [vec![1], vec![0, 2], vec![2]] {
assert_eq!(
run(Device::Gpu, &dims, axes.clone(), &x),
run(Device::Cpu, &dims, axes.clone(), &x),
"wgpu reverse axes={axes:?}"
);
}
}