use super::{FfnActivation, GeluFfnStage, VitSelfAttnSpec, VitSelfAttnStage};
use crate::layer::LayerStack;
use crate::stage::FlowStage;
fn layer_prefix(idx: usize) -> String {
format!("layers.{idx}")
}
pub fn transformer_encoder_layer(
layer_idx: usize,
hidden_size: usize,
num_heads: usize,
eps: f32,
norm_first: bool,
act: FfnActivation,
) -> FlowStage {
let lp = layer_prefix(layer_idx);
let attn = FlowStage::VitSelfAttn(VitSelfAttnStage::new(VitSelfAttnSpec {
qkv_weight: format!("{lp}.self_attn.in_proj_weight"),
qkv_bias: format!("{lp}.self_attn.in_proj_bias"),
out_weight: format!("{lp}.self_attn.out_proj.weight"),
out_bias: format!("{lp}.self_attn.out_proj.bias"),
hidden_size,
num_heads,
head_dim: hidden_size / num_heads,
}));
let ffn = FlowStage::GeluFfn(GeluFfnStage::with_activation(
format!("{lp}.linear1.weight"),
format!("{lp}.linear1.bias"),
format!("{lp}.linear2.weight"),
format!("{lp}.linear2.bias"),
act,
));
let (n1w, n1b) = (format!("{lp}.norm1.weight"), format!("{lp}.norm1.bias"));
let (n2w, n2b) = (format!("{lp}.norm2.weight"), format!("{lp}.norm2.bias"));
let stack = LayerStack::named(format!("layer{layer_idx}"));
let stack = if norm_first {
stack
.residual_save()
.layer_norm(n1w, n1b, eps)
.stage(attn)
.residual_add()
.residual_save()
.layer_norm(n2w, n2b, eps)
.stage(ffn)
.residual_add()
} else {
stack
.residual_save()
.stage(attn)
.residual_add()
.layer_norm(n1w, n1b, eps)
.residual_save()
.stage(ffn)
.residual_add()
.layer_norm(n2w, n2b, eps)
};
stack.build()
}
#[cfg(test)]
mod tests {
use super::*;
use crate::flow::ModelFlow;
use crate::weight::MapWeights;
use rlx_ir::{DType, Shape};
fn insert_layer(w: &mut MapWeights, idx: usize, d: usize, ffn: usize) {
let lp = format!("layers.{idx}");
w.insert(
format!("{lp}.self_attn.in_proj_weight"),
vec![0.01; 3 * d * d],
vec![3 * d, d],
);
w.insert(
format!("{lp}.self_attn.in_proj_bias"),
vec![0.0; 3 * d],
vec![3 * d],
);
w.insert(
format!("{lp}.self_attn.out_proj.weight"),
vec![0.01; d * d],
vec![d, d],
);
w.insert(
format!("{lp}.self_attn.out_proj.bias"),
vec![0.0; d],
vec![d],
);
w.insert(
format!("{lp}.linear1.weight"),
vec![0.01; ffn * d],
vec![ffn, d],
);
w.insert(format!("{lp}.linear1.bias"), vec![0.0; ffn], vec![ffn]);
w.insert(
format!("{lp}.linear2.weight"),
vec![0.01; d * ffn],
vec![d, ffn],
);
w.insert(format!("{lp}.linear2.bias"), vec![0.0; d], vec![d]);
w.insert(format!("{lp}.norm1.weight"), vec![1.0; d], vec![d]);
w.insert(format!("{lp}.norm1.bias"), vec![0.0; d], vec![d]);
w.insert(format!("{lp}.norm2.weight"), vec![1.0; d], vec![d]);
w.insert(format!("{lp}.norm2.bias"), vec![0.0; d], vec![d]);
}
fn build_trunk(depth: usize, norm_first: bool, act: FfnActivation) {
let (batch, seq, d, heads, ffn) = (1usize, 3usize, 4usize, 2usize, 8usize);
let mut w = MapWeights::default();
for i in 0..depth {
insert_layer(&mut w, i, d, ffn);
}
let built = ModelFlow::new("tel")
.input("hidden", Shape::new(&[batch, seq, d], DType::F32))
.attn_mask_ones(batch, seq)
.repeat_transformer_encoder_layers(depth, d, heads, LN_EPS, norm_first, act)
.output("hidden")
.build(&mut w)
.unwrap();
let shape = built.primary_shape();
assert_eq!(shape.rank(), 3);
let got: Vec<usize> = shape.dims().iter().map(|d| d.unwrap_static()).collect();
assert_eq!(got, vec![batch, seq, d]);
assert!(built.into_hir().unwrap().len() >= depth * 8);
}
const LN_EPS: f32 = 1e-5;
#[test]
fn post_norm_relu_builds() {
build_trunk(2, false, FfnActivation::Relu);
}
#[test]
fn pre_norm_relu_builds() {
build_trunk(2, true, FfnActivation::Relu);
}
#[test]
fn pre_norm_gelu_builds() {
build_trunk(1, true, FfnActivation::GeluErf);
build_trunk(1, true, FfnActivation::GeluTanh);
}
}