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.
This commit is contained in:
russell@unturf.com 2026-06-10 12:39:28 -04:00
parent a0d9e9a966
commit 585571c609
No known key found for this signature in database

View file

@ -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")