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.
Files changed (60) hide show
  1. {kernel_tuner-0.3.2 → kernel_tuner-0.4.1}/PKG-INFO +6 -5
  2. {kernel_tuner-0.3.2 → kernel_tuner-0.4.1}/README.rst +3 -2
  3. kernel_tuner-0.4.1/kernel_tuner/__init__.py +5 -0
  4. {kernel_tuner-0.3.2 → kernel_tuner-0.4.1}/kernel_tuner/c.py +19 -9
  5. {kernel_tuner-0.3.2 → kernel_tuner-0.4.1}/kernel_tuner/core.py +206 -53
  6. {kernel_tuner-0.3.2 → kernel_tuner-0.4.1}/kernel_tuner/cuda.py +121 -69
  7. kernel_tuner-0.4.1/kernel_tuner/cupy.py +305 -0
  8. kernel_tuner-0.4.1/kernel_tuner/hyper.py +90 -0
  9. kernel_tuner-0.4.1/kernel_tuner/integration.py +412 -0
  10. {kernel_tuner-0.3.2 → kernel_tuner-0.4.1}/kernel_tuner/interface.py +195 -176
  11. {kernel_tuner-0.3.2 → kernel_tuner-0.4.1}/kernel_tuner/kernelbuilder.py +19 -4
  12. {kernel_tuner-0.3.2 → kernel_tuner-0.4.1}/kernel_tuner/nvml.py +115 -9
  13. kernel_tuner-0.4.1/kernel_tuner/observers.py +78 -0
  14. {kernel_tuner-0.3.2 → kernel_tuner-0.4.1}/kernel_tuner/opencl.py +53 -12
  15. {kernel_tuner-0.3.2 → kernel_tuner-0.4.1}/kernel_tuner/runners/sequential.py +11 -12
  16. kernel_tuner-0.4.1/kernel_tuner/runners/simulation.py +246 -0
  17. {kernel_tuner-0.3.2 → kernel_tuner-0.4.1}/kernel_tuner/strategies/basinhopping.py +4 -2
  18. kernel_tuner-0.4.1/kernel_tuner/strategies/bayes_opt.py +837 -0
  19. 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
  20. kernel_tuner-0.4.1/kernel_tuner/strategies/bayes_opt_alt_HyperOpt.py +71 -0
  21. kernel_tuner-0.4.1/kernel_tuner/strategies/bayes_opt_alt_ScikitOpt.py +71 -0
  22. {kernel_tuner-0.3.2 → kernel_tuner-0.4.1}/kernel_tuner/strategies/brute_force.py +5 -3
  23. {kernel_tuner-0.3.2 → kernel_tuner-0.4.1}/kernel_tuner/strategies/diff_evo.py +5 -3
  24. {kernel_tuner-0.3.2 → kernel_tuner-0.4.1}/kernel_tuner/strategies/firefly_algorithm.py +4 -3
  25. {kernel_tuner-0.3.2 → kernel_tuner-0.4.1}/kernel_tuner/strategies/genetic_algorithm.py +95 -79
  26. {kernel_tuner-0.3.2 → kernel_tuner-0.4.1}/kernel_tuner/strategies/minimize.py +38 -35
  27. kernel_tuner-0.4.1/kernel_tuner/strategies/mls.py +121 -0
  28. {kernel_tuner-0.3.2 → kernel_tuner-0.4.1}/kernel_tuner/strategies/random_sample.py +6 -8
  29. {kernel_tuner-0.3.2 → kernel_tuner-0.4.1}/kernel_tuner/strategies/simulated_annealing.py +8 -6
  30. {kernel_tuner-0.3.2 → kernel_tuner-0.4.1}/kernel_tuner/util.py +167 -74
  31. {kernel_tuner-0.3.2 → kernel_tuner-0.4.1}/kernel_tuner/wrappers.py +1 -1
  32. {kernel_tuner-0.3.2 → kernel_tuner-0.4.1}/kernel_tuner.egg-info/PKG-INFO +6 -5
  33. {kernel_tuner-0.3.2 → kernel_tuner-0.4.1}/kernel_tuner.egg-info/SOURCES.txt +12 -0
  34. {kernel_tuner-0.3.2 → kernel_tuner-0.4.1}/kernel_tuner.egg-info/requires.txt +3 -0
  35. {kernel_tuner-0.3.2 → kernel_tuner-0.4.1}/setup.py +10 -15
  36. kernel_tuner-0.4.1/test/test_c_functions.py +282 -0
  37. {kernel_tuner-0.3.2 → kernel_tuner-0.4.1}/test/test_core.py +71 -28
  38. kernel_tuner-0.4.1/test/test_cuda_functions.py +64 -0
  39. kernel_tuner-0.4.1/test/test_cuda_mocked.py +144 -0
  40. kernel_tuner-0.4.1/test/test_integration.py +167 -0
  41. {kernel_tuner-0.3.2 → kernel_tuner-0.4.1}/test/test_interface.py +21 -15
  42. kernel_tuner-0.4.1/test/test_kernelbuilder.py +57 -0
  43. {kernel_tuner-0.3.2 → kernel_tuner-0.4.1}/test/test_minimize.py +3 -7
  44. kernel_tuner-0.4.1/test/test_observers.py +36 -0
  45. {kernel_tuner-0.3.2 → kernel_tuner-0.4.1}/test/test_opencl_functions.py +16 -15
  46. kernel_tuner-0.4.1/test/test_runners.py +112 -0
  47. {kernel_tuner-0.3.2 → kernel_tuner-0.4.1}/test/test_util_functions.py +125 -79
  48. kernel_tuner-0.3.2/kernel_tuner/__init__.py +0 -3
  49. kernel_tuner-0.3.2/test/test_c_functions.py +0 -218
  50. kernel_tuner-0.3.2/test/test_cuda_functions.py +0 -61
  51. kernel_tuner-0.3.2/test/test_cuda_mocked.py +0 -138
  52. kernel_tuner-0.3.2/test/test_runners.py +0 -96
  53. {kernel_tuner-0.3.2 → kernel_tuner-0.4.1}/LICENSE +0 -0
  54. {kernel_tuner-0.3.2 → kernel_tuner-0.4.1}/MANIFEST.in +0 -0
  55. {kernel_tuner-0.3.2 → kernel_tuner-0.4.1}/kernel_tuner/runners/__init__.py +0 -0
  56. {kernel_tuner-0.3.2 → kernel_tuner-0.4.1}/kernel_tuner/strategies/__init__.py +0 -0
  57. {kernel_tuner-0.3.2 → kernel_tuner-0.4.1}/kernel_tuner/strategies/pso.py +0 -0
  58. {kernel_tuner-0.3.2 → kernel_tuner-0.4.1}/kernel_tuner.egg-info/dependency_links.txt +0 -0
  59. {kernel_tuner-0.3.2 → kernel_tuner-0.4.1}/kernel_tuner.egg-info/top_level.txt +0 -0
  60. {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.2
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
- .. |Build Status| image:: https://api.travis-ci.org/benvanwerkhoven/kernel_tuner.svg?branch=master
176
- :target: https://travis-ci.org/benvanwerkhoven/kernel_tuner
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 :: 4 - Beta
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
- .. |Build Status| image:: https://api.travis-ci.org/benvanwerkhoven/kernel_tuner.svg?branch=master
168
- :target: https://travis-ci.org/benvanwerkhoven/kernel_tuner
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
@@ -0,0 +1,5 @@
1
+
2
+ from kernel_tuner.integration import store_results, create_device_targets
3
+
4
+ from kernel_tuner.interface import tune_kernel, run_kernel
5
+
@@ -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 kernel_name: The name of the kernel to be compiled, used to lookup the
114
- function after compilation.
115
- :type kernel_name: string
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
- compiler_options.append("-fopenmp")
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, kernel_instance.name)
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 = {'CUDA': '.cu', 'OpenCL': '.cl', 'C': '.c'}
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 Exception("Sorry, support for languages other than CUDA, OpenCL, or C is not implemented yet")
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", instance.name, "reason: too many resources requested for launch")
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
- if isinstance(arg, numpy.ndarray):
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 #runtime failure occured that should be ignored, skip correctness check
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, numpy.ndarray):
276
- result_host.append(numpy.zeros_like(arg))
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 Exception("Kernel result verification failed for: " + util.get_config_string(instance.params))
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 constant memory arguments to compiled module
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", instance.name, "reason: too much shared memory used")
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 Exception("Error cannot copy constant memory arguments when language is not CUDA")
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 Exception("Error cannot copy texture memory arguments when language is not CUDA")
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 numpy.prod(threads) > self.dev.max_threads:
449
+ if np.prod(threads) > self.dev.max_threads:
389
450
  if verbose:
390
- print("skipping config", instance_string, "reason: too many threads per block")
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, kernel_options.block_size_names)
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
- del self.dev
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 numpy.allclose"""
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: #skip None elements in the answer list
444
- if isinstance(answer[i], numpy.ndarray) and isinstance(arg, numpy.ndarray):
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 " + str(i)
447
- + " of the expected results list is not of the same dtype as the kernel output: "
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 " + str(i)
451
- + " of the expected results list has a size different from "
452
- + "the kernel argument: "
453
- + str(answer[i].size) + " != " + str(arg.size) + ".")
454
- elif isinstance(answer[i], numpy.number) and isinstance(arg, numpy.number):
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
- + " of the expected results list is not the same as the kernel output: "
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], numpy.ndarray) or not isinstance(answer[i], numpy.number):
462
- raise TypeError("Element " + str(i)
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 " + str(i)
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
- output_test = numpy.allclose(expected, result, atol=atol)
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
- numpy.set_printoptions(edgeitems=50)
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"