use fuel_ir::Shape;
use fuel_ir::error::Result;
#[cfg(test)]
use fuel_tensor::Device;
use fuel_tensor::{lazy::Tensor, lazy::WeightStorage};
use std::sync::Arc;
#[derive(Debug, Clone, PartialEq)]
pub struct BeitConfig {
pub embed_dim: usize,
pub depth: usize,
pub num_heads: usize,
pub num_channels: usize,
pub image_size: usize,
pub patch_size: usize,
pub mlp_ratio: usize,
pub layer_norm_eps: f64,
pub num_classes: usize,
pub qkv_bias: bool,
pub proj_bias: bool,
}
impl BeitConfig {
pub fn head_dim(&self) -> usize {
self.embed_dim / self.num_heads
}
pub fn num_patches_per_side(&self) -> usize {
self.image_size / self.patch_size
}
pub fn num_patches(&self) -> usize {
let p = self.num_patches_per_side();
p * p
}
pub fn num_tokens(&self) -> usize {
self.num_patches() + 1
}
pub fn num_relative_distance(&self) -> usize {
let w = self.num_patches_per_side();
(2 * w - 1) * (2 * w - 1) + 3
}
pub fn vit_base() -> Self {
Self {
embed_dim: 768,
depth: 12,
num_heads: 12,
num_channels: 3,
image_size: 384,
patch_size: 16,
mlp_ratio: 4,
layer_norm_eps: 1e-6,
num_classes: 1000,
qkv_bias: true,
proj_bias: true,
}
}
}
#[derive(Debug, Clone)]
pub struct BeitBlockWeights {
pub norm1_gain: Arc<[f32]>,
pub norm1_bias: Arc<[f32]>,
pub qkv: WeightStorage,
pub qkv_bias: Option<Arc<[f32]>>,
pub proj: WeightStorage,
pub proj_bias: Option<Arc<[f32]>>,
pub ls1_gamma: Arc<[f32]>,
pub relative_position_bias_table: Arc<[f32]>,
pub norm2_gain: Arc<[f32]>,
pub norm2_bias: Arc<[f32]>,
pub fc1: WeightStorage,
pub fc1_bias: Arc<[f32]>,
pub fc2: WeightStorage,
pub fc2_bias: Arc<[f32]>,
pub ls2_gamma: Arc<[f32]>,
}
#[derive(Debug, Clone)]
pub struct BeitWeights {
pub patch_proj: Arc<[f32]>,
pub patch_proj_bias: Arc<[f32]>,
pub cls_token: Arc<[f32]>,
pub blocks: Vec<BeitBlockWeights>,
pub final_ln_gain: Arc<[f32]>,
pub final_ln_bias: Arc<[f32]>,
pub head: WeightStorage,
pub head_bias: Arc<[f32]>,
}
#[derive(Debug, Clone)]
pub struct BeitModel {
pub config: BeitConfig,
pub weights: BeitWeights,
relative_position_index: Arc<[u32]>,
}
impl BeitModel {
pub fn new(config: BeitConfig, weights: BeitWeights) -> Self {
let relative_position_index =
Arc::from(build_relative_position_index(config.num_patches_per_side()));
Self {
config,
weights,
relative_position_index,
}
}
pub fn forward(&self, pixel_values: &Tensor) -> Result<Tensor> {
let cfg = &self.config;
let weights = &self.weights;
let dims = pixel_values.shape();
let dims = dims.dims();
assert_eq!(dims.len(), 4);
let batch = dims[0];
assert_eq!(batch, 1, "v1 supports batch == 1");
if dims[2] != cfg.image_size || dims[3] != cfg.image_size {
return Err(fuel_ir::error::Error::Msg(format!(
"beit: input spatial dims ({}, {}) must equal image_size {}; this model is \
fixed-resolution (position interpolation deferred), and the patch reshape targets \
a config-derived num_patches — it guards the patch COUNT, not the per-axis size, \
so a count-preserving resize would otherwise pass silently rather than error",
dims[2], dims[3], cfg.image_size,
)));
}
let conv_w = pixel_values.const_f32_like(
Arc::clone(&weights.patch_proj),
Shape::from_dims(&[
cfg.embed_dim,
cfg.num_channels,
cfg.patch_size,
cfg.patch_size,
]),
)?;
let conv_b = pixel_values.const_f32_like(
Arc::clone(&weights.patch_proj_bias),
Shape::from_dims(&[cfg.embed_dim]),
)?;
let conv_out = pixel_values.conv2d(
&conv_w,
Some(&conv_b),
(cfg.patch_size, cfg.patch_size),
(0, 0),
1,
)?;
let np = cfg.num_patches();
let patches = conv_out
.reshape(Shape::from_dims(&[batch, cfg.embed_dim, np]))?
.permute([0, 2, 1_usize])?;
let cls = pixel_values.const_f32_like(
Arc::clone(&weights.cls_token),
Shape::from_dims(&[1, 1, cfg.embed_dim]),
)?;
let cls_bc = cls.broadcast_to(Shape::from_dims(&[batch, 1, cfg.embed_dim]))?;
let mut h = cls_bc.concat(&patches, 1_usize)?;
for block in &weights.blocks {
h = self.apply_block(&h, block, pixel_values)?;
}
let patch_mean = h
.slice(1_usize, 1, np)?
.mean_dim(1_usize)?
.reshape(Shape::from_dims(&[batch, cfg.embed_dim]))?;
let pooled_ln = patch_mean.layer_norm_affine(
std::sync::Arc::clone(&weights.final_ln_gain),
std::sync::Arc::clone(&weights.final_ln_bias),
cfg.layer_norm_eps,
)?;
let logits = weights
.head
.apply_linear(&pooled_ln, cfg.embed_dim, cfg.num_classes)?;
let bias_t = pixel_values.const_f32_like(
Arc::clone(&weights.head_bias),
Shape::from_dims(&[cfg.num_classes]),
)?;
logits.broadcast_add(&bias_t)
}
pub fn forward_intermediate_layers(
&self,
pixel_values: &Tensor,
layer_ids: &[usize],
) -> Result<Vec<Tensor>> {
let cfg = &self.config;
let weights = &self.weights;
let dims = pixel_values.shape();
let dims = dims.dims();
assert_eq!(dims.len(), 4);
let batch = dims[0];
assert_eq!(batch, 1, "v1 supports batch == 1");
if dims[2] != cfg.image_size || dims[3] != cfg.image_size {
return Err(fuel_ir::error::Error::Msg(format!(
"beit: input spatial dims ({}, {}) must equal image_size {}; this model is \
fixed-resolution (position interpolation deferred), and the patch reshape targets \
a config-derived num_patches — it guards the patch COUNT, not the per-axis size, \
so a count-preserving resize would otherwise pass silently rather than error",
dims[2], dims[3], cfg.image_size,
)));
}
assert!(!layer_ids.is_empty(), "layer_ids must not be empty");
for w in layer_ids.windows(2) {
assert!(w[0] < w[1], "layer_ids must be strictly increasing");
}
let depth = weights.blocks.len();
assert!(
*layer_ids.last().unwrap() < depth,
"layer_ids must all be in [0, num_blocks = {depth})",
);
let conv_w = pixel_values.const_f32_like(
Arc::clone(&weights.patch_proj),
Shape::from_dims(&[
cfg.embed_dim,
cfg.num_channels,
cfg.patch_size,
cfg.patch_size,
]),
)?;
let conv_b = pixel_values.const_f32_like(
Arc::clone(&weights.patch_proj_bias),
Shape::from_dims(&[cfg.embed_dim]),
)?;
let conv_out = pixel_values.conv2d(
&conv_w,
Some(&conv_b),
(cfg.patch_size, cfg.patch_size),
(0, 0),
1,
)?;
let np = cfg.num_patches();
let patches = conv_out
.reshape(Shape::from_dims(&[batch, cfg.embed_dim, np]))?
.permute([0, 2, 1_usize])?;
let cls = pixel_values.const_f32_like(
Arc::clone(&weights.cls_token),
Shape::from_dims(&[1, 1, cfg.embed_dim]),
)?;
let cls_bc = cls.broadcast_to(Shape::from_dims(&[batch, 1, cfg.embed_dim]))?;
let mut h = cls_bc.concat(&patches, 1_usize)?;
let mut out = Vec::with_capacity(layer_ids.len());
let mut next_capture = 0;
for (idx, block) in weights.blocks.iter().enumerate() {
h = self.apply_block(&h, block, pixel_values)?;
if next_capture < layer_ids.len() && layer_ids[next_capture] == idx {
out.push(h.clone());
next_capture += 1;
}
}
Ok(out)
}
fn apply_block(&self, x: &Tensor, block: &BeitBlockWeights, anchor: &Tensor) -> Result<Tensor> {
let cfg = &self.config;
let dims = x.shape();
let dims = dims.dims();
let batch = dims[0];
let seq = dims[1]; let h = cfg.embed_dim;
let n_heads = cfg.num_heads;
let head_dim = cfg.head_dim();
let x_norm = x.layer_norm_affine(
std::sync::Arc::clone(&block.norm1_gain),
std::sync::Arc::clone(&block.norm1_bias),
cfg.layer_norm_eps,
)?;
let qkv_lin = block.qkv.apply_linear(&x_norm, h, 3 * h)?;
let qkv = match &block.qkv_bias {
None => qkv_lin,
Some(b) => {
let bt = anchor.const_f32_like(Arc::clone(b), Shape::from_dims(&[3 * h]))?;
qkv_lin.broadcast_add(&bt)?
}
};
let q = qkv.slice(2_usize, 0, h)?;
let k = qkv.slice(2_usize, h, h)?;
let v = qkv.slice(2_usize, 2 * h, h)?;
let _ = (batch, seq);
let q = q.split_heads(n_heads, head_dim)?;
let k = k.split_heads(n_heads, head_dim)?;
let v = v.split_heads(n_heads, head_dim)?;
let rel_bias = self.build_relative_position_bias(
anchor,
&block.relative_position_bias_table,
seq,
n_heads,
)?;
let k_t = k.transpose()?;
let scale = 1.0_f64 / (head_dim as f64).sqrt();
let scores = q.matmul(&k_t)?.mul_scalar(scale);
let scores = scores.broadcast_add(&rel_bias)?;
let probs = scores.softmax_last_dim()?;
let ctx = probs.matmul(&v)?;
let merged = ctx.merge_heads()?;
let proj = block.proj.apply_linear(&merged, h, h)?;
let attn_out = match &block.proj_bias {
None => proj,
Some(b) => {
let bt = anchor.const_f32_like(Arc::clone(b), Shape::from_dims(&[h]))?;
proj.broadcast_add(&bt)?
}
};
let ls1_t = anchor.const_f32_like(Arc::clone(&block.ls1_gamma), Shape::from_dims(&[h]))?;
let h1 = x.add(&attn_out.broadcast_mul(&ls1_t)?)?;
let h1_norm = h1.layer_norm_affine(
std::sync::Arc::clone(&block.norm2_gain),
std::sync::Arc::clone(&block.norm2_bias),
cfg.layer_norm_eps,
)?;
let mlp_h = cfg.embed_dim * cfg.mlp_ratio;
let fc1 = block.fc1.apply_linear(&h1_norm, h, mlp_h)?;
let fc1_bias_t =
anchor.const_f32_like(Arc::clone(&block.fc1_bias), Shape::from_dims(&[mlp_h]))?;
let fc1 = fc1.broadcast_add(&fc1_bias_t)?.gelu();
let fc2 = block.fc2.apply_linear(&fc1, mlp_h, h)?;
let fc2_bias_t =
anchor.const_f32_like(Arc::clone(&block.fc2_bias), Shape::from_dims(&[h]))?;
let mlp_out = fc2.broadcast_add(&fc2_bias_t)?;
let ls2_t = anchor.const_f32_like(Arc::clone(&block.ls2_gamma), Shape::from_dims(&[h]))?;
h1.add(&mlp_out.broadcast_mul(&ls2_t)?)
}
fn build_relative_position_bias(
&self,
anchor: &Tensor,
bias_table: &Arc<[f32]>,
seq: usize,
n_heads: usize,
) -> Result<Tensor> {
assert_eq!(seq, self.config.num_tokens());
let nrd = self.config.num_relative_distance();
assert_eq!(bias_table.len(), nrd * n_heads);
let idx = &self.relative_position_index;
assert_eq!(idx.len(), seq * seq);
let mut bias_data = vec![0.0_f32; n_heads * seq * seq];
for q in 0..seq {
for kv in 0..seq {
let bucket = idx[q * seq + kv] as usize;
for h in 0..n_heads {
let src = bucket * n_heads + h;
let dst = h * seq * seq + q * seq + kv;
bias_data[dst] = bias_table[src];
}
}
}
let bias = anchor.const_f32_like(
Arc::from(bias_data),
Shape::from_dims(&[1, n_heads, seq, seq]),
)?;
Ok(bias)
}
}
#[allow(clippy::erasing_op)]
fn build_relative_position_index(num_patches_per_side: usize) -> Vec<u32> {
let w = num_patches_per_side;
let w_area = w * w;
let nb = w_area + 1;
let two_w_m1 = (2 * w - 1) as u32;
let nrd = (2 * w - 1) * (2 * w - 1) + 3;
let mut idx = vec![0_u32; nb * nb];
for a in 0..w_area {
let r_a = a / w;
let c_a = a % w;
for b in 0..w_area {
let r_b = b / w;
let c_b = b % w;
let dr = (r_a as i32 - r_b as i32 + (w as i32 - 1)) as u32;
let dc = (c_a as i32 - c_b as i32 + (w as i32 - 1)) as u32;
let bucket = dr * two_w_m1 + dc;
idx[(a + 1) * nb + (b + 1)] = bucket;
}
}
let cls_to_patch = (nrd - 3) as u32;
for j in 1..nb {
idx[0 * nb + j] = cls_to_patch;
}
let patch_to_cls = (nrd - 2) as u32;
for i in 1..nb {
idx[i * nb + 0] = patch_to_cls;
}
idx[0] = (nrd - 1) as u32;
idx
}
impl BeitWeights {
pub fn load_from_mmapped(
st: &fuel_loaders::safetensors::MmapedSafetensors,
cfg: &BeitConfig,
) -> Result<Self> {
use fuel_tensor::{
lazy::load_tensor_as_f32, lazy::load_transposed_matrix,
lazy::load_transposed_matrix_preserve_dtype as ltm,
};
let h = cfg.embed_dim;
let mlp = cfg.mlp_ratio * h;
let opt_bias = |name: String| -> Option<Arc<[f32]>> {
load_tensor_as_f32(st, &name).ok().map(Arc::from)
};
let patch_proj = Arc::from(load_tensor_as_f32(
st,
"beit.embeddings.patch_embeddings.projection.weight",
)?);
let patch_proj_bias = Arc::from(load_tensor_as_f32(
st,
"beit.embeddings.patch_embeddings.projection.bias",
)?);
let cls_token = Arc::from(load_tensor_as_f32(st, "beit.embeddings.cls_token")?);
let mut blocks = Vec::with_capacity(cfg.depth);
for i in 0..cfg.depth {
let p = format!("beit.encoder.layer.{i}");
let norm1_gain = Arc::from(load_tensor_as_f32(
st,
&format!("{p}.layernorm_before.weight"),
)?);
let norm1_bias = Arc::from(load_tensor_as_f32(
st,
&format!("{p}.layernorm_before.bias"),
)?);
let q =
load_transposed_matrix(st, &format!("{p}.attention.attention.query.weight"), h, h)?;
let k =
load_transposed_matrix(st, &format!("{p}.attention.attention.key.weight"), h, h)?;
let v =
load_transposed_matrix(st, &format!("{p}.attention.attention.value.weight"), h, h)?;
let mut fused = vec![0.0_f32; h * 3 * h];
for row in 0..h {
fused[row * 3 * h..row * 3 * h + h].copy_from_slice(&q[row * h..(row + 1) * h]);
fused[row * 3 * h + h..row * 3 * h + 2 * h]
.copy_from_slice(&k[row * h..(row + 1) * h]);
fused[row * 3 * h + 2 * h..row * 3 * h + 3 * h]
.copy_from_slice(&v[row * h..(row + 1) * h]);
}
let qkv = WeightStorage::F32(Arc::from(fused));
let qkv_bias = if cfg.qkv_bias {
let q_b = opt_bias(format!("{p}.attention.attention.query.bias"));
let v_b = opt_bias(format!("{p}.attention.attention.value.bias"));
let q_b = q_b.unwrap_or_else(|| Arc::from(vec![0.0_f32; h]));
let v_b = v_b.unwrap_or_else(|| Arc::from(vec![0.0_f32; h]));
let mut fused = vec![0.0_f32; 3 * h];
fused[..h].copy_from_slice(&q_b);
fused[2 * h..].copy_from_slice(&v_b);
Some(Arc::from(fused))
} else {
None
};
let proj = ltm(st, &format!("{p}.attention.output.dense.weight"), h, h)?;
let proj_bias = if cfg.proj_bias {
opt_bias(format!("{p}.attention.output.dense.bias"))
} else {
None
};
let ls1_gamma = Arc::from(load_tensor_as_f32(st, &format!("{p}.lambda_1"))?);
let relative_position_bias_table = Arc::from(load_tensor_as_f32(
st,
&format!(
"{p}.attention.attention.relative_position_bias.relative_position_bias_table"
),
)?);
let norm2_gain = Arc::from(load_tensor_as_f32(
st,
&format!("{p}.layernorm_after.weight"),
)?);
let norm2_bias = Arc::from(load_tensor_as_f32(
st,
&format!("{p}.layernorm_after.bias"),
)?);
let fc1 = ltm(st, &format!("{p}.intermediate.dense.weight"), mlp, h)?;
let fc1_bias = Arc::from(load_tensor_as_f32(
st,
&format!("{p}.intermediate.dense.bias"),
)?);
let fc2 = ltm(st, &format!("{p}.output.dense.weight"), h, mlp)?;
let fc2_bias = Arc::from(load_tensor_as_f32(st, &format!("{p}.output.dense.bias"))?);
let ls2_gamma = Arc::from(load_tensor_as_f32(st, &format!("{p}.lambda_2"))?);
blocks.push(BeitBlockWeights {
norm1_gain,
norm1_bias,
qkv,
qkv_bias,
proj,
proj_bias,
ls1_gamma,
relative_position_bias_table,
norm2_gain,
norm2_bias,
fc1,
fc1_bias,
fc2,
fc2_bias,
ls2_gamma,
});
}
let final_ln_gain = Arc::from(load_tensor_as_f32(st, "beit.layernorm.weight")?);
let final_ln_bias = Arc::from(load_tensor_as_f32(st, "beit.layernorm.bias")?);
let head = ltm(st, "classifier.weight", cfg.num_classes, h)?;
let head_bias = Arc::from(
load_tensor_as_f32(st, "classifier.bias")
.unwrap_or_else(|_| vec![0.0_f32; cfg.num_classes]),
);
Ok(Self {
patch_proj,
patch_proj_bias,
cls_token,
blocks,
final_ln_gain,
final_ln_bias,
head,
head_bias,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
fn vec_of(n: usize, next: &mut dyn FnMut() -> f32) -> Arc<[f32]> {
Arc::from((0..n).map(|_| next()).collect::<Vec<_>>())
}
fn tiny_weights(cfg: &BeitConfig) -> BeitWeights {
let mut s: u32 = 42424;
let next = move || -> f32 {
s = s.wrapping_mul(1103515245).wrapping_add(12345);
((s >> 16) as u16 as f32 / 65535.0 - 0.5) * 0.05
};
let mut nb: Box<dyn FnMut() -> f32> = Box::new(next);
let h = cfg.embed_dim;
let p = cfg.patch_size;
let c = cfg.num_channels;
let mlp_h = cfg.embed_dim * cfg.mlp_ratio;
let nrd = cfg.num_relative_distance();
let patch_proj = vec_of(h * c * p * p, &mut *nb);
let patch_proj_bias = vec_of(h, &mut *nb);
let cls_token = vec_of(h, &mut *nb);
let blocks: Vec<BeitBlockWeights> = (0..cfg.depth)
.map(|_| BeitBlockWeights {
norm1_gain: Arc::from(vec![1.0_f32; h]),
norm1_bias: Arc::from(vec![0.0_f32; h]),
qkv: WeightStorage::F32(vec_of(h * (3 * h), &mut *nb)),
qkv_bias: if cfg.qkv_bias {
Some(vec_of(3 * h, &mut *nb))
} else {
None
},
proj: WeightStorage::F32(vec_of(h * h, &mut *nb)),
proj_bias: if cfg.proj_bias {
Some(vec_of(h, &mut *nb))
} else {
None
},
ls1_gamma: vec_of(h, &mut *nb),
relative_position_bias_table: vec_of(nrd * cfg.num_heads, &mut *nb),
norm2_gain: Arc::from(vec![1.0_f32; h]),
norm2_bias: Arc::from(vec![0.0_f32; h]),
fc1: WeightStorage::F32(vec_of(h * mlp_h, &mut *nb)),
fc1_bias: vec_of(mlp_h, &mut *nb),
fc2: WeightStorage::F32(vec_of(mlp_h * h, &mut *nb)),
fc2_bias: vec_of(h, &mut *nb),
ls2_gamma: vec_of(h, &mut *nb),
})
.collect();
let final_ln_gain = Arc::from(vec![1.0_f32; h]);
let final_ln_bias = Arc::from(vec![0.0_f32; h]);
let head = WeightStorage::F32(vec_of(h * cfg.num_classes, &mut *nb));
let head_bias = vec_of(cfg.num_classes, &mut *nb);
BeitWeights {
patch_proj,
patch_proj_bias,
cls_token,
blocks,
final_ln_gain,
final_ln_bias,
head,
head_bias,
}
}
fn tiny_config() -> BeitConfig {
BeitConfig {
embed_dim: 16,
depth: 2,
num_heads: 4,
num_channels: 3,
image_size: 16,
patch_size: 4,
mlp_ratio: 2,
layer_norm_eps: 1e-6,
num_classes: 8,
qkv_bias: true,
proj_bias: true,
}
}
fn tiny_image(cfg: &BeitConfig) -> Tensor {
let n_pix = 1 * cfg.num_channels * cfg.image_size * cfg.image_size;
let img_data: Vec<f32> = (0..n_pix).map(|i| i as f32 / n_pix as f32).collect();
Tensor::from_f32(
Arc::from(img_data),
Shape::from_dims(&[1, cfg.num_channels, cfg.image_size, cfg.image_size]),
&Device::cpu(),
)
.unwrap()
}
#[test]
fn forward_shape_and_finite() {
let cfg = tiny_config();
let model = BeitModel::new(cfg.clone(), tiny_weights(&cfg));
let img = tiny_image(&cfg);
let logits = model.forward(&img).unwrap();
assert_eq!(logits.shape().dims(), &[1, cfg.num_classes]);
for &v in &logits.realize_f32() {
assert!(v.is_finite());
}
}
#[test]
fn rel_pos_bias_is_wired() {
let cfg = tiny_config();
let base = tiny_weights(&cfg);
let mut zeroed = base.clone();
let nrd = cfg.num_relative_distance();
zeroed.blocks[0].relative_position_bias_table =
Arc::from(vec![0.0_f32; nrd * cfg.num_heads]);
let m_base = BeitModel::new(cfg.clone(), base);
let m_zero = BeitModel::new(cfg.clone(), zeroed);
let img_a = tiny_image(&cfg);
let img_b = tiny_image(&cfg);
let a = m_base.forward(&img_a).unwrap().realize_f32();
let b = m_zero.forward(&img_b).unwrap().realize_f32();
let mut max_diff = 0.0_f32;
for (x, y) in a.iter().zip(b.iter()) {
max_diff = max_diff.max((x - y).abs());
}
assert!(
max_diff > 1e-8,
"relative position bias must affect output, max_diff = {max_diff}"
);
}
#[test]
#[allow(clippy::needless_range_loop)]
fn relative_position_index_structure() {
let idx = build_relative_position_index(2);
assert_eq!(idx.len(), 25);
let nrd = (2 * 2 - 1) * (2 * 2 - 1) + 3; assert_eq!(idx[0], (nrd - 1) as u32);
for j in 1..5 {
assert_eq!(idx[j], (nrd - 3) as u32);
}
for i in 1..5 {
assert_eq!(idx[i * 5], (nrd - 2) as u32);
}
let same_bucket: u32 = (1 * 3 + 1) as u32;
for k in 1..=4 {
assert_eq!(idx[k * 5 + k], same_bucket);
}
}
#[test]
fn forward_intermediate_layers_shape() {
let cfg = tiny_config();
let model = BeitModel::new(cfg.clone(), tiny_weights(&cfg));
let img = tiny_image(&cfg);
let outs = model
.forward_intermediate_layers(&img, &[0_usize, 1])
.unwrap();
assert_eq!(outs.len(), 2);
let np = cfg.num_patches();
for out in &outs {
assert_eq!(out.shape().dims(), &[1, np + 1, cfg.embed_dim]);
for &v in &out.realize_f32() {
assert!(v.is_finite(), "non-finite intermediate: {v}");
}
}
let a = outs[0].realize_f32();
let b = outs[1].realize_f32();
let mut max_diff = 0.0_f32;
for (x, y) in a.iter().zip(b.iter()) {
max_diff = max_diff.max((x - y).abs());
}
assert!(
max_diff > 1e-7,
"layer 0 and layer 1 intermediates must differ, max_diff = {max_diff}"
);
}
}