1use super::voxtral_llama::{VoxtralLlama, VoxtralLlamaCache, VoxtralLlamaConfig};
2use candle::{DType, Device, IndexOp, Module, Result, Tensor, D};
3use candle_nn::{
4 layer_norm, linear, linear_no_bias, Conv1d, Dropout, LayerNorm, Linear, VarBuilder,
5};
6use rand::Rng;
7
8#[derive(Debug, Clone)]
9pub struct VoxtralEncoderConfig {
10 pub vocab_size: usize,
11 pub hidden_size: usize,
12 pub intermediate_size: usize,
13 pub num_hidden_layers: usize,
14 pub num_attention_heads: usize,
15 pub num_key_value_heads: usize,
16 pub head_dim: usize,
17 pub scale_embedding: bool,
18 pub activation_function: String,
19 pub num_mel_bins: usize,
20 pub max_source_positions: usize,
21 pub initializer_range: f64,
22 pub attention_dropout: f64,
23 pub dropout: f64,
25 pub layerdrop: f64,
26 pub activation_dropout: f64,
27}
28
29#[derive(Debug, Clone)]
30pub struct VoxtralConfig {
31 pub audio_config: VoxtralEncoderConfig,
32 pub text_config: VoxtralLlamaConfig,
33 pub audio_token_id: usize,
34 pub projector_hidden_act: String,
35}
36
37impl Default for VoxtralConfig {
38 fn default() -> Self {
39 Self {
40 audio_config: VoxtralEncoderConfig::default(),
41 text_config: VoxtralLlamaConfig::voxtral_3b(),
42 audio_token_id: 24,
43 projector_hidden_act: "gelu".to_string(),
44 }
45 }
46}
47
48impl Default for VoxtralEncoderConfig {
49 fn default() -> Self {
50 Self {
51 vocab_size: 51866,
52 hidden_size: 1280,
53 intermediate_size: 5120,
54 num_hidden_layers: 32,
55 num_attention_heads: 20,
56 num_key_value_heads: 20,
57 head_dim: 64,
58 scale_embedding: false,
59 activation_function: "gelu".to_string(),
60 num_mel_bins: 128,
61 max_source_positions: 1500,
62 initializer_range: 0.02,
63 attention_dropout: 0.0,
64 dropout: 0.0,
66 layerdrop: 0.0,
67 activation_dropout: 0.0,
68 }
69 }
70}
71
72impl VoxtralEncoderConfig {
73 pub fn with_whisper_compatibility(mut self) -> Self {
75 self.dropout = 0.0;
76 self.layerdrop = 0.0;
77 self.activation_dropout = 0.0;
78 self
79 }
80}
81
82#[derive(Debug, Clone)]
84pub struct VoxtralCache {
85 cache: VoxtralLlamaCache,
86 audio_processed: bool,
87 cached_audio_embeds: Option<Tensor>,
88 cached_audio_positions: Option<Vec<(usize, usize)>>,
89}
90
91#[derive(Debug, Clone)]
92pub struct VoxtralGenerationConfig {
93 pub max_new_tokens: usize,
94 pub temperature: f64,
95 pub top_p: Option<f64>,
96 pub device: Device,
97 pub cache: Option<VoxtralCache>,
99}
100
101impl VoxtralGenerationConfig {
102 pub fn new(device: Device) -> Self {
103 Self {
104 max_new_tokens: 500,
105 temperature: 0.0,
106 top_p: None,
107 device,
108 cache: None,
109 }
110 }
111}
112
113impl VoxtralCache {
114 pub fn new(
115 use_kv_cache: bool,
116 dtype: DType,
117 config: &VoxtralLlamaConfig,
118 device: &Device,
119 ) -> Result<Self> {
120 Ok(Self {
121 cache: VoxtralLlamaCache::new(use_kv_cache, dtype, config, device)?,
122 audio_processed: false,
123 cached_audio_embeds: None,
124 cached_audio_positions: None,
125 })
126 }
127
128 pub fn reset(&mut self) {
129 self.audio_processed = false;
131 self.cached_audio_embeds = None;
132 self.cached_audio_positions = None;
133 }
136}
137
138fn safe_clamp(x: &Tensor) -> Result<Tensor> {
140 match x.dtype() {
141 DType::F16 => {
142 let max_val = 64504.0;
144 x.clamp(-max_val, max_val)
145 }
146 DType::BF16 => {
147 Ok(x.clone())
149 }
150 _ => Ok(x.clone()),
151 }
152}
153
154pub fn replace_audio_tokens(
156 inputs_embeds: &Tensor,
157 audio_embeds: &Tensor,
158 audio_positions: &[(usize, usize)],
159 device: &Device,
160) -> Result<Tensor> {
161 if audio_positions.is_empty() {
162 return Ok(inputs_embeds.clone());
163 }
164
165 let (batch_size, seq_len, hidden_size) = inputs_embeds.dims3()?;
166 let num_audio_tokens = audio_positions.len();
167
168 let audio_embeds_dims = audio_embeds.dims2()?;
170 let total_audio_embeds = audio_embeds_dims.0;
171
172 let audio_embeds = if total_audio_embeds >= num_audio_tokens {
175 if num_audio_tokens == total_audio_embeds {
177 audio_embeds.clone()
178 } else {
179 audio_embeds.i(0..num_audio_tokens)?
180 }
181 } else {
182 candle::bail!(
183 "Not enough audio embeddings: need {}, got {}. Input sequence should have {} audio tokens.",
184 num_audio_tokens,
185 total_audio_embeds,
186 total_audio_embeds
187 );
188 };
189
190 let mut result = inputs_embeds.clone();
192
193 for (idx, &(batch_idx, seq_idx)) in audio_positions.iter().enumerate() {
196 if batch_idx >= batch_size || seq_idx >= seq_len {
197 candle::bail!(
198 "Invalid audio position: ({}, {}) for tensor shape ({}, {}, {})",
199 batch_idx,
200 seq_idx,
201 batch_size,
202 seq_len,
203 hidden_size
204 );
205 }
206
207 let audio_embed = audio_embeds.i(idx)?;
209
210 let mut position_mask = vec![0f32; batch_size * seq_len];
212 position_mask[batch_idx * seq_len + seq_idx] = 1.0;
213 let position_mask = Tensor::new(position_mask.as_slice(), device)?
214 .reshape((batch_size, seq_len, 1))?
215 .to_dtype(inputs_embeds.dtype())?;
216
217 let audio_embed_broadcast = audio_embed.unsqueeze(0)?.unsqueeze(0)?.broadcast_as((
219 batch_size,
220 seq_len,
221 hidden_size,
222 ))?;
223
224 let inverse_mask = (1.0 - &position_mask)?;
226 result = (result.broadcast_mul(&inverse_mask)?
227 + audio_embed_broadcast.broadcast_mul(&position_mask)?)?;
228 }
229
230 Ok(result)
231}
232
233pub fn find_audio_token_positions(
235 input_ids: &Tensor,
236 audio_token_id: usize,
237) -> Result<Vec<(usize, usize)>> {
238 let input_ids = if input_ids.dtype() == candle::DType::U32 {
240 input_ids.to_dtype(candle::DType::I64)?
241 } else {
242 input_ids.clone()
243 };
244
245 let input_ids = input_ids.to_vec2::<i64>()?;
246 let mut positions = Vec::new();
247
248 for (batch_idx, sequence) in input_ids.iter().enumerate() {
249 for (seq_idx, &token_id) in sequence.iter().enumerate() {
250 if token_id as usize == audio_token_id {
251 positions.push((batch_idx, seq_idx));
252 }
253 }
254 }
255
256 Ok(positions)
257}
258
259#[derive(Debug, Clone)]
260struct VoxtralAttention {
261 q_proj: Linear,
262 k_proj: Linear,
263 v_proj: Linear,
264 out_proj: Linear,
265 num_heads: usize,
266 head_dim: usize,
267 scaling: f64,
268 attention_dropout: Dropout,
269}
270
271impl VoxtralAttention {
272 fn new(cfg: &VoxtralEncoderConfig, vb: VarBuilder) -> Result<Self> {
273 let embed_dim = cfg.hidden_size;
274 let num_heads = cfg.num_attention_heads;
275 let head_dim = embed_dim / num_heads;
276
277 if head_dim * num_heads != embed_dim {
278 candle::bail!(
279 "embed_dim must be divisible by num_heads ({} % {} != 0)",
280 embed_dim,
281 num_heads
282 );
283 }
284
285 let scaling = (head_dim as f64).powf(-0.5);
286
287 let q_proj = linear(embed_dim, embed_dim, vb.pp("q_proj"))?;
288 let k_proj = linear_no_bias(embed_dim, embed_dim, vb.pp("k_proj"))?;
289 let v_proj = linear(embed_dim, embed_dim, vb.pp("v_proj"))?;
290 let out_proj = linear(embed_dim, embed_dim, vb.pp("out_proj"))?;
291
292 let attention_dropout = Dropout::new(cfg.attention_dropout as f32);
293
294 Ok(Self {
295 q_proj,
296 k_proj,
297 v_proj,
298 out_proj,
299 num_heads,
300 head_dim,
301 scaling,
302 attention_dropout,
303 })
304 }
305
306 fn reshape_for_scores(&self, x: &Tensor, seq_len: usize, bsz: usize) -> Result<Tensor> {
307 x.reshape((bsz, seq_len, self.num_heads, self.head_dim))?
308 .transpose(1, 2)?
309 .contiguous()
310 }
311}
312
313impl Module for VoxtralAttention {
314 fn forward(&self, x: &Tensor) -> Result<Tensor> {
315 let (bsz, seq_len, _) = x.dims3()?;
316
317 let q = (self.q_proj.forward(x)? * self.scaling)?;
319 let k = self.k_proj.forward(x)?;
320 let v = self.v_proj.forward(x)?;
321
322 let q = self.reshape_for_scores(&q, seq_len, bsz)?;
324 let k = self.reshape_for_scores(&k, seq_len, bsz)?;
325 let v = self.reshape_for_scores(&v, seq_len, bsz)?;
326
327 let scores = q.matmul(&k.transpose(D::Minus2, D::Minus1)?)?;
330
331 let attn_weights = candle_nn::ops::softmax_last_dim(&scores)?;
333
334 let attn_weights = self.attention_dropout.forward(&attn_weights, false)?;
336
337 let attn_output = attn_weights.matmul(&v)?;
339
340 let attn_output = attn_output.transpose(1, 2)?.contiguous()?.reshape((
342 bsz,
343 seq_len,
344 self.num_heads * self.head_dim,
345 ))?;
346
347 self.out_proj.forward(&attn_output)
348 }
349}
350
351#[derive(Debug, Clone)]
352struct VoxtralEncoderLayer {
353 self_attn: VoxtralAttention,
354 self_attn_layer_norm: LayerNorm,
355 fc1: Linear,
356 fc2: Linear,
357 final_layer_norm: LayerNorm,
358 activation: candle_nn::Activation,
359 dropout: Dropout,
360 activation_dropout: Dropout,
361}
362
363impl VoxtralEncoderLayer {
364 fn new(cfg: &VoxtralEncoderConfig, vb: VarBuilder) -> Result<Self> {
365 let embed_dim = cfg.hidden_size;
366
367 let self_attn = VoxtralAttention::new(cfg, vb.pp("self_attn"))?;
368 let self_attn_layer_norm = layer_norm(embed_dim, 1e-5, vb.pp("self_attn_layer_norm"))?;
369 let fc1 = linear(embed_dim, cfg.intermediate_size, vb.pp("fc1"))?;
370 let fc2 = linear(cfg.intermediate_size, embed_dim, vb.pp("fc2"))?;
371 let final_layer_norm = layer_norm(embed_dim, 1e-5, vb.pp("final_layer_norm"))?;
372
373 let activation = match cfg.activation_function.as_str() {
374 "gelu" => candle_nn::Activation::Gelu,
375 "relu" => candle_nn::Activation::Relu,
376 _ => candle::bail!(
377 "Unsupported activation function: {}",
378 cfg.activation_function
379 ),
380 };
381
382 let dropout = Dropout::new(cfg.dropout as f32);
383 let activation_dropout = Dropout::new(cfg.activation_dropout as f32);
384
385 Ok(Self {
386 self_attn,
387 self_attn_layer_norm,
388 fc1,
389 fc2,
390 final_layer_norm,
391 activation,
392 dropout,
393 activation_dropout,
394 })
395 }
396
397 pub fn get_fc1_out_dim(&self) -> usize {
398 self.fc1.weight().dims()[0]
401 }
402
403 fn forward(&self, x: &Tensor, training: bool) -> Result<Tensor> {
404 let residual = x;
406 let x = self.self_attn_layer_norm.forward(x)?;
407 let x = self.self_attn.forward(&x)?;
408 let x = self.dropout.forward(&x, training)?;
409 let x = (x + residual)?;
410
411 let residual = &x;
413 let x = self.final_layer_norm.forward(&x)?;
414 let x = self.fc1.forward(&x)?;
415 let x = x.apply(&self.activation)?;
416 let x = self.activation_dropout.forward(&x, training)?;
417 let x = self.fc2.forward(&x)?;
418 let x = self.dropout.forward(&x, training)?;
419 let x = (x + residual)?;
420
421 safe_clamp(&x)
423 }
424}
425
426#[derive(Debug, Clone)]
427pub struct VoxtralEncoder {
428 conv1: Conv1d,
429 conv2: Conv1d,
430 embed_positions: Tensor,
431 layers: Vec<VoxtralEncoderLayer>,
432 layer_norm: LayerNorm,
433 dropout: Dropout,
434 layerdrop: f64,
435}
436
437impl VoxtralEncoder {
438 pub fn new(cfg: &VoxtralEncoderConfig, vb: VarBuilder) -> Result<Self> {
439 let cfg = cfg.clone().with_whisper_compatibility();
441
442 let embed_dim = cfg.hidden_size;
443
444 let conv1 = candle_nn::conv1d(
446 cfg.num_mel_bins,
447 embed_dim,
448 3,
449 candle_nn::Conv1dConfig {
450 padding: 1,
451 ..Default::default()
452 },
453 vb.pp("conv1"),
454 )?;
455
456 let conv2 = candle_nn::conv1d(
457 embed_dim,
458 embed_dim,
459 3,
460 candle_nn::Conv1dConfig {
461 stride: 2,
462 padding: 1,
463 ..Default::default()
464 },
465 vb.pp("conv2"),
466 )?;
467
468 let embed_positions = vb.get(
470 (cfg.max_source_positions, embed_dim),
471 "embed_positions.weight",
472 )?;
473
474 let mut layers = Vec::with_capacity(cfg.num_hidden_layers);
476 for i in 0..cfg.num_hidden_layers {
477 layers.push(VoxtralEncoderLayer::new(
478 &cfg,
479 vb.pp(format!("layers.{i}")),
480 )?);
481 }
482
483 let layer_norm = layer_norm(embed_dim, 1e-5, vb.pp("layer_norm"))?;
484 let dropout = Dropout::new(cfg.dropout as f32);
485
486 Ok(Self {
487 conv1,
488 conv2,
489 embed_positions,
490 layers,
491 layer_norm,
492 dropout,
493 layerdrop: cfg.layerdrop,
494 })
495 }
496
497 pub fn forward(&self, input_features: &Tensor) -> Result<Tensor> {
498 self.forward_with_training(input_features, false)
499 }
500
501 pub fn forward_with_training(&self, input_features: &Tensor, training: bool) -> Result<Tensor> {
502 let expected_dtype = self.conv1.weight().dtype();
504 let input_features = if input_features.dtype() != expected_dtype {
505 input_features.to_dtype(expected_dtype)?
506 } else {
507 input_features.clone()
508 };
509
510 let x = if false {
512 let conv1_weight_f32 = self.conv1.weight().to_dtype(DType::F32)?;
515 let conv1_bias_f32 = if let Some(bias) = self.conv1.bias() {
516 Some(bias.to_dtype(DType::F32)?)
517 } else {
518 None
519 };
520
521 let mut conv_result = input_features.conv1d(&conv1_weight_f32, 1, 1, 1, 1)?;
523 if let Some(bias) = conv1_bias_f32 {
524 conv_result = conv_result.broadcast_add(&bias.unsqueeze(0)?.unsqueeze(2)?)?;
525 }
526 conv_result
527 } else {
528 self.conv1.forward(&input_features)?
529 };
530
531 let x = x.gelu()?;
533
534 let x = if false {
536 let conv2_weight_f32 = self.conv2.weight().to_dtype(DType::F32)?;
539 let conv2_bias_f32 = if let Some(bias) = self.conv2.bias() {
540 Some(bias.to_dtype(DType::F32)?)
541 } else {
542 None
543 };
544
545 let mut conv_result = x.conv1d(&conv2_weight_f32, 2, 1, 1, 1)?;
547 if let Some(bias) = conv2_bias_f32 {
548 conv_result = conv_result.broadcast_add(&bias.unsqueeze(0)?.unsqueeze(2)?)?;
549 }
550 conv_result
551 } else {
552 self.conv2.forward(&x)?
553 };
554
555 let x = x.gelu()?;
557
558 let x = x.transpose(1, 2)?;
560
561 let seq_len = x.dim(1)?;
563 let positions = self.embed_positions.i(..seq_len)?;
564
565 let x = if false {
568 let x_f32 = x.to_dtype(candle::DType::F32)?;
571 let positions_f32 = positions.to_dtype(candle::DType::F32)?;
572 x_f32.broadcast_add(&positions_f32)? } else if x.dtype() != positions.dtype() {
574 let x_f32 = x.to_dtype(candle::DType::F32)?;
576 let result_f32 = x_f32.broadcast_add(&positions)?;
577 result_f32.to_dtype(x.dtype())?
579 } else {
580 x.broadcast_add(&positions)?
581 };
582
583 let mut x = self.dropout.forward(&x, training)?;
585
586 for (idx, layer) in self.layers.iter().enumerate() {
587 x = self.forward_layer_with_dropout(&x, layer, idx, training)?;
589 }
590
591 let x = self.layer_norm.forward(&x)?;
593
594 Ok(x)
595 }
596
597 fn forward_layer_with_dropout(
599 &self,
600 x: &Tensor,
601 layer: &VoxtralEncoderLayer,
602 _layer_idx: usize,
603 training: bool,
604 ) -> Result<Tensor> {
605 if training && self.layerdrop > 0.0 {
606 let mut rng = rand::rng();
608 let keep_prob = 1.0 - self.layerdrop;
609 let keep: bool = rng.random::<f64>() < keep_prob;
610
611 if !keep {
612 return Ok(x.clone());
614 }
615 }
616
617 layer.forward(x, training)
618 }
619
620 pub fn get_intermediate_size(&self) -> usize {
622 if !self.layers.is_empty() {
623 self.layers[0].get_fc1_out_dim()
624 } else {
625 5120 }
628 }
629
630 pub fn process_long_audio(
632 &self,
633 input_features: &Tensor,
634 chunk_size: usize,
635 overlap: usize,
636 ) -> Result<Tensor> {
637 let (_batch_size, _num_mel, seq_len) = input_features.dims3()?;
638
639 if seq_len <= chunk_size {
640 return self.forward(input_features);
641 }
642
643 let mut outputs = Vec::new();
644 let step = chunk_size - overlap;
645
646 for start in (0..seq_len).step_by(step) {
647 let end = (start + chunk_size).min(seq_len);
648 let chunk = input_features.i((.., .., start..end))?;
649
650 let output = self.forward(&chunk)?;
652
653 if !outputs.is_empty() && overlap > 0 {
655 let overlap_frames = overlap / 2; let last_output: &mut Tensor = outputs.last_mut().unwrap();
657 let last_len = last_output.dim(1)?;
658
659 let overlap_start = last_len.saturating_sub(overlap_frames);
661 let overlap_new = output.i((.., ..overlap_frames, ..))?;
662 let overlap_old = last_output.i((.., overlap_start.., ..))?;
663 let averaged = ((overlap_old + overlap_new)? * 0.5)?;
664
665 *last_output =
667 Tensor::cat(&[&last_output.i((.., ..overlap_start, ..))?, &averaged], 1)?;
668
669 outputs.push(output.i((.., overlap_frames.., ..))?);
671 } else {
672 outputs.push(output);
673 }
674 }
675
676 let outputs_ref: Vec<&Tensor> = outputs.iter().collect();
678 Tensor::cat(&outputs_ref, 1)
679 }
680}
681
682#[derive(Debug, Clone)]
683pub struct VoxtralMultiModalProjector {
684 linear_1: Linear,
685 linear_2: Linear,
686 activation: candle_nn::Activation,
687}
688
689impl VoxtralMultiModalProjector {
690 pub fn new(cfg: &VoxtralConfig, vb: VarBuilder) -> Result<Self> {
691 let linear_1 = linear_no_bias(
692 cfg.audio_config.intermediate_size,
693 cfg.text_config.hidden_size,
694 vb.pp("linear_1"),
695 )?;
696
697 let linear_2 = linear_no_bias(
698 cfg.text_config.hidden_size,
699 cfg.text_config.hidden_size,
700 vb.pp("linear_2"),
701 )?;
702
703 let activation = match cfg.projector_hidden_act.as_str() {
704 "gelu" => candle_nn::Activation::Gelu,
705 "relu" => candle_nn::Activation::Relu,
706 _ => candle::bail!(
707 "Unsupported projector activation: {}",
708 cfg.projector_hidden_act
709 ),
710 };
711
712 Ok(Self {
713 linear_1,
714 linear_2,
715 activation,
716 })
717 }
718
719 pub fn forward(&self, audio_features: &Tensor) -> Result<Tensor> {
720 let x = self.linear_1.forward(audio_features)?;
721 let x = x.apply(&self.activation)?;
722 self.linear_2.forward(&x)
723 }
724}
725
726#[derive(Debug, Clone)]
727pub struct VoxtralForConditionalGeneration {
728 audio_tower: VoxtralEncoder,
729 language_model: VoxtralLlama,
730 multi_modal_projector: VoxtralMultiModalProjector,
731 audio_token_id: usize,
732 audio_config: VoxtralEncoderConfig,
733 text_config: VoxtralLlamaConfig,
734}
735
736impl VoxtralForConditionalGeneration {
737 pub fn new(cfg: &VoxtralConfig, vb: VarBuilder) -> Result<Self> {
738 let audio_tower = VoxtralEncoder::new(&cfg.audio_config, vb.pp("audio_tower"))?;
739 let language_model = VoxtralLlama::load(vb.pp("language_model"), &cfg.text_config)?;
740 let multi_modal_projector =
741 VoxtralMultiModalProjector::new(cfg, vb.pp("multi_modal_projector"))?;
742
743 Ok(Self {
744 audio_tower,
745 language_model,
746 multi_modal_projector,
747 audio_token_id: cfg.audio_token_id,
748 audio_config: cfg.audio_config.clone(),
749 text_config: cfg.text_config.clone(),
750 })
751 }
752
753 pub fn audio_token_id(&self) -> usize {
755 self.audio_token_id
756 }
757
758 pub fn text_config(&self) -> &VoxtralLlamaConfig {
760 &self.text_config
761 }
762
763 pub fn audio_config(&self) -> &VoxtralEncoderConfig {
765 &self.audio_config
766 }
767
768 pub fn get_audio_embeds(&self, input_features: &Tensor) -> Result<Tensor> {
770 let audio_outputs = self.audio_tower.forward(input_features)?;
771
772 let (batch_size, seq_len, hidden_size) = audio_outputs.dims3()?;
776
777 let total_elements = batch_size * seq_len * hidden_size;
781 let new_batch_size = total_elements / self.audio_config.intermediate_size;
782
783 if total_elements % self.audio_config.intermediate_size != 0 {
785 return Err(candle::Error::DimOutOfRange {
786 shape: candle::Shape::from_dims(&[batch_size, seq_len, hidden_size]),
787 dim: 0,
788 op: "reshape",
789 });
790 }
791
792 let audio_hidden =
793 audio_outputs.reshape((new_batch_size, self.audio_config.intermediate_size))?;
794
795 let projected = self.multi_modal_projector.forward(&audio_hidden)?;
797
798 Ok(projected)
801 }
802
803 pub fn get_audio_embeds_chunked(
805 &self,
806 input_features: &Tensor,
807 chunk_size: usize,
808 overlap: usize,
809 ) -> Result<Tensor> {
810 let audio_outputs =
811 self.audio_tower
812 .process_long_audio(input_features, chunk_size, overlap)?;
813
814 let (batch_size, seq_len, hidden_size) = audio_outputs.dims3()?;
816 let total_elements = batch_size * seq_len * hidden_size;
818 let new_batch_size = total_elements / self.audio_config.intermediate_size;
819 let audio_hidden =
820 audio_outputs.reshape((new_batch_size, self.audio_config.intermediate_size))?;
821
822 let projected = self.multi_modal_projector.forward(&audio_hidden)?;
823
824 let text_hidden_size = self.text_config.hidden_size;
826 let projected = projected.reshape((batch_size, seq_len, text_hidden_size))?;
827
828 let pooled = projected.mean(1)?; Ok(pooled)
833 }
834
835 pub fn forward(
837 &self,
838 input_ids: &Tensor,
839 input_features: Option<&Tensor>,
840 cache: &mut VoxtralCache,
841 index_pos: usize,
842 ) -> Result<Tensor> {
843 let mut inputs_embeds = self.language_model.embed(input_ids)?;
845
846 if let Some(features) = input_features {
848 if !cache.audio_processed {
849 let audio_embeds = self.get_audio_embeds(features)?;
850
851 let audio_positions = find_audio_token_positions(input_ids, self.audio_token_id)?;
852
853 cache.cached_audio_embeds = Some(audio_embeds.clone());
855 cache.cached_audio_positions = Some(audio_positions.clone());
856 cache.audio_processed = true;
857
858 inputs_embeds = replace_audio_tokens(
859 &inputs_embeds,
860 &audio_embeds,
861 &audio_positions,
862 input_ids.device(),
863 )?;
864 }
865 }
866
867 self.language_model
869 .forward_input_embed(&inputs_embeds, index_pos, &mut cache.cache)
870 }
871
872 pub fn generate(
874 &self,
875 input_ids: &Tensor,
876 input_features: Option<&Tensor>,
877 config: VoxtralGenerationConfig,
878 ) -> Result<Vec<u32>> {
879 if config.max_new_tokens == 0 {
881 return input_ids.i(0)?.to_vec1::<u32>(); }
883
884 if config.temperature < 0.0 {
885 candle::bail!(
886 "Temperature must be non-negative, got {}",
887 config.temperature
888 );
889 }
890
891 if let Some(p) = config.top_p {
892 if !(0.0..=1.0).contains(&p) {
893 candle::bail!("top_p must be between 0 and 1, got {}", p);
894 }
895 }
896
897 let mut final_cache = if let Some(cache) = config.cache {
898 cache
899 } else {
900 let dummy_token = Tensor::new(&[1u32], &config.device)?;
902 let dummy_embed = self.language_model.embed(&dummy_token)?;
903 let model_dtype = dummy_embed.dtype();
904 VoxtralCache::new(true, model_dtype, &self.text_config, &config.device)?
905 };
906 let mut tokens = input_ids.i(0)?.to_vec1::<u32>()?; let initial_len = tokens.len();
908
909 for idx in 0..config.max_new_tokens {
910 let (input, index_pos) = if idx == 0 {
911 (input_ids.clone(), 0)
912 } else {
913 let last_token = tokens[tokens.len() - 1];
915 let calculated_pos = initial_len + idx - 1;
916 (
917 Tensor::new(&[last_token], &config.device)?.unsqueeze(0)?,
918 calculated_pos,
919 )
920 };
921
922 let logits = if idx == 0 {
923 match self.forward(&input, input_features, &mut final_cache, index_pos) {
925 Ok(logits) => logits,
926 Err(e) => {
927 return Err(candle::Error::Msg(format!(
928 "Failed to generate tokens: {e}"
929 )));
930 }
931 }
932 } else {
933 match self.forward(&input, None, &mut final_cache, index_pos) {
935 Ok(logits) => logits,
936 Err(e) => {
937 return Err(candle::Error::Msg(format!(
938 "Failed to generate tokens: {e}"
939 )));
940 }
941 }
942 };
943
944 let logits = if logits.dims().len() == 3 {
946 logits.i((.., logits.dim(1)? - 1, ..))?
948 } else {
949 logits
951 };
952
953 let next_token = if config.temperature > 0.0 {
954 let prs = (logits / config.temperature)?;
956 let prs = candle_nn::ops::softmax_last_dim(&prs)?;
957
958 if let Some(top_p_val) = config.top_p {
959 sample_top_p(&prs.squeeze(0)?, top_p_val, &config.device)?
961 } else {
962 let probs_vec = prs.squeeze(0)?.to_vec1::<f32>()?;
964 let mut rng = rand::rng();
965 let mut cumsum = 0.0;
966 let rand_val: f32 = rng.random();
967 let mut sampled = 0u32;
968
969 for (idx, &prob) in probs_vec.iter().enumerate() {
970 cumsum += prob;
971 if cumsum > rand_val {
972 sampled = idx as u32;
973 break;
974 }
975 }
976 sampled
977 }
978 } else {
979 let argmax_result = match logits.argmax(D::Minus1) {
981 Ok(result) => result,
982 Err(e) => {
983 return Err(candle::Error::Msg(format!("Argmax failed: {e}")));
984 }
985 };
986
987 if argmax_result.dims().is_empty() {
990 match argmax_result.to_scalar::<u32>() {
992 Ok(token) => token,
993 Err(e) => {
994 return Err(candle::Error::Msg(format!("to_scalar failed: {e}")));
995 }
996 }
997 } else if argmax_result.dims() == [1] {
998 match argmax_result.i(0) {
1000 Ok(scalar_tensor) => match scalar_tensor.to_scalar::<u32>() {
1001 Ok(token) => token,
1002 Err(e) => {
1003 return Err(candle::Error::Msg(format!(
1004 "to_scalar on extracted element failed: {e}"
1005 )));
1006 }
1007 },
1008 Err(e) => {
1009 return Err(candle::Error::Msg(format!(
1010 "indexing argmax result failed: {e}"
1011 )));
1012 }
1013 }
1014 } else {
1015 return Err(candle::Error::Msg(format!(
1016 "Unexpected argmax result shape: {:?}",
1017 argmax_result.shape()
1018 )));
1019 }
1020 };
1021
1022 tokens.push(next_token);
1023
1024 let eos_tokens = [2u32, 128001, 128009, 128256]; if eos_tokens.contains(&next_token) {
1031 break;
1032 }
1033
1034 if next_token == 0 && tokens.len() > 5 {
1036 let last_5_tokens = &tokens[tokens.len() - 5..];
1037 if last_5_tokens.iter().all(|&t| t == 0) {
1038 break;
1039 }
1040 }
1041 }
1042
1043 Ok(tokens)
1044 }
1045}
1046
1047fn sample_top_p(probs: &Tensor, top_p: f64, _device: &Device) -> Result<u32> {
1049 let (sorted_probs, sorted_indices) = probs.sort_last_dim(false)?;
1050 let cumsum = sorted_probs.cumsum(D::Minus1)?;
1051 let mask = cumsum.le(top_p)?;
1052
1053 let filtered_probs = sorted_probs.where_cond(&mask, &Tensor::zeros_like(&sorted_probs)?)?;
1055 let filtered_probs = (&filtered_probs / filtered_probs.sum_keepdim(D::Minus1)?)?;
1056
1057 let probs_vec = filtered_probs.to_vec1::<f32>()?;
1060 let mut cumsum = 0.0;
1061 let mut rng = rand::rng();
1062 let rand_val: f32 = rng.random();
1063 let mut sample_idx = 0;
1064
1065 for (idx, &prob) in probs_vec.iter().enumerate() {
1066 cumsum += prob;
1067 if cumsum > rand_val {
1068 sample_idx = idx;
1069 break;
1070 }
1071 }
1072
1073 sorted_indices.i(sample_idx)?.to_scalar::<u32>()
1074}