yoavzamir Claude Opus 4.8 commited on
Commit
410c144
·
1 Parent(s): 009b500

main: background self-warm thread keeps the model hot 24/7

Browse files

User is on a paid always-on (never-sleep) Space, yet the first request after a
day idle still took ~30s. Cause: the model path goes cold during idle (memory
paged out / CPU throttled) and /warmup never runs the model, so it can't keep it
warm. Added a daemon thread that runs a tiny LOCAL Wav2Vec2 inference every
SELF_WARM_INTERVAL_SEC (default 240s) — forward pass only, NO STT API calls
(those cost money) — so the model never goes cold regardless of app activity.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

Files changed (1) hide show
  1. main.py +37 -0
main.py CHANGED
@@ -13,6 +13,7 @@ import sys
13
  import uuid
14
  import time
15
  import tempfile
 
16
  from contextlib import asynccontextmanager
17
 
18
  from fastapi import FastAPI, File, Form, UploadFile, HTTPException, Header
@@ -80,6 +81,14 @@ VALID_WORDS = {
80
  }
81
 
82
 
 
 
 
 
 
 
 
 
83
  @asynccontextmanager
84
  async def lifespan(app: FastAPI):
85
  """Pre-load Wav2Vec2 model AND Supabase client before accepting requests.
@@ -122,6 +131,34 @@ async def lifespan(app: FastAPI):
122
  except Exception as e:
123
  print(f"[WARMUP] dummy scoring setup failed: {e}")
124
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
125
  yield
126
 
127
 
 
13
  import uuid
14
  import time
15
  import tempfile
16
+ import threading
17
  from contextlib import asynccontextmanager
18
 
19
  from fastapi import FastAPI, File, Form, UploadFile, HTTPException, Header
 
81
  }
82
 
83
 
84
+ # How often the background thread re-runs a tiny LOCAL inference to keep the
85
+ # Wav2Vec2 model hot. The server is always-on, but after a day idle the model
86
+ # path goes cold (memory paged out / CPU throttled) and the first real request
87
+ # paid ~30s. /warmup never runs the model, so it can't prevent this. 240s keeps
88
+ # the model warm with negligible cost. Override via env if needed.
89
+ SELF_WARM_INTERVAL_SEC = int(os.environ.get("SELF_WARM_INTERVAL_SEC", "240"))
90
+
91
+
92
  @asynccontextmanager
93
  async def lifespan(app: FastAPI):
94
  """Pre-load Wav2Vec2 model AND Supabase client before accepting requests.
 
131
  except Exception as e:
132
  print(f"[WARMUP] dummy scoring setup failed: {e}")
133
 
134
+ # Background self-warm: keep the local Wav2Vec2 model HOT 24/7. The server is
135
+ # always-on (paid, never-sleep), but after a day idle the model path goes cold
136
+ # and the first real request paid ~30s. This daemon thread runs a tiny LOCAL
137
+ # inference every few minutes — Wav2Vec2 forward pass only, NO STT API calls
138
+ # (those cost money) — so the model never goes cold, independent of the app.
139
+ def _self_warm_loop():
140
+ import time as _time
141
+ import numpy as np
142
+ import soundfile as sf
143
+ warm_wav = os.path.join(tempfile.gettempdir(), "selfwarm.wav")
144
+ try:
145
+ rng = np.random.default_rng(1)
146
+ sf.write(warm_wav, (rng.standard_normal(8000) * 0.01).astype("float32"), 16000)
147
+ except Exception as e:
148
+ print(f"[SELF-WARM] setup failed, warmer not started: {e}")
149
+ return
150
+ from phoneme_extractor import shin_vs_samekh
151
+ while True:
152
+ _time.sleep(SELF_WARM_INTERVAL_SEC)
153
+ try:
154
+ shin_vs_samekh(warm_wav) # local Wav2Vec2 forward pass, no network
155
+ print("[SELF-WARM] model kept hot")
156
+ except Exception as e:
157
+ print(f"[SELF-WARM] tick failed: {e}")
158
+
159
+ threading.Thread(target=_self_warm_loop, daemon=True).start()
160
+ print(f"[SELF-WARM] background warmer started (every {SELF_WARM_INTERVAL_SEC}s)")
161
+
162
  yield
163
 
164