#[cfg(target_os = "macos")]
fn main() {
use rlx_metal::device::metal_device;
use rlx_metal::mps_graph::{MpsGraph, mps_graph_supported};
use rlx_metal::mtl::MTLResourceOptions;
if !mps_graph_supported() {
eprintln!("no MPSGraph");
return;
}
let dev = metal_device().expect("metal");
let batch = 4usize;
let seq = 15usize;
let hidden = 768usize;
let num_heads = 12usize;
let head_dim = hidden / num_heads;
let intermediate = 4 * hidden;
let m = batch * seq;
let alloc_buf = |n_floats: usize| -> rlx_metal::mtl::Buffer {
dev.device
.new_buffer((n_floats * 4) as u64, MTLResourceOptions::StorageModeShared)
};
let fill = |buf: &rlx_metal::mtl::Buffer, n: usize, seed: f32| unsafe {
let p = buf.contents() as *mut f32;
for i in 0..n {
*p.add(i) = (seed + i as f32 * 0.01).sin() * 0.05;
}
};
let buf_x = alloc_buf(m * hidden);
let buf_qkv_w = alloc_buf(hidden * 3 * hidden);
let buf_qkv_b = alloc_buf(3 * hidden);
let buf_out_w = alloc_buf(hidden * hidden);
let buf_out_b = alloc_buf(hidden);
let buf_ln1_g = alloc_buf(hidden);
let buf_ln1_b = alloc_buf(hidden);
let buf_fc1_w = alloc_buf(hidden * intermediate);
let buf_fc1_b = alloc_buf(intermediate);
let buf_fc2_w = alloc_buf(intermediate * hidden);
let buf_fc2_b = alloc_buf(hidden);
let buf_ln2_g = alloc_buf(hidden);
let buf_ln2_b = alloc_buf(hidden);
let buf_out = alloc_buf(m * hidden);
fill(&buf_x, m * hidden, 0.1);
fill(&buf_qkv_w, hidden * 3 * hidden, 0.2);
fill(&buf_qkv_b, 3 * hidden, 0.3);
fill(&buf_out_w, hidden * hidden, 0.4);
fill(&buf_out_b, hidden, 0.5);
unsafe {
let g = buf_ln1_g.contents() as *mut f32;
let b = buf_ln1_b.contents() as *mut f32;
let g2 = buf_ln2_g.contents() as *mut f32;
let b2 = buf_ln2_b.contents() as *mut f32;
for i in 0..hidden {
*g.add(i) = 1.0;
*b.add(i) = 0.0;
*g2.add(i) = 1.0;
*b2.add(i) = 0.0;
}
}
fill(&buf_fc1_w, hidden * intermediate, 0.6);
fill(&buf_fc1_b, intermediate, 0.7);
fill(&buf_fc2_w, intermediate * hidden, 0.8);
fill(&buf_fc2_b, hidden, 0.9);
const F32: u32 = 0x10000000 | 32;
let g = MpsGraph::new();
let x = g.placeholder(&[m, hidden], F32, "x");
let qkv_w = g.placeholder(&[hidden, 3 * hidden], F32, "qkv_w");
let qkv_b = g.placeholder(&[3 * hidden], F32, "qkv_b");
let out_w = g.placeholder(&[hidden, hidden], F32, "out_w");
let out_b = g.placeholder(&[hidden], F32, "out_b");
let ln1_g = g.placeholder(&[hidden], F32, "ln1_g");
let ln1_b = g.placeholder(&[hidden], F32, "ln1_b");
let fc1_w = g.placeholder(&[hidden, intermediate], F32, "fc1_w");
let fc1_b = g.placeholder(&[intermediate], F32, "fc1_b");
let fc2_w = g.placeholder(&[intermediate, hidden], F32, "fc2_w");
let fc2_b = g.placeholder(&[hidden], F32, "fc2_b");
let ln2_g = g.placeholder(&[hidden], F32, "ln2_g");
let ln2_b = g.placeholder(&[hidden], F32, "ln2_b");
let qkv = g.add(&g.matmul(&x, &qkv_w), &qkv_b);
let qkv_r = g.reshape(&qkv, &[batch, seq, 3, num_heads, head_dim]);
let attn = g.matmul(&x, &out_w); let attn = g.add(&attn, &out_b);
let x1 = g.add(&x, &attn);
let x1_n = g.layer_norm(&x1, &ln1_g, &ln1_b, &[1], 1e-12);
let fc1 = g.add(&g.matmul(&x1_n, &fc1_w), &fc1_b);
let fc1_a = g.gelu(&fc1);
let fc2 = g.add(&g.matmul(&fc1_a, &fc2_w), &fc2_b);
let x2 = g.add(&x1_n, &fc2);
let out = g.layer_norm(&x2, &ln2_g, &ln2_b, &[1], 1e-12);
let _ = qkv_r;
let bufs: Vec<&rlx_metal::mtl::Buffer> = vec![
&buf_x, &buf_qkv_w, &buf_qkv_b, &buf_out_w, &buf_out_b, &buf_ln1_g, &buf_ln1_b, &buf_fc1_w,
&buf_fc1_b, &buf_fc2_w, &buf_fc2_b, &buf_ln2_g, &buf_ln2_b,
];
let tensors: Vec<&_> = vec![
&x, &qkv_w, &qkv_b, &out_w, &out_b, &ln1_g, &ln1_b, &fc1_w, &fc1_b, &fc2_w, &fc2_b, &ln2_g,
&ln2_b,
];
let shapes: Vec<Vec<usize>> = vec![
vec![m, hidden],
vec![hidden, 3 * hidden],
vec![3 * hidden],
vec![hidden, hidden],
vec![hidden],
vec![hidden],
vec![hidden],
vec![hidden, intermediate],
vec![intermediate],
vec![intermediate, hidden],
vec![hidden],
vec![hidden],
vec![hidden],
];
let dts = vec![F32; tensors.len()];
let offsets = vec![0usize; bufs.len()];
for _ in 0..5 {
g.run_jit(
&dev.queue,
&tensors,
&bufs,
&offsets,
&shapes,
&dts,
&[&out],
&[&buf_out],
&[0],
&[vec![m, hidden]],
&[F32],
);
}
let n_iter = 50;
let t0 = std::time::Instant::now();
for _ in 0..n_iter {
g.run_jit(
&dev.queue,
&tensors,
&bufs,
&offsets,
&shapes,
&dts,
&[&out],
&[&buf_out],
&[0],
&[vec![m, hidden]],
&[F32],
);
}
let avg_ms = t0.elapsed().as_secs_f64() * 1000.0 / n_iter as f64;
let out_data: &[f32] =
unsafe { std::slice::from_raw_parts(buf_out.contents() as *const f32, m * hidden) };
let any_nan = out_data.iter().any(|v| !v.is_finite());
println!("batch={batch} seq={seq} hidden={hidden} heads={num_heads} ffn={intermediate}");
println!("avg per layer: {avg_ms:.3} ms ({n_iter} iters)");
println!("output[0..4] = {:?}", &out_data[0..4]);
println!("output[hidden*m-4..] = {:?}", &out_data[m * hidden - 4..]);
if any_nan {
eprintln!("✗ output contains NaN/Inf");
std::process::exit(1);
}
println!("✓ Full transformer-block-as-MPSGraph executed");
}
#[cfg(not(target_os = "macos"))]
fn main() {
eprintln!("requires macOS");
}