use rlx_ir::op::{Activation, BinaryOp};
use rlx_ir::{DType, Graph, Shape};
use rlx_runtime::{Device, Session};
mod common;
const F: DType = DType::F32;
fn target() -> Device {
match rlx_ir::env::var("RLX_PARITY_DEVICE") {
Some(s) => rlx_runtime::parse_device(&s).unwrap_or(Device::Cuda),
None => Device::Cuda,
}
}
fn seeded(n: usize, seed: u64) -> Vec<f32> {
let mut s = seed.wrapping_mul(0x9E37_79B9_7F4A_7C15).wrapping_add(1);
(0..n)
.map(|_| {
s = s.wrapping_add(0x9E37_79B9_7F4A_7C15);
let mut z = s;
z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
z ^= z >> 31;
((z >> 40) as f32 / (1u32 << 24) as f32) * 0.4 - 0.2
})
.collect()
}
fn elementwise_graph(m: usize, d: usize, layers: usize, host_every: usize) -> Graph {
let mut g = Graph::new("elementwise_hybrid");
let s = Shape::new(&[m, d], F);
let x = g.input("x", s.clone());
let scale = g.input("scale", s.clone());
let mut h = x;
for l in 0..layers {
let a = g.activation(Activation::Silu, h, s.clone());
let sa = g.binary(BinaryOp::Mul, a, scale, s.clone());
h = g.binary(BinaryOp::Add, h, sa, s.clone());
if (l + 1) % host_every == 0 && l + 1 < layers {
h = g.sort(h, 1, false, s.clone());
}
}
g.set_outputs(vec![h]);
g
}
#[test]
fn cuda_elementwise_segmented_capture_matches_cpu() {
let _gpu = common::serialize_gpu();
let dev = target();
if common::skip_unless(dev) {
eprintln!("skip cuda_segmented_capture_elementwise ({dev:?} unavailable)");
return;
}
let (m, d, layers, host_every) = (32, 128, 9, 3);
let x = seeded(m * d, 11);
let scale = seeded(m * d, 22);
let feed: [(&str, &[f32]); 2] = [("x", &x), ("scale", &scale)];
let cpu = Session::new(Device::Cpu)
.compile(elementwise_graph(m, d, layers, host_every))
.run(&feed)
.remove(0);
let mut cuda = Session::new(dev).compile(elementwise_graph(m, d, layers, host_every));
let o1 = cuda.run(&feed).remove(0);
let o2 = cuda.run(&feed).remove(0);
let o3 = cuda.run(&feed).remove(0);
assert_eq!(o1.len(), m * d);
let maxd = |o: &[f32]| {
o.iter()
.zip(&cpu)
.map(|(a, b)| (a - b).abs())
.fold(0.0f32, f32::max)
};
let (d1, d2, d3) = (maxd(&o1), maxd(&o2), maxd(&o3));
eprintln!("[elem] max|CUDA-CPU| warmup={d1:.3e} capture={d2:.3e} replay={d3:.3e}");
assert!(
d1 < 1e-4 && d2 < 1e-4 && d3 < 1e-4,
"elementwise diverged from CPU"
);
assert_eq!(o1, o2, "warm-up != capture (segmented determinism)");
assert_eq!(o2, o3, "capture != replay (segmented determinism)");
}