Skip to content

Commit d223dfb

Browse files
authored
[UR][Benchmarks] GROMACS/Grappa benchmarks added to the suite (#17934)
Gromacs package and Grappa benchmarking datasets added to the benchmark suite
1 parent e319bc3 commit d223dfb

File tree

5 files changed

+289
-4
lines changed

5 files changed

+289
-4
lines changed

devops/scripts/benchmarks/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
/html/data.js

devops/scripts/benchmarks/benches/base.py

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from dataclasses import dataclass
77
import os
88
import shutil
9+
import subprocess
910
from pathlib import Path
1011
from utils.result import BenchmarkMetadata, BenchmarkTag, Result
1112
from options import options
@@ -68,7 +69,9 @@ def get_adapter_full_path():
6869
False
6970
), f"could not find adapter file {adapter_path} (and in similar lib paths)"
7071

71-
def run_bench(self, command, env_vars, ld_library=[], add_sycl=True):
72+
def run_bench(
73+
self, command, env_vars, ld_library=[], add_sycl=True, use_stdout=True
74+
):
7275
env_vars = env_vars.copy()
7376
if options.ur is not None:
7477
env_vars.update(
@@ -80,13 +83,18 @@ def run_bench(self, command, env_vars, ld_library=[], add_sycl=True):
8083
ld_libraries = options.extra_ld_libraries.copy()
8184
ld_libraries.extend(ld_library)
8285

83-
return run(
86+
result = run(
8487
command=command,
8588
env_vars=env_vars,
8689
add_sycl=add_sycl,
8790
cwd=options.benchmark_cwd,
8891
ld_library=ld_libraries,
89-
).stdout.decode()
92+
)
93+
94+
if use_stdout:
95+
return result.stdout.decode()
96+
else:
97+
return result.stderr.decode()
9098

9199
def create_data_path(self, name, skip_data_dir=False):
92100
if skip_data_dir:
Lines changed: 268 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,268 @@
1+
# Copyright (C) 2025 Intel Corporation
2+
# Part of the Unified-Runtime Project, under the Apache License v2.0 with LLVM Exceptions.
3+
# See LICENSE.TXT
4+
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
5+
6+
import os
7+
import subprocess
8+
from pathlib import Path
9+
from .base import Suite, Benchmark
10+
from options import options
11+
from utils.utils import git_clone, download, run, create_build_path
12+
from utils.result import Result
13+
import re
14+
15+
16+
class GromacsBench(Suite):
17+
18+
def git_url(self):
19+
return "https://gitlab.com/gromacs/gromacs.git"
20+
21+
def git_tag(self):
22+
return "v2025.1"
23+
24+
def grappa_url(self):
25+
return "https://zenodo.org/record/11234002/files/grappa-1.5k-6.1M_rc0.9.tar.gz"
26+
27+
def grappa_file(self):
28+
return Path(os.path.basename(self.grappa_url()))
29+
30+
def __init__(self, directory):
31+
self.directory = Path(directory).resolve()
32+
model_path = str(self.directory / self.grappa_file()).replace(".tar.gz", "")
33+
self.grappa_dir = Path(model_path)
34+
build_path = create_build_path(self.directory, "gromacs-build")
35+
self.gromacs_build_path = Path(build_path)
36+
self.gromacs_src = self.directory / "gromacs-repo"
37+
38+
def name(self):
39+
return "Gromacs Bench"
40+
41+
def benchmarks(self) -> list[Benchmark]:
42+
return [
43+
GromacsBenchmark(self, "0006", "pme", "graphs"),
44+
GromacsBenchmark(self, "0006", "pme", "eager"),
45+
GromacsBenchmark(self, "0006", "rf", "graphs"),
46+
GromacsBenchmark(self, "0006", "rf", "eager"),
47+
# some people may need it
48+
# GromacsBenchmark(self, "0192", "pme", "eager"),
49+
# GromacsBenchmark(self, "0192", "rf", "eager"),
50+
]
51+
52+
def setup(self):
53+
self.gromacs_src = git_clone(
54+
self.directory,
55+
"gromacs-repo",
56+
self.git_url(),
57+
self.git_tag(),
58+
)
59+
60+
# TODO: Detect the GPU architecture and set the appropriate flags
61+
62+
# Build GROMACS
63+
run(
64+
[
65+
"cmake",
66+
f"-S {str(self.directory)}/gromacs-repo",
67+
f"-B {self.gromacs_build_path}",
68+
f"-DCMAKE_BUILD_TYPE=Release",
69+
f"-DCMAKE_CXX_COMPILER=clang++",
70+
f"-DCMAKE_C_COMPILER=clang",
71+
f"-DGMX_GPU=SYCL",
72+
f"-DGMX_SYCL_ENABLE_GRAPHS=ON",
73+
f"-DGMX_SYCL_ENABLE_EXPERIMENTAL_SUBMIT_API=ON",
74+
f"-DGMX_FFT_LIBRARY=MKL",
75+
f"-DGMX_GPU_FFT_LIBRARY=MKL",
76+
f"-DGMX_GPU_NB_CLUSTER_SIZE=8",
77+
f"-DGMX_GPU_NB_NUM_CLUSTER_PER_CELL_X=1",
78+
f"-DGMX_OPENMP=OFF",
79+
],
80+
add_sycl=True,
81+
)
82+
run(
83+
f"cmake --build {self.gromacs_build_path} -j {options.build_jobs}",
84+
add_sycl=True,
85+
)
86+
download(
87+
self.directory,
88+
self.grappa_url(),
89+
self.directory / self.grappa_file(),
90+
checksum="cc02be35ba85c8b044e47d097661dffa8bea57cdb3db8b5da5d01cdbc94fe6c8902652cfe05fb9da7f2af0698be283a2",
91+
untar=True,
92+
)
93+
94+
def teardown(self):
95+
pass
96+
97+
98+
class GromacsBenchmark(Benchmark):
99+
def __init__(self, suite, model, type, option):
100+
self.suite = suite
101+
self.model = model # The model name (e.g., "0001.5")
102+
self.type = type # The type of benchmark ("pme" or "rf")
103+
self.option = option # "graphs" or "eager"
104+
105+
self.gromacs_src = suite.gromacs_src
106+
self.grappa_dir = suite.grappa_dir
107+
self.gmx_path = suite.gromacs_build_path / "bin" / "gmx"
108+
109+
if self.type == "pme":
110+
self.extra_args = [
111+
"-pme",
112+
"gpu",
113+
"-pmefft",
114+
"gpu",
115+
"-notunepme",
116+
]
117+
else:
118+
self.extra_args = []
119+
120+
def name(self):
121+
return f"gromacs-{self.model}-{self.type}-{self.option}"
122+
123+
def setup(self):
124+
if self.type != "rf" and self.type != "pme":
125+
raise ValueError(f"Unknown benchmark type: {self.type}")
126+
127+
if self.option != "graphs" and self.option != "eager":
128+
raise ValueError(f"Unknown option: {self.option}")
129+
130+
if not self.gmx_path.exists():
131+
raise FileNotFoundError(f"gmx executable not found at {self.gmx_path}")
132+
133+
model_dir = self.grappa_dir / self.model
134+
135+
if not model_dir.exists():
136+
raise FileNotFoundError(f"Model directory not found: {model_dir}")
137+
138+
cmd_list = [
139+
str(self.gmx_path),
140+
"grompp",
141+
"-f",
142+
f"{str(self.grappa_dir)}/{self.type}.mdp",
143+
"-c",
144+
str(model_dir / "conf.gro"),
145+
"-p",
146+
str(model_dir / "topol.top"),
147+
"-o",
148+
f"{str(model_dir)}/{self.type}.tpr",
149+
]
150+
151+
# Generate configuration files
152+
self.conf_result = run(
153+
cmd_list,
154+
add_sycl=True,
155+
)
156+
157+
def run(self, env_vars):
158+
model_dir = self.grappa_dir / self.model
159+
160+
env_vars.update({"SYCL_CACHE_PERSISTENT": "1"})
161+
162+
if self.option == "graphs":
163+
env_vars.update({"GMX_CUDA_GRAPH": "1"})
164+
165+
# Run benchmark
166+
command = [
167+
str(self.gmx_path),
168+
"mdrun",
169+
"-s",
170+
f"{str(model_dir)}/{self.type}.tpr",
171+
"-nb",
172+
"gpu",
173+
"-update",
174+
"gpu",
175+
"-bonded",
176+
"gpu",
177+
"-ntmpi",
178+
"1",
179+
"-ntomp",
180+
"1",
181+
"-nobackup",
182+
"-noconfout",
183+
"-nstlist",
184+
"100",
185+
"-pin",
186+
"on",
187+
"-resethway",
188+
] + self.extra_args
189+
190+
mdrun_output = self.run_bench(
191+
command,
192+
env_vars,
193+
add_sycl=True,
194+
use_stdout=False,
195+
)
196+
197+
if self.type == "pme" and not self._validate_correctness(
198+
options.benchmark_cwd + "/md.log"
199+
):
200+
raise ValueError(
201+
f"Validation failed: Conserved energy drift exceeds threshold in {model_dir / 'md.log'}"
202+
)
203+
204+
time = self._extract_execution_time(mdrun_output)
205+
206+
if options.verbose:
207+
print(f"[{self.name()}] Time: {time:.3f} seconds")
208+
209+
return [
210+
Result(
211+
label=f"{self.name()}",
212+
value=time,
213+
unit="s",
214+
command=command,
215+
env=env_vars,
216+
stdout=mdrun_output,
217+
git_url=self.suite.git_url(),
218+
git_hash=self.suite.git_tag(),
219+
)
220+
]
221+
222+
def _extract_execution_time(self, log_content):
223+
# Look for the line containing "Time:"
224+
# and extract the first numeric value after it
225+
time_lines = [line for line in log_content.splitlines() if "Time:" in line]
226+
227+
if len(time_lines) != 1:
228+
raise ValueError(
229+
f"Expected exactly 1 line containing 'Time:' in the log content, "
230+
f"but found {len(time_lines)}."
231+
)
232+
233+
for part in time_lines[0].split():
234+
if part.replace(".", "", 1).isdigit():
235+
return float(part)
236+
237+
raise ValueError(f"No numeric value found in the 'Time:' line.")
238+
239+
def _validate_correctness(self, log_file):
240+
threshold = 1e-3 # Define an acceptable energy drift threshold
241+
242+
log_file = Path(log_file)
243+
if not log_file.exists():
244+
raise FileNotFoundError(f"Log file not found: {log_file}")
245+
246+
sci_pattern = r"([-+]?\d*\.\d+(?:e[-+]?\d+)?)"
247+
with open(log_file, "r") as file:
248+
for line in file:
249+
if "Conserved energy drift:" in line:
250+
match = re.search(sci_pattern, line, re.IGNORECASE)
251+
if match:
252+
try:
253+
drift_value = float(match.group(1))
254+
return abs(drift_value) <= threshold
255+
except ValueError:
256+
print(
257+
f"Parsed drift value: {drift_value} exceeds threshold"
258+
)
259+
return False
260+
else:
261+
raise ValueError(
262+
f"No valid numerical value found in line: {line}"
263+
)
264+
265+
raise ValueError(f"Conserved Energy Drift not found in log file: {log_file}")
266+
267+
def teardown(self):
268+
pass

devops/scripts/benchmarks/main.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
77

88
from benches.compute import *
9+
from benches.gromacs import GromacsBench
910
from benches.velocity import VelocityBench
1011
from benches.syclbench import *
1112
from benches.llamacpp import *
@@ -166,6 +167,7 @@ def main(directory, additional_env_vars, save_name, compare_names, filter):
166167
SyclBench(directory),
167168
LlamaCppBench(directory),
168169
UMFSuite(directory),
170+
GromacsBench(directory),
169171
TestSuite(),
170172
]
171173

@@ -198,6 +200,7 @@ def main(directory, additional_env_vars, save_name, compare_names, filter):
198200
except Exception as e:
199201
failures[s.name()] = f"Suite setup failure: {e}"
200202
print(f"{type(s).__name__} setup failed. Benchmarks won't be added.")
203+
print(f"failed: {e}")
201204
else:
202205
print(f"{type(s).__name__} setup complete.")
203206
benchmarks += suite_benchmarks
@@ -250,7 +253,7 @@ def main(directory, additional_env_vars, save_name, compare_names, filter):
250253
print(f"tearing down {benchmark.name()}... ", flush=True)
251254
benchmark.teardown()
252255
if options.verbose:
253-
print("{benchmark.name()} teardown complete.")
256+
print(f"{benchmark.name()} teardown complete.")
254257

255258
this_name = options.current_run_name
256259
chart_data = {}

devops/scripts/benchmarks/presets.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
presets: dict[str, list[str]] = {
99
"Full": [
1010
"Compute Benchmarks",
11+
"Gromacs Bench",
1112
"llama.cpp bench",
1213
"SYCL-Bench",
1314
"Velocity Bench",
@@ -24,12 +25,16 @@
2425
],
2526
"Normal": [
2627
"Compute Benchmarks",
28+
"Gromacs Bench",
2729
"llama.cpp bench",
2830
"Velocity Bench",
2931
],
3032
"Test": [
3133
"Test Suite",
3234
],
35+
"Gromacs": [
36+
"Gromacs Bench",
37+
],
3338
}
3439

3540

0 commit comments

Comments
 (0)