Akshay30 commited on
Commit
7b9f40a
Β·
1 Parent(s): 2f4af3f

Add startup diagnostics for model initialization

Browse files
__pycache__/app.cpython-312.pyc ADDED
Binary file (19 kB). View file
 
app.py CHANGED
@@ -60,7 +60,6 @@ allowed_origins = os.getenv(
60
  CORS(app, origins=allowed_origins.split(","))
61
 
62
  # Global components
63
- import threading
64
  config = Config()
65
  groq_client = None
66
  clip_classifier = None
@@ -69,16 +68,6 @@ script_detector = None
69
  cuneiform_processor = None
70
  references = {}
71
 
72
- # Live model preloading status tracking
73
- model_status = {
74
- "status": "loading",
75
- "groq": "pending",
76
- "clip": "pending",
77
- "translator": "pending",
78
- "cuneiform": "pending",
79
- "script_detector": "pending"
80
- }
81
-
82
 
83
  def load_references():
84
  """Load references from JSON file"""
@@ -113,77 +102,57 @@ def load_references():
113
  }
114
 
115
 
116
- def initialize_models_async():
117
- """Load models sequentially in the background to prevent blocking Flask startup"""
118
- global groq_client, clip_classifier, hf_models, script_detector, cuneiform_processor, model_status
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
119
  try:
120
- print("[INFO] Background model preloading thread started...")
121
-
122
- # Log GPU Diagnostics
123
- log_gpu_info()
124
-
125
- # Load references first
126
- load_references()
127
-
128
- # Groq
129
- model_status["groq"] = "loading"
130
- groq_client = GroqClient()
131
- model_status["groq"] = "ready" if groq_client.is_available() else "unavailable"
132
- print(f"[INFO] Groq client initialization complete: {model_status['groq']}")
133
-
134
- # CLIP
135
- model_status["clip"] = "loading"
136
- clip_classifier = CLIPClassifier()
137
- model_status["clip"] = "ready" if (clip_classifier and clip_classifier.pipeline is not None) else "failed"
138
- print(f"[INFO] CLIP classifier initialization complete: {model_status['clip']}")
139
-
140
- # HF Translator
141
- model_status["translator"] = "loading"
142
- hf_models = HuggingFaceModels()
143
- model_status["translator"] = "ready" if (hf_models and hf_models.get_translator() is not None) else "failed"
144
- print(f"[INFO] Hugging Face models initialization complete: {model_status['translator']}")
145
-
146
- # Cuneiform Processor
147
- model_status["cuneiform"] = "loading"
148
- try:
149
- print("[INFO] Initializing cuneiform processor...")
150
- cuneiform_processor = CuneiformProcessor(
151
- groq_client=groq_client,
152
- references=references,
153
- clip_classifier=clip_classifier
154
- )
155
- model_status["cuneiform"] = "ready" if cuneiform_processor.cuneiform_available else "unavailable"
156
- except Exception as e:
157
- print(f"[ERROR] Failed to initialize cuneiform processor: {e}")
158
- model_status["cuneiform"] = "failed"
159
- cuneiform_processor = None
160
- print(f"[INFO] Cuneiform processor initialization complete: {model_status['cuneiform']}")
161
-
162
- # Script Detection Service
163
- model_status["script_detector"] = "loading"
164
- script_detector = ScriptDetectionService(
165
  groq_client=groq_client,
166
  references=references,
167
- clip_classifier=clip_classifier,
168
- translator_pipe=hf_models.get_translator(),
169
- cuneiform_processor=cuneiform_processor
170
  )
171
- model_status["script_detector"] = "ready"
172
- print(f"[INFO] Script detection service initialization complete: {model_status['script_detector']}")
173
-
174
- model_status["status"] = "ready"
175
- print("[SUCCESS] All models initialized successfully in the background")
176
-
177
  except Exception as e:
178
- model_status["status"] = "failed"
179
- print(f"[ERROR] Critical failure in background model initialization: {e}")
180
-
181
-
182
- def initialize_models():
183
- """Spawn background thread to load models"""
184
- print("[INFO] Spawning background thread for model initialization...")
185
- model_status["status"] = "loading"
186
- threading.Thread(target=initialize_models_async, daemon=True).start()
 
 
 
 
187
 
188
 
189
  @app.route('/analyze', methods=['POST'])
@@ -192,13 +161,6 @@ def analyze():
192
  tmp_path = None
193
 
194
  try:
195
- # Check if models are fully loaded
196
- if model_status["status"] != "ready":
197
- return jsonify({
198
- "error": "Models are still loading in the background. Please try again in a few moments.",
199
- "status": "loading",
200
- "models_status": model_status
201
- }), 503
202
 
203
  # Validate request
204
  if 'image' not in request.files:
@@ -411,10 +373,18 @@ def chat():
411
 
412
  @app.route('/health', methods=['GET'])
413
  def health_check():
414
- """Health check endpoint returning real-time load status"""
 
 
 
 
 
 
 
415
  return jsonify({
416
- "status": "healthy" if model_status["status"] == "ready" else "initializing",
417
- "models_status": model_status
 
418
  })
419
 
420
 
@@ -440,33 +410,32 @@ def info():
440
  })
441
 
442
 
443
- # --- Model initialization ---
444
- # When running under gunicorn (or any WSGI server), __name__ != "__main__",
445
- # so we initialize models at module level. The gunicorn --preload flag ensures
446
- # this runs once in the master process before forking workers.
447
  def _auto_initialize():
448
- """Initialize models when running under a WSGI server (gunicorn, waitress, etc.)"""
449
  if os.getenv("WERKZEUG_RUN_MAIN") == "true":
450
  # Flask reloader child process β€” handled by __main__ block
451
  return
452
- print("[INIT] WSGI server detected β€” initializing models...")
453
- initialize_models()
454
 
455
 
456
  if __name__ == "__main__":
457
- print("[INIT] Starting Ancient Script Recognition System...")
458
 
459
  # Start Flask app
460
  port = int(os.getenv("PORT", 7860))
461
  debug = os.getenv("DEBUG", "False").lower() == "true"
462
 
463
- # Initialize all models (only in child process if debug mode is on to avoid duplicate threads)
464
  if not debug or os.environ.get("WERKZEUG_RUN_MAIN") == "true":
465
- initialize_models()
466
  else:
467
- print("[INFO] Reloader active. Model initialization deferred to child process.")
468
 
469
- print(f"[INFO] Starting server on port {port}")
470
  app.run(host="0.0.0.0", port=port, debug=debug)
471
  else:
472
  # Running under gunicorn / WSGI
 
60
  CORS(app, origins=allowed_origins.split(","))
61
 
62
  # Global components
 
63
  config = Config()
64
  groq_client = None
65
  clip_classifier = None
 
68
  cuneiform_processor = None
69
  references = {}
70
 
 
 
 
 
 
 
 
 
 
 
71
 
72
  def load_references():
73
  """Load references from JSON file"""
 
102
  }
103
 
104
 
105
+ def initialize_components():
106
+ """Initialize lightweight component wrappers synchronously.
107
+
108
+ No heavy model weights are loaded here β€” all ML models use lazy loading
109
+ and will download/load on their first inference call. This ensures the
110
+ app starts instantly on resource-constrained environments like HF Spaces.
111
+ """
112
+ global groq_client, clip_classifier, hf_models, script_detector, cuneiform_processor
113
+ import time as _time
114
+ _t0 = _time.time()
115
+
116
+ print("[INIT] Initializing components (lazy loading β€” no model weights loaded yet)...", flush=True)
117
+
118
+ # Log GPU Diagnostics
119
+ log_gpu_info()
120
+
121
+ # Load references (small JSON file, instant)
122
+ load_references()
123
+
124
+ # Groq client (API key check only, no model download)
125
+ groq_client = GroqClient()
126
+ groq_status = "ready" if groq_client.is_available() else "unavailable"
127
+ print(f"[INIT] Groq client: {groq_status}", flush=True)
128
+
129
+ # CLIP classifier (lazy β€” model loads on first classify call)
130
+ clip_classifier = CLIPClassifier()
131
+
132
+ # HF Translator (lazy β€” model loads on first translate call)
133
+ hf_models = HuggingFaceModels()
134
+
135
+ # Cuneiform processor (lazy β€” CLIP & translator load on first use)
136
  try:
137
+ cuneiform_processor = CuneiformProcessor(
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
138
  groq_client=groq_client,
139
  references=references,
140
+ clip_classifier=clip_classifier
 
 
141
  )
 
 
 
 
 
 
142
  except Exception as e:
143
+ print(f"[ERROR] Failed to create cuneiform processor: {e}", flush=True)
144
+ cuneiform_processor = None
145
+
146
+ # Script detection service (creates processor instances, all lazy)
147
+ script_detector = ScriptDetectionService(
148
+ groq_client=groq_client,
149
+ references=references,
150
+ clip_classifier=clip_classifier,
151
+ translator_pipe=hf_models.get_translator(),
152
+ cuneiform_processor=cuneiform_processor
153
+ )
154
+
155
+ print(f"[INIT] All components ready in {_time.time()-_t0:.1f}s (models will load on first request)", flush=True)
156
 
157
 
158
  @app.route('/analyze', methods=['POST'])
 
161
  tmp_path = None
162
 
163
  try:
 
 
 
 
 
 
 
164
 
165
  # Validate request
166
  if 'image' not in request.files:
 
373
 
374
  @app.route('/health', methods=['GET'])
375
  def health_check():
376
+ """Health check endpoint β€” app is always ready, models load lazily on demand"""
377
+ models_loaded = {
378
+ "groq": groq_client.is_available() if groq_client else False,
379
+ "clip": clip_classifier.is_loaded if clip_classifier else False,
380
+ "translator": hf_models is not None if hf_models else False,
381
+ "cuneiform": cuneiform_processor is not None if cuneiform_processor else False,
382
+ "script_detector": script_detector is not None
383
+ }
384
  return jsonify({
385
+ "status": "healthy",
386
+ "architecture": "lazy_loading",
387
+ "models_loaded": models_loaded
388
  })
389
 
390
 
 
410
  })
411
 
412
 
413
+ # --- Component initialization ---
414
+ # Lightweight init runs synchronously at module level. No heavy model weights
415
+ # are loaded here β€” all ML models use lazy loading on first inference call.
 
416
  def _auto_initialize():
417
+ """Initialize components when running under a WSGI server (gunicorn, waitress, etc.)"""
418
  if os.getenv("WERKZEUG_RUN_MAIN") == "true":
419
  # Flask reloader child process β€” handled by __main__ block
420
  return
421
+ print("[INIT] WSGI server detected β€” initializing components...", flush=True)
422
+ initialize_components()
423
 
424
 
425
  if __name__ == "__main__":
426
+ print("[INIT] Starting Ancient Script Recognition System (lazy loading)...", flush=True)
427
 
428
  # Start Flask app
429
  port = int(os.getenv("PORT", 7860))
430
  debug = os.getenv("DEBUG", "False").lower() == "true"
431
 
432
+ # Initialize lightweight components (only in child process if debug mode is on)
433
  if not debug or os.environ.get("WERKZEUG_RUN_MAIN") == "true":
434
+ initialize_components()
435
  else:
436
+ print("[INFO] Reloader active. Component initialization deferred to child process.")
437
 
438
+ print(f"[INFO] Starting server on port {port}", flush=True)
439
  app.run(host="0.0.0.0", port=port, debug=debug)
440
  else:
441
  # Running under gunicorn / WSGI
models/__pycache__/clip_classifier.cpython-312.pyc ADDED
Binary file (9.5 kB). View file
 
models/clip_classifier.py CHANGED
@@ -1,3 +1,4 @@
 
1
  import torch
2
  from transformers import CLIPProcessor, CLIPModel
3
  from PIL import Image
@@ -5,44 +6,71 @@ import numpy as np
5
  from config import Config
6
  from utils.gpu_diagnostics import log_model_device
7
 
 
8
  class CLIPClassifier:
9
  def __init__(self):
10
  self.config = Config()
11
  self.device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
12
  self.model = None
13
  self.processor = None
14
-
15
- # Load CLIP model and processor with fallback
 
 
 
 
 
 
16
  model_name = getattr(self.config, 'CLIP_MODEL', 'openai/clip-vit-base-patch32')
17
  try:
18
- print(f"[INFO] Loading CLIP model: {model_name}...")
 
19
  self.model = CLIPModel.from_pretrained(model_name)
 
 
 
20
  self.processor = CLIPProcessor.from_pretrained(model_name)
 
 
 
21
  self.model.to(self.device)
22
- self.model.eval() # Set model to evaluation mode
23
  log_model_device("CLIP script classifier", self.device)
24
- print(f"[INFO] CLIP model loaded on {self.device}")
 
 
25
  except Exception as e:
26
- print(f"[WARN] Failed to load CLIP model '{model_name}': {e}")
27
  fallback_name = "openai/clip-vit-base-patch32"
28
  try:
29
- print(f"[INFO] Loading fallback CLIP model: {fallback_name}...")
 
30
  self.model = CLIPModel.from_pretrained(fallback_name)
31
  self.processor = CLIPProcessor.from_pretrained(fallback_name)
 
 
32
  self.model.to(self.device)
33
- self.model.eval() # Set model to evaluation mode
34
  log_model_device("CLIP script classifier (fallback)", self.device)
35
- print(f"[INFO] Fallback CLIP model loaded on {self.device}")
 
36
  except Exception as fe:
37
- print(f"[ERROR] Failed to load fallback CLIP model: {fe}")
38
 
39
  @property
40
  def pipeline(self):
41
  """Property checked in app.py/test.py to ensure model is initialized"""
42
  return self.model if self.model is not None else None
43
 
 
 
 
 
 
44
  def classify_script_type(self, image):
45
  """Classify script type of image into one of the four supported categories"""
 
 
46
  if not self.pipeline:
47
  return "unknown", 0.0
48
 
@@ -84,6 +112,8 @@ class CLIPClassifier:
84
 
85
  def classify_symbols(self, crops, candidate_labels):
86
  """Classify segmented symbol image crops against candidate labels"""
 
 
87
  if not self.pipeline or not crops or not candidate_labels:
88
  return [None] * len(crops) if crops else []
89
 
 
1
+ import time
2
  import torch
3
  from transformers import CLIPProcessor, CLIPModel
4
  from PIL import Image
 
6
  from config import Config
7
  from utils.gpu_diagnostics import log_model_device
8
 
9
+
10
  class CLIPClassifier:
11
  def __init__(self):
12
  self.config = Config()
13
  self.device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
14
  self.model = None
15
  self.processor = None
16
+ self._loaded = False
17
+ print("[INFO] CLIPClassifier created (lazy β€” model will load on first use)", flush=True)
18
+
19
+ def _ensure_loaded(self):
20
+ """Lazily load CLIP model and processor on first use, with fallback."""
21
+ if self._loaded:
22
+ return
23
+
24
  model_name = getattr(self.config, 'CLIP_MODEL', 'openai/clip-vit-base-patch32')
25
  try:
26
+ _t0 = time.time()
27
+ print(f"[CLIP LAZY] Step 1/4 β€” Loading CLIPModel: {model_name}...", flush=True)
28
  self.model = CLIPModel.from_pretrained(model_name)
29
+ print(f"[CLIP LAZY] Step 2/4 β€” CLIPModel loaded in {time.time()-_t0:.1f}s. Loading CLIPProcessor...", flush=True)
30
+
31
+ _t1 = time.time()
32
  self.processor = CLIPProcessor.from_pretrained(model_name)
33
+ print(f"[CLIP LAZY] Step 3/4 β€” CLIPProcessor loaded in {time.time()-_t1:.1f}s. Moving to {self.device}...", flush=True)
34
+
35
+ _t2 = time.time()
36
  self.model.to(self.device)
37
+ self.model.eval()
38
  log_model_device("CLIP script classifier", self.device)
39
+ print(f"[CLIP LAZY] Step 4/4 β€” CLIP ready on {self.device} β€” total {time.time()-_t0:.1f}s", flush=True)
40
+ self._loaded = True
41
+
42
  except Exception as e:
43
+ print(f"[WARN] Failed to load CLIP model '{model_name}': {e}", flush=True)
44
  fallback_name = "openai/clip-vit-base-patch32"
45
  try:
46
+ _t0 = time.time()
47
+ print(f"[CLIP LAZY] Fallback 1/2 β€” Loading: {fallback_name}...", flush=True)
48
  self.model = CLIPModel.from_pretrained(fallback_name)
49
  self.processor = CLIPProcessor.from_pretrained(fallback_name)
50
+ print(f"[CLIP LAZY] Fallback 2/2 β€” Moving to {self.device}...", flush=True)
51
+
52
  self.model.to(self.device)
53
+ self.model.eval()
54
  log_model_device("CLIP script classifier (fallback)", self.device)
55
+ print(f"[CLIP LAZY] Fallback CLIP ready β€” total {time.time()-_t0:.1f}s", flush=True)
56
+ self._loaded = True
57
  except Exception as fe:
58
+ print(f"[ERROR] Failed to load fallback CLIP model: {fe}", flush=True)
59
 
60
  @property
61
  def pipeline(self):
62
  """Property checked in app.py/test.py to ensure model is initialized"""
63
  return self.model if self.model is not None else None
64
 
65
+ @property
66
+ def is_loaded(self):
67
+ """Check if model has been lazily loaded yet."""
68
+ return self._loaded
69
+
70
  def classify_script_type(self, image):
71
  """Classify script type of image into one of the four supported categories"""
72
+ self._ensure_loaded()
73
+
74
  if not self.pipeline:
75
  return "unknown", 0.0
76
 
 
112
 
113
  def classify_symbols(self, crops, candidate_labels):
114
  """Classify segmented symbol image crops against candidate labels"""
115
+ self._ensure_loaded()
116
+
117
  if not self.pipeline or not crops or not candidate_labels:
118
  return [None] * len(crops) if crops else []
119