Skip to content

Commit 090440a

Browse files
committed
bugfix: switching GPUs
closes #110
1 parent ab6af1a commit 090440a

File tree

5 files changed

+86
-41
lines changed

5 files changed

+86
-41
lines changed

pyclesperanto_prototype/_tier0/_device.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import pyopencl as cl
22
from typing import Callable, List, Optional
3+
from functools import lru_cache
34

45
# TODO: we should discuss whether this collection is actually the best thing to pass
56
# around. might be better to work lower level with contexts...
@@ -19,6 +20,11 @@ def __repr__(self) -> str:
1920
def name(self) -> str:
2021
return self.device.name
2122

23+
@lru_cache(maxsize=128)
24+
def program_from_source(self, source):
25+
from ._program import OCLProgram
26+
return OCLProgram(src_str=source, dev=self)
27+
2228

2329
def score_device(dev: cl.Device) -> float:
2430
score = 4e12 if dev.type == cl.device_type.GPU else 2e12
@@ -57,6 +63,11 @@ def select_device(name: str = None, dev_type: str = None, score_key=None) -> Dev
5763
_current_device._instance = Device(device, context, queue)
5864
return _current_device._instance
5965

66+
def new_device(name: str = None, dev_type: str = None, score_key=None) -> Device:
67+
device = filter_devices(name, dev_type, score_key)[-1]
68+
context = cl.Context(devices=[device])
69+
queue = cl.CommandQueue(context)
70+
return Device(device, context, queue)
6071

6172
def filter_devices(
6273
name: str = None, dev_type: str = None, score_key=None
@@ -99,3 +110,4 @@ def set_device_scoring_key(func: Callable[[cl.Device], int]) -> None:
99110
except Exception as e:
100111
raise ValueError(f"Scoring algorithm invalid: {e}")
101112
_current_device.score_key = func
113+

pyclesperanto_prototype/_tier0/_execute.py

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@
55
import numpy as np
66

77
import pyopencl as cl
8-
from ._pycl import OCLProgram, _OCLImage
8+
from ._pycl import _OCLImage
9+
from ._device import Device
10+
from ._program import OCLProgram
911

1012
if not os.getenv("PYOPENCL_COMPILER_OUTPUT"):
1113
import warnings
@@ -68,7 +70,7 @@ def get_ocl_source(anchor, opencl_kernel_filename):
6870
#define WRITE_{key}_IMAGE(a,b,c) write_image{typeId}(a,b,c)
6971
"""
7072

71-
def execute(anchor, opencl_kernel_filename, kernel_name, global_size, parameters, prog : OCLProgram = None, constants = None, image_size_independent_kernel_compilation : bool = True):
73+
def execute(anchor, opencl_kernel_filename, kernel_name, global_size, parameters, prog : OCLProgram = None, constants = None, image_size_independent_kernel_compilation : bool = True, device: Device = None):
7274
"""
7375
Convenience method for calling opencl kernel files
7476
@@ -274,8 +276,12 @@ def execute(anchor, opencl_kernel_filename, kernel_name, global_size, parameters
274276
# time_stamp = time.time()
275277

276278
defines.append(get_ocl_source(anchor, opencl_kernel_filename))
277-
278-
prog = OCLProgram.from_source("\n".join(defines))
279+
280+
if device is None:
281+
from ._device import get_device
282+
device = get_device()
283+
prog = device.program_from_source("\n".join(defines))
284+
#prog = OCLProgram.from_source("\n".join(defines))
279285

280286
# Todo: the order of the arguments matters; fix that
281287
# print("Compilation " + opencl_kernel_filename + " took " + str((time.time() - time_stamp) * 1000) + " ms")
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
2+
import pyopencl as cl
3+
import warnings
4+
from functools import lru_cache
5+
6+
class OCLProgram(cl.Program):
7+
""" a wrapper class representing a CPU/GPU Program
8+
example:
9+
prog = OCLProgram("mykernels.cl",build_options=["-D FLAG"])
10+
"""
11+
_wait_for_kernel_finish = None
12+
13+
def __init__(self, file_name=None, src_str=None, build_options=[], dev=None):
14+
if file_name is not None:
15+
with open(file_name, "r") as f:
16+
src_str = f.read()
17+
18+
if src_str is None:
19+
raise ValueError("empty src_str! ")
20+
21+
if dev is None:
22+
from ._device import get_device
23+
dev = get_device()
24+
25+
self._dev = dev
26+
self._kernel_dict = {}
27+
super().__init__(self._dev.context, src_str)
28+
self.build(options=build_options)
29+
30+
def run_kernel(self, name, global_size, local_size, *args, **kwargs):
31+
if name not in self._kernel_dict:
32+
self._kernel_dict[name] = getattr(self, name)
33+
34+
self._kernel_dict[name](
35+
self._dev.queue, global_size, local_size, *args, **kwargs
36+
)
37+
if OCLProgram._wait_for_kernel_finish:
38+
self._dev.queue.finish()
39+
40+
@classmethod
41+
@lru_cache(maxsize=128)
42+
def from_source(cls, source):
43+
warnings.warn("OCLProgram.from_source is deprecated. Use Device.program_from_source instead.")
44+
return cls(src_str=source)

pyclesperanto_prototype/_tier0/_pycl.py

Lines changed: 1 addition & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import os
22
import sys
3+
34
import numpy as np
45
import pyopencl as cl
56
from pyopencl import characterize
67
from pyopencl import array
7-
from functools import lru_cache
88
from ._device import get_device
99

1010
""" Below here, vendored from GPUtools
@@ -38,43 +38,7 @@
3838
"""
3939

4040

41-
class OCLProgram(cl.Program):
42-
""" a wrapper class representing a CPU/GPU Program
43-
example:
44-
prog = OCLProgram("mykernels.cl",build_options=["-D FLAG"])
45-
"""
46-
_wait_for_kernel_finish = None
47-
48-
def __init__(self, file_name=None, src_str=None, build_options=[], dev=None):
49-
if file_name is not None:
50-
with open(file_name, "r") as f:
51-
src_str = f.read()
52-
53-
if src_str is None:
54-
raise ValueError("empty src_str! ")
55-
56-
if dev is None:
57-
dev = get_device()
5841

59-
self._dev = dev
60-
self._kernel_dict = {}
61-
super().__init__(self._dev.context, src_str)
62-
self.build(options=build_options)
63-
64-
def run_kernel(self, name, global_size, local_size, *args, **kwargs):
65-
if name not in self._kernel_dict:
66-
self._kernel_dict[name] = getattr(self, name)
67-
68-
self._kernel_dict[name](
69-
self._dev.queue, global_size, local_size, *args, **kwargs
70-
)
71-
if OCLProgram._wait_for_kernel_finish:
72-
self._dev.queue.finish()
73-
74-
@classmethod
75-
@lru_cache(maxsize=128)
76-
def from_source(cls, source):
77-
return cls(src_str=source)
7842

7943

8044
cl_image_datatype_dict = {

tests/test_gpu_switch.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import numpy as np
2+
import pyclesperanto_prototype as cle
3+
4+
# this test may only work on a machine with multiple GPUs
5+
def test_gpu_switch():
6+
image = np.random.random((100,100))
7+
8+
# select NVidia
9+
print(cle.select_device("RTX"))
10+
cle.gaussian_blur(image, sigma_x=1, sigma_y=1)
11+
12+
# select AMD
13+
print(cle.select_device("gfx"))
14+
cle.gaussian_blur(image, sigma_x=1, sigma_y=1)
15+
16+
# select Intel
17+
print(cle.select_device("Intel"))
18+
cle.gaussian_blur(image, sigma_x=1, sigma_y=1)
19+

0 commit comments

Comments
 (0)