kernel-tuner 0.3.0__tar.gz → 0.3.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 (44) hide show
  1. {kernel_tuner-0.3.0 → kernel_tuner-0.3.1}/PKG-INFO +4 -4
  2. {kernel_tuner-0.3.0 → kernel_tuner-0.3.1}/kernel_tuner/c.py +20 -12
  3. {kernel_tuner-0.3.0 → kernel_tuner-0.3.1}/kernel_tuner/core.py +4 -1
  4. {kernel_tuner-0.3.0 → kernel_tuner-0.3.1}/kernel_tuner/cuda.py +21 -4
  5. kernel_tuner-0.3.1/kernel_tuner/cupy_backend.py +331 -0
  6. {kernel_tuner-0.3.0 → kernel_tuner-0.3.1}/kernel_tuner/interface.py +14 -8
  7. kernel_tuner-0.3.1/kernel_tuner/kernelbuilder.py +98 -0
  8. {kernel_tuner-0.3.0 → kernel_tuner-0.3.1}/kernel_tuner/opencl.py +3 -3
  9. {kernel_tuner-0.3.0 → kernel_tuner-0.3.1}/kernel_tuner/strategies/bayes_opt.py +12 -1
  10. {kernel_tuner-0.3.0 → kernel_tuner-0.3.1}/kernel_tuner/util.py +0 -1
  11. {kernel_tuner-0.3.0 → kernel_tuner-0.3.1}/kernel_tuner.egg-info/PKG-INFO +4 -4
  12. {kernel_tuner-0.3.0 → kernel_tuner-0.3.1}/kernel_tuner.egg-info/SOURCES.txt +2 -0
  13. {kernel_tuner-0.3.0 → kernel_tuner-0.3.1}/setup.py +1 -1
  14. {kernel_tuner-0.3.0 → kernel_tuner-0.3.1}/test/test_c_functions.py +34 -1
  15. {kernel_tuner-0.3.0 → kernel_tuner-0.3.1}/test/test_cuda_functions.py +9 -3
  16. {kernel_tuner-0.3.0 → kernel_tuner-0.3.1}/test/test_cuda_mocked.py +5 -2
  17. {kernel_tuner-0.3.0 → kernel_tuner-0.3.1}/test/test_interface.py +3 -3
  18. {kernel_tuner-0.3.0 → kernel_tuner-0.3.1}/test/test_opencl_functions.py +6 -1
  19. {kernel_tuner-0.3.0 → kernel_tuner-0.3.1}/test/test_util_functions.py +1 -0
  20. {kernel_tuner-0.3.0 → kernel_tuner-0.3.1}/LICENSE +0 -0
  21. {kernel_tuner-0.3.0 → kernel_tuner-0.3.1}/MANIFEST.in +0 -0
  22. {kernel_tuner-0.3.0 → kernel_tuner-0.3.1}/README.rst +0 -0
  23. {kernel_tuner-0.3.0 → kernel_tuner-0.3.1}/kernel_tuner/__init__.py +0 -0
  24. {kernel_tuner-0.3.0 → kernel_tuner-0.3.1}/kernel_tuner/nvml.py +0 -0
  25. {kernel_tuner-0.3.0 → kernel_tuner-0.3.1}/kernel_tuner/runners/__init__.py +0 -0
  26. {kernel_tuner-0.3.0 → kernel_tuner-0.3.1}/kernel_tuner/runners/sequential.py +0 -0
  27. {kernel_tuner-0.3.0 → kernel_tuner-0.3.1}/kernel_tuner/strategies/__init__.py +0 -0
  28. {kernel_tuner-0.3.0 → kernel_tuner-0.3.1}/kernel_tuner/strategies/basinhopping.py +0 -0
  29. {kernel_tuner-0.3.0 → kernel_tuner-0.3.1}/kernel_tuner/strategies/brute_force.py +0 -0
  30. {kernel_tuner-0.3.0 → kernel_tuner-0.3.1}/kernel_tuner/strategies/diff_evo.py +0 -0
  31. {kernel_tuner-0.3.0 → kernel_tuner-0.3.1}/kernel_tuner/strategies/firefly_algorithm.py +0 -0
  32. {kernel_tuner-0.3.0 → kernel_tuner-0.3.1}/kernel_tuner/strategies/genetic_algorithm.py +0 -0
  33. {kernel_tuner-0.3.0 → kernel_tuner-0.3.1}/kernel_tuner/strategies/minimize.py +0 -0
  34. {kernel_tuner-0.3.0 → kernel_tuner-0.3.1}/kernel_tuner/strategies/pso.py +0 -0
  35. {kernel_tuner-0.3.0 → kernel_tuner-0.3.1}/kernel_tuner/strategies/random_sample.py +0 -0
  36. {kernel_tuner-0.3.0 → kernel_tuner-0.3.1}/kernel_tuner/strategies/simulated_annealing.py +0 -0
  37. {kernel_tuner-0.3.0 → kernel_tuner-0.3.1}/kernel_tuner/wrappers.py +0 -0
  38. {kernel_tuner-0.3.0 → kernel_tuner-0.3.1}/kernel_tuner.egg-info/dependency_links.txt +0 -0
  39. {kernel_tuner-0.3.0 → kernel_tuner-0.3.1}/kernel_tuner.egg-info/requires.txt +0 -0
  40. {kernel_tuner-0.3.0 → kernel_tuner-0.3.1}/kernel_tuner.egg-info/top_level.txt +0 -0
  41. {kernel_tuner-0.3.0 → kernel_tuner-0.3.1}/setup.cfg +0 -0
  42. {kernel_tuner-0.3.0 → kernel_tuner-0.3.1}/test/test_core.py +0 -0
  43. {kernel_tuner-0.3.0 → kernel_tuner-0.3.1}/test/test_minimize.py +0 -0
  44. {kernel_tuner-0.3.0 → kernel_tuner-0.3.1}/test/test_runners.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: kernel_tuner
3
- Version: 0.3.0
3
+ Version: 0.3.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
@@ -202,8 +202,8 @@ Classifier: Topic :: Software Development
202
202
  Classifier: Topic :: System :: Distributed Computing
203
203
  Classifier: Development Status :: 4 - Beta
204
204
  Provides-Extra: dev
205
- Provides-Extra: doc
206
- Provides-Extra: cuda_opencl
207
- Provides-Extra: tutorial
208
205
  Provides-Extra: cuda
206
+ Provides-Extra: tutorial
209
207
  Provides-Extra: opencl
208
+ Provides-Extra: cuda_opencl
209
+ Provides-Extra: doc
@@ -105,7 +105,7 @@ class CFunctions(object):
105
105
  ctype_args[i] = Argument(numpy=data, ctypes=data_ctypes)
106
106
  return ctype_args
107
107
 
108
- def compile(self, kernel_name, kernel_string):
108
+ def compile(self, kernel_instance):
109
109
  """call the C compiler to compile the kernel, return the function
110
110
 
111
111
  :param kernel_name: The name of the kernel to be compiled, used to lookup the
@@ -118,7 +118,9 @@ class CFunctions(object):
118
118
  :returns: An ctypes function that can be called directly.
119
119
  :rtype: ctypes._FuncPtr
120
120
  """
121
- logging.debug('compiling ' + kernel_name)
121
+ logging.debug('compiling ' + kernel_instance.name)
122
+
123
+ kernel_string = kernel_instance.kernel_string
122
124
 
123
125
  if self.lib != None:
124
126
  self.cleanup_lib()
@@ -134,25 +136,31 @@ class CFunctions(object):
134
136
  else:
135
137
  compiler_options.append("-fopenmp")
136
138
 
137
- # TODO: Get suffix from KernelSources, and adapt the logic below
139
+ #if filename is known, use that one
140
+ suffix = kernel_instance.kernel_source.get_user_suffix()
138
141
 
139
- #select right suffix based on compiler
140
- suffix = ".cc"
142
+ #if code contains device code, suffix .cu is required
143
+ device_code_signals = ["__global", "__syncthreads()", "threadIdx"]
144
+ if any([snippet in kernel_string for snippet in device_code_signals]):
145
+ suffix = ".cu"
141
146
 
142
147
  #detect whether to use nvcc as default instead of g++, may overrule an explicitly passed g++
143
- if ("#include <cuda" in kernel_string) or ("cudaMemcpy" in kernel_string):
148
+ if (suffix == ".cu") or ("#include <cuda" in kernel_string) or ("cudaMemcpy" in kernel_string):
144
149
  if self.compiler == "g++" and self.nvcc_available:
145
150
  self.compiler = "nvcc"
146
151
 
147
- #if contains device code suffix .cu is required by nvcc
148
- if self.compiler == "nvcc" and "__global__" in kernel_string:
149
- suffix = ".cu"
150
- if self.compiler in ["gfortran", "pgfortran", "ftn", "ifort"]:
151
- suffix = ".F90"
152
+ if suffix is None:
153
+ #select right suffix based on compiler
154
+ suffix = ".cc"
155
+
156
+ if self.compiler in ["gfortran", "pgfortran", "ftn", "ifort"]:
157
+ suffix = ".F90"
152
158
 
153
159
  if self.compiler == "nvcc":
154
160
  compiler_options = ["-Xcompiler=" + c for c in compiler_options]
155
161
 
162
+ #this basically checks if we aren't compiling Fortran
163
+ #at the moment any C, C++, or CUDA code is assumed to use extern "C" linkage
156
164
  if ".c" in suffix:
157
165
  if not "extern \"C\"" in kernel_string:
158
166
  kernel_string = "extern \"C\" {\n" + kernel_string + "\n}"
@@ -194,7 +202,7 @@ class CFunctions(object):
194
202
 
195
203
 
196
204
  self.lib = numpy.ctypeslib.load_library(filename, '.')
197
- func = getattr(self.lib, kernel_name)
205
+ func = getattr(self.lib, kernel_instance.name)
198
206
  func.restype = C.c_float
199
207
 
200
208
  finally:
@@ -310,6 +310,9 @@ class DeviceInterface(object):
310
310
  return None
311
311
 
312
312
  #add constant memory arguments to compiled module
313
+ if kernel_options.smem_args is not None:
314
+ self.dev.copy_shared_memory_args(kernel_options.smem_args)
315
+ #add constant memory arguments to compiled module
313
316
  if kernel_options.cmem_args is not None:
314
317
  self.dev.copy_constant_memory_args(kernel_options.cmem_args)
315
318
  #add texture memory arguments to compiled module
@@ -341,7 +344,7 @@ class DeviceInterface(object):
341
344
  #compile kernel_string into device func
342
345
  func = None
343
346
  try:
344
- func = self.dev.compile(instance.name, instance.kernel_string)
347
+ func = self.dev.compile(instance)
345
348
  except Exception as e:
346
349
  #compiles may fail because certain kernel configurations use too
347
350
  #much shared memory for example, the desired behavior is to simply
@@ -47,7 +47,13 @@ class CudaFunctions(object):
47
47
 
48
48
  drv.init()
49
49
  self.context = drv.Device(device).make_context()
50
- self.nvml = nvml(device)
50
+
51
+ try:
52
+ self.nvml = nvml(device)
53
+ self.nvml.pwr_usage()
54
+ self.use_nvml = True
55
+ except:
56
+ self.use_nvml = False
51
57
 
52
58
  #inspect device properties
53
59
  devprops = {str(k): v for (k, v) in self.context.get_device().get_attributes().items()}
@@ -71,6 +77,9 @@ class CudaFunctions(object):
71
77
  #create a stream
72
78
  self.stream = drv.Stream()
73
79
 
80
+ #default dynamically allocated shared memory size, can be overwritten using smem_args
81
+ self.smem_size = 0
82
+
74
83
  #collect environment information
75
84
  env = dict()
76
85
  env["device_name"] = self.context.get_device().name()
@@ -113,7 +122,7 @@ class CudaFunctions(object):
113
122
  return gpu_args
114
123
 
115
124
 
116
- def compile(self, kernel_name, kernel_string):
125
+ def compile(self, kernel_instance):
117
126
  """call the CUDA compiler to compile the kernel, return the device function
118
127
 
119
128
  :param kernel_name: The name of the kernel to be compiled, used to lookup the
@@ -126,6 +135,9 @@ class CudaFunctions(object):
126
135
  :returns: An CUDA kernel that can be called directly.
127
136
  :rtype: pycuda.driver.Function
128
137
  """
138
+ kernel_string = kernel_instance.kernel_string
139
+ kernel_name = kernel_instance.name
140
+
129
141
  try:
130
142
  no_extern_c = 'extern "C"' in kernel_string
131
143
 
@@ -191,7 +203,8 @@ class CudaFunctions(object):
191
203
  #measure power usage until kernel is done
192
204
  t0 = time.time()
193
205
  while not end.query():
194
- power_readings.append([time.time()-t0, self.nvml.pwr_usage()])
206
+ if self.use_nvml:
207
+ power_readings.append([time.time()-t0, self.nvml.pwr_usage()])
195
208
  end.synchronize()
196
209
  execution_time = end.time_since(start) #ms
197
210
  result["times"].append(execution_time)
@@ -233,6 +246,10 @@ class CudaFunctions(object):
233
246
  logging.debug(v.flags)
234
247
  drv.memcpy_htod(symbol, v)
235
248
 
249
+ def copy_shared_memory_args(self, smem_args):
250
+ """add shared memory arguments to the kernel"""
251
+ self.smem_size = smem_args["size"]
252
+
236
253
  def copy_texture_memory_args(self, texmem_args):
237
254
  """adds texture memory arguments to the most recently compiled module
238
255
 
@@ -308,7 +325,7 @@ class CudaFunctions(object):
308
325
  of the grid
309
326
  :type grid: tuple(int, int)
310
327
  """
311
- func(*gpu_args, block=threads, grid=grid, stream=stream, texrefs=self.texrefs)
328
+ func(*gpu_args, block=threads, grid=grid, stream=stream, shared=self.smem_size, texrefs=self.texrefs)
312
329
 
313
330
  def memset(self, allocation, value, size):
314
331
  """set the memory in allocation to the value in value
@@ -0,0 +1,331 @@
1
+ """This module contains all CUDA specific kernel_tuner functions"""
2
+ from __future__ import print_function
3
+
4
+ import logging
5
+ import time
6
+ import numpy as np
7
+
8
+ import cupy as cp
9
+
10
+ import cupy.cuda as cuda
11
+
12
+ from kernel_tuner.nvml import nvml
13
+
14
+
15
+
16
+
17
+ class CupyBackend(object):
18
+ """Class that groups the CUDA functions on maintains state about the device"""
19
+
20
+ def __init__(self, device=0, iterations=7, compiler_options=None):
21
+ """instantiate CudaFunctions object used for interacting with the CUDA device
22
+
23
+ Instantiating this object will inspect and store certain device properties at
24
+ runtime, which are used during compilation and/or execution of kernels by the
25
+ kernel tuner. It also maintains a reference to the most recently compiled
26
+ source module for copying data to constant memory before kernel launch.
27
+
28
+ :param device: Number of CUDA device to use for this context
29
+ :type device: int
30
+
31
+ :param iterations: Number of iterations used while benchmarking a kernel, 7 by default.
32
+ :type iterations: int
33
+ """
34
+
35
+ self.allocations = []
36
+ self.compiler_options = compiler_options
37
+ self.iterations = iterations
38
+
39
+ self.dev = cuda.Device(device)
40
+ self.dev.use() #make this the current device
41
+ self.smem_size = 0
42
+ self.use_nvml = False
43
+
44
+ self.stream = cuda.Stream()
45
+ self.max_threads = self.dev.attributes["MaxThreadsPerBlock"]
46
+ self.cc = self.dev.compute_capability
47
+
48
+ self.env = {"attributes": self.dev.attributes}
49
+ self.name = "cupy does not name device"
50
+
51
+
52
+
53
+ def ready_argument_list(self, arguments):
54
+ """ready argument list to be passed to the kernel, allocates gpu mem
55
+
56
+ :param arguments: List of arguments to be passed to the kernel.
57
+ The order should match the argument list on the CUDA kernel.
58
+ Allowed values are np.ndarray, and/or np.int32, np.float32, and so on.
59
+ :type arguments: list(numpy objects)
60
+
61
+ :returns: A list of arguments that can be passed to an CUDA kernel.
62
+ :rtype: list( pycuda.driver.DeviceAllocation, np.int32, ... )
63
+ """
64
+ gpu_args = []
65
+ for arg in arguments:
66
+ # if arg i is a numpy array copy to device
67
+ if isinstance(arg, np.ndarray):
68
+ alloc = cp.asarray(arg)
69
+ self.allocations.append(alloc)
70
+ gpu_args.append(alloc)
71
+ else: # if not an array, just pass argument along
72
+ #if we remove other checks this might just pass along cupy arrays too
73
+ gpu_args.append(arg)
74
+ return gpu_args
75
+
76
+
77
+ def compile(self, kernel_instance):
78
+ """call the CUDA compiler to compile the kernel, return the device function
79
+
80
+ :param kernel_name: The name of the kernel to be compiled, used to lookup the
81
+ function after compilation.
82
+ :type kernel_name: string
83
+
84
+ :param kernel_string: The CUDA kernel code that contains the function `kernel_name`
85
+ :type kernel_string: string
86
+
87
+ :returns: An CUDA kernel that can be called directly.
88
+ :rtype: pycuda.driver.Function
89
+ """
90
+ kernel_string = kernel_instance.kernel_string
91
+ kernel_name = kernel_instance.name
92
+
93
+ try:
94
+ #mimic pycuda behavior for the moment, this can be removed once
95
+ #https://github.com/cupy/cupy/pull/3319
96
+ #has been merged
97
+ no_extern_c = not 'extern "C"' in kernel_string
98
+ if no_extern_c:
99
+ kernel_string = 'extern "C" {\n' + kernel_string + '\n}\n'
100
+
101
+ #self.current_module = cp.RawModule(code=kernel_string, options=self.compiler_options)
102
+ self.current_module = cp.RawModule(code=kernel_string)
103
+
104
+ func = self.current_module.get_function(kernel_name)
105
+ return func
106
+ except Exception as e:
107
+ if "uses too much shared data" in e.stderr:
108
+ raise Exception("uses too much shared data")
109
+ else:
110
+ raise e
111
+
112
+
113
+ def benchmark(self, func, gpu_args, threads, grid):
114
+ """runs the kernel and measures time repeatedly, returns average time
115
+
116
+ Runs the kernel and measures kernel execution time repeatedly, number of
117
+ iterations is set during the creation of CudaFunctions. Benchmark returns
118
+ a robust average, from all measurements the fastest and slowest runs are
119
+ discarded and the rest is included in the returned average. The reason for
120
+ this is to be robust against initialization artifacts and other exceptional
121
+ cases.
122
+
123
+ :param func: A PyCuda kernel compiled for this specific kernel configuration
124
+ :type func: pycuda.driver.Function
125
+
126
+ :param gpu_args: A list of arguments to the kernel, order should match the
127
+ order in the code. Allowed values are either variables in global memory
128
+ or single values passed by value.
129
+ :type gpu_args: list( pycuda.driver.DeviceAllocation, np.int32, ...)
130
+
131
+ :param threads: A tuple listing the number of threads in each dimension of
132
+ the thread block
133
+ :type threads: tuple(int, int, int)
134
+
135
+ :param grid: A tuple listing the number of thread blocks in each dimension
136
+ of the grid
137
+ :type grid: tuple(int, int)
138
+
139
+ :returns: A dictionary with benchmark results.
140
+ :rtype: dict()
141
+ """
142
+ result = dict()
143
+ result["times"] = []
144
+ result["power"] = []
145
+ energy = []
146
+ start = cuda.Event()
147
+ end = cuda.Event()
148
+ self.dev.synchronize()
149
+ for _ in range(self.iterations):
150
+ power_readings = []
151
+ self.stream.record(start)
152
+ self.run_kernel(func, gpu_args, threads, grid, stream=self.stream)
153
+ self.stream.record(end)
154
+ #measure power usage until kernel is done
155
+ t0 = time.time()
156
+ while not end.done:
157
+ if self.use_nvml:
158
+ power_readings.append([time.time()-t0, self.nvml.pwr_usage()])
159
+ end.synchronize()
160
+ execution_time = cuda.get_elapsed_time(start, end) #ms
161
+ result["times"].append(execution_time)
162
+
163
+ #pre and postfix to start at 0 and end at kernel end
164
+ if power_readings:
165
+ power_readings = [[0.0, power_readings[0][1]]] + power_readings
166
+ power_readings = power_readings + [[execution_time / 1000.0, power_readings[-1][1]]]
167
+ result["power"].append(power_readings) #time in s, power usage in milliwatts
168
+
169
+ #compute energy consumption as area under curve
170
+ x = [d[0] for d in power_readings]
171
+ y = [d[1]/1000.0 for d in power_readings] #convert to watts
172
+ energy.append(np.trapz(y,x)) #in Joule
173
+
174
+ result["time"] = np.mean(result["times"])
175
+ if (self.iterations > 10) and energy:
176
+ result["energy"] = np.mean(energy[10:])
177
+ return result
178
+
179
+ def copy_constant_memory_args(self, cmem_args):
180
+ """adds constant memory arguments to the most recently compiled module
181
+
182
+ :param cmem_args: A dictionary containing the data to be passed to the
183
+ device constant memory. The format to be used is as follows: A
184
+ string key is used to name the constant memory symbol to which the
185
+ value needs to be copied. Similar to regular arguments, these need
186
+ to be numpy objects, such as np.ndarray or np.int32, and so on.
187
+ :type cmem_args: dict( string: np.ndarray, ... )
188
+ """
189
+ logging.debug('copy_constant_memory_args called')
190
+ logging.debug('current module: ' + str(self.current_module))
191
+ for k, v in cmem_args.items():
192
+ #symbol = self.current_module.get_global(k)[0]
193
+ logging.debug('copying to symbol: ' + str(symbol))
194
+ logging.debug('array to be copied: ')
195
+ logging.debug(v.nbytes)
196
+ logging.debug(v.dtype)
197
+ logging.debug(v.flags)
198
+ #drv.memcpy_htod(symbol, v)
199
+
200
+ def copy_shared_memory_args(self, smem_args):
201
+ """add shared memory arguments to the kernel"""
202
+ self.smem_size = smem_args["size"]
203
+
204
+ def copy_texture_memory_args(self, texmem_args):
205
+ """adds texture memory arguments to the most recently compiled module
206
+
207
+ :param texmem_args: A dictionary containing the data to be passed to the
208
+ device texture memory. See tune_kernel().
209
+ :type texmem_args: dict
210
+ """
211
+
212
+ """
213
+ filter_mode_map = { 'point': drv.filter_mode.POINT,
214
+ 'linear': drv.filter_mode.LINEAR }
215
+ address_mode_map = { 'border': drv.address_mode.BORDER,
216
+ 'clamp': drv.address_mode.CLAMP,
217
+ 'mirror': drv.address_mode.MIRROR,
218
+ 'wrap': drv.address_mode.WRAP }
219
+
220
+ logging.debug('copy_texture_memory_args called')
221
+ logging.debug('current module: ' + str(self.current_module))
222
+ self.texrefs = []
223
+ for k, v in texmem_args.items():
224
+ tex = self.current_module.get_texref(k)
225
+ self.texrefs.append(tex)
226
+
227
+ logging.debug('copying to texture: ' + str(k))
228
+ if not isinstance(v, dict):
229
+ data = v
230
+ else:
231
+ data = v['array']
232
+ logging.debug('texture to be copied: ')
233
+ logging.debug(data.nbytes)
234
+ logging.debug(data.dtype)
235
+ logging.debug(data.flags)
236
+
237
+ drv.matrix_to_texref(data, tex, order="C")
238
+
239
+ if isinstance(v, dict):
240
+ if 'address_mode' in v and v['address_mode'] is not None:
241
+ # address_mode is set per axis
242
+ amode = v['address_mode']
243
+ if not isinstance(amode, list):
244
+ amode = [ amode ] * data.ndim
245
+ for i, m in enumerate(amode):
246
+ try:
247
+ if m is not None:
248
+ tex.set_address_mode(i, address_mode_map[m])
249
+ except KeyError:
250
+ raise ValueError('Unknown address mode: ' + m)
251
+ if 'filter_mode' in v and v['filter_mode'] is not None:
252
+ fmode = v['filter_mode']
253
+ try:
254
+ tex.set_filter_mode(filter_mode_map[fmode])
255
+ except KeyError:
256
+ raise ValueError('Unknown filter mode: ' + fmode)
257
+ if 'normalized_coordinates' in v and v['normalized_coordinates']:
258
+ tex.set_flags(tex.get_flags() | drv.TRSF_NORMALIZED_COORDINATES)
259
+ """
260
+ pass
261
+
262
+
263
+ def run_kernel(self, func, gpu_args, threads, grid, stream=None):
264
+ """runs the CUDA kernel passed as 'func'
265
+
266
+ :param func: A PyCuda kernel compiled for this specific kernel configuration
267
+ :type func: pycuda.driver.Function
268
+
269
+ :param gpu_args: A list of arguments to the kernel, order should match the
270
+ order in the code. Allowed values are either variables in global memory
271
+ or single values passed by value.
272
+ :type gpu_args: list( pycuda.driver.DeviceAllocation, np.int32, ...)
273
+
274
+ :param threads: A tuple listing the number of threads in each dimension of
275
+ the thread block
276
+ :type threads: tuple(int, int, int)
277
+
278
+ :param grid: A tuple listing the number of thread blocks in each dimension
279
+ of the grid
280
+ :type grid: tuple(int, int)
281
+ """
282
+ func(grid, threads, gpu_args, shared_mem=self.smem_size)
283
+
284
+ def memset(self, allocation, value, size):
285
+ """set the memory in allocation to the value in value
286
+
287
+ :param allocation: A GPU memory allocation unit
288
+ :type allocation: pycuda.driver.DeviceAllocation
289
+
290
+ :param value: The value to set the memory to
291
+ :type value: a single 8-bit unsigned int
292
+
293
+ :param size: The size of to the allocation unit in bytes
294
+ :type size: int
295
+
296
+ """
297
+ #drv.memset_d8(allocation, value, size)
298
+ allocation = cp.full_like(allocation, value)
299
+
300
+
301
+ def memcpy_dtoh(self, dest, src):
302
+ """perform a device to host memory copy
303
+
304
+ :param dest: A numpy array in host memory to store the data
305
+ :type dest: np.ndarray
306
+
307
+ :param src: A GPU memory allocation unit
308
+ :type src: pycuda.driver.DeviceAllocation
309
+ """
310
+ if isinstance(src, cp.ndarray):
311
+ dest = cp.ndarray.get()
312
+ else:
313
+ dest = src
314
+
315
+ def memcpy_htod(self, dest, src):
316
+ """perform a host to device memory copy
317
+
318
+ :param dest: A GPU memory allocation unit
319
+ :type dest: pycuda.driver.DeviceAllocation
320
+
321
+ :param src: A numpy array in host memory to store the data
322
+ :type src: np.ndarray
323
+ """
324
+ if isinstance(dest, cp.ndarray):
325
+ cp.copyto(dest, src) #might fail if src is not cupy array
326
+ else:
327
+ dest = src
328
+
329
+ units = {'time': 'ms',
330
+ 'power': 's,mW',
331
+ 'energy': 'J'}
@@ -80,13 +80,12 @@ _kernel_options = Options([
80
80
  parameters. This function should take one positional argument,
81
81
  which will be used to pass a dict containing the parameters.
82
82
  The function should return a string with the source code for
83
- the kernel.""",
84
- "string or list and/or callable")),
85
- ("lang", ("""Specifies the language used for GPU kernels. The kernel_tuner
83
+ the kernel.""", "string or list and/or callable")),
84
+ ("lang", ("""Specifies the language used for GPU kernels. The kernel_tuner
86
85
  automatically detects the language, but if it fails, you may specify
87
86
  the language using this argument, currently supported: "CUDA",
88
87
  "OpenCL", or "C".""", "string")),
89
- ("problem_size", ("""An int or string, or 1,2,3-dimensional tuple
88
+ ("problem_size", ("""An int or string, or 1,2,3-dimensional tuple
90
89
  containing the size from which the grid dimensions of the kernel
91
90
  will be computed.
92
91
 
@@ -129,6 +128,12 @@ _kernel_options = Options([
129
128
  the grid dimensions in the z-direction, ["block_size_z"] by default.
130
129
  If you do not want to divide the problem_size, you should pass an empty list.
131
130
  See grid_div_x for more details.""", "list")),
131
+ ("smem_args", ("""CUDA-specific feature for specifying shared memory options
132
+ to the kernel. At the moment only 'size' is supported, but setting the
133
+ shared memory configuration on Kepler GPUs for example could be added
134
+ in the future. Size should denote the number of bytes for to use when
135
+ dynamically allocating shared memory.""",
136
+ "dict(string: numpy object)")),
132
137
  ("cmem_args", ("""CUDA-specific feature for specifying constant memory
133
138
  arguments to the kernel. In OpenCL these are handled as normal
134
139
  kernel arguments, but in CUDA you can copy to a symbol. The way you
@@ -142,7 +147,8 @@ _kernel_options = Options([
142
147
  the texture contents. These contents can be either simply a numpy object,
143
148
  or a dictionary containing the numpy object under the key 'array' plus the
144
149
  configuration options 'filter_mode' ('point' or 'linear), 'address_mode'
145
- (a list of 'border', 'clamp', 'mirror', 'wrap' per axis), 'normalized_coordinates' (True/False).""",
150
+ (a list of 'border', 'clamp', 'mirror', 'wrap' per axis),
151
+ 'normalized_coordinates' (True/False).""",
146
152
  "dict(string: numpy object or dict)")),
147
153
  ("block_size_names", ("""A list of strings that replace the defaults for the names
148
154
  that denote the thread block dimensions. If not passed, the behavior
@@ -309,7 +315,7 @@ _tuning_options = Options([
309
315
  * too many resources requested for launch
310
316
 
311
317
  verbose is False by default.""", "bool")),
312
- ("cache",("""filename for caching/logging benchmarked instances
318
+ ("cache", ("""filename for caching/logging benchmarked instances
313
319
  filename uses suffix ".json"
314
320
  if the file exists it is read and tuning continues from this file
315
321
  """, "string"))
@@ -357,7 +363,7 @@ _tune_kernel_docstring = """ Tune a CUDA kernel given a set of tunable parameter
357
363
  def tune_kernel(kernel_name, kernel_string, problem_size, arguments,
358
364
  tune_params, grid_div_x=None, grid_div_y=None, grid_div_z=None,
359
365
  restrictions=None, answer=None, atol=1e-6, verify=None, verbose=False,
360
- lang=None, device=0, platform=0, cmem_args=None, texmem_args=None,
366
+ lang=None, device=0, platform=0, smem_args=None, cmem_args=None, texmem_args=None,
361
367
  compiler=None, compiler_options=None, log=None,
362
368
  iterations=7, block_size_names=None, quiet=False, strategy=None, strategy_options=None,
363
369
  cache=None):
@@ -490,7 +496,7 @@ _run_kernel_docstring = """Compile and run a single kernel
490
496
 
491
497
  def run_kernel(kernel_name, kernel_string, problem_size, arguments,
492
498
  params, grid_div_x=None, grid_div_y=None, grid_div_z=None,
493
- lang=None, device=0, platform=0, cmem_args=None, texmem_args=None, compiler=None, compiler_options=None,
499
+ lang=None, device=0, platform=0, smem_args=None, cmem_args=None, texmem_args=None, compiler=None, compiler_options=None,
494
500
  block_size_names=None, quiet=False, log=None):
495
501
 
496
502
  if log:
@@ -0,0 +1,98 @@
1
+ import numpy as np
2
+
3
+ from kernel_tuner import core
4
+ from kernel_tuner.interface import Options, _kernel_options
5
+
6
+ class PythonKernel(object):
7
+
8
+ def __init__(self, kernel_name, kernel_string, problem_size, arguments, params, inputs=None, outputs=None, device=0, platform=0,
9
+ block_size_names=None, grid_div_x=None, grid_div_y=None, grid_div_z=None, verbose=True, lang=None):
10
+ """ Construct Python helper object to compile and call the kernel from Python
11
+
12
+ This object compiles a GPU kernel parameterized using the parameters in params.
13
+ GPU memory is allocated for each argument using its size and type as listed in arguments.
14
+ The object can be called directly as a function with the kernel arguments as function arguments.
15
+ Kernel arguments marked as inputs will be copied to the GPU on every kernel launch.
16
+ Only the kernel arguments marked as outputs will be returned, note that the result is always
17
+ returned in a list, even when there is only one output.
18
+
19
+ Most of the arguments to this function are the same as with tune_kernel or run_kernel in Kernel Tuner,
20
+ and are therefore not duplicated here. The two new arguments are:
21
+
22
+ :param inputs: a boolean list of length arguments to signal whether an argument is input to the kernel
23
+ :type inputs: list(bool)
24
+
25
+ :param outputs: a boolean list of length arguments to signal whether an argument is output of the kernel
26
+ :type outputs: list(bool)
27
+
28
+ """
29
+ #construct device interface
30
+ kernel_source = core.KernelSource(kernel_string, lang)
31
+ self.dev = core.DeviceInterface(kernel_source, device=device)
32
+
33
+ #construct kernel_options to hold information about the kernel
34
+ opts = locals()
35
+ kernel_options = Options([(k, opts[k]) for k in _kernel_options.keys() if k in opts.keys()])
36
+
37
+ #instantiate the kernel given the parameters in params
38
+ self.kernel_instance = self.dev.create_kernel_instance(kernel_source, kernel_options, params, verbose)
39
+
40
+ #compile the kernel
41
+ self.func = self.dev.compile_kernel(self.kernel_instance, verbose)
42
+
43
+ #setup GPU memory
44
+ self.gpu_args = self.dev.ready_argument_list(arguments)
45
+ if inputs:
46
+ self.inputs = inputs
47
+ else:
48
+ self.inputs = [True for _ in arguments]
49
+ if outputs:
50
+ self.outputs = outputs
51
+ else:
52
+ self.outputs = [True for _ in arguments]
53
+
54
+ def update_gpu_args(self, args):
55
+ for i, arg in enumerate(args):
56
+ if self.inputs[i]:
57
+ if isinstance(args[i], np.ndarray):
58
+ self.dev.dev.memcpy_htod(self.gpu_args[i], arg)
59
+ else:
60
+ self.gpu_args[i] = arg
61
+ return self.gpu_args
62
+
63
+ def get_gpu_result(self, args):
64
+ results = []
65
+ for i, _ in enumerate(self.gpu_args):
66
+ if self.outputs[i] and isinstance(args[i], np.ndarray):
67
+ res = np.zeros_like(args[i])
68
+ self.dev.memcpy_dtoh(res, self.gpu_args[i])
69
+ results.append(res)
70
+ return results
71
+
72
+ def run_kernel(self, args):
73
+ """Run the GPU kernel
74
+
75
+ Copy the arguments marked as inputs to the GPU
76
+ Call the GPU kernel
77
+ Copy the arguments marked as outputs from the GPU
78
+ Return the outputs in a list of numpy arrays
79
+
80
+ :param args: A list with the kernel arguments as numpy arrays or numpy scalars
81
+ :type args: list(np.ndarray or np.generic)
82
+ """
83
+ self.update_gpu_args(args)
84
+ self.dev.run_kernel(self.func, self.gpu_args, self.kernel_instance)
85
+ return self.get_gpu_result(args)
86
+
87
+ def __call__(self, *args):
88
+ """Run the GPU kernel
89
+
90
+ Copy the arguments marked as inputs to the GPU
91
+ Call the GPU kernel
92
+ Copy the arguments marked as outputs from the GPU
93
+ Return the outputs in a list of numpy arrays
94
+
95
+ :param *args: A variable number of kernel arguments as numpy arrays or numpy scalars
96
+ :type *args: np.ndarray or np.generic
97
+ """
98
+ return self.run_kernel(args)
@@ -69,7 +69,7 @@ class OpenCLFunctions(object):
69
69
  gpu_args.append(arg)
70
70
  return gpu_args
71
71
 
72
- def compile(self, kernel_name, kernel_string):
72
+ def compile(self, kernel_instance):
73
73
  """call the OpenCL compiler to compile the kernel, return the device function
74
74
 
75
75
  :param kernel_name: The name of the kernel to be compiled, used to lookup the
@@ -82,8 +82,8 @@ class OpenCLFunctions(object):
82
82
  :returns: An OpenCL kernel that can be called directly.
83
83
  :rtype: pyopencl.Kernel
84
84
  """
85
- prg = cl.Program(self.ctx, kernel_string).build(options=self.compiler_options)
86
- func = getattr(prg, kernel_name)
85
+ prg = cl.Program(self.ctx, kernel_instance.kernel_string).build(options=self.compiler_options)
86
+ func = getattr(prg, kernel_instance.name)
87
87
  return func
88
88
 
89
89
  def benchmark(self, func, gpu_args, threads, grid):
@@ -2,7 +2,15 @@
2
2
  from __future__ import print_function
3
3
 
4
4
  from collections import OrderedDict
5
- from bayes_opt import BayesianOptimization
5
+
6
+ try:
7
+ from bayes_opt import BayesianOptimization
8
+ bayes_opt_present=True
9
+ except Exception:
10
+ BayesianOptimization = None
11
+ bayes_opt_present=False
12
+
13
+
6
14
 
7
15
  from kernel_tuner.strategies import minimize
8
16
 
@@ -47,6 +55,9 @@ def tune(runner, kernel_options, device_options, tuning_options):
47
55
  # acq="ei", xi=1e-1
48
56
  # acq="ucb", kappa=10
49
57
 
58
+ if not bayes_opt_present:
59
+ raise ImportError("Error: optional dependency Bayesian Optimization not installed")
60
+
50
61
  #defaults as used by Bayesian Optimization Python package
51
62
  acq = tuning_options.strategy_options.get("method", "poi")
52
63
  kappa = tuning_options.strategy_options.get("kappa", 2.576)
@@ -10,7 +10,6 @@ import logging
10
10
  import warnings
11
11
  import re
12
12
 
13
- import pytest
14
13
  import numpy
15
14
 
16
15
  default_block_size_names = ["block_size_x", "block_size_y", "block_size_z"]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: kernel-tuner
3
- Version: 0.3.0
3
+ Version: 0.3.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
@@ -202,8 +202,8 @@ Classifier: Topic :: Software Development
202
202
  Classifier: Topic :: System :: Distributed Computing
203
203
  Classifier: Development Status :: 4 - Beta
204
204
  Provides-Extra: dev
205
- Provides-Extra: doc
206
- Provides-Extra: cuda_opencl
207
- Provides-Extra: tutorial
208
205
  Provides-Extra: cuda
206
+ Provides-Extra: tutorial
209
207
  Provides-Extra: opencl
208
+ Provides-Extra: cuda_opencl
209
+ Provides-Extra: doc
@@ -7,7 +7,9 @@ kernel_tuner/__init__.py
7
7
  kernel_tuner/c.py
8
8
  kernel_tuner/core.py
9
9
  kernel_tuner/cuda.py
10
+ kernel_tuner/cupy_backend.py
10
11
  kernel_tuner/interface.py
12
+ kernel_tuner/kernelbuilder.py
11
13
  kernel_tuner/nvml.py
12
14
  kernel_tuner/opencl.py
13
15
  kernel_tuner/util.py
@@ -14,7 +14,7 @@ else:
14
14
 
15
15
  setup(
16
16
  name="kernel_tuner",
17
- version="0.3.0",
17
+ version="0.3.1",
18
18
  author="Ben van Werkhoven",
19
19
  author_email="b.vanwerkhoven@esciencecenter.nl",
20
20
  description=("An easy to use CUDA/OpenCL kernel tuner in Python"),
@@ -10,6 +10,7 @@ except ImportError:
10
10
  from unittest.mock import patch, Mock
11
11
 
12
12
  from kernel_tuner.c import CFunctions, Argument
13
+ from kernel_tuner.core import KernelSource, KernelInstance
13
14
 
14
15
 
15
16
  def test_ready_argument_list1():
@@ -119,9 +120,12 @@ def test_compile(npct, subprocess):
119
120
 
120
121
  kernel_string = "this is a fake C program"
121
122
  kernel_name = "blabla"
123
+ kernel_sources = KernelSource(kernel_string, "C")
124
+ kernel_instance = KernelInstance(kernel_name, kernel_sources, kernel_string, [], None, None, dict(), [])
122
125
 
123
126
  cfunc = CFunctions()
124
- f = cfunc.compile(kernel_name, kernel_string)
127
+
128
+ f = cfunc.compile(kernel_instance)
125
129
 
126
130
  print(subprocess.mock_calls)
127
131
  print(npct.mock_calls)
@@ -141,6 +145,35 @@ def test_compile(npct, subprocess):
141
145
  assert not os.path.isfile(filename + ".so")
142
146
 
143
147
 
148
+ @patch('kernel_tuner.c.subprocess')
149
+ @patch('kernel_tuner.c.numpy.ctypeslib')
150
+ def test_compile_detects_device_code(npct, subprocess):
151
+
152
+ kernel_string = "this code clearly contains device code __global__ kernel(float* arg){ return; }"
153
+ kernel_name = "blabla"
154
+ kernel_sources = KernelSource(kernel_string, "C")
155
+ kernel_instance = KernelInstance(kernel_name, kernel_sources, kernel_string, [], None, None, dict(), [])
156
+
157
+ cfunc = CFunctions()
158
+
159
+ f = cfunc.compile(kernel_instance)
160
+
161
+ print(subprocess.check_call.call_args_list)
162
+
163
+ #assert the filename suffix used for source compilation is .cu
164
+ dot_cu_used = False
165
+ for call in subprocess.check_call.call_args_list:
166
+ args, kwargs = call
167
+ args = args[0]
168
+ print(args)
169
+ if args[0] == 'nvcc' and args[1] == '-c':
170
+ assert args[2][-3:] == '.cu'
171
+ dot_cu_used = True
172
+
173
+ assert dot_cu_used
174
+
175
+
176
+
144
177
  def test_memset():
145
178
  a = [1, 2, 3, 4]
146
179
  x = numpy.array(a).astype(numpy.float32)
@@ -2,6 +2,7 @@ import numpy
2
2
  from .context import skip_if_no_cuda
3
3
 
4
4
  from kernel_tuner import cuda
5
+ from kernel_tuner.core import KernelSource, KernelInstance
5
6
 
6
7
  try:
7
8
  import pycuda.driver
@@ -39,11 +40,15 @@ def test_compile():
39
40
  }
40
41
  """
41
42
 
43
+ kernel_sources = KernelSource(original_kernel, "cuda")
44
+
45
+
42
46
  kernel_string = original_kernel.replace("shared_size", str(100*1024*1024))
47
+ kernel_instance = KernelInstance("vector_add", kernel_sources, kernel_string, [], None, None, dict(), [])
43
48
 
44
49
  dev = cuda.CudaFunctions(0)
45
50
  try:
46
- func = dev.compile("vector_add", kernel_string)
51
+ func = dev.compile(kernel_instance)
47
52
  assert isinstance(func, pycuda.driver.Function)
48
53
  print("Expected an exception because too much shared memory is requested")
49
54
  assert False
@@ -55,15 +60,16 @@ def test_compile():
55
60
  assert False
56
61
 
57
62
  kernel_string = original_kernel.replace("shared_size", str(100))
63
+ kernel_instance = KernelInstance("vector_add", kernel_sources, kernel_string, [], None, None, dict(), [])
58
64
  try:
59
- func = dev.compile("vector_add", kernel_string)
65
+ func = dev.compile( kernel_instance)
60
66
  assert True
61
67
  except Exception as e:
62
68
  print("Did not expect any exception:")
63
69
  print(str(e))
64
70
  assert False
65
71
 
66
- def dummy_func(a, b, block=0, grid=0, stream=None, texrefs=None):
72
+ def dummy_func(a, b, block=0, grid=0, stream=None, shared=0, texrefs=None):
67
73
  pass
68
74
 
69
75
  @skip_if_no_cuda
@@ -6,6 +6,7 @@ except ImportError:
6
6
  from unittest.mock import patch, Mock
7
7
 
8
8
  from kernel_tuner import cuda
9
+ from kernel_tuner.core import KernelSource, KernelInstance
9
10
 
10
11
  def setup_mock(drv):
11
12
  context = Mock()
@@ -53,7 +54,9 @@ def test_compile(drv, *args):
53
54
 
54
55
  #call compile
55
56
  kernel_string = "__global__ void vector_add()"
56
- func = dev.compile("vector_add", kernel_string)
57
+ kernel_sources = KernelSource(kernel_string, "cuda")
58
+ kernel_instance = KernelInstance("vector_add", kernel_sources, kernel_string, [], None, None, dict(), [])
59
+ func = dev.compile(kernel_instance)
57
60
 
58
61
  #verify behavior
59
62
  assert dev.source_mod.call_count == 1
@@ -66,7 +69,7 @@ def test_compile(drv, *args):
66
69
  assert optional_args['arch'] == 'compute_55'
67
70
 
68
71
 
69
- def dummy_func(a, b, block=0, grid=0, stream=None, texrefs=None):
72
+ def dummy_func(a, b, block=0, grid=0, shared=0, stream=None, texrefs=None):
70
73
  pass
71
74
 
72
75
  @patch('kernel_tuner.cuda.nvml')
@@ -30,8 +30,7 @@ def test_interface_calls_functions(dev_interface):
30
30
  kernel_name, kernel_string, size, args, tune_params = get_fake_kernel()
31
31
  tune_kernel(kernel_name, kernel_string, size, args, tune_params, verbose=True)
32
32
 
33
- expected = "#define block_size_z 1\n#define block_size_y 1\n#define block_size_x 128\n#define grid_size_z 1\n#define grid_size_y 1\n#define grid_size_x 10\n__global__ void fake_kernel(int number)"
34
- dev.compile.assert_called_once_with("fake_kernel", expected)
33
+ dev.compile.assert_called_once()
35
34
  dev.benchmark.assert_called_once_with('compile', 'ready_argument_list', (128, 1, 1), (10, 1, 1))
36
35
 
37
36
  @patch('kernel_tuner.core.CudaFunctions')
@@ -45,7 +44,8 @@ def test_interface_handles_max_threads(dev_interface):
45
44
  kernel_string = "__global__ void fake_kernel(int number)"
46
45
  tune_kernel("fake_kernel", kernel_string, (1,1), [numpy.int32(0)], tune_params, lang="CUDA")
47
46
 
48
- dev.compile.assert_called_once_with("fake_kernel", "#define block_size_z 1\n#define block_size_y 1\n#define block_size_x 256\n#define grid_size_z 1\n#define grid_size_y 1\n#define grid_size_x 1\n__global__ void fake_kernel(int number)")
47
+ # verify that only a single instance of the kernel is compiled
48
+ dev.compile.assert_called_once()
49
49
 
50
50
  @patch('kernel_tuner.core.CudaFunctions')
51
51
  def test_interface_handles_compile_error(dev_interface):
@@ -2,12 +2,15 @@ import numpy as np
2
2
  from .context import skip_if_no_opencl
3
3
 
4
4
  from kernel_tuner import opencl
5
+ from kernel_tuner.core import KernelSource, KernelInstance
6
+
5
7
  try:
6
8
  import pyopencl
7
9
  except Exception:
8
10
  pass
9
11
 
10
12
 
13
+
11
14
  @skip_if_no_opencl
12
15
  def test_ready_argument_list():
13
16
 
@@ -41,10 +44,12 @@ def test_compile():
41
44
  }
42
45
  """
43
46
 
47
+ kernel_sources = KernelSource(original_kernel, "opencl")
44
48
  kernel_string = original_kernel.replace("shared_size", str(1024))
49
+ kernel_instance = KernelInstance("sum", kernel_sources, kernel_string, [], None, None, dict(), [])
45
50
 
46
51
  dev = opencl.OpenCLFunctions(0)
47
- func = dev.compile("sum", kernel_string)
52
+ func = dev.compile(kernel_instance)
48
53
 
49
54
  assert isinstance(func, pyopencl.Kernel)
50
55
 
@@ -4,6 +4,7 @@ import json
4
4
  import warnings
5
5
 
6
6
  import numpy
7
+ import pytest
7
8
  from pytest import raises
8
9
 
9
10
  from .context import skip_if_no_cuda, skip_if_no_opencl
File without changes
File without changes
File without changes
File without changes