pltobing commited on
Commit
4d53432
Β·
1 Parent(s): 08c67e2

feat: further improve the handling of cont. stream

Browse files

- Previously, as `step_idx` was reset after exceeding
limit of next turn length, that next turn will actually
use the very first audio reference instead of continuing
from the generated wav list. This causes discontinuity
and confusion on the generation. Now, it has been fixed.
- Further, the very first audio reference is now included
in the list of reference wav array. This is to maintain
coherence from the very first reference, to the first
generated audio chunks, and so on. Therefore the list
buffer ring of the reference wav for subsequent rounds is
initiated by the very first audio ref., then populated
more by the generated audio. It is now with max. length
of 8 s., the list ring.
- Fix the sampling parameters to make the generation more
robust and consistent, as we are feeding out-of-distribution
sequence structure to the model: temperature to 0.75,
top_p to 0.85, repetition_penalty to 9.5, repetition_window to 75.
- Now, the system works robustly with respect to the pronunciation
and hallucination.
- There is still a bug on the audio initial pulse between rounds
of text. This most likely caused by the reset of codec cache
every rounds due to the pad right of the last chunk for each round.
Need to further investigate, if that is the case, the last chunk
of each round needs to be generated by another onnx session
with dynamic axes on the frame length. Otherwise, it is probably
needing crossfade.

src/inference/qwen3_tts_inferencer_onnx.py CHANGED
@@ -119,7 +119,7 @@ _NUM_CODE_GROUPS = 16
119
 
120
  # For continuous streaming with multi turn texts
121
  _LENGTH_RESET_LIMIT_FOR_START_OF_MULTI_TURN = 50
122
- _REF_WAV_LENGTH_CUT_LIMIT_FOR_START_OF_MULTI_TURN = 144000
123
 
124
 
125
  # ── CPU sampling helpers ──────────────────────────────────────────────────────
@@ -650,24 +650,6 @@ class Qwen3TTSInferencerONNX:
650
  self._local_prefill_sin_rope_ov = _make_device_ortvalue(
651
  (1, 2, self._head_dim), np.float32, self._device, cuda_device_id
652
  )
653
- # self._local_prefill_cache_write_mask_ov = _make_device_ortvalue(
654
- # (1, self._local_num_key_value_heads, self._num_code_groups, self._local_head_dim),
655
- # np.bool,
656
- # self._device,
657
- # cuda_device_id,
658
- # )
659
- # self._local_prefill_key_gqa_expanded_ov = _make_device_ortvalue(
660
- # (1, self._local_num_attention_heads, self._num_code_groups, self._local_head_dim),
661
- # np.float32,
662
- # self._device,
663
- # cuda_device_id,
664
- # )
665
- # self._local_prefill_value_gqa_expanded_ov = _make_device_ortvalue(
666
- # (1, self._local_num_attention_heads, self._num_code_groups, self._local_head_dim),
667
- # np.float32,
668
- # self._device,
669
- # cuda_device_id,
670
- # )
671
 
672
  # Pre-allocate output OrtValues for Local Talker prefill
673
  # Backbone outputs last_hidden [1, cp_hidden_size]; logits come from lm_head
@@ -689,24 +671,6 @@ class Qwen3TTSInferencerONNX:
689
  self._local_step_sin_rope_ov = _make_device_ortvalue(
690
  (1, 1, self._head_dim), np.float32, self._device, cuda_device_id
691
  )
692
- # self._local_step_cache_write_mask_ov = _make_device_ortvalue(
693
- # (1, self._local_num_key_value_heads, self._num_code_groups, self._local_head_dim),
694
- # np.bool,
695
- # self._device,
696
- # cuda_device_id,
697
- # )
698
- # self._local_step_key_gqa_expanded_ov = _make_device_ortvalue(
699
- # (1, self._local_num_attention_heads, self._num_code_groups, self._local_head_dim),
700
- # np.float32,
701
- # self._device,
702
- # cuda_device_id,
703
- # )
704
- # self._local_step_value_gqa_expanded_ov = _make_device_ortvalue(
705
- # (1, self._local_num_attention_heads, self._num_code_groups, self._local_head_dim),
706
- # np.float32,
707
- # self._device,
708
- # cuda_device_id,
709
- # )
710
 
711
  # Pre-allocate output OrtValue for the unified Local Talker backbone step
712
  self._local_step_hidden_ov = _make_device_ortvalue(
@@ -775,9 +739,6 @@ class Qwen3TTSInferencerONNX:
775
  self._codec_codes_ov = _make_device_ortvalue(
776
  (1, self._num_code_groups, self.chunk_frames), np.int64, self._device, cuda_device_id
777
  )
778
- # self._codec_cache_position_ov = _make_device_ortvalue(
779
- # (self.chunk_frames), np.int64, self._device, cuda_device_id
780
- # )
781
  self._codec_attention_mask_ov = _make_device_ortvalue(
782
  (1, 1, self.chunk_frames, self._speech_tokenizer_sliding_window), np.float32, self._device, cuda_device_id
783
  )
@@ -819,7 +780,7 @@ class Qwen3TTSInferencerONNX:
819
  self._last_hidden_states_np: Optional[np.ndarray] = None
820
  self._step_idx = 0
821
  self._talker_seq_len = 0
822
- self._generated_wav_list = None
823
 
824
  # ── Streaming text state ──────────────────────────────────────────────
825
  self._text_cache = ""
@@ -878,7 +839,6 @@ class Qwen3TTSInferencerONNX:
878
  b.bind_input_device("codes", self._codec_codes_ov)
879
  b.bind_input_device("hidden_state_cache", self._codec_hidden_cache_ov)
880
  b.bind_input_device("pre_conv_hidden_state_cache", self._codec_pre_conv_cache_ov)
881
- # b.bind_input_device("cache_position", self._codec_cache_position_ov)
882
  b.bind_input_device("attention_mask", self._codec_attention_mask_ov)
883
  b.bind_input_device("cos_rope", self._codec_cos_rope_ov)
884
  b.bind_input_device("sin_rope", self._codec_sin_rope_ov)
@@ -929,9 +889,6 @@ class Qwen3TTSInferencerONNX:
929
  b.bind_input_device("attention_mask", self._local_prefill_attention_mask_ov)
930
  b.bind_input_device("cos_rope", self._local_prefill_cos_rope_ov)
931
  b.bind_input_device("sin_rope", self._local_prefill_sin_rope_ov)
932
- # b.bind_input_device("cache_write_mask", self._local_prefill_cache_write_mask_ov)
933
- # b.bind_input_device("key_gqa_expanded", self._local_prefill_key_gqa_expanded_ov)
934
- # b.bind_input_device("value_gqa_expanded", self._local_prefill_value_gqa_expanded_ov)
935
  for i in range(self._local_num_hidden_layers):
936
  b.bind_input_device(f"past_key_{i}", self._kv_local_ov[2 * i])
937
  b.bind_input_device(f"past_value_{i}", self._kv_local_ov[2 * i + 1])
@@ -977,9 +934,6 @@ class Qwen3TTSInferencerONNX:
977
  b.bind_input_device("attention_mask", self._local_step_attention_mask_ov)
978
  b.bind_input_device("cos_rope", self._local_step_cos_rope_ov)
979
  b.bind_input_device("sin_rope", self._local_step_sin_rope_ov)
980
- # b.bind_input_device("cache_write_mask", self._local_step_cache_write_mask_ov)
981
- # b.bind_input_device("key_gqa_expanded", self._local_step_key_gqa_expanded_ov)
982
- # b.bind_input_device("value_gqa_expanded", self._local_step_value_gqa_expanded_ov)
983
  for i in range(self._local_num_hidden_layers):
984
  b.bind_input_device(f"past_key_{i}", self._kv_local_ov[2 * i])
985
  b.bind_input_device(f"past_value_{i}", self._kv_local_ov[2 * i + 1])
@@ -1227,34 +1181,6 @@ class Qwen3TTSInferencerONNX:
1227
 
1228
  return cos.astype(np.float32), sin.astype(np.float32)
1229
 
1230
- # ── Other helper functions ────────────────────────────────────────────────
1231
-
1232
- # def _make_local_cache_write_mask(self, batch_size=1):
1233
- # t = np.arange(self._num_code_groups, dtype=np.int64).reshape(batch_size, 1, 1, self._num_code_groups)
1234
-
1235
- # cache_position = np.asarray([0, 1], dtype=np.int64).reshape(batch_size, 1, -1, 1)
1236
- # mask = (t == cache_position).any(axis=2) # shape [1, 1, max_seq_len]
1237
- # mask = mask[..., None] # shape [1, 1, max_seq_len, 1]
1238
- # mask_list = [
1239
- # np.broadcast_to(
1240
- # mask, (batch_size, self._local_num_key_value_heads, self._num_code_groups, self._local_head_dim)
1241
- # )
1242
- # ]
1243
-
1244
- # # cache write mask for each codebook from 1 to 15 (0 is generated by talker)
1245
- # for pos in range(2, self._num_code_groups):
1246
- # cache_position = np.asarray([pos], dtype=np.int64).reshape(1, 1, -1, 1)
1247
- # mask = (t == cache_position).any(axis=2) # shape [1, 1, max_seq_len]
1248
- # mask = mask[..., None] # shape [1, 1, max_seq_len, 1]
1249
- # mask_list.append(
1250
- # np.broadcast_to(
1251
- # mask, (batch_size, self._local_num_key_value_heads, self._num_code_groups, self._local_head_dim)
1252
- # )
1253
- # )
1254
- # # this is used for torch.where inside attention function to update KV-cache buffer in-place
1255
-
1256
- # return mask_list
1257
-
1258
  # ── CPU sampling ──────────────────────────────────────────────────────────
1259
 
1260
  def _sample(
@@ -1296,7 +1222,7 @@ class Qwen3TTSInferencerONNX:
1296
  wav = wav.mean(axis=1)
1297
  return wav.astype(np.float32), int(sr)
1298
 
1299
- def create_voice_clone_spkemb(self, ref_audio: AudioLike) -> NDArrayFloat:
1300
  wav, sr = self._normalize_audio_inputs(ref_audio)
1301
  if sr != self._speaker_encoder_sample_rate:
1302
  wav = librosa.resample(
@@ -1304,6 +1230,10 @@ class Qwen3TTSInferencerONNX:
1304
  orig_sr=int(sr),
1305
  target_sr=self._speaker_encoder_sample_rate,
1306
  )
 
 
 
 
1307
  mels = mel_spectrogram_numpy(
1308
  wav,
1309
  n_fft=1024,
@@ -1321,9 +1251,6 @@ class Qwen3TTSInferencerONNX:
1321
  def _build_assistant_text(self) -> str:
1322
  return "<|im_start|>assistant\n"
1323
 
1324
- def _build_ending_assistant_text(self) -> str:
1325
- return "<|im_end|>\n"
1326
-
1327
  def _prefill_embeds(self, audio_info: AudioLike, language: str) -> np.ndarray:
1328
  """Build the 9-token prefill embedding for the Talker.
1329
 
@@ -1409,28 +1336,35 @@ class Qwen3TTSInferencerONNX:
1409
 
1410
  # ── Talker prefill (IOBinding + CUDA graph) ─────────────────────────────
1411
 
1412
- def prefill(self) -> None:
1413
  """Run the 9-token prefill pass (IOBinding + CUDA graph) and snapshot KV."""
1414
- if self._step_idx == 0:
1415
  audio_info = self._audio_ref_path
1416
  else:
1417
- log.info(f"prefill cont generated_wav_list {self._generated_wav_list} {len(self._generated_wav_list)}")
1418
- wav_history = np.concatenate(self._generated_wav_list, axis=-1)[0, 0]
 
 
 
 
 
1419
  cut_idx = 0
1420
  while len(wav_history) > _REF_WAV_LENGTH_CUT_LIMIT_FOR_START_OF_MULTI_TURN:
1421
- wav_history = wav_history[self._generated_wav_list[cut_idx].shape[-1] :]
1422
  cut_idx += 1
1423
- log.info(f"prefill cont wav_history {wav_history} {wav_history.shape} {wav_history.dtype}")
1424
- self._generated_wav_list = self._generated_wav_list[cut_idx:]
1425
  log.info(
1426
- f"prefill cont generated_wav_list_after cut_idx {cut_idx} {self._generated_wav_list} {len(self._generated_wav_list)}"
 
 
 
 
 
 
1427
  )
1428
  audio_info = (
1429
  wav_history,
1430
  24000,
1431
  )
1432
- # use previously generated audio sequence
1433
- # from previous list of texts with the window limit 50 as reference
1434
  inputs_embeds = self._prefill_embeds(audio_info, self._language)
1435
  if self._step_idx == 0:
1436
  cache_position = np.arange(_TALKER_PREFILL_LEN, dtype=np.int64)
@@ -1460,69 +1394,7 @@ class Qwen3TTSInferencerONNX:
1460
  [codec_eos],[None],[codec_pad] -> continue with codec_bos and generation
1461
  """
1462
 
1463
- # text_ids = np.array([[self._tts_pad_token_id, self._tts_bos_token_id]], dtype=np.int64)
1464
- # text_ids = np.array([[self._tts_pad_token_id]], dtype=np.int64)
1465
- # log.info(f"prefill_cont text_ids {text_ids} {text_ids.shape}")
1466
- # outputs = self._text_embed_proj.run(["text_emb_out"], {"text_ids": text_ids}) # 3
1467
- # # embeds = outputs[0]
1468
- # # tts_pad_embed, tts_bos_embed = embeds[:, :1], embeds[:, 1:] # 2 * [1 1 d]
1469
- # tts_pad_embed = outputs[0]
1470
- # # log.info(f"prefill_cont tts_bos_embed {tts_bos_embed} {tts_bos_embed.shape} {tts_bos_embed.dtype}")
1471
- # log.info(f"prefill_cont tts_pad_embed {tts_pad_embed} {tts_pad_embed.shape} {tts_pad_embed.dtype}")
1472
-
1473
- # # codec_ids = np.array([[self._codec_eos_token_id, self._codec_pad_id]], dtype=np.int64)
1474
- # codec_ids = np.array([[self._codec_eos_token_id]], dtype=np.int64)
1475
- # log.info(f"prefill_cont codec_ids {codec_ids} {codec_ids.shape}")
1476
- # outputs = self._talker_codec_embed.run(["codec_emb"], {"codec_ids": codec_ids})
1477
- # # embeds = outputs[0]
1478
- # # codec_eos_embed, codec_pad_embed = embeds[:, :1], embeds[:, 1:] # 2 * [1 1 d]
1479
- # codec_eos_embed = outputs[0]
1480
- # log.info(f"prefill_cont codec_eos_embed {codec_eos_embed} {codec_eos_embed.shape} {codec_eos_embed.dtype}")
1481
- # # log.info(f"prefill_cont codec_pad_embed {codec_pad_embed} {codec_pad_embed.shape} {codec_pad_embed.dtype}")
1482
-
1483
- # tts_pad_codec_eos_embed = tts_pad_embed + codec_eos_embed
1484
- # # tts_bos_codec_pad_embed = tts_bos_embed + codec_pad_embed
1485
-
1486
- # self._tokenize_texts([self._build_ending_assistant_text(), self._build_assistant_text()]),
1487
- # self._tokenize_texts([self._build_assistant_text()]),
1488
- # prefix_tokens = np.expand_dims(
1489
- # np.array(
1490
- # self._tokenize_texts([self._build_ending_assistant_text()]),
1491
- # dtype=np.int64,
1492
- # ),
1493
- # axis=0,
1494
- # )
1495
- # log.info(f"prefill_cont prefix_tokens {prefix_tokens} {prefix_tokens.shape}")
1496
- # outputs = self._text_embed_proj.run(["text_emb_out"], {"text_ids": prefix_tokens}) # 3
1497
- # _talker_input_embed_role = outputs[0]
1498
- # log.info(
1499
- # f"prefill_cont _talker_input_embed_role {_talker_input_embed_role} {_talker_input_embed_role.shape} {_talker_input_embed_role.dtype}"
1500
- # )
1501
-
1502
- # talker_input_embed = _talker_input_embed_role
1503
- # talker_input_embed = np.concatenate(
1504
- # (_talker_input_embed_role, tts_bos_codec_pad_embed), axis=1
1505
- # ) # 6
1506
- # (tts_pad_codec_eos_embed, _talker_input_embed_role, tts_bos_codec_pad_embed), axis=1
1507
- # talker_input_embed = np.concatenate((tts_pad_codec_eos_embed, tts_bos_codec_pad_embed), axis=1) # 2
1508
- # talker_input_embed = np.concatenate((tts_pad_codec_eos_embed, _talker_input_embed_role), axis=1) # 3
1509
- # log.info(
1510
- # f"prefill_cont talker_input_embed {talker_input_embed} {talker_input_embed.shape} {talker_input_embed.dtype}"
1511
- # )
1512
-
1513
- # log.info(f"prefill_cont talker_seq_len {self._talker_seq_len} step_idx {self._step_idx}")
1514
- # for i in range(talker_input_embed.shape[1]):
1515
- # inputs_embeds = talker_input_embed[:, i : i + 1]
1516
- # cache_position = np.array([self._talker_seq_len], dtype=np.int64)
1517
- # causal_rows = self._causal_tril[:, :, cache_position, :] # [1,1,q_len,MAX]
1518
- # attn_mask = np.where(causal_rows, 0.0, -np.inf).astype(np.float32) # [1, 1, q_len, MAX_SEQ_LEN]
1519
- # cos_rope = self._cos_rope[:, :, cache_position].copy()
1520
- # sin_rope = self._sin_rope[:, :, cache_position].copy()
1521
- # self._run_talker_step(inputs_embeds, cache_position, attn_mask, cos_rope, sin_rope)
1522
- # self._talker_seq_len += 1
1523
- # self._step_idx += 1
1524
-
1525
- self.prefill()
1526
  # Snapshot the post-prefill KV state for turn resets
1527
  self._kv_talker_prefill_np = self._snapshot_talker_kv()
1528
  self._prefilled = True
@@ -1599,7 +1471,6 @@ class Qwen3TTSInferencerONNX:
1599
  _copy_numpy_to_ortvalue(attention_mask, self._local_prefill_attention_mask_ov)
1600
  _copy_numpy_to_ortvalue(cos_rope, self._local_prefill_cos_rope_ov)
1601
  _copy_numpy_to_ortvalue(sin_rope, self._local_prefill_sin_rope_ov)
1602
- # _copy_numpy_to_ortvalue(cache_write_mask, self._local_prefill_cache_write_mask_ov)
1603
  b.run()
1604
  return self._local_prefill_hidden_ov.numpy() # [1, cp_hidden_size]
1605
 
@@ -1625,7 +1496,6 @@ class Qwen3TTSInferencerONNX:
1625
  _copy_numpy_to_ortvalue(attention_mask, self._local_step_attention_mask_ov)
1626
  _copy_numpy_to_ortvalue(cos_rope, self._local_step_cos_rope_ov)
1627
  _copy_numpy_to_ortvalue(sin_rope, self._local_step_sin_rope_ov)
1628
- # _copy_numpy_to_ortvalue(cache_write_mask, self._local_step_cache_write_mask_ov)
1629
  b.run()
1630
  return self._local_step_hidden_ov.numpy() # [1, cp_hidden_size]
1631
 
@@ -1654,12 +1524,10 @@ class Qwen3TTSInferencerONNX:
1654
  cos_rope: np.ndarray, # [1, chunk_length, 64]
1655
  sin_rope: np.ndarray, # [1, chunk_length, 64]
1656
  ) -> np.ndarray:
1657
- # pos: np.ndarray, # [chunk_length]
1658
  """Execute one Codec Decoder step via IOBinding and return wav as NumPy."""
1659
  b = self._codec_decoder_bound
1660
  # Copy numpy inputs to buffer in-place
1661
  _copy_numpy_to_ortvalue(chunk_tokens, self._codec_codes_ov)
1662
- # _copy_numpy_to_ortvalue(pos, self._codec_cache_position_ov)
1663
  _copy_numpy_to_ortvalue(attention_mask, self._codec_attention_mask_ov)
1664
  _copy_numpy_to_ortvalue(cos_rope, self._codec_cos_rope_ov)
1665
  _copy_numpy_to_ortvalue(sin_rope, self._codec_sin_rope_ov)
@@ -1703,14 +1571,9 @@ class Qwen3TTSInferencerONNX:
1703
  cos_rope = self._cos_rope[:, :, cache_position].copy()
1704
  sin_rope = self._sin_rope[:, :, cache_position].copy()
1705
  logits = self._run_talker_step(inputs_embeds, cache_position, attn_mask, cos_rope, sin_rope) # [1, vocab]
1706
- # log.info(f"step-idx-{self._step_idx} logits {logits} {logits.shape} {logits.dtype}")
1707
 
1708
  # Retrieve hidden states for Local Talker prefill
1709
  self._last_hidden_states_np = self._talker_hidden_ov.numpy() # [1, 1, H]
1710
- # log.info(
1711
- # f"step-idx-{self._step_idx} self._last_hidden_states_np {self._last_hidden_states_np} "
1712
- # f"{self._last_hidden_states_np.shape} {self._last_hidden_states_np.dtype}"
1713
- # )
1714
 
1715
  # ── Sample first token (CPU) ──────────────────────────────────────────
1716
  history = self._generated_tokens[:, :, 0]
@@ -1768,7 +1631,6 @@ class Qwen3TTSInferencerONNX:
1768
  attn_mask_prefill = np.where(causal_rows, 0.0, -np.inf).astype(np.float32) # [1, 1, q_len, MAX_SEQ_LEN]
1769
  cos_rope_prefill = self._cos_rope_local[:, cache_position_prefill].copy()
1770
  sin_rope_prefill = self._sin_rope_local[:, cache_position_prefill].copy()
1771
- # cache_write_mask_prefill = self._local_cache_write_mask_list[0]
1772
 
1773
  # Backbone prefill β†’ last_hidden [1, cp_hidden_size]
1774
  hidden = self._run_local_prefill(
@@ -1778,7 +1640,6 @@ class Qwen3TTSInferencerONNX:
1778
  cos_rope_prefill,
1779
  sin_rope_prefill,
1780
  )
1781
- # cache_write_mask_prefill,
1782
  # Batched lm_head β†’ [15, local_vocab_size]; select group 0 (codebook group 1)
1783
  all_logits = self._run_local_lm_head(hidden) # [15, local_vocab_size]
1784
  logits_g1 = all_logits[0:1, :] # [1, local_vocab_size]
@@ -1806,7 +1667,6 @@ class Qwen3TTSInferencerONNX:
1806
  attn_mask_step = np.where(causal_rows, 0.0, -np.inf).astype(np.float32) # [1, 1, q_len, MAX_SEQ_LEN]
1807
  cos_rope_step = self._cos_rope_local[:, cache_position_step].copy()
1808
  sin_rope_step = self._sin_rope_local[:, cache_position_step].copy()
1809
- # cache_write_mask_step = self._local_cache_write_mask_list[step_i - 1]
1810
 
1811
  # Unified backbone step β†’ last_hidden [1, cp_hidden_size]
1812
  hidden = self._run_local_step(
@@ -1816,7 +1676,6 @@ class Qwen3TTSInferencerONNX:
1816
  cos_rope_step,
1817
  sin_rope_step,
1818
  )
1819
- # cache_write_mask_step,
1820
  # Batched lm_head β†’ [15, local_vocab_size]; select the current group
1821
  all_logits = self._run_local_lm_head(hidden)
1822
  logits_gi = all_logits[head_idx : head_idx + 1, :] # [1, local_vocab_size]
@@ -1948,11 +1807,9 @@ class Qwen3TTSInferencerONNX:
1948
  attn_mask_ls = np.where(causal_rows, 0.0, -np.inf).astype(np.float32) # [1, 1, q_len, MAX_SEQ_LEN]
1949
  cos_rope_ls = self._cos_rope_local[:, cache_pos_ls].copy()
1950
  sin_rope_ls = self._sin_rope_local[:, cache_pos_ls].copy()
1951
- # cache_write_mask_ls = self._local_cache_write_mask_list[1]
1952
  for _ in range(n_iter):
1953
  t0 = time.perf_counter()
1954
  self._run_local_step(dummy_ls_embeds, cache_pos_ls, attn_mask_ls, cos_rope_ls, sin_rope_ls)
1955
- # cache_write_mask_ls
1956
  times["local_step"].append((time.perf_counter() - t0) * 1000)
1957
  exec_time_tot = sum(times["local_step"]) / 1000
1958
  avg_exec_time = exec_time_tot / len(times["local_step"]) * 1000
@@ -2081,7 +1938,8 @@ class Qwen3TTSInferencerONNX:
2081
  while self._pending_tokens and not self.is_finished:
2082
  token = self._pending_tokens.pop(0)
2083
  log.info(
2084
- f"drain_pending_tokens token {token} -> pending_tokens {self._pending_tokens} {len(self._pending_tokens)}"
 
2085
  )
2086
  output = self.step(token)
2087
  if output is not None:
@@ -2094,7 +1952,8 @@ class Qwen3TTSInferencerONNX:
2094
  for segment in self._extract_text_segments(force=False):
2095
  tokenized_segment = self._tokenize_texts([segment])
2096
  log.info(
2097
- f"push_text segment {segment} {len(segment)} -> tokenized_segment {tokenized_segment} {len(tokenized_segment)}"
 
2098
  )
2099
  self._pending_tokens.extend(tokenized_segment)
2100
  log.info(f"push_text pending_tokens {self._pending_tokens}")
@@ -2206,10 +2065,10 @@ class Qwen3TTSInferencerONNX:
2206
  )
2207
 
2208
  self._codec_step_idx += self.chunk_frames
2209
- if self._generated_wav_list is not None:
2210
- self._generated_wav_list.append(wav.copy())
2211
  else:
2212
- self._generated_wav_list = [wav.copy()]
2213
  return wav
2214
 
2215
  def _overlap_samples(self, wav: NDArrayFloat) -> int:
 
119
 
120
  # For continuous streaming with multi turn texts
121
  _LENGTH_RESET_LIMIT_FOR_START_OF_MULTI_TURN = 50
122
+ _REF_WAV_LENGTH_CUT_LIMIT_FOR_START_OF_MULTI_TURN = 192000
123
 
124
 
125
  # ── CPU sampling helpers ──────────────────────────────────────────────────────
 
650
  self._local_prefill_sin_rope_ov = _make_device_ortvalue(
651
  (1, 2, self._head_dim), np.float32, self._device, cuda_device_id
652
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
653
 
654
  # Pre-allocate output OrtValues for Local Talker prefill
655
  # Backbone outputs last_hidden [1, cp_hidden_size]; logits come from lm_head
 
671
  self._local_step_sin_rope_ov = _make_device_ortvalue(
672
  (1, 1, self._head_dim), np.float32, self._device, cuda_device_id
673
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
674
 
675
  # Pre-allocate output OrtValue for the unified Local Talker backbone step
676
  self._local_step_hidden_ov = _make_device_ortvalue(
 
739
  self._codec_codes_ov = _make_device_ortvalue(
740
  (1, self._num_code_groups, self.chunk_frames), np.int64, self._device, cuda_device_id
741
  )
 
 
 
742
  self._codec_attention_mask_ov = _make_device_ortvalue(
743
  (1, 1, self.chunk_frames, self._speech_tokenizer_sliding_window), np.float32, self._device, cuda_device_id
744
  )
 
780
  self._last_hidden_states_np: Optional[np.ndarray] = None
781
  self._step_idx = 0
782
  self._talker_seq_len = 0
783
+ self._ref_wav_history_list = None
784
 
785
  # ── Streaming text state ──────────────────────────────────────────────
786
  self._text_cache = ""
 
839
  b.bind_input_device("codes", self._codec_codes_ov)
840
  b.bind_input_device("hidden_state_cache", self._codec_hidden_cache_ov)
841
  b.bind_input_device("pre_conv_hidden_state_cache", self._codec_pre_conv_cache_ov)
 
842
  b.bind_input_device("attention_mask", self._codec_attention_mask_ov)
843
  b.bind_input_device("cos_rope", self._codec_cos_rope_ov)
844
  b.bind_input_device("sin_rope", self._codec_sin_rope_ov)
 
889
  b.bind_input_device("attention_mask", self._local_prefill_attention_mask_ov)
890
  b.bind_input_device("cos_rope", self._local_prefill_cos_rope_ov)
891
  b.bind_input_device("sin_rope", self._local_prefill_sin_rope_ov)
 
 
 
892
  for i in range(self._local_num_hidden_layers):
893
  b.bind_input_device(f"past_key_{i}", self._kv_local_ov[2 * i])
894
  b.bind_input_device(f"past_value_{i}", self._kv_local_ov[2 * i + 1])
 
934
  b.bind_input_device("attention_mask", self._local_step_attention_mask_ov)
935
  b.bind_input_device("cos_rope", self._local_step_cos_rope_ov)
936
  b.bind_input_device("sin_rope", self._local_step_sin_rope_ov)
 
 
 
937
  for i in range(self._local_num_hidden_layers):
938
  b.bind_input_device(f"past_key_{i}", self._kv_local_ov[2 * i])
939
  b.bind_input_device(f"past_value_{i}", self._kv_local_ov[2 * i + 1])
 
1181
 
1182
  return cos.astype(np.float32), sin.astype(np.float32)
1183
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1184
  # ── CPU sampling ──────────────────────────────────────────────────────────
1185
 
1186
  def _sample(
 
1222
  wav = wav.mean(axis=1)
1223
  return wav.astype(np.float32), int(sr)
1224
 
1225
+ def create_voice_clone_spkemb(self, ref_audio: AudioLike, init_cont_stream: bool = False) -> NDArrayFloat:
1226
  wav, sr = self._normalize_audio_inputs(ref_audio)
1227
  if sr != self._speaker_encoder_sample_rate:
1228
  wav = librosa.resample(
 
1230
  orig_sr=int(sr),
1231
  target_sr=self._speaker_encoder_sample_rate,
1232
  )
1233
+ if self._ref_wav_history_list is None or len(self._ref_wav_history_list) == 0:
1234
+ # split ref wavs into chunks of 0.32s audio for ref wav history ring list buffer
1235
+ # for the continuous streaming
1236
+ self._ref_wav_history_list = [wav[None, None, i : i + 7680] for i in range(0, len(wav), 7680)]
1237
  mels = mel_spectrogram_numpy(
1238
  wav,
1239
  n_fft=1024,
 
1251
  def _build_assistant_text(self) -> str:
1252
  return "<|im_start|>assistant\n"
1253
 
 
 
 
1254
  def _prefill_embeds(self, audio_info: AudioLike, language: str) -> np.ndarray:
1255
  """Build the 9-token prefill embedding for the Talker.
1256
 
 
1336
 
1337
  # ── Talker prefill (IOBinding + CUDA graph) ─────────────────────────────
1338
 
1339
+ def prefill(self, cont_stream: bool = False) -> None:
1340
  """Run the 9-token prefill pass (IOBinding + CUDA graph) and snapshot KV."""
1341
+ if not cont_stream:
1342
  audio_info = self._audio_ref_path
1343
  else:
1344
+ # use previously generated audio sequence
1345
+ # from previous list of texts with the window limit 50 as reference
1346
+ log.info(
1347
+ f"prefill cont ref_wav_history_list {self._ref_wav_history_list} {len(self._ref_wav_history_list)}"
1348
+ )
1349
+ wav_history = np.concatenate(self._ref_wav_history_list, axis=-1)[0, 0]
1350
+ log.info(f"prefill cont wav_history {wav_history} {wav_history.shape} {wav_history.dtype}")
1351
  cut_idx = 0
1352
  while len(wav_history) > _REF_WAV_LENGTH_CUT_LIMIT_FOR_START_OF_MULTI_TURN:
1353
+ wav_history = wav_history[self._ref_wav_history_list[cut_idx].shape[-1] :]
1354
  cut_idx += 1
 
 
1355
  log.info(
1356
+ f"prefill cont wav_history_after cut_idx {cut_idx} "
1357
+ f"{wav_history} {wav_history.shape} {wav_history.dtype}"
1358
+ )
1359
+ self._ref_wav_history_list = self._ref_wav_history_list[cut_idx:]
1360
+ log.info(
1361
+ f"prefill cont ref_wav_history_list_after cut_idx {cut_idx} "
1362
+ f"{self._ref_wav_history_list} {len(self._ref_wav_history_list)}"
1363
  )
1364
  audio_info = (
1365
  wav_history,
1366
  24000,
1367
  )
 
 
1368
  inputs_embeds = self._prefill_embeds(audio_info, self._language)
1369
  if self._step_idx == 0:
1370
  cache_position = np.arange(_TALKER_PREFILL_LEN, dtype=np.int64)
 
1394
  [codec_eos],[None],[codec_pad] -> continue with codec_bos and generation
1395
  """
1396
 
1397
+ self.prefill(cont_stream=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1398
  # Snapshot the post-prefill KV state for turn resets
1399
  self._kv_talker_prefill_np = self._snapshot_talker_kv()
1400
  self._prefilled = True
 
1471
  _copy_numpy_to_ortvalue(attention_mask, self._local_prefill_attention_mask_ov)
1472
  _copy_numpy_to_ortvalue(cos_rope, self._local_prefill_cos_rope_ov)
1473
  _copy_numpy_to_ortvalue(sin_rope, self._local_prefill_sin_rope_ov)
 
1474
  b.run()
1475
  return self._local_prefill_hidden_ov.numpy() # [1, cp_hidden_size]
1476
 
 
1496
  _copy_numpy_to_ortvalue(attention_mask, self._local_step_attention_mask_ov)
1497
  _copy_numpy_to_ortvalue(cos_rope, self._local_step_cos_rope_ov)
1498
  _copy_numpy_to_ortvalue(sin_rope, self._local_step_sin_rope_ov)
 
1499
  b.run()
1500
  return self._local_step_hidden_ov.numpy() # [1, cp_hidden_size]
1501
 
 
1524
  cos_rope: np.ndarray, # [1, chunk_length, 64]
1525
  sin_rope: np.ndarray, # [1, chunk_length, 64]
1526
  ) -> np.ndarray:
 
1527
  """Execute one Codec Decoder step via IOBinding and return wav as NumPy."""
1528
  b = self._codec_decoder_bound
1529
  # Copy numpy inputs to buffer in-place
1530
  _copy_numpy_to_ortvalue(chunk_tokens, self._codec_codes_ov)
 
1531
  _copy_numpy_to_ortvalue(attention_mask, self._codec_attention_mask_ov)
1532
  _copy_numpy_to_ortvalue(cos_rope, self._codec_cos_rope_ov)
1533
  _copy_numpy_to_ortvalue(sin_rope, self._codec_sin_rope_ov)
 
1571
  cos_rope = self._cos_rope[:, :, cache_position].copy()
1572
  sin_rope = self._sin_rope[:, :, cache_position].copy()
1573
  logits = self._run_talker_step(inputs_embeds, cache_position, attn_mask, cos_rope, sin_rope) # [1, vocab]
 
1574
 
1575
  # Retrieve hidden states for Local Talker prefill
1576
  self._last_hidden_states_np = self._talker_hidden_ov.numpy() # [1, 1, H]
 
 
 
 
1577
 
1578
  # ── Sample first token (CPU) ──────────────────────────────────────────
1579
  history = self._generated_tokens[:, :, 0]
 
1631
  attn_mask_prefill = np.where(causal_rows, 0.0, -np.inf).astype(np.float32) # [1, 1, q_len, MAX_SEQ_LEN]
1632
  cos_rope_prefill = self._cos_rope_local[:, cache_position_prefill].copy()
1633
  sin_rope_prefill = self._sin_rope_local[:, cache_position_prefill].copy()
 
1634
 
1635
  # Backbone prefill β†’ last_hidden [1, cp_hidden_size]
1636
  hidden = self._run_local_prefill(
 
1640
  cos_rope_prefill,
1641
  sin_rope_prefill,
1642
  )
 
1643
  # Batched lm_head β†’ [15, local_vocab_size]; select group 0 (codebook group 1)
1644
  all_logits = self._run_local_lm_head(hidden) # [15, local_vocab_size]
1645
  logits_g1 = all_logits[0:1, :] # [1, local_vocab_size]
 
1667
  attn_mask_step = np.where(causal_rows, 0.0, -np.inf).astype(np.float32) # [1, 1, q_len, MAX_SEQ_LEN]
1668
  cos_rope_step = self._cos_rope_local[:, cache_position_step].copy()
1669
  sin_rope_step = self._sin_rope_local[:, cache_position_step].copy()
 
1670
 
1671
  # Unified backbone step β†’ last_hidden [1, cp_hidden_size]
1672
  hidden = self._run_local_step(
 
1676
  cos_rope_step,
1677
  sin_rope_step,
1678
  )
 
1679
  # Batched lm_head β†’ [15, local_vocab_size]; select the current group
1680
  all_logits = self._run_local_lm_head(hidden)
1681
  logits_gi = all_logits[head_idx : head_idx + 1, :] # [1, local_vocab_size]
 
1807
  attn_mask_ls = np.where(causal_rows, 0.0, -np.inf).astype(np.float32) # [1, 1, q_len, MAX_SEQ_LEN]
1808
  cos_rope_ls = self._cos_rope_local[:, cache_pos_ls].copy()
1809
  sin_rope_ls = self._sin_rope_local[:, cache_pos_ls].copy()
 
1810
  for _ in range(n_iter):
1811
  t0 = time.perf_counter()
1812
  self._run_local_step(dummy_ls_embeds, cache_pos_ls, attn_mask_ls, cos_rope_ls, sin_rope_ls)
 
1813
  times["local_step"].append((time.perf_counter() - t0) * 1000)
1814
  exec_time_tot = sum(times["local_step"]) / 1000
1815
  avg_exec_time = exec_time_tot / len(times["local_step"]) * 1000
 
1938
  while self._pending_tokens and not self.is_finished:
1939
  token = self._pending_tokens.pop(0)
1940
  log.info(
1941
+ f"drain_pending_tokens token {token} -> "
1942
+ f"pending_tokens {self._pending_tokens} {len(self._pending_tokens)}"
1943
  )
1944
  output = self.step(token)
1945
  if output is not None:
 
1952
  for segment in self._extract_text_segments(force=False):
1953
  tokenized_segment = self._tokenize_texts([segment])
1954
  log.info(
1955
+ f"push_text segment {segment} {len(segment)} -> "
1956
+ f"tokenized_segment {tokenized_segment} {len(tokenized_segment)}"
1957
  )
1958
  self._pending_tokens.extend(tokenized_segment)
1959
  log.info(f"push_text pending_tokens {self._pending_tokens}")
 
2065
  )
2066
 
2067
  self._codec_step_idx += self.chunk_frames
2068
+ if self._ref_wav_history_list is not None:
2069
+ self._ref_wav_history_list.append(wav.copy())
2070
  else:
2071
+ self._ref_wav_history_list = [wav.copy()]
2072
  return wav
2073
 
2074
  def _overlap_samples(self, wav: NDArrayFloat) -> int:
test_qwen3-tts-streaming_onnx.py CHANGED
@@ -131,11 +131,11 @@ _TEXT = [
131
  ]
132
  _OUTPUT_SAMPLE_RATE = 24000
133
  _CHUNK_FRAMES = 4
134
- _TEMPERATURE = 0.85
135
- _TOP_P = 0.8
136
  _TOP_K = 50
137
- _REPETITION_PENALTY = 1.9
138
- _REPETITION_WINDOW = 50
139
  _WARMUP_ITERS = 50
140
 
141
  # ---------------------------------------------------------------------------
 
131
  ]
132
  _OUTPUT_SAMPLE_RATE = 24000
133
  _CHUNK_FRAMES = 4
134
+ _TEMPERATURE = 0.75
135
+ _TOP_P = 0.85
136
  _TOP_K = 50
137
+ _REPETITION_PENALTY = 9.5
138
+ _REPETITION_WINDOW = 75
139
  _WARMUP_ITERS = 50
140
 
141
  # ---------------------------------------------------------------------------