mate-runtime-cuda 0.1.0__tar.gz

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.
@@ -0,0 +1,30 @@
1
+ # Python
2
+ .venv/
3
+ __pycache__/
4
+ *.py[cod]
5
+ *.egg-info/
6
+ dist/
7
+ build/
8
+ .pytest_cache/
9
+
10
+ # uv
11
+ uv.lock
12
+
13
+ # Node / Cloudflare Worker
14
+ worker/node_modules/
15
+ worker/.wrangler/
16
+ worker/dist/
17
+
18
+ # Results (local benchmark output)
19
+ results/
20
+
21
+ # Secrets / local config
22
+ .env
23
+ *.env.local
24
+
25
+ # OS
26
+ .DS_Store
27
+ Thumbs.db
28
+
29
+ # Internal planning notes
30
+ BENCH_NOTES.md
@@ -0,0 +1,15 @@
1
+ Metadata-Version: 2.4
2
+ Name: mate-runtime-cuda
3
+ Version: 0.1.0
4
+ Summary: NVIDIA CUDA runtime plugin for mate-bench
5
+ Project-URL: Homepage, https://github.com/T0nd3/mate-bench
6
+ Project-URL: Repository, https://github.com/T0nd3/mate-bench
7
+ Author-email: Benjamin Fäuster <benjamin.faeuster@web.de>
8
+ License: MIT
9
+ Classifier: License :: OSI Approved :: MIT License
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: Programming Language :: Python :: 3.11
12
+ Classifier: Programming Language :: Python :: 3.12
13
+ Classifier: Topic :: System :: Benchmark
14
+ Requires-Python: >=3.11
15
+ Requires-Dist: mate-bench<0.2,>=0.1
@@ -0,0 +1,19 @@
1
+ # mate-runtime-cuda
2
+
3
+ NVIDIA CUDA runtime plugin for [mate-bench](https://github.com/T0nd3/mate-bench).
4
+
5
+ Detects GPU name, chip, VRAM, CUDA version and driver from `nvidia-smi`.
6
+
7
+ ```bash
8
+ pip install mate-bench mate-runtime-cuda
9
+ mate list-runtimes
10
+ ```
11
+
12
+ ## Requirements
13
+
14
+ - NVIDIA GPU with drivers installed (`nvidia-smi` must be in PATH)
15
+ - CUDA 11.x or newer
16
+
17
+ ## Supported GPUs
18
+
19
+ Pascal (GTX 10xx) through Blackwell (RTX 50xx), including data centre GPUs (A100, H100, V100).
@@ -0,0 +1,41 @@
1
+ [project]
2
+ name = "mate-runtime-cuda"
3
+ version = "0.1.0"
4
+ description = "NVIDIA CUDA runtime plugin for mate-bench"
5
+ requires-python = ">=3.11"
6
+ license = {text = "MIT"}
7
+ authors = [{name = "Benjamin Fäuster", email = "benjamin.faeuster@web.de"}]
8
+ classifiers = [
9
+ "License :: OSI Approved :: MIT License",
10
+ "Programming Language :: Python :: 3",
11
+ "Programming Language :: Python :: 3.11",
12
+ "Programming Language :: Python :: 3.12",
13
+ "Topic :: System :: Benchmark",
14
+ ]
15
+ dependencies = [
16
+ "mate-bench>=0.1,<0.2",
17
+ ]
18
+
19
+ [project.urls]
20
+ Homepage = "https://github.com/T0nd3/mate-bench"
21
+ Repository = "https://github.com/T0nd3/mate-bench"
22
+
23
+ [project.entry-points."mate_bench.runtime"]
24
+ cuda = "mate_runtime_cuda:CudaRuntime"
25
+
26
+ [tool.mate-bench]
27
+ requires_mate_bench = ">=0.1,<0.2"
28
+ api_version = 1
29
+
30
+ [dependency-groups]
31
+ dev = ["pytest>=8.0", "ruff>=0.4"]
32
+
33
+ [tool.pytest.ini_options]
34
+ markers = ["integration: requires an NVIDIA GPU with CUDA drivers"]
35
+
36
+ [build-system]
37
+ requires = ["hatchling"]
38
+ build-backend = "hatchling.build"
39
+
40
+ [tool.hatch.build.targets.wheel]
41
+ packages = ["src/mate_runtime_cuda"]
@@ -0,0 +1,28 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any
4
+
5
+ from mate_bench.plugin import PluginManifest
6
+
7
+ from ._detect import GpuInfo, is_cuda_available, query_gpu
8
+
9
+
10
+ class CudaRuntime:
11
+ name = "cuda"
12
+ manifest = PluginManifest(requires_mate_bench=">=0.1,<0.2", api_version=1)
13
+
14
+ def is_available(self) -> bool:
15
+ return is_cuda_available()
16
+
17
+ def gpu_info(self, index: int = 0) -> dict[str, Any]:
18
+ info: GpuInfo = query_gpu(index)
19
+ return {
20
+ "gpu_vendor": "nvidia",
21
+ "gpu_name": info.name,
22
+ "gpu_chip": info.chip,
23
+ "vram_gb": info.vram_gb,
24
+ "runtime": info.cuda_version,
25
+ "driver": info.driver_version,
26
+ "_gpu_name_raw": info.name_raw,
27
+ "_gpu_name_known": info.name_known,
28
+ }
@@ -0,0 +1,85 @@
1
+ from __future__ import annotations
2
+
3
+ import re
4
+ import subprocess
5
+ from dataclasses import dataclass
6
+
7
+ from ._names import chip_from_name, normalize
8
+
9
+
10
+ @dataclass
11
+ class GpuInfo:
12
+ chip: str # die codename, e.g. "AD102"
13
+ name: str # normalized, e.g. "RTX 4090"
14
+ name_raw: str # as reported by nvidia-smi, e.g. "NVIDIA GeForce RTX 4090"
15
+ name_known: bool # False → unknown model, stored raw + flagged
16
+ vram_gb: float
17
+ cuda_version: str # e.g. "CUDA 12.4"
18
+ driver_version: str # e.g. "550.54.15"
19
+
20
+
21
+ def is_cuda_available() -> bool:
22
+ """CUDA is available if nvidia-smi lists at least one GPU."""
23
+ return bool(_run(["nvidia-smi", "-L"]))
24
+
25
+
26
+ def query_gpu(index: int = 0) -> GpuInfo:
27
+ """Query GPU info for the given device index via nvidia-smi."""
28
+ rows = _query_smi(
29
+ ["name", "memory.total", "driver_version", "cuda_version"],
30
+ units=False,
31
+ )
32
+ if not rows:
33
+ raise RuntimeError("No NVIDIA GPU found. Are the drivers installed?")
34
+ if index >= len(rows):
35
+ raise IndexError(f"GPU index {index} out of range ({len(rows)} GPUs found)")
36
+
37
+ row = rows[index]
38
+ raw_name = row[0].strip()
39
+ vram_mib = _parse_float(row[1])
40
+ driver = row[2].strip()
41
+ cuda = row[3].strip()
42
+
43
+ name, name_known = normalize(raw_name)
44
+ chip, chip_known = chip_from_name(name)
45
+ if not chip_known:
46
+ chip = name # fall back to display name as chip key
47
+
48
+ return GpuInfo(
49
+ chip=chip,
50
+ name=name,
51
+ name_raw=raw_name,
52
+ name_known=name_known and chip_known,
53
+ vram_gb=round(vram_mib / 1024, 1),
54
+ cuda_version=f"CUDA {cuda}" if cuda else "CUDA unknown",
55
+ driver_version=driver or "unknown",
56
+ )
57
+
58
+
59
+ # ── internal helpers ──────────────────────────────────────────────────────────
60
+
61
+
62
+ def _run(cmd: list[str], timeout: int = 10) -> str:
63
+ try:
64
+ result = subprocess.run(cmd, capture_output=True, text=True, timeout=timeout)
65
+ return result.stdout
66
+ except (FileNotFoundError, subprocess.TimeoutExpired, OSError):
67
+ return ""
68
+
69
+
70
+ def _query_smi(fields: list[str], units: bool = False) -> list[list[str]]:
71
+ """Run nvidia-smi --query-gpu and return parsed rows."""
72
+ cmd = [
73
+ "nvidia-smi",
74
+ f"--query-gpu={','.join(fields)}",
75
+ "--format=csv,noheader" + ("" if units else ",nounits"),
76
+ ]
77
+ out = _run(cmd)
78
+ if not out.strip():
79
+ return []
80
+ return [line.split(",") for line in out.strip().splitlines() if line.strip()]
81
+
82
+
83
+ def _parse_float(s: str) -> float:
84
+ m = re.search(r"[\d.]+", s)
85
+ return float(m.group()) if m else 0.0
@@ -0,0 +1,95 @@
1
+ from __future__ import annotations
2
+
3
+ import re
4
+
5
+ # (substring to match in normalized name, chip codename)
6
+ # Ordered from most specific to least specific.
7
+ _GPU_CHIP_MAP: list[tuple[str, str]] = [
8
+ # Blackwell — RTX 50xx
9
+ ("RTX 5090", "GB202"),
10
+ ("RTX 5080", "GB203"),
11
+ ("RTX 5070 Ti", "GB203"),
12
+ ("RTX 5070", "GB205"),
13
+ ("RTX 5060 Ti", "GB206"),
14
+ ("RTX 5060", "GB206"),
15
+ # Ada Lovelace — RTX 40xx + professional
16
+ ("RTX 6000 Ada", "AD102"),
17
+ ("RTX 4090", "AD102"),
18
+ ("RTX 4080 Super", "AD103"),
19
+ ("RTX 4080", "AD103"),
20
+ ("RTX 4070 Ti Super", "AD103"),
21
+ ("RTX 4070 Ti", "AD104"),
22
+ ("RTX 4070 Super", "AD104"),
23
+ ("RTX 4070", "AD104"),
24
+ ("RTX 4500 Ada", "AD104"),
25
+ ("RTX 4000 Ada", "AD104"),
26
+ ("RTX 4060 Ti", "AD106"),
27
+ ("RTX 4060", "AD107"),
28
+ # Hopper
29
+ ("H200", "GH200"),
30
+ ("H100", "GH100"),
31
+ # Ampere — data centre
32
+ ("A100", "GA100"),
33
+ ("A800", "GA100"),
34
+ ("RTX A6000", "GA102"),
35
+ ("RTX A5000", "GA102"),
36
+ ("RTX A4000", "GA104"),
37
+ ("RTX A3000", "GA104"),
38
+ ("RTX A2000", "GA106"),
39
+ # Ampere — RTX 30xx
40
+ ("RTX 3090 Ti", "GA102"),
41
+ ("RTX 3090", "GA102"),
42
+ ("RTX 3080 Ti", "GA102"),
43
+ ("RTX 3080", "GA102"),
44
+ ("RTX 3070 Ti", "GA104"),
45
+ ("RTX 3070", "GA104"),
46
+ ("RTX 3060 Ti", "GA104"),
47
+ ("RTX 3060", "GA106"),
48
+ ("RTX 3050", "GA107"),
49
+ # Volta
50
+ ("Titan V", "GV100"),
51
+ ("V100", "GV100"),
52
+ # Turing — RTX 20xx
53
+ ("RTX 2080 Ti", "TU102"),
54
+ ("RTX 2080 Super", "TU104"),
55
+ ("RTX 2080", "TU104"),
56
+ ("RTX 2070 Super", "TU104"),
57
+ ("RTX 2070", "TU106"),
58
+ ("RTX 2060 Super", "TU106"),
59
+ ("RTX 2060", "TU106"),
60
+ # Turing — GTX 16xx
61
+ ("GTX 1660 Ti", "TU116"),
62
+ ("GTX 1660 Super", "TU116"),
63
+ ("GTX 1660", "TU116"),
64
+ ("GTX 1650 Super", "TU116"),
65
+ ("GTX 1650", "TU117"),
66
+ # Pascal — GTX 10xx
67
+ ("Titan Xp", "GP102"),
68
+ ("GTX 1080 Ti", "GP102"),
69
+ ("GTX 1080", "GP104"),
70
+ ("GTX 1070 Ti", "GP104"),
71
+ ("GTX 1070", "GP104"),
72
+ ("GTX 1060", "GP106"),
73
+ ("GTX 1050 Ti", "GP107"),
74
+ ("GTX 1050", "GP107"),
75
+ ]
76
+
77
+ _STRIP_PREFIX = re.compile(r"^(?:NVIDIA\s+)?(?:GeForce\s+|Tesla\s+|Quadro\s+)?", re.IGNORECASE)
78
+
79
+
80
+ def normalize(raw: str) -> tuple[str, bool]:
81
+ """Return (display_name, is_known_chip).
82
+
83
+ Strips vendor/product-line prefixes and looks up the die codename.
84
+ """
85
+ display = _STRIP_PREFIX.sub("", raw).strip()
86
+ _, known = chip_from_name(display)
87
+ return display, known
88
+
89
+
90
+ def chip_from_name(normalized: str) -> tuple[str, bool]:
91
+ """Return (chip_codename, is_known) for a normalized GPU name."""
92
+ for substring, chip in _GPU_CHIP_MAP:
93
+ if substring.lower() in normalized.lower():
94
+ return chip, True
95
+ return normalized, False
File without changes
@@ -0,0 +1,11 @@
1
+ # nvidia-smi --query-gpu=name,memory.total,driver_version,cuda_version --format=csv,noheader,nounits
2
+
3
+ SMI_RTX4090 = "NVIDIA GeForce RTX 4090, 24564, 550.54.15, 12.4"
4
+ SMI_RTX3080 = "NVIDIA GeForce RTX 3080, 10240, 535.183.01, 12.2"
5
+ SMI_A100 = "NVIDIA A100-SXM4-80GB, 81920, 520.61.05, 11.8"
6
+ SMI_UNKNOWN = "NVIDIA SomeUnknownGPU, 8192, 550.54.15, 12.4"
7
+ SMI_EMPTY = ""
8
+
9
+ # nvidia-smi -L
10
+ SMI_LIST_RTX4090 = "GPU 0: NVIDIA GeForce RTX 4090 (UUID: GPU-abc123)\n"
11
+ SMI_LIST_EMPTY = ""
@@ -0,0 +1,136 @@
1
+ from __future__ import annotations
2
+
3
+ from unittest.mock import patch
4
+
5
+ import pytest
6
+
7
+ from mate_runtime_cuda._detect import _query_smi, is_cuda_available, query_gpu
8
+ from mate_runtime_cuda._names import chip_from_name, normalize
9
+
10
+ from .fixtures import (
11
+ SMI_A100,
12
+ SMI_EMPTY,
13
+ SMI_LIST_EMPTY,
14
+ SMI_LIST_RTX4090,
15
+ SMI_RTX3080,
16
+ SMI_RTX4090,
17
+ SMI_UNKNOWN,
18
+ )
19
+
20
+ # ── is_cuda_available ─────────────────────────────────────────────────────────
21
+
22
+
23
+ class TestIsCudaAvailable:
24
+ def test_available_when_smi_returns_output(self):
25
+ with patch("mate_runtime_cuda._detect._run", return_value=SMI_LIST_RTX4090):
26
+ assert is_cuda_available() is True
27
+
28
+ def test_unavailable_when_smi_empty(self):
29
+ with patch("mate_runtime_cuda._detect._run", return_value=SMI_LIST_EMPTY):
30
+ assert is_cuda_available() is False
31
+
32
+
33
+ # ── _query_smi ────────────────────────────────────────────────────────────────
34
+
35
+
36
+ class TestQuerySmi:
37
+ def test_parses_rtx4090_row(self):
38
+ with patch("mate_runtime_cuda._detect._run", return_value=SMI_RTX4090):
39
+ rows = _query_smi(["name", "memory.total", "driver_version", "cuda_version"])
40
+ assert len(rows) == 1
41
+ assert "RTX 4090" in rows[0][0]
42
+
43
+ def test_empty_returns_empty_list(self):
44
+ with patch("mate_runtime_cuda._detect._run", return_value=SMI_EMPTY):
45
+ assert _query_smi(["name"]) == []
46
+
47
+
48
+ # ── query_gpu ─────────────────────────────────────────────────────────────────
49
+
50
+
51
+ class TestQueryGpu:
52
+ def test_rtx4090(self):
53
+ with patch("mate_runtime_cuda._detect._run", return_value=SMI_RTX4090):
54
+ info = query_gpu(0)
55
+ assert info.chip == "AD102"
56
+ assert info.name == "RTX 4090"
57
+ assert info.name_raw == "NVIDIA GeForce RTX 4090"
58
+ assert info.name_known is True
59
+ assert info.vram_gb == 24.0
60
+ assert info.cuda_version == "CUDA 12.4"
61
+ assert info.driver_version == "550.54.15"
62
+
63
+ def test_rtx3080(self):
64
+ with patch("mate_runtime_cuda._detect._run", return_value=SMI_RTX3080):
65
+ info = query_gpu(0)
66
+ assert info.chip == "GA102"
67
+ assert info.name == "RTX 3080"
68
+ assert info.vram_gb == 10.0
69
+
70
+ def test_a100(self):
71
+ with patch("mate_runtime_cuda._detect._run", return_value=SMI_A100):
72
+ info = query_gpu(0)
73
+ assert info.chip == "GA100"
74
+ assert info.vram_gb == 80.0
75
+
76
+ def test_unknown_gpu_not_known(self):
77
+ with patch("mate_runtime_cuda._detect._run", return_value=SMI_UNKNOWN):
78
+ info = query_gpu(0)
79
+ assert info.name_known is False
80
+ assert info.name == "SomeUnknownGPU"
81
+
82
+ def test_no_gpu_raises(self):
83
+ with (
84
+ patch("mate_runtime_cuda._detect._run", return_value=SMI_EMPTY),
85
+ pytest.raises(RuntimeError, match="No NVIDIA GPU found"),
86
+ ):
87
+ query_gpu(0)
88
+
89
+ def test_index_out_of_range_raises(self):
90
+ with (
91
+ patch("mate_runtime_cuda._detect._run", return_value=SMI_RTX4090),
92
+ pytest.raises(IndexError),
93
+ ):
94
+ query_gpu(1)
95
+
96
+
97
+ # ── normalize & chip_from_name ────────────────────────────────────────────────
98
+
99
+
100
+ class TestNormalize:
101
+ @pytest.mark.parametrize(
102
+ "raw, expected_name",
103
+ [
104
+ ("NVIDIA GeForce RTX 4090", "RTX 4090"),
105
+ ("NVIDIA GeForce RTX 3080", "RTX 3080"),
106
+ ("NVIDIA A100-SXM4-80GB", "A100-SXM4-80GB"),
107
+ ("NVIDIA GeForce GTX 1080 Ti", "GTX 1080 Ti"),
108
+ ("NVIDIA GeForce RTX 2080 Ti", "RTX 2080 Ti"),
109
+ ],
110
+ )
111
+ def test_strips_prefix(self, raw, expected_name):
112
+ name, _ = normalize(raw)
113
+ assert name == expected_name
114
+
115
+ @pytest.mark.parametrize(
116
+ "raw, expected_chip",
117
+ [
118
+ ("NVIDIA GeForce RTX 4090", "AD102"),
119
+ ("NVIDIA GeForce RTX 3090", "GA102"),
120
+ ("NVIDIA GeForce RTX 3080", "GA102"),
121
+ ("NVIDIA GeForce RTX 3070", "GA104"),
122
+ ("NVIDIA GeForce RTX 2080 Ti", "TU102"),
123
+ ("NVIDIA GeForce GTX 1080 Ti", "GP102"),
124
+ ("NVIDIA H100", "GH100"),
125
+ ("NVIDIA A100-SXM4-80GB", "GA100"),
126
+ ],
127
+ )
128
+ def test_known_chips(self, raw, expected_chip):
129
+ name, _ = normalize(raw)
130
+ chip, known = chip_from_name(name)
131
+ assert chip == expected_chip
132
+ assert known is True
133
+
134
+ def test_unknown_chip(self):
135
+ _, known = normalize("NVIDIA Futuristic GPU XYZ")
136
+ assert known is False