1use candle::{DType, Module, Result, Tensor, D};
7use candle_nn::{Conv1d, Conv2d, Conv2dConfig, VarBuilder};
8
9use super::config::Gemma4AudioConfig;
10
11#[derive(Debug, Clone)]
14struct RmsNorm {
15 weight: Tensor,
16 eps: f64,
17}
18
19impl RmsNorm {
20 fn new(dim: usize, eps: f64, vb: VarBuilder) -> Result<Self> {
21 let weight = vb.get(dim, "weight")?;
22 Ok(Self { weight, eps })
23 }
24}
25
26impl Module for RmsNorm {
27 fn forward(&self, x: &Tensor) -> Result<Tensor> {
28 let x_dtype = x.dtype();
29 let internal_dtype = match x_dtype {
30 DType::F16 | DType::BF16 => DType::F32,
31 d => d,
32 };
33 let hidden_size = x.dim(D::Minus1)?;
34 let x = x.to_dtype(internal_dtype)?;
35 let norm_x = (x.sqr()?.sum_keepdim(D::Minus1)? / hidden_size as f64)?;
36 let x_normed = x.broadcast_div(&(norm_x + self.eps)?.sqrt()?)?;
37 x_normed.to_dtype(x_dtype)?.broadcast_mul(&self.weight)
38 }
39}
40
41#[derive(Debug, Clone)]
44struct LayerNorm {
45 eps: f64,
46 dim: usize,
47}
48
49impl LayerNorm {
50 fn new(dim: usize, eps: f64) -> Self {
51 Self { eps, dim }
52 }
53}
54
55impl Module for LayerNorm {
56 fn forward(&self, x: &Tensor) -> Result<Tensor> {
57 let x_dtype = x.dtype();
58 let x = x.to_dtype(DType::F32)?;
59 let mean = x.mean_keepdim(D::Minus1)?;
60 let x = x.broadcast_sub(&mean)?;
61 let var = (x.sqr()?.sum_keepdim(D::Minus1)? / self.dim as f64)?;
62 let x = x.broadcast_div(&(var + self.eps)?.sqrt()?)?;
63 x.to_dtype(x_dtype)
64 }
65}
66
67#[derive(Debug, Clone)]
70struct SSCPConvBlock {
71 conv: Conv2d,
72 norm: LayerNorm,
73 manual_padding: (usize, usize, usize, usize), time_stride: usize,
75 #[allow(dead_code)]
76 out_channels: usize,
77}
78
79impl SSCPConvBlock {
80 fn new(
81 cfg: &Gemma4AudioConfig,
82 idx: usize,
83 input_freq_dim: usize,
84 vb: VarBuilder,
85 ) -> Result<Self> {
86 let in_channels = if idx == 0 {
87 1
88 } else {
89 cfg.sscp_conv_channel_size[idx - 1]
90 };
91 let out_channels = cfg.sscp_conv_channel_size[idx];
92 let kernel_t = cfg.sscp_conv_kernel_size[idx][0];
93 let _kernel_f = cfg.sscp_conv_kernel_size[idx][1];
94 let stride_t = cfg.sscp_conv_stride_size[idx][0];
95 let _stride_f = cfg.sscp_conv_stride_size[idx][1];
96
97 let half = kernel_t / 2;
99 let (pad_t_top, pad_t_bottom) = (half, half);
100 let pad_f_left = 1;
101 let pad_f_right = 1;
102
103 let _ = input_freq_dim; let conv = candle_nn::conv2d_no_bias(
106 in_channels,
107 out_channels,
108 kernel_t, Conv2dConfig {
110 stride: stride_t,
111 padding: 0,
112 dilation: 1,
113 groups: 1,
114 cudnn_fwd_algo: None,
115 },
116 vb.pp("conv"),
117 )?;
118 let norm = LayerNorm::new(out_channels, cfg.rms_norm_eps);
119
120 Ok(Self {
121 conv,
122 norm,
123 manual_padding: (pad_f_left, pad_f_right, pad_t_top, pad_t_bottom),
124 time_stride: stride_t,
125 out_channels,
126 })
127 }
128
129 fn forward(
130 &self,
131 audio_encodings: &Tensor,
132 audio_mel_mask: &Tensor,
133 ) -> Result<(Tensor, Tensor)> {
134 let valid_mask = audio_mel_mask
136 .eq(0.0)?
137 .unsqueeze(1)?
138 .unsqueeze(D::Minus1)?
139 .to_dtype(audio_encodings.dtype())?;
140 let audio_encodings = audio_encodings.broadcast_mul(&valid_mask)?;
141
142 let audio_encodings = audio_encodings
144 .pad_with_zeros(D::Minus1, self.manual_padding.0, self.manual_padding.1)?
145 .pad_with_zeros(D::Minus2, self.manual_padding.2, self.manual_padding.3)?;
146
147 let audio_encodings = self.conv.forward(&audio_encodings)?;
148
149 let t_out = audio_encodings.dim(2)?;
151 let output_mask = subsample_mask(audio_mel_mask, self.time_stride, t_out)?;
152
153 let x = audio_encodings.permute((0, 2, 3, 1))?;
155 let x = self.norm.forward(&x)?;
156 let x = x.permute((0, 3, 1, 2))?.relu()?;
157 Ok((x, output_mask))
158 }
159}
160
161fn subsample_mask(mask: &Tensor, stride: usize, target_len: usize) -> Result<Tensor> {
162 let mask_len = mask.dim(1)?;
163 let indices: Vec<u32> = (0..target_len)
164 .map(|i| (i * stride).min(mask_len - 1) as u32)
165 .collect();
166 let indices = Tensor::from_vec(indices, target_len, mask.device())?;
167 mask.index_select(&indices, 1)
168}
169
170#[derive(Debug, Clone)]
173struct SubSampleConvProjection {
174 conv_0: SSCPConvBlock,
175 conv_1: SSCPConvBlock,
176 input_proj_linear: candle_nn::Linear,
177}
178
179impl SubSampleConvProjection {
180 fn new(cfg: &Gemma4AudioConfig, vb: VarBuilder) -> Result<Self> {
181 let mut current_f = cfg.input_feat_size;
182 let mut f_out_dims = Vec::new();
183
184 for i in 0..2 {
185 let kernel_w = cfg.sscp_conv_kernel_size[i][1];
186 let stride_w = cfg.sscp_conv_stride_size[i][1];
187 let f_in_padded = current_f + 2; let f_out = (f_in_padded - kernel_w) / stride_w + 1;
189 f_out_dims.push(f_out);
190 current_f = f_out;
191 }
192
193 let conv_0 = SSCPConvBlock::new(cfg, 0, cfg.input_feat_size, vb.pp("layer0"))?;
194 let conv_1 = SSCPConvBlock::new(cfg, 1, f_out_dims[0], vb.pp("layer1"))?;
195
196 let final_c_out = cfg.sscp_conv_channel_size[1];
197 let final_f_out = f_out_dims[1];
198 let input_proj_linear = candle_nn::linear_no_bias(
199 final_c_out * final_f_out,
200 cfg.hidden_size,
201 vb.pp("input_proj_linear"),
202 )?;
203
204 Ok(Self {
205 conv_0,
206 conv_1,
207 input_proj_linear,
208 })
209 }
210
211 fn forward(&self, audio_mel: &Tensor, audio_mel_mask: &Tensor) -> Result<(Tensor, Tensor)> {
212 let x = audio_mel.unsqueeze(1)?;
213 let (x, mask) = self.conv_0.forward(&x, audio_mel_mask)?;
214 let (x, mask) = self.conv_1.forward(&x, &mask)?;
215
216 let (b, c_out, t_out, f_out) = x.dims4()?;
217 let x = x
218 .transpose(1, 2)?
219 .transpose(2, 3)?
220 .reshape((b, t_out, f_out * c_out))?;
221 Ok((self.input_proj_linear.forward(&x)?, mask))
222 }
223}
224
225#[derive(Debug, Clone)]
228struct RelativePositionEmbedding {
229 pos_proj: candle_nn::Linear,
230 inv_timescales: Tensor,
231 pos_indices: Tensor,
232 num_heads: usize,
233 head_dim: usize,
234}
235
236impl RelativePositionEmbedding {
237 fn new(cfg: &Gemma4AudioConfig, vb: VarBuilder) -> Result<Self> {
238 let num_heads = cfg.conf_num_attention_heads;
239 let channels = cfg.hidden_size;
240 let head_dim = channels / num_heads;
241 let max_backward = cfg.conf_attention_context_left.saturating_sub(1);
242 let max_forward = cfg.conf_attention_context_right;
243 let num_timescales = channels / 2;
244
245 let pos_proj =
246 candle_nn::linear_no_bias(channels, num_heads * head_dim, vb.pp("relative_k_proj"))?;
247
248 let min_timescale = 1.0_f64;
249 let max_timescale = 10_000.0_f64;
250 let log_timescale_increment =
251 (max_timescale / min_timescale).ln() / num_timescales.saturating_sub(1).max(1) as f64;
252 let inv_timescales = Tensor::from_vec(
253 (0..num_timescales)
254 .map(|i| (min_timescale * (-log_timescale_increment * i as f64).exp()) as f32)
255 .collect::<Vec<_>>(),
256 (1, 1, num_timescales),
257 vb.device(),
258 )?;
259
260 let pos_values: Vec<i64> = (-(max_forward as i64)..=max_backward as i64)
261 .rev()
262 .collect();
263 let span = pos_values.len();
264 let pos_indices = Tensor::from_vec(pos_values, (1, span), vb.device())?;
265
266 Ok(Self {
267 pos_proj,
268 inv_timescales,
269 pos_indices,
270 num_heads,
271 head_dim,
272 })
273 }
274
275 fn get_timing_signal(&self, position: &Tensor, dtype: DType) -> Result<Tensor> {
276 let position = position.to_dtype(DType::F32)?.unsqueeze(D::Minus1)?;
277 let inv_timescales = self.inv_timescales.to_device(position.device())?;
278 let scaled_time = position.broadcast_mul(&inv_timescales)?;
279 let sin_emb = scaled_time.sin()?;
280 let cos_emb = scaled_time.cos()?;
281 Tensor::cat(&[sin_emb, cos_emb], D::Minus1)?.to_dtype(dtype)
282 }
283
284 fn forward(&self, queries: &Tensor, keys: &Tensor) -> Result<Tensor> {
285 let (batch_size, num_query_blocks, query_block_size, _num_heads, head_dim) =
286 queries.dims5()?;
287 let key_context_size = keys.dim(2)?;
288 let max_span_plus_1 = self.pos_indices.dim(1)?;
289
290 let pos_indices = self.pos_indices.to_device(queries.device())?;
291 let sin_emb_timing = self.get_timing_signal(&pos_indices, queries.dtype())?;
292 let projected_sin_emb = self.pos_proj.forward(&sin_emb_timing)?;
293 let sin_emb = projected_sin_emb
294 .reshape((1, max_span_plus_1, self.num_heads, self.head_dim))?
295 .squeeze(0)?;
296
297 let queries_p = queries.transpose(1, 3)?.transpose(2, 3)?.contiguous()?;
299 let keys_p_t = keys
300 .transpose(1, 3)?
301 .transpose(2, 3)?
302 .transpose(3, 4)?
303 .contiguous()?;
304
305 let queries_3d = queries_p.reshape((
306 batch_size * self.num_heads * num_query_blocks,
307 query_block_size,
308 head_dim,
309 ))?;
310 let keys_3d = keys_p_t.reshape((
311 batch_size * self.num_heads * num_query_blocks,
312 head_dim,
313 key_context_size,
314 ))?;
315 let term_ac = queries_3d.matmul(&keys_3d)?.reshape((
316 batch_size,
317 self.num_heads,
318 num_query_blocks,
319 query_block_size,
320 key_context_size,
321 ))?;
322
323 let q_transposed = queries.transpose(1, 3)?.transpose(2, 3)?;
325 let s_transposed = sin_emb.transpose(0, 2)?.transpose(0, 1)?;
326 let q_reshaped = q_transposed.reshape((
327 batch_size * self.num_heads,
328 num_query_blocks * query_block_size,
329 head_dim,
330 ))?;
331 let s_broadcast = s_transposed
332 .unsqueeze(0)?
333 .broadcast_as((batch_size, self.num_heads, head_dim, max_span_plus_1))?
334 .reshape((batch_size * self.num_heads, head_dim, max_span_plus_1))?
335 .contiguous()?;
336 let term_bd_unshifted = q_reshaped.contiguous()?.matmul(&s_broadcast)?.reshape((
337 batch_size,
338 self.num_heads,
339 num_query_blocks,
340 query_block_size,
341 max_span_plus_1,
342 ))?;
343
344 let pad_amount = (key_context_size + 1) - max_span_plus_1;
346 let term_bd_padded = term_bd_unshifted.pad_with_zeros(D::Minus1, 0, pad_amount)?;
347 let term_bd_reshaped = term_bd_padded.reshape((
348 batch_size,
349 self.num_heads,
350 num_query_blocks,
351 query_block_size * (key_context_size + 1),
352 ))?;
353 let term_bd_sliced =
354 term_bd_reshaped.narrow(D::Minus1, 0, query_block_size * key_context_size)?;
355 let term_bd = term_bd_sliced.reshape((
356 batch_size,
357 self.num_heads,
358 num_query_blocks,
359 query_block_size,
360 key_context_size,
361 ))?;
362
363 term_ac.broadcast_add(&term_bd)
364 }
365}
366
367#[derive(Debug, Clone)]
370struct ConformerAttention {
371 q_proj: candle_nn::Linear,
372 k_proj: candle_nn::Linear,
373 v_proj: candle_nn::Linear,
374 post: candle_nn::Linear,
375 relative_position_embedding: RelativePositionEmbedding,
376 per_dim_scale_softplus: Tensor,
377 pre_attn_norm: RmsNorm,
378 post_norm: RmsNorm,
379 num_heads: usize,
380 head_dim: usize,
381 chunk_size: usize,
382 max_past_horizon: usize,
383 max_future_horizon: usize,
384 context_size: usize,
385 q_scale: f64,
386 k_scale: f64,
387 softcap: f64,
388 invalid_logits_value: f64,
389 local_causal_valid_mask: Tensor,
390 gradient_clipping: f64,
391 hidden_size: usize,
392}
393
394impl ConformerAttention {
395 fn new(cfg: &Gemma4AudioConfig, vb: VarBuilder) -> Result<Self> {
396 let num_heads = cfg.conf_num_attention_heads;
397 let hidden_size = cfg.hidden_size;
398 let head_dim = hidden_size / num_heads;
399 let chunk_size = cfg.conf_attention_chunk_size;
400 let max_past_horizon = cfg.conf_attention_context_left.saturating_sub(1);
401 let max_future_horizon = cfg.conf_attention_context_right;
402 let context_size = chunk_size + max_past_horizon + max_future_horizon;
403
404 let attn_vb = vb.pp("self_attn");
405 let relative_position_embedding = RelativePositionEmbedding::new(cfg, attn_vb.clone())?;
406 let per_dim_scale = attn_vb.get(head_dim, "per_dim_scale")?;
407 let q_proj =
408 candle_nn::linear_no_bias(hidden_size, num_heads * head_dim, attn_vb.pp("q_proj"))?;
409 let k_proj =
410 candle_nn::linear_no_bias(hidden_size, num_heads * head_dim, attn_vb.pp("k_proj"))?;
411 let v_proj =
412 candle_nn::linear_no_bias(hidden_size, num_heads * head_dim, attn_vb.pp("v_proj"))?;
413 let post = candle_nn::linear_no_bias(hidden_size, hidden_size, attn_vb.pp("post"))?;
414
415 let pre_attn_norm = RmsNorm::new(hidden_size, cfg.rms_norm_eps, vb.pp("norm_pre_attn"))?;
416 let post_norm = RmsNorm::new(hidden_size, cfg.rms_norm_eps, vb.pp("norm_post_attn"))?;
417
418 let q_scale = (head_dim as f64).powf(-0.5) / 2.0_f64.ln();
419 let k_scale = (1.0_f64 + std::f64::consts::E).ln() / 2.0_f64.ln();
420
421 let mut mask_vec = vec![0u8; chunk_size * context_size];
423 for i in 0..chunk_size {
424 for j in 0..context_size {
425 let lower = j >= i;
426 let upper =
427 (j as isize) <= (i as isize + (max_past_horizon + max_future_horizon) as isize);
428 if lower && upper {
429 mask_vec[i * context_size + j] = 1;
430 }
431 }
432 }
433 let local_causal_valid_mask =
434 Tensor::from_vec(mask_vec, (chunk_size, context_size), vb.device())?
435 .to_dtype(DType::U8)?;
436
437 let per_dim_scale_softplus = {
438 let ones = Tensor::ones_like(&per_dim_scale)?.to_dtype(DType::F32)?;
439 let exp_scale = per_dim_scale.to_dtype(DType::F32)?.exp()?;
440 ones.broadcast_add(&exp_scale)?.log()?
441 };
442
443 Ok(Self {
444 q_proj,
445 k_proj,
446 v_proj,
447 post,
448 relative_position_embedding,
449 per_dim_scale_softplus,
450 pre_attn_norm,
451 post_norm,
452 num_heads,
453 head_dim,
454 chunk_size,
455 max_past_horizon,
456 max_future_horizon,
457 context_size,
458 q_scale,
459 k_scale,
460 softcap: cfg.conf_attention_logit_cap,
461 invalid_logits_value: cfg.conf_attention_invalid_logits_value,
462 local_causal_valid_mask,
463 gradient_clipping: cfg.gradient_clipping,
464 hidden_size: cfg.hidden_size,
465 })
466 }
467
468 fn convert_to_block(&self, x: &Tensor) -> Result<Tensor> {
469 let dims = x.dims().to_vec();
470 let (b, t) = (dims[0], dims[1]);
471 let num_blocks = t.div_ceil(self.chunk_size);
472 let padding_len = num_blocks * self.chunk_size - t;
473 let x = if padding_len > 0 {
474 x.pad_with_zeros(1, 0, padding_len)?
475 } else {
476 x.clone()
477 };
478 let mut new_shape = vec![b, num_blocks, self.chunk_size];
479 new_shape.extend_from_slice(&dims[2..]);
480 x.reshape(new_shape)
481 }
482
483 fn extract_block_context(&self, x: &Tensor) -> Result<Tensor> {
484 let pad_left = self.max_past_horizon;
485 let pad_right = self.max_future_horizon + self.chunk_size - 1;
486 let x = x.pad_with_zeros(1, pad_left, pad_right)?;
487 let frame_len = self.context_size;
488 let frame_step = self.chunk_size;
489 let time_dim = x.dim(1)?;
490 let num_windows = (time_dim - frame_len) / frame_step + 1;
491
492 let mut windows = Vec::with_capacity(num_windows);
493 for i in 0..num_windows {
494 let start_idx = i * frame_step;
495 windows.push(x.narrow(1, start_idx, frame_len)?);
496 }
497 Tensor::stack(&windows, 1)
498 }
499
500 fn forward(&self, x: &Tensor, mask: &Tensor) -> Result<Tensor> {
501 let residual = x;
502 let x = x.clamp(-self.gradient_clipping, self.gradient_clipping)?;
503 let x = self.pre_attn_norm.forward(&x)?;
504
505 let q = self.q_proj.forward(&x)?.to_dtype(DType::F32)?;
506 let k = self.k_proj.forward(&x)?.to_dtype(DType::F32)?;
507 let v = self.v_proj.forward(&x)?.to_dtype(DType::F32)?;
508
509 let (b, t, _) = x.dims3()?;
510
511 let q = q.reshape((b, t, self.num_heads, self.head_dim))?;
512 let k = k.reshape((b, t, self.num_heads, self.head_dim))?;
513 let v = v.reshape((b, t, self.num_heads, self.head_dim))?;
514
515 let per_dim_scale = self
516 .per_dim_scale_softplus
517 .to_device(x.device())?
518 .to_dtype(DType::F32)?;
519
520 let q = q
522 .affine(self.q_scale, 0.0)?
523 .broadcast_mul(&per_dim_scale.reshape((1, 1, 1, self.head_dim))?)?;
524 let k = k.affine(self.k_scale, 0.0)?;
525
526 let query_blocks = self.convert_to_block(&q)?;
528 let key_blocks = self.extract_block_context(&k)?;
529 let value_blocks = self.extract_block_context(&v)?;
530 let num_query_blocks = query_blocks.dim(1)?;
531
532 let key_blocks = if key_blocks.dim(2)? != self.context_size {
534 if key_blocks.dim(2)? < self.context_size {
535 key_blocks.pad_with_zeros(2, 0, self.context_size - key_blocks.dim(2)?)?
536 } else {
537 key_blocks.narrow(2, 0, self.context_size)?
538 }
539 } else {
540 key_blocks
541 };
542 let value_blocks = if value_blocks.dim(2)? != self.context_size {
543 if value_blocks.dim(2)? < self.context_size {
544 value_blocks.pad_with_zeros(2, 0, self.context_size - value_blocks.dim(2)?)?
545 } else {
546 value_blocks.narrow(2, 0, self.context_size)?
547 }
548 } else {
549 value_blocks
550 };
551
552 let key_blocks = if key_blocks.dim(1)? > num_query_blocks {
554 key_blocks.narrow(1, 0, num_query_blocks)?
555 } else {
556 key_blocks
557 };
558 let value_blocks = if value_blocks.dim(1)? > num_query_blocks {
559 value_blocks.narrow(1, 0, num_query_blocks)?
560 } else {
561 value_blocks
562 };
563
564 let original_valid = mask.eq(0.0)?.to_dtype(DType::U8)?;
566 let extracted_valid = self.extract_block_context(&original_valid)?;
567 let extracted_valid = if extracted_valid.rank() == 4 {
568 extracted_valid.reshape((b, num_query_blocks, self.context_size))?
569 } else {
570 extracted_valid
571 };
572 let extracted_valid = if extracted_valid.dim(D::Minus1)? != self.context_size {
573 if extracted_valid.dim(D::Minus1)? < self.context_size {
574 extracted_valid.pad_with_zeros(
575 D::Minus1,
576 0,
577 self.context_size - extracted_valid.dim(D::Minus1)?,
578 )?
579 } else {
580 extracted_valid.narrow(D::Minus1, 0, self.context_size)?
581 }
582 } else {
583 extracted_valid
584 };
585
586 let cond_input = extracted_valid.unsqueeze(1)?.unsqueeze(3)?;
587 let cond_causal = self
588 .local_causal_valid_mask
589 .to_device(x.device())?
590 .unsqueeze(0)?
591 .unsqueeze(0)?
592 .unsqueeze(0)?;
593 let final_cond = cond_input
594 .to_dtype(DType::U8)?
595 .broadcast_mul(&cond_causal.to_dtype(DType::U8)?)?;
596
597 let logits = self
599 .relative_position_embedding
600 .forward(&query_blocks, &key_blocks)?;
601 let logits = ((logits / self.softcap)?.tanh()? * self.softcap)?;
602
603 let final_cond = final_cond.broadcast_as(logits.shape())?;
605
606 let invalid_logits = Tensor::new(self.invalid_logits_value as f32, logits.device())?
607 .broadcast_as(logits.shape())?;
608 let masked_logits = final_cond.where_cond(&logits, &invalid_logits)?;
609 let probabilities = candle_nn::ops::softmax_last_dim(&masked_logits.to_dtype(DType::F32)?)?;
610
611 let (b_dim, n_dim, u_dim, w_dim, c_dim) = probabilities.dims5()?;
613 let h_dim = value_blocks.dim(D::Minus1)?;
614 let probs_p = probabilities.permute((0, 2, 1, 3, 4))?.reshape((
615 b_dim * u_dim * n_dim,
616 w_dim,
617 c_dim,
618 ))?;
619 let vals_p = value_blocks.permute((0, 1, 3, 2, 4))?.reshape((
620 b_dim * u_dim * n_dim,
621 c_dim,
622 h_dim,
623 ))?;
624 let context_vectors = probs_p
625 .matmul(&vals_p)?
626 .reshape((b_dim, u_dim, n_dim, w_dim, h_dim))?
627 .permute((0, 1, 3, 2, 4))?
628 .reshape((
629 b,
630 num_query_blocks * self.chunk_size,
631 self.num_heads,
632 self.head_dim,
633 ))?
634 .narrow(1, 0, t)?;
635
636 let context_vectors = context_vectors.reshape((b, t, self.hidden_size))?;
637 let out = self
638 .post
639 .forward(&context_vectors)?
640 .clamp(-self.gradient_clipping, self.gradient_clipping)?;
641 residual.broadcast_add(&self.post_norm.forward(&out)?)
642 }
643}
644
645#[derive(Debug, Clone)]
648struct ConformerFeedForward {
649 scale: f64,
650 pre_layer_norm: RmsNorm,
651 ffw_layer_1: candle_nn::Linear,
652 ffw_layer_2: candle_nn::Linear,
653 post_layer_norm: RmsNorm,
654 gradient_clipping: f64,
655}
656
657impl ConformerFeedForward {
658 fn new(cfg: &Gemma4AudioConfig, vb: VarBuilder) -> Result<Self> {
659 Ok(Self {
660 scale: cfg.conf_residual_weight,
661 pre_layer_norm: RmsNorm::new(
662 cfg.hidden_size,
663 cfg.rms_norm_eps,
664 vb.pp("pre_layer_norm"),
665 )?,
666 ffw_layer_1: candle_nn::linear_no_bias(
667 cfg.hidden_size,
668 cfg.hidden_size * 4,
669 vb.pp("ffw_layer_1"),
670 )?,
671 ffw_layer_2: candle_nn::linear_no_bias(
672 cfg.hidden_size * 4,
673 cfg.hidden_size,
674 vb.pp("ffw_layer_2"),
675 )?,
676 post_layer_norm: RmsNorm::new(
677 cfg.hidden_size,
678 cfg.rms_norm_eps,
679 vb.pp("post_layer_norm"),
680 )?,
681 gradient_clipping: cfg.gradient_clipping,
682 })
683 }
684
685 fn forward(&self, x: &Tensor) -> Result<Tensor> {
686 let residual = x;
687 let x = x.clamp(-self.gradient_clipping, self.gradient_clipping)?;
688 let x = self.pre_layer_norm.forward(&x)?;
689 let x = candle_nn::ops::silu(&self.ffw_layer_1.forward(&x)?)?;
690 let x = self
691 .ffw_layer_2
692 .forward(&x)?
693 .clamp(-self.gradient_clipping, self.gradient_clipping)?;
694 let x = self.post_layer_norm.forward(&x)?;
695 residual.broadcast_add(&(x * self.scale)?)
696 }
697}
698
699#[derive(Debug, Clone)]
702struct ConformerLightConv1d {
703 pre_layer_norm: RmsNorm,
704 depthwise_conv1d: Conv1d,
705 conv_norm: RmsNorm,
706 linear_start: candle_nn::Linear,
707 linear_end: candle_nn::Linear,
708 causal_padding: usize,
709 gradient_clipping: f64,
710}
711
712impl ConformerLightConv1d {
713 fn new(cfg: &Gemma4AudioConfig, vb: VarBuilder) -> Result<Self> {
714 Ok(Self {
715 pre_layer_norm: RmsNorm::new(
716 cfg.hidden_size,
717 cfg.rms_norm_eps,
718 vb.pp("pre_layer_norm"),
719 )?,
720 linear_start: candle_nn::linear_no_bias(
721 cfg.hidden_size,
722 cfg.hidden_size * 2,
723 vb.pp("linear_start"),
724 )?,
725 depthwise_conv1d: candle_nn::conv1d_no_bias(
726 cfg.hidden_size,
727 cfg.hidden_size,
728 cfg.conf_conv_kernel_size,
729 candle_nn::Conv1dConfig {
730 stride: 1,
731 padding: 0,
732 dilation: 1,
733 groups: cfg.hidden_size,
734 cudnn_fwd_algo: None,
735 },
736 vb.pp("depthwise_conv1d"),
737 )?,
738 conv_norm: RmsNorm::new(cfg.hidden_size, cfg.rms_norm_eps, vb.pp("conv_norm"))?,
739 linear_end: candle_nn::linear_no_bias(
740 cfg.hidden_size,
741 cfg.hidden_size,
742 vb.pp("linear_end"),
743 )?,
744 causal_padding: cfg.conf_conv_kernel_size - 1,
745 gradient_clipping: cfg.gradient_clipping,
746 })
747 }
748
749 fn forward(&self, audio_encodings: &Tensor) -> Result<Tensor> {
750 let residual = audio_encodings;
751 let x = self.pre_layer_norm.forward(audio_encodings)?;
752 let x = self.linear_start.forward(&x)?;
753 let half = x.dim(D::Minus1)? / 2;
754 let x1 = x.narrow(D::Minus1, 0, half)?;
755 let x2 = x.narrow(D::Minus1, half, half)?;
756 let x = (x1 * candle_nn::ops::sigmoid(&x2)?)?;
757 let x = x.transpose(D::Minus1, D::Minus2)?;
758 let x = x.pad_with_zeros(D::Minus1, self.causal_padding, 0)?;
759 let x = self
760 .depthwise_conv1d
761 .forward(&x.to_dtype(DType::F32)?)?
762 .to_dtype(audio_encodings.dtype())?
763 .transpose(D::Minus2, D::Minus1)?
764 .clamp(-self.gradient_clipping, self.gradient_clipping)?;
765 let x = self.conv_norm.forward(&x)?;
766 let x = candle_nn::ops::silu(&x)?;
767 let x = self.linear_end.forward(&x)?;
768 residual.broadcast_add(&x)
769 }
770}
771
772#[derive(Debug, Clone)]
775struct ConformerBlock {
776 ffw_layer_start: ConformerFeedForward,
777 attention: ConformerAttention,
778 lconv1d: ConformerLightConv1d,
779 ffw_layer_end: ConformerFeedForward,
780 norm: RmsNorm,
781 gradient_clipping: f64,
782}
783
784impl ConformerBlock {
785 fn new(cfg: &Gemma4AudioConfig, vb: VarBuilder) -> Result<Self> {
786 Ok(Self {
787 ffw_layer_start: ConformerFeedForward::new(cfg, vb.pp("feed_forward1"))?,
788 attention: ConformerAttention::new(cfg, vb.clone())?,
789 lconv1d: ConformerLightConv1d::new(cfg, vb.pp("lconv1d"))?,
790 ffw_layer_end: ConformerFeedForward::new(cfg, vb.pp("feed_forward2"))?,
791 norm: RmsNorm::new(cfg.hidden_size, cfg.rms_norm_eps, vb.pp("norm_out"))?,
792 gradient_clipping: cfg.gradient_clipping,
793 })
794 }
795
796 fn forward(&self, audio_encodings: &Tensor, audio_mel_mask: &Tensor) -> Result<Tensor> {
797 let x = self.ffw_layer_start.forward(audio_encodings)?;
798 let x = self.attention.forward(&x, audio_mel_mask)?;
799 let x = self.lconv1d.forward(&x)?;
800 let x = self
801 .ffw_layer_end
802 .forward(&x)?
803 .clamp(-self.gradient_clipping, self.gradient_clipping)?;
804 self.norm.forward(&x)
805 }
806}
807
808#[derive(Debug, Clone)]
811pub struct AudioModel {
812 subsample_conv_projection: SubSampleConvProjection,
813 conformer: Vec<ConformerBlock>,
814 conf_reduction_factor: usize,
815 output_proj: Option<candle_nn::Linear>,
816}
817
818impl AudioModel {
819 pub fn new(cfg: &Gemma4AudioConfig, vb: VarBuilder) -> Result<Self> {
820 let subsample_conv_projection =
821 SubSampleConvProjection::new(cfg, vb.pp("subsample_conv_projection"))?;
822 let mut conformer = Vec::with_capacity(cfg.conf_num_hidden_layers);
823 let vb_layers = vb.pp("layers");
824 for i in 0..cfg.conf_num_hidden_layers {
825 conformer.push(ConformerBlock::new(cfg, vb_layers.pp(i))?);
826 }
827 let output_proj = if let Some(output_dim) = cfg.output_proj_dims {
828 Some(candle_nn::linear(
829 cfg.hidden_size,
830 output_dim,
831 vb.pp("output_proj"),
832 )?)
833 } else {
834 None
835 };
836 Ok(Self {
837 subsample_conv_projection,
838 conformer,
839 conf_reduction_factor: cfg.conf_reduction_factor,
840 output_proj,
841 })
842 }
843
844 pub fn forward(&self, audio_mel: &Tensor, audio_mel_mask: &Tensor) -> Result<(Tensor, Tensor)> {
845 let (mut audio_encodings, mut current_mask) = self
846 .subsample_conv_projection
847 .forward(audio_mel, audio_mel_mask)?;
848
849 for block in &self.conformer {
850 audio_encodings = block.forward(&audio_encodings, ¤t_mask)?;
851 }
852
853 if self.conf_reduction_factor > 1 {
855 let stride = self.conf_reduction_factor;
856 let enc_len = audio_encodings.dim(1)?;
857 let reduced_len = enc_len.div_ceil(stride);
858 let indices: Vec<u32> = (0..reduced_len)
859 .map(|i| (i * stride).min(enc_len - 1) as u32)
860 .collect();
861 let indices = Tensor::from_vec(indices, reduced_len, audio_encodings.device())?;
862 audio_encodings = audio_encodings.index_select(&indices, 1)?;
863 current_mask = current_mask.index_select(&indices, 1)?;
864 }
865
866 if let Some(ref output_proj) = self.output_proj {
867 audio_encodings = output_proj.forward(&audio_encodings)?;
868 }
869
870 let enc_len = audio_encodings.dim(1)?;
872 let mask_len = current_mask.dim(1)?;
873 if mask_len != enc_len {
874 if enc_len < mask_len {
875 current_mask = current_mask.narrow(1, 0, enc_len)?;
876 } else {
877 current_mask = current_mask.pad_with_zeros(1, 0, enc_len - mask_len)?;
878 }
879 }
880
881 let valid_mask = current_mask.eq(0.0)?;
883 let zeros = Tensor::zeros_like(&audio_encodings)?;
884 let audio_encodings = valid_mask
885 .unsqueeze(D::Minus1)?
886 .broadcast_as(audio_encodings.shape())?
887 .where_cond(&audio_encodings, &zeros)?;
888
889 Ok((audio_encodings, current_mask))
890 }
891}