Перевод tracking на SQLite + HTTP-клиенты (file-store запрещён в MLflow 3.x)

- start_ui.sh: SQLite backend (sqlite:///mlflow.db) вместо file-store
- все скрипты src/: mlflow.set_tracking_uri(http://localhost:5555) с env-override
- README: сервер должен быть запущен до запуска скриптов
- AGENTS.md: обновлена архитектура хранения и gotcha
This commit is contained in:
2026-07-21 00:10:02 +03:00
parent fadecb9dc7
commit 9c3321b3a7
12 changed files with 48 additions and 30 deletions
+4 -4
View File
@@ -28,7 +28,7 @@ mlflow-practice/
├── python_env.yaml # окружение для MLproject (python 3.10) ├── python_env.yaml # окружение для MLproject (python 3.10)
├── requirements.txt # pip-зависимости ├── requirements.txt # pip-зависимости
├── setup_server.sh # установка окружения (РАЗВИЛКА macOS/Linux) ├── setup_server.sh # установка окружения (РАЗВИЛКА macOS/Linux)
├── start_ui.sh # запуск MLflow Tracking Server (UI на :5555, file-store) ├── start_ui.sh # запуск MLflow Tracking Server (UI на :5555, SQLite backend)
├── .gitignore ├── .gitignore
├── scripts/ ├── scripts/
│ └── setup_git.sh # настройка Git (HTTPS + токен) │ └── setup_git.sh # настройка Git (HTTPS + токен)
@@ -44,7 +44,7 @@ mlflow-practice/
│ └── grid_search_cv.py # Урок 9: GridSearchCV + autolog │ └── grid_search_cv.py # Урок 9: GridSearchCV + autolog
├── data/ # датасеты (MNIST скачается автоматически) ├── data/ # датасеты (MNIST скачается автоматически)
├── artifacts/ # графики, отчёты (логируются в MLflow) ├── artifacts/ # графики, отчёты (логируются в MLflow)
└── mlruns/ # локальный file store MLflow (в .gitignore) └── mlruns/ # старые запуски file-store (в .gitignore, не используется)
``` ```
--- ---
@@ -143,10 +143,10 @@ mlflow run . -P epochs=10 -P batch_size=256 -P lr=0.001 --entry-point gpu
- `autolog_demo.py` намеренно отключает сигнатуры (`log_model_signatures=False`) как демо опций autolog — там warning ожидаем. - `autolog_demo.py` намеренно отключает сигнатуры (`log_model_signatures=False`) как демо опций autolog — там warning ожидаем.
- **INFO про переменные окружения** (`OPENAI_API_KEY` и т.п.) при логировании подавлено через `MLFLOW_RECORD_ENV_VARS_IN_MODEL_LOGGING=false` в скриптах, логирующих модели. - **INFO про переменные окружения** (`OPENAI_API_KEY` и т.п.) при логировании подавлено через `MLFLOW_RECORD_ENV_VARS_IN_MODEL_LOGGING=false` в скриптах, логирующих модели.
- **`python -m venv` падает на Linux (ensurepip)**: на Debian/Ubuntu без пакета `python3.13-venv` (или `python3-venv`) создание venv падает на шаге `ensurepip` (`non-zero exit status 1`). `setup_server.sh` ловит это и пробует fallback: `venv --without-pip` + bootstrap pip через `get-pip.py`. Альтернатива руками — `sudo apt-get install -y python3.13-venv python3.13-dev`. - **`python -m venv` падает на Linux (ensurepip)**: на Debian/Ubuntu без пакета `python3.13-venv` (или `python3-venv`) создание venv падает на шаге `ensurepip` (`non-zero exit status 1`). `setup_server.sh` ловит это и пробует fallback: `venv --without-pip` + bootstrap pip через `get-pip.py`. Альтернатива руками — `sudo apt-get install -y python3.13-venv python3.13-dev`.
- **File-store как СЕРВЕРНЫЙ бэкенд в maintenance mode (MLflow 3.x)**: `mlflow server --backend-store-uri file://...` по умолчанию падает с `MlflowException ... filesystem tracking backend is in maintenance mode`. Официальный opt-out `export MLFLOW_ALLOW_FILE_STORE=true` (уже выставлено в `start_ui.sh` перед file-блоком). Это касается ТОЛЬКО сервера; клиентский file store (как пишут скрипты в `./mlruns`) работает без opt-out. Альтернатива без opt-out — SQLite-бэкенд (закомментированный блок в `start_ui.sh`), но тогда скрипты должны логировать на сервер через `mlflow.set_tracking_uri('http://localhost:5555')`. - **File-store как tracking backend запрещён в MLflow 3.x** (maintenance mode): падает и `mlflow server --backend-store-uri file://...`, и клиент-скрипт с дефолтным `./mlruns` — оба с `MlflowException ... in maintenance mode`. Поэтому проект использует **SQLite** (`sqlite:///mlflow.db`) как tracking backend, а скрипты логируют на сервер по HTTP. Опциональный opt-out `MLFLOW_ALLOW_FILE_STORE=true` НЕ используем (он может быть убран в будущем). Артефакты при этом лежат на локальном диске (`./artifacts`, file artifact repository — он НЕ в maintenance mode).
- **`input_example` для `mlflow.pytorch.log_model` — только numpy**, не `torch.Tensor`: MLflow 3.x валидирует пример входа и падает с `MlflowException ... but got '<class 'torch.Tensor'>'`. В `train_gpu.py` передаётся `np.random.rand(1,1,28,28).astype(np.float32)`. Аналогично для других flavor'ов — пример должен быть DataFrame/ndarray/dict/list/скаляр. - **`input_example` для `mlflow.pytorch.log_model` — только numpy**, не `torch.Tensor`: MLflow 3.x валидирует пример входа и падает с `MlflowException ... but got '<class 'torch.Tensor'>'`. В `train_gpu.py` передаётся `np.random.rand(1,1,28,28).astype(np.float32)`. Аналогично для других flavor'ов — пример должен быть DataFrame/ndarray/dict/list/скаляр.
- **urllib3 `NotOpenSSLWarning`** на macOS (системный `ssl` собран с LibreSSL 2.8.3, urllib3 v2 просит OpenSSL 1.1.1+) — стороннее шумовое предупреждение, к проекту отношения не имеет; не подавляем (чтобы не прятать реальные предупреждения). - **urllib3 `NotOpenSSLWarning`** на macOS (системный `ssl` собран с LibreSSL 2.8.3, urllib3 v2 просит OpenSSL 1.1.1+) — стороннее шумовое предупреждение, к проекту отношения не имеет; не подавляем (чтобы не прятать реальные предупреждения).
- **«Эксперимент не виден в UI» (главная причина)**: скрипты по умолчанию пишут в локальный file store `./mlruns`, а `start_ui.sh` раньше поднимал сервер на **SQLite** (`mlflow.db`) — два разных хранилища, UI ничего не показывал. Теперь `start_ui.sh` использует `--backend-store-uri file://${PWD}/mlruns`**то же хранилище, что и скрипты**, поэтому всё видно без настройки tracking URI. Альтернатива (SQLite-бэкенд) оставлена закомментированной в `start_ui.sh`; при её включении в скриптах нужно задать `mlflow.set_tracking_uri('http://localhost:5555')` (или `MLFLOW_TRACKING_URI`). - **Архитектура хранения (SQLite + HTTP-клиенты)**: `start_ui.sh` поднимает сервер с `--backend-store-uri sqlite:///${PWD}/mlflow.db`. Все скрипты в `src/` содержат `mlflow.set_tracking_uri(os.environ.get("MLFLOW_TRACKING_URI", "http://localhost:5555"))` — пишут на сервер. UI и скрипты работают с **одной** sqlite-базой → эксперименты сразу видны. **Сервер должен быть запущен до запуска скриптов** (иначе `ConnectionError` к localhost:5555). Переопределить адрес: `export MLFLOW_TRACKING_URI=http://<host>:5555` (например, при логировании на удалённый сервер).
- `mlruns/`, `artifacts/*`, `data/MNIST/`, `mlflow.db` — в `.gitignore`, - `mlruns/`, `artifacts/*`, `data/MNIST/`, `mlflow.db` — в `.gitignore`,
в репозиторий не попадают. в репозиторий не попадают.
+7 -1
View File
@@ -48,13 +48,19 @@ pip install torch torchvision --index-url https://download.pytorch.org/whl/cu121
pip install flask # для serve_model.py pip install flask # для serve_model.py
``` ```
### 2. Запуск MLflow UI ### 2. Запуск MLflow UI (Tracking Server)
```bash ```bash
./start_ui.sh ./start_ui.sh
# → http://10.0.0.7:5555 # → http://10.0.0.7:5555
``` ```
> Сервер использует SQLite-бэкенд (`mlflow.db`). Скрипты логируют на него
> по HTTP (`http://localhost:5555`), поэтому **сервер должен быть запущен
> и работать в отдельном терминале всё время, пока вы запускаете уроки**.
> Иначе скрипты упадут с `ConnectionError` к `localhost:5555`.
> Адрес можно переопределить: `export MLFLOW_TRACKING_URI=http://<host>:5555`.
### 3. Уроки по порядку ### 3. Уроки по порядку
```bash ```bash
+1
View File
@@ -21,6 +21,7 @@ import mlflow.sklearn
# Подавляем INFO MLflow о переменных окружения (напр. OPENAI_API_KEY) при логировании модели # Подавляем INFO MLflow о переменных окружения (напр. OPENAI_API_KEY) при логировании модели
import os import os
os.environ.setdefault("MLFLOW_RECORD_ENV_VARS_IN_MODEL_LOGGING", "false") os.environ.setdefault("MLFLOW_RECORD_ENV_VARS_IN_MODEL_LOGGING", "false")
mlflow.set_tracking_uri(os.environ.get("MLFLOW_TRACKING_URI", "http://localhost:5555"))
def main(): def main():
+3
View File
@@ -11,6 +11,9 @@ import argparse
import mlflow import mlflow
from mlflow.tracking import MlflowClient from mlflow.tracking import MlflowClient
import os
mlflow.set_tracking_uri(os.environ.get("MLFLOW_TRACKING_URI", "http://localhost:5555"))
def main(): def main():
parser = argparse.ArgumentParser() parser = argparse.ArgumentParser()
+1
View File
@@ -18,6 +18,7 @@ import mlflow.sklearn
# Подавляем INFO MLflow о переменных окружения (напр. OPENAI_API_KEY) при логировании модели # Подавляем INFO MLflow о переменных окружения (напр. OPENAI_API_KEY) при логировании модели
import os import os
os.environ.setdefault("MLFLOW_RECORD_ENV_VARS_IN_MODEL_LOGGING", "false") os.environ.setdefault("MLFLOW_RECORD_ENV_VARS_IN_MODEL_LOGGING", "false")
mlflow.set_tracking_uri(os.environ.get("MLFLOW_TRACKING_URI", "http://localhost:5555"))
def main(): def main():
+3
View File
@@ -21,6 +21,9 @@ from sklearn.model_selection import train_test_split
import mlflow import mlflow
import os
mlflow.set_tracking_uri(os.environ.get("MLFLOW_TRACKING_URI", "http://localhost:5555"))
def main(): def main():
parser = argparse.ArgumentParser(description="MLflow hyperparameter sweep") parser = argparse.ArgumentParser(description="MLflow hyperparameter sweep")
+3
View File
@@ -12,6 +12,9 @@ from sklearn.datasets import load_digits
import mlflow import mlflow
import os
mlflow.set_tracking_uri(os.environ.get("MLFLOW_TRACKING_URI", "http://localhost:5555"))
def main(): def main():
# Загружаем модель из реестра по имени и стадии # Загружаем модель из реестра по имени и стадии
+3
View File
@@ -15,6 +15,9 @@ import argparse
import mlflow import mlflow
from mlflow.tracking import MlflowClient from mlflow.tracking import MlflowClient
import os
mlflow.set_tracking_uri(os.environ.get("MLFLOW_TRACKING_URI", "http://localhost:5555"))
def main(): def main():
parser = argparse.ArgumentParser(description="Register best model in MLflow") parser = argparse.ArgumentParser(description="Register best model in MLflow")
+3
View File
@@ -24,6 +24,9 @@ import numpy as np
from sklearn.datasets import load_digits from sklearn.datasets import load_digits
import mlflow import mlflow
import os
mlflow.set_tracking_uri(os.environ.get("MLFLOW_TRACKING_URI", "http://localhost:5555"))
from flask import Flask, request, jsonify from flask import Flask, request, jsonify
+1
View File
@@ -35,6 +35,7 @@ import mlflow.pytorch
# Подавляем INFO MLflow о переменных окружения (напр. OPENAI_API_KEY) при логировании модели # Подавляем INFO MLflow о переменных окружения (напр. OPENAI_API_KEY) при логировании модели
import os import os
os.environ.setdefault("MLFLOW_RECORD_ENV_VARS_IN_MODEL_LOGGING", "false") os.environ.setdefault("MLFLOW_RECORD_ENV_VARS_IN_MODEL_LOGGING", "false")
mlflow.set_tracking_uri(os.environ.get("MLFLOW_TRACKING_URI", "http://localhost:5555"))
# ─── Модель: простая CNN ─── # ─── Модель: простая CNN ───
+1
View File
@@ -24,6 +24,7 @@ import mlflow.sklearn
# Подавляем INFO MLflow о переменных окружения (напр. OPENAI_API_KEY) при логировании модели # Подавляем INFO MLflow о переменных окружения (напр. OPENAI_API_KEY) при логировании модели
import os import os
os.environ.setdefault("MLFLOW_RECORD_ENV_VARS_IN_MODEL_LOGGING", "false") os.environ.setdefault("MLFLOW_RECORD_ENV_VARS_IN_MODEL_LOGGING", "false")
mlflow.set_tracking_uri(os.environ.get("MLFLOW_TRACKING_URI", "http://localhost:5555"))
from mlflow.models.signature import infer_signature from mlflow.models.signature import infer_signature
+17 -24
View File
@@ -3,45 +3,38 @@
# Запуск MLflow Tracking Server # Запуск MLflow Tracking Server
# Откройте в браузере: http://localhost:5555 # Откройте в браузере: http://localhost:5555
# #
# Backend store = file://$(pwd)/mlruns (тот же каталог, куда скрипты # Backend store = SQLite (mlflow.db) — recommended MLflow backend.
# пишут по умолчанию). Поэтому UI видит все эксперименты/запуски # Скрипты в src/ логируют на этот сервер (http://localhost:5555) через
# без доп. настройки tracking URI в скриптах. # mlflow.set_tracking_uri(...) — поэтому UI и скрипты работают с одной
# базой, и эксперименты сразу видны в UI.
# #
# ВАЖНО: file-store как СЕРВЕРНЫЙ бэкенд в новых MLflow (3.x) переведён в # ⚠️ file-store как tracking backend в новых MLflow (3.x) переведён в
# maintenance mode и по умолчанию запрещён. Официальный opt-out — # maintenance mode и запрещён (ошибка и на сервере, и у клиента-скрипта).
# MLFLOW_ALLOW_FILE_STORE=true (его и выставляем ниже). Это касается только # Поэтому используем sqlite. Артефакты при этом лежат на локальном диске
# `mlflow server`; клиентский file store (как пишут скрипты) работает и без него. # в ./artifacts (file-based artifact repository — он НЕ в maintenance mode).
# #
# Альтернатива — SQLite-бэкенд (recommended MLflow, без opt-out). Тогда # Запустите сервер ДО запуска скриптов обучения:
# раскомментируйте блок SQLITE ниже и закомментируйте FILE, а в скриптах # ./start_ui.sh # в одном терминале
# задайте mlflow.set_tracking_uri("http://localhost:5555") # python src/train_simple.py ... # в другом (нужен работающий сервер)
# (или export MLFLOW_TRACKING_URI=http://localhost:5555).
# ============================================================ # ============================================================
set -e set -e
cd "$(dirname "$0")" cd "$(dirname "$0")"
PORT=5555 PORT="${MLFLOW_PORT:-5555}"
echo "🚀 Запускаем MLflow Tracking Server..." echo "🚀 Запускаем MLflow Tracking Server..."
echo " UI: http://localhost:${PORT}" echo " UI: http://localhost:${PORT}"
echo " Store: file://${PWD}/mlruns" echo " Backend: sqlite:///${PWD}/mlflow.db"
echo " Artifacts: file://${PWD}/artifacts" echo " Artifacts: file://${PWD}/artifacts"
echo "" echo ""
echo " Скрипты логируют сюда через MLFLOW_TRACKING_URI=http://localhost:${PORT}"
echo " (можно переопределить: export MLFLOW_TRACKING_URI=http://<host>:${PORT})"
echo ""
echo " Нажмите Ctrl+C для остановки" echo " Нажмите Ctrl+C для остановки"
echo "" echo ""
# ─── FILE backend (по умолчанию — единое хранилище со скриптами) ───
# MLFLOW_ALLOW_FILE_STORE=true — opt-out из maintenance mode file-store бэкенда.
export MLFLOW_ALLOW_FILE_STORE=true
mlflow server \ mlflow server \
--backend-store-uri "file://${PWD}/mlruns" \ --backend-store-uri "sqlite:///${PWD}/mlflow.db" \
--default-artifact-root "file://${PWD}/artifacts" \ --default-artifact-root "file://${PWD}/artifacts" \
--host 0.0.0.0 \ --host 0.0.0.0 \
--port "${PORT}" --port "${PORT}"
# ─── SQLITE backend (альтернатива — см. комментарий в шапке) ──────
# mlflow server \
# --backend-store-uri sqlite:///mlflow.db \
# --default-artifact-root ./artifacts \
# --host 0.0.0.0 \
# --port "${PORT}"