nvq 0.4.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.
- nvq/__init__.py +186 -0
- nvq/documents.py +490 -0
- nvq-0.4.0.dist-info/METADATA +25 -0
- nvq-0.4.0.dist-info/RECORD +6 -0
- nvq-0.4.0.dist-info/WHEEL +4 -0
- nvq-0.4.0.dist-info/entry_points.txt +2 -0
nvq/__init__.py
ADDED
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
"""nvq runs the nvq binary and hands back what it says about NVIDIA cards. The linux x86_64 and aarch64
|
|
2
|
+
wheels carry the binary (bin/nvq, built in manylinux2014: glibc 2.17 and newer), so `pip install nvq` is
|
|
3
|
+
the whole install, and a version of this package runs exactly the binary released with it. The wheel for
|
|
4
|
+
any other platform carries none: NVQ(path=...) names one there.
|
|
5
|
+
|
|
6
|
+
n = nvq.NVQ() # the package's own binary; NVQ(path=...) runs another
|
|
7
|
+
cards = n.list() # every card, lost ones included; no CUDA context opened
|
|
8
|
+
probes = n.probe_all() # does each card run a kernel, and its capability sheet
|
|
9
|
+
for ev in n.watch(every=1.0): ... # Xid events, lost cards, samples
|
|
10
|
+
|
|
11
|
+
Documents are the parsed JSON itself, keys exactly as the contract names them (card["tempC"]). Their
|
|
12
|
+
types (documents.py) are TypedDicts generated from the JSON Schema the binary prints with `nvq schema`
|
|
13
|
+
(`make types`), so a type checker knows every key and a value the card cannot report is a key that is
|
|
14
|
+
absent, never 0. `nvq` on the command line is the binary itself."""
|
|
15
|
+
|
|
16
|
+
# NVQ.list shadows the builtin inside the class body: annotations there must not be evaluated.
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
import json
|
|
20
|
+
import os
|
|
21
|
+
import subprocess
|
|
22
|
+
import sys
|
|
23
|
+
import tempfile
|
|
24
|
+
from collections.abc import Generator
|
|
25
|
+
from dataclasses import dataclass
|
|
26
|
+
from pathlib import Path
|
|
27
|
+
from typing import Any
|
|
28
|
+
|
|
29
|
+
from . import documents
|
|
30
|
+
from .documents import (
|
|
31
|
+
Card,
|
|
32
|
+
EventEccDouble,
|
|
33
|
+
EventLost,
|
|
34
|
+
EventSample,
|
|
35
|
+
EventStart,
|
|
36
|
+
EventWaitError,
|
|
37
|
+
EventXid,
|
|
38
|
+
ListOk,
|
|
39
|
+
ProbeFailed,
|
|
40
|
+
ProbeOk,
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
__all__ = [
|
|
44
|
+
"BINARY",
|
|
45
|
+
"NVQ",
|
|
46
|
+
"SCHEMA",
|
|
47
|
+
"Card",
|
|
48
|
+
"Error",
|
|
49
|
+
"EventEccDouble",
|
|
50
|
+
"EventLost",
|
|
51
|
+
"EventSample",
|
|
52
|
+
"EventStart",
|
|
53
|
+
"EventWaitError",
|
|
54
|
+
"EventXid",
|
|
55
|
+
"ListOk",
|
|
56
|
+
"ProbeFailed",
|
|
57
|
+
"ProbeOk",
|
|
58
|
+
"WatchEvent",
|
|
59
|
+
"documents",
|
|
60
|
+
]
|
|
61
|
+
|
|
62
|
+
type WatchEvent = EventStart | EventXid | EventEccDouble | EventLost | EventWaitError | EventSample
|
|
63
|
+
|
|
64
|
+
SCHEMA = 1
|
|
65
|
+
"""The document version this package reads."""
|
|
66
|
+
|
|
67
|
+
BINARY = Path(__file__).parent / "bin" / "nvq"
|
|
68
|
+
"""The binary this wheel carries; absent from the wheel for platforms nvq does not run on."""
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class Error(Exception):
|
|
72
|
+
"""A failure nvq reported as a document: `code` from the schema, nvq's `exit` code. When NVML is
|
|
73
|
+
unusable, `list` holds the kernel's roster all the same, every card in state "unknown"."""
|
|
74
|
+
|
|
75
|
+
def __init__(self, code: str, detail: str, exit: int, list: ListOk | None = None):
|
|
76
|
+
super().__init__(f"nvq: {code}: {detail} (exit {exit})")
|
|
77
|
+
self.code = code
|
|
78
|
+
self.detail = detail
|
|
79
|
+
self.exit = exit
|
|
80
|
+
self.list = list
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
@dataclass(frozen=True, kw_only=True)
|
|
84
|
+
class NVQ:
|
|
85
|
+
"""The binary to run: `path`, or when None the one this wheel carries.
|
|
86
|
+
|
|
87
|
+
`deadline` (seconds) bounds one list or probe inside nvq itself (--deadline-ms); None keeps nvq's
|
|
88
|
+
default (10 s list, 60 s probe). Each call's `timeout` bounds the whole process from outside: nvq is
|
|
89
|
+
killed and subprocess.TimeoutExpired raised."""
|
|
90
|
+
|
|
91
|
+
path: Path | str | None = None
|
|
92
|
+
deadline: float | None = None
|
|
93
|
+
|
|
94
|
+
def list(self, timeout: float | None = None) -> ListOk:
|
|
95
|
+
"""Every card the kernel bound. A lost card is a card with state "lost", not an error."""
|
|
96
|
+
doc, code = self._run(["list"], timeout)
|
|
97
|
+
if "cards" not in doc:
|
|
98
|
+
raise _error(doc, code)
|
|
99
|
+
if "error" in doc:
|
|
100
|
+
raise Error(doc["error"]["code"], doc["error"]["detail"], code, doc)
|
|
101
|
+
return doc
|
|
102
|
+
|
|
103
|
+
def probe(self, uuid: str, timeout: float | None = None) -> ProbeOk | ProbeFailed:
|
|
104
|
+
"""Runs a kernel on one card, with only that card visible to CUDA. It opens a CUDA context: do
|
|
105
|
+
not aim it at a card mid-work you care about. A failed step is a ProbeFailed, not an error."""
|
|
106
|
+
doc, code = self._run(["probe", uuid], timeout)
|
|
107
|
+
if "ok" not in doc:
|
|
108
|
+
raise _error(doc, code)
|
|
109
|
+
return doc
|
|
110
|
+
|
|
111
|
+
def probe_all(
|
|
112
|
+
self, timeout: float | None = None
|
|
113
|
+
) -> list[ProbeOk | ProbeFailed | documents.ErrorDocument]:
|
|
114
|
+
"""Probes every card at once, each in its own process: a hung card cannot stall the others. An
|
|
115
|
+
entry is a child's own document, so a child that failed as a whole is an error document."""
|
|
116
|
+
doc, code = self._run(["probe", "all"], timeout)
|
|
117
|
+
if "probes" not in doc:
|
|
118
|
+
raise _error(doc, code)
|
|
119
|
+
return doc["probes"]
|
|
120
|
+
|
|
121
|
+
def watch(self, every: float | None = None) -> Generator[WatchEvent]:
|
|
122
|
+
"""Events until nvq exits or the iterator is closed (which kills nvq). `every` (seconds) adds a
|
|
123
|
+
sample per card at that interval. nvq ending on its own raises: watch runs until killed."""
|
|
124
|
+
args = ["watch", *(["--every-ms", str(round(every * 1000))] if every else [])]
|
|
125
|
+
with (
|
|
126
|
+
tempfile.TemporaryFile() as stderr,
|
|
127
|
+
subprocess.Popen([str(self._binary()), *args], stdout=subprocess.PIPE, stderr=stderr) as p,
|
|
128
|
+
):
|
|
129
|
+
assert p.stdout is not None
|
|
130
|
+
try:
|
|
131
|
+
for line in p.stdout:
|
|
132
|
+
if not line.strip():
|
|
133
|
+
continue
|
|
134
|
+
doc = _document(line, args)
|
|
135
|
+
if "event" not in doc:
|
|
136
|
+
raise _error(doc, p.wait())
|
|
137
|
+
yield doc
|
|
138
|
+
code = p.wait()
|
|
139
|
+
stderr.seek(0)
|
|
140
|
+
raise RuntimeError(f"nvq watch ended: exit {code}: {stderr.read().decode().strip()}")
|
|
141
|
+
finally:
|
|
142
|
+
p.kill() # a no-op once nvq exited; leaving the with block reaps it
|
|
143
|
+
|
|
144
|
+
def _binary(self) -> Path:
|
|
145
|
+
return Path(self.path) if self.path is not None else _own_binary()
|
|
146
|
+
|
|
147
|
+
def _run(self, args: list[str], timeout: float | None) -> tuple[Any, int]:
|
|
148
|
+
"""The document nvq printed and its exit code. A process that printed none (not found, killed, a
|
|
149
|
+
usage error) raises here; any other exit is the document's to explain."""
|
|
150
|
+
deadline = ["--deadline-ms", str(round(self.deadline * 1000))] if self.deadline is not None else []
|
|
151
|
+
p = subprocess.run(
|
|
152
|
+
[str(self._binary()), *deadline, *args], capture_output=True, timeout=timeout, check=False
|
|
153
|
+
)
|
|
154
|
+
if not p.stdout.strip():
|
|
155
|
+
stderr = p.stderr.decode(errors="replace").strip()
|
|
156
|
+
raise RuntimeError(f"nvq {' '.join(args)}: exit {p.returncode}, no document: {stderr}")
|
|
157
|
+
return _document(p.stdout, args), p.returncode
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def _own_binary() -> Path:
|
|
161
|
+
if not BINARY.exists():
|
|
162
|
+
raise FileNotFoundError(
|
|
163
|
+
f"nvq: this wheel carries no binary for {sys.platform} (only the linux x86_64 and aarch64 "
|
|
164
|
+
f"wheels do); pass NVQ(path=...)"
|
|
165
|
+
)
|
|
166
|
+
return BINARY
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def _document(raw: bytes, args: list[str]) -> Any:
|
|
170
|
+
"""A parsed document, typed by the caller: the schema, not this function, vouches for its shape."""
|
|
171
|
+
doc = json.loads(raw)
|
|
172
|
+
if not isinstance(doc, dict) or doc.get("nvq") != SCHEMA:
|
|
173
|
+
raise ValueError(f"nvq {' '.join(args)}: not a version {SCHEMA} document: {raw[:200]!r}")
|
|
174
|
+
return doc
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def _error(doc: Any, code: int) -> Error:
|
|
178
|
+
if not isinstance(doc.get("error"), dict):
|
|
179
|
+
raise TypeError(f"nvq: neither the expected document nor an error: {json.dumps(doc)[:200]}")
|
|
180
|
+
return Error(doc["error"]["code"], doc["error"]["detail"], code)
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def main() -> None:
|
|
184
|
+
"""`nvq` on PATH: becomes the carried binary, arguments and exit code untouched."""
|
|
185
|
+
binary = _own_binary()
|
|
186
|
+
os.execv(binary, [str(binary), *sys.argv[1:]])
|
nvq/documents.py
ADDED
|
@@ -0,0 +1,490 @@
|
|
|
1
|
+
# generated by datamodel-codegen:
|
|
2
|
+
# filename: nvq.schema.json
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
from typing import Any, Literal, NotRequired, TypedDict
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
type Nvq = Any
|
|
9
|
+
"""
|
|
10
|
+
NVIDIA cards as JSON. Every document carries "nvq": 1, the version of this schema; additive fields keep it, a removal or a change of meaning bumps it. Readers ignore keys they do not know. A value the card cannot report is absent, never zero, and named under "unsupported".
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
type SchemaVersion = Literal[1]
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
type Uuid = str
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
type Bus = str
|
|
21
|
+
"""
|
|
22
|
+
PCI location as the kernel prints it
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
type UnixTime = float
|
|
27
|
+
"""
|
|
28
|
+
seconds since the epoch, millisecond precision
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
type Code = Literal[
|
|
33
|
+
"uninitialized",
|
|
34
|
+
"invalid_argument",
|
|
35
|
+
"not_supported",
|
|
36
|
+
"no_permission",
|
|
37
|
+
"already_initialized",
|
|
38
|
+
"not_found",
|
|
39
|
+
"insufficient_size",
|
|
40
|
+
"insufficient_power",
|
|
41
|
+
"driver_not_loaded",
|
|
42
|
+
"timeout",
|
|
43
|
+
"irq_issue",
|
|
44
|
+
"library_not_found",
|
|
45
|
+
"function_not_found",
|
|
46
|
+
"corrupted_inforom",
|
|
47
|
+
"gpu_is_lost",
|
|
48
|
+
"reset_required",
|
|
49
|
+
"operating_system",
|
|
50
|
+
"lib_rm_version_mismatch",
|
|
51
|
+
"in_use",
|
|
52
|
+
"memory",
|
|
53
|
+
"no_data",
|
|
54
|
+
"vgpu_ecc_not_supported",
|
|
55
|
+
"insufficient_resources",
|
|
56
|
+
"freq_not_supported",
|
|
57
|
+
"argument_version_mismatch",
|
|
58
|
+
"deprecated",
|
|
59
|
+
"not_ready",
|
|
60
|
+
"gpu_not_found",
|
|
61
|
+
"invalid_state",
|
|
62
|
+
"unknown",
|
|
63
|
+
"too_many_cards",
|
|
64
|
+
"deadline",
|
|
65
|
+
]
|
|
66
|
+
"""
|
|
67
|
+
Why something failed: NVML's return codes by name, plus nvq's own.
|
|
68
|
+
"""
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class ErrorBody(TypedDict):
|
|
72
|
+
code: Code
|
|
73
|
+
detail: str
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
type Limit = Literal[
|
|
77
|
+
"gpu_idle",
|
|
78
|
+
"applications_clocks",
|
|
79
|
+
"sw_power_cap",
|
|
80
|
+
"hw_slowdown",
|
|
81
|
+
"sync_boost",
|
|
82
|
+
"sw_thermal",
|
|
83
|
+
"hw_thermal",
|
|
84
|
+
"hw_power_brake",
|
|
85
|
+
"display_clocks",
|
|
86
|
+
]
|
|
87
|
+
"""
|
|
88
|
+
Why the card's clocks are below their maximum right now (NVML clock event reasons).
|
|
89
|
+
"""
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
class MemoryMiB(TypedDict):
|
|
93
|
+
total: int
|
|
94
|
+
used: int
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
class PowerW(TypedDict):
|
|
98
|
+
limit: NotRequired[float]
|
|
99
|
+
"""
|
|
100
|
+
enforced limit
|
|
101
|
+
"""
|
|
102
|
+
draw: NotRequired[float]
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
class ClocksMHz(TypedDict):
|
|
106
|
+
sm: int
|
|
107
|
+
mem: int
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
class UtilPct(TypedDict):
|
|
111
|
+
gpu: int
|
|
112
|
+
mem: int
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
class Pcie(TypedDict):
|
|
116
|
+
"""
|
|
117
|
+
the link now and at best; a riser shows here (gen 1, width 1)
|
|
118
|
+
"""
|
|
119
|
+
|
|
120
|
+
gen: int
|
|
121
|
+
width: int
|
|
122
|
+
maxGen: int
|
|
123
|
+
maxWidth: int
|
|
124
|
+
replays: NotRequired[int]
|
|
125
|
+
"""
|
|
126
|
+
link-level retransmissions since the driver loaded; one that keeps rising is a failing riser or slot
|
|
127
|
+
"""
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
class Process(TypedDict):
|
|
131
|
+
pid: int
|
|
132
|
+
usedMiB: NotRequired[int]
|
|
133
|
+
"""
|
|
134
|
+
absent when NVML cannot see it (another container)
|
|
135
|
+
"""
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
type Ms = dict[str, float]
|
|
139
|
+
"""
|
|
140
|
+
milliseconds per step, in the order run
|
|
141
|
+
"""
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
class ProbeFailed(TypedDict):
|
|
145
|
+
nvq: SchemaVersion
|
|
146
|
+
uuid: Uuid
|
|
147
|
+
ok: Literal[False]
|
|
148
|
+
step: str
|
|
149
|
+
"""
|
|
150
|
+
load, init, device, uuid, name, memory, driver, attributes, context, alloc, memset, jit, function, launch, sync, copy, verify, free, unload, teardown; in probe all also hang (killed or stuck) and crash (died without a document)
|
|
151
|
+
"""
|
|
152
|
+
error: str
|
|
153
|
+
"""
|
|
154
|
+
a CUDA_ERROR_* name, or not_visible, more_than_one_visible, uuid_mismatch, no_compute_capability, wrong_result, library_not_found, function_not_found: <name>, killed_at_deadline, stuck_in_driver, no_output
|
|
155
|
+
"""
|
|
156
|
+
ms: NotRequired[Ms]
|
|
157
|
+
signal: NotRequired[int]
|
|
158
|
+
"""
|
|
159
|
+
crash: the signal that ended the child
|
|
160
|
+
"""
|
|
161
|
+
exit: NotRequired[int]
|
|
162
|
+
"""
|
|
163
|
+
crash: the child's exit code
|
|
164
|
+
"""
|
|
165
|
+
pid: NotRequired[int]
|
|
166
|
+
"""
|
|
167
|
+
stuck_in_driver: the child that could not be reaped
|
|
168
|
+
"""
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
class Arch(TypedDict):
|
|
172
|
+
compute: str
|
|
173
|
+
"""
|
|
174
|
+
"8.6"
|
|
175
|
+
"""
|
|
176
|
+
sm: str
|
|
177
|
+
"""
|
|
178
|
+
the compile target, "sm_86"
|
|
179
|
+
"""
|
|
180
|
+
family: Literal[
|
|
181
|
+
"kepler", "maxwell", "pascal", "volta", "turing", "ampere", "ada", "hopper", "blackwell", "unknown"
|
|
182
|
+
]
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
class Layout(TypedDict):
|
|
186
|
+
sms: NotRequired[int]
|
|
187
|
+
"""
|
|
188
|
+
streaming multiprocessors
|
|
189
|
+
"""
|
|
190
|
+
warpSize: NotRequired[int]
|
|
191
|
+
maxThreadsPerBlock: NotRequired[int]
|
|
192
|
+
maxThreadsPerSM: NotRequired[int]
|
|
193
|
+
maxBlocksPerSM: NotRequired[int]
|
|
194
|
+
maxWarpsPerSM: NotRequired[int]
|
|
195
|
+
"""
|
|
196
|
+
maxThreadsPerSM / warpSize
|
|
197
|
+
"""
|
|
198
|
+
maxBlockDim: NotRequired[list[int]]
|
|
199
|
+
maxGridDim: NotRequired[list[int]]
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
class Registers(TypedDict):
|
|
203
|
+
perBlock: NotRequired[int]
|
|
204
|
+
perSM: NotRequired[int]
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
class SharedBytes(TypedDict):
|
|
208
|
+
perBlock: NotRequired[int]
|
|
209
|
+
"""
|
|
210
|
+
without opting in
|
|
211
|
+
"""
|
|
212
|
+
perBlockOptin: NotRequired[int]
|
|
213
|
+
"""
|
|
214
|
+
the most a kernel may request with cuFuncSetAttribute
|
|
215
|
+
"""
|
|
216
|
+
perSM: NotRequired[int]
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
class Memory(TypedDict):
|
|
220
|
+
totalMiB: int
|
|
221
|
+
busWidthBits: NotRequired[int]
|
|
222
|
+
clockMHz: NotRequired[int]
|
|
223
|
+
peakGBs: NotRequired[float]
|
|
224
|
+
"""
|
|
225
|
+
clockMHz × 2 (double data rate) × busWidthBits / 8, in GB/s
|
|
226
|
+
"""
|
|
227
|
+
l2Bytes: NotRequired[int]
|
|
228
|
+
l2PersistingBytes: NotRequired[int]
|
|
229
|
+
constantBytes: NotRequired[int]
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
class ProbeOk(TypedDict):
|
|
233
|
+
nvq: SchemaVersion
|
|
234
|
+
uuid: Uuid
|
|
235
|
+
ok: Literal[True]
|
|
236
|
+
name: str
|
|
237
|
+
cuda: str
|
|
238
|
+
"""
|
|
239
|
+
CUDA driver API version, "13.1"
|
|
240
|
+
"""
|
|
241
|
+
arch: Arch
|
|
242
|
+
layout: Layout
|
|
243
|
+
registers: Registers
|
|
244
|
+
sharedBytes: SharedBytes
|
|
245
|
+
memory: Memory
|
|
246
|
+
smClockMHz: NotRequired[int]
|
|
247
|
+
"""
|
|
248
|
+
the SM's peak clock
|
|
249
|
+
"""
|
|
250
|
+
copyEngines: NotRequired[int]
|
|
251
|
+
"""
|
|
252
|
+
copies that can overlap a kernel
|
|
253
|
+
"""
|
|
254
|
+
concurrentKernels: NotRequired[bool]
|
|
255
|
+
computeMode: NotRequired[Literal["default", "prohibited", "exclusive_process", "unknown"]]
|
|
256
|
+
ecc: NotRequired[bool]
|
|
257
|
+
integrated: NotRequired[bool]
|
|
258
|
+
multiGpuBoard: NotRequired[bool]
|
|
259
|
+
unsupported: NotRequired[list[str]]
|
|
260
|
+
"""
|
|
261
|
+
attributes the driver did not report
|
|
262
|
+
"""
|
|
263
|
+
ms: Ms
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
class WatchCard(TypedDict):
|
|
267
|
+
uuid: Uuid
|
|
268
|
+
bus: Bus
|
|
269
|
+
state: Literal["ok", "lost", "error"]
|
|
270
|
+
error: NotRequired[Code]
|
|
271
|
+
events: NotRequired[list[Literal["xid", "ecc_double"]]]
|
|
272
|
+
"""
|
|
273
|
+
event kinds registered for this card
|
|
274
|
+
"""
|
|
275
|
+
eventsError: NotRequired[Code]
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
class EventXid(TypedDict):
|
|
279
|
+
"""
|
|
280
|
+
An Xid error the driver raised; uuid and bus absent when NVML named a device nvq does not watch.
|
|
281
|
+
"""
|
|
282
|
+
|
|
283
|
+
nvq: SchemaVersion
|
|
284
|
+
event: Literal["xid"]
|
|
285
|
+
at: UnixTime
|
|
286
|
+
uuid: NotRequired[Uuid]
|
|
287
|
+
bus: NotRequired[Bus]
|
|
288
|
+
xid: int
|
|
289
|
+
meaning: str
|
|
290
|
+
"""
|
|
291
|
+
NVIDIA's name for it; empty for an Xid nvq has no name for
|
|
292
|
+
"""
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
class EventEccDouble(TypedDict):
|
|
296
|
+
"""
|
|
297
|
+
A double-bit ECC error; uuid and bus absent when NVML named a device nvq does not watch.
|
|
298
|
+
"""
|
|
299
|
+
|
|
300
|
+
nvq: SchemaVersion
|
|
301
|
+
event: Literal["ecc_double"]
|
|
302
|
+
at: UnixTime
|
|
303
|
+
uuid: NotRequired[Uuid]
|
|
304
|
+
bus: NotRequired[Bus]
|
|
305
|
+
data: int
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
class EventLost(TypedDict):
|
|
309
|
+
"""
|
|
310
|
+
A card NVML stopped reaching, reported once.
|
|
311
|
+
"""
|
|
312
|
+
|
|
313
|
+
nvq: SchemaVersion
|
|
314
|
+
event: Literal["lost"]
|
|
315
|
+
at: UnixTime
|
|
316
|
+
uuid: Uuid
|
|
317
|
+
bus: Bus
|
|
318
|
+
error: Code
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
class EventWaitError(TypedDict):
|
|
322
|
+
"""
|
|
323
|
+
Waiting for driver events failed; watch retries after a second.
|
|
324
|
+
"""
|
|
325
|
+
|
|
326
|
+
nvq: SchemaVersion
|
|
327
|
+
event: Literal["wait_error"]
|
|
328
|
+
at: UnixTime
|
|
329
|
+
error: Code
|
|
330
|
+
|
|
331
|
+
|
|
332
|
+
class EventSample(TypedDict):
|
|
333
|
+
"""
|
|
334
|
+
One card's readings, every --every-ms; a reading the card cannot give is absent.
|
|
335
|
+
"""
|
|
336
|
+
|
|
337
|
+
nvq: SchemaVersion
|
|
338
|
+
event: Literal["sample"]
|
|
339
|
+
at: UnixTime
|
|
340
|
+
uuid: Uuid
|
|
341
|
+
bus: Bus
|
|
342
|
+
tempC: NotRequired[int]
|
|
343
|
+
powerW: NotRequired[float]
|
|
344
|
+
smMHz: NotRequired[int]
|
|
345
|
+
utilPct: NotRequired[int]
|
|
346
|
+
fanPct: NotRequired[int]
|
|
347
|
+
energyJ: NotRequired[float]
|
|
348
|
+
"""
|
|
349
|
+
cumulative, as in list
|
|
350
|
+
"""
|
|
351
|
+
pcieReplays: NotRequired[int]
|
|
352
|
+
"""
|
|
353
|
+
cumulative, as in list
|
|
354
|
+
"""
|
|
355
|
+
limits: NotRequired[list[Limit]]
|
|
356
|
+
|
|
357
|
+
|
|
358
|
+
class Version(TypedDict):
|
|
359
|
+
nvq: SchemaVersion
|
|
360
|
+
version: str
|
|
361
|
+
|
|
362
|
+
|
|
363
|
+
class ErrorDocument(TypedDict):
|
|
364
|
+
"""
|
|
365
|
+
The whole command failed, or (in list) NVML could not be used.
|
|
366
|
+
"""
|
|
367
|
+
|
|
368
|
+
nvq: SchemaVersion
|
|
369
|
+
error: ErrorBody
|
|
370
|
+
|
|
371
|
+
|
|
372
|
+
class Card(TypedDict):
|
|
373
|
+
index: int
|
|
374
|
+
"""
|
|
375
|
+
position in bus order; not CUDA's or nvidia-smi's index
|
|
376
|
+
"""
|
|
377
|
+
uuid: Uuid
|
|
378
|
+
bus: Bus
|
|
379
|
+
minor: int
|
|
380
|
+
"""
|
|
381
|
+
/dev/nvidia<minor>; -1 when the kernel did not say
|
|
382
|
+
"""
|
|
383
|
+
model: str
|
|
384
|
+
state: Literal["ok", "lost", "error", "unknown"]
|
|
385
|
+
"""
|
|
386
|
+
ok: NVML reaches it. lost: fell off the bus (gpu_is_lost). error: another NVML failure. unknown: NVML unusable
|
|
387
|
+
"""
|
|
388
|
+
error: NotRequired[Code]
|
|
389
|
+
"""
|
|
390
|
+
with state lost or error
|
|
391
|
+
"""
|
|
392
|
+
compute: NotRequired[str]
|
|
393
|
+
"""
|
|
394
|
+
CUDA compute capability, "8.6"
|
|
395
|
+
"""
|
|
396
|
+
memoryMiB: NotRequired[MemoryMiB]
|
|
397
|
+
powerW: NotRequired[PowerW]
|
|
398
|
+
tempC: NotRequired[int]
|
|
399
|
+
fanPct: NotRequired[int]
|
|
400
|
+
"""
|
|
401
|
+
target fan speed; consumer cards with fan control off report not_supported
|
|
402
|
+
"""
|
|
403
|
+
clocksMHz: NotRequired[ClocksMHz]
|
|
404
|
+
utilPct: NotRequired[UtilPct]
|
|
405
|
+
pstate: NotRequired[int]
|
|
406
|
+
"""
|
|
407
|
+
performance state, 0 fastest
|
|
408
|
+
"""
|
|
409
|
+
pcie: NotRequired[Pcie]
|
|
410
|
+
"""
|
|
411
|
+
the link now and at best; a riser shows here (gen 1, width 1)
|
|
412
|
+
"""
|
|
413
|
+
energyJ: NotRequired[float]
|
|
414
|
+
"""
|
|
415
|
+
energy the card used since the driver loaded, joules; the difference of two readings over their interval is average power
|
|
416
|
+
"""
|
|
417
|
+
persistence: NotRequired[bool]
|
|
418
|
+
limits: NotRequired[list[Limit]]
|
|
419
|
+
processes: NotRequired[list[Process]]
|
|
420
|
+
"""
|
|
421
|
+
compute processes holding the card
|
|
422
|
+
"""
|
|
423
|
+
unsupported: NotRequired[list[str]]
|
|
424
|
+
"""
|
|
425
|
+
fields this card does not report
|
|
426
|
+
"""
|
|
427
|
+
errors: NotRequired[dict[str, Code]]
|
|
428
|
+
"""
|
|
429
|
+
fields whose query failed, by field
|
|
430
|
+
"""
|
|
431
|
+
|
|
432
|
+
|
|
433
|
+
type Probe = ErrorDocument | ProbeOk | ProbeFailed
|
|
434
|
+
|
|
435
|
+
|
|
436
|
+
class ProbeAllOk(TypedDict):
|
|
437
|
+
"""
|
|
438
|
+
One entry per card; a card that failed is an entry that says how, not a failure.
|
|
439
|
+
"""
|
|
440
|
+
|
|
441
|
+
nvq: SchemaVersion
|
|
442
|
+
probes: list[ErrorDocument | ProbeOk | ProbeFailed]
|
|
443
|
+
|
|
444
|
+
|
|
445
|
+
class EventStart(TypedDict):
|
|
446
|
+
"""
|
|
447
|
+
First line: the cards watched and the event kinds registered on each.
|
|
448
|
+
"""
|
|
449
|
+
|
|
450
|
+
nvq: SchemaVersion
|
|
451
|
+
event: Literal["start"]
|
|
452
|
+
at: UnixTime
|
|
453
|
+
cards: list[WatchCard]
|
|
454
|
+
|
|
455
|
+
|
|
456
|
+
class ListOk(TypedDict):
|
|
457
|
+
"""
|
|
458
|
+
Every card the kernel bound; lost ones are cards with state lost, not a failure.
|
|
459
|
+
"""
|
|
460
|
+
|
|
461
|
+
nvq: SchemaVersion
|
|
462
|
+
driver: str
|
|
463
|
+
"""
|
|
464
|
+
kernel module version from /proc, "590.48.01"; empty when unreadable
|
|
465
|
+
"""
|
|
466
|
+
nvml: NotRequired[str]
|
|
467
|
+
"""
|
|
468
|
+
NVML library version
|
|
469
|
+
"""
|
|
470
|
+
cuda: NotRequired[str]
|
|
471
|
+
"""
|
|
472
|
+
highest CUDA version the driver supports, "13.1"
|
|
473
|
+
"""
|
|
474
|
+
error: NotRequired[ErrorBody]
|
|
475
|
+
"""
|
|
476
|
+
NVML could not be used; every card's state is unknown
|
|
477
|
+
"""
|
|
478
|
+
cards: list[Card]
|
|
479
|
+
|
|
480
|
+
|
|
481
|
+
type ProbeAll = ErrorDocument | ProbeAllOk
|
|
482
|
+
|
|
483
|
+
|
|
484
|
+
type Event = ErrorDocument | EventStart | EventXid | EventEccDouble | EventLost | EventWaitError | EventSample
|
|
485
|
+
"""
|
|
486
|
+
One line of watch, told apart by "event".
|
|
487
|
+
"""
|
|
488
|
+
|
|
489
|
+
|
|
490
|
+
type List = ErrorDocument | ListOk
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: nvq
|
|
3
|
+
Version: 0.4.0
|
|
4
|
+
Summary: NVIDIA cards as JSON, for programs: list, probe, watch. Carries the nvq binary.
|
|
5
|
+
Project-URL: Source, https://github.com/adriangalilea/nvq
|
|
6
|
+
Requires-Python: >=3.12
|
|
7
|
+
Description-Content-Type: text/markdown
|
|
8
|
+
|
|
9
|
+
# nvq
|
|
10
|
+
|
|
11
|
+
NVIDIA cards as JSON, for programs. This package carries the [nvq](https://github.com/adriangalilea/nvq) binary for linux x86_64 and aarch64 (glibc 2.17 or newer) and runs it: `pip install nvq` is the whole install, and `nvq` lands on PATH too.
|
|
12
|
+
|
|
13
|
+
```python
|
|
14
|
+
import nvq
|
|
15
|
+
|
|
16
|
+
n = nvq.NVQ()
|
|
17
|
+
for card in n.list()["cards"]:
|
|
18
|
+
print(card["model"], card["state"], card.get("tempC"))
|
|
19
|
+
|
|
20
|
+
for ev in n.watch(every=1.0):
|
|
21
|
+
if ev["event"] == "xid":
|
|
22
|
+
print(ev["xid"], ev["meaning"])
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Documents are the parsed JSON, keys as the [contract](https://github.com/adriangalilea/nvq/blob/main/schema/nvq.schema.json) names them; a value the card cannot report is an absent key. Their types (`nvq.documents`) are TypedDicts generated from that schema. A command that failed as a whole raises `nvq.Error` with its `code` and `exit`; a lost card or a failed probe step is data, not an exception. Each call takes a `timeout`: past it nvq is killed, so a card hung in the driver never hangs your program.
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
nvq/__init__.py,sha256=7I8wXhYMzImZFKZ_bKAD8U1QLWsLS2UeMDr3-s_QS9A,7579
|
|
2
|
+
nvq/documents.py,sha256=rDkiC6EZTzoE83DjklT6FUP9SAU37ZGUROvBR2CP7aE,10661
|
|
3
|
+
nvq-0.4.0.dist-info/METADATA,sha256=GkjO0jzWKsck7M6-JqwtkQSGigV0K_xXdMIZ7un9jbo,1251
|
|
4
|
+
nvq-0.4.0.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
|
|
5
|
+
nvq-0.4.0.dist-info/entry_points.txt,sha256=xREF6LumDfLbWCmIbVcbPvHCvvFNz9tPcdcfNuRRNXQ,33
|
|
6
|
+
nvq-0.4.0.dist-info/RECORD,,
|