use super::WaveNetA2;
use crate::math::common::AlignedVec;
use crate::models::a2::conv1d_ch3::A2Conv1dCh3;
use crate::models::a2::conv1d_ch8::A2Conv1dCh8;
use crate::models::a2::film::{FiLMConfig, FiLMLayer};
use crate::models::a2::head::A2HeadConv;
use crate::models::a2::layer::A2Layer;
use crate::models::a2::params::{
A2_DILATIONS, A2_HEAD_KERNEL_SIZE, A2_KERNEL_SIZES, A2_NUM_LAYERS,
};
use crate::models::a2::weights_layout::{
FILM_KEYS, film_bias_count, film_bias_count_generic, film_weight_count,
film_weight_count_generic, transpose_conv1d_interleaved_4wide, transpose_dense_f32,
transpose_head_w,
};
impl<const CH: usize> WaveNetA2<CH> {
#[expect(
clippy::too_many_lines,
reason = "Function body contains exhaustive A2 kernel-size dispatch table — splitting into sub-functions would scatter related logic"
)]
pub fn set_weights(&mut self, weights: &[f32]) -> Result<(), String> {
let total = weights.len();
let mut pos: usize = 0;
let rw_f32 = read_slice(weights, &mut pos, CH, total, "rechannel_w")?;
let rechannel_w = AlignedVec::from_vec(rw_f32.to_vec())
.expect("allocation should succeed for test-sized buffers");
let mut layers = Vec::with_capacity(A2_NUM_LAYERS);
for i in 0..A2_NUM_LAYERS {
let ksize = A2_KERNEL_SIZES[i];
let dilation = A2_DILATIONS[i];
let conv_w_count = CH * CH * ksize;
let num_blocks = CH.div_ceil(4);
let conv_w_padded = num_blocks * 4 * CH * ksize;
let conv_w_f32 = read_slice(
weights,
&mut pos,
conv_w_count,
total,
&format!("layer[{i}].conv_w"),
)?;
let conv_w_f32_owned: Vec<f32> = conv_w_f32.to_vec();
let mut conv_w = AlignedVec::new(conv_w_padded, 0.0f32)
.expect("allocation should succeed for test-sized buffers");
transpose_conv1d_interleaved_4wide(conv_w_f32, &mut conv_w, CH, CH, ksize);
let conv_b_f32 =
read_slice(weights, &mut pos, CH, total, &format!("layer[{i}].conv_b"))?;
let conv_b = AlignedVec::from_vec(conv_b_f32.to_vec())
.expect("allocation should succeed for test-sized buffers");
let conv = crate::models::a2::conv1d::A2Conv1d::new(
conv_w,
conv_b.clone(),
true,
dilation,
CH,
CH,
ksize,
);
let conv_ch = match CH {
3 => {
let ch3 = A2Conv1dCh3::new(conv_w_f32, CH, CH, ksize, dilation, conv_b_f32)
.map_err(|e| format!("{e}"))?;
Some(crate::models::a2::layer::A2ConvCh::Ch3(ch3))
}
8 => {
let ch8 = A2Conv1dCh8::new(&conv_w_f32_owned, CH, CH, ksize, dilation, &conv_b)
.map_err(|e| format!("{e}"))?;
Some(crate::models::a2::layer::A2ConvCh::Ch8(ch8))
}
_ => None,
};
let mixin_w_f32 =
read_slice(weights, &mut pos, CH, total, &format!("layer[{i}].mixin_w"))?;
let mixin_w = AlignedVec::from_vec(mixin_w_f32.to_vec())
.expect("allocation should succeed for test-sized buffers");
let l1x1_w_f32 = read_slice(
weights,
&mut pos,
CH * CH,
total,
&format!("layer[{i}].l1x1_w"),
)?;
let mut l1x1_w = AlignedVec::new(CH * CH, 0.0f32)
.expect("allocation should succeed for test-sized buffers");
transpose_dense_f32(l1x1_w_f32, &mut l1x1_w, CH, CH);
let l1x1_b_f32 =
read_slice(weights, &mut pos, CH, total, &format!("layer[{i}].l1x1_b"))?;
let l1x1_b = AlignedVec::from_vec(l1x1_b_f32.to_vec())
.expect("allocation should succeed for test-sized buffers");
let mut layer = A2Layer::new(conv, mixin_w, l1x1_w, l1x1_b);
if let Some(conv_ch) = conv_ch {
layer.conv_ch = Some(conv_ch);
}
if let Some(ref raw) = self.layer_raw {
let configs = parse_film_configs(raw);
load_film_for_layer(&mut layer, &configs, CH, 1, 1, weights, &mut pos, total, i)?;
}
layers.push(layer);
}
let head_w_f32 = read_slice(weights, &mut pos, A2_HEAD_KERNEL_SIZE * CH, total, "head_w")?;
let mut head_w = AlignedVec::new(A2_HEAD_KERNEL_SIZE * CH, 0.0f32)
.expect("allocation should succeed for test-sized buffers");
transpose_head_w(head_w_f32, &mut head_w, CH, A2_HEAD_KERNEL_SIZE);
let head_b = {
let s = read_slice(weights, &mut pos, 1, total, "head_b")?;
if !s[0].is_finite() {
return Err(format!(
"set_weights: head_b is not finite (value: {:e})",
s[0]
));
}
s[0]
};
let head_scale = {
let s = read_slice(weights, &mut pos, 1, total, "head_scale")?;
if !s[0].is_finite() {
return Err(format!(
"set_weights: head_scale is not finite (value: {:e})",
s[0]
));
}
s[0]
};
if pos != total {
return Err(format!(
"set_weights: stream has {} unconsumed f32 after loading all weights (consumed {}, total {})",
total - pos,
pos,
total
));
}
self.rechannel_w_f32 = rechannel_w;
self.layers = layers;
self.head_conv = Some(A2HeadConv::new(head_w, head_b, head_scale, CH));
Ok(())
}
}
#[inline]
pub(crate) fn read_slice<'a>(
weights: &'a [f32],
pos: &mut usize,
n: usize,
total: usize,
label: &str,
) -> Result<&'a [f32], String> {
if *pos + n > total {
return Err(format!(
"set_weights: stream exhausted at position {} (need {} for \"{}\", total {})",
*pos, n, label, total
));
}
let slice = &weights[*pos..*pos + n];
*pos += n;
Ok(slice)
}
pub(crate) fn parse_single_film_config(raw: &serde_json::Value, key: &str) -> FiLMConfig {
let obj = match raw.get(key).and_then(|v| v.as_object()) {
Some(o) => o,
None => return FiLMConfig::default(),
};
FiLMConfig {
active: obj.get("active").and_then(|a| a.as_bool()).unwrap_or(false),
shift: obj.get("shift").and_then(|s| s.as_bool()).unwrap_or(true),
groups: obj
.get("groups")
.and_then(|g| g.as_u64())
.map(|g| g as u32)
.unwrap_or(1),
}
}
pub(crate) fn parse_film_configs(raw: &serde_json::Value) -> [FiLMConfig; 8] {
let mut configs = [FiLMConfig::default(); 8];
for &(key, idx) in FILM_KEYS {
configs[idx] = parse_single_film_config(raw, key);
}
configs
}
pub(crate) fn set_layer_film(
layer: &mut A2Layer,
_config: &FiLMConfig,
idx: usize,
film: FiLMLayer,
) -> Result<(), String> {
match idx {
0 => layer.conv_pre_film = Some(film),
1 => layer.conv_post_film = Some(film),
2 => layer.input_mixin_pre_film = Some(film),
3 => layer.input_mixin_post_film = Some(film),
4 => layer.activation_pre_film = Some(film),
5 => layer.activation_post_film = Some(film),
6 => layer.layer1x1_post_film = Some(film),
7 => layer.head1x1_post_film = Some(film),
_ => return Err(format!("FiLM slot index {} out of range (0-7)", idx)),
}
Ok(())
}
#[cfg_attr(
not(test),
expect(
dead_code,
reason = "Retained for future integration when gated feature is enabled"
)
)]
pub(crate) fn film_weight_count_cfg(
config: &FiLMConfig,
cond_size: usize,
channels: usize,
) -> usize {
film_weight_count(config.groups, cond_size, channels, config.shift)
}
#[cfg_attr(
not(test),
expect(
dead_code,
reason = "Retained for future integration when gated feature is enabled"
)
)]
pub(crate) fn film_bias_count_cfg(config: &FiLMConfig, channels: usize) -> usize {
film_bias_count(channels, config.shift)
}
#[expect(
clippy::too_many_arguments,
reason = "A2 model weight-setter requiring many dimension parameters to safely map weight slices to layer buffers"
)]
pub(crate) fn load_film_for_layer(
layer: &mut A2Layer,
configs: &[FiLMConfig; 8],
channels: usize,
cond_size: usize,
head_channels: usize,
weights: &[f32],
pos: &mut usize,
total: usize,
layer_idx: usize,
) -> Result<(), String> {
for (idx, config) in configs.iter().enumerate() {
if !config.active {
continue;
}
let film_channels = match idx {
2 => cond_size,
7 => head_channels,
_ => channels,
};
let (w_count, b_count) = if cond_size > 1 {
(
film_weight_count_generic(config.groups, cond_size, film_channels, config.shift),
film_bias_count_generic(film_channels),
)
} else {
(
film_weight_count(config.groups, cond_size, film_channels, config.shift),
film_bias_count(film_channels, config.shift),
)
};
let key = FILM_KEYS[idx].0;
let film_w = read_slice(
weights,
pos,
w_count,
total,
&format!("layer[{layer_idx}].{key}.w"),
)?;
let film_b = read_slice(
weights,
pos,
b_count,
total,
&format!("layer[{layer_idx}].{key}.b"),
)?;
let film_layer = FiLMLayer::load(
*config,
cond_size,
film_channels,
film_w.to_vec(),
film_b.to_vec(),
)
.map_err(|e| format!("{e}"))?;
set_layer_film(layer, config, idx, film_layer)?;
}
Ok(())
}
#[cfg(test)]
#[path = "set_weights_test.rs"]
mod set_weights_test;