1use std::path::Path;
38
39use anyhow::{Context, Result};
40use burn::module::{Module, Param};
41use burn::nn::{Linear, LinearConfig, LinearLayout};
42use burn::prelude::Backend;
43use burn::tensor::activation::{silu, softmax};
44use burn::tensor::{DType, Int, Tensor, TensorData};
45use burn_store::{BurnpackStore, ModuleSnapshot};
46
47use crate::acestep::config::AceStepConfig;
48use crate::acestep::fsq::ResidualFsq;
49
50#[derive(Module, Debug)]
53pub struct ConditionRmsNorm<B: Backend> {
54 pub weight: Param<Tensor<B, 1>>,
55 pub epsilon: f64,
56}
57
58impl<B: Backend> ConditionRmsNorm<B> {
59 pub fn new(device: &B::Device, hidden_size: usize, epsilon: f64) -> Self {
60 Self {
61 weight: Param::from_tensor(Tensor::<B, 1>::ones([hidden_size], device)),
62 epsilon,
63 }
64 }
65
66 pub fn forward<const D: usize>(&self, hidden: Tensor<B, D>) -> Tensor<B, D> {
67 let dtype = hidden.dtype();
68 let rms = (hidden.clone().cast(DType::F32).square().mean_dim(D - 1) + self.epsilon).sqrt();
69 (hidden / rms.cast(dtype)) * self.weight.val().unsqueeze()
70 }
71}
72
73#[derive(Clone, Debug)]
74struct AttentionMeta {
75 num_heads: usize,
76 num_kv_heads: usize,
77 head_dim: usize,
78 scaling: f32,
79 sliding_window: Option<usize>,
82}
83
84#[derive(Module, Debug)]
86pub struct AceStepAttention<B: Backend> {
87 pub q_proj: Linear<B>,
88 pub k_proj: Linear<B>,
89 pub v_proj: Linear<B>,
90 pub o_proj: Linear<B>,
91 pub q_norm: ConditionRmsNorm<B>,
92 pub k_norm: ConditionRmsNorm<B>,
93 #[module(skip)]
94 meta: AttentionMeta,
95}
96
97impl<B: Backend> AceStepAttention<B> {
98 pub fn new(config: &AceStepConfig, layer_idx: usize, device: &B::Device) -> Self {
99 let head_dim = config.head_dim;
100 let sliding_window = config
101 .is_sliding_layer(layer_idx)
102 .then_some(config.sliding_window);
103 Self {
104 q_proj: linear_no_bias(
105 device,
106 config.hidden_size,
107 config.num_attention_heads * head_dim,
108 ),
109 k_proj: linear_no_bias(
110 device,
111 config.hidden_size,
112 config.num_key_value_heads * head_dim,
113 ),
114 v_proj: linear_no_bias(
115 device,
116 config.hidden_size,
117 config.num_key_value_heads * head_dim,
118 ),
119 o_proj: linear_no_bias(
120 device,
121 config.num_attention_heads * head_dim,
122 config.hidden_size,
123 ),
124 q_norm: ConditionRmsNorm::new(device, head_dim, config.rms_norm_eps),
125 k_norm: ConditionRmsNorm::new(device, head_dim, config.rms_norm_eps),
126 meta: AttentionMeta {
127 num_heads: config.num_attention_heads,
128 num_kv_heads: config.num_key_value_heads,
129 head_dim,
130 scaling: (head_dim as f32).powf(-0.5),
131 sliding_window,
132 },
133 }
134 }
135
136 pub fn forward(
140 &self,
141 hidden: Tensor<B, 3>,
142 cos: &Tensor<B, 4>,
143 sin: &Tensor<B, 4>,
144 additive_mask: Option<Tensor<B, 4>>,
145 ) -> Tensor<B, 3> {
146 let [batch, seq_len, _] = hidden.dims();
147 let num_heads = self.meta.num_heads;
148 let num_kv_heads = self.meta.num_kv_heads;
149 let head_dim = self.meta.head_dim;
150
151 let q = self
152 .q_proj
153 .forward(hidden.clone())
154 .reshape([batch, seq_len, num_heads, head_dim]);
155 let k =
156 self.k_proj
157 .forward(hidden.clone())
158 .reshape([batch, seq_len, num_kv_heads, head_dim]);
159 let v = self
160 .v_proj
161 .forward(hidden)
162 .reshape([batch, seq_len, num_kv_heads, head_dim]);
163
164 let q = apply_rotary_pos_emb(self.q_norm.forward(q), cos, sin).swap_dims(1, 2);
166 let k = apply_rotary_pos_emb(self.k_norm.forward(k), cos, sin).swap_dims(1, 2);
167 let v = v.swap_dims(1, 2);
168
169 let (k, v) = if num_heads != num_kv_heads {
170 let repeats = num_heads / num_kv_heads;
171 (repeat_kv(k, repeats), repeat_kv(v, repeats))
172 } else {
173 (k, v)
174 };
175
176 let scores = q.matmul(k.swap_dims(2, 3)).mul_scalar(self.meta.scaling);
177 let scores = match additive_mask {
178 Some(mask) => scores + mask,
179 None => scores,
180 };
181 let dtype = scores.dtype();
183 let weights = softmax(scores.cast(DType::F32), 3).cast(dtype);
184 let attended =
185 weights
186 .matmul(v)
187 .swap_dims(1, 2)
188 .reshape([batch, seq_len, num_heads * head_dim]);
189 self.o_proj.forward(attended)
190 }
191}
192
193#[derive(Module, Debug)]
195pub struct AceStepMlp<B: Backend> {
196 pub gate_proj: Linear<B>,
197 pub up_proj: Linear<B>,
198 pub down_proj: Linear<B>,
199}
200
201impl<B: Backend> AceStepMlp<B> {
202 pub fn new(config: &AceStepConfig, device: &B::Device) -> Self {
203 Self {
204 gate_proj: linear_no_bias(device, config.hidden_size, config.intermediate_size),
205 up_proj: linear_no_bias(device, config.hidden_size, config.intermediate_size),
206 down_proj: linear_no_bias(device, config.intermediate_size, config.hidden_size),
207 }
208 }
209
210 pub fn forward(&self, hidden: Tensor<B, 3>) -> Tensor<B, 3> {
211 let gate = silu(self.gate_proj.forward(hidden.clone()));
212 let up = self.up_proj.forward(hidden);
213 self.down_proj.forward(gate * up)
214 }
215}
216
217#[derive(Module, Debug)]
221pub struct AceStepEncoderLayer<B: Backend> {
222 pub self_attn: AceStepAttention<B>,
223 pub mlp: AceStepMlp<B>,
224 pub input_layernorm: ConditionRmsNorm<B>,
225 pub post_attention_layernorm: ConditionRmsNorm<B>,
226}
227
228impl<B: Backend> AceStepEncoderLayer<B> {
229 pub fn new(config: &AceStepConfig, layer_idx: usize, device: &B::Device) -> Self {
230 Self {
231 self_attn: AceStepAttention::new(config, layer_idx, device),
232 mlp: AceStepMlp::new(config, device),
233 input_layernorm: ConditionRmsNorm::new(device, config.hidden_size, config.rms_norm_eps),
234 post_attention_layernorm: ConditionRmsNorm::new(
235 device,
236 config.hidden_size,
237 config.rms_norm_eps,
238 ),
239 }
240 }
241
242 pub fn forward(
247 &self,
248 hidden: Tensor<B, 3>,
249 cos: &Tensor<B, 4>,
250 sin: &Tensor<B, 4>,
251 padding_mask: Option<&Tensor<B, 2, Int>>,
252 ) -> Tensor<B, 3> {
253 let sliding_window = self.self_attn.meta.sliding_window;
254 let additive_mask = if sliding_window.is_some() || padding_mask.is_some() {
255 let [batch, seq_len, _] = hidden.dims();
256 let device = hidden.device();
257 let padding = padding_mask.map(|mask| {
258 mask.clone()
259 .to_data()
260 .convert::<i64>()
261 .to_vec::<i64>()
262 .expect("padding mask must materialize as i64")
263 });
264 Some(additive_attention_mask::<B>(
265 batch,
266 seq_len,
267 sliding_window,
268 padding,
269 &device,
270 ))
271 } else {
272 None
273 };
274
275 let residual = hidden.clone();
276 let normed = self.input_layernorm.forward(hidden);
277 let hidden = residual + self.self_attn.forward(normed, cos, sin, additive_mask);
278
279 let residual = hidden.clone();
280 let normed = self.post_attention_layernorm.forward(hidden);
281 residual + self.mlp.forward(normed)
282 }
283}
284
285#[derive(Module, Debug)]
289pub struct LyricEncoder<B: Backend> {
290 pub embed_tokens: Linear<B>,
291 pub layers: Vec<AceStepEncoderLayer<B>>,
292 pub norm: ConditionRmsNorm<B>,
293 head_dim: usize,
294 rope_theta: f64,
295}
296
297impl<B: Backend> LyricEncoder<B> {
298 pub fn new(config: &AceStepConfig, device: &B::Device) -> Self {
299 Self {
300 embed_tokens: linear_with_bias(device, config.text_hidden_dim, config.hidden_size),
301 layers: (0..config.num_lyric_encoder_hidden_layers)
302 .map(|layer_idx| AceStepEncoderLayer::new(config, layer_idx, device))
303 .collect(),
304 norm: ConditionRmsNorm::new(device, config.hidden_size, config.rms_norm_eps),
305 head_dim: config.head_dim,
306 rope_theta: config.rope_theta,
307 }
308 }
309
310 pub fn forward(
316 &self,
317 lyric_hidden_states: Tensor<B, 3>,
318 lyric_mask: Tensor<B, 2, Int>,
319 ) -> Tensor<B, 3> {
320 let [_, seq_len, _] = lyric_hidden_states.dims();
321 let device = lyric_hidden_states.device();
322 let (cos, sin) = rotary_cos_sin::<B>(seq_len, self.head_dim, self.rope_theta, &device);
323
324 let mut hidden = self.embed_tokens.forward(lyric_hidden_states);
325 for layer in &self.layers {
326 hidden = layer.forward(hidden, &cos, &sin, Some(&lyric_mask));
327 }
328 self.norm.forward(hidden)
329 }
330}
331
332#[derive(Module, Debug)]
337pub struct TimbreEncoder<B: Backend> {
338 pub embed_tokens: Linear<B>,
339 pub layers: Vec<AceStepEncoderLayer<B>>,
340 pub norm: ConditionRmsNorm<B>,
341 head_dim: usize,
342 rope_theta: f64,
343}
344
345impl<B: Backend> TimbreEncoder<B> {
346 pub fn new(config: &AceStepConfig, device: &B::Device) -> Self {
347 Self {
348 embed_tokens: linear_with_bias(
349 device,
350 config.audio_acoustic_hidden_dim,
351 config.hidden_size,
352 ),
353 layers: (0..config.num_timbre_encoder_hidden_layers)
354 .map(|layer_idx| AceStepEncoderLayer::new(config, layer_idx, device))
355 .collect(),
356 norm: ConditionRmsNorm::new(device, config.hidden_size, config.rms_norm_eps),
357 head_dim: config.head_dim,
358 rope_theta: config.rope_theta,
359 }
360 }
361
362 pub fn forward(&self, refer_latents: Tensor<B, 3>) -> Tensor<B, 3> {
366 let [batch, seq_len, _] = refer_latents.dims();
367 let device = refer_latents.device();
368 let (cos, sin) = rotary_cos_sin::<B>(seq_len, self.head_dim, self.rope_theta, &device);
369
370 let mut hidden = self.embed_tokens.forward(refer_latents);
371 for layer in &self.layers {
372 hidden = layer.forward(hidden, &cos, &sin, None);
373 }
374 let hidden = self.norm.forward(hidden);
375 let hidden_dim = self.norm.weight.dims()[0];
376 hidden.slice([0..batch, 0..1, 0..hidden_dim])
377 }
378}
379
380#[derive(Module, Debug)]
385pub struct AudioTokenDetokenizer<B: Backend> {
386 pub embed_tokens: Linear<B>,
387 pub special_tokens: Param<Tensor<B, 3>>,
388 pub layers: Vec<AceStepEncoderLayer<B>>,
389 pub norm: ConditionRmsNorm<B>,
390 pub proj_out: Linear<B>,
391 pool_window_size: usize,
392 head_dim: usize,
393 rope_theta: f64,
394 acoustic_dim: usize,
395}
396
397impl<B: Backend> AudioTokenDetokenizer<B> {
398 pub fn new(config: &AceStepConfig, device: &B::Device) -> Self {
399 Self {
400 embed_tokens: linear_with_bias(device, config.hidden_size, config.hidden_size),
401 special_tokens: Param::from_tensor(Tensor::<B, 3>::zeros(
402 [1, config.pool_window_size, config.hidden_size],
403 device,
404 )),
405 layers: (0..config.num_attention_pooler_hidden_layers)
406 .map(|layer_idx| AceStepEncoderLayer::new(config, layer_idx, device))
407 .collect(),
408 norm: ConditionRmsNorm::new(device, config.hidden_size, config.rms_norm_eps),
409 proj_out: linear_with_bias(
410 device,
411 config.hidden_size,
412 config.audio_acoustic_hidden_dim,
413 ),
414 pool_window_size: config.pool_window_size,
415 head_dim: config.head_dim,
416 rope_theta: config.rope_theta,
417 acoustic_dim: config.audio_acoustic_hidden_dim,
418 }
419 }
420
421 pub fn forward(&self, q: Tensor<B, 3>) -> Tensor<B, 3> {
424 let [batch, t5, _] = q.dims();
425 let device = q.device();
426 let (cos, sin) = rotary_cos_sin::<B>(
427 self.pool_window_size,
428 self.head_dim,
429 self.rope_theta,
430 &device,
431 );
432
433 let embedded = self.embed_tokens.forward(q);
434 let mut hidden = self.expand_codes(embedded);
435 for layer in &self.layers {
436 hidden = layer.forward(hidden, &cos, &sin, None);
437 }
438 let hidden = self.norm.forward(hidden);
439 self.proj_out.forward(hidden).reshape([
440 batch,
441 t5 * self.pool_window_size,
442 self.acoustic_dim,
443 ])
444 }
445
446 fn expand_codes(&self, embedded: Tensor<B, 3>) -> Tensor<B, 3> {
449 let [batch, t5, hidden_size] = embedded.dims();
450 let repeated = embedded
451 .unsqueeze_dim::<4>(2)
452 .repeat_dim(2, self.pool_window_size);
453 let special = self.special_tokens.val().unsqueeze_dim::<4>(1);
454 (repeated + special).reshape([batch * t5, self.pool_window_size, hidden_size])
455 }
456}
457
458#[derive(Module, Debug)]
462pub struct AceStepCondition<B: Backend> {
463 pub text_projector: Linear<B>,
464 pub lyric_encoder: LyricEncoder<B>,
465 pub timbre_encoder: TimbreEncoder<B>,
466 pub detokenizer: AudioTokenDetokenizer<B>,
467 pub quantizer: ResidualFsq<B>,
468}
469
470impl<B: Backend> AceStepCondition<B> {
471 pub fn new(config: &AceStepConfig, device: &B::Device) -> Self {
472 Self {
473 text_projector: linear_no_bias(device, config.text_hidden_dim, config.hidden_size),
474 lyric_encoder: LyricEncoder::new(config, device),
475 timbre_encoder: TimbreEncoder::new(config, device),
476 detokenizer: AudioTokenDetokenizer::new(config, device),
477 quantizer: ResidualFsq::new(device),
478 }
479 }
480
481 pub fn from_burnpack(config: &AceStepConfig, path: &Path, device: &B::Device) -> Result<Self> {
484 let mut model = Self::new(config, device);
485 let mut store = BurnpackStore::from_file(path).zero_copy(true);
486 model
487 .load_from(&mut store)
488 .with_context(|| format!("failed to load condition weights from {}", path.display()))?;
489 Ok(model)
490 }
491
492 pub fn encode(
507 &self,
508 text_hidden: Tensor<B, 3>,
509 lyric_hidden: Tensor<B, 3>,
510 lyric_mask: Tensor<B, 2, Int>,
511 silence_ref_latents: Tensor<B, 3>,
512 ) -> Tensor<B, 3> {
513 let [batch, text_len, _] = text_hidden.dims();
514 let device = text_hidden.device();
515
516 let lyric = self.lyric_encoder.forward(lyric_hidden, lyric_mask.clone());
517 let timbre = self.timbre_encoder.forward(silence_ref_latents);
518 let text = self.text_projector.forward(text_hidden);
519
520 let timbre_mask = Tensor::<B, 2, Int>::ones([batch, 1], &device);
521 let text_mask = Tensor::<B, 2, Int>::ones([batch, text_len], &device);
522
523 let (packed, mask) = pack_sequences(lyric, timbre, lyric_mask, timbre_mask);
524 let (encoder_hidden_states, _final_mask) = pack_sequences(packed, text, mask, text_mask);
525 encoder_hidden_states
526 }
527
528 pub fn codes_to_hints(&self, audio_codes: Tensor<B, 2, Int>) -> Tensor<B, 3> {
533 let hints = self.quantizer.decode_indices(audio_codes);
534 self.detokenizer.forward(hints)
535 }
536}
537
538pub fn pack_sequences<B: Backend>(
546 hidden1: Tensor<B, 3>,
547 hidden2: Tensor<B, 3>,
548 mask1: Tensor<B, 2, Int>,
549 mask2: Tensor<B, 2, Int>,
550) -> (Tensor<B, 3>, Tensor<B, 2, Int>) {
551 let [batch, len1, dim] = hidden1.dims();
552 let len2 = hidden2.dims()[1];
553 let len = len1 + len2;
554 let dtype = hidden1.dtype();
555 let device = hidden1.device();
556
557 let hidden1_data = hidden1
558 .to_data()
559 .convert::<f32>()
560 .to_vec::<f32>()
561 .expect("hidden1 must materialize as f32");
562 let hidden2_data = hidden2
563 .to_data()
564 .convert::<f32>()
565 .to_vec::<f32>()
566 .expect("hidden2 must materialize as f32");
567 let mask1_data = mask1
568 .to_data()
569 .convert::<i64>()
570 .to_vec::<i64>()
571 .expect("mask1 must materialize as i64");
572 let mask2_data = mask2
573 .to_data()
574 .convert::<i64>()
575 .to_vec::<i64>()
576 .expect("mask2 must materialize as i64");
577
578 let mut packed = vec![0.0f32; batch * len * dim];
579 let mut new_mask = vec![0i64; batch * len];
580
581 for b in 0..batch {
582 let mut order: Vec<(i64, usize)> = (0..len1)
585 .map(|i| (mask1_data[b * len1 + i], i))
586 .chain((0..len2).map(|j| (mask2_data[b * len2 + j], len1 + j)))
587 .collect();
588 order.sort_by_key(|&(mask, _)| std::cmp::Reverse(mask));
589
590 let valid_count = order.iter().filter(|&&(mask, _)| mask > 0).count();
591 for (new_pos, &(_, old_pos)) in order.iter().enumerate() {
592 let source = if old_pos < len1 {
593 &hidden1_data[(b * len1 + old_pos) * dim..][..dim]
594 } else {
595 &hidden2_data[(b * len2 + old_pos - len1) * dim..][..dim]
596 };
597 packed[(b * len + new_pos) * dim..][..dim].copy_from_slice(source);
598 }
599 for (new_pos, slot) in new_mask[b * len..(b + 1) * len].iter_mut().enumerate() {
600 *slot = i64::from(new_pos < valid_count);
601 }
602 }
603
604 let packed =
605 Tensor::<B, 3>::from_data(TensorData::new(packed, [batch, len, dim]), &device).cast(dtype);
606 let new_mask = Tensor::<B, 2, Int>::from_data(TensorData::new(new_mask, [batch, len]), &device);
607 (packed, new_mask)
608}
609
610fn additive_attention_mask<B: Backend>(
614 batch: usize,
615 seq_len: usize,
616 sliding_window: Option<usize>,
617 padding: Option<Vec<i64>>,
618 device: &B::Device,
619) -> Tensor<B, 4> {
620 let mut values = vec![f32::MIN; batch * seq_len * seq_len];
621 for b in 0..batch {
622 for i in 0..seq_len {
623 for j in 0..seq_len {
624 let within_window = sliding_window.is_none_or(|window| i.abs_diff(j) <= window);
625 let key_valid = padding
626 .as_ref()
627 .is_none_or(|mask| mask[b * seq_len + j] != 0);
628 if within_window && key_valid {
629 values[(b * seq_len + i) * seq_len + j] = 0.0;
630 }
631 }
632 }
633 }
634 Tensor::<B, 4>::from_data(
635 TensorData::new(values, [batch, 1, seq_len, seq_len]),
636 device,
637 )
638}
639
640fn rotary_cos_sin<B: Backend>(
644 seq_len: usize,
645 head_dim: usize,
646 theta: f64,
647 device: &B::Device,
648) -> (Tensor<B, 4>, Tensor<B, 4>) {
649 let half = head_dim / 2;
650 let inv_freq: Vec<f32> = (0..half)
651 .map(|i| 1.0 / theta.powf(2.0 * i as f64 / head_dim as f64) as f32)
652 .collect();
653 let mut cos_values = Vec::with_capacity(seq_len * head_dim);
654 let mut sin_values = Vec::with_capacity(seq_len * head_dim);
655 for pos in 0..seq_len {
656 for _ in 0..2 {
657 for &freq in &inv_freq {
658 let angle = pos as f32 * freq;
659 cos_values.push(angle.cos());
660 sin_values.push(angle.sin());
661 }
662 }
663 }
664 let shape = [1, seq_len, 1, head_dim];
665 (
666 Tensor::<B, 4>::from_data(TensorData::new(cos_values, shape), device),
667 Tensor::<B, 4>::from_data(TensorData::new(sin_values, shape), device),
668 )
669}
670
671fn rotate_half<B: Backend>(x: Tensor<B, 4>) -> Tensor<B, 4> {
672 let [batch, seq_len, heads, head_dim] = x.dims();
673 let half = head_dim / 2;
674 let x1 = x.clone().slice([0..batch, 0..seq_len, 0..heads, 0..half]);
675 let x2 = x.slice([0..batch, 0..seq_len, 0..heads, half..head_dim]);
676 Tensor::cat(vec![x2.neg(), x1], 3)
677}
678
679fn apply_rotary_pos_emb<B: Backend>(
680 x: Tensor<B, 4>,
681 cos: &Tensor<B, 4>,
682 sin: &Tensor<B, 4>,
683) -> Tensor<B, 4> {
684 x.clone() * cos.clone() + rotate_half(x) * sin.clone()
685}
686
687fn repeat_kv<B: Backend>(tensor: Tensor<B, 4>, repeats: usize) -> Tensor<B, 4> {
689 let [batch, heads, seq_len, head_dim] = tensor.dims();
690 tensor
691 .unsqueeze_dim::<5>(2)
692 .repeat_dim(2, repeats)
693 .reshape([batch, heads * repeats, seq_len, head_dim])
694}
695
696fn linear_no_bias<B: Backend>(device: &B::Device, d_input: usize, d_output: usize) -> Linear<B> {
697 LinearConfig::new(d_input, d_output)
698 .with_bias(false)
699 .with_layout(LinearLayout::Col)
700 .init(device)
701}
702
703fn linear_with_bias<B: Backend>(device: &B::Device, d_input: usize, d_output: usize) -> Linear<B> {
704 LinearConfig::new(d_input, d_output)
705 .with_layout(LinearLayout::Col)
706 .init(device)
707}
708
709#[cfg(test)]
710mod tests {
711 use super::*;
712 use burn::backend::NdArray;
713
714 type TestBackend = NdArray<f32>;
715
716 fn tiny_config() -> AceStepConfig {
717 AceStepConfig {
718 hidden_size: 32,
719 intermediate_size: 64,
720 num_hidden_layers: 2,
721 num_attention_heads: 4,
722 num_key_value_heads: 2,
723 head_dim: 8,
724 rms_norm_eps: 1e-6,
725 rope_theta: 1_000_000.0,
726 sliding_window: 2,
727 in_channels: 12,
728 audio_acoustic_hidden_dim: 4,
729 patch_size: 2,
730 text_hidden_dim: 8,
731 num_lyric_encoder_hidden_layers: 2,
732 num_timbre_encoder_hidden_layers: 2,
733 timbre_fix_frame: 750,
734 pool_window_size: 5,
735 num_attention_pooler_hidden_layers: 2,
736 fsq_dim: 32,
737 fsq_input_levels: vec![8, 8, 8, 5, 5, 5],
738 vocab_size: 64003,
739 layer_types: vec![
740 "sliding_attention".to_string(),
741 "full_attention".to_string(),
742 ],
743 is_turbo: true,
744 }
745 }
746
747 fn assert_finite(tensor: Tensor<TestBackend, 3>) {
748 let values = tensor
749 .to_data()
750 .to_vec::<f32>()
751 .expect("output should materialize as f32");
752 assert!(
753 values.iter().all(|v| v.is_finite()),
754 "output contains non-finite values"
755 );
756 }
757
758 #[test]
759 fn encode_shapes_and_finiteness() {
760 let config = tiny_config();
761 let device = Default::default();
762 let condition = AceStepCondition::<TestBackend>::new(&config, &device);
763
764 let text_hidden =
765 Tensor::from_data(TensorData::new(vec![0.01f32; 3 * 8], [1, 3, 8]), &device);
766 let lyric_hidden =
767 Tensor::from_data(TensorData::new(vec![-0.02f32; 2 * 8], [1, 2, 8]), &device);
768 let lyric_mask = Tensor::<TestBackend, 2, Int>::from_data([[1, 1]], &device);
769 let silence_ref_latents = Tensor::zeros([1, 750, 4], &device);
770
771 let encoded = condition.encode(text_hidden, lyric_hidden, lyric_mask, silence_ref_latents);
772 assert_eq!(encoded.dims(), [1, 6, 32]);
774 assert_finite(encoded);
775 }
776
777 #[test]
778 fn pack_sequences_stable_sorts_valid_first() {
779 let device = Default::default();
780 let hidden1 = Tensor::<TestBackend, 3>::from_data(
783 [[[10.0], [20.0], [30.0]], [[60.0], [70.0], [80.0]]],
784 &device,
785 );
786 let hidden2 =
787 Tensor::<TestBackend, 3>::from_data([[[40.0], [50.0]], [[90.0], [100.0]]], &device);
788 let mask1 = Tensor::<TestBackend, 2, Int>::from_data([[1, 0, 1], [0, 1, 0]], &device);
789 let mask2 = Tensor::<TestBackend, 2, Int>::from_data([[0, 1], [1, 0]], &device);
790
791 let (packed, new_mask) = pack_sequences(hidden1, hidden2, mask1, mask2);
792 assert_eq!(packed.dims(), [2, 5, 1]);
793 let packed_values = packed.to_data().to_vec::<f32>().expect("packed values");
794 assert_eq!(
795 packed_values,
796 vec![10.0, 30.0, 50.0, 20.0, 40.0, 70.0, 90.0, 60.0, 80.0, 100.0]
797 );
798 let mask_values: Vec<i64> = new_mask
799 .to_data()
800 .convert::<i64>()
801 .to_vec::<i64>()
802 .expect("mask values");
803 assert_eq!(mask_values, vec![1, 1, 1, 0, 0, 1, 1, 0, 0, 0]);
804 }
805
806 #[test]
807 fn codes_to_hints_shape_and_finiteness() {
808 let config = tiny_config();
809 let device = Default::default();
810 let mut condition = AceStepCondition::<TestBackend>::new(&config, &device);
811 condition.quantizer = ResidualFsq {
814 project_in: LinearConfig::new(32, 6).with_bias(true).init(&device),
815 project_out: LinearConfig::new(6, 32).with_bias(true).init(&device),
816 };
817
818 let codes = Tensor::<TestBackend, 2, Int>::from_data([[0, 63999]], &device);
819 let hints = condition.codes_to_hints(codes);
820 assert_eq!(hints.dims(), [1, 10, 4]);
821 assert_finite(hints);
822 }
823
824 #[test]
825 fn detokenizer_expand_repeats_and_adds_special_tokens() {
826 let config = tiny_config();
827 let device = Default::default();
828 let mut detokenizer = AudioTokenDetokenizer::<TestBackend>::new(&config, &device);
829
830 let special: Vec<f32> = (0..5 * 32).map(|i| i as f32 * 0.001).collect();
832 detokenizer.special_tokens = Param::from_tensor(Tensor::from_data(
833 TensorData::new(special, [1, 5, 32]),
834 &device,
835 ));
836
837 let q: Vec<f32> = (0..2 * 32).map(|i| i as f32 * 0.01).collect();
838 let q = Tensor::<TestBackend, 3>::from_data(TensorData::new(q, [1, 2, 32]), &device);
839 let embedded = detokenizer.embed_tokens.forward(q);
840 let expanded = detokenizer.expand_codes(embedded.clone());
841 assert_eq!(expanded.dims(), [2, 5, 32]);
842
843 let embedded_values = embedded.to_data().to_vec::<f32>().expect("embedded");
844 let expanded_values = expanded.to_data().to_vec::<f32>().expect("expanded");
845 for t in 0..2 {
846 for p in 0..5 {
847 for h in 0..32 {
848 let expected = embedded_values[t * 32 + h] + (p * 32 + h) as f32 * 0.001;
849 let actual = expanded_values[(t * 5 + p) * 32 + h];
850 assert!(
851 (actual - expected).abs() < 1e-5,
852 "mismatch at t={t} p={p} h={h}: {actual} vs {expected}"
853 );
854 }
855 }
856 }
857 }
858
859 #[test]
860 fn lyric_encoder_ignores_padded_values() {
861 let config = tiny_config();
862 let device = Default::default();
863 let encoder = LyricEncoder::<TestBackend>::new(&config, &device);
864 let mask = Tensor::<TestBackend, 2, Int>::from_data([[1, 0]], &device);
865
866 let mut row_a = vec![0.05f32; 8];
867 row_a.extend(vec![1.0f32; 8]);
868 let mut row_b = vec![0.05f32; 8];
869 row_b.extend(vec![-7.0f32; 8]);
870 let input_a =
871 Tensor::<TestBackend, 3>::from_data(TensorData::new(row_a, [1, 2, 8]), &device);
872 let input_b =
873 Tensor::<TestBackend, 3>::from_data(TensorData::new(row_b, [1, 2, 8]), &device);
874
875 let out_a = encoder.forward(input_a, mask.clone());
876 let out_b = encoder.forward(input_b, mask);
877 let valid_a = out_a
878 .slice([0..1, 0..1, 0..32])
879 .to_data()
880 .to_vec::<f32>()
881 .expect("out_a");
882 let valid_b = out_b
883 .slice([0..1, 0..1, 0..32])
884 .to_data()
885 .to_vec::<f32>()
886 .expect("out_b");
887 for (a, b) in valid_a.iter().zip(valid_b.iter()) {
888 assert!(
889 (a - b).abs() < 1e-5,
890 "padded key values leaked into valid output: {a} vs {b}"
891 );
892 }
893 }
894}