nvprobe 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.
nvprobe/__init__.py ADDED
@@ -0,0 +1,3 @@
1
+ """nvProbe — NVIDIA GPU benchmark suite for CUDA workload automation."""
2
+
3
+ __version__ = "0.1.0"
@@ -0,0 +1,24 @@
1
+ """Benchmark modules for nvProbe."""
2
+
3
+ from nvprobe.benchmarks.bandwidth import BandwidthBenchmark
4
+ from nvprobe.benchmarks.custom import CustomCudaBenchmark
5
+ from nvprobe.benchmarks.hpl import HplBenchmark
6
+ from nvprobe.benchmarks.hpcg import HpcgBenchmark
7
+ from nvprobe.benchmarks.mlperf import MlperfBenchmark
8
+
9
+ BENCHMARK_REGISTRY: dict[str, type] = {
10
+ "bandwidth": BandwidthBenchmark,
11
+ "custom": CustomCudaBenchmark,
12
+ "hpl": HplBenchmark,
13
+ "hpcg": HpcgBenchmark,
14
+ "mlperf": MlperfBenchmark,
15
+ }
16
+
17
+ __all__ = [
18
+ "BENCHMARK_REGISTRY",
19
+ "BandwidthBenchmark",
20
+ "CustomCudaBenchmark",
21
+ "HplBenchmark",
22
+ "HpcgBenchmark",
23
+ "MlperfBenchmark",
24
+ ]
@@ -0,0 +1 @@
1
+ """nvProbe CUDA test modules — called as subprocesses by benchmark runners."""
@@ -0,0 +1,111 @@
1
+ #!/usr/bin/env python3
2
+ """Memory bandwidth benchmark — measures host↔device and device↔device transfer rates.
3
+
4
+ Usage:
5
+ python -m nvprobe.benchmarks._cuda.bandwidth_test \
6
+ --gpu 0 --sizes 1,4,16,64,256,1024 --iterations 100 --precision fp32
7
+
8
+ Output: JSON to stdout with per-size bandwidth measurements.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import argparse
14
+ import json
15
+ import time
16
+ from typing import Any
17
+
18
+ import cupy as cp
19
+
20
+ from nvprobe.benchmarks._cuda.utils import get_gpu_info, output_json
21
+
22
+
23
+ def run_bandwidth_test(
24
+ gpu_index: int,
25
+ sizes_mb: list[int],
26
+ iterations: int,
27
+ precision: str,
28
+ ) -> dict[str, Any]:
29
+ """Run bandwidth test across specified buffer sizes."""
30
+ cp.cuda.Device(gpu_index).use()
31
+
32
+ dtype_map = {
33
+ "fp32": cp.float32,
34
+ "fp16": cp.float16,
35
+ "int8": cp.int8,
36
+ }
37
+ dtype = dtype_map.get(precision, cp.float32)
38
+
39
+ results: dict[str, Any] = {"h2d": {}, "d2h": {}, "d2d": {}}
40
+
41
+ for size_mb in sizes_mb:
42
+ n_elements = (size_mb * 1024 * 1024) // dtype.itemsize
43
+ host_data = cp.ones(n_elements, dtype=dtype)
44
+ device_data = cp.empty(n_elements, dtype=dtype)
45
+
46
+ # Warmup
47
+ for _ in range(min(10, iterations)):
48
+ device_data.copy_from(host_data)
49
+ cp.cuda.Stream.null.synchronize()
50
+
51
+ # Host → Device
52
+ start = cp.cuda.Event()
53
+ end = cp.cuda.Event()
54
+ start.record()
55
+ for _ in range(iterations):
56
+ device_data.copy_from(host_data)
57
+ end.record()
58
+ end.synchronize()
59
+ h2d_ms = cp.cuda.get_elapsed_time(start, end)
60
+ h2d_bw = (size_mb * iterations) / (h2d_ms / 1000) # MB/s
61
+ results["h2d"][str(size_mb)] = round(h2d_bw, 2)
62
+
63
+ # Device → Host
64
+ start.record()
65
+ for _ in range(iterations):
66
+ host_data.copy_from(device_data)
67
+ end.record()
68
+ end.synchronize()
69
+ d2h_ms = cp.cuda.get_elapsed_time(start, end)
70
+ d2h_bw = (size_mb * iterations) / (d2h_ms / 1000)
71
+ results["d2h"][str(size_mb)] = round(d2h_bw, 2)
72
+
73
+ # Device → Device
74
+ device_data2 = cp.empty(n_elements, dtype=dtype)
75
+ start.record()
76
+ for _ in range(iterations):
77
+ device_data2.copy_from(device_data)
78
+ end.record()
79
+ end.synchronize()
80
+ d2d_ms = cp.cuda.get_elapsed_time(start, end)
81
+ d2d_bw = (size_mb * iterations) / (d2d_ms / 1000)
82
+ results["d2d"][str(size_mb)] = round(d2d_bw, 2)
83
+
84
+ return results
85
+
86
+
87
+ def main() -> None:
88
+ parser = argparse.ArgumentParser(description="nvProbe Bandwidth Test")
89
+ parser.add_argument("--gpu", type=int, default=0)
90
+ parser.add_argument("--sizes", type=str, default="1,4,16,64,256,1024")
91
+ parser.add_argument("--iterations", type=int, default=100)
92
+ parser.add_argument("--precision", type=str, default="fp32")
93
+ args = parser.parse_args()
94
+
95
+ sizes = [int(s) for s in args.sizes.split(",")]
96
+ gpu_info = get_gpu_info(args.gpu)
97
+ bw_results = run_bandwidth_test(args.gpu, sizes, args.iterations, args.precision)
98
+
99
+ output_json({
100
+ "benchmark": "bandwidth",
101
+ "gpu_model": gpu_info["model"],
102
+ "gpu_index": args.gpu,
103
+ "precision": args.precision,
104
+ "iterations": args.iterations,
105
+ "sizes_mb": sizes,
106
+ "metrics": bw_results,
107
+ })
108
+
109
+
110
+ if __name__ == "__main__":
111
+ main()
@@ -0,0 +1,174 @@
1
+ #!/usr/bin/env python3
2
+ """Custom CUDA kernels benchmark — matmul, conv2d, attention microbenchmarks.
3
+
4
+ Usage:
5
+ python -m nvprobe.benchmarks._cuda.custom_kernels \
6
+ --gpu 0 --kernels matmul,conv2d,attention \
7
+ --sizes 512,1024,2048 --iterations 50 --precision fp32 --batch-size 32
8
+
9
+ Output: JSON to stdout with per-kernel, per-size performance metrics.
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ import argparse
15
+ import time
16
+ from typing import Any
17
+
18
+ import cupy as cp
19
+ from cupyx.scipy import ndimage as cp_ndimage
20
+
21
+ from nvprobe.benchmarks._cuda.utils import get_gpu_info, output_json
22
+
23
+
24
+ def bench_matmul(gpu_index: int, sizes: list[int], iterations: int, precision: str, batch_size: int) -> dict[str, Any]:
25
+ """Benchmark matrix multiplication."""
26
+ cp.cuda.Device(gpu_index).use()
27
+ dtype = cp.float32 if precision == "fp32" else cp.float16 if precision == "fp16" else cp.float32
28
+ results: dict[str, Any] = {}
29
+
30
+ for n in sizes:
31
+ a = cp.random.random((batch_size, n, n), dtype=dtype)
32
+ b = cp.random.random((batch_size, n, n), dtype=dtype)
33
+
34
+ # Warmup
35
+ for _ in range(min(5, iterations)):
36
+ _ = a @ b
37
+ cp.cuda.Stream.null.synchronize()
38
+
39
+ start = cp.cuda.Event()
40
+ end = cp.cuda.Event()
41
+ start.record()
42
+ for _ in range(iterations):
43
+ _ = a @ b
44
+ end.record()
45
+ end.synchronize()
46
+
47
+ elapsed_ms = cp.cuda.get_elapsed_time(start, end)
48
+ avg_ms = elapsed_ms / iterations
49
+ # GFLOPS = 2 * batch_size * n^3 / (avg_time_in_seconds * 1e9)
50
+ gflops = (2 * batch_size * n**3) / (avg_ms / 1000 * 1e9)
51
+ results[str(n)] = {"avg_ms": round(avg_ms, 4), "gflops": round(gflops, 2)}
52
+
53
+ return results
54
+
55
+
56
+ def bench_conv2d(gpu_index: int, sizes: list[int], iterations: int, precision: str, batch_size: int) -> dict[str, Any]:
57
+ """Benchmark 2D convolution."""
58
+ cp.cuda.Device(gpu_index).use()
59
+ dtype = cp.float32 if precision == "fp32" else cp.float16 if precision == "fp16" else cp.float32
60
+ results: dict[str, Any] = {}
61
+
62
+ for n in sizes:
63
+ channels = 64
64
+ kernel_size = 3
65
+ inp = cp.random.random((batch_size, channels, n, n), dtype=dtype)
66
+ weight = cp.random.random((channels, channels, kernel_size, kernel_size), dtype=dtype)
67
+
68
+ # Warmup using correlation (equivalent to conv2d)
69
+ for _ in range(min(5, iterations)):
70
+ for b in range(batch_size):
71
+ for c_out in range(channels):
72
+ cp_ndimage.correlate(inp[b, 0], weight[c_out, 0], mode="constant")
73
+ cp.cuda.Stream.null.synchronize()
74
+
75
+ start = cp.cuda.Event()
76
+ end = cp.cuda.Event()
77
+ start.record()
78
+ for _ in range(iterations):
79
+ for b in range(batch_size):
80
+ for c_out in range(min(4, channels)):
81
+ cp_ndimage.correlate(inp[b, 0], weight[c_out, 0], mode="constant")
82
+ end.record()
83
+ end.synchronize()
84
+
85
+ elapsed_ms = cp.cuda.get_elapsed_time(start, end)
86
+ avg_ms = elapsed_ms / iterations
87
+ results[str(n)] = {"avg_ms": round(avg_ms, 4), "throughput": round(batch_size * min(4, channels) * iterations / (elapsed_ms / 1000), 2)}
88
+
89
+ return results
90
+
91
+
92
+ def bench_attention(gpu_index: int, sizes: list[int], iterations: int, precision: str, batch_size: int) -> dict[str, Any]:
93
+ """Benchmark scaled dot-product attention: Q @ K^T / sqrt(d) @ V."""
94
+ cp.cuda.Device(gpu_index).use()
95
+ dtype = cp.float32 if precision == "fp32" else cp.float16 if precision == "fp16" else cp.float32
96
+ results: dict[str, Any] = {}
97
+
98
+ for seq_len in sizes:
99
+ d_model = min(128, seq_len) # head dimension
100
+ q = cp.random.random((batch_size, seq_len, d_model), dtype=dtype)
101
+ k = cp.random.random((batch_size, seq_len, d_model), dtype=dtype)
102
+ v = cp.random.random((batch_size, seq_len, d_model), dtype=dtype)
103
+ scale = d_model ** -0.5
104
+
105
+ # Warmup
106
+ for _ in range(min(5, iterations)):
107
+ scores = q @ k.transpose(0, 2, 1) * scale
108
+ weights = cp.softmax(scores, axis=-1)
109
+ _ = weights @ v
110
+ cp.cuda.Stream.null.synchronize()
111
+
112
+ start = cp.cuda.Event()
113
+ end = cp.cuda.Event()
114
+ start.record()
115
+ for _ in range(iterations):
116
+ scores = q @ k.transpose(0, 2, 1) * scale
117
+ weights = cp.softmax(scores, axis=-1)
118
+ _ = weights @ v
119
+ end.record()
120
+ end.synchronize()
121
+
122
+ elapsed_ms = cp.cuda.get_elapsed_time(start, end)
123
+ avg_ms = elapsed_ms / iterations
124
+ # Approximate FLOPS: 2*B*S*D (Q@K^T) + B*S*S (softmax) + 2*B*S*D (W@V)
125
+ flops = 2 * batch_size * seq_len * d_model + batch_size * seq_len * seq_len + 2 * batch_size * seq_len * d_model
126
+ tflops = flops / (avg_ms / 1000 * 1e12)
127
+ results[str(seq_len)] = {"avg_ms": round(avg_ms, 4), "tflops": round(tflops, 4)}
128
+
129
+ return results
130
+
131
+
132
+ KERNEL_MAP = {
133
+ "matmul": bench_matmul,
134
+ "conv2d": bench_conv2d,
135
+ "attention": bench_attention,
136
+ }
137
+
138
+
139
+ def main() -> None:
140
+ parser = argparse.ArgumentParser(description="nvProbe Custom CUDA Kernels Benchmark")
141
+ parser.add_argument("--gpu", type=int, default=0)
142
+ parser.add_argument("--kernels", type=str, default="matmul")
143
+ parser.add_argument("--sizes", type=str, default="512,1024,2048")
144
+ parser.add_argument("--iterations", type=int, default=50)
145
+ parser.add_argument("--precision", type=str, default="fp32")
146
+ parser.add_argument("--batch-size", type=int, default=32)
147
+ args = parser.parse_args()
148
+
149
+ kernels = [k.strip() for k in args.kernels.split(",")]
150
+ sizes = [int(s) for s in args.sizes.split(",")]
151
+ gpu_info = get_gpu_info(args.gpu)
152
+
153
+ all_results: dict[str, Any] = {}
154
+ for kernel_name in kernels:
155
+ bench_fn = KERNEL_MAP.get(kernel_name)
156
+ if bench_fn is None:
157
+ all_results[kernel_name] = {"error": f"unknown kernel '{kernel_name}'"}
158
+ continue
159
+ all_results[kernel_name] = bench_fn(args.gpu, sizes, args.iterations, args.precision, args.batch_size)
160
+
161
+ output_json({
162
+ "benchmark": "custom",
163
+ "gpu_model": gpu_info["model"],
164
+ "gpu_index": args.gpu,
165
+ "precision": args.precision,
166
+ "batch_size": args.batch_size,
167
+ "iterations": args.iterations,
168
+ "kernels": kernels,
169
+ "metrics": all_results,
170
+ })
171
+
172
+
173
+ if __name__ == "__main__":
174
+ main()
@@ -0,0 +1,29 @@
1
+ """Shared utilities for CUDA test modules."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import sys
7
+ from typing import Any
8
+
9
+
10
+ def output_json(data: dict[str, Any]) -> None:
11
+ """Print result as JSON to stdout and exit."""
12
+ print(json.dumps(data, default=str))
13
+ sys.exit(0)
14
+
15
+
16
+ def get_gpu_info(gpu_index: int) -> dict[str, Any]:
17
+ """Get GPU name and memory via cupy."""
18
+ try:
19
+ import cupy as cp
20
+ cp.cuda.Device(gpu_index).use()
21
+ mem = cp.cuda.Device(gpu_index).mem_info
22
+ props = cp.cuda.runtime.getDeviceProperties()
23
+ return {
24
+ "model": props["name"].decode() if isinstance(props["name"], bytes) else str(props["name"]),
25
+ "memory_total_mb": mem[1] // (1024 * 1024),
26
+ "memory_free_mb": mem[0] // (1024 * 1024),
27
+ }
28
+ except ImportError:
29
+ return {"model": "unknown (cupy not installed)", "memory_total_mb": 0, "memory_free_mb": 0}
@@ -0,0 +1,61 @@
1
+ """Bandwidth benchmark — measures memory bandwidth (host↔device, device↔device)."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import subprocess
7
+ from typing import Any
8
+
9
+ from nvprobe.benchmarks.base import BaseBenchmark, BenchmarkResult
10
+
11
+
12
+ class BandwidthBenchmark(BaseBenchmark):
13
+ """Memory bandwidth benchmark using cuda-memcheck or custom CUDA kernel."""
14
+
15
+ name = "bandwidth"
16
+
17
+ def run_local(self, gpu_index: int, precision: str, batch_size: int) -> BenchmarkResult:
18
+ sizes_mb = self.params.get("sizes_mb", [1, 4, 16, 64, 256, 1024])
19
+ iterations = self.params.get("iterations", 100)
20
+
21
+ cmd = [
22
+ "python3", "-m", "nvprobe.benchmarks._cuda.bandwidth_test",
23
+ "--gpu", str(gpu_index),
24
+ "--sizes", ",".join(str(s) for s in sizes_mb),
25
+ "--iterations", str(iterations),
26
+ "--precision", precision,
27
+ ]
28
+
29
+ try:
30
+ proc = subprocess.run(
31
+ cmd, capture_output=True, text=True, timeout=600, check=True,
32
+ )
33
+ data = json.loads(proc.stdout)
34
+ return BenchmarkResult(
35
+ benchmark=self.name,
36
+ gpu_model=data.get("gpu_model", "unknown"),
37
+ gpu_index=gpu_index,
38
+ precision=precision,
39
+ batch_size=batch_size,
40
+ metrics=data.get("metrics", {}),
41
+ raw_output=proc.stdout,
42
+ )
43
+ except (subprocess.CalledProcessError, json.JSONDecodeError, subprocess.TimeoutExpired) as exc:
44
+ return BenchmarkResult(
45
+ benchmark=self.name, gpu_model="unknown", gpu_index=gpu_index,
46
+ precision=precision, batch_size=batch_size,
47
+ success=False, error=str(exc),
48
+ )
49
+
50
+ def build_slurm_script(self, gpu_index: int, precision: str, batch_size: int) -> str:
51
+ """Return shell commands for this benchmark (without SBATCH headers)."""
52
+ sizes_mb = self.params.get("sizes_mb", [1, 4, 16, 64, 256, 1024])
53
+ iterations = self.params.get("iterations", 100)
54
+ return f"""export CUDA_VISIBLE_DEVICES={gpu_index}
55
+
56
+ python3 -m nvprobe.benchmarks._cuda.bandwidth_test \\
57
+ --gpu 0 \\
58
+ --sizes {','.join(str(s) for s in sizes_mb)} \\
59
+ --iterations {iterations} \\
60
+ --precision {precision}
61
+ """
@@ -0,0 +1,51 @@
1
+ """Base class for all benchmark modules."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from abc import ABC, abstractmethod
6
+ from dataclasses import dataclass, field
7
+ from pathlib import Path
8
+ from typing import Any
9
+
10
+
11
+ @dataclass
12
+ class BenchmarkResult:
13
+ """Result from a single benchmark execution."""
14
+
15
+ benchmark: str
16
+ gpu_model: str
17
+ gpu_index: int
18
+ precision: str
19
+ batch_size: int
20
+ metrics: dict[str, Any] = field(default_factory=dict)
21
+ raw_output: str = ""
22
+ success: bool = True
23
+ error: str = ""
24
+
25
+
26
+ class BaseBenchmark(ABC):
27
+ """Abstract base for all benchmark implementations."""
28
+
29
+ name: str = "base"
30
+
31
+ def __init__(self, params: dict[str, Any]) -> None:
32
+ self.params = params
33
+
34
+ @abstractmethod
35
+ def run_local(self, gpu_index: int, precision: str, batch_size: int) -> BenchmarkResult:
36
+ """Run the benchmark locally on a specific GPU. Returns result."""
37
+
38
+ @abstractmethod
39
+ def build_slurm_script(self, gpu_index: int, precision: str, batch_size: int) -> str:
40
+ """Return sbatch script content for this benchmark."""
41
+
42
+ def parse_slurm_output(self, output: str, gpu_index: int, precision: str, batch_size: int) -> BenchmarkResult:
43
+ """Parse Slurm job output into a BenchmarkResult. Override for custom parsing."""
44
+ return BenchmarkResult(
45
+ benchmark=self.name,
46
+ gpu_model="unknown",
47
+ gpu_index=gpu_index,
48
+ precision=precision,
49
+ batch_size=batch_size,
50
+ raw_output=output,
51
+ )
@@ -0,0 +1,67 @@
1
+ """Custom CUDA kernels benchmark — matmul, conv2d, attention microbenchmarks."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import subprocess
7
+ from typing import Any
8
+
9
+ from nvprobe.benchmarks.base import BaseBenchmark, BenchmarkResult
10
+
11
+
12
+ class CustomCudaBenchmark(BaseBenchmark):
13
+ """Benchmark user-defined CUDA kernels (matmul, conv2d, attention)."""
14
+
15
+ name = "custom"
16
+
17
+ def run_local(self, gpu_index: int, precision: str, batch_size: int) -> BenchmarkResult:
18
+ kernels = self.params.get("kernels", ["matmul"])
19
+ matrix_sizes = self.params.get("matrix_sizes", [1024])
20
+ iterations = self.params.get("iterations", 50)
21
+
22
+ cmd = [
23
+ "python3", "-m", "nvprobe.benchmarks._cuda.custom_kernels",
24
+ "--gpu", str(gpu_index),
25
+ "--kernels", ",".join(kernels),
26
+ "--sizes", ",".join(str(s) for s in matrix_sizes),
27
+ "--iterations", str(iterations),
28
+ "--precision", precision,
29
+ "--batch-size", str(batch_size),
30
+ ]
31
+
32
+ try:
33
+ proc = subprocess.run(
34
+ cmd, capture_output=True, text=True, timeout=1200, check=True,
35
+ )
36
+ data = json.loads(proc.stdout)
37
+ return BenchmarkResult(
38
+ benchmark=self.name,
39
+ gpu_model=data.get("gpu_model", "unknown"),
40
+ gpu_index=gpu_index,
41
+ precision=precision,
42
+ batch_size=batch_size,
43
+ metrics=data.get("metrics", {}),
44
+ raw_output=proc.stdout,
45
+ )
46
+ except (subprocess.CalledProcessError, json.JSONDecodeError, subprocess.TimeoutExpired) as exc:
47
+ return BenchmarkResult(
48
+ benchmark=self.name, gpu_model="unknown", gpu_index=gpu_index,
49
+ precision=precision, batch_size=batch_size,
50
+ success=False, error=str(exc),
51
+ )
52
+
53
+ def build_slurm_script(self, gpu_index: int, precision: str, batch_size: int) -> str:
54
+ """Return shell commands for this benchmark (without SBATCH headers)."""
55
+ kernels = self.params.get("kernels", ["matmul"])
56
+ matrix_sizes = self.params.get("matrix_sizes", [1024])
57
+ iterations = self.params.get("iterations", 50)
58
+ return f"""export CUDA_VISIBLE_DEVICES={gpu_index}
59
+
60
+ python3 -m nvprobe.benchmarks._cuda.custom_kernels \\
61
+ --gpu 0 \\
62
+ --kernels {','.join(kernels)} \\
63
+ --sizes {','.join(str(s) for s in matrix_sizes)} \\
64
+ --iterations {iterations} \\
65
+ --precision {precision} \\
66
+ --batch-size {batch_size}
67
+ """
@@ -0,0 +1,65 @@
1
+ """HPCG (High Performance Conjugate Gradients) benchmark wrapper."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import subprocess
6
+ from typing import Any
7
+
8
+ from nvprobe.benchmarks.base import BaseBenchmark, BenchmarkResult
9
+
10
+
11
+ class HpcgBenchmark(BaseBenchmark):
12
+ """Wrapper around a pre-compiled HPCG binary (xhpcg) via Slurm."""
13
+
14
+ name = "hpcg"
15
+
16
+ def run_local(self, gpu_index: int, precision: str, batch_size: int) -> BenchmarkResult:
17
+ binary = self.params.get("binary", "xhpcg")
18
+ grid_sizes = self.params.get("grid_sizes", [128])
19
+
20
+ try:
21
+ proc = subprocess.run(
22
+ [binary, "--grid", str(grid_sizes[0])],
23
+ capture_output=True, text=True, timeout=3600, check=True,
24
+ env={"CUDA_VISIBLE_DEVICES": str(gpu_index)},
25
+ )
26
+ gflops = _parse_hpcg_output(proc.stdout)
27
+ return BenchmarkResult(
28
+ benchmark=self.name,
29
+ gpu_model="unknown",
30
+ gpu_index=gpu_index,
31
+ precision=precision,
32
+ batch_size=batch_size,
33
+ metrics={"gflops": gflops, "grid_size": grid_sizes[0]},
34
+ raw_output=proc.stdout,
35
+ )
36
+ except (subprocess.CalledProcessError, subprocess.TimeoutExpired) as exc:
37
+ return BenchmarkResult(
38
+ benchmark=self.name, gpu_model="unknown", gpu_index=gpu_index,
39
+ precision=precision, batch_size=batch_size,
40
+ success=False, error=str(exc),
41
+ )
42
+
43
+ def build_slurm_script(self, gpu_index: int, precision: str, batch_size: int) -> str:
44
+ """Return shell commands for this benchmark (without SBATCH headers)."""
45
+ binary = self.params.get("binary", "xhpcg")
46
+ grid_sizes = self.params.get("grid_sizes", [128])
47
+ return f"""export CUDA_VISIBLE_DEVICES={gpu_index}
48
+
49
+ for GS in {' '.join(str(s) for s in grid_sizes)}; do
50
+ {binary} --grid $GS
51
+ done
52
+ """
53
+
54
+
55
+ def _parse_hpcg_output(output: str) -> float:
56
+ """Extract GFLOPS from HPCG output."""
57
+ for line in output.splitlines():
58
+ if "gflops" in line.lower():
59
+ parts = line.split()
60
+ for part in parts:
61
+ try:
62
+ return float(part)
63
+ except ValueError:
64
+ continue
65
+ return 0.0
@@ -0,0 +1,73 @@
1
+ """HPL (High Performance Linpack) benchmark wrapper."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import subprocess
6
+ from typing import Any
7
+
8
+ from nvprobe.benchmarks.base import BaseBenchmark, BenchmarkResult
9
+
10
+
11
+ class HplBenchmark(BaseBenchmark):
12
+ """Wrapper around a pre-compiled HPL binary (xhpl) via Slurm."""
13
+
14
+ name = "hpl"
15
+
16
+ def run_local(self, gpu_index: int, precision: str, batch_size: int) -> BenchmarkResult:
17
+ binary = self.params.get("binary", "xhpl")
18
+ problem_sizes = self.params.get("problem_sizes", [2048])
19
+
20
+ try:
21
+ proc = subprocess.run(
22
+ [binary, "--problem-size", str(problem_sizes[0])],
23
+ capture_output=True, text=True, timeout=3600, check=True,
24
+ env={"CUDA_VISIBLE_DEVICES": str(gpu_index)},
25
+ )
26
+ gflops = _parse_hpl_output(proc.stdout)
27
+ return BenchmarkResult(
28
+ benchmark=self.name,
29
+ gpu_model="unknown",
30
+ gpu_index=gpu_index,
31
+ precision=precision,
32
+ batch_size=batch_size,
33
+ metrics={"gflops": gflops, "problem_size": problem_sizes[0]},
34
+ raw_output=proc.stdout,
35
+ )
36
+ except (subprocess.CalledProcessError, subprocess.TimeoutExpired) as exc:
37
+ return BenchmarkResult(
38
+ benchmark=self.name, gpu_model="unknown", gpu_index=gpu_index,
39
+ precision=precision, batch_size=batch_size,
40
+ success=False, error=str(exc),
41
+ )
42
+
43
+ def build_slurm_script(self, gpu_index: int, precision: str, batch_size: int) -> str:
44
+ """Return shell commands for this benchmark (without SBATCH headers)."""
45
+ binary = self.params.get("binary", "xhpl")
46
+ problem_sizes = self.params.get("problem_sizes", [2048])
47
+ return f"""export CUDA_VISIBLE_DEVICES={gpu_index}
48
+
49
+ for PS in {' '.join(str(s) for s in problem_sizes)}; do
50
+ {binary} --problem-size $PS
51
+ done
52
+ """
53
+
54
+
55
+ def _parse_hpl_output(output: str) -> float:
56
+ """Extract GFLOPS from HPL output. Looks for 'GFLOPS' or 'HPL_outof' patterns."""
57
+ for line in output.splitlines():
58
+ line_lower = line.lower()
59
+ if "gflops" in line_lower:
60
+ parts = line.split()
61
+ for part in parts:
62
+ try:
63
+ return float(part)
64
+ except ValueError:
65
+ continue
66
+ if "hpl_outof" in line_lower or "g_flops" in line_lower:
67
+ parts = line.split("=")
68
+ if len(parts) > 1:
69
+ try:
70
+ return float(parts[1].strip().split()[0])
71
+ except (ValueError, IndexError):
72
+ continue
73
+ return 0.0