fix: OrtValue.from_dlpack rejects zero-size tensors as non-contiguous (#27451)
### Description
Fixes `IsContiguousTensor` in `dlpack_converter.cc` to correctly accept
zero-size tensors, which are vacuously contiguous regardless of their
strides.
**Root cause:** `IsContiguousTensor` iterates dimensions right-to-left.
NumPy 2.x sets **all strides to 0** for zero-size tensors. For shape
`(1, 8, 0, 128)`, the check hits dim 3 (size=128, stride=0) and fails
(`128 != 1 && stride[3]=0 != running_size=1`) before ever reaching the
zero dimension — so the existing in-loop early-return never fires.
**Fix:** Replace the in-loop zero-dimension check with a pre-scan that
short-circuits before any stride validation:
```cpp
// Zero-size tensors are vacuously contiguous regardless of strides.
// (NumPy 2.x sets all strides to 0 for zero-size tensors.)
for (int i = 0; i < tensor.ndim; i++) {
if (tensor.shape[i] == 0) {
return true;
}
}
```
**Tests:** Adds `test_ort_value_dlpack_zero_size` covering:
- `(1, 8, 0, 128)` — the KV-cache use case from the issue
- `(0,)`, `(0, 4)`, `(4, 0)` — other zero-size shapes
Both the `arr.__dlpack__()` path and the OrtValue round-trip
(`to_dlpack` → `from_dlpack`) are exercised.
### Motivation and Context
`OrtValue.from_dlpack` raised `RuntimeError: ORT only supports
contiguous tensor for now` for any zero-size NumPy array (e.g. shape
`(1, 8, 0, 128)`), making it unusable for KV-cache models whose initial
cache has a zero-length sequence dimension.
`OrtValue.ortvalue_from_numpy` handled the same arrays correctly — only
the DLPack path was broken.
<!-- START COPILOT ORIGINAL PROMPT -->
<details>
<summary>Original prompt</summary>
>
> ----
>
> *This section details on the original issue you should resolve*
>
> <issue_title>OrtValue.from_dlpack rejects zero-size tensors as
non-contiguous</issue_title>
> <issue_description>## Describe the issue
>
> `OrtValue.from_dlpack` rejects zero-size NumPy arrays (e.g. shape `(1,
8, 0, 128)`) with the error:
>
> ```
> RuntimeError: ORT only supports contiguous tensor for now.
> ```
>
> Zero-size tensors are vacuously contiguous, so the contiguity check in
`dlpack_converter.cc` is incorrect for this edge case. This matters in
practice for KV-cache models where the initial cache has a zero-length
sequence dimension.
>
> `OrtValue.ortvalue_from_numpy` handles zero-size arrays correctly—only
the DLPack path is broken.
>
> ## To reproduce
>
> ```python
> import numpy as np
> import onnxruntime as ort
> import onnxruntime.capi._pybind_state as _ort_c
>
> arr = np.zeros((1, 8, 0, 128), dtype=np.float32)
>
> # Works
> v1 = ort.OrtValue.ortvalue_from_numpy(arr)
> print(v1.shape()) # [1, 8, 0, 128]
>
> # Fails
> v2 = ort.OrtValue(_ort_c.OrtValue.from_dlpack(arr.__dlpack__(),
False), arr)
> # RuntimeError: ...dlpack_converter.cc:242...
IsContiguousTensor(dlpack->dl_tensor)
> # was false. ORT only supports contiguous tensor for now.
> ```
>
> ## Expected behavior
>
> `OrtValue.from_dlpack` should accept zero-size tensors, since they are
trivially contiguous.
>
> ## Root cause
>
> `IsContiguousTensor` in
[`onnxruntime/core/dlpack/dlpack_converter.cc`](https://github.com/microsoft/onnxruntime/blob/main/onnxruntime/core/dlpack/dlpack_converter.cc)
likely fails because the stride computation does not account for
dimensions of size 0.
>
> ## Urgency
>
> Low — easily worked around by falling back to `ortvalue_from_numpy`
for zero-size inputs.
>
> ## Environment
>
> - **onnxruntime**: 1.24.2
> - **Python**: 3.13
> - **OS**: Linux (WSL2, x86_64)
> </issue_description>
>
> <agent_instructions>Fix the issue and create good
tests</agent_instructions>
>
> ## Comments on the Issue (you are @copilot in this section)
>
> <comments>
> <comment_new><author>@justinchuby</author><body>
> Wow - this is an issue I asked copilot to create. It does a perfect
job.</body></comment_new>
> <comment_new><author>@justinchuby</author><body>
> Let me try to also assign copilot</body></comment_new>
> </comments>
>
</details>
<!-- START COPILOT CODING AGENT SUFFIX -->
- Fixes microsoft/onnxruntime#27450
<!-- START COPILOT CODING AGENT TIPS -->
---
🔒 GitHub Advanced Security automatically protects Copilot coding agent
pull requests. You can protect all pull requests by enabling Advanced
Security for your repositories. [Learn more about Advanced
Security.](https://gh.io/cca-advanced-security)
---------
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: justinchuby <11205048+justinchuby@users.noreply.github.com>