feat[next-dace]: Support external memory for transient arrays - #2719
feat[next-dace]: Support external memory for transient arrays#2719edopao wants to merge 25 commits into
Conversation
f331eb9 to
63f4bf5
Compare
egparedes
left a comment
There was a problem hiding this comment.
It looks good. I just some comments and questions, I guess mostly for me to understand some decisions
617e411 to
0ea8985
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 15 out of 15 changed files in this pull request and generated no new comments.
Suppressed comments (10)
src/gt4py/next/program_processors/runners/dace/workflow/backend.py:147
transient_memory_modeis checked withis nothere; use value equality so callers can pass a string value (the enum isstr, Enum) without silently bypassing the intended warning path.
elif transient_memory_mode := optimization_args.get("transient_memory_mode"):
if transient_memory_mode is not gtx_transformations.TransientMemoryMode.EXTERNAL:
src/gt4py/next/program_processors/runners/dace/workflow/compilation.py:178
- The RuntimeError suggests calling
set_external_workspace(), but that method is onDaCeDecoratedProgram/ injected via the backend, not onCompiledDaceProgramitself. The message should point callers to the correct API entry point.
if self.external_workspace is None:
raise RuntimeError(
"External workspace is not set. Please call `set_external_workspace()`"
" before the first call to the program."
)
src/gt4py/next/program_processors/runners/dace/workflow/backend.py:142
make_dace_backendcomparestransient_memory_modeusingis, which will fail when the value comes in as an equivalent string (e.g. from config/CLI) rather than the exact Enum singleton, causing the EXTERNAL/workspace validation to be skipped.
This issue also appears on line 146 of the same file.
if external_workspace is None:
if (
optimization_args.get("transient_memory_mode")
is gtx_transformations.TransientMemoryMode.EXTERNAL
):
src/gt4py/next/program_processors/runners/dace/workflow/backend.py:40
DaCeBackend.load_artifactalways injects an empty dict whenexternal_workspaceisNone, which defeats the downstream "workspace is not set" guard (it becomes "device not found") and can make failures harder to understand.
def load_artifact(self, artifact: stages.CompilationArtifact) -> stages.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 {})
src/gt4py/next/program_processors/runners/dace/workflow/compilation.py:80
- Using
asserthere turns a missing GPU device type into anAssertionError(and can be stripped with-O). This should be a proper runtime error so external workspace setup fails predictably.
This issue also appears on line 174 of the same file.
elif storage == dace.StorageType.GPU_Global:
assert core_defs.CUPY_DEVICE_TYPE is not None
device = core_defs.CUPY_DEVICE_TYPE
else:
src/gt4py/next/program_processors/runners/dace/workflow/compilation.py:100
- The
_validate_external_workspacedocstring claims an alignment check and referencesrequest.nbytes/request.alignment, but the function currently only validates array-interface presence and (optionally)nbytes. This documentation should match the actual checks.
ValueError: If ``wsp`` is smaller than ``request.nbytes`` or its
base pointer is not aligned to ``request.alignment`` bytes.
src/gt4py/next/program_processors/runners/dace/workflow/compilation.py:107
- The TypeError message still refers to an "External memory allocator" returning the buffer, but the new API passes a workspace buffer directly. This wording is misleading for users debugging invalid workspace objects.
raise TypeError(
f"External memory allocator returned {type(wsp).__name__!r} for storage "
f"{storage!r}, which does not expose `__array_interface__` or "
f"`__cuda_array_interface__`."
)
src/gt4py/next/program_processors/runners/dace/workflow/decoration.py:36
- The docstring says teardown is forwarded so the pool can release external workspaces, but external workspace lifetime is explicitly caller-owned (and
ExecutableProgramis just aCallable). This sentence is misleading.
program. Teardown is forwarded to the underlying ``CompiledDaceProgram``
so that the generic otf pool -- which only sees this callable -- can
release external-memory workspaces when the pool is finalized.
tests/next_tests/unit_tests/program_processor_tests/runners_tests/dace_tests/test_dace_backend.py:375
- In the EXTERNAL/CPU branch the comment says external-memory API calls should replace host malloc/free, but the assertions currently require
new/mallocanddelete/freeto appear. This makes the test validate the opposite of what the comment (and EXTERNAL mode intent) describes.
# CPU external mode should route transient workspace setup via
# external-memory API calls rather than host malloc/free calls.
assert any(marker in generated_code for marker in ("new ", "malloc"))
assert any(marker in generated_code for marker in ("delete ", "free"))
src/gt4py/next/program_processors/runners/dace/transformations/auto_optimize.py:121
TransientMemoryModedocs stateSCOPEDis the default strategy, butgt_auto_optimizecurrently defaults toTransientMemoryMode.POOL. Either the default or the documentation should be aligned.
- `SCOPED`: Transients are allocated and deallocated in the scope of the SDFG
where they are defined, being it the top-level SDFG or a nested one.
This is the default strategy.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 15 out of 15 changed files in this pull request and generated 1 comment.
Suppressed comments (4)
tests/next_tests/unit_tests/program_processor_tests/runners_tests/dace_tests/test_dace_compilation.py:437
- There is no unit test that a host-only workspace (only
__array_interface__) is rejected when the SDFG requestsGPU_Globalworkspace. Without this, a regression could silently accept an invalid buffer for GPU storage.
def test_construct_arguments_accepts_gpu_workspace():
"""A device workspace with matching size is accepted and installed."""
workspace = _make_array_buffer(nbytes=128, cuda=True)
program = _make_compiled_program(
external_workspace={core_defs.DeviceType.CUDA: workspace},
workspace_sizes={dace.StorageType.GPU_Global: 128},
)
src/gt4py/next/program_processors/runners/dace/workflow/decoration.py:72
- The comment refers to
fun.csdfg_args, but the attribute iscsdfg_argv. This makes the first-call logic harder to follow when debugging.
# NOTE: If this is the first time then we will generate an exception because
# `fun.csdfg_args` is `None`
# TODO(phimuell, edopao): Think about refactor the code such that the update
# of the argument vector is a Method of the `CompiledDaceProgram`.
self._update_sdfg_call_args(args, self._fun.csdfg_argv, offset_provider) # type: ignore[arg-type] # Will error out in first call.
src/gt4py/next/program_processors/runners/dace/transformations/utils.py:69
- The TODO says “Find out why scalars are processed”, but this branch is taken for data nodes that are not arrays or scalars. This is misleading when reading the lifetime-selection logic.
desc = dnode.desc(nsdfg)
if not desc.transient or type(desc) not in {dace.data.Array, dace.data.Scalar}:
# TODO(phimuell): Find out why scalars are processed.
not_modify_lifetime.add(dnode.data)
continue
tests/next_tests/unit_tests/program_processor_tests/runners_tests/dace_tests/test_dace_backend.py:395
- The comment says CPU external mode should use external-memory APIs “rather than host malloc/free calls”, but the assertions below currently require
new/mallocanddelete/freemarkers to be present. Either the comment or the assertions should be adjusted so they describe the same expected behavior.
# CPU external mode should route transient workspace setup via
# external-memory API calls rather than host malloc/free calls.
assert any(marker in generated_code for marker in ("new ", "malloc"))
assert any(marker in generated_code for marker in ("delete ", "free"))
This pull request introduces a more flexible and extensible way to control the allocation and lifetime of transient arrays in the DaCe backend, replacing the previous `make_persistent` and `gpu_memory_pool` options with a new `TransientMemoryMode` policy. It also adds support for externally managed memory via an `external_memory_allocator` parameter, and refactors related utility functions to support these changes. **API and Configuration Improvements:** * Introduced the `TransientMemoryMode` enum to specify transient array lifetime/allocation strategies (`SCOPED`, `PERSISTENT`, `POOL`, `EXTERNAL`), and replaced the `make_persistent` and `gpu_memory_pool` parameters throughout the codebase with this unified approach. [[1]](diffhunk://#diff-ddbc0e8ab1d5b89929dfbd12477936ae3acad531911fa9afb125066b6fe74706R114-R127) [[2]](diffhunk://#diff-ddbc0e8ab1d5b89929dfbd12477936ae3acad531911fa9afb125066b6fe74706L133) [[3]](diffhunk://#diff-ddbc0e8ab1d5b89929dfbd12477936ae3acad531911fa9afb125066b6fe74706L177-R185) [[4]](diffhunk://#diff-ddbc0e8ab1d5b89929dfbd12477936ae3acad531911fa9afb125066b6fe74706L197) [[5]](diffhunk://#diff-ddbc0e8ab1d5b89929dfbd12477936ae3acad531911fa9afb125066b6fe74706L403-L409) [[6]](diffhunk://#diff-ddbc0e8ab1d5b89929dfbd12477936ae3acad531911fa9afb125066b6fe74706L921-L923) [[7]](diffhunk://#diff-ddbc0e8ab1d5b89929dfbd12477936ae3acad531911fa9afb125066b6fe74706L942-R963) [[8]](diffhunk://#diff-c7967b0fa16e0de89cb43f3c81e9aee884122772946f031d50fb31d1cb16215bR20) [[9]](diffhunk://#diff-c7967b0fa16e0de89cb43f3c81e9aee884122772946f031d50fb31d1cb16215bR123) * Added support for an `external_memory_allocator` parameter in the DaCe backend and workflow, which allows users to provide custom allocation logic for external memory. This is validated to only be used with `transient_memory_mode=external`. [[1]](diffhunk://#diff-04258cb5c4aa36c40ab7da9246c27d69866a707135824bba4f76d46e93de72fbR40) [[2]](diffhunk://#diff-04258cb5c4aa36c40ab7da9246c27d69866a707135824bba4f76d46e93de72fbR52) [[3]](diffhunk://#diff-04258cb5c4aa36c40ab7da9246c27d69866a707135824bba4f76d46e93de72fbR67) [[4]](diffhunk://#diff-04258cb5c4aa36c40ab7da9246c27d69866a707135824bba4f76d46e93de72fbR82-R83) [[5]](diffhunk://#diff-04258cb5c4aa36c40ab7da9246c27d69866a707135824bba4f76d46e93de72fbR120-R138) [[6]](diffhunk://#diff-89b749ff9b80be5fdb8a7e0d05411d2b992b6a5892f819c7a3e001c4241a6c0dR184) [[7]](diffhunk://#diff-91264fda895ecc56c869858ac9724e1ff60ed6514a121d1cd4f323197053e5aaR38) [[8]](diffhunk://#diff-91264fda895ecc56c869858ac9724e1ff60ed6514a121d1cd4f323197053e5aaR77) **Utility Function Refactoring:** * Refactored the persistent transients utility into a more general `_gt_configure_transient_lifetime` function, which now supports both `Persistent` and `External` lifetimes. Exposed two public functions: `gt_make_transients_persistent` and the new `gt_make_transients_external`. [[1]](diffhunk://#diff-6ce6c03a192e5b5b7e735ecc3aa1071dc7ce0ac5d90f206f948b1e3f8e952637L29-R39) [[2]](diffhunk://#diff-6ce6c03a192e5b5b7e735ecc3aa1071dc7ce0ac5d90f206f948b1e3f8e952637L84-R70) [[3]](diffhunk://#diff-c7967b0fa16e0de89cb43f3c81e9aee884122772946f031d50fb31d1cb16215bL87-R88) [[4]](diffhunk://#diff-c7967b0fa16e0de89cb43f3c81e9aee884122772946f031d50fb31d1cb16215bR137) **Documentation and Safety:** * Updated docstrings and comments to reflect the new transient memory modes and clarify the behavior and constraints of each mode, including safety considerations when using global-lifetime transients. [[1]](diffhunk://#diff-ddbc0e8ab1d5b89929dfbd12477936ae3acad531911fa9afb125066b6fe74706L177-R185) [[2]](diffhunk://#diff-6ce6c03a192e5b5b7e735ecc3aa1071dc7ce0ac5d90f206f948b1e3f8e952637L84-R70) These changes make the backend's memory management more robust and adaptable, and lay the groundwork for advanced use cases such as externally managed memory.
This pull request introduces support for external workspace memory allocation in the DaCe backend, allowing users to provide custom memory allocators for transient workspaces. It updates the compilation workflow to use an external allocator when configured, ensures workspaces are only allocated once, and adds comprehensive tests for the new functionality. The changes also improve device type handling and test coverage for transient memory modes. **External workspace allocator integration:** * Added an `external_memory_allocator` parameter to `CompiledDaceProgram` and `DaCeCompilationArtifact`, allowing external allocation of workspace memory buffers. Workspaces are now tracked and only allocated once per compiled program instance. (`src/gt4py/next/program_processors/runners/dace/workflow/compilation.py` [[1]](diffhunk://#diff-89b749ff9b80be5fdb8a7e0d05411d2b992b6a5892f819c7a3e001c4241a6c0dR88-R96) [[2]](diffhunk://#diff-89b749ff9b80be5fdb8a7e0d05411d2b992b6a5892f819c7a3e001c4241a6c0dR116-R137) [[3]](diffhunk://#diff-89b749ff9b80be5fdb8a7e0d05411d2b992b6a5892f819c7a3e001c4241a6c0dR194-R207) [[4]](diffhunk://#diff-89b749ff9b80be5fdb8a7e0d05411d2b992b6a5892f819c7a3e001c4241a6c0dR299) * Implemented the `_map_workspace_storage_to_device` helper to map DaCe storage types to device types for allocation. (`src/gt4py/next/program_processors/runners/dace/workflow/compilation.py` [src/gt4py/next/program_processors/runners/dace/workflow/compilation.pyR50-R61](diffhunk://#diff-89b749ff9b80be5fdb8a7e0d05411d2b992b6a5892f819c7a3e001c4241a6c0dR50-R61)) * Ensured external workspaces are configured during argument construction and only set up once. (`src/gt4py/next/program_processors/runners/dace/workflow/compilation.py` [src/gt4py/next/program_processors/runners/dace/workflow/compilation.pyR146](diffhunk://#diff-89b749ff9b80be5fdb8a7e0d05411d2b992b6a5892f819c7a3e001c4241a6c0dR146)) **Testing and validation:** * Added tests to verify that external workspace allocation works as intended, including correct device routing, error propagation, and single allocation per program instance. (`tests/next_tests/unit_tests/program_processor_tests/runners_tests/dace_tests/test_dace_compilation.py` [tests/next_tests/unit_tests/program_processor_tests/runners_tests/dace_tests/test_dace_compilation.pyR235-R311](diffhunk://#diff-57ff279a6de9ce815a6bde8ae126007687da1d07f6ad28626e998541fc9d89b3R235-R311)) * Extended backend tests to check that different transient memory modes (EXTERNAL, POOL, PERSISTENT, SCOPED) use the correct allocation APIs and honor the external allocator, with device-specific checks for CPU and GPU. (`tests/next_tests/unit_tests/program_processor_tests/runners_tests/dace_tests/test_dace_backend.py` [tests/next_tests/unit_tests/program_processor_tests/runners_tests/dace_tests/test_dace_backend.pyR213-R378](diffhunk://#diff-19fed38498781ceef82337d097e43458274d2ebe7e0bae4578839e9fbed15194R213-R378)) **Device type handling improvements:** * Updated device type selection in tests to use `core_defs.CUPY_DEVICE_TYPE` for GPU tests, ensuring compatibility with the available GPU backend. (`tests/next_tests/unit_tests/program_processor_tests/runners_tests/dace_tests/test_dace_backend.py` [tests/next_tests/unit_tests/program_processor_tests/runners_tests/dace_tests/test_dace_backend.pyR26-R43](diffhunk://#diff-19fed38498781ceef82337d097e43458274d2ebe7e0bae4578839e9fbed15194R26-R43)) These changes make the DaCe backend more flexible and robust by supporting custom workspace allocation, improving test coverage, and ensuring correct behavior across memory modes and device types.
This pull request introduces a new "External Memory Allocator" mode for DaCe transients in the `gt4py.next` backend, allowing explicit, caller-managed workspace allocation and teardown. This enables reusing a single workspace buffer across multiple SDFGs, removing per-call allocation overhead and providing more control over memory lifetime. The public API now exposes a typed allocator protocol, and the backend enforces that allocators are picklable to ensure compatibility with process-based compilation. The changes also include explicit pool-driven teardown of resources and improved error handling for allocator pickling. **External Memory Allocator for DaCe Transients** *Design and API additions:* - Added an explicit `EXTERNAL` mode to `TransientMemoryMode`, backed by a caller-supplied `ExternalMemoryAllocator` protocol. This protocol is defined with typed `allocate` and `deallocate` methods and requires picklability. (`[[1]](diffhunk://#diff-b86bd892b20495d184cedc1d9057e4b1ef0e837d723e998628332943d900aa74R1-R147)`, `[[2]](diffhunk://#diff-ddbc0e8ab1d5b89929dfbd12477936ae3acad531911fa9afb125066b6fe74706R117-R178)`, `[[3]](diffhunk://#diff-ddbc0e8ab1d5b89929dfbd12477936ae3acad531911fa9afb125066b6fe74706L133-R202)`) - Introduced `AllocationRequest` and `Buffer` type alias to formalize the workspace allocation contract. (`[src/gt4py/next/program_processors/runners/dace/transformations/auto_optimize.pyR117-R178](diffhunk://#diff-ddbc0e8ab1d5b89929dfbd12477936ae3acad531911fa9afb125066b6fe74706R117-R178)`) - Re-exported `AllocationRequest`, `Buffer`, and `ExternalMemoryAllocator` from the `transformations` package for public use. (`[[1]](diffhunk://#diff-c7967b0fa16e0de89cb43f3c81e9aee884122772946f031d50fb31d1cb16215bL16-R19)`, `[[2]](diffhunk://#diff-c7967b0fa16e0de89cb43f3c81e9aee884122772946f031d50fb31d1cb16215bR95-R99)`) *Backend and resource management:* - Updated the DaCe backend to accept an `ExternalMemoryAllocator` instance, with improved documentation and type safety. (`[[1]](diffhunk://#diff-04258cb5c4aa36c40ab7da9246c27d69866a707135824bba4f76d46e93de72fbL67-R66)`, `[[2]](diffhunk://#diff-04258cb5c4aa36c40ab7da9246c27d69866a707135824bba4f76d46e93de72fbL82-R85)`) - Added a check to ensure the allocator is picklable at backend construction, raising a clear error if not. (`[src/gt4py/next/program_processors/runners/dace/workflow/compilation.pyR43-R75](diffhunk://#diff-89b749ff9b80be5fdb8a7e0d05411d2b992b6a5892f819c7a3e001c4241a6c0dR43-R75)`) - Implemented explicit resource teardown: when a `CompiledProgramsPool` is deleted, any compiled programs with a `finalize()` method have it called to release external resources, with warnings on failure. (`[[1]](diffhunk://#diff-cae915aa5b7a62aa4a6eb295978c894fc43270cf6e08e82fca56414bf37d7cdeR88-R113)`, `[[2]](diffhunk://#diff-cae915aa5b7a62aa4a6eb295978c894fc43270cf6e08e82fca56414bf37d7cdeR404-R417)`) *Documentation:* - Added ADR 0026 documenting the rationale, design, and consequences of the external memory allocator feature. (`[docs/development/ADRs/next/0026-External_Memory_Allocator.mdR1-R147](diffhunk://#diff-b86bd892b20495d184cedc1d9057e4b1ef0e837d723e998628332943d900aa74R1-R147)`) These changes provide a robust, explicit, and user-extensible mechanism for managing transient workspace memory in DaCe-based workflows, improving performance and resource control for advanced use cases.
This reverts commit dd47b2e.
This reverts commit 696488d.
This reverts commit 63f4bf5.
…der fingerprint (#2739) This pull request refactors and improves the GPU trace marker logic in the DaCe compilation workflow, enhances test coverage, and clarifies test parameterization. The main functional change is to ensure that the fingerprint of the build folder includes the modified SDFG after applying the GPU transaction markers. This way, enabling or not the GPU transaction markers results in different build artifacts. Tests are updated to reflect the new marker application logic and to ensure that build artifacts change when relevant compilation settings differ. **DaCe compilation logic improvements:** * Refactored `_add_tx_markers` to take and return an `ExtensionSource`, only modifying the SDFG if GPU scheduling is detected, and returning a new `ExtensionSource` with updated SDFG JSON. * Updated the main DaCe compiler logic to call `_add_tx_markers` only when GPU trace markers are requested and the device type is GPU, ensuring markers are not redundantly applied. [[1]](diffhunk://#diff-89b749ff9b80be5fdb8a7e0d05411d2b992b6a5892f819c7a3e001c4241a6c0dL200-R222) [[2]](diffhunk://#diff-89b749ff9b80be5fdb8a7e0d05411d2b992b6a5892f819c7a3e001c4241a6c0dL218-R235) **Test suite improvements:** * Refactored tests to use a real `program_source` fixture and removed unnecessary mocks and spies, directly verifying the presence or absence of GPU TX markers on the compiled SDFG. [[1]](diffhunk://#diff-57ff279a6de9ce815a6bde8ae126007687da1d07f6ad28626e998541fc9d89b3L82-R83) [[2]](diffhunk://#diff-57ff279a6de9ce815a6bde8ae126007687da1d07f6ad28626e998541fc9d89b3R100-L109) [[3]](diffhunk://#diff-57ff279a6de9ce815a6bde8ae126007687da1d07f6ad28626e998541fc9d89b3L119-L123) [[4]](diffhunk://#diff-57ff279a6de9ce815a6bde8ae126007687da1d07f6ad28626e998541fc9d89b3L138-L173) * Added new tests to assert that identical compilation inputs produce the same artifact, and that changing instrumentation or compiler flags results in different artifacts. [[1]](diffhunk://#diff-57ff279a6de9ce815a6bde8ae126007687da1d07f6ad28626e998541fc9d89b3R181-R203) [[2]](diffhunk://#diff-57ff279a6de9ce815a6bde8ae126007687da1d07f6ad28626e998541fc9d89b3R213-R248) **Test parameterization and device naming:** * Updated device type parameterization in both compilation and translation tests to use consistent and descriptive IDs (`CPU`, `GPU`, `CUDA`, `ROCM`) and to use the correct device type constants. [[1]](diffhunk://#diff-19fed38498781ceef82337d097e43458274d2ebe7e0bae4578839e9fbed15194L31-R33) [[2]](diffhunk://#diff-8ba502dd24c52188c94e85f0bcd66c31fbe3093077168033f99c14eb19ac1cb4L54-R55) These changes improve the correctness, maintainability, and clarity of both the DaCe compilation workflow and its associated tests.
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
f909c37 to
73a16cc
Compare
This pull request introduces a new "external workspace" memory mode for DaCe transients in the
gt4py.nextDaCe backend, allowing users to provide their own workspace buffers and thereby avoid per-call GPU memory allocation overhead. The change is documented in a new ADR and is implemented by extending the transient memory mode enum, updating backend and compilation workflows, and making the memory management API more flexible and explicit. The pull request also cleans up and unifies transient memory handling throughout the codebase.The most important changes are:
External Workspace Memory Mode (EXTERNAL) Implementation:
TransientMemoryModeenum with anEXTERNALoption, allowing users to supply their own workspace buffers for DaCe transients. This is documented in a detailed ADR (docs/development/ADRs/next/0027-External_Workspace_Memory.md) and referenced in the ADR index. [1] [2] [3]Backend and Compilation Workflow Updates:
load_artifactmethod to theBackendclass, allowing backends to inject runtime data (such as external workspaces) into loaded programs. The DaCe backend overrides this to install the user-supplied workspace.CompiledProgramsPoolto use the backend'sload_artifactmethod instead of directly callingartifact.load(), ensuring that all loaded programs receive the correct workspace configuration. [1] [2]API and Import Cleanup:
__all__lists to expose the newTransientMemoryModeand the refactored utility function for configuring transient lifetimes, replacing the previous persistent-only approach. [1] [2] [3]These changes collectively provide a more flexible, efficient, and explicit mechanism for managing workspace memory in DaCe-based GT4Py workflows, particularly for repeated or multi-program GPU workloads.