1use anyhow::{Context, Result};
7use burn::module::{Param, ParamId};
8use burn::nn::Linear;
9use burn::tensor::activation::softmax;
10use burn::tensor::backend::Backend;
11use burn::tensor::Tensor;
12use safetensors::SafeTensors;
13
14use crate::models::layers::{RmsNorm, SwiGLU};
15use crate::models::weights::{linear_from_weights, load_tensor};
16use crate::tts::codec::alibi::ALiBi;
17use crate::tts::codec::layer_scale::LayerScale;
18use crate::tts::codec::qk_norm::QkNorm;
19
20pub struct CodecAttention<B: Backend> {
27 wq: Linear<B>,
28 wk: Linear<B>,
29 wv: Linear<B>,
30 wo: Linear<B>,
31 qk_norm: QkNorm<B>,
32 alibi: ALiBi,
33 n_heads: usize,
34 head_dim: usize,
35 scale: f32,
36 sliding_window: usize,
37}
38
39impl<B: Backend> CodecAttention<B> {
40 #[allow(clippy::too_many_arguments)]
42 pub fn new(
43 wq: Linear<B>,
44 wk: Linear<B>,
45 wv: Linear<B>,
46 wo: Linear<B>,
47 qk_norm: QkNorm<B>,
48 n_heads: usize,
49 head_dim: usize,
50 sliding_window: usize,
51 ) -> Self {
52 Self {
53 wq,
54 wk,
55 wv,
56 wo,
57 qk_norm,
58 alibi: ALiBi::new(n_heads),
59 n_heads,
60 head_dim,
61 scale: (head_dim as f32).powf(-0.5),
62 sliding_window,
63 }
64 }
65
66 pub fn forward(&self, x: Tensor<B, 3>) -> Tensor<B, 3> {
74 let [batch, seq_len, _dim] = x.dims();
75 let device = x.device();
76
77 let q = self.wq.forward(x.clone());
79 let k = self.wk.forward(x.clone());
80 let v = self.wv.forward(x);
81
82 let q = q.reshape([batch, seq_len, self.n_heads, self.head_dim]);
84 let k = k.reshape([batch, seq_len, self.n_heads, self.head_dim]);
85 let v = v.reshape([batch, seq_len, self.n_heads, self.head_dim]);
86
87 let q = q.swap_dims(1, 2);
89 let k = k.swap_dims(1, 2);
90 let v = v.swap_dims(1, 2);
91
92 let (q, k) = self.qk_norm.forward(q, k);
94
95 let scores = q.matmul(k.swap_dims(2, 3)) * self.scale;
97
98 let alibi_bias = self.alibi.bias::<B>(seq_len, seq_len, &device);
100 let scores = scores + alibi_bias;
101
102 let scores = apply_causal_sliding_window_mask(scores, seq_len, self.sliding_window);
104
105 let attn = softmax(scores, 3);
107
108 let out = attn.matmul(v);
110
111 let out = out.swap_dims(1, 2);
113 let out = out.reshape([batch, seq_len, self.n_heads * self.head_dim]);
114
115 self.wo.forward(out)
117 }
118}
119
120fn apply_causal_sliding_window_mask<B: Backend>(
124 scores: Tensor<B, 4>,
125 seq_len: usize,
126 window: usize,
127) -> Tensor<B, 4> {
128 let device = scores.device();
129 let mut mask_data = vec![0.0f32; seq_len * seq_len];
130 for i in 0..seq_len {
131 for j in 0..seq_len {
132 if j > i || i.abs_diff(j) > window {
133 mask_data[i * seq_len + j] = f32::NEG_INFINITY;
134 }
135 }
136 }
137 let mask: Tensor<B, 1> = Tensor::from_floats(mask_data.as_slice(), &device);
138 let mask: Tensor<B, 2> = mask.reshape([seq_len, seq_len]);
139 let mask: Tensor<B, 4> = mask.unsqueeze_dim::<3>(0).unsqueeze_dim(0);
140 scores + mask
141}
142
143pub struct CodecTransformerLayer<B: Backend> {
153 attention_norm: RmsNorm<B>,
155 attention: CodecAttention<B>,
157 attention_scale: LayerScale<B>,
159 ffn_norm: RmsNorm<B>,
161 ffn: SwiGLU<B>,
163 ffn_scale: LayerScale<B>,
165}
166
167impl<B: Backend> CodecTransformerLayer<B> {
168 #[allow(clippy::too_many_arguments)]
170 pub fn new(
171 attention_norm: RmsNorm<B>,
172 attention: CodecAttention<B>,
173 attention_scale: LayerScale<B>,
174 ffn_norm: RmsNorm<B>,
175 ffn: SwiGLU<B>,
176 ffn_scale: LayerScale<B>,
177 ) -> Self {
178 Self {
179 attention_norm,
180 attention,
181 attention_scale,
182 ffn_norm,
183 ffn,
184 ffn_scale,
185 }
186 }
187
188 #[allow(clippy::too_many_arguments)]
199 pub fn from_safetensors(
200 safetensors: &SafeTensors,
201 prefix: &str,
202 n_heads: usize,
203 head_dim: usize,
204 sliding_window: usize,
205 norm_eps: f64,
206 device: &B::Device,
207 ) -> Result<Self> {
208 let wq_weight: Tensor<B, 2> = load_tensor(
210 safetensors,
211 &format!("{prefix}.attention.wq.weight"),
212 device,
213 )
214 .context("Loading wq")?;
215 let wk_weight: Tensor<B, 2> = load_tensor(
216 safetensors,
217 &format!("{prefix}.attention.wk.weight"),
218 device,
219 )
220 .context("Loading wk")?;
221 let wv_weight: Tensor<B, 2> = load_tensor(
222 safetensors,
223 &format!("{prefix}.attention.wv.weight"),
224 device,
225 )
226 .context("Loading wv")?;
227 let wo_weight: Tensor<B, 2> = load_tensor(
228 safetensors,
229 &format!("{prefix}.attention.wo.weight"),
230 device,
231 )
232 .context("Loading wo")?;
233
234 let wq = linear_from_weights(wq_weight, None);
235 let wk = linear_from_weights(wk_weight, None);
236 let wv = linear_from_weights(wv_weight, None);
237 let wo = linear_from_weights(wo_weight, None);
238
239 let q_norm_weight: Tensor<B, 1> = load_tensor(
241 safetensors,
242 &format!("{prefix}.attention.q_norm.weight"),
243 device,
244 )
245 .context("Loading q_norm")?;
246 let k_norm_weight: Tensor<B, 1> = load_tensor(
247 safetensors,
248 &format!("{prefix}.attention.k_norm.weight"),
249 device,
250 )
251 .context("Loading k_norm")?;
252 let qk_norm = QkNorm::new(q_norm_weight, k_norm_weight, n_heads, head_dim);
253
254 let attention =
255 CodecAttention::new(wq, wk, wv, wo, qk_norm, n_heads, head_dim, sliding_window);
256
257 let attn_scale_weight: Tensor<B, 1> =
259 load_tensor(safetensors, &format!("{prefix}.attention_scale"), device)
260 .context("Loading attention_scale")?;
261 let ffn_scale_weight: Tensor<B, 1> =
262 load_tensor(safetensors, &format!("{prefix}.ffn_scale"), device)
263 .context("Loading ffn_scale")?;
264 let attention_scale = LayerScale::new(attn_scale_weight);
265 let ffn_scale = LayerScale::new(ffn_scale_weight);
266
267 let attn_norm_weight: Tensor<B, 1> = load_tensor(
269 safetensors,
270 &format!("{prefix}.attention_norm.weight"),
271 device,
272 )
273 .context("Loading attention_norm")?;
274 let ffn_norm_weight: Tensor<B, 1> =
275 load_tensor(safetensors, &format!("{prefix}.ffn_norm.weight"), device)
276 .context("Loading ffn_norm")?;
277
278 let attention_norm = RmsNorm {
279 weight: burn::nn::RmsNorm {
280 gamma: Param::initialized(ParamId::new(), attn_norm_weight),
281 epsilon: norm_eps,
282 },
283 };
284 let ffn_norm = RmsNorm {
285 weight: burn::nn::RmsNorm {
286 gamma: Param::initialized(ParamId::new(), ffn_norm_weight),
287 epsilon: norm_eps,
288 },
289 };
290
291 let w1_weight: Tensor<B, 2> = load_tensor(
293 safetensors,
294 &format!("{prefix}.feed_forward.w1.weight"),
295 device,
296 )
297 .context("Loading ffn w1")?;
298 let w2_weight: Tensor<B, 2> = load_tensor(
299 safetensors,
300 &format!("{prefix}.feed_forward.w2.weight"),
301 device,
302 )
303 .context("Loading ffn w2")?;
304 let w3_weight: Tensor<B, 2> = load_tensor(
305 safetensors,
306 &format!("{prefix}.feed_forward.w3.weight"),
307 device,
308 )
309 .context("Loading ffn w3")?;
310
311 let w1 = linear_from_weights(w1_weight, None);
312 let w2 = linear_from_weights(w2_weight, None);
313 let w3 = linear_from_weights(w3_weight, None);
314 let ffn = SwiGLU::new(w1, w2, w3);
315
316 Ok(Self {
317 attention_norm,
318 attention,
319 attention_scale,
320 ffn_norm,
321 ffn,
322 ffn_scale,
323 })
324 }
325
326 pub fn forward(&self, x: Tensor<B, 3>) -> Tensor<B, 3> {
334 let residual = x.clone();
336 let h = self.attention_norm.forward(x);
337 let h = self.attention.forward(h);
338 let h = self.attention_scale.forward(h);
339 let x = h + residual;
340
341 let residual = x.clone();
343 let h = self.ffn_norm.forward(x);
344 let h = self.ffn.forward(h);
345 let h = self.ffn_scale.forward(h);
346 h + residual
347 }
348}
349
350#[cfg(test)]
351mod tests {
352 use super::*;
353 use burn::backend::Wgpu;
354 use burn::nn::LinearConfig;
355 type TestBackend = Wgpu;
356
357 fn make_test_layer(
359 dim: usize,
360 n_heads: usize,
361 head_dim: usize,
362 ffn_dim: usize,
363 sliding_window: usize,
364 device: &<TestBackend as Backend>::Device,
365 ) -> CodecTransformerLayer<TestBackend> {
366 let eps = 1e-5;
367
368 let wq = LinearConfig::new(dim, dim).with_bias(false).init(device);
370 let wk = LinearConfig::new(dim, dim).with_bias(false).init(device);
371 let wv = LinearConfig::new(dim, dim).with_bias(false).init(device);
372 let wo = LinearConfig::new(dim, dim).with_bias(false).init(device);
373
374 let q_weight = Tensor::<TestBackend, 1>::ones([dim], device);
375 let k_weight = Tensor::<TestBackend, 1>::ones([dim], device);
376 let qk_norm = QkNorm::new(q_weight, k_weight, n_heads, head_dim);
377
378 let attention =
379 CodecAttention::new(wq, wk, wv, wo, qk_norm, n_heads, head_dim, sliding_window);
380
381 let attn_scale = LayerScale::new(Tensor::ones([dim], device) * 0.01);
383 let ffn_scale_layer = LayerScale::new(Tensor::ones([dim], device) * 0.01);
384
385 let attention_norm = RmsNorm {
387 weight: burn::nn::RmsNorm {
388 gamma: Param::initialized(
389 ParamId::new(),
390 Tensor::<TestBackend, 1>::ones([dim], device),
391 ),
392 epsilon: eps,
393 },
394 };
395 let ffn_norm = RmsNorm {
396 weight: burn::nn::RmsNorm {
397 gamma: Param::initialized(
398 ParamId::new(),
399 Tensor::<TestBackend, 1>::ones([dim], device),
400 ),
401 epsilon: eps,
402 },
403 };
404
405 use crate::models::layers::SwiGLUConfig;
407 let ffn = SwiGLUConfig::new(dim, ffn_dim)
408 .with_bias(false)
409 .init(device);
410
411 CodecTransformerLayer::new(
412 attention_norm,
413 attention,
414 attn_scale,
415 ffn_norm,
416 ffn,
417 ffn_scale_layer,
418 )
419 }
420
421 #[test]
422 fn test_codec_layer_output_shape() {
423 let device = Default::default();
424 let layer = make_test_layer(64, 4, 16, 256, 4, &device);
425
426 let x = Tensor::<TestBackend, 3>::zeros([1, 10, 64], &device);
427 let out = layer.forward(x);
428
429 assert_eq!(out.dims(), [1, 10, 64]);
430 }
431
432 #[test]
433 fn test_codec_layer_batch_shape() {
434 let device = Default::default();
435 let layer = make_test_layer(64, 4, 16, 256, 8, &device);
436
437 let x = Tensor::<TestBackend, 3>::zeros([2, 5, 64], &device);
438 let out = layer.forward(x);
439
440 assert_eq!(out.dims(), [2, 5, 64]);
441 }
442
443 #[test]
444 fn test_codec_layer_real_dims() {
445 let device = Default::default();
447 let layer = make_test_layer(1024, 8, 128, 4096, 2, &device);
448
449 let x = Tensor::<TestBackend, 3>::zeros([1, 8, 1024], &device);
450 let out = layer.forward(x);
451
452 assert_eq!(out.dims(), [1, 8, 1024]);
453 }
454
455 #[test]
456 fn test_codec_layer_residual_connection() {
457 let device = Default::default();
461 let dim = 32;
462 let n_heads = 2;
463 let head_dim = 16;
464
465 let layer = make_test_layer(dim, n_heads, head_dim, 128, 4, &device);
466
467 let x = Tensor::<TestBackend, 3>::ones([1, 4, dim], &device) * 0.5;
468 let out = layer.forward(x.clone());
469
470 let diff = (out - x).abs().max();
472 let diff_val = diff.to_data().as_slice::<f32>().unwrap()[0];
473
474 assert!(
477 diff_val < 5.0,
478 "Residual connection should keep output near input, got max diff {}",
479 diff_val
480 );
481 }
482
483 #[test]
484 fn test_codec_attention_shape() {
485 let device = Default::default();
486 let dim = 64;
487 let n_heads = 4;
488 let head_dim = 16;
489
490 let wq = LinearConfig::new(dim, dim).with_bias(false).init(&device);
491 let wk = LinearConfig::new(dim, dim).with_bias(false).init(&device);
492 let wv = LinearConfig::new(dim, dim).with_bias(false).init(&device);
493 let wo = LinearConfig::new(dim, dim).with_bias(false).init(&device);
494
495 let q_weight = Tensor::<TestBackend, 1>::ones([dim], &device);
496 let k_weight = Tensor::<TestBackend, 1>::ones([dim], &device);
497 let qk_norm = QkNorm::new(q_weight, k_weight, n_heads, head_dim);
498
499 let attn = CodecAttention::new(wq, wk, wv, wo, qk_norm, n_heads, head_dim, 4);
500
501 let x = Tensor::<TestBackend, 3>::zeros([2, 10, dim], &device);
502 let out = attn.forward(x);
503
504 assert_eq!(out.dims(), [2, 10, dim]);
505 }
506
507 #[test]
508 fn test_causal_sliding_window_mask() {
509 let device: <TestBackend as Backend>::Device = Default::default();
512 let scores = Tensor::<TestBackend, 4>::zeros([1, 1, 5, 5], &device);
513 let masked = apply_causal_sliding_window_mask::<TestBackend>(scores, 5, 2);
514
515 let data = masked.to_data();
516 let vals = data.as_slice::<f32>().unwrap();
517
518 for i in 0..5 {
520 for j in 0..5 {
521 let idx = i * 5 + j;
522 if j > i {
523 assert!(
525 vals[idx].is_infinite() && vals[idx] < 0.0,
526 "Position ({}, {}) should be masked (future), got {}",
527 i,
528 j,
529 vals[idx]
530 );
531 } else if i.abs_diff(j) > 2 {
532 assert!(
534 vals[idx].is_infinite() && vals[idx] < 0.0,
535 "Position ({}, {}) should be masked (window), got {}",
536 i,
537 j,
538 vals[idx]
539 );
540 } else {
541 assert!(
543 vals[idx] == 0.0,
544 "Position ({}, {}) should be visible, got {}",
545 i,
546 j,
547 vals[idx]
548 );
549 }
550 }
551 }
552 }
553
554 #[test]
555 fn test_different_sliding_windows() {
556 let device: <TestBackend as Backend>::Device = Default::default();
558 let seq_len = 8;
559
560 let scores_w2 = Tensor::<TestBackend, 4>::zeros([1, 1, seq_len, seq_len], &device);
561 let scores_w4 = Tensor::<TestBackend, 4>::zeros([1, 1, seq_len, seq_len], &device);
562
563 let masked_w2 = apply_causal_sliding_window_mask::<TestBackend>(scores_w2, seq_len, 2);
564 let masked_w4 = apply_causal_sliding_window_mask::<TestBackend>(scores_w4, seq_len, 4);
565
566 let w2_data = masked_w2.to_data();
567 let w4_data = masked_w4.to_data();
568 let w2_vals = w2_data.as_slice::<f32>().unwrap();
569 let w4_vals = w4_data.as_slice::<f32>().unwrap();
570
571 let w2_visible = w2_vals.iter().filter(|&&v| v == 0.0).count();
573 let w4_visible = w4_vals.iter().filter(|&&v| v == 0.0).count();
574 assert!(
575 w4_visible > w2_visible,
576 "Window=4 ({} visible) should have more visible positions than window=2 ({})",
577 w4_visible,
578 w2_visible
579 );
580 }
581}