sshtel commited on
Commit
3cfd932
·
unverified ·
2 Parent(s): 43cb842871cd89

Merge pull request #8 from MomsVoiceAI/fix/answer-audio

Browse files

fix: Answer audio - use Mimi streaming decoder + flash-attn

Files changed (2) hide show
  1. app.py +15 -4
  2. inference_lfm.py +52 -4
app.py CHANGED
@@ -420,10 +420,21 @@ print("[MomsVoice] Creating stock voice profile (vivian)...")
420
  _ensure_stock_voice_profile()
421
  print("[MomsVoice] Stock voice profile ready.")
422
 
423
- print("[MomsVoice] Preloading LFM2.5-Audio-1.5B Q&A model...")
424
- from inference_lfm import get_lfm_model
425
- get_lfm_model()
426
- print("[MomsVoice] LFM Q&A model ready. All models preloaded.")
 
 
 
 
 
 
 
 
 
 
 
427
 
428
 
429
  # Gradio Application Core setup
 
420
  _ensure_stock_voice_profile()
421
  print("[MomsVoice] Stock voice profile ready.")
422
 
423
+ # Load LFM Q&A model asynchronously — users typically start with Play tab,
424
+ # so LFM can load in the background while they browse stories.
425
+ import threading
426
+
427
+ def _load_lfm_background():
428
+ from inference_lfm import get_lfm_model, warmup_lfm
429
+ print("[MomsVoice] (background) Loading LFM2.5-Audio-1.5B Q&A model...")
430
+ get_lfm_model()
431
+ print("[MomsVoice] (background) LFM loaded. Running warmup generation...")
432
+ warmup_lfm()
433
+ print("[MomsVoice] (background) LFM Q&A model ready (compiled + warmed up).")
434
+
435
+ _lfm_thread = threading.Thread(target=_load_lfm_background, daemon=True)
436
+ _lfm_thread.start()
437
+ print("[MomsVoice] LFM loading in background thread. App starting now.")
438
 
439
 
440
  # Gradio Application Core setup
inference_lfm.py CHANGED
@@ -70,6 +70,16 @@ def _module_device(module, fallback: torch.device) -> torch.device:
70
  return getattr(module, "device", fallback)
71
 
72
 
 
 
 
 
 
 
 
 
 
 
73
  def get_lfm_model():
74
  """Load LFM2.5-Audio-1.5B model. Cached after first call."""
75
  global _processor, _model
@@ -98,21 +108,59 @@ def get_lfm_model():
98
  if moved_model is not None:
99
  _model = moved_model
100
  _model = _model.eval()
 
 
 
 
 
 
 
101
  # Force lazy Mimi construction after the processor is on the target device,
102
  # and fail early if the streaming decoder cannot run there.
103
  mimi = _processor.mimi.eval()
104
- if device.type == "cuda":
105
- with torch.no_grad(), mimi.streaming(1):
106
- mimi.decode(torch.randint(0, 2048, (1, 8, 1), device=device))
107
  logger.info("LFM2.5-Audio loaded on %s.", _module_device(_model, device))
108
  return _processor, _model
109
 
110
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
111
  def answer_question_audio(
112
  question_audio_path: str | None = None,
113
  question_text: str | None = None,
114
  story_context: str = "",
115
- max_new_tokens: int = 200,
116
  ) -> tuple[str, np.ndarray | None, int]:
117
  """
118
  Answer a question about the story using LFM2.5-Audio end-to-end.
 
70
  return getattr(module, "device", fallback)
71
 
72
 
73
+ def _reset_mimi_streaming_state(mimi) -> None:
74
+ """Best-effort reset of Mimi's internal streaming buffers."""
75
+ for attr in ("_streaming_state", "_states"):
76
+ if hasattr(mimi, attr):
77
+ try:
78
+ setattr(mimi, attr, None)
79
+ except Exception:
80
+ pass
81
+
82
+
83
  def get_lfm_model():
84
  """Load LFM2.5-Audio-1.5B model. Cached after first call."""
85
  global _processor, _model
 
108
  if moved_model is not None:
109
  _model = moved_model
110
  _model = _model.eval()
111
+ # torch.compile for inference speedup on Linux/CUDA (skip on Windows)
112
+ if device.type == "cuda" and os.name != "nt":
113
+ try:
114
+ _model = torch.compile(_model, mode="reduce-overhead", dynamic=True)
115
+ logger.info("torch.compile applied to LFM model (reduce-overhead, dynamic).")
116
+ except Exception as exc:
117
+ logger.warning("torch.compile failed, using eager mode: %s", exc)
118
  # Force lazy Mimi construction after the processor is on the target device,
119
  # and fail early if the streaming decoder cannot run there.
120
  mimi = _processor.mimi.eval()
121
+ _reset_mimi_streaming_state(mimi)
 
 
122
  logger.info("LFM2.5-Audio loaded on %s.", _module_device(_model, device))
123
  return _processor, _model
124
 
125
 
126
+ def warmup_lfm():
127
+ """Run a dummy generation to trigger torch.compile and warm CUDA graphs.
128
+ Call once at startup after get_lfm_model() to eliminate first-query latency.
129
+ """
130
+ try:
131
+ processor, model = get_lfm_model()
132
+ from liquid_audio import ChatState
133
+ device = _module_device(model, _select_device())
134
+ dtype = next(model.parameters()).dtype
135
+ mimi = processor.mimi.eval()
136
+
137
+ _reset_mimi_streaming_state(mimi)
138
+ with GPU_INFERENCE_LOCK, torch.no_grad():
139
+ chat = ChatState(processor, dtype=dtype)
140
+ chat.new_turn("system")
141
+ chat.add_text("You are a helpful assistant.")
142
+ chat.end_turn()
143
+ chat.new_turn("user")
144
+ chat.add_text("Hi")
145
+ chat.end_turn()
146
+ chat.new_turn("assistant")
147
+
148
+ with mimi.streaming(1):
149
+ for t in model.generate_interleaved(
150
+ **chat, max_new_tokens=10, audio_temperature=1.0, audio_top_k=4,
151
+ ):
152
+ pass # Just trigger compilation, discard output
153
+ _reset_mimi_streaming_state(mimi)
154
+ logger.info("LFM warmup complete — torch.compile graphs cached.")
155
+ except Exception as exc:
156
+ logger.warning("LFM warmup failed (non-fatal): %s", exc)
157
+
158
+
159
  def answer_question_audio(
160
  question_audio_path: str | None = None,
161
  question_text: str | None = None,
162
  story_context: str = "",
163
+ max_new_tokens: int = 100,
164
  ) -> tuple[str, np.ndarray | None, int]:
165
  """
166
  Answer a question about the story using LFM2.5-Audio end-to-end.