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.
Files changed (54) hide show
  1. {kernel_tuner-0.3.2 → kernel_tuner-0.4.0}/PKG-INFO +3 -3
  2. kernel_tuner-0.4.0/kernel_tuner/__init__.py +5 -0
  3. {kernel_tuner-0.3.2 → kernel_tuner-0.4.0}/kernel_tuner/c.py +19 -9
  4. {kernel_tuner-0.3.2 → kernel_tuner-0.4.0}/kernel_tuner/core.py +164 -43
  5. {kernel_tuner-0.3.2 → kernel_tuner-0.4.0}/kernel_tuner/cuda.py +54 -45
  6. kernel_tuner-0.4.0/kernel_tuner/cupy.py +303 -0
  7. kernel_tuner-0.4.0/kernel_tuner/integration.py +409 -0
  8. {kernel_tuner-0.3.2 → kernel_tuner-0.4.0}/kernel_tuner/interface.py +181 -174
  9. {kernel_tuner-0.3.2 → kernel_tuner-0.4.0}/kernel_tuner/kernelbuilder.py +19 -4
  10. {kernel_tuner-0.3.2 → kernel_tuner-0.4.0}/kernel_tuner/nvml.py +115 -9
  11. kernel_tuner-0.4.0/kernel_tuner/observers.py +78 -0
  12. {kernel_tuner-0.3.2 → kernel_tuner-0.4.0}/kernel_tuner/opencl.py +50 -12
  13. {kernel_tuner-0.3.2 → kernel_tuner-0.4.0}/kernel_tuner/runners/sequential.py +11 -12
  14. kernel_tuner-0.4.0/kernel_tuner/runners/simulation.py +244 -0
  15. {kernel_tuner-0.3.2 → kernel_tuner-0.4.0}/kernel_tuner/strategies/basinhopping.py +4 -2
  16. {kernel_tuner-0.3.2 → kernel_tuner-0.4.0}/kernel_tuner/strategies/brute_force.py +5 -3
  17. {kernel_tuner-0.3.2 → kernel_tuner-0.4.0}/kernel_tuner/strategies/diff_evo.py +5 -3
  18. {kernel_tuner-0.3.2 → kernel_tuner-0.4.0}/kernel_tuner/strategies/firefly_algorithm.py +4 -3
  19. {kernel_tuner-0.3.2 → kernel_tuner-0.4.0}/kernel_tuner/strategies/genetic_algorithm.py +40 -30
  20. {kernel_tuner-0.3.2 → kernel_tuner-0.4.0}/kernel_tuner/strategies/minimize.py +30 -29
  21. {kernel_tuner-0.3.2 → kernel_tuner-0.4.0}/kernel_tuner/strategies/random_sample.py +6 -8
  22. {kernel_tuner-0.3.2 → kernel_tuner-0.4.0}/kernel_tuner/strategies/simulated_annealing.py +8 -6
  23. {kernel_tuner-0.3.2 → kernel_tuner-0.4.0}/kernel_tuner/util.py +204 -100
  24. {kernel_tuner-0.3.2 → kernel_tuner-0.4.0}/kernel_tuner/wrappers.py +1 -1
  25. {kernel_tuner-0.3.2 → kernel_tuner-0.4.0}/kernel_tuner.egg-info/PKG-INFO +3 -3
  26. {kernel_tuner-0.3.2 → kernel_tuner-0.4.0}/kernel_tuner.egg-info/SOURCES.txt +7 -0
  27. {kernel_tuner-0.3.2 → kernel_tuner-0.4.0}/kernel_tuner.egg-info/requires.txt +1 -0
  28. {kernel_tuner-0.3.2 → kernel_tuner-0.4.0}/setup.py +5 -4
  29. kernel_tuner-0.4.0/test/test_c_functions.py +282 -0
  30. {kernel_tuner-0.3.2 → kernel_tuner-0.4.0}/test/test_core.py +71 -28
  31. kernel_tuner-0.4.0/test/test_cuda_functions.py +64 -0
  32. kernel_tuner-0.4.0/test/test_cuda_mocked.py +144 -0
  33. kernel_tuner-0.4.0/test/test_integration.py +167 -0
  34. {kernel_tuner-0.3.2 → kernel_tuner-0.4.0}/test/test_interface.py +21 -15
  35. kernel_tuner-0.4.0/test/test_kernelbuilder.py +57 -0
  36. {kernel_tuner-0.3.2 → kernel_tuner-0.4.0}/test/test_minimize.py +3 -7
  37. kernel_tuner-0.4.0/test/test_observers.py +36 -0
  38. {kernel_tuner-0.3.2 → kernel_tuner-0.4.0}/test/test_opencl_functions.py +16 -15
  39. {kernel_tuner-0.3.2 → kernel_tuner-0.4.0}/test/test_runners.py +39 -42
  40. {kernel_tuner-0.3.2 → kernel_tuner-0.4.0}/test/test_util_functions.py +125 -79
  41. kernel_tuner-0.3.2/kernel_tuner/__init__.py +0 -3
  42. kernel_tuner-0.3.2/test/test_c_functions.py +0 -218
  43. kernel_tuner-0.3.2/test/test_cuda_functions.py +0 -61
  44. kernel_tuner-0.3.2/test/test_cuda_mocked.py +0 -138
  45. {kernel_tuner-0.3.2 → kernel_tuner-0.4.0}/LICENSE +0 -0
  46. {kernel_tuner-0.3.2 → kernel_tuner-0.4.0}/MANIFEST.in +0 -0
  47. {kernel_tuner-0.3.2 → kernel_tuner-0.4.0}/README.rst +0 -0
  48. {kernel_tuner-0.3.2 → kernel_tuner-0.4.0}/kernel_tuner/runners/__init__.py +0 -0
  49. {kernel_tuner-0.3.2 → kernel_tuner-0.4.0}/kernel_tuner/strategies/__init__.py +0 -0
  50. {kernel_tuner-0.3.2 → kernel_tuner-0.4.0}/kernel_tuner/strategies/bayes_opt.py +0 -0
  51. {kernel_tuner-0.3.2 → kernel_tuner-0.4.0}/kernel_tuner/strategies/pso.py +0 -0
  52. {kernel_tuner-0.3.2 → kernel_tuner-0.4.0}/kernel_tuner.egg-info/dependency_links.txt +0 -0
  53. {kernel_tuner-0.3.2 → kernel_tuner-0.4.0}/kernel_tuner.egg-info/top_level.txt +0 -0
  54. {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.2
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 :: 4 - Beta
203
+ Classifier: Development Status :: 5 - Production/Stable
204
204
  Provides-Extra: doc
205
205
  Provides-Extra: cuda
206
206
  Provides-Extra: opencl
@@ -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,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 = {'CUDA': '.cu', 'OpenCL': '.cl', 'C': '.c'}
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
- if isinstance(arg, numpy.ndarray):
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 #runtime failure occured that should be ignored, skip correctness check
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, numpy.ndarray):
276
- result_host.append(numpy.zeros_like(arg))
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 numpy.prod(threads) > self.dev.max_threads:
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, kernel_options.block_size_names)
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
- del self.dev
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 numpy.allclose"""
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: #skip None elements in the answer list
444
- if isinstance(answer[i], numpy.ndarray) and isinstance(arg, numpy.ndarray):
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
- + " of the expected results list is not of the same dtype as the kernel output: "
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
- + " 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):
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
- + " of the expected results list is not the same as the kernel output: "
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], 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.")
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
- output_test = numpy.allclose(expected, result, atol=atol)
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
- numpy.set_printoptions(edgeitems=50)
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 __del__(self):
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, numpy.ndarray):
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
- power_readings = []
200
- start.record(stream=self.stream)
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
- #measure power usage until kernel is done
204
- t0 = time.time()
205
- while not end.query():
206
- if self.use_nvml:
207
- power_readings.append([time.time()-t0, self.nvml.pwr_usage()])
208
- end.synchronize()
209
- execution_time = end.time_since(start) #ms
210
- result["times"].append(execution_time)
211
-
212
- #pre and postfix to start at 0 and end at kernel end
213
- if power_readings:
214
- power_readings = [[0.0, power_readings[0][1]]] + power_readings
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):