### 🚀 ML Models Runtime Preloading - **🔧 models loading**: Перенесена предзагрузка ML моделей из Docker build в runtime startup - Убрана предзагрузка из `Dockerfile` - модели теперь загружаются после монтирования `/dump` папки - Добавлена async функция `preload_models()` в `services/search.py` для фоновой загрузки - Интеграция предзагрузки в `lifespan` функцию `main.py` - Использование `asyncio.run_in_executor()` для неблокирующей загрузки моделей - Исправлена проблема с недоступностью `/dump` папки во время сборки Docker образа
This commit is contained in:
@@ -51,6 +51,77 @@ os.environ.setdefault("HF_HOME", MODELS_CACHE_DIR)
|
||||
background_tasks: List[asyncio.Task] = []
|
||||
|
||||
|
||||
async def preload_models() -> None:
|
||||
"""🚀 Асинхронная предзагрузка моделей для кеширования"""
|
||||
logger.info("🔄 Начинаем предзагрузку моделей...")
|
||||
|
||||
# Ждем импорта SentenceTransformer
|
||||
_lazy_import_sentence_transformers()
|
||||
|
||||
if SentenceTransformer is None:
|
||||
logger.error("❌ SentenceTransformer недоступен для предзагрузки")
|
||||
return
|
||||
|
||||
# Создаем папку для кеша
|
||||
Path(MODELS_CACHE_DIR).mkdir(parents=True, exist_ok=True)
|
||||
logger.info(f"📁 Используем кеш директорию: {MODELS_CACHE_DIR}")
|
||||
|
||||
# Список моделей для предзагрузки
|
||||
models = [
|
||||
"paraphrase-multilingual-MiniLM-L12-v2", # Основная многоязычная модель
|
||||
"all-MiniLM-L6-v2", # Fallback модель
|
||||
]
|
||||
|
||||
for model_name in models:
|
||||
try:
|
||||
# Проверяем, есть ли модель в кеше
|
||||
if _is_model_cached(model_name):
|
||||
logger.info(f"🔍 Модель уже в кеше: {model_name}")
|
||||
continue
|
||||
|
||||
logger.info(f"🔽 Загружаем модель: {model_name}")
|
||||
|
||||
# Запускаем загрузку в executor чтобы не блокировать event loop
|
||||
loop = asyncio.get_event_loop()
|
||||
await loop.run_in_executor(
|
||||
None, lambda name=model_name: SentenceTransformer(name, cache_folder=MODELS_CACHE_DIR)
|
||||
)
|
||||
|
||||
logger.info(f"✅ Модель загружена: {model_name}")
|
||||
|
||||
except Exception as e:
|
||||
logger.warning(f"❌ Не удалось загрузить {model_name}: {e}")
|
||||
|
||||
logger.info("🚀 Предзагрузка моделей завершена!")
|
||||
|
||||
|
||||
def _is_model_cached(model_name: str) -> bool:
|
||||
"""🔍 Проверяет наличие модели в кеше"""
|
||||
try:
|
||||
cache_path = Path(MODELS_CACHE_DIR)
|
||||
model_cache_name = f"models--sentence-transformers--{model_name}"
|
||||
model_path = cache_path / model_cache_name
|
||||
|
||||
if not model_path.exists():
|
||||
return False
|
||||
|
||||
# Проверяем наличие snapshots папки (новый формат HuggingFace)
|
||||
snapshots_path = model_path / "snapshots"
|
||||
if snapshots_path.exists():
|
||||
# Ищем любой snapshot с config.json
|
||||
for snapshot_dir in snapshots_path.iterdir():
|
||||
if snapshot_dir.is_dir():
|
||||
config_file = snapshot_dir / "config.json"
|
||||
if config_file.exists():
|
||||
return True
|
||||
|
||||
# Fallback: проверяем старый формат
|
||||
config_file = model_path / "config.json"
|
||||
return config_file.exists()
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
|
||||
def _lazy_import_sentence_transformers():
|
||||
"""🔄 Lazy import SentenceTransformer для избежания блокировки старта приложения"""
|
||||
global SentenceTransformer # noqa: PLW0603
|
||||
@@ -83,37 +154,6 @@ class MuveraWrapper:
|
||||
self.encoder = None
|
||||
self._model_loaded = False
|
||||
|
||||
def _is_model_cached(self, model_name: str) -> bool:
|
||||
"""🔍 Проверяет наличие модели в кеше"""
|
||||
try:
|
||||
# Проверяем наличие папки модели в кеше
|
||||
cache_path = Path(MODELS_CACHE_DIR)
|
||||
|
||||
# SentenceTransformer сохраняет модели в формате models--org--model-name
|
||||
model_cache_name = f"models--sentence-transformers--{model_name}"
|
||||
model_path = cache_path / model_cache_name
|
||||
|
||||
# Проверяем существование папки модели
|
||||
if not model_path.exists():
|
||||
return False
|
||||
|
||||
# Проверяем наличие snapshots папки (новый формат HuggingFace)
|
||||
snapshots_path = model_path / "snapshots"
|
||||
if snapshots_path.exists():
|
||||
# Ищем любой snapshot с config.json
|
||||
for snapshot_dir in snapshots_path.iterdir():
|
||||
if snapshot_dir.is_dir():
|
||||
config_file = snapshot_dir / "config.json"
|
||||
if config_file.exists():
|
||||
return True
|
||||
|
||||
# Fallback: проверяем старый формат
|
||||
config_file = model_path / "config.json"
|
||||
return config_file.exists()
|
||||
except Exception as e:
|
||||
logger.debug(f"Error checking model cache for {model_name}: {e}")
|
||||
return False
|
||||
|
||||
def _ensure_model_loaded(self) -> bool:
|
||||
"""🔄 Убеждаемся что модель загружена (lazy loading)"""
|
||||
if self._model_loaded:
|
||||
@@ -129,7 +169,7 @@ class MuveraWrapper:
|
||||
logger.info(f"💾 Using models cache directory: {MODELS_CACHE_DIR}")
|
||||
|
||||
# Проверяем наличие основной модели
|
||||
is_cached = self._is_model_cached(primary_model)
|
||||
is_cached = _is_model_cached(primary_model)
|
||||
if is_cached:
|
||||
logger.info(f"🔍 Found cached model: {primary_model}")
|
||||
else:
|
||||
@@ -150,7 +190,7 @@ class MuveraWrapper:
|
||||
# Fallback - простая модель
|
||||
try:
|
||||
fallback_model = "all-MiniLM-L6-v2"
|
||||
is_fallback_cached = self._is_model_cached(fallback_model)
|
||||
is_fallback_cached = _is_model_cached(fallback_model)
|
||||
if is_fallback_cached:
|
||||
logger.info(f"🔍 Found cached fallback model: {fallback_model}")
|
||||
else:
|
||||
|
||||
Reference in New Issue
Block a user