Add YOLO preflight runtime diagnostics
This commit is contained in:
+1
-1
@@ -299,7 +299,7 @@ To validate only local model/manifest paths on a machine without optional AI dep
|
||||
python scripts/yolo_preflight.py --model-path /absolute/path/to/local-model.pt --tile-manifest-path /absolute/path/to/manifest.json --assume-dependencies --json
|
||||
```
|
||||
|
||||
The preflight checks configuration, dependency availability, local model file existence, tile manifest validity, tile count and referenced tile paths. It does not load a YOLO model, run inference or download weights.
|
||||
The preflight checks configuration, dependency availability, local model file existence, tile manifest validity, tile count and referenced tile paths. JSON output also includes runtime diagnostics for the model directory, `YOLO_CONFIG_DIR`, installed `torch`/`ultralytics` versions and CUDA availability when dependency checks pass. It does not load a YOLO model, run inference or download weights.
|
||||
|
||||
### Run backend
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
from importlib import metadata
|
||||
from pathlib import Path
|
||||
from typing import Any, Type
|
||||
|
||||
@@ -42,6 +44,10 @@ class YoloPreflightService:
|
||||
"max_tiles": resolved_settings.yolo_max_tiles,
|
||||
"will_download_models": False,
|
||||
"will_run_inference": False,
|
||||
"runtime": YoloPreflightService._runtime_details(
|
||||
settings=resolved_settings,
|
||||
assume_dependencies=assume_dependencies,
|
||||
),
|
||||
}
|
||||
|
||||
if not resolved_settings.yolo_enabled:
|
||||
@@ -50,6 +56,8 @@ class YoloPreflightService:
|
||||
|
||||
dependencies_available = True if assume_dependencies else yolo_adapter_class.dependencies_available()
|
||||
result["checks"]["dependencies_available"] = dependencies_available
|
||||
if dependencies_available and not assume_dependencies:
|
||||
result["runtime"]["cuda_available"] = YoloPreflightService._torch_cuda_available()
|
||||
if not dependencies_available:
|
||||
result["status"] = "dependency_unavailable"
|
||||
result["message"] = "YOLO dependencies are not installed. Install backend optional extras with geointel-backend[ai]."
|
||||
@@ -115,3 +123,35 @@ class YoloPreflightService:
|
||||
else:
|
||||
result["message"] = "Configured YOLO preflight passed. No model was loaded and no inference was run."
|
||||
return result
|
||||
|
||||
@staticmethod
|
||||
def _runtime_details(*, settings: Settings, assume_dependencies: bool) -> dict[str, Any]:
|
||||
model_directory = None
|
||||
if settings.yolo_model_path:
|
||||
model_directory = str(Path(settings.yolo_model_path).expanduser().parent)
|
||||
return {
|
||||
"dependencies_assumed": assume_dependencies,
|
||||
"model_directory": model_directory,
|
||||
"yolo_config_dir": os.environ.get("YOLO_CONFIG_DIR"),
|
||||
"torch_version": YoloPreflightService._package_version("torch"),
|
||||
"ultralytics_version": YoloPreflightService._package_version("ultralytics"),
|
||||
"cuda_available": None,
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def _package_version(package_name: str) -> str | None:
|
||||
try:
|
||||
return metadata.version(package_name)
|
||||
except metadata.PackageNotFoundError:
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def _torch_cuda_available() -> bool | None:
|
||||
try:
|
||||
import torch
|
||||
except Exception:
|
||||
return None
|
||||
try:
|
||||
return bool(torch.cuda.is_available())
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
@@ -54,7 +54,9 @@ def _manifest(tmp_path: Path, tile_count: int = 1) -> Path:
|
||||
return manifest_path
|
||||
|
||||
|
||||
def test_yolo_preflight_reports_disabled_without_loading_model(tmp_path: Path) -> None:
|
||||
def test_yolo_preflight_reports_disabled_without_loading_model(tmp_path: Path, monkeypatch) -> None:
|
||||
monkeypatch.setenv("YOLO_CONFIG_DIR", str(tmp_path / "ultralytics"))
|
||||
|
||||
result = YoloPreflightService.run(
|
||||
settings=Settings(yolo_enabled=False, yolo_model_path=str(tmp_path / "missing.pt")),
|
||||
tile_manifest_path=str(tmp_path / "missing-manifest.json"),
|
||||
@@ -66,6 +68,12 @@ def test_yolo_preflight_reports_disabled_without_loading_model(tmp_path: Path) -
|
||||
assert result["checks"]["dependencies_available"] is None
|
||||
assert result["checks"]["model_file_exists"] is None
|
||||
assert result["checks"]["manifest_valid"] is None
|
||||
assert result["runtime"]["dependencies_assumed"] is False
|
||||
assert result["runtime"]["model_directory"] == str(tmp_path)
|
||||
assert result["runtime"]["yolo_config_dir"] == str(tmp_path / "ultralytics")
|
||||
assert "torch_version" in result["runtime"]
|
||||
assert "ultralytics_version" in result["runtime"]
|
||||
assert "cuda_available" in result["runtime"]
|
||||
|
||||
|
||||
def test_yolo_preflight_distinguishes_missing_dependencies_from_missing_model(tmp_path: Path) -> None:
|
||||
@@ -102,6 +110,25 @@ def test_yolo_preflight_validates_model_and_manifest_without_importing_yolo(tmp_
|
||||
assert result["tile_count"] == 2
|
||||
assert result["will_download_models"] is False
|
||||
assert result["will_run_inference"] is False
|
||||
assert result["runtime"]["dependencies_assumed"] is False
|
||||
|
||||
|
||||
def test_yolo_preflight_marks_assumed_dependencies_in_runtime_details(tmp_path: Path) -> None:
|
||||
model_path = tmp_path / "model.pt"
|
||||
model_path.write_bytes(b"weights")
|
||||
manifest_path = _manifest(tmp_path, tile_count=1)
|
||||
|
||||
result = YoloPreflightService.run(
|
||||
settings=Settings(yolo_enabled=True, yolo_model_path=str(model_path), yolo_max_tiles=4),
|
||||
tile_manifest_path=str(manifest_path),
|
||||
yolo_adapter_class=MissingDependencyAdapter,
|
||||
assume_dependencies=True,
|
||||
)
|
||||
|
||||
assert result["status"] == "ready"
|
||||
assert result["checks"]["dependencies_available"] is True
|
||||
assert result["runtime"]["dependencies_assumed"] is True
|
||||
assert result["runtime"]["model_directory"] == str(tmp_path)
|
||||
|
||||
|
||||
def test_yolo_preflight_can_explicitly_smoke_load_local_model(tmp_path: Path) -> None:
|
||||
|
||||
Reference in New Issue
Block a user