fix: allow Qwen3-TTS to work without flash-attn (eager attention fallback)
flash-attn fails to build on some systems but Qwen3-TTS works fine without it using eager attention (slower but functional). Changed flash-attn from required to recommended: backend detection no longer requires it, provider falls back to eager attention, and install script messaging reflects the non-critical nature of the failure. https://claude.ai/code/session_01965m1v3f1cK6gDV1JHioA8
+51 −25 · 4 files changed
-
INSTALL_TTS.md+12 −2 -
scripts/install.sh+7 −6 -
src/tts_provider.py+28 −16 -
src/whispering_ui/main.py+4 −1
@@ -29,15 +29,17 @@ source .venv/bin/activate # Step 1: Install Qwen3-TTS pip install qwen-tts -# Step 2: Install flash-attn (REQUIRED - must install AFTER torch) +# Step 2 (optional but recommended): Install flash-attn for faster inference # --no-build-isolation is needed because flash-attn needs torch at build time MAX_JOBS=4 pip install flash-attn --no-build-isolation # NOTE: Do NOT use "uv pip install flash-attn" - it will fail. # Use regular pip with --no-build-isolation instead. +# If flash-attn fails to build, Qwen3-TTS will still work using +# eager attention (slower but fully functional). # Verify -python -c "from qwen_tts import Qwen3TTSModel; import flash_attn; print('Qwen3-TTS OK')" +python -c "from qwen_tts import Qwen3TTSModel; print('Qwen3-TTS OK')" ``` ### Option B: Chatterbox TTS (ResembleAI) @@ -90,6 +92,14 @@ If you see `ModuleNotFoundError: No module named 'distutils'`: - Use `pip install chatterbox-tts --no-deps` (already handled by install.sh) - Or switch to Qwen3-TTS which doesn't have this issue +### flash-attn build failure (Qwen3-TTS) + +If `pip install flash-attn --no-build-isolation` fails to compile: +- **This is not critical** - Qwen3-TTS will fall back to eager attention (slower but works) +- flash-attn requires: CUDA toolkit headers, a compatible GPU, and torch already installed +- The app will print a note at startup if flash-attn is missing +- To retry later: `MAX_JOBS=4 pip install flash-attn --no-build-isolation` + ### No audio output - Check that `sounddevice` is installed: `pip install sounddevice` @@ -246,17 +246,18 @@ install_qwen3() { rm -rf "$TEMP_DIR" } - # Install flash-attn (required for Qwen3-TTS performance) + # Install flash-attn (recommended for Qwen3-TTS performance, not required) # Must use --no-build-isolation because flash-attn needs torch at build time # but doesn't declare it as a build dependency - echo -e "${YELLOW}Installing flash-attn (required for Qwen3-TTS)...${NC}" + echo -e "${YELLOW}Installing flash-attn (recommended for Qwen3-TTS performance)...${NC}" echo -e "${BLUE}This may take a few minutes to compile...${NC}" MAX_JOBS=4 $PYTHON_CMD -m pip install -U flash-attn --no-build-isolation 2>/dev/null && { echo -e "${GREEN}✓ flash-attn installed${NC}" } || { - echo -e "${RED}✗ flash-attn installation failed${NC}" - echo -e "${BLUE} Try manually: pip install flash-attn --no-build-isolation${NC}" - echo -e "${BLUE} Make sure torch is installed first and you have a compatible GPU + CUDA toolkit.${NC}" + echo -e "${YELLOW}⚠ flash-attn installation failed (not critical)${NC}" + echo -e "${BLUE} Qwen3-TTS will still work using eager attention (slower).${NC}" + echo -e "${BLUE} To try again later: pip install flash-attn --no-build-isolation${NC}" + echo -e "${BLUE} Requires: compatible GPU + CUDA toolkit + torch already installed.${NC}" } # Verify installation @@ -264,7 +265,7 @@ install_qwen3() { echo -e "${YELLOW} Note: Qwen3-TTS import test failed. It may still work at runtime.${NC}" } $PYTHON_CMD -c "import flash_attn; print(' ✓ flash-attn import successful')" 2>/dev/null || { - echo -e "${YELLOW} Warning: flash-attn not available. Qwen3-TTS will be slower.${NC}" + echo -e "${YELLOW} Note: flash-attn not available. Qwen3-TTS will use eager attention (slower but functional).${NC}" } echo } @@ -36,10 +36,11 @@ def get_available_backends() -> dict[str, bool]: except (ImportError, ModuleNotFoundError, Exception): backends["chatterbox"] = False - # Check Qwen3-TTS - verify both the model class and flash-attn are importable + # Check Qwen3-TTS - only require qwen_tts itself. + # flash-attn is recommended for performance but NOT required; + # Qwen3-TTS falls back to manual PyTorch attention without it. try: from qwen_tts import Qwen3TTSModel # noqa: F401 - import flash_attn # noqa: F401 backends["qwen3"] = True except (ImportError, ModuleNotFoundError, Exception): backends["qwen3"] = False @@ -47,6 +48,15 @@ def get_available_backends() -> dict[str, bool]: return backends +def is_flash_attn_available() -> bool: + """Check if flash-attn is installed (recommended for Qwen3-TTS performance).""" + try: + import flash_attn # noqa: F401 + return True + except (ImportError, ModuleNotFoundError, Exception): + return False + + # --------------------------------------------------------------------------- # Base provider # --------------------------------------------------------------------------- @@ -240,17 +250,13 @@ class Qwen3TTSProvider(BaseTTSProvider): kwargs = {"device_map": f"{self.device}:0" if self.device == "cuda" else self.device} kwargs["dtype"] = dtype - # flash-attn is required for Qwen3-TTS - try: - import flash_attn # noqa: F401 + # Use flash-attn if available (faster), otherwise fall back to eager + if is_flash_attn_available(): kwargs["attn_implementation"] = "flash_attention_2" - except ImportError: - raise ImportError( - "flash-attn is required for Qwen3-TTS but is not installed.\n" - "Install with: pip install flash-attn --no-build-isolation\n" - "(torch must be installed first)\n" - "Or run: ./scripts/install.sh --tts=qwen3" - ) + else: + print("Note: flash-attn not installed. Using eager attention (slower).") + print(" Install for better performance: pip install flash-attn --no-build-isolation") + kwargs["attn_implementation"] = "eager" self.model = Qwen3TTSModel.from_pretrained(model_name, **kwargs) self._initialized = True @@ -268,7 +274,6 @@ class Qwen3TTSProvider(BaseTTSProvider): try: from qwen_tts import Qwen3TTSModel - import flash_attn # noqa: F401 model_name = self._get_model_name(clone=True) print(f"Loading Qwen3-TTS clone model ({model_name})...") @@ -276,15 +281,20 @@ class Qwen3TTSProvider(BaseTTSProvider): 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["attn_implementation"] = "flash_attention_2" + + if is_flash_attn_available(): + kwargs["attn_implementation"] = "flash_attention_2" + else: + kwargs["attn_implementation"] = "eager" self._clone_model = Qwen3TTSModel.from_pretrained(model_name, **kwargs) print("Qwen3-TTS clone model loaded") except ImportError as e: raise ImportError( - "flash-attn is required for Qwen3-TTS.\n" - "Install with: pip install flash-attn --no-build-isolation" + "Qwen3-TTS is not installed.\n" + "Install with: pip install qwen-tts\n" + "Or run: ./scripts/install.sh --tts=qwen3" ) from e except Exception as e: raise RuntimeError(f"Failed to load Qwen3-TTS clone model: {e}") from e @@ -420,6 +430,8 @@ if __name__ == "__main__": for name, available in backends.items(): status = "INSTALLED" if available else "not installed" print(f" {name}: {status}") + flash_ok = is_flash_attn_available() + print(f" flash-attn: {'INSTALLED' if flash_ok else 'not installed (Qwen3-TTS will use eager attention)'}") # Test with first available backend for name, available in backends.items(): @@ -90,7 +90,7 @@ def main(): # tts_available=True means the TTS UI section is visible. # tts_backends_available tracks which engines are actually installed. try: - from tts_provider import get_available_backends + from tts_provider import get_available_backends, is_flash_attn_available from tts_controller import TTSController backends = get_available_backends() @@ -122,6 +122,9 @@ def main(): print(f" TTS backend {name}: {status}") if avail: has_any = True + if backends.get("qwen3"): + flash_ok = is_flash_attn_available() + print(f" flash-attn: {'available' if flash_ok else 'not installed (Qwen3 will use eager attention)'}") if not has_any: state.tts_enabled = False # Don't auto-enable if nothing is installed print(" No TTS backends installed.")