disaster-triage-command / models /model_manager.py
Keerthisujana's picture
Deploy Disaster Triage Command
1676aa7
Raw
History Blame Contribute Delete
3.49 kB
"""
Model Manager.
DESIGN DECISION: this entire application runs on free CPU Basic hardware —
no ZeroGPU is requested anywhere. All three models were deliberately chosen
small enough (343MB classifier, ~100MB captioner, 0.5B-param LLM) that CPU
inference is fast enough for a live demo without fighting GPU quota/cold-start
behavior. This is the "smallest model that produces a convincing demo"
principle applied literally.
Models are loaded lazily (only when first needed) and cached as module-level
singletons so repeated calls during one demo run don't reload from disk.
Every load is wrapped so a failure degrades the relevant agent instead of
crashing the whole Space (section 25).
"""
from __future__ import annotations
import logging
import threading
logger = logging.getLogger("model_manager")
logging.basicConfig(level=logging.INFO)
_lock = threading.Lock()
_cache: dict[str, object] = {}
CLASSIFIER_MODEL_ID = "Luwayy/disaster_images_model"
CAPTION_MODEL_ID = "cnmoro/tiny-image-captioning"
LLM_MODEL_ID = "Qwen/Qwen2.5-0.5B-Instruct"
class ModelLoadError(Exception):
pass
def _get_or_load(key: str, loader_fn):
with _lock:
if key in _cache:
return _cache[key]
try:
logger.info(f"Loading model for '{key}' (first use, will be cached)...")
obj = loader_fn()
_cache[key] = obj
logger.info(f"Model '{key}' loaded and cached.")
return obj
except Exception as e:
logger.error(f"Failed to load model '{key}': {e}")
raise ModelLoadError(f"Could not load model for {key}: {e}") from e
def get_classifier():
"""Image classification pipeline: disaster damage-type classification."""
def _load():
from transformers import pipeline
return pipeline("image-classification", model=CLASSIFIER_MODEL_ID)
return _get_or_load("classifier", _load)
def get_captioner():
"""Image captioning model: produces the 'evidence' text for a zone's photo."""
def _load():
from transformers import AutoTokenizer, AutoImageProcessor, VisionEncoderDecoderModel
tokenizer = AutoTokenizer.from_pretrained(CAPTION_MODEL_ID)
image_processor = AutoImageProcessor.from_pretrained(CAPTION_MODEL_ID)
model = VisionEncoderDecoderModel.from_pretrained(CAPTION_MODEL_ID)
model.eval()
return {"model": model, "tokenizer": tokenizer, "image_processor": image_processor}
return _get_or_load("captioner", _load)
def get_llm():
"""Small instruct LLM used for report parsing (JSON extraction) and explanation."""
def _load():
from transformers import AutoModelForCausalLM, AutoTokenizer
tokenizer = AutoTokenizer.from_pretrained(LLM_MODEL_ID)
model = AutoModelForCausalLM.from_pretrained(LLM_MODEL_ID, torch_dtype="auto")
model.eval()
return {"model": model, "tokenizer": tokenizer}
return _get_or_load("llm", _load)
def release_all():
"""Frees cached models. Not called during normal demo operation, but available
if the Space needs to recover memory between sessions."""
with _lock:
_cache.clear()
logger.info("All cached models released.")
def preload_status() -> dict[str, bool]:
"""Reports which models are currently loaded, for the UI's system status strip."""
return {
"classifier": "classifier" in _cache,
"captioner": "captioner" in _cache,
"llm": "llm" in _cache,
}