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

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions