Spaces:
Sleeping
Revenir a ZeroGPU pour toutes les fonctions : le mode hybride plante
Browse filesTentative de garder seulement train_cnn_callback sur @spaces.GPU et de
forcer torch.device("cpu") ailleurs (MLP, evaluation, prediction,
extraction de features) pour eviter de toucher au quota GPU sur ces
chemins. Ca plante en production : import spaces monkey-patche torch
pour que torch.cuda.is_available() reponde True partout (meme hors
decorateur), et un chemin bas niveau echappe a cette emulation des que
du vrai calcul (construction de modele, forward/backward) est declenche
hors du decorateur -> "CUDA error: no CUDA-capable device is detected"
malgre device=cpu explicite. L'exception est avalee par le try/except
du callback donc pas moyen d'avoir la trace complete sans instrumenter
davantage, ce qui ne vaut pas le detour pour un contournement qui reste
fragile de toute facon.
Retour a la configuration deja validee : les six fonctions (MLP, CNN,
extraction de features, evaluation, prediction, test aleatoire) sont a
nouveau decorees @spaces.GPU, avec les durees dynamiques calibrees pour
MLP et CNN conservees. Reste du meme avis que la calibration des durees
+ le resserrement des curseurs UI est la bonne facon de limiter la
pression sur le quota journalier, pas une bascule CPU partielle.
- app.py +25 -8
- backbone_utils.py +1 -4
- predict_utils.py +3 -7
- train_utils.py +2 -9
|
@@ -40,6 +40,27 @@ def refresh_gallery_callback(split_name, class_name, max_images):
|
|
| 40 |
# Tab 2 — MLP (baseline)
|
| 41 |
# ---------------------------------------------------------------------------
|
| 42 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 43 |
def train_mlp_callback(
|
| 44 |
num_layers, hidden_dim, dropout,
|
| 45 |
learning_rate, weight_decay, batch_size, epochs,
|
|
@@ -78,14 +99,6 @@ def train_mlp_callback(
|
|
| 78 |
# ---------------------------------------------------------------------------
|
| 79 |
# Tab 3 — SimpleCNN
|
| 80 |
# ---------------------------------------------------------------------------
|
| 81 |
-
# Seul le CNN reste sur ZeroGPU : entraîner un vrai CNN (convolutions, plusieurs
|
| 82 |
-
# époques de backward) s'est révélé ~9x plus lent sur CPU basic (601s vs 65s
|
| 83 |
-
# mesurés) alors que le MLP (~3.8x, 252s vs 66.6s) et tout ce qui n'est que de
|
| 84 |
-
# l'inférence (extraction de features, évaluation, prédiction) restent
|
| 85 |
-
# raisonnables sur CPU. Sur un Space matériel ZeroGPU, seules les fonctions
|
| 86 |
-
# décorées @spaces.GPU empruntent un vrai GPU (et consomment le quota du
|
| 87 |
-
# visiteur) ; tout le reste de ce fichier tourne sur le CPU du conteneur
|
| 88 |
-
# sans jamais toucher au quota.
|
| 89 |
|
| 90 |
def cnn_gpu_duration(
|
| 91 |
num_conv_blocks, base_filters, kernel_size, use_batchnorm,
|
|
@@ -155,6 +168,7 @@ def train_cnn_callback(
|
|
| 155 |
# Tab 4 — Backbone + ML classique
|
| 156 |
# ---------------------------------------------------------------------------
|
| 157 |
|
|
|
|
| 158 |
def extract_features_callback():
|
| 159 |
try:
|
| 160 |
_, class_names, counts = extract_all_features()
|
|
@@ -247,6 +261,7 @@ def download_model_callback(model_name, request: gr.Request):
|
|
| 247 |
return None
|
| 248 |
|
| 249 |
|
|
|
|
| 250 |
def evaluate_callback(model_name, request: gr.Request):
|
| 251 |
try:
|
| 252 |
summary, report_df, cm_df, cm_path = evaluate_saved_model(model_name, request.session_hash)
|
|
@@ -255,6 +270,7 @@ def evaluate_callback(model_name, request: gr.Request):
|
|
| 255 |
return {"Erreur": str(e)}, None, None, None
|
| 256 |
|
| 257 |
|
|
|
|
| 258 |
def predict_callback(model_name, image, request: gr.Request):
|
| 259 |
try:
|
| 260 |
return predict_uploaded_image(model_name, image, request.session_hash)
|
|
@@ -262,6 +278,7 @@ def predict_callback(model_name, image, request: gr.Request):
|
|
| 262 |
return f"Échec :\n{e}", None
|
| 263 |
|
| 264 |
|
|
|
|
| 265 |
def random_test_callback(model_name, request: gr.Request):
|
| 266 |
try:
|
| 267 |
return test_random_sample(model_name, request.session_hash)
|
|
|
|
| 40 |
# Tab 2 — MLP (baseline)
|
| 41 |
# ---------------------------------------------------------------------------
|
| 42 |
|
| 43 |
+
def mlp_gpu_duration(
|
| 44 |
+
num_layers, hidden_dim, dropout,
|
| 45 |
+
learning_rate, weight_decay, batch_size, epochs,
|
| 46 |
+
model_tag,
|
| 47 |
+
request: gr.Request,
|
| 48 |
+
):
|
| 49 |
+
# Calibré sur deux exécutions réelles :
|
| 50 |
+
# 2 couches, hidden_dim=256, epochs=30 -> 66.6s (2.22 s/époque)
|
| 51 |
+
# 5 couches, hidden_dim=1024, epochs=50 -> 140.8s (2.82 s/époque)
|
| 52 |
+
# Le jeu de données est minuscule (peu de pas par époque) : le temps est
|
| 53 |
+
# dominé par un overhead fixe, hidden_dim ne le fait varier que doucement
|
| 54 |
+
# (x4 sur hidden_dim -> seulement +27% par époque). On plafonne à 180s :
|
| 55 |
+
# nettement sous le quota journalier d'un compte gratuit (300s), pour
|
| 56 |
+
# qu'un seul entraînement au pire réglage ne consomme pas tout le quota
|
| 57 |
+
# du jour d'un·e étudiant·e.
|
| 58 |
+
per_epoch = 2.22 + 0.0008 * max(0, int(hidden_dim) - 256)
|
| 59 |
+
estimated = 15 + per_epoch * int(epochs)
|
| 60 |
+
return min(180, max(45, int(estimated * 1.4)))
|
| 61 |
+
|
| 62 |
+
|
| 63 |
+
@spaces.GPU(duration=mlp_gpu_duration)
|
| 64 |
def train_mlp_callback(
|
| 65 |
num_layers, hidden_dim, dropout,
|
| 66 |
learning_rate, weight_decay, batch_size, epochs,
|
|
|
|
| 99 |
# ---------------------------------------------------------------------------
|
| 100 |
# Tab 3 — SimpleCNN
|
| 101 |
# ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 102 |
|
| 103 |
def cnn_gpu_duration(
|
| 104 |
num_conv_blocks, base_filters, kernel_size, use_batchnorm,
|
|
|
|
| 168 |
# Tab 4 — Backbone + ML classique
|
| 169 |
# ---------------------------------------------------------------------------
|
| 170 |
|
| 171 |
+
@spaces.GPU(duration=60)
|
| 172 |
def extract_features_callback():
|
| 173 |
try:
|
| 174 |
_, class_names, counts = extract_all_features()
|
|
|
|
| 261 |
return None
|
| 262 |
|
| 263 |
|
| 264 |
+
@spaces.GPU(duration=120)
|
| 265 |
def evaluate_callback(model_name, request: gr.Request):
|
| 266 |
try:
|
| 267 |
summary, report_df, cm_df, cm_path = evaluate_saved_model(model_name, request.session_hash)
|
|
|
|
| 270 |
return {"Erreur": str(e)}, None, None, None
|
| 271 |
|
| 272 |
|
| 273 |
+
@spaces.GPU(duration=60)
|
| 274 |
def predict_callback(model_name, image, request: gr.Request):
|
| 275 |
try:
|
| 276 |
return predict_uploaded_image(model_name, image, request.session_hash)
|
|
|
|
| 278 |
return f"Échec :\n{e}", None
|
| 279 |
|
| 280 |
|
| 281 |
+
@spaces.GPU(duration=60)
|
| 282 |
def random_test_callback(model_name, request: gr.Request):
|
| 283 |
try:
|
| 284 |
return test_random_sample(model_name, request.session_hash)
|
|
@@ -37,10 +37,7 @@ def extract_all_features(batch_size: int = 64):
|
|
| 37 |
|
| 38 |
from data_utils import prepare_splits, get_class_names, HFDatasetWrapper, get_eval_transform
|
| 39 |
|
| 40 |
-
|
| 41 |
-
# Sur du matériel ZeroGPU, torch.cuda.is_available() répond True même
|
| 42 |
-
# hors décorateur (mode émulation), mais aucun GPU réel n'est alloué ici.
|
| 43 |
-
device = torch.device("cpu")
|
| 44 |
backbone = load_backbone(device)
|
| 45 |
backbone.eval()
|
| 46 |
|
|
|
|
| 37 |
|
| 38 |
from data_utils import prepare_splits, get_class_names, HFDatasetWrapper, get_eval_transform
|
| 39 |
|
| 40 |
+
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
|
|
|
|
|
|
|
|
|
|
| 41 |
backbone = load_backbone(device)
|
| 42 |
backbone.eval()
|
| 43 |
|
|
@@ -6,7 +6,7 @@ from PIL import Image
|
|
| 6 |
|
| 7 |
from config import CLASSICAL_MODEL_TYPES
|
| 8 |
from data_utils import get_eval_transform, prepare_splits, get_class_names
|
| 9 |
-
from train_utils import load_model, _load_meta
|
| 10 |
|
| 11 |
|
| 12 |
def _extract_feature(image: Image.Image, device: torch.device) -> np.ndarray:
|
|
@@ -28,9 +28,7 @@ def predict_uploaded_image(model_name: str, image: Image.Image, session_id: str)
|
|
| 28 |
meta = _load_meta(model_name, session_id)
|
| 29 |
model_type = meta["config"].get("model_type", "cnn")
|
| 30 |
class_names = meta["config"]["class_names"]
|
| 31 |
-
|
| 32 |
-
# le commentaire dans train_mlp, train_utils.py).
|
| 33 |
-
device = torch.device("cpu")
|
| 34 |
|
| 35 |
if model_type in CLASSICAL_MODEL_TYPES:
|
| 36 |
from classical_ml_utils import load_classical_pipeline
|
|
@@ -64,9 +62,7 @@ def test_random_sample(model_name: str, session_id: str):
|
|
| 64 |
meta = _load_meta(model_name, session_id)
|
| 65 |
model_type = meta["config"].get("model_type", "cnn")
|
| 66 |
class_names = get_class_names()
|
| 67 |
-
|
| 68 |
-
# le commentaire dans train_mlp, train_utils.py).
|
| 69 |
-
device = torch.device("cpu")
|
| 70 |
|
| 71 |
splits = prepare_splits()
|
| 72 |
test_dataset = splits["test"]
|
|
|
|
| 6 |
|
| 7 |
from config import CLASSICAL_MODEL_TYPES
|
| 8 |
from data_utils import get_eval_transform, prepare_splits, get_class_names
|
| 9 |
+
from train_utils import load_model, get_runtime_device, _load_meta
|
| 10 |
|
| 11 |
|
| 12 |
def _extract_feature(image: Image.Image, device: torch.device) -> np.ndarray:
|
|
|
|
| 28 |
meta = _load_meta(model_name, session_id)
|
| 29 |
model_type = meta["config"].get("model_type", "cnn")
|
| 30 |
class_names = meta["config"]["class_names"]
|
| 31 |
+
device = get_runtime_device()
|
|
|
|
|
|
|
| 32 |
|
| 33 |
if model_type in CLASSICAL_MODEL_TYPES:
|
| 34 |
from classical_ml_utils import load_classical_pipeline
|
|
|
|
| 62 |
meta = _load_meta(model_name, session_id)
|
| 63 |
model_type = meta["config"].get("model_type", "cnn")
|
| 64 |
class_names = get_class_names()
|
| 65 |
+
device = get_runtime_device()
|
|
|
|
|
|
|
| 66 |
|
| 67 |
splits = prepare_splits()
|
| 68 |
test_dataset = splits["test"]
|
|
@@ -451,12 +451,7 @@ def train_mlp(
|
|
| 451 |
epochs: int = 30,
|
| 452 |
model_tag: str = "",
|
| 453 |
):
|
| 454 |
-
|
| 455 |
-
# matériel ZeroGPU, torch.cuda.is_available() répond True même hors
|
| 456 |
-
# décorateur (mode émulation), mais aucun GPU réel n'est alloué ici —
|
| 457 |
-
# utiliser get_runtime_device() planterait avec "Low-level CUDA init
|
| 458 |
-
# reached". On force donc le CPU explicitement.
|
| 459 |
-
device = torch.device("cpu")
|
| 460 |
train_loader, val_loader, test_loader, class_names = make_loaders(batch_size)
|
| 461 |
num_classes = len(class_names)
|
| 462 |
input_size = 3 * IMAGE_SIZE * IMAGE_SIZE
|
|
@@ -573,9 +568,7 @@ def evaluate_saved_model(model_name: str, session_id: str):
|
|
| 573 |
|
| 574 |
|
| 575 |
def _evaluate_neural(model_name: str, meta: dict, session_id: str):
|
| 576 |
-
|
| 577 |
-
# le commentaire dans train_mlp).
|
| 578 |
-
device = torch.device("cpu")
|
| 579 |
model, meta = load_model(model_name, device, session_id)
|
| 580 |
|
| 581 |
batch_size = int(meta["config"].get("batch_size", 16))
|
|
|
|
| 451 |
epochs: int = 30,
|
| 452 |
model_tag: str = "",
|
| 453 |
):
|
| 454 |
+
device = get_runtime_device()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 455 |
train_loader, val_loader, test_loader, class_names = make_loaders(batch_size)
|
| 456 |
num_classes = len(class_names)
|
| 457 |
input_size = 3 * IMAGE_SIZE * IMAGE_SIZE
|
|
|
|
| 568 |
|
| 569 |
|
| 570 |
def _evaluate_neural(model_name: str, meta: dict, session_id: str):
|
| 571 |
+
device = get_runtime_device()
|
|
|
|
|
|
|
| 572 |
model, meta = load_model(model_name, device, session_id)
|
| 573 |
|
| 574 |
batch_size = int(meta["config"].get("batch_size", 16))
|