Skip to content

Commit 84cad07

Browse files
authored
Merge pull request #2322 from oneapi-src/llamacpp
add llama.cpp benchmark
2 parents 2e639b5 + 0d106ee commit 84cad07

File tree

5 files changed

+235
-19
lines changed

5 files changed

+235
-19
lines changed

scripts/benchmarks/benches/base.py

Lines changed: 9 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
from pathlib import Path
99
from .result import Result
1010
from .options import options
11-
from utils.utils import run
11+
from utils.utils import download, run
1212
import urllib.request
1313
import tarfile
1414

@@ -26,7 +26,7 @@ def get_adapter_full_path():
2626
assert False, \
2727
f"could not find adapter file {adapter_path} (and in similar lib paths)"
2828

29-
def run_bench(self, command, env_vars):
29+
def run_bench(self, command, env_vars, ld_library=[]):
3030
env_vars_with_forced_adapter = env_vars.copy()
3131
if options.ur is not None:
3232
env_vars_with_forced_adapter.update(
@@ -36,7 +36,8 @@ def run_bench(self, command, env_vars):
3636
command=command,
3737
env_vars=env_vars_with_forced_adapter,
3838
add_sycl=True,
39-
cwd=options.benchmark_cwd
39+
cwd=options.benchmark_cwd,
40+
ld_library=ld_library
4041
).stdout.decode()
4142

4243
def create_data_path(self, name):
@@ -49,17 +50,9 @@ def create_data_path(self, name):
4950

5051
return data_path
5152

52-
def download_untar(self, name, url, file):
53+
def download(self, name, url, file, untar = False):
5354
self.data_path = self.create_data_path(name)
54-
data_file = os.path.join(self.data_path, file)
55-
if not Path(data_file).exists():
56-
print(f"{data_file} does not exist, downloading")
57-
urllib.request.urlretrieve(url, data_file)
58-
file = tarfile.open(data_file)
59-
file.extractall(self.data_path)
60-
file.close()
61-
else:
62-
print(f"{data_file} exists, skipping...")
55+
return download(self.data_path, url, file, True)
6356

6457
def name(self):
6558
raise NotImplementedError()
@@ -79,6 +72,9 @@ def run(self, env_vars) -> list[Result]:
7972
def teardown(self):
8073
raise NotImplementedError()
8174

75+
def ignore_iterations(self):
76+
return False
77+
8278
class Suite:
8379
def benchmarks(self) -> list[Benchmark]:
8480
raise NotImplementedError()
Lines changed: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
1+
# Copyright (C) 2024 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 csv
7+
import io
8+
from pathlib import Path
9+
import re
10+
import shutil
11+
from utils.utils import download, git_clone
12+
from .base import Benchmark, Suite
13+
from .result import Result
14+
from utils.utils import run, create_build_path
15+
from .options import options
16+
import os
17+
18+
class OneAPI:
19+
# random unique number for benchmark oneAPI installation
20+
ONEAPI_BENCHMARK_INSTANCE_ID = 98765
21+
def __init__(self, directory):
22+
self.oneapi_dir = os.path.join(directory, 'oneapi')
23+
Path(self.oneapi_dir).mkdir(parents=True, exist_ok=True)
24+
# delete if some option is set?
25+
26+
# can we just hardcode these links?
27+
self.install_package('dnnl', 'https://registrationcenter-download.intel.com/akdlm/IRC_NAS/87e117ab-039b-437d-9c80-dcd5c9e675d5/intel-onednn-2025.0.0.862_offline.sh')
28+
self.install_package('mkl', 'https://registrationcenter-download.intel.com/akdlm/IRC_NAS/79153e0f-74d7-45af-b8c2-258941adf58a/intel-onemkl-2025.0.0.940_offline.sh')
29+
return
30+
31+
def install_package(self, name, url):
32+
package_path = os.path.join(self.oneapi_dir, name)
33+
if Path(package_path).exists():
34+
print(f"{package_path} exists, skipping installing oneAPI package {name}...")
35+
return
36+
37+
package = download(self.oneapi_dir, url, f'package_{name}.sh')
38+
try:
39+
print(f"installing f{name}")
40+
run(f"sh {package} -a -s --eula accept --install-dir {self.oneapi_dir} --instance f{self.ONEAPI_BENCHMARK_INSTANCE_ID}")
41+
except:
42+
print("oneAPI installation likely exists already")
43+
return
44+
print(f"f{name} installation complete")
45+
46+
def package_dir(self, package, dir):
47+
return os.path.join(self.oneapi_dir, package, 'latest', dir)
48+
49+
def package_cmake(self, package):
50+
package_lib = self.package_dir(package, 'lib')
51+
return os.path.join(package_lib, 'cmake', package)
52+
53+
def mkl_lib(self):
54+
return self.package_dir('mkl', 'lib')
55+
56+
def mkl_include(self):
57+
return self.package_dir('mkl', 'include')
58+
59+
def mkl_cmake(self):
60+
return self.package_cmake('mkl')
61+
62+
def dnn_lib(self):
63+
return self.package_dir('dnnl', 'lib')
64+
65+
def dnn_include(self):
66+
return self.package_dir('dnnl', 'include')
67+
68+
def dnn_cmake(self):
69+
return self.package_cmake('dnnl')
70+
71+
def tbb_lib(self):
72+
return self.package_dir('tbb', 'lib')
73+
74+
def tbb_cmake(self):
75+
return self.package_cmake('tbb')
76+
77+
def compiler_lib(self):
78+
return self.package_dir('compiler', 'lib')
79+
80+
def ld_libraries(self):
81+
return [
82+
self.compiler_lib(),
83+
self.mkl_lib(),
84+
self.tbb_lib(),
85+
self.dnn_lib()
86+
]
87+
88+
class LlamaCppBench(Suite):
89+
def __init__(self, directory):
90+
if options.sycl is None:
91+
return
92+
93+
self.directory = directory
94+
95+
def setup(self):
96+
if options.sycl is None:
97+
return
98+
99+
repo_path = git_clone(self.directory, "llamacpp-repo", "https://github.com/ggerganov/llama.cpp", "1ee9eea094fe5846c7d8d770aa7caa749d246b23")
100+
101+
self.models_dir = os.path.join(self.directory, 'models')
102+
Path(self.models_dir).mkdir(parents=True, exist_ok=True)
103+
104+
self.model = download(self.models_dir, "https://huggingface.co/microsoft/Phi-3-mini-4k-instruct-gguf/resolve/main/Phi-3-mini-4k-instruct-q4.gguf", "Phi-3-mini-4k-instruct-q4.gguf")
105+
106+
self.oneapi = OneAPI(self.directory)
107+
108+
self.build_path = create_build_path(self.directory, 'llamacpp-build')
109+
110+
configure_command = [
111+
"cmake",
112+
f"-B {self.build_path}",
113+
f"-S {repo_path}",
114+
f"-DCMAKE_BUILD_TYPE=Release",
115+
f"-DGGML_SYCL=ON",
116+
f"-DCMAKE_C_COMPILER=clang",
117+
f"-DCMAKE_CXX_COMPILER=clang++",
118+
f"-DDNNL_DIR={self.oneapi.dnn_cmake()}",
119+
f"-DTBB_DIR={self.oneapi.tbb_cmake()}",
120+
f'-DCMAKE_CXX_FLAGS=-I"{self.oneapi.mkl_include()}"',
121+
f'-DCMAKE_SHARED_LINKER_FLAGS=-L{self.oneapi.compiler_lib()} -L{self.oneapi.mkl_lib()}'
122+
]
123+
print(f"{self.__class__.__name__}: Run {configure_command}")
124+
run(configure_command, add_sycl=True)
125+
print(f"{self.__class__.__name__}: Run cmake --build {self.build_path} -j")
126+
run(f"cmake --build {self.build_path} -j", add_sycl=True, ld_library=self.oneapi.ld_libraries())
127+
128+
def benchmarks(self) -> list[Benchmark]:
129+
if options.sycl is None:
130+
return []
131+
132+
return [
133+
LlamaBench(self)
134+
]
135+
136+
class LlamaBench(Benchmark):
137+
def __init__(self, bench):
138+
self.bench = bench
139+
super().__init__(bench.directory)
140+
141+
def unit(self):
142+
return "token/s"
143+
144+
def setup(self):
145+
self.benchmark_bin = os.path.join(self.bench.build_path, 'bin', 'llama-bench')
146+
147+
def name(self):
148+
return f"llama.cpp"
149+
150+
def lower_is_better(self):
151+
return False
152+
153+
def ignore_iterations(self):
154+
return True
155+
156+
def run(self, env_vars) -> list[Result]:
157+
command = [
158+
f"{self.benchmark_bin}",
159+
"--output", "csv",
160+
"-n", "128",
161+
"-p", "512",
162+
"-b", "128,256,512",
163+
"--numa", "isolate",
164+
"-t", "56", # TODO: use only as many threads as numa node 0 has cpus
165+
"--model", f"{self.bench.model}",
166+
]
167+
168+
result = self.run_bench(command, env_vars, ld_library=self.bench.oneapi.ld_libraries())
169+
parsed = self.parse_output(result)
170+
results = []
171+
for r in parsed:
172+
(extra_label, mean) = r
173+
label = f"{self.name()} {extra_label}"
174+
results.append(Result(label=label, value=mean, command=command, env=env_vars, stdout=result))
175+
return results
176+
177+
def parse_output(self, output):
178+
csv_file = io.StringIO(output)
179+
reader = csv.DictReader(csv_file)
180+
181+
results = []
182+
for row in reader:
183+
try:
184+
n_batch = row["n_batch"]
185+
avg_ts = float(row["avg_ts"])
186+
n_prompt = int(row["n_prompt"])
187+
label = "Prompt Processing" if n_prompt != 0 else "Text Generation"
188+
label += f" Batched {n_batch}"
189+
results.append((label, avg_ts))
190+
except KeyError as e:
191+
raise ValueError(f"Error parsing output: {e}")
192+
193+
return results
194+
195+
def teardown(self):
196+
return

scripts/benchmarks/benches/velocity.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@ def __init__(self, vb: VelocityBench):
140140
super().__init__("sobel_filter", "sobel_filter", vb)
141141

142142
def download_deps(self):
143-
self.download_untar("sobel_filter", "https://github.com/oneapi-src/Velocity-Bench/raw/main/sobel_filter/res/sobel_filter_data.tgz?download=", "sobel_filter_data.tgz")
143+
self.download("sobel_filter", "https://github.com/oneapi-src/Velocity-Bench/raw/main/sobel_filter/res/sobel_filter_data.tgz?download=", "sobel_filter_data.tgz", untar=True)
144144
return
145145

146146
def name(self):
@@ -203,7 +203,7 @@ def __init__(self, vb: VelocityBench):
203203
super().__init__("easywave", "easyWave_sycl", vb)
204204

205205
def download_deps(self):
206-
self.download_untar("easywave", "https://git.gfz-potsdam.de/id2/geoperil/easyWave/-/raw/master/data/examples.tar.gz", "examples.tar.gz")
206+
self.download("easywave", "https://git.gfz-potsdam.de/id2/geoperil/easyWave/-/raw/master/data/examples.tar.gz", "examples.tar.gz", untar=True)
207207

208208
def name(self):
209209
return "Velocity-Bench Easywave"

scripts/benchmarks/main.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from benches.compute import *
99
from benches.velocity import VelocityBench
1010
from benches.syclbench import *
11+
from benches.llamacpp import *
1112
from benches.test import TestSuite
1213
from benches.options import Compare, options
1314
from output_markdown import generate_markdown
@@ -27,7 +28,8 @@ def main(directory, additional_env_vars, save_name, compare_names, filter):
2728
suites = [
2829
ComputeBench(directory),
2930
VelocityBench(directory),
30-
SyclBench(directory)
31+
SyclBench(directory),
32+
LlamaCppBench(directory),
3133
#TestSuite()
3234
] if not options.dry_run else []
3335

@@ -64,7 +66,8 @@ def main(directory, additional_env_vars, save_name, compare_names, filter):
6466
try:
6567
merged_env_vars = {**additional_env_vars}
6668
iteration_results = []
67-
for iter in range(options.iterations):
69+
iterations = options.iterations if not benchmark.ignore_iterations() else 1
70+
for iter in range(iterations):
6871
print(f"running {benchmark.name()}, iteration {iter}... ", end='', flush=True)
6972
bench_results = benchmark.run(merged_env_vars)
7073
if bench_results is not None:

scripts/benchmarks/utils/utils.py

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,24 +5,32 @@
55

66
import os
77
import shutil
8-
import subprocess # nosec B404
8+
import subprocess
9+
10+
import tarfile
11+
import urllib # nosec B404
912
from benches.options import options
1013
from pathlib import Path
1114

12-
def run(command, env_vars={}, cwd=None, add_sycl=False):
15+
def run(command, env_vars={}, cwd=None, add_sycl=False, ld_library=[]):
1316
try:
1417
if isinstance(command, str):
1518
command = command.split()
1619

1720
env = os.environ.copy()
1821

22+
for ldlib in ld_library:
23+
env['LD_LIBRARY_PATH'] = ldlib + os.pathsep + env.get('LD_LIBRARY_PATH', '')
24+
25+
# order is important, we want provided sycl rt libraries to be first
1926
if add_sycl:
2027
sycl_bin_path = os.path.join(options.sycl, 'bin')
2128
env['PATH'] = sycl_bin_path + os.pathsep + env.get('PATH', '')
2229
sycl_lib_path = os.path.join(options.sycl, 'lib')
2330
env['LD_LIBRARY_PATH'] = sycl_lib_path + os.pathsep + env.get('LD_LIBRARY_PATH', '')
2431

2532
env.update(env_vars)
33+
2634
result = subprocess.run(command, cwd=cwd, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=env, timeout=options.timeout) # nosec B603
2735

2836
if options.verbose:
@@ -88,3 +96,16 @@ def create_build_path(directory, name):
8896
Path(build_path).mkdir(parents=True, exist_ok=True)
8997

9098
return build_path
99+
100+
def download(dir, url, file, untar = False):
101+
data_file = os.path.join(dir, file)
102+
if not Path(data_file).exists():
103+
print(f"{data_file} does not exist, downloading")
104+
urllib.request.urlretrieve(url, data_file)
105+
if untar:
106+
file = tarfile.open(data_file)
107+
file.extractall(dir)
108+
file.close()
109+
else:
110+
print(f"{data_file} exists, skipping...")
111+
return data_file

0 commit comments

Comments
 (0)