Kuangwei Chen commited on
Commit ·
6e12827
1
Parent(s): ad3fd89
Update nano-tts
Browse files- gpt2_decoder.py +15 -2
- modeling_nanotts_global_local.py +702 -13
- pytorch_model.bin +1 -1
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=
|
| 201 |
-
key_length=
|
| 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 |
-
|
| 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
|
| 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 |
-
|
| 1538 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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.
|
| 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 |
-
|
| 1688 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1689 |
audio_tokenizer=audio_tokenizer,
|
| 1690 |
-
|
| 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.
|
| 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:
|
| 3 |
size 234693095
|
|
|
|
| 1 |
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:24003f2f11ac8a2cbf70514db2d8f1c02fb451aa6b3c0bffc9da09f31cd7caa5
|
| 3 |
size 234693095
|