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
|
- LIDAR_OUTPUT_DIR=/data/output
|
||||||
# Les générations lancées depuis une webapp distante utilisent le GPU
|
# Les générations lancées depuis une webapp distante utilisent le GPU
|
||||||
- LIDAR_GPU=1
|
- 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
|
# 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_REMOTE_TOKEN sur chaque webapp distante (sinon, laisser commenté)
|
||||||
# - LIDAR_API_TOKEN=change-moi
|
# - LIDAR_API_TOKEN=change-moi
|
||||||
|
|||||||
@ -8,6 +8,7 @@ import logging
|
|||||||
import signal
|
import signal
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
|
from .pipeline import resolve_workers
|
||||||
from .pipeline import LidarArchaeoPipeline
|
from .pipeline import LidarArchaeoPipeline
|
||||||
from .gpu import log_gpu_status
|
from .gpu import log_gpu_status
|
||||||
|
|
||||||
@ -117,9 +118,10 @@ def main():
|
|||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"-w", "--workers",
|
"-w", "--workers",
|
||||||
type=int,
|
type=str,
|
||||||
default=1,
|
default="auto",
|
||||||
help="Nombre de workers pour traitement parallèle (défaut: 1)"
|
help="Nombre de workers pour traitement parallèle, ou « auto » = "
|
||||||
|
"cœurs - 2 évalué au lancement du run (défaut: auto)"
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"-g", "--gpu",
|
"-g", "--gpu",
|
||||||
@ -361,7 +363,7 @@ def main():
|
|||||||
input_dir=args.input,
|
input_dir=args.input,
|
||||||
output_dir=args.output,
|
output_dir=args.output,
|
||||||
resolution=args.resolution,
|
resolution=args.resolution,
|
||||||
workers=args.workers,
|
workers=resolve_workers(args.workers),
|
||||||
force=args.force,
|
force=args.force,
|
||||||
ground_method=args.ground_classification,
|
ground_method=args.ground_classification,
|
||||||
ign_classes=args.ign_classes,
|
ign_classes=args.ign_classes,
|
||||||
|
|||||||
@ -9,6 +9,7 @@ LidarArchaeoPipeline coordinates the full processing chain:
|
|||||||
|
|
||||||
import logging
|
import logging
|
||||||
import multiprocessing
|
import multiprocessing
|
||||||
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
import time
|
import time
|
||||||
from concurrent.futures import ProcessPoolExecutor, as_completed, TimeoutError as FuturesTimeoutError
|
from concurrent.futures import ProcessPoolExecutor, as_completed, TimeoutError as FuturesTimeoutError
|
||||||
@ -25,6 +26,25 @@ except RuntimeError:
|
|||||||
logger = logging.getLogger("lidar")
|
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):
|
def _file_basename(path):
|
||||||
"""Extract base name from a LAZ/LAS file, removing all known extensions.
|
"""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("input", help="Input directory")
|
||||||
parser.add_argument("-o", "--output", default="/data/output")
|
parser.add_argument("-o", "--output", default="/data/output")
|
||||||
parser.add_argument("-r", "--resolution", type=float, default=0.5)
|
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("-f", "--force", action="store_true")
|
||||||
parser.add_argument("--file", nargs="+", type=str, default=None)
|
parser.add_argument("--file", nargs="+", type=str, default=None)
|
||||||
parser.add_argument("--keep-tif", action="store_true")
|
parser.add_argument("--keep-tif", action="store_true")
|
||||||
@ -23,7 +23,7 @@ class TestCLIParsing:
|
|||||||
assert args.input == "./input"
|
assert args.input == "./input"
|
||||||
assert args.output == "/data/output"
|
assert args.output == "/data/output"
|
||||||
assert args.resolution == 0.5
|
assert args.resolution == 0.5
|
||||||
assert args.workers == 1
|
assert args.workers == "auto"
|
||||||
assert args.force is False
|
assert args.force is False
|
||||||
assert args.file is None
|
assert args.file is None
|
||||||
assert args.keep_tif is False
|
assert args.keep_tif is False
|
||||||
|
|||||||
@ -254,3 +254,26 @@ class TestEffectiveGroundMethod:
|
|||||||
p = LidarArchaeoPipeline(tmpdir, tmpdir, ground_method='smrf',
|
p = LidarArchaeoPipeline(tmpdir, tmpdir, ground_method='smrf',
|
||||||
ign_classes="sol,unclassified")
|
ign_classes="sol,unclassified")
|
||||||
assert p._effective_ground_method() == "smrf"
|
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:
|
if reclassify:
|
||||||
cmd += ["--force-classification"]
|
cmd += ["--force-classification"]
|
||||||
if os.environ.get("LIDAR_GPU", "") == "1":
|
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
|
# Carte régénérée après chaque tuile terminée : la webapp l'affiche en
|
||||||
# direct via /api/tiles pendant le run.
|
# direct via /api/tiles pendant le run.
|
||||||
cmd += ["--incremental-index"]
|
cmd += ["--incremental-index"]
|
||||||
|
|||||||
Reference in New Issue
Block a user