From faf5be476037d3baf4da3515789906a176f9b040 Mon Sep 17 00:00:00 2001
From: shuishen <1109946754@qq.com>
Date: Tue, 25 Aug 2026 12:23:59 +0800
Subject: [PATCH] feat(pointcloud): auto-select verified GPU runtime
---
scripts/serve_workbench_console.py | 65 +++++++++++++++++++++++---------
1 files changed, 47 insertions(+), 18 deletions(-)
diff --git a/scripts/serve_workbench_console.py b/scripts/serve_workbench_console.py
index 65cd83f..b1d12ca 100644
--- a/scripts/serve_workbench_console.py
+++ b/scripts/serve_workbench_console.py
@@ -76,6 +76,8 @@
POINTCLOUD_INFERENCE_JOBS_LOCK = threading.Lock()
MAX_ANNOTATION_LABELS = 400_000
POINTCLOUD_CLASS_CODES = {1, 2, 5, 6, 15, 16}
+POINTCLOUD_CPU_ENVIRONMENT = "05-3d-pointcloud"
+POINTCLOUD_GPU_ENVIRONMENT = "05-3d-pointcloud-gpu"
class ApiError(ValueError):
@@ -128,6 +130,39 @@
for chunk in iter(lambda: stream.read(8 * 1024 * 1024), b""):
digest.update(chunk)
return digest.hexdigest()
+
+
+def pointcloud_execution_environment(root: Path, requested_device: str = "auto") -> dict[str, str]:
+ """Select only a fixed point-cloud interpreter after a short CUDA probe.
+
+ The console never accepts a browser-supplied Python path. A failed or
+ unavailable GPU environment is an expected condition for ``auto`` and
+ falls back to the retained CPU environment.
+ """
+ if requested_device not in {"auto", "cpu", "cuda"}:
+ raise ApiError("Point-cloud device must be auto, cpu, or cuda.")
+ cpu_python = root / ".venvs" / POINTCLOUD_CPU_ENVIRONMENT / "Scripts" / "python.exe"
+ gpu_python = root / ".venvs" / POINTCLOUD_GPU_ENVIRONMENT / "Scripts" / "python.exe"
+ if requested_device != "cpu" and gpu_python.is_file():
+ try:
+ probe = subprocess.run(
+ [str(gpu_python), "-c", "import json, torch; print(json.dumps({'cuda': bool(torch.cuda.is_available()), 'torch': torch.__version__}))"],
+ cwd=root,
+ capture_output=True,
+ text=True,
+ timeout=20,
+ check=False,
+ )
+ payload = json.loads(probe.stdout.strip().splitlines()[-1]) if probe.returncode == 0 and probe.stdout.strip() else {}
+ if payload.get("cuda") is True and isinstance(payload.get("torch"), str):
+ return {"python": str(gpu_python), "device": "cuda", "environment": POINTCLOUD_GPU_ENVIRONMENT, "torchVersion": payload["torch"]}
+ except (OSError, subprocess.SubprocessError, json.JSONDecodeError, IndexError):
+ pass
+ if requested_device == "cuda":
+ raise ApiError("CUDA was requested, but the fixed point-cloud GPU environment is unavailable.")
+ if not cpu_python.is_file():
+ raise ApiError("3D point-cloud CPU virtual environment is unavailable. Run the capability setup first.")
+ return {"python": str(cpu_python), "device": "cpu", "environment": POINTCLOUD_CPU_ENVIRONMENT, "torchVersion": "unknown"}
def trajectory_runs(root: Path) -> list[dict[str, Any]]:
@@ -441,11 +476,10 @@
return dict(value) if value else None
-def execute_pointcloud_training_job(root: Path, job_id: str, annotation: Path, output: Path, device: str) -> None:
+def execute_pointcloud_training_job(root: Path, job_id: str, annotation: Path, output: Path, execution: dict[str, str]) -> None:
with POINTCLOUD_TRAINING_JOBS_LOCK:
POINTCLOUD_TRAINING_JOBS[job_id].update({"status": "running", "stage": "training", "startedAt": datetime.now(UTC).isoformat()})
- python = root / ".venvs" / "05-3d-pointcloud" / "Scripts" / "python.exe"
- command = [str(python), str(root / "capabilities" / "05-3d-pointcloud" / "train_pointcloud_semantic_model.py"), "--annotation", str(annotation), "--output", str(output), "--device", device]
+ command = [execution["python"], str(root / "capabilities" / "05-3d-pointcloud" / "train_pointcloud_semantic_model.py"), "--annotation", str(annotation), "--output", str(output), "--device", execution["device"]]
try:
with RUN_LOCK:
completed = subprocess.run(command, cwd=root, capture_output=True, text=True, timeout=14_400, check=False)
@@ -521,11 +555,10 @@
raise ApiError("LAS/LAZ 文件没有点记录。请选择包含实际 RGB 点位的完整点云文件,而不是空分块。")
-def execute_pointcloud_inference_job(root: Path, job_id: str, model: Path, source: Path, output: Path) -> None:
+def execute_pointcloud_inference_job(root: Path, job_id: str, model: Path, source: Path, output: Path, execution: dict[str, str]) -> None:
with POINTCLOUD_INFERENCE_JOBS_LOCK:
POINTCLOUD_INFERENCE_JOBS[job_id].update({"status": "running", "stage": "inference", "startedAt": datetime.now(UTC).isoformat()})
- python = root / ".venvs" / "05-3d-pointcloud" / "Scripts" / "python.exe"
- command = [str(python), str(root / "capabilities" / "05-3d-pointcloud" / "apply_pointcloud_semantic_model.py"), "--model", str(model), "--input", str(source), "--output", str(output), "--device", "cpu"]
+ command = [execution["python"], str(root / "capabilities" / "05-3d-pointcloud" / "apply_pointcloud_semantic_model.py"), "--model", str(model), "--input", str(source), "--output", str(output), "--device", execution["device"]]
try:
with RUN_LOCK:
completed = subprocess.run(command, cwd=root, capture_output=True, text=True, timeout=14_400, check=False)
@@ -1643,9 +1676,9 @@
source_bytes[name] = raw_path.stat().st_size
shutil.copyfile(raw_path, processed_root / name)
output = self.root / "shared" / "outputs" / "05-3d-pointcloud" / "runs" / run_id
- python = self.root / ".venvs" / "05-3d-pointcloud" / "Scripts" / "python.exe"
+ python = self.root / ".venvs" / POINTCLOUD_CPU_ENVIRONMENT / "Scripts" / "python.exe"
if not python.is_file():
- raise ApiError("3D point-cloud virtual environment is unavailable. Run the capability setup first.")
+ raise ApiError("3D point-cloud CPU virtual environment is unavailable. Run the capability setup first.")
command = [str(python), str(self.root / "capabilities" / "05-3d-pointcloud" / "run_pointcloud_understanding.py"), "--input", str(processed_root), "--output", str(output), "--ground-up-axis", "z"]
with RUN_LOCK:
self.run_command(command, 900)
@@ -1710,15 +1743,13 @@
record = load_json(annotation)
if record.get("schema_version") != 1:
raise ApiError("The selected annotation revision is unavailable.")
- python = self.root / ".venvs" / "05-3d-pointcloud" / "Scripts" / "python.exe"
- if not python.is_file():
- raise ApiError("3D point-cloud virtual environment is unavailable. Run the capability setup first.")
+ execution = pointcloud_execution_environment(self.root, device)
job_id = uuid4().hex
output = self.root / "shared" / "outputs" / "05-3d-pointcloud" / "training-runs" / make_run_id("semantic-model")
- job = {"id": job_id, "annotationId": annotation_id, "status": "queued", "stage": "queued", "device": device, "createdAt": datetime.now(UTC).isoformat()}
+ job = {"id": job_id, "annotationId": annotation_id, "status": "queued", "stage": "queued", "requestedDevice": device, "device": execution["device"], "environment": execution["environment"], "torchVersion": execution["torchVersion"], "createdAt": datetime.now(UTC).isoformat()}
with POINTCLOUD_TRAINING_JOBS_LOCK:
POINTCLOUD_TRAINING_JOBS[job_id] = job
- thread = threading.Thread(target=execute_pointcloud_training_job, args=(self.root, job_id, annotation, output, device), daemon=True, name=f"pointcloud-training-{job_id[:8]}")
+ thread = threading.Thread(target=execute_pointcloud_training_job, args=(self.root, job_id, annotation, output, execution), daemon=True, name=f"pointcloud-training-{job_id[:8]}")
thread.start()
return dict(job)
@@ -1735,9 +1766,7 @@
if Path(name).suffix.lower() not in suffixes:
raise ApiError("Model inference requires a PLY, PCD, XYZ, LAS, or LAZ point cloud.")
validate_pointcloud_model_input(staged_path)
- python = self.root / ".venvs" / "05-3d-pointcloud" / "Scripts" / "python.exe"
- if not python.is_file():
- raise ApiError("3D point-cloud virtual environment is unavailable. Run the capability setup first.")
+ execution = pointcloud_execution_environment(self.root)
run_id = make_run_id("semantic-inference")
raw_root = self.root / "shared" / "data" / "raw" / "05-3d-pointcloud" / "model-inference-runs" / run_id
processed_root = self.root / "shared" / "data" / "processed" / "05-3d-pointcloud" / "model-inference-runs" / run_id
@@ -1760,10 +1789,10 @@
except ValueError as exc:
raise ApiError("Selected model is outside the allowed training output directory.") from exc
job_id = uuid4().hex
- job = {"id": job_id, "runId": run_id, "modelId": model_id, "inputName": name, "status": "queued", "stage": "queued", "device": "cpu", "createdAt": datetime.now(UTC).isoformat(), "sourceSha256": actual_sha256, "rawInput": relative_path(self.root, raw_path), "processedInput": relative_path(self.root, processed_path)}
+ job = {"id": job_id, "runId": run_id, "modelId": model_id, "inputName": name, "status": "queued", "stage": "queued", "requestedDevice": "auto", "device": execution["device"], "environment": execution["environment"], "torchVersion": execution["torchVersion"], "createdAt": datetime.now(UTC).isoformat(), "sourceSha256": actual_sha256, "rawInput": relative_path(self.root, raw_path), "processedInput": relative_path(self.root, processed_path)}
with POINTCLOUD_INFERENCE_JOBS_LOCK:
POINTCLOUD_INFERENCE_JOBS[job_id] = job
- thread = threading.Thread(target=execute_pointcloud_inference_job, args=(self.root, job_id, model_path, processed_path, output), daemon=True, name=f"pointcloud-inference-{job_id[:8]}")
+ thread = threading.Thread(target=execute_pointcloud_inference_job, args=(self.root, job_id, model_path, processed_path, output, execution), daemon=True, name=f"pointcloud-inference-{job_id[:8]}")
thread.start()
return dict(job)
--
Gitblit v1.9.3