CI and images / lint (push) Successful in 3s
CI and images / extension-version (push) Successful in 3s
CI and images / frontend-build (push) Successful in 27s
CI and images / backend-lint-and-test (push) Successful in 34s
CI and images / integration (push) Successful in 2m22s
CI and images / sign-extension (push) Successful in 4s
CI and images / build-web (push) Successful in 2m46s
CI and images / smoke-web (push) Successful in 50s
CI and images / build-agent (push) Successful in 6m35s
CI and images / promote (push) Successful in 2s
torch and onnxruntime both fall back to the CPU without raising, so the agent that ran CPU-bound for weeks after a driver update leased and checked in like a healthy one. - The agent sends its startup accel report on every lease and heartbeat. - The server keeps a bounded copy on the roster row. A running agent with a runtime off the GPU becomes `degraded`, with a sentence naming the runtime and the reason. - The top nav shows it amber. - The agent page carries a banner, and its pill reads "CPU only". Also: the bandwidth field gets the page's − / + stepper, and both number fields drop the browser's spin arrows. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LVjrnpQjRgHdvq95rASoiR
123 lines
3.7 KiB
Python
123 lines
3.7 KiB
Python
"""The agent's startup report of which runtime landed on the GPU (#1451).
|
|
|
|
Both runtimes fall back to the CPU without raising, so the report is the only
|
|
thing that says so. These pin the distinction it exists for: onnxruntime
|
|
listing the CUDA provider is not the same as the provider being able to load
|
|
its libraries.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import types
|
|
|
|
from agent.fc_agent import accel
|
|
|
|
|
|
def _ort(providers, preload=None):
|
|
mod = types.SimpleNamespace(
|
|
__version__="1.30.0",
|
|
__file__="/site/onnxruntime/__init__.py",
|
|
get_available_providers=lambda: providers,
|
|
)
|
|
if preload is not None:
|
|
mod.preload_dlls = preload
|
|
return mod
|
|
|
|
|
|
def _imp(mod):
|
|
return lambda name: mod
|
|
|
|
|
|
class _Cudart:
|
|
"""libcudart as ctypes sees it: cudaGetDeviceCount writes through a pointer."""
|
|
|
|
def __init__(self, rc=0, count=1):
|
|
self.rc = rc
|
|
self.count = count
|
|
self.cudaGetErrorString = lambda rc: b"unknown error"
|
|
|
|
def cudaGetDeviceCount(self, ref):
|
|
ref._obj.value = self.count
|
|
return self.rc
|
|
|
|
|
|
def _loader(calls, cudart):
|
|
def load(path, mode=0):
|
|
calls.append(path)
|
|
return cudart if path == "libcudart.so.13" else None
|
|
return load
|
|
|
|
|
|
GPU_BUILD = ["CUDAExecutionProvider", "CPUExecutionProvider"]
|
|
|
|
|
|
def test_onnx_on_gpu_when_the_provider_loads_and_a_device_answers():
|
|
calls = []
|
|
s = accel.onnx_status(
|
|
_imp(_ort(GPU_BUILD, lambda: calls.append("preload"))),
|
|
load=_loader(calls, _Cudart()),
|
|
)
|
|
assert s["device"] == "cuda"
|
|
assert calls[0] == "preload"
|
|
assert any(c.endswith("capi/libonnxruntime_providers_cuda.so") for c in calls)
|
|
|
|
|
|
def test_onnx_libraries_loading_is_not_a_gpu_when_cuda_cannot_initialise():
|
|
"""The 2026-09-24 case: every library resolved, cuInit failed."""
|
|
s = accel.onnx_status(_imp(_ort(GPU_BUILD)), load=_loader([], _Cudart(rc=999)))
|
|
assert s["device"] == "cpu"
|
|
assert "unknown error" in s["error"]
|
|
|
|
|
|
def test_onnx_listed_but_unloadable_reports_cpu_with_the_reason():
|
|
def load(path, mode=0):
|
|
if path.endswith("providers_cuda.so"):
|
|
raise OSError("libcudart.so.13: cannot open shared object file")
|
|
|
|
s = accel.onnx_status(_imp(_ort(GPU_BUILD)), load=load)
|
|
assert s["device"] == "cpu"
|
|
assert "libcudart.so.13" in s["error"]
|
|
|
|
|
|
def test_onnx_cpu_build_never_tries_the_cuda_library():
|
|
def load(path, mode=0):
|
|
raise AssertionError("a CPU build has no CUDA provider to load")
|
|
|
|
s = accel.onnx_status(_imp(_ort(["CPUExecutionProvider"])), load=load)
|
|
assert s["device"] == "cpu"
|
|
|
|
|
|
def test_torch_reports_cpu_when_cuda_is_unavailable():
|
|
torch = types.SimpleNamespace(
|
|
__version__="2.14.0+cu130",
|
|
version=types.SimpleNamespace(cuda="13.0"),
|
|
cuda=types.SimpleNamespace(is_available=lambda: False),
|
|
)
|
|
s = accel.torch_status(_imp(torch))
|
|
assert s == {"version": "2.14.0+cu130", "cuda_build": "13.0", "device": "cpu"}
|
|
|
|
|
|
def test_a_missing_runtime_is_reported_not_raised():
|
|
def imp(name):
|
|
raise ImportError(f"No module named {name!r}")
|
|
|
|
assert accel.torch_status(imp)["device"] == "unavailable"
|
|
assert accel.onnx_status(imp)["device"] == "unavailable"
|
|
|
|
|
|
def test_summary_is_what_the_server_stores(monkeypatch):
|
|
"""Device plus a bounded reason, per runtime — sent on every lease."""
|
|
monkeypatch.setattr(accel, "LAST", {
|
|
"torch": {"version": "2.14.0", "device": "cuda", "gpu": "RTX"},
|
|
"onnx": {"version": "1.30.0", "device": "cpu", "error": "e" * 500},
|
|
})
|
|
assert accel.summary() == {
|
|
"torch": {"device": "cuda"},
|
|
"onnx": {"device": "cpu", "error": "e" * 200},
|
|
}
|
|
|
|
|
|
def test_summary_before_the_report_is_none(monkeypatch):
|
|
monkeypatch.setattr(accel, "LAST", {})
|
|
assert accel.summary() is None
|