|
@@ -4,6 +4,8 @@ import re
|
|
|
import threading
|
|
import threading
|
|
|
import time
|
|
import time
|
|
|
import traceback
|
|
import traceback
|
|
|
|
|
+from concurrent.futures import ThreadPoolExecutor, as_completed
|
|
|
|
|
+from copy import deepcopy
|
|
|
from dataclasses import dataclass
|
|
from dataclasses import dataclass
|
|
|
from pathlib import Path
|
|
from pathlib import Path
|
|
|
from typing import Callable, Literal, Optional, Tuple, Union
|
|
from typing import Callable, Literal, Optional, Tuple, Union
|
|
@@ -796,7 +798,7 @@ def launch_thread_safe_queue(
|
|
|
response_queue = item.response_queue
|
|
response_queue = item.response_queue
|
|
|
|
|
|
|
|
try:
|
|
try:
|
|
|
- for chunk in generate_long_parallel_batched(
|
|
|
|
|
|
|
+ for chunk in generate_long_batched(
|
|
|
model=model, decode_one_token=decode_one_token, **kwargs
|
|
model=model, decode_one_token=decode_one_token, **kwargs
|
|
|
):
|
|
):
|
|
|
response_queue.put(
|
|
response_queue.put(
|
|
@@ -820,25 +822,38 @@ def launch_thread_safe_queue(
|
|
|
return input_queue
|
|
return input_queue
|
|
|
|
|
|
|
|
|
|
|
|
|
-import torch
|
|
|
|
|
-from copy import deepcopy
|
|
|
|
|
-from typing import List, Optional
|
|
|
|
|
-
|
|
|
|
|
-
|
|
|
|
|
-# =========================
|
|
|
|
|
-# 1. Prompt 构建(从 generate_long 抽出)
|
|
|
|
|
-# =========================
|
|
|
|
|
-def build_prompt(
|
|
|
|
|
|
|
+@torch.inference_mode()
|
|
|
|
|
+def generate_long_batched(
|
|
|
*,
|
|
*,
|
|
|
model,
|
|
model,
|
|
|
- device,
|
|
|
|
|
- text,
|
|
|
|
|
- chunk_length,
|
|
|
|
|
- prompt_text=None,
|
|
|
|
|
- prompt_tokens=None,
|
|
|
|
|
|
|
+ device: Union[str, torch.device],
|
|
|
|
|
+ decode_one_token: Callable,
|
|
|
|
|
+ text: str,
|
|
|
|
|
+ num_samples: int = 1,
|
|
|
|
|
+ max_new_tokens: int = 0,
|
|
|
|
|
+ top_p: float = 0.9,
|
|
|
|
|
+ top_k: int = 30,
|
|
|
|
|
+ repetition_penalty: float = 1.1,
|
|
|
|
|
+ temperature: float = 1.0,
|
|
|
|
|
+ compile: bool = False,
|
|
|
|
|
+ iterative_prompt: bool = True,
|
|
|
|
|
+ chunk_length: int = 512,
|
|
|
|
|
+ prompt_text: Optional[Union[str, list[str]]] = None,
|
|
|
|
|
+ prompt_tokens: Optional[Union[torch.Tensor, list[torch.Tensor]]] = None,
|
|
|
):
|
|
):
|
|
|
- tokenizer = model.tokenizer
|
|
|
|
|
- max_length = model.config.max_seq_len
|
|
|
|
|
|
|
+ assert 0 < top_p <= 1, "top_p must be in (0, 1]"
|
|
|
|
|
+ assert 0 < temperature < 2, "temperature must be in (0, 2)"
|
|
|
|
|
+
|
|
|
|
|
+ logger.info(f"generate_long.param.device: {device}")
|
|
|
|
|
+ logger.info(f"generate_long.param.text: {text}")
|
|
|
|
|
+ logger.info(f"generate_long.param.max_new_tokens: {max_new_tokens}")
|
|
|
|
|
+ logger.info(f"generate_long.param.top_p: {top_p}")
|
|
|
|
|
+ logger.info(f"generate_long.param.top_k: {top_k}")
|
|
|
|
|
+ logger.info(f"generate_long.param.temperature: {temperature}")
|
|
|
|
|
+ logger.info(f"generate_long.param.compile: {compile}")
|
|
|
|
|
+ logger.info(f"generate_long.param.chunk_length: {chunk_length}")
|
|
|
|
|
+ logger.info(f"generate_long.param.prompt_text: {prompt_text}")
|
|
|
|
|
+ logger.info(f"generate_long.param.prompt_tokens: {prompt_tokens}")
|
|
|
|
|
|
|
|
use_prompt = bool(prompt_text) and bool(prompt_tokens)
|
|
use_prompt = bool(prompt_text) and bool(prompt_tokens)
|
|
|
|
|
|
|
@@ -846,29 +861,41 @@ def build_prompt(
|
|
|
prompt_text = [prompt_text]
|
|
prompt_text = [prompt_text]
|
|
|
prompt_tokens = [prompt_tokens]
|
|
prompt_tokens = [prompt_tokens]
|
|
|
|
|
|
|
|
|
|
+ if use_prompt:
|
|
|
|
|
+ assert len(prompt_text) == len(prompt_tokens)
|
|
|
|
|
+
|
|
|
if prompt_tokens:
|
|
if prompt_tokens:
|
|
|
prompt_tokens = [p.cpu() for p in prompt_tokens]
|
|
prompt_tokens = [p.cpu() for p in prompt_tokens]
|
|
|
|
|
|
|
|
- # -------- system prompt --------
|
|
|
|
|
|
|
+ tokenizer = model.tokenizer
|
|
|
|
|
+ max_length = model.config.max_seq_len
|
|
|
|
|
+ model_size = sum(p.numel() for p in model.parameters() if p.requires_grad)
|
|
|
|
|
+
|
|
|
|
|
+ # =========================
|
|
|
|
|
+ # build base conversation(不动)
|
|
|
|
|
+ # =========================
|
|
|
base_conversation = Conversation()
|
|
base_conversation = Conversation()
|
|
|
|
|
|
|
|
if use_prompt:
|
|
if use_prompt:
|
|
|
- tagged = []
|
|
|
|
|
|
|
+ tagged_prompt_text = []
|
|
|
for i, t in enumerate(prompt_text):
|
|
for i, t in enumerate(prompt_text):
|
|
|
if not re.search(r"<\|speaker:\d+\|>", t):
|
|
if not re.search(r"<\|speaker:\d+\|>", t):
|
|
|
- tagged.append(f"<|speaker:{i}|>{t}")
|
|
|
|
|
|
|
+ tagged_prompt_text.append(f"<|speaker:{i}|>{t}")
|
|
|
else:
|
|
else:
|
|
|
- tagged.append(t)
|
|
|
|
|
|
|
+ tagged_prompt_text.append(t)
|
|
|
|
|
|
|
|
system_parts = [
|
|
system_parts = [
|
|
|
- TextPart(text="convert the provided text to speech reference to the following:\n\nText:\n", cal_loss=False, ),
|
|
|
|
|
- TextPart(text="\n".join(tagged), cal_loss=False),
|
|
|
|
|
- TextPart(text="\n\nSpeech:\n", cal_loss=False)
|
|
|
|
|
|
|
+ TextPart(
|
|
|
|
|
+ text="convert the provided text to speech reference to the following:\n\nText:\n",
|
|
|
|
|
+ cal_loss=False,
|
|
|
|
|
+ ),
|
|
|
]
|
|
]
|
|
|
|
|
|
|
|
|
|
+ system_parts.append(TextPart(text="\n".join(tagged_prompt_text), cal_loss=False))
|
|
|
|
|
+ system_parts.append(TextPart(text="\n\nSpeech:\n", cal_loss=False))
|
|
|
|
|
+
|
|
|
all_codes = torch.cat(prompt_tokens, dim=1)
|
|
all_codes = torch.cat(prompt_tokens, dim=1)
|
|
|
system_parts.append(VQPart(codes=all_codes, cal_loss=False))
|
|
system_parts.append(VQPart(codes=all_codes, cal_loss=False))
|
|
|
-
|
|
|
|
|
else:
|
|
else:
|
|
|
system_parts = [
|
|
system_parts = [
|
|
|
TextPart(text="convert the provided text to speech", cal_loss=False)
|
|
TextPart(text="convert the provided text to speech", cal_loss=False)
|
|
@@ -884,32 +911,39 @@ def build_prompt(
|
|
|
)
|
|
)
|
|
|
)
|
|
)
|
|
|
|
|
|
|
|
- # -------- chunk text --------
|
|
|
|
|
|
|
+ # =========================
|
|
|
|
|
+ # split batches(不动)
|
|
|
|
|
+ # =========================
|
|
|
turns = split_text_by_speaker(text)
|
|
turns = split_text_by_speaker(text)
|
|
|
- batches = (
|
|
|
|
|
- group_turns_into_batches(turns, max_speakers=5, max_bytes=chunk_length)
|
|
|
|
|
- if turns else [text]
|
|
|
|
|
- )
|
|
|
|
|
|
|
+ if turns:
|
|
|
|
|
+ batches = group_turns_into_batches(
|
|
|
|
|
+ turns, max_speakers=5, max_bytes=chunk_length
|
|
|
|
|
+ )
|
|
|
|
|
+ else:
|
|
|
|
|
+ batches = [text]
|
|
|
|
|
+
|
|
|
|
|
+ logger.info(f"Split into {len(batches)} batches")
|
|
|
|
|
|
|
|
- # 只做 chunk
|
|
|
|
|
- encoded_prompts = []
|
|
|
|
|
- metas = []
|
|
|
|
|
|
|
+ # =========================
|
|
|
|
|
+ # worker function(核心)
|
|
|
|
|
+ # =========================
|
|
|
|
|
+ def run_one_batch(b_idx: int, b_text: str):
|
|
|
|
|
+ conversation = deepcopy(base_conversation)
|
|
|
|
|
|
|
|
- for batch_text in batches:
|
|
|
|
|
- conv = deepcopy(base_conversation)
|
|
|
|
|
|
|
+ logger.info(f"[Batch {b_idx}] start")
|
|
|
|
|
|
|
|
- conv.append(
|
|
|
|
|
|
|
+ conversation.append(
|
|
|
Message(
|
|
Message(
|
|
|
role="user",
|
|
role="user",
|
|
|
- parts=[TextPart(text=batch_text, cal_loss=False)],
|
|
|
|
|
|
|
+ parts=[TextPart(text=b_text, cal_loss=False)],
|
|
|
cal_loss=False,
|
|
cal_loss=False,
|
|
|
add_im_start=True,
|
|
add_im_start=True,
|
|
|
add_im_end=True,
|
|
add_im_end=True,
|
|
|
)
|
|
)
|
|
|
)
|
|
)
|
|
|
|
|
|
|
|
- conv_gen = deepcopy(conv)
|
|
|
|
|
- conv_gen.append(
|
|
|
|
|
|
|
+ conversation_gen = deepcopy(conversation)
|
|
|
|
|
+ conversation_gen.append(
|
|
|
Message(
|
|
Message(
|
|
|
role="assistant",
|
|
role="assistant",
|
|
|
parts=[],
|
|
parts=[],
|
|
@@ -920,138 +954,85 @@ def build_prompt(
|
|
|
)
|
|
)
|
|
|
)
|
|
)
|
|
|
|
|
|
|
|
- encoded, audio_masks, audio_parts = conv_gen.encode_for_inference(
|
|
|
|
|
|
|
+ encoded, audio_masks, audio_parts = conversation_gen.encode_for_inference(
|
|
|
tokenizer,
|
|
tokenizer,
|
|
|
- num_codebooks=model.config.num_codebooks,
|
|
|
|
|
|
|
+ num_codebooks=model.config.num_codebooks
|
|
|
)
|
|
)
|
|
|
|
|
|
|
|
if encoded.size(1) > max_length - 2048:
|
|
if encoded.size(1) > max_length - 2048:
|
|
|
- raise ValueError("Prompt too long")
|
|
|
|
|
|
|
+ raise ValueError("prompt too long")
|
|
|
|
|
|
|
|
encoded = encoded.to(device)
|
|
encoded = encoded.to(device)
|
|
|
-
|
|
|
|
|
- encoded_prompts.append(encoded)
|
|
|
|
|
- metas.append(
|
|
|
|
|
- {
|
|
|
|
|
- "audio_masks": audio_masks,
|
|
|
|
|
- "audio_parts": audio_parts,
|
|
|
|
|
- "prompt_len": encoded.size(1),
|
|
|
|
|
- "text": batch_text,
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ prompt_length = encoded.size(1)
|
|
|
|
|
+
|
|
|
|
|
+ y = generate(
|
|
|
|
|
+ model=model,
|
|
|
|
|
+ prompt=encoded,
|
|
|
|
|
+ max_new_tokens=max_new_tokens,
|
|
|
|
|
+ audio_masks=audio_masks,
|
|
|
|
|
+ audio_parts=audio_parts,
|
|
|
|
|
+ decode_one_token=decode_one_token,
|
|
|
|
|
+ temperature=temperature,
|
|
|
|
|
+ top_p=top_p,
|
|
|
|
|
+ top_k=top_k,
|
|
|
)
|
|
)
|
|
|
|
|
|
|
|
- return encoded_prompts, metas
|
|
|
|
|
|
|
+ codes = y[1:, prompt_length:-1].clone()
|
|
|
|
|
|
|
|
|
|
+ logger.info(f"[Batch {b_idx}] done")
|
|
|
|
|
|
|
|
-# =========================
|
|
|
|
|
-# 2. Batched decode(核心)
|
|
|
|
|
-# =========================
|
|
|
|
|
-def generate_batched(
|
|
|
|
|
- *,
|
|
|
|
|
- model,
|
|
|
|
|
- prompts: List[torch.Tensor],
|
|
|
|
|
- decode_one_token,
|
|
|
|
|
- max_new_tokens,
|
|
|
|
|
- temperature,
|
|
|
|
|
- top_p,
|
|
|
|
|
- top_k,
|
|
|
|
|
-):
|
|
|
|
|
- """
|
|
|
|
|
- prompts: List[Tensor] [1, T]
|
|
|
|
|
- return: List[Tensor] decoded sequences
|
|
|
|
|
- """
|
|
|
|
|
|
|
+ return b_idx, codes, b_text
|
|
|
|
|
|
|
|
- device = prompts[0].device
|
|
|
|
|
-
|
|
|
|
|
- max_len = max(p.size(1) for p in prompts)
|
|
|
|
|
|
|
+ # =========================
|
|
|
|
|
+ # parallel execution(关键修改点)
|
|
|
|
|
+ # =========================
|
|
|
|
|
+ for sample_idx in range(num_samples):
|
|
|
|
|
|
|
|
- padded = []
|
|
|
|
|
- for p in prompts:
|
|
|
|
|
- if p.size(1) < max_len:
|
|
|
|
|
- pad = torch.zeros((1, max_len - p.size(1)), dtype=p.dtype, device=device)
|
|
|
|
|
- p = torch.cat([p, pad], dim=1)
|
|
|
|
|
- padded.append(p)
|
|
|
|
|
|
|
+ if torch.cuda.is_available():
|
|
|
|
|
+ torch.cuda.synchronize()
|
|
|
|
|
|
|
|
- y = torch.cat(padded, dim=0) # [B, T]
|
|
|
|
|
|
|
+ t0 = time.perf_counter()
|
|
|
|
|
|
|
|
- for _ in range(max_new_tokens):
|
|
|
|
|
- next_token = decode_one_token(
|
|
|
|
|
- model,
|
|
|
|
|
- y,
|
|
|
|
|
- temperature=temperature,
|
|
|
|
|
- top_p=top_p,
|
|
|
|
|
- top_k=top_k,
|
|
|
|
|
- audio_masks=None,
|
|
|
|
|
- audio_parts=None,
|
|
|
|
|
- ) # [B, 1]
|
|
|
|
|
|
|
+ results = {}
|
|
|
|
|
|
|
|
- y = torch.cat([y, next_token], dim=1)
|
|
|
|
|
|
|
+ with ThreadPoolExecutor(max_workers=3) as executor:
|
|
|
|
|
+ futures = [
|
|
|
|
|
+ executor.submit(run_one_batch, i, b)
|
|
|
|
|
+ for i, b in enumerate(batches)
|
|
|
|
|
+ ]
|
|
|
|
|
|
|
|
- outputs = [y[i:i + 1] for i in range(y.size(0))]
|
|
|
|
|
- return outputs
|
|
|
|
|
|
|
+ for f in as_completed(futures):
|
|
|
|
|
+ batch_idx, codes, batch_text = f.result()
|
|
|
|
|
|
|
|
|
|
+ results[batch_idx] = codes
|
|
|
|
|
|
|
|
-# =========================
|
|
|
|
|
-# 3. 并行 TTS 主入口
|
|
|
|
|
-# =========================
|
|
|
|
|
-@torch.inference_mode()
|
|
|
|
|
-def generate_long_parallel_batched(
|
|
|
|
|
- *,
|
|
|
|
|
- model,
|
|
|
|
|
- device: Union[str, torch.device],
|
|
|
|
|
- decode_one_token: Callable,
|
|
|
|
|
- text: str,
|
|
|
|
|
- num_samples: int = 1,
|
|
|
|
|
- max_new_tokens: int = 0,
|
|
|
|
|
- top_p: float = 0.9,
|
|
|
|
|
- top_k: int = 30,
|
|
|
|
|
- repetition_penalty: float = 1.1,
|
|
|
|
|
- temperature: float = 1.0,
|
|
|
|
|
- compile: bool = False,
|
|
|
|
|
- iterative_prompt: bool = True,
|
|
|
|
|
- chunk_length: int = 512,
|
|
|
|
|
- prompt_text: Optional[Union[str, list[str]]] = None,
|
|
|
|
|
- prompt_tokens: Optional[Union[torch.Tensor, list[torch.Tensor]]] = None,
|
|
|
|
|
-):
|
|
|
|
|
- """
|
|
|
|
|
- 最小侵入版本:
|
|
|
|
|
- chunk + batch decode + concat
|
|
|
|
|
- """
|
|
|
|
|
-
|
|
|
|
|
- # ===== 1. build prompts =====
|
|
|
|
|
- encoded_prompts, metas = build_prompt(
|
|
|
|
|
- model=model,
|
|
|
|
|
- device=device,
|
|
|
|
|
- text=text,
|
|
|
|
|
- chunk_length=chunk_length,
|
|
|
|
|
- prompt_text=prompt_text,
|
|
|
|
|
- prompt_tokens=prompt_tokens,
|
|
|
|
|
- )
|
|
|
|
|
-
|
|
|
|
|
- # ===== 2. batched decode =====
|
|
|
|
|
- outputs = generate_batched(
|
|
|
|
|
- model=model,
|
|
|
|
|
- prompts=encoded_prompts,
|
|
|
|
|
- decode_one_token=decode_one_token,
|
|
|
|
|
- max_new_tokens=max_new_tokens,
|
|
|
|
|
- temperature=temperature,
|
|
|
|
|
- top_p=top_p,
|
|
|
|
|
- top_k=top_k,
|
|
|
|
|
- )
|
|
|
|
|
|
|
+ # ⭐ 保持你原来的 streaming 行为
|
|
|
|
|
+ yield GenerateResponse(
|
|
|
|
|
+ action="sample",
|
|
|
|
|
+ codes=codes,
|
|
|
|
|
+ text=batch_text,
|
|
|
|
|
+ )
|
|
|
|
|
|
|
|
- # ===== 3. merge outputs =====
|
|
|
|
|
- all_codes = []
|
|
|
|
|
|
|
+ # =========================
|
|
|
|
|
+ # stats(不动逻辑)
|
|
|
|
|
+ # =========================
|
|
|
|
|
+ all_codes = [results[i] for i in sorted(results)]
|
|
|
|
|
|
|
|
- for y, meta in zip(outputs, metas):
|
|
|
|
|
- prompt_len = meta["prompt_len"]
|
|
|
|
|
|
|
+ final_latency = time.perf_counter() - t0
|
|
|
|
|
|
|
|
- codes = y[1:, prompt_len:-1].clone()
|
|
|
|
|
|
|
+ logger.info(f"Sample {sample_idx} done in {final_latency:.2f}s")
|
|
|
|
|
|
|
|
- all_codes.append(codes)
|
|
|
|
|
|
|
+ if torch.cuda.is_available():
|
|
|
|
|
+ logger.info(
|
|
|
|
|
+ f"GPU Memory used: {torch.cuda.max_memory_reserved() / 1e9:.2f} GB"
|
|
|
|
|
+ )
|
|
|
|
|
|
|
|
- final_codes = torch.cat(all_codes, dim=1)
|
|
|
|
|
|
|
+ # cleanup
|
|
|
|
|
+ del results
|
|
|
|
|
+ import gc
|
|
|
|
|
+ gc.collect()
|
|
|
|
|
|
|
|
- return final_codes
|
|
|
|
|
|
|
+ yield GenerateResponse(action="next")
|
|
|
|
|
|
|
|
|
|
|
|
|
# ============================================
|
|
# ============================================
|