kernel-tuner 0.1.0b0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- kernel_tuner-0.1.0b0/LICENSE +14 -0
- kernel_tuner-0.1.0b0/MANIFEST.in +4 -0
- kernel_tuner-0.1.0b0/PKG-INFO +136 -0
- kernel_tuner-0.1.0b0/README.md +134 -0
- kernel_tuner-0.1.0b0/kernel_tuner/__init__.py +3 -0
- kernel_tuner-0.1.0b0/kernel_tuner/c.py +217 -0
- kernel_tuner-0.1.0b0/kernel_tuner/core.py +131 -0
- kernel_tuner-0.1.0b0/kernel_tuner/cuda.py +200 -0
- kernel_tuner-0.1.0b0/kernel_tuner/interface.py +381 -0
- kernel_tuner-0.1.0b0/kernel_tuner/opencl.py +165 -0
- kernel_tuner-0.1.0b0/kernel_tuner/runners/__init__.py +0 -0
- kernel_tuner-0.1.0b0/kernel_tuner/runners/random_sample.py +104 -0
- kernel_tuner-0.1.0b0/kernel_tuner/runners/sequential_brute_force.py +93 -0
- kernel_tuner-0.1.0b0/kernel_tuner/util.py +198 -0
- kernel_tuner-0.1.0b0/kernel_tuner.egg-info/PKG-INFO +136 -0
- kernel_tuner-0.1.0b0/kernel_tuner.egg-info/SOURCES.txt +28 -0
- kernel_tuner-0.1.0b0/kernel_tuner.egg-info/dependency_links.txt +1 -0
- kernel_tuner-0.1.0b0/kernel_tuner.egg-info/requires.txt +5 -0
- kernel_tuner-0.1.0b0/kernel_tuner.egg-info/top_level.txt +1 -0
- kernel_tuner-0.1.0b0/requirements.txt +6 -0
- kernel_tuner-0.1.0b0/setup.cfg +8 -0
- kernel_tuner-0.1.0b0/setup.py +39 -0
- kernel_tuner-0.1.0b0/test/test_c_functions.py +127 -0
- kernel_tuner-0.1.0b0/test/test_cuda_functions.py +82 -0
- kernel_tuner-0.1.0b0/test/test_cuda_mocked.py +98 -0
- kernel_tuner-0.1.0b0/test/test_interface.py +135 -0
- kernel_tuner-0.1.0b0/test/test_kernel_tuner.py +260 -0
- kernel_tuner-0.1.0b0/test/test_opencl_functions.py +74 -0
- kernel_tuner-0.1.0b0/test/test_runners.py +28 -0
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
* Copyright 2016 Netherlands eScience Center
|
|
2
|
+
|
|
3
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
you may not use this file except in compliance with the License.
|
|
5
|
+
You may obtain a copy of the License at
|
|
6
|
+
|
|
7
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
|
|
9
|
+
Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
See the License for the specific language governing permissions and
|
|
13
|
+
limitations under the License.
|
|
14
|
+
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
Metadata-Version: 1.1
|
|
2
|
+
Name: kernel_tuner
|
|
3
|
+
Version: 0.1.0b0
|
|
4
|
+
Summary: A simple CUDA/OpenCL kernel tuner in Python
|
|
5
|
+
Home-page: http://benvanwerkhoven.github.io/kernel_tuner/
|
|
6
|
+
Author: Ben van Werkhoven
|
|
7
|
+
Author-email: b.vanwerkhoven@esciencecenter.nl
|
|
8
|
+
License: Apache 2.0
|
|
9
|
+
Description:
|
|
10
|
+
A simple CUDA/OpenCL kernel tuner in Python
|
|
11
|
+
===========================================
|
|
12
|
+
|
|
13
|
+
The goal of this project is to provide a - as simple as possible - tool
|
|
14
|
+
for tuning CUDA and OpenCL kernels. This implies that any CUDA or OpenCL
|
|
15
|
+
kernel can be tuned without requiring extensive changes to the original
|
|
16
|
+
kernel code.
|
|
17
|
+
|
|
18
|
+
A very common problem in GPU programming is that some combination of
|
|
19
|
+
thread block dimensions and other kernel parameters, like tiling or
|
|
20
|
+
unrolling factors, results in dramatically better performance than other
|
|
21
|
+
kernel configurations. The goal of auto-tuning is to automate the
|
|
22
|
+
process of finding the best performing configuration for a given device.
|
|
23
|
+
|
|
24
|
+
This kernel tuner aims that you can directly use the tuned kernel
|
|
25
|
+
without introducing any new dependencies. The tuned kernels can
|
|
26
|
+
afterwards be used independently of the programming environment, whether
|
|
27
|
+
that is using C/C++/Java/Fortran or Python doesn't matter.
|
|
28
|
+
|
|
29
|
+
The kernel_tuner module currently only contains main one function which
|
|
30
|
+
is called tune_kernel to which you pass at least the kernel name, a string
|
|
31
|
+
containing the kernel code, the problem size, a list of kernel function
|
|
32
|
+
arguments, and a dictionary of tunable parameters. There are also a lot
|
|
33
|
+
of optional parameters, for a complete list see the full documentation of
|
|
34
|
+
tune_kernel.
|
|
35
|
+
|
|
36
|
+
Installation
|
|
37
|
+
------------
|
|
38
|
+
| clone the repository
|
|
39
|
+
| ``git clone git@github.com:benvanwerkhoven/kernel_tuner.git``
|
|
40
|
+
| change into the top-level directory
|
|
41
|
+
| ``cd kernel_tuner``
|
|
42
|
+
| install using
|
|
43
|
+
| ``pip install -r requirements.txt``
|
|
44
|
+
| ``pip install .``
|
|
45
|
+
|
|
46
|
+
Dependencies
|
|
47
|
+
------------
|
|
48
|
+
* Python 2.7 or Python 3.5
|
|
49
|
+
* PyCuda and/or PyOpenCL (https://mathema.tician.de/software/)
|
|
50
|
+
|
|
51
|
+
Example usage
|
|
52
|
+
-------------
|
|
53
|
+
The following shows a simple example for tuning a CUDA kernel:
|
|
54
|
+
|
|
55
|
+
::
|
|
56
|
+
|
|
57
|
+
kernel_string = """
|
|
58
|
+
__global__ void vector_add(float *c, float *a, float *b, int n) {
|
|
59
|
+
int i = blockIdx.x * block_size_x + threadIdx.x;
|
|
60
|
+
if (i<n) {
|
|
61
|
+
c[i] = a[i] + b[i];
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
"""
|
|
65
|
+
|
|
66
|
+
size = 10000000
|
|
67
|
+
problem_size = (size, 1)
|
|
68
|
+
|
|
69
|
+
a = numpy.random.randn(size).astype(numpy.float32)
|
|
70
|
+
b = numpy.random.randn(size).astype(numpy.float32)
|
|
71
|
+
c = numpy.zeros_like(b)
|
|
72
|
+
n = numpy.int32(size)
|
|
73
|
+
args = [c, a, b, n]
|
|
74
|
+
|
|
75
|
+
tune_params = dict()
|
|
76
|
+
tune_params["block_size_x"] = [128+64*i for i in range(15)]
|
|
77
|
+
|
|
78
|
+
tune_kernel("vector_add", kernel_string, problem_size, args, tune_params)
|
|
79
|
+
|
|
80
|
+
The exact same Python code can be used to tune an OpenCL kernel:
|
|
81
|
+
|
|
82
|
+
::
|
|
83
|
+
|
|
84
|
+
kernel_string = """
|
|
85
|
+
__kernel void vector_add(__global float *c, __global float *a, __global float *b, int n) {
|
|
86
|
+
int i = get_global_id(0);
|
|
87
|
+
if (i<n) {
|
|
88
|
+
c[i] = a[i] + b[i];
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
"""
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
Or even just a C function, see the example `here <https://github.com/benvanwerkhoven/kernel_tuner/blob/master/examples/c/vector_add.py>`_.
|
|
95
|
+
|
|
96
|
+
You can find these and many - more extensive - example codes, in the
|
|
97
|
+
`examples <https://github.com/benvanwerkhoven/kernel_tuner/blob/master/examples/>`_
|
|
98
|
+
directory. See the `full documentation <http://benvanwerkhoven.github.io/kernel_tuner/sphinxdoc/html/index.html>`_
|
|
99
|
+
for several highly detailed tutorial-style explanations of example
|
|
100
|
+
kernels and the scripts to tune them.
|
|
101
|
+
|
|
102
|
+
Author
|
|
103
|
+
------
|
|
104
|
+
Ben van Werkhoven <b.vanwerkhoven@esciencenter.nl>
|
|
105
|
+
|
|
106
|
+
Copyright and License
|
|
107
|
+
---------------------
|
|
108
|
+
* Copyright 2016 Netherlands eScience Center
|
|
109
|
+
|
|
110
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
111
|
+
you may not use this file except in compliance with the License.
|
|
112
|
+
You may obtain a copy of the License at
|
|
113
|
+
|
|
114
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
115
|
+
|
|
116
|
+
Unless required by applicable law or agreed to in writing, software
|
|
117
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
118
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
119
|
+
See the License for the specific language governing permissions and
|
|
120
|
+
limitations under the License.
|
|
121
|
+
|
|
122
|
+
Keywords: auto-tuning gpu computing pycuda cuda pyopencl opencl
|
|
123
|
+
Platform: UNKNOWN
|
|
124
|
+
Classifier: Environment :: Console
|
|
125
|
+
Classifier: Intended Audience :: Developers
|
|
126
|
+
Classifier: Intended Audience :: Science/Research
|
|
127
|
+
Classifier: Intended Audience :: Education
|
|
128
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
129
|
+
Classifier: Natural Language :: English
|
|
130
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
131
|
+
Classifier: Programming Language :: Python :: 2.7
|
|
132
|
+
Classifier: Programming Language :: Python :: 3.5
|
|
133
|
+
Classifier: Topic :: Scientific/Engineering
|
|
134
|
+
Classifier: Topic :: Software Development
|
|
135
|
+
Classifier: Topic :: System :: Distributed Computing
|
|
136
|
+
Classifier: Development Status :: 4 - Beta
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
|
|
2
|
+
A simple CUDA/OpenCL kernel tuner in Python
|
|
3
|
+
====================================
|
|
4
|
+
[](https://travis-ci.org/benvanwerkhoven/kernel_tuner)
|
|
5
|
+
[](https://www.codacy.com/app/b-vanwerkhoven/kernel_tuner)
|
|
6
|
+
[](https://www.codacy.com/app/b-vanwerkhoven/kernel_tuner)
|
|
7
|
+
|
|
8
|
+
The goal of this project is to provide a - as simple as possible - tool
|
|
9
|
+
for tuning CUDA and OpenCL kernels. This implies that any CUDA or OpenCL
|
|
10
|
+
kernel can be tuned without requiring extensive changes to the original
|
|
11
|
+
kernel code.
|
|
12
|
+
|
|
13
|
+
A very common problem in GPU programming is that some combination of
|
|
14
|
+
thread block dimensions and other kernel parameters, like tiling or
|
|
15
|
+
unrolling factors, results in dramatically better performance than other
|
|
16
|
+
kernel configurations. The goal of auto-tuning is to automate the
|
|
17
|
+
process of finding the best performing configuration for a given device.
|
|
18
|
+
|
|
19
|
+
This kernel tuner aims that you can directly use the tuned kernel
|
|
20
|
+
without introducing any new dependencies. The tuned kernels can
|
|
21
|
+
afterwards be used independently of the programming environment, whether
|
|
22
|
+
that is using C/C++/Java/Fortran or Python doesn't matter.
|
|
23
|
+
|
|
24
|
+
The kernel_tuner module currently only contains main one function which
|
|
25
|
+
is called tune_kernel to which you pass at least the kernel name, a string
|
|
26
|
+
containing the kernel code, the problem size, a list of kernel function
|
|
27
|
+
arguments, and a dictionary of tunable parameters. There are also a lot
|
|
28
|
+
of optional parameters, for a complete list see the full documentation of
|
|
29
|
+
[tune_kernel](http://benvanwerkhoven.github.io/kernel_tuner/sphinxdoc/html/details.html).
|
|
30
|
+
|
|
31
|
+
Documentation
|
|
32
|
+
-------------
|
|
33
|
+
The full documentation is available [here](http://benvanwerkhoven.github.io/kernel_tuner/sphinxdoc/html/index.html).
|
|
34
|
+
|
|
35
|
+
Installation
|
|
36
|
+
------------
|
|
37
|
+
clone the repository
|
|
38
|
+
`git clone git@github.com:benvanwerkhoven/kernel_tuner.git`
|
|
39
|
+
change into the top-level directory
|
|
40
|
+
`cd kernel_tuner`
|
|
41
|
+
install using
|
|
42
|
+
`pip install -r requirements.txt`
|
|
43
|
+
`pip install .`
|
|
44
|
+
|
|
45
|
+
Dependencies
|
|
46
|
+
------------
|
|
47
|
+
* Python 2.7 or Python 3.5
|
|
48
|
+
* PyCuda and/or PyOpenCL (https://mathema.tician.de/software/)
|
|
49
|
+
|
|
50
|
+
Example usage
|
|
51
|
+
-------------
|
|
52
|
+
The following shows a simple example for tuning a CUDA kernel:
|
|
53
|
+
|
|
54
|
+
```python
|
|
55
|
+
kernel_string = """
|
|
56
|
+
__global__ void vector_add(float *c, float *a, float *b, int n) {
|
|
57
|
+
int i = blockIdx.x * block_size_x + threadIdx.x;
|
|
58
|
+
if (i<n) {
|
|
59
|
+
c[i] = a[i] + b[i];
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
"""
|
|
63
|
+
|
|
64
|
+
size = 10000000
|
|
65
|
+
problem_size = (size, 1)
|
|
66
|
+
|
|
67
|
+
a = numpy.random.randn(size).astype(numpy.float32)
|
|
68
|
+
b = numpy.random.randn(size).astype(numpy.float32)
|
|
69
|
+
c = numpy.zeros_like(b)
|
|
70
|
+
n = numpy.int32(size)
|
|
71
|
+
args = [c, a, b, n]
|
|
72
|
+
|
|
73
|
+
tune_params = dict()
|
|
74
|
+
tune_params["block_size_x"] = [128+64*i for i in range(15)]
|
|
75
|
+
|
|
76
|
+
tune_kernel("vector_add", kernel_string, problem_size, args, tune_params)
|
|
77
|
+
```
|
|
78
|
+
The exact same Python code can be used to tune an OpenCL kernel:
|
|
79
|
+
```python
|
|
80
|
+
kernel_string = """
|
|
81
|
+
__kernel void vector_add(__global float *c, __global float *a, __global float *b, int n) {
|
|
82
|
+
int i = get_global_id(0);
|
|
83
|
+
if (i<n) {
|
|
84
|
+
c[i] = a[i] + b[i];
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
"""
|
|
88
|
+
```
|
|
89
|
+
Or even just a C function, see the example [here](https://github.com/benvanwerkhoven/kernel_tuner/blob/master/examples/c/vector_add.py).
|
|
90
|
+
|
|
91
|
+
You can find these and many - more extensive - example codes, in the [examples](https://github.com/benvanwerkhoven/kernel_tuner/blob/master/examples/) directory.
|
|
92
|
+
See the [full documentation](http://benvanwerkhoven.github.io/kernel_tuner/sphinxdoc/html/index.html) for several highly detailed tutorial-style explanations of example kernels and the scripts to tune them.
|
|
93
|
+
|
|
94
|
+
Tuning host and kernel code
|
|
95
|
+
---------------------------
|
|
96
|
+
It is also possible to tune for combinations of tunable parameters in both host and kernel code. This allows for a number of powerfull things, such as tuning the number of streams for a kernel that uses CUDA Streams or OpenCL Command Queues to overlap transfers between host and device with kernel execution. This can be done in combination with tuning the parameters inside the kernel code. See the [convolution_streams example code](https://github.com/benvanwerkhoven/kernel_tuner/blob/master/examples/) and the [documentation](http://benvanwerkhoven.github.io/kernel_tuner/sphinxdoc/html/hostcode.html) for a detailed explanation of the kernel tuner Python script.
|
|
97
|
+
|
|
98
|
+
Correctness verification
|
|
99
|
+
------------------------
|
|
100
|
+
Optionally, you can let the kernel tuner verify the output of every kernel it compiles and benchmarks,
|
|
101
|
+
by passing an `answer` list. This list matches the list of arguments to the kernel, but contains the expected output of the kernel. Input arguments are replaced with None.
|
|
102
|
+
```python
|
|
103
|
+
answer = [a+b, None, None] # the order matches the arguments (in args) to the kernel
|
|
104
|
+
tune_kernel("vector_add", kernel_string, problem_size, args, tune_params, answer=answer)
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Contribution guide
|
|
108
|
+
------------------
|
|
109
|
+
The kernel tuner follows the Google Python style guide, with Sphinxdoc docstrings for module public functions. If you want to
|
|
110
|
+
contribute to the project please fork it, create a branch including your addition, and create a pull request.
|
|
111
|
+
|
|
112
|
+
The tests use relative imports and can be run directly after making
|
|
113
|
+
changes to the code. To run all tests use `nosetests` in the main directory.
|
|
114
|
+
To run the examples after code changes, you need to run `pip install --upgrade .` in the main directory.
|
|
115
|
+
Documentation is generated by typing `make html` in the doc directory, the contents
|
|
116
|
+
of `doc/build/html/` should then be copied to `sphinxdoc` directory of the `gh-pages` branch.
|
|
117
|
+
|
|
118
|
+
Before creating a pull request please ensure the following:
|
|
119
|
+
* You have written unit tests to test your additions
|
|
120
|
+
* All unit tests pass
|
|
121
|
+
* The examples still work and produce the same (or better) results
|
|
122
|
+
* The code is compatible with both Python 2.7 and Python 3.5
|
|
123
|
+
* An entry about the change or addition is created in CHANGELOG.md
|
|
124
|
+
|
|
125
|
+
Contributing authors so far:
|
|
126
|
+
* Ben van Werkhoven
|
|
127
|
+
* Berend Weel
|
|
128
|
+
|
|
129
|
+
Related work
|
|
130
|
+
------------
|
|
131
|
+
You may also like [CLTune](https://github.com/CNugteren/CLTune) by Cedric
|
|
132
|
+
Nugteren. CLTune is a C++ library for kernel tuning and supports various
|
|
133
|
+
advanced features like machine learning to optimize the time spent on tuning
|
|
134
|
+
kernels.
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
""" This module contains the functionality for running and compiling C functions """
|
|
2
|
+
|
|
3
|
+
import numpy
|
|
4
|
+
import ctypes as C
|
|
5
|
+
import subprocess
|
|
6
|
+
import os
|
|
7
|
+
import errno
|
|
8
|
+
|
|
9
|
+
import numpy.ctypeslib
|
|
10
|
+
|
|
11
|
+
from kernel_tuner.util import get_temp_filename, delete_temp_file
|
|
12
|
+
|
|
13
|
+
class CFunctions(object):
|
|
14
|
+
"""Class that groups the code for running and compiling C functions"""
|
|
15
|
+
|
|
16
|
+
def __init__(self, iterations=7):
|
|
17
|
+
"""instantiate CFunctions object used for interacting with C code
|
|
18
|
+
|
|
19
|
+
:param iterations: Number of iterations used while benchmarking a kernel, 7 by default.
|
|
20
|
+
:type iterations: int
|
|
21
|
+
"""
|
|
22
|
+
self.ITERATIONS = iterations
|
|
23
|
+
self.max_threads = 1024
|
|
24
|
+
|
|
25
|
+
#test if nvcc is available, otherwise use gcc
|
|
26
|
+
self.compiler = "nvcc"
|
|
27
|
+
try:
|
|
28
|
+
subprocess.check_call([self.compiler, "--version"], stdout=open(os.devnull, 'w'))
|
|
29
|
+
except OSError as e:
|
|
30
|
+
self.compiler = "gcc"
|
|
31
|
+
if e.errno != errno.ENOENT:
|
|
32
|
+
raise e
|
|
33
|
+
|
|
34
|
+
def ready_argument_list(self, arguments):
|
|
35
|
+
"""ready argument list to be passed to the C function
|
|
36
|
+
|
|
37
|
+
:param arguments: List of arguments to be passed to the C function.
|
|
38
|
+
The order should match the argument list on the C function.
|
|
39
|
+
Allowed values are numpy.ndarray, and/or numpy.int32, numpy.float32, and so on.
|
|
40
|
+
:type arguments: list(numpy objects)
|
|
41
|
+
|
|
42
|
+
:returns: A list of arguments that can be passed to the C function.
|
|
43
|
+
:rtype: list()
|
|
44
|
+
"""
|
|
45
|
+
self.arg_mapping = dict()
|
|
46
|
+
ctype_args = []
|
|
47
|
+
for arg in arguments:
|
|
48
|
+
if isinstance(arg, numpy.ndarray):
|
|
49
|
+
if arg.dtype == 'float32':
|
|
50
|
+
ctype_args.append(arg.ctypes.data_as(C.POINTER(C.c_float)))
|
|
51
|
+
elif arg.dtype == 'float64':
|
|
52
|
+
ctype_args.append(arg.ctypes.data_as(C.POINTER(C.c_double)))
|
|
53
|
+
elif arg.dtype == 'int32':
|
|
54
|
+
ctype_args.append(arg.ctypes.data_as(C.POINTER(C.c_int)))
|
|
55
|
+
else:
|
|
56
|
+
raise TypeError("unknown dtype for ndarray")
|
|
57
|
+
self.arg_mapping[str(ctype_args[-1])] = arg.shape
|
|
58
|
+
elif numpy.isscalar(arg):
|
|
59
|
+
if hasattr(arg, 'dtype'):
|
|
60
|
+
if str(arg.dtype).startswith('int'):
|
|
61
|
+
ctype_args.append(int(arg))
|
|
62
|
+
elif str(arg.dtype).startswith('float'):
|
|
63
|
+
ctype_args.append(float(arg))
|
|
64
|
+
else:
|
|
65
|
+
raise TypeError("Argument is scalar with a dtype, but does not start with int or float")
|
|
66
|
+
else:
|
|
67
|
+
ctype_args.append(arg)
|
|
68
|
+
else:
|
|
69
|
+
raise TypeError("Argument is not a numpy.ndarray and is not a scalar %s" % type(arg))
|
|
70
|
+
|
|
71
|
+
return ctype_args
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def compile(self, kernel_name, kernel_string):
|
|
75
|
+
"""call the C compiler to compile the kernel, return the function
|
|
76
|
+
|
|
77
|
+
:param kernel_name: The name of the kernel to be compiled, used to lookup the
|
|
78
|
+
function after compilation.
|
|
79
|
+
:type kernel_name: string
|
|
80
|
+
|
|
81
|
+
:param kernel_string: The C code that contains the function `kernel_name`
|
|
82
|
+
:type kernel_string: string
|
|
83
|
+
|
|
84
|
+
:returns: An ctypes function that can be called directly.
|
|
85
|
+
:rtype: ctypes._FuncPtr
|
|
86
|
+
"""
|
|
87
|
+
filename = get_temp_filename()
|
|
88
|
+
source_file = filename+".cc"
|
|
89
|
+
kernel_string = "extern \"C\" {\n" + kernel_string + "\n}"
|
|
90
|
+
|
|
91
|
+
compiler_options = ["-fPIC"]
|
|
92
|
+
if "#include <omp.h>" in kernel_string:
|
|
93
|
+
compiler_options.append("-fopenmp")
|
|
94
|
+
|
|
95
|
+
if self.compiler == "nvcc":
|
|
96
|
+
source_file = source_file[:-1] + "u"
|
|
97
|
+
compiler_options = ["-Xcompiler=" + c for c in compiler_options]
|
|
98
|
+
#might be better to have an optional argument to pass the desired compute capability
|
|
99
|
+
compiler_options += ["-arch=compute_52"]
|
|
100
|
+
|
|
101
|
+
lib_args = []
|
|
102
|
+
if "CL/cl.h" in kernel_string:
|
|
103
|
+
lib_args = ["-lOpenCL"]
|
|
104
|
+
|
|
105
|
+
try:
|
|
106
|
+
with open(source_file, 'w') as f:
|
|
107
|
+
f.write(kernel_string)
|
|
108
|
+
|
|
109
|
+
subprocess.check_call([self.compiler, "-c", source_file] + compiler_options + ["-o", filename+".o"])
|
|
110
|
+
subprocess.check_call([self.compiler, filename+".o"] + compiler_options + [ "-shared", "-o", filename+".so"] + lib_args)
|
|
111
|
+
|
|
112
|
+
self.lib = numpy.ctypeslib.load_library(filename, '.')
|
|
113
|
+
|
|
114
|
+
finally:
|
|
115
|
+
delete_temp_file(source_file)
|
|
116
|
+
delete_temp_file(filename+".o")
|
|
117
|
+
delete_temp_file(filename+".so")
|
|
118
|
+
|
|
119
|
+
func = getattr(self.lib, kernel_name)
|
|
120
|
+
func.restype = C.c_float
|
|
121
|
+
|
|
122
|
+
return func
|
|
123
|
+
|
|
124
|
+
def benchmark(self, func, c_args, threads, grid):
|
|
125
|
+
"""runs the kernel repeatedly, returns averaged returned value
|
|
126
|
+
|
|
127
|
+
The C function tuning is a little bit more flexible than direct CUDA
|
|
128
|
+
or OpenCL kernel tuning. The C function needs to measure time, or some
|
|
129
|
+
other quality metric you wish to tune on, on its own and should
|
|
130
|
+
therefore return a single floating-point value.
|
|
131
|
+
|
|
132
|
+
Benchmark runs the C function repeatedly and returns the average of the
|
|
133
|
+
values returned by the C function. The number of iterations is set
|
|
134
|
+
during the creation of the CFunctions object. For all measurements the
|
|
135
|
+
lowest and highest values are discarded and the rest is included in the
|
|
136
|
+
average. The reason for this is to be robust against initialization
|
|
137
|
+
artifacts and other exceptional cases.
|
|
138
|
+
|
|
139
|
+
:param func: A C function compiled for this specific configuration
|
|
140
|
+
:type func: ctypes._FuncPtr
|
|
141
|
+
|
|
142
|
+
:param c_args: A list of arguments to the function, order should match the
|
|
143
|
+
order in the code. The list should be prepared using
|
|
144
|
+
ready_argument_list().
|
|
145
|
+
:type c_args: list()
|
|
146
|
+
|
|
147
|
+
:param threads: Ignored, but left as argument for now to have the same
|
|
148
|
+
interface as CudaFunctions and OpenCLFunctions.
|
|
149
|
+
:type threads: any
|
|
150
|
+
|
|
151
|
+
:param grid: Ignored, but left as argument for now to have the same
|
|
152
|
+
interface as CudaFunctions and OpenCLFunctions.
|
|
153
|
+
:type grid: any
|
|
154
|
+
|
|
155
|
+
:returns: A robust average of values returned by the C function.
|
|
156
|
+
:rtype: float
|
|
157
|
+
"""
|
|
158
|
+
results = []
|
|
159
|
+
for _ in range(self.ITERATIONS):
|
|
160
|
+
value = self.run_kernel(func, c_args, threads, grid)
|
|
161
|
+
results.append(value)
|
|
162
|
+
results = sorted(results)
|
|
163
|
+
return numpy.mean(results[1:-1])
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def run_kernel(self, func, c_args, threads, grid):
|
|
167
|
+
"""runs the kernel once, returns whatever the kernel returns
|
|
168
|
+
|
|
169
|
+
:param func: A C function compiled for this specific configuration
|
|
170
|
+
:type func: ctypes._FuncPtr
|
|
171
|
+
|
|
172
|
+
:param c_args: A list of arguments to the function, order should match the
|
|
173
|
+
order in the code. The list should be prepared using
|
|
174
|
+
ready_argument_list().
|
|
175
|
+
:type c_args: list()
|
|
176
|
+
|
|
177
|
+
:param threads: Ignored, but left as argument for now to have the same
|
|
178
|
+
interface as CudaFunctions and OpenCLFunctions.
|
|
179
|
+
:type threads: any
|
|
180
|
+
|
|
181
|
+
:param grid: Ignored, but left as argument for now to have the same
|
|
182
|
+
interface as CudaFunctions and OpenCLFunctions.
|
|
183
|
+
:type grid: any
|
|
184
|
+
|
|
185
|
+
:returns: A robust average of values returned by the C function.
|
|
186
|
+
:rtype: float
|
|
187
|
+
"""
|
|
188
|
+
return func(*c_args)
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def memset(self, allocation, value, size):
|
|
192
|
+
"""set the memory in allocation to the value in value
|
|
193
|
+
|
|
194
|
+
:param allocation: A memory allocation unit
|
|
195
|
+
:type allocation: pycuda.driver.DeviceAllocation
|
|
196
|
+
|
|
197
|
+
:param value: The value to set the memory to
|
|
198
|
+
:type value: a single 32-bit float or int
|
|
199
|
+
|
|
200
|
+
:param size: The size of to the allocation unit in bytes
|
|
201
|
+
:type size: int
|
|
202
|
+
"""
|
|
203
|
+
C.memset(allocation, value, size)
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
def memcpy_dtoh(self, dest, src):
|
|
207
|
+
"""a simple memcpy expects a ctypes pointer, returns a numpy array
|
|
208
|
+
|
|
209
|
+
:param dest: A numpy array to store the data
|
|
210
|
+
:type dest: numpy.ndarray
|
|
211
|
+
|
|
212
|
+
:param src: A ctypes pointer to some memory allocation
|
|
213
|
+
:type src: ctypes.pointer
|
|
214
|
+
"""
|
|
215
|
+
dest[:] = numpy.ctypeslib.as_array(src, shape=self.arg_mapping[str(src)])
|
|
216
|
+
|
|
217
|
+
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
""" Module for grouping the core functionality needed by most runners """
|
|
2
|
+
from __future__ import print_function
|
|
3
|
+
|
|
4
|
+
from kernel_tuner.cuda import CudaFunctions
|
|
5
|
+
from kernel_tuner.opencl import OpenCLFunctions
|
|
6
|
+
from kernel_tuner.c import CFunctions
|
|
7
|
+
from kernel_tuner.util import *
|
|
8
|
+
|
|
9
|
+
def get_device_interface(lang, device, platform):
|
|
10
|
+
if lang == "CUDA":
|
|
11
|
+
dev = CudaFunctions(device)
|
|
12
|
+
elif lang == "OpenCL":
|
|
13
|
+
dev = OpenCLFunctions(device, platform)
|
|
14
|
+
elif lang == "C":
|
|
15
|
+
dev = CFunctions()
|
|
16
|
+
else:
|
|
17
|
+
raise UnImplementedException("Sorry, support for languages other than CUDA, OpenCL, or C is not implemented yet")
|
|
18
|
+
return dev
|
|
19
|
+
|
|
20
|
+
def check_kernel_correctness(dev, func, gpu_args, threads, grid, answer, params, verbose, atol=1e-6):
|
|
21
|
+
"""runs the kernel once and checks the result against answer"""
|
|
22
|
+
for result, expected in zip(gpu_args, answer):
|
|
23
|
+
if expected is not None:
|
|
24
|
+
dev.memset(result, 0, expected.nbytes)
|
|
25
|
+
try:
|
|
26
|
+
dev.run_kernel(func, gpu_args, threads, grid)
|
|
27
|
+
except Exception as e:
|
|
28
|
+
if "too many resources requested for launch" in str(e) or "OUT_OF_RESOURCES" in str(e):
|
|
29
|
+
#ignore this error for now, it will show up when benchmarking the kernel
|
|
30
|
+
return True
|
|
31
|
+
else:
|
|
32
|
+
raise e
|
|
33
|
+
correct = True
|
|
34
|
+
for result,expected in zip(gpu_args,answer):
|
|
35
|
+
if expected is not None:
|
|
36
|
+
result_host = numpy.zeros_like(expected)
|
|
37
|
+
dev.memcpy_dtoh(result_host, result)
|
|
38
|
+
output_test = numpy.allclose(result_host.ravel(), expected.ravel(), atol=atol)
|
|
39
|
+
if not output_test and verbose:
|
|
40
|
+
print("Error: " + get_config_string(params) + " detected during correctness check")
|
|
41
|
+
print("Printing kernel output and expected result, set verbose=False to suppress this debug print")
|
|
42
|
+
numpy.set_printoptions(edgeitems=500)
|
|
43
|
+
print("Kernel output:")
|
|
44
|
+
print(result_host)
|
|
45
|
+
print("Expected:")
|
|
46
|
+
print(expected)
|
|
47
|
+
correct = correct and output_test
|
|
48
|
+
if not correct:
|
|
49
|
+
raise Exception("Error: " + get_config_string(params) + " failed correctness check")
|
|
50
|
+
return correct
|
|
51
|
+
|
|
52
|
+
def compile_kernel(dev, kernel_name, kernel_string, params, grid, instance_string, verbose):
|
|
53
|
+
"""compile the kernel for this specific instance"""
|
|
54
|
+
|
|
55
|
+
#prepare kernel_string for compilation
|
|
56
|
+
name, kernel_string = setup_kernel_strings(kernel_name, kernel_string, params, grid, instance_string)
|
|
57
|
+
|
|
58
|
+
#compile kernel_string into device func
|
|
59
|
+
func = None
|
|
60
|
+
try:
|
|
61
|
+
func = dev.compile(name, kernel_string)
|
|
62
|
+
except Exception as e:
|
|
63
|
+
#compiles may fail because certain kernel configurations use too
|
|
64
|
+
#much shared memory for example, the desired behavior is to simply
|
|
65
|
+
#skip over this configuration and try the next one
|
|
66
|
+
if "uses too much shared data" in str(e):
|
|
67
|
+
if verbose:
|
|
68
|
+
print("skipping config", instance_string, "reason: too much shared memory used")
|
|
69
|
+
else:
|
|
70
|
+
raise e
|
|
71
|
+
return func
|
|
72
|
+
|
|
73
|
+
def benchmark(dev, func, gpu_args, threads, grid, instance_string, verbose):
|
|
74
|
+
"""benchmark the kernel instance"""
|
|
75
|
+
time = None
|
|
76
|
+
try:
|
|
77
|
+
time = dev.benchmark(func, gpu_args, threads, grid)
|
|
78
|
+
except Exception as e:
|
|
79
|
+
#some launches may fail because too many registers are required
|
|
80
|
+
#to run the kernel given the current thread block size
|
|
81
|
+
#the desired behavior is to simply skip over this configuration
|
|
82
|
+
#and proceed to try the next one
|
|
83
|
+
if "too many resources requested for launch" in str(e) or "OUT_OF_RESOURCES" in str(e):
|
|
84
|
+
if verbose:
|
|
85
|
+
print("skipping config", instance_string, "reason: too many resources requested for launch")
|
|
86
|
+
else:
|
|
87
|
+
print("Error while benchmarking:", instance_string)
|
|
88
|
+
raise e
|
|
89
|
+
return time
|
|
90
|
+
|
|
91
|
+
def compile_and_benchmark(dev, gpu_args, kernel_name, original_kernel, params,
|
|
92
|
+
problem_size, grid_div_y, grid_div_x, cmem_args, answer, atol, instance_string, verbose):
|
|
93
|
+
|
|
94
|
+
#setup thread block and grid dimensions
|
|
95
|
+
threads, grid = setup_block_and_grid(dev, problem_size, grid_div_y, grid_div_x, params, instance_string, verbose)
|
|
96
|
+
if threads is None:
|
|
97
|
+
return None
|
|
98
|
+
|
|
99
|
+
temp_files = dict()
|
|
100
|
+
|
|
101
|
+
try:
|
|
102
|
+
#obtain the kernel_string and prepare additional files, if any
|
|
103
|
+
if isinstance(original_kernel, list):
|
|
104
|
+
kernel_string, temp_files = prepare_list_of_files(original_kernel, params, grid)
|
|
105
|
+
else:
|
|
106
|
+
kernel_string = get_kernel_string(original_kernel)
|
|
107
|
+
|
|
108
|
+
#compile the kernel
|
|
109
|
+
func = compile_kernel(dev, kernel_name, kernel_string, params, grid, instance_string, verbose)
|
|
110
|
+
if func is None:
|
|
111
|
+
return None
|
|
112
|
+
|
|
113
|
+
#add constant memory arguments to compiled module
|
|
114
|
+
if cmem_args is not None:
|
|
115
|
+
dev.copy_constant_memory_args(cmem_args)
|
|
116
|
+
|
|
117
|
+
#test kernel for correctness and benchmark
|
|
118
|
+
if answer is not None:
|
|
119
|
+
check_kernel_correctness(dev, func, gpu_args, threads, grid, answer, params, verbose, atol)
|
|
120
|
+
|
|
121
|
+
#benchmark
|
|
122
|
+
time = benchmark(dev, func, gpu_args, threads, grid, instance_string, verbose)
|
|
123
|
+
|
|
124
|
+
#clean up any temporary files
|
|
125
|
+
finally:
|
|
126
|
+
for v in temp_files.values():
|
|
127
|
+
delete_temp_file(v)
|
|
128
|
+
|
|
129
|
+
return time
|
|
130
|
+
|
|
131
|
+
|