kernel-tuner 0.3.1__tar.gz → 0.3.2__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.
- {kernel_tuner-0.3.1 → kernel_tuner-0.3.2}/PKG-INFO +11 -11
- {kernel_tuner-0.3.1 → kernel_tuner-0.3.2}/README.rst +7 -7
- {kernel_tuner-0.3.1 → kernel_tuner-0.3.2}/kernel_tuner/c.py +5 -3
- {kernel_tuner-0.3.1 → kernel_tuner-0.3.2}/kernel_tuner/core.py +4 -3
- {kernel_tuner-0.3.1 → kernel_tuner-0.3.2}/kernel_tuner/interface.py +5 -3
- {kernel_tuner-0.3.1 → kernel_tuner-0.3.2}/kernel_tuner/runners/sequential.py +18 -5
- {kernel_tuner-0.3.1 → kernel_tuner-0.3.2}/kernel_tuner/strategies/minimize.py +14 -5
- {kernel_tuner-0.3.1 → kernel_tuner-0.3.2}/kernel_tuner/util.py +98 -13
- {kernel_tuner-0.3.1 → kernel_tuner-0.3.2}/kernel_tuner/wrappers.py +1 -1
- {kernel_tuner-0.3.1 → kernel_tuner-0.3.2}/kernel_tuner.egg-info/PKG-INFO +11 -11
- {kernel_tuner-0.3.1 → kernel_tuner-0.3.2}/kernel_tuner.egg-info/SOURCES.txt +0 -1
- {kernel_tuner-0.3.1 → kernel_tuner-0.3.2}/setup.py +1 -1
- {kernel_tuner-0.3.1 → kernel_tuner-0.3.2}/test/test_c_functions.py +2 -2
- {kernel_tuner-0.3.1 → kernel_tuner-0.3.2}/test/test_core.py +1 -1
- {kernel_tuner-0.3.1 → kernel_tuner-0.3.2}/test/test_cuda_functions.py +3 -23
- {kernel_tuner-0.3.1 → kernel_tuner-0.3.2}/test/test_cuda_mocked.py +1 -2
- {kernel_tuner-0.3.1 → kernel_tuner-0.3.2}/test/test_interface.py +9 -9
- {kernel_tuner-0.3.1 → kernel_tuner-0.3.2}/test/test_minimize.py +1 -0
- {kernel_tuner-0.3.1 → kernel_tuner-0.3.2}/test/test_util_functions.py +39 -3
- kernel_tuner-0.3.1/kernel_tuner/cupy_backend.py +0 -331
- {kernel_tuner-0.3.1 → kernel_tuner-0.3.2}/LICENSE +0 -0
- {kernel_tuner-0.3.1 → kernel_tuner-0.3.2}/MANIFEST.in +0 -0
- {kernel_tuner-0.3.1 → kernel_tuner-0.3.2}/kernel_tuner/__init__.py +0 -0
- {kernel_tuner-0.3.1 → kernel_tuner-0.3.2}/kernel_tuner/cuda.py +0 -0
- {kernel_tuner-0.3.1 → kernel_tuner-0.3.2}/kernel_tuner/kernelbuilder.py +0 -0
- {kernel_tuner-0.3.1 → kernel_tuner-0.3.2}/kernel_tuner/nvml.py +0 -0
- {kernel_tuner-0.3.1 → kernel_tuner-0.3.2}/kernel_tuner/opencl.py +0 -0
- {kernel_tuner-0.3.1 → kernel_tuner-0.3.2}/kernel_tuner/runners/__init__.py +0 -0
- {kernel_tuner-0.3.1 → kernel_tuner-0.3.2}/kernel_tuner/strategies/__init__.py +0 -0
- {kernel_tuner-0.3.1 → kernel_tuner-0.3.2}/kernel_tuner/strategies/basinhopping.py +0 -0
- {kernel_tuner-0.3.1 → kernel_tuner-0.3.2}/kernel_tuner/strategies/bayes_opt.py +0 -0
- {kernel_tuner-0.3.1 → kernel_tuner-0.3.2}/kernel_tuner/strategies/brute_force.py +0 -0
- {kernel_tuner-0.3.1 → kernel_tuner-0.3.2}/kernel_tuner/strategies/diff_evo.py +0 -0
- {kernel_tuner-0.3.1 → kernel_tuner-0.3.2}/kernel_tuner/strategies/firefly_algorithm.py +0 -0
- {kernel_tuner-0.3.1 → kernel_tuner-0.3.2}/kernel_tuner/strategies/genetic_algorithm.py +0 -0
- {kernel_tuner-0.3.1 → kernel_tuner-0.3.2}/kernel_tuner/strategies/pso.py +0 -0
- {kernel_tuner-0.3.1 → kernel_tuner-0.3.2}/kernel_tuner/strategies/random_sample.py +0 -0
- {kernel_tuner-0.3.1 → kernel_tuner-0.3.2}/kernel_tuner/strategies/simulated_annealing.py +0 -0
- {kernel_tuner-0.3.1 → kernel_tuner-0.3.2}/kernel_tuner.egg-info/dependency_links.txt +0 -0
- {kernel_tuner-0.3.1 → kernel_tuner-0.3.2}/kernel_tuner.egg-info/requires.txt +0 -0
- {kernel_tuner-0.3.1 → kernel_tuner-0.3.2}/kernel_tuner.egg-info/top_level.txt +0 -0
- {kernel_tuner-0.3.1 → kernel_tuner-0.3.2}/setup.cfg +0 -0
- {kernel_tuner-0.3.1 → kernel_tuner-0.3.2}/test/test_opencl_functions.py +0 -0
- {kernel_tuner-0.3.1 → kernel_tuner-0.3.2}/test/test_runners.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: kernel_tuner
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.2
|
|
4
4
|
Summary: An easy to use CUDA/OpenCL kernel tuner in Python
|
|
5
5
|
Home-page: http://benvanwerkhoven.github.io/kernel_tuner/
|
|
6
6
|
Author: Ben van Werkhoven
|
|
@@ -9,7 +9,7 @@ License: Apache 2.0
|
|
|
9
9
|
Description: Kernel Tuner
|
|
10
10
|
============
|
|
11
11
|
|
|
12
|
-
|Build Status| |CodeCov Badge| |PyPi Badge|
|
|
12
|
+
|Build Status| |CodeCov Badge| |PyPi Badge| |Zenodo Badge| |SonarCloud Badge| |FairSoftware Badge|
|
|
13
13
|
|
|
14
14
|
Kernel Tuner simplifies the software development of optimized and auto-tuned GPU programs, by enabling Python-based unit testing of GPU code and making it easy to develop scripts for auto-tuning GPU kernels. This also means no extensive changes and no new dependencies are required in the kernel code. The kernels can still be compiled and used as normal from any host programming language.
|
|
15
15
|
|
|
@@ -178,12 +178,12 @@ Description: Kernel Tuner
|
|
|
178
178
|
:target: https://codecov.io/gh/benvanwerkhoven/kernel_tuner
|
|
179
179
|
.. |PyPi Badge| image:: https://img.shields.io/pypi/v/kernel_tuner.svg?colorB=blue
|
|
180
180
|
:target: https://pypi.python.org/pypi/kernel_tuner/
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
181
|
+
.. |Zenodo Badge| image:: https://zenodo.org/badge/DOI/10.5281/zenodo.1220113.svg
|
|
182
|
+
:target: https://doi.org/10.5281/zenodo.1220113
|
|
183
|
+
.. |SonarCloud Badge| image:: https://sonarcloud.io/api/project_badges/measure?project=benvanwerkhoven_kernel_tuner&metric=alert_status
|
|
184
|
+
:target: https://sonarcloud.io/dashboard?id=benvanwerkhoven_kernel_tuner
|
|
185
|
+
.. |FairSoftware Badge| image:: https://img.shields.io/badge/fair--software.eu-%E2%97%8F%20%20%E2%97%8F%20%20%E2%97%8F%20%20%E2%97%8F%20%20%E2%97%8F-green
|
|
186
|
+
:target: https://fair-software.eu
|
|
187
187
|
|
|
188
188
|
Keywords: auto-tuning gpu computing pycuda cuda pyopencl opencl
|
|
189
189
|
Platform: UNKNOWN
|
|
@@ -201,9 +201,9 @@ Classifier: Topic :: Scientific/Engineering
|
|
|
201
201
|
Classifier: Topic :: Software Development
|
|
202
202
|
Classifier: Topic :: System :: Distributed Computing
|
|
203
203
|
Classifier: Development Status :: 4 - Beta
|
|
204
|
-
Provides-Extra:
|
|
204
|
+
Provides-Extra: doc
|
|
205
205
|
Provides-Extra: cuda
|
|
206
|
-
Provides-Extra: tutorial
|
|
207
206
|
Provides-Extra: opencl
|
|
208
207
|
Provides-Extra: cuda_opencl
|
|
209
|
-
Provides-Extra:
|
|
208
|
+
Provides-Extra: tutorial
|
|
209
|
+
Provides-Extra: dev
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
Kernel Tuner
|
|
2
2
|
============
|
|
3
3
|
|
|
4
|
-
|Build Status| |CodeCov Badge| |PyPi Badge|
|
|
4
|
+
|Build Status| |CodeCov Badge| |PyPi Badge| |Zenodo Badge| |SonarCloud Badge| |FairSoftware Badge|
|
|
5
5
|
|
|
6
6
|
Kernel Tuner simplifies the software development of optimized and auto-tuned GPU programs, by enabling Python-based unit testing of GPU code and making it easy to develop scripts for auto-tuning GPU kernels. This also means no extensive changes and no new dependencies are required in the kernel code. The kernels can still be compiled and used as normal from any host programming language.
|
|
7
7
|
|
|
@@ -170,9 +170,9 @@ Cedric Nugteren. CLTune is a C++ library for kernel tuning.
|
|
|
170
170
|
:target: https://codecov.io/gh/benvanwerkhoven/kernel_tuner
|
|
171
171
|
.. |PyPi Badge| image:: https://img.shields.io/pypi/v/kernel_tuner.svg?colorB=blue
|
|
172
172
|
:target: https://pypi.python.org/pypi/kernel_tuner/
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
173
|
+
.. |Zenodo Badge| image:: https://zenodo.org/badge/DOI/10.5281/zenodo.1220113.svg
|
|
174
|
+
:target: https://doi.org/10.5281/zenodo.1220113
|
|
175
|
+
.. |SonarCloud Badge| image:: https://sonarcloud.io/api/project_badges/measure?project=benvanwerkhoven_kernel_tuner&metric=alert_status
|
|
176
|
+
:target: https://sonarcloud.io/dashboard?id=benvanwerkhoven_kernel_tuner
|
|
177
|
+
.. |FairSoftware Badge| image:: https://img.shields.io/badge/fair--software.eu-%E2%97%8F%20%20%E2%97%8F%20%20%E2%97%8F%20%20%E2%97%8F%20%20%E2%97%8F-green
|
|
178
|
+
:target: https://fair-software.eu
|
|
@@ -90,19 +90,21 @@ class CFunctions(object):
|
|
|
90
90
|
if not isinstance(arg, (numpy.ndarray, numpy.number)):
|
|
91
91
|
raise TypeError("Argument is not numpy ndarray or numpy scalar %s" % type(arg))
|
|
92
92
|
dtype_str = str(arg.dtype)
|
|
93
|
-
data = arg.copy()
|
|
93
|
+
#data = arg.copy()
|
|
94
94
|
if isinstance(arg, numpy.ndarray):
|
|
95
95
|
if dtype_str in dtype_map.keys():
|
|
96
96
|
# In numpy <= 1.15, ndarray.ctypes.data_as does not itself keep a reference
|
|
97
97
|
# to its underlying array, so we need to store a reference to arg.copy()
|
|
98
98
|
# in the Argument object manually to avoid it being deleted.
|
|
99
99
|
# (This changed in numpy > 1.15.)
|
|
100
|
-
data_ctypes = data.ctypes.data_as(C.POINTER(dtype_map[dtype_str]))
|
|
100
|
+
#data_ctypes = data.ctypes.data_as(C.POINTER(dtype_map[dtype_str]))
|
|
101
|
+
data_ctypes = arg.ctypes.data_as(C.POINTER(dtype_map[dtype_str]))
|
|
101
102
|
else:
|
|
102
103
|
raise TypeError("unknown dtype for ndarray")
|
|
103
104
|
elif isinstance(arg, numpy.generic):
|
|
104
105
|
data_ctypes = dtype_map[dtype_str](arg)
|
|
105
|
-
ctype_args[i] = Argument(numpy=data, ctypes=data_ctypes)
|
|
106
|
+
#ctype_args[i] = Argument(numpy=data, ctypes=data_ctypes)
|
|
107
|
+
ctype_args[i] = Argument(numpy=arg, ctypes=data_ctypes)
|
|
106
108
|
return ctype_args
|
|
107
109
|
|
|
108
110
|
def compile(self, kernel_instance):
|
|
@@ -115,7 +115,7 @@ class KernelSource(object):
|
|
|
115
115
|
|
|
116
116
|
ks = self.get_kernel_string(i, params)
|
|
117
117
|
# add preprocessor statements
|
|
118
|
-
n, ks = util.prepare_kernel_string(kernel_name, ks, params, grid, threads, block_size_names)
|
|
118
|
+
n, ks = util.prepare_kernel_string(kernel_name, ks, params, grid, threads, block_size_names, self.lang)
|
|
119
119
|
|
|
120
120
|
if i == 0:
|
|
121
121
|
# primary kernel source
|
|
@@ -175,7 +175,7 @@ class KernelSource(object):
|
|
|
175
175
|
class DeviceInterface(object):
|
|
176
176
|
"""Class that offers a High-Level Device Interface to the rest of the Kernel Tuner"""
|
|
177
177
|
|
|
178
|
-
def __init__(self, kernel_source, device=0, platform=0,
|
|
178
|
+
def __init__(self, kernel_source, device=0, platform=0, quiet=False, compiler=None, compiler_options=None, iterations=7):
|
|
179
179
|
""" Instantiate the DeviceInterface, based on language in kernel source
|
|
180
180
|
|
|
181
181
|
:param kernel_source The kernel sources
|
|
@@ -349,7 +349,8 @@ class DeviceInterface(object):
|
|
|
349
349
|
#compiles may fail because certain kernel configurations use too
|
|
350
350
|
#much shared memory for example, the desired behavior is to simply
|
|
351
351
|
#skip over this configuration and try the next one
|
|
352
|
-
|
|
352
|
+
shared_mem_error_messages = ["uses too much shared data", "local memory limit exceeded"]
|
|
353
|
+
if any(msg in str(e) for msg in shared_mem_error_messages):
|
|
353
354
|
logging.debug('compile_kernel failed due to kernel using too much shared memory')
|
|
354
355
|
if verbose:
|
|
355
356
|
print("skipping config", instance.name, "reason: too much shared memory used")
|
|
@@ -318,7 +318,8 @@ _tuning_options = Options([
|
|
|
318
318
|
("cache", ("""filename for caching/logging benchmarked instances
|
|
319
319
|
filename uses suffix ".json"
|
|
320
320
|
if the file exists it is read and tuning continues from this file
|
|
321
|
-
""", "string"))
|
|
321
|
+
""", "string")),
|
|
322
|
+
("metrics", ("specifies user-defined metrics", "OrderedDict"))
|
|
322
323
|
])
|
|
323
324
|
|
|
324
325
|
_device_options = Options([
|
|
@@ -366,7 +367,7 @@ def tune_kernel(kernel_name, kernel_string, problem_size, arguments,
|
|
|
366
367
|
lang=None, device=0, platform=0, smem_args=None, cmem_args=None, texmem_args=None,
|
|
367
368
|
compiler=None, compiler_options=None, log=None,
|
|
368
369
|
iterations=7, block_size_names=None, quiet=False, strategy=None, strategy_options=None,
|
|
369
|
-
cache=None):
|
|
370
|
+
cache=None, metrics=None):
|
|
370
371
|
|
|
371
372
|
if log:
|
|
372
373
|
logging.basicConfig(filename=kernel_name + datetime.now().strftime('%Y%m%d-%H:%M:%S') + '.log', level=log)
|
|
@@ -450,7 +451,8 @@ def tune_kernel(kernel_name, kernel_string, problem_size, arguments,
|
|
|
450
451
|
if results: #checks if results is not empty
|
|
451
452
|
best_config = min(results, key=lambda x: x['time'])
|
|
452
453
|
units = getattr(runner, "units", None)
|
|
453
|
-
print("best performing configuration:"
|
|
454
|
+
print("best performing configuration:")
|
|
455
|
+
util.print_config_output(tune_params, best_config, device_options.quiet, metrics, units)
|
|
454
456
|
else:
|
|
455
457
|
print("no results to report")
|
|
456
458
|
|
|
@@ -4,7 +4,7 @@ from __future__ import print_function
|
|
|
4
4
|
from collections import OrderedDict
|
|
5
5
|
import logging
|
|
6
6
|
|
|
7
|
-
from kernel_tuner.util import get_config_string, store_cache
|
|
7
|
+
from kernel_tuner.util import get_config_string, store_cache, process_metrics, print_config_output
|
|
8
8
|
from kernel_tuner.core import DeviceInterface
|
|
9
9
|
|
|
10
10
|
|
|
@@ -35,6 +35,8 @@ class SequentialRunner(object):
|
|
|
35
35
|
self.quiet = device_options.quiet
|
|
36
36
|
self.kernel_source = kernel_source
|
|
37
37
|
|
|
38
|
+
self.warmed_up = False
|
|
39
|
+
|
|
38
40
|
#move data to the GPU
|
|
39
41
|
self.gpu_args = self.dev.ready_argument_list(kernel_options.arguments)
|
|
40
42
|
|
|
@@ -66,6 +68,11 @@ class SequentialRunner(object):
|
|
|
66
68
|
for element in parameter_space:
|
|
67
69
|
params = OrderedDict(zip(tuning_options.tune_params.keys(), element))
|
|
68
70
|
|
|
71
|
+
#attempt to warmup the GPU by running the first config in the parameter space and ignoring the result
|
|
72
|
+
if not self.warmed_up:
|
|
73
|
+
self.dev.compile_and_benchmark(self.kernel_source, self.gpu_args, params, kernel_options, tuning_options)
|
|
74
|
+
self.warmed_up = True
|
|
75
|
+
|
|
69
76
|
#check if element is in the cache
|
|
70
77
|
x_int = ",".join([str(i) for i in element])
|
|
71
78
|
if tuning_options.cache:
|
|
@@ -87,14 +94,16 @@ class SequentialRunner(object):
|
|
|
87
94
|
time = result
|
|
88
95
|
|
|
89
96
|
params['time'] = time
|
|
90
|
-
output_string = get_config_string(params, params.keys(), self.units)
|
|
91
|
-
logging.debug(output_string)
|
|
92
|
-
if not self.quiet:
|
|
93
|
-
print(output_string)
|
|
94
97
|
|
|
95
98
|
if isinstance(result, dict):
|
|
96
99
|
params.update(result)
|
|
97
100
|
|
|
101
|
+
|
|
102
|
+
if tuning_options.metrics:
|
|
103
|
+
params = process_metrics(params, tuning_options.metrics)
|
|
104
|
+
|
|
105
|
+
print_config_output(tuning_options.tune_params, params, self.quiet, tuning_options.metrics, self.units)
|
|
106
|
+
|
|
98
107
|
store_cache(x_int, params, tuning_options)
|
|
99
108
|
results.append(params)
|
|
100
109
|
|
|
@@ -104,3 +113,7 @@ class SequentialRunner(object):
|
|
|
104
113
|
def __del__(self):
|
|
105
114
|
if hasattr(self, 'dev'):
|
|
106
115
|
del self.dev
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
|
|
@@ -98,19 +98,28 @@ def _cost_func(x, kernel_options, tuning_options, runner, results):
|
|
|
98
98
|
|
|
99
99
|
def get_bounds_x0_eps(tuning_options):
|
|
100
100
|
"""compute bounds, x0 (the initial guess), and eps"""
|
|
101
|
-
values = tuning_options.tune_params.values()
|
|
101
|
+
values = list(tuning_options.tune_params.values())
|
|
102
|
+
|
|
103
|
+
if "x0" in tuning_options.strategy_options:
|
|
104
|
+
x0 = tuning_options.strategy_options.x0
|
|
105
|
+
else:
|
|
106
|
+
x0 = None
|
|
102
107
|
|
|
103
108
|
if tuning_options.scaling:
|
|
104
|
-
#bounds = [(0, 1) for _ in values]
|
|
105
|
-
#x0 = [0.5 for _ in bounds]
|
|
106
109
|
eps = numpy.amin([1.0/len(v) for v in values])
|
|
107
110
|
|
|
108
111
|
#reducing interval from [0, 1] to [0, eps*len(v)]
|
|
109
112
|
bounds = [(0, eps*len(v)) for v in values]
|
|
110
|
-
x0
|
|
113
|
+
if x0:
|
|
114
|
+
#x0 has been supplied by the user, map x0 into [0, eps*len(v)]
|
|
115
|
+
for i, e in enumerate(values):
|
|
116
|
+
x0[i] = eps*values[i].index(x0[i])
|
|
117
|
+
else:
|
|
118
|
+
x0 = [0.5*eps*len(v) for v in values]
|
|
111
119
|
else:
|
|
112
120
|
bounds = get_bounds(tuning_options.tune_params)
|
|
113
|
-
|
|
121
|
+
if not x0:
|
|
122
|
+
x0 = [(min_v+max_v)/2.0 for (min_v, max_v) in bounds]
|
|
114
123
|
eps = 1e9
|
|
115
124
|
for v_list in values:
|
|
116
125
|
vals = numpy.sort(v_list)
|
|
@@ -10,7 +10,7 @@ import logging
|
|
|
10
10
|
import warnings
|
|
11
11
|
import re
|
|
12
12
|
|
|
13
|
-
import numpy
|
|
13
|
+
import numpy as np
|
|
14
14
|
|
|
15
15
|
default_block_size_names = ["block_size_x", "block_size_y", "block_size_z"]
|
|
16
16
|
|
|
@@ -36,7 +36,7 @@ def check_argument_list(kernel_name, kernel_string, args):
|
|
|
36
36
|
""" raise an exception if a kernel arguments do not match host arguments """
|
|
37
37
|
kernel_arguments = list()
|
|
38
38
|
collected_errors = list()
|
|
39
|
-
for iterator in re.finditer(kernel_name + "[ \n\t]*" + "\(", kernel_string):
|
|
39
|
+
for iterator in re.finditer(kernel_name + "[ \n\t]*" + r"\(", kernel_string):
|
|
40
40
|
kernel_start = iterator.end()
|
|
41
41
|
kernel_end = kernel_string.find(")", kernel_start)
|
|
42
42
|
if kernel_start != 0:
|
|
@@ -49,11 +49,11 @@ def check_argument_list(kernel_name, kernel_string, args):
|
|
|
49
49
|
for (i, arg) in enumerate(args):
|
|
50
50
|
kernel_argument = arguments[i]
|
|
51
51
|
|
|
52
|
-
if not isinstance(arg, (
|
|
53
|
-
raise TypeError("Argument at position " + str(i) + " of type: " + str(type(arg)) + " should be of type
|
|
52
|
+
if not isinstance(arg, (np.ndarray, np.generic)):
|
|
53
|
+
raise TypeError("Argument at position " + str(i) + " of type: " + str(type(arg)) + " should be of type np.ndarray or numpy scalar")
|
|
54
54
|
|
|
55
55
|
correct = True
|
|
56
|
-
if isinstance(arg,
|
|
56
|
+
if isinstance(arg, np.ndarray) and not "*" in kernel_argument:
|
|
57
57
|
correct = False #array is passed to non-pointer kernel argument
|
|
58
58
|
|
|
59
59
|
if correct and check_argument_type(str(arg.dtype), kernel_argument):
|
|
@@ -131,6 +131,12 @@ def detect_language(kernel_string):
|
|
|
131
131
|
|
|
132
132
|
def get_config_string(params, keys=None, units=None):
|
|
133
133
|
""" return a compact string representation of a measurement """
|
|
134
|
+
def compact_number(v):
|
|
135
|
+
if isinstance(v, float):
|
|
136
|
+
return "{:.3f}".format(round(v, 3))
|
|
137
|
+
else:
|
|
138
|
+
return str(v)
|
|
139
|
+
|
|
134
140
|
compact_str_items = []
|
|
135
141
|
if not keys:
|
|
136
142
|
keys = params.keys()
|
|
@@ -140,7 +146,7 @@ def get_config_string(params, keys=None, units=None):
|
|
|
140
146
|
unit = ""
|
|
141
147
|
if isinstance(units, dict): #check if not None not enough, units could be mocked which causes errors
|
|
142
148
|
unit = units.get(k, "")
|
|
143
|
-
compact_str_items.append(k + "=" +
|
|
149
|
+
compact_str_items.append(k + "=" + compact_number(v) + unit)
|
|
144
150
|
# and finally join them
|
|
145
151
|
compact_str = ", ".join(compact_str_items)
|
|
146
152
|
return compact_str
|
|
@@ -154,9 +160,9 @@ def get_grid_dimensions(current_problem_size, params, grid_div, block_size_names
|
|
|
154
160
|
divisor_list = [default]
|
|
155
161
|
else:
|
|
156
162
|
return 1
|
|
157
|
-
return
|
|
163
|
+
return np.prod([int(eval(replace_param_occurrences(s, params))) for s in divisor_list])
|
|
158
164
|
divisors = [get_dimension_divisor(d, block_size_names[i], params) for i, d in enumerate(grid_div)]
|
|
159
|
-
return tuple(int(
|
|
165
|
+
return tuple(int(np.ceil(float(current_problem_size[i]) / float(d))) for i, d in enumerate(divisors))
|
|
160
166
|
|
|
161
167
|
def get_instance_string(params):
|
|
162
168
|
""" combine the parameters to a string mostly used for debug output
|
|
@@ -206,13 +212,13 @@ def get_kernel_string(kernel_source, params=None):
|
|
|
206
212
|
|
|
207
213
|
def get_problem_size(problem_size, params):
|
|
208
214
|
"""compute current problem size"""
|
|
209
|
-
if isinstance(problem_size, (str, int,
|
|
215
|
+
if isinstance(problem_size, (str, int, np.integer)):
|
|
210
216
|
problem_size = (problem_size, )
|
|
211
217
|
current_problem_size = [1, 1, 1]
|
|
212
218
|
for i, s in enumerate(problem_size):
|
|
213
219
|
if isinstance(s, str):
|
|
214
220
|
current_problem_size[i] = int(eval(replace_param_occurrences(s, params)))
|
|
215
|
-
elif isinstance(s, (int,
|
|
221
|
+
elif isinstance(s, (int, np.integer)):
|
|
216
222
|
current_problem_size[i] = s
|
|
217
223
|
else:
|
|
218
224
|
raise TypeError("Error: problem_size should only contain strings or integers")
|
|
@@ -234,6 +240,60 @@ def get_thread_block_dimensions(params, block_size_names=None):
|
|
|
234
240
|
block_size_z = params.get(block_size_names[2], 1)
|
|
235
241
|
return (int(block_size_x), int(block_size_y), int(block_size_z))
|
|
236
242
|
|
|
243
|
+
def print_config_output(tune_params, params, quiet, metrics, units):
|
|
244
|
+
"""print the configuration string with tunable parameters and benchmark results"""
|
|
245
|
+
print_keys = list(tune_params.keys())+["time"]
|
|
246
|
+
if metrics:
|
|
247
|
+
print_keys += metrics.keys()
|
|
248
|
+
output_string = get_config_string(params, print_keys, units)
|
|
249
|
+
if not quiet:
|
|
250
|
+
print(output_string)
|
|
251
|
+
|
|
252
|
+
def process_metrics(params, metrics):
|
|
253
|
+
""" process user-defined metrics for derived benchmark results
|
|
254
|
+
|
|
255
|
+
Metrics must be an OrderedDict to support composable metrics. The dictionary keys describe
|
|
256
|
+
the name given to this user-defined metric and will be used as the key in the results dictionaries
|
|
257
|
+
return by Kernel Tuner. The values describe how to calculate the user-defined metric, using either a
|
|
258
|
+
string expression in which the tunable parameters and benchmark results can be used as variables, or
|
|
259
|
+
as a function that accepts a dictionary as argument.
|
|
260
|
+
Example:
|
|
261
|
+
metrics = OrderedDict()
|
|
262
|
+
metrics["x"] = "10000 / time"
|
|
263
|
+
metrics["x2"] = "x*x"
|
|
264
|
+
|
|
265
|
+
Note that the values in the metric dictionary can also be functions that accept params as argument.
|
|
266
|
+
Example:
|
|
267
|
+
metrics = OrderedDict()
|
|
268
|
+
metrics["GFLOP/s"] = lambda p : 10000 / p["time"]
|
|
269
|
+
|
|
270
|
+
:param params: A dictionary with tunable parameters and benchmark results.
|
|
271
|
+
:type params: dict
|
|
272
|
+
|
|
273
|
+
:param metrics: An OrderedDict with user-defined metrics that can be used to create derived benchmark results.
|
|
274
|
+
:type metrics: OrderedDict
|
|
275
|
+
|
|
276
|
+
:returns: An updated params dictionary with the derived metrics inserted along with the benchmark results.
|
|
277
|
+
:rtype: dict
|
|
278
|
+
|
|
279
|
+
"""
|
|
280
|
+
if not isinstance(metrics, OrderedDict):
|
|
281
|
+
raise ValueError("metrics should be an OrderedDict to preserve order and support composability")
|
|
282
|
+
for k, v in metrics.items():
|
|
283
|
+
if isinstance(v, str):
|
|
284
|
+
value = eval(replace_param_occurrences(v, params))
|
|
285
|
+
elif callable(v):
|
|
286
|
+
value = v(params)
|
|
287
|
+
else:
|
|
288
|
+
raise ValueError("metric dicts values should be strings or callable")
|
|
289
|
+
if not k in params:
|
|
290
|
+
params[k] = value
|
|
291
|
+
else:
|
|
292
|
+
raise ValueError("metric dicts keys should not already exist in params")
|
|
293
|
+
return params
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
|
|
237
297
|
def looks_like_a_filename(kernel_source):
|
|
238
298
|
""" attempt to detect whether source code or a filename was passed """
|
|
239
299
|
logging.debug('looks_like_a_filename called')
|
|
@@ -256,7 +316,7 @@ def looks_like_a_filename(kernel_source):
|
|
|
256
316
|
logging.debug('kernel_source is a filename: %s' % str(result))
|
|
257
317
|
return result
|
|
258
318
|
|
|
259
|
-
def prepare_kernel_string(kernel_name, kernel_string, params, grid, threads, block_size_names):
|
|
319
|
+
def prepare_kernel_string(kernel_name, kernel_string, params, grid, threads, block_size_names, lang):
|
|
260
320
|
""" prepare kernel string for compilation
|
|
261
321
|
|
|
262
322
|
Prepends the kernel with a series of C preprocessor defines specific
|
|
@@ -298,11 +358,24 @@ def prepare_kernel_string(kernel_name, kernel_string, params, grid, threads, blo
|
|
|
298
358
|
for i, g in enumerate(threads):
|
|
299
359
|
kernel_string = "#define " + block_size_names[i] + " " + str(g) + "\n" + kernel_string
|
|
300
360
|
for k, v in params.items():
|
|
301
|
-
if k
|
|
361
|
+
if "loop_unroll_factor" in k and lang == "CUDA":
|
|
362
|
+
#this handles the special case that in CUDA
|
|
363
|
+
#pragma unroll loop_unroll_factor, loop_unroll_factor should be a constant integer expression
|
|
364
|
+
#in OpenCL this isn't the case and we can just insert "#define loop_unroll_factor N"
|
|
365
|
+
if v > 0: # using 0 to disable loop unrolling for this loop
|
|
366
|
+
kernel_string = "const int " + k + " = " + str(v) + ";\n" + kernel_string
|
|
367
|
+
else:
|
|
368
|
+
kernel_string = re.sub(r"\n\s*#pragma\s+unroll\s+" + k, "\n", kernel_string) # + r"[^\S]*"
|
|
369
|
+
elif k not in block_size_names:
|
|
302
370
|
kernel_string = "#define " + k + " " + str(v) + "\n" + kernel_string
|
|
371
|
+
|
|
303
372
|
name = kernel_name
|
|
373
|
+
|
|
374
|
+
#also insert kernel_tuner token
|
|
375
|
+
kernel_string = "#define kernel_tuner 1\n" + kernel_string
|
|
304
376
|
#name = kernel_name + "_" + get_instance_string(params)
|
|
305
377
|
#kernel_string = kernel_string.replace(kernel_name, name)
|
|
378
|
+
|
|
306
379
|
return name, kernel_string
|
|
307
380
|
|
|
308
381
|
def read_file(filename):
|
|
@@ -467,11 +540,23 @@ def close_cache(cache):
|
|
|
467
540
|
|
|
468
541
|
|
|
469
542
|
def store_cache(key, params, tuning_options):
|
|
543
|
+
""" stores a new entry (key, params) to the cachefile """
|
|
544
|
+
# create converter for dumping numpy objects to JSON
|
|
545
|
+
def npconverter(obj):
|
|
546
|
+
if isinstance(obj, np.integer):
|
|
547
|
+
return int(obj)
|
|
548
|
+
elif isinstance(obj, np.floating):
|
|
549
|
+
return float(obj)
|
|
550
|
+
elif isinstance(obj, np.ndarray):
|
|
551
|
+
return obj.tolist()
|
|
552
|
+
else:
|
|
553
|
+
return obj.__str__()
|
|
554
|
+
|
|
470
555
|
logging.debug('store_cache called, cache=%s, cachefile=%s' % (tuning_options.cache, tuning_options.cachefile))
|
|
471
556
|
if isinstance(tuning_options.cache, dict):
|
|
472
557
|
if not key in tuning_options.cache:
|
|
473
558
|
tuning_options.cache[key] = params
|
|
474
559
|
if tuning_options.cachefile:
|
|
475
560
|
with open(tuning_options.cachefile, "a") as cachefile:
|
|
476
|
-
cachefile.write("\n" + json.dumps({key: params})[1:-1] + ",")
|
|
561
|
+
cachefile.write("\n" + json.dumps({key: params}, default=npconverter)[1:-1] + ",")
|
|
477
562
|
|
|
@@ -90,7 +90,7 @@ def cpp(function_name, kernel_source, args, convert_to_array=None):
|
|
|
90
90
|
for i, arg in enumerate(args):
|
|
91
91
|
if convert_to_array[i]:
|
|
92
92
|
if not isinstance(arg, np.ndarray):
|
|
93
|
-
ValueError("conversion to array reference only supported for arguments that are numpy arrays, use length-1 numpy array to pass a scalar by reference")
|
|
93
|
+
raise ValueError("conversion to array reference only supported for arguments that are numpy arrays, use length-1 numpy array to pass a scalar by reference")
|
|
94
94
|
if np.prod(arg.shape) > 1:
|
|
95
95
|
#convert pointer to a reference to an array
|
|
96
96
|
arg_shape = "".join("[%d]" % i for i in arg.shape)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: kernel-tuner
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.2
|
|
4
4
|
Summary: An easy to use CUDA/OpenCL kernel tuner in Python
|
|
5
5
|
Home-page: http://benvanwerkhoven.github.io/kernel_tuner/
|
|
6
6
|
Author: Ben van Werkhoven
|
|
@@ -9,7 +9,7 @@ License: Apache 2.0
|
|
|
9
9
|
Description: Kernel Tuner
|
|
10
10
|
============
|
|
11
11
|
|
|
12
|
-
|Build Status| |CodeCov Badge| |PyPi Badge|
|
|
12
|
+
|Build Status| |CodeCov Badge| |PyPi Badge| |Zenodo Badge| |SonarCloud Badge| |FairSoftware Badge|
|
|
13
13
|
|
|
14
14
|
Kernel Tuner simplifies the software development of optimized and auto-tuned GPU programs, by enabling Python-based unit testing of GPU code and making it easy to develop scripts for auto-tuning GPU kernels. This also means no extensive changes and no new dependencies are required in the kernel code. The kernels can still be compiled and used as normal from any host programming language.
|
|
15
15
|
|
|
@@ -178,12 +178,12 @@ Description: Kernel Tuner
|
|
|
178
178
|
:target: https://codecov.io/gh/benvanwerkhoven/kernel_tuner
|
|
179
179
|
.. |PyPi Badge| image:: https://img.shields.io/pypi/v/kernel_tuner.svg?colorB=blue
|
|
180
180
|
:target: https://pypi.python.org/pypi/kernel_tuner/
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
181
|
+
.. |Zenodo Badge| image:: https://zenodo.org/badge/DOI/10.5281/zenodo.1220113.svg
|
|
182
|
+
:target: https://doi.org/10.5281/zenodo.1220113
|
|
183
|
+
.. |SonarCloud Badge| image:: https://sonarcloud.io/api/project_badges/measure?project=benvanwerkhoven_kernel_tuner&metric=alert_status
|
|
184
|
+
:target: https://sonarcloud.io/dashboard?id=benvanwerkhoven_kernel_tuner
|
|
185
|
+
.. |FairSoftware Badge| image:: https://img.shields.io/badge/fair--software.eu-%E2%97%8F%20%20%E2%97%8F%20%20%E2%97%8F%20%20%E2%97%8F%20%20%E2%97%8F-green
|
|
186
|
+
:target: https://fair-software.eu
|
|
187
187
|
|
|
188
188
|
Keywords: auto-tuning gpu computing pycuda cuda pyopencl opencl
|
|
189
189
|
Platform: UNKNOWN
|
|
@@ -201,9 +201,9 @@ Classifier: Topic :: Scientific/Engineering
|
|
|
201
201
|
Classifier: Topic :: Software Development
|
|
202
202
|
Classifier: Topic :: System :: Distributed Computing
|
|
203
203
|
Classifier: Development Status :: 4 - Beta
|
|
204
|
-
Provides-Extra:
|
|
204
|
+
Provides-Extra: doc
|
|
205
205
|
Provides-Extra: cuda
|
|
206
|
-
Provides-Extra: tutorial
|
|
207
206
|
Provides-Extra: opencl
|
|
208
207
|
Provides-Extra: cuda_opencl
|
|
209
|
-
Provides-Extra:
|
|
208
|
+
Provides-Extra: tutorial
|
|
209
|
+
Provides-Extra: dev
|
|
@@ -89,9 +89,9 @@ def test_ready_argument_list5():
|
|
|
89
89
|
|
|
90
90
|
assert all(output[0].numpy == arg1)
|
|
91
91
|
|
|
92
|
-
# test that a copy has been made
|
|
92
|
+
# test that a copy has not been made
|
|
93
93
|
arg1[0] = arg1[0] + 1
|
|
94
|
-
assert
|
|
94
|
+
assert all(output[0].numpy == arg1)
|
|
95
95
|
|
|
96
96
|
|
|
97
97
|
def test_byte_array_arguments():
|
|
@@ -39,7 +39,7 @@ def env():
|
|
|
39
39
|
verbose = True
|
|
40
40
|
kernel_options = Options(kernel_name="vector_add", kernel_string=kernel_string, problem_size=args[-1], arguments=args, lang=lang,
|
|
41
41
|
grid_div_x=None, grid_div_y=None, grid_div_z=None, cmem_args=None, texmem_args=None, block_size_names=None)
|
|
42
|
-
device_options = Options(device=0, platform=0,
|
|
42
|
+
device_options = Options(device=0, platform=0, quiet=False, compiler=None, compiler_options=None)
|
|
43
43
|
dev = core.DeviceInterface(kernel_source, iterations=7, **device_options)
|
|
44
44
|
instance = dev.create_kernel_instance(kernel_source, kernel_options, params, verbose)
|
|
45
45
|
|
|
@@ -29,38 +29,18 @@ def test_ready_argument_list():
|
|
|
29
29
|
@skip_if_no_cuda
|
|
30
30
|
def test_compile():
|
|
31
31
|
|
|
32
|
-
|
|
32
|
+
kernel_string = """
|
|
33
33
|
__global__ void vector_add(float *c, float *a, float *b, int n) {
|
|
34
|
-
__shared__ float test[shared_size];
|
|
35
34
|
int i = blockIdx.x * blockDim.x + threadIdx.x;
|
|
36
35
|
if (i<n) {
|
|
37
|
-
|
|
38
|
-
c[i] = test[0] + b[i];
|
|
36
|
+
c[i] = a[i] + b[i];
|
|
39
37
|
}
|
|
40
38
|
}
|
|
41
39
|
"""
|
|
42
40
|
|
|
43
|
-
kernel_sources = KernelSource(
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
kernel_string = original_kernel.replace("shared_size", str(100*1024*1024))
|
|
41
|
+
kernel_sources = KernelSource(kernel_string, "cuda")
|
|
47
42
|
kernel_instance = KernelInstance("vector_add", kernel_sources, kernel_string, [], None, None, dict(), [])
|
|
48
|
-
|
|
49
43
|
dev = cuda.CudaFunctions(0)
|
|
50
|
-
try:
|
|
51
|
-
func = dev.compile(kernel_instance)
|
|
52
|
-
assert isinstance(func, pycuda.driver.Function)
|
|
53
|
-
print("Expected an exception because too much shared memory is requested")
|
|
54
|
-
assert False
|
|
55
|
-
except Exception as e:
|
|
56
|
-
if "uses too much shared data" in str(e):
|
|
57
|
-
assert True
|
|
58
|
-
else:
|
|
59
|
-
print("Expected a different exception:" + str(e))
|
|
60
|
-
assert False
|
|
61
|
-
|
|
62
|
-
kernel_string = original_kernel.replace("shared_size", str(100))
|
|
63
|
-
kernel_instance = KernelInstance("vector_add", kernel_sources, kernel_string, [], None, None, dict(), [])
|
|
64
44
|
try:
|
|
65
45
|
func = dev.compile( kernel_instance)
|
|
66
46
|
assert True
|
|
@@ -81,8 +81,7 @@ def test_benchmark(drv, *args):
|
|
|
81
81
|
drv.Event.return_value.time_since.return_value = 0.1
|
|
82
82
|
|
|
83
83
|
dev = cuda.CudaFunctions(0)
|
|
84
|
-
|
|
85
|
-
res = dev.benchmark(dummy_func, args, (1,2), (1,2))
|
|
84
|
+
res = dev.benchmark(dummy_func, [1,2], (1,2), (1,2))
|
|
86
85
|
assert res["time"] > 0
|
|
87
86
|
|
|
88
87
|
assert dev.context.synchronize.call_count == 1
|
|
@@ -30,8 +30,8 @@ def test_interface_calls_functions(dev_interface):
|
|
|
30
30
|
kernel_name, kernel_string, size, args, tune_params = get_fake_kernel()
|
|
31
31
|
tune_kernel(kernel_name, kernel_string, size, args, tune_params, verbose=True)
|
|
32
32
|
|
|
33
|
-
dev.compile.
|
|
34
|
-
dev.benchmark.
|
|
33
|
+
dev.compile.assert_called()
|
|
34
|
+
dev.benchmark.assert_called_with('compile', 'ready_argument_list', (128, 1, 1), (10, 1, 1))
|
|
35
35
|
|
|
36
36
|
@patch('kernel_tuner.core.CudaFunctions')
|
|
37
37
|
def test_interface_handles_max_threads(dev_interface):
|
|
@@ -45,7 +45,7 @@ def test_interface_handles_max_threads(dev_interface):
|
|
|
45
45
|
tune_kernel("fake_kernel", kernel_string, (1,1), [numpy.int32(0)], tune_params, lang="CUDA")
|
|
46
46
|
|
|
47
47
|
# verify that only a single instance of the kernel is compiled
|
|
48
|
-
dev.compile.
|
|
48
|
+
dev.compile.assert_called()
|
|
49
49
|
|
|
50
50
|
@patch('kernel_tuner.core.CudaFunctions')
|
|
51
51
|
def test_interface_handles_compile_error(dev_interface):
|
|
@@ -58,7 +58,7 @@ def test_interface_handles_compile_error(dev_interface):
|
|
|
58
58
|
kernel_string = "__global__ void fake_kernel(int number)"
|
|
59
59
|
tune_kernel("fake_kernel", kernel_string, (1,1), [numpy.int32(0)], tune_params, lang="CUDA")
|
|
60
60
|
|
|
61
|
-
assert dev.compile.call_count ==
|
|
61
|
+
assert dev.compile.call_count == 2
|
|
62
62
|
assert dev.benchmark.called == False
|
|
63
63
|
|
|
64
64
|
@patch('kernel_tuner.core.CudaFunctions')
|
|
@@ -72,8 +72,8 @@ def test_interface_handles_restriction(dev_interface):
|
|
|
72
72
|
kernel_string = "__global__ void fake_kernel(int number)"
|
|
73
73
|
tune_kernel("fake_kernel", kernel_string, (1,1), [numpy.int32(0)], tune_params, restrictions=restrict, lang="CUDA", verbose=True)
|
|
74
74
|
|
|
75
|
-
assert dev.compile.call_count ==
|
|
76
|
-
dev.benchmark.
|
|
75
|
+
assert dev.compile.call_count == 2
|
|
76
|
+
dev.benchmark.assert_called_with('compile', 'ready_argument_list', (256, 1, 1), (1, 1, 1))
|
|
77
77
|
|
|
78
78
|
@patch('kernel_tuner.core.CudaFunctions')
|
|
79
79
|
def test_interface_handles_runtime_error(dev_interface):
|
|
@@ -86,8 +86,8 @@ def test_interface_handles_runtime_error(dev_interface):
|
|
|
86
86
|
kernel_string = "__global__ void fake_kernel(int number)"
|
|
87
87
|
results, _ = tune_kernel("fake_kernel",kernel_string, (1,1), [numpy.int32(0)], tune_params, lang="CUDA")
|
|
88
88
|
|
|
89
|
-
assert dev.compile.call_count ==
|
|
90
|
-
dev.benchmark.
|
|
89
|
+
assert dev.compile.call_count == 2
|
|
90
|
+
dev.benchmark.assert_called_with('compile', 'ready_argument_list', (256, 1, 1), (1, 1, 1))
|
|
91
91
|
assert len(results) == 0
|
|
92
92
|
|
|
93
93
|
@patch('kernel_tuner.core.CudaFunctions')
|
|
@@ -99,6 +99,6 @@ def test_run_kernel(dev_interface):
|
|
|
99
99
|
answer = run_kernel(kernel_name, kernel_string, size, args, {"block_size_x": 128})
|
|
100
100
|
|
|
101
101
|
assert dev.compile.call_count == 1
|
|
102
|
-
dev.run_kernel.
|
|
102
|
+
dev.run_kernel.assert_called_with('compile', 'ready_argument_list', (128, 1, 1), (10, 1, 1))
|
|
103
103
|
assert answer[0] == size
|
|
104
104
|
|
|
@@ -126,8 +126,9 @@ def test_prepare_kernel_string():
|
|
|
126
126
|
params = dict()
|
|
127
127
|
params["is"] = 8
|
|
128
128
|
|
|
129
|
-
_, output = prepare_kernel_string("this", kernel, params, (3,7), (1,2,3), block_size_names)
|
|
130
|
-
expected = "#define
|
|
129
|
+
_, output = prepare_kernel_string("this", kernel, params, (3,7), (1,2,3), block_size_names, "")
|
|
130
|
+
expected = "#define kernel_tuner 1\n" \
|
|
131
|
+
"#define is 8\n" \
|
|
131
132
|
"#define block_size_z 3\n" \
|
|
132
133
|
"#define block_size_y 2\n" \
|
|
133
134
|
"#define block_size_x 1\n" \
|
|
@@ -454,7 +455,7 @@ def test_process_cache():
|
|
|
454
455
|
assert len(tuning_options.cache) == 0
|
|
455
456
|
|
|
456
457
|
#store one entry in the cache
|
|
457
|
-
params = {"x": 4, "time": 0.1234}
|
|
458
|
+
params = {"x": 4, "time": numpy.float32(0.1234)}
|
|
458
459
|
store_cache("4", params, tuning_options)
|
|
459
460
|
assert len(tuning_options.cache) == 1
|
|
460
461
|
|
|
@@ -487,3 +488,38 @@ def test_process_cache():
|
|
|
487
488
|
finally:
|
|
488
489
|
delete_temp_file(cache)
|
|
489
490
|
#pass
|
|
491
|
+
|
|
492
|
+
|
|
493
|
+
def test_process_metrics():
|
|
494
|
+
params = {"x": 15, "b": 12}
|
|
495
|
+
metrics = OrderedDict()
|
|
496
|
+
metrics["y"] = lambda p : p["x"]
|
|
497
|
+
|
|
498
|
+
#test if lambda function is correctly evaluated
|
|
499
|
+
params = process_metrics(params, metrics)
|
|
500
|
+
assert params["y"] == params["x"]
|
|
501
|
+
|
|
502
|
+
#test if we can do the same with a string
|
|
503
|
+
params = {"x": 15, "b": 12}
|
|
504
|
+
metrics["y"] = "x"
|
|
505
|
+
params = process_metrics(params, metrics)
|
|
506
|
+
assert params["y"] == params["x"]
|
|
507
|
+
|
|
508
|
+
#test if composability works correctly
|
|
509
|
+
params = {"x": 15, "b": 12}
|
|
510
|
+
metrics = OrderedDict()
|
|
511
|
+
metrics["y"] = "x"
|
|
512
|
+
metrics["z"] = "y"
|
|
513
|
+
params = process_metrics(params, metrics)
|
|
514
|
+
assert params["z"] == params["x"]
|
|
515
|
+
|
|
516
|
+
#test ValueError is raised when metrics is not an OrderedDict
|
|
517
|
+
with pytest.raises(ValueError):
|
|
518
|
+
params = process_metrics(params, {})
|
|
519
|
+
|
|
520
|
+
#test ValueError is raised when b already exists in params
|
|
521
|
+
params = {"x": 15, "b": 12}
|
|
522
|
+
metrics = OrderedDict()
|
|
523
|
+
metrics["b"] = "x"
|
|
524
|
+
with pytest.raises(ValueError):
|
|
525
|
+
params = process_metrics(params, metrics)
|
|
@@ -1,331 +0,0 @@
|
|
|
1
|
-
"""This module contains all CUDA specific kernel_tuner functions"""
|
|
2
|
-
from __future__ import print_function
|
|
3
|
-
|
|
4
|
-
import logging
|
|
5
|
-
import time
|
|
6
|
-
import numpy as np
|
|
7
|
-
|
|
8
|
-
import cupy as cp
|
|
9
|
-
|
|
10
|
-
import cupy.cuda as cuda
|
|
11
|
-
|
|
12
|
-
from kernel_tuner.nvml import nvml
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
class CupyBackend(object):
|
|
18
|
-
"""Class that groups the CUDA functions on maintains state about the device"""
|
|
19
|
-
|
|
20
|
-
def __init__(self, device=0, iterations=7, compiler_options=None):
|
|
21
|
-
"""instantiate CudaFunctions object used for interacting with the CUDA device
|
|
22
|
-
|
|
23
|
-
Instantiating this object will inspect and store certain device properties at
|
|
24
|
-
runtime, which are used during compilation and/or execution of kernels by the
|
|
25
|
-
kernel tuner. It also maintains a reference to the most recently compiled
|
|
26
|
-
source module for copying data to constant memory before kernel launch.
|
|
27
|
-
|
|
28
|
-
:param device: Number of CUDA device to use for this context
|
|
29
|
-
:type device: int
|
|
30
|
-
|
|
31
|
-
:param iterations: Number of iterations used while benchmarking a kernel, 7 by default.
|
|
32
|
-
:type iterations: int
|
|
33
|
-
"""
|
|
34
|
-
|
|
35
|
-
self.allocations = []
|
|
36
|
-
self.compiler_options = compiler_options
|
|
37
|
-
self.iterations = iterations
|
|
38
|
-
|
|
39
|
-
self.dev = cuda.Device(device)
|
|
40
|
-
self.dev.use() #make this the current device
|
|
41
|
-
self.smem_size = 0
|
|
42
|
-
self.use_nvml = False
|
|
43
|
-
|
|
44
|
-
self.stream = cuda.Stream()
|
|
45
|
-
self.max_threads = self.dev.attributes["MaxThreadsPerBlock"]
|
|
46
|
-
self.cc = self.dev.compute_capability
|
|
47
|
-
|
|
48
|
-
self.env = {"attributes": self.dev.attributes}
|
|
49
|
-
self.name = "cupy does not name device"
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
def ready_argument_list(self, arguments):
|
|
54
|
-
"""ready argument list to be passed to the kernel, allocates gpu mem
|
|
55
|
-
|
|
56
|
-
:param arguments: List of arguments to be passed to the kernel.
|
|
57
|
-
The order should match the argument list on the CUDA kernel.
|
|
58
|
-
Allowed values are np.ndarray, and/or np.int32, np.float32, and so on.
|
|
59
|
-
:type arguments: list(numpy objects)
|
|
60
|
-
|
|
61
|
-
:returns: A list of arguments that can be passed to an CUDA kernel.
|
|
62
|
-
:rtype: list( pycuda.driver.DeviceAllocation, np.int32, ... )
|
|
63
|
-
"""
|
|
64
|
-
gpu_args = []
|
|
65
|
-
for arg in arguments:
|
|
66
|
-
# if arg i is a numpy array copy to device
|
|
67
|
-
if isinstance(arg, np.ndarray):
|
|
68
|
-
alloc = cp.asarray(arg)
|
|
69
|
-
self.allocations.append(alloc)
|
|
70
|
-
gpu_args.append(alloc)
|
|
71
|
-
else: # if not an array, just pass argument along
|
|
72
|
-
#if we remove other checks this might just pass along cupy arrays too
|
|
73
|
-
gpu_args.append(arg)
|
|
74
|
-
return gpu_args
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
def compile(self, kernel_instance):
|
|
78
|
-
"""call the CUDA compiler to compile the kernel, return the device function
|
|
79
|
-
|
|
80
|
-
:param kernel_name: The name of the kernel to be compiled, used to lookup the
|
|
81
|
-
function after compilation.
|
|
82
|
-
:type kernel_name: string
|
|
83
|
-
|
|
84
|
-
:param kernel_string: The CUDA kernel code that contains the function `kernel_name`
|
|
85
|
-
:type kernel_string: string
|
|
86
|
-
|
|
87
|
-
:returns: An CUDA kernel that can be called directly.
|
|
88
|
-
:rtype: pycuda.driver.Function
|
|
89
|
-
"""
|
|
90
|
-
kernel_string = kernel_instance.kernel_string
|
|
91
|
-
kernel_name = kernel_instance.name
|
|
92
|
-
|
|
93
|
-
try:
|
|
94
|
-
#mimic pycuda behavior for the moment, this can be removed once
|
|
95
|
-
#https://github.com/cupy/cupy/pull/3319
|
|
96
|
-
#has been merged
|
|
97
|
-
no_extern_c = not 'extern "C"' in kernel_string
|
|
98
|
-
if no_extern_c:
|
|
99
|
-
kernel_string = 'extern "C" {\n' + kernel_string + '\n}\n'
|
|
100
|
-
|
|
101
|
-
#self.current_module = cp.RawModule(code=kernel_string, options=self.compiler_options)
|
|
102
|
-
self.current_module = cp.RawModule(code=kernel_string)
|
|
103
|
-
|
|
104
|
-
func = self.current_module.get_function(kernel_name)
|
|
105
|
-
return func
|
|
106
|
-
except Exception as e:
|
|
107
|
-
if "uses too much shared data" in e.stderr:
|
|
108
|
-
raise Exception("uses too much shared data")
|
|
109
|
-
else:
|
|
110
|
-
raise e
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
def benchmark(self, func, gpu_args, threads, grid):
|
|
114
|
-
"""runs the kernel and measures time repeatedly, returns average time
|
|
115
|
-
|
|
116
|
-
Runs the kernel and measures kernel execution time repeatedly, number of
|
|
117
|
-
iterations is set during the creation of CudaFunctions. Benchmark returns
|
|
118
|
-
a robust average, from all measurements the fastest and slowest runs are
|
|
119
|
-
discarded and the rest is included in the returned average. The reason for
|
|
120
|
-
this is to be robust against initialization artifacts and other exceptional
|
|
121
|
-
cases.
|
|
122
|
-
|
|
123
|
-
:param func: A PyCuda kernel compiled for this specific kernel configuration
|
|
124
|
-
:type func: pycuda.driver.Function
|
|
125
|
-
|
|
126
|
-
:param gpu_args: A list of arguments to the kernel, order should match the
|
|
127
|
-
order in the code. Allowed values are either variables in global memory
|
|
128
|
-
or single values passed by value.
|
|
129
|
-
:type gpu_args: list( pycuda.driver.DeviceAllocation, np.int32, ...)
|
|
130
|
-
|
|
131
|
-
:param threads: A tuple listing the number of threads in each dimension of
|
|
132
|
-
the thread block
|
|
133
|
-
:type threads: tuple(int, int, int)
|
|
134
|
-
|
|
135
|
-
:param grid: A tuple listing the number of thread blocks in each dimension
|
|
136
|
-
of the grid
|
|
137
|
-
:type grid: tuple(int, int)
|
|
138
|
-
|
|
139
|
-
:returns: A dictionary with benchmark results.
|
|
140
|
-
:rtype: dict()
|
|
141
|
-
"""
|
|
142
|
-
result = dict()
|
|
143
|
-
result["times"] = []
|
|
144
|
-
result["power"] = []
|
|
145
|
-
energy = []
|
|
146
|
-
start = cuda.Event()
|
|
147
|
-
end = cuda.Event()
|
|
148
|
-
self.dev.synchronize()
|
|
149
|
-
for _ in range(self.iterations):
|
|
150
|
-
power_readings = []
|
|
151
|
-
self.stream.record(start)
|
|
152
|
-
self.run_kernel(func, gpu_args, threads, grid, stream=self.stream)
|
|
153
|
-
self.stream.record(end)
|
|
154
|
-
#measure power usage until kernel is done
|
|
155
|
-
t0 = time.time()
|
|
156
|
-
while not end.done:
|
|
157
|
-
if self.use_nvml:
|
|
158
|
-
power_readings.append([time.time()-t0, self.nvml.pwr_usage()])
|
|
159
|
-
end.synchronize()
|
|
160
|
-
execution_time = cuda.get_elapsed_time(start, end) #ms
|
|
161
|
-
result["times"].append(execution_time)
|
|
162
|
-
|
|
163
|
-
#pre and postfix to start at 0 and end at kernel end
|
|
164
|
-
if power_readings:
|
|
165
|
-
power_readings = [[0.0, power_readings[0][1]]] + power_readings
|
|
166
|
-
power_readings = power_readings + [[execution_time / 1000.0, power_readings[-1][1]]]
|
|
167
|
-
result["power"].append(power_readings) #time in s, power usage in milliwatts
|
|
168
|
-
|
|
169
|
-
#compute energy consumption as area under curve
|
|
170
|
-
x = [d[0] for d in power_readings]
|
|
171
|
-
y = [d[1]/1000.0 for d in power_readings] #convert to watts
|
|
172
|
-
energy.append(np.trapz(y,x)) #in Joule
|
|
173
|
-
|
|
174
|
-
result["time"] = np.mean(result["times"])
|
|
175
|
-
if (self.iterations > 10) and energy:
|
|
176
|
-
result["energy"] = np.mean(energy[10:])
|
|
177
|
-
return result
|
|
178
|
-
|
|
179
|
-
def copy_constant_memory_args(self, cmem_args):
|
|
180
|
-
"""adds constant memory arguments to the most recently compiled module
|
|
181
|
-
|
|
182
|
-
:param cmem_args: A dictionary containing the data to be passed to the
|
|
183
|
-
device constant memory. The format to be used is as follows: A
|
|
184
|
-
string key is used to name the constant memory symbol to which the
|
|
185
|
-
value needs to be copied. Similar to regular arguments, these need
|
|
186
|
-
to be numpy objects, such as np.ndarray or np.int32, and so on.
|
|
187
|
-
:type cmem_args: dict( string: np.ndarray, ... )
|
|
188
|
-
"""
|
|
189
|
-
logging.debug('copy_constant_memory_args called')
|
|
190
|
-
logging.debug('current module: ' + str(self.current_module))
|
|
191
|
-
for k, v in cmem_args.items():
|
|
192
|
-
#symbol = self.current_module.get_global(k)[0]
|
|
193
|
-
logging.debug('copying to symbol: ' + str(symbol))
|
|
194
|
-
logging.debug('array to be copied: ')
|
|
195
|
-
logging.debug(v.nbytes)
|
|
196
|
-
logging.debug(v.dtype)
|
|
197
|
-
logging.debug(v.flags)
|
|
198
|
-
#drv.memcpy_htod(symbol, v)
|
|
199
|
-
|
|
200
|
-
def copy_shared_memory_args(self, smem_args):
|
|
201
|
-
"""add shared memory arguments to the kernel"""
|
|
202
|
-
self.smem_size = smem_args["size"]
|
|
203
|
-
|
|
204
|
-
def copy_texture_memory_args(self, texmem_args):
|
|
205
|
-
"""adds texture memory arguments to the most recently compiled module
|
|
206
|
-
|
|
207
|
-
:param texmem_args: A dictionary containing the data to be passed to the
|
|
208
|
-
device texture memory. See tune_kernel().
|
|
209
|
-
:type texmem_args: dict
|
|
210
|
-
"""
|
|
211
|
-
|
|
212
|
-
"""
|
|
213
|
-
filter_mode_map = { 'point': drv.filter_mode.POINT,
|
|
214
|
-
'linear': drv.filter_mode.LINEAR }
|
|
215
|
-
address_mode_map = { 'border': drv.address_mode.BORDER,
|
|
216
|
-
'clamp': drv.address_mode.CLAMP,
|
|
217
|
-
'mirror': drv.address_mode.MIRROR,
|
|
218
|
-
'wrap': drv.address_mode.WRAP }
|
|
219
|
-
|
|
220
|
-
logging.debug('copy_texture_memory_args called')
|
|
221
|
-
logging.debug('current module: ' + str(self.current_module))
|
|
222
|
-
self.texrefs = []
|
|
223
|
-
for k, v in texmem_args.items():
|
|
224
|
-
tex = self.current_module.get_texref(k)
|
|
225
|
-
self.texrefs.append(tex)
|
|
226
|
-
|
|
227
|
-
logging.debug('copying to texture: ' + str(k))
|
|
228
|
-
if not isinstance(v, dict):
|
|
229
|
-
data = v
|
|
230
|
-
else:
|
|
231
|
-
data = v['array']
|
|
232
|
-
logging.debug('texture to be copied: ')
|
|
233
|
-
logging.debug(data.nbytes)
|
|
234
|
-
logging.debug(data.dtype)
|
|
235
|
-
logging.debug(data.flags)
|
|
236
|
-
|
|
237
|
-
drv.matrix_to_texref(data, tex, order="C")
|
|
238
|
-
|
|
239
|
-
if isinstance(v, dict):
|
|
240
|
-
if 'address_mode' in v and v['address_mode'] is not None:
|
|
241
|
-
# address_mode is set per axis
|
|
242
|
-
amode = v['address_mode']
|
|
243
|
-
if not isinstance(amode, list):
|
|
244
|
-
amode = [ amode ] * data.ndim
|
|
245
|
-
for i, m in enumerate(amode):
|
|
246
|
-
try:
|
|
247
|
-
if m is not None:
|
|
248
|
-
tex.set_address_mode(i, address_mode_map[m])
|
|
249
|
-
except KeyError:
|
|
250
|
-
raise ValueError('Unknown address mode: ' + m)
|
|
251
|
-
if 'filter_mode' in v and v['filter_mode'] is not None:
|
|
252
|
-
fmode = v['filter_mode']
|
|
253
|
-
try:
|
|
254
|
-
tex.set_filter_mode(filter_mode_map[fmode])
|
|
255
|
-
except KeyError:
|
|
256
|
-
raise ValueError('Unknown filter mode: ' + fmode)
|
|
257
|
-
if 'normalized_coordinates' in v and v['normalized_coordinates']:
|
|
258
|
-
tex.set_flags(tex.get_flags() | drv.TRSF_NORMALIZED_COORDINATES)
|
|
259
|
-
"""
|
|
260
|
-
pass
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
def run_kernel(self, func, gpu_args, threads, grid, stream=None):
|
|
264
|
-
"""runs the CUDA kernel passed as 'func'
|
|
265
|
-
|
|
266
|
-
:param func: A PyCuda kernel compiled for this specific kernel configuration
|
|
267
|
-
:type func: pycuda.driver.Function
|
|
268
|
-
|
|
269
|
-
:param gpu_args: A list of arguments to the kernel, order should match the
|
|
270
|
-
order in the code. Allowed values are either variables in global memory
|
|
271
|
-
or single values passed by value.
|
|
272
|
-
:type gpu_args: list( pycuda.driver.DeviceAllocation, np.int32, ...)
|
|
273
|
-
|
|
274
|
-
:param threads: A tuple listing the number of threads in each dimension of
|
|
275
|
-
the thread block
|
|
276
|
-
:type threads: tuple(int, int, int)
|
|
277
|
-
|
|
278
|
-
:param grid: A tuple listing the number of thread blocks in each dimension
|
|
279
|
-
of the grid
|
|
280
|
-
:type grid: tuple(int, int)
|
|
281
|
-
"""
|
|
282
|
-
func(grid, threads, gpu_args, shared_mem=self.smem_size)
|
|
283
|
-
|
|
284
|
-
def memset(self, allocation, value, size):
|
|
285
|
-
"""set the memory in allocation to the value in value
|
|
286
|
-
|
|
287
|
-
:param allocation: A GPU memory allocation unit
|
|
288
|
-
:type allocation: pycuda.driver.DeviceAllocation
|
|
289
|
-
|
|
290
|
-
:param value: The value to set the memory to
|
|
291
|
-
:type value: a single 8-bit unsigned int
|
|
292
|
-
|
|
293
|
-
:param size: The size of to the allocation unit in bytes
|
|
294
|
-
:type size: int
|
|
295
|
-
|
|
296
|
-
"""
|
|
297
|
-
#drv.memset_d8(allocation, value, size)
|
|
298
|
-
allocation = cp.full_like(allocation, value)
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
def memcpy_dtoh(self, dest, src):
|
|
302
|
-
"""perform a device to host memory copy
|
|
303
|
-
|
|
304
|
-
:param dest: A numpy array in host memory to store the data
|
|
305
|
-
:type dest: np.ndarray
|
|
306
|
-
|
|
307
|
-
:param src: A GPU memory allocation unit
|
|
308
|
-
:type src: pycuda.driver.DeviceAllocation
|
|
309
|
-
"""
|
|
310
|
-
if isinstance(src, cp.ndarray):
|
|
311
|
-
dest = cp.ndarray.get()
|
|
312
|
-
else:
|
|
313
|
-
dest = src
|
|
314
|
-
|
|
315
|
-
def memcpy_htod(self, dest, src):
|
|
316
|
-
"""perform a host to device memory copy
|
|
317
|
-
|
|
318
|
-
:param dest: A GPU memory allocation unit
|
|
319
|
-
:type dest: pycuda.driver.DeviceAllocation
|
|
320
|
-
|
|
321
|
-
:param src: A numpy array in host memory to store the data
|
|
322
|
-
:type src: np.ndarray
|
|
323
|
-
"""
|
|
324
|
-
if isinstance(dest, cp.ndarray):
|
|
325
|
-
cp.copyto(dest, src) #might fail if src is not cupy array
|
|
326
|
-
else:
|
|
327
|
-
dest = src
|
|
328
|
-
|
|
329
|
-
units = {'time': 'ms',
|
|
330
|
-
'power': 's,mW',
|
|
331
|
-
'energy': 'J'}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|