Files
diodiogod-TTS-Audio-Suite/engines/chatterbox/continuous_worker_processor.py
diodiogod 6e6a9e4ed6 Fix streaming parallelization and voice mapping issues
- Implement proper streaming model pre-loading with StreamingModelManager
- Fix character alias resolution in voice discovery system
- Add crash protection to streaming workers
- Fix TTS model loading in traditional mode
- Simplify unified UI by removing confusing batch processing toggles
- Ensure voice_refs maps both original and resolved character names
- Add centralized language mapping for streaming multilingual support

Streaming now works correctly with proper voice assignments and language switching.
2025-08-09 23:15:21 -03:00

277 lines
9.9 KiB
Python

"""
Continuous Worker Processor - True Non-Stop Parallelization for ChatterBox
This implements what the user actually wanted:
- Workers continuously process work without waiting for batches
- No idle workers when user sets high worker counts
- Maintains the beautiful logging structure from the original
- Falls back gracefully to sequential processing for 0-1 workers
"""
import torch
import time
import threading
from queue import Queue, Empty
from typing import List, Dict, Any, Optional
from dataclasses import dataclass
from concurrent.futures import ThreadPoolExecutor
import gc
@dataclass
class ContinuousWorkItem:
"""Single work item for continuous processing."""
text_index: int
text: str
temperature: float
cfg_weight: float
exaggeration: float
@dataclass
class ContinuousResult:
"""Result from continuous processing."""
text_index: int
audio: torch.Tensor
processing_time: float
worker_id: int
success: bool = True
error_msg: str = ""
class ContinuousWorkerProcessor:
"""
True continuous worker processor that maintains workers always busy.
Falls back to sequential processing gracefully for 0-1 workers.
Maintains beautiful logging structure.
"""
def __init__(self, tts_model):
self.tts_model = tts_model
def process_texts_continuously(
self,
texts: List[str],
temperature: float,
cfg_weight: float,
exaggeration: float,
max_workers: int
) -> List[torch.Tensor]:
"""
Process texts with continuous workers or fallback to sequential.
Args:
texts: List of texts to process
temperature: Sampling temperature
cfg_weight: CFG weight
exaggeration: Exaggeration factor
max_workers: Number of workers (0-1 = sequential, >1 = continuous)
Returns:
List of audio tensors
"""
if not texts:
return []
# SMART FALLBACK: Use sequential processing for 0-1 workers
if max_workers <= 1:
print(f"→ SEQUENTIAL FALLBACK: {len(texts)} texts (workers={max_workers})")
return self._process_sequential(texts, temperature, cfg_weight, exaggeration)
# Use continuous worker processing for >1 workers
print(f"🌊 CONTINUOUS PROCESSING: {len(texts)} texts with {max_workers} workers")
return self._process_with_continuous_workers(texts, temperature, cfg_weight, exaggeration, max_workers)
def _process_sequential(
self,
texts: List[str],
temperature: float,
cfg_weight: float,
exaggeration: float
) -> List[torch.Tensor]:
"""Sequential processing - clean and organized like the original."""
results = []
for i, text in enumerate(texts):
print(f" 🎤 Sequential {i+1}/{len(texts)}: {text[:40]}...")
try:
audio = self.tts_model.generate(
text=text,
audio_prompt_path=None, # Already prepared
temperature=temperature,
cfg_weight=cfg_weight,
exaggeration=exaggeration
)
results.append(audio)
print(f" ✅ Sequential {i+1}: Completed")
except Exception as e:
print(f" ❌ Sequential {i+1}: Failed - {e}")
results.append(torch.zeros(1, 1000))
print(f"✅ SEQUENTIAL COMPLETE: {len(texts)} texts processed")
return results
def _process_with_continuous_workers(
self,
texts: List[str],
temperature: float,
cfg_weight: float,
exaggeration: float,
max_workers: int
) -> List[torch.Tensor]:
"""
Continuous worker processing - workers never idle.
Maintains beautiful logging structure.
"""
# Build work queue
work_queue = Queue()
result_queue = Queue()
shutdown_event = threading.Event()
# Fill queue with all work
for i, text in enumerate(texts):
work_item = ContinuousWorkItem(
text_index=i,
text=text,
temperature=temperature,
cfg_weight=cfg_weight,
exaggeration=exaggeration
)
work_queue.put(work_item)
print(f"📥 Work queue filled: {len(texts)} items")
# Start workers
workers = []
worker_threads = []
for worker_id in range(max_workers):
worker = ContinuousWorker(
worker_id=worker_id + 1,
work_queue=work_queue,
result_queue=result_queue,
tts_model=self.tts_model,
shutdown_event=shutdown_event
)
thread = threading.Thread(target=worker.run)
thread.start()
workers.append(worker)
worker_threads.append(thread)
print(f"🚀 Started {max_workers} continuous workers")
# Collect results
results = [None] * len(texts)
completed_count = 0
start_time = time.time()
while completed_count < len(texts):
try:
result = result_queue.get(timeout=5.0)
results[result.text_index] = result.audio
completed_count += 1
# Beautiful progress logging (like original)
if completed_count % max(1, len(texts) // 10) == 0:
progress = int(100 * completed_count / len(texts))
elapsed = time.time() - start_time
rate = completed_count / elapsed if elapsed > 0 else 0
print(f"📊 Continuous progress: {completed_count}/{len(texts)} ({progress}%) - {rate:.1f}/sec")
except Empty:
print("⏳ Waiting for continuous workers...")
continue
# Shutdown workers
shutdown_event.set()
# Send shutdown signals
for _ in range(max_workers):
work_queue.put(None)
# Wait for threads
for thread in worker_threads:
thread.join(timeout=3.0)
total_time = time.time() - start_time
throughput = len(texts) / total_time if total_time > 0 else 0
print(f"✅ CONTINUOUS COMPLETE: {len(texts)} texts in {total_time:.1f}s ({throughput:.2f}/sec)")
return results
class ContinuousWorker:
"""Individual continuous worker that processes from shared queue."""
def __init__(self, worker_id: int, work_queue: Queue, result_queue: Queue,
tts_model, shutdown_event: threading.Event):
self.worker_id = worker_id
self.work_queue = work_queue
self.result_queue = result_queue
self.tts_model = tts_model
self.shutdown_event = shutdown_event
self.processed_count = 0
def run(self):
"""Main worker loop - continuously processes work."""
print(f" 🧵 Worker {self.worker_id}: Ready for continuous work")
while not self.shutdown_event.is_set():
try:
# Get next work item
work_item = self.work_queue.get(timeout=1.0)
if work_item is None: # Shutdown signal
break
print(f" 🧵 Worker {self.worker_id}: Processing: {work_item.text[:30]}...")
# Process work item
start_time = time.time()
try:
audio = self.tts_model.generate(
text=work_item.text,
audio_prompt_path=None, # Already prepared
temperature=work_item.temperature,
cfg_weight=work_item.cfg_weight,
exaggeration=work_item.exaggeration
)
processing_time = time.time() - start_time
self.processed_count += 1
result = ContinuousResult(
text_index=work_item.text_index,
audio=audio,
processing_time=processing_time,
worker_id=self.worker_id,
success=True
)
print(f" ✅ Worker {self.worker_id}: Completed in {processing_time:.1f}s")
except Exception as e:
processing_time = time.time() - start_time
result = ContinuousResult(
text_index=work_item.text_index,
audio=torch.zeros(1, 1000),
processing_time=processing_time,
worker_id=self.worker_id,
success=False,
error_msg=str(e)
)
print(f" ❌ Worker {self.worker_id}: Failed - {e}")
# Send result back
self.result_queue.put(result)
self.work_queue.task_done()
except Empty:
# Timeout - check shutdown and continue
continue
except Exception as e:
print(f" ❌ Worker {self.worker_id}: Unexpected error - {e}")
print(f" 🛑 Worker {self.worker_id}: Shutdown ({self.processed_count} processed)")