# *****************************************************************************
# Copyright (c) 2026, Intel Corporation
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
# - Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
# - Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
# - Neither the name of the copyright holder nor the names of its contributors
# may be used to endorse or promote products derived from this software
# without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
# THE POSSIBILITY OF SUCH DAMAGE.
# *****************************************************************************
import dpctl
from dpctl._sycl_device_factory import _cached_default_device
from dpctl._sycl_queue_manager import get_device_cached_queue
from ._compute_follows_data import get_execution_queue
__doc__ = "Implementation of array API mandated Device class"
[docs]
class Device:
"""
An object representing Data-API concept of device.
This is a wrapper around :class:`dpctl.SyclQueue` with custom
formatting. The class does not have public constructor,
but a class method :meth:`dpnp.tensor.Device.create_device` to construct
it from `device` keyword argument in Array-API functions.
Instance can be queried for ``sycl_queue``, ``sycl_context``,
or ``sycl_device``.
"""
__device_queue_map__ = {}
sycl_queue_ = None
def __new__(cls, *args, **kwargs):
raise TypeError("No public constructor")
@classmethod
def create_device(cls, device=None):
"""
Creates instance of Device from argument.
Parameters
----------
device : {None, Device, dpctl.SyclQueue, dpctl.SyclDevice}, optional
Device specification, i.e. ``None``, :class:`.Device`,
:class:`dpctl.SyclQueue`, or a :class:`dpctl.SyclDevice`
corresponding to a root SYCL device.
Default: ``None``.
Raises
------
ValueError
if an instance of :class:`dpctl.SyclDevice` corresponding
to a sub-device was specified as the argument.
SyclQueueCreationError
if :class:`dpctl.SyclQueue` could not be
created from the argument.
"""
dev = device
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_ = get_device_cached_queue(dev)
else:
raise ValueError(
f"Using non-root device {dev} to specify offloading "
"target is ambiguous. Please use dpctl.SyclQueue "
"targeting this device"
)
else:
if dev is None:
_dev = _cached_default_device()
else:
_dev = dpctl.SyclDevice(dev)
obj.sycl_queue_ = get_device_cached_queue(_dev)
return obj
@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` targeted by this :class:`.Device`."""
return self.sycl_queue_.sycl_device
def __repr__(self):
try:
sd = self.sycl_device
except AttributeError as exc:
raise ValueError(
f"Instance of {self.__class__} is not initialized"
) from exc
try:
fs = sd.filter_string
return f"Device({fs})"
except TypeError:
# This is a sub-device
return repr(self.sycl_queue)
def print_device_info(self):
"""Outputs information about targeted SYCL device"""
self.sycl_device.print_device_info()
def wait(self):
"""Call ``wait`` method of the underlying ``sycl_queue``."""
self.sycl_queue_.wait()
def __eq__(self, other):
"""Equality comparison based on underlying ``sycl_queue``."""
if isinstance(other, Device):
return self.sycl_queue.__eq__(other.sycl_queue)
elif isinstance(other, dpctl.SyclQueue):
return self.sycl_queue.__eq__(other)
return False
def __hash__(self):
"""Compute object's hash value."""
return self.sycl_queue.__hash__()
def normalize_queue_device(sycl_queue=None, device=None):
"""
Utility to process exclusive keyword arguments 'device'
and 'sycl_queue' in functions of ``dpnp.tensor``.
Parameters
----------
sycl_queue : {None, dpctl.SyclQueue}, optional
explicitly indicates where USM allocation is done
and the population code (if any) is executed.
Value ``None`` is interpreted as get the SYCL queue
from ``device`` keyword, or use default queue.
Default: ``None``.
device : {None, str, dpctl.SyclDevice, dpctl.SyclQueue, dpnp.tensor.Device}, optional
array-API keyword indicating non-partitioned SYCL device
where array is allocated.
Default: ``None``.
Returns
-------
out : dpctl.SyclQueue
:class:`dpctl.SyclQueue` object implied by either of provided
keywords. If both are ``None``, ``dpctl.SyclQueue()`` is returned.
If both are specified and imply the same queue, ``sycl_queue``
is returned.
Raises
------
TypeError
if argument is not of the expected type, or keywords
imply incompatible queues.
"""
q = sycl_queue
d = device
if q is None:
d = Device.create_device(d)
return d.sycl_queue
if not isinstance(q, dpctl.SyclQueue):
raise TypeError(f"Expected dpctl.SyclQueue, got {type(q)}")
if d is None:
return q
d = Device.create_device(d)
qq = get_execution_queue(
(
q,
d.sycl_queue,
)
)
if qq is None:
raise TypeError(
"sycl_queue and device keywords can not be both specified"
)
return qq