kernel-tuner 0.3.2__tar.gz → 0.4.0__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.2 → kernel_tuner-0.4.0}/PKG-INFO +3 -3
- kernel_tuner-0.4.0/kernel_tuner/__init__.py +5 -0
- {kernel_tuner-0.3.2 → kernel_tuner-0.4.0}/kernel_tuner/c.py +19 -9
- {kernel_tuner-0.3.2 → kernel_tuner-0.4.0}/kernel_tuner/core.py +164 -43
- {kernel_tuner-0.3.2 → kernel_tuner-0.4.0}/kernel_tuner/cuda.py +54 -45
- kernel_tuner-0.4.0/kernel_tuner/cupy.py +303 -0
- kernel_tuner-0.4.0/kernel_tuner/integration.py +409 -0
- {kernel_tuner-0.3.2 → kernel_tuner-0.4.0}/kernel_tuner/interface.py +181 -174
- {kernel_tuner-0.3.2 → kernel_tuner-0.4.0}/kernel_tuner/kernelbuilder.py +19 -4
- {kernel_tuner-0.3.2 → kernel_tuner-0.4.0}/kernel_tuner/nvml.py +115 -9
- kernel_tuner-0.4.0/kernel_tuner/observers.py +78 -0
- {kernel_tuner-0.3.2 → kernel_tuner-0.4.0}/kernel_tuner/opencl.py +50 -12
- {kernel_tuner-0.3.2 → kernel_tuner-0.4.0}/kernel_tuner/runners/sequential.py +11 -12
- kernel_tuner-0.4.0/kernel_tuner/runners/simulation.py +244 -0
- {kernel_tuner-0.3.2 → kernel_tuner-0.4.0}/kernel_tuner/strategies/basinhopping.py +4 -2
- {kernel_tuner-0.3.2 → kernel_tuner-0.4.0}/kernel_tuner/strategies/brute_force.py +5 -3
- {kernel_tuner-0.3.2 → kernel_tuner-0.4.0}/kernel_tuner/strategies/diff_evo.py +5 -3
- {kernel_tuner-0.3.2 → kernel_tuner-0.4.0}/kernel_tuner/strategies/firefly_algorithm.py +4 -3
- {kernel_tuner-0.3.2 → kernel_tuner-0.4.0}/kernel_tuner/strategies/genetic_algorithm.py +40 -30
- {kernel_tuner-0.3.2 → kernel_tuner-0.4.0}/kernel_tuner/strategies/minimize.py +30 -29
- {kernel_tuner-0.3.2 → kernel_tuner-0.4.0}/kernel_tuner/strategies/random_sample.py +6 -8
- {kernel_tuner-0.3.2 → kernel_tuner-0.4.0}/kernel_tuner/strategies/simulated_annealing.py +8 -6
- {kernel_tuner-0.3.2 → kernel_tuner-0.4.0}/kernel_tuner/util.py +204 -100
- {kernel_tuner-0.3.2 → kernel_tuner-0.4.0}/kernel_tuner/wrappers.py +1 -1
- {kernel_tuner-0.3.2 → kernel_tuner-0.4.0}/kernel_tuner.egg-info/PKG-INFO +3 -3
- {kernel_tuner-0.3.2 → kernel_tuner-0.4.0}/kernel_tuner.egg-info/SOURCES.txt +7 -0
- {kernel_tuner-0.3.2 → kernel_tuner-0.4.0}/kernel_tuner.egg-info/requires.txt +1 -0
- {kernel_tuner-0.3.2 → kernel_tuner-0.4.0}/setup.py +5 -4
- kernel_tuner-0.4.0/test/test_c_functions.py +282 -0
- {kernel_tuner-0.3.2 → kernel_tuner-0.4.0}/test/test_core.py +71 -28
- kernel_tuner-0.4.0/test/test_cuda_functions.py +64 -0
- kernel_tuner-0.4.0/test/test_cuda_mocked.py +144 -0
- kernel_tuner-0.4.0/test/test_integration.py +167 -0
- {kernel_tuner-0.3.2 → kernel_tuner-0.4.0}/test/test_interface.py +21 -15
- kernel_tuner-0.4.0/test/test_kernelbuilder.py +57 -0
- {kernel_tuner-0.3.2 → kernel_tuner-0.4.0}/test/test_minimize.py +3 -7
- kernel_tuner-0.4.0/test/test_observers.py +36 -0
- {kernel_tuner-0.3.2 → kernel_tuner-0.4.0}/test/test_opencl_functions.py +16 -15
- {kernel_tuner-0.3.2 → kernel_tuner-0.4.0}/test/test_runners.py +39 -42
- {kernel_tuner-0.3.2 → kernel_tuner-0.4.0}/test/test_util_functions.py +125 -79
- kernel_tuner-0.3.2/kernel_tuner/__init__.py +0 -3
- kernel_tuner-0.3.2/test/test_c_functions.py +0 -218
- kernel_tuner-0.3.2/test/test_cuda_functions.py +0 -61
- kernel_tuner-0.3.2/test/test_cuda_mocked.py +0 -138
- {kernel_tuner-0.3.2 → kernel_tuner-0.4.0}/LICENSE +0 -0
- {kernel_tuner-0.3.2 → kernel_tuner-0.4.0}/MANIFEST.in +0 -0
- {kernel_tuner-0.3.2 → kernel_tuner-0.4.0}/README.rst +0 -0
- {kernel_tuner-0.3.2 → kernel_tuner-0.4.0}/kernel_tuner/runners/__init__.py +0 -0
- {kernel_tuner-0.3.2 → kernel_tuner-0.4.0}/kernel_tuner/strategies/__init__.py +0 -0
- {kernel_tuner-0.3.2 → kernel_tuner-0.4.0}/kernel_tuner/strategies/bayes_opt.py +0 -0
- {kernel_tuner-0.3.2 → kernel_tuner-0.4.0}/kernel_tuner/strategies/pso.py +0 -0
- {kernel_tuner-0.3.2 → kernel_tuner-0.4.0}/kernel_tuner.egg-info/dependency_links.txt +0 -0
- {kernel_tuner-0.3.2 → kernel_tuner-0.4.0}/kernel_tuner.egg-info/top_level.txt +0 -0
- {kernel_tuner-0.3.2 → kernel_tuner-0.4.0}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: kernel_tuner
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
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
|
|
@@ -194,13 +194,13 @@ Classifier: Intended Audience :: Education
|
|
|
194
194
|
Classifier: License :: OSI Approved :: Apache Software License
|
|
195
195
|
Classifier: Natural Language :: English
|
|
196
196
|
Classifier: Operating System :: POSIX :: Linux
|
|
197
|
-
Classifier: Programming Language :: Python :: 3.5
|
|
198
197
|
Classifier: Programming Language :: Python :: 3.6
|
|
199
198
|
Classifier: Programming Language :: Python :: 3.7
|
|
199
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
200
200
|
Classifier: Topic :: Scientific/Engineering
|
|
201
201
|
Classifier: Topic :: Software Development
|
|
202
202
|
Classifier: Topic :: System :: Distributed Computing
|
|
203
|
-
Classifier: Development Status ::
|
|
203
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
204
204
|
Provides-Extra: doc
|
|
205
205
|
Provides-Extra: cuda
|
|
206
206
|
Provides-Extra: opencl
|
|
@@ -73,6 +73,12 @@ class CFunctions(object):
|
|
|
73
73
|
self.env = env
|
|
74
74
|
self.name = platform.processor()
|
|
75
75
|
|
|
76
|
+
def __enter__(self):
|
|
77
|
+
return self
|
|
78
|
+
|
|
79
|
+
def __exit__(self, *exc):
|
|
80
|
+
pass
|
|
81
|
+
|
|
76
82
|
def ready_argument_list(self, arguments):
|
|
77
83
|
"""ready argument list to be passed to the C function
|
|
78
84
|
|
|
@@ -110,12 +116,9 @@ class CFunctions(object):
|
|
|
110
116
|
def compile(self, kernel_instance):
|
|
111
117
|
"""call the C compiler to compile the kernel, return the function
|
|
112
118
|
|
|
113
|
-
:param
|
|
114
|
-
|
|
115
|
-
:type
|
|
116
|
-
|
|
117
|
-
:param kernel_string: The C code that contains the function `kernel_name`
|
|
118
|
-
:type kernel_string: string
|
|
119
|
+
:param kernel_instance: An object representing the specific instance of the tunable kernel
|
|
120
|
+
in the parameter space.
|
|
121
|
+
:type kernel_instance: kernel_tuner.core.KernelInstance
|
|
119
122
|
|
|
120
123
|
:returns: An ctypes function that can be called directly.
|
|
121
124
|
:rtype: ctypes._FuncPtr
|
|
@@ -123,6 +126,7 @@ class CFunctions(object):
|
|
|
123
126
|
logging.debug('compiling ' + kernel_instance.name)
|
|
124
127
|
|
|
125
128
|
kernel_string = kernel_instance.kernel_string
|
|
129
|
+
kernel_name = kernel_instance.name
|
|
126
130
|
|
|
127
131
|
if self.lib != None:
|
|
128
132
|
self.cleanup_lib()
|
|
@@ -136,7 +140,10 @@ class CFunctions(object):
|
|
|
136
140
|
if self.compiler == "pgfortran":
|
|
137
141
|
compiler_options.append("-mp")
|
|
138
142
|
else:
|
|
139
|
-
|
|
143
|
+
if "#pragma acc" in kernel_string or "!$acc" in kernel_string:
|
|
144
|
+
compiler_options.append("-fopenacc")
|
|
145
|
+
else:
|
|
146
|
+
compiler_options.append("-fopenmp")
|
|
140
147
|
|
|
141
148
|
#if filename is known, use that one
|
|
142
149
|
suffix = kernel_instance.kernel_source.get_user_suffix()
|
|
@@ -191,6 +198,10 @@ class CFunctions(object):
|
|
|
191
198
|
kernel_name = match.group(1) + "_mp_" + kernel_name + "_"
|
|
192
199
|
elif self.compiler == "pgfortran":
|
|
193
200
|
kernel_name = match.group(1) + "_" + kernel_name + "_"
|
|
201
|
+
else:
|
|
202
|
+
#for functions outside of modules
|
|
203
|
+
if self.compiler in ["gfortran", "ftn", "ifort", "pgfortran"]:
|
|
204
|
+
kernel_name = kernel_name + "_"
|
|
194
205
|
|
|
195
206
|
try:
|
|
196
207
|
write_file(source_file, kernel_string)
|
|
@@ -202,9 +213,8 @@ class CFunctions(object):
|
|
|
202
213
|
subprocess.check_call([self.compiler, "-c", source_file] + compiler_options + ["-o", filename + ".o"])
|
|
203
214
|
subprocess.check_call([self.compiler, filename + ".o"] + compiler_options + ["-shared", "-o", filename + lib_extension] + lib_args)
|
|
204
215
|
|
|
205
|
-
|
|
206
216
|
self.lib = numpy.ctypeslib.load_library(filename, '.')
|
|
207
|
-
func = getattr(self.lib,
|
|
217
|
+
func = getattr(self.lib, kernel_name)
|
|
208
218
|
func.restype = C.c_float
|
|
209
219
|
|
|
210
220
|
finally:
|
|
@@ -1,18 +1,27 @@
|
|
|
1
1
|
""" Module for grouping the core functionality needed by most runners """
|
|
2
2
|
from __future__ import print_function
|
|
3
3
|
|
|
4
|
+
import re
|
|
4
5
|
from collections import namedtuple
|
|
5
6
|
import resource
|
|
6
7
|
import logging
|
|
7
|
-
import numpy
|
|
8
|
+
import numpy as np
|
|
8
9
|
|
|
10
|
+
try:
|
|
11
|
+
import cupy as cp
|
|
12
|
+
except ImportError:
|
|
13
|
+
cp = np
|
|
14
|
+
|
|
15
|
+
from kernel_tuner.cupy import CupyFunctions
|
|
9
16
|
from kernel_tuner.cuda import CudaFunctions
|
|
10
17
|
from kernel_tuner.opencl import OpenCLFunctions
|
|
11
18
|
from kernel_tuner.c import CFunctions
|
|
19
|
+
from kernel_tuner.nvml import NVMLObserver
|
|
12
20
|
import kernel_tuner.util as util
|
|
13
21
|
|
|
14
22
|
_KernelInstance = namedtuple("_KernelInstance", ["name", "kernel_source", "kernel_string", "temp_files", "threads", "grid", "params", "arguments"])
|
|
15
23
|
|
|
24
|
+
|
|
16
25
|
class KernelInstance(_KernelInstance):
|
|
17
26
|
"""Class that represents the specific parameterized instance of a kernel"""
|
|
18
27
|
|
|
@@ -40,10 +49,11 @@ class KernelSource(object):
|
|
|
40
49
|
must be filenames.
|
|
41
50
|
"""
|
|
42
51
|
|
|
43
|
-
def __init__(self, kernel_sources, lang):
|
|
52
|
+
def __init__(self, kernel_name, kernel_sources, lang):
|
|
44
53
|
if not isinstance(kernel_sources, list):
|
|
45
54
|
kernel_sources = [kernel_sources]
|
|
46
55
|
self.kernel_sources = kernel_sources
|
|
56
|
+
self.kernel_name = kernel_name
|
|
47
57
|
if lang is None:
|
|
48
58
|
if callable(self.kernel_sources[0]):
|
|
49
59
|
raise TypeError("Please specify language when using a code generator function")
|
|
@@ -74,7 +84,6 @@ class KernelSource(object):
|
|
|
74
84
|
kernel_source = self.kernel_sources[index]
|
|
75
85
|
return util.get_kernel_string(kernel_source, params)
|
|
76
86
|
|
|
77
|
-
|
|
78
87
|
def prepare_list_of_files(self, kernel_name, params, grid, threads, block_size_names):
|
|
79
88
|
""" prepare the kernel string along with any additional files
|
|
80
89
|
|
|
@@ -134,7 +143,6 @@ class KernelSource(object):
|
|
|
134
143
|
|
|
135
144
|
return name, kernel_string, temp_files
|
|
136
145
|
|
|
137
|
-
|
|
138
146
|
def get_user_suffix(self, index=0):
|
|
139
147
|
""" Get the suffix of the kernel filename, if the user specified one. Return None otherwise.
|
|
140
148
|
"""
|
|
@@ -154,7 +162,11 @@ class KernelSource(object):
|
|
|
154
162
|
if suffix is not None:
|
|
155
163
|
return suffix
|
|
156
164
|
|
|
157
|
-
_suffixes = {
|
|
165
|
+
_suffixes = {
|
|
166
|
+
'CUDA': '.cu',
|
|
167
|
+
'OpenCL': '.cl',
|
|
168
|
+
'C': '.c'
|
|
169
|
+
}
|
|
158
170
|
try:
|
|
159
171
|
return _suffixes[self.lang]
|
|
160
172
|
except KeyError:
|
|
@@ -175,7 +187,7 @@ class KernelSource(object):
|
|
|
175
187
|
class DeviceInterface(object):
|
|
176
188
|
"""Class that offers a High-Level Device Interface to the rest of the Kernel Tuner"""
|
|
177
189
|
|
|
178
|
-
def __init__(self, kernel_source, device=0, platform=0, quiet=False, compiler=None, compiler_options=None, iterations=7):
|
|
190
|
+
def __init__(self, kernel_source, device=0, platform=0, quiet=False, compiler=None, compiler_options=None, iterations=7, observers=None):
|
|
179
191
|
""" Instantiate the DeviceInterface, based on language in kernel source
|
|
180
192
|
|
|
181
193
|
:param kernel_source The kernel sources
|
|
@@ -210,13 +222,24 @@ class DeviceInterface(object):
|
|
|
210
222
|
logging.debug('DeviceInterface instantiated, lang=%s', lang)
|
|
211
223
|
|
|
212
224
|
if lang == "CUDA":
|
|
213
|
-
dev = CudaFunctions(device, compiler_options=compiler_options, iterations=iterations)
|
|
225
|
+
dev = CudaFunctions(device, compiler_options=compiler_options, iterations=iterations, observers=observers)
|
|
226
|
+
elif lang.upper() == "CUPY":
|
|
227
|
+
dev = CupyFunctions(device, compiler_options=compiler_options, iterations=iterations, observers=observers)
|
|
214
228
|
elif lang == "OpenCL":
|
|
215
|
-
dev = OpenCLFunctions(device, platform, compiler_options=compiler_options, iterations=iterations)
|
|
229
|
+
dev = OpenCLFunctions(device, platform, compiler_options=compiler_options, iterations=iterations, observers=observers)
|
|
216
230
|
elif lang == "C":
|
|
217
231
|
dev = CFunctions(compiler=compiler, compiler_options=compiler_options, iterations=iterations)
|
|
218
232
|
else:
|
|
219
233
|
raise Exception("Sorry, support for languages other than CUDA, OpenCL, or C is not implemented yet")
|
|
234
|
+
|
|
235
|
+
#look for NVMLObserver in observers, if present, enable special tunable parameters through nvml
|
|
236
|
+
self.use_nvml = False
|
|
237
|
+
if observers:
|
|
238
|
+
for obs in observers:
|
|
239
|
+
if isinstance(obs, NVMLObserver):
|
|
240
|
+
self.nvml = obs.nvml
|
|
241
|
+
self.use_nvml = True
|
|
242
|
+
|
|
220
243
|
self.lang = lang
|
|
221
244
|
self.dev = dev
|
|
222
245
|
self.units = dev.units
|
|
@@ -224,12 +247,29 @@ class DeviceInterface(object):
|
|
|
224
247
|
if not quiet:
|
|
225
248
|
print("Using: " + self.dev.name)
|
|
226
249
|
|
|
250
|
+
dev.__enter__()
|
|
251
|
+
|
|
252
|
+
def __enter__(self):
|
|
253
|
+
return self
|
|
254
|
+
|
|
227
255
|
def benchmark(self, func, gpu_args, instance, verbose):
|
|
228
256
|
"""benchmark the kernel instance"""
|
|
229
257
|
logging.debug('benchmark ' + instance.name)
|
|
230
258
|
logging.debug('thread block dimensions x,y,z=%d,%d,%d', *instance.threads)
|
|
231
259
|
logging.debug('grid dimensions x,y,z=%d,%d,%d', *instance.grid)
|
|
232
260
|
|
|
261
|
+
if self.use_nvml:
|
|
262
|
+
if "nvml_pwr_limit" in instance.params:
|
|
263
|
+
new_limit = int(instance.params["nvml_pwr_limit"] * 1000) #user specifies in Watt, but nvml uses milliWatt
|
|
264
|
+
if self.nvml.pwr_limit != new_limit:
|
|
265
|
+
self.nvml.pwr_limit = new_limit
|
|
266
|
+
if "nvml_gr_clock" in instance.params:
|
|
267
|
+
self.nvml.gr_clock = instance.params["nvml_gr_clock"]
|
|
268
|
+
if "nvml_sm_clock" in instance.params:
|
|
269
|
+
self.nvml.sm_clock = instance.params["nvml_sm_clock"]
|
|
270
|
+
if "nvml_mem_clock" in instance.params:
|
|
271
|
+
self.nvml.mem_clock = instance.params["nvml_mem_clock"]
|
|
272
|
+
|
|
233
273
|
result = None
|
|
234
274
|
try:
|
|
235
275
|
result = self.dev.benchmark(func, gpu_args, instance.threads, instance.grid)
|
|
@@ -260,20 +300,19 @@ class DeviceInterface(object):
|
|
|
260
300
|
#re-copy original contents of output arguments to GPU memory, to overwrite any changes
|
|
261
301
|
#by earlier kernel runs
|
|
262
302
|
for i, arg in enumerate(instance.arguments):
|
|
263
|
-
if verify or answer[i] is not None:
|
|
264
|
-
|
|
265
|
-
self.dev.memcpy_htod(gpu_args[i], arg)
|
|
303
|
+
if verify or answer[i] is not None and isinstance(arg, (np.ndarray, cp.ndarray)):
|
|
304
|
+
self.dev.memcpy_htod(gpu_args[i], arg)
|
|
266
305
|
|
|
267
306
|
#run the kernel
|
|
268
307
|
check = self.run_kernel(func, gpu_args, instance)
|
|
269
308
|
if not check:
|
|
270
|
-
return True
|
|
309
|
+
return True #runtime failure occured that should be ignored, skip correctness check
|
|
271
310
|
|
|
272
311
|
#retrieve gpu results to host memory
|
|
273
312
|
result_host = []
|
|
274
313
|
for i, arg in enumerate(instance.arguments):
|
|
275
|
-
if (verify or answer[i] is not None) and isinstance(arg,
|
|
276
|
-
result_host.append(
|
|
314
|
+
if (verify or answer[i] is not None) and isinstance(arg, (np.ndarray, cp.ndarray)):
|
|
315
|
+
result_host.append(np.zeros_like(arg))
|
|
277
316
|
self.dev.memcpy_dtoh(result_host[-1], gpu_args[i])
|
|
278
317
|
else:
|
|
279
318
|
result_host.append(None)
|
|
@@ -294,7 +333,7 @@ class DeviceInterface(object):
|
|
|
294
333
|
instance_string = util.get_instance_string(params)
|
|
295
334
|
|
|
296
335
|
logging.debug('compile_and_benchmark ' + instance_string)
|
|
297
|
-
mem_usage = round(resource.getrusage(resource.RUSAGE_SELF).ru_maxrss/1024.0, 1)
|
|
336
|
+
mem_usage = round(resource.getrusage(resource.RUSAGE_SELF).ru_maxrss / 1024.0, 1)
|
|
298
337
|
logging.debug('Memory usage : %2.2f MB', mem_usage)
|
|
299
338
|
|
|
300
339
|
verbose = tuning_options.verbose
|
|
@@ -385,13 +424,18 @@ class DeviceInterface(object):
|
|
|
385
424
|
|
|
386
425
|
#setup thread block and grid dimensions
|
|
387
426
|
threads, grid = util.setup_block_and_grid(kernel_options.problem_size, grid_div, params, kernel_options.block_size_names)
|
|
388
|
-
if
|
|
427
|
+
if np.prod(threads) > self.dev.max_threads:
|
|
389
428
|
if verbose:
|
|
390
429
|
print("skipping config", instance_string, "reason: too many threads per block")
|
|
391
430
|
return None
|
|
392
431
|
|
|
393
432
|
#obtain the kernel_string and prepare additional files, if any
|
|
394
|
-
name, kernel_string, temp_files = kernel_source.prepare_list_of_files(kernel_options.kernel_name, params, grid, threads,
|
|
433
|
+
name, kernel_string, temp_files = kernel_source.prepare_list_of_files(kernel_options.kernel_name, params, grid, threads,
|
|
434
|
+
kernel_options.block_size_names)
|
|
435
|
+
|
|
436
|
+
#check for templated kernel
|
|
437
|
+
if kernel_source.lang == "CUDA" and "<" in name and ">" in name:
|
|
438
|
+
kernel_string, name = wrap_templated_kernel(kernel_string, name)
|
|
395
439
|
|
|
396
440
|
#collect everything we know about this instance and return it
|
|
397
441
|
return KernelInstance(name, kernel_source, kernel_string, temp_files, threads, grid, params, kernel_options.arguments)
|
|
@@ -425,45 +469,37 @@ class DeviceInterface(object):
|
|
|
425
469
|
raise e
|
|
426
470
|
return True
|
|
427
471
|
|
|
428
|
-
|
|
429
|
-
def __del__(self):
|
|
472
|
+
def __exit__(self, *exc):
|
|
430
473
|
if hasattr(self, 'dev'):
|
|
431
|
-
|
|
432
|
-
|
|
474
|
+
self.dev.__exit__(*exc)
|
|
433
475
|
|
|
434
476
|
|
|
435
477
|
def _default_verify_function(instance, answer, result_host, atol, verbose):
|
|
436
|
-
"""default verify function based on
|
|
478
|
+
"""default verify function based on np.allclose"""
|
|
437
479
|
|
|
438
480
|
#first check if the length is the same
|
|
439
481
|
if len(instance.arguments) != len(answer):
|
|
440
482
|
raise TypeError("The length of argument list and provided results do not match.")
|
|
441
483
|
#for each element in the argument list, check if the types match
|
|
442
484
|
for i, arg in enumerate(instance.arguments):
|
|
443
|
-
if answer[i] is not None:
|
|
444
|
-
if isinstance(answer[i],
|
|
485
|
+
if answer[i] is not None: #skip None elements in the answer list
|
|
486
|
+
if isinstance(answer[i], (np.ndarray, cp.ndarray)) and isinstance(arg, (np.ndarray, cp.ndarray)):
|
|
445
487
|
if answer[i].dtype != arg.dtype:
|
|
446
|
-
raise TypeError("Element " + str(i)
|
|
447
|
-
+ "
|
|
448
|
-
+ str(answer[i].dtype) + " != " + str(arg.dtype) + ".")
|
|
488
|
+
raise TypeError("Element " + str(i) + " of the expected results list is not of the same dtype as the kernel output: " +
|
|
489
|
+
str(answer[i].dtype) + " != " + str(arg.dtype) + ".")
|
|
449
490
|
if answer[i].size != arg.size:
|
|
450
|
-
raise TypeError("Element " + str(i)
|
|
451
|
-
+ "
|
|
452
|
-
|
|
453
|
-
+ str(answer[i].size) + " != " + str(arg.size) + ".")
|
|
454
|
-
elif isinstance(answer[i], numpy.number) and isinstance(arg, numpy.number):
|
|
491
|
+
raise TypeError("Element " + str(i) + " of the expected results list has a size different from " + "the kernel argument: " +
|
|
492
|
+
str(answer[i].size) + " != " + str(arg.size) + ".")
|
|
493
|
+
elif isinstance(answer[i], np.number) and isinstance(arg, np.number):
|
|
455
494
|
if answer[i].dtype != arg.dtype:
|
|
456
|
-
raise TypeError("Element " + str(i)
|
|
457
|
-
|
|
458
|
-
+ str(answer[i].dtype) + " != " + str(arg.dtype) + ".")
|
|
495
|
+
raise TypeError("Element " + str(i) + " of the expected results list is not the same as the kernel output: " + str(answer[i].dtype) +
|
|
496
|
+
" != " + str(arg.dtype) + ".")
|
|
459
497
|
else:
|
|
460
498
|
#either answer[i] and argument have different types or answer[i] is not a numpy type
|
|
461
|
-
if not isinstance(answer[i],
|
|
462
|
-
raise TypeError("Element " + str(i)
|
|
463
|
-
+ " of expected results list is not a numpy array or numpy scalar.")
|
|
499
|
+
if not isinstance(answer[i], np.ndarray) or not isinstance(answer[i], np.number):
|
|
500
|
+
raise TypeError("Element " + str(i) + " of expected results list is not a numpy array or numpy scalar.")
|
|
464
501
|
else:
|
|
465
|
-
raise TypeError("Element " + str(i)
|
|
466
|
-
+ " of expected results list and kernel arguments have different types.")
|
|
502
|
+
raise TypeError("Element " + str(i) + " of expected results list and kernel arguments have different types.")
|
|
467
503
|
|
|
468
504
|
def _ravel(a):
|
|
469
505
|
if hasattr(a, 'ravel') and len(a.shape) > 1:
|
|
@@ -482,13 +518,16 @@ def _default_verify_function(instance, answer, result_host, atol, verbose):
|
|
|
482
518
|
|
|
483
519
|
result = _ravel(result_host[i])
|
|
484
520
|
expected = _flatten(expected)
|
|
485
|
-
|
|
521
|
+
if any([isinstance(array, cp.ndarray) for array in [expected, result]]):
|
|
522
|
+
output_test = cp.allclose(expected, result, atol=atol)
|
|
523
|
+
else:
|
|
524
|
+
output_test = np.allclose(expected, result, atol=atol)
|
|
486
525
|
|
|
487
526
|
if not output_test and verbose:
|
|
488
527
|
print("Error: " + util.get_config_string(instance.params) + " detected during correctness check")
|
|
489
|
-
print("this error occured when checking value of the %oth kernel argument" % (i,))
|
|
528
|
+
print("this error occured when checking value of the %oth kernel argument" % (i, ))
|
|
490
529
|
print("Printing kernel output and expected result, set verbose=False to suppress this debug print")
|
|
491
|
-
|
|
530
|
+
np.set_printoptions(edgeitems=50)
|
|
492
531
|
print("Kernel output:")
|
|
493
532
|
print(result)
|
|
494
533
|
print("Expected:")
|
|
@@ -499,3 +538,85 @@ def _default_verify_function(instance, answer, result_host, atol, verbose):
|
|
|
499
538
|
logging.debug('correctness check has found a correctness issue')
|
|
500
539
|
|
|
501
540
|
return correct
|
|
541
|
+
|
|
542
|
+
|
|
543
|
+
|
|
544
|
+
#these functions facilitate compiling templated kernels with PyCuda
|
|
545
|
+
def split_argument_list(argument_list):
|
|
546
|
+
"""split all arguments in a list into types and names"""
|
|
547
|
+
regex = r"(.*[\s*]+)(.*)?"
|
|
548
|
+
type_list = []
|
|
549
|
+
name_list = []
|
|
550
|
+
for arg in argument_list:
|
|
551
|
+
match = re.match(regex, arg, re.S)
|
|
552
|
+
if not match:
|
|
553
|
+
raise ValueError("error parsing templated kernel argument list")
|
|
554
|
+
type_list.append(re.sub(r"\s+", " ", match.group(1).strip(), re.S))
|
|
555
|
+
name_list.append(match.group(2).strip())
|
|
556
|
+
return type_list, name_list
|
|
557
|
+
|
|
558
|
+
def apply_template_typenames(type_list, templated_typenames):
|
|
559
|
+
"""replace the typename tokens in type_list with their templated typenames"""
|
|
560
|
+
def replace_typename_token(matchobj):
|
|
561
|
+
"""function for a whitespace preserving token regex replace"""
|
|
562
|
+
#replace only the match, leaving the whitespace around it as is
|
|
563
|
+
return matchobj.group(1) + templated_typenames[matchobj.group(2)] + matchobj.group(3)
|
|
564
|
+
for i, arg_type in enumerate(type_list):
|
|
565
|
+
for k,v in templated_typenames.items():
|
|
566
|
+
#if the templated typename occurs as a token in the string, meaning that it is enclosed in
|
|
567
|
+
#beginning of string or whitespace, and end of string, whitespace or star
|
|
568
|
+
regex = r"(^|\s+)(" + k + r")($|\s+|\*)"
|
|
569
|
+
sub = re.sub(regex, replace_typename_token, arg_type, re.S)
|
|
570
|
+
type_list[i] = sub
|
|
571
|
+
|
|
572
|
+
def get_templated_typenames(template_parameters, template_arguments):
|
|
573
|
+
"""based on the template parameters and arguments, create dict with templated typenames"""
|
|
574
|
+
templated_typenames = {}
|
|
575
|
+
for i, param in enumerate(template_parameters):
|
|
576
|
+
if "typename " in param:
|
|
577
|
+
typename = param[9:]
|
|
578
|
+
templated_typenames[typename] = template_arguments[i]
|
|
579
|
+
return templated_typenames
|
|
580
|
+
|
|
581
|
+
def wrap_templated_kernel(kernel_string, kernel_name):
|
|
582
|
+
"""rewrite kernel_string to insert wrapper function for templated kernel"""
|
|
583
|
+
#parse kernel_name to find template_arguments and real kernel name
|
|
584
|
+
name = kernel_name.split("<")[0]
|
|
585
|
+
template_arguments = re.search(r".*?<(.*)>", kernel_name, re.S).group(1).split(',')
|
|
586
|
+
|
|
587
|
+
#parse templated kernel definition
|
|
588
|
+
#relatively strict regex that does not allow nested template parameters like vector<TF>
|
|
589
|
+
#within the template parameter list
|
|
590
|
+
regex = r"template\s*<([^>]*?)>\s*__global__\s+void\s+" + name + r"\s*\((.*?)\)\s*\{"
|
|
591
|
+
match = re.search(regex, kernel_string, re.S)
|
|
592
|
+
if not match:
|
|
593
|
+
raise ValueError("could not find templated kernel definition")
|
|
594
|
+
|
|
595
|
+
template_parameters = match.group(1).split(',')
|
|
596
|
+
argument_list = match.group(2).split(',')
|
|
597
|
+
argument_list = [s.strip() for s in argument_list] #remove extra whitespace around 'type name' strings
|
|
598
|
+
|
|
599
|
+
type_list, name_list = split_argument_list(argument_list)
|
|
600
|
+
|
|
601
|
+
templated_typenames = get_templated_typenames(template_parameters, template_arguments)
|
|
602
|
+
apply_template_typenames(type_list, templated_typenames)
|
|
603
|
+
|
|
604
|
+
#replace __global__ with __device__ in the templated kernel definition
|
|
605
|
+
#could do a more precise replace, but __global__ cannot be used elsewhere in the definition
|
|
606
|
+
definition = match.group(0).replace("__global__", "__device__")
|
|
607
|
+
|
|
608
|
+
#generate code for the compile-time template instantiation
|
|
609
|
+
template_instantiation = f"template __device__ void {kernel_name}(" + ", ".join(type_list) + ");\n"
|
|
610
|
+
|
|
611
|
+
#generate code for the wrapper kernel
|
|
612
|
+
new_arg_list = ", ".join([" ".join((a, b)) for a, b in zip(type_list, name_list)])
|
|
613
|
+
wrapper_function = "\nextern \"C\" __global__ void " + name + "_wrapper(" + new_arg_list + ") {\n " + \
|
|
614
|
+
kernel_name + "(" + ", ".join(name_list) + ");\n}\n"
|
|
615
|
+
|
|
616
|
+
#copy kernel_string, replace definition and append template instantiation and wrapper function
|
|
617
|
+
new_kernel_string = kernel_string[:]
|
|
618
|
+
new_kernel_string = new_kernel_string.replace(match.group(0), definition)
|
|
619
|
+
new_kernel_string += "\n" + template_instantiation
|
|
620
|
+
new_kernel_string += wrapper_function
|
|
621
|
+
|
|
622
|
+
return new_kernel_string, name + "_wrapper"
|
|
@@ -3,8 +3,9 @@ from __future__ import print_function
|
|
|
3
3
|
|
|
4
4
|
import logging
|
|
5
5
|
import time
|
|
6
|
-
import numpy
|
|
6
|
+
import numpy as np
|
|
7
7
|
|
|
8
|
+
from kernel_tuner.observers import BenchmarkObserver
|
|
8
9
|
from kernel_tuner.nvml import nvml
|
|
9
10
|
|
|
10
11
|
#embedded in try block to be able to generate documentation
|
|
@@ -23,10 +24,28 @@ except ImportError:
|
|
|
23
24
|
DynamicSourceModule = None
|
|
24
25
|
|
|
25
26
|
|
|
27
|
+
class CudaRuntimeObserver(BenchmarkObserver):
|
|
28
|
+
""" Observer that measures time using CUDA events during benchmarking """
|
|
29
|
+
def __init__(self, dev):
|
|
30
|
+
self.dev = dev
|
|
31
|
+
self.stream = dev.stream
|
|
32
|
+
self.start = dev.start
|
|
33
|
+
self.end = dev.end
|
|
34
|
+
self.times = []
|
|
35
|
+
|
|
36
|
+
def after_finish(self):
|
|
37
|
+
self.times.append(self.end.time_since(self.start)) #ms
|
|
38
|
+
|
|
39
|
+
def get_results(self):
|
|
40
|
+
results = {"time": np.average(self.times), "times": self.times.copy()}
|
|
41
|
+
self.times = []
|
|
42
|
+
return results
|
|
43
|
+
|
|
44
|
+
|
|
26
45
|
class CudaFunctions(object):
|
|
27
46
|
"""Class that groups the CUDA functions on maintains state about the device"""
|
|
28
47
|
|
|
29
|
-
def __init__(self, device=0, iterations=7, compiler_options=None):
|
|
48
|
+
def __init__(self, device=0, iterations=7, compiler_options=None, observers=None):
|
|
30
49
|
"""instantiate CudaFunctions object used for interacting with the CUDA device
|
|
31
50
|
|
|
32
51
|
Instantiating this object will inspect and store certain device properties at
|
|
@@ -48,13 +67,6 @@ class CudaFunctions(object):
|
|
|
48
67
|
drv.init()
|
|
49
68
|
self.context = drv.Device(device).make_context()
|
|
50
69
|
|
|
51
|
-
try:
|
|
52
|
-
self.nvml = nvml(device)
|
|
53
|
-
self.nvml.pwr_usage()
|
|
54
|
-
self.use_nvml = True
|
|
55
|
-
except:
|
|
56
|
-
self.use_nvml = False
|
|
57
|
-
|
|
58
70
|
#inspect device properties
|
|
59
71
|
devprops = {str(k): v for (k, v) in self.context.get_device().get_attributes().items()}
|
|
60
72
|
self.max_threads = devprops['MAX_THREADS_PER_BLOCK']
|
|
@@ -64,6 +76,7 @@ class CudaFunctions(object):
|
|
|
64
76
|
self.cc = str(cc[0])+str(cc[1])
|
|
65
77
|
self.iterations = iterations
|
|
66
78
|
self.current_module = None
|
|
79
|
+
self.func = None
|
|
67
80
|
self.compiler_options = compiler_options or []
|
|
68
81
|
|
|
69
82
|
#select PyCUDA source module
|
|
@@ -74,12 +87,20 @@ class CudaFunctions(object):
|
|
|
74
87
|
if not self.source_mod:
|
|
75
88
|
raise ImportError("Error: pycuda not correctly installed, please ensure pycuda is installed on the same CUDA installation as you're using right now")
|
|
76
89
|
|
|
77
|
-
#create a stream
|
|
90
|
+
#create a stream and events
|
|
78
91
|
self.stream = drv.Stream()
|
|
92
|
+
self.start = drv.Event()
|
|
93
|
+
self.end = drv.Event()
|
|
79
94
|
|
|
80
95
|
#default dynamically allocated shared memory size, can be overwritten using smem_args
|
|
81
96
|
self.smem_size = 0
|
|
82
97
|
|
|
98
|
+
#setup observers
|
|
99
|
+
self.observers = observers or []
|
|
100
|
+
self.observers.append(CudaRuntimeObserver(self))
|
|
101
|
+
for obs in self.observers:
|
|
102
|
+
obs.register_device(self)
|
|
103
|
+
|
|
83
104
|
#collect environment information
|
|
84
105
|
env = dict()
|
|
85
106
|
env["device_name"] = self.context.get_device().name()
|
|
@@ -91,7 +112,10 @@ class CudaFunctions(object):
|
|
|
91
112
|
self.env = env
|
|
92
113
|
self.name = env["device_name"]
|
|
93
114
|
|
|
94
|
-
def
|
|
115
|
+
def __enter__(self):
|
|
116
|
+
return self
|
|
117
|
+
|
|
118
|
+
def __exit__(self, *exc):
|
|
95
119
|
for gpu_mem in self.allocations:
|
|
96
120
|
if hasattr(gpu_mem, 'free'): #if needed for when using mocks during testing
|
|
97
121
|
gpu_mem.free()
|
|
@@ -112,7 +136,7 @@ class CudaFunctions(object):
|
|
|
112
136
|
gpu_args = []
|
|
113
137
|
for arg in arguments:
|
|
114
138
|
# if arg i is a numpy array copy to device
|
|
115
|
-
if isinstance(arg,
|
|
139
|
+
if isinstance(arg, np.ndarray):
|
|
116
140
|
alloc = drv.mem_alloc(arg.nbytes)
|
|
117
141
|
self.allocations.append(alloc)
|
|
118
142
|
gpu_args.append(alloc)
|
|
@@ -150,8 +174,8 @@ class CudaFunctions(object):
|
|
|
150
174
|
code=('sm_' + self.cc) if self.cc != "00" else None,
|
|
151
175
|
cache_dir=False, no_extern_c=no_extern_c)
|
|
152
176
|
|
|
153
|
-
func = self.current_module.get_function(kernel_name)
|
|
154
|
-
return func
|
|
177
|
+
self.func = self.current_module.get_function(kernel_name)
|
|
178
|
+
return self.func
|
|
155
179
|
except drv.CompileError as e:
|
|
156
180
|
if "uses too much shared data" in e.stderr:
|
|
157
181
|
raise Exception("uses too much shared data")
|
|
@@ -189,40 +213,25 @@ class CudaFunctions(object):
|
|
|
189
213
|
:rtype: dict()
|
|
190
214
|
"""
|
|
191
215
|
result = dict()
|
|
192
|
-
result["times"] = []
|
|
193
|
-
result["power"] = []
|
|
194
|
-
energy = []
|
|
195
|
-
start = drv.Event()
|
|
196
|
-
end = drv.Event()
|
|
197
216
|
self.context.synchronize()
|
|
198
217
|
for _ in range(self.iterations):
|
|
199
|
-
|
|
200
|
-
|
|
218
|
+
self.start.record(stream=self.stream)
|
|
219
|
+
for obs in self.observers:
|
|
220
|
+
obs.before_start()
|
|
201
221
|
self.run_kernel(func, gpu_args, threads, grid, stream=self.stream)
|
|
202
|
-
end.record(stream=self.stream)
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
while not end.query():
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
end.synchronize()
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
power_readings = power_readings + [[execution_time / 1000.0, power_readings[-1][1]]]
|
|
216
|
-
result["power"].append(power_readings) #time in s, power usage in milliwatts
|
|
217
|
-
|
|
218
|
-
#compute energy consumption as area under curve
|
|
219
|
-
x = [d[0] for d in power_readings]
|
|
220
|
-
y = [d[1]/1000.0 for d in power_readings] #convert to watts
|
|
221
|
-
energy.append(numpy.trapz(y,x)) #in Joule
|
|
222
|
-
|
|
223
|
-
result["time"] = numpy.mean(result["times"])
|
|
224
|
-
if (self.iterations > 10) and energy:
|
|
225
|
-
result["energy"] = numpy.mean(energy[10:])
|
|
222
|
+
self.end.record(stream=self.stream)
|
|
223
|
+
for obs in self.observers:
|
|
224
|
+
obs.after_start()
|
|
225
|
+
while not self.end.query():
|
|
226
|
+
for obs in self.observers:
|
|
227
|
+
obs.during()
|
|
228
|
+
self.end.synchronize()
|
|
229
|
+
for obs in self.observers:
|
|
230
|
+
obs.after_finish()
|
|
231
|
+
|
|
232
|
+
for obs in self.observers:
|
|
233
|
+
result.update(obs.get_results())
|
|
234
|
+
|
|
226
235
|
return result
|
|
227
236
|
|
|
228
237
|
def copy_constant_memory_args(self, cmem_args):
|