use super::conv1d::A2Conv1d;
use super::conv1d_ch::A2Conv1dCh;
use super::film::FiLMLayer;
use super::params::A2_LEAKY_SLOPE;
use crate::math::common::{AlignedVec, SimdMath};
pub enum A2ConvCh {
Ch3(A2Conv1dCh<3>),
Ch8(A2Conv1dCh<8>),
}
pub struct A2Layer {
pub conv: A2Conv1d,
pub conv_ch: Option<A2ConvCh>,
pub mixin_w: AlignedVec<f32>,
pub l1x1_w: AlignedVec<f32>,
pub l1x1_b: AlignedVec<f32>,
pub conv_pre_film: Option<FiLMLayer>,
pub conv_post_film: Option<FiLMLayer>,
pub input_mixin_pre_film: Option<FiLMLayer>,
pub input_mixin_post_film: Option<FiLMLayer>,
pub activation_pre_film: Option<FiLMLayer>,
pub activation_post_film: Option<FiLMLayer>,
pub layer1x1_post_film: Option<FiLMLayer>,
pub head1x1_post_film: Option<FiLMLayer>,
}
impl A2Layer {
pub fn new(
conv: A2Conv1d,
mixin_w: AlignedVec<f32>,
l1x1_w: AlignedVec<f32>,
l1x1_b: AlignedVec<f32>,
) -> Self {
let ch = conv.out_ch();
debug_assert_eq!(mixin_w.len(), ch);
debug_assert_eq!(l1x1_w.len(), ch * ch);
debug_assert_eq!(l1x1_b.len(), ch);
Self {
conv,
conv_ch: None,
mixin_w,
l1x1_w,
l1x1_b,
conv_pre_film: None,
conv_post_film: None,
input_mixin_pre_film: None,
input_mixin_post_film: None,
activation_pre_film: None,
activation_post_film: None,
layer1x1_post_film: None,
head1x1_post_film: None,
}
}
pub fn new_with_ch3(
conv: A2Conv1d,
ch3_conv: A2Conv1dCh<3>,
mixin_w: AlignedVec<f32>,
l1x1_w: AlignedVec<f32>,
l1x1_b: AlignedVec<f32>,
) -> Self {
debug_assert_eq!(conv.out_ch(), 3);
debug_assert_eq!(mixin_w.len(), 3);
debug_assert_eq!(l1x1_w.len(), 9);
debug_assert_eq!(l1x1_b.len(), 3);
Self {
conv,
conv_ch: Some(A2ConvCh::Ch3(ch3_conv)),
mixin_w,
l1x1_w,
l1x1_b,
conv_pre_film: None,
conv_post_film: None,
input_mixin_pre_film: None,
input_mixin_post_film: None,
activation_pre_film: None,
activation_post_film: None,
layer1x1_post_film: None,
head1x1_post_film: None,
}
}
pub fn new_with_ch8(
conv: A2Conv1d,
ch8_conv: A2Conv1dCh<8>,
mixin_w: AlignedVec<f32>,
l1x1_w: AlignedVec<f32>,
l1x1_b: AlignedVec<f32>,
) -> Self {
debug_assert_eq!(conv.out_ch(), 8);
debug_assert_eq!(mixin_w.len(), 8);
debug_assert_eq!(l1x1_w.len(), 64);
debug_assert_eq!(l1x1_b.len(), 8);
Self {
conv,
conv_ch: Some(A2ConvCh::Ch8(ch8_conv)),
mixin_w,
l1x1_w,
l1x1_b,
conv_pre_film: None,
conv_post_film: None,
input_mixin_pre_film: None,
input_mixin_post_film: None,
activation_pre_film: None,
activation_post_film: None,
layer1x1_post_film: None,
head1x1_post_film: None,
}
}
pub fn new_dyn(
conv: A2Conv1d,
mixin_w: AlignedVec<f32>,
l1x1_w: AlignedVec<f32>,
l1x1_b: AlignedVec<f32>,
l1x1_out_ch: usize,
bottleneck: usize,
condition_size: usize,
) -> Self {
let ch = conv.out_ch();
debug_assert_eq!(mixin_w.len(), ch * condition_size);
debug_assert_eq!(l1x1_w.len(), bottleneck * l1x1_out_ch);
debug_assert_eq!(l1x1_b.len(), l1x1_out_ch);
Self {
conv,
conv_ch: None,
mixin_w,
l1x1_w,
l1x1_b,
conv_pre_film: None,
conv_post_film: None,
input_mixin_pre_film: None,
input_mixin_post_film: None,
activation_pre_film: None,
activation_post_film: None,
layer1x1_post_film: None,
head1x1_post_film: None,
}
}
#[inline(always)]
pub fn channels(&self) -> usize {
self.conv.out_ch()
}
#[inline(always)]
pub fn groups(&self) -> usize {
self.conv.groups()
}
#[inline(always)]
pub fn is_depthwise(&self) -> bool {
self.conv.is_depthwise()
}
#[inline(always)]
pub fn kernel_size(&self) -> usize {
self.conv.kernel_size()
}
#[inline(always)]
pub fn dilation(&self) -> usize {
self.conv.dilation()
}
#[expect(
clippy::too_many_arguments,
reason = "A2 neural network layer requiring many shape/stride/buffer parameters for dynamic topology construction"
)]
#[inline(always)]
pub fn process_single_frame<M: SimdMath>(
&self,
layer_history: &[f32],
frame_idx: usize,
input_cond: f32,
head_accum: &mut [f32],
head_col: usize,
z_buf: &mut [f32],
layer_in_out: &mut [f32],
is_first: bool,
is_last: bool,
) {
let ch = self.channels();
debug_assert!(z_buf.len() >= ch);
debug_assert!(layer_in_out.len() >= ch);
unsafe {
self.conv
.process_single_frame::<M>(layer_history, z_buf, frame_idx, None);
}
let mixin = &self.mixin_w;
for c in 0..ch {
z_buf[c] += mixin[c] * input_cond;
}
for z in z_buf.iter_mut().take(ch) {
if *z < 0.0 {
*z *= A2_LEAKY_SLOPE;
}
}
let head_off = head_col * ch;
if is_first {
head_accum[head_off..head_off + ch].copy_from_slice(&z_buf[..ch]);
} else {
for (c, z_val) in z_buf.iter().enumerate().take(ch) {
head_accum[head_off + c] += *z_val;
}
}
if !is_last {
let l1x1 = &self.l1x1_w;
let bias = &self.l1x1_b;
for c in 0..ch {
let mut sum = bias[c];
for u in 0..ch {
sum += l1x1[u * ch + c] * z_buf[u];
}
layer_in_out[c] += sum;
}
}
}
}
#[expect(
clippy::too_many_arguments,
reason = "A2 neural network layer requiring many shape/stride/buffer parameters for dynamic topology construction"
)]
pub fn a2_layer_single_frame_scalar_ref(
conv_weights: &[f32],
conv_bias: &[f32],
conv_do_bias: bool,
dilation: usize,
kernel_size: usize,
layer_history: &[f32],
frame_idx: usize,
mixin_w: &[f32],
input_cond: f32,
l1x1_w: &[f32],
l1x1_b: &[f32],
head_accum: &mut [f32],
head_col: usize,
layer_in_out: &mut [f32],
is_first: bool,
is_last: bool,
) {
let ch = mixin_w.len();
let mut z_buf = vec![0.0f32; ch];
super::conv1d_fallback::a2_conv1d_single_frame_fallback(
conv_weights,
conv_bias,
conv_do_bias,
dilation,
ch, ch,
kernel_size,
layer_history,
frame_idx,
None,
&mut z_buf,
);
for c in 0..ch {
z_buf[c] += mixin_w[c] * input_cond;
}
for z in z_buf.iter_mut().take(ch) {
if *z < 0.0 {
*z *= A2_LEAKY_SLOPE;
}
}
let head_off = head_col * ch;
if is_first {
head_accum[head_off..head_off + ch].copy_from_slice(&z_buf[..ch]);
} else {
for c in 0..ch {
head_accum[head_off + c] += z_buf[c];
}
}
if !is_last {
for c in 0..ch {
let mut sum = l1x1_b[c];
for u in 0..ch {
sum += l1x1_w[u * ch + c] * z_buf[u];
}
layer_in_out[c] += sum;
}
}
}
#[cfg(test)]
#[path = "layer_test.rs"]
mod tests;