Split webapp for Raspberry Pi deployment, remote generation API and sync
La webapp (carte + vignettes) et la génération de tuiles se déploient sur deux machines : image légère Dockerfile.webapp (FastAPI + Pillow AVIF natif + pyproj) sur Raspberry Pi, pipeline complet sur la machine de traitement. LIDAR_GENERATION_URL délègue /api/generate, /api/preview et /api/status ; /api/sync ramène les tuiles par rsync puis régénère vignettes et index localement. Token partagé optionnel (LIDAR_API_TOKEN/LIDAR_REMOTE_TOKEN). Retire du dépôt les journaux internes (.swival, audit-findings) et les données (data/, notebooks/). Doc : docs/DEPLOY_WEBAPP.md.
This commit is contained in:
@ -27,6 +27,12 @@ _gpu_reason = None
|
||||
# GPU restriction from -g flag (host-level indices)
|
||||
_restricted_gpu_ids: list[int] | None = None
|
||||
|
||||
# Vrai si CUDA_VISIBLE_DEVICES a été écrit par _init_gpu lui-même (choix du
|
||||
# meilleur GPU). Cette valeur NE DOIT PAS être traitée comme une restriction
|
||||
# externe : sinon available_gpu_ids() ne retourne plus que le GPU choisi et
|
||||
# tous les workers reçoivent le même gpu_id (GPU 1 jamais utilisé).
|
||||
_env_set_by_init = False
|
||||
|
||||
# Discovered GPU candidates (populated by _pick_gpu)
|
||||
_gpu_candidates: list = []
|
||||
|
||||
@ -38,7 +44,7 @@ def _pick_gpu() -> list:
|
||||
result = subprocess.run(
|
||||
['nvidia-smi', '--query-gpu=index,name,compute_cap,memory.total',
|
||||
'--format=csv,noheader,nounits'],
|
||||
capture_output=True, text=True, timeout=5,
|
||||
capture_output=True, text=True, timeout=15,
|
||||
)
|
||||
if result.returncode != 0:
|
||||
return []
|
||||
@ -86,10 +92,12 @@ def _filter_candidates(gpus: list) -> list:
|
||||
|
||||
nvidia-smi lists ALL GPUs even when CUDA_VISIBLE_DEVICES is set
|
||||
(driver 580.x behavior), so we must filter manually.
|
||||
La variable écrite par _init_gpu (choix auto du meilleur GPU) est ignorée :
|
||||
seules les restrictions externes (run.sh -g, compose) comptent.
|
||||
"""
|
||||
# Filter by CUDA_VISIBLE_DEVICES if set
|
||||
# Filter by CUDA_VISIBLE_DEVICES if set externally
|
||||
cuda_visible = os.environ.get('CUDA_VISIBLE_DEVICES')
|
||||
if cuda_visible is not None:
|
||||
if cuda_visible is not None and not _env_set_by_init:
|
||||
try:
|
||||
visible = {int(i.strip()) for i in cuda_visible.split(',')}
|
||||
gpus = [g for g in gpus if g[0] in visible]
|
||||
@ -104,6 +112,40 @@ def _filter_candidates(gpus: list) -> list:
|
||||
return gpus
|
||||
|
||||
|
||||
def _runtime_candidates():
|
||||
"""Détection GPU de repli via le runtime CuPy (sans nvidia-smi).
|
||||
|
||||
nvidia-smi peut dépasser son timeout quand le système est chargé :
|
||||
_pick_gpu() retourne alors [] et les workers passent à tort en CPU.
|
||||
Les indices CuPy sont renumérotés selon CUDA_VISIBLE_DEVICES — on les
|
||||
remappe en indices hôtes pour rester compatible avec _filter_candidates.
|
||||
"""
|
||||
try:
|
||||
import cupy as _cp_runtime
|
||||
n = _cp_runtime.cuda.runtime.getDeviceCount()
|
||||
cuda_visible = os.environ.get('CUDA_VISIBLE_DEVICES')
|
||||
try:
|
||||
host_ids = [int(v.strip()) for v in cuda_visible.split(',')]
|
||||
except (ValueError, AttributeError):
|
||||
host_ids = list(range(n))
|
||||
gpus = []
|
||||
for i in range(min(n, len(host_ids))):
|
||||
props = _cp_runtime.cuda.runtime.getDeviceProperties(i)
|
||||
name = props.get('name', b'?')
|
||||
if isinstance(name, bytes):
|
||||
name = name.decode()
|
||||
major = int(props.get('major', 0))
|
||||
minor = int(props.get('minor', 0))
|
||||
mem_mi = int(props.get('totalGlobalMem', 0)) // (1024 * 1024)
|
||||
cap = f"{major}.{minor}"
|
||||
score = major * 1000 + minor * 100 + mem_mi
|
||||
gpus.append((host_ids[i], name, cap, mem_mi, score, major))
|
||||
gpus.sort(key=lambda g: g[4], reverse=True)
|
||||
return gpus
|
||||
except Exception:
|
||||
return []
|
||||
|
||||
|
||||
def _init_gpu():
|
||||
"""Lazily initialize CuPy on first GPU use.
|
||||
|
||||
@ -118,6 +160,9 @@ def _init_gpu():
|
||||
_gpu_initialized = True
|
||||
|
||||
candidates = _filter_candidates(_gpu_candidates)
|
||||
if not candidates:
|
||||
# Repli runtime : nvidia-smi a échoué (timeout système chargé)
|
||||
candidates = _filter_candidates(_runtime_candidates())
|
||||
if not candidates:
|
||||
logger.info("Pas de GPU utilisable — mode CPU uniquement")
|
||||
_xp = np
|
||||
@ -192,6 +237,8 @@ def _init_gpu():
|
||||
return
|
||||
|
||||
idx, name, mem_mi = _working_gpu
|
||||
global _env_set_by_init
|
||||
_env_set_by_init = True
|
||||
os.environ['CUDA_VISIBLE_DEVICES'] = str(idx)
|
||||
|
||||
import cupy as _real_cupy
|
||||
@ -238,9 +285,19 @@ def restrict_gpus(gpu_ids: list[int], set_env_var: bool = False):
|
||||
|
||||
|
||||
def set_active_gpu(gpu_id):
|
||||
"""Restrict to a single GPU by host-level index."""
|
||||
"""Restrict to a single GPU by host-level index.
|
||||
|
||||
Appelé par les workers spawnés (_process_file_standalone) : CuPy n'y est
|
||||
pas encore initialisé (le worker n'exécute pas log_gpu_status), il faut
|
||||
donc déclencher _init_gpu() ici, sinon le worker retombe silencieusement
|
||||
en CPU. On réduit aussi CUDA_VISIBLE_DEVICES à ce seul GPU avant l'init
|
||||
pour que le device 0 du worker soit le bon : sans cela, tous les workers
|
||||
avec plusieurs GPU visibles partagent le premier d'entre eux.
|
||||
"""
|
||||
global _restricted_gpu_ids
|
||||
_restricted_gpu_ids = [gpu_id]
|
||||
os.environ['CUDA_VISIBLE_DEVICES'] = str(gpu_id)
|
||||
_init_gpu()
|
||||
|
||||
|
||||
def _gpu_available():
|
||||
|
||||
Reference in New Issue
Block a user