Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
125 commits
Select commit Hold shift + click to select a range
c1832e4
Now let's see what is going on.
philip-paul-mueller May 7, 2026
9f10099
Merge remote-tracking branch 'origin/main' into phimuell_test_new_dac…
philip-paul-mueller May 7, 2026
2fe5ad3
Updated GT4Py, aka DaCe.
philip-paul-mueller May 11, 2026
3b8a1e3
Merge remote-tracking branch 'origin/main' into phimuell_test_new_dac…
philip-paul-mueller May 26, 2026
79d56ed
Updated GT4Py, such that the new code gen is now actually used.
philip-paul-mueller May 27, 2026
66c5afd
Merge remote-tracking branch 'origin/main' into phimuell_test_new_dac…
philip-paul-mueller May 27, 2026
dfa6755
update uv lock
edopao May 27, 2026
a21a4f8
Update.
philip-paul-mueller May 28, 2026
6cf1ea0
Merge branch 'main' into phimuell_test_new_dace_gpu_codegen
edopao May 28, 2026
17541a0
update uv lock
edopao May 28, 2026
8647b4b
GT4Py embedded: test premap fix
havogt May 28, 2026
54567df
add concat_where marker
havogt May 28, 2026
617bdb4
fix is_neighbor_table
havogt May 28, 2026
7a03f68
remove the marker
havogt May 28, 2026
56c6b04
test as_offset and concat_where
havogt May 29, 2026
f90d413
point to gt4py main
havogt Jun 2, 2026
4c3b055
Updated.
philip-paul-mueller Jun 2, 2026
188e33d
Merge remote-tracking branch 'origin/main' into phimuell_test_new_dac…
philip-paul-mueller Jun 2, 2026
37c1917
Merge remote-tracking branch 'origin/try-gt4py-premap-fix' into phimu…
philip-paul-mueller Jun 2, 2026
edd36fd
Run pre-commit.
philip-paul-mueller Jun 3, 2026
05e51aa
Updated packages.
philip-paul-mueller Jun 3, 2026
61680cf
Let's disable GTFN to see what DaCe does. And I am wondering why the …
philip-paul-mueller Jun 3, 2026
e4e400c
Applied new formating to the lock file.
philip-paul-mueller Jun 3, 2026
14971c4
Revert "test as_offset and concat_where"
philip-paul-mueller Jun 3, 2026
a99aa10
Updated packages.
philip-paul-mueller Jun 3, 2026
1e4d8ae
Applied formating.
philip-paul-mueller Jun 3, 2026
ff63592
Updated GT4Py and DaCe dependencies.
philip-paul-mueller Jun 4, 2026
20d1e37
Applied formating.
philip-paul-mueller Jun 4, 2026
521038f
Updated GT4Py and DaCe.
philip-paul-mueller Jun 4, 2026
81c1bf4
Updated.
philip-paul-mueller Jun 4, 2026
bfc4931
Let's try this version.
philip-paul-mueller Jun 8, 2026
4d76e98
New version to try.
philip-paul-mueller Jun 8, 2026
5bf29f7
Updated GT4Py.
philip-paul-mueller Jun 8, 2026
670e45f
Disable CPU execution as it is not needed for this kind of work.
philip-paul-mueller Jun 8, 2026
0ee19cc
I am a bit concerned that I have to increase the time limit.
philip-paul-mueller Jun 8, 2026
fa2a068
Merge remote-tracking branch 'icon4py/main' into phimuell_test_new_da…
philip-paul-mueller Jun 9, 2026
c0ad736
Forced to use the new DaCe code generator, althogh I am super sure th…
philip-paul-mueller Jun 9, 2026
d03d451
Updated GT4Py.
philip-paul-mueller Jun 9, 2026
ddb44aa
Updated GT4Py.
philip-paul-mueller Jun 9, 2026
0d5fa81
Updated GT4Py and DaCe.
philip-paul-mueller Jun 10, 2026
225e5d1
Updated GT4Py and DaCe.
philip-paul-mueller Jun 10, 2026
aafd9f5
Updated GT4Py and DaCe.
philip-paul-mueller Jun 10, 2026
25057d7
Updated GT4Py and DaCe.
philip-paul-mueller Jun 10, 2026
631b442
Updated GT4Py and DaCe.
philip-paul-mueller Jun 11, 2026
c41df38
Merge remote-tracking branch 'icon4py/main' into phimuell_test_new_da…
philip-paul-mueller Jun 11, 2026
d86639b
Updated DaCe & GT4Py.
philip-paul-mueller Jun 15, 2026
1c14235
first integartion draft
edopao Jul 24, 2026
88fc81a
icrease size of workspace memory
edopao Jul 27, 2026
5946289
edit
edopao Jul 28, 2026
7ca09b4
Merge remote-tracking branch 'origin/main' into dace_ext_memory
edopao Jul 28, 2026
b213755
edit
edopao Jul 28, 2026
a636338
update uv lock
edopao Aug 3, 2026
f99e84b
update uv lock
edopao Aug 3, 2026
919dd0c
edit
edopao Aug 3, 2026
d26f0dd
edit
edopao Aug 3, 2026
fe819c0
edit
edopao Aug 3, 2026
b063f59
edit
edopao Aug 3, 2026
b2fcf95
edit
edopao Aug 3, 2026
a4cf8a1
edit
edopao Aug 3, 2026
c53f19c
edit
edopao Aug 3, 2026
66f190d
edit
edopao Aug 3, 2026
71686ee
edit
edopao Aug 3, 2026
fe68f2a
edit
edopao Aug 4, 2026
d374eec
update uv lock
edopao Aug 4, 2026
ce3dd6c
edit
edopao Aug 4, 2026
21a063e
update uv lock
edopao Aug 4, 2026
f66b3b0
Merge branch 'main' into dace_ext_memory
edopao Aug 4, 2026
d8c6257
update uv lock
edopao Aug 5, 2026
8bdcabf
Merge branch 'main' into dace_ext_memory
edopao Aug 5, 2026
832723a
readd async_sdfg_call
edopao Aug 5, 2026
b73462f
update uv lock
edopao Aug 5, 2026
c2ce6cb
update uv lock
edopao Aug 5, 2026
002f84c
update uv lock
edopao Aug 5, 2026
abcaffd
lower test parallelism
edopao Aug 5, 2026
147c84e
Merge remote-tracking branch 'origin/main' into dace_ext_memory
edopao Aug 5, 2026
916d993
edit
edopao Aug 5, 2026
9b84632
edit ci config
edopao Aug 5, 2026
f026586
Merge remote-tracking branch 'origin/main' into dace_ext_memory
edopao Aug 5, 2026
6c004f1
Merge branch 'main' into edopao/test_new_dace_gpu_codegen
edopao Aug 6, 2026
486cd2f
edit
edopao Aug 6, 2026
fcbce05
update uv lock
edopao Aug 6, 2026
937c22d
update uv lock
edopao Aug 6, 2026
5908b46
Merge branch 'dace_ext_memory' into edopao/test_new_dace_gpu_codegen
edopao Aug 6, 2026
c07d6c3
update config
edopao Aug 6, 2026
765e3fa
update config
edopao Aug 6, 2026
4ff7844
update uv lock
edopao Aug 6, 2026
5a27e39
edit
edopao Aug 6, 2026
d4e12ef
update uv lock
edopao Aug 6, 2026
046a311
Merge remote-tracking branch 'origin/main' into edopao/test_new_dace_…
edopao Aug 10, 2026
52e295a
upgrade compiler from gcc-12 to gcc-13
edopao Aug 10, 2026
e45e2de
Merge branch 'main' into edopao/test_new_dace_gpu_codegen
edopao Aug 10, 2026
a72bde0
switch to gt4py main
edopao Aug 10, 2026
7d7fc4a
undo changes related to new dace codegen
edopao Aug 10, 2026
365953b
introduce WorkspaceConfig
edopao Aug 11, 2026
73dacfb
Merge remote-tracking branch 'origin/main' into dace_ext_workspace
edopao Aug 11, 2026
733e606
edit
edopao Aug 11, 2026
c6a99a2
edit
edopao Aug 11, 2026
8e866e4
edit
edopao Aug 11, 2026
fe5334e
edit
edopao Aug 11, 2026
3af2e9d
edit
edopao Aug 11, 2026
83c598e
edit
edopao Aug 11, 2026
4f99e6a
edit
edopao Aug 11, 2026
34fa3b8
edit
edopao Aug 11, 2026
37c98d9
edit
edopao Aug 11, 2026
6adc24a
edit
edopao Aug 12, 2026
207d024
edit
edopao Aug 12, 2026
0de6b32
Merge remote-tracking branch 'origin/main' into dace_ext_workspace
edopao Aug 12, 2026
54e832e
edit
edopao Aug 12, 2026
b75972f
edit
edopao Aug 12, 2026
eda411f
Merge branch 'main' into dace_ext_workspace
edopao Aug 18, 2026
35ae427
edit
edopao Aug 19, 2026
67adeb1
add tests
edopao Aug 19, 2026
3fa6ab4
Merge branch 'main' into dace_ext_workspace
edopao Aug 19, 2026
dcec1e7
Merge branch 'main' into dace_ext_workspace
edopao Aug 20, 2026
ca9d260
Merge branch 'main' into dace_ext_workspace
edopao Aug 20, 2026
7991377
Merge branch 'main' into dace_ext_workspace
edopao Aug 26, 2026
564c10d
address review comments
edopao Aug 26, 2026
60e78f9
remove misleading comment
edopao Aug 26, 2026
b3f7d51
Merge branch 'main' into dace_ext_workspace
edopao Aug 31, 2026
530f311
remove alignment argument
edopao Sep 1, 2026
041e58b
update stale ref
edopao Sep 2, 2026
d50afa0
Merge branch 'main' into dace_ext_workspace
edopao Sep 2, 2026
68e6d05
Merge branch 'main' into dace_ext_workspace
edopao Sep 16, 2026
9929f11
Merge branch 'main' into dace_ext_workspace
edopao Sep 18, 2026
404016a
address review comments
edopao Sep 18, 2026
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
122 changes: 122 additions & 0 deletions model/common/src/icon4py/model/common/backend_configuration.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
# ICON4Py - ICON inspired code in Python and GT4Py
#
# Copyright (c) 2022-2024, ETH Zurich and MeteoSwiss
# All rights reserved.
#
# Please, refer to the LICENSE file in the root directory.
# SPDX-License-Identifier: BSD-3-Clause
"""External workspace allocation for the DaCe backend.

The DaCe backend in GT4Py can be configured with an
:class:`~gt4py.next.program_processors.runners.dace.workflow.common.ExternalWorkspace`
to provide workspace memory for transient SDFG arrays
(``transient_memory_mode = EXTERNAL``). When a :class:`BackendConfig` is
provided, :func:`get_dace_options <icon4py.model.common.model_options.get_dace_options>`
calls the :class:`IconWorkspaceAllocator` defined here — a process-wide
singleton that caches a single workspace slab per device and reuses it across
every compiled program.

The size of the workspace is configurable per experiment via :class:`BackendConfig`
(see :func:`backend_config_from_env` for an environment-variable based default).
"""

from __future__ import annotations

import dataclasses
import os
import typing
from collections.abc import Iterable
from typing import ClassVar, Final

import gt4py.next as gtx
from gt4py.next.program_processors.runners.dace.workflow import common as gtx_wfdcommon

from icon4py.model.common.config import options as common_conf_opt
from icon4py.model.common.utils import data_allocation


@dataclasses.dataclass(frozen=True, kw_only=True)
class BackendConfig:
"""External DaCe workspace sizing, configurable per experiment."""
Comment thread
edopao marked this conversation as resolved.

workspace_size: typing.Annotated[
int,
common_conf_opt.ConfigOption(
description=(
"Size of the workspace memory (in Bytes) for externally allocated "
"temporary fields. This is a performance feature of the DaCe backend "
"to avoid runtime allocation of temporary fields, for each program "
"call. Note that the memory buffer is allocated once and shared "
"across all compiled programs."
),
icon_equivalent=None,
),
] = 256 * 1024 * 1024 # 256 MiB

def __post_init__(self) -> None:
if self.workspace_size <= 0:
raise ValueError(f"'workspace_size' must be positive, got {self.workspace_size}.")


def backend_config_from_env() -> BackendConfig | None:
"""Build a :class:`BackendConfig` from environment variables.

Reads ``ICON4PY_BACKEND_WORKSPACE_SIZE`` and returns ``None`` when it is
not set.
"""
size = os.environ.get("ICON4PY_BACKEND_WORKSPACE_SIZE")
if size is None:
return None
return BackendConfig(workspace_size=int(size))


def _get_slab(nbytes: int, device: gtx.DeviceType) -> data_allocation.NDArray:
"""Allocate a `nbytes`-byte buffer allocated on ``device``."""
xp = data_allocation.array_ns(use_cupy=(device != gtx.DeviceType.CPU))
return xp.empty(nbytes, dtype=xp.uint8)


class IconWorkspaceAllocator:
"""Singleton workspace allocator for the DaCe backend.

Exactly one instance exists per process (enforced by `__new__`); all DaCe
backends share it via the module-level `ICON_WORKSPACE_ALLOCATOR`. It keeps
a single private workspace slab per device in `_workspace_slabs`, reused
across every compiled program. On a cache hit the slab's size is validated
against the value passed to `allocate`.
"""

_instance: ClassVar[IconWorkspaceAllocator | None] = None
_workspace_slabs: ClassVar[dict[gtx.DeviceType, data_allocation.NDArray]] = {}

def __new__(cls) -> IconWorkspaceAllocator:
if cls._instance is None:
cls._instance = super().__new__(cls)
return cls._instance

def allocate(
self,
devices: gtx.DeviceType | Iterable[gtx.DeviceType],
*,
size: int,
) -> gtx_wfdcommon.ExternalWorkspace:
if isinstance(devices, gtx.DeviceType):
devices = [devices]
wsp: gtx_wfdcommon.ExternalWorkspace = {}
for dev in devices:
if (cached := self._workspace_slabs.get(dev)) is not None:
if cached.nbytes != size:
raise ValueError(
f"Workspace size mismatch for {dev!s}: cached slab has "
f"{cached.nbytes} bytes but 'allocate' was called with "
f"size={size}."
)
wsp[dev] = cached
else:
slab = _get_slab(size, dev)
self._workspace_slabs[dev] = slab
wsp[dev] = slab
return wsp


ICON_WORKSPACE_ALLOCATOR: Final[IconWorkspaceAllocator] = IconWorkspaceAllocator()
27 changes: 26 additions & 1 deletion model/common/src/icon4py/model/common/model_backends.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,17 @@
#
# Please, refer to the LICENSE file in the root directory.
# SPDX-License-Identifier: BSD-3-Clause
from __future__ import annotations

from typing import Any, Final, TypeAlias, TypeGuard

import gt4py.next as gtx
import gt4py.next.custom_layout_allocators as gtx_allocators
import gt4py.next.typing as gtx_typing
from gt4py.next import backend as gtx_backend, custom_layout_allocators as gtx_allocators
from gt4py.next import backend as gtx_backend
from gt4py.next.program_processors.runners import dace as gtx_dace, gtfn
from gt4py.next.program_processors.runners.dace import transformations as gtx_transformations
from gt4py.next.program_processors.runners.dace.workflow import common as gtx_wfdcommon


# DeviceType should always be imported from here, as we might replace it by an ICON4Py internal implementation
Expand Down Expand Up @@ -81,6 +86,7 @@ def make_custom_dace_backend(
use_metrics: bool = True,
use_zero_origin: bool = False,
use_max_domain_range_on_unstructured_shift: bool | None = None,
external_workspace: gtx_wfdcommon.ExternalWorkspace | None = None,
**_,
) -> gtx_typing.Backend:
"""Customize the dace backend with the given configuration parameters.
Expand All @@ -97,15 +103,34 @@ def make_custom_dace_backend(
use_max_domain_range_on_unstructured_shift: When True, compute `as_fieldop`
expressions everywhere. Otherwise, when all connectivities are given
at compile time, infer the minimal domain of all `as_fieldop` statically.
external_workspace: The external workspace memory to use as storage for
the transient arrays. If `None`, the transient arrays will be allocated
inside the SDFG with scope lifetime.

Returns:
A dace backend with custom configuration for the target device.
"""
if external_workspace is not None:
if optimization_args is None:
optimization_args = {
"transient_memory_mode": gtx_transformations.TransientMemoryMode.EXTERNAL,
}
elif transient_memory_mode := optimization_args.get("transient_memory_mode"):
if transient_memory_mode != gtx_transformations.TransientMemoryMode.EXTERNAL:
raise ValueError(
f"Cannot use external workspace with transient_memory_mode={transient_memory_mode}."
)
else:
optimization_args["transient_memory_mode"] = (
gtx_transformations.TransientMemoryMode.EXTERNAL
)

on_gpu = device == GPU
return gtx_dace.make_dace_backend(
gpu=on_gpu,
auto_optimize=auto_optimize,
async_sdfg_call=async_sdfg_call,
external_workspace=external_workspace,
optimization_args=optimization_args,
unstructured_horizontal_has_unit_stride=True,
use_metrics=use_metrics,
Expand Down
44 changes: 35 additions & 9 deletions model/common/src/icon4py/model/common/model_options.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
from gt4py.next import backend as gtx_backend
from gt4py.next.program_processors.runners.dace import transformations as gtx_transformations

from icon4py.model.common import model_backends
from icon4py.model.common import backend_configuration as backend_cfg, model_backends


log = logging.getLogger(__name__)
Expand All @@ -35,11 +35,25 @@ def _dace_remove_access_node_copies(sdfg: dace.SDFG) -> None:


def get_dace_options(
program_name: str, **backend_descriptor: Any
program_name: str,
backend_config: backend_cfg.BackendConfig | None,
**backend_descriptor: Any,
) -> model_backends.BackendDescriptor:
is_rocm_device = backend_descriptor.get("device") == model_backends.DeviceType.ROCM
device = backend_descriptor.get("device") or model_backends.CPU
optimization_args = backend_descriptor.get("optimization_args", {})
optimization_hooks = optimization_args.get("optimization_hooks", {})

if backend_config is not None:
# The workspace memory allows to avoid the overhead of runtime allocations,
# which are expensive in the AMD runtime.
backend_descriptor["external_workspace"] = backend_cfg.ICON_WORKSPACE_ALLOCATOR.allocate(
device,
Comment thread
edopao marked this conversation as resolved.
size=backend_config.workspace_size,
)
optimization_args["transient_memory_mode"] = (
gtx_transformations.TransientMemoryMode.EXTERNAL
)

if program_name in [
"vertically_implicit_solver_at_corrector_step",
"vertically_implicit_solver_at_predictor_step",
Expand All @@ -60,7 +74,7 @@ def get_dace_options(
backend_descriptor["use_zero_origin"] = True
if program_name == "graupel_run":
optimization_args["fuse_tasklets"] = True
if not is_rocm_device:
if device != model_backends.DeviceType.ROCM:
optimization_args["gpu_maxnreg"] = 80
optimization_args["gpu_block_size_2d"] = (64, 6)
optimization_args["gpu_memory_pool"] = False
Expand All @@ -78,12 +92,17 @@ def get_gtfn_options(
return backend_descriptor


def get_options(program_name: str, **backend_descriptor: Any) -> model_backends.BackendDescriptor:
def get_options(
program_name: str,
*,
backend_config: backend_cfg.BackendConfig | None,
**backend_descriptor: Any,
) -> model_backends.BackendDescriptor:
if "backend_factory" not in backend_descriptor:
# here we could set a backend_factory per program
backend_descriptor["backend_factory"] = model_backends.make_custom_dace_backend
if backend_descriptor["backend_factory"] == model_backends.make_custom_dace_backend:
backend_descriptor = get_dace_options(program_name, **backend_descriptor)
backend_descriptor = get_dace_options(program_name, backend_config, **backend_descriptor)
if backend_descriptor["backend_factory"] == model_backends.make_custom_gtfn_backend:
backend_descriptor = get_gtfn_options(program_name, **backend_descriptor)

Expand All @@ -96,7 +115,9 @@ def customize_backend(
| model_backends.DeviceType
| model_backends.BackendDescriptor
| None,
backend_config: backend_cfg.BackendConfig | None = None,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Depending on how important it is to remember to set the BackendConfig, would it make sense to make this non-optional? Or alternatively make the default a BackendConfig() so you don't have to deal with the None case later?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

For now, BackendConfig is only used to setup an external workspace, which is a very special case (a workaround for the AMD platform, indeed). I would propose to keep it optional for now. If we extend BackendConfig, it is a good idea to make it non-optional.

) -> gtx_typing.Backend | None:
backend_config = backend_config or backend_cfg.backend_config_from_env()
program_name = program.__name__ if program is not None else ""
if backend is None or isinstance(backend, gtx_backend.Backend):
backend_name = backend.name if backend is not None else "embedded"
Expand All @@ -106,7 +127,9 @@ def customize_backend(
backend_descriptor = (
{"device": backend} if isinstance(backend, model_backends.DeviceType) else backend
)
backend_descriptor = get_options(program_name, **backend_descriptor)
backend_descriptor = get_options(
program_name, backend_config=backend_config, **backend_descriptor
)
backend_descriptor["device"] = backend_descriptor.get(
"device", model_backends.CPU
) # set default device
Expand All @@ -132,10 +155,11 @@ def setup_program(
horizontal_sizes: dict[str, gtx.int32] | None = None,
vertical_sizes: dict[str, gtx.int32] | None = None,
offset_provider: gtx_typing.OffsetProvider | None = None,
backend_config: backend_cfg.BackendConfig | None = None,
) -> Callable[..., None]:
"""
This function processes arguments to the GT4Py program. It
- binds arguments that don't change during model run ('constant_args', 'horizontal_sizes', "vertical_sizes');
- binds arguments that don't change during model run ('constant_args', 'horizontal_sizes', 'vertical_sizes');
- inlines scalar arguments into the GT4Py program at compile-time (via GT4Py's 'compile').
Args:
- backend: GT4Py backend,
Expand All @@ -145,14 +169,16 @@ def setup_program(
- horizontal_sizes: horizontal domain bounds,
- vertical_sizes: vertical domain bounds,
- offset_provider: GT4Py offset_provider,
- backend_config: external DaCe workspace sizing, or `None` to fall back
to the 'ICON4PY_BACKEND_WORKSPACE_SIZE' environment variable.
"""
constant_args = {} if constant_args is None else constant_args
variants = {} if variants is None else variants
horizontal_sizes = {} if horizontal_sizes is None else horizontal_sizes
vertical_sizes = {} if vertical_sizes is None else vertical_sizes
offset_provider = {} if offset_provider is None else offset_provider

backend = customize_backend(program, backend)
backend = customize_backend(program, backend, backend_config=backend_config)

bound_static_args = {k: v for k, v in constant_args.items() if gtx.is_scalar_type(v)}
static_args_program = program.with_backend(backend)
Expand Down
19 changes: 10 additions & 9 deletions model/common/src/icon4py/model/common/utils/data_allocation.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@

from __future__ import annotations

import logging as log
import math
from types import ModuleType
from typing import TYPE_CHECKING, Any, TypeAlias, TypeGuard, TypeVar
Expand Down Expand Up @@ -71,23 +70,25 @@ def as_numpy(array: NDArrayInterface) -> np.ndarray:
return cp.asnumpy(array)


def _array_ns(try_cupy: bool) -> ModuleType:
"""CuPy if requested and installed, NumPy otherwise."""
if try_cupy:
def array_ns(use_cupy: bool) -> ModuleType:
"""CuPy if requested, NumPy otherwise.

Raises RuntimeError if CuPy is requested but not available.
"""
if use_cupy:
try:
import cupy as cp # noqa: PLC0415 [import-outside-top-level]

return cp
except ImportError:
log.warning("No cupy installed, falling back to numpy for array_ns")
except ImportError as err:
raise RuntimeError(f"cupy is not available: {err!r}.") from err
return cp
import numpy as np # noqa: PLC0415 [import-outside-top-level]

return np


def import_array_ns(allocator: gtx_typing.Allocator | None) -> ModuleType:
"""Import cupy or numpy depending on a chosen GT4Py backend DevicType."""
return _array_ns(device_utils.is_cupy_device(allocator))
return array_ns(device_utils.is_cupy_device(allocator))


def scalar_like_array[ScalarT: gtx_typing.Scalar](
Expand Down
Loading
Loading