Repository navigation
[BUG]: StridedMemoryView.from_dlpack ignores DLTensor.byte_offset #2592
Description
Type of Bug
Silent Failure
Component
cuda.core
Describe the bug
The DLPack specification places a tensor's first element at data + byte_offset. StridedMemoryView.from_dlpack sets ptr from data alone, so a producer that reports an allocation base in data and expresses a slice in byte_offset yields a view whose ptr is short by exactly byte_offset bytes. Nothing raises, and every consumer built on ptr reads the wrong memory, including the __dlpack__ re-export, which writes ptr back out as data with byte_offset = 0 and so loses the offset permanently. from_any_interface is affected too, since it delegates to from_dlpack for a DLPack producer. The capsule-consuming helper in the same module, _smv_from_dlpack_capsule, does add byte_offset.
How to Reproduce
This producer describes src[8:] the way the spec allows, with the allocation base in data and the slice in byte_offset. It exits 0 when ptr is correct and 1 when it is not.
import ctypes
import numpy as np
from cuda.core.utils import StridedMemoryView
i64p = ctypes.POINTER(ctypes.c_int64)
class DLTensor(ctypes.Structure):
_fields_ = [("data", ctypes.c_void_p), ("device_type", ctypes.c_int32), ("device_id", ctypes.c_int32),
("ndim", ctypes.c_int32), ("code", ctypes.c_uint8), ("bits", ctypes.c_uint8),
("lanes", ctypes.c_uint16), ("shape", i64p), ("strides", i64p), ("byte_offset", ctypes.c_uint64)]
class DLManagedTensorVersioned(ctypes.Structure):
_fields_ = [("major", ctypes.c_uint32), ("minor", ctypes.c_uint32), ("manager_ctx", ctypes.c_void_p),
("deleter", ctypes.c_void_p), ("flags", ctypes.c_uint64), ("dl_tensor", DLTensor)]
ctypes.pythonapi.PyCapsule_New.argtypes = (ctypes.c_void_p, ctypes.c_char_p, ctypes.c_void_p)
ctypes.pythonapi.PyCapsule_New.restype = ctypes.py_object
src = np.arange(16, dtype=np.int32)
offset = 8 * src.itemsize
shape, strides = (ctypes.c_int64 * 1)(8), (ctypes.c_int64 * 1)(1)
t = DLManagedTensorVersioned(major=1, minor=0)
# Describe src[8:] the way the DLPack spec allows: allocation base in `data`,
# the slice expressed as `byte_offset`. First element is at data + byte_offset.
t.dl_tensor = DLTensor(data=src.ctypes.data, device_type=1, device_id=0, ndim=1, code=0, bits=32,
lanes=1, shape=shape, strides=strides, byte_offset=offset)
class Producer:
def __dlpack_device__(self):
return (1, 0) # kDLCPU
def __dlpack__(self, stream=None, max_version=None):
return ctypes.pythonapi.PyCapsule_New(ctypes.addressof(t), b"dltensor_versioned", None)
view = StridedMemoryView.from_dlpack(Producer(), stream_ptr=-1)
print("expected ptr :", src.ctypes.data + offset)
print("view.ptr :", view.ptr)
print("expected data:", src[8:])
print("view data :", np.from_dlpack(view))
raise SystemExit(0 if view.ptr == src.ctypes.data + offset else 1)Output:
expected ptr : 387455296
view.ptr : 387455264
expected data: [ 8 9 10 11 12 13 14 15]
view data : [0 1 2 3 4 5 6 7]
The process exits 1. Reproduced against a build of main at commit 757731a.
Expected behavior
ptr should be data + byte_offset, so the view begins at the tensor's first element, and the reproducer prints the producer's data and exits 0.
Activity
aryanputta commentedon Aug 10, 2026
I would like to prepare a focused fix for this. The import path in
cuda_core/cuda/core/_memoryview.pyxcurrently setsbuf.ptrfromdl_tensor.databut omitsdl_tensor.byte_offset; the capsule helper path already includes the offset. I have a minimal regression test using a versioned CPU DLPack capsule with a nonzero offset. Please confirm this is the intended fix before I open a PR.rparolin commentedon Aug 14, 2026
Hi @aryanputta. Thanks for offering to tackle this issue. I've assigned you as the owner of this ticket.
Thanks!
aryanputta commentedon Aug 15, 2026
Thanks @rparolin. Before I write anything, two PRs already fix this and I do not want to add a third:
I opened #2595 on Aug 10 and closed it myself the same day as a duplicate of #2594. So my recommendation as the assigned owner is to take one of the existing PRs rather than have me re-submit.
The production fix is the same one line in all three, in
view_as_dlpackincuda_core/cuda/core/_memoryview.pyx:I audited whether one line is actually sufficient.
byte_offsetappears at exactly three sites in that file:_smv_setup_dl_tensor_common: the export path setsdata = ptrandbyte_offset = 0. That is a valid DLPack encoding and needs no change._smv_from_dlpack_capsule: already adds the offset. This is the behavior the other path should match.view_as_dlpack: the defect.So the fix is complete, and the round-trip loss described in the report is a consequence of the import side, not a second bug on the export side. No other consumer needs touching, because everything downstream reads
ptr.Between the two open PRs I would take #2594, on repo convention rather than correctness, since both fixes are identical:
#2592backlink; fix(core): honour DLTensor.byte_offset in StridedMemoryView.from_dlpack #2576's has no issue link.dl_tensor.shape[0]in place, but shape and strides live in one producer-owned block, so writing to it reaches past the field under test. cuda.core: account for DLTensor.byte_offset in from_dlpack #2594 instead allocates 9 elements and views 8, so settingbyte_offsetalone re-describes a valid slice and nothing shared is written.#2594 needs a
mainmerge before it can go in. It is 30 commits behind and GitHub reports it as conflicting. @fedonman, are you able to mergemaininto it? If you would rather not carry it, say so and I will take the change forward under your authorship with the same tests.One note on both test files: the new tests are marked
@pytest.mark.agent_authored(model="claude-opus-5"), so perAGENTS.mdthey have not had a human review pass yet.aryanputta commentedon Aug 15, 2026
Correction to my comment above, on one point about #2576.
I wrote that #2576's test "mutates
dl_tensor.shape[0]in place, but shape and strides live in one producer-owned block, so writing to it reaches past the field under test." That is overstated and unfair to @LeSingh1. Shape and strides do share one allocation,_smv_setup_dl_tensormallocs2 * ndimint64s and pointsstridesatshape + ndim, butshape[0]is a legitimate slot inside it, not a write past anything. I checked the bounds both ways:byte_offset = 4withshape[0] = 7spans bytes [4, 32). In bounds.byte_offset = 4with shape left at 8 spans [4, 36). In bounds.Both tests are correct. The real difference is only that #2594 varies one field and #2576 varies two, which is a style preference, not a defect.
My recommendation of #2594 stands, but on the two remaining grounds rather than three: the explanatory comment above the fix in #2576 is the kind @rwgk has asked to be removed from private helpers (#2196, #2239), and #2594's release note carries the
#2592backlink while #2576's has none. Both are minor and either PR would close this.cuda.core: account for DLTensor.byte_offset in from_dlpack (#2594)