Rendre la carte autonome, télécharger la pyramide en fond, refermer les trous de zoom et borner les workers GPU par la VRAM

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Antoine Jacquin
2026-09-27 11:23:51 +02:00
parent 934e2dab55
commit 147a469ddc
10 changed files with 586 additions and 37 deletions

View File

@ -294,6 +294,76 @@ def set_active_gpu(gpu_id):
_init_gpu()
def force_cpu():
"""Interdit le GPU à ce processus (worker en excédent de VRAM).
Aucun candidat ne survit au filtre et CUDA_VISIBLE_DEVICES vide empêche
CuPy de créer un contexte (~300 Mo de VRAM par processus sinon).
"""
global _restricted_gpu_ids
_restricted_gpu_ids = []
os.environ['CUDA_VISIBLE_DEVICES'] = ''
_init_gpu()
# Pic de VRAM d'un worker (Mo) : contexte CUDA (~300) + calage conjoint des
# lignes sur CuPy (~70-100 o par point sol en float64, ~15 M points par dalle
# 0,2 m + bande de 100 m) + transformée de distance de _fill_nans (indices
# int32 ×2 sur ~7000² px ≈ 400 Mo) ; le pool CuPy garde ses blocs entre deux
# étapes. Estimation depuis le code, à ajuster par LIDAR_GPU_WORKER_MIB.
GPU_WORKER_MIB = int(os.environ.get("LIDAR_GPU_WORKER_MIB", "2048") or 2048)
# VRAM laissée libre sur chaque GPU (affichage, autres processus).
GPU_RESERVE_MIB = int(os.environ.get("LIDAR_GPU_RESERVE_MIB", "512") or 512)
def gpu_free_mib():
"""VRAM libre par GPU (indice hôte → Mo) via nvidia-smi, sans contexte CUDA.
Dictionnaire vide si la mesure échoue : l'appelant ne borne alors rien.
"""
try:
import subprocess
result = subprocess.run(
['nvidia-smi', '--query-gpu=index,memory.free',
'--format=csv,noheader,nounits'],
capture_output=True, text=True, timeout=15,
)
if result.returncode != 0:
return {}
free = {}
for line in result.stdout.strip().splitlines():
parts = [p.strip() for p in line.split(',')]
if len(parts) >= 2:
free[int(parts[0])] = int(parts[1])
return free
except Exception:
return {}
def gpu_worker_slots(gpu_ids, n_workers, free_mib=None):
"""Place de chaque worker du pool : indice GPU, -1 (CPU forcé) ou None.
Chaque GPU reçoit au plus (VRAM libre − réserve) / GPU_WORKER_MIB
workers (au moins un), entrelacés entre GPU ; l'excédent tourne en CPU
au lieu de saturer la VRAM (OOM). Sans GPU : None partout (choix laissé
au worker). VRAM inconnue : round-robin sans borne (comportement
historique).
"""
if not gpu_ids:
return [None] * n_workers
if free_mib is None:
free_mib = gpu_free_mib()
if not all(g in free_mib for g in gpu_ids):
return [gpu_ids[i % len(gpu_ids)] for i in range(n_workers)]
capacity = {g: max(1, (free_mib[g] - GPU_RESERVE_MIB) // GPU_WORKER_MIB)
for g in gpu_ids}
slots = []
for rank in range(max(capacity.values())):
slots += [g for g in gpu_ids if capacity[g] > rank]
slots = slots[:n_workers]
return slots + [-1] * (n_workers - len(slots))
def _gpu_available():
"""Check if GPU is usable right now."""
try: