33
44import gc
55import logging
6+ import os
67import random
78import numpy as np
89import torch
3839
3940logger = logging .getLogger (__name__ )
4041
41- # Log Turbo availability status at module load time
42+ # Log BF16 setting at module load so it's visible in startup logs
43+ # (BF16_ENABLED is resolved after logger is set up — logged in initialize_tts_model)
4244if TURBO_AVAILABLE :
4345 logger .info ("ChatterboxTurboTTS is available in the installed chatterbox package." )
4446else :
7981 "shush" ,
8082]
8183
84+ # --- BF16 optimization flag ---
85+ # TTS_BF16: controls whether T3/S3Gen are converted to bfloat16 and whether
86+ # autocast is used during inference.
87+ # auto (default) — enable only if torch.cuda.is_bf16_supported()
88+ # on / 1 / true — force-enable (assumes hardware supports it)
89+ # off / 0 / false — disable even if hardware supports it
90+ def _resolve_bf16_setting () -> bool :
91+ val = os .environ .get ("TTS_BF16" , "auto" ).strip ().lower ()
92+ if val in ("off" , "0" , "false" ):
93+ return False
94+ if val in ("on" , "1" , "true" ):
95+ return True
96+ # auto: detect at runtime
97+ if torch .cuda .is_available ():
98+ return torch .cuda .is_bf16_supported ()
99+ return False
100+
101+ BF16_ENABLED : bool = _resolve_bf16_setting ()
102+
82103# --- Global Module Variables ---
83104chatterbox_model : Optional [ChatterboxTTS ] = None
84105MODEL_LOADED : bool = False
90111loaded_model_type : Optional [str ] = None # "original" or "turbo"
91112loaded_model_class_name : Optional [str ] = None # "ChatterboxTTS" or "ChatterboxTurboTTS"
92113
114+ # Voice conditioning cache: avoids re-encoding the same voice file on every request.
115+ # Key: (resolved_path, file_mtime, exaggeration) — mtime invalidates if file changes.
116+ _conds_cache : dict = {}
117+
118+
119+ def _conds_cache_key (path : str , exaggeration : float ) -> tuple :
120+ try :
121+ mtime = os .path .getmtime (path )
122+ except OSError :
123+ mtime = 0.0
124+ return (path , mtime , exaggeration )
125+
93126
94127def set_seed (seed_value : int ):
95128 """
@@ -307,6 +340,10 @@ def load_model() -> bool:
307340
308341 model_device = resolved_device_str
309342 logger .info (f"Final device selection: { model_device } " )
343+ logger .info (
344+ f"BF16 optimization: { 'enabled' if BF16_ENABLED else 'disabled' } "
345+ f"(TTS_BF16={ os .environ .get ('TTS_BF16' , 'auto' )} )"
346+ )
310347
311348 # Get the model selector from config
312349 model_selector = config_manager .get_string ("model.repo_id" , "chatterbox-turbo" )
@@ -329,6 +366,17 @@ def load_model() -> bool:
329366 # Load the model using from_pretrained - handles HuggingFace downloads automatically
330367 chatterbox_model = model_class .from_pretrained (device = model_device )
331368
369+ # Convert T3 to bfloat16 if enabled.
370+ # Token generation is memory-bandwidth bound; bf16 halves bytes read per
371+ # forward pass. S3Gen is intentionally kept in float32 — it runs only
372+ # 2 CFM timesteps and bf16 causes token/mask size mismatches.
373+ if BF16_ENABLED :
374+ if hasattr (chatterbox_model , "t3" ):
375+ chatterbox_model .t3 = chatterbox_model .t3 .bfloat16 ()
376+ logger .info ("T3 model converted to bfloat16 for faster token generation." )
377+ else :
378+ logger .info ("BF16 optimization disabled (TTS_BF16=off or hardware unsupported)." )
379+
332380 # Store model metadata
333381 loaded_model_type = model_type
334382 loaded_model_class_name = model_class .__name__
@@ -423,25 +471,45 @@ def synthesize(
423471 f"language={ language } "
424472 )
425473
426- # Call the core model's generate method
427- # Multilingual model requires language_id parameter
428- if loaded_model_type == "multilingual" :
429- wav_tensor = chatterbox_model .generate (
430- text = text ,
431- language_id = language ,
432- audio_prompt_path = audio_prompt_path ,
433- temperature = temperature ,
434- exaggeration = exaggeration ,
435- cfg_weight = cfg_weight ,
436- )
437- else :
438- wav_tensor = chatterbox_model .generate (
439- text = text ,
440- audio_prompt_path = audio_prompt_path ,
441- temperature = temperature ,
442- exaggeration = exaggeration ,
443- cfg_weight = cfg_weight ,
444- )
474+ # Voice conditioning cache: skip re-encoding the same voice file.
475+ # Turbo ignores exaggeration in conds; others include it in the key.
476+ effective_prompt = audio_prompt_path
477+ conds_key = None
478+ if audio_prompt_path and hasattr (chatterbox_model , "conds" ):
479+ ex_for_key = 0.0 if loaded_model_type == "turbo" else exaggeration
480+ conds_key = _conds_cache_key (audio_prompt_path , ex_for_key )
481+ if conds_key in _conds_cache :
482+ chatterbox_model .conds = _conds_cache [conds_key ]
483+ effective_prompt = None # conds already set, skip prepare_conditionals
484+ logger .debug (f"Voice cache hit: { audio_prompt_path } " )
485+
486+ # Call the core model's generate method.
487+ # autocast promotes float32 inputs to bfloat16 to match T3/S3Gen weights,
488+ # keeping numerically sensitive ops (softmax, norms) in float32 automatically.
489+ with torch .autocast ("cuda" , dtype = torch .bfloat16 , enabled = BF16_ENABLED ):
490+ if loaded_model_type == "multilingual" :
491+ wav_tensor = chatterbox_model .generate (
492+ text = text ,
493+ language_id = language ,
494+ audio_prompt_path = effective_prompt ,
495+ temperature = temperature ,
496+ exaggeration = exaggeration ,
497+ cfg_weight = cfg_weight ,
498+ )
499+ else :
500+ wav_tensor = chatterbox_model .generate (
501+ text = text ,
502+ audio_prompt_path = effective_prompt ,
503+ temperature = temperature ,
504+ exaggeration = exaggeration ,
505+ cfg_weight = cfg_weight ,
506+ )
507+
508+ # Store conds in cache after first compute for this voice.
509+ if conds_key is not None and effective_prompt is not None :
510+ if chatterbox_model .conds is not None :
511+ _conds_cache [conds_key ] = chatterbox_model .conds
512+ logger .debug (f"Cached voice conditionals for: { audio_prompt_path } " )
445513
446514 # The ChatterboxTTS.generate method already returns a CPU tensor.
447515 return wav_tensor , chatterbox_model .sr
@@ -507,7 +575,7 @@ def reload_model() -> bool:
507575 Returns:
508576 bool: True if the new model loaded successfully, False otherwise.
509577 """
510- global chatterbox_model , MODEL_LOADED , model_device , loaded_model_type , loaded_model_class_name
578+ global chatterbox_model , MODEL_LOADED , model_device , loaded_model_type , loaded_model_class_name , _conds_cache
511579
512580 logger .info ("Initiating model hot-swap/reload sequence..." )
513581
@@ -517,10 +585,12 @@ def reload_model() -> bool:
517585 del chatterbox_model
518586 chatterbox_model = None
519587
520- # 2. Reset state flags
588+ # 2. Reset state flags and clear voice cache (conds are model-specific)
521589 MODEL_LOADED = False
522590 loaded_model_type = None
523591 loaded_model_class_name = None
592+ _conds_cache .clear ()
593+ logger .info ("Voice conditioning cache cleared." )
524594
525595 # 3. Force Python Garbage Collection
526596 gc .collect ()
0 commit comments