Spaces:
Sleeping
Sleeping
| """ | |
| Shared compute selection for PyTorch, ONNX Runtime, and LC0 backends. | |
| **Default (no flags):** ``auto`` — use GPU when the stack reports it is available. | |
| **Override from the command line only** (no environment variables required):: | |
| python -m chess_tutor.export_training_dataset --ort-provider cuda ... | |
| python -m chess_tutor.heuristic_mlp_torch --device cuda --amp ... | |
| python -m chess_tutor.analyze_player_games_heuristics --ort-provider cuda --device cuda ... | |
| python -m chess_tutor.build_player_moves_db --lc0-backend cuda ... | |
| Force CPU:: | |
| python -m chess_tutor.export_training_dataset --ort-provider cpu ... | |
| """ | |
| from __future__ import annotations | |
| import argparse | |
| import os | |
| import sys | |
| from pathlib import Path | |
| from typing import List, Optional, Sequence, Tuple | |
| DEFAULT_SPEC = "auto" | |
| ORT_PROVIDER_CHOICES = ("auto", "cpu", "cuda", "tensorrt") | |
| TORCH_DEVICE_CHOICES = ("auto", "cpu", "cuda", "mps") | |
| LC0_BACKEND_CHOICES = ("auto", "cpu", "eigen", "cuda", "cuda-fp16", "opencl", "blas") | |
| def ort_available_providers() -> Tuple[str, ...]: | |
| import onnxruntime as ort | |
| return tuple(ort.get_available_providers()) | |
| def cuda_ort_available() -> bool: | |
| return "CUDAExecutionProvider" in set(ort_available_providers()) | |
| def cuda_torch_available() -> bool: | |
| import torch | |
| return bool(torch.cuda.is_available()) | |
| def _warn(msg: str) -> None: | |
| print(msg, file=sys.stderr) | |
| def _nvidia_wheel_lib_dir(mod_name: str) -> Optional[str]: | |
| """``.../site-packages/nvidia/<pkg>/lib`` from a pip NVIDIA wheel (namespace-safe).""" | |
| import importlib.util | |
| spec = importlib.util.find_spec(mod_name) | |
| if spec is None or not spec.submodule_search_locations: | |
| return None | |
| lib = Path(spec.submodule_search_locations[0]) / "lib" | |
| return str(lib) if lib.is_dir() else None | |
| def nvidia_pip_lib_dirs() -> Tuple[str, ...]: | |
| """ | |
| Directories from NVIDIA pip wheels (``nvidia-cudnn-cu12``, ``nvidia-cu13``, etc.). | |
| PyTorch installs these but does not require ``ldconfig`` entries; ONNX Runtime | |
| needs them on ``LD_LIBRARY_PATH`` before the CUDA EP loads. | |
| """ | |
| dirs: List[str] = [] | |
| for mod_name in ( | |
| "nvidia.cu13", | |
| "nvidia.cuda_runtime", | |
| "nvidia.cublas", | |
| "nvidia.cudnn", | |
| ): | |
| lib_dir = _nvidia_wheel_lib_dir(mod_name) | |
| if lib_dir and lib_dir not in dirs: | |
| dirs.append(lib_dir) | |
| return tuple(dirs) | |
| def prepend_ld_library_path(extra_dirs: Sequence[str]) -> None: | |
| """Prepend directories to ``LD_LIBRARY_PATH`` if not already present.""" | |
| current = os.environ.get("LD_LIBRARY_PATH", "") | |
| current_parts = [p for p in current.split(":") if p] | |
| new_parts: List[str] = [] | |
| for d in extra_dirs: | |
| if d and d not in current_parts and d not in new_parts: | |
| new_parts.append(d) | |
| if not new_parts: | |
| return | |
| os.environ["LD_LIBRARY_PATH"] = ":".join(new_parts + current_parts) | |
| _ort_gpu_libs_ready = False | |
| def _preload_shared_libs_in_dir(lib_dir: Path) -> None: | |
| """ | |
| Load pip-shipped ``.so`` files into the global namespace. | |
| Setting ``LD_LIBRARY_PATH`` from Python is not enough on Linux for ONNX Runtime's | |
| late-loaded ``providers_cuda.so``; ``ctypes.CDLL(..., RTLD_GLOBAL)`` matches what | |
| PyTorch effectively does for ``nvidia-*`` wheels. | |
| """ | |
| import ctypes | |
| mode = getattr(ctypes, "RTLD_GLOBAL", 0x100) | |
| if not lib_dir.is_dir(): | |
| return | |
| for so in sorted(lib_dir.glob("*.so*")): | |
| if not so.is_file(): | |
| continue | |
| try: | |
| ctypes.CDLL(str(so.resolve()), mode=mode) | |
| except OSError: | |
| continue | |
| def ensure_ort_gpu_runtime_libs() -> None: | |
| """Expose pip-shipped CUDA/cuDNN libs to ONNX Runtime (no-op if not installed).""" | |
| global _ort_gpu_libs_ready | |
| if _ort_gpu_libs_ready: | |
| return | |
| dirs = nvidia_pip_lib_dirs() | |
| prepend_ld_library_path(dirs) | |
| for lib_dir in dirs: | |
| _preload_shared_libs_in_dir(Path(lib_dir)) | |
| _ort_gpu_libs_ready = True | |
| def _normalize_spec(spec: Optional[str]) -> str: | |
| """CLI value or ``auto`` when the flag was omitted (``None``).""" | |
| if spec is None or not str(spec).strip(): | |
| return DEFAULT_SPEC | |
| return str(spec).strip().lower() | |
| def resolve_ort_providers(spec: Optional[str] = None) -> List[str]: | |
| """ | |
| Return an ONNX Runtime provider list (first entry is preferred). | |
| ``auto`` uses CUDA when ``CUDAExecutionProvider`` is registered (``onnxruntime-gpu``). | |
| """ | |
| raw = _normalize_spec(spec) | |
| available = set(ort_available_providers()) | |
| def _cpu_only() -> List[str]: | |
| return ["CPUExecutionProvider"] | |
| if raw == "cpu": | |
| return _cpu_only() | |
| if raw == "cuda": | |
| if "CUDAExecutionProvider" in available: | |
| return ["CUDAExecutionProvider", "CPUExecutionProvider"] | |
| _warn( | |
| "--ort-provider cuda requested but CUDAExecutionProvider is not available; " | |
| "install onnxruntime-gpu and CUDA. Using CPU." | |
| ) | |
| return _cpu_only() | |
| if raw == "tensorrt": | |
| chain: List[str] = [] | |
| if "TensorrtExecutionProvider" in available: | |
| chain.append("TensorrtExecutionProvider") | |
| if "CUDAExecutionProvider" in available: | |
| chain.append("CUDAExecutionProvider") | |
| chain.append("CPUExecutionProvider") | |
| if len(chain) == 1: | |
| _warn( | |
| "--ort-provider tensorrt requested but no TensorRT/CUDA EPs found; using CPU." | |
| ) | |
| return chain | |
| if raw == "auto": | |
| if "CUDAExecutionProvider" in available: | |
| return ["CUDAExecutionProvider", "CPUExecutionProvider"] | |
| return _cpu_only() | |
| raise ValueError( | |
| f"Unknown ORT provider spec {spec!r}; use one of {ORT_PROVIDER_CHOICES}" | |
| ) | |
| def resolve_torch_device(spec: Optional[str] = None) -> str: | |
| """Return a PyTorch device string (``cpu``, ``cuda``, ``mps``).""" | |
| import torch | |
| raw = _normalize_spec(spec) | |
| if raw == "cpu": | |
| return "cpu" | |
| if raw == "cuda": | |
| if torch.cuda.is_available(): | |
| return "cuda" | |
| _warn("--device cuda requested but torch.cuda.is_available() is False; using cpu.") | |
| return "cpu" | |
| if raw == "mps": | |
| if getattr(torch.backends, "mps", None) is not None and torch.backends.mps.is_available(): | |
| return "mps" | |
| _warn("--device mps requested but MPS is not available; using cpu.") | |
| return "cpu" | |
| if raw == "auto": | |
| if torch.cuda.is_available(): | |
| return "cuda" | |
| if getattr(torch.backends, "mps", None) is not None and torch.backends.mps.is_available(): | |
| return "mps" | |
| return "cpu" | |
| raise ValueError( | |
| f"Unknown torch device spec {spec!r}; use one of {TORCH_DEVICE_CHOICES}" | |
| ) | |
| def torch_device_supports_amp(device: str) -> bool: | |
| """Whether ``torch.amp`` / legacy CUDA AMP is reasonable on this device.""" | |
| return device == "cuda" | |
| def resolve_lc0_bindings_backend(spec: Optional[str] = None) -> str: | |
| """ | |
| Backend name for ``lczero.backends.Backend(weights, backend=...)``. | |
| ``auto`` uses ``eigen`` (CPU). Pass ``--lc0-bindings-backend cuda`` when bindings | |
| were built with CUDA support. | |
| """ | |
| raw = _normalize_spec(spec) | |
| if raw == "auto": | |
| return "eigen" | |
| if raw in LC0_BACKEND_CHOICES or raw: | |
| return raw | |
| raise ValueError( | |
| f"Unknown LC0 bindings backend {spec!r}; examples: eigen, cuda (see lc0 docs)" | |
| ) | |
| def resolve_lc0_uci_backend(spec: Optional[str] = None) -> Optional[str]: | |
| """ | |
| LC0 UCI ``--backend=`` CLI value, or ``None`` to omit (lc0 binary default). | |
| ``auto`` omits the flag (a CUDA-built ``lc0`` defaults to ``cuda-auto``). | |
| ``eigen`` / ``cpu`` force the CPU Eigen backend (recommended on WSL when CUDA crashes). | |
| """ | |
| raw = _normalize_spec(spec) | |
| if raw == "auto": | |
| return None | |
| if raw == "cpu": | |
| return "eigen" | |
| if raw == "eigen": | |
| return "eigen" | |
| return raw | |
| def lc0_uci_extra_args(spec: Optional[str] = None) -> List[str]: | |
| """Extra CLI args for the ``lc0`` binary (e.g. ``['--backend=cuda']``).""" | |
| backend = resolve_lc0_uci_backend(spec) | |
| if backend is None: | |
| return [] | |
| return [f"--backend={backend}"] | |
| def warn_if_ort_fell_back_to_cpu( | |
| requested_providers: Sequence[str], | |
| sess: object, | |
| ) -> None: | |
| """Warn when CUDA/TensorRT was requested but the session is CPU-only.""" | |
| try: | |
| active = list(sess.get_providers()) # type: ignore[attr-defined] | |
| except Exception: | |
| return | |
| if not active: | |
| return | |
| wants_gpu = any( | |
| p in requested_providers | |
| for p in ("CUDAExecutionProvider", "TensorrtExecutionProvider") | |
| ) | |
| if wants_gpu and active[0] == "CPUExecutionProvider": | |
| pip_dirs = nvidia_pip_lib_dirs() | |
| hint = ( | |
| f"pip cuDNN dirs: {pip_dirs}" if pip_dirs else "pip install nvidia-cudnn-cu12" | |
| ) | |
| _warn( | |
| "ONNX Runtime fell back to CPU despite GPU provider preference. " | |
| f"Ensure libcudnn.so.9 is on LD_LIBRARY_PATH ({hint}), or use --ort-provider cpu. " | |
| "See https://onnxruntime.ai/docs/execution-providers/CUDA-ExecutionProvider.html#requirements" | |
| ) | |
| def log_ort_session_providers(sess: object, *, label: str = "ONNX Runtime") -> None: | |
| """Log active providers after creating an ``InferenceSession``.""" | |
| try: | |
| active = sess.get_providers() # type: ignore[attr-defined] | |
| except Exception: | |
| return | |
| _warn(f"{label}: session providers = {active}") | |
| def log_compute_plan( | |
| *, | |
| torch_spec: Optional[str] = None, | |
| ort_spec: Optional[str] = None, | |
| lc0_bindings_spec: Optional[str] = None, | |
| lc0_uci_spec: Optional[str] = None, | |
| ) -> None: | |
| """One-line stderr summary of resolved compute choices (call after parsing args).""" | |
| parts: List[str] = [] | |
| if torch_spec is not None: | |
| parts.append(f"torch={resolve_torch_device(torch_spec)}") | |
| if ort_spec is not None: | |
| parts.append(f"ort={resolve_ort_providers(ort_spec)}") | |
| if lc0_bindings_spec is not None: | |
| parts.append(f"lc0_bindings={resolve_lc0_bindings_backend(lc0_bindings_spec)}") | |
| if lc0_uci_spec is not None: | |
| uci = resolve_lc0_uci_backend(lc0_uci_spec) | |
| parts.append(f"lc0_uci={uci or '(lc0 default)'}") | |
| if parts: | |
| _warn("Compute: " + ", ".join(parts)) | |
| def add_compute_arguments( | |
| parser: argparse.ArgumentParser, | |
| *, | |
| include_torch: bool = False, | |
| include_ort: bool = False, | |
| include_lc0_bindings: bool = False, | |
| include_lc0_uci: bool = False, | |
| ) -> None: | |
| """Register ``--device`` / ``--ort-provider`` / LC0 backend flags (default: auto).""" | |
| if include_torch: | |
| parser.add_argument( | |
| "--device", | |
| default=DEFAULT_SPEC, | |
| choices=TORCH_DEVICE_CHOICES, | |
| help="PyTorch device (default: auto — cuda if available, else mps, else cpu).", | |
| ) | |
| if include_ort: | |
| parser.add_argument( | |
| "--ort-provider", | |
| default=DEFAULT_SPEC, | |
| choices=ORT_PROVIDER_CHOICES, | |
| help=( | |
| "ONNX Runtime EP (default: auto — CUDA if onnxruntime-gpu is installed). " | |
| "Use cpu to force CPU." | |
| ), | |
| ) | |
| if include_lc0_bindings: | |
| parser.add_argument( | |
| "--lc0-bindings-backend", | |
| default=DEFAULT_SPEC, | |
| metavar="NAME", | |
| help=( | |
| "lczero.backends plane encoder (default: auto→eigen). " | |
| "Use cuda only if bindings were built with CUDA." | |
| ), | |
| ) | |
| if include_lc0_uci: | |
| parser.add_argument( | |
| "--lc0-backend", | |
| default=DEFAULT_SPEC, | |
| choices=LC0_BACKEND_CHOICES, | |
| help=( | |
| "lc0 UCI --backend=… (default: auto→lc0 default). " | |
| "Use cuda or cuda-fp16 with a GPU-built lc0 binary." | |
| ), | |
| ) | |