Skip to content

Commit 698a293

Browse files
add handle for custom device (#61)
This pull request addresses issue #41 by implementing a new feature in Pyccel that allows users to define a custom device **Commit Summary** - Adding handler for custom device and its code generation. - Adding test --------- Co-authored-by: EmilyBourne <[email protected]>
1 parent 82e807b commit 698a293

File tree

9 files changed

+112
-6
lines changed

9 files changed

+112
-6
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ All notable changes to this project will be documented in this file.
1010
- #59 : Updated `cuda` clash checker.
1111
- #42 : Add support for custom kernel in`cuda`.
1212
- #42 : Add Cuda module to Pyccel. Add support for `cuda.synchronize` function.
13+
- #41 : Add support for custom device in`cuda`.
1314

1415
## \[UNRELEASED\]
1516

docs/cuda.md

+24-1
Original file line numberDiff line numberDiff line change
@@ -20,4 +20,27 @@ threadsperblock = 1
2020
# Call your kernel function
2121
my_kernel[blockspergrid, threadsperblock]()
2222

23-
```
23+
```
24+
25+
### device
26+
27+
Device functions are similar to kernels, but are executed within the context of a kernel. They can be called only from kernels or device functions, and are typically used for operations that are too small to justify launching a separate kernel, or for operations that need to be performed repeatedly within the context of a kernel.
28+
29+
```python
30+
from pyccel.decorators import device, kernel
31+
32+
@device
33+
def add(x, y):
34+
return x + y
35+
36+
@kernel
37+
def my_kernel():
38+
x = 1
39+
y = 2
40+
z = add(x, y)
41+
print(z)
42+
43+
my_kernel[1, 1]()
44+
45+
```
46+

pyccel/codegen/printing/cucode.py

+4-3
Original file line numberDiff line numberDiff line change
@@ -86,9 +86,10 @@ def function_signature(self, expr, print_arg_names = True):
8686
str
8787
Signature of the function.
8888
"""
89-
cuda_decorater = '__global__' if 'kernel' in expr.decorators else ''
89+
cuda_decorator = '__global__' if 'kernel' in expr.decorators else \
90+
'__device__' if 'device' in expr.decorators else ''
9091
c_function_signature = super().function_signature(expr, print_arg_names)
91-
return f'{cuda_decorater} {c_function_signature}'
92+
return f'{cuda_decorator} {c_function_signature}'
9293

9394
def _print_KernelCall(self, expr):
9495
func = expr.funcdef
@@ -109,7 +110,7 @@ def _print_ModuleHeader(self, expr):
109110
cuda_headers = ""
110111
for f in expr.module.funcs:
111112
if not f.is_inline:
112-
if 'kernel' in f.decorators: # Checking for 'kernel' decorator
113+
if 'kernel' in f.decorators or 'device' in f.decorators:
113114
cuda_headers += self.function_signature(f) + ';\n'
114115
else:
115116
funcs += self.function_signature(f) + ';\n'

pyccel/decorators.py

+19
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
__all__ = (
1212
'allow_negative_index',
1313
'bypass',
14+
'device',
1415
'elemental',
1516
'inline',
1617
'private',
@@ -141,3 +142,21 @@ def __getitem__(self, args):
141142
return self._f
142143

143144
return KernelAccessor(f)
145+
146+
def device(f):
147+
"""
148+
Decorator for marking a function as a GPU device function.
149+
150+
This decorator is used to mark a Python function as a GPU device function.
151+
152+
Parameters
153+
----------
154+
f : Function
155+
The function to be marked as a device.
156+
157+
Returns
158+
-------
159+
f
160+
The function marked as a device.
161+
"""
162+
return f

pyccel/errors/messages.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,7 @@
166166
INVALID_KERNEL_LAUNCH_CONFIG = 'Expected exactly 2 parameters for kernel launch'
167167
INVALID_KERNEL_CALL_BP_GRID = 'Invalid Block per grid parameter for Kernel call'
168168
INVALID_KERNEL_CALL_TP_BLOCK = 'Invalid Thread per Block parameter for Kernel call'
169-
169+
INVAlID_DEVICE_CALL = 'A function decorated with "device" should be called only from a "kernel" or another "device" function.'
170170

171171

172172

pyccel/parser/semantic.py

+6-1
Original file line numberDiff line numberDiff line change
@@ -135,9 +135,10 @@
135135
UNUSED_DECORATORS, UNSUPPORTED_POINTER_RETURN_VALUE, PYCCEL_RESTRICTION_OPTIONAL_NONE,
136136
PYCCEL_RESTRICTION_PRIMITIVE_IMMUTABLE, PYCCEL_RESTRICTION_IS_ISNOT,
137137
FOUND_DUPLICATED_IMPORT, UNDEFINED_WITH_ACCESS, MACRO_MISSING_HEADER_OR_FUNC, PYCCEL_RESTRICTION_INHOMOG_SET,
138-
MISSING_KERNEL_CONFIGURATION,
138+
MISSING_KERNEL_CONFIGURATION, INVAlID_DEVICE_CALL,
139139
INVALID_KERNEL_LAUNCH_CONFIG, INVALID_KERNEL_CALL_BP_GRID, INVALID_KERNEL_CALL_TP_BLOCK)
140140

141+
141142
from pyccel.parser.base import BasicParser
142143
from pyccel.parser.syntactic import SyntaxParser
143144

@@ -1078,6 +1079,10 @@ def _handle_function(self, expr, func, args, *, is_method = False, use_build_fun
10781079
FunctionCall/PyccelFunction
10791080
The semantic representation of the call.
10801081
"""
1082+
1083+
if isinstance(func, FunctionDef) and 'device' in func.decorators:
1084+
if 'kernel' not in self.scope.decorators and 'device' not in self.scope.decorators:
1085+
errors.report(INVAlID_DEVICE_CALL,symbol=expr, severity='fatal')
10811086
if isinstance(func, PyccelFunctionDef):
10821087
if use_build_functions:
10831088
annotation_method = '_build_' + func.cls_name.__name__

tests/cuda/test_device_semantic.py

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# pylint: disable=missing-function-docstring, missing-module-docstring
2+
import pytest
3+
4+
from pyccel import epyccel
5+
from pyccel.decorators import device
6+
from pyccel.errors.errors import Errors, PyccelSemanticError
7+
from pyccel.errors.messages import (INVAlID_DEVICE_CALL,)
8+
9+
10+
@pytest.mark.cuda
11+
def test_invalid_device_call():
12+
def invalid_device_call():
13+
@device
14+
def device_call():
15+
pass
16+
def fake_kernel_call():
17+
device_call()
18+
19+
fake_kernel_call()
20+
21+
errors = Errors()
22+
23+
with pytest.raises(PyccelSemanticError):
24+
epyccel(invalid_device_call, language="cuda")
25+
26+
assert errors.has_errors()
27+
28+
assert errors.num_messages() == 1
29+
30+
error_info = [*errors.error_info_map.values()][0][0]
31+
assert INVAlID_DEVICE_CALL == error_info.message
+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# pylint: disable=missing-function-docstring, missing-module-docstring
2+
from pyccel.decorators import device, kernel
3+
from pyccel import cuda
4+
5+
@device
6+
def device_call():
7+
print("Hello from device")
8+
9+
@kernel
10+
def kernel_call():
11+
device_call()
12+
13+
def f():
14+
kernel_call[1,1]()
15+
cuda.synchronize()
16+
17+
if __name__ == '__main__':
18+
f()

tests/pyccel/test_pyccel.py

+8
Original file line numberDiff line numberDiff line change
@@ -742,6 +742,14 @@ def test_kernel_collision(gpu_available):
742742
pyccel_test("scripts/kernel/kernel_name_collision.py",
743743
language="cuda", execute_code=gpu_available)
744744

745+
#------------------------------------------------------------------------------
746+
747+
@pytest.mark.cuda
748+
def test_device_call(gpu_available):
749+
types = str
750+
pyccel_test("scripts/kernel/device_test.py",
751+
language="cuda", output_dtype=types, execute_code=gpu_available)
752+
745753
#------------------------------------------------------------------------------
746754
def test_print_strings(language):
747755
types = str

0 commit comments

Comments
 (0)