1use std::path::Path;
39
40use anyhow::{Context, Result};
41use burn::module::{Module, Param};
42use burn::prelude::Backend;
43use burn::tensor::Tensor;
44use burn::tensor::module::{conv_transpose1d, conv1d};
45use burn::tensor::ops::{ConvOptions, ConvTransposeOptions};
46use burn_store::{BurnpackStore, ModuleSnapshot};
47use serde::{Deserialize, Serialize};
48
49#[derive(Debug, Clone, Serialize, Deserialize)]
51pub struct OobleckVaeConfig {
52 pub decoder_input_channels: usize,
54 pub decoder_channels: usize,
56 pub channel_multiples: Vec<usize>,
58 pub downsampling_ratios: Vec<usize>,
60 pub audio_channels: usize,
62 pub sampling_rate: usize,
64}
65
66impl OobleckVaeConfig {
67 pub fn load(path: &Path) -> Result<Self> {
68 let text = std::fs::read_to_string(path)
69 .with_context(|| format!("failed to read VAE config from {}", path.display()))?;
70 serde_json::from_str(&text)
71 .with_context(|| format!("failed to parse VAE config from {}", path.display()))
72 }
73
74 pub fn hop_length(&self) -> usize {
76 self.downsampling_ratios.iter().product()
77 }
78}
79
80#[derive(Module, Debug)]
83pub struct Snake1d<B: Backend> {
84 pub alpha: Param<Tensor<B, 3>>,
85 pub beta: Param<Tensor<B, 3>>,
86}
87
88impl<B: Backend> Snake1d<B> {
89 pub fn new(channels: usize, device: &B::Device) -> Self {
90 Self {
91 alpha: Param::from_tensor(Tensor::zeros([1, channels, 1], device)),
92 beta: Param::from_tensor(Tensor::zeros([1, channels, 1], device)),
93 }
94 }
95
96 pub fn forward(&self, x: Tensor<B, 3>) -> Tensor<B, 3> {
97 let alpha = self.alpha.val().exp();
98 let beta = self.beta.val().exp();
99 x.clone() + (alpha * x).sin().powf_scalar(2.0) / (beta + 1e-9)
100 }
101}
102
103#[derive(Module, Debug)]
106pub struct WnConv1d<B: Backend> {
107 weight_g: Param<Tensor<B, 3>>,
108 weight_v: Param<Tensor<B, 3>>,
109 bias: Option<Param<Tensor<B, 1>>>,
110 padding: usize,
111 dilation: usize,
112}
113
114impl<B: Backend> WnConv1d<B> {
115 fn new(
116 in_channels: usize,
117 out_channels: usize,
118 kernel_size: usize,
119 padding: usize,
120 dilation: usize,
121 bias: bool,
122 device: &B::Device,
123 ) -> Self {
124 Self {
125 weight_g: Param::from_tensor(Tensor::ones([out_channels, 1, 1], device)),
126 weight_v: Param::from_tensor(Tensor::zeros(
127 [out_channels, in_channels, kernel_size],
128 device,
129 )),
130 bias: bias.then(|| Param::from_tensor(Tensor::zeros([out_channels], device))),
131 padding,
132 dilation,
133 }
134 }
135
136 fn weight(&self) -> Tensor<B, 3> {
138 let g = self.weight_g.val();
139 let v = self.weight_v.val();
140 let out_channels = v.dims()[0];
141 let v_norm = v
142 .clone()
143 .powf_scalar(2.0)
144 .sum_dim(2)
145 .sum_dim(1)
146 .sqrt()
147 .reshape([out_channels, 1, 1]);
148 g * v / (v_norm + 1e-12)
149 }
150
151 fn forward(&self, x: Tensor<B, 3>) -> Tensor<B, 3> {
152 let bias = self.bias.as_ref().map(|bias| bias.val());
153 conv1d(
154 x,
155 self.weight(),
156 bias,
157 ConvOptions::new([1], [self.padding], [self.dilation], 1),
158 )
159 }
160}
161
162#[derive(Module, Debug)]
164pub struct WnConvTranspose1d<B: Backend> {
165 weight_g: Param<Tensor<B, 3>>,
166 weight_v: Param<Tensor<B, 3>>,
167 bias: Param<Tensor<B, 1>>,
168 stride: usize,
169 padding: usize,
170}
171
172impl<B: Backend> WnConvTranspose1d<B> {
173 fn new(in_channels: usize, out_channels: usize, stride: usize, device: &B::Device) -> Self {
174 Self {
175 weight_g: Param::from_tensor(Tensor::ones([in_channels, 1, 1], device)),
176 weight_v: Param::from_tensor(Tensor::zeros(
177 [in_channels, out_channels, 2 * stride],
178 device,
179 )),
180 bias: Param::from_tensor(Tensor::zeros([out_channels], device)),
181 stride,
182 padding: stride.div_ceil(2),
183 }
184 }
185
186 fn weight(&self) -> Tensor<B, 3> {
189 let g = self.weight_g.val();
190 let v = self.weight_v.val();
191 let in_channels = v.dims()[0];
192 let v_norm = v
193 .clone()
194 .powf_scalar(2.0)
195 .sum_dim(2)
196 .sum_dim(1)
197 .sqrt()
198 .reshape([in_channels, 1, 1]);
199 g * v / (v_norm + 1e-12)
200 }
201
202 fn forward(&self, x: Tensor<B, 3>) -> Tensor<B, 3> {
203 conv_transpose1d(
204 x,
205 self.weight(),
206 Some(self.bias.val()),
207 ConvTransposeOptions::new([self.stride], [self.padding], [0], [1], 1),
208 )
209 }
210}
211
212#[derive(Module, Debug)]
216pub struct OobleckResidualUnit<B: Backend> {
217 pub snake1: Snake1d<B>,
218 pub conv1: WnConv1d<B>,
219 pub snake2: Snake1d<B>,
220 pub conv2: WnConv1d<B>,
221}
222
223impl<B: Backend> OobleckResidualUnit<B> {
224 pub fn new(channels: usize, dilation: usize, device: &B::Device) -> Self {
225 Self {
226 snake1: Snake1d::new(channels, device),
227 conv1: WnConv1d::new(channels, channels, 7, 3 * dilation, dilation, true, device),
228 snake2: Snake1d::new(channels, device),
229 conv2: WnConv1d::new(channels, channels, 1, 0, 1, true, device),
230 }
231 }
232
233 pub fn forward(&self, x: Tensor<B, 3>) -> Tensor<B, 3> {
234 let residual = x.clone();
235 let out = self.conv1.forward(self.snake1.forward(x));
236 let out = self.conv2.forward(self.snake2.forward(out));
237 residual + out
238 }
239}
240
241#[derive(Module, Debug)]
244pub struct OobleckDecoderBlock<B: Backend> {
245 pub snake1: Snake1d<B>,
246 pub conv_t1: WnConvTranspose1d<B>,
247 pub res_unit1: OobleckResidualUnit<B>,
248 pub res_unit2: OobleckResidualUnit<B>,
249 pub res_unit3: OobleckResidualUnit<B>,
250}
251
252impl<B: Backend> OobleckDecoderBlock<B> {
253 pub fn new(in_channels: usize, out_channels: usize, stride: usize, device: &B::Device) -> Self {
254 Self {
255 snake1: Snake1d::new(in_channels, device),
256 conv_t1: WnConvTranspose1d::new(in_channels, out_channels, stride, device),
257 res_unit1: OobleckResidualUnit::new(out_channels, 1, device),
258 res_unit2: OobleckResidualUnit::new(out_channels, 3, device),
259 res_unit3: OobleckResidualUnit::new(out_channels, 9, device),
260 }
261 }
262
263 pub fn forward(&self, x: Tensor<B, 3>) -> Tensor<B, 3> {
264 let x = self.conv_t1.forward(self.snake1.forward(x));
265 let x = self.res_unit1.forward(x);
266 let x = self.res_unit2.forward(x);
267 self.res_unit3.forward(x)
268 }
269}
270
271#[derive(Module, Debug)]
274pub struct OobleckDecoder<B: Backend> {
275 pub conv1: WnConv1d<B>,
276 pub block: Vec<OobleckDecoderBlock<B>>,
277 pub snake1: Snake1d<B>,
278 pub conv2: WnConv1d<B>,
279 pub hop_length: usize,
281 latent_channels: usize,
282}
283
284impl<B: Backend> OobleckDecoder<B> {
285 pub fn new(config: &OobleckVaeConfig, device: &B::Device) -> Self {
286 let mut multiples = Vec::with_capacity(config.channel_multiples.len() + 1);
289 multiples.push(1);
290 multiples.extend_from_slice(&config.channel_multiples);
291
292 let num_stages = config.downsampling_ratios.len();
293 let top_channels = config.decoder_channels * multiples[num_stages];
294
295 let conv1 = WnConv1d::new(
296 config.decoder_input_channels,
297 top_channels,
298 7,
299 3,
300 1,
301 true,
302 device,
303 );
304
305 let block = (0..num_stages)
307 .map(|i| {
308 let stride = config.downsampling_ratios[num_stages - 1 - i];
309 let in_channels = config.decoder_channels * multiples[num_stages - i];
310 let out_channels = config.decoder_channels * multiples[num_stages - i - 1];
311 OobleckDecoderBlock::new(in_channels, out_channels, stride, device)
312 })
313 .collect();
314
315 let snake1 = Snake1d::new(config.decoder_channels, device);
316 let conv2 = WnConv1d::new(
317 config.decoder_channels,
318 config.audio_channels,
319 7,
320 3,
321 1,
322 false,
323 device,
324 );
325
326 Self {
327 conv1,
328 block,
329 snake1,
330 conv2,
331 hop_length: config.hop_length(),
332 latent_channels: config.decoder_input_channels,
333 }
334 }
335
336 pub fn from_burnpack(
339 config: &OobleckVaeConfig,
340 path: &Path,
341 device: &B::Device,
342 ) -> Result<Self> {
343 let mut model = Self::new(config, device);
344 let mut store = BurnpackStore::from_file(path).zero_copy(true);
345 model.load_from(&mut store).map_err(|err| {
346 anyhow::anyhow!("failed to load VAE decoder from {}: {err}", path.display())
347 })?;
348 Ok(model)
349 }
350
351 pub fn forward(&self, latents: Tensor<B, 3>) -> Tensor<B, 3> {
354 let x = self.conv1.forward(latents);
355 let x = self
356 .block
357 .iter()
358 .fold(x, |hidden, block| block.forward(hidden));
359 let x = self.snake1.forward(x);
360 self.conv2.forward(x)
361 }
362
363 pub fn decode(&self, latents: Tensor<B, 3>) -> Tensor<B, 3> {
371 let [batch, frames, channels] = latents.dims();
372 assert_eq!(
373 channels, self.latent_channels,
374 "expected {} latent channels, got {channels}",
375 self.latent_channels
376 );
377 let audio = self.forward(latents.swap_dims(1, 2));
378
379 let target = frames * self.hop_length;
380 let [_, _, length] = audio.dims();
381 if length > target {
382 audio.slice([0..batch, 0..self.conv2_channels(), 0..target])
383 } else if length < target {
384 let device = audio.device();
385 let padding = Tensor::zeros([batch, self.conv2_channels(), target - length], &device);
386 Tensor::cat(vec![audio, padding], 2)
387 } else {
388 audio
389 }
390 }
391
392 fn conv2_channels(&self) -> usize {
393 self.conv2.weight_v.dims()[0]
394 }
395}
396
397#[cfg(test)]
398mod tests {
399 use super::*;
400 use burn::backend::ndarray::{NdArray, NdArrayDevice};
401
402 type TestBackend = NdArray<f32>;
403
404 fn tiny_config() -> OobleckVaeConfig {
405 OobleckVaeConfig {
406 decoder_input_channels: 4,
407 decoder_channels: 8,
408 channel_multiples: vec![1, 2],
409 downsampling_ratios: vec![2, 3],
410 audio_channels: 2,
411 sampling_rate: 48_000,
412 }
413 }
414
415 #[test]
416 fn decode_produces_stereo_audio_of_expected_length() {
417 let device = NdArrayDevice::default();
418 let decoder = OobleckDecoder::<TestBackend>::new(&tiny_config(), &device);
419
420 let latents = Tensor::<TestBackend, 3>::random(
421 [1, 5, 4],
422 burn::tensor::Distribution::Normal(0.0, 1.0),
423 &device,
424 );
425 let audio = decoder.decode(latents);
426
427 assert_eq!(audio.dims(), [1, 2, 5 * 6]);
428 let values: Vec<f32> = audio.into_data().to_vec().unwrap();
429 assert!(values.iter().all(|v| v.is_finite()));
430 }
431
432 #[test]
433 fn decode_upsamples_each_stage() {
434 let device = NdArrayDevice::default();
435 let config = tiny_config();
436 let decoder = OobleckDecoder::<TestBackend>::new(&config, &device);
437
438 let x = Tensor::<TestBackend, 3>::zeros([1, 4, 5], &device);
440 let x = decoder.conv1.forward(x);
441 assert_eq!(x.dims(), [1, 16, 5]);
442 let x = decoder.block[0].forward(x);
443 assert_eq!(x.dims(), [1, 8, 14]); let x = decoder.block[1].forward(x);
445 assert_eq!(x.dims(), [1, 8, 28]); }
447
448 #[test]
449 fn residual_unit_preserves_shape() {
450 let device = NdArrayDevice::default();
451 let unit = OobleckResidualUnit::<TestBackend>::new(8, 3, &device);
452 let x = Tensor::<TestBackend, 3>::random(
453 [2, 8, 11],
454 burn::tensor::Distribution::Normal(0.0, 1.0),
455 &device,
456 );
457 assert_eq!(unit.forward(x).dims(), [2, 8, 11]);
458 }
459
460 #[test]
461 fn loads_real_vae_config() {
462 let config = OobleckVaeConfig::load(
463 &Path::new(env!("CARGO_MANIFEST_DIR"))
464 .join("src/acestep/testdata/oobleck_vae_config.json"),
465 )
466 .unwrap();
467 assert_eq!(config.decoder_input_channels, 64);
468 assert_eq!(config.decoder_channels, 128);
469 assert_eq!(config.channel_multiples, vec![1, 2, 4, 8, 16]);
470 assert_eq!(config.downsampling_ratios, vec![2, 4, 4, 6, 10]);
471 assert_eq!(config.audio_channels, 2);
472 assert_eq!(config.sampling_rate, 48_000);
473 assert_eq!(config.hop_length(), 1920);
474 }
475}