Spaces:
Running
Running
| # ============================================================ | |
| # 📄 الملف: app/models_loader.py | |
| # 🎯 الغرض: تحميل وإدارة الموارد الثقيلة في الذاكرة (مرة واحدة فقط). | |
| # | |
| # الموارد هي: | |
| # - البيانات المنظّفة (data.csv) | |
| # - ملفات الـ embeddings المحفوظة (.npy) | |
| # - نماذج SentenceTransformer | |
| # | |
| # الفكرة: هذه الموارد كبيرة وبطيئة التحميل، فنحمّلها مرة واحدة | |
| # ونحتفظ بها في كائن واحد (state) يُشارَك بين كل الطلبات، | |
| # بدل ما نحمّلها في كل طلب بحث (وهذا يبطّئ كل شيء). | |
| # ============================================================ | |
| import numpy as np | |
| import pandas as pd | |
| from sentence_transformers import SentenceTransformer | |
| import config | |
| class ResourceManager: | |
| """يحتفظ بكل الموارد المحمّلة ويحمّلها كسولًا (lazy) عند أول استخدام.""" | |
| def __init__(self): | |
| self.df: pd.DataFrame | None = None | |
| self.embeddings: dict[str, np.ndarray] = {} # model_key -> مصفوفة embeddings | |
| self.models: dict[str, SentenceTransformer] = {} # model_key -> النموذج المحمّل | |
| # -------------------------------------------------------- | |
| # 📂 تحميل البيانات المنظّفة | |
| # -------------------------------------------------------- | |
| def get_dataframe(self) -> pd.DataFrame: | |
| if self.df is None: | |
| if not config.PROCESSED_DATA_PATH.exists(): | |
| raise FileNotFoundError( | |
| f"ملف البيانات المنظّفة غير موجود: {config.PROCESSED_DATA_PATH}\n" | |
| "شغّل أولًا: python scripts/prepare_data.py" | |
| ) | |
| self.df = pd.read_csv(config.PROCESSED_DATA_PATH) | |
| return self.df | |
| # -------------------------------------------------------- | |
| # 🔢 تحميل الـ embeddings المحفوظة لنموذج معيّن | |
| # -------------------------------------------------------- | |
| def get_embeddings(self, model_key: str) -> np.ndarray: | |
| if model_key not in self.embeddings: | |
| path = config.embedding_path(model_key) | |
| if not path.exists(): | |
| raise FileNotFoundError( | |
| f"ملف الـ embeddings غير موجود: {path}\n" | |
| "شغّل أولًا: python scripts/build_embeddings.py" | |
| ) | |
| # mmap_mode="r": تبقى المصفوفة على القرص ولا تُحمّل كاملة في | |
| # الذاكرة — يوفّر ~مئات الميغابايت (مهم للنشر على ذاكرة محدودة). | |
| self.embeddings[model_key] = np.load(path, mmap_mode="r") | |
| return self.embeddings[model_key] | |
| # -------------------------------------------------------- | |
| # 🤖 تحميل نموذج SentenceTransformer (كسولًا) | |
| # -------------------------------------------------------- | |
| def get_model(self, model_key: str, hf_id: str) -> SentenceTransformer: | |
| if model_key not in self.models: | |
| print(f"⏳ تحميل النموذج: {model_key} ...") | |
| self.models[model_key] = SentenceTransformer(hf_id, device=config.DEVICE) | |
| print(f"✅ تم تحميل: {model_key}") | |
| return self.models[model_key] | |
| # -------------------------------------------------------- | |
| # 🔥 تسخين: تحميل كل النماذج والـ embeddings مسبقًا | |
| # -------------------------------------------------------- | |
| def warmup(self): | |
| """يُستدعى عند إقلاع الخادم لتحميل كل شيء مقدّمًا.""" | |
| self.get_dataframe() | |
| for cfg in config.MODELS: | |
| self.get_embeddings(cfg["key"]) | |
| self.get_model(cfg["key"], cfg["hf_id"]) | |
| # نسخة وحيدة (singleton) تُستخدم في كل المشروع | |
| resources = ResourceManager() | |