Taejin commited on
Commit
6ee38ff
·
1 Parent(s): 02f5805

Lazy load models for ZeroGPU Space startup

Browse files
Files changed (1) hide show
  1. app.py +71 -16
app.py CHANGED
@@ -20,6 +20,17 @@ import html
20
  import threading
21
  import traceback
22
 
 
 
 
 
 
 
 
 
 
 
 
23
  # Global variables for controlling the flow of the program
24
  MAX_LINES_DISPLAYED = 10
25
  MAX_NUM_OF_SPEAKERS = 8
@@ -30,6 +41,7 @@ FILE_PROCESSING_STOPPED = threading.Event()
30
  FILE_PROCESSING_STOPPED.set()
31
  MICROPHONE_PROCESSING_ENABLED: bool = False
32
  MICROPHONE_PROCESSING_LOCK = threading.Lock()
 
33
  CONFIG_RENEWED_FLAG: bool = False
34
  START_TIME_OFFSET_EXAMPLES: float = -1.6 # seconds
35
  START_TIME_OFFSET_FILE_UPLOAD: float = -0.8 # seconds
@@ -147,13 +159,26 @@ config_file = MultitalkerTranscriptionConfig(
147
  audio_pre_loading=True,
148
  )
149
 
150
- # Initialize the streaming session
151
- session_mic= StreamingSession()
152
- session_mic.setup_streaming_session(config_mic)
153
  session_file = StreamingSession()
154
- session_file.setup_streaming_session(config_file)
 
155
  MAX_NUM_OF_SPEAKERS = min(config_mic.max_num_of_spks, config_file.max_num_of_spks)
156
 
 
 
 
 
 
 
 
 
 
 
 
 
157
  def render_diarization_state(diar_state, config):
158
  """Render Sortformer's speaker cache, FIFO, and current chunk predictions."""
159
  num_speakers = int(config.max_num_of_spks)
@@ -200,10 +225,12 @@ def format_transcript(text):
200
  if not text:
201
  return ""
202
 
203
- single_speaker_mode = (
204
- session_mic.multispk_asr_streamer._single_speaker_mode
205
- or session_file.multispk_asr_streamer._single_speaker_mode
206
- )
 
 
207
  current_color = "#76B900" if single_speaker_mode else "#A8A8A8"
208
 
209
  for raw_line in (line for line in text.strip().splitlines() if line.strip()):
@@ -356,6 +383,7 @@ def update_config_for_file(evt: gr.SelectData, state=None):
356
 
357
  def file_processing(filepath, state, file_upload=False, start_time_offset=0.0):
358
  """Handles file uploads and processes the audio in chunks with real-time simulation."""
 
359
  print(f"[DEBUG] file_processing() FIRST LINE: filepath: {filepath}, state[is_playing]: {state.get('is_playing', False)}, file_upload: {file_upload}")
360
  if not filepath or state.get("should_stop", False):
361
  yield state, state["diar_html"], format_transcript(state.get("full_transcript", ""))
@@ -499,6 +527,7 @@ async def show_progress_bar_with_sleep(filepath, num_steps=500, seconds_per_mb=0
499
 
500
  async def file_processing_example_click_async(filepath, state, start_time_offset=0.0):
501
  """Async wrapper for file_processing with pause/resume support (doesn't update audio component)."""
 
502
  state["should_stop"] = False
503
  state["is_playing"] = False
504
  state["pause_start_time"] = None
@@ -614,12 +643,14 @@ def file_processing_example_click(filepath, state, start_time_offset=0.0):
614
  loop.close()
615
  FILE_PROCESSING_STOPPED.set()
616
 
 
617
  def file_processing_example_click_upload(filepath, state):
618
  """Wrapper for file upload with upload offset."""
619
  # file_processing_example_click is a generator, so yield from it
620
  for result in file_processing_example_click(filepath, state, start_time_offset=START_TIME_OFFSET_FILE_UPLOAD):
621
  yield result
622
 
 
623
  def file_processing_example_click_examples(filepath, state):
624
  """Wrapper for examples with examples offset."""
625
  # file_processing_example_click is a generator, so yield from it
@@ -665,6 +696,7 @@ async def file_processing_upload_event_async(filepath, state):
665
 
666
  yield s, html_diar, transcript
667
 
 
668
  def file_processing_upload_event(filepath, state):
669
  """Wrapper that runs the async function synchronously for Gradio compatibility."""
670
  print(f"[DEBUG] First line of file_processing_upload_event(): filepath: {filepath}, state[is_playing]: {state.get('is_playing', False)}")
@@ -730,6 +762,7 @@ def example_selection_stop_file_processing(state, filepath):
730
 
731
  def file_upload_stop_file_processing(state):
732
  """Stops file processing when audio is cleared and prepares for new file."""
 
733
  state["mode"] = "idle"
734
  state["last_processed_file"] = None
735
  state["is_playing"] = False
@@ -745,6 +778,7 @@ def file_upload_stop_file_processing(state):
745
 
746
  def stop_file_processing_example(state):
747
  """Stops file processing when audio is cleared."""
 
748
  state["mode"] = "idle"
749
  state["last_processed_file"] = None
750
  print(f"[DEBUG] global RUDE_LOOP_STOP is set to True")
@@ -786,8 +820,10 @@ def hide_examples_panel():
786
  """Collapse the example picker after an example is selected."""
787
  return False, gr.update(visible=False), "Choose Example"
788
 
 
789
  def reset_on_tab_switch(state):
790
  """Stop active processing and recreate both streaming instances."""
 
791
  global MICROPHONE_PROCESSING_ENABLED, RUDE_LOOP_STOP
792
  RUDE_LOOP_STOP = True
793
  MICROPHONE_PROCESSING_ENABLED = False
@@ -836,8 +872,10 @@ def trigger_end_of_file_flag(state):
836
  print(f"[DEBUG] END OF FILE TRIGGERED trigger_end_of_file_flag(): end_of_file set to {state['end_of_file']}")
837
  return state
838
 
 
839
  def start_stream_processing(state):
840
  """Initiates stream processing mode."""
 
841
  global MICROPHONE_PROCESSING_ENABLED
842
  print(f"[DEBUG] start_stream_processing() CALLED - mode before: {state.get('mode', 'None')}")
843
  MICROPHONE_PROCESSING_ENABLED = True
@@ -853,8 +891,10 @@ def start_stream_processing(state):
853
  print(f"[DEBUG] start_stream_processing() RETURNING - mode after: '{state['mode']}'")
854
  return state, gr.Button(value="Pause", visible=True)
855
 
 
856
  def toggle_microphone_pause(state):
857
  """Pause or resume microphone transcription while recording continues."""
 
858
  if state.get("mode") != "stream":
859
  return (
860
  state,
@@ -883,8 +923,10 @@ def toggle_microphone_pause(state):
883
  format_transcript(state.get("full_transcript", "")),
884
  )
885
 
 
886
  def stop_stream_processing(state):
887
  """Stops stream processing mode."""
 
888
  global MICROPHONE_PROCESSING_ENABLED
889
  print(f"[DEBUG] stop_stream_processing() CALLED - mode before: {state.get('mode', 'None')}")
890
  MICROPHONE_PROCESSING_ENABLED = False
@@ -907,8 +949,10 @@ def stop_stream_processing(state):
907
 
908
  return state, diar_html_output, transcript_textbox_html, gr.Button(value="Pause", visible=False)
909
 
 
910
  def stream_processing(state, audio_data):
911
  """Handles microphone streaming."""
 
912
  # Initialize state if it's None or doesn't have required fields
913
  if state is None:
914
  state = {"mode": "stream", "full_transcript": "", "diar_html_initial": ""}
@@ -1015,6 +1059,7 @@ def reload_models_with_checkbox_update(
1015
  )
1016
  return html_string, single_speaker_mode
1017
 
 
1018
  def reload_models(
1019
  audio_pre_loading,
1020
  streaming_chunk_size,
@@ -1025,6 +1070,7 @@ def reload_models(
1025
  sent_break_sec,
1026
  ):
1027
  """Reloads the streaming session with new configurations."""
 
1028
  try:
1029
  num_speakers = min(int(num_speakers), MAX_NUM_OF_SPEAKERS)
1030
  if single_speaker_mode:
@@ -1106,13 +1152,22 @@ def get_text_box_html(
1106
 
1107
  def load_and_reset():
1108
  """Resets the UI state on page load."""
1109
- AUDIO_PRE_LOADING = session_file._audio_pre_loading
1110
- STREAMING_CHUNK_SIZE = session_file.multispk_asr_streamer._att_context_size[1]
1111
- SINGLE_SPEAKER_MODE = 'ON' if session_file.multispk_asr_streamer._single_speaker_mode else 'OFF'
1112
- NUMBER_OF_SPEAKERS = f"{session_file.multispk_asr_streamer._max_num_of_spks}"
1113
- TEXT_BREAK_SILENCE = f"{session_file.multispk_asr_streamer._sent_break_sec} seconds"
1114
- CACHE_GATING_STATUS = 'ON' if session_file.multispk_asr_streamer._cache_gating else 'OFF'
1115
- BINARY_DIAR_PREDICTION_STATUS = 'ON' if session_file.multispk_asr_streamer._binary_diar_preds else 'OFF'
 
 
 
 
 
 
 
 
 
1116
 
1117
  status_textbox_html= get_text_box_html(
1118
  audio_pre_loading=AUDIO_PRE_LOADING,
@@ -1124,7 +1179,7 @@ def load_and_reset():
1124
  text_break_silence=TEXT_BREAK_SILENCE,
1125
  )
1126
  diar_html_output = render_diarization_state(None, config_file)
1127
- transcript_textbox = "<p style='text-align: center; font-size:20px;'><b>No transcription yet.</b></p>"
1128
  return {"should_stop": False,
1129
  "stream_step_num": 0,
1130
  "microphone_paused": False,
 
20
  import threading
21
  import traceback
22
 
23
+ try:
24
+ import spaces
25
+ except ImportError:
26
+ class _SpacesNoop:
27
+ def GPU(self, *args, **kwargs):
28
+ def decorator(fn):
29
+ return fn
30
+ return decorator
31
+
32
+ spaces = _SpacesNoop()
33
+
34
  # Global variables for controlling the flow of the program
35
  MAX_LINES_DISPLAYED = 10
36
  MAX_NUM_OF_SPEAKERS = 8
 
41
  FILE_PROCESSING_STOPPED.set()
42
  MICROPHONE_PROCESSING_ENABLED: bool = False
43
  MICROPHONE_PROCESSING_LOCK = threading.Lock()
44
+ GPU_DURATION_SECONDS = int(os.environ.get("SPACES_GPU_DURATION", "120"))
45
  CONFIG_RENEWED_FLAG: bool = False
46
  START_TIME_OFFSET_EXAMPLES: float = -1.6 # seconds
47
  START_TIME_OFFSET_FILE_UPLOAD: float = -0.8 # seconds
 
159
  audio_pre_loading=True,
160
  )
161
 
162
+ # Initialize streaming session containers. Models are loaded lazily so ZeroGPU
163
+ # Spaces can start the app before CUDA is allocated to a request.
164
+ session_mic = StreamingSession()
165
  session_file = StreamingSession()
166
+ MODEL_INIT_LOCK = threading.Lock()
167
+ MODELS_INITIALIZED = False
168
  MAX_NUM_OF_SPEAKERS = min(config_mic.max_num_of_spks, config_file.max_num_of_spks)
169
 
170
+
171
+ def ensure_sessions_initialized(reload=False):
172
+ """Load or reload ASR/diarization sessions on demand."""
173
+ global MODELS_INITIALIZED
174
+ with MODEL_INIT_LOCK:
175
+ should_reload = reload and MODELS_INITIALIZED
176
+ if MODELS_INITIALIZED and not reload:
177
+ return
178
+ session_mic.setup_streaming_session(config_mic, reload=should_reload)
179
+ session_file.setup_streaming_session(config_file, reload=should_reload)
180
+ MODELS_INITIALIZED = True
181
+
182
  def render_diarization_state(diar_state, config):
183
  """Render Sortformer's speaker cache, FIFO, and current chunk predictions."""
184
  num_speakers = int(config.max_num_of_spks)
 
225
  if not text:
226
  return ""
227
 
228
+ single_speaker_mode = False
229
+ if MODELS_INITIALIZED:
230
+ single_speaker_mode = (
231
+ session_mic.multispk_asr_streamer._single_speaker_mode
232
+ or session_file.multispk_asr_streamer._single_speaker_mode
233
+ )
234
  current_color = "#76B900" if single_speaker_mode else "#A8A8A8"
235
 
236
  for raw_line in (line for line in text.strip().splitlines() if line.strip()):
 
383
 
384
  def file_processing(filepath, state, file_upload=False, start_time_offset=0.0):
385
  """Handles file uploads and processes the audio in chunks with real-time simulation."""
386
+ ensure_sessions_initialized()
387
  print(f"[DEBUG] file_processing() FIRST LINE: filepath: {filepath}, state[is_playing]: {state.get('is_playing', False)}, file_upload: {file_upload}")
388
  if not filepath or state.get("should_stop", False):
389
  yield state, state["diar_html"], format_transcript(state.get("full_transcript", ""))
 
527
 
528
  async def file_processing_example_click_async(filepath, state, start_time_offset=0.0):
529
  """Async wrapper for file_processing with pause/resume support (doesn't update audio component)."""
530
+ ensure_sessions_initialized()
531
  state["should_stop"] = False
532
  state["is_playing"] = False
533
  state["pause_start_time"] = None
 
643
  loop.close()
644
  FILE_PROCESSING_STOPPED.set()
645
 
646
+ @spaces.GPU(duration=GPU_DURATION_SECONDS)
647
  def file_processing_example_click_upload(filepath, state):
648
  """Wrapper for file upload with upload offset."""
649
  # file_processing_example_click is a generator, so yield from it
650
  for result in file_processing_example_click(filepath, state, start_time_offset=START_TIME_OFFSET_FILE_UPLOAD):
651
  yield result
652
 
653
+ @spaces.GPU(duration=GPU_DURATION_SECONDS)
654
  def file_processing_example_click_examples(filepath, state):
655
  """Wrapper for examples with examples offset."""
656
  # file_processing_example_click is a generator, so yield from it
 
696
 
697
  yield s, html_diar, transcript
698
 
699
+ @spaces.GPU(duration=GPU_DURATION_SECONDS)
700
  def file_processing_upload_event(filepath, state):
701
  """Wrapper that runs the async function synchronously for Gradio compatibility."""
702
  print(f"[DEBUG] First line of file_processing_upload_event(): filepath: {filepath}, state[is_playing]: {state.get('is_playing', False)}")
 
762
 
763
  def file_upload_stop_file_processing(state):
764
  """Stops file processing when audio is cleared and prepares for new file."""
765
+ ensure_sessions_initialized()
766
  state["mode"] = "idle"
767
  state["last_processed_file"] = None
768
  state["is_playing"] = False
 
778
 
779
  def stop_file_processing_example(state):
780
  """Stops file processing when audio is cleared."""
781
+ ensure_sessions_initialized()
782
  state["mode"] = "idle"
783
  state["last_processed_file"] = None
784
  print(f"[DEBUG] global RUDE_LOOP_STOP is set to True")
 
820
  """Collapse the example picker after an example is selected."""
821
  return False, gr.update(visible=False), "Choose Example"
822
 
823
+ @spaces.GPU(duration=GPU_DURATION_SECONDS)
824
  def reset_on_tab_switch(state):
825
  """Stop active processing and recreate both streaming instances."""
826
+ ensure_sessions_initialized()
827
  global MICROPHONE_PROCESSING_ENABLED, RUDE_LOOP_STOP
828
  RUDE_LOOP_STOP = True
829
  MICROPHONE_PROCESSING_ENABLED = False
 
872
  print(f"[DEBUG] END OF FILE TRIGGERED trigger_end_of_file_flag(): end_of_file set to {state['end_of_file']}")
873
  return state
874
 
875
+ @spaces.GPU(duration=GPU_DURATION_SECONDS)
876
  def start_stream_processing(state):
877
  """Initiates stream processing mode."""
878
+ ensure_sessions_initialized()
879
  global MICROPHONE_PROCESSING_ENABLED
880
  print(f"[DEBUG] start_stream_processing() CALLED - mode before: {state.get('mode', 'None')}")
881
  MICROPHONE_PROCESSING_ENABLED = True
 
891
  print(f"[DEBUG] start_stream_processing() RETURNING - mode after: '{state['mode']}'")
892
  return state, gr.Button(value="Pause", visible=True)
893
 
894
+ @spaces.GPU(duration=GPU_DURATION_SECONDS)
895
  def toggle_microphone_pause(state):
896
  """Pause or resume microphone transcription while recording continues."""
897
+ ensure_sessions_initialized()
898
  if state.get("mode") != "stream":
899
  return (
900
  state,
 
923
  format_transcript(state.get("full_transcript", "")),
924
  )
925
 
926
+ @spaces.GPU(duration=GPU_DURATION_SECONDS)
927
  def stop_stream_processing(state):
928
  """Stops stream processing mode."""
929
+ ensure_sessions_initialized()
930
  global MICROPHONE_PROCESSING_ENABLED
931
  print(f"[DEBUG] stop_stream_processing() CALLED - mode before: {state.get('mode', 'None')}")
932
  MICROPHONE_PROCESSING_ENABLED = False
 
949
 
950
  return state, diar_html_output, transcript_textbox_html, gr.Button(value="Pause", visible=False)
951
 
952
+ @spaces.GPU(duration=GPU_DURATION_SECONDS)
953
  def stream_processing(state, audio_data):
954
  """Handles microphone streaming."""
955
+ ensure_sessions_initialized()
956
  # Initialize state if it's None or doesn't have required fields
957
  if state is None:
958
  state = {"mode": "stream", "full_transcript": "", "diar_html_initial": ""}
 
1059
  )
1060
  return html_string, single_speaker_mode
1061
 
1062
+ @spaces.GPU(duration=GPU_DURATION_SECONDS)
1063
  def reload_models(
1064
  audio_pre_loading,
1065
  streaming_chunk_size,
 
1070
  sent_break_sec,
1071
  ):
1072
  """Reloads the streaming session with new configurations."""
1073
+ ensure_sessions_initialized()
1074
  try:
1075
  num_speakers = min(int(num_speakers), MAX_NUM_OF_SPEAKERS)
1076
  if single_speaker_mode:
 
1152
 
1153
  def load_and_reset():
1154
  """Resets the UI state on page load."""
1155
+ if MODELS_INITIALIZED:
1156
+ AUDIO_PRE_LOADING = session_file._audio_pre_loading
1157
+ STREAMING_CHUNK_SIZE = session_file.multispk_asr_streamer._att_context_size[1]
1158
+ SINGLE_SPEAKER_MODE = 'ON' if session_file.multispk_asr_streamer._single_speaker_mode else 'OFF'
1159
+ NUMBER_OF_SPEAKERS = f"{session_file.multispk_asr_streamer._max_num_of_spks}"
1160
+ TEXT_BREAK_SILENCE = f"{session_file.multispk_asr_streamer._sent_break_sec} seconds"
1161
+ CACHE_GATING_STATUS = 'ON' if session_file.multispk_asr_streamer._cache_gating else 'OFF'
1162
+ BINARY_DIAR_PREDICTION_STATUS = 'ON' if session_file.multispk_asr_streamer._binary_diar_preds else 'OFF'
1163
+ else:
1164
+ AUDIO_PRE_LOADING = config_file.audio_pre_loading
1165
+ STREAMING_CHUNK_SIZE = config_file.att_context_size[1]
1166
+ SINGLE_SPEAKER_MODE = 'ON' if config_file.single_speaker_mode else 'OFF'
1167
+ NUMBER_OF_SPEAKERS = f"{config_file.max_num_of_spks}"
1168
+ TEXT_BREAK_SILENCE = f"{config_file.sent_break_sec} seconds"
1169
+ CACHE_GATING_STATUS = 'ON' if config_file.cache_gating else 'OFF'
1170
+ BINARY_DIAR_PREDICTION_STATUS = 'ON' if config_file.binary_diar_preds else 'OFF'
1171
 
1172
  status_textbox_html= get_text_box_html(
1173
  audio_pre_loading=AUDIO_PRE_LOADING,
 
1179
  text_break_silence=TEXT_BREAK_SILENCE,
1180
  )
1181
  diar_html_output = render_diarization_state(None, config_file)
1182
+ transcript_textbox = "<p style='text-align: center; font-size:20px;'><b>No transcription yet. Models load on first transcription request.</b></p>"
1183
  return {"should_stop": False,
1184
  "stream_step_num": 0,
1185
  "microphone_paused": False,