Skip to content

Commit 96df456

Browse files
Improve dpctl.memory._memory coverage
1. Added a test to check MemoryConstructor(other_memory_object) 2. Add test_sycl_usm test to use copy_from_device 3. Added tests to check validation of __sycl_usm_array_interface__ in memory objects
1 parent 5c0132c commit 96df456

File tree

1 file changed

+237
-25
lines changed

1 file changed

+237
-25
lines changed

dpctl/tests/test_sycl_usm.py

Lines changed: 237 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -48,12 +48,20 @@ def __sycl_usm_array_interface(self):
4848
not has_sycl_platforms(),
4949
reason="No SYCL devices except the default host device.",
5050
)
51-
def test_memory_create():
51+
def test_memory_create(memory_ctor):
52+
import sys
53+
5254
nbytes = 1024
53-
queue = dpctl.get_current_queue()
54-
mobj = MemoryUSMShared(nbytes, alignment=64, queue=queue)
55+
queue = dpctl.SyclQueue()
56+
mobj = memory_ctor(nbytes, alignment=64, queue=queue)
5557
assert mobj.nbytes == nbytes
5658
assert hasattr(mobj, "__sycl_usm_array_interface__")
59+
assert len(mobj) == nbytes
60+
assert mobj.size == nbytes
61+
assert mobj._context == queue.sycl_context
62+
assert type(repr(mobj)) is str
63+
assert type(bytes(mobj)) is bytes
64+
assert sys.getsizeof(mobj) > nbytes
5765

5866

5967
@pytest.mark.skipif(
@@ -69,7 +77,7 @@ def test_memory_create_with_np():
6977

7078
def _create_memory():
7179
nbytes = 1024
72-
queue = dpctl.get_current_queue()
80+
queue = dpctl.SyclQueue()
7381
mobj = MemoryUSMShared(nbytes, alignment=64, queue=queue)
7482
return mobj
7583

@@ -90,38 +98,36 @@ def test_memory_without_context():
9098

9199
# Without context
92100
assert mobj.get_usm_type() == "shared"
101+
assert mobj.get_usm_type(syclobj=dpctl.SyclContext()) == "shared"
93102

94103

95104
@pytest.mark.skipif(not has_cpu(), reason="No SYCL CPU device available.")
96105
def test_memory_cpu_context():
97106
mobj = _create_memory()
98107

99-
# CPU context
100-
with dpctl.device_context("opencl:cpu:0"):
101-
# type respective to the context in which
102-
# memory was created
103-
usm_type = mobj.get_usm_type()
104-
assert usm_type == "shared"
108+
# type respective to the context in which
109+
# memory was created
110+
usm_type = mobj.get_usm_type()
111+
assert usm_type == "shared"
105112

106-
current_queue = dpctl.get_current_queue()
107-
# type as view from current queue
108-
usm_type = mobj.get_usm_type(current_queue)
109-
# type can be unknown if current queue is
110-
# not in the same SYCL context
111-
assert usm_type in ["unknown", "shared"]
113+
cpu_queue = dpctl.SyclQueue("cpu")
114+
# type as view from CPU queue
115+
usm_type = mobj.get_usm_type(cpu_queue)
116+
# type can be unknown if current queue is
117+
# not in the same SYCL context
118+
assert usm_type in ["unknown", "shared"]
112119

113120

114121
@pytest.mark.skipif(not has_gpu(), reason="No OpenCL GPU queues available")
115122
def test_memory_gpu_context():
116123
mobj = _create_memory()
117124

118125
# GPU context
119-
with dpctl.device_context("opencl:gpu:0"):
120-
usm_type = mobj.get_usm_type()
121-
assert usm_type == "shared"
122-
current_queue = dpctl.get_current_queue()
123-
usm_type = mobj.get_usm_type(current_queue)
124-
assert usm_type in ["unknown", "shared"]
126+
usm_type = mobj.get_usm_type()
127+
assert usm_type == "shared"
128+
gpu_queue = dpctl.SyclQueue("opencl:gpu")
129+
usm_type = mobj.get_usm_type(gpu_queue)
130+
assert usm_type in ["unknown", "shared"]
125131

126132

127133
@pytest.mark.skipif(
@@ -166,10 +172,10 @@ def test_zero_copy():
166172
not has_sycl_platforms(),
167173
reason="No SYCL devices except the default host device.",
168174
)
169-
def test_pickling():
175+
def test_pickling(memory_ctor):
170176
import pickle
171177

172-
mobj = _create_memory()
178+
mobj = memory_ctor(1024, alignment=64)
173179
host_src_obj = _create_host_buf(mobj.nbytes)
174180
mobj.copy_from_host(host_src_obj)
175181

@@ -185,6 +191,22 @@ def test_pickling():
185191
), "Pickling/unpickling should be changing pointer"
186192

187193

194+
@pytest.mark.skipif(
195+
not has_sycl_platforms(),
196+
reason="No SYCL devices except the default host device.",
197+
)
198+
def test_pickling_reconstructor_invalid_type(memory_ctor):
199+
import pickle
200+
201+
mobj = memory_ctor(1024, alignment=64)
202+
good_pickle_bytes = pickle.dumps(mobj)
203+
usm_types = expected_usm_type(memory_ctor).encode("utf-8")
204+
i = good_pickle_bytes.index(usm_types)
205+
bad_pickle_bytes = good_pickle_bytes[:i] + b"u" + good_pickle_bytes[i + 1 :]
206+
with pytest.raises(ValueError):
207+
pickle.loads(bad_pickle_bytes)
208+
209+
188210
@pytest.fixture(params=[MemoryUSMShared, MemoryUSMDevice, MemoryUSMHost])
189211
def memory_ctor(request):
190212
return request.param
@@ -256,12 +278,15 @@ def test_sycl_usm_array_interface(memory_ctor):
256278

257279

258280
class View:
259-
def __init__(self, buf, shape, strides, offset, syclobj=None):
281+
def __init__(
282+
self, buf, shape, strides, offset, syclobj=None, transf_fn=None
283+
):
260284
self.buffer_ = buf
261285
self.shape_ = shape
262286
self.strides_ = strides
263287
self.offset_ = offset
264288
self.syclobj_ = syclobj
289+
self.transf_fn_ = transf_fn
265290

266291
@property
267292
def __sycl_usm_array_interface__(self):
@@ -271,6 +296,8 @@ def __sycl_usm_array_interface__(self):
271296
sua_iface["strides"] = self.strides_
272297
if self.syclobj_:
273298
sua_iface["syclobj"] = self.syclobj_
299+
if self.transf_fn_:
300+
sua_iface = self.transf_fn_(sua_iface)
274301
return sua_iface
275302

276303

@@ -338,6 +365,106 @@ def test_suai_non_contig_2D(memory_ctor):
338365
assert np.array_equal(res, expected_res)
339366

340367

368+
def test_suai_invalid_suai():
369+
n_bytes = 2 * 3 * 5 * 128
370+
try:
371+
q = dpctl.SyclQueue()
372+
except dpctl.SyclQueueCreationError:
373+
pytest.skip("Could not create default queue")
374+
try:
375+
buf = MemoryUSMShared(n_bytes, queue=q)
376+
except Exception:
377+
pytest.skip("USM-shared allocation failed")
378+
379+
# different syclobj values
380+
class DuckSyclObject:
381+
def __init__(self, syclobj):
382+
self.syclobj = syclobj
383+
384+
def _get_capsule(self):
385+
return self.syclobj._get_capsule()
386+
387+
ctx = q.sycl_context
388+
for syclobj in [
389+
q,
390+
DuckSyclObject(q),
391+
q._get_capsule(),
392+
ctx,
393+
DuckSyclObject(ctx),
394+
ctx._get_capsule(),
395+
]:
396+
v = View(buf, shape=(n_bytes,), strides=(1,), offset=0, syclobj=syclobj)
397+
MemoryUSMShared(v)
398+
with pytest.raises(ValueError):
399+
MemoryUSMDevice(v)
400+
with pytest.raises(ValueError):
401+
MemoryUSMHost(v)
402+
403+
# version validation
404+
def invalid_version(suai_iface):
405+
"Set version to invalid"
406+
suai_iface["version"] = 0
407+
return suai_iface
408+
409+
v = View(
410+
buf, shape=(n_bytes,), strides=(1,), offset=0, transf_fn=invalid_version
411+
)
412+
with pytest.raises(ValueError):
413+
MemoryUSMShared(v)
414+
415+
# data validation
416+
def invalid_data(suai_iface):
417+
"Set data to invalid"
418+
suai_iface["data"] = tuple()
419+
return suai_iface
420+
421+
v = View(
422+
buf, shape=(n_bytes,), strides=(1,), offset=0, transf_fn=invalid_data
423+
)
424+
with pytest.raises(ValueError):
425+
MemoryUSMShared(v)
426+
# set shape to a negative value
427+
v = View(buf, shape=(-n_bytes,), strides=(2,), offset=0)
428+
with pytest.raises(ValueError):
429+
MemoryUSMShared(v)
430+
v = View(buf, shape=(-n_bytes,), strides=None, offset=0)
431+
with pytest.raises(ValueError):
432+
MemoryUSMShared(v)
433+
# shape validation
434+
v = View(buf, shape=None, strides=(1,), offset=0)
435+
with pytest.raises(ValueError):
436+
MemoryUSMShared(v)
437+
438+
# typestr validation
439+
def invalid_typestr(suai_iface):
440+
suai_iface["typestr"] = "invalid"
441+
return suai_iface
442+
443+
v = View(
444+
buf, shape=(n_bytes,), strides=(1,), offset=0, transf_fn=invalid_typestr
445+
)
446+
with pytest.raises(ValueError):
447+
MemoryUSMShared(v)
448+
449+
def unsupported_typestr(suai_iface):
450+
suai_iface["typestr"] = "O"
451+
return suai_iface
452+
453+
v = View(
454+
buf,
455+
shape=(n_bytes,),
456+
strides=(1,),
457+
offset=0,
458+
transf_fn=unsupported_typestr,
459+
)
460+
with pytest.raises(ValueError):
461+
MemoryUSMShared(v)
462+
# set strides to invalid value
463+
v = View(buf, shape=(n_bytes,), strides=Ellipsis, offset=0)
464+
with pytest.raises(ValueError):
465+
MemoryUSMShared(v)
466+
467+
341468
def check_view(v):
342469
"""
343470
Memory object created from duck __sycl_usm_array_interface__ argument
@@ -389,3 +516,88 @@ def test_with_constructor(memory_ctor):
389516
syclobj=buf.sycl_device.filter_string,
390517
)
391518
check_view(v)
519+
520+
521+
@pytest.mark.skipif(
522+
not has_sycl_platforms(),
523+
reason="No SYCL devices except the default host device.",
524+
)
525+
def test_cpython_api(memory_ctor):
526+
import ctypes
527+
import sys
528+
529+
mobj = memory_ctor(1024)
530+
mod = sys.modules[mobj.__class__.__module__]
531+
# get capsules storing function pointers
532+
mem_ptr_fn_cap = mod.__pyx_capi__["get_usm_pointer"]
533+
mem_ctx_fn_cap = mod.__pyx_capi__["get_context"]
534+
mem_nby_fn_cap = mod.__pyx_capi__["get_nbytes"]
535+
# construct Python callable to invoke "get_usm_pointer"
536+
cap_ptr_fn = ctypes.pythonapi.PyCapsule_GetPointer
537+
cap_ptr_fn.restype = ctypes.c_void_p
538+
cap_ptr_fn.argtypes = [ctypes.py_object, ctypes.c_char_p]
539+
mem_ptr_fn_ptr = cap_ptr_fn(
540+
mem_ptr_fn_cap, b"DPCTLSyclUSMRef (struct Py_MemoryObject *)"
541+
)
542+
mem_ctx_fn_ptr = cap_ptr_fn(
543+
mem_ctx_fn_cap, b"DPCTLSyclContextRef (struct Py_MemoryObject *)"
544+
)
545+
mem_nby_fn_ptr = cap_ptr_fn(
546+
mem_nby_fn_cap, b"size_t (struct Py_MemoryObject *)"
547+
)
548+
callable_maker = ctypes.PYFUNCTYPE(ctypes.c_void_p, ctypes.py_object)
549+
get_ptr_fn = callable_maker(mem_ptr_fn_ptr)
550+
get_ctx_fn = callable_maker(mem_ctx_fn_ptr)
551+
get_nby_fn = callable_maker(mem_nby_fn_ptr)
552+
553+
capi_ptr = get_ptr_fn(mobj)
554+
direct_ptr = mobj._pointer
555+
assert capi_ptr == direct_ptr
556+
capi_ctx_ref = get_ctx_fn(mobj)
557+
direct_ctx_ref = mobj._context.addressof_ref()
558+
assert capi_ctx_ref == direct_ctx_ref
559+
capi_nbytes = get_nby_fn(mobj)
560+
direct_nbytes = mobj.nbytes
561+
assert capi_nbytes == direct_nbytes
562+
563+
564+
def test_memory_construction_from_other_memory_objects():
565+
try:
566+
q = dpctl.SyclQueue()
567+
except dpctl.SyclQueueCreationError:
568+
pytest.skip("Default queue could not be created")
569+
m_sh = MemoryUSMShared(256, queue=q)
570+
m_de = MemoryUSMDevice(256, queue=q)
571+
m_ho = MemoryUSMHost(256, queue=q)
572+
with pytest.raises(ValueError):
573+
MemoryUSMDevice(m_sh)
574+
with pytest.raises(ValueError):
575+
MemoryUSMHost(m_de)
576+
with pytest.raises(ValueError):
577+
MemoryUSMShared(m_ho)
578+
m1 = MemoryUSMDevice(m_sh, copy=True)
579+
m2 = MemoryUSMHost(m_de, copy=True)
580+
m3 = MemoryUSMShared(m_de, copy=True)
581+
assert bytes(m1) == bytes(m_sh)
582+
assert bytes(m2) == bytes(m3)
583+
584+
585+
def test_memory_copy_between_contexts():
586+
try:
587+
q = dpctl.SyclQueue("cpu")
588+
except dpctl.SyclQueueCreationError:
589+
pytest.skip("CPU queue could not be created")
590+
d = q.sycl_device
591+
n = d.max_compute_units
592+
n_half = n // 2
593+
d0, d1 = d.create_sub_devices(partition=[n_half, n - n_half])
594+
q0 = dpctl.SyclQueue(d0)
595+
q1 = dpctl.SyclQueue(d1)
596+
m0 = MemoryUSMDevice(256, queue=q0)
597+
m1 = MemoryUSMDevice(256, queue=q1)
598+
host_buf = b"abcd" * 64
599+
m0.copy_from_host(host_buf)
600+
m1.copy_from_device(m0)
601+
copy_buf = bytearray(256)
602+
m1.copy_to_host(copy_buf)
603+
assert host_buf == copy_buf

0 commit comments

Comments
 (0)