← Whispering

fix: cuDNN error, FA2 dtype warning, choppy playback, stop behavior

062eda82b19a995ac27db460d7a68f2dcc05927f · Claude · Feb 10, 2026 18:51

1. Chatterbox cuDNN error: enable cudnn.benchmark and set
   float32_matmul_precision("high") before loading on CUDA

2. Flash Attention 2 dtype warning: pass torch_dtype (not dtype) to
   Qwen3TTSModel.from_pretrained so FA2 sees the specified bfloat16

3. Choppy TTS playback: batch-drain the playback queue before
   synthesizing so multiple short text segments merge into one
   synthesis+playback pass instead of individual choppy cycles.
   Also check stop flag after synthesis before playing.

4. Mic stop not halting TTS: call tts_controller.stop_playback()
   immediately in stop_recording() so in-flight TTS audio stops
   when the user clicks stop

https://claude.ai/code/session_01965m1v3f1cK6gDV1JHioA8

+44 −6 · 3 files changed

  • src/tts_controller.py +24 −1
  • src/tts_provider.py +14 −5
  • src/whispering_ui/bridge.py +6 −0
diff --git a/src/tts_controller.py b/src/tts_controller.py
index aa0b757..10ca7e6 100644
--- a/src/tts_controller.py
+++ b/src/tts_controller.py
@@ -324,7 +324,11 @@ class TTSController:
             self._playback_thread.start()
 
     def _playback_loop(self):
-        """Background thread that processes the playback queue."""
+        """Background thread that processes the playback queue.
+
+        Batches all queued text segments into a single synthesis+playback
+        pass to eliminate choppy gaps between segments.
+        """
         import sounddevice as sd
 
         while not self._playback_stop.is_set():
@@ -338,6 +342,22 @@ class TTSController:
                 break
 
             text, also_save, file_format = item
+
+            # Drain any additional queued items and merge into one text block.
+            # This prevents choppy playback when many short segments arrive
+            # in rapid succession from the AI processor.
+            while True:
+                try:
+                    extra = self._playback_queue.get_nowait()
+                except queue.Empty:
+                    break
+                if extra is None:
+                    break
+                extra_text, extra_save, extra_fmt = extra
+                text = text.rstrip() + " " + extra_text.lstrip()
+                also_save = also_save or extra_save
+                file_format = extra_fmt  # use latest format
+
             try:
                 self._is_playing = True
 
@@ -350,6 +370,9 @@ class TTSController:
 
                 audio, sample_rate = result
 
+                if self._playback_stop.is_set():
+                    break
+
                 # Save to file if requested
                 if also_save:
                     import time
diff --git a/src/tts_provider.py b/src/tts_provider.py
index 420d3d3..4b40545 100644
--- a/src/tts_provider.py
+++ b/src/tts_provider.py
@@ -124,6 +124,13 @@ class ChatterboxProvider(BaseTTSProvider):
             return
 
         try:
+            if self.device == "cuda":
+                # Prevent cuDNN errors by enabling benchmark mode and setting
+                # matmul precision for tensor cores
+                torch.backends.cudnn.enabled = True
+                torch.backends.cudnn.benchmark = True
+                torch.set_float32_matmul_precision("high")
+
             if self.model_type == "multilingual":
                 from chatterbox.mtl_tts import ChatterboxMultilingualTTS
                 print(f"Loading multilingual ChatterboxTTS on {self.device}...")
@@ -147,7 +154,8 @@ class ChatterboxProvider(BaseTTSProvider):
             if "cuDNN" in error_str or "CUDA" in error_str:
                 raise RuntimeError(
                     f"CUDA/cuDNN error loading ChatterboxTTS:\n{e}\n\n"
-                    "Check that PyTorch CUDA version matches your GPU driver."
+                    "Try: torch.backends.cudnn.benchmark = True\n"
+                    "Or check that PyTorch CUDA version matches your GPU driver."
                 ) from e
             raise RuntimeError(f"Failed to load ChatterboxTTS: {e}") from e
 
@@ -254,9 +262,10 @@ class Qwen3TTSProvider(BaseTTSProvider):
             print(f"Loading Qwen3-TTS ({model_name}) on {self.device}...")
 
             # Determine dtype and attention implementation
-            dtype = torch.bfloat16 if self.device == "cuda" else torch.float32
+            # Use torch_dtype (not dtype) so Flash Attention 2 sees the dtype
+            compute_dtype = torch.bfloat16 if self.device == "cuda" else torch.float32
             kwargs = {"device_map": f"{self.device}:0" if self.device == "cuda" else self.device}
-            kwargs["dtype"] = dtype
+            kwargs["torch_dtype"] = compute_dtype
 
             # flash-attn is required
             try:
@@ -298,9 +307,9 @@ class Qwen3TTSProvider(BaseTTSProvider):
             model_name = self._get_model_name(clone=True)
             print(f"Loading Qwen3-TTS clone model ({model_name})...")
 
-            dtype = torch.bfloat16 if self.device == "cuda" else torch.float32
+            compute_dtype = torch.bfloat16 if self.device == "cuda" else torch.float32
             kwargs = {"device_map": f"{self.device}:0" if self.device == "cuda" else self.device}
-            kwargs["dtype"] = dtype
+            kwargs["torch_dtype"] = compute_dtype
             kwargs["attn_implementation"] = "flash_attention_2"
 
             self._clone_model = Qwen3TTSModel.from_pretrained(model_name, **kwargs)
diff --git a/src/whispering_ui/bridge.py b/src/whispering_ui/bridge.py
index 93de338..fe52c0b 100644
--- a/src/whispering_ui/bridge.py
+++ b/src/whispering_ui/bridge.py
@@ -276,6 +276,12 @@ class ProcessingBridge:
         self.ready[0] = False
         self.state.status_message = "Stopping..."
 
+        # Stop any in-flight TTS playback immediately
+        if self.tts_controller:
+            self.tts_controller.stop_playback()
+            self.state.tts_is_playing = False
+            self.state.tts_status_message = ""
+
         # Stop polling
         if self.poll_timer:
             self.poll_timer.deactivate()