Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Using move_to on a numpy array with negative stride crashes the Python process #34307

Description

@betatim

The move_to function we have for moving arrays from one namespace to another can crash the Python process when you ask it to move an array with negative strides. I found this while working on PCA where we have an array with negative stride (Vt from _fit_truncated).

The code below is a reproducer:

import numpy as np
import torch
import sklearn
from sklearn.utils._array_api import get_namespace_and_device, move_to

def make_negative_strided():
    """A small, contiguous-source array viewed with a negative stride."""
    a = np.arange(12.0).reshape(3, 4)[:, ::-1]
    assert not a.flags["C_CONTIGUOUS"]
    assert a.strides[-1] < 0
    return a

a = make_negative_strided()
with sklearn.config_context(array_api_dispatch=True):
    xp, _, device = get_namespace_and_device(torch.asarray([1.0]))
    try:
        move_to(a, xp=xp, device=device)
    except Exception as exc:  # it is not caught
        print("Caught a Python exception (unexpected):", type(exc).__name__, exc)
    else:
        # this doesn't happen
        print("move_to returned without error (unexpected)")

    # instead the process exits with a SIGABRT

AI tells me that a possible fix is to call np.ascontiguousarray(array) if the input array is an array with negative stride. In addition we should only apply this workaround in the case where the move_to target is torch. The comment for the workaround should also include a link to pytorch/pytorch#188023 and this issue so we can know when the work around can be removed.

Something like this, but with a check for torch as target:

--- a/sklearn/utils/_array_api.py
+++ b/sklearn/utils/_array_api.py
@@ def move_to(*arrays, xp, device):
             xp_array, _, device_array = get_namespace_and_device(array)
             if xp == xp_array and device == device_array:
                 converted_arrays.append(array)
             else:
+                strides = getattr(array, "strides", None)
+                if strides is not None and any(
+                    stride < 0 for stride in strides
+                ):
+                    # Some libraries (e.g. torch) do not raise a catchable
+                    # Python exception but abort the whole process when a NumPy
+                    # array with negative or zero strides is imported via DLPack
+                    # (such arrays are produced for instance by ``a[::-1]``
+                    # reversals, as done by ARPACK). The ``except`` clause below
+                    # therefore cannot protect against it, so the source array is
+                    # made contiguous first to guarantee positive strides.
+                    array = numpy.ascontiguousarray(array)
                 try:
                     # The dlpack protocol is the future proof and library agnostic
                     # method to transfer arrays across namespace and device boundaries
                     # hence this method is attempted first and going through NumPy is
                     # only used as fallback in case of failure.
                     # Note: copy=None is the default since array-api 2023.12. Namespace
                     # libraries should only trigger a copy automatically if needed.
                     array_converted = xp.from_dlpack(array, device=device)

What do others think about this? I am also wondering if this is a bug in numpy's DLPack implementation (should not expose arrays with negative stride) or if this is a bug in torch (should check for negative stride before doing anything)?

I think that numpy.ascontiguousarray(array) is free for most arrays, those that already full fill it. For arrays that are not already contiguous I don't know what the cost is, but I think there is no alternative?

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions