kernel-tuner 0.3.2__tar.gz → 0.4.1__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.1}/PKG-INFO +6 -5
- {kernel_tuner-0.3.2 → kernel_tuner-0.4.1}/README.rst +3 -2
- kernel_tuner-0.4.1/kernel_tuner/__init__.py +5 -0
- {kernel_tuner-0.3.2 → kernel_tuner-0.4.1}/kernel_tuner/c.py +19 -9
- {kernel_tuner-0.3.2 → kernel_tuner-0.4.1}/kernel_tuner/core.py +206 -53
- {kernel_tuner-0.3.2 → kernel_tuner-0.4.1}/kernel_tuner/cuda.py +121 -69
- kernel_tuner-0.4.1/kernel_tuner/cupy.py +305 -0
- kernel_tuner-0.4.1/kernel_tuner/hyper.py +90 -0
- kernel_tuner-0.4.1/kernel_tuner/integration.py +412 -0
- {kernel_tuner-0.3.2 → kernel_tuner-0.4.1}/kernel_tuner/interface.py +195 -176
- {kernel_tuner-0.3.2 → kernel_tuner-0.4.1}/kernel_tuner/kernelbuilder.py +19 -4
- {kernel_tuner-0.3.2 → kernel_tuner-0.4.1}/kernel_tuner/nvml.py +115 -9
- kernel_tuner-0.4.1/kernel_tuner/observers.py +78 -0
- {kernel_tuner-0.3.2 → kernel_tuner-0.4.1}/kernel_tuner/opencl.py +53 -12
- {kernel_tuner-0.3.2 → kernel_tuner-0.4.1}/kernel_tuner/runners/sequential.py +11 -12
- kernel_tuner-0.4.1/kernel_tuner/runners/simulation.py +246 -0
- {kernel_tuner-0.3.2 → kernel_tuner-0.4.1}/kernel_tuner/strategies/basinhopping.py +4 -2
- kernel_tuner-0.4.1/kernel_tuner/strategies/bayes_opt.py +837 -0
- kernel_tuner-0.3.2/kernel_tuner/strategies/bayes_opt.py → kernel_tuner-0.4.1/kernel_tuner/strategies/bayes_opt_alt_BayesOpt.py +16 -30
- kernel_tuner-0.4.1/kernel_tuner/strategies/bayes_opt_alt_HyperOpt.py +71 -0
- kernel_tuner-0.4.1/kernel_tuner/strategies/bayes_opt_alt_ScikitOpt.py +71 -0
- {kernel_tuner-0.3.2 → kernel_tuner-0.4.1}/kernel_tuner/strategies/brute_force.py +5 -3
- {kernel_tuner-0.3.2 → kernel_tuner-0.4.1}/kernel_tuner/strategies/diff_evo.py +5 -3
- {kernel_tuner-0.3.2 → kernel_tuner-0.4.1}/kernel_tuner/strategies/firefly_algorithm.py +4 -3
- {kernel_tuner-0.3.2 → kernel_tuner-0.4.1}/kernel_tuner/strategies/genetic_algorithm.py +95 -79
- {kernel_tuner-0.3.2 → kernel_tuner-0.4.1}/kernel_tuner/strategies/minimize.py +38 -35
- kernel_tuner-0.4.1/kernel_tuner/strategies/mls.py +121 -0
- {kernel_tuner-0.3.2 → kernel_tuner-0.4.1}/kernel_tuner/strategies/random_sample.py +6 -8
- {kernel_tuner-0.3.2 → kernel_tuner-0.4.1}/kernel_tuner/strategies/simulated_annealing.py +8 -6
- {kernel_tuner-0.3.2 → kernel_tuner-0.4.1}/kernel_tuner/util.py +167 -74
- {kernel_tuner-0.3.2 → kernel_tuner-0.4.1}/kernel_tuner/wrappers.py +1 -1
- {kernel_tuner-0.3.2 → kernel_tuner-0.4.1}/kernel_tuner.egg-info/PKG-INFO +6 -5
- {kernel_tuner-0.3.2 → kernel_tuner-0.4.1}/kernel_tuner.egg-info/SOURCES.txt +12 -0
- {kernel_tuner-0.3.2 → kernel_tuner-0.4.1}/kernel_tuner.egg-info/requires.txt +3 -0
- {kernel_tuner-0.3.2 → kernel_tuner-0.4.1}/setup.py +10 -15
- kernel_tuner-0.4.1/test/test_c_functions.py +282 -0
- {kernel_tuner-0.3.2 → kernel_tuner-0.4.1}/test/test_core.py +71 -28
- kernel_tuner-0.4.1/test/test_cuda_functions.py +64 -0
- kernel_tuner-0.4.1/test/test_cuda_mocked.py +144 -0
- kernel_tuner-0.4.1/test/test_integration.py +167 -0
- {kernel_tuner-0.3.2 → kernel_tuner-0.4.1}/test/test_interface.py +21 -15
- kernel_tuner-0.4.1/test/test_kernelbuilder.py +57 -0
- {kernel_tuner-0.3.2 → kernel_tuner-0.4.1}/test/test_minimize.py +3 -7
- kernel_tuner-0.4.1/test/test_observers.py +36 -0
- {kernel_tuner-0.3.2 → kernel_tuner-0.4.1}/test/test_opencl_functions.py +16 -15
- kernel_tuner-0.4.1/test/test_runners.py +112 -0
- {kernel_tuner-0.3.2 → kernel_tuner-0.4.1}/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/test/test_runners.py +0 -96
- {kernel_tuner-0.3.2 → kernel_tuner-0.4.1}/LICENSE +0 -0
- {kernel_tuner-0.3.2 → kernel_tuner-0.4.1}/MANIFEST.in +0 -0
- {kernel_tuner-0.3.2 → kernel_tuner-0.4.1}/kernel_tuner/runners/__init__.py +0 -0
- {kernel_tuner-0.3.2 → kernel_tuner-0.4.1}/kernel_tuner/strategies/__init__.py +0 -0
- {kernel_tuner-0.3.2 → kernel_tuner-0.4.1}/kernel_tuner/strategies/pso.py +0 -0
- {kernel_tuner-0.3.2 → kernel_tuner-0.4.1}/kernel_tuner.egg-info/dependency_links.txt +0 -0
- {kernel_tuner-0.3.2 → kernel_tuner-0.4.1}/kernel_tuner.egg-info/top_level.txt +0 -0
- {kernel_tuner-0.3.2 → kernel_tuner-0.4.1}/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.1
|
|
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
|
|
@@ -172,8 +172,9 @@ Description: Kernel Tuner
|
|
|
172
172
|
You may also like `CLTune <https://github.com/CNugteren/CLTune>`__ by
|
|
173
173
|
Cedric Nugteren. CLTune is a C++ library for kernel tuning.
|
|
174
174
|
|
|
175
|
-
|
|
176
|
-
|
|
175
|
+
|
|
176
|
+
.. |Build Status| image:: https://github.com/benvanwerkhoven/kernel_tuner/actions/workflows/python-app.yml/badge.svg
|
|
177
|
+
:target: https://github.com/benvanwerkhoven/kernel_tuner/actions/workflows/python-app.yml
|
|
177
178
|
.. |CodeCov Badge| image:: https://codecov.io/gh/benvanwerkhoven/kernel_tuner/branch/master/graph/badge.svg
|
|
178
179
|
:target: https://codecov.io/gh/benvanwerkhoven/kernel_tuner
|
|
179
180
|
.. |PyPi Badge| image:: https://img.shields.io/pypi/v/kernel_tuner.svg?colorB=blue
|
|
@@ -194,13 +195,13 @@ Classifier: Intended Audience :: Education
|
|
|
194
195
|
Classifier: License :: OSI Approved :: Apache Software License
|
|
195
196
|
Classifier: Natural Language :: English
|
|
196
197
|
Classifier: Operating System :: POSIX :: Linux
|
|
197
|
-
Classifier: Programming Language :: Python :: 3.5
|
|
198
198
|
Classifier: Programming Language :: Python :: 3.6
|
|
199
199
|
Classifier: Programming Language :: Python :: 3.7
|
|
200
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
200
201
|
Classifier: Topic :: Scientific/Engineering
|
|
201
202
|
Classifier: Topic :: Software Development
|
|
202
203
|
Classifier: Topic :: System :: Distributed Computing
|
|
203
|
-
Classifier: Development Status ::
|
|
204
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
204
205
|
Provides-Extra: doc
|
|
205
206
|
Provides-Extra: cuda
|
|
206
207
|
Provides-Extra: opencl
|
|
@@ -164,8 +164,9 @@ Related work
|
|
|
164
164
|
You may also like `CLTune <https://github.com/CNugteren/CLTune>`__ by
|
|
165
165
|
Cedric Nugteren. CLTune is a C++ library for kernel tuning.
|
|
166
166
|
|
|
167
|
-
|
|
168
|
-
|
|
167
|
+
|
|
168
|
+
.. |Build Status| image:: https://github.com/benvanwerkhoven/kernel_tuner/actions/workflows/python-app.yml/badge.svg
|
|
169
|
+
:target: https://github.com/benvanwerkhoven/kernel_tuner/actions/workflows/python-app.yml
|
|
169
170
|
.. |CodeCov Badge| image:: https://codecov.io/gh/benvanwerkhoven/kernel_tuner/branch/master/graph/badge.svg
|
|
170
171
|
:target: https://codecov.io/gh/benvanwerkhoven/kernel_tuner
|
|
171
172
|
.. |PyPi Badge| image:: https://img.shields.io/pypi/v/kernel_tuner.svg?colorB=blue
|
|
@@ -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,35 @@
|
|
|
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
|
|
|
22
|
+
|
|
23
|
+
try:
|
|
24
|
+
import torch
|
|
25
|
+
except ImportError:
|
|
26
|
+
torch = util.TorchPlaceHolder()
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
|
|
14
30
|
_KernelInstance = namedtuple("_KernelInstance", ["name", "kernel_source", "kernel_string", "temp_files", "threads", "grid", "params", "arguments"])
|
|
15
31
|
|
|
32
|
+
|
|
16
33
|
class KernelInstance(_KernelInstance):
|
|
17
34
|
"""Class that represents the specific parameterized instance of a kernel"""
|
|
18
35
|
|
|
@@ -40,10 +57,11 @@ class KernelSource(object):
|
|
|
40
57
|
must be filenames.
|
|
41
58
|
"""
|
|
42
59
|
|
|
43
|
-
def __init__(self, kernel_sources, lang):
|
|
60
|
+
def __init__(self, kernel_name, kernel_sources, lang):
|
|
44
61
|
if not isinstance(kernel_sources, list):
|
|
45
62
|
kernel_sources = [kernel_sources]
|
|
46
63
|
self.kernel_sources = kernel_sources
|
|
64
|
+
self.kernel_name = kernel_name
|
|
47
65
|
if lang is None:
|
|
48
66
|
if callable(self.kernel_sources[0]):
|
|
49
67
|
raise TypeError("Please specify language when using a code generator function")
|
|
@@ -74,7 +92,6 @@ class KernelSource(object):
|
|
|
74
92
|
kernel_source = self.kernel_sources[index]
|
|
75
93
|
return util.get_kernel_string(kernel_source, params)
|
|
76
94
|
|
|
77
|
-
|
|
78
95
|
def prepare_list_of_files(self, kernel_name, params, grid, threads, block_size_names):
|
|
79
96
|
""" prepare the kernel string along with any additional files
|
|
80
97
|
|
|
@@ -134,7 +151,6 @@ class KernelSource(object):
|
|
|
134
151
|
|
|
135
152
|
return name, kernel_string, temp_files
|
|
136
153
|
|
|
137
|
-
|
|
138
154
|
def get_user_suffix(self, index=0):
|
|
139
155
|
""" Get the suffix of the kernel filename, if the user specified one. Return None otherwise.
|
|
140
156
|
"""
|
|
@@ -154,7 +170,11 @@ class KernelSource(object):
|
|
|
154
170
|
if suffix is not None:
|
|
155
171
|
return suffix
|
|
156
172
|
|
|
157
|
-
_suffixes = {
|
|
173
|
+
_suffixes = {
|
|
174
|
+
'CUDA': '.cu',
|
|
175
|
+
'OpenCL': '.cl',
|
|
176
|
+
'C': '.c'
|
|
177
|
+
}
|
|
158
178
|
try:
|
|
159
179
|
return _suffixes[self.lang]
|
|
160
180
|
except KeyError:
|
|
@@ -175,7 +195,7 @@ class KernelSource(object):
|
|
|
175
195
|
class DeviceInterface(object):
|
|
176
196
|
"""Class that offers a High-Level Device Interface to the rest of the Kernel Tuner"""
|
|
177
197
|
|
|
178
|
-
def __init__(self, kernel_source, device=0, platform=0, quiet=False, compiler=None, compiler_options=None, iterations=7):
|
|
198
|
+
def __init__(self, kernel_source, device=0, platform=0, quiet=False, compiler=None, compiler_options=None, iterations=7, observers=None):
|
|
179
199
|
""" Instantiate the DeviceInterface, based on language in kernel source
|
|
180
200
|
|
|
181
201
|
:param kernel_source The kernel sources
|
|
@@ -210,26 +230,55 @@ class DeviceInterface(object):
|
|
|
210
230
|
logging.debug('DeviceInterface instantiated, lang=%s', lang)
|
|
211
231
|
|
|
212
232
|
if lang == "CUDA":
|
|
213
|
-
dev = CudaFunctions(device, compiler_options=compiler_options, iterations=iterations)
|
|
233
|
+
dev = CudaFunctions(device, compiler_options=compiler_options, iterations=iterations, observers=observers)
|
|
234
|
+
elif lang.upper() == "CUPY":
|
|
235
|
+
dev = CupyFunctions(device, compiler_options=compiler_options, iterations=iterations, observers=observers)
|
|
214
236
|
elif lang == "OpenCL":
|
|
215
|
-
dev = OpenCLFunctions(device, platform, compiler_options=compiler_options, iterations=iterations)
|
|
237
|
+
dev = OpenCLFunctions(device, platform, compiler_options=compiler_options, iterations=iterations, observers=observers)
|
|
216
238
|
elif lang == "C":
|
|
217
239
|
dev = CFunctions(compiler=compiler, compiler_options=compiler_options, iterations=iterations)
|
|
218
240
|
else:
|
|
219
|
-
raise
|
|
241
|
+
raise ValueError("Sorry, support for languages other than CUDA, OpenCL, or C is not implemented yet")
|
|
242
|
+
|
|
243
|
+
#look for NVMLObserver in observers, if present, enable special tunable parameters through nvml
|
|
244
|
+
self.use_nvml = False
|
|
245
|
+
if observers:
|
|
246
|
+
for obs in observers:
|
|
247
|
+
if isinstance(obs, NVMLObserver):
|
|
248
|
+
self.nvml = obs.nvml
|
|
249
|
+
self.use_nvml = True
|
|
250
|
+
|
|
220
251
|
self.lang = lang
|
|
221
252
|
self.dev = dev
|
|
222
253
|
self.units = dev.units
|
|
223
254
|
self.name = dev.name
|
|
255
|
+
self.max_threads = dev.max_threads
|
|
224
256
|
if not quiet:
|
|
225
257
|
print("Using: " + self.dev.name)
|
|
226
258
|
|
|
259
|
+
dev.__enter__()
|
|
260
|
+
|
|
261
|
+
def __enter__(self):
|
|
262
|
+
return self
|
|
263
|
+
|
|
227
264
|
def benchmark(self, func, gpu_args, instance, verbose):
|
|
228
265
|
"""benchmark the kernel instance"""
|
|
229
266
|
logging.debug('benchmark ' + instance.name)
|
|
230
267
|
logging.debug('thread block dimensions x,y,z=%d,%d,%d', *instance.threads)
|
|
231
268
|
logging.debug('grid dimensions x,y,z=%d,%d,%d', *instance.grid)
|
|
232
269
|
|
|
270
|
+
if self.use_nvml:
|
|
271
|
+
if "nvml_pwr_limit" in instance.params:
|
|
272
|
+
new_limit = int(instance.params["nvml_pwr_limit"] * 1000) #user specifies in Watt, but nvml uses milliWatt
|
|
273
|
+
if self.nvml.pwr_limit != new_limit:
|
|
274
|
+
self.nvml.pwr_limit = new_limit
|
|
275
|
+
if "nvml_gr_clock" in instance.params:
|
|
276
|
+
self.nvml.gr_clock = instance.params["nvml_gr_clock"]
|
|
277
|
+
if "nvml_sm_clock" in instance.params:
|
|
278
|
+
self.nvml.sm_clock = instance.params["nvml_sm_clock"]
|
|
279
|
+
if "nvml_mem_clock" in instance.params:
|
|
280
|
+
self.nvml.mem_clock = instance.params["nvml_mem_clock"]
|
|
281
|
+
|
|
233
282
|
result = None
|
|
234
283
|
try:
|
|
235
284
|
result = self.dev.benchmark(func, gpu_args, instance.threads, instance.grid)
|
|
@@ -242,7 +291,7 @@ class DeviceInterface(object):
|
|
|
242
291
|
if any([skip_str in str(e) for skip_str in skippable_exceptions]):
|
|
243
292
|
logging.debug('benchmark fails due to runtime failure too many resources required')
|
|
244
293
|
if verbose:
|
|
245
|
-
print("skipping config
|
|
294
|
+
print(f"skipping config {util.get_instance_string(instance.params)} reason: too many resources requested for launch")
|
|
246
295
|
else:
|
|
247
296
|
logging.debug('benchmark encountered runtime failure: ' + str(e))
|
|
248
297
|
print("Error while benchmarking:", instance.name)
|
|
@@ -260,21 +309,27 @@ class DeviceInterface(object):
|
|
|
260
309
|
#re-copy original contents of output arguments to GPU memory, to overwrite any changes
|
|
261
310
|
#by earlier kernel runs
|
|
262
311
|
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)
|
|
312
|
+
if (verify or answer[i] is not None) and isinstance(arg, (np.ndarray, cp.ndarray, torch.Tensor)):
|
|
313
|
+
self.dev.memcpy_htod(gpu_args[i], arg)
|
|
266
314
|
|
|
267
315
|
#run the kernel
|
|
268
316
|
check = self.run_kernel(func, gpu_args, instance)
|
|
269
317
|
if not check:
|
|
270
|
-
return True
|
|
318
|
+
return True #runtime failure occured that should be ignored, skip correctness check
|
|
271
319
|
|
|
272
320
|
#retrieve gpu results to host memory
|
|
273
321
|
result_host = []
|
|
274
322
|
for i, arg in enumerate(instance.arguments):
|
|
275
|
-
if (verify or answer[i] is not None) and isinstance(arg,
|
|
276
|
-
result_host.append(
|
|
323
|
+
if (verify or answer[i] is not None) and isinstance(arg, (np.ndarray, cp.ndarray)):
|
|
324
|
+
result_host.append(np.zeros_like(arg))
|
|
277
325
|
self.dev.memcpy_dtoh(result_host[-1], gpu_args[i])
|
|
326
|
+
elif isinstance(arg, torch.Tensor) and isinstance(answer[i], torch.Tensor):
|
|
327
|
+
if not answer[i].is_cuda:
|
|
328
|
+
#if the answer is on the host, copy gpu output to host as well
|
|
329
|
+
result_host.append(torch.zeros_like(answer[i]))
|
|
330
|
+
self.dev.memcpy_dtoh(result_host[-1], gpu_args[i].tensor)
|
|
331
|
+
else:
|
|
332
|
+
result_host.append(gpu_args[i].tensor)
|
|
278
333
|
else:
|
|
279
334
|
result_host.append(None)
|
|
280
335
|
|
|
@@ -285,7 +340,7 @@ class DeviceInterface(object):
|
|
|
285
340
|
correct = _default_verify_function(instance, answer, result_host, atol, verbose)
|
|
286
341
|
|
|
287
342
|
if not correct:
|
|
288
|
-
raise
|
|
343
|
+
raise RuntimeError("Kernel result verification failed for: " + util.get_config_string(instance.params))
|
|
289
344
|
return True
|
|
290
345
|
|
|
291
346
|
def compile_and_benchmark(self, kernel_source, gpu_args, params, kernel_options, tuning_options):
|
|
@@ -294,7 +349,7 @@ class DeviceInterface(object):
|
|
|
294
349
|
instance_string = util.get_instance_string(params)
|
|
295
350
|
|
|
296
351
|
logging.debug('compile_and_benchmark ' + instance_string)
|
|
297
|
-
mem_usage = round(resource.getrusage(resource.RUSAGE_SELF).ru_maxrss/1024.0, 1)
|
|
352
|
+
mem_usage = round(resource.getrusage(resource.RUSAGE_SELF).ru_maxrss / 1024.0, 1)
|
|
298
353
|
logging.debug('Memory usage : %2.2f MB', mem_usage)
|
|
299
354
|
|
|
300
355
|
verbose = tuning_options.verbose
|
|
@@ -309,9 +364,9 @@ class DeviceInterface(object):
|
|
|
309
364
|
if func is None:
|
|
310
365
|
return None
|
|
311
366
|
|
|
312
|
-
#add
|
|
367
|
+
#add shared memory arguments to compiled module
|
|
313
368
|
if kernel_options.smem_args is not None:
|
|
314
|
-
self.dev.copy_shared_memory_args(kernel_options.smem_args)
|
|
369
|
+
self.dev.copy_shared_memory_args(util.get_smem_args(kernel_options.smem_args, params))
|
|
315
370
|
#add constant memory arguments to compiled module
|
|
316
371
|
if kernel_options.cmem_args is not None:
|
|
317
372
|
self.dev.copy_constant_memory_args(kernel_options.cmem_args)
|
|
@@ -353,30 +408,36 @@ class DeviceInterface(object):
|
|
|
353
408
|
if any(msg in str(e) for msg in shared_mem_error_messages):
|
|
354
409
|
logging.debug('compile_kernel failed due to kernel using too much shared memory')
|
|
355
410
|
if verbose:
|
|
356
|
-
print("skipping config
|
|
411
|
+
print(f"skipping config {util.get_instance_string(instance.params)} reason: too much shared memory used")
|
|
357
412
|
else:
|
|
358
413
|
logging.debug('compile_kernel failed due to error: ' + str(e))
|
|
359
414
|
print("Error while compiling:", instance.name)
|
|
360
415
|
raise e
|
|
361
416
|
return func
|
|
362
417
|
|
|
418
|
+
def copy_shared_memory_args(self, smem_args):
|
|
419
|
+
"""adds shared memory arguments to the most recently compiled module, if using CUDA"""
|
|
420
|
+
if self.lang == "CUDA":
|
|
421
|
+
self.dev.copy_shared_memory_args(smem_args)
|
|
422
|
+
else:
|
|
423
|
+
raise RuntimeError("Error cannot copy shared memory arguments when language is not CUDA")
|
|
424
|
+
|
|
363
425
|
def copy_constant_memory_args(self, cmem_args):
|
|
364
426
|
"""adds constant memory arguments to the most recently compiled module, if using CUDA"""
|
|
365
427
|
if self.lang == "CUDA":
|
|
366
428
|
self.dev.copy_constant_memory_args(cmem_args)
|
|
367
429
|
else:
|
|
368
|
-
raise
|
|
430
|
+
raise RuntimeError("Error cannot copy constant memory arguments when language is not CUDA")
|
|
369
431
|
|
|
370
432
|
def copy_texture_memory_args(self, texmem_args):
|
|
371
433
|
"""adds texture memory arguments to the most recently compiled module, if using CUDA"""
|
|
372
434
|
if self.lang == "CUDA":
|
|
373
435
|
self.dev.copy_texture_memory_args(texmem_args)
|
|
374
436
|
else:
|
|
375
|
-
raise
|
|
437
|
+
raise RuntimeError("Error cannot copy texture memory arguments when language is not CUDA")
|
|
376
438
|
|
|
377
439
|
def create_kernel_instance(self, kernel_source, kernel_options, params, verbose):
|
|
378
440
|
"""create kernel instance from kernel source, parameters, problem size, grid divisors, and so on"""
|
|
379
|
-
instance_string = util.get_instance_string(params)
|
|
380
441
|
grid_div = (kernel_options.grid_div_x, kernel_options.grid_div_y, kernel_options.grid_div_z)
|
|
381
442
|
|
|
382
443
|
#insert default block_size_names if needed
|
|
@@ -385,13 +446,18 @@ class DeviceInterface(object):
|
|
|
385
446
|
|
|
386
447
|
#setup thread block and grid dimensions
|
|
387
448
|
threads, grid = util.setup_block_and_grid(kernel_options.problem_size, grid_div, params, kernel_options.block_size_names)
|
|
388
|
-
if
|
|
449
|
+
if np.prod(threads) > self.dev.max_threads:
|
|
389
450
|
if verbose:
|
|
390
|
-
print("skipping config
|
|
451
|
+
print(f"skipping config {util.get_instance_string(params)} reason: too many threads per block")
|
|
391
452
|
return None
|
|
392
453
|
|
|
393
454
|
#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,
|
|
455
|
+
name, kernel_string, temp_files = kernel_source.prepare_list_of_files(kernel_options.kernel_name, params, grid, threads,
|
|
456
|
+
kernel_options.block_size_names)
|
|
457
|
+
|
|
458
|
+
#check for templated kernel
|
|
459
|
+
if kernel_source.lang == "CUDA" and "<" in name and ">" in name:
|
|
460
|
+
kernel_string, name = wrap_templated_kernel(kernel_string, name)
|
|
395
461
|
|
|
396
462
|
#collect everything we know about this instance and return it
|
|
397
463
|
return KernelInstance(name, kernel_source, kernel_string, temp_files, threads, grid, params, kernel_options.arguments)
|
|
@@ -425,45 +491,45 @@ class DeviceInterface(object):
|
|
|
425
491
|
raise e
|
|
426
492
|
return True
|
|
427
493
|
|
|
428
|
-
|
|
429
|
-
def __del__(self):
|
|
494
|
+
def __exit__(self, *exc):
|
|
430
495
|
if hasattr(self, 'dev'):
|
|
431
|
-
|
|
432
|
-
|
|
496
|
+
self.dev.__exit__(*exc)
|
|
433
497
|
|
|
434
498
|
|
|
435
499
|
def _default_verify_function(instance, answer, result_host, atol, verbose):
|
|
436
|
-
"""default verify function based on
|
|
500
|
+
"""default verify function based on np.allclose"""
|
|
437
501
|
|
|
438
502
|
#first check if the length is the same
|
|
439
503
|
if len(instance.arguments) != len(answer):
|
|
440
504
|
raise TypeError("The length of argument list and provided results do not match.")
|
|
441
505
|
#for each element in the argument list, check if the types match
|
|
442
506
|
for i, arg in enumerate(instance.arguments):
|
|
443
|
-
if answer[i] is not None:
|
|
444
|
-
if isinstance(answer[i],
|
|
507
|
+
if answer[i] is not None: #skip None elements in the answer list
|
|
508
|
+
if isinstance(answer[i], (np.ndarray, cp.ndarray)) and isinstance(arg, (np.ndarray, cp.ndarray)):
|
|
445
509
|
if answer[i].dtype != arg.dtype:
|
|
446
|
-
raise TypeError("Element " +
|
|
447
|
-
+ "
|
|
448
|
-
+ str(answer[i].dtype) + " != " + str(arg.dtype) + ".")
|
|
510
|
+
raise TypeError(f"Element {i} of the expected results list is not of the same dtype as the kernel output: " +
|
|
511
|
+
str(answer[i].dtype) + " != " + str(arg.dtype) + ".")
|
|
449
512
|
if answer[i].size != arg.size:
|
|
450
|
-
raise TypeError("Element " +
|
|
451
|
-
+ "
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
513
|
+
raise TypeError(f"Element {i} of the expected results list has a size different from " + "the kernel argument: " +
|
|
514
|
+
str(answer[i].size) + " != " + str(arg.size) + ".")
|
|
515
|
+
elif isinstance(answer[i], torch.Tensor) and isinstance(arg, torch.Tensor):
|
|
516
|
+
if answer[i].dtype != arg.dtype:
|
|
517
|
+
raise TypeError(f"Element {i} of the expected results list is not of the same dtype as the kernel output: " +
|
|
518
|
+
str(answer[i].dtype) + " != " + str(arg.dtype) + ".")
|
|
519
|
+
if answer[i].size() != arg.size():
|
|
520
|
+
raise TypeError(f"Element {i} of the expected results list has a size different from " + "the kernel argument: " +
|
|
521
|
+
str(answer[i].size) + " != " + str(arg.size) + ".")
|
|
522
|
+
|
|
523
|
+
elif isinstance(answer[i], np.number) and isinstance(arg, np.number):
|
|
455
524
|
if answer[i].dtype != arg.dtype:
|
|
456
|
-
raise TypeError("Element " + str(i)
|
|
457
|
-
|
|
458
|
-
+ str(answer[i].dtype) + " != " + str(arg.dtype) + ".")
|
|
525
|
+
raise TypeError(f"Element {i} of the expected results list is not the same as the kernel output: " + str(answer[i].dtype) +
|
|
526
|
+
" != " + str(arg.dtype) + ".")
|
|
459
527
|
else:
|
|
460
528
|
#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
|
|
463
|
-
+ " of expected results list is not a numpy array or numpy scalar.")
|
|
529
|
+
if not isinstance(answer[i], (np.ndarray, cp.ndarray, torch.Tensor)) or not isinstance(answer[i], np.number):
|
|
530
|
+
raise TypeError(f"Element {i} of expected results list is not a numpy/cupy ndarray, torch Tensor or numpy scalar.")
|
|
464
531
|
else:
|
|
465
|
-
raise TypeError("Element
|
|
466
|
-
+ " of expected results list and kernel arguments have different types.")
|
|
532
|
+
raise TypeError(f"Element {i} of expected results list and kernel arguments have different types.")
|
|
467
533
|
|
|
468
534
|
def _ravel(a):
|
|
469
535
|
if hasattr(a, 'ravel') and len(a.shape) > 1:
|
|
@@ -482,13 +548,18 @@ def _default_verify_function(instance, answer, result_host, atol, verbose):
|
|
|
482
548
|
|
|
483
549
|
result = _ravel(result_host[i])
|
|
484
550
|
expected = _flatten(expected)
|
|
485
|
-
|
|
551
|
+
if any([isinstance(array, cp.ndarray) for array in [expected, result]]):
|
|
552
|
+
output_test = cp.allclose(expected, result, atol=atol)
|
|
553
|
+
elif isinstance(expected, torch.Tensor) and isinstance(result, torch.Tensor):
|
|
554
|
+
output_test = torch.allclose(expected, result, atol=atol)
|
|
555
|
+
else:
|
|
556
|
+
output_test = np.allclose(expected, result, atol=atol)
|
|
486
557
|
|
|
487
558
|
if not output_test and verbose:
|
|
488
559
|
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,))
|
|
560
|
+
print("this error occured when checking value of the %oth kernel argument" % (i, ))
|
|
490
561
|
print("Printing kernel output and expected result, set verbose=False to suppress this debug print")
|
|
491
|
-
|
|
562
|
+
np.set_printoptions(edgeitems=50)
|
|
492
563
|
print("Kernel output:")
|
|
493
564
|
print(result)
|
|
494
565
|
print("Expected:")
|
|
@@ -499,3 +570,85 @@ def _default_verify_function(instance, answer, result_host, atol, verbose):
|
|
|
499
570
|
logging.debug('correctness check has found a correctness issue')
|
|
500
571
|
|
|
501
572
|
return correct
|
|
573
|
+
|
|
574
|
+
|
|
575
|
+
|
|
576
|
+
#these functions facilitate compiling templated kernels with PyCuda
|
|
577
|
+
def split_argument_list(argument_list):
|
|
578
|
+
"""split all arguments in a list into types and names"""
|
|
579
|
+
regex = r"(.*[\s*]+)(.*)?"
|
|
580
|
+
type_list = []
|
|
581
|
+
name_list = []
|
|
582
|
+
for arg in argument_list:
|
|
583
|
+
match = re.match(regex, arg, re.S)
|
|
584
|
+
if not match:
|
|
585
|
+
raise ValueError("error parsing templated kernel argument list")
|
|
586
|
+
type_list.append(re.sub(r"\s+", " ", match.group(1).strip(), re.S))
|
|
587
|
+
name_list.append(match.group(2).strip())
|
|
588
|
+
return type_list, name_list
|
|
589
|
+
|
|
590
|
+
def apply_template_typenames(type_list, templated_typenames):
|
|
591
|
+
"""replace the typename tokens in type_list with their templated typenames"""
|
|
592
|
+
def replace_typename_token(matchobj):
|
|
593
|
+
"""function for a whitespace preserving token regex replace"""
|
|
594
|
+
#replace only the match, leaving the whitespace around it as is
|
|
595
|
+
return matchobj.group(1) + templated_typenames[matchobj.group(2)] + matchobj.group(3)
|
|
596
|
+
for i, arg_type in enumerate(type_list):
|
|
597
|
+
for k,v in templated_typenames.items():
|
|
598
|
+
#if the templated typename occurs as a token in the string, meaning that it is enclosed in
|
|
599
|
+
#beginning of string or whitespace, and end of string, whitespace or star
|
|
600
|
+
regex = r"(^|\s+)(" + k + r")($|\s+|\*)"
|
|
601
|
+
sub = re.sub(regex, replace_typename_token, arg_type, re.S)
|
|
602
|
+
type_list[i] = sub
|
|
603
|
+
|
|
604
|
+
def get_templated_typenames(template_parameters, template_arguments):
|
|
605
|
+
"""based on the template parameters and arguments, create dict with templated typenames"""
|
|
606
|
+
templated_typenames = {}
|
|
607
|
+
for i, param in enumerate(template_parameters):
|
|
608
|
+
if "typename " in param:
|
|
609
|
+
typename = param[9:]
|
|
610
|
+
templated_typenames[typename] = template_arguments[i]
|
|
611
|
+
return templated_typenames
|
|
612
|
+
|
|
613
|
+
def wrap_templated_kernel(kernel_string, kernel_name):
|
|
614
|
+
"""rewrite kernel_string to insert wrapper function for templated kernel"""
|
|
615
|
+
#parse kernel_name to find template_arguments and real kernel name
|
|
616
|
+
name = kernel_name.split("<")[0]
|
|
617
|
+
template_arguments = re.search(r".*?<(.*)>", kernel_name, re.S).group(1).split(',')
|
|
618
|
+
|
|
619
|
+
#parse templated kernel definition
|
|
620
|
+
#relatively strict regex that does not allow nested template parameters like vector<TF>
|
|
621
|
+
#within the template parameter list
|
|
622
|
+
regex = r"template\s*<([^>]*?)>\s*__global__\s+void\s+" + name + r"\s*\((.*?)\)\s*\{"
|
|
623
|
+
match = re.search(regex, kernel_string, re.S)
|
|
624
|
+
if not match:
|
|
625
|
+
raise ValueError("could not find templated kernel definition")
|
|
626
|
+
|
|
627
|
+
template_parameters = match.group(1).split(',')
|
|
628
|
+
argument_list = match.group(2).split(',')
|
|
629
|
+
argument_list = [s.strip() for s in argument_list] #remove extra whitespace around 'type name' strings
|
|
630
|
+
|
|
631
|
+
type_list, name_list = split_argument_list(argument_list)
|
|
632
|
+
|
|
633
|
+
templated_typenames = get_templated_typenames(template_parameters, template_arguments)
|
|
634
|
+
apply_template_typenames(type_list, templated_typenames)
|
|
635
|
+
|
|
636
|
+
#replace __global__ with __device__ in the templated kernel definition
|
|
637
|
+
#could do a more precise replace, but __global__ cannot be used elsewhere in the definition
|
|
638
|
+
definition = match.group(0).replace("__global__", "__device__")
|
|
639
|
+
|
|
640
|
+
#generate code for the compile-time template instantiation
|
|
641
|
+
template_instantiation = f"template __device__ void {kernel_name}(" + ", ".join(type_list) + ");\n"
|
|
642
|
+
|
|
643
|
+
#generate code for the wrapper kernel
|
|
644
|
+
new_arg_list = ", ".join([" ".join((a, b)) for a, b in zip(type_list, name_list)])
|
|
645
|
+
wrapper_function = "\nextern \"C\" __global__ void " + name + "_wrapper(" + new_arg_list + ") {\n " + \
|
|
646
|
+
kernel_name + "(" + ", ".join(name_list) + ");\n}\n"
|
|
647
|
+
|
|
648
|
+
#copy kernel_string, replace definition and append template instantiation and wrapper function
|
|
649
|
+
new_kernel_string = kernel_string[:]
|
|
650
|
+
new_kernel_string = new_kernel_string.replace(match.group(0), definition)
|
|
651
|
+
new_kernel_string += "\n" + template_instantiation
|
|
652
|
+
new_kernel_string += wrapper_function
|
|
653
|
+
|
|
654
|
+
return new_kernel_string, name + "_wrapper"
|