feat: revised the continuous streaming to work
Browse files- After several testings, the best way is to
append another prefill_embed to the last KV-cache,
while still continuing the length position index.
- This needs to be reset if the start of the next
round of text is equal or greater than position
50 (4 s.). It is the optimum length to not make
the model confused as we append multiple prefill
sequences in between the chunks.
- The next thing is the speaker embedding. Here,
it is revised to use the waveform sequence of the
previous round to make the prosody consistent. As
from our observation, the speaker embedding affects
not only the identity, but also other acoustics,
as well as prosody.
- With factorization of speaker embedding, the previous
point will be better in the performance. However,
for now, this method will work to allow fully
streamable text-to-speech, where you have multiple
rounds of texts.
|
@@ -117,6 +117,10 @@ _LOCAL_MAX_SEQ_LEN = 16
|
|
| 117 |
_NUM_CODE_GROUPS = 16
|
| 118 |
|
| 119 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 120 |
# ── CPU sampling helpers ──────────────────────────────────────────────────────
|
| 121 |
|
| 122 |
|
|
@@ -208,7 +212,7 @@ def _make_session(
|
|
| 208 |
opts.execution_mode = ort.ExecutionMode.ORT_SEQUENTIAL
|
| 209 |
opts.enable_cpu_mem_arena = True
|
| 210 |
opts.enable_mem_pattern = True
|
| 211 |
-
opts.log_severity_level =
|
| 212 |
|
| 213 |
if use_cuda:
|
| 214 |
cuda_opts = {
|
|
@@ -814,6 +818,7 @@ class Qwen3TTSInferencerONNX:
|
|
| 814 |
self._last_hidden_states_np: Optional[np.ndarray] = None
|
| 815 |
self._step_idx = 0
|
| 816 |
self._talker_seq_len = 0
|
|
|
|
| 817 |
|
| 818 |
# ── Streaming text state ──────────────────────────────────────────────
|
| 819 |
self._text_cache = ""
|
|
@@ -821,6 +826,7 @@ class Qwen3TTSInferencerONNX:
|
|
| 821 |
self._prefilled = False
|
| 822 |
self._text_ended = False
|
| 823 |
self._turn_idx = 0
|
|
|
|
| 824 |
|
| 825 |
# ── Audio buffer ──────────────────────────────────────────────────────
|
| 826 |
self._prev_tail: Optional[np.ndarray] = None
|
|
@@ -1314,7 +1320,10 @@ class Qwen3TTSInferencerONNX:
|
|
| 1314 |
def _build_assistant_text(self) -> str:
|
| 1315 |
return "<|im_start|>assistant\n"
|
| 1316 |
|
| 1317 |
-
def
|
|
|
|
|
|
|
|
|
|
| 1318 |
"""Build the 9-token prefill embedding for the Talker.
|
| 1319 |
|
| 1320 |
Layout (matches modeling_qwen3_tts.py generate_icl_prompt):
|
|
@@ -1330,7 +1339,7 @@ class Qwen3TTSInferencerONNX:
|
|
| 1330 |
else:
|
| 1331 |
language_id = self._codec_language_id[language.lower()]
|
| 1332 |
log.info(f"_prefill_embeds language_id {language_id}")
|
| 1333 |
-
speaker_embed = self.create_voice_clone_spkemb(
|
| 1334 |
log.info(f"_prefill_embeds speaker_embed {speaker_embed} {speaker_embed.shape}")
|
| 1335 |
|
| 1336 |
codec_prefill_list = np.array(
|
|
@@ -1401,18 +1410,115 @@ class Qwen3TTSInferencerONNX:
|
|
| 1401 |
|
| 1402 |
def prefill(self) -> None:
|
| 1403 |
"""Run the 9-token prefill pass (IOBinding + CUDA graph) and snapshot KV."""
|
| 1404 |
-
|
| 1405 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1406 |
causal_rows = self._causal_tril[:, :, cache_position, :] # [1,1,q_len,MAX]
|
| 1407 |
attn_mask = np.where(causal_rows, 0.0, -np.inf).astype(np.float32) # [1, 1, q_len, MAX_SEQ_LEN]
|
| 1408 |
cos_rope = self._cos_rope[:, :, cache_position].copy()
|
| 1409 |
sin_rope = self._sin_rope[:, :, cache_position].copy()
|
| 1410 |
self._run_talker_prefill(inputs_embeds, cache_position, attn_mask, cos_rope, sin_rope)
|
| 1411 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1412 |
# Snapshot the post-prefill KV state for turn resets
|
| 1413 |
self._kv_talker_prefill_np = self._snapshot_talker_kv()
|
| 1414 |
self._prefilled = True
|
| 1415 |
-
log.info(f"Prefill done; talker_seq_len={self._talker_seq_len}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1416 |
|
| 1417 |
# ── Talker prefill execution (IOBinding + CUDA graph) ───────────────────
|
| 1418 |
|
|
@@ -1563,13 +1669,15 @@ class Qwen3TTSInferencerONNX:
|
|
| 1563 |
return self._last_audio_tokens
|
| 1564 |
|
| 1565 |
# ── Build inputs_embeds ───────────────────────────────────────────────
|
| 1566 |
-
if self._step_idx > 0:
|
| 1567 |
codec_embeds = self._last_first_token_embed + self._last_local_tokens_embed
|
| 1568 |
else:
|
| 1569 |
codec_embeds = self._talker_codec_embed.run(
|
| 1570 |
["codec_emb"],
|
| 1571 |
{"codec_ids": np.array([[self._codec_bos_id]], dtype=np.int64)},
|
| 1572 |
)[0]
|
|
|
|
|
|
|
| 1573 |
|
| 1574 |
if text_token is not None:
|
| 1575 |
text_ids = np.array([[text_token]], dtype=np.int64)
|
|
@@ -1594,7 +1702,6 @@ class Qwen3TTSInferencerONNX:
|
|
| 1594 |
# f"step-idx-{self._step_idx} self._last_hidden_states_np {self._last_hidden_states_np} "
|
| 1595 |
# f"{self._last_hidden_states_np.shape} {self._last_hidden_states_np.dtype}"
|
| 1596 |
# )
|
| 1597 |
-
self._talker_seq_len += 1
|
| 1598 |
|
| 1599 |
# ── Sample first token (CPU) ──────────────────────────────────────────
|
| 1600 |
history = self._generated_tokens[:, :, 0]
|
|
@@ -1603,6 +1710,9 @@ class Qwen3TTSInferencerONNX:
|
|
| 1603 |
|
| 1604 |
self._last_first_token = first_token
|
| 1605 |
self._is_stopping = bool(first_token[0] == self._codec_eos_token_id)
|
|
|
|
|
|
|
|
|
|
| 1606 |
if self.is_finished:
|
| 1607 |
return None
|
| 1608 |
|
|
@@ -1616,7 +1726,6 @@ class Qwen3TTSInferencerONNX:
|
|
| 1616 |
# ── Run Local Talker (15 steps) ───────────────────────────────────────
|
| 1617 |
self._generate_local_transformer()
|
| 1618 |
|
| 1619 |
-
self._step_idx += 1
|
| 1620 |
return self._last_audio_tokens
|
| 1621 |
|
| 1622 |
# ── Local Talker inner loop ───────────────────────────────────────────────
|
|
@@ -1953,13 +2062,18 @@ class Qwen3TTSInferencerONNX:
|
|
| 1953 |
|
| 1954 |
def _drain_pending_tokens(self) -> List[NDArrayInt]:
|
| 1955 |
outputs: List[NDArrayInt] = []
|
|
|
|
| 1956 |
if not self._prefilled:
|
| 1957 |
-
self.
|
| 1958 |
-
|
| 1959 |
-
|
|
|
|
|
|
|
| 1960 |
while self._pending_tokens and not self.is_finished:
|
| 1961 |
token = self._pending_tokens.pop(0)
|
| 1962 |
-
log.info(
|
|
|
|
|
|
|
| 1963 |
output = self.step(token)
|
| 1964 |
if output is not None:
|
| 1965 |
outputs.append(output)
|
|
@@ -1967,18 +2081,24 @@ class Qwen3TTSInferencerONNX:
|
|
| 1967 |
|
| 1968 |
def push_text(self, text_fragment: str) -> List[NDArrayInt]:
|
| 1969 |
self._text_cache += text_fragment
|
| 1970 |
-
log.info(f"
|
| 1971 |
for segment in self._extract_text_segments(force=False):
|
| 1972 |
-
self.
|
| 1973 |
-
log.info(
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1974 |
return self._drain_pending_tokens()
|
| 1975 |
|
| 1976 |
def end_text(self) -> List[NDArrayInt]:
|
| 1977 |
self._text_ended = True
|
|
|
|
| 1978 |
if self._text_cache:
|
| 1979 |
self._pending_tokens.extend(self._tokenize_texts([self._text_cache]))
|
| 1980 |
self._text_cache = ""
|
| 1981 |
self._pending_tokens.extend([np.array([self._tts_eos_token_id], dtype=np.int64)][0])
|
|
|
|
| 1982 |
return self._drain_pending_tokens()
|
| 1983 |
|
| 1984 |
def drain(self, max_steps: Optional[int] = None) -> List[NDArrayInt]:
|
|
@@ -2077,6 +2197,10 @@ class Qwen3TTSInferencerONNX:
|
|
| 2077 |
)
|
| 2078 |
|
| 2079 |
self._codec_step_idx += self.chunk_frames
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2080 |
return wav
|
| 2081 |
|
| 2082 |
def _overlap_samples(self, wav: NDArrayFloat) -> int:
|
|
@@ -2141,7 +2265,7 @@ class Qwen3TTSInferencerONNX:
|
|
| 2141 |
self._last_hidden_states_np = None
|
| 2142 |
self._step_idx = 0
|
| 2143 |
|
| 2144 |
-
def reset_turn(self, reset_cache: bool =
|
| 2145 |
"""Reset for a new turn. If ``reset_cache=True``, also clears the prefill KV."""
|
| 2146 |
self._turn_idx += 1
|
| 2147 |
self._text_cache = ""
|
|
@@ -2149,6 +2273,12 @@ class Qwen3TTSInferencerONNX:
|
|
| 2149 |
self._prefilled = False
|
| 2150 |
self._text_ended = False
|
| 2151 |
self._prev_tail = None
|
|
|
|
| 2152 |
self._buffer = []
|
| 2153 |
self._buffer_len = 0
|
| 2154 |
-
self.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 117 |
_NUM_CODE_GROUPS = 16
|
| 118 |
|
| 119 |
|
| 120 |
+
# For continuous streaming with multi turn texts
|
| 121 |
+
_LENGTH_RESET_LIMIT_FOR_START_OF_MULTI_TURN = 50
|
| 122 |
+
|
| 123 |
+
|
| 124 |
# ── CPU sampling helpers ──────────────────────────────────────────────────────
|
| 125 |
|
| 126 |
|
|
|
|
| 212 |
opts.execution_mode = ort.ExecutionMode.ORT_SEQUENTIAL
|
| 213 |
opts.enable_cpu_mem_arena = True
|
| 214 |
opts.enable_mem_pattern = True
|
| 215 |
+
opts.log_severity_level = 2
|
| 216 |
|
| 217 |
if use_cuda:
|
| 218 |
cuda_opts = {
|
|
|
|
| 818 |
self._last_hidden_states_np: Optional[np.ndarray] = None
|
| 819 |
self._step_idx = 0
|
| 820 |
self._talker_seq_len = 0
|
| 821 |
+
self._generated_wav = None
|
| 822 |
|
| 823 |
# ── Streaming text state ──────────────────────────────────────────────
|
| 824 |
self._text_cache = ""
|
|
|
|
| 826 |
self._prefilled = False
|
| 827 |
self._text_ended = False
|
| 828 |
self._turn_idx = 0
|
| 829 |
+
self._first_step_next_round = False
|
| 830 |
|
| 831 |
# ── Audio buffer ──────────────────────────────────────────────────────
|
| 832 |
self._prev_tail: Optional[np.ndarray] = None
|
|
|
|
| 1320 |
def _build_assistant_text(self) -> str:
|
| 1321 |
return "<|im_start|>assistant\n"
|
| 1322 |
|
| 1323 |
+
def _build_ending_assistant_text(self) -> str:
|
| 1324 |
+
return "<|im_end|>\n"
|
| 1325 |
+
|
| 1326 |
+
def _prefill_embeds(self, audio_info: AudioLike, language: str) -> np.ndarray:
|
| 1327 |
"""Build the 9-token prefill embedding for the Talker.
|
| 1328 |
|
| 1329 |
Layout (matches modeling_qwen3_tts.py generate_icl_prompt):
|
|
|
|
| 1339 |
else:
|
| 1340 |
language_id = self._codec_language_id[language.lower()]
|
| 1341 |
log.info(f"_prefill_embeds language_id {language_id}")
|
| 1342 |
+
speaker_embed = self.create_voice_clone_spkemb(audio_info) # [B, 1, 512]
|
| 1343 |
log.info(f"_prefill_embeds speaker_embed {speaker_embed} {speaker_embed.shape}")
|
| 1344 |
|
| 1345 |
codec_prefill_list = np.array(
|
|
|
|
| 1410 |
|
| 1411 |
def prefill(self) -> None:
|
| 1412 |
"""Run the 9-token prefill pass (IOBinding + CUDA graph) and snapshot KV."""
|
| 1413 |
+
if self._step_idx == 0:
|
| 1414 |
+
audio_info = self._audio_ref_path
|
| 1415 |
+
else:
|
| 1416 |
+
wav_history = self._generated_wav[0, 0]
|
| 1417 |
+
log.info(f"prefill cont wav_history {wav_history} {wav_history.shape} {wav_history.dtype}")
|
| 1418 |
+
audio_info = (
|
| 1419 |
+
wav_history,
|
| 1420 |
+
24000,
|
| 1421 |
+
)
|
| 1422 |
+
self._generated_wav = None
|
| 1423 |
+
# use previously generated audio sequence
|
| 1424 |
+
# from previous list of texts with the window limit 50 as reference
|
| 1425 |
+
inputs_embeds = self._prefill_embeds(audio_info, self._language)
|
| 1426 |
+
if self._step_idx == 0:
|
| 1427 |
+
cache_position = np.arange(_TALKER_PREFILL_LEN, dtype=np.int64)
|
| 1428 |
+
else:
|
| 1429 |
+
cache_position = np.arange(
|
| 1430 |
+
self._talker_seq_len, self._talker_seq_len + _TALKER_PREFILL_LEN, dtype=np.int64
|
| 1431 |
+
)
|
| 1432 |
causal_rows = self._causal_tril[:, :, cache_position, :] # [1,1,q_len,MAX]
|
| 1433 |
attn_mask = np.where(causal_rows, 0.0, -np.inf).astype(np.float32) # [1, 1, q_len, MAX_SEQ_LEN]
|
| 1434 |
cos_rope = self._cos_rope[:, :, cache_position].copy()
|
| 1435 |
sin_rope = self._sin_rope[:, :, cache_position].copy()
|
| 1436 |
self._run_talker_prefill(inputs_embeds, cache_position, attn_mask, cos_rope, sin_rope)
|
| 1437 |
+
if self._step_idx == 0:
|
| 1438 |
+
self._talker_seq_len = _TALKER_PREFILL_LEN
|
| 1439 |
+
else:
|
| 1440 |
+
self._talker_seq_len = self._talker_seq_len + _TALKER_PREFILL_LEN
|
| 1441 |
+
self._step_idx = self._talker_seq_len
|
| 1442 |
# Snapshot the post-prefill KV state for turn resets
|
| 1443 |
self._kv_talker_prefill_np = self._snapshot_talker_kv()
|
| 1444 |
self._prefilled = True
|
| 1445 |
+
log.info(f"Prefill done; talker_seq_len={self._talker_seq_len}; step_idx={self._step_idx}")
|
| 1446 |
+
|
| 1447 |
+
def prefill_cont(self) -> None:
|
| 1448 |
+
"""
|
| 1449 |
+
Run the prefill for continuation pass (IOBinding + CUDA graph) and snapshot KV.
|
| 1450 |
+
[text_pad],[token_ids("<im_end>\n")],[text_bos] -> continue with the first text token
|
| 1451 |
+
[codec_eos],[None],[codec_pad] -> continue with codec_bos and generation
|
| 1452 |
+
"""
|
| 1453 |
+
|
| 1454 |
+
# text_ids = np.array([[self._tts_pad_token_id, self._tts_bos_token_id]], dtype=np.int64)
|
| 1455 |
+
# text_ids = np.array([[self._tts_pad_token_id]], dtype=np.int64)
|
| 1456 |
+
# log.info(f"prefill_cont text_ids {text_ids} {text_ids.shape}")
|
| 1457 |
+
# outputs = self._text_embed_proj.run(["text_emb_out"], {"text_ids": text_ids}) # 3
|
| 1458 |
+
# # embeds = outputs[0]
|
| 1459 |
+
# # tts_pad_embed, tts_bos_embed = embeds[:, :1], embeds[:, 1:] # 2 * [1 1 d]
|
| 1460 |
+
# tts_pad_embed = outputs[0]
|
| 1461 |
+
# # log.info(f"prefill_cont tts_bos_embed {tts_bos_embed} {tts_bos_embed.shape} {tts_bos_embed.dtype}")
|
| 1462 |
+
# log.info(f"prefill_cont tts_pad_embed {tts_pad_embed} {tts_pad_embed.shape} {tts_pad_embed.dtype}")
|
| 1463 |
+
|
| 1464 |
+
# # codec_ids = np.array([[self._codec_eos_token_id, self._codec_pad_id]], dtype=np.int64)
|
| 1465 |
+
# codec_ids = np.array([[self._codec_eos_token_id]], dtype=np.int64)
|
| 1466 |
+
# log.info(f"prefill_cont codec_ids {codec_ids} {codec_ids.shape}")
|
| 1467 |
+
# outputs = self._talker_codec_embed.run(["codec_emb"], {"codec_ids": codec_ids})
|
| 1468 |
+
# # embeds = outputs[0]
|
| 1469 |
+
# # codec_eos_embed, codec_pad_embed = embeds[:, :1], embeds[:, 1:] # 2 * [1 1 d]
|
| 1470 |
+
# codec_eos_embed = outputs[0]
|
| 1471 |
+
# log.info(f"prefill_cont codec_eos_embed {codec_eos_embed} {codec_eos_embed.shape} {codec_eos_embed.dtype}")
|
| 1472 |
+
# # log.info(f"prefill_cont codec_pad_embed {codec_pad_embed} {codec_pad_embed.shape} {codec_pad_embed.dtype}")
|
| 1473 |
+
|
| 1474 |
+
# tts_pad_codec_eos_embed = tts_pad_embed + codec_eos_embed
|
| 1475 |
+
# # tts_bos_codec_pad_embed = tts_bos_embed + codec_pad_embed
|
| 1476 |
+
|
| 1477 |
+
# self._tokenize_texts([self._build_ending_assistant_text(), self._build_assistant_text()]),
|
| 1478 |
+
# self._tokenize_texts([self._build_assistant_text()]),
|
| 1479 |
+
# prefix_tokens = np.expand_dims(
|
| 1480 |
+
# np.array(
|
| 1481 |
+
# self._tokenize_texts([self._build_ending_assistant_text()]),
|
| 1482 |
+
# dtype=np.int64,
|
| 1483 |
+
# ),
|
| 1484 |
+
# axis=0,
|
| 1485 |
+
# )
|
| 1486 |
+
# log.info(f"prefill_cont prefix_tokens {prefix_tokens} {prefix_tokens.shape}")
|
| 1487 |
+
# outputs = self._text_embed_proj.run(["text_emb_out"], {"text_ids": prefix_tokens}) # 3
|
| 1488 |
+
# _talker_input_embed_role = outputs[0]
|
| 1489 |
+
# log.info(
|
| 1490 |
+
# f"prefill_cont _talker_input_embed_role {_talker_input_embed_role} {_talker_input_embed_role.shape} {_talker_input_embed_role.dtype}"
|
| 1491 |
+
# )
|
| 1492 |
+
|
| 1493 |
+
# talker_input_embed = _talker_input_embed_role
|
| 1494 |
+
# talker_input_embed = np.concatenate(
|
| 1495 |
+
# (_talker_input_embed_role, tts_bos_codec_pad_embed), axis=1
|
| 1496 |
+
# ) # 6
|
| 1497 |
+
# (tts_pad_codec_eos_embed, _talker_input_embed_role, tts_bos_codec_pad_embed), axis=1
|
| 1498 |
+
# talker_input_embed = np.concatenate((tts_pad_codec_eos_embed, tts_bos_codec_pad_embed), axis=1) # 2
|
| 1499 |
+
# talker_input_embed = np.concatenate((tts_pad_codec_eos_embed, _talker_input_embed_role), axis=1) # 3
|
| 1500 |
+
# log.info(
|
| 1501 |
+
# f"prefill_cont talker_input_embed {talker_input_embed} {talker_input_embed.shape} {talker_input_embed.dtype}"
|
| 1502 |
+
# )
|
| 1503 |
+
|
| 1504 |
+
# log.info(f"prefill_cont talker_seq_len {self._talker_seq_len} step_idx {self._step_idx}")
|
| 1505 |
+
# for i in range(talker_input_embed.shape[1]):
|
| 1506 |
+
# inputs_embeds = talker_input_embed[:, i : i + 1]
|
| 1507 |
+
# cache_position = np.array([self._talker_seq_len], dtype=np.int64)
|
| 1508 |
+
# causal_rows = self._causal_tril[:, :, cache_position, :] # [1,1,q_len,MAX]
|
| 1509 |
+
# attn_mask = np.where(causal_rows, 0.0, -np.inf).astype(np.float32) # [1, 1, q_len, MAX_SEQ_LEN]
|
| 1510 |
+
# cos_rope = self._cos_rope[:, :, cache_position].copy()
|
| 1511 |
+
# sin_rope = self._sin_rope[:, :, cache_position].copy()
|
| 1512 |
+
# self._run_talker_step(inputs_embeds, cache_position, attn_mask, cos_rope, sin_rope)
|
| 1513 |
+
# self._talker_seq_len += 1
|
| 1514 |
+
# self._step_idx += 1
|
| 1515 |
+
|
| 1516 |
+
self.prefill()
|
| 1517 |
+
# Snapshot the post-prefill KV state for turn resets
|
| 1518 |
+
self._kv_talker_prefill_np = self._snapshot_talker_kv()
|
| 1519 |
+
self._prefilled = True
|
| 1520 |
+
self._first_step_next_round = True
|
| 1521 |
+
log.info(f"Prefill cont done; talker_seq_len={self._talker_seq_len}; step_idx={self._step_idx}")
|
| 1522 |
|
| 1523 |
# ── Talker prefill execution (IOBinding + CUDA graph) ───────────────────
|
| 1524 |
|
|
|
|
| 1669 |
return self._last_audio_tokens
|
| 1670 |
|
| 1671 |
# ── Build inputs_embeds ───────────────────────────────────────────────
|
| 1672 |
+
if self._step_idx > 0 and not self._first_step_next_round:
|
| 1673 |
codec_embeds = self._last_first_token_embed + self._last_local_tokens_embed
|
| 1674 |
else:
|
| 1675 |
codec_embeds = self._talker_codec_embed.run(
|
| 1676 |
["codec_emb"],
|
| 1677 |
{"codec_ids": np.array([[self._codec_bos_id]], dtype=np.int64)},
|
| 1678 |
)[0]
|
| 1679 |
+
if self._first_step_next_round:
|
| 1680 |
+
self._first_step_next_round = False
|
| 1681 |
|
| 1682 |
if text_token is not None:
|
| 1683 |
text_ids = np.array([[text_token]], dtype=np.int64)
|
|
|
|
| 1702 |
# f"step-idx-{self._step_idx} self._last_hidden_states_np {self._last_hidden_states_np} "
|
| 1703 |
# f"{self._last_hidden_states_np.shape} {self._last_hidden_states_np.dtype}"
|
| 1704 |
# )
|
|
|
|
| 1705 |
|
| 1706 |
# ── Sample first token (CPU) ──────────────────────────────────────────
|
| 1707 |
history = self._generated_tokens[:, :, 0]
|
|
|
|
| 1710 |
|
| 1711 |
self._last_first_token = first_token
|
| 1712 |
self._is_stopping = bool(first_token[0] == self._codec_eos_token_id)
|
| 1713 |
+
|
| 1714 |
+
self._talker_seq_len += 1
|
| 1715 |
+
self._step_idx += 1
|
| 1716 |
if self.is_finished:
|
| 1717 |
return None
|
| 1718 |
|
|
|
|
| 1726 |
# ── Run Local Talker (15 steps) ───────────────────────────────────────
|
| 1727 |
self._generate_local_transformer()
|
| 1728 |
|
|
|
|
| 1729 |
return self._last_audio_tokens
|
| 1730 |
|
| 1731 |
# ── Local Talker inner loop ───────────────────────────────────────────────
|
|
|
|
| 2062 |
|
| 2063 |
def _drain_pending_tokens(self) -> List[NDArrayInt]:
|
| 2064 |
outputs: List[NDArrayInt] = []
|
| 2065 |
+
log.info(f"drain_pending_tokens is_prefilled {self._prefilled} is_finished {self.is_finished}")
|
| 2066 |
if not self._prefilled:
|
| 2067 |
+
if self._step_idx == 0:
|
| 2068 |
+
self.prefill()
|
| 2069 |
+
else:
|
| 2070 |
+
self.prefill_cont()
|
| 2071 |
+
log.info(f"drain_pending_tokens pending_tokens {self._pending_tokens} {len(self._pending_tokens)}")
|
| 2072 |
while self._pending_tokens and not self.is_finished:
|
| 2073 |
token = self._pending_tokens.pop(0)
|
| 2074 |
+
log.info(
|
| 2075 |
+
f"drain_pending_tokens token {token} -> pending_tokens {self._pending_tokens} {len(self._pending_tokens)}"
|
| 2076 |
+
)
|
| 2077 |
output = self.step(token)
|
| 2078 |
if output is not None:
|
| 2079 |
outputs.append(output)
|
|
|
|
| 2081 |
|
| 2082 |
def push_text(self, text_fragment: str) -> List[NDArrayInt]:
|
| 2083 |
self._text_cache += text_fragment
|
| 2084 |
+
log.info(f"push_text text_fragment {text_fragment} -> text_cache {self._text_cache}")
|
| 2085 |
for segment in self._extract_text_segments(force=False):
|
| 2086 |
+
tokenized_segment = self._tokenize_texts([segment])
|
| 2087 |
+
log.info(
|
| 2088 |
+
f"push_text segment {segment} {len(segment)} -> tokenized_segment {tokenized_segment} {len(tokenized_segment)}"
|
| 2089 |
+
)
|
| 2090 |
+
self._pending_tokens.extend(tokenized_segment)
|
| 2091 |
+
log.info(f"push_text pending_tokens {self._pending_tokens}")
|
| 2092 |
return self._drain_pending_tokens()
|
| 2093 |
|
| 2094 |
def end_text(self) -> List[NDArrayInt]:
|
| 2095 |
self._text_ended = True
|
| 2096 |
+
log.info(f"end_text text_cache {self._text_cache} {len(self._text_cache)}")
|
| 2097 |
if self._text_cache:
|
| 2098 |
self._pending_tokens.extend(self._tokenize_texts([self._text_cache]))
|
| 2099 |
self._text_cache = ""
|
| 2100 |
self._pending_tokens.extend([np.array([self._tts_eos_token_id], dtype=np.int64)][0])
|
| 2101 |
+
log.info(f"end_text pending_tokens {self._pending_tokens} {len(self._pending_tokens)}")
|
| 2102 |
return self._drain_pending_tokens()
|
| 2103 |
|
| 2104 |
def drain(self, max_steps: Optional[int] = None) -> List[NDArrayInt]:
|
|
|
|
| 2197 |
)
|
| 2198 |
|
| 2199 |
self._codec_step_idx += self.chunk_frames
|
| 2200 |
+
if self._generated_wav is not None:
|
| 2201 |
+
self._generated_wav = np.concatenate((self._generated_wav, wav.copy()), axis=-1)
|
| 2202 |
+
else:
|
| 2203 |
+
self._generated_wav = wav.copy()
|
| 2204 |
return wav
|
| 2205 |
|
| 2206 |
def _overlap_samples(self, wav: NDArrayFloat) -> int:
|
|
|
|
| 2265 |
self._last_hidden_states_np = None
|
| 2266 |
self._step_idx = 0
|
| 2267 |
|
| 2268 |
+
def reset_turn(self, reset_cache: Optional[bool] = None) -> None:
|
| 2269 |
"""Reset for a new turn. If ``reset_cache=True``, also clears the prefill KV."""
|
| 2270 |
self._turn_idx += 1
|
| 2271 |
self._text_cache = ""
|
|
|
|
| 2273 |
self._prefilled = False
|
| 2274 |
self._text_ended = False
|
| 2275 |
self._prev_tail = None
|
| 2276 |
+
self._is_stopping = False
|
| 2277 |
self._buffer = []
|
| 2278 |
self._buffer_len = 0
|
| 2279 |
+
if self._step_idx >= _LENGTH_RESET_LIMIT_FOR_START_OF_MULTI_TURN:
|
| 2280 |
+
log.info(f"reset_turn reset_generation_state step-idx-{self._step_idx}")
|
| 2281 |
+
self._first_step_next_round = False
|
| 2282 |
+
self.reset_generation_state(False)
|
| 2283 |
+
elif reset_cache is not None:
|
| 2284 |
+
self.reset_generation_state(keep_prefill_cache=not reset_cache)
|
|
@@ -102,23 +102,33 @@ _CODEC_CONFIG_PATH = "./configs/speech_tokenizer_config.json"
|
|
| 102 |
# _AUDIO_REF_PATH = "./audio_ref/female_shadowheart.flac"
|
| 103 |
_AUDIO_REF_PATH = "./audio_ref/male_stewie.mp3"
|
| 104 |
_OUTPUT_WAV_DIR = "./audio_synth/"
|
| 105 |
-
_LANGUAGE = "russian"
|
| 106 |
# _LANGUAGE = "english"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 107 |
# _TEXT = "в зависимости от времени не только точность, но и низкая задержка."
|
| 108 |
_TEXT = [
|
| 109 |
-
"
|
| 110 |
-
"
|
| 111 |
-
"
|
| 112 |
-
"
|
| 113 |
-
"
|
| 114 |
-
"
|
| 115 |
-
"те
|
| 116 |
-
"
|
| 117 |
-
"
|
| 118 |
-
"д
|
| 119 |
]
|
| 120 |
-
# _TEXT = ["This is a test, ", "how about this one?", "Is this also?"]
|
| 121 |
-
# _TEXT = ["This is not a test.", "How about this one?", "Is this also not?"]
|
| 122 |
_OUTPUT_SAMPLE_RATE = 24000
|
| 123 |
_CHUNK_FRAMES = 4
|
| 124 |
_TEMPERATURE = 0.85
|
|
@@ -204,7 +214,7 @@ def run_streaming_tts(
|
|
| 204 |
t_start = time.perf_counter()
|
| 205 |
|
| 206 |
for i, text in enumerate(text_list):
|
| 207 |
-
inferencer.reset_turn(reset_cache=
|
| 208 |
log.info(f"Synthesising: {text!r}")
|
| 209 |
|
| 210 |
# ── Stream text deltas ────────────────────────────────────────────────────
|
|
|
|
| 102 |
# _AUDIO_REF_PATH = "./audio_ref/female_shadowheart.flac"
|
| 103 |
_AUDIO_REF_PATH = "./audio_ref/male_stewie.mp3"
|
| 104 |
_OUTPUT_WAV_DIR = "./audio_synth/"
|
|
|
|
| 105 |
# _LANGUAGE = "english"
|
| 106 |
+
# _TEXT = [
|
| 107 |
+
# "Depending on the time,",
|
| 108 |
+
# "not only accuracy,",
|
| 109 |
+
# "but also low latency is important.",
|
| 110 |
+
# "If it is not instant,",
|
| 111 |
+
# "then, the human interaction is lost",
|
| 112 |
+
# "We are finally reaching a moment",
|
| 113 |
+
# "where the technology is fast enough",
|
| 114 |
+
# "for people to simply communicate.",
|
| 115 |
+
# "And that is a huge shift",
|
| 116 |
+
# "for global business",
|
| 117 |
+
# ]
|
| 118 |
+
_LANGUAGE = "russian"
|
| 119 |
# _TEXT = "в зависимости от времени не только точность, но и низкая задержка."
|
| 120 |
_TEXT = [
|
| 121 |
+
"В зависимости от ситуации,",
|
| 122 |
+
"важна не только точность,",
|
| 123 |
+
"но и низкая задержка.",
|
| 124 |
+
"Если это происходит не мгновенно,",
|
| 125 |
+
"то теряется эффект живого общения.",
|
| 126 |
+
"Мы наконец-то достигли момента,",
|
| 127 |
+
"когда технологии стали достаточно быстрыми,",
|
| 128 |
+
"чтобы люди могли просто общаться.",
|
| 129 |
+
"И это огромный сдвиг",
|
| 130 |
+
"для мирового бизнеса.",
|
| 131 |
]
|
|
|
|
|
|
|
| 132 |
_OUTPUT_SAMPLE_RATE = 24000
|
| 133 |
_CHUNK_FRAMES = 4
|
| 134 |
_TEMPERATURE = 0.85
|
|
|
|
| 214 |
t_start = time.perf_counter()
|
| 215 |
|
| 216 |
for i, text in enumerate(text_list):
|
| 217 |
+
inferencer.reset_turn(reset_cache=None)
|
| 218 |
log.info(f"Synthesising: {text!r}")
|
| 219 |
|
| 220 |
# ── Stream text deltas ────────────────────────────────────────────────────
|