This is an automated email from the ASF dual-hosted git repository.

cyx-6 pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/tvm-ffi.git


The following commit(s) were added to refs/heads/main by this push:
     new 8e32a61  feat(python): add tensor methods to align with C++ APIs (#604)
8e32a61 is described below

commit 8e32a61eec2e6e0e6d7c8aa1324f22354d5c32ca
Author: Kaining Zhong <[email protected]>
AuthorDate: Thu Jun 4 23:11:09 2026 -0700

    feat(python): add tensor methods to align with C++ APIs (#604)
    
    Some of the commonly used tensor methods (`ndim`, `numel`, etc.) are
    provided in C++ APIs but not in python APIs. Adding these would make it
    handy especially for users who are familiar with pytorch APIs.
    
    ---------
    
    Signed-off-by: Kaining Zhong <[email protected]>
---
 docs/reference/python/index.rst  |  5 ++++-
 python/tvm_ffi/core.pyi          |  5 +++++
 python/tvm_ffi/cython/tensor.pxi | 40 ++++++++++++++++++++++++++++++++++++++++
 tests/python/test_tensor.py      | 33 +++++++++++++++++++++++++++++++++
 4 files changed, 82 insertions(+), 1 deletion(-)

diff --git a/docs/reference/python/index.rst b/docs/reference/python/index.rst
index a59a587..33881c0 100644
--- a/docs/reference/python/index.rst
+++ b/docs/reference/python/index.rst
@@ -48,7 +48,10 @@ Tensor
   Device
   DLDeviceType
   device
-
+  ndim
+  numel
+  size
+  is_contiguous
 
 Function
 ~~~~~~~~
diff --git a/python/tvm_ffi/core.pyi b/python/tvm_ffi/core.pyi
index 892a3b7..c651236 100644
--- a/python/tvm_ffi/core.pyi
+++ b/python/tvm_ffi/core.pyi
@@ -160,6 +160,11 @@ class Tensor(Object):
     @property
     def shape(self) -> tuple[int, ...]: ...
     @property
+    def ndim(self) -> int: ...
+    def numel(self) -> int: ...
+    def size(self, idx: int) -> int: ...
+    def is_contiguous(self) -> bool: ...
+    @property
     def strides(self) -> tuple[int, ...]: ...
     @property
     def dtype(self) -> Any: ...
diff --git a/python/tvm_ffi/cython/tensor.pxi b/python/tvm_ffi/cython/tensor.pxi
index dcf8e19..01a9fb0 100644
--- a/python/tvm_ffi/cython/tensor.pxi
+++ b/python/tvm_ffi/cython/tensor.pxi
@@ -276,6 +276,46 @@ cdef class Tensor(CObject):
         """Tensor shape as a tuple of integers."""
         return tuple(self.cdltensor.shape[i] for i in 
range(self.cdltensor.ndim))
 
+    @property
+    def ndim(self) -> int:
+        """Number of dimensions of the tensor."""
+        return self.cdltensor.ndim
+
+    def numel(self) -> int:
+        """Total number of elements in the tensor."""
+        cdef int64_t count = 1
+        cdef int i
+        for i in range(self.cdltensor.ndim):
+            count *= self.cdltensor.shape[i]
+        return count
+
+    def size(self, idx: int) -> int:
+        """Get the size of the ``idx``-th dimension. Negative ``idx`` counts 
from the last dimension."""
+        cdef int ndim = self.cdltensor.ndim
+        if idx < -ndim or idx >= ndim:
+            raise IndexError(
+                f"Dimension {idx} out of range for tensor with {ndim} 
dimensions"
+            )
+        if idx < 0:
+            idx += ndim
+        return self.cdltensor.shape[idx]
+
+    def is_contiguous(self) -> bool:
+        """True if the Tensor is C-contiguous (row-major), False otherwise."""
+        if self.cdltensor.strides == NULL:
+            return True
+        cdef int64_t expected_stride = 1
+        cdef int i
+        cdef int k
+        for i in range(self.cdltensor.ndim, 0, -1):
+            k = i - 1
+            if self.cdltensor.shape[k] == 1:
+                continue
+            if self.cdltensor.strides[k] != expected_stride:
+                return False
+            expected_stride *= self.cdltensor.shape[k]
+        return True
+
     @property
     def strides(self) -> tuple[int, ...]:
         """Tensor strides as a tuple of integers."""
diff --git a/tests/python/test_tensor.py b/tests/python/test_tensor.py
index cd5ac09..b0bb7c5 100644
--- a/tests/python/test_tensor.py
+++ b/tests/python/test_tensor.py
@@ -39,6 +39,11 @@ def test_tensor_attributes() -> None:
     x = tvm_ffi.from_dlpack(data)
     assert isinstance(x, tvm_ffi.Tensor)
     assert x.shape == (10, 8, 4, 2)
+    assert x.ndim == 4
+    assert x.numel() == 640
+    assert x.size(0) == 10
+    assert x.size(-1) == 2
+    assert x.is_contiguous()
     assert x.strides == (64, 8, 2, 1)
     assert x.dtype == tvm_ffi.dtype("int16")
     assert x.device.dlpack_device_type() == tvm_ffi.DLDeviceType.kDLCPU
@@ -47,6 +52,34 @@ def test_tensor_attributes() -> None:
     np.testing.assert_equal(x2, data)
 
 
+def test_empty_tensor_attributes() -> None:
+    data: npt.NDArray[Any] = np.zeros((4, 0, 4), dtype="int16")
+    if not hasattr(data, "__dlpack__"):
+        return
+    x = tvm_ffi.from_dlpack(data)
+    assert isinstance(x, tvm_ffi.Tensor)
+    assert x.shape == (4, 0, 4)
+    assert x.ndim == 3
+    assert x.strides == (0, 4, 1)
+    assert x.numel() == 0
+    assert x.is_contiguous()
+
+
+def test_non_contiguous_tensor_attributes() -> None:
+    data: npt.NDArray[Any] = np.zeros((4, 4, 4), dtype="int16")
+    slice = data[1:3, :, 1:3]
+    if not hasattr(slice, "__dlpack__"):
+        return
+    x = tvm_ffi.from_dlpack(slice)
+    assert isinstance(x, tvm_ffi.Tensor)
+    assert x.shape == (2, 4, 2)
+    assert x.numel() == 16
+    assert x.size(0) == 2
+    assert x.size(-1) == 2
+    assert not x.is_contiguous()
+    assert x.strides == (16, 4, 1)
+
+
 def test_shape_object() -> None:
     shape = tvm_ffi.Shape((10, 8, 4, 2))
     assert isinstance(shape, tvm_ffi.Shape)

Reply via email to