From f2c5f186e7b67c9ddac634f1af6f6eaed544d4ed Mon Sep 17 00:00:00 2001 From: Oleksandr Pavlyk Date: Mon, 10 May 2021 16:49:25 -0500 Subject: [PATCH 1/2] Added Device class representing Data-API notion of device Data-API notion of device is higher level abstraction. The class does allow for public constructor, has class method to create instance instead. It typesets as Device(filter_string) for parent devices and Device(queue) for sub-devices. It can constructed from - filter selector string - SyclDevice corresponding to a root device (attempt at passing sub-device raises and error) - SyclQueue - Another instance of Device class It implements sycl_queue, sycl_device, sycl_context attributes. usm_ndarray adds .device property, and .to_device(device) method. ``` In [5]: X = dpt.usm_ndarray((4, 5), dtype='d') In [6]: X.device Out[6]: Device(level_zero:gpu:0) In [7]: Y = X.to_device('cpu') In [8]: Y.device Out[8]: Device(opencl:cpu:0) ``` --- dpctl/tensor/_device.py | 80 ++++++++++++++++++++++++++++++++++++++ dpctl/tensor/_usmarray.pyx | 46 +++++++++++++++++++++- 2 files changed, 124 insertions(+), 2 deletions(-) create mode 100644 dpctl/tensor/_device.py diff --git a/dpctl/tensor/_device.py b/dpctl/tensor/_device.py new file mode 100644 index 0000000000..0f95176c62 --- /dev/null +++ b/dpctl/tensor/_device.py @@ -0,0 +1,80 @@ +# Data Parallel Control (dpctl) +# +# Copyright 2020-2021 Intel Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import dpctl + + +class Device: + """ + Class representing Data-API concept of Device + + This is a wrapper around `dpctl.SyclQueue` with custom + formatting. The class does not have public constructor, + but a class method to construct it from device= keyword + in Array-API functions. + + Instance can be queries for `sycl_queue`, `sycl_context`, + or `sycl_device` + """ + + def __new__(cls, *args, **kwargs): + raise TypeError("No public constructor") + + @classmethod + def create_device(cls, dev): + obj = super().__new__(cls) + if isinstance(dev, Device): + obj.sycl_queue_ = dev.sycl_queue + elif isinstance(dev, dpctl.SyclQueue): + obj.sycl_queue_ = dev + elif isinstance(dev, dpctl.SyclDevice): + par = dev.parent_device + if par is None: + obj.sycl_queue_ = dpctl.SyclQueue(dev) + else: + raise ValueError( + "Using non-root device {} to specify offloading " + "target is ambiguous. Please use dpctl.SyclQueue " + "targeting this device".format(dev) + ) + else: + obj.sycl_queue_ = dpctl.SyclQueue(dev) + return obj + + @property + def sycl_queue(self): + return self.sycl_queue_ + + @property + def sycl_context(self): + return self.sycl_queue_.sycl_context + + @property + def sycl_device(self): + return self.sycl_queue_.sycl_device + + def __repr__(self): + try: + sd = self.sycl_device + except AttributeError: + raise ValueError( + "Instance of {} is not initialized".format(self.__class__) + ) + try: + fs = sd.filter_string + return "Device({})".format(fs) + except TypeError: + # This is a sub-device + return repr(self.sycl_queue) diff --git a/dpctl/tensor/_usmarray.pyx b/dpctl/tensor/_usmarray.pyx index 21db4435a8..7663f5415c 100644 --- a/dpctl/tensor/_usmarray.pyx +++ b/dpctl/tensor/_usmarray.pyx @@ -22,6 +22,8 @@ import numpy as np import dpctl import dpctl.memory as dpmem +from ._device import Device + from cpython.mem cimport PyMem_Free from cpython.tuple cimport PyTuple_New, PyTuple_SetItem @@ -181,8 +183,8 @@ cdef class usm_ndarray: raise ValueError( "buffer='{}' is not understood. " "Recognized values are 'device', 'shared', 'host', " - "or an object with __sycl_usm_array_interface__ " - "property".format(buffer)) + "an instance of `MemoryUSM*` object, or a usm_ndarray" + "".format(buffer)) elif isinstance(buffer, usm_ndarray): _buffer = buffer.usm_data else: @@ -428,6 +430,13 @@ cdef class usm_ndarray: q = self.sycl_queue return q.sycl_device + @property + def device(self): + """ + Returns data-API object representing residence of the array data. + """ + return Device.create_device(self.sycl_queue) + @property def sycl_context(self): """ @@ -475,6 +484,39 @@ cdef class usm_ndarray: res.flags_ |= (self.flags_ & USM_ARRAY_WRITEABLE) return res + def to_device(self, target_device): + """ + Transfer array to target device + """ + d = Device.create_device(target_device) + if (d.sycl_device == self.sycl_device): + return self + elif (d.sycl_context == self.sycl_context): + res = usm_ndarray( + self.shape, + self.dtype, + buffer=self.usm_data, + strides=self.strides, + offset=self.get_offset() + ) + res.flags_ = self.flags + return res + else: + nbytes = self.usm_data.nbytes + new_buffer = type(self.usm_data)( + nbytes, queue=d.sycl_queue + ) + new_buffer.copy_from_device(self.usm_data) + res = usm_ndarray( + self.shape, + self.dtype, + buffer=new_buffer, + strides=self.strides, + offset=self.get_offset() + ) + res.flags_ = self.flags + return res + cdef usm_ndarray _real_view(usm_ndarray ary): """ From 4acf56ca27f0d23e4c85afa1b55aa6c5b9abab4d Mon Sep 17 00:00:00 2001 From: Oleksandr Pavlyk Date: Wed, 12 May 2021 16:40:29 -0500 Subject: [PATCH 2/2] Added more docstrings --- dpctl/tensor/_device.py | 32 ++++++++++++++++++++++++++++---- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/dpctl/tensor/_device.py b/dpctl/tensor/_device.py index 0f95176c62..feb4598957 100644 --- a/dpctl/tensor/_device.py +++ b/dpctl/tensor/_device.py @@ -18,15 +18,15 @@ class Device: """ - Class representing Data-API concept of Device + Class representing Data-API concept of device. - This is a wrapper around `dpctl.SyclQueue` with custom + This is a wrapper around :class:`dpctl.SyclQueue` with custom formatting. The class does not have public constructor, but a class method to construct it from device= keyword in Array-API functions. - Instance can be queries for `sycl_queue`, `sycl_context`, - or `sycl_device` + Instance can be queried for ``sycl_queue``, ``sycl_context``, + or ``sycl_device``. """ def __new__(cls, *args, **kwargs): @@ -34,6 +34,21 @@ def __new__(cls, *args, **kwargs): @classmethod def create_device(cls, dev): + """ + Device.create_device(device) + + Creates instance of Device from argument. + + Args: + device: None, :class:`.Device`, :class:`dpctl.SyclQueue`, or + a :class:`dpctl.SyclDevice` corresponding to a root + SYCL device. + Raises: + ValueError: if an instance of :class:`dpctl.SycDevice` corresponding + to a sub-device was specified as the argument + SyclQueueCreationError: if :class:`dpctl.SyclQueue` could not be + created from the argument + """ obj = super().__new__(cls) if isinstance(dev, Device): obj.sycl_queue_ = dev.sycl_queue @@ -55,14 +70,23 @@ def create_device(cls, dev): @property def sycl_queue(self): + """ + :class:`dpctl.SyclQueue` used to offload to this :class:`.Device`. + """ return self.sycl_queue_ @property def sycl_context(self): + """ + :class:`dpctl.SyclContext` associated with this :class:`.Device`. + """ return self.sycl_queue_.sycl_context @property def sycl_device(self): + """ + :class:`dpctl.SyclDevice` targed by this :class:`.Device`. + """ return self.sycl_queue_.sycl_device def __repr__(self):