futhark-0.24.2: rts/python/opencl.py
# Stub code for OpenCL setup.
import pyopencl as cl
import numpy as np
import sys
if cl.version.VERSION < (2015, 2):
raise Exception(
"Futhark requires at least PyOpenCL version 2015.2. Installed version is %s."
% cl.version.VERSION_TEXT
)
def parse_preferred_device(s):
pref_num = 0
if len(s) > 1 and s[0] == "#":
i = 1
while i < len(s):
if not s[i].isdigit():
break
else:
pref_num = pref_num * 10 + int(s[i])
i += 1
while i < len(s) and s[i].isspace():
i += 1
return (s[i:], pref_num)
else:
return (s, 0)
def get_prefered_context(
interactive=False, platform_pref=None, device_pref=None
):
if device_pref != None:
(device_pref, device_num) = parse_preferred_device(device_pref)
else:
device_num = 0
if interactive:
return cl.create_some_context(interactive=True)
def blacklisted(p, d):
return (
platform_pref == None
and device_pref == None
and p.name == "Apple"
and d.name.find("Intel(R) Core(TM)") >= 0
)
def platform_ok(p):
return not platform_pref or p.name.find(platform_pref) >= 0
def device_ok(d):
return not device_pref or d.name.find(device_pref) >= 0
device_matches = 0
for p in cl.get_platforms():
if not platform_ok(p):
continue
for d in p.get_devices():
if blacklisted(p, d) or not device_ok(d):
continue
if device_matches == device_num:
return cl.Context(devices=[d])
else:
device_matches += 1
raise Exception(
"No OpenCL platform and device matching constraints found."
)
def param_assignment(s):
name, value = s.split("=")
return (name, int(value))
def check_types(self, required_types):
if "f64" in required_types:
if (
self.device.get_info(cl.device_info.PREFERRED_VECTOR_WIDTH_DOUBLE)
== 0
):
raise Exception(
"Program uses double-precision floats, but this is not supported on chosen device: %s"
% self.device.name
)
def apply_size_heuristics(self, size_heuristics, sizes):
for platform_name, device_type, size, valuef in size_heuristics:
if (
sizes[size] == None
and self.platform.name.find(platform_name) >= 0
and (self.device.type & device_type) == device_type
):
sizes[size] = valuef(self.device)
return sizes
def initialise_opencl_object(
self,
program_src="",
build_options=[],
command_queue=None,
interactive=False,
platform_pref=None,
device_pref=None,
default_group_size=None,
default_num_groups=None,
default_tile_size=None,
default_reg_tile_size=None,
default_threshold=None,
size_heuristics=[],
required_types=[],
all_sizes={},
user_sizes={},
):
if command_queue is None:
self.ctx = get_prefered_context(
interactive, platform_pref, device_pref
)
self.queue = cl.CommandQueue(self.ctx)
else:
self.ctx = command_queue.context
self.queue = command_queue
self.device = self.queue.device
self.platform = self.device.platform
self.pool = cl.tools.MemoryPool(cl.tools.ImmediateAllocator(self.queue))
device_type = self.device.type
check_types(self, required_types)
max_group_size = int(self.device.max_work_group_size)
max_tile_size = int(np.sqrt(self.device.max_work_group_size))
self.max_group_size = max_group_size
self.max_tile_size = max_tile_size
self.max_threshold = 0
self.max_num_groups = 0
self.max_local_memory = int(self.device.local_mem_size)
# Futhark reserves 4 bytes of local memory for its own purposes.
self.max_local_memory -= 4
# See comment in rts/c/opencl.h.
if self.platform.name.find("NVIDIA CUDA") >= 0:
self.max_local_memory -= 12
elif self.platform.name.find("AMD") >= 0:
self.max_local_memory -= 16
self.free_list = {}
self.global_failure = self.pool.allocate(np.int32().itemsize)
cl.enqueue_fill_buffer(
self.queue, self.global_failure, np.int32(-1), 0, np.int32().itemsize
)
self.global_failure_args = self.pool.allocate(
np.int64().itemsize * (self.global_failure_args_max + 1)
)
self.failure_is_an_option = np.int32(0)
if "default_group_size" in sizes:
default_group_size = sizes["default_group_size"]
del sizes["default_group_size"]
if "default_num_groups" in sizes:
default_num_groups = sizes["default_num_groups"]
del sizes["default_num_groups"]
if "default_tile_size" in sizes:
default_tile_size = sizes["default_tile_size"]
del sizes["default_tile_size"]
if "default_reg_tile_size" in sizes:
default_reg_tile_size = sizes["default_reg_tile_size"]
del sizes["default_reg_tile_size"]
if "default_threshold" in sizes:
default_threshold = sizes["default_threshold"]
del sizes["default_threshold"]
default_group_size_set = default_group_size != None
default_tile_size_set = default_tile_size != None
default_sizes = apply_size_heuristics(
self,
size_heuristics,
{
"group_size": default_group_size,
"tile_size": default_tile_size,
"reg_tile_size": default_reg_tile_size,
"num_groups": default_num_groups,
"lockstep_width": None,
"threshold": default_threshold,
},
)
default_group_size = default_sizes["group_size"]
default_num_groups = default_sizes["num_groups"]
default_threshold = default_sizes["threshold"]
default_tile_size = default_sizes["tile_size"]
default_reg_tile_size = default_sizes["reg_tile_size"]
lockstep_width = default_sizes["lockstep_width"]
if default_group_size > max_group_size:
if default_group_size_set:
sys.stderr.write(
"Note: Device limits group size to {} (down from {})\n".format(
max_tile_size, default_group_size
)
)
default_group_size = max_group_size
if default_tile_size > max_tile_size:
if default_tile_size_set:
sys.stderr.write(
"Note: Device limits tile size to {} (down from {})\n".format(
max_tile_size, default_tile_size
)
)
default_tile_size = max_tile_size
for k, v in user_sizes.items():
if k in all_sizes:
all_sizes[k]["value"] = v
else:
raise Exception(
"Unknown size: {}\nKnown sizes: {}".format(
k, " ".join(all_sizes.keys())
)
)
self.sizes = {}
for k, v in all_sizes.items():
if v["class"] == "group_size":
max_value = max_group_size
default_value = default_group_size
elif v["class"] == "num_groups":
max_value = max_group_size # Intentional!
default_value = default_num_groups
elif v["class"] == "tile_size":
max_value = max_tile_size
default_value = default_tile_size
elif v["class"] == "reg_tile_size":
max_value = None
default_value = default_reg_tile_size
elif v["class"].startswith("threshold"):
max_value = None
default_value = default_threshold
else:
# Bespoke sizes have no limit or default.
max_value = None
if v["value"] == None:
self.sizes[k] = default_value
elif max_value != None and v["value"] > max_value:
sys.stderr.write(
"Note: Device limits {} to {} (down from {}\n".format(
k, max_value, v["value"]
)
)
self.sizes[k] = max_value
else:
self.sizes[k] = v["value"]
# XXX: we perform only a subset of z-encoding here. Really, the
# compiler should provide us with the variables to which
# parameters are mapped.
if len(program_src) >= 0:
build_options += ["-DLOCKSTEP_WIDTH={}".format(lockstep_width)]
build_options += ["-D{}={}".format("max_group_size", max_group_size)]
build_options += [
"-D{}={}".format(
s.replace("z", "zz")
.replace(".", "zi")
.replace("#", "zh")
.replace("'", "zq"),
v,
)
for (s, v) in self.sizes.items()
]
if self.platform.name == "Oclgrind":
build_options += ["-DEMULATE_F16"]
return cl.Program(self.ctx, program_src).build(build_options)
def opencl_alloc(self, min_size, tag):
min_size = 1 if min_size == 0 else min_size
assert min_size > 0
return self.pool.allocate(min_size)
def opencl_free_all(self):
self.pool.free_held()
def sync(self):
failure = np.empty(1, dtype=np.int32)
cl.enqueue_copy(self.queue, failure, self.global_failure, is_blocking=True)
self.failure_is_an_option = np.int32(0)
if failure[0] >= 0:
# Reset failure information.
cl.enqueue_fill_buffer(
self.queue,
self.global_failure,
np.int32(-1),
0,
np.int32().itemsize,
)
# Read failure args.
failure_args = np.empty(
self.global_failure_args_max + 1, dtype=np.int64
)
cl.enqueue_copy(
self.queue,
failure_args,
self.global_failure_args,
is_blocking=True,
)
raise Exception(self.failure_msgs[failure[0]].format(*failure_args))