Skip to content

Commit ac05c1a

Browse files
author
Diptorup Deb
authored
Merge pull request #1193 from IntelPython/feature/add_python_object_createion_for_unboxing_sycl_event_and_queue
Feature/add python object createion for unboxing sycl event and queue
2 parents a030c85 + f46e145 commit ac05c1a

File tree

11 files changed

+193
-47
lines changed

11 files changed

+193
-47
lines changed

.github/workflows/pre-commit.yml

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ jobs:
99
pre-commit:
1010
runs-on: ubuntu-20.04
1111
steps:
12-
- uses: actions/checkout@v2
13-
- uses: actions/setup-python@v2
14-
- uses: pre-commit/[email protected]
12+
- uses: actions/checkout@v3
13+
- uses: actions/setup-python@v3
14+
with:
15+
python-version: '3.11'
16+
- uses: pre-commit/[email protected]

numba_dpex/core/datamodel/models.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,10 @@ def __init__(self, dmm, fe_type):
148148
"meminfo",
149149
types.MemInfoPointer(types.pyobject),
150150
),
151+
(
152+
"parent",
153+
types.pyobject,
154+
),
151155
(
152156
"queue_ref",
153157
types.CPointer(types.int8),

numba_dpex/core/runtime/_dpexrt_python.c

Lines changed: 33 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1256,6 +1256,7 @@ static int DPEXRT_sycl_queue_from_python(NRT_api_functions *nrt,
12561256
Py_INCREF(queue_obj);
12571257
queue_struct->meminfo =
12581258
nrt->manage_memory(queue_obj, NRT_MemInfo_pyobject_dtor);
1259+
queue_struct->parent = queue_obj;
12591260
queue_struct->queue_ref = queue_ref;
12601261

12611262
return 0;
@@ -1287,29 +1288,26 @@ static int DPEXRT_sycl_queue_from_python(NRT_api_functions *nrt,
12871288
static PyObject *DPEXRT_sycl_queue_to_python(NRT_api_functions *nrt,
12881289
queuestruct_t *queuestruct)
12891290
{
1290-
PyObject *orig_queue = NULL;
1291-
1292-
orig_queue = nrt->get_data(queuestruct->meminfo);
1293-
// FIXME: Better error checking is needed to enforce the boxing of the queue
1294-
// object. For now, only the minimal is done as the returning of SyclQueue
1295-
// from a dpjit function should not be a used often and the dpctl C API for
1296-
// type checking etc. is not ready.
1297-
if (orig_queue == NULL) {
1298-
PyErr_Format(PyExc_ValueError,
1299-
"In 'box_from_queuestruct_parent', "
1300-
"failed to create a new dpctl.SyclQueue object.");
1301-
return NULL;
1291+
PyObject *queue_obj = queuestruct->parent;
1292+
1293+
if (queue_obj == NULL) {
1294+
// Make create copy of queue_ref so we don't need to manage nrt lifetime
1295+
// from python object.
1296+
queue_obj = SyclQueue_Make(queuestruct->queue_ref);
1297+
}
1298+
else {
1299+
// Unfortunately we can not optimize (nrt->release that triggers
1300+
// Py_DECREF() from the destructor) and Py_INCREF() because nrt may need
1301+
// the object even if we return it to python.
1302+
// We need to increase reference count because we are returning new
1303+
// reference to the same queue.
1304+
Py_INCREF(queue_obj);
13021305
}
13031306

1304-
// TODO: is there any way to release meminfo without calling dtor so we dont
1305-
// call incref, decref one after another.
1306-
// We need to increase reference count because we are returning new
1307-
// reference to the same queue.
1308-
Py_INCREF(orig_queue);
13091307
// We need to release meminfo since we are taking ownership back.
13101308
nrt->release(queuestruct->meminfo);
13111309

1312-
return orig_queue;
1310+
return queue_obj;
13131311
}
13141312

13151313
/*----------------------------------------------------------------------------*/
@@ -1384,33 +1382,32 @@ static int DPEXRT_sycl_event_from_python(NRT_api_functions *nrt,
13841382
static PyObject *DPEXRT_sycl_event_to_python(NRT_api_functions *nrt,
13851383
eventstruct_t *eventstruct)
13861384
{
1387-
PyObject *orig_event = NULL;
1385+
PyObject *event_obj = NULL;
13881386
PyGILState_STATE gstate;
13891387

1390-
orig_event = nrt->get_data(eventstruct->meminfo);
1391-
// FIXME: Better error checking is needed to enforce the boxing of the event
1392-
// object. For now, only the minimal is done as the returning of SyclEvent
1393-
// from a dpjit function should not be a used often and the dpctl C API for
1394-
// type checking etc. is not ready.
1395-
if (orig_event == NULL) {
1396-
PyErr_Format(PyExc_ValueError,
1397-
"In 'box_from_eventstruct_parent', "
1398-
"failed to create a new dpctl.SyclEvent object.");
1399-
return NULL;
1400-
}
1388+
event_obj = nrt->get_data(eventstruct->meminfo);
14011389

14021390
DPEXRT_DEBUG(
14031391
drt_debug_print("DPEXRT-DEBUG: In DPEXRT_sycl_event_to_python.\n"););
14041392

1405-
// TODO: is there any way to release meminfo without calling dtor so we dont
1406-
// call incref, decref one after another.
1407-
// We need to increase reference count because we are returning new
1408-
// reference to the same event.
1409-
Py_INCREF(orig_event);
1393+
if (event_obj == NULL) {
1394+
// Make create copy of event_ref so we don't need to manage nrt lifetime
1395+
// from python object.
1396+
event_obj = SyclEvent_Make(eventstruct->event_ref);
1397+
}
1398+
else {
1399+
// Unfortunately we can not optimize (nrt->release that triggers
1400+
// Py_DECREF() from the destructor) and Py_INCREF() because nrt may need
1401+
// the object even if we return it to python.
1402+
// We need to increase reference count because we are returning new
1403+
// reference to the same event.
1404+
Py_INCREF(event_obj);
1405+
}
1406+
14101407
// We need to release meminfo since we are taking ownership back.
14111408
nrt->release(eventstruct->meminfo);
14121409

1413-
return orig_event;
1410+
return event_obj;
14141411
}
14151412

14161413
/*----------------------------------------------------------------------------*/

numba_dpex/core/runtime/_queuestruct.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,11 @@
1212
#pragma once
1313

1414
#include "numba/core/runtime/nrt_external.h"
15+
#include <Python.h>
1516

1617
typedef struct
1718
{
1819
NRT_MemInfo *meminfo;
20+
PyObject *parent;
1921
void *queue_ref;
2022
} queuestruct_t;

numba_dpex/dpnp_iface/_intrinsic.py

Lines changed: 61 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,12 @@
66

77
from dpctl import get_device_cached_queue
88
from llvmlite import ir as llvmir
9-
from llvmlite.ir import Constant
9+
from llvmlite.ir import Constant, IRBuilder
1010
from llvmlite.ir.types import DoubleType, FloatType
1111
from numba import types
1212
from numba.core import cgutils
1313
from numba.core import config as numba_config
14+
from numba.core import errors, imputils
1415
from numba.core.typing import signature
1516
from numba.extending import intrinsic, overload_classmethod
1617
from numba.np.arrayobj import (
@@ -73,8 +74,7 @@ def make_queue(context, builder, py_dpctl_sycl_queue):
7374
pyapi, py_dpctl_sycl_queue_addr, queue_struct_voidptr
7475
)
7576

76-
queue_struct = builder.load(queue_struct_ptr)
77-
queue_ref = builder.extract_value(queue_struct, 1)
77+
queue_ref = queue_struct_proxy.queue_ref
7878

7979
return_values = namedtuple(
8080
"return_values", "queue_ref queue_address_ptr pyapi"
@@ -1077,3 +1077,61 @@ def codegen(context, builder, sig, args):
10771077
return ary._getvalue()
10781078

10791079
return signature, codegen
1080+
1081+
1082+
@intrinsic
1083+
def ol_dpnp_nd_array_sycl_queue(
1084+
ty_context,
1085+
ty_dpnp_nd_array: DpnpNdArray,
1086+
):
1087+
if not isinstance(ty_dpnp_nd_array, DpnpNdArray):
1088+
raise errors.TypingError("Argument must be DpnpNdArray")
1089+
1090+
ty_queue: DpctlSyclQueue = ty_dpnp_nd_array.queue
1091+
1092+
sig = ty_queue(ty_dpnp_nd_array)
1093+
1094+
def codegen(context, builder: IRBuilder, sig, args: list):
1095+
array_proxy = cgutils.create_struct_proxy(ty_dpnp_nd_array)(
1096+
context,
1097+
builder,
1098+
value=args[0],
1099+
)
1100+
1101+
queue_ref = array_proxy.sycl_queue
1102+
1103+
queue_struct_proxy = cgutils.create_struct_proxy(ty_queue)(
1104+
context, builder
1105+
)
1106+
1107+
queue_struct_proxy.queue_ref = queue_ref
1108+
queue_struct_proxy.meminfo = array_proxy.meminfo
1109+
1110+
# Warning: current implementation prevents whole object from being
1111+
# destroyed as long as sycl_queue attribute is being used. It should be
1112+
# okay since anywere we use it as an argument callee creates a copy
1113+
# so it does not steel reference.
1114+
#
1115+
# We can avoid it by:
1116+
# queue_ref_copy = sycl.dpctl_queue_copy(builder, queue_ref) #noqa E800
1117+
# queue_struct_proxy.queue_ref = queue_ref_copy #noqa E800
1118+
# queue_struct->meminfo =
1119+
# nrt->manage_memory(queue_ref_copy, DPCTLEvent_Delete);
1120+
# but it will allocate new meminfo object which can negatively affect
1121+
# performance.
1122+
# Speaking philosophically attribute is a part of the object and as long
1123+
# as nobody can still the reference it is a part of the owner object
1124+
# and lifetime is tied to it.
1125+
# TODO: we want to have queue: queuestruct_t instead of
1126+
# queue_ref: QueueRef as an attribute for DPNPNdArray.
1127+
1128+
queue_value = queue_struct_proxy._getvalue()
1129+
1130+
# We need to incref meminfo so that queue model is preventing parent
1131+
# ndarray from being destroyed, that can destroy queue that we are
1132+
# using.
1133+
return imputils.impl_ret_borrowed(
1134+
context, builder, ty_queue, queue_value
1135+
)
1136+
1137+
return sig, codegen

numba_dpex/dpnp_iface/arrayobj.py

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
from numba.core.types.containers import UniTuple
1212
from numba.core.typing.npydecl import parse_dtype as _ty_parse_dtype
1313
from numba.core.typing.npydecl import parse_shape as _ty_parse_shape
14-
from numba.extending import overload
14+
from numba.extending import overload, overload_attribute
1515
from numba.np.arrayobj import getitem_arraynd_intp as np_getitem_arraynd_intp
1616
from numba.np.numpy_support import is_nonelike
1717

@@ -27,6 +27,7 @@
2727
impl_dpnp_ones_like,
2828
impl_dpnp_zeros,
2929
impl_dpnp_zeros_like,
30+
ol_dpnp_nd_array_sycl_queue,
3031
)
3132

3233
# =========================================================================
@@ -1085,3 +1086,23 @@ def getitem_arraynd_intp(context, builder, sig, args):
10851086
ret = builder.insert_value(ret, sycl_queue_attr, sycl_queue_attr_pos)
10861087

10871088
return ret
1089+
1090+
1091+
@overload_attribute(DpnpNdArray, "sycl_queue")
1092+
def dpnp_nd_array_sycl_queue(arr):
1093+
"""Returns :class:`dpctl.SyclQueue` object associated with USM data.
1094+
1095+
This is an overloaded attribute implementation for dpnp.sycl_queue.
1096+
1097+
Args:
1098+
arr (numba_dpex.core.types.DpnpNdArray): Input array from which to
1099+
take sycl_queue.
1100+
1101+
Returns:
1102+
function: Local function `ol_dpnp_nd_array_sycl_queue()`.
1103+
"""
1104+
1105+
def get(arr):
1106+
return ol_dpnp_nd_array_sycl_queue(arr)
1107+
1108+
return get
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# SPDX-FileCopyrightText: 2020 - 2023 Intel Corporation
2+
#
3+
# SPDX-License-Identifier: Apache-2.0
4+
5+
"""
6+
Tests for boxing for dpnp.ndarray
7+
"""
8+
9+
import dpnp
10+
import pytest
11+
from dpctl import SyclQueue
12+
13+
from numba_dpex import dpjit
14+
15+
16+
def test_boxing_without_parent():
17+
"""Test unboxing of the queue that does not have parent"""
18+
19+
@dpjit
20+
def func() -> SyclQueue:
21+
arr = dpnp.empty(10)
22+
queue = arr.sycl_queue
23+
return queue
24+
25+
q: SyclQueue = func()
26+
27+
assert len(q.sycl_device.filter_string) > 0

numba_dpex/tests/core/types/DpctlSyclQueue/test_queue_ref_attr.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,16 +31,21 @@ def are_queues_equal(typingctx, ty_queue1, ty_queue2):
3131

3232
# defines the custom code generation
3333
def codegen(context, builder, sig, args):
34+
q1 = cgutils.create_struct_proxy(ty_queue1)(
35+
context, builder, value=args[0]
36+
)
37+
q2 = cgutils.create_struct_proxy(ty_queue2)(
38+
context, builder, value=args[1]
39+
)
40+
3441
fnty = llvmir.FunctionType(
3542
cgutils.bool_t, [cgutils.voidptr_t, cgutils.voidptr_t]
3643
)
3744
fn = cgutils.get_or_insert_function(
3845
builder.module, fnty, "DPCTLQueue_AreEq"
3946
)
40-
qref1 = builder.extract_value(args[0], 1)
41-
qref2 = builder.extract_value(args[1], 1)
4247

43-
ret = builder.call(fn, [qref1, qref2])
48+
ret = builder.call(fn, [q1.queue_ref, q2.queue_ref])
4449

4550
return ret
4651

numba_dpex/tests/dpjit_tests/dpnp/test_dpnp_empty.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,3 +139,33 @@ def func(shape, queue):
139139
with pytest.raises(errors.TypingError):
140140
queue = dpctl.SyclQueue()
141141
func(10, queue)
142+
143+
144+
@pytest.mark.xfail(reason="dpjit allocates new dpctl.SyclQueue on boxing")
145+
# TODO: remove test_dpnp_empty_with_dpjit_queue_temp once pass.
146+
def test_dpnp_empty_with_dpjit_queue():
147+
"""Test if dpnp array can be created with a queue from another array"""
148+
149+
@dpjit
150+
def func(a):
151+
queue = a.sycl_queue
152+
return dpnp.empty(10, sycl_queue=queue)
153+
154+
a = dpnp.empty(10)
155+
b = func(a)
156+
157+
assert id(a.sycl_queue) == id(b.sycl_queue)
158+
159+
160+
def test_dpnp_empty_with_dpjit_queue_temp():
161+
"""Test if dpnp array can be created with a queue from another array"""
162+
163+
@dpjit
164+
def func(a):
165+
queue = a.sycl_queue
166+
return dpnp.empty(10, sycl_queue=queue)
167+
168+
a = dpnp.empty(10)
169+
b = func(a)
170+
171+
assert a.sycl_queue == b.sycl_queue

numba_dpex/tests/dpjit_tests/parfors/test_dpnp_logic_ops.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,6 @@ def f(a, b):
7878
)
7979

8080

81-
@pytest.mark.xfail
8281
def test_unary_ops(unary_op, input_arrays):
8382
a = input_arrays[0]
8483
uop = getattr(dpnp, unary_op)

0 commit comments

Comments
 (0)