fix: codec decoder input structure and bugs
Browse files- Codec decoder now receives pre-computed
attention_mask, cos/sin RoPE, similar as talker
and local talker.
- The computation of attention_mask previously
also bugged because new KV is assigned from the
right, and the attention_mask has to ignore
left pads that have not yet reached sliding_window
length.
- The above point caused initial sound bugs in the
previous commits, which has now been fixed.
- All input axes for codec decoder are also now made
static in the export to make the graph capture better.
It still can be made dynamic in the export with consistent
length at runtime, but graph capture will perform better
if the exported model already contains pure static axes.
- This includes batch and chunk_frames, batch=1,
chunk_frames=4 (320 ms).
- Next will be to make all exported axes static
for talker, local_talker, and lm_head,
as these are also captured by CUDA graphs.
|
@@ -1,3 +1,3 @@
|
|
| 1 |
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:
|
| 3 |
-
size
|
|
|
|
| 1 |
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:5db3b88ca51c04b39ad2603ab781887fef300a65b5614f7cbeff1be86d0da51c
|
| 3 |
+
size 456699494
|
|
@@ -372,7 +372,6 @@ class Qwen3TTSInferencerONNX:
|
|
| 372 |
cuda_device_id : int
|
| 373 |
enable_cuda_graph : bool
|
| 374 |
num_threads : int
|
| 375 |
-
chunk_frames : int
|
| 376 |
temperature : float
|
| 377 |
top_p : float
|
| 378 |
top_k : int
|
|
@@ -401,8 +400,8 @@ class Qwen3TTSInferencerONNX:
|
|
| 401 |
use_cuda: bool = True,
|
| 402 |
cuda_device_id: int = 0,
|
| 403 |
enable_cuda_graph: bool = True,
|
| 404 |
-
num_threads: int = 4,
|
| 405 |
chunk_frames: int = 4,
|
|
|
|
| 406 |
temperature: float = 0.85,
|
| 407 |
top_p: float = 0.8,
|
| 408 |
top_k: int = 50,
|
|
@@ -472,13 +471,14 @@ class Qwen3TTSInferencerONNX:
|
|
| 472 |
self._speech_tokenizer_latent_dim = dec_cfg.latent_dim
|
| 473 |
self._speech_tokenizer_codebook_dim = dec_cfg.codebook_dim
|
| 474 |
self._speech_tokenizer_head_dim = dec_cfg.head_dim
|
|
|
|
| 475 |
self._speech_tokenizer_num_attention_heads = dec_cfg.num_attention_heads
|
| 476 |
self._speech_tokenizer_num_hidden_layers = dec_cfg.num_hidden_layers
|
| 477 |
self._speech_tokenizer_num_key_value_heads = dec_cfg.num_key_value_heads
|
| 478 |
self._speech_tokenizer_sliding_window = dec_cfg.sliding_window
|
| 479 |
self._speech_tokenizer_decoder_left_context_size = 25
|
| 480 |
self._speech_tokenizer_decoder_total_upsample = 1920
|
| 481 |
-
self._codec_chunk_frames =
|
| 482 |
self.output_sample_rate = self._speech_tokenizer_config.output_sample_rate
|
| 483 |
|
| 484 |
with open(model_config_path, "r") as f:
|
|
@@ -546,7 +546,7 @@ class Qwen3TTSInferencerONNX:
|
|
| 546 |
# Streaming / buffering
|
| 547 |
self.text_buffer_size = 32
|
| 548 |
self.min_text_chunk_chars = 8
|
| 549 |
-
self.chunk_frames =
|
| 550 |
self.overlap_frames = 0
|
| 551 |
self._max_steps = _TALKER_MAX_SEQ_LEN - _TALKER_PREFILL_LEN # 751
|
| 552 |
|
|
@@ -565,7 +565,7 @@ class Qwen3TTSInferencerONNX:
|
|
| 565 |
self._causal_tril = _causal_tril.reshape(1, 1, _TALKER_MAX_SEQ_LEN, _TALKER_MAX_SEQ_LEN)
|
| 566 |
|
| 567 |
# Define cos and sin table for RoPE embedding
|
| 568 |
-
self._cos_rope, self._sin_rope = self.
|
| 569 |
|
| 570 |
# Pre-allocate input OrtValues for Talker prefill
|
| 571 |
self._talker_prefill_inputs_embeds_ov = _make_device_ortvalue(
|
|
@@ -624,7 +624,11 @@ class Qwen3TTSInferencerONNX:
|
|
| 624 |
# For each, [B, H, 16, D], with varying 1/0 depending on the position
|
| 625 |
|
| 626 |
# Define cos and sin table for Local RoPE embedding
|
| 627 |
-
self._cos_rope_local, self._sin_rope_local = self.
|
|
|
|
|
|
|
|
|
|
|
|
|
| 628 |
|
| 629 |
# Pre-allocate input OrtValues for Local Talker prefill
|
| 630 |
self._local_prefill_inputs_embeds_ov = _make_device_ortvalue(
|
|
@@ -743,12 +747,39 @@ class Qwen3TTSInferencerONNX:
|
|
| 743 |
self._codec_decoder_bound = _BoundSession(self._codec_decoder_sess, self._device, cuda_device_id)
|
| 744 |
self._codec_step_idx = 0
|
| 745 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 746 |
# Pre-allocate OrtValues input for Codec Decoder
|
| 747 |
self._codec_codes_ov = _make_device_ortvalue(
|
| 748 |
(1, self._num_code_groups, self.chunk_frames), np.int64, self._device, cuda_device_id
|
| 749 |
)
|
| 750 |
-
self._codec_cache_position_ov = _make_device_ortvalue(
|
| 751 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 752 |
)
|
| 753 |
|
| 754 |
# Pre-allocate OrtValues for Codec Decoder
|
|
@@ -837,9 +868,12 @@ class Qwen3TTSInferencerONNX:
|
|
| 837 |
b.bind_output_device("current_pre_conv_hidden_state_cache", self._codec_pre_conv_cache_ov)
|
| 838 |
|
| 839 |
b.bind_input_device("codes", self._codec_codes_ov)
|
| 840 |
-
b.bind_input_device("cache_position", self._codec_cache_position_ov)
|
| 841 |
b.bind_input_device("hidden_state_cache", self._codec_hidden_cache_ov)
|
| 842 |
b.bind_input_device("pre_conv_hidden_state_cache", self._codec_pre_conv_cache_ov)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 843 |
|
| 844 |
for i in range(self._speech_tokenizer_num_hidden_layers):
|
| 845 |
b.bind_input_device(f"past_key_{i}", self._codec_kv_ovs[2 * i])
|
|
@@ -1021,7 +1055,7 @@ class Qwen3TTSInferencerONNX:
|
|
| 1021 |
|
| 1022 |
# ββ Helper to build RoPE cos and sin tables for the talker ββββββββββββββββ
|
| 1023 |
|
| 1024 |
-
def
|
| 1025 |
"""
|
| 1026 |
Build NumPy RoPE cosine and sine lookup tables with interleaved multimodal layout.
|
| 1027 |
|
|
@@ -1128,7 +1162,7 @@ class Qwen3TTSInferencerONNX:
|
|
| 1128 |
|
| 1129 |
return cos.astype(np.float32), sin.astype(np.float32)
|
| 1130 |
|
| 1131 |
-
def
|
| 1132 |
"""
|
| 1133 |
Build NumPy RoPE cosine and sine lookup tables for the local talker.
|
| 1134 |
|
|
@@ -1160,9 +1194,6 @@ class Qwen3TTSInferencerONNX:
|
|
| 1160 |
Sine table with shape matching the Torch version, including the
|
| 1161 |
extra singleton dimension inserted at axis 1.
|
| 1162 |
"""
|
| 1163 |
-
rope_dim = self._code_predictor_config.head_dim
|
| 1164 |
-
base = self._code_predictor_config.rope_theta
|
| 1165 |
-
|
| 1166 |
# Build inverse frequencies for RoPE.
|
| 1167 |
inv_idx = np.arange(0, rope_dim, 2, dtype=np.float32)
|
| 1168 |
|
|
@@ -1172,7 +1203,7 @@ class Qwen3TTSInferencerONNX:
|
|
| 1172 |
inv_freq_expanded = np.broadcast_to(
|
| 1173 |
inv_freq[None, :, None], (1, len(inv_idx), 1)
|
| 1174 |
) # shape (bs, 1, rope_dim, 1)
|
| 1175 |
-
position_ids = np.arange(
|
| 1176 |
position_ids_expanded = np.broadcast_to(
|
| 1177 |
position_ids[None, None, :], (1, 1, len(position_ids))
|
| 1178 |
) # shape (bs, 1, positions)
|
|
@@ -1503,13 +1534,19 @@ class Qwen3TTSInferencerONNX:
|
|
| 1503 |
def _run_codec_decoder(
|
| 1504 |
self,
|
| 1505 |
chunk_tokens: np.ndarray, # [1, 16, chunk_length]
|
| 1506 |
-
|
|
|
|
|
|
|
| 1507 |
) -> np.ndarray:
|
|
|
|
| 1508 |
"""Execute one Codec Decoder step via IOBinding and return wav as NumPy."""
|
| 1509 |
b = self._codec_decoder_bound
|
| 1510 |
# Copy numpy inputs to buffer in-place
|
| 1511 |
_copy_numpy_to_ortvalue(chunk_tokens, self._codec_codes_ov)
|
| 1512 |
-
_copy_numpy_to_ortvalue(pos, self._codec_cache_position_ov)
|
|
|
|
|
|
|
|
|
|
| 1513 |
# All KV and output bindings are already wired statically
|
| 1514 |
b.run()
|
| 1515 |
# Copy wav to CPU
|
|
@@ -1548,19 +1585,20 @@ class Qwen3TTSInferencerONNX:
|
|
| 1548 |
cos_rope = self._cos_rope[:, :, cache_position].copy()
|
| 1549 |
sin_rope = self._sin_rope[:, :, cache_position].copy()
|
| 1550 |
logits = self._run_talker_step(inputs_embeds, cache_position, attn_mask, cos_rope, sin_rope) # [1, vocab]
|
| 1551 |
-
log.info(f"step-idx-{self._step_idx} logits {logits} {logits.shape} {logits.dtype}")
|
| 1552 |
|
| 1553 |
# Retrieve hidden states for Local Talker prefill
|
| 1554 |
self._last_hidden_states_np = self._talker_hidden_ov.numpy() # [1, 1, H]
|
| 1555 |
-
log.info(
|
| 1556 |
-
|
| 1557 |
-
|
| 1558 |
-
)
|
| 1559 |
self._talker_seq_len += 1
|
| 1560 |
|
| 1561 |
# ββ Sample first token (CPU) βββββββββββββββββββββββββββββββββββοΏ½οΏ½οΏ½ββββββ
|
| 1562 |
history = self._generated_tokens[:, :, 0]
|
| 1563 |
first_token = self._sample(logits, history, suppress=self._suppress_tokens)
|
|
|
|
| 1564 |
|
| 1565 |
self._last_first_token = first_token
|
| 1566 |
self._is_stopping = bool(first_token[0] == self._codec_eos_token_id)
|
|
@@ -1686,6 +1724,7 @@ class Qwen3TTSInferencerONNX:
|
|
| 1686 |
)[
|
| 1687 |
None, :, :
|
| 1688 |
] # [1, 1, 16]
|
|
|
|
| 1689 |
|
| 1690 |
self._last_audio_tokens = audio_tokens.copy()
|
| 1691 |
self._generated_tokens = np.concatenate([self._generated_tokens, audio_tokens.copy()], axis=1)
|
|
@@ -1822,10 +1861,29 @@ class Qwen3TTSInferencerONNX:
|
|
| 1822 |
n_iter = n_iter // self.chunk_frames
|
| 1823 |
codec_step_idx = 0
|
| 1824 |
for n in range(n_iter):
|
| 1825 |
-
pos = np.arange(codec_step_idx, codec_step_idx +
|
| 1826 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1827 |
t0 = time.perf_counter()
|
| 1828 |
-
self._run_codec_decoder(chunk_tokens,
|
| 1829 |
times["codec_decoder"].append((time.perf_counter() - t0) * 1000) # ms
|
| 1830 |
exec_time_tot = sum(times["codec_decoder"]) / 1000
|
| 1831 |
avg_exec_time = exec_time_tot / len(times["codec_decoder"]) * 1000
|
|
@@ -1897,8 +1955,10 @@ class Qwen3TTSInferencerONNX:
|
|
| 1897 |
if not self._prefilled:
|
| 1898 |
self.prefill()
|
| 1899 |
return outputs
|
|
|
|
| 1900 |
while self._pending_tokens and not self.is_finished:
|
| 1901 |
token = self._pending_tokens.pop(0)
|
|
|
|
| 1902 |
output = self.step(token)
|
| 1903 |
if output is not None:
|
| 1904 |
outputs.append(output)
|
|
@@ -1906,8 +1966,10 @@ class Qwen3TTSInferencerONNX:
|
|
| 1906 |
|
| 1907 |
def push_text(self, text_fragment: str) -> List[NDArrayInt]:
|
| 1908 |
self._text_cache += text_fragment
|
|
|
|
| 1909 |
for segment in self._extract_text_segments(force=False):
|
| 1910 |
self._pending_tokens.extend(self._tokenize_texts([segment]))
|
|
|
|
| 1911 |
return self._drain_pending_tokens()
|
| 1912 |
|
| 1913 |
def end_text(self) -> List[NDArrayInt]:
|
|
@@ -1977,12 +2039,30 @@ class Qwen3TTSInferencerONNX:
|
|
| 1977 |
|
| 1978 |
# cache_position for the decoder transformer
|
| 1979 |
# It starts from 0 and grows. The model handles the sliding window internally.
|
| 1980 |
-
pos = np.arange(self._codec_step_idx, self._codec_step_idx +
|
| 1981 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1982 |
|
| 1983 |
# Run the wired graph
|
| 1984 |
# chunk_tokens need to be .copy(), otherwise it messed up if we bind input and/or use CUDA graph
|
| 1985 |
-
wav = self._run_codec_decoder(chunk_tokens.copy(),
|
| 1986 |
# new memory address, so that output wav not only the last repeated
|
| 1987 |
|
| 1988 |
# The decoder output already discards the upsampled left-context frames (25 frames)
|
|
@@ -1994,6 +2074,7 @@ class Qwen3TTSInferencerONNX:
|
|
| 1994 |
f"{np.min(wav)} {np.mean(wav)} {np.std(wav)} {np.max(wav)} {wav.shape}"
|
| 1995 |
)
|
| 1996 |
|
|
|
|
| 1997 |
return wav
|
| 1998 |
|
| 1999 |
def _overlap_samples(self, wav: NDArrayFloat) -> int:
|
|
|
|
| 372 |
cuda_device_id : int
|
| 373 |
enable_cuda_graph : bool
|
| 374 |
num_threads : int
|
|
|
|
| 375 |
temperature : float
|
| 376 |
top_p : float
|
| 377 |
top_k : int
|
|
|
|
| 400 |
use_cuda: bool = True,
|
| 401 |
cuda_device_id: int = 0,
|
| 402 |
enable_cuda_graph: bool = True,
|
|
|
|
| 403 |
chunk_frames: int = 4,
|
| 404 |
+
num_threads: int = 4,
|
| 405 |
temperature: float = 0.85,
|
| 406 |
top_p: float = 0.8,
|
| 407 |
top_k: int = 50,
|
|
|
|
| 471 |
self._speech_tokenizer_latent_dim = dec_cfg.latent_dim
|
| 472 |
self._speech_tokenizer_codebook_dim = dec_cfg.codebook_dim
|
| 473 |
self._speech_tokenizer_head_dim = dec_cfg.head_dim
|
| 474 |
+
self._speech_tokenizer_rope_theta = dec_cfg.rope_theta
|
| 475 |
self._speech_tokenizer_num_attention_heads = dec_cfg.num_attention_heads
|
| 476 |
self._speech_tokenizer_num_hidden_layers = dec_cfg.num_hidden_layers
|
| 477 |
self._speech_tokenizer_num_key_value_heads = dec_cfg.num_key_value_heads
|
| 478 |
self._speech_tokenizer_sliding_window = dec_cfg.sliding_window
|
| 479 |
self._speech_tokenizer_decoder_left_context_size = 25
|
| 480 |
self._speech_tokenizer_decoder_total_upsample = 1920
|
| 481 |
+
self._codec_chunk_frames = chunk_frames
|
| 482 |
self.output_sample_rate = self._speech_tokenizer_config.output_sample_rate
|
| 483 |
|
| 484 |
with open(model_config_path, "r") as f:
|
|
|
|
| 546 |
# Streaming / buffering
|
| 547 |
self.text_buffer_size = 32
|
| 548 |
self.min_text_chunk_chars = 8
|
| 549 |
+
self.chunk_frames = self._codec_chunk_frames
|
| 550 |
self.overlap_frames = 0
|
| 551 |
self._max_steps = _TALKER_MAX_SEQ_LEN - _TALKER_PREFILL_LEN # 751
|
| 552 |
|
|
|
|
| 565 |
self._causal_tril = _causal_tril.reshape(1, 1, _TALKER_MAX_SEQ_LEN, _TALKER_MAX_SEQ_LEN)
|
| 566 |
|
| 567 |
# Define cos and sin table for RoPE embedding
|
| 568 |
+
self._cos_rope, self._sin_rope = self._build_multimodal_rope_tables_numpy()
|
| 569 |
|
| 570 |
# Pre-allocate input OrtValues for Talker prefill
|
| 571 |
self._talker_prefill_inputs_embeds_ov = _make_device_ortvalue(
|
|
|
|
| 624 |
# For each, [B, H, 16, D], with varying 1/0 depending on the position
|
| 625 |
|
| 626 |
# Define cos and sin table for Local RoPE embedding
|
| 627 |
+
self._cos_rope_local, self._sin_rope_local = self._build_rope_tables_numpy(
|
| 628 |
+
rope_dim=self._code_predictor_config.head_dim,
|
| 629 |
+
base=self._code_predictor_config.rope_theta,
|
| 630 |
+
max_pos=self._num_code_groups,
|
| 631 |
+
)
|
| 632 |
|
| 633 |
# Pre-allocate input OrtValues for Local Talker prefill
|
| 634 |
self._local_prefill_inputs_embeds_ov = _make_device_ortvalue(
|
|
|
|
| 747 |
self._codec_decoder_bound = _BoundSession(self._codec_decoder_sess, self._device, cuda_device_id)
|
| 748 |
self._codec_step_idx = 0
|
| 749 |
|
| 750 |
+
# Define causal_tril for attention_mask Local prefill and step
|
| 751 |
+
_causal_tril_codec = np.tril(
|
| 752 |
+
np.ones((self._speech_tokenizer_sliding_window, self._speech_tokenizer_sliding_window), dtype=bool)
|
| 753 |
+
)
|
| 754 |
+
self._causal_tril_codec = _causal_tril_codec.reshape(
|
| 755 |
+
1, 1, self._speech_tokenizer_sliding_window, self._speech_tokenizer_sliding_window
|
| 756 |
+
)
|
| 757 |
+
# Define cos and sin table for Codec RoPE embedding
|
| 758 |
+
self._cos_rope_codec, self._sin_rope_codec = self._build_rope_tables_numpy(
|
| 759 |
+
rope_dim=self._speech_tokenizer_head_dim,
|
| 760 |
+
base=self._speech_tokenizer_rope_theta,
|
| 761 |
+
max_pos=(
|
| 762 |
+
self._max_steps
|
| 763 |
+
if self._max_steps % self.chunk_frames == 0
|
| 764 |
+
else self._max_steps + self.chunk_frames - (self._max_steps % self.chunk_frames)
|
| 765 |
+
),
|
| 766 |
+
)
|
| 767 |
+
|
| 768 |
# Pre-allocate OrtValues input for Codec Decoder
|
| 769 |
self._codec_codes_ov = _make_device_ortvalue(
|
| 770 |
(1, self._num_code_groups, self.chunk_frames), np.int64, self._device, cuda_device_id
|
| 771 |
)
|
| 772 |
+
# self._codec_cache_position_ov = _make_device_ortvalue(
|
| 773 |
+
# (self.chunk_frames), np.int64, self._device, cuda_device_id
|
| 774 |
+
# )
|
| 775 |
+
self._codec_attention_mask_ov = _make_device_ortvalue(
|
| 776 |
+
(1, 1, self.chunk_frames, self._speech_tokenizer_sliding_window), np.float32, self._device, cuda_device_id
|
| 777 |
+
)
|
| 778 |
+
self._codec_cos_rope_ov = _make_device_ortvalue(
|
| 779 |
+
(1, self.chunk_frames, self._speech_tokenizer_head_dim), np.float32, self._device, cuda_device_id
|
| 780 |
+
)
|
| 781 |
+
self._codec_sin_rope_ov = _make_device_ortvalue(
|
| 782 |
+
(1, self.chunk_frames, self._speech_tokenizer_head_dim), np.float32, self._device, cuda_device_id
|
| 783 |
)
|
| 784 |
|
| 785 |
# Pre-allocate OrtValues for Codec Decoder
|
|
|
|
| 868 |
b.bind_output_device("current_pre_conv_hidden_state_cache", self._codec_pre_conv_cache_ov)
|
| 869 |
|
| 870 |
b.bind_input_device("codes", self._codec_codes_ov)
|
|
|
|
| 871 |
b.bind_input_device("hidden_state_cache", self._codec_hidden_cache_ov)
|
| 872 |
b.bind_input_device("pre_conv_hidden_state_cache", self._codec_pre_conv_cache_ov)
|
| 873 |
+
# b.bind_input_device("cache_position", self._codec_cache_position_ov)
|
| 874 |
+
b.bind_input_device("attention_mask", self._codec_attention_mask_ov)
|
| 875 |
+
b.bind_input_device("cos_rope", self._codec_cos_rope_ov)
|
| 876 |
+
b.bind_input_device("sin_rope", self._codec_sin_rope_ov)
|
| 877 |
|
| 878 |
for i in range(self._speech_tokenizer_num_hidden_layers):
|
| 879 |
b.bind_input_device(f"past_key_{i}", self._codec_kv_ovs[2 * i])
|
|
|
|
| 1055 |
|
| 1056 |
# ββ Helper to build RoPE cos and sin tables for the talker ββββββββββββββββ
|
| 1057 |
|
| 1058 |
+
def _build_multimodal_rope_tables_numpy(self):
|
| 1059 |
"""
|
| 1060 |
Build NumPy RoPE cosine and sine lookup tables with interleaved multimodal layout.
|
| 1061 |
|
|
|
|
| 1162 |
|
| 1163 |
return cos.astype(np.float32), sin.astype(np.float32)
|
| 1164 |
|
| 1165 |
+
def _build_rope_tables_numpy(self, rope_dim: int, base: float, max_pos: int):
|
| 1166 |
"""
|
| 1167 |
Build NumPy RoPE cosine and sine lookup tables for the local talker.
|
| 1168 |
|
|
|
|
| 1194 |
Sine table with shape matching the Torch version, including the
|
| 1195 |
extra singleton dimension inserted at axis 1.
|
| 1196 |
"""
|
|
|
|
|
|
|
|
|
|
| 1197 |
# Build inverse frequencies for RoPE.
|
| 1198 |
inv_idx = np.arange(0, rope_dim, 2, dtype=np.float32)
|
| 1199 |
|
|
|
|
| 1203 |
inv_freq_expanded = np.broadcast_to(
|
| 1204 |
inv_freq[None, :, None], (1, len(inv_idx), 1)
|
| 1205 |
) # shape (bs, 1, rope_dim, 1)
|
| 1206 |
+
position_ids = np.arange(max_pos, dtype=np.float32)
|
| 1207 |
position_ids_expanded = np.broadcast_to(
|
| 1208 |
position_ids[None, None, :], (1, 1, len(position_ids))
|
| 1209 |
) # shape (bs, 1, positions)
|
|
|
|
| 1534 |
def _run_codec_decoder(
|
| 1535 |
self,
|
| 1536 |
chunk_tokens: np.ndarray, # [1, 16, chunk_length]
|
| 1537 |
+
attention_mask: np.ndarray, # [1, 1, chunk_length, 72]
|
| 1538 |
+
cos_rope: np.ndarray, # [1, chunk_length, 64]
|
| 1539 |
+
sin_rope: np.ndarray, # [1, chunk_length, 64]
|
| 1540 |
) -> np.ndarray:
|
| 1541 |
+
# pos: np.ndarray, # [chunk_length]
|
| 1542 |
"""Execute one Codec Decoder step via IOBinding and return wav as NumPy."""
|
| 1543 |
b = self._codec_decoder_bound
|
| 1544 |
# Copy numpy inputs to buffer in-place
|
| 1545 |
_copy_numpy_to_ortvalue(chunk_tokens, self._codec_codes_ov)
|
| 1546 |
+
# _copy_numpy_to_ortvalue(pos, self._codec_cache_position_ov)
|
| 1547 |
+
_copy_numpy_to_ortvalue(attention_mask, self._codec_attention_mask_ov)
|
| 1548 |
+
_copy_numpy_to_ortvalue(cos_rope, self._codec_cos_rope_ov)
|
| 1549 |
+
_copy_numpy_to_ortvalue(sin_rope, self._codec_sin_rope_ov)
|
| 1550 |
# All KV and output bindings are already wired statically
|
| 1551 |
b.run()
|
| 1552 |
# Copy wav to CPU
|
|
|
|
| 1585 |
cos_rope = self._cos_rope[:, :, cache_position].copy()
|
| 1586 |
sin_rope = self._sin_rope[:, :, cache_position].copy()
|
| 1587 |
logits = self._run_talker_step(inputs_embeds, cache_position, attn_mask, cos_rope, sin_rope) # [1, vocab]
|
| 1588 |
+
# log.info(f"step-idx-{self._step_idx} logits {logits} {logits.shape} {logits.dtype}")
|
| 1589 |
|
| 1590 |
# Retrieve hidden states for Local Talker prefill
|
| 1591 |
self._last_hidden_states_np = self._talker_hidden_ov.numpy() # [1, 1, H]
|
| 1592 |
+
# log.info(
|
| 1593 |
+
# f"step-idx-{self._step_idx} self._last_hidden_states_np {self._last_hidden_states_np} "
|
| 1594 |
+
# f"{self._last_hidden_states_np.shape} {self._last_hidden_states_np.dtype}"
|
| 1595 |
+
# )
|
| 1596 |
self._talker_seq_len += 1
|
| 1597 |
|
| 1598 |
# ββ Sample first token (CPU) βββββββββββββββββββββββββββββββββββοΏ½οΏ½οΏ½ββββββ
|
| 1599 |
history = self._generated_tokens[:, :, 0]
|
| 1600 |
first_token = self._sample(logits, history, suppress=self._suppress_tokens)
|
| 1601 |
+
log.info(f"step-idx-{self._step_idx} vq {first_token} {first_token.shape} {first_token.dtype}")
|
| 1602 |
|
| 1603 |
self._last_first_token = first_token
|
| 1604 |
self._is_stopping = bool(first_token[0] == self._codec_eos_token_id)
|
|
|
|
| 1724 |
)[
|
| 1725 |
None, :, :
|
| 1726 |
] # [1, 1, 16]
|
| 1727 |
+
log.info(f"local-step-idx-{self._step_idx} vq-rvq {audio_tokens} {audio_tokens.shape} {audio_tokens.dtype}")
|
| 1728 |
|
| 1729 |
self._last_audio_tokens = audio_tokens.copy()
|
| 1730 |
self._generated_tokens = np.concatenate([self._generated_tokens, audio_tokens.copy()], axis=1)
|
|
|
|
| 1861 |
n_iter = n_iter // self.chunk_frames
|
| 1862 |
codec_step_idx = 0
|
| 1863 |
for n in range(n_iter):
|
| 1864 |
+
pos = np.arange(codec_step_idx, codec_step_idx + self.chunk_frames, dtype=np.int64)
|
| 1865 |
+
cos_rope = self._cos_rope_codec[:, pos].copy()
|
| 1866 |
+
sin_rope = self._sin_rope_codec[:, pos].copy()
|
| 1867 |
+
if codec_step_idx + self.chunk_frames < self._speech_tokenizer_sliding_window:
|
| 1868 |
+
# attention mask goes from the right to the left
|
| 1869 |
+
# we pad left False if the current + chunks still less than sliding window left
|
| 1870 |
+
pad_len = self._speech_tokenizer_sliding_window - (codec_step_idx + self.chunk_frames)
|
| 1871 |
+
# here, basically, we need to pad left because we start from the right
|
| 1872 |
+
# and there still remaining amounts before it attends full sliding window length
|
| 1873 |
+
# so we must not attend to those previous remaining amounts, which is less than pos. 0
|
| 1874 |
+
causal_rows_right = self._causal_tril_codec[:, :, pos, :-pad_len] # [1,1,q_len,72-accum_len]
|
| 1875 |
+
causal_rows = np.pad(
|
| 1876 |
+
causal_rows_right, ((0, 0), (0, 0), (0, 0), (pad_len, 0)), mode="constant", constant_values=False
|
| 1877 |
+
)
|
| 1878 |
+
else:
|
| 1879 |
+
# this case where the current + chunks is exactly sliding window length
|
| 1880 |
+
# or more than sliding window length
|
| 1881 |
+
# simply take the last chunks from the causal tril
|
| 1882 |
+
causal_rows = self._causal_tril_codec[:, :, -self.chunk_frames :, :] # [1,1,q_len,72]
|
| 1883 |
+
attn_mask = np.where(causal_rows, 0.0, -np.inf).astype(np.float32) # [1, 1, q_len, 72]
|
| 1884 |
+
codec_step_idx += self.chunk_frames
|
| 1885 |
t0 = time.perf_counter()
|
| 1886 |
+
self._run_codec_decoder(chunk_tokens, attn_mask, cos_rope, sin_rope)
|
| 1887 |
times["codec_decoder"].append((time.perf_counter() - t0) * 1000) # ms
|
| 1888 |
exec_time_tot = sum(times["codec_decoder"]) / 1000
|
| 1889 |
avg_exec_time = exec_time_tot / len(times["codec_decoder"]) * 1000
|
|
|
|
| 1955 |
if not self._prefilled:
|
| 1956 |
self.prefill()
|
| 1957 |
return outputs
|
| 1958 |
+
log.info(f"self._pending_tokens {self._pending_tokens}")
|
| 1959 |
while self._pending_tokens and not self.is_finished:
|
| 1960 |
token = self._pending_tokens.pop(0)
|
| 1961 |
+
log.info(f"pop token {token} -> pending_tokens {self._pending_tokens}")
|
| 1962 |
output = self.step(token)
|
| 1963 |
if output is not None:
|
| 1964 |
outputs.append(output)
|
|
|
|
| 1966 |
|
| 1967 |
def push_text(self, text_fragment: str) -> List[NDArrayInt]:
|
| 1968 |
self._text_cache += text_fragment
|
| 1969 |
+
log.info(f"push text_fragment {text_fragment} -> text_cache {self._text_cache}")
|
| 1970 |
for segment in self._extract_text_segments(force=False):
|
| 1971 |
self._pending_tokens.extend(self._tokenize_texts([segment]))
|
| 1972 |
+
log.info(f"push segment {segment} -> pending_tokens {self._pending_tokens}")
|
| 1973 |
return self._drain_pending_tokens()
|
| 1974 |
|
| 1975 |
def end_text(self) -> List[NDArrayInt]:
|
|
|
|
| 2039 |
|
| 2040 |
# cache_position for the decoder transformer
|
| 2041 |
# It starts from 0 and grows. The model handles the sliding window internally.
|
| 2042 |
+
pos = np.arange(self._codec_step_idx, self._codec_step_idx + self.chunk_frames, dtype=np.int64)
|
| 2043 |
+
cos_rope = self._cos_rope_codec[:, pos].copy()
|
| 2044 |
+
sin_rope = self._sin_rope_codec[:, pos].copy()
|
| 2045 |
+
if self._codec_step_idx + self.chunk_frames < self._speech_tokenizer_sliding_window:
|
| 2046 |
+
# attention mask goes from the right to the left
|
| 2047 |
+
# we pad left False if the current + chunks still less than sliding window left
|
| 2048 |
+
pad_len = self._speech_tokenizer_sliding_window - (self._codec_step_idx + self.chunk_frames)
|
| 2049 |
+
# here, basically, we need to pad left because we start from the right
|
| 2050 |
+
# and there still remaining amounts before it attends full sliding window length
|
| 2051 |
+
# so we must not attend to those previous remaining amounts, which is less than pos. 0
|
| 2052 |
+
causal_rows_right = self._causal_tril_codec[:, :, pos, :-pad_len] # [1,1,q_len,72-accum_len]
|
| 2053 |
+
causal_rows = np.pad(
|
| 2054 |
+
causal_rows_right, ((0, 0), (0, 0), (0, 0), (pad_len, 0)), mode="constant", constant_values=False
|
| 2055 |
+
)
|
| 2056 |
+
else:
|
| 2057 |
+
# this case where the current + chunks is exactly sliding window length
|
| 2058 |
+
# or more than sliding window length
|
| 2059 |
+
# simply take the last chunks from the causal tril
|
| 2060 |
+
causal_rows = self._causal_tril_codec[:, :, -self.chunk_frames :, :] # [1,1,q_len,72]
|
| 2061 |
+
attn_mask = np.where(causal_rows, 0.0, -np.inf).astype(np.float32) # [1, 1, q_len, 72]
|
| 2062 |
|
| 2063 |
# Run the wired graph
|
| 2064 |
# chunk_tokens need to be .copy(), otherwise it messed up if we bind input and/or use CUDA graph
|
| 2065 |
+
wav = self._run_codec_decoder(chunk_tokens.copy(), attn_mask, cos_rope, sin_rope).copy()
|
| 2066 |
# new memory address, so that output wav not only the last repeated
|
| 2067 |
|
| 2068 |
# The decoder output already discards the upsampled left-context frames (25 frames)
|
|
|
|
| 2074 |
f"{np.min(wav)} {np.mean(wav)} {np.std(wav)} {np.max(wav)} {wav.shape}"
|
| 2075 |
)
|
| 2076 |
|
| 2077 |
+
self._codec_step_idx += self.chunk_frames
|
| 2078 |
return wav
|
| 2079 |
|
| 2080 |
def _overlap_samples(self, wav: NDArrayFloat) -> int:
|
|
@@ -34,7 +34,6 @@ Usage:
|
|
| 34 |
--repetition_penalty 1.9 \
|
| 35 |
--repetition_window 50 \
|
| 36 |
--num_threads 4 \
|
| 37 |
-
--chunk_frames 4 \
|
| 38 |
--prompt_wav audio_ref/speaker.[wav|flac|mp3] \
|
| 39 |
--out_wav output.wav \
|
| 40 |
--text "Text to be synthesized" \
|
|
@@ -237,7 +236,6 @@ def parse_args() -> argparse.Namespace:
|
|
| 237 |
p.add_argument("--output_wav_path", default=None)
|
| 238 |
p.add_argument("--language", default=_LANGUAGE)
|
| 239 |
p.add_argument("--text", default=_TEXT, help="Text to synthesise.")
|
| 240 |
-
p.add_argument("--chunk_frames", type=int, default=_CHUNK_FRAMES)
|
| 241 |
p.add_argument("--temperature", type=float, default=_TEMPERATURE)
|
| 242 |
p.add_argument("--top_p", type=float, default=_TOP_P)
|
| 243 |
p.add_argument("--top_k", type=int, default=_TOP_K)
|
|
@@ -329,7 +327,7 @@ def main() -> None:
|
|
| 329 |
cuda_device_id=args.cuda_device_id,
|
| 330 |
enable_cuda_graph=args.enable_cuda_graph,
|
| 331 |
num_threads=args.num_threads,
|
| 332 |
-
chunk_frames=
|
| 333 |
temperature=args.temperature,
|
| 334 |
top_p=args.top_p,
|
| 335 |
top_k=args.top_k,
|
|
|
|
| 34 |
--repetition_penalty 1.9 \
|
| 35 |
--repetition_window 50 \
|
| 36 |
--num_threads 4 \
|
|
|
|
| 37 |
--prompt_wav audio_ref/speaker.[wav|flac|mp3] \
|
| 38 |
--out_wav output.wav \
|
| 39 |
--text "Text to be synthesized" \
|
|
|
|
| 236 |
p.add_argument("--output_wav_path", default=None)
|
| 237 |
p.add_argument("--language", default=_LANGUAGE)
|
| 238 |
p.add_argument("--text", default=_TEXT, help="Text to synthesise.")
|
|
|
|
| 239 |
p.add_argument("--temperature", type=float, default=_TEMPERATURE)
|
| 240 |
p.add_argument("--top_p", type=float, default=_TOP_P)
|
| 241 |
p.add_argument("--top_k", type=int, default=_TOP_K)
|
|
|
|
| 327 |
cuda_device_id=args.cuda_device_id,
|
| 328 |
enable_cuda_graph=args.enable_cuda_graph,
|
| 329 |
num_threads=args.num_threads,
|
| 330 |
+
chunk_frames=_CHUNK_FRAMES,
|
| 331 |
temperature=args.temperature,
|
| 332 |
top_p=args.top_p,
|
| 333 |
top_k=args.top_k,
|