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

Skip to content

Conversation

@scikit-learn-bot
Copy link
Contributor

Update lock files.

Note

If the CI tasks fail, create a new branch based on this PR and add the required fixes to that branch.

@github-actions
Copy link

github-actions bot commented Dec 1, 2025

✔️ Linting Passed

All linting checks passed. Your pull request is in excellent shape! ☀️

Generated for commit: f2a079b. Link to the linter CI: here

@lesteve
Copy link
Member

lesteve commented Dec 1, 2025

macOS arm64 failure build log

Seems related to dlpack not being able to use mps device (full traceback at the bottom)

>               dlpack = ext_tensor.__dlpack__(**kwargs)
                         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E               ValueError: unsupported device requested

Maybe related to pytorch 2.8 -> 2.9.1?

Details

_ test_probabilistic_metrics_array_api[torch-mps-float32-True-False-brier_score_loss] _
[gw0] darwin -- Python 3.13.9 /Users/runner/miniconda3/envs/testvenv/bin/python

prob_metric = <function brier_score_loss at 0x11a74f920>, str_y_true = False
use_sample_weight = True, array_namespace = 'torch', device_ = 'mps'
dtype_name = 'float32'

    @pytest.mark.parametrize(
        "prob_metric", [brier_score_loss, log_loss, d2_brier_score, d2_log_loss_score]
    )
    @pytest.mark.parametrize("str_y_true", [False, True])
    @pytest.mark.parametrize("use_sample_weight", [False, True])
    @pytest.mark.parametrize(
        "array_namespace, device_, dtype_name", yield_namespace_device_dtype_combinations()
    )
    def test_probabilistic_metrics_array_api(
        prob_metric, str_y_true, use_sample_weight, array_namespace, device_, dtype_name
    ):
        """Test that :func:`brier_score_loss`, :func:`log_loss`, func:`d2_brier_score`
        and :func:`d2_log_loss_score` work correctly with the array API for binary
        and mutli-class inputs.
        """
        xp = _array_api_for_tests(array_namespace, device_)
        sample_weight = np.array([1, 2, 3, 1]) if use_sample_weight else None
    
        # binary case
        extra_kwargs = {}
        if str_y_true:
            y_true_np = np.array(["yes", "no", "yes", "no"])
            y_true_xp_or_np = np.asarray(y_true_np)
            if "brier" in prob_metric.__name__:
                # `brier_score_loss` and `d2_brier_score` require specifying the
                # `pos_label`
                extra_kwargs["pos_label"] = "yes"
        else:
            y_true_np = np.array([1, 0, 1, 0])
            y_true_xp_or_np = xp.asarray(y_true_np, device=device_)
    
        y_prob_np = np.array([0.5, 0.2, 0.7, 0.6], dtype=dtype_name)
        y_prob_xp = xp.asarray(y_prob_np, device=device_)
        metric_score_np = prob_metric(
            y_true_np, y_prob_np, sample_weight=sample_weight, **extra_kwargs
        )
        with config_context(array_api_dispatch=True):
>           metric_score_xp = prob_metric(
                y_true_xp_or_np, y_prob_xp, sample_weight=sample_weight, **extra_kwargs
            )

array_namespace = 'torch'
device_    = 'mps'
dtype_name = 'float32'
extra_kwargs = {}
metric_score_np = 0.13714286046368734
prob_metric = <function brier_score_loss at 0x11a74f920>
sample_weight = array([1, 2, 3, 1])
str_y_true = False
use_sample_weight = True
xp         = <module 'sklearn.externals.array_api_compat.torch' from '/Users/runner/work/scikit-learn/scikit-learn/sklearn/externals/array_api_compat/torch/__init__.py'>
y_prob_np  = array([0.5, 0.2, 0.7, 0.6], dtype=float32)
y_prob_xp  = tensor([0.5000, 0.2000, 0.7000, 0.6000], device='mps:0')
y_true_np  = array([1, 0, 1, 0])
y_true_xp_or_np = tensor([1, 0, 1, 0], device='mps:0')

../sklearn/metrics/tests/test_classification.py:3674: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
../sklearn/utils/_param_validation.py:218: in wrapper
    return func(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^
        args       = (tensor([1, 0, 1, 0], device='mps:0'), tensor([0.5000, 0.2000, 0.7000, 0.6000], device='mps:0'))
        func       = <function brier_score_loss at 0x11a74f880>
        func_sig   = <Signature (y_true, y_proba, *, sample_weight=None, pos_label=None, labels=None, scale_by_half='auto')>
        global_skip_validation = False
        kwargs     = {'sample_weight': array([1, 2, 3, 1])}
        parameter_constraints = {'labels': ['array-like', None], 'pos_label': [<class 'numbers.Real'>, <class 'str'>, 'boolean', None], 'sample_weight...ay-like', None], 'scale_by_half': ['boolean', <sklearn.utils._param_validation.StrOptions object at 0x11a72f5b0>], ...}
        params     = {'labels': None, 'pos_label': None, 'sample_weight': array([1, 2, 3, 1]), 'scale_by_half': 'auto', ...}
        prefer_skip_nested_validation = True
        to_ignore  = ['self', 'cls']
../sklearn/metrics/_classification.py:3770: in brier_score_loss
    sample_weight = move_to(sample_weight, xp=xp, device=device_)
                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
        _          = True
        device_    = device(type='mps', index=0)
        labels     = None
        pos_label  = None
        sample_weight = array([1, 2, 3, 1])
        scale_by_half = 'auto'
        xp         = <module 'sklearn.externals.array_api_compat.torch' from '/Users/runner/work/scikit-learn/scikit-learn/sklearn/externals/array_api_compat/torch/__init__.py'>
        y_proba    = tensor([0.5000, 0.2000, 0.7000, 0.6000], device='mps:0')
        y_true     = tensor([1, 0, 1, 0], device='mps:0')
../sklearn/utils/_array_api.py:519: in move_to
    array_converted = xp.from_dlpack(array, device=device)
                      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
        _          = True
        array      = array([1, 2, 3, 1])
        arrays     = (array([1, 2, 3, 1]),)
        converted_arrays = []
        device     = device(type='mps', index=0)
        device_array = 'cpu'
        is_none    = False
        is_sparse  = False
        none_mask  = [False]
        sparse_mask = [False]
        xp         = <module 'sklearn.externals.array_api_compat.torch' from '/Users/runner/work/scikit-learn/scikit-learn/sklearn/externals/array_api_compat/torch/__init__.py'>
        xp_array   = <module 'sklearn.externals.array_api_compat.numpy' from '/Users/runner/work/scikit-learn/scikit-learn/sklearn/externals/array_api_compat/numpy/__init__.py'>
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

ext_tensor = array([1, 2, 3, 1])

    def from_dlpack(
        ext_tensor: Any,
        *,
        device: Optional[_Device] = None,
        copy: Optional[bool] = None
    ) -> 'torch.Tensor':
        """from_dlpack(ext_tensor) -> Tensor
    
        Converts a tensor from an external library into a ``torch.Tensor``.
    
        The returned PyTorch tensor will share the memory with the input tensor
        (which may have come from another library). Note that in-place operations
        will therefore also affect the data of the input tensor. This may lead to
        unexpected issues (e.g., other libraries may have read-only flags or
        immutable data structures), so the user should only do this if they know
        for sure that this is fine.
    
        Args:
            ext_tensor (object with ``__dlpack__`` attribute, or a DLPack capsule):
                The tensor or DLPack capsule to convert.
    
                If ``ext_tensor`` is a tensor (or ndarray) object, it must support
                the ``__dlpack__`` protocol (i.e., have a ``ext_tensor.__dlpack__``
                method). Otherwise ``ext_tensor`` may be a DLPack capsule, which is
                an opaque ``PyCapsule`` instance, typically produced by a
                ``to_dlpack`` function or method.
    
            device (torch.device or str or None): An optional PyTorch device
                specifying where to place the new tensor. If None (default), the
                new tensor will be on the same device as ``ext_tensor``.
    
            copy (bool or None): An optional boolean indicating whether or not to copy
                ``self``. If None, PyTorch will copy only if necessary.
    
        Examples::
    
            >>> import torch.utils.dlpack
            >>> t = torch.arange(4)
    
            # Convert a tensor directly (supported in PyTorch >= 1.10)
            >>> t2 = torch.from_dlpack(t)
            >>> t2[:2] = -1  # show that memory is shared
            >>> t2
            tensor([-1, -1,  2,  3])
            >>> t
            tensor([-1, -1,  2,  3])
    
            # The old-style DLPack usage, with an intermediate capsule object
            >>> capsule = torch.utils.dlpack.to_dlpack(t)
            >>> capsule
            <capsule object "dltensor" at ...>
            >>> t3 = torch.from_dlpack(capsule)
            >>> t3
            tensor([-1, -1,  2,  3])
            >>> t3[0] = -9  # now we're sharing memory between 3 tensors
            >>> t3
            tensor([-9, -1,  2,  3])
            >>> t2
            tensor([-9, -1,  2,  3])
            >>> t
            tensor([-9, -1,  2,  3])
    
        """
        if hasattr(ext_tensor, '__dlpack__'):
            # Only populate kwargs if any of the optional arguments are, in fact, not None. Otherwise,
            # leave them out, since we might end up falling back to no-extra-kwargs __dlpack__ call.
            kwargs: dict[str, Any] = {}
            kwargs["max_version"] = (1, 0)
    
            if copy is not None:
                kwargs["copy"] = copy
    
            # Parse the device parameter.
            # At this moment, it can either be a torch.device or a str representing
            # a torch.device, e.g. "cpu", "cuda", etc.
            if device is not None:
                if isinstance(device, str):
                    device = torch.device(device)
                assert isinstance(device, torch.device), (
                    f"from_dlpack: unsupported device type: {type(device)}"
                )
                kwargs["dl_device"] = torch._C._torchDeviceToDLDevice(device)
    
            ext_device = ext_tensor.__dlpack_device__()
            # ext_device is either CUDA or ROCm, we need to pass the current
            # stream
            if ext_device[0] in (DLDeviceType.kDLCUDA, DLDeviceType.kDLROCM):
                stream = torch.cuda.current_stream(f'cuda:{ext_device[1]}')
                # cuda_stream is the pointer to the stream and it is a public
                # attribute, but it is not documented
                # The array API specify that the default legacy stream must be passed
                # with a value of 1 for CUDA
                # https://data-apis.org/array-api/latest/API_specification/array_object.html?dlpack-self-stream-none#dlpack-self-stream-none
                is_cuda = ext_device[0] == DLDeviceType.kDLCUDA
                # Since pytorch is not using PTDS by default, lets directly pass
                # the legacy stream
                stream_ptr = 1 if is_cuda and stream.cuda_stream == 0 else stream.cuda_stream
                kwargs["stream"] = stream_ptr
    
            try:
                # Try running __dlpack__ while specifying `max_version` argument.
>               dlpack = ext_tensor.__dlpack__(**kwargs)
                         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E               ValueError: unsupported device requested

copy       = None
device     = device(type='mps', index=0)
ext_device = (1, 0)
ext_tensor = array([1, 2, 3, 1])
kwargs     = {'dl_device': (8, 0), 'max_version': (1, 0)}

@ogrisel
Copy link
Member

ogrisel commented Dec 1, 2025

Let me investigate locally.

@ogrisel
Copy link
Member

ogrisel commented Dec 1, 2025

Based on my reading of the __dlpack__ spec, this looks like a bug in NumPy:

https://data-apis.org/array-api/latest/API_specification/generated/array_api.array.__dlpack__.html#:~:text=dl_device,-(Optional

It should raise BufferError rather than ValueError in that case.

@ogrisel
Copy link
Member

ogrisel commented Dec 1, 2025

I reported the problem upstream: numpy/numpy#30341.

For the time being, maybe we could catch ValueError with an inline comment in move_to.

@lesteve
Copy link
Member

lesteve commented Dec 2, 2025

I merge main to see whether #32827 had the intended effect of fixing the macOS arm64 build.

https://conda.anaconda.org/conda-forge/linux-64/libclang13-21.1.6-default_h746c552_0.conda#f5b64315835b284c7eb5332202b1e14b
https://conda.anaconda.org/conda-forge/noarch/meson-python-0.18.0-pyh70fd9c4_0.conda#576c04b9d9f8e45285fb4d9452c26133
https://conda.anaconda.org/conda-forge/linux-64/mkl-2025.3.0-h0e700b2_462.conda#a2e8e73f7132ea5ea70fda6f3cf05578
https://conda.anaconda.org/conda-forge/linux-64/pulseaudio-client-17.0-h9a6aba3_3.conda#b8ea447fdf62e3597cb8d2fae4eb1a90
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wow pulseaudio-client this is amazing, why do we need this to build the doc 🤔. Note we already had it in our lock-file before, this PR does not change this ...

@lesteve
Copy link
Member

lesteve commented Dec 2, 2025

macOS arm64 build is passing see build log

I enabled auto-merge.

@lesteve lesteve enabled auto-merge (squash) December 2, 2025 11:04
@lesteve lesteve merged commit 19dbd54 into scikit-learn:main Dec 2, 2025
37 checks passed
lesteve added a commit to lesteve/scikit-learn that referenced this pull request Dec 9, 2025
@lesteve lesteve mentioned this pull request Dec 9, 2025
14 tasks
lesteve added a commit to lesteve/scikit-learn that referenced this pull request Dec 9, 2025
lesteve added a commit that referenced this pull request Dec 9, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants