minhahwang Copilot commited on
Commit
5bb5f05
·
1 Parent(s): d397f1a

fix: proper Mimi decode on HF Space — only disable dynamo on Windows, explicit device handling

Browse files

- Only set TORCHDYNAMO_DISABLE on Windows (HF Space has Triton)
- Use torch.no_grad() matching official demo pattern
- Warmup Mimi at load time to catch device errors early
- Explicit mimi_device for code tensors
- Log decode failures with device info for debugging
- Force format=wav for mic input, restore autoplay=True

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Files changed (2) hide show
  1. app.py +2 -1
  2. inference_lfm.py +85 -58
app.py CHANGED
@@ -554,6 +554,7 @@ with gr.Blocks(title="MomsVoice", css=css_code) as demo:
554
  sources=["microphone"], type="filepath",
555
  label="🎤 Ask your question — answer generates when you stop recording",
556
  show_label=True, streaming=False,
 
557
  elem_id="qa_audio_input",
558
  )
559
  question_text = gr.Textbox(visible=False)
@@ -1092,7 +1093,7 @@ with gr.Blocks(title="MomsVoice", css=css_code) as demo:
1092
  </div>
1093
  """
1094
  if answer_audio_path_out:
1095
- return gr.HTML(value=answer_html, visible=True), gr.Audio(value=str(answer_audio_path_out), visible=True)
1096
  return gr.HTML(value=answer_html, visible=True), gr.Audio(visible=False)
1097
 
1098
  submit_question_btn.click(
 
554
  sources=["microphone"], type="filepath",
555
  label="🎤 Ask your question — answer generates when you stop recording",
556
  show_label=True, streaming=False,
557
+ format="wav",
558
  elem_id="qa_audio_input",
559
  )
560
  question_text = gr.Textbox(visible=False)
 
1093
  </div>
1094
  """
1095
  if answer_audio_path_out:
1096
+ return gr.HTML(value=answer_html, visible=True), gr.Audio(value=str(answer_audio_path_out), visible=True, autoplay=True)
1097
  return gr.HTML(value=answer_html, visible=True), gr.Audio(visible=False)
1098
 
1099
  submit_question_btn.click(
inference_lfm.py CHANGED
@@ -10,7 +10,8 @@ import logging
10
  import os
11
  import threading
12
 
13
- os.environ.setdefault("TORCHDYNAMO_DISABLE", "1") # Mimi codec uses torch.compile; skip on Windows/no-Triton
 
14
 
15
  import torch
16
  import numpy as np
@@ -52,6 +53,16 @@ def _move_module(module, device: torch.device, dtype: torch.dtype):
52
  return module
53
 
54
 
 
 
 
 
 
 
 
 
 
 
55
  def _module_device(module, fallback: torch.device) -> torch.device:
56
  try:
57
  return next(module.parameters()).device
@@ -70,11 +81,12 @@ def get_lfm_model():
70
  device = _select_device()
71
  dtype = _select_dtype()
72
  logger.info("Loading %s on %s (%s)...", HF_REPO, device, dtype)
73
- _processor = LFM2AudioProcessor.from_pretrained(HF_REPO).eval()
74
  try:
75
  _model = LFM2AudioModel.from_pretrained(
76
  HF_REPO,
77
- torch_dtype=dtype if device.type == "cuda" else torch.float32,
 
78
  ).eval()
79
  except TypeError:
80
  _model = LFM2AudioModel.from_pretrained(HF_REPO).eval()
@@ -86,6 +98,12 @@ def get_lfm_model():
86
  if moved_model is not None:
87
  _model = moved_model
88
  _model = _model.eval()
 
 
 
 
 
 
89
  logger.info("LFM2.5-Audio loaded on %s.", _module_device(_model, device))
90
  return _processor, _model
91
 
@@ -102,63 +120,72 @@ def answer_question_audio(
102
  Accepts either audio input (child's voice) or text input.
103
  Returns (answer_text, audio_waveform_or_None, sample_rate).
104
  """
105
- from liquid_audio import ChatState, LFMModality
106
 
107
  processor, model = get_lfm_model()
108
-
109
- chat = ChatState(processor)
110
-
111
- # System prompt with story context
112
- chat.new_turn("system")
113
- system_prompt = (
114
- "You are a friendly storyteller answering a child's question about a bedtime story. "
115
- "Answer in 1-2 short, simple sentences using warm, age-appropriate language. "
116
- "Only use information from the story context below.\n\n"
117
- f"Story context:\n{story_context[:3000]}"
118
- )
119
- chat.add_text(system_prompt)
120
- chat.end_turn()
121
-
122
- # User turn — audio or text
123
- chat.new_turn("user")
124
- if question_audio_path:
125
- import librosa
126
- wav_np, sr = librosa.load(question_audio_path, sr=16000, mono=True)
127
- wav = torch.from_numpy(wav_np).unsqueeze(0).to(_module_device(model, _select_device()))
128
- sr = 16000
129
- chat.add_audio(wav, sr)
130
- elif question_text:
131
- chat.add_text(question_text)
132
- else:
133
- return "Please ask a question!", None, SAMPLE_RATE
134
- chat.end_turn()
135
-
136
- # Generate answer using Mimi streaming decode (official approach)
137
- chat.new_turn("assistant")
138
- text_out = []
139
- wav_chunks = []
140
-
141
- mimi = processor.mimi
142
-
143
- with GPU_INFERENCE_LOCK, torch.inference_mode(), mimi.streaming(1):
144
- for t in model.generate_interleaved(
145
- **chat,
146
- max_new_tokens=max_new_tokens,
147
- audio_temperature=1.0,
148
- audio_top_k=4,
149
- ):
150
- if t.numel() == 1:
151
- text_out.append(t)
152
- elif t.numel() == 8:
153
- # Skip EOS marker (all codes == 2048)
154
- if torch.all(t >= 2048):
155
- continue
156
- # Decode each audio frame via Mimi streaming codec
157
- try:
158
- wav_chunk = mimi.decode(t[None, :, None])
159
- wav_chunks.append(wav_chunk.cpu())
160
- except Exception:
161
- pass # Skip invalid frames
 
 
 
 
 
 
 
 
 
162
 
163
  # Decode text
164
  answer_text = ""
 
10
  import os
11
  import threading
12
 
13
+ if os.name == "nt":
14
+ os.environ.setdefault("TORCHDYNAMO_DISABLE", "1") # Windows typically lacks Triton for torch.compile
15
 
16
  import torch
17
  import numpy as np
 
53
  return module
54
 
55
 
56
+ def _first_parameter_device(module, fallback: torch.device) -> torch.device:
57
+ try:
58
+ return next(module.parameters()).device
59
+ except Exception:
60
+ try:
61
+ return next(module.buffers()).device
62
+ except Exception:
63
+ return fallback
64
+
65
+
66
  def _module_device(module, fallback: torch.device) -> torch.device:
67
  try:
68
  return next(module.parameters()).device
 
81
  device = _select_device()
82
  dtype = _select_dtype()
83
  logger.info("Loading %s on %s (%s)...", HF_REPO, device, dtype)
84
+ _processor = LFM2AudioProcessor.from_pretrained(HF_REPO, device=device).eval()
85
  try:
86
  _model = LFM2AudioModel.from_pretrained(
87
  HF_REPO,
88
+ dtype=dtype if device.type == "cuda" else torch.float32,
89
+ device=device,
90
  ).eval()
91
  except TypeError:
92
  _model = LFM2AudioModel.from_pretrained(HF_REPO).eval()
 
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
 
 
120
  Accepts either audio input (child's voice) or text input.
121
  Returns (answer_text, audio_waveform_or_None, sample_rate).
122
  """
123
+ from liquid_audio import ChatState
124
 
125
  processor, model = get_lfm_model()
126
+ device = _module_device(model, _select_device())
127
+ dtype = next(model.parameters()).dtype
128
+
129
+ with GPU_INFERENCE_LOCK, torch.no_grad():
130
+ chat = ChatState(processor, dtype=dtype)
131
+
132
+ # System prompt with story context
133
+ chat.new_turn("system")
134
+ system_prompt = (
135
+ "You are a friendly storyteller answering a child's question about a bedtime story. "
136
+ "Answer in 1-2 short, simple sentences using warm, age-appropriate language. "
137
+ "Only use information from the story context below.\n\n"
138
+ f"Story context:\n{story_context[:3000]}"
139
+ )
140
+ chat.add_text(system_prompt)
141
+ chat.end_turn()
142
+
143
+ # User turn — audio or text
144
+ chat.new_turn("user")
145
+ if question_audio_path:
146
+ import librosa
147
+ wav_np, sr = librosa.load(question_audio_path, sr=16000, mono=True)
148
+ wav = torch.from_numpy(wav_np).unsqueeze(0).to(device)
149
+ chat.add_audio(wav, sr)
150
+ elif question_text:
151
+ chat.add_text(question_text)
152
+ else:
153
+ return "Please ask a question!", None, SAMPLE_RATE
154
+ chat.end_turn()
155
+
156
+ # Generate answer using Mimi streaming decode (official approach)
157
+ chat.new_turn("assistant")
158
+ text_out = []
159
+ wav_chunks = []
160
+
161
+ mimi = processor.mimi.eval()
162
+ mimi_device = _first_parameter_device(mimi, device)
163
+
164
+ with mimi.streaming(1):
165
+ for t in model.generate_interleaved(
166
+ **chat,
167
+ max_new_tokens=max_new_tokens,
168
+ audio_temperature=1.0,
169
+ audio_top_k=4,
170
+ ):
171
+ if t.numel() == 1:
172
+ text_out.append(t)
173
+ elif t.numel() == 8:
174
+ # Skip EOS/invalid marker frames before they poison the stream.
175
+ if (t >= 2048).any() or (t < 0).any():
176
+ continue
177
+ codes = t.reshape(1, 8, 1).to(device=mimi_device, dtype=torch.long)
178
+ try:
179
+ wav_chunk = mimi.decode(codes)
180
+ wav_chunks.append(wav_chunk.cpu())
181
+ except Exception as exc:
182
+ logger.warning(
183
+ "Mimi decode skipped frame: %s (codes_device=%s, mimi_device=%s, shape=%s)",
184
+ exc,
185
+ codes.device,
186
+ mimi_device,
187
+ tuple(codes.shape),
188
+ )
189
 
190
  # Decode text
191
  answer_text = ""