Workers parallèles adaptatifs : -w auto = cœurs - 2, résolu au lancement de chaque run
This commit is contained in:
@ -33,7 +33,7 @@ services:
|
||||
- LIDAR_OUTPUT_DIR=/data/output
|
||||
# Les générations lancées depuis une webapp distante utilisent le GPU
|
||||
- LIDAR_GPU=1
|
||||
- LIDAR_WORKERS=10
|
||||
- LIDAR_WORKERS=auto
|
||||
# Protéger l'API si le réseau n'est pas de confiance : même valeur que
|
||||
# LIDAR_REMOTE_TOKEN sur chaque webapp distante (sinon, laisser commenté)
|
||||
# - LIDAR_API_TOKEN=change-moi
|
||||
|
||||
@ -8,6 +8,7 @@ import logging
|
||||
import signal
|
||||
import sys
|
||||
|
||||
from .pipeline import resolve_workers
|
||||
from .pipeline import LidarArchaeoPipeline
|
||||
from .gpu import log_gpu_status
|
||||
|
||||
@ -117,9 +118,10 @@ def main():
|
||||
)
|
||||
parser.add_argument(
|
||||
"-w", "--workers",
|
||||
type=int,
|
||||
default=1,
|
||||
help="Nombre de workers pour traitement parallèle (défaut: 1)"
|
||||
type=str,
|
||||
default="auto",
|
||||
help="Nombre de workers pour traitement parallèle, ou « auto » = "
|
||||
"cœurs - 2 évalué au lancement du run (défaut: auto)"
|
||||
)
|
||||
parser.add_argument(
|
||||
"-g", "--gpu",
|
||||
@ -361,7 +363,7 @@ def main():
|
||||
input_dir=args.input,
|
||||
output_dir=args.output,
|
||||
resolution=args.resolution,
|
||||
workers=args.workers,
|
||||
workers=resolve_workers(args.workers),
|
||||
force=args.force,
|
||||
ground_method=args.ground_classification,
|
||||
ign_classes=args.ign_classes,
|
||||
|
||||
@ -9,6 +9,7 @@ LidarArchaeoPipeline coordinates the full processing chain:
|
||||
|
||||
import logging
|
||||
import multiprocessing
|
||||
import os
|
||||
import shutil
|
||||
import time
|
||||
from concurrent.futures import ProcessPoolExecutor, as_completed, TimeoutError as FuturesTimeoutError
|
||||
@ -25,6 +26,25 @@ except RuntimeError:
|
||||
logger = logging.getLogger("lidar")
|
||||
|
||||
|
||||
def resolve_workers(value):
|
||||
"""Traduit l'option -w en nombre effectif de workers.
|
||||
|
||||
Résolu au lancement de chaque run (pas au démarrage du serveur, ni par
|
||||
tuile : le pool de processus vit le temps du run). « auto » = cœurs
|
||||
logiques - 2 (un pour l'OS/serveur, un pour les phases I/O et
|
||||
l'indexation), borné [2, 16] — chaque worker traite une tuile et peut
|
||||
lancer un processus PDAL en flux, le compte reste raisonnable même sur
|
||||
une grosse machine.
|
||||
"""
|
||||
if isinstance(value, str) and value.strip().lower() == "auto":
|
||||
cpus = os.cpu_count() or 4
|
||||
return max(2, min(cpus - 2, 16))
|
||||
try:
|
||||
return max(1, int(value))
|
||||
except (TypeError, ValueError):
|
||||
return 1
|
||||
|
||||
|
||||
def _file_basename(path):
|
||||
"""Extract base name from a LAZ/LAS file, removing all known extensions.
|
||||
|
||||
|
||||
@ -14,7 +14,7 @@ class TestCLIParsing:
|
||||
parser.add_argument("input", help="Input directory")
|
||||
parser.add_argument("-o", "--output", default="/data/output")
|
||||
parser.add_argument("-r", "--resolution", type=float, default=0.5)
|
||||
parser.add_argument("-w", "--workers", type=int, default=1)
|
||||
parser.add_argument("-w", "--workers", type=str, default="auto")
|
||||
parser.add_argument("-f", "--force", action="store_true")
|
||||
parser.add_argument("--file", nargs="+", type=str, default=None)
|
||||
parser.add_argument("--keep-tif", action="store_true")
|
||||
@ -23,7 +23,7 @@ class TestCLIParsing:
|
||||
assert args.input == "./input"
|
||||
assert args.output == "/data/output"
|
||||
assert args.resolution == 0.5
|
||||
assert args.workers == 1
|
||||
assert args.workers == "auto"
|
||||
assert args.force is False
|
||||
assert args.file is None
|
||||
assert args.keep_tif is False
|
||||
|
||||
@ -254,3 +254,26 @@ class TestEffectiveGroundMethod:
|
||||
p = LidarArchaeoPipeline(tmpdir, tmpdir, ground_method='smrf',
|
||||
ign_classes="sol,unclassified")
|
||||
assert p._effective_ground_method() == "smrf"
|
||||
|
||||
|
||||
class TestResolveWorkers:
|
||||
def test_auto_scales_with_cpus(self):
|
||||
from lidar_pipeline.pipeline import resolve_workers
|
||||
import os
|
||||
w = resolve_workers('auto')
|
||||
assert w == max(2, min((os.cpu_count() or 4) - 2, 16))
|
||||
|
||||
def test_auto_bounded(self):
|
||||
from lidar_pipeline.pipeline import resolve_workers
|
||||
# Bornes : jamais sous 2, jamais au-dessus de 16
|
||||
assert 2 <= resolve_workers('auto') <= 16
|
||||
|
||||
def test_explicit_int(self):
|
||||
from lidar_pipeline.pipeline import resolve_workers
|
||||
assert resolve_workers('4') == 4
|
||||
assert resolve_workers(7) == 7
|
||||
|
||||
def test_invalid_falls_back_to_one(self):
|
||||
from lidar_pipeline.pipeline import resolve_workers
|
||||
assert resolve_workers('abc') == 1
|
||||
assert resolve_workers(None) == 1
|
||||
|
||||
@ -1280,7 +1280,13 @@ def _build_command(tiles, regenerate=False, ground_class="ign", ign_classes="sol
|
||||
if reclassify:
|
||||
cmd += ["--force-classification"]
|
||||
if os.environ.get("LIDAR_GPU", "") == "1":
|
||||
cmd += ["-g", "all", "-w", os.environ.get("LIDAR_WORKERS", "10")]
|
||||
cmd += ["-g", "all"]
|
||||
# Workers : entier explicite via LIDAR_WORKERS, sinon « auto » —
|
||||
# résolu par le pipeline au départ de chaque run (cœurs de la
|
||||
# machine au moment du lancement).
|
||||
workers = os.environ.get("LIDAR_WORKERS", "auto").strip()
|
||||
if workers.lower() not in ("auto", ""):
|
||||
cmd += ["-w", workers]
|
||||
# Carte régénérée après chaque tuile terminée : la webapp l'affiche en
|
||||
# direct via /api/tiles pendant le run.
|
||||
cmd += ["--incremental-index"]
|
||||
|
||||
Reference in New Issue
Block a user