Text-to-Speech
PyTorch
moss_tts_nano
custom_code
Kuangwei Chen commited on
Commit
6e12827
·
1 Parent(s): ad3fd89

Update nano-tts

Browse files
gpt2_decoder.py CHANGED
@@ -194,13 +194,24 @@ class NanoGPT2Attention(nn.Module):
194
  key = key.transpose(1, 2)
195
  value = value.transpose(1, 2)
196
  mask = None
 
197
  if attention_mask is not None:
 
 
198
  mask = self._causal_attention_mask(
199
  attention_mask=attention_mask,
200
- query_length=query.shape[-2],
201
- key_length=key.shape[-2],
202
  device=query.device,
203
  )
 
 
 
 
 
 
 
 
204
  output = torch.nn.functional.scaled_dot_product_attention(
205
  query,
206
  key,
@@ -209,6 +220,8 @@ class NanoGPT2Attention(nn.Module):
209
  dropout_p=self.attn_dropout if self.training else 0.0,
210
  is_causal=mask is None,
211
  )
 
 
212
  return output.transpose(1, 2).contiguous()
213
 
214
  def _flash_attention(
 
194
  key = key.transpose(1, 2)
195
  value = value.transpose(1, 2)
196
  mask = None
197
+ query_attention_mask = None
198
  if attention_mask is not None:
199
+ query_length = query.shape[-2]
200
+ key_length = key.shape[-2]
201
  mask = self._causal_attention_mask(
202
  attention_mask=attention_mask,
203
+ query_length=query_length,
204
+ key_length=key_length,
205
  device=query.device,
206
  )
207
+ query_attention_mask = attention_mask[:, -query_length:].to(dtype=torch.bool, device=query.device)
208
+ if not bool(query_attention_mask.all()):
209
+ # SDPA can produce NaNs when a query row is fully masked. For padded query positions,
210
+ # keep a single aligned key visible, then zero the query output after attention.
211
+ mask = mask.expand(query.shape[0], -1, -1, -1).clone()
212
+ invalid_batch, invalid_query = torch.nonzero(~query_attention_mask, as_tuple=True)
213
+ aligned_key = invalid_query + max(key_length - query_length, 0)
214
+ mask[invalid_batch, :, invalid_query, aligned_key] = True
215
  output = torch.nn.functional.scaled_dot_product_attention(
216
  query,
217
  key,
 
220
  dropout_p=self.attn_dropout if self.training else 0.0,
221
  is_causal=mask is None,
222
  )
223
+ if query_attention_mask is not None and not bool(query_attention_mask.all()):
224
+ output = output.masked_fill(~query_attention_mask[:, None, :, None], 0.0)
225
  return output.transpose(1, 2).contiguous()
226
 
227
  def _flash_attention(
modeling_nanotts_global_local.py CHANGED
@@ -4,10 +4,11 @@ from __future__ import annotations
4
  import json
5
  import logging
6
  import os
 
7
  from contextlib import nullcontext
8
  from dataclasses import dataclass
9
  from pathlib import Path
10
- from typing import Any, Optional, Union
11
 
12
  import numpy as np
13
  import torch
@@ -666,6 +667,49 @@ class NanoTTSGlobalLocalForCausalLM(NanoTTSPreTrainedModel):
666
  )
667
  return resolved_batch_size
668
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
669
  def _generate_audio_token_ids_with_fallback(
670
  self,
671
  *,
@@ -740,6 +784,67 @@ class NanoTTSGlobalLocalForCausalLM(NanoTTSPreTrainedModel):
740
  )
741
  return self._extract_waveform_and_sample_rate(decoded, fallback_sample_rate=target_sample_rate)
742
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
743
  def _build_generation_row(
744
  self,
745
  batch_size: int,
@@ -756,6 +861,38 @@ class NanoTTSGlobalLocalForCausalLM(NanoTTSPreTrainedModel):
756
  row[:, :, 1:] = audio_token_ids.unsqueeze(1)
757
  return row
758
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
759
  def _sample_next_token(
760
  self,
761
  logits: torch.FloatTensor,
@@ -1300,6 +1437,74 @@ class NanoTTSGlobalLocalForCausalLM(NanoTTSPreTrainedModel):
1300
  with self._audio_tokenizer_inference_context(audio_tokenizer, decode_codes.device):
1301
  return batch_decode_fn([decode_codes], num_quantizers=effective_nq, chunk_duration=None)
1302
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1303
  @staticmethod
1304
  def _resolve_audio_tokenizer_downsample_rate(audio_tokenizer) -> int:
1305
  for holder in (audio_tokenizer, getattr(audio_tokenizer, "config", None)):
@@ -1405,8 +1610,7 @@ class NanoTTSGlobalLocalForCausalLM(NanoTTSPreTrainedModel):
1405
  )
1406
  return local_outputs.last_hidden_state[:, -1, :]
1407
 
1408
- @torch.no_grad()
1409
- def generate(
1410
  self,
1411
  input_ids: torch.LongTensor,
1412
  attention_mask: Optional[torch.Tensor] = None,
@@ -1422,7 +1626,7 @@ class NanoTTSGlobalLocalForCausalLM(NanoTTSPreTrainedModel):
1422
  audio_repetition_penalty: float = 1.0,
1423
  use_kv_cache: bool = True,
1424
  return_dict_in_generate: bool = True,
1425
- ):
1426
  if input_ids.ndim == 2:
1427
  input_ids = input_ids.unsqueeze(0)
1428
  if input_ids.ndim != 3:
@@ -1442,7 +1646,7 @@ class NanoTTSGlobalLocalForCausalLM(NanoTTSPreTrainedModel):
1442
  past_key_values = None
1443
  local_dtype = self.local_transformer.ln_f.weight.dtype
1444
 
1445
- for _ in range(max_new_frames):
1446
  generated_audio_history = torch.stack(generated_frames, dim=1) if generated_frames else None
1447
  global_inputs_embeds = self._build_inputs_embeds(current_model_input_ids)
1448
  global_outputs = self.transformer(
@@ -1528,14 +1732,484 @@ class NanoTTSGlobalLocalForCausalLM(NanoTTSPreTrainedModel):
1528
  else:
1529
  current_model_input_ids = current_input_ids
1530
 
 
 
 
 
 
 
 
 
1531
  if generated_frames:
1532
  audio_token_ids = torch.stack(generated_frames, dim=1)
1533
  else:
1534
  audio_token_ids = torch.empty((batch_size, 0, self.config.n_vq), dtype=torch.long, device=input_ids.device)
1535
 
1536
  if not return_dict_in_generate:
1537
- return audio_token_ids
1538
- return NanoTTSGenerationOutput(audio_token_ids=audio_token_ids, prompt_input_ids=input_ids)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1539
 
1540
  @torch.no_grad()
1541
  def inference(
@@ -1565,6 +2239,8 @@ class NanoTTSGlobalLocalForCausalLM(NanoTTSPreTrainedModel):
1565
  use_kv_cache: bool = True,
1566
  voice_clone_max_text_tokens: int = DEFAULT_VOICE_CLONE_MAX_TEXT_TOKENS,
1567
  voice_clone_max_memory_per_sample_gb: float = DEFAULT_VOICE_CLONE_MAX_MEMORY_PER_SAMPLE_GB,
 
 
1568
  ) -> dict[str, Any]:
1569
  resolved_device = self._resolve_device(device)
1570
  effective_nq = self._resolve_inference_nq(nq)
@@ -1631,13 +2307,17 @@ class NanoTTSGlobalLocalForCausalLM(NanoTTSPreTrainedModel):
1631
  decoded_sample_rate: Optional[int] = None
1632
 
1633
  if resolved_mode == "voice_clone" and len(voice_clone_text_chunks) > 1:
1634
- voice_clone_chunk_batch_size = self._resolve_voice_clone_chunk_batch_size(
1635
  resolved_device=resolved_device,
1636
  chunk_count=len(voice_clone_text_chunks),
1637
  max_memory_per_sample_gb=float(voice_clone_max_memory_per_sample_gb),
 
 
 
1638
  )
1639
  else:
1640
  voice_clone_chunk_batch_size = 1
 
1641
 
1642
  for batch_start in range(0, len(voice_clone_text_chunks), voice_clone_chunk_batch_size):
1643
  batch_chunks = voice_clone_text_chunks[batch_start : batch_start + voice_clone_chunk_batch_size]
@@ -1677,17 +2357,23 @@ class NanoTTSGlobalLocalForCausalLM(NanoTTSPreTrainedModel):
1677
  resolved_device=resolved_device,
1678
  )
1679
 
 
1680
  for sample_index in range(len(batch_chunks)):
1681
  audio_token_ids = self._trim_generated_audio_token_ids(
1682
  batched_audio_token_ids[sample_index],
1683
  effective_nq=effective_nq,
1684
  )
1685
  generated_audio_token_chunks.append(audio_token_ids)
1686
-
1687
- if resolved_mode == "voice_clone" and len(voice_clone_text_chunks) > 1:
1688
- decoded_waveform, current_sample_rate = self._decode_audio_token_ids_to_waveform(
 
 
 
 
 
1689
  audio_tokenizer=audio_tokenizer,
1690
- audio_token_ids=audio_token_ids,
1691
  target_sample_rate=target_sample_rate,
1692
  effective_nq=effective_nq,
1693
  resolved_device=resolved_device,
@@ -1698,7 +2384,7 @@ class NanoTTSGlobalLocalForCausalLM(NanoTTSPreTrainedModel):
1698
  raise ValueError(
1699
  f"Decoded sample rates differ across voice_clone chunks: {decoded_sample_rate} vs {current_sample_rate}"
1700
  )
1701
- decoded_waveform_chunks.append(decoded_waveform)
1702
 
1703
  if generated_audio_token_chunks:
1704
  audio_token_ids = torch.cat(generated_audio_token_chunks, dim=0)
@@ -1754,4 +2440,7 @@ class NanoTTSGlobalLocalForCausalLM(NanoTTSPreTrainedModel):
1754
  "audio_token_ids": audio_token_ids.detach().cpu(),
1755
  "waveform": waveform,
1756
  "reference_audio_token_ids": None if prompt_audio_codes is None else prompt_audio_codes.detach().cpu(),
 
 
 
1757
  }
 
4
  import json
5
  import logging
6
  import os
7
+ import time
8
  from contextlib import nullcontext
9
  from dataclasses import dataclass
10
  from pathlib import Path
11
+ from typing import Any, Iterator, Optional, Sequence, Union
12
 
13
  import numpy as np
14
  import torch
 
667
  )
668
  return resolved_batch_size
669
 
670
+ @staticmethod
671
+ def _resolve_requested_batch_size_limit(requested_batch_size: Optional[int]) -> Optional[int]:
672
+ if requested_batch_size is None:
673
+ return None
674
+ resolved_batch_size = int(requested_batch_size)
675
+ if resolved_batch_size <= 0:
676
+ return None
677
+ return max(1, resolved_batch_size)
678
+
679
+ def _resolve_effective_voice_clone_batch_sizes(
680
+ self,
681
+ *,
682
+ resolved_device: torch.device,
683
+ chunk_count: int,
684
+ max_memory_per_sample_gb: float,
685
+ requested_tts_max_batch_size: Optional[int] = None,
686
+ requested_codec_max_batch_size: Optional[int] = None,
687
+ realtime_streaming: bool = False,
688
+ ) -> tuple[int, int]:
689
+ effective_tts_batch_size = self._resolve_voice_clone_chunk_batch_size(
690
+ resolved_device=resolved_device,
691
+ chunk_count=chunk_count,
692
+ max_memory_per_sample_gb=max_memory_per_sample_gb,
693
+ )
694
+ requested_tts_limit = self._resolve_requested_batch_size_limit(requested_tts_max_batch_size)
695
+ requested_codec_limit = self._resolve_requested_batch_size_limit(requested_codec_max_batch_size)
696
+
697
+ if requested_tts_limit is not None:
698
+ effective_tts_batch_size = min(effective_tts_batch_size, requested_tts_limit)
699
+ if realtime_streaming and requested_codec_limit is not None:
700
+ effective_tts_batch_size = min(effective_tts_batch_size, requested_codec_limit)
701
+
702
+ effective_tts_batch_size = max(1, min(int(chunk_count), int(effective_tts_batch_size)))
703
+
704
+ if realtime_streaming:
705
+ effective_codec_batch_size = effective_tts_batch_size
706
+ elif requested_codec_limit is None:
707
+ effective_codec_batch_size = 1
708
+ else:
709
+ effective_codec_batch_size = max(1, min(int(requested_codec_limit), int(effective_tts_batch_size)))
710
+
711
+ return int(effective_tts_batch_size), int(effective_codec_batch_size)
712
+
713
  def _generate_audio_token_ids_with_fallback(
714
  self,
715
  *,
 
784
  )
785
  return self._extract_waveform_and_sample_rate(decoded, fallback_sample_rate=target_sample_rate)
786
 
787
+ def _decode_audio_token_id_batch_to_waveforms(
788
+ self,
789
+ *,
790
+ audio_tokenizer,
791
+ audio_token_id_batches: Sequence[torch.LongTensor],
792
+ target_sample_rate: int,
793
+ effective_nq: int,
794
+ resolved_device: torch.device,
795
+ ) -> tuple[list[torch.FloatTensor], int]:
796
+ if not audio_token_id_batches:
797
+ return [], target_sample_rate
798
+
799
+ if len(audio_token_id_batches) == 1:
800
+ waveform, sample_rate = self._decode_audio_token_ids_to_waveform(
801
+ audio_tokenizer=audio_tokenizer,
802
+ audio_token_ids=audio_token_id_batches[0],
803
+ target_sample_rate=target_sample_rate,
804
+ effective_nq=effective_nq,
805
+ resolved_device=resolved_device,
806
+ )
807
+ return [waveform], sample_rate
808
+
809
+ decode_codes = [
810
+ self._prepare_audio_codes_for_decode(audio_token_ids.to(resolved_device), nq=effective_nq)
811
+ for audio_token_ids in audio_token_id_batches
812
+ ]
813
+ batch_decode_fn = getattr(audio_tokenizer, "batch_decode", None)
814
+ if batch_decode_fn is None:
815
+ raise AttributeError("audio_tokenizer must provide a batch_decode method.")
816
+
817
+ try:
818
+ with self._audio_tokenizer_inference_context(audio_tokenizer, resolved_device):
819
+ decode_output = batch_decode_fn(
820
+ decode_codes,
821
+ num_quantizers=effective_nq,
822
+ chunk_duration=None,
823
+ )
824
+ return self._extract_batch_waveforms_and_sample_rate(
825
+ decode_output,
826
+ fallback_sample_rate=target_sample_rate,
827
+ batch_size=len(audio_token_id_batches),
828
+ )
829
+ except Exception:
830
+ logging.warning(
831
+ "batched audio decode failed; falling back to per-chunk decode for batch_size=%d",
832
+ len(audio_token_id_batches),
833
+ exc_info=True,
834
+ )
835
+ waveform_rows: list[torch.FloatTensor] = []
836
+ sample_rate = target_sample_rate
837
+ for audio_token_ids in audio_token_id_batches:
838
+ waveform_row, sample_rate = self._decode_audio_token_ids_to_waveform(
839
+ audio_tokenizer=audio_tokenizer,
840
+ audio_token_ids=audio_token_ids,
841
+ target_sample_rate=target_sample_rate,
842
+ effective_nq=effective_nq,
843
+ resolved_device=resolved_device,
844
+ )
845
+ waveform_rows.append(waveform_row)
846
+ return waveform_rows, sample_rate
847
+
848
  def _build_generation_row(
849
  self,
850
  batch_size: int,
 
861
  row[:, :, 1:] = audio_token_ids.unsqueeze(1)
862
  return row
863
 
864
+ @staticmethod
865
+ def _compute_stream_lead_seconds(
866
+ emitted_samples_total: int,
867
+ sample_rate: int,
868
+ first_audio_emitted_at: Optional[float],
869
+ ) -> float:
870
+ if first_audio_emitted_at is None or sample_rate <= 0:
871
+ return 0.0
872
+ elapsed_seconds = max(0.0, time.monotonic() - first_audio_emitted_at)
873
+ emitted_seconds = float(emitted_samples_total) / float(sample_rate)
874
+ return emitted_seconds - elapsed_seconds
875
+
876
+ @staticmethod
877
+ def _resolve_stream_decode_frame_budget(
878
+ *,
879
+ emitted_samples_total: int,
880
+ sample_rate: int,
881
+ first_audio_emitted_at: Optional[float],
882
+ ) -> int:
883
+ lead_seconds = NanoTTSGlobalLocalForCausalLM._compute_stream_lead_seconds(
884
+ emitted_samples_total=emitted_samples_total,
885
+ sample_rate=sample_rate,
886
+ first_audio_emitted_at=first_audio_emitted_at,
887
+ )
888
+ if first_audio_emitted_at is None or lead_seconds < 0.20:
889
+ return 1
890
+ if lead_seconds < 0.55:
891
+ return 2
892
+ if lead_seconds < 1.10:
893
+ return 4
894
+ return 8
895
+
896
  def _sample_next_token(
897
  self,
898
  logits: torch.FloatTensor,
 
1437
  with self._audio_tokenizer_inference_context(audio_tokenizer, decode_codes.device):
1438
  return batch_decode_fn([decode_codes], num_quantizers=effective_nq, chunk_duration=None)
1439
 
1440
+ def _extract_batch_waveforms_and_sample_rate(
1441
+ self,
1442
+ decode_output: Any,
1443
+ fallback_sample_rate: int,
1444
+ batch_size: int,
1445
+ ) -> tuple[list[torch.FloatTensor], int]:
1446
+ sample_rate = fallback_sample_rate
1447
+ audio = decode_output
1448
+ audio_lengths = None
1449
+
1450
+ for key in ("sample_rate", "sampling_rate"):
1451
+ value = getattr(decode_output, key, None)
1452
+ if value is not None:
1453
+ sample_rate = int(value)
1454
+ break
1455
+ for key in ("waveform", "audio", "wav", "samples"):
1456
+ value = getattr(decode_output, key, None)
1457
+ if value is not None:
1458
+ audio = value
1459
+ break
1460
+ for key in ("audio_lengths", "waveform_lengths", "lengths"):
1461
+ value = getattr(decode_output, key, None)
1462
+ if value is not None:
1463
+ audio_lengths = value
1464
+ break
1465
+
1466
+ if isinstance(decode_output, dict):
1467
+ for key in ("sample_rate", "sampling_rate"):
1468
+ if key in decode_output:
1469
+ sample_rate = int(decode_output[key])
1470
+ break
1471
+ for key in ("waveform", "audio", "wav", "samples"):
1472
+ if key in decode_output:
1473
+ audio = decode_output[key]
1474
+ break
1475
+ for key in ("audio_lengths", "waveform_lengths", "lengths"):
1476
+ if key in decode_output:
1477
+ audio_lengths = decode_output[key]
1478
+ break
1479
+
1480
+ audio_tensor = torch.as_tensor(audio, dtype=torch.float32)
1481
+ if audio_tensor.ndim == 2:
1482
+ audio_tensor = audio_tensor.unsqueeze(0)
1483
+ if audio_tensor.ndim != 3:
1484
+ raise ValueError(f"Expected batched decoded audio with 3 dims, got {tuple(audio_tensor.shape)}")
1485
+ if audio_tensor.shape[0] != int(batch_size):
1486
+ raise ValueError(
1487
+ f"Expected decoded batch size {batch_size}, got audio tensor shape {tuple(audio_tensor.shape)}"
1488
+ )
1489
+
1490
+ if audio_lengths is None:
1491
+ lengths_tensor = torch.full(
1492
+ (batch_size,),
1493
+ int(audio_tensor.shape[-1]),
1494
+ device=audio_tensor.device,
1495
+ dtype=torch.long,
1496
+ )
1497
+ else:
1498
+ lengths_tensor = torch.as_tensor(audio_lengths, dtype=torch.long, device=audio_tensor.device).reshape(-1)
1499
+ if lengths_tensor.numel() != int(batch_size):
1500
+ raise ValueError(f"Expected {batch_size} decoded audio lengths, got {int(lengths_tensor.numel())}")
1501
+
1502
+ waveform_rows: list[torch.FloatTensor] = []
1503
+ for row_index in range(batch_size):
1504
+ row_length = max(0, int(lengths_tensor[row_index].item()))
1505
+ waveform_rows.append(audio_tensor[row_index, :, :row_length].detach().cpu())
1506
+ return waveform_rows, sample_rate
1507
+
1508
  @staticmethod
1509
  def _resolve_audio_tokenizer_downsample_rate(audio_tokenizer) -> int:
1510
  for holder in (audio_tokenizer, getattr(audio_tokenizer, "config", None)):
 
1610
  )
1611
  return local_outputs.last_hidden_state[:, -1, :]
1612
 
1613
+ def _iter_generation_events(
 
1614
  self,
1615
  input_ids: torch.LongTensor,
1616
  attention_mask: Optional[torch.Tensor] = None,
 
1626
  audio_repetition_penalty: float = 1.0,
1627
  use_kv_cache: bool = True,
1628
  return_dict_in_generate: bool = True,
1629
+ ) -> Iterator[dict[str, Any]]:
1630
  if input_ids.ndim == 2:
1631
  input_ids = input_ids.unsqueeze(0)
1632
  if input_ids.ndim != 3:
 
1646
  past_key_values = None
1647
  local_dtype = self.local_transformer.ln_f.weight.dtype
1648
 
1649
+ for step_index in range(max_new_frames):
1650
  generated_audio_history = torch.stack(generated_frames, dim=1) if generated_frames else None
1651
  global_inputs_embeds = self._build_inputs_embeds(current_model_input_ids)
1652
  global_outputs = self.transformer(
 
1732
  else:
1733
  current_model_input_ids = current_input_ids
1734
 
1735
+ yield {
1736
+ "type": "frame",
1737
+ "step_index": int(step_index),
1738
+ "audio_token_ids": padded_next_frame.detach().clone(),
1739
+ "active_mask": should_continue.detach().clone(),
1740
+ "finished_mask": finished.detach().clone(),
1741
+ }
1742
+
1743
  if generated_frames:
1744
  audio_token_ids = torch.stack(generated_frames, dim=1)
1745
  else:
1746
  audio_token_ids = torch.empty((batch_size, 0, self.config.n_vq), dtype=torch.long, device=input_ids.device)
1747
 
1748
  if not return_dict_in_generate:
1749
+ yield {"type": "final", "audio_token_ids": audio_token_ids}
1750
+ return
1751
+ yield {
1752
+ "type": "final",
1753
+ "generation": NanoTTSGenerationOutput(audio_token_ids=audio_token_ids, prompt_input_ids=input_ids),
1754
+ }
1755
+
1756
+ @torch.no_grad()
1757
+ def generate(
1758
+ self,
1759
+ input_ids: torch.LongTensor,
1760
+ attention_mask: Optional[torch.Tensor] = None,
1761
+ nq: Optional[int] = None,
1762
+ max_new_frames: int = 300,
1763
+ do_sample: bool = False,
1764
+ text_temperature: float = 1.5,
1765
+ text_top_p: float = 1.0,
1766
+ text_top_k: int = 50,
1767
+ audio_temperature: float = 1.7,
1768
+ audio_top_p: float = 0.8,
1769
+ audio_top_k: int = 25,
1770
+ audio_repetition_penalty: float = 1.0,
1771
+ use_kv_cache: bool = True,
1772
+ return_dict_in_generate: bool = True,
1773
+ ):
1774
+ final_output: Any = None
1775
+ for event in self._iter_generation_events(
1776
+ input_ids=input_ids,
1777
+ attention_mask=attention_mask,
1778
+ nq=nq,
1779
+ max_new_frames=max_new_frames,
1780
+ do_sample=do_sample,
1781
+ text_temperature=text_temperature,
1782
+ text_top_p=text_top_p,
1783
+ text_top_k=text_top_k,
1784
+ audio_temperature=audio_temperature,
1785
+ audio_top_p=audio_top_p,
1786
+ audio_top_k=audio_top_k,
1787
+ audio_repetition_penalty=audio_repetition_penalty,
1788
+ use_kv_cache=use_kv_cache,
1789
+ return_dict_in_generate=return_dict_in_generate,
1790
+ ):
1791
+ if event["type"] != "final":
1792
+ continue
1793
+ final_output = event.get("generation", event.get("audio_token_ids"))
1794
+ if final_output is None:
1795
+ raise RuntimeError("Generation finished without producing a final output.")
1796
+ return final_output
1797
+
1798
+ @torch.no_grad()
1799
+ def generate_stream(
1800
+ self,
1801
+ input_ids: torch.LongTensor,
1802
+ attention_mask: Optional[torch.Tensor] = None,
1803
+ nq: Optional[int] = None,
1804
+ max_new_frames: int = 300,
1805
+ do_sample: bool = False,
1806
+ text_temperature: float = 1.5,
1807
+ text_top_p: float = 1.0,
1808
+ text_top_k: int = 50,
1809
+ audio_temperature: float = 1.7,
1810
+ audio_top_p: float = 0.8,
1811
+ audio_top_k: int = 25,
1812
+ audio_repetition_penalty: float = 1.0,
1813
+ use_kv_cache: bool = True,
1814
+ return_dict_in_generate: bool = True,
1815
+ ) -> Iterator[dict[str, Any]]:
1816
+ yield from self._iter_generation_events(
1817
+ input_ids=input_ids,
1818
+ attention_mask=attention_mask,
1819
+ nq=nq,
1820
+ max_new_frames=max_new_frames,
1821
+ do_sample=do_sample,
1822
+ text_temperature=text_temperature,
1823
+ text_top_p=text_top_p,
1824
+ text_top_k=text_top_k,
1825
+ audio_temperature=audio_temperature,
1826
+ audio_top_p=audio_top_p,
1827
+ audio_top_k=audio_top_k,
1828
+ audio_repetition_penalty=audio_repetition_penalty,
1829
+ use_kv_cache=use_kv_cache,
1830
+ return_dict_in_generate=return_dict_in_generate,
1831
+ )
1832
+
1833
+ @torch.no_grad()
1834
+ def inference_stream(
1835
+ self,
1836
+ text: str,
1837
+ output_audio_path: Union[str, Path],
1838
+ mode: str = "continuation",
1839
+ prompt_text: Optional[str] = None,
1840
+ prompt_audio_path: Optional[Union[str, Path]] = None,
1841
+ reference_audio_path: Optional[Union[str, Path]] = None,
1842
+ text_tokenizer=None,
1843
+ text_tokenizer_path: Optional[str] = None,
1844
+ audio_tokenizer=None,
1845
+ audio_tokenizer_type: Optional[str] = None,
1846
+ audio_tokenizer_pretrained_name_or_path: Optional[str] = None,
1847
+ device: Optional[Union[str, torch.device]] = None,
1848
+ nq: Optional[int] = None,
1849
+ max_new_frames: int = 300,
1850
+ do_sample: bool = False,
1851
+ text_temperature: float = 1.5,
1852
+ text_top_p: float = 1.0,
1853
+ text_top_k: int = 50,
1854
+ audio_temperature: float = 1.7,
1855
+ audio_top_p: float = 0.8,
1856
+ audio_top_k: int = 25,
1857
+ audio_repetition_penalty: float = 1.0,
1858
+ use_kv_cache: bool = True,
1859
+ voice_clone_max_text_tokens: int = DEFAULT_VOICE_CLONE_MAX_TEXT_TOKENS,
1860
+ voice_clone_max_memory_per_sample_gb: float = DEFAULT_VOICE_CLONE_MAX_MEMORY_PER_SAMPLE_GB,
1861
+ tts_max_batch_size: int = 0,
1862
+ codec_max_batch_size: int = 0,
1863
+ ) -> Iterator[dict[str, Any]]:
1864
+ resolved_device = self._resolve_device(device)
1865
+ effective_nq = self._resolve_inference_nq(nq)
1866
+ if next(self.parameters()).device != resolved_device:
1867
+ self.to(resolved_device)
1868
+
1869
+ was_training = self.training
1870
+ self.eval()
1871
+
1872
+ text_tokenizer = self._load_text_tokenizer(
1873
+ text_tokenizer=text_tokenizer,
1874
+ text_tokenizer_path=text_tokenizer_path,
1875
+ )
1876
+ audio_tokenizer = self._load_audio_tokenizer(
1877
+ audio_tokenizer=audio_tokenizer,
1878
+ audio_tokenizer_type=audio_tokenizer_type,
1879
+ audio_tokenizer_pretrained_name_or_path=audio_tokenizer_pretrained_name_or_path,
1880
+ device=resolved_device,
1881
+ )
1882
+
1883
+ target_sample_rate = self._resolve_audio_tokenizer_sample_rate(audio_tokenizer)
1884
+ target_channels = self._resolve_audio_tokenizer_channels(audio_tokenizer)
1885
+ effective_prompt_audio_path = prompt_audio_path or reference_audio_path
1886
+ resolved_mode = self._resolve_inference_mode(
1887
+ mode=mode,
1888
+ has_prompt_text=prompt_text is not None,
1889
+ has_prompt_audio=effective_prompt_audio_path is not None,
1890
+ )
1891
+ if reference_audio_path is not None and prompt_audio_path is None:
1892
+ logging.warning(
1893
+ "reference_audio_path=%s is treated as prompt_audio_path for backward compatibility.",
1894
+ reference_audio_path,
1895
+ )
1896
+
1897
+ prompt_audio_codes = None
1898
+ if effective_prompt_audio_path is not None:
1899
+ waveform, sample_rate = self._load_reference_audio(
1900
+ effective_prompt_audio_path,
1901
+ target_sample_rate,
1902
+ target_channels,
1903
+ )
1904
+ encoded = self._call_audio_encode(
1905
+ audio_tokenizer=audio_tokenizer,
1906
+ waveform=waveform.to(resolved_device),
1907
+ sample_rate=sample_rate,
1908
+ )
1909
+ prompt_audio_codes = self._mask_unused_audio_channels(
1910
+ self._normalize_audio_codes(encoded),
1911
+ nq=effective_nq,
1912
+ ).to(resolved_device)
1913
+
1914
+ if resolved_mode == "voice_clone":
1915
+ split_voice_clone_text_chunks = self._split_text_into_best_sentences(
1916
+ text_tokenizer=text_tokenizer,
1917
+ text=text,
1918
+ max_tokens=voice_clone_max_text_tokens,
1919
+ )
1920
+ voice_clone_text_chunks = split_voice_clone_text_chunks if len(split_voice_clone_text_chunks) > 1 else [text]
1921
+ else:
1922
+ voice_clone_text_chunks = [text]
1923
+
1924
+ if resolved_mode == "voice_clone" and len(voice_clone_text_chunks) > 1:
1925
+ voice_clone_chunk_batch_size, voice_clone_codec_batch_size = self._resolve_effective_voice_clone_batch_sizes(
1926
+ resolved_device=resolved_device,
1927
+ chunk_count=len(voice_clone_text_chunks),
1928
+ max_memory_per_sample_gb=float(voice_clone_max_memory_per_sample_gb),
1929
+ requested_tts_max_batch_size=tts_max_batch_size,
1930
+ requested_codec_max_batch_size=codec_max_batch_size,
1931
+ realtime_streaming=True,
1932
+ )
1933
+ else:
1934
+ voice_clone_chunk_batch_size = 1
1935
+ voice_clone_codec_batch_size = 1
1936
+
1937
+ generated_audio_token_chunks: list[torch.LongTensor] = []
1938
+ emitted_waveform_segments: list[torch.FloatTensor] = []
1939
+ decoded_sample_rate: Optional[int] = None
1940
+ emitted_samples_total = 0
1941
+ first_audio_emitted_at: Optional[float] = None
1942
+ streaming_reset_fn = getattr(audio_tokenizer, "_reset_batch_decode_streaming_state", None)
1943
+
1944
+ try:
1945
+ for batch_start in range(0, len(voice_clone_text_chunks), voice_clone_chunk_batch_size):
1946
+ batch_chunks = voice_clone_text_chunks[batch_start : batch_start + voice_clone_chunk_batch_size]
1947
+ batch_prompt_input_ids: list[torch.LongTensor] = []
1948
+ batch_attention_masks: list[torch.BoolTensor] = []
1949
+ for text_chunk in batch_chunks:
1950
+ prompt_input_ids, attention_mask = self.build_inference_input_ids(
1951
+ text=text_chunk,
1952
+ text_tokenizer=text_tokenizer,
1953
+ mode=resolved_mode,
1954
+ prompt_text=prompt_text,
1955
+ prompt_audio_codes=prompt_audio_codes,
1956
+ device=resolved_device,
1957
+ )
1958
+ batch_prompt_input_ids.append(prompt_input_ids)
1959
+ batch_attention_masks.append(attention_mask)
1960
+
1961
+ batched_prompt_input_ids, batched_attention_mask = self._left_pad_inference_batch(
1962
+ input_id_batches=batch_prompt_input_ids,
1963
+ attention_mask_batches=batch_attention_masks,
1964
+ device=resolved_device,
1965
+ )
1966
+
1967
+ row_states = [
1968
+ {
1969
+ "pending_decode_frames": [],
1970
+ "decoded_audio_segments": [],
1971
+ "generation_complete": False,
1972
+ "pause_emitted": False,
1973
+ }
1974
+ for _ in batch_chunks
1975
+ ]
1976
+ batch_emit_index = 0
1977
+ codec_stream_started = False
1978
+
1979
+ if resolved_mode == "continuation" and prompt_audio_codes is not None:
1980
+ prompt_decode_codes = self._prepare_audio_codes_for_decode(prompt_audio_codes, nq=effective_nq)
1981
+ _ = audio_tokenizer.batch_decode(
1982
+ [prompt_decode_codes],
1983
+ num_quantizers=effective_nq,
1984
+ streaming=True,
1985
+ max_batch_size=1,
1986
+ reset_stream=True,
1987
+ )
1988
+ codec_stream_started = True
1989
+
1990
+ def _emit_ready_segments() -> Iterator[dict[str, Any]]:
1991
+ nonlocal batch_emit_index, emitted_samples_total, first_audio_emitted_at
1992
+ active_sample_rate = decoded_sample_rate or target_sample_rate
1993
+ while batch_emit_index < len(batch_chunks):
1994
+ state = row_states[batch_emit_index]
1995
+ decoded_segments = state["decoded_audio_segments"]
1996
+ if decoded_segments:
1997
+ next_segment = decoded_segments.pop(0)
1998
+ if next_segment.numel() == 0 or int(next_segment.shape[-1]) <= 0:
1999
+ continue
2000
+ emitted_waveform_segments.append(next_segment)
2001
+ if first_audio_emitted_at is None:
2002
+ first_audio_emitted_at = time.monotonic()
2003
+ emitted_samples_total += int(next_segment.shape[-1])
2004
+ yield {
2005
+ "type": "audio",
2006
+ "waveform": next_segment,
2007
+ "sample_rate": active_sample_rate,
2008
+ "chunk_index": batch_start + batch_emit_index,
2009
+ "is_pause": False,
2010
+ "emitted_audio_seconds": float(emitted_samples_total) / float(active_sample_rate),
2011
+ "lead_seconds": self._compute_stream_lead_seconds(
2012
+ emitted_samples_total=emitted_samples_total,
2013
+ sample_rate=active_sample_rate,
2014
+ first_audio_emitted_at=first_audio_emitted_at,
2015
+ ),
2016
+ }
2017
+ continue
2018
+
2019
+ if not state["generation_complete"]:
2020
+ break
2021
+
2022
+ if (
2023
+ resolved_mode == "voice_clone"
2024
+ and len(voice_clone_text_chunks) > 1
2025
+ and not state["pause_emitted"]
2026
+ and (batch_start + batch_emit_index) < len(voice_clone_text_chunks) - 1
2027
+ ):
2028
+ state["pause_emitted"] = True
2029
+ pause_seconds = self._estimate_voice_clone_inter_chunk_pause_seconds(
2030
+ voice_clone_text_chunks[batch_start + batch_emit_index]
2031
+ )
2032
+ pause_samples = max(0, int(round(float(active_sample_rate) * pause_seconds)))
2033
+ if pause_samples > 0:
2034
+ silence = torch.zeros((target_channels, pause_samples), dtype=torch.float32)
2035
+ emitted_waveform_segments.append(silence)
2036
+ if first_audio_emitted_at is None:
2037
+ first_audio_emitted_at = time.monotonic()
2038
+ emitted_samples_total += int(silence.shape[-1])
2039
+ yield {
2040
+ "type": "audio",
2041
+ "waveform": silence,
2042
+ "sample_rate": active_sample_rate,
2043
+ "chunk_index": batch_start + batch_emit_index,
2044
+ "is_pause": True,
2045
+ "emitted_audio_seconds": float(emitted_samples_total) / float(active_sample_rate),
2046
+ "lead_seconds": self._compute_stream_lead_seconds(
2047
+ emitted_samples_total=emitted_samples_total,
2048
+ sample_rate=active_sample_rate,
2049
+ first_audio_emitted_at=first_audio_emitted_at,
2050
+ ),
2051
+ }
2052
+ batch_emit_index += 1
2053
+ continue
2054
+
2055
+ batch_emit_index += 1
2056
+
2057
+ def _maybe_decode_pending(force: bool) -> Iterator[dict[str, Any]]:
2058
+ nonlocal codec_stream_started, decoded_sample_rate
2059
+ pending_counts = [len(state["pending_decode_frames"]) for state in row_states]
2060
+ total_pending = sum(pending_counts)
2061
+ if total_pending <= 0:
2062
+ return
2063
+
2064
+ active_sample_rate = decoded_sample_rate or target_sample_rate
2065
+ head_pending = pending_counts[batch_emit_index] if batch_emit_index < len(batch_chunks) else 0
2066
+ lead_seconds = self._compute_stream_lead_seconds(
2067
+ emitted_samples_total=emitted_samples_total,
2068
+ sample_rate=active_sample_rate,
2069
+ first_audio_emitted_at=first_audio_emitted_at,
2070
+ )
2071
+ decode_budget = self._resolve_stream_decode_frame_budget(
2072
+ emitted_samples_total=emitted_samples_total,
2073
+ sample_rate=active_sample_rate,
2074
+ first_audio_emitted_at=first_audio_emitted_at,
2075
+ )
2076
+
2077
+ if not force:
2078
+ should_decode = False
2079
+ if first_audio_emitted_at is None and head_pending > 0:
2080
+ should_decode = True
2081
+ elif head_pending > 0 and lead_seconds < 0.45:
2082
+ should_decode = True
2083
+ elif max(pending_counts) >= decode_budget:
2084
+ should_decode = True
2085
+ elif lead_seconds < 0.0 and total_pending > 0:
2086
+ should_decode = True
2087
+ if not should_decode:
2088
+ return
2089
+
2090
+ decode_window = max(pending_counts) if force else max(1, decode_budget)
2091
+ empty_codes = torch.empty((effective_nq, 0), dtype=torch.long, device=resolved_device)
2092
+ codes_list: list[torch.Tensor] = []
2093
+ for state in row_states:
2094
+ take_count = min(len(state["pending_decode_frames"]), decode_window)
2095
+ if take_count <= 0:
2096
+ codes_list.append(empty_codes)
2097
+ continue
2098
+ frame_rows = state["pending_decode_frames"][:take_count]
2099
+ del state["pending_decode_frames"][:take_count]
2100
+ frame_tensor = torch.cat(frame_rows, dim=0).to(device=resolved_device, dtype=torch.long)
2101
+ codes_list.append(frame_tensor[:, :effective_nq].transpose(0, 1).contiguous())
2102
+
2103
+ decode_output = audio_tokenizer.batch_decode(
2104
+ codes_list,
2105
+ num_quantizers=effective_nq,
2106
+ streaming=True,
2107
+ max_batch_size=(voice_clone_codec_batch_size if not codec_stream_started else None),
2108
+ reset_stream=not codec_stream_started,
2109
+ )
2110
+ codec_stream_started = True
2111
+ waveform_rows, current_sample_rate = self._extract_batch_waveforms_and_sample_rate(
2112
+ decode_output,
2113
+ fallback_sample_rate=target_sample_rate,
2114
+ batch_size=len(batch_chunks),
2115
+ )
2116
+ if decoded_sample_rate is None:
2117
+ decoded_sample_rate = current_sample_rate
2118
+ elif decoded_sample_rate != current_sample_rate:
2119
+ raise ValueError(
2120
+ f"Decoded sample rates differ across streaming decode calls: {decoded_sample_rate} vs {current_sample_rate}"
2121
+ )
2122
+
2123
+ for row_index, waveform_row in enumerate(waveform_rows):
2124
+ if waveform_row.numel() == 0 or int(waveform_row.shape[-1]) <= 0:
2125
+ continue
2126
+ row_states[row_index]["decoded_audio_segments"].append(waveform_row)
2127
+
2128
+ yield from _emit_ready_segments()
2129
+
2130
+ try:
2131
+ final_generation = None
2132
+ for event in self.generate_stream(
2133
+ input_ids=batched_prompt_input_ids,
2134
+ attention_mask=batched_attention_mask,
2135
+ nq=effective_nq,
2136
+ max_new_frames=max_new_frames,
2137
+ do_sample=do_sample,
2138
+ text_temperature=text_temperature,
2139
+ text_top_p=text_top_p,
2140
+ text_top_k=text_top_k,
2141
+ audio_temperature=audio_temperature,
2142
+ audio_top_p=audio_top_p,
2143
+ audio_top_k=audio_top_k,
2144
+ audio_repetition_penalty=audio_repetition_penalty,
2145
+ use_kv_cache=use_kv_cache,
2146
+ return_dict_in_generate=True,
2147
+ ):
2148
+ if event["type"] == "frame":
2149
+ frame_audio_token_ids = event["audio_token_ids"]
2150
+ active_mask = event["active_mask"]
2151
+ finished_mask = event["finished_mask"]
2152
+ for row_index in range(len(batch_chunks)):
2153
+ if bool(active_mask[row_index].item()):
2154
+ row_states[row_index]["pending_decode_frames"].append(
2155
+ frame_audio_token_ids[row_index : row_index + 1].detach().clone()
2156
+ )
2157
+ if bool(finished_mask[row_index].item()):
2158
+ row_states[row_index]["generation_complete"] = True
2159
+ yield from _maybe_decode_pending(force=False)
2160
+ continue
2161
+ final_generation = event.get("generation")
2162
+
2163
+ if final_generation is None:
2164
+ raise RuntimeError("Streaming generation finished without a final output.")
2165
+
2166
+ for state in row_states:
2167
+ state["generation_complete"] = True
2168
+
2169
+ yield from _maybe_decode_pending(force=True)
2170
+ yield from _emit_ready_segments()
2171
+
2172
+ batched_audio_token_ids = self._mask_unused_audio_channels(final_generation.audio_token_ids, nq=effective_nq)
2173
+ for sample_index in range(len(batch_chunks)):
2174
+ generated_audio_token_chunks.append(
2175
+ self._trim_generated_audio_token_ids(
2176
+ batched_audio_token_ids[sample_index],
2177
+ effective_nq=effective_nq,
2178
+ )
2179
+ )
2180
+ finally:
2181
+ if codec_stream_started and callable(streaming_reset_fn):
2182
+ streaming_reset_fn()
2183
+
2184
+ if generated_audio_token_chunks:
2185
+ audio_token_ids = torch.cat(generated_audio_token_chunks, dim=0)
2186
+ else:
2187
+ audio_token_ids = torch.empty((0, self.config.n_vq), dtype=torch.long, device=resolved_device)
2188
+
2189
+ if emitted_waveform_segments:
2190
+ waveform = torch.cat(emitted_waveform_segments, dim=-1)
2191
+ else:
2192
+ waveform = torch.zeros((target_channels, 0), dtype=torch.float32)
2193
+
2194
+ decoded_sample_rate = decoded_sample_rate or target_sample_rate
2195
+ output_path = Path(output_audio_path)
2196
+ output_path.parent.mkdir(parents=True, exist_ok=True)
2197
+ torchaudio.save(str(output_path), waveform, decoded_sample_rate)
2198
+
2199
+ yield {
2200
+ "type": "result",
2201
+ "audio_path": str(output_path),
2202
+ "sample_rate": decoded_sample_rate,
2203
+ "audio_token_ids": audio_token_ids.detach().cpu(),
2204
+ "waveform": waveform,
2205
+ "reference_audio_token_ids": None if prompt_audio_codes is None else prompt_audio_codes.detach().cpu(),
2206
+ "voice_clone_text_chunks": voice_clone_text_chunks,
2207
+ "voice_clone_chunk_batch_size": int(voice_clone_chunk_batch_size),
2208
+ "voice_clone_codec_batch_size": int(voice_clone_codec_batch_size),
2209
+ }
2210
+ finally:
2211
+ if was_training:
2212
+ self.train()
2213
 
2214
  @torch.no_grad()
2215
  def inference(
 
2239
  use_kv_cache: bool = True,
2240
  voice_clone_max_text_tokens: int = DEFAULT_VOICE_CLONE_MAX_TEXT_TOKENS,
2241
  voice_clone_max_memory_per_sample_gb: float = DEFAULT_VOICE_CLONE_MAX_MEMORY_PER_SAMPLE_GB,
2242
+ tts_max_batch_size: int = 0,
2243
+ codec_max_batch_size: int = 0,
2244
  ) -> dict[str, Any]:
2245
  resolved_device = self._resolve_device(device)
2246
  effective_nq = self._resolve_inference_nq(nq)
 
2307
  decoded_sample_rate: Optional[int] = None
2308
 
2309
  if resolved_mode == "voice_clone" and len(voice_clone_text_chunks) > 1:
2310
+ voice_clone_chunk_batch_size, voice_clone_codec_batch_size = self._resolve_effective_voice_clone_batch_sizes(
2311
  resolved_device=resolved_device,
2312
  chunk_count=len(voice_clone_text_chunks),
2313
  max_memory_per_sample_gb=float(voice_clone_max_memory_per_sample_gb),
2314
+ requested_tts_max_batch_size=tts_max_batch_size,
2315
+ requested_codec_max_batch_size=codec_max_batch_size,
2316
+ realtime_streaming=False,
2317
  )
2318
  else:
2319
  voice_clone_chunk_batch_size = 1
2320
+ voice_clone_codec_batch_size = 1
2321
 
2322
  for batch_start in range(0, len(voice_clone_text_chunks), voice_clone_chunk_batch_size):
2323
  batch_chunks = voice_clone_text_chunks[batch_start : batch_start + voice_clone_chunk_batch_size]
 
2357
  resolved_device=resolved_device,
2358
  )
2359
 
2360
+ batch_audio_token_chunks: list[torch.LongTensor] = []
2361
  for sample_index in range(len(batch_chunks)):
2362
  audio_token_ids = self._trim_generated_audio_token_ids(
2363
  batched_audio_token_ids[sample_index],
2364
  effective_nq=effective_nq,
2365
  )
2366
  generated_audio_token_chunks.append(audio_token_ids)
2367
+ batch_audio_token_chunks.append(audio_token_ids)
2368
+
2369
+ if resolved_mode == "voice_clone" and len(voice_clone_text_chunks) > 1:
2370
+ for codec_batch_start in range(0, len(batch_audio_token_chunks), voice_clone_codec_batch_size):
2371
+ codec_audio_token_batches = batch_audio_token_chunks[
2372
+ codec_batch_start : codec_batch_start + voice_clone_codec_batch_size
2373
+ ]
2374
+ decoded_waveforms, current_sample_rate = self._decode_audio_token_id_batch_to_waveforms(
2375
  audio_tokenizer=audio_tokenizer,
2376
+ audio_token_id_batches=codec_audio_token_batches,
2377
  target_sample_rate=target_sample_rate,
2378
  effective_nq=effective_nq,
2379
  resolved_device=resolved_device,
 
2384
  raise ValueError(
2385
  f"Decoded sample rates differ across voice_clone chunks: {decoded_sample_rate} vs {current_sample_rate}"
2386
  )
2387
+ decoded_waveform_chunks.extend(decoded_waveforms)
2388
 
2389
  if generated_audio_token_chunks:
2390
  audio_token_ids = torch.cat(generated_audio_token_chunks, dim=0)
 
2440
  "audio_token_ids": audio_token_ids.detach().cpu(),
2441
  "waveform": waveform,
2442
  "reference_audio_token_ids": None if prompt_audio_codes is None else prompt_audio_codes.detach().cpu(),
2443
+ "voice_clone_text_chunks": voice_clone_text_chunks,
2444
+ "voice_clone_chunk_batch_size": int(voice_clone_chunk_batch_size),
2445
+ "voice_clone_codec_batch_size": int(voice_clone_codec_batch_size),
2446
  }
pytorch_model.bin CHANGED
@@ -1,3 +1,3 @@
1
  version https://git-lfs.github.com/spec/v1
2
- oid sha256:d19e63fdc6a35f61a7d1c27e06bfacaba7c1ed40ea3c619c86efc64bcd50a496
3
  size 234693095
 
1
  version https://git-lfs.github.com/spec/v1
2
+ oid sha256:24003f2f11ac8a2cbf70514db2d8f1c02fb451aa6b3c0bffc9da09f31cd7caa5
3
  size 234693095