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 +3 -0
- nvprobe/benchmarks/__init__.py +24 -0
- nvprobe/benchmarks/_cuda/__init__.py +1 -0
- nvprobe/benchmarks/_cuda/bandwidth_test.py +111 -0
- nvprobe/benchmarks/_cuda/custom_kernels.py +174 -0
- nvprobe/benchmarks/_cuda/utils.py +29 -0
- nvprobe/benchmarks/bandwidth.py +61 -0
- nvprobe/benchmarks/base.py +51 -0
- nvprobe/benchmarks/custom.py +67 -0
- nvprobe/benchmarks/hpcg.py +65 -0
- nvprobe/benchmarks/hpl.py +73 -0
- nvprobe/benchmarks/mlperf.py +61 -0
- nvprobe/cli.py +134 -0
- nvprobe/config.py +100 -0
- nvprobe/db.py +207 -0
- nvprobe/reporter.py +552 -0
- nvprobe/runner.py +78 -0
- nvprobe/slurm.py +232 -0
- nvprobe-0.1.0.dist-info/METADATA +209 -0
- nvprobe-0.1.0.dist-info/RECORD +25 -0
- nvprobe-0.1.0.dist-info/WHEEL +5 -0
- nvprobe-0.1.0.dist-info/entry_points.txt +2 -0
- nvprobe-0.1.0.dist-info/licenses/LICENSE +199 -0
- nvprobe-0.1.0.dist-info/licenses/NOTICE +5 -0
- nvprobe-0.1.0.dist-info/top_level.txt +1 -0
nvprobe/__init__.py
ADDED
|
@@ -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
|