gdsdiff 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- gdsdiff/__init__.py +148 -0
- gdsdiff/__main__.py +7 -0
- gdsdiff/_environment.py +12 -0
- gdsdiff/_version.py +34 -0
- gdsdiff/acceptance_evidence.py +530 -0
- gdsdiff/api.py +2955 -0
- gdsdiff/backends/__init__.py +26 -0
- gdsdiff/backends/base.py +77 -0
- gdsdiff/backends/boundary_loop_ctypes.py +191 -0
- gdsdiff/backends/cpu.py +20 -0
- gdsdiff/backends/cpu_ctypes.py +255 -0
- gdsdiff/backends/cuda.py +40 -0
- gdsdiff/backends/cuda_ctypes.py +1297 -0
- gdsdiff/backends/exact.py +424 -0
- gdsdiff/backends/geometry_ctypes.py +252 -0
- gdsdiff/backends/identity.py +53 -0
- gdsdiff/backends/metal.py +198 -0
- gdsdiff/backends/metal_ctypes.py +866 -0
- gdsdiff/backends/polygon_extract_ctypes.py +233 -0
- gdsdiff/backends/structural_ctypes.py +130 -0
- gdsdiff/boundary_loops.py +616 -0
- gdsdiff/cache.py +544 -0
- gdsdiff/canonicalize.py +176 -0
- gdsdiff/cli.py +352 -0
- gdsdiff/config.py +257 -0
- gdsdiff/cuda_setup.py +174 -0
- gdsdiff/design_history.py +65 -0
- gdsdiff/diagnostics.py +42 -0
- gdsdiff/diff_gds.py +66 -0
- gdsdiff/exact_diff_markdown.py +979 -0
- gdsdiff/exact_oracle.py +649 -0
- gdsdiff/extract.py +376 -0
- gdsdiff/gds_metadata.py +43 -0
- gdsdiff/geometry.py +112 -0
- gdsdiff/grid.py +143 -0
- gdsdiff/hierarchy.py +215 -0
- gdsdiff/hierarchy_extract.py +263 -0
- gdsdiff/inventory.py +153 -0
- gdsdiff/multiprocessing_support.py +39 -0
- gdsdiff/native.py +135 -0
- gdsdiff/native_cache.py +265 -0
- gdsdiff/native_src/cpu_prefilter.cpp +349 -0
- gdsdiff/native_src/gds_boundary_loops.cpp +505 -0
- gdsdiff/native_src/gds_geometry_scan.cpp +601 -0
- gdsdiff/native_src/gds_polygon_extract.cpp +594 -0
- gdsdiff/native_src/gds_structural_scan.cpp +335 -0
- gdsdiff/native_src/gdsdiff/CMakeLists.txt +74 -0
- gdsdiff/native_src/gdsdiff/core.cpp +191 -0
- gdsdiff/native_src/gdsdiff/core.hpp +96 -0
- gdsdiff/native_src/gdsdiff/cuda_assignment.cu +304 -0
- gdsdiff/native_src/gdsdiff/cuda_assignment.cuh +33 -0
- gdsdiff/native_src/gdsdiff/cuda_candidates.cu +133 -0
- gdsdiff/native_src/gdsdiff/cuda_candidates.cuh +17 -0
- gdsdiff/native_src/gdsdiff/cuda_ctypes.cu +4528 -0
- gdsdiff/native_src/gdsdiff/cuda_memory.cu +194 -0
- gdsdiff/native_src/gdsdiff/cuda_memory.cuh +34 -0
- gdsdiff/native_src/gdsdiff/metal_ctypes.mm +2791 -0
- gdsdiff/native_src/gdsdiff/python_bindings.cpp +21 -0
- gdsdiff/native_src/gdsdiff/test_core.cpp +93 -0
- gdsdiff/native_src/gdsdiff/test_cuda_assignment.cu +109 -0
- gdsdiff/native_src/gdsdiff/test_cuda_candidates.cu +94 -0
- gdsdiff/native_src/gdsdiff/test_cuda_memory.cu +97 -0
- gdsdiff/policy.py +305 -0
- gdsdiff/polygon_components.py +860 -0
- gdsdiff/profiles.py +152 -0
- gdsdiff/py.typed +1 -0
- gdsdiff/rect_coverage.py +384 -0
- gdsdiff/report.py +354 -0
- gdsdiff/schemas/gdsdiff_report-v1.schema.json +92 -0
- gdsdiff/semantics.py +75 -0
- gdsdiff/source_edge_diff.py +1120 -0
- gdsdiff/spatial.py +71 -0
- gdsdiff/structural_guard.py +161 -0
- gdsdiff/surplus_coverage.py +255 -0
- gdsdiff/synthetic_suite.py +1643 -0
- gdsdiff/templates.py +709 -0
- gdsdiff/tiling.py +153 -0
- gdsdiff/windowed_exact.py +270 -0
- gdsdiff/windows.py +184 -0
- gdsdiff-0.1.0.dist-info/METADATA +135 -0
- gdsdiff-0.1.0.dist-info/RECORD +85 -0
- gdsdiff-0.1.0.dist-info/WHEEL +5 -0
- gdsdiff-0.1.0.dist-info/entry_points.txt +4 -0
- gdsdiff-0.1.0.dist-info/licenses/LICENSE +674 -0
- gdsdiff-0.1.0.dist-info/top_level.txt +1 -0
gdsdiff/native.py
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import importlib.util
|
|
4
|
+
import os
|
|
5
|
+
import shutil
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
from functools import lru_cache
|
|
8
|
+
|
|
9
|
+
from ._environment import get_environment
|
|
10
|
+
from .report import TOOL_VERSION
|
|
11
|
+
|
|
12
|
+
CUDA_ARCH_ENV = "GDSDIFF_CUDA_ARCHITECTURES"
|
|
13
|
+
LEGACY_CUDA_ARCH_ENV = "GDS_GPU_COMPARE_CUDA_ARCHITECTURES"
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass(frozen=True)
|
|
17
|
+
class NativeCapabilities:
|
|
18
|
+
tool_version: str
|
|
19
|
+
core_extension_available: bool
|
|
20
|
+
cuda_extension_available: bool
|
|
21
|
+
cuda_ctypes_available: bool
|
|
22
|
+
metal_ctypes_available: bool
|
|
23
|
+
cmake_available: bool
|
|
24
|
+
nvcc_available: bool
|
|
25
|
+
cuda_architectures: tuple[str, ...]
|
|
26
|
+
cuda_compile_flags: tuple[str, ...] = ()
|
|
27
|
+
|
|
28
|
+
@property
|
|
29
|
+
def cpu_available(self) -> bool:
|
|
30
|
+
return True
|
|
31
|
+
|
|
32
|
+
@property
|
|
33
|
+
def cuda_available(self) -> bool:
|
|
34
|
+
return self.cuda_extension_available or self.cuda_ctypes_available
|
|
35
|
+
|
|
36
|
+
@property
|
|
37
|
+
def metal_available(self) -> bool:
|
|
38
|
+
return self.metal_ctypes_available
|
|
39
|
+
|
|
40
|
+
def to_json(self) -> dict[str, object]:
|
|
41
|
+
return {
|
|
42
|
+
"tool_version": self.tool_version,
|
|
43
|
+
"cpu_available": self.cpu_available,
|
|
44
|
+
"core_extension_available": self.core_extension_available,
|
|
45
|
+
"cuda_extension_available": self.cuda_extension_available,
|
|
46
|
+
"cuda_ctypes_available": self.cuda_ctypes_available,
|
|
47
|
+
"cuda_available": self.cuda_available,
|
|
48
|
+
"metal_ctypes_available": self.metal_ctypes_available,
|
|
49
|
+
"metal_available": self.metal_available,
|
|
50
|
+
"cmake_available": self.cmake_available,
|
|
51
|
+
"nvcc_available": self.nvcc_available,
|
|
52
|
+
"cuda_architectures": list(self.cuda_architectures),
|
|
53
|
+
"cuda_compile_flags": list(self.cuda_compile_flags),
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def discover_native_capabilities() -> NativeCapabilities:
|
|
58
|
+
"""Inspect already prepared capabilities without compiling native code."""
|
|
59
|
+
|
|
60
|
+
return NativeCapabilities(
|
|
61
|
+
tool_version=TOOL_VERSION,
|
|
62
|
+
core_extension_available=_module_available("gdsdiff._gds_compare_core"),
|
|
63
|
+
cuda_extension_available=_module_available("gdsdiff._gds_compare_cuda"),
|
|
64
|
+
cuda_ctypes_available=_cuda_ctypes_available(),
|
|
65
|
+
metal_ctypes_available=_metal_ctypes_available(),
|
|
66
|
+
cmake_available=_which_tool("cmake", os.environ.get("PATH")) is not None,
|
|
67
|
+
nvcc_available=resolve_nvcc_path() is not None,
|
|
68
|
+
cuda_architectures=_cuda_architectures_from_env(),
|
|
69
|
+
cuda_compile_flags=nvcc_architecture_flags(),
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
@lru_cache(maxsize=16)
|
|
74
|
+
def _module_available(name: str) -> bool:
|
|
75
|
+
try:
|
|
76
|
+
return importlib.util.find_spec(name) is not None
|
|
77
|
+
except (ImportError, AttributeError, ValueError):
|
|
78
|
+
return False
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
@lru_cache(maxsize=16)
|
|
82
|
+
def _which_tool(name: str, path: str | None) -> str | None:
|
|
83
|
+
"""Resolve a tool once per PATH value while honoring runtime PATH changes."""
|
|
84
|
+
|
|
85
|
+
return shutil.which(name, path=path)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def _cuda_ctypes_available() -> bool:
|
|
89
|
+
from .backends.cuda_ctypes import prepared_cuda_library_path
|
|
90
|
+
|
|
91
|
+
return prepared_cuda_library_path().is_file()
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def _metal_ctypes_available() -> bool:
|
|
95
|
+
from .native_cache import native_cache_dir
|
|
96
|
+
|
|
97
|
+
return any(native_cache_dir().glob("_gds_compare_metal_ctypes-*.dylib"))
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def resolve_nvcc_path() -> str | None:
|
|
101
|
+
"""Return the CUDA compiler from PATH or conventional toolkit locations."""
|
|
102
|
+
|
|
103
|
+
resolved = _which_tool("nvcc", os.environ.get("PATH"))
|
|
104
|
+
if resolved:
|
|
105
|
+
return resolved
|
|
106
|
+
candidates = []
|
|
107
|
+
for variable in ("CUDA_HOME", "CUDA_PATH"):
|
|
108
|
+
if os.environ.get(variable):
|
|
109
|
+
candidates.append(os.path.join(os.environ[variable], "bin", "nvcc"))
|
|
110
|
+
candidates.extend(("/usr/local/cuda/bin/nvcc", "/opt/cuda/bin/nvcc"))
|
|
111
|
+
return next((candidate for candidate in candidates if os.path.isfile(candidate) and os.access(candidate, os.X_OK)), None)
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def _cuda_architectures_from_env() -> tuple[str, ...]:
|
|
115
|
+
raw = get_environment(CUDA_ARCH_ENV, LEGACY_CUDA_ARCH_ENV, "") or ""
|
|
116
|
+
return tuple(part.strip() for part in raw.replace(";", ",").split(",") if part.strip())
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def nvcc_architecture_flags() -> tuple[str, ...]:
|
|
120
|
+
"""Translate configured CUDA architectures into native-SASS nvcc flags."""
|
|
121
|
+
|
|
122
|
+
configured_architectures = _cuda_architectures_from_env()
|
|
123
|
+
if not configured_architectures:
|
|
124
|
+
return ("-arch=native",)
|
|
125
|
+
flags = []
|
|
126
|
+
for configured in configured_architectures:
|
|
127
|
+
architecture = configured.lower()
|
|
128
|
+
for prefix in ("sm_", "compute_"):
|
|
129
|
+
if architecture.startswith(prefix):
|
|
130
|
+
architecture = architecture[len(prefix) :]
|
|
131
|
+
break
|
|
132
|
+
if not architecture.isdigit():
|
|
133
|
+
raise ValueError(f"invalid CUDA architecture {configured!r}; expected values such as '89' or '120'")
|
|
134
|
+
flags.append(f"--generate-code=arch=compute_{architecture},code=sm_{architecture}")
|
|
135
|
+
return tuple(flags)
|
gdsdiff/native_cache.py
ADDED
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import hashlib
|
|
4
|
+
import ctypes
|
|
5
|
+
import os
|
|
6
|
+
import platform
|
|
7
|
+
import shutil
|
|
8
|
+
import subprocess
|
|
9
|
+
import sys
|
|
10
|
+
import time
|
|
11
|
+
import uuid
|
|
12
|
+
from contextlib import contextmanager
|
|
13
|
+
from functools import lru_cache
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
from typing import Callable, Iterable, Iterator
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
ENV_NATIVE_CACHE_DIR = "GDSDIFF_NATIVE_CACHE_DIR"
|
|
19
|
+
LEGACY_ENV_NATIVE_CACHE_DIR = "SCGDS_NATIVE_CACHE_DIR"
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def cached_native_library_path(filename: str) -> Path:
|
|
23
|
+
return native_cache_dir() / filename
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def content_addressed_library_path(
|
|
27
|
+
filename: str,
|
|
28
|
+
*,
|
|
29
|
+
sources: Iterable[Path],
|
|
30
|
+
build_key: Iterable[str] = (),
|
|
31
|
+
) -> Path:
|
|
32
|
+
"""Return a cache path keyed by source content and the build contract."""
|
|
33
|
+
|
|
34
|
+
source_paths = tuple(Path(source) for source in sources)
|
|
35
|
+
digest = hashlib.sha256(b"gdsdiff-native-cache-v1\0")
|
|
36
|
+
for item in native_build_fingerprint(tuple(build_key)):
|
|
37
|
+
digest.update(str(item).encode("utf-8"))
|
|
38
|
+
digest.update(b"\0")
|
|
39
|
+
for source in source_paths:
|
|
40
|
+
digest.update(source.name.encode("utf-8"))
|
|
41
|
+
digest.update(b"\0")
|
|
42
|
+
digest.update(source.read_bytes())
|
|
43
|
+
digest.update(b"\0")
|
|
44
|
+
source_hash = digest.hexdigest()[:20]
|
|
45
|
+
candidate = Path(filename)
|
|
46
|
+
suffix = candidate.suffix
|
|
47
|
+
stem = candidate.name[: -len(suffix)] if suffix else candidate.name
|
|
48
|
+
return native_cache_dir() / f"{stem}-{source_hash}{suffix}"
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def ensure_cached_native_library(
|
|
52
|
+
filename: str,
|
|
53
|
+
*,
|
|
54
|
+
sources: Iterable[Path],
|
|
55
|
+
build_key: Iterable[str],
|
|
56
|
+
builder: Callable[[Path], None],
|
|
57
|
+
) -> Path:
|
|
58
|
+
"""Build and atomically publish a content-addressed native library."""
|
|
59
|
+
|
|
60
|
+
source_paths = tuple(Path(source) for source in sources)
|
|
61
|
+
path = content_addressed_library_path(filename, sources=source_paths, build_key=build_key)
|
|
62
|
+
if path.is_file():
|
|
63
|
+
return path
|
|
64
|
+
_ensure_private_cache_directory(path.parent)
|
|
65
|
+
lock_path = path.with_name(f".{path.name}.lock")
|
|
66
|
+
with _exclusive_cache_lock(lock_path):
|
|
67
|
+
if path.is_file():
|
|
68
|
+
return path
|
|
69
|
+
temporary = path.with_name(f".{path.stem}.{os.getpid()}.{uuid.uuid4().hex}{path.suffix}")
|
|
70
|
+
try:
|
|
71
|
+
builder(temporary)
|
|
72
|
+
if not temporary.is_file() or temporary.stat().st_size == 0:
|
|
73
|
+
raise RuntimeError(f"native builder did not create a non-empty library: {temporary}")
|
|
74
|
+
os.replace(temporary, path)
|
|
75
|
+
finally:
|
|
76
|
+
temporary.unlink(missing_ok=True)
|
|
77
|
+
return path
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def native_cache_dir() -> Path:
|
|
81
|
+
configured = os.environ.get(ENV_NATIVE_CACHE_DIR) or os.environ.get(LEGACY_ENV_NATIVE_CACHE_DIR)
|
|
82
|
+
if configured:
|
|
83
|
+
base = Path(configured).expanduser()
|
|
84
|
+
elif sys.platform == "darwin":
|
|
85
|
+
base = Path.home() / "Library" / "Caches" / "gdsdiff" / "native"
|
|
86
|
+
elif os.name == "nt":
|
|
87
|
+
root = os.environ.get("LOCALAPPDATA")
|
|
88
|
+
base = Path(root).expanduser() if root else Path.home() / "AppData" / "Local"
|
|
89
|
+
base = base / "gdsdiff" / "native"
|
|
90
|
+
else:
|
|
91
|
+
root = os.environ.get("XDG_CACHE_HOME")
|
|
92
|
+
base = Path(root).expanduser() if root else Path.home() / ".cache"
|
|
93
|
+
base = base / "gdsdiff" / "native"
|
|
94
|
+
return base / _platform_tag()
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def native_build_fingerprint(build_key: tuple[str, ...]) -> tuple[str, ...]:
|
|
98
|
+
"""Augment a native build contract with compiler and device identity."""
|
|
99
|
+
|
|
100
|
+
if not build_key:
|
|
101
|
+
return build_key
|
|
102
|
+
compiler = build_key[0]
|
|
103
|
+
fingerprint = (*build_key, *_executable_fingerprint(compiler, os.environ.get("PATH")))
|
|
104
|
+
if Path(compiler).name.lower().startswith("nvcc") and "-arch=native" in build_key:
|
|
105
|
+
fingerprint = (*fingerprint, *_cuda_device_fingerprint(os.environ.get("CUDA_VISIBLE_DEVICES")))
|
|
106
|
+
return fingerprint
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
@lru_cache(maxsize=32)
|
|
110
|
+
def _executable_fingerprint(command: str, path: str | None) -> tuple[str, ...]:
|
|
111
|
+
resolved = shutil.which(command, path=path)
|
|
112
|
+
if resolved is None:
|
|
113
|
+
return (f"compiler={command}", "compiler_resolved=unavailable")
|
|
114
|
+
executable = Path(resolved).resolve()
|
|
115
|
+
try:
|
|
116
|
+
stat = executable.stat()
|
|
117
|
+
identity = f"{stat.st_size}:{stat.st_mtime_ns}"
|
|
118
|
+
except OSError:
|
|
119
|
+
identity = "stat-unavailable"
|
|
120
|
+
try:
|
|
121
|
+
completed = subprocess.run(
|
|
122
|
+
[str(executable), "--version"],
|
|
123
|
+
check=False,
|
|
124
|
+
capture_output=True,
|
|
125
|
+
text=True,
|
|
126
|
+
timeout=10,
|
|
127
|
+
)
|
|
128
|
+
version = (completed.stdout or completed.stderr).strip()
|
|
129
|
+
except (OSError, subprocess.SubprocessError):
|
|
130
|
+
version = "version-unavailable"
|
|
131
|
+
return (
|
|
132
|
+
f"compiler={command}",
|
|
133
|
+
f"compiler_resolved={executable}",
|
|
134
|
+
f"compiler_identity={identity}",
|
|
135
|
+
f"compiler_version={version}",
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
@lru_cache(maxsize=8)
|
|
140
|
+
def _cuda_device_fingerprint(visible_devices: str | None) -> tuple[str, ...]:
|
|
141
|
+
driver_devices = _cuda_driver_api_fingerprint()
|
|
142
|
+
if driver_devices is not None:
|
|
143
|
+
return (f"CUDA_VISIBLE_DEVICES={visible_devices or ''}", f"cuda_driver_devices={driver_devices}")
|
|
144
|
+
try:
|
|
145
|
+
completed = subprocess.run(
|
|
146
|
+
[
|
|
147
|
+
"nvidia-smi",
|
|
148
|
+
"--query-gpu=index,uuid,name,compute_cap,driver_version",
|
|
149
|
+
"--format=csv,noheader,nounits",
|
|
150
|
+
],
|
|
151
|
+
check=False,
|
|
152
|
+
capture_output=True,
|
|
153
|
+
text=True,
|
|
154
|
+
timeout=10,
|
|
155
|
+
)
|
|
156
|
+
devices = completed.stdout.strip() if completed.returncode == 0 else "unavailable"
|
|
157
|
+
except (OSError, subprocess.SubprocessError):
|
|
158
|
+
devices = "unavailable"
|
|
159
|
+
return (f"CUDA_VISIBLE_DEVICES={visible_devices or ''}", f"cuda_devices={devices}")
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def _cuda_driver_api_fingerprint() -> str | None:
|
|
163
|
+
"""Read visible device identity without creating a CUDA context."""
|
|
164
|
+
|
|
165
|
+
try:
|
|
166
|
+
driver = ctypes.CDLL("libcuda.so.1")
|
|
167
|
+
driver.cuInit.argtypes = [ctypes.c_uint]
|
|
168
|
+
driver.cuInit.restype = ctypes.c_int
|
|
169
|
+
driver.cuDeviceGetCount.argtypes = [ctypes.POINTER(ctypes.c_int)]
|
|
170
|
+
driver.cuDeviceGetCount.restype = ctypes.c_int
|
|
171
|
+
driver.cuDeviceGet.argtypes = [ctypes.POINTER(ctypes.c_int), ctypes.c_int]
|
|
172
|
+
driver.cuDeviceGet.restype = ctypes.c_int
|
|
173
|
+
driver.cuDeviceComputeCapability.argtypes = [ctypes.POINTER(ctypes.c_int), ctypes.POINTER(ctypes.c_int), ctypes.c_int]
|
|
174
|
+
driver.cuDeviceComputeCapability.restype = ctypes.c_int
|
|
175
|
+
driver.cuDeviceGetName.argtypes = [ctypes.c_char_p, ctypes.c_int, ctypes.c_int]
|
|
176
|
+
driver.cuDeviceGetName.restype = ctypes.c_int
|
|
177
|
+
driver.cuDriverGetVersion.argtypes = [ctypes.POINTER(ctypes.c_int)]
|
|
178
|
+
driver.cuDriverGetVersion.restype = ctypes.c_int
|
|
179
|
+
if driver.cuInit(0) != 0:
|
|
180
|
+
return None
|
|
181
|
+
count = ctypes.c_int()
|
|
182
|
+
version = ctypes.c_int()
|
|
183
|
+
if driver.cuDeviceGetCount(ctypes.byref(count)) != 0 or driver.cuDriverGetVersion(ctypes.byref(version)) != 0:
|
|
184
|
+
return None
|
|
185
|
+
devices = []
|
|
186
|
+
for ordinal in range(count.value):
|
|
187
|
+
device = ctypes.c_int()
|
|
188
|
+
major = ctypes.c_int()
|
|
189
|
+
minor = ctypes.c_int()
|
|
190
|
+
name = ctypes.create_string_buffer(256)
|
|
191
|
+
if driver.cuDeviceGet(ctypes.byref(device), ordinal) != 0:
|
|
192
|
+
return None
|
|
193
|
+
if driver.cuDeviceComputeCapability(ctypes.byref(major), ctypes.byref(minor), device.value) != 0:
|
|
194
|
+
return None
|
|
195
|
+
if driver.cuDeviceGetName(name, len(name), device.value) != 0:
|
|
196
|
+
return None
|
|
197
|
+
devices.append(f"{ordinal}:{name.value.decode('utf-8', errors='replace')}:sm_{major.value}{minor.value}")
|
|
198
|
+
return f"driver_api={version.value};" + ";".join(devices)
|
|
199
|
+
except (AttributeError, OSError, ValueError):
|
|
200
|
+
return None
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
def _ensure_private_cache_directory(path: Path) -> None:
|
|
204
|
+
path.mkdir(parents=True, exist_ok=True, mode=0o700)
|
|
205
|
+
if os.name == "nt":
|
|
206
|
+
return
|
|
207
|
+
if path.is_symlink():
|
|
208
|
+
raise PermissionError(f"native cache directory must not be a symbolic link: {path}")
|
|
209
|
+
stat = path.stat()
|
|
210
|
+
if hasattr(os, "getuid") and stat.st_uid != os.getuid():
|
|
211
|
+
raise PermissionError(f"native cache directory is not owned by the current user: {path}")
|
|
212
|
+
if stat.st_mode & 0o077:
|
|
213
|
+
path.chmod(stat.st_mode & ~0o077)
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
def _platform_tag() -> str:
|
|
217
|
+
system = platform.system().lower() or "unknown"
|
|
218
|
+
machine = platform.machine().lower() or "unknown"
|
|
219
|
+
python_tag = f"py{sys.version_info.major}{sys.version_info.minor}"
|
|
220
|
+
return f"{system}-{machine}-{python_tag}"
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
@contextmanager
|
|
224
|
+
def _exclusive_cache_lock(
|
|
225
|
+
path: Path,
|
|
226
|
+
*,
|
|
227
|
+
timeout_s: float = 300.0,
|
|
228
|
+
stale_after_s: float = 900.0,
|
|
229
|
+
) -> Iterator[None]:
|
|
230
|
+
started = time.monotonic()
|
|
231
|
+
descriptor: int | None = None
|
|
232
|
+
while descriptor is None:
|
|
233
|
+
try:
|
|
234
|
+
descriptor = os.open(path, os.O_CREAT | os.O_EXCL | os.O_WRONLY, 0o600)
|
|
235
|
+
except (FileExistsError, PermissionError) as exc:
|
|
236
|
+
# Windows can report an existing O_EXCL lock as access denied when
|
|
237
|
+
# another process or thread still owns the directory entry.
|
|
238
|
+
if isinstance(exc, PermissionError) and sys.platform != "win32":
|
|
239
|
+
raise
|
|
240
|
+
try:
|
|
241
|
+
age_s = time.time() - path.stat().st_mtime
|
|
242
|
+
except FileNotFoundError:
|
|
243
|
+
continue
|
|
244
|
+
except PermissionError:
|
|
245
|
+
# A Windows sharing violation is itself evidence that the lock
|
|
246
|
+
# is live; do not misclassify it as a cache permission failure.
|
|
247
|
+
age_s = 0.0
|
|
248
|
+
if age_s > stale_after_s:
|
|
249
|
+
try:
|
|
250
|
+
path.unlink()
|
|
251
|
+
except FileNotFoundError:
|
|
252
|
+
pass
|
|
253
|
+
continue
|
|
254
|
+
if time.monotonic() - started >= timeout_s:
|
|
255
|
+
raise TimeoutError(f"timed out waiting for native cache lock: {path}")
|
|
256
|
+
time.sleep(0.05)
|
|
257
|
+
try:
|
|
258
|
+
os.write(descriptor, f"pid={os.getpid()}\n".encode("ascii"))
|
|
259
|
+
os.close(descriptor)
|
|
260
|
+
descriptor = None
|
|
261
|
+
yield
|
|
262
|
+
finally:
|
|
263
|
+
if descriptor is not None:
|
|
264
|
+
os.close(descriptor)
|
|
265
|
+
path.unlink(missing_ok=True)
|