From 585571c609c0c9083737679c0a85b08df9c0af2d Mon Sep 17 00:00:00 2001 From: "russell@unturf.com" Date: Wed, 10 Jun 2026 12:39:28 -0400 Subject: [PATCH] speech.py --engines: default to F5 only; 'all' opts into every engine Previous default ('--engines unset' == all engines enabled) made it too easy for stray tts-1-qwen requests to silently load 10 GiB of weights onto a GPU shared with an LLM. Flipped: the lean F5-only allowlist is now the default, and operators explicitly opt into more. python speech.py # default: {tts-1-f5} python speech.py --engines f5,piper # F5 + Piper python speech.py --engines all # every importable engine (old default) python speech.py --engines qwen,f5 # back to Qwen + F5 for GPU servers Implementation: _parse_engines_env() now returns set(DEFAULT_ENGINES) when SPEECH_ENABLED_ENGINES is unset, and treats the literal 'all' as a None sentinel (no allowlist applied). DEFAULT_ENGINES = frozenset({'tts-1-f5'}). Workers still pick this up via SPEECH_ENABLED_ENGINES env var; __main__ only sets the var when --engines was passed, so default-path workers re-parse the empty env -> DEFAULT_ENGINES path. --- speech.py | 31 ++++++++++++++++++++++--------- 1 file changed, 22 insertions(+), 9 deletions(-) diff --git a/speech.py b/speech.py index c576abd..315d7e0 100755 --- a/speech.py +++ b/speech.py @@ -82,10 +82,18 @@ ENGINE_SHORT_TO_MODEL = { } ALL_MODEL_IDS = set(ENGINE_SHORT_TO_MODEL.values()) +# Lean default — F5 alone fits comfortably on a GPU shared with an LLM. +# Override with --engines (or SPEECH_ENABLED_ENGINES env var): +# --engines f5,piper -> {tts-1-f5, tts-1} +# --engines all -> no allowlist (every importable engine enabled) +DEFAULT_ENGINES = frozenset({'tts-1-f5'}) + def _parse_engines_env(): raw = os.environ.get('SPEECH_ENABLED_ENGINES', '').strip() if not raw: - return None + return set(DEFAULT_ENGINES) + if raw.lower() == 'all': + return None # explicit opt-in to every engine allowed = set() for e in (s.strip() for s in raw.split(',')): if not e: @@ -98,11 +106,11 @@ def _parse_engines_env(): raise ValueError( f"SPEECH_ENABLED_ENGINES: unknown engine '{e}' " f"(valid short names: {', '.join(sorted(ENGINE_SHORT_TO_MODEL))}, " - f"or full tts-1-* ids)" + f"full tts-1-* ids, or the keyword 'all')" ) return allowed -ENABLED_ENGINES = _parse_engines_env() # None = all enabled; set = allowlist +ENABLED_ENGINES = _parse_engines_env() # None = unrestricted; set = allowlist def is_engine_available(model_id): """Whether the TTS engine for a given model_id is loadable in this process. @@ -1539,9 +1547,8 @@ if __name__ == "__main__": help="Comma-separated allowlist of TTS engines to enable. " "Short names: f5, qwen, piper, xtts, silero, kokoro " "(or full model IDs like tts-1-f5). " - "Default: all engines enabled. " - "Example: --engines f5,piper keeps speech lean enough to " - "share GPU with an LLM server.") + "Pass --engines all to enable every importable engine. " + "Default: f5 only (lean, fits on a GPU shared with an LLM).") args = parser.parse_args() @@ -1549,7 +1556,9 @@ if __name__ == "__main__": logger.add(sink=sys.stderr, level=args.log_level) # Propagate --engines to uvicorn workers via env var (workers re-import - # this module + read SPEECH_ENABLED_ENGINES into ENABLED_ENGINES). + # this module + read SPEECH_ENABLED_ENGINES into ENABLED_ENGINES at + # module load). When --engines is omitted, the env var stays unset + # and _parse_engines_env() falls through to DEFAULT_ENGINES (f5 only). if args.engines: os.environ['SPEECH_ENABLED_ENGINES'] = args.engines try: @@ -1557,9 +1566,13 @@ if __name__ == "__main__": except ValueError as e: logger.error(str(e)) sys.exit(2) - logger.info(f"--engines: restricting to {sorted(ENABLED_ENGINES)}") + if ENABLED_ENGINES is None: + logger.info("--engines all: every importable engine enabled") + else: + logger.info(f"--engines: restricting to {sorted(ENABLED_ENGINES)}") else: - logger.info("All engines enabled (no --engines restriction)") + logger.info(f"Default engine allowlist: {sorted(ENABLED_ENGINES)} " + f"(pass --engines all or --engines f5,qwen,... to override)") if args.preload and not XTTS_AVAILABLE: logger.error("Cannot preload XTTS model - XTTS dependencies not available")