1use super::WaveNetA2;
13use crate::math::common::AlignedVec;
14use crate::models::a2::conv1d_ch3::A2Conv1dCh3;
15use crate::models::a2::conv1d_ch8::A2Conv1dCh8;
16use crate::models::a2::film::{FiLMConfig, FiLMLayer};
17use crate::models::a2::head::A2HeadConv;
18use crate::models::a2::layer::A2Layer;
19use crate::models::a2::params::{
20 A2_DILATIONS, A2_HEAD_KERNEL_SIZE, A2_KERNEL_SIZES, A2_NUM_LAYERS,
21};
22use crate::models::a2::weights_layout::{
23 FILM_KEYS, film_bias_count, film_bias_count_generic, film_weight_count,
24 film_weight_count_generic, transpose_conv1d_interleaved_4wide, transpose_dense_f32,
25 transpose_head_w,
26};
27
28impl<const CH: usize> WaveNetA2<CH> {
29 #[expect(
45 clippy::too_many_lines,
46 reason = "Function body contains exhaustive A2 kernel-size dispatch table — splitting into sub-functions would scatter related logic"
47 )]
48 pub fn set_weights(&mut self, weights: &[f32]) -> Result<(), String> {
49 let total = weights.len();
50 let mut pos: usize = 0;
51
52 let rw_f32 = read_slice(weights, &mut pos, CH, total, "rechannel_w")?;
54 let rechannel_w = AlignedVec::from_vec(rw_f32.to_vec())
55 .expect("allocation should succeed for test-sized buffers");
56
57 let mut layers = Vec::with_capacity(A2_NUM_LAYERS);
59
60 for i in 0..A2_NUM_LAYERS {
61 let ksize = A2_KERNEL_SIZES[i];
62 let dilation = A2_DILATIONS[i];
63 let conv_w_count = CH * CH * ksize;
64 let num_blocks = CH.div_ceil(4);
65 let conv_w_padded = num_blocks * 4 * CH * ksize;
66
67 let conv_w_f32 = read_slice(
70 weights,
71 &mut pos,
72 conv_w_count,
73 total,
74 &format!("layer[{i}].conv_w"),
75 )?;
76 let conv_w_f32_owned: Vec<f32> = conv_w_f32.to_vec();
78 let mut conv_w = AlignedVec::new(conv_w_padded, 0.0f32)
80 .expect("allocation should succeed for test-sized buffers");
81 transpose_conv1d_interleaved_4wide(conv_w_f32, &mut conv_w, CH, CH, ksize);
82
83 let conv_b_f32 =
85 read_slice(weights, &mut pos, CH, total, &format!("layer[{i}].conv_b"))?;
86 let conv_b = AlignedVec::from_vec(conv_b_f32.to_vec())
87 .expect("allocation should succeed for test-sized buffers");
88
89 let conv = crate::models::a2::conv1d::A2Conv1d::new(
91 conv_w,
92 conv_b.clone(),
93 true,
94 dilation,
95 CH,
96 CH,
97 ksize,
98 );
99
100 let conv_ch = match CH {
103 3 => {
104 let ch3 = A2Conv1dCh3::new(conv_w_f32, CH, CH, ksize, dilation, conv_b_f32)
105 .map_err(|e| format!("{e}"))?;
106 Some(crate::models::a2::layer::A2ConvCh::Ch3(ch3))
107 }
108 8 => {
109 let ch8 = A2Conv1dCh8::new(&conv_w_f32_owned, CH, CH, ksize, dilation, &conv_b)
110 .map_err(|e| format!("{e}"))?;
111 Some(crate::models::a2::layer::A2ConvCh::Ch8(ch8))
112 }
113 _ => None,
114 };
115
116 let mixin_w_f32 =
119 read_slice(weights, &mut pos, CH, total, &format!("layer[{i}].mixin_w"))?;
120 let mixin_w = AlignedVec::from_vec(mixin_w_f32.to_vec())
121 .expect("allocation should succeed for test-sized buffers");
122
123 let l1x1_w_f32 = read_slice(
126 weights,
127 &mut pos,
128 CH * CH,
129 total,
130 &format!("layer[{i}].l1x1_w"),
131 )?;
132 let mut l1x1_w = AlignedVec::new(CH * CH, 0.0f32)
133 .expect("allocation should succeed for test-sized buffers");
134 transpose_dense_f32(l1x1_w_f32, &mut l1x1_w, CH, CH);
135
136 let l1x1_b_f32 =
138 read_slice(weights, &mut pos, CH, total, &format!("layer[{i}].l1x1_b"))?;
139 let l1x1_b = AlignedVec::from_vec(l1x1_b_f32.to_vec())
140 .expect("allocation should succeed for test-sized buffers");
141
142 let mut layer = A2Layer::new(conv, mixin_w, l1x1_w, l1x1_b);
144 if let Some(conv_ch) = conv_ch {
145 layer.conv_ch = Some(conv_ch);
146 }
147
148 if let Some(ref raw) = self.layer_raw {
150 let configs = parse_film_configs(raw);
151 load_film_for_layer(&mut layer, &configs, CH, 1, 1, weights, &mut pos, total, i)?;
152 }
153
154 layers.push(layer);
155 }
156
157 let head_w_f32 = read_slice(weights, &mut pos, A2_HEAD_KERNEL_SIZE * CH, total, "head_w")?;
159 let mut head_w = AlignedVec::new(A2_HEAD_KERNEL_SIZE * CH, 0.0f32)
160 .expect("allocation should succeed for test-sized buffers");
161 transpose_head_w(head_w_f32, &mut head_w, CH, A2_HEAD_KERNEL_SIZE);
162
163 let head_b = {
164 let s = read_slice(weights, &mut pos, 1, total, "head_b")?;
165 if !s[0].is_finite() {
166 return Err(format!(
167 "set_weights: head_b is not finite (value: {:e})",
168 s[0]
169 ));
170 }
171 s[0]
172 };
173
174 let head_scale = {
176 let s = read_slice(weights, &mut pos, 1, total, "head_scale")?;
177 if !s[0].is_finite() {
178 return Err(format!(
179 "set_weights: head_scale is not finite (value: {:e})",
180 s[0]
181 ));
182 }
183 s[0]
184 };
185
186 if pos != total {
188 return Err(format!(
189 "set_weights: stream has {} unconsumed f32 after loading all weights (consumed {}, total {})",
190 total - pos,
191 pos,
192 total
193 ));
194 }
195
196 self.rechannel_w_f32 = rechannel_w;
198 self.layers = layers;
199 self.head_conv = Some(A2HeadConv::new(head_w, head_b, head_scale, CH));
200
201 Ok(())
202 }
203}
204
205#[inline]
212pub(crate) fn read_slice<'a>(
213 weights: &'a [f32],
214 pos: &mut usize,
215 n: usize,
216 total: usize,
217 label: &str,
218) -> Result<&'a [f32], String> {
219 if *pos + n > total {
220 return Err(format!(
221 "set_weights: stream exhausted at position {} (need {} for \"{}\", total {})",
222 *pos, n, label, total
223 ));
224 }
225 let slice = &weights[*pos..*pos + n];
226 *pos += n;
227 Ok(slice)
228}
229
230pub(crate) fn parse_single_film_config(raw: &serde_json::Value, key: &str) -> FiLMConfig {
235 let obj = match raw.get(key).and_then(|v| v.as_object()) {
236 Some(o) => o,
237 None => return FiLMConfig::default(),
238 };
239 FiLMConfig {
240 active: obj.get("active").and_then(|a| a.as_bool()).unwrap_or(false),
241 shift: obj.get("shift").and_then(|s| s.as_bool()).unwrap_or(true),
242 groups: obj
243 .get("groups")
244 .and_then(|g| g.as_u64())
245 .map(|g| g as u32)
246 .unwrap_or(1),
247 }
248}
249
250pub(crate) fn parse_film_configs(raw: &serde_json::Value) -> [FiLMConfig; 8] {
251 let mut configs = [FiLMConfig::default(); 8];
252 for &(key, idx) in FILM_KEYS {
253 configs[idx] = parse_single_film_config(raw, key);
254 }
255 configs
256}
257
258pub(crate) fn set_layer_film(
259 layer: &mut A2Layer,
260 _config: &FiLMConfig,
261 idx: usize,
262 film: FiLMLayer,
263) -> Result<(), String> {
264 match idx {
265 0 => layer.conv_pre_film = Some(film),
266 1 => layer.conv_post_film = Some(film),
267 2 => layer.input_mixin_pre_film = Some(film),
268 3 => layer.input_mixin_post_film = Some(film),
269 4 => layer.activation_pre_film = Some(film),
270 5 => layer.activation_post_film = Some(film),
271 6 => layer.layer1x1_post_film = Some(film),
272 7 => layer.head1x1_post_film = Some(film),
273 _ => return Err(format!("FiLM slot index {} out of range (0-7)", idx)),
274 }
275 Ok(())
276}
277
278#[cfg_attr(
280 not(test),
281 expect(
282 dead_code,
283 reason = "Retained for future integration when gated feature is enabled"
284 )
285)]
286pub(crate) fn film_weight_count_cfg(
287 config: &FiLMConfig,
288 cond_size: usize,
289 channels: usize,
290) -> usize {
291 film_weight_count(config.groups, cond_size, channels, config.shift)
292}
293
294#[cfg_attr(
296 not(test),
297 expect(
298 dead_code,
299 reason = "Retained for future integration when gated feature is enabled"
300 )
301)]
302pub(crate) fn film_bias_count_cfg(config: &FiLMConfig, channels: usize) -> usize {
303 film_bias_count(channels, config.shift)
304}
305
306#[expect(
307 clippy::too_many_arguments,
308 reason = "A2 model weight-setter requiring many dimension parameters to safely map weight slices to layer buffers"
309)]
310pub(crate) fn load_film_for_layer(
311 layer: &mut A2Layer,
312 configs: &[FiLMConfig; 8],
313 channels: usize,
314 cond_size: usize,
315 head_channels: usize,
316 weights: &[f32],
317 pos: &mut usize,
318 total: usize,
319 layer_idx: usize,
320) -> Result<(), String> {
321 for (idx, config) in configs.iter().enumerate() {
322 if !config.active {
323 continue;
324 }
325 let film_channels = match idx {
326 2 => cond_size,
327 7 => head_channels,
328 _ => channels,
329 };
330 let (w_count, b_count) = if cond_size > 1 {
331 (
333 film_weight_count_generic(config.groups, cond_size, film_channels, config.shift),
334 film_bias_count_generic(film_channels),
335 )
336 } else {
337 (
338 film_weight_count(config.groups, cond_size, film_channels, config.shift),
339 film_bias_count(film_channels, config.shift),
340 )
341 };
342 let key = FILM_KEYS[idx].0;
343
344 let film_w = read_slice(
345 weights,
346 pos,
347 w_count,
348 total,
349 &format!("layer[{layer_idx}].{key}.w"),
350 )?;
351 let film_b = read_slice(
352 weights,
353 pos,
354 b_count,
355 total,
356 &format!("layer[{layer_idx}].{key}.b"),
357 )?;
358
359 let film_layer = FiLMLayer::load(
360 *config,
361 cond_size,
362 film_channels,
363 film_w.to_vec(),
364 film_b.to_vec(),
365 )
366 .map_err(|e| format!("{e}"))?;
367 set_layer_film(layer, config, idx, film_layer)?;
368 }
369 Ok(())
370}
371
372#[cfg(test)]
373#[path = "set_weights_test.rs"]
374mod set_weights_test;