Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
69 commits
Select commit Hold shift + click to select a range
f918acf
update uv lock
edopao Sep 24, 2026
94551f6
remove workaround
edopao May 8, 2026
972e8dc
edit lowering
edopao Sep 25, 2026
7ece292
code refactoring
edopao Sep 25, 2026
0240faf
refactor[next-dace]: integrate and restate nested SDFG connectors
edopao Sep 30, 2026
bb4bebe
refactor[next-dace]: use outer data descriptors for reduce connectors
edopao Oct 1, 2026
fb0bfcc
unstage file
edopao Oct 5, 2026
0c9d769
fix merge conflict
edopao Oct 5, 2026
508e439
update uv lock
edopao Oct 5, 2026
d1da3ed
fixes
edopao Oct 9, 2026
a97dc43
switch to dace dev branch
edopao Oct 9, 2026
724549c
fix remove_access_node_copies
edopao Oct 9, 2026
c896927
update uv lock
edopao Oct 9, 2026
2f2cc58
fix[next-dace]: move variable-length Register transients to GPU_Global
edopao Oct 9, 2026
db3da3c
Revert "fix[next-dace]: move variable-length Register transients to G…
edopao Oct 9, 2026
4e21760
update uv lock
edopao Oct 9, 2026
3749657
update uv lock
edopao Oct 12, 2026
7bcfc04
replace Indices with Range
edopao Oct 12, 2026
1e0c1c4
update uv lock
edopao Oct 12, 2026
0bdd4a6
fix[next-dace]: restate NestedSDFG connectors before stride propagati…
edopao Oct 12, 2026
acd8a63
set explicitly allow_intermediates_with_symbolic_size=False
edopao Oct 12, 2026
90710d4
update uv lock
edopao Oct 12, 2026
1411a87
update uv lock
edopao Oct 13, 2026
9666bd5
fix generator to list
edopao Oct 13, 2026
6f8bd84
fix generator to list
edopao Oct 13, 2026
997c4d0
edit dace version
edopao Oct 13, 2026
60d5070
fix generator to list
edopao Oct 13, 2026
02259e7
fix generator to list
edopao Oct 13, 2026
d783a8b
Add SDFG int64 argument tp specify an external gpu stream
edopao Sep 22, 2026
89f7c52
change configurable stream to compile time arg
edopao Sep 22, 2026
8eb52c2
edit
edopao Sep 24, 2026
e49f241
edit
edopao Sep 24, 2026
6939d03
fix test
edopao Sep 24, 2026
38b4268
fix test
edopao Sep 24, 2026
37988ce
cast external stream
edopao Sep 28, 2026
ebd3915
switch to new dace gpu codegen
edopao Sep 28, 2026
8d2c4a3
edit
edopao Sep 28, 2026
3b1aab3
switch to dace fork
edopao Sep 28, 2026
c23608f
pass external gpu stream as SDFG runtime argument
edopao Sep 28, 2026
f0c4a38
do not add external gpu stream arg in orchestration
edopao Sep 28, 2026
cace025
update uv lock
edopao Sep 28, 2026
13ef855
fix fixture in codegen test
edopao Sep 29, 2026
5843918
use __dace_gpu_set_all_streams and fix stale comments
edopao Sep 29, 2026
b97d1ed
Support stream objects as external GPU stream handles
edopao Sep 29, 2026
2cd4211
fix merge conflicts
edopao Oct 13, 2026
ab24527
edit
edopao Oct 13, 2026
3ded8c2
update uv lock
edopao Oct 16, 2026
f520d5b
Merge branch 'main' into dace_noview_nested_sdfg
edopao Oct 23, 2026
e22aef1
update uv lock
edopao Oct 23, 2026
9bfea69
fix
edopao Oct 23, 2026
b080870
switch to pypi
edopao Oct 26, 2026
4ee97a3
edit lowering
edopao Oct 26, 2026
e283105
cartesian - convert legacy nested sdfgs before inlining - #2954
edopao Oct 27, 2026
cce7af4
address review comments
edopao Oct 28, 2026
ae9a3cb
apply review comments
edopao Oct 28, 2026
3ee4d50
Merge remote-tracking branch 'origin/main' into dace_noview_nested_sdfg
edopao Oct 28, 2026
8cbfd3b
edit assert
edopao Oct 28, 2026
e2fa72c
edit
edopao Oct 28, 2026
4cd6bf7
address review comments
edopao Oct 29, 2026
88d5310
fix
edopao Oct 29, 2026
1bc4952
another fix
edopao Oct 29, 2026
7812230
address review comments
edopao Oct 30, 2026
44bb451
do not rename inner symbols, find new name when adding a symbol
edopao Oct 30, 2026
b8a86f4
edit
edopao Oct 30, 2026
5bb48ef
call integrate_into_parent only on root SDFG
edopao Oct 30, 2026
d08177b
Revert "call integrate_into_parent only on root SDFG"
edopao Oct 30, 2026
c216d37
remove call to NestedSDFG.integrate_into_parent()
edopao Nov 2, 2026
363fe86
Merge remote-tracking branch 'origin/dace_noview_nested_sdfg' into da…
edopao Nov 2, 2026
8db103a
Merge branch 'main' into dace_external_stream
edopao Nov 2, 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
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -434,6 +434,7 @@ url = 'https://gridtools.github.io/pypi/'
# dace = {index = "gridtools"}
[tool.uv.sources]
atlas4py = { index = "test.pypi" }
dace = { git = "https://github.com/spcl/dace", branch = "new-gpu-codegen-dev" }

# -- versioningit --
[tool.versioningit]
Expand Down
23 changes: 18 additions & 5 deletions src/gt4py/next/program_processors/runners/dace/workflow/backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,18 @@

@dataclasses.dataclass(frozen=True)
class DaCeBackend(backend.Backend[Any]):
"""DaCe backend with support for injecting an external workspace at load time."""
"""DaCe backend with support for injecting an external workspace and GPU stream at load time."""

external_workspace: gtx_wfdcommon.ExternalWorkspace | None = None
external_gpu_stream: gtx_wfdcommon.GPUStreamHandle | None = None

def load_artifact(self, artifact: artifacts.CompilationArtifact) -> artifacts.ExecutableProgram:
program = super().load_artifact(artifact)
assert isinstance(program, gtx_wfddecoration.DaCeDecoratedProgram)
# Inject the backend-level workspace so it is used when arguments are constructed.
program.set_external_workspace(self.external_workspace or {})
# Inject the backend-level GPU stream, it is passed as argument to the SDFG.
program.set_external_gpu_stream(self.external_gpu_stream)
return program


Expand Down Expand Up @@ -70,14 +73,16 @@ def make_dace_toolchain(
allocator=cfg.make_allocator(),
transforms=backend.DEFAULT_TRANSFORMS,
external_workspace=cfg.external_workspace,
external_gpu_stream=cfg.external_gpu_stream,
)


def make_dace_backend(
gpu: bool,
auto_optimize: bool = True,
async_sdfg_call: bool = True,
sync_sdfg_call: bool = False,
optimization_args: dict[str, Any] | None = None,
external_gpu_stream: gtx_wfdcommon.GPUStreamHandle | None = None,
external_workspace: gtx_wfdcommon.ExternalWorkspace | None = None,
unstructured_horizontal_has_unit_stride: bool = config.UNSTRUCTURED_HORIZONTAL_HAS_UNIT_STRIDE,
use_metrics: bool = True,
Expand All @@ -94,10 +99,17 @@ def make_dace_backend(
Args:
gpu: Enable GPU transformations and code generation.
auto_optimize: Enable the SDFG auto-optimize pipeline.
async_sdfg_call: Make an asynchronous SDFG call on GPU to allow overlapping
of GPU kernel execution with the Python driver code.
sync_sdfg_call: Force a synchronous SDFG call on GPU, instead of allowing
the GPU kernel execution to overlap with the Python driver code.
optimization_args: A `dict` containing configuration parameters for
the SDFG auto-optimize pipeline, see `gt_auto_optimize()`.
external_gpu_stream: An external GPU stream to be used for the SDFG call,
which allows to synchronize the execution of GPU kernels with external
workload and to share the stream memory pool. It can be the pointer
value of the stream, as an integer, or a stream object such as
`cupy.cuda.Stream`, see `GPUStreamHandle`. The stream handle is passed
to the SDFG at runtime, on the first call, thus it does not affect the
generated code. If not provided, the default stream is used.
external_workspace: Workspace memory externally allocated, which is used
for SDFG's transient arrays when `transient_memory_mode` is `EXTERNAL`.
unstructured_horizontal_has_unit_stride: When the memory layout has unit stride
Expand Down Expand Up @@ -131,12 +143,13 @@ def make_dace_backend(
gpu=gpu,
auto_optimize=auto_optimize,
external_workspace=external_workspace,
external_gpu_stream=external_gpu_stream,
unstructured_horizontal_has_unit_stride=unstructured_horizontal_has_unit_stride,
),
translation=functools.partial(
gtx_wfdfactory.make_dace_translator,
auto_optimize_args=optimization_args,
async_sdfg_call=async_sdfg_call,
sync_sdfg_call=sync_sdfg_call,
use_metrics=use_metrics,
disable_field_origin_on_program_arguments=use_zero_origin,
use_max_domain_range_on_unstructured_shift=use_max_domain_range_on_unstructured_shift,
Expand Down
87 changes: 81 additions & 6 deletions src/gt4py/next/program_processors/runners/dace/workflow/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from typing import Any, Final, Generator, Optional, TypeAlias

import dace
from typing_extensions import Protocol, runtime_checkable

from gt4py._core import definitions as core_defs
from gt4py.eve import xtyping
Expand All @@ -34,6 +35,14 @@
"""DaCe datatype of `SDFG_ARG_METRIC_COMPUTE_TIME` argument."""


SDFG_ARG_EXTERNAL_GPU_STREAM: Final[str] = "gt_external_gpu_stream"
"""Name of SDFG argument to input an external GPU stream to GT4Py."""


SDFG_ARG_EXTERNAL_GPU_STREAM_DTYPE: Final[dace.dtypes.typeclass] = dace.int64
"""DaCe datatype of `SDFG_ARG_EXTERNAL_GPU_STREAM` argument, see `GPUStreamHandle`."""


ExternalWorkspace: TypeAlias = dict[
core_defs.DeviceType, xtyping.ArrayInterface | xtyping.CUDAArrayInterface
]
Expand All @@ -45,6 +54,59 @@
"""


@runtime_checkable
class GPUStream(Protocol):
"""A GPU stream object that exposes its handle, for example `cupy.cuda.Stream`."""

@property
def ptr(self) -> int:
"""The pointer value of the stream (`cudaStream_t` or `hipStream_t`)."""
...

def __hash__(self) -> int:
return self.ptr


GPUStreamHandle: TypeAlias = int | GPUStream
""" Handle of a GPU stream (`cudaStream_t` or `hipStream_t`).

Either the pointer value of the stream as an integer, or a stream object that
exposes it as the `ptr` attribute, for example `cupy.cuda.Stream`. It is passed
to the SDFG at runtime as an integer, see `SDFG_ARG_EXTERNAL_GPU_STREAM` and
`get_gpu_stream_ptr()`.
"""


DEFAULT_GPU_STREAM: Final[int] = 0
"""The pointer value of the default GPU stream, i.e. `nullptr`."""


def get_gpu_stream_ptr(stream: GPUStreamHandle | None) -> int:
"""Return the pointer value of a GPU stream, as an integer.

Args:
stream: The GPU stream handle. `None` selects the default stream.

Returns:
The pointer value of the stream, `DEFAULT_GPU_STREAM` for the default stream.

Raises:
TypeError: If `stream` is neither an integer nor a stream object that exposes
the `ptr` attribute.
"""
if stream is None:
return DEFAULT_GPU_STREAM
# NOTE: `bool` is a subclass of `int`, but it is not a valid stream handle.
if isinstance(stream, int) and not isinstance(stream, bool):
return stream
if isinstance(stream, GPUStream) and isinstance(stream.ptr, int):
return stream.ptr
raise TypeError(
f"Invalid GPU stream: should be of type 'int' or a stream object with an integer "
f"'ptr' attribute, for example 'cupy.cuda.Stream', got '{type(stream).__name__}'."
)


def set_dace_config(
device_type: core_defs.DeviceType,
cmake_build_type: Optional[gtx_config.CMakeBuildType] = None,
Expand Down Expand Up @@ -138,14 +200,27 @@ def set_dace_config(
# In some stencils, for example `apply_diffusion_to_w`, the cuda codegen messes
# up with the cuda streams, i.e. it allocates N streams but uses N+1. The first
# idea was to use just one stream. However, even in that case the generator
# generated wrong code. The current approach is to use the default stream, i.e.
# setting `max_concurrent_streams` to `-1`. However, the draw back is, that
# apparently then all synchronization is disabled, even the one at the very
# end of the SDFG call. To correct for that we are using either
# `make_sdfg_call_sync()` or `make_sdfg_call_async()`, see there or in
# [DaCe issue#2120](https://github.com/spcl/dace/issues/2120) for more.
# generated wrong code. The current approach is to use a single stream, i.e.
# setting `max_concurrent_streams` to `-1`. With the experimental CUDA codegen
# (see below), all GPU work is then scheduled on the streams stored in
# `__state->gpu_context->streams`, which are all set to the same stream: the
# default stream or an external stream, see `add_external_gpu_stream_arg()`.
# The synchronization at the end of the SDFG call is controlled by gt4py, see
# `make_sdfg_call_sync()` and [DaCe issue#2120](https://github.com/spcl/dace/issues/2120).
dace.Config.set("compiler.cuda.max_concurrent_streams", value=-1)

# Use the experimental CUDA codegen: unlike the legacy one, it launches kernels
# and copies on the streams stored in `__state->gpu_context->streams`, even
# when `max_concurrent_streams` is `-1`. This allows to replace the default
# stream with an external stream, see `add_external_gpu_stream_arg()`.
dace.Config.set("compiler.cuda.implementation", value="experimental")

# The experimental CUDA codegen synchronizes the GPU streams at the end of
# the SDFG by default, which makes the SDFG call synchronous. We disable it
# because, when a synchronous call is requested, gt4py adds the stream
# synchronization itself, see `make_sdfg_call_sync()`.
dace.Config.set("compiler.cuda.synchronize_on_exit", value=False)

# This assumes that a process will only use one type of GPU.
if device_type == core_defs.DeviceType.ROCM:
dace.Config.set("compiler.cuda.backend", value="hip")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,9 @@ class DaCeDecoratedProgram:
program. External workspace memory (when the SDFG uses
``TransientMemoryMode.EXTERNAL``) is installed onto the underlying
``CompiledDaceProgram`` before the first call via `set_external_workspace`;
its lifetime is owned by the caller, not by this wrapper.
its lifetime is owned by the caller, not by this wrapper. Similarly, an external
GPU stream can be set before the first call via `set_external_gpu_stream`; it
is passed as argument to the SDFG and applied on SDFG initialization.
"""

def __init__(
Expand All @@ -43,6 +45,7 @@ def __init__(
device_type: core_defs.DeviceType = core_defs.DeviceType.CPU,
) -> None:
self._fun = fun
self._external_gpu_stream: gtx_wfdcommon.GPUStreamHandle | None = None
# Retrieve metrics level from GT4Py environment variable.
self._collect_time = metrics.is_level_enabled(metrics.PERFORMANCE)
self._collect_time_arg = np.array(
Expand Down Expand Up @@ -85,6 +88,10 @@ def __call__(
gtx_wfdcommon.SDFG_ARG_METRIC_LEVEL: metrics.get_current_level(),
gtx_wfdcommon.SDFG_ARG_METRIC_COMPUTE_TIME: self._collect_time_arg,
}
if gtx_wfdcommon.SDFG_ARG_EXTERNAL_GPU_STREAM in self._fun.sdfg_program.sdfg.symbols:
this_call_args[gtx_wfdcommon.SDFG_ARG_EXTERNAL_GPU_STREAM] = (
gtx_wfdcommon.get_gpu_stream_ptr(self._external_gpu_stream)
)
self._fun.construct_arguments(**this_call_args)

# Perform the call to the SDFG.
Expand All @@ -98,6 +105,38 @@ def __call__(
def set_external_workspace(self, external_workspace: gtx_wfdcommon.ExternalWorkspace) -> None:
"""Set the external workspace for the underlying compiled program.

This method should be called before the first call to the program.
This method should be called before the first call to the program, because
the workspace is installed when the SDFG arguments are constructed.

Args:
external_workspace: Mapping from device types to the array-like objects
used as workspace memory for the SDFG transient arrays, when the
transient memory mode is `EXTERNAL`.
"""
self._fun.external_workspace = external_workspace

def set_external_gpu_stream(
self, external_gpu_stream: gtx_wfdcommon.GPUStreamHandle | None
) -> None:
"""Set the external GPU stream used by the underlying compiled program.

This method should be called before the first call to the program, because
the stream is applied on SDFG initialization.

Args:
external_gpu_stream: The handle of the external GPU stream, either as an
integer or as a stream object, see `GPUStreamHandle`. `None` selects
the default stream.

Raises:
RuntimeError: If the program was already called.
TypeError: If `external_gpu_stream` is not a valid GPU stream handle.
"""
if self._fun.csdfg_argv is not None:
raise RuntimeError(
"The external GPU stream must be set before the first call to the program."
)
# Validate the stream handle, but store it as given: a stream object is thus
# kept alive as long as the program, which uses its pointer value.
gtx_wfdcommon.get_gpu_stream_ptr(external_gpu_stream)
self._external_gpu_stream = external_gpu_stream
16 changes: 12 additions & 4 deletions src/gt4py/next/program_processors/runners/dace/workflow/factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,18 +43,26 @@ class DaCeConfig(next_backend.ToolchainConfig):
#: `EXTERNAL` transient memory mode, which stores the transients in it. The
#: workspace is a dict, so a config holding one is not hashable.
external_workspace: gtx_wfdcommon.ExternalWorkspace | None = None
#: GPU stream allocated outside the SDFG. The toolchain injects it into the
#: loaded programs, and the translation step then passes it to the SDFG as an
#: argument. The stream is an integer, so a config holding one is hashable.
external_gpu_stream: gtx_wfdcommon.GPUStreamHandle | None = None

#: Name of the function that binds the SDFG arguments. The bindings and the
#: compilation steps must agree on it.
bind_func_name: ClassVar[str] = "update_sdfg_args"

def __post_init__(self) -> None:
# Validate the stream handle early, it is only used when the program is loaded.
gtx_wfdcommon.get_gpu_stream_ptr(self.external_gpu_stream)


def make_dace_translator(
cfg: DaCeConfig,
/,
*,
auto_optimize_args: dict[str, Any] | None = None,
async_sdfg_call: bool = True,
sync_sdfg_call: bool = True,
use_metrics: bool = True,
disable_field_origin_on_program_arguments: bool = False,
use_max_domain_range_on_unstructured_shift: bool | None = None,
Expand All @@ -70,8 +78,8 @@ def make_dace_translator(
see `gt_auto_optimize()`. The parameters `DaCeTranslator` derives
itself cannot be set here. With an external workspace in `cfg`, the
`transient_memory_mode` defaults to `EXTERNAL`.
async_sdfg_call: Make an asynchronous SDFG call, to overlap GPU kernel
execution with the Python driver code. Only effective on GPU.
sync_sdfg_call: Make a synchronous SDFG call, to ensure that the GPU kernel
execution is completed before the Python driver code continues. Only effective on GPU.
use_metrics: Add SDFG instrumentation for stencil compute time.
disable_field_origin_on_program_arguments: Assume that all fields passed
as program arguments have zero-based origin, which skips the range
Expand Down Expand Up @@ -116,7 +124,7 @@ def make_dace_translator(
device_type=cfg.device_type,
auto_optimize=cfg.auto_optimize,
auto_optimize_args=optimization_args,
async_sdfg_call=async_sdfg_call and cfg.gpu,
sync_sdfg_call=sync_sdfg_call,
unstructured_horizontal_has_unit_stride=cfg.unstructured_horizontal_has_unit_stride,
use_metrics=use_metrics,
disable_field_origin_on_program_arguments=disable_field_origin_on_program_arguments,
Expand Down
Loading
Loading