pltobing commited on
Commit
f8a6b67
Β·
1 Parent(s): 267344b

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.

qwen3-tts_onnx/codec_decoder_model.onnx CHANGED
@@ -1,3 +1,3 @@
1
  version https://git-lfs.github.com/spec/v1
2
- oid sha256:246c19dba19adf0ad2620b82fece12e17e874a5d9bd52a3b089fd8b8107b57de
3
- size 456761251
 
1
  version https://git-lfs.github.com/spec/v1
2
+ oid sha256:5db3b88ca51c04b39ad2603ab781887fef300a65b5614f7cbeff1be86d0da51c
3
+ size 456699494
src/inference/qwen3_tts_inferencer_onnx.py CHANGED
@@ -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 = 4
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 = 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._build_rope_tables_numpy()
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._build_local_rope_tables_numpy()
 
 
 
 
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
- (self.chunk_frames), np.int64, self._device, cuda_device_id
 
 
 
 
 
 
 
 
 
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 _build_rope_tables_numpy(self):
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 _build_local_rope_tables_numpy(self):
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(self._num_code_groups, dtype=np.float32)
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
- pos: np.ndarray, # [chunk_length]
 
 
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
- f"step-idx-{self._step_idx} self._last_hidden_states_np {self._last_hidden_states_np} "
1557
- f"{self._last_hidden_states_np.shape} {self._last_hidden_states_np.dtype}"
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 + chunk_tokens.shape[-1], dtype=np.int64)
1826
- codec_step_idx += chunk_tokens.shape[-1]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1827
  t0 = time.perf_counter()
1828
- self._run_codec_decoder(chunk_tokens, pos)
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 + chunk_tokens.shape[-1], dtype=np.int64)
1981
- self._codec_step_idx += chunk_tokens.shape[-1]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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(), pos).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:
test_qwen3-tts-streaming_onnx.py CHANGED
@@ -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=args.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,