Skip to content
Open
Show file tree
Hide file tree
Changes from 12 commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
7ab5211
Implement CupyArrayContext
matthiasdiener Feb 16, 2024
96b7a3d
print device name in test
matthiasdiener Feb 16, 2024
8dee38d
pylint
matthiasdiener Feb 17, 2024
2c025eb
Merge branch 'main' into cupyactx
matthiasdiener May 24, 2024
d6e3136
Merge branch 'main' into cupyactx
matthiasdiener Sep 6, 2024
bfa648a
update with current numpy actx
matthiasdiener Sep 6, 2024
27e5a19
restore some tests
matthiasdiener Sep 6, 2024
6d507e1
ruff
matthiasdiener Sep 6, 2024
8fb4e0b
make cupy import optional
matthiasdiener Sep 6, 2024
be70b67
CI fixes
matthiasdiener Sep 6, 2024
677419b
remove a few spurious changes
matthiasdiener Sep 6, 2024
6250211
change CI cupy integration
matthiasdiener Sep 6, 2024
d61f0cf
simplify CI install slightly
matthiasdiener Sep 6, 2024
5871ae7
Merge branch 'main' into cupyactx
matthiasdiener Nov 14, 2024
9c56443
Merge branch 'main' into cupyactx
matthiasdiener Dec 3, 2024
fd95813
lint
matthiasdiener Dec 3, 2024
5f4c4d9
update docs
matthiasdiener Dec 3, 2024
a8fe272
Merge branch 'main' into cupyactx
matthiasdiener Jan 31, 2025
2296c6d
improve array container support in {to,from}_numpy
matthiasdiener Jan 31, 2025
8fd5488
WS fix
matthiasdiener Jan 31, 2025
6f3cd94
fixes
matthiasdiener Feb 3, 2025
ab8266d
allow optional device selection
matthiasdiener Feb 7, 2025
79b0bc3
try running cupy via gitlab
matthiasdiener Feb 7, 2025
70aff99
debug more
matthiasdiener Feb 7, 2025
8b5c6cf
Revert "debug more"
matthiasdiener Feb 7, 2025
8561b2f
only test pocl-cpu in cupy test
matthiasdiener Feb 7, 2025
904e061
fix conda build
matthiasdiener Feb 7, 2025
340f9dc
add to coverage table
matthiasdiener Feb 8, 2025
3ee28cc
keep device in clone()
matthiasdiener Feb 11, 2025
ce33f23
remove loopy handling
matthiasdiener Feb 11, 2025
cd2a366
Revert "keep device in clone()"
matthiasdiener Feb 12, 2025
8e7e1f1
Revert "allow optional device selection"
matthiasdiener Feb 12, 2025
326e164
No need for separate method
matthiasdiener Feb 17, 2025
4d429f8
remove unneeded functions
matthiasdiener Feb 17, 2025
69e2133
add a comment on non-blocking cp.asnumpy
matthiasdiener Feb 17, 2025
f83ee9c
add README links
matthiasdiener Feb 17, 2025
fae85a4
fix typo
matthiasdiener Feb 17, 2025
d9ce8d5
clean up loopy cache
matthiasdiener Feb 17, 2025
1f5649c
Merge branch 'main' into cupyactx
matthiasdiener Apr 11, 2025
f16bc29
Merge branch 'main' into cupyactx
matthiasdiener May 5, 2025
ebd4828
Merge branch 'main' into cupyactx
matthiasdiener May 30, 2025
dc62cdc
Merge branch 'main' into cupyactx
matthiasdiener Jun 16, 2025
22649a0
bpr things
matthiasdiener Jun 16, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@ jobs:
run: |
USE_CONDA_BUILD=1
curl -L -O https://gitlab.tiker.net/inducer/ci-support/raw/master/prepare-and-run-pylint.sh

CONDA_ENVIRONMENT=.test-conda-env-py3.yml
echo "- cupy" >> "$CONDA_ENVIRONMENT"
Comment thread
alexfikl marked this conversation as resolved.
Outdated

. ./prepare-and-run-pylint.sh "$(basename $GITHUB_REPOSITORY)" examples/*.py test/test_*.py

mypy:
Expand All @@ -55,6 +59,7 @@ jobs:

build_py_project_in_conda_env
python -m pip install mypy pytest
conda install cupy
./run-mypy.sh

pytest3_pocl:
Expand Down
2 changes: 2 additions & 0 deletions arraycontext/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@
ScalarLike,
tag_axes,
)
from .impl.cupy import CupyArrayContext
from .impl.jax import EagerJAXArrayContext
from .impl.numpy import NumpyArrayContext
from .impl.pyopencl import PyOpenCLArrayContext
Expand All @@ -105,6 +106,7 @@
"ArrayOrContainerT",
"ArrayT",
"CommonSubexpressionTag",
"CupyArrayContext",
"EagerJAXArrayContext",
"ElementwiseMapKernelTag",
"NotAnArrayContainerError",
Expand Down
174 changes: 174 additions & 0 deletions arraycontext/impl/cupy/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
from __future__ import annotations


"""
.. currentmodule:: arraycontext

A mod :`cupy`-based array context.

.. autoclass:: CupyArrayContext
"""

__copyright__ = """
Copyright (C) 2024 University of Illinois Board of Trustees
"""

__license__ = """
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
"""

from typing import Any

import loopy as lp
from pytools.tag import ToTagSetConvertible

from arraycontext.container.traversal import rec_map_array_container, with_array_context
from arraycontext.context import (
Array,
ArrayContext,
ArrayOrContainerOrScalar,
ArrayOrContainerOrScalarT,
NumpyOrContainerOrScalar,
UntransformedCodeWarning,
)


class CupyNonObjectArrayMetaclass(type):
def __instancecheck__(cls, instance: Any) -> bool:
import cupy as cp # type: ignore[import-untyped]
return isinstance(instance, cp.ndarray) and instance.dtype != object


class CupyNonObjectArray(metaclass=CupyNonObjectArrayMetaclass):
pass


class CupyArrayContext(ArrayContext):
"""
A :class:`ArrayContext` that uses :class:`cupy.ndarray` to represent arrays.

.. automethod:: __init__
"""

_loopy_transform_cache: dict[lp.TranslationUnit, lp.ExecutorBase]
Comment thread
alexfikl marked this conversation as resolved.
Outdated

def __init__(self) -> None:

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Would it be useful to specify a device on array context creation.

https://docs.cupy.dev/en/stable/reference/generated/cupy.cuda.Device.html#cupy-cuda-device

Is device usage "sticky" per created array? If not, what happens if a device is currently default that doesn't have the data?

@matthiasdiener matthiasdiener Feb 7, 2025

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

I've added an optional argument to __init__ to specify the device in ab8266d. Is that what you had in mind?

Is device usage "sticky" per created array? If not, what happens if a device is currently default that doesn't have the data?

I don't think it is sticky, see https://docs.cupy.dev/en/stable/user_guide/basic.html#current-device for some context:

Note
If the array’s device and the current device mismatch, CuPy functions try to establish peer-to-peer memory access (P2P) between them so that the current device can directly read the array from another device. Note that P2P is available only when the topology permits it. If P2P is unavailable, such an attempt will fail with ValueError.

Edit: I think I misunderstood your question - I think it is sticky in the sense that array's don't get migrated automatically across devices.

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Wait, what? The text you quoted seems to imply that cupy will attempt automatic migration.

My read on this situation is that it's the "current device" setting in cupy is the only thing that matters, there's no point in exposing this as a constructor parameter since it won't "stick" with the created arrays.

@matthiasdiener matthiasdiener Feb 12, 2025

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Wait, what? The text you quoted seems to imply that cupy will attempt automatic migration.

Hmm, where do you see the implication of migration? My understanding is that it will access the memory on the other device, but not migrate the memory (i.e., it's a bit like a remote NUMA access):

On Porter:

>>> import cupy as cp
>>> a = cp.array([1,2,3])
>>> a.device
<CUDA Device 0>
>>> cp.cuda.runtime.setDevice(1)
>>> a.device
<CUDA Device 0>
>>> b = a + 4
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "cupy/_core/core.pyx", line 1275, in cupy._core.core._ndarray_base.__add__
  File "cupy/_core/core.pyx", line 1699, in cupy._core.core._ndarray_base.__array_ufunc__
  File "cupy/_core/_kernel.pyx", line 1286, in cupy._core._kernel.ufunc.__call__
  File "cupy/_core/_kernel.pyx", line 159, in cupy._core._kernel._preprocess_args
  File "cupy/_core/_kernel.pyx", line 130, in cupy._core._kernel._preprocess_arg
  File "cupy/_core/_kernel.pyx", line 120, in cupy._core._kernel._check_peer_access
ValueError: The device where the array resides (0) is different from the current device (1). Peer access is unavailable between these devices.

On Lassen:

>>> import cupy as cp
>>> a = cp.array([1,2,3])
>>> a.device
<CUDA Device 0>
>>> cp.cuda.runtime.setDevice(1)
>>> a.device
<CUDA Device 0>
>>> b = a + 4
<stdin>:1: PerformanceWarning: The device where the array resides (0) is different from the current device (1). Peer access has been activated automatically.
>>> b
array([5, 6, 7])
>>> b.device
<CUDA Device 1>
>>> a.device
<CUDA Device 0>

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

OIC, nvm, I misunderstood this, too. Still, I think wrapping "set current device" via the constructor isn't helpful, since it won't exercise positive control over array placement. That is, unless we intend to also set the active device before every computation. (Which, in actuality, we kind of can't, at least not without fully wrapping all cupy functionality.)

@matthiasdiener matthiasdiener Feb 12, 2025

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Ok, I'll revert the relevant commits.

Edit: reverted in cd2a366 , 8e7e1f1

super().__init__()
self._loopy_transform_cache = {}

array_types = (CupyNonObjectArray,)
Comment thread
alexfikl marked this conversation as resolved.

def _get_fake_numpy_namespace(self):
from .fake_numpy import CupyFakeNumpyNamespace
return CupyFakeNumpyNamespace(self)

# {{{ ArrayContext interface

def clone(self):
return type(self)()
Comment thread
matthiasdiener marked this conversation as resolved.

def from_numpy(self,
array: NumpyOrContainerOrScalar
) -> ArrayOrContainerOrScalar:
import cupy as cp
return cp.array(array)

def to_numpy(self,
array: ArrayOrContainerOrScalar
) -> NumpyOrContainerOrScalar:
import cupy as cp
return cp.asnumpy(array)

def call_loopy(
self,
t_unit: lp.TranslationUnit, **kwargs: Any
) -> dict[str, Array]:
t_unit = t_unit.copy(target=lp.ExecutableCTarget())
Comment thread
alexfikl marked this conversation as resolved.
Outdated
try:
executor = self._loopy_transform_cache[t_unit]
except KeyError:
executor = self.transform_loopy_program(t_unit).executor()
self._loopy_transform_cache[t_unit] = executor

_, result = executor(**kwargs)

return result

def freeze(self, array):
import cupy as cp

def _freeze(ary):
return cp.asnumpy(ary)
Comment thread
matthiasdiener marked this conversation as resolved.
Outdated

return with_array_context(rec_map_array_container(_freeze, array), actx=None)

def thaw(self, array):
import cupy as cp

def _thaw(ary):
return cp.array(ary)

return with_array_context(rec_map_array_container(_thaw, array), actx=self)

# }}}

def transform_loopy_program(self, t_unit):
Comment thread
matthiasdiener marked this conversation as resolved.
Outdated
from warnings import warn
warn("Using the base "
f"{type(self).__name__}.transform_loopy_program "
"to transform a translation unit. "
"This is a no-op and will result in unoptimized C code for"
"the requested optimization, all in a single statement."
"This will work, but is unlikely to be performant."
f"Instead, subclass {type(self).__name__} and implement "
"the specific transform logic required to transform the program "
"for your package or application. Check higher-level packages "
"(e.g. meshmode), which may already have subclasses you may want "
"to build on.",
UntransformedCodeWarning, stacklevel=2)

return t_unit

def tag(self,
tags: ToTagSetConvertible,
array: ArrayOrContainerOrScalarT) -> ArrayOrContainerOrScalarT:
# Cupy (like numpy) doesn't support tagging
return array

def tag_axis(self,
iaxis: int, tags: ToTagSetConvertible,
array: ArrayOrContainerOrScalarT) -> ArrayOrContainerOrScalarT:
# Cupy (like numpy) doesn't support tagging
return array

def einsum(self, spec, *args, arg_names=None, tagged=()):
import cupy as cp
return cp.einsum(spec, *args)

@property
def permits_inplace_modification(self):
return True

@property
def supports_nonscalar_broadcasting(self):
return True

@property
def permits_advanced_indexing(self):
return True
Loading