Skip to content

.data.ptr on array views ignores USM offset — all slices return base allocation pointer #2781

@abagusetty

Description

@abagusetty

dpnp_array.data.ptr always returns the base USM allocation pointer for views/slices, ignoring the offset tracked internally by dpctl.tensor.usm_ndarray. This means arr[0].data.ptr == arr[1].data.ptr for any multi-dimensional array, making it impossible to pass correct device pointers to external kernels via .data.ptr.
This is related to but distinct from a recent issue #2641. That issue covered dpnp.ndarray(shape, buffer=usm_memory_obj) dropping the offset during construction. This issue is about .data.ptr not reflecting offsets on any view, even when the underlying dpctl layer tracks them correctly.

A big shoutout to AI for helping with several cases!

Reproducer:

import dpnp
import dpctl.tensor as dpt
import numpy as np

comp = 4
nao_max = 50
nao_sub = 30
MIN_BLK_SIZE = 4096
ngrids = MIN_BLK_SIZE  # ip1 - ip0 == full block for simplicity

def ptr(a):
    return int(a.data.ptr)

def usm_offset(a):
    if hasattr(a, '__sycl_usm_array_interface__'):
        return a.__sycl_usm_array_interface__.get('offset', 0)
    return 'N/A'

def check_views(label, arr):
    print(f"\n=== {label} ===")
    print(f"  shape      = {arr.shape}")
    print(f"  dtype      = {arr.dtype}")
    print(f"  data.ptr   = {hex(ptr(arr))}")
    print(f"  USM offset = {usm_offset(arr)}")
    print(f"  arr[0].data.ptr = {hex(ptr(arr[0]))}")
    if arr.shape[0] > 1:
        print(f"  arr[1].data.ptr = {hex(ptr(arr[1]))}")
        expected_diff = arr.shape[1] * arr.shape[2] * arr.itemsize
        actual_diff = ptr(arr[1]) - ptr(arr[0])
        print(f"  arr[0]==arr[1]?  {ptr(arr[0]) == ptr(arr[1])}")
        print(f"  expected diff    = {expected_diff}")
        print(f"  actual diff      = {actual_diff}")
        print(f"  CORRECT?         {'YES' if actual_diff == expected_diff else 'NO -- BUG!'}")


# ── The original allocation (like buf = cupy.empty(...)) ──
buf = dpnp.empty((comp, nao_max, MIN_BLK_SIZE), dtype=dpnp.float64, order='C')
print(f"buf.shape = {buf.shape}, buf.data.ptr = {hex(ptr(buf))}")
print(f"type(buf.data) = {type(buf.data)}")

shape = (comp, nao_sub, ngrids)

# ── Test 1: dpnp.ndarray(shape, buffer=buf.data)  [the broken pattern] ──
out1 = dpnp.ndarray(shape, dtype=buf.dtype, buffer=buf.data)
check_views("Test 1: dpnp.ndarray(shape, buffer=buf.data)", out1)

# ── Test 2: dpnp.ndarray(shape, buffer=buf)  [the working pattern] ──
out2 = dpnp.ndarray(shape, dtype=buf.dtype, buffer=buf)
check_views("Test 2: dpnp.ndarray(shape, buffer=buf)", out2)

# ── Test 3: flat + reshape via dpnp ──
flat_size = comp * nao_sub * ngrids
flat3 = dpnp.ndarray((flat_size,), dtype=buf.dtype, buffer=buf.data)
out3 = flat3.reshape(shape)
check_views("Test 3: flat dpnp(buffer=buf.data).reshape()", out3)

# ── Test 4: flat dpnp + dpnp.ndarray(shape, buffer=flat) ──
flat4 = dpnp.ndarray((flat_size,), dtype=buf.dtype, buffer=buf.data)
out4 = dpnp.ndarray(shape, dtype=buf.dtype, buffer=flat4)
check_views("Test 4: dpnp.ndarray(shape, buffer=flat_dpnp)", out4)

# ── Test 5: dpctl.tensor direct construction ──
usm5 = dpt.usm_ndarray(shape, dtype=dpt.float64, buffer=buf.data)
print(f"\n=== Test 5: dpctl.tensor.usm_ndarray(shape, buffer=buf.data) ===")
print(f"  shape      = {usm5.shape}")
v0 = usm5[0]
v1 = usm5[1]
print(f"  usm[0] offset = {v0.__sycl_usm_array_interface__.get('offset', 0)}")
print(f"  usm[1] offset = {v1.__sycl_usm_array_interface__.get('offset', 0)}")
print(f"  usm[0] data[0] = {hex(v0.__sycl_usm_array_interface__['data'][0])}")
print(f"  usm[1] data[0] = {hex(v1.__sycl_usm_array_interface__['data'][0])}")
expected_diff = nao_sub * ngrids * buf.itemsize
offset_diff = (v1.__sycl_usm_array_interface__.get('offset', 0) -
               v0.__sycl_usm_array_interface__.get('offset', 0)) * buf.itemsize
data_diff = (v1.__sycl_usm_array_interface__['data'][0] -
             v0.__sycl_usm_array_interface__['data'][0])
total_diff = offset_diff + data_diff
print(f"  expected byte diff = {expected_diff}")
print(f"  total byte diff    = {total_diff}  (data_diff={data_diff}, offset_diff={offset_diff})")
print(f"  CORRECT?           {'YES' if total_diff == expected_diff else 'NO -- BUG!'}")

# ── Test 6: dpctl construct + wrap in dpnp ──
usm6 = dpt.usm_ndarray(shape, dtype=dpt.float64, buffer=buf.data)
out6 = dpnp.dpnp_array.dpnp_array._create_from_usm_ndarray(usm6)
check_views("Test 6: dpctl usm_ndarray -> dpnp wrap", out6)

# ── Test 7: dpctl flat + reshape ──
flat_usm7 = dpt.usm_ndarray((flat_size,), dtype=dpt.float64, buffer=buf.data)
shaped_usm7 = dpt.reshape(flat_usm7, shape)
print(f"\n=== Test 7: dpctl flat + dpt.reshape ===")
v0 = shaped_usm7[0]
v1 = shaped_usm7[1]
print(f"  shaped[0] offset = {v0.__sycl_usm_array_interface__.get('offset', 0)}")
print(f"  shaped[1] offset = {v1.__sycl_usm_array_interface__.get('offset', 0)}")
offset_diff = (v1.__sycl_usm_array_interface__.get('offset', 0) -
               v0.__sycl_usm_array_interface__.get('offset', 0)) * buf.itemsize
data_diff = (v1.__sycl_usm_array_interface__['data'][0] -
             v0.__sycl_usm_array_interface__['data'][0])
total_diff = offset_diff + data_diff
print(f"  expected byte diff = {expected_diff}")
print(f"  total byte diff    = {total_diff}")
print(f"  CORRECT?           {'YES' if total_diff == expected_diff else 'NO -- BUG!'}")

# wrap in dpnp
out7 = dpnp.dpnp_array.dpnp_array._create_from_usm_ndarray(shaped_usm7)
check_views("Test 7b: dpctl flat+reshape -> dpnp wrap", out7)

# ── Summary ──
print("\n" + "="*60)
print("SUMMARY")
print("="*60)
for label, arr in [
    ("Test 1: buffer=buf.data", out1),
    ("Test 2: buffer=buf", out2),
    ("Test 3: flat(buf.data).reshape", out3),
    ("Test 4: ndarray(shape, buffer=flat)", out4),
    ("Test 6: dpctl->dpnp wrap", out6),
    ("Test 7b: dpctl flat+reshape->dpnp", out7),
]:
    if arr.shape[0] > 1:
        diff = ptr(arr[1]) - ptr(arr[0])
        expected = arr.shape[1] * arr.shape[2] * arr.itemsize
        ok = "OK" if diff == expected else "BROKEN"
        print(f"  {label:40s} ptr diff={diff:>10d}  expected={expected:>10d}  [{ok}]")

output:

$ python3 ./test_dpnp_issuedataptr.py 
buf.shape = (4, 50, 4096), buf.data.ptr = 0xff00000000200000
type(buf.data) = <class 'dpnp.memory._memory.MemoryUSMDevice'>

=== Test 1: dpnp.ndarray(shape, buffer=buf.data) ===
  shape      = (4, 30, 4096)
  dtype      = float64
  data.ptr   = 0xff00000000200000
  USM offset = 0
  arr[0].data.ptr = 0xff00000000200000
  arr[1].data.ptr = 0xff00000000200000
  arr[0]==arr[1]?  True
  expected diff    = 983040
  actual diff      = 0
  CORRECT?         NO -- BUG!

=== Test 2: dpnp.ndarray(shape, buffer=buf) ===
  shape      = (4, 30, 4096)
  dtype      = float64
  data.ptr   = 0xff00000000200000
  USM offset = 0
  arr[0].data.ptr = 0xff00000000200000
  arr[1].data.ptr = 0xff000000002f0000
  arr[0]==arr[1]?  False
  expected diff    = 983040
  actual diff      = 983040
  CORRECT?         YES

=== Test 3: flat dpnp(buffer=buf.data).reshape() ===
  shape      = (4, 30, 4096)
  dtype      = float64
  data.ptr   = 0xff00000000200000
  USM offset = 0
  arr[0].data.ptr = 0xff00000000200000
  arr[1].data.ptr = 0xff00000000200000
  arr[0]==arr[1]?  True
  expected diff    = 983040
  actual diff      = 0
  CORRECT?         NO -- BUG!

=== Test 4: dpnp.ndarray(shape, buffer=flat_dpnp) ===
  shape      = (4, 30, 4096)
  dtype      = float64
  data.ptr   = 0xff00000000200000
  USM offset = 0
  arr[0].data.ptr = 0xff00000000200000
  arr[1].data.ptr = 0xff00000000200000
  arr[0]==arr[1]?  True
  expected diff    = 983040
  actual diff      = 0
  CORRECT?         NO -- BUG!

=== Test 5: dpctl.tensor.usm_ndarray(shape, buffer=buf.data) ===
  shape      = (4, 30, 4096)
  usm[0] offset = 0
  usm[1] offset = 122880
  usm[0] data[0] = 0xff00000000200000
  usm[1] data[0] = 0xff00000000200000
  expected byte diff = 983040
  total byte diff    = 983040  (data_diff=0, offset_diff=983040)
  CORRECT?           YES

=== Test 6: dpctl usm_ndarray -> dpnp wrap ===
  shape      = (4, 30, 4096)
  dtype      = float64
  data.ptr   = 0xff00000000200000
  USM offset = 0
  arr[0].data.ptr = 0xff00000000200000
  arr[1].data.ptr = 0xff00000000200000
  arr[0]==arr[1]?  True
  expected diff    = 983040
  actual diff      = 0
  CORRECT?         NO -- BUG!

=== Test 7: dpctl flat + dpt.reshape ===
  shaped[0] offset = 0
  shaped[1] offset = 122880
  expected byte diff = 983040
  total byte diff    = 983040
  CORRECT?           YES

=== Test 7b: dpctl flat+reshape -> dpnp wrap ===
  shape      = (4, 30, 4096)
  dtype      = float64
  data.ptr   = 0xff00000000200000
  USM offset = 0
  arr[0].data.ptr = 0xff00000000200000
  arr[1].data.ptr = 0xff00000000200000
  arr[0]==arr[1]?  True
  expected diff    = 983040
  actual diff      = 0
  CORRECT?         NO -- BUG!

============================================================
SUMMARY
============================================================
  Test 1: buffer=buf.data                  ptr diff=         0  expected=    983040  [BROKEN]
  Test 2: buffer=buf                       ptr diff=    983040  expected=    983040  [OK]
  Test 3: flat(buf.data).reshape           ptr diff=         0  expected=    983040  [BROKEN]
  Test 4: ndarray(shape, buffer=flat)      ptr diff=         0  expected=    983040  [BROKEN]
  Test 6: dpctl->dpnp wrap                 ptr diff=         0  expected=    983040  [BROKEN]
  Test 7b: dpctl flat+reshape->dpnp        ptr diff=         0  expected=    983040  [BROKEN]

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions