Skip to content

Commit 54d2109

Browse files
authored
Resolve an issue with .data.ptr ignoring USM offset on array views (#2812)
The `create_data()` function had an early return when `usm_data` was already a dpnp memory class instance, which skipped setting the `ptr` attribute. This caused all array views to report the same base pointer, making `arr[0].data.ptr == arr[1].data.ptr` even though they point to different memory locations. The PR proposes a fix ensuring `ptr` is always set to `x._pointer`, which points to the start of the array's data (including any offset for views), rather than `usm_data._pointer` which points to the base buffer. Additionally, always create a new memory wrapper instance to avoid shared state when the same `usm_data` is used for multiple views. This PR closes #2781. - [x] Have you provided a meaningful PR description? - [x] Have you added a test, reproducer or referred to an issue with a reproducer? - [x] Have you tested your changes locally for CPU and GPU devices? - [x] Have you made sure that new changes do not introduce compiler warnings? - [ ] Have you checked performance impact of proposed changes? - [ ] Have you added documentation for your changes, if necessary? - [x] Have you added your changes to the changelog?
1 parent 21d5e1a commit 54d2109

File tree

3 files changed

+110
-10
lines changed

3 files changed

+110
-10
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ Also, that release drops support for Python 3.9, making Python 3.10 the minimum
7676
* Resolved an issue causing `dpnp.linspace` to return an incorrect output shape when inputs were passed as arrays [#2712](https://github.com/IntelPython/dpnp/pull/2712)
7777
* Resolved an issue where `dpnp` always returns the base allocation pointer, when the view start is expected [#2651](https://github.com/IntelPython/dpnp/pull/2651)
7878
* Fixed an issue causing an exception in `dpnp.geomspace` and `dpnp.logspace` when called with explicit `device` keyword but any input array is allocated on another device [#2723](https://github.com/IntelPython/dpnp/pull/2723)
79+
* Fixed `.data.ptr` property on array views to correctly return the pointer to the view's data location instead of the base allocation pointer [#2812](https://github.com/IntelPython/dpnp/pull/2812)
7980

8081
### Security
8182

dpnp/memory/_memory.py

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -98,13 +98,18 @@ def create_data(x):
9898
usm_data = x.usm_data
9999

100100
if isinstance(usm_data, tuple(dispatch.values())):
101-
return usm_data
102-
103-
cls = dispatch.get(type(usm_data), None)
104-
if cls:
105-
data = cls(usm_data)
106-
# `ptr` is expecting to point at the start of the array's data,
107-
# while `usm_data._pointer` is a pointer at the start of memory buffer
108-
data.ptr = x._pointer
109-
return data
110-
raise TypeError(f"Expected USM memory, but got {type(usm_data)}")
101+
# usm_data is already an instance of MemoryUSM<type> class
102+
cls = usm_data.__class__
103+
elif (cls := dispatch.get(type(usm_data))) is not None:
104+
pass # cls is set
105+
else:
106+
raise TypeError(f"Expected USM memory, but got {type(usm_data)}")
107+
108+
# create a new instance each time since usm_data might be a view
109+
# of another array
110+
data = cls(usm_data)
111+
112+
# `ptr` is expecting to point at the start of the array's data,
113+
# while `usm_data._pointer` is a pointer at the start of memory buffer
114+
data.ptr = x._pointer
115+
return data

dpnp/tests/test_memory.py

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,41 @@ def test_wrong_usm_data(self):
2727
with pytest.raises(TypeError):
2828
dpm.create_data(d)
2929

30+
def test_dpctl_view(self):
31+
a = dpt.arange(10)
32+
view = a[3:]
33+
34+
data = dpm.create_data(view)
35+
assert data.ptr == view._pointer
36+
37+
def test_dpctl_different_views(self):
38+
a = dpt.reshape(dpt.arange(12), (3, 4))
39+
40+
data0 = dpm.create_data(a[0])
41+
data1 = dpm.create_data(a[1])
42+
43+
# Verify independent wrapper objects
44+
assert data0 is not data1
45+
46+
# Verify correct pointers
47+
assert data0.ptr == a[0]._pointer
48+
assert data1.ptr == a[1]._pointer
49+
assert data0.ptr != data1.ptr
50+
51+
def test_repeated_calls(self):
52+
a = dpt.arange(20)
53+
view = a[5:15]
54+
55+
# Multiple calls should return independent objects with same ptr
56+
data1 = dpm.create_data(view)
57+
data2 = dpm.create_data(view)
58+
59+
assert data1 is not data2, "Should create independent wrapper objects"
60+
assert data1.ptr == data2.ptr, "Both should point to same location"
61+
assert data1.ptr == view._pointer
62+
63+
64+
class TestNdarray:
3065
def test_ndarray_from_data(self):
3166
a = dpnp.empty(5)
3267
b = dpnp.ndarray(a.shape, buffer=a.data)
@@ -42,3 +77,62 @@ def test_view_non_zero_offset(self):
4277
pl = dpnp.ndarray((n, m), dtype=a.dtype, buffer=sl)
4378
assert pl.data.ptr == sl.data.ptr
4479
assert a.data.ptr != sl.data.ptr
80+
81+
def test_slices_2d(self):
82+
# Create 2D array and verify slices have different pointers
83+
a = dpnp.arange(12, dtype=dpnp.float32).reshape(3, 4)
84+
85+
# Each row should have a different pointer
86+
row0_ptr = a[0].data.ptr
87+
row1_ptr = a[1].data.ptr
88+
row2_ptr = a[2].data.ptr
89+
90+
assert (
91+
row0_ptr != row1_ptr
92+
), "a[0] and a[1] should have different pointers"
93+
assert (
94+
row1_ptr != row2_ptr
95+
), "a[1] and a[2] should have different pointers"
96+
97+
# Check byte offsets match expected stride
98+
stride = a.strides[0] # stride between rows in bytes
99+
assert row1_ptr - row0_ptr == stride
100+
assert row2_ptr - row1_ptr == stride
101+
102+
def test_slices_multidimensional(self):
103+
# 3D array
104+
a = dpnp.zeros((5, 10, 20), dtype=dpnp.int32)
105+
106+
# Different slices along first axis should have different pointers
107+
slice0_ptr = a[0].data.ptr
108+
slice1_ptr = a[1].data.ptr
109+
110+
assert slice0_ptr != slice1_ptr
111+
assert slice1_ptr - slice0_ptr == a.strides[0]
112+
113+
def test_repeated_access(self):
114+
a = dpnp.arange(20).reshape(4, 5)
115+
116+
# Multiple accesses to same slice should give same ptr value
117+
ptr1 = a[2].data.ptr
118+
ptr2 = a[2].data.ptr
119+
120+
assert ptr1 == ptr2, "Same slice should have consistent ptr value"
121+
122+
# But different slices should have different ptrs
123+
assert a[0].data.ptr != a[2].data.ptr
124+
125+
def test_array_on_view_with_slicing(self):
126+
# Original array
127+
a = dpnp.arange(24, dtype=dpnp.float32).reshape(6, 4)
128+
129+
# Create view using slicing
130+
view = a[2:5]
131+
132+
# Construct new array from view
133+
new_arr = dpnp.ndarray(view.shape, dtype=view.dtype, buffer=view)
134+
135+
# Pointers should match
136+
assert new_arr.data.ptr == view.data.ptr
137+
# And should be different from base array
138+
assert new_arr.data.ptr != a.data.ptr

0 commit comments

Comments
 (0)