1use std::path::Path;
53
54use anyhow::{Context, Result};
55use burn::module::{Module, Param};
56use burn::nn::conv::{Conv1d, Conv1dConfig, ConvTranspose1d, ConvTranspose1dConfig};
57use burn::nn::{Linear, LinearConfig, LinearLayout};
58use burn::prelude::Backend;
59use burn::tensor::activation::{silu, softmax};
60use burn::tensor::{DType, Distribution, Tensor, TensorData};
61use burn_store::{BurnpackStore, ModuleSnapshot};
62
63use crate::acestep::config::AceStepConfig;
64use crate::acestep::qwen3::Qwen3RmsNorm;
65
66pub const TURBO_TIMESTEPS: [f32; 8] = [
70 1.0,
71 0.954_545_44,
72 0.9,
73 0.833_333_3,
74 0.75,
75 0.642_857_13,
76 0.5,
77 0.3,
78];
79
80pub const SFT_TIMESTEPS: [f32; 50] = {
83 let mut schedule = [0.0_f32; 50];
84 let mut i = 0;
85 while i < 50 {
86 schedule[i] = 1.0 - i as f32 / 50.0;
87 i += 1;
88 }
89 schedule
90};
91
92const TIME_EMBED_CHANNELS: usize = 256;
94const TIME_EMBED_SCALE: f32 = 1000.0;
96const TIME_EMBED_MAX_PERIOD: f32 = 10_000.0;
98const MASK_MIN: f32 = -1.0e30;
100
101#[derive(Module, Debug)]
104pub struct AceStepAttention<B: Backend> {
105 pub q_proj: Linear<B>,
106 pub k_proj: Linear<B>,
107 pub v_proj: Linear<B>,
108 pub o_proj: Linear<B>,
109 pub q_norm: Qwen3RmsNorm<B>,
110 pub k_norm: Qwen3RmsNorm<B>,
111 #[module(skip)]
112 meta: AttentionMeta,
113}
114
115#[derive(Clone, Debug)]
116struct AttentionMeta {
117 num_heads: usize,
118 num_kv_heads: usize,
119 head_dim: usize,
120 scaling: f32,
121}
122
123impl<B: Backend> AceStepAttention<B> {
124 fn new(config: &AceStepConfig, device: &B::Device) -> Self {
125 let head_dim = config.head_dim;
126 Self {
127 q_proj: linear_no_bias(
128 device,
129 config.hidden_size,
130 config.num_attention_heads * head_dim,
131 ),
132 k_proj: linear_no_bias(
133 device,
134 config.hidden_size,
135 config.num_key_value_heads * head_dim,
136 ),
137 v_proj: linear_no_bias(
138 device,
139 config.hidden_size,
140 config.num_key_value_heads * head_dim,
141 ),
142 o_proj: linear_no_bias(
143 device,
144 config.num_attention_heads * head_dim,
145 config.hidden_size,
146 ),
147 q_norm: Qwen3RmsNorm::new(device, head_dim, config.rms_norm_eps),
148 k_norm: Qwen3RmsNorm::new(device, head_dim, config.rms_norm_eps),
149 meta: AttentionMeta {
150 num_heads: config.num_attention_heads,
151 num_kv_heads: config.num_key_value_heads,
152 head_dim,
153 scaling: (head_dim as f32).powf(-0.5),
154 },
155 }
156 }
157
158 pub fn project_kv(&self, encoder: &Tensor<B, 3>) -> (Tensor<B, 4>, Tensor<B, 4>) {
163 let [batch, seq_len, _] = encoder.dims();
164 let kv_heads = self.meta.num_kv_heads;
165 let head_dim = self.meta.head_dim;
166
167 let k = self
168 .k_norm
169 .forward(
170 self.k_proj
171 .forward(encoder.clone())
172 .reshape([batch, seq_len, kv_heads, head_dim]),
173 )
174 .swap_dims(1, 2);
175 let v = self
176 .v_proj
177 .forward(encoder.clone())
178 .reshape([batch, seq_len, kv_heads, head_dim])
179 .swap_dims(1, 2);
180
181 let repeats = self.meta.num_heads / self.meta.num_kv_heads;
182 (repeat_kv(k, repeats), repeat_kv(v, repeats))
183 }
184
185 pub fn forward_self(
188 &self,
189 hidden: Tensor<B, 3>,
190 cos: &Tensor<B, 4>,
191 sin: &Tensor<B, 4>,
192 mask: Option<&Tensor<B, 2>>,
193 ) -> Tensor<B, 3> {
194 let [batch, seq_len, _] = hidden.dims();
195 let num_heads = self.meta.num_heads;
196 let kv_heads = self.meta.num_kv_heads;
197 let head_dim = self.meta.head_dim;
198
199 let q = self.q_norm.forward(
200 self.q_proj
201 .forward(hidden.clone())
202 .reshape([batch, seq_len, num_heads, head_dim]),
203 );
204 let k = self.k_norm.forward(
205 self.k_proj
206 .forward(hidden.clone())
207 .reshape([batch, seq_len, kv_heads, head_dim]),
208 );
209 let v = self
210 .v_proj
211 .forward(hidden)
212 .reshape([batch, seq_len, kv_heads, head_dim]);
213
214 let q = apply_rotary_pos_emb(q, cos, sin).swap_dims(1, 2);
216 let k = apply_rotary_pos_emb(k, cos, sin).swap_dims(1, 2);
217 let v = v.swap_dims(1, 2);
218
219 let repeats = num_heads / kv_heads;
220 let k = repeat_kv(k, repeats);
221 let v = repeat_kv(v, repeats);
222
223 let attended = self.attend(q, k, v, mask);
224 let attended = attended
225 .swap_dims(1, 2)
226 .reshape([batch, seq_len, num_heads * head_dim]);
227 self.o_proj.forward(attended)
228 }
229
230 pub fn forward_cross(
233 &self,
234 hidden: Tensor<B, 3>,
235 key: &Tensor<B, 4>,
236 value: &Tensor<B, 4>,
237 ) -> Tensor<B, 3> {
238 let [batch, seq_len, _] = hidden.dims();
239 let num_heads = self.meta.num_heads;
240 let head_dim = self.meta.head_dim;
241
242 let q = self
243 .q_norm
244 .forward(
245 self.q_proj
246 .forward(hidden)
247 .reshape([batch, seq_len, num_heads, head_dim]),
248 )
249 .swap_dims(1, 2);
250
251 let attended = self.attend(q, key.clone(), value.clone(), None);
252 let attended = attended
253 .swap_dims(1, 2)
254 .reshape([batch, seq_len, num_heads * head_dim]);
255 self.o_proj.forward(attended)
256 }
257
258 fn attend(
261 &self,
262 q: Tensor<B, 4>,
263 k: Tensor<B, 4>,
264 v: Tensor<B, 4>,
265 mask: Option<&Tensor<B, 2>>,
266 ) -> Tensor<B, 4> {
267 let dtype = q.dtype();
268 let mut scores = q.matmul(k.swap_dims(2, 3)).mul_scalar(self.meta.scaling);
269 if let Some(mask) = mask {
270 let [rows, cols] = mask.dims();
271 scores = scores + mask.clone().reshape([1, 1, rows, cols]);
272 }
273 let weights = softmax(scores.cast(DType::F32), 3).cast(dtype);
274 weights.matmul(v)
275 }
276}
277
278#[derive(Module, Debug)]
280pub struct AceStepMlp<B: Backend> {
281 pub gate_proj: Linear<B>,
282 pub up_proj: Linear<B>,
283 pub down_proj: Linear<B>,
284}
285
286impl<B: Backend> AceStepMlp<B> {
287 fn new(config: &AceStepConfig, device: &B::Device) -> Self {
288 Self {
289 gate_proj: linear_no_bias(device, config.hidden_size, config.intermediate_size),
290 up_proj: linear_no_bias(device, config.hidden_size, config.intermediate_size),
291 down_proj: linear_no_bias(device, config.intermediate_size, config.hidden_size),
292 }
293 }
294
295 pub fn forward(&self, hidden: Tensor<B, 3>) -> Tensor<B, 3> {
296 let gate = silu(self.gate_proj.forward(hidden.clone()));
297 let up = self.up_proj.forward(hidden);
298 self.down_proj.forward(gate * up)
299 }
300}
301
302#[derive(Module, Debug)]
306pub struct AceStepDiTLayer<B: Backend> {
307 pub self_attn_norm: Qwen3RmsNorm<B>,
308 pub self_attn: AceStepAttention<B>,
309 pub cross_attn_norm: Qwen3RmsNorm<B>,
310 pub cross_attn: AceStepAttention<B>,
311 pub mlp_norm: Qwen3RmsNorm<B>,
312 pub mlp: AceStepMlp<B>,
313 pub scale_shift_table: Param<Tensor<B, 3>>,
315 #[module(skip)]
316 sliding: bool,
317}
318
319impl<B: Backend> AceStepDiTLayer<B> {
320 fn new(config: &AceStepConfig, layer: usize, device: &B::Device) -> Self {
321 let hidden = config.hidden_size;
322 Self {
323 self_attn_norm: Qwen3RmsNorm::new(device, hidden, config.rms_norm_eps),
324 self_attn: AceStepAttention::new(config, device),
325 cross_attn_norm: Qwen3RmsNorm::new(device, hidden, config.rms_norm_eps),
326 cross_attn: AceStepAttention::new(config, device),
327 mlp_norm: Qwen3RmsNorm::new(device, hidden, config.rms_norm_eps),
328 mlp: AceStepMlp::new(config, device),
329 scale_shift_table: Param::from_tensor(randn_scaled([1, 6, hidden], hidden, device)),
330 sliding: config.is_sliding_layer(layer),
331 }
332 }
333
334 pub fn forward(
335 &self,
336 hidden: Tensor<B, 3>,
337 rope: (&Tensor<B, 4>, &Tensor<B, 4>),
338 timestep_proj: &Tensor<B, 3>,
339 cross_kv: (&Tensor<B, 4>, &Tensor<B, 4>),
340 mask: Option<&Tensor<B, 2>>,
341 ) -> Tensor<B, 3> {
342 let (cos, sin) = rope;
343 let (cross_key, cross_value) = cross_kv;
344 let modulation = self.scale_shift_table.val() + timestep_proj.clone();
346 let chunks = modulation.chunk(6, 1);
347 let (shift_msa, scale_msa, gate_msa) =
348 (chunks[0].clone(), chunks[1].clone(), chunks[2].clone());
349 let (c_shift_msa, c_scale_msa, c_gate_msa) =
350 (chunks[3].clone(), chunks[4].clone(), chunks[5].clone());
351
352 let normed = self.self_attn_norm.forward(hidden.clone()) * (scale_msa + 1.0) + shift_msa;
353 let attn = self.self_attn.forward_self(normed, cos, sin, mask);
354 let hidden = hidden + attn * gate_msa;
355
356 let normed = self.cross_attn_norm.forward(hidden.clone());
358 let hidden = hidden
359 + self
360 .cross_attn
361 .forward_cross(normed, cross_key, cross_value);
362
363 let normed = self.mlp_norm.forward(hidden.clone()) * (c_scale_msa + 1.0) + c_shift_msa;
364 hidden + self.mlp.forward(normed) * c_gate_msa
365 }
366}
367
368#[derive(Module, Debug)]
372pub struct TimestepEmbedding<B: Backend> {
373 pub linear_1: Linear<B>,
374 pub linear_2: Linear<B>,
375 pub time_proj: Linear<B>,
376 #[module(skip)]
377 meta: TimestepMeta,
378}
379
380#[derive(Clone, Debug)]
381struct TimestepMeta {
382 in_channels: usize,
383 embed_dim: usize,
384}
385
386impl<B: Backend> TimestepEmbedding<B> {
387 fn new(embed_dim: usize, device: &B::Device) -> Self {
388 Self {
389 linear_1: linear_with_bias(device, TIME_EMBED_CHANNELS, embed_dim),
390 linear_2: linear_with_bias(device, embed_dim, embed_dim),
391 time_proj: linear_with_bias(device, embed_dim, 6 * embed_dim),
392 meta: TimestepMeta {
393 in_channels: TIME_EMBED_CHANNELS,
394 embed_dim,
395 },
396 }
397 }
398
399 pub fn forward(&self, t: f32, device: &B::Device) -> (Tensor<B, 2>, Tensor<B, 3>) {
401 let sinusoid = sinusoidal_timestep::<B>(t, self.meta.in_channels, device);
402 let temb = self.linear_2.forward(silu(self.linear_1.forward(sinusoid)));
403 let proj = self
404 .time_proj
405 .forward(silu(temb.clone()))
406 .reshape([1, 6, self.meta.embed_dim]);
407 (temb, proj)
408 }
409}
410
411#[derive(Module, Debug)]
414pub struct PatchEmbed<B: Backend> {
415 pub conv: Conv1d<B>,
416}
417
418#[derive(Module, Debug)]
421pub struct PatchUnembed<B: Backend> {
422 pub conv: ConvTranspose1d<B>,
423}
424
425pub struct CrossKv<B: Backend> {
428 keys: Vec<Tensor<B, 4>>,
429 values: Vec<Tensor<B, 4>>,
430}
431
432#[derive(Module, Debug)]
434pub struct AceStepDiT<B: Backend> {
435 pub proj_in: PatchEmbed<B>,
436 pub time_embed: TimestepEmbedding<B>,
437 pub time_embed_r: TimestepEmbedding<B>,
438 pub condition_embedder: Linear<B>,
439 pub layers: Vec<AceStepDiTLayer<B>>,
440 pub norm_out: Qwen3RmsNorm<B>,
441 pub proj_out: PatchUnembed<B>,
442 pub scale_shift_table: Param<Tensor<B, 3>>,
444 #[module(skip)]
445 meta: DiTMeta,
446}
447
448#[derive(Clone, Debug)]
449struct DiTMeta {
450 head_dim: usize,
451 rope_theta: f64,
452 sliding_window: usize,
453 patch_size: usize,
454 any_sliding: bool,
455}
456
457impl<B: Backend> AceStepDiT<B> {
458 pub fn new(config: &AceStepConfig, device: &B::Device) -> Self {
459 let hidden = config.hidden_size;
460 Self {
461 proj_in: PatchEmbed {
462 conv: Conv1dConfig::new(config.in_channels, hidden, config.patch_size)
463 .with_stride(config.patch_size)
464 .with_bias(true)
465 .init(device),
466 },
467 time_embed: TimestepEmbedding::new(hidden, device),
468 time_embed_r: TimestepEmbedding::new(hidden, device),
469 condition_embedder: linear_with_bias(device, hidden, hidden),
470 layers: (0..config.num_hidden_layers)
471 .map(|layer| AceStepDiTLayer::new(config, layer, device))
472 .collect(),
473 norm_out: Qwen3RmsNorm::new(device, hidden, config.rms_norm_eps),
474 proj_out: PatchUnembed {
475 conv: ConvTranspose1dConfig::new(
476 [hidden, config.audio_acoustic_hidden_dim],
477 config.patch_size,
478 )
479 .with_stride(config.patch_size)
480 .with_bias(true)
481 .init(device),
482 },
483 scale_shift_table: Param::from_tensor(randn_scaled([1, 2, hidden], hidden, device)),
484 meta: DiTMeta {
485 head_dim: config.head_dim,
486 rope_theta: config.rope_theta,
487 sliding_window: config.sliding_window,
488 patch_size: config.patch_size,
489 any_sliding: (0..config.num_hidden_layers)
490 .any(|layer| config.is_sliding_layer(layer)),
491 },
492 }
493 }
494
495 pub fn from_burnpack(config: &AceStepConfig, path: &Path, device: &B::Device) -> Result<Self> {
496 let mut model = Self::new(config, device);
497 let mut store = BurnpackStore::from_file(path).zero_copy(true);
498 model.load_from(&mut store).with_context(|| {
499 format!("failed to load AceStep DiT weights from {}", path.display())
500 })?;
501 Ok(model)
502 }
503
504 pub fn forward(
514 &self,
515 xt: Tensor<B, 3>,
516 t: f32,
517 context: Tensor<B, 3>,
518 encoder_hidden_states: Tensor<B, 3>,
519 ) -> Tensor<B, 3> {
520 let kv = self.prepare_cross_kv(encoder_hidden_states);
521 self.forward_with_kv(xt, t, context, &kv)
522 }
523
524 pub fn prepare_cross_kv(&self, encoder_hidden_states: Tensor<B, 3>) -> CrossKv<B> {
528 let conditioned = self.condition_embedder.forward(encoder_hidden_states);
529 let mut keys = Vec::with_capacity(self.layers.len());
530 let mut values = Vec::with_capacity(self.layers.len());
531 for layer in &self.layers {
532 let (key, value) = layer.cross_attn.project_kv(&conditioned);
533 keys.push(key);
534 values.push(value);
535 }
536 CrossKv { keys, values }
537 }
538
539 pub fn forward_with_kv(
543 &self,
544 xt: Tensor<B, 3>,
545 t: f32,
546 context: Tensor<B, 3>,
547 kv: &CrossKv<B>,
548 ) -> Tensor<B, 3> {
549 let device = xt.device();
550 let [batch, seq_len, _] = xt.dims();
551
552 let x = Tensor::cat(vec![context, xt], 2);
554 let remainder = seq_len % self.meta.patch_size;
556 let x = if remainder != 0 {
557 let pad_len = self.meta.patch_size - remainder;
558 let in_channels = x.dims()[2];
559 let pad = Tensor::zeros([batch, pad_len, in_channels], &device);
560 Tensor::cat(vec![x, pad], 1)
561 } else {
562 x
563 };
564
565 let mut hidden = self.proj_in.conv.forward(x.swap_dims(1, 2)).swap_dims(1, 2);
567 let patch_len = hidden.dims()[1];
568
569 let (cos, sin) =
570 rotary_cos_sin::<B>(patch_len, self.meta.head_dim, self.meta.rope_theta, &device);
571 let sliding_mask = if self.meta.any_sliding {
572 Some(sliding_window_mask::<B>(
573 patch_len,
574 self.meta.sliding_window,
575 &device,
576 ))
577 } else {
578 None
579 };
580
581 let (temb_t, proj_t) = self.time_embed.forward(t, &device);
584 let (temb_r, proj_r) = self.time_embed_r.forward(0.0, &device);
585 let temb = temb_t + temb_r;
586 let timestep_proj = proj_t + proj_r;
587
588 for (index, layer) in self.layers.iter().enumerate() {
589 let mask = if layer.sliding {
590 sliding_mask.as_ref()
591 } else {
592 None
593 };
594 hidden = layer.forward(
595 hidden,
596 (&cos, &sin),
597 ×tep_proj,
598 (&kv.keys[index], &kv.values[index]),
599 mask,
600 );
601 }
602
603 let modulation = self.scale_shift_table.val() + temb.unsqueeze_dim::<3>(1);
605 let chunks = modulation.chunk(2, 1);
606 let shift = chunks[0].clone();
607 let scale = chunks[1].clone();
608 let out = self.norm_out.forward(hidden) * (scale + 1.0) + shift;
609
610 let out = self
612 .proj_out
613 .conv
614 .forward(out.swap_dims(1, 2))
615 .swap_dims(1, 2);
616 out.narrow(1, 0, seq_len)
617 }
618
619 pub fn sample_turbo(
634 &self,
635 noise: Tensor<B, 3>,
636 context: Tensor<B, 3>,
637 encoder_hidden_states: Tensor<B, 3>,
638 timesteps: &[f32],
639 mut progress: Option<&mut dyn FnMut(usize, usize)>,
640 ) -> Tensor<B, 3> {
641 let kv = self.prepare_cross_kv(encoder_hidden_states);
642 let total = timesteps.len();
643 let mut xt = noise;
644 for (index, &t_cur) in timesteps.iter().enumerate() {
645 let v = self.forward_with_kv(xt.clone(), t_cur, context.clone(), &kv);
646 let dt = if index + 1 == total {
647 t_cur
648 } else {
649 t_cur - timesteps[index + 1]
650 };
651 xt = xt - v * dt;
652 if let Some(callback) = progress.as_mut() {
653 callback(index + 1, total);
654 }
655 }
656 xt
657 }
658}
659
660fn sinusoidal_timestep<B: Backend>(t: f32, channels: usize, device: &B::Device) -> Tensor<B, 2> {
663 let half = channels / 2;
664 let scaled = t * TIME_EMBED_SCALE;
665 let mut data = Vec::with_capacity(channels);
666 for k in 0..half {
667 let freq = (-TIME_EMBED_MAX_PERIOD.ln() * k as f32 / half as f32).exp();
668 data.push((scaled * freq).cos());
669 }
670 for k in 0..half {
671 let freq = (-TIME_EMBED_MAX_PERIOD.ln() * k as f32 / half as f32).exp();
672 data.push((scaled * freq).sin());
673 }
674 Tensor::<B, 2>::from_data(TensorData::new(data, [1, channels]), device)
675}
676
677fn rotary_cos_sin<B: Backend>(
681 seq_len: usize,
682 head_dim: usize,
683 theta: f64,
684 device: &B::Device,
685) -> (Tensor<B, 4>, Tensor<B, 4>) {
686 let half = head_dim / 2;
687 let inv_freq: Vec<f32> = (0..half)
688 .map(|i| 1.0 / theta.powf(2.0 * i as f64 / head_dim as f64) as f32)
689 .collect();
690 let mut cos_values = Vec::with_capacity(seq_len * head_dim);
691 let mut sin_values = Vec::with_capacity(seq_len * head_dim);
692 for pos in 0..seq_len {
693 for _ in 0..2 {
694 for &freq in &inv_freq {
695 let angle = pos as f32 * freq;
696 cos_values.push(angle.cos());
697 sin_values.push(angle.sin());
698 }
699 }
700 }
701 let shape = [1, seq_len, 1, head_dim];
702 (
703 Tensor::<B, 4>::from_data(TensorData::new(cos_values, shape), device),
704 Tensor::<B, 4>::from_data(TensorData::new(sin_values, shape), device),
705 )
706}
707
708fn rotate_half<B: Backend>(x: Tensor<B, 4>) -> Tensor<B, 4> {
709 let [batch, seq_len, heads, head_dim] = x.dims();
710 let half = head_dim / 2;
711 let x1 = x.clone().slice([0..batch, 0..seq_len, 0..heads, 0..half]);
712 let x2 = x.slice([0..batch, 0..seq_len, 0..heads, half..head_dim]);
713 Tensor::cat(vec![x2.neg(), x1], 3)
714}
715
716fn apply_rotary_pos_emb<B: Backend>(
717 x: Tensor<B, 4>,
718 cos: &Tensor<B, 4>,
719 sin: &Tensor<B, 4>,
720) -> Tensor<B, 4> {
721 x.clone() * cos.clone() + rotate_half(x) * sin.clone()
722}
723
724fn repeat_kv<B: Backend>(tensor: Tensor<B, 4>, repeats: usize) -> Tensor<B, 4> {
727 if repeats == 1 {
728 return tensor;
729 }
730 let [batch, heads, seq_len, head_dim] = tensor.dims();
731 tensor
732 .unsqueeze_dim::<5>(2)
733 .repeat_dim(2, repeats)
734 .reshape([batch, heads * repeats, seq_len, head_dim])
735}
736
737fn sliding_window_mask<B: Backend>(
740 seq_len: usize,
741 window: usize,
742 device: &B::Device,
743) -> Tensor<B, 2> {
744 let mut data = Vec::with_capacity(seq_len * seq_len);
745 for row in 0..seq_len {
746 for col in 0..seq_len {
747 let visible = row.abs_diff(col) <= window;
748 data.push(if visible { 0.0 } else { MASK_MIN });
749 }
750 }
751 Tensor::<B, 2>::from_data(TensorData::new(data, [seq_len, seq_len]), device)
752}
753
754fn randn_scaled<B: Backend, const D: usize>(
756 shape: [usize; D],
757 hidden: usize,
758 device: &B::Device,
759) -> Tensor<B, D> {
760 Tensor::random(
761 shape,
762 Distribution::Normal(0.0, 1.0 / (hidden as f64).sqrt()),
763 device,
764 )
765}
766
767fn linear_no_bias<B: Backend>(device: &B::Device, d_input: usize, d_output: usize) -> Linear<B> {
768 LinearConfig::new(d_input, d_output)
769 .with_bias(false)
770 .with_layout(LinearLayout::Col)
771 .init(device)
772}
773
774fn linear_with_bias<B: Backend>(device: &B::Device, d_input: usize, d_output: usize) -> Linear<B> {
775 LinearConfig::new(d_input, d_output)
776 .with_bias(true)
777 .with_layout(LinearLayout::Col)
778 .init(device)
779}
780
781#[cfg(test)]
782mod tests {
783 use super::*;
784 use burn::backend::NdArray;
785
786 type TestBackend = NdArray<f32>;
787
788 fn tiny_config() -> AceStepConfig {
792 let base = AceStepConfig::load(
793 &Path::new(env!("CARGO_MANIFEST_DIR"))
794 .join("src/acestep/testdata/acestep_v15_turbo_config.json"),
795 )
796 .expect("reference config must parse");
797 AceStepConfig {
798 hidden_size: 64,
799 intermediate_size: 128,
800 num_hidden_layers: 2,
801 num_attention_heads: 4,
802 num_key_value_heads: 2,
803 head_dim: 16,
804 sliding_window: 2,
805 in_channels: 12,
806 audio_acoustic_hidden_dim: 4,
807 patch_size: 2,
808 layer_types: vec![
809 "sliding_attention".to_string(),
810 "full_attention".to_string(),
811 ],
812 ..base
813 }
814 }
815
816 fn tiny_model() -> AceStepDiT<TestBackend> {
817 let device = Default::default();
818 AceStepDiT::new(&tiny_config(), &device)
819 }
820
821 fn assert_finite(tensor: &Tensor<TestBackend, 3>, what: &str) {
822 let values = tensor
823 .clone()
824 .to_data()
825 .to_vec::<f32>()
826 .expect("output should materialize as f32");
827 assert!(
828 values.iter().all(|v| v.is_finite()),
829 "{what} contains non-finite values"
830 );
831 }
832
833 #[test]
834 fn forward_shapes_and_finite() {
835 let model = tiny_model();
836 let device = Default::default();
837 let xt =
838 Tensor::<TestBackend, 3>::random([1, 10, 4], Distribution::Normal(0.0, 1.0), &device);
839 let context =
840 Tensor::<TestBackend, 3>::random([1, 10, 8], Distribution::Normal(0.0, 1.0), &device);
841 let encoder =
842 Tensor::<TestBackend, 3>::random([1, 7, 64], Distribution::Normal(0.0, 1.0), &device);
843
844 let v = model.forward(xt, 0.5, context, encoder);
845 assert_eq!(v.dims(), [1, 10, 4]);
846 assert_finite(&v, "velocity");
847 }
848
849 #[test]
850 fn odd_sequence_length_is_padded_and_cropped() {
851 let model = tiny_model();
852 let device = Default::default();
853 let xt =
854 Tensor::<TestBackend, 3>::random([1, 9, 4], Distribution::Normal(0.0, 1.0), &device);
855 let context =
856 Tensor::<TestBackend, 3>::random([1, 9, 8], Distribution::Normal(0.0, 1.0), &device);
857 let encoder =
858 Tensor::<TestBackend, 3>::random([1, 7, 64], Distribution::Normal(0.0, 1.0), &device);
859
860 let v = model.forward(xt, 0.5, context, encoder);
861 assert_eq!(v.dims(), [1, 9, 4]);
862 assert_finite(&v, "velocity for odd T");
863 }
864
865 #[test]
866 fn forward_with_cached_kv_matches_forward() {
867 let model = tiny_model();
868 let device = Default::default();
869 let xt =
870 Tensor::<TestBackend, 3>::random([1, 10, 4], Distribution::Normal(0.0, 1.0), &device);
871 let context =
872 Tensor::<TestBackend, 3>::random([1, 10, 8], Distribution::Normal(0.0, 1.0), &device);
873 let encoder =
874 Tensor::<TestBackend, 3>::random([1, 7, 64], Distribution::Normal(0.0, 1.0), &device);
875
876 let direct = model.forward(xt.clone(), 0.5, context.clone(), encoder.clone());
877 let kv = model.prepare_cross_kv(encoder);
878 let cached = model.forward_with_kv(xt, 0.5, context, &kv);
879 let diff = (direct - cached).abs().max().to_data().to_vec::<f32>();
880 assert!(
881 diff.expect("max diff")[0] <= 0.0,
882 "cached-KV forward must be numerically identical"
883 );
884 }
885
886 #[test]
887 fn sliding_window_mask_band_pattern() {
888 let device = Default::default();
889 let mask = sliding_window_mask::<TestBackend>(5, 2, &device);
890 assert_eq!(mask.dims(), [5, 5]);
891 let values = mask.to_data().to_vec::<f32>().expect("mask values");
892 for row in 0..5usize {
893 for col in 0..5usize {
894 let expected = if row.abs_diff(col) <= 2 {
895 0.0
896 } else {
897 MASK_MIN
898 };
899 assert_eq!(
900 values[row * 5 + col],
901 expected,
902 "mask[{row}][{col}] should be {expected}"
903 );
904 }
905 }
906 }
907
908 #[test]
909 fn timestep_embedding_shapes_and_cos_sin_order() {
910 let model = tiny_model();
911 let device = Default::default();
912
913 let (temb, proj) = model.time_embed.forward(0.5, &device);
914 assert_eq!(temb.dims(), [1, 64]);
915 assert_eq!(proj.dims(), [1, 6, 64]);
916
917 let sinusoid = sinusoidal_timestep::<TestBackend>(0.0, TIME_EMBED_CHANNELS, &device);
920 let values = sinusoid.to_data().to_vec::<f32>().expect("sinusoid values");
921 let half = TIME_EMBED_CHANNELS / 2;
922 assert!(values[..half].iter().all(|v| (*v - 1.0).abs() < 1e-6));
923 assert!(values[half..].iter().all(|v| v.abs() < 1e-6));
924
925 let (temb_r, proj_r) = model.time_embed_r.forward(0.0, &device);
927 let temb_r_values = temb_r.to_data().to_vec::<f32>().expect("temb_r values");
928 let proj_r_values = proj_r.to_data().to_vec::<f32>().expect("proj_r values");
929 assert!(temb_r_values.iter().any(|v| v.abs() > 1e-6));
930 assert!(proj_r_values.iter().any(|v| v.abs() > 1e-6));
931 }
932
933 #[test]
934 fn sample_turbo_euler_loop() {
935 let model = tiny_model();
936 let device = Default::default();
937 let noise =
938 Tensor::<TestBackend, 3>::random([1, 10, 4], Distribution::Normal(0.0, 1.0), &device);
939 let context =
940 Tensor::<TestBackend, 3>::random([1, 10, 8], Distribution::Normal(0.0, 1.0), &device);
941 let encoder =
942 Tensor::<TestBackend, 3>::random([1, 7, 64], Distribution::Normal(0.0, 1.0), &device);
943 let timesteps = [1.0, 0.75, 0.5, 0.3];
944
945 let mut calls = 0usize;
946 let mut progress = |done: usize, total: usize| {
947 calls += 1;
948 assert_eq!(done, calls);
949 assert_eq!(total, timesteps.len());
950 };
951 let latent = model.sample_turbo(noise, context, encoder, ×teps, Some(&mut progress));
952
953 assert_eq!(calls, timesteps.len());
954 assert_eq!(latent.dims(), [1, 10, 4]);
955 assert_finite(&latent, "sampled latent");
956 }
957
958 #[test]
959 fn default_schedule_has_eight_descending_steps() {
960 assert_eq!(TURBO_TIMESTEPS.len(), 8);
961 assert_eq!(TURBO_TIMESTEPS[0], 1.0);
962 assert_eq!(TURBO_TIMESTEPS[7], 0.3);
963 assert!(TURBO_TIMESTEPS.windows(2).all(|w| w[0] > w[1]));
964 }
965}