From 7b1fa24de1b42a37a2ca80e9bd7cf5fe7fa70a65 Mon Sep 17 00:00:00 2001 From: Matt Wala Date: Thu, 9 Apr 2020 12:10:45 -0500 Subject: [PATCH 1/7] Add stub of test_array --- test/test_array.py | 48 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 test/test_array.py diff --git a/test/test_array.py b/test/test_array.py new file mode 100644 index 0000000..98de63e --- /dev/null +++ b/test/test_array.py @@ -0,0 +1,48 @@ +#!/usr/bin/env python + +__copyright__ = "Copyright (C) 2020 Matt Wala" + +__license__ = """ +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +""" + +import numpy as np +import numpy.linalg as la +import sys + +import pytest + +import pyopencl as cl +import pyopencl.array as cl_array +import pyopencl.cltypes as cltypes +import pyopencl.tools as cl_tools +from pyopencl.tools import ( # noqa + pytest_generate_tests_for_pyopencl as pytest_generate_tests) + + +if __name__ == "__main__": + # make sure that import failures get reported, instead of skipping the + # tests. + if len(sys.argv) > 1: + exec(sys.argv[1]) + else: + from pytest import main + main([__file__]) + +# vim: filetype=pyopencl:fdm=marker -- GitLab From 75bc91bc972d894a016d9bdfa5c236ebd947f308 Mon Sep 17 00:00:00 2001 From: Matt Wala Date: Thu, 9 Apr 2020 17:36:32 -0500 Subject: [PATCH 2/7] WIP: Basic symbolic array support --- arrayzy/__init__.py | 2 +- arrayzy/array.py | 164 ++++++++++++++++++++++++++++++++++++-------- test/test_array.py | 32 +++++++++ 3 files changed, 169 insertions(+), 29 deletions(-) diff --git a/arrayzy/__init__.py b/arrayzy/__init__.py index a2831ea..b7028a0 100644 --- a/arrayzy/__init__.py +++ b/arrayzy/__init__.py @@ -1 +1 @@ -from arrayzy.array import Array, Context, make_context +from arrayzy.array import Array, Context, make_context, make_sym diff --git a/arrayzy/array.py b/arrayzy/array.py index a365f31..c52c82e 100644 --- a/arrayzy/array.py +++ b/arrayzy/array.py @@ -20,8 +20,14 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ +import islpy as isl import loopy as lp +import numpy as np + +import pymbolic import pymbolic.primitives as prim +from pytools import memoize_method +from pyopencl.compyte.array import get_common_dtype class Context: @@ -44,7 +50,8 @@ class Context: *program* may not define any names starting with underscores. """ - def __init__(self, program, bindings, target): + def __init__(self, queue, program, bindings, target): + self.queue = queue self._program = program self.bindings = bindings self.target = target @@ -53,28 +60,33 @@ class Context: # update_program. @property def program(self): - self.program + return self._program + + @property + def parameters(self): + pass def update_program(self, program): - self.program = program + self._program = program def get_parameter(self, name): - if name in self.program.all_variable_names(): - if name not in self.program.all_params(): - # FIXME: ... data dependent control flow? - raise ValueError( - f"'{name}' is not a domain parameter " - "in this context") - - else: - return prim.Variable(name) - else: + if name not in self.program.all_variable_names(): self.update_program( self.program.copy( - arguments=self.program.args + [ + args=self.program.args + [ lp.ValueArg(name, dtype=self.program.index_dtype) ])) + """ + if name not in self.program.all_params(): + # FIXME: ... data dependent control flow? + raise ValueError( + f"'{name}' is not a domain parameter " + "in this context") + """ + + return prim.Variable(name) + class Target: pass @@ -104,9 +116,9 @@ def make_context(arg): raise ValueError(f"invalid argument type: {type(arg).__name__}") import loopy as lp - program = lp.make_kernel("{[]:}", [], target=target.get_loopy_target()) + program = lp.make_kernel("{:}", [], target=target.get_loopy_target()) - return Context(program, {}, target) + return Context(arg, program, {}, target) class Array: @@ -121,8 +133,11 @@ class Array: An instance of :class:`loopy.types.LoopyType` or *None* to indicate that the type of the array is not yet known. - .. attribute:: expression + .. attribute:: is_materialized + Whether this array is backed by actual storage. + + .. attribute:: expression """ def __init__(self, context, shape, dtype, expression): @@ -135,34 +150,127 @@ class Array: pass def eval(self, **kwargs): + _, (out,) = self._knl()(self.context.queue, **kwargs) + return out + + @property + def _dim_names(self): + return tuple(f"_{i}" for i in range(len(self.shape))) + + def __getitem__(self, indices): + # TODO pass + @property + def T(self): + def swap_last_two_dims(arr): + if len(arr) < 2: + return arr + arr_copy = list(arr) + arr_copy[-2], arr_copy[-1] = arr_copy[-1], arr_copy[-2] + return type(arr)(arr_copy) + + index_map = dict( + zip(self._dim_names, + map(prim.Variable, swap_last_two_dims(self._dim_names)))) + + expression = pymbolic.substitute(self.expression, index_map) + shape = swap_last_two_dims(self.shape) + + return Array(self.context, shape, self.dtype, expression) + + def __mul__(self, other): + if not np.isscalar(other): + raise TypeError("only scalar multiplication supported") + return Array( + self.context, + self.shape, + get_common_dtype(self, other, allow_double=True), + other * self.expression) + + __rmul__ = __mul__ + + @property + def ndim(self): + return len(self.shape) + + @memoize_method + def _knl(self): + knl = self.context.program + + out = lp.GlobalArg("_out", shape=self.shape, dtype=self.dtype, + order="C") + + # FIXME: Won't work for scalars. + out_inames = [f"_out_{i}" for i in range(self.ndim)] + + out_expr = pymbolic.substitute( + self.expression, + dict(zip(self._dim_names, map(prim.Variable, out_inames)))) + + # Build output domain. + params = [] + from loopy.symbolic import get_dependencies + for sdep in map(get_dependencies, self.shape): + for dep in sdep: + params.append(self.context.get_parameter(dep).name) + + dom = isl.BasicSet.universe( + isl.Space.create_from_names( + isl.DEFAULT_CONTEXT, + set=out_inames, + params=params)) + + from loopy.symbolic import aff_from_expr + affs = isl.affs_from_space(dom.space) + for iname, expr in zip(out_inames, self.shape): + dom &= affs[0].le_set(affs[iname]) + dom &= affs[iname].lt_set(aff_from_expr(dom.space, expr)) + dom, = dom.get_basic_sets() + + # Build output instruction. + from loopy.kernel.instruction import make_assignment + out_insn = make_assignment( + (prim.Variable("_out")[tuple(map(prim.Variable, out_inames))],), + out_expr, + id="_out", + within_inames=frozenset(out_inames)) + + return knl.copy( + domains=knl.domains + [dom], + instructions=knl.instructions + [out_insn], + args=knl.args + [out]) + def store(self, prefix="tmp"): + """Stores the array in a temporary.""" pass -def make_sym(context, name, shape, dtype=None): +def make_sym(context, name, shape, dtype=None, order="C"): if name in context.program.all_variable_names(): raise ValueError(f"name '{name}' already in use in context") - arg = lp.ArrayArg(name, shape=shape, dtype=dtype) - from loopy.symbolic import get_dependencies - for sdep in get_dependencies(si for si in arg.shape): - context.get_parameter(sdep) + arg = lp.GlobalArg(name, shape=shape, dtype=dtype, order=order) - context.update_program(context.program.copy( - arguments=context.program.arguments + [arg])) + shape = arg.shape + dtype = arg.dtype - # TODO make sure "name" is not taken + from loopy.symbolic import get_dependencies + for sdep in map(get_dependencies, arg.shape): + for dep in sdep: + context.get_parameter(dep) + + context.update_program( + context.program.copy( + args=context.program.args + [arg])) v_name = prim.Variable(name) - subscripts = tuple(prim.Variable(???) for i in range(len(shape))) + subscripts = tuple(prim.Variable(f"_{i}") for i in range(len(shape))) - return Array(context, expression=v_name[subscripts]) + return Array(context, shape, dtype, expression=v_name[subscripts]) def zeros(context, shape, dtype): pass - # vim: foldmethod=marker diff --git a/test/test_array.py b/test/test_array.py index 98de63e..bb39276 100644 --- a/test/test_array.py +++ b/test/test_array.py @@ -22,6 +22,8 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ +import arrayzy as az + import numpy as np import numpy.linalg as la import sys @@ -36,6 +38,36 @@ from pyopencl.tools import ( # noqa pytest_generate_tests_for_pyopencl as pytest_generate_tests) +def test_symbolic_array(ctx_getter): + cl_ctx = ctx_getter() + queue = cl.CommandQueue(cl_ctx) + ctx = az.make_context(queue) + x = az.make_sym(ctx, "x", shape="n", dtype=np.float64) + x_in = cl.array.to_device(queue, np.array([1., 2., 3., 4., 5.])) + assert (x.eval(x=x_in).get() == x_in.get()).all() + + +def test_transpose(ctx_getter): + cl_ctx = ctx_getter() + queue = cl.CommandQueue(cl_ctx) + ctx = az.make_context(queue) + x = az.make_sym(ctx, "x", shape="n,m", dtype=np.float64) + x_in = cl.array.to_device( + queue, + np.array([[1., 2., 3., 4., 5.], [6., 7., 8., 9., 10.]])) + assert (x.T.eval(x=x_in).get() == x_in.get().T).all() + + +def test_scalar_multiply(ctx_getter): + cl_ctx = ctx_getter() + queue = cl.CommandQueue(cl_ctx) + ctx = az.make_context(queue) + x = az.make_sym(ctx, "x", shape="n", dtype=np.float64) + x_in = np.array([1., 2., 3., 4., 5.]) + x_in = cl.array.to_device(queue, np.array([1., 2., 3., 4., 5.])) + assert ((2*x).eval(x=x_in).get() == (2*x_in).get()).all() + + if __name__ == "__main__": # make sure that import failures get reported, instead of skipping the # tests. -- GitLab From 53ad7902f64811f552f74320ea23a02c36286b4a Mon Sep 17 00:00:00 2001 From: Matt Wala Date: Fri, 10 Apr 2020 16:24:24 -0500 Subject: [PATCH 3/7] Add a basic CI configuration --- .gitlab-ci.yml | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 .gitlab-ci.yml diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000..b8bbe44 --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,36 @@ +Python 3 POCL: + script: + - export PY_EXE=python3 + - export PYOPENCL_TEST=portable + - export EXTRA_INSTALL="pybind11 numpy mako" + - export LOOPY_NO_CACHE=1 + - curl -L -O -k https://gitlab.tiker.net/inducer/ci-support/raw/master/build-and-test-py-project.sh + - ". ./build-and-test-py-project.sh" + tags: + - python3 + - pocl + except: + - tags + artifacts: + reports: + junit: test/pytest.xml + +Pylint: + script: + - export PY_EXE=python3 + - EXTRA_INSTALL="pybind11 numpy mako" + - curl -L -O -k https://gitlab.tiker.net/inducer/ci-support/raw/master/prepare-and-run-pylint.sh + - ". ./prepare-and-run-pylint.sh arrayzy test/test_*.py" + tags: + - python3 + except: + - tags + +Flake8: + script: + - curl -L -O -k https://gitlab.tiker.net/inducer/ci-support/raw/master/prepare-and-run-flake8.sh + - ". ./prepare-and-run-flake8.sh arrayzy test" + tags: + - python3 + except: + - tags -- GitLab From 13043b741ae0fcc4be232c2e89be39ecc7415e09 Mon Sep 17 00:00:00 2001 From: Matt Wala Date: Fri, 10 Apr 2020 16:30:05 -0500 Subject: [PATCH 4/7] Add requirements.txt with pyopencl --- requirements.txt | 1 + 1 file changed, 1 insertion(+) create mode 100644 requirements.txt diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..7819c26 --- /dev/null +++ b/requirements.txt @@ -0,0 +1 @@ +git+https://github.com/inducer/pyopencl.git -- GitLab From 76b0a756988abf6b910b4ec83d5599e01e99d948 Mon Sep 17 00:00:00 2001 From: Matt Wala Date: Fri, 10 Apr 2020 16:45:03 -0500 Subject: [PATCH 5/7] Use loopy's type inference to get the type of a new array --- arrayzy/array.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/arrayzy/array.py b/arrayzy/array.py index c52c82e..f3a6d0a 100644 --- a/arrayzy/array.py +++ b/arrayzy/array.py @@ -27,7 +27,6 @@ import numpy as np import pymbolic import pymbolic.primitives as prim from pytools import memoize_method -from pyopencl.compyte.array import get_common_dtype class Context: @@ -62,6 +61,12 @@ class Context: def program(self): return self._program + def _infer_type(self, expr): + """Infer the type of an expression in the kernel being built.""" + from loopy.type_inference import TypeInferenceMapper + mapper = TypeInferenceMapper(self._program) + return mapper(expr) + @property def parameters(self): pass @@ -182,11 +187,12 @@ class Array: def __mul__(self, other): if not np.isscalar(other): raise TypeError("only scalar multiplication supported") + new_expr = other * self.expression return Array( self.context, self.shape, - get_common_dtype(self, other, allow_double=True), - other * self.expression) + self.context._infer_type(new_expr), + new_expr) __rmul__ = __mul__ -- GitLab From 82b44deeee40c5c117276a21137e319aca59d6c4 Mon Sep 17 00:00:00 2001 From: Matt Wala Date: Fri, 10 Apr 2020 16:54:04 -0500 Subject: [PATCH 6/7] Fix, test transpose for arbitrary dims --- arrayzy/array.py | 11 ++--------- test/test_array.py | 8 +++++--- 2 files changed, 7 insertions(+), 12 deletions(-) diff --git a/arrayzy/array.py b/arrayzy/array.py index f3a6d0a..11274a8 100644 --- a/arrayzy/array.py +++ b/arrayzy/array.py @@ -168,19 +168,12 @@ class Array: @property def T(self): - def swap_last_two_dims(arr): - if len(arr) < 2: - return arr - arr_copy = list(arr) - arr_copy[-2], arr_copy[-1] = arr_copy[-1], arr_copy[-2] - return type(arr)(arr_copy) - index_map = dict( zip(self._dim_names, - map(prim.Variable, swap_last_two_dims(self._dim_names)))) + map(prim.Variable, reversed(self._dim_names)))) expression = pymbolic.substitute(self.expression, index_map) - shape = swap_last_two_dims(self.shape) + shape = tuple(reversed(self.shape)) return Array(self.context, shape, self.dtype, expression) diff --git a/test/test_array.py b/test/test_array.py index bb39276..6a483bb 100644 --- a/test/test_array.py +++ b/test/test_array.py @@ -47,14 +47,16 @@ def test_symbolic_array(ctx_getter): assert (x.eval(x=x_in).get() == x_in.get()).all() -def test_transpose(ctx_getter): +@pytest.mark.parametrize("dim", (1, 2, 3)) +def test_transpose(ctx_getter, dim): cl_ctx = ctx_getter() queue = cl.CommandQueue(cl_ctx) ctx = az.make_context(queue) - x = az.make_sym(ctx, "x", shape="n,m", dtype=np.float64) + shape = ("l", "m", "n")[-dim:] + x = az.make_sym(ctx, "x", shape=shape, dtype=np.float64) x_in = cl.array.to_device( queue, - np.array([[1., 2., 3., 4., 5.], [6., 7., 8., 9., 10.]])) + np.arange(24, dtype=float).reshape((2, 3, -1)[-dim:])) assert (x.T.eval(x=x_in).get() == x_in.get().T).all() -- GitLab From 7ffe02ffaaea273c26bec49158053f200344807e Mon Sep 17 00:00:00 2001 From: Matt Wala Date: Fri, 10 Apr 2020 16:56:03 -0500 Subject: [PATCH 7/7] flake8 fixes --- arrayzy/__init__.py | 2 +- arrayzy/array.py | 6 ++++-- test/test_array.py | 9 ++++----- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/arrayzy/__init__.py b/arrayzy/__init__.py index b7028a0..9d6dae8 100644 --- a/arrayzy/__init__.py +++ b/arrayzy/__init__.py @@ -1 +1 @@ -from arrayzy.array import Array, Context, make_context, make_sym +from arrayzy.array import Array, Context, make_context, make_sym # noqa diff --git a/arrayzy/array.py b/arrayzy/array.py index 11274a8..4dd2985 100644 --- a/arrayzy/array.py +++ b/arrayzy/array.py @@ -167,7 +167,7 @@ class Array: pass @property - def T(self): + def T(self): # noqa index_map = dict( zip(self._dim_names, map(prim.Variable, reversed(self._dim_names)))) @@ -230,7 +230,9 @@ class Array: # Build output instruction. from loopy.kernel.instruction import make_assignment out_insn = make_assignment( - (prim.Variable("_out")[tuple(map(prim.Variable, out_inames))],), + ( + prim.Variable("_out")[ + tuple(map(prim.Variable, out_inames))],), out_expr, id="_out", within_inames=frozenset(out_inames)) diff --git a/test/test_array.py b/test/test_array.py index 6a483bb..14090f4 100644 --- a/test/test_array.py +++ b/test/test_array.py @@ -25,15 +25,14 @@ THE SOFTWARE. import arrayzy as az import numpy as np -import numpy.linalg as la import sys -import pytest +import pytest # noqa import pyopencl as cl -import pyopencl.array as cl_array -import pyopencl.cltypes as cltypes -import pyopencl.tools as cl_tools +import pyopencl.array as cl_array # noqa +import pyopencl.cltypes as cltypes # noqa +import pyopencl.tools as cl_tools # noqa from pyopencl.tools import ( # noqa pytest_generate_tests_for_pyopencl as pytest_generate_tests) -- GitLab