Repository: GridTools/gt4py
Pull request #1811 (2025-01-21 12:37:58)

refactor[next][dace]: cleanup dace backend module
PR #1753 removed the `dace_iterator` backend, that before existed by side of `dace_fieldview` backend. Code common to these two backends was placed in `dace_common` module.

The current GTIR-DaCe backend supports both iterator view and field view flavors of the GTIR, so there is no need for a distinction at the top level of the backend module.

This PR applies the following cleanup tasks:
```
runners/dace_common/dace_backend -> runners/dace_fieldview/sdfg_callable
runners/dace_common/utility -> runners/dace_fieldview/utils
runners/dace_common/workflow, runners/dace_fieldview/workflow -> runners/dace_fieldview/workflow
runners/dace_fiedview/utility -> runners/dace_fieldview/gtir_sdfg_utils
runners/dace_fieldview -> runners/dace
```

The module `runners/dace/workflow` was also split into sub-modules. A doc string was added to `runners/dace/workflow/__init__.py`


Patch:
diff --git a/src/gt4py/next/ffront/decorator.py b/src/gt4py/next/ffront/decorator.py
index 7e2abc44..ecaf1a76 100644
--- a/src/gt4py/next/ffront/decorator.py
+++ b/src/gt4py/next/ffront/decorator.py
@@ -306,7 +306,7 @@ class FrozenProgram:
 
 
 try:
-    from gt4py.next.program_processors.runners.dace_fieldview.program import Program
+    from gt4py.next.program_processors.runners.dace.program import Program
 except ImportError:
     pass
 
diff --git a/src/gt4py/next/program_processors/runners/dace/__init__.py b/src/gt4py/next/program_processors/runners/dace/__init__.py
new file mode 100644
index 00000000..85405854
--- /dev/null
+++ b/src/gt4py/next/program_processors/runners/dace/__init__.py
@@ -0,0 +1,27 @@
+# GT4Py - GridTools Framework
+#
+# Copyright (c) 2014-2024, ETH Zurich
+# All rights reserved.
+#
+# Please, refer to the LICENSE file in the root directory.
+# SPDX-License-Identifier: BSD-3-Clause
+
+
+from gt4py.next.program_processors.runners.dace.gtir_sdfg import build_sdfg_from_gtir
+from gt4py.next.program_processors.runners.dace.sdfg_callable import get_sdfg_args
+from gt4py.next.program_processors.runners.dace.workflow.backend import (
+    run_dace_cpu,
+    run_dace_cpu_noopt,
+    run_dace_gpu,
+    run_dace_gpu_noopt,
+)
+
+
+__all__ = [
+    "build_sdfg_from_gtir",
+    "get_sdfg_args",
+    "run_dace_cpu",
+    "run_dace_cpu_noopt",
+    "run_dace_gpu",
+    "run_dace_gpu_noopt",
+]
diff --git a/src/gt4py/next/program_processors/runners/dace_fieldview/gtir_builtin_translators.py b/src/gt4py/next/program_processors/runners/dace/gtir_builtin_translators.py
similarity index 97%
rename from src/gt4py/next/program_processors/runners/dace_fieldview/gtir_builtin_translators.py
rename to src/gt4py/next/program_processors/runners/dace/gtir_builtin_translators.py
index b0d09e0a..0fe776c3 100644
--- a/src/gt4py/next/program_processors/runners/dace_fieldview/gtir_builtin_translators.py
+++ b/src/gt4py/next/program_processors/runners/dace/gtir_builtin_translators.py
@@ -23,19 +23,19 @@ from gt4py.next.iterator.ir_utils import (
     domain_utils,
     ir_makers as im,
 )
-from gt4py.next.program_processors.runners.dace_common import utility as dace_utils
-from gt4py.next.program_processors.runners.dace_fieldview import (
+from gt4py.next.program_processors.runners.dace import (
     gtir_dataflow,
     gtir_python_codegen,
     gtir_sdfg,
-    utility as dace_gtir_utils,
+    gtir_sdfg_utils,
+    utils as gtx_dace_utils,
 )
-from gt4py.next.program_processors.runners.dace_fieldview.gtir_scan_translator import translate_scan
+from gt4py.next.program_processors.runners.dace.gtir_scan_translator import translate_scan
 from gt4py.next.type_system import type_info as ti, type_specifications as ts
 
 
 if TYPE_CHECKING:
-    from gt4py.next.program_processors.runners.dace_fieldview import gtir_sdfg
+    from gt4py.next.program_processors.runners.dace import gtir_sdfg
 
 
 def get_domain_indices(
@@ -54,7 +54,7 @@ def get_domain_indices(
         as `dace.subsets.Indices`, it should be converted to `dace.subsets.Range` before
         being used in memlet subset because ranges are better supported throughout DaCe.
     """
-    index_variables = [dace.symbolic.SymExpr(dace_gtir_utils.get_map_variable(dim)) for dim in dims]
+    index_variables = [dace.symbolic.SymExpr(gtir_sdfg_utils.get_map_variable(dim)) for dim in dims]
     if offsets is None:
         return dace_subsets.Indices(index_variables)
     else:
@@ -158,7 +158,7 @@ def flatten_tuples(name: str, arg: FieldopResult) -> list[tuple[str, FieldopData
     """
     if isinstance(arg, tuple):
         tuple_type = get_tuple_type(arg)
-        tuple_symbols = dace_gtir_utils.flatten_tuple_fields(name, tuple_type)
+        tuple_symbols = gtir_sdfg_utils.flatten_tuple_fields(name, tuple_type)
         tuple_data_fields = gtx_utils.flatten_nested_tuple(arg)
         return [
             (str(tsym.id), tfield)
@@ -353,7 +353,7 @@ def _create_field_operator(
         "fieldop",
         state,
         ndrange={
-            dace_gtir_utils.get_map_variable(dim): f"{lower_bound}:{upper_bound}"
+            gtir_sdfg_utils.get_map_variable(dim): f"{lower_bound}:{upper_bound}"
             for dim, lower_bound, upper_bound in domain
         },
     )
@@ -372,7 +372,7 @@ def _create_field_operator(
         )
     else:
         # handle tuples of fields
-        output_symbol_tree = dace_gtir_utils.make_symbol_tree("x", node_type)
+        output_symbol_tree = gtir_sdfg_utils.make_symbol_tree("x", node_type)
         return gtx_utils.tree_map(
             lambda output_edge, output_sym: _create_field_operator_impl(
                 sdfg_builder, sdfg, state, domain, output_edge, output_sym.type, map_exit
@@ -590,13 +590,13 @@ def translate_index(
     domain = extract_domain(node.annex.domain)
     assert len(domain) == 1
     dim, _, _ = domain[0]
-    dim_index = dace_gtir_utils.get_map_variable(dim)
+    dim_index = gtir_sdfg_utils.get_map_variable(dim)
 
     index_data, _ = sdfg_builder.add_temp_scalar(sdfg, INDEX_DTYPE)
     index_node = state.add_access(index_data)
     index_value = gtir_dataflow.ValueExpr(
         dc_node=index_node,
-        gt_dtype=dace_utils.as_itir_type(INDEX_DTYPE),
+        gt_dtype=gtx_dace_utils.as_itir_type(INDEX_DTYPE),
     )
     index_write_tasklet = sdfg_builder.add_tasklet(
         "index",
@@ -643,7 +643,7 @@ def _get_data_nodes(
         return sdfg_builder.make_field(data_node, data_type)
 
     elif isinstance(data_type, ts.TupleType):
-        symbol_tree = dace_gtir_utils.make_symbol_tree(data_name, data_type)
+        symbol_tree = gtir_sdfg_utils.make_symbol_tree(data_name, data_type)
         return gtx_utils.tree_map(
             lambda sym: _get_data_nodes(sdfg, state, sdfg_builder, sym.id, sym.type)
         )(symbol_tree)
@@ -669,7 +669,7 @@ def _get_symbolic_value(
     )
     temp_name, _ = sdfg.add_scalar(
         temp_name or sdfg.temp_data_name(),
-        dace_utils.as_dace_type(scalar_type),
+        gtx_dace_utils.as_dace_type(scalar_type),
         find_new_name=True,
         transient=True,
     )
@@ -808,7 +808,7 @@ def translate_scalar_expr(
             dace.Memlet(data=arg_node.data, subset="0"),
         )
     # finally, create temporary for the result value
-    temp_name, _ = sdfg_builder.add_temp_scalar(sdfg, dace_utils.as_dace_type(node.type))
+    temp_name, _ = sdfg_builder.add_temp_scalar(sdfg, gtx_dace_utils.as_dace_type(node.type))
     temp_node = state.add_access(temp_name)
     state.add_edge(
         tasklet_node,
diff --git a/src/gt4py/next/program_processors/runners/dace_fieldview/gtir_dataflow.py b/src/gt4py/next/program_processors/runners/dace/gtir_dataflow.py
similarity index 98%
rename from src/gt4py/next/program_processors/runners/dace_fieldview/gtir_dataflow.py
rename to src/gt4py/next/program_processors/runners/dace/gtir_dataflow.py
index 966ade5c..e00e363a 100644
--- a/src/gt4py/next/program_processors/runners/dace_fieldview/gtir_dataflow.py
+++ b/src/gt4py/next/program_processors/runners/dace/gtir_dataflow.py
@@ -32,11 +32,11 @@ from gt4py.next import common as gtx_common, utils as gtx_utils
 from gt4py.next.iterator import builtins, ir as gtir
 from gt4py.next.iterator.ir_utils import common_pattern_matcher as cpm, ir_makers as im
 from gt4py.next.iterator.transforms import symbol_ref_utils
-from gt4py.next.program_processors.runners.dace_common import utility as dace_utils
-from gt4py.next.program_processors.runners.dace_fieldview import (
+from gt4py.next.program_processors.runners.dace import (
     gtir_python_codegen,
     gtir_sdfg,
-    utility as dace_gtir_utils,
+    gtir_sdfg_utils,
+    utils as gtx_dace_utils,
 )
 from gt4py.next.type_system import type_info as ti, type_specifications as ts
 
@@ -274,7 +274,7 @@ DACE_REDUCTION_MAPPING: dict[str, dace.dtypes.ReductionType] = {
 
 def get_reduce_params(node: gtir.FunCall) -> tuple[str, SymbolExpr, SymbolExpr]:
     assert isinstance(node.type, ts.ScalarType)
-    dc_dtype = dace_utils.as_dace_type(node.type)
+    dc_dtype = gtx_dace_utils.as_dace_type(node.type)
 
     assert isinstance(node.fun, gtir.FunCall)
     assert len(node.fun.args) == 2
@@ -467,7 +467,7 @@ class LambdaToDataflow(eve.NodeVisitor):
         src_connector: str,
         use_array: bool = False,
     ) -> ValueExpr:
-        data_type = dace_utils.as_itir_type(dc_dtype)
+        data_type = gtx_dace_utils.as_itir_type(dc_dtype)
         if use_array:
             # In some cases, such as result data with list-type annotation, we want
             # that output data is represented as an array (single-element 1D array)
@@ -676,7 +676,7 @@ class LambdaToDataflow(eve.NodeVisitor):
             if isinstance(arg, tuple):
                 ptype = get_tuple_type(arg)  # type: ignore[arg-type]
                 psymbol = im.sym(pname, ptype)
-                psymbol_tree = dace_gtir_utils.make_symbol_tree(pname, ptype)
+                psymbol_tree = gtir_sdfg_utils.make_symbol_tree(pname, ptype)
                 inner_arg = gtx_utils.tree_map(
                     lambda tsym, targ: self._visit_if_branch_arg(
                         if_sdfg, if_branch_state, tsym.id, targ, if_sdfg_input_memlets
@@ -772,7 +772,7 @@ class LambdaToDataflow(eve.NodeVisitor):
         )
 
         nsdfg = dace.SDFG(self.unique_nsdfg_name(prefix="if_stmt"))
-        nsdfg.debuginfo = dace_utils.debug_info(node, default=self.sdfg.debuginfo)
+        nsdfg.debuginfo = gtir_sdfg_utils.debug_info(node, default=self.sdfg.debuginfo)
 
         # create states inside the nested SDFG for the if-branches
         if use_conditional_block:
@@ -812,7 +812,7 @@ class LambdaToDataflow(eve.NodeVisitor):
                 edge.connect(map_entry=None)
 
             if isinstance(node.type, ts.TupleType):
-                out_symbol_tree = dace_gtir_utils.make_symbol_tree("__output", node.type)
+                out_symbol_tree = gtir_sdfg_utils.make_symbol_tree("__output", node.type)
                 outer_value = gtx_utils.tree_map(
                     lambda x, y, nstate=nstate: self._visit_if_branch_result(nsdfg, nstate, x, y)
                 )(output_tree, out_symbol_tree)
@@ -880,7 +880,7 @@ class LambdaToDataflow(eve.NodeVisitor):
         assert all(isinstance(index, SymbolExpr) for index in it.indices.values())
 
         field_desc = it.field.desc(self.sdfg)
-        connectivity = dace_utils.connectivity_identifier(offset)
+        connectivity = gtx_dace_utils.connectivity_identifier(offset)
         # initially, the storage for the connectivty tables is created as transient;
         # when the tables are used, the storage is changed to non-transient,
         # as the corresponding arrays are supposed to be allocated by the SDFG caller
@@ -926,7 +926,7 @@ class LambdaToDataflow(eve.NodeVisitor):
         )
         neighbors_node = self.state.add_access(neighbors_temp)
         offset_type = gtx_common.Dimension(offset, gtx_common.DimensionKind.LOCAL)
-        neighbor_idx = dace_gtir_utils.get_map_variable(offset_type)
+        neighbor_idx = gtir_sdfg_utils.get_map_variable(offset_type)
 
         index_connector = "__index"
         output_connector = "__val"
@@ -979,7 +979,7 @@ class LambdaToDataflow(eve.NodeVisitor):
         list_desc = list_arg.dc_node.desc(self.sdfg)
         assert len(list_desc.shape) == 1
 
-        result_dtype = dace_utils.as_dace_type(list_arg.gt_dtype.element_type)
+        result_dtype = gtx_dace_utils.as_dace_type(list_arg.gt_dtype.element_type)
         result, _ = self.subgraph_builder.add_temp_scalar(self.sdfg, result_dtype)
         result_node = self.state.add_access(result)
 
@@ -1039,7 +1039,7 @@ class LambdaToDataflow(eve.NodeVisitor):
         assert len(node.fun.args) == 1  # the operation to be mapped on the arguments
 
         assert isinstance(node.type.element_type, ts.ScalarType)
-        dc_dtype = dace_utils.as_dace_type(node.type.element_type)
+        dc_dtype = gtx_dace_utils.as_dace_type(node.type.element_type)
 
         input_connectors = [f"__arg{i}" for i in range(len(node.args))]
         output_connector = "__out"
@@ -1081,7 +1081,7 @@ class LambdaToDataflow(eve.NodeVisitor):
             raise ValueError("Unexpected arguments to map expression with different neighborhood.")
         offset_type, offset_provider_type = next(iter(input_connectivity_types.items()))
         local_size = offset_provider_type.max_neighbors
-        map_index = dace_gtir_utils.get_map_variable(offset_type)
+        map_index = gtir_sdfg_utils.get_map_variable(offset_type)
 
         # The dataflow we build in this class has some loose connections on input edges.
         # These edges are described as set of nodes, that will have to be connected to
@@ -1115,11 +1115,11 @@ class LambdaToDataflow(eve.NodeVisitor):
         if offset_provider_type.has_skip_values:
             # In case the `map_` input expressions contain skip values, we use
             # the connectivity-based offset provider as mask for map computation.
-            connectivity = dace_utils.connectivity_identifier(offset_type.value)
+            connectivity = gtx_dace_utils.connectivity_identifier(offset_type.value)
             connectivity_desc = self.sdfg.arrays[connectivity]
             connectivity_desc.transient = False
 
-            origin_map_index = dace_gtir_utils.get_map_variable(offset_provider_type.source_dim)
+            origin_map_index = gtir_sdfg_utils.get_map_variable(offset_provider_type.source_dim)
 
             connectivity_slice = self._construct_local_view(
                 MemletExpr(
@@ -1187,14 +1187,14 @@ class LambdaToDataflow(eve.NodeVisitor):
         corresponding neighbor index in the connectivity table is valid, or the
         identity value if the neighbor index is missing.
         """
-        origin_map_index = dace_gtir_utils.get_map_variable(offset_provider_type.source_dim)
+        origin_map_index = gtir_sdfg_utils.get_map_variable(offset_provider_type.source_dim)
 
         assert (
             isinstance(input_expr.gt_dtype, ts.ListType)
             and input_expr.gt_dtype.offset_type is not None
         )
         offset_type = input_expr.gt_dtype.offset_type
-        connectivity = dace_utils.connectivity_identifier(offset_type.value)
+        connectivity = gtx_dace_utils.connectivity_identifier(offset_type.value)
         connectivity_node = self.state.add_access(connectivity)
         connectivity_desc = connectivity_node.desc(self.sdfg)
         connectivity_desc.transient = False
@@ -1506,7 +1506,7 @@ class LambdaToDataflow(eve.NodeVisitor):
 
     def _visit_shift(self, node: gtir.FunCall) -> IteratorExpr:
         # convert builtin-index type to dace type
-        IndexDType: Final = dace_utils.as_dace_type(
+        IndexDType: Final = gtx_dace_utils.as_dace_type(
             ts.ScalarType(kind=getattr(ts.ScalarKind, builtins.INTEGER_INDEX_BUILTIN.upper()))
         )
 
@@ -1536,7 +1536,7 @@ class LambdaToDataflow(eve.NodeVisitor):
             # initially, the storage for the connectivity tables is created as transient;
             # when the tables are used, the storage is changed to non-transient,
             # so the corresponding arrays are supposed to be allocated by the SDFG caller
-            offset_table = dace_utils.connectivity_identifier(offset)
+            offset_table = gtx_dace_utils.connectivity_identifier(offset)
             self.sdfg.arrays[offset_table].transient = False
             offset_table_node = self.state.add_access(offset_table)
 
@@ -1604,13 +1604,13 @@ class LambdaToDataflow(eve.NodeVisitor):
             # Therefore we handle `ListType` as a single-element array with shape (1,)
             # that will be accessed in a map expression on a local domain.
             assert isinstance(node.type.element_type, ts.ScalarType)
-            dc_dtype = dace_utils.as_dace_type(node.type.element_type)
+            dc_dtype = gtx_dace_utils.as_dace_type(node.type.element_type)
             # In order to ease the lowring of the parent expression on local dimension,
             # we represent the scalar value as a single-element 1D array.
             use_array = True
         else:
             assert isinstance(node.type, ts.ScalarType)
-            dc_dtype = dace_utils.as_dace_type(node.type)
+            dc_dtype = gtx_dace_utils.as_dace_type(node.type)
             use_array = False
 
         return self._construct_tasklet_result(dc_dtype, tasklet_node, "result", use_array=use_array)
@@ -1713,7 +1713,7 @@ class LambdaToDataflow(eve.NodeVisitor):
         )
 
     def visit_Literal(self, node: gtir.Literal) -> SymbolExpr:
-        dc_dtype = dace_utils.as_dace_type(node.type)
+        dc_dtype = gtx_dace_utils.as_dace_type(node.type)
         return SymbolExpr(node.value, dc_dtype)
 
     def visit_SymRef(
diff --git a/src/gt4py/next/program_processors/runners/dace_fieldview/gtir_python_codegen.py b/src/gt4py/next/program_processors/runners/dace/gtir_python_codegen.py
similarity index 100%
rename from src/gt4py/next/program_processors/runners/dace_fieldview/gtir_python_codegen.py
rename to src/gt4py/next/program_processors/runners/dace/gtir_python_codegen.py
diff --git a/src/gt4py/next/program_processors/runners/dace_fieldview/gtir_scan_translator.py b/src/gt4py/next/program_processors/runners/dace/gtir_scan_translator.py
similarity index 97%
rename from src/gt4py/next/program_processors/runners/dace_fieldview/gtir_scan_translator.py
rename to src/gt4py/next/program_processors/runners/dace/gtir_scan_translator.py
index 27551a68..d3d8e101 100644
--- a/src/gt4py/next/program_processors/runners/dace_fieldview/gtir_scan_translator.py
+++ b/src/gt4py/next/program_processors/runners/dace/gtir_scan_translator.py
@@ -30,18 +30,17 @@ from dace import subsets as dace_subsets
 from gt4py.next import common as gtx_common, utils as gtx_utils
 from gt4py.next.iterator import ir as gtir
 from gt4py.next.iterator.ir_utils import common_pattern_matcher as cpm, ir_makers as im
-from gt4py.next.program_processors.runners.dace_common import utility as dace_utils
-from gt4py.next.program_processors.runners.dace_fieldview import (
+from gt4py.next.program_processors.runners.dace import (
     gtir_builtin_translators as gtir_translators,
     gtir_dataflow,
     gtir_sdfg,
-    utility as dace_gtir_utils,
+    gtir_sdfg_utils,
 )
 from gt4py.next.type_system import type_info as ti, type_specifications as ts
 
 
 if TYPE_CHECKING:
-    from gt4py.next.program_processors.runners.dace_fieldview import gtir_sdfg
+    from gt4py.next.program_processors.runners.dace import gtir_sdfg
 
 
 def _parse_scan_fieldop_arg(
@@ -215,7 +214,7 @@ def _create_scan_field_operator(
             "fieldop",
             state,
             ndrange={
-                dace_gtir_utils.get_map_variable(dim): f"{lower_bound}:{upper_bound}"
+                gtir_sdfg_utils.get_map_variable(dim): f"{lower_bound}:{upper_bound}"
                 for dim, lower_bound, upper_bound in domain
                 if not sdfg_builder.is_column_axis(dim)
             },
@@ -234,7 +233,7 @@ def _create_scan_field_operator(
         # handle tuples of fields
         # the symbol name 'x' in the call below is not used, we only need
         # the tree structure of the `TupleType` definition to pass to `tree_map()`
-        output_symbol_tree = dace_gtir_utils.make_symbol_tree("x", node_type)
+        output_symbol_tree = gtir_sdfg_utils.make_symbol_tree("x", node_type)
         return gtx_utils.tree_map(
             lambda output_edge, output_sym: (
                 _create_scan_field_operator_impl(
@@ -317,7 +316,7 @@ def _lower_lambda_to_nested_sdfg(
 
     # the lambda expression, i.e. body of the scan, will be created inside a nested SDFG.
     nsdfg = dace.SDFG(sdfg_builder.unique_nsdfg_name(sdfg, "scan"))
-    nsdfg.debuginfo = dace_utils.debug_info(lambda_node, default=sdfg.debuginfo)
+    nsdfg.debuginfo = gtir_sdfg_utils.debug_info(lambda_node, default=sdfg.debuginfo)
     lambda_translator = sdfg_builder.setup_nested_context(
         lambda_node, nsdfg, lambda_symbols, lambda_field_offsets
     )
@@ -332,7 +331,7 @@ def _lower_lambda_to_nested_sdfg(
     scan_dim, scan_lower_bound, scan_upper_bound = scan_domain[0]
 
     # extract the scan loop range
-    scan_loop_var = dace_gtir_utils.get_map_variable(scan_dim)
+    scan_loop_var = gtir_sdfg_utils.get_map_variable(scan_dim)
 
     # in case the scan operator computes a list (not a scalar), we need to add an extra dimension
     def get_scan_output_shape(
@@ -358,7 +357,7 @@ def _lower_lambda_to_nested_sdfg(
     # This dataflow will write the initial value of the scan carry variable.
     init_state = nsdfg.add_state("scan_init", is_start_block=True)
     scan_carry_input = (
-        dace_gtir_utils.make_symbol_tree(scan_carry_symbol.id, scan_carry_symbol.type)
+        gtir_sdfg_utils.make_symbol_tree(scan_carry_symbol.id, scan_carry_symbol.type)
         if isinstance(scan_carry_symbol.type, ts.TupleType)
         else scan_carry_symbol
     )
@@ -620,7 +619,7 @@ def translate_scan(
     if isinstance(scan_carry_type, ts.TupleType):
         lambda_flat_outs = {
             str(sym.id): sym.type
-            for sym in dace_gtir_utils.flatten_tuple_fields(
+            for sym in gtir_sdfg_utils.flatten_tuple_fields(
                 _scan_output_name(scan_carry), scan_carry_type
             )
         }
diff --git a/src/gt4py/next/program_processors/runners/dace_fieldview/gtir_sdfg.py b/src/gt4py/next/program_processors/runners/dace/gtir_sdfg.py
similarity index 96%
rename from src/gt4py/next/program_processors/runners/dace_fieldview/gtir_sdfg.py
rename to src/gt4py/next/program_processors/runners/dace/gtir_sdfg.py
index 2139ffe5..b306a593 100644
--- a/src/gt4py/next/program_processors/runners/dace_fieldview/gtir_sdfg.py
+++ b/src/gt4py/next/program_processors/runners/dace/gtir_sdfg.py
@@ -31,10 +31,10 @@ from gt4py.next.iterator import ir as gtir
 from gt4py.next.iterator.ir_utils import common_pattern_matcher as cpm
 from gt4py.next.iterator.transforms import prune_casts as ir_prune_casts, symbol_ref_utils
 from gt4py.next.iterator.type_system import inference as gtir_type_inference
-from gt4py.next.program_processors.runners.dace_common import utility as dace_utils
-from gt4py.next.program_processors.runners.dace_fieldview import (
+from gt4py.next.program_processors.runners.dace import (
     gtir_builtin_translators,
-    utility as dace_gtir_utils,
+    gtir_sdfg_utils,
+    utils as gtx_dace_utils,
 )
 from gt4py.next.type_system import type_specifications as ts, type_translation as tt
 
@@ -332,17 +332,17 @@ class GTIRToSDFG(eve.NodeVisitor, SDFGBuilder):
             Two lists of symbols, one for the shape and the other for the strides of the array.
         """
         dc_dtype = gtir_builtin_translators.INDEX_DTYPE
-        neighbor_table_types = dace_utils.filter_connectivity_types(self.offset_provider_type)
+        neighbor_table_types = gtx_dace_utils.filter_connectivity_types(self.offset_provider_type)
         shape = [
             (
                 neighbor_table_types[dim.value].max_neighbors
                 if dim.kind == gtx_common.DimensionKind.LOCAL
-                else dace.symbol(dace_utils.field_size_symbol_name(name, i), dc_dtype)
+                else dace.symbol(gtx_dace_utils.field_size_symbol_name(name, i), dc_dtype)
             )
             for i, dim in enumerate(dims)
         ]
         strides = [
-            dace.symbol(dace_utils.field_stride_symbol_name(name, i), dc_dtype)
+            dace.symbol(gtx_dace_utils.field_stride_symbol_name(name, i), dc_dtype)
             for i in range(len(dims))
         ]
         return shape, strides
@@ -383,7 +383,7 @@ class GTIRToSDFG(eve.NodeVisitor, SDFGBuilder):
         """
         if isinstance(gt_type, ts.TupleType):
             tuple_fields = []
-            for sym in dace_gtir_utils.flatten_tuple_fields(name, gt_type):
+            for sym in gtir_sdfg_utils.flatten_tuple_fields(name, gt_type):
                 assert isinstance(sym.type, ts.DataType)
                 tuple_fields.extend(
                     self._add_storage(sdfg, symbolic_arguments, sym.id, sym.type, transient)
@@ -397,7 +397,7 @@ class GTIRToSDFG(eve.NodeVisitor, SDFGBuilder):
             if not isinstance(gt_type.dtype, ts.ScalarType):
                 raise ValueError(f"Field type '{gt_type.dtype}' not supported.")
             # handle default case: field with one or more dimensions
-            dc_dtype = dace_utils.as_dace_type(gt_type.dtype)
+            dc_dtype = gtx_dace_utils.as_dace_type(gt_type.dtype)
             # Use symbolic shape, which allows to invoke the program with fields of different size;
             # and symbolic strides, which enables decoupling the memory layout from generated code.
             sym_shape, sym_strides = self._make_array_shape_and_strides(name, gt_type.dims)
@@ -405,8 +405,8 @@ class GTIRToSDFG(eve.NodeVisitor, SDFGBuilder):
             return [(name, gt_type)]
 
         elif isinstance(gt_type, ts.ScalarType):
-            dc_dtype = dace_utils.as_dace_type(gt_type)
-            if dace_utils.is_field_symbol(name) or name in symbolic_arguments:
+            dc_dtype = gtx_dace_utils.as_dace_type(gt_type)
+            if gtx_dace_utils.is_field_symbol(name) or name in symbolic_arguments:
                 if name in sdfg.symbols:
                     # Sometimes, when the field domain is implicitly derived from the
                     # field domain, the gt4py lowering adds the field size as a scalar
@@ -416,7 +416,7 @@ class GTIRToSDFG(eve.NodeVisitor, SDFGBuilder):
                     # created by `_make_array_shape_and_strides()`, when allocating
                     # storage for field arguments. We assume that the scalar argument
                     # for field size, if present, always follows the field argument.
-                    assert dace_utils.is_field_symbol(name)
+                    assert gtx_dace_utils.is_field_symbol(name)
                     if sdfg.symbols[name].dtype != dc_dtype:
                         raise ValueError(
                             f"Type mismatch on argument {name}: got {dc_dtype}, expected {sdfg.symbols[name].dtype}."
@@ -501,7 +501,7 @@ class GTIRToSDFG(eve.NodeVisitor, SDFGBuilder):
             self.global_symbols[pname] = param.type
 
         # add SDFG storage for connectivity tables
-        for offset, connectivity_type in dace_utils.filter_connectivity_types(
+        for offset, connectivity_type in gtx_dace_utils.filter_connectivity_types(
             self.offset_provider_type
         ).items():
             scalar_type = tt.from_dtype(connectivity_type.dtype)
@@ -514,7 +514,10 @@ class GTIRToSDFG(eve.NodeVisitor, SDFGBuilder):
             # the connectivity tables that are not used. The remaining unused transient arrays
             # are removed by the dace simplify pass.
             self._add_storage(
-                sdfg, symbolic_arguments, dace_utils.connectivity_identifier(offset), gt_type
+                sdfg,
+                symbolic_arguments,
+                gtx_dace_utils.connectivity_identifier(offset),
+                gt_type,
             )
 
         # the list of all sdfg arguments (aka non-transient arrays) which include tuple-element fields
@@ -537,7 +540,7 @@ class GTIRToSDFG(eve.NodeVisitor, SDFGBuilder):
         assert len(self.field_offsets) == 0
 
         sdfg = dace.SDFG(node.id)
-        sdfg.debuginfo = dace_utils.debug_info(node)
+        sdfg.debuginfo = gtir_sdfg_utils.debug_info(node)
 
         # start block of the stateful graph
         entry_state = sdfg.add_state("program_entry", is_start_block=True)
@@ -560,7 +563,7 @@ class GTIRToSDFG(eve.NodeVisitor, SDFGBuilder):
         for i, stmt in enumerate(node.body):
             # include `debuginfo` only for `ir.Program` and `ir.Stmt` nodes: finer granularity would be too messy
             head_state = sdfg.add_state_after(head_state, f"stmt_{i}")
-            head_state._debuginfo = dace_utils.debug_info(stmt, default=sdfg.debuginfo)
+            head_state._debuginfo = gtir_sdfg_utils.debug_info(stmt, default=sdfg.debuginfo)
             head_state = self.visit(stmt, sdfg=sdfg, state=head_state)
 
         # remove unused connectivity tables (by design, arrays are marked as non-transient when they are used)
@@ -568,7 +571,7 @@ class GTIRToSDFG(eve.NodeVisitor, SDFGBuilder):
             unused_connectivities = [
                 data
                 for data, datadesc in nsdfg.arrays.items()
-                if dace_utils.is_connectivity_identifier(data, self.offset_provider_type)
+                if gtx_dace_utils.is_connectivity_identifier(data, self.offset_provider_type)
                 and datadesc.transient
             ]
             for data in unused_connectivities:
@@ -757,7 +760,7 @@ class GTIRToSDFG(eve.NodeVisitor, SDFGBuilder):
                 elif field_domain_offset := self.field_offsets.get(p_name, None):
                     return {p_name: field_domain_offset}
             elif isinstance(p_type, ts.TupleType):
-                tsyms = dace_gtir_utils.flatten_tuple_fields(p_name, p_type)
+                tsyms = gtir_sdfg_utils.flatten_tuple_fields(p_name, p_type)
                 return functools.reduce(
                     lambda field_offsets, sym: (
                         field_offsets | get_field_domain_offset(sym.id, sym.type)  # type: ignore[arg-type]
@@ -774,7 +777,7 @@ class GTIRToSDFG(eve.NodeVisitor, SDFGBuilder):
 
         # lower let-statement lambda node as a nested SDFG
         nsdfg = dace.SDFG(name=self.unique_nsdfg_name(sdfg, "lambda"))
-        nsdfg.debuginfo = dace_utils.debug_info(node, default=sdfg.debuginfo)
+        nsdfg.debuginfo = gtir_sdfg_utils.debug_info(node, default=sdfg.debuginfo)
         lambda_translator = self.setup_nested_context(
             node.expr, nsdfg, lambda_symbols, lambda_field_offsets
         )
@@ -792,8 +795,8 @@ class GTIRToSDFG(eve.NodeVisitor, SDFGBuilder):
         # we they are stored as non-transient array and scalar objects.
         #
         connectivity_arrays = {
-            dace_utils.connectivity_identifier(offset)
-            for offset in dace_utils.filter_connectivity_types(self.offset_provider_type)
+            gtx_dace_utils.connectivity_identifier(offset)
+            for offset in gtx_dace_utils.filter_connectivity_types(self.offset_provider_type)
         }
 
         input_memlets = {}
@@ -858,7 +861,7 @@ class GTIRToSDFG(eve.NodeVisitor, SDFGBuilder):
             inputs=set(input_memlets.keys()),
             outputs=lambda_outputs,
             symbol_mapping=nsdfg_symbols_mapping,
-            debuginfo=dace_utils.debug_info(node, default=sdfg.debuginfo),
+            debuginfo=gtir_sdfg_utils.debug_info(node, default=sdfg.debuginfo),
         )
 
         for connector, memlet in input_memlets.items():
@@ -965,7 +968,7 @@ def build_sdfg_from_gtir(
     # such as arrays and scalars. GT4Py uses a unicode symbols ('ᐞ') as name
     # separator in the SSA pass, which generates invalid symbols for DaCe.
     # Here we find new names for invalid symbols present in the IR.
-    ir = dace_gtir_utils.replace_invalid_symbols(ir)
+    ir = gtir_sdfg_utils.replace_invalid_symbols(ir)
 
     sdfg_genenerator = GTIRToSDFG(offset_provider_type, column_axis)
     sdfg = sdfg_genenerator.visit(ir)
diff --git a/src/gt4py/next/program_processors/runners/dace_fieldview/utility.py b/src/gt4py/next/program_processors/runners/dace/gtir_sdfg_utils.py
similarity index 86%
rename from src/gt4py/next/program_processors/runners/dace_fieldview/utility.py
rename to src/gt4py/next/program_processors/runners/dace/gtir_sdfg_utils.py
index 61215291..9a27cad2 100644
--- a/src/gt4py/next/program_processors/runners/dace_fieldview/utility.py
+++ b/src/gt4py/next/program_processors/runners/dace/gtir_sdfg_utils.py
@@ -8,7 +8,7 @@
 
 from __future__ import annotations
 
-from typing import Dict, TypeVar
+from typing import Dict, Optional, TypeVar
 
 import dace
 
@@ -19,6 +19,22 @@ from gt4py.next.iterator.ir_utils import ir_makers as im
 from gt4py.next.type_system import type_specifications as ts
 
 
+def debug_info(
+    node: gtir.Node, *, default: Optional[dace.dtypes.DebugInfo] = None
+) -> Optional[dace.dtypes.DebugInfo]:
+    """Include the GT4Py node location as debug information in the corresponding SDFG nodes."""
+    location = node.location
+    if location:
+        return dace.dtypes.DebugInfo(
+            start_line=location.line,
+            start_column=location.column if location.column else 0,
+            end_line=location.end_line if location.end_line else -1,
+            end_column=location.end_column if location.end_column else 0,
+            filename=location.filename,
+        )
+    return default
+
+
 def get_map_variable(dim: gtx_common.Dimension) -> str:
     """
     Format map variable name based on the naming convention for application-specific SDFG transformations.
diff --git a/src/gt4py/next/program_processors/runners/dace_fieldview/program.py b/src/gt4py/next/program_processors/runners/dace/program.py
similarity index 91%
rename from src/gt4py/next/program_processors/runners/dace_fieldview/program.py
rename to src/gt4py/next/program_processors/runners/dace/program.py
index 7f809152..78016db0 100644
--- a/src/gt4py/next/program_processors/runners/dace_fieldview/program.py
+++ b/src/gt4py/next/program_processors/runners/dace/program.py
@@ -20,7 +20,7 @@ from gt4py.next.ffront import decorator
 from gt4py.next.iterator import ir as itir, transforms as itir_transforms
 from gt4py.next.iterator.transforms import extractors as extractors
 from gt4py.next.otf import arguments, recipes, toolchain
-from gt4py.next.program_processors.runners.dace_common import utility as dace_utils
+from gt4py.next.program_processors.runners.dace import utils as gtx_dace_utils
 from gt4py.next.type_system import type_specifications as ts
 
 
@@ -152,15 +152,15 @@ class Program(decorator.Program, dace.frontend.python.common.SDFGConvertible):
             in_arrays_with_id = [
                 (name, conn_id)
                 for name in with_table
-                if (conn_id := dace_utils.connectivity_identifier(name))
+                if (conn_id := gtx_dace_utils.connectivity_identifier(name))
                 in self.sdfg_closure_cache["arrays"]
             ]
             in_arrays = (name for name, _ in in_arrays_with_id)
             name_axis = list(itertools.product(in_arrays, [0, 1]))
 
             def size_symbol_name(name: str, axis: int) -> str:
-                return dace_utils.field_size_symbol_name(
-                    dace_utils.connectivity_identifier(name), axis
+                return gtx_dace_utils.field_size_symbol_name(
+                    gtx_dace_utils.connectivity_identifier(name), axis
                 )
 
             connectivity_tables_size_symbols = {
@@ -169,8 +169,8 @@ class Program(decorator.Program, dace.frontend.python.common.SDFGConvertible):
             }
 
             def stride_symbol_name(name: str, axis: int) -> str:
-                return dace_utils.field_stride_symbol_name(
-                    dace_utils.connectivity_identifier(name), axis
+                return gtx_dace_utils.field_stride_symbol_name(
+                    gtx_dace_utils.connectivity_identifier(name), axis
                 )
 
             connectivity_table_stride_symbols = {
@@ -196,12 +196,12 @@ class Program(decorator.Program, dace.frontend.python.common.SDFGConvertible):
                     self.connectivity_tables_data_descriptors[conn_id] = dace.data.Array(
                         dtype=dace.dtypes.dtype_to_typeclass(conn.dtype.dtype.type),
                         shape=[
-                            symbols[dace_utils.field_size_symbol_name(conn_id, 0)],
-                            symbols[dace_utils.field_size_symbol_name(conn_id, 1)],
+                            symbols[gtx_dace_utils.field_size_symbol_name(conn_id, 0)],
+                            symbols[gtx_dace_utils.field_size_symbol_name(conn_id, 1)],
                         ],
                         strides=[
-                            symbols[dace_utils.field_stride_symbol_name(conn_id, 0)],
-                            symbols[dace_utils.field_stride_symbol_name(conn_id, 1)],
+                            symbols[gtx_dace_utils.field_stride_symbol_name(conn_id, 0)],
+                            symbols[gtx_dace_utils.field_stride_symbol_name(conn_id, 1)],
                         ],
                         storage=Program.connectivity_tables_data_descriptors["storage"],
                     )
@@ -221,7 +221,7 @@ def _crosscheck_dace_parsing(dace_parsed_args: list[Any], gt4py_program_args: li
     ):
         match dace_parsed_arg:
             case dace.data.Scalar():
-                assert dace_parsed_arg.dtype == dace_utils.as_dace_type(gt4py_program_arg)
+                assert dace_parsed_arg.dtype == gtx_dace_utils.as_dace_type(gt4py_program_arg)
             case bool() | np.bool_():
                 assert isinstance(gt4py_program_arg, ts.ScalarType)
                 assert gt4py_program_arg.kind == ts.ScalarKind.BOOL
@@ -238,7 +238,7 @@ def _crosscheck_dace_parsing(dace_parsed_args: list[Any], gt4py_program_args: li
                 assert isinstance(gt4py_program_arg, ts.FieldType)
                 assert isinstance(gt4py_program_arg.dtype, ts.ScalarType)
                 assert len(dace_parsed_arg.shape) == len(gt4py_program_arg.dims)
-                assert dace_parsed_arg.dtype == dace_utils.as_dace_type(gt4py_program_arg.dtype)
+                assert dace_parsed_arg.dtype == gtx_dace_utils.as_dace_type(gt4py_program_arg.dtype)
             case dace.data.Structure() | dict() | collections.OrderedDict():
                 # offset provider
                 pass
diff --git a/src/gt4py/next/program_processors/runners/dace_common/dace_backend.py b/src/gt4py/next/program_processors/runners/dace/sdfg_callable.py
similarity index 98%
rename from src/gt4py/next/program_processors/runners/dace_common/dace_backend.py
rename to src/gt4py/next/program_processors/runners/dace/sdfg_callable.py
index 387619c6..ecd23619 100644
--- a/src/gt4py/next/program_processors/runners/dace_common/dace_backend.py
+++ b/src/gt4py/next/program_processors/runners/dace/sdfg_callable.py
@@ -15,7 +15,7 @@ import numpy as np
 from gt4py._core import definitions as core_defs
 from gt4py.next import common as gtx_common
 
-from . import utility as dace_utils
+from . import utils as gtx_dace_utils
 
 
 try:
@@ -116,7 +116,7 @@ def get_sdfg_conn_args(
     connectivity_args = {}
     for offset, connectivity in offset_provider.items():
         if gtx_common.is_neighbor_table(connectivity):
-            param = dace_utils.connectivity_identifier(offset)
+            param = gtx_dace_utils.connectivity_identifier(offset)
             if param in sdfg.arrays:
                 connectivity_args[param] = _ensure_is_on_device(connectivity.ndarray, device)
 
diff --git a/src/gt4py/next/program_processors/runners/dace_fieldview/transformations/__init__.py b/src/gt4py/next/program_processors/runners/dace/transformations/__init__.py
similarity index 96%
rename from src/gt4py/next/program_processors/runners/dace_fieldview/transformations/__init__.py
rename to src/gt4py/next/program_processors/runners/dace/transformations/__init__.py
index 0902bd66..c8e1cf29 100644
--- a/src/gt4py/next/program_processors/runners/dace_fieldview/transformations/__init__.py
+++ b/src/gt4py/next/program_processors/runners/dace/transformations/__init__.py
@@ -42,7 +42,7 @@ from .strides import (
     gt_propagate_strides_from_access_node,
     gt_propagate_strides_of,
 )
-from .util import gt_find_constant_arguments, gt_make_transients_persistent
+from .utils import gt_find_constant_arguments, gt_make_transients_persistent
 
 
 __all__ = [
diff --git a/src/gt4py/next/program_processors/runners/dace_fieldview/transformations/auto_optimize.py b/src/gt4py/next/program_processors/runners/dace/transformations/auto_optimize.py
similarity index 99%
rename from src/gt4py/next/program_processors/runners/dace_fieldview/transformations/auto_optimize.py
rename to src/gt4py/next/program_processors/runners/dace/transformations/auto_optimize.py
index 4a06d2f4..849730db 100644
--- a/src/gt4py/next/program_processors/runners/dace_fieldview/transformations/auto_optimize.py
+++ b/src/gt4py/next/program_processors/runners/dace/transformations/auto_optimize.py
@@ -15,9 +15,7 @@ from dace.transformation import dataflow as dace_dataflow
 from dace.transformation.auto import auto_optimize as dace_aoptimize
 
 from gt4py.next import common as gtx_common
-from gt4py.next.program_processors.runners.dace_fieldview import (
-    transformations as gtx_transformations,
-)
+from gt4py.next.program_processors.runners.dace import transformations as gtx_transformations
 
 
 def gt_auto_optimize(
diff --git a/src/gt4py/next/program_processors/runners/dace_fieldview/transformations/gpu_utils.py b/src/gt4py/next/program_processors/runners/dace/transformations/gpu_utils.py
similarity index 99%
rename from src/gt4py/next/program_processors/runners/dace_fieldview/transformations/gpu_utils.py
rename to src/gt4py/next/program_processors/runners/dace/transformations/gpu_utils.py
index 7b14144e..8bae56cd 100644
--- a/src/gt4py/next/program_processors/runners/dace_fieldview/transformations/gpu_utils.py
+++ b/src/gt4py/next/program_processors/runners/dace/transformations/gpu_utils.py
@@ -22,9 +22,7 @@ from dace import (
 from dace.codegen.targets import cpp as dace_cpp
 from dace.sdfg import nodes as dace_nodes
 
-from gt4py.next.program_processors.runners.dace_fieldview import (
-    transformations as gtx_transformations,
-)
+from gt4py.next.program_processors.runners.dace import transformations as gtx_transformations
 
 
 def gt_gpu_transformation(
diff --git a/src/gt4py/next/program_processors/runners/dace_fieldview/transformations/local_double_buffering.py b/src/gt4py/next/program_processors/runners/dace/transformations/local_double_buffering.py
similarity index 98%
rename from src/gt4py/next/program_processors/runners/dace_fieldview/transformations/local_double_buffering.py
rename to src/gt4py/next/program_processors/runners/dace/transformations/local_double_buffering.py
index 52f1de3d..02ecbe28 100644
--- a/src/gt4py/next/program_processors/runners/dace_fieldview/transformations/local_double_buffering.py
+++ b/src/gt4py/next/program_processors/runners/dace/transformations/local_double_buffering.py
@@ -18,9 +18,7 @@ from dace import (
 )
 from dace.sdfg import nodes as dace_nodes
 
-from gt4py.next.program_processors.runners.dace_fieldview import (
-    transformations as gtx_transformations,
-)
+from gt4py.next.program_processors.runners.dace import transformations as gtx_transformations
 
 
 def gt_create_local_double_buffering(
@@ -243,7 +241,7 @@ def _check_if_map_must_be_handled_classify_adjacent_access_node(
 
     # Currently we do not handle view, as they need to be traced.
     #  TODO(phimuell): Implement
-    if gtx_transformations.util.is_view(data_desc, sdfg):
+    if gtx_transformations.utils.is_view(data_desc, sdfg):
         return False
 
     # TODO(phimuell): Check if there is a access node on the inner side, then we do not have to do it.
@@ -357,7 +355,7 @@ def _check_if_map_must_be_handled(
         if (
             len(inner_read_edges) == 1
             and isinstance(inner_read_edges[0].dst, dace_nodes.AccessNode)
-            and not gtx_transformations.util.is_view(inner_read_edges[0].dst, sdfg)
+            and not gtx_transformations.utils.is_view(inner_read_edges[0].dst, sdfg)
         ):
             inout_datas.pop(inout_data_name)
             continue
diff --git a/src/gt4py/next/program_processors/runners/dace_fieldview/transformations/loop_blocking.py b/src/gt4py/next/program_processors/runners/dace/transformations/loop_blocking.py
similarity index 99%
rename from src/gt4py/next/program_processors/runners/dace_fieldview/transformations/loop_blocking.py
rename to src/gt4py/next/program_processors/runners/dace/transformations/loop_blocking.py
index 27b6c680..344b0b8c 100644
--- a/src/gt4py/next/program_processors/runners/dace_fieldview/transformations/loop_blocking.py
+++ b/src/gt4py/next/program_processors/runners/dace/transformations/loop_blocking.py
@@ -19,7 +19,7 @@ from dace.sdfg import graph as dace_graph, nodes as dace_nodes
 from dace.transformation import helpers as dace_helpers
 
 from gt4py.next import common as gtx_common
-from gt4py.next.program_processors.runners.dace_fieldview import utility as gtx_dace_fieldview_util
+from gt4py.next.program_processors.runners.dace import gtir_sdfg_utils
 
 
 @dace_properties.make_properties
@@ -83,7 +83,7 @@ class LoopBlocking(dace_transformation.SingleStateTransformation):
     ) -> None:
         super().__init__()
         if isinstance(blocking_parameter, gtx_common.Dimension):
-            blocking_parameter = gtx_dace_fieldview_util.get_map_variable(blocking_parameter)
+            blocking_parameter = gtir_sdfg_utils.get_map_variable(blocking_parameter)
         if blocking_parameter is not None:
             self.blocking_parameter = blocking_parameter
         if blocking_size is not None:
diff --git a/src/gt4py/next/program_processors/runners/dace_fieldview/transformations/map_fusion_helper.py b/src/gt4py/next/program_processors/runners/dace/transformations/map_fusion_helper.py
similarity index 100%
rename from src/gt4py/next/program_processors/runners/dace_fieldview/transformations/map_fusion_helper.py
rename to src/gt4py/next/program_processors/runners/dace/transformations/map_fusion_helper.py
diff --git a/src/gt4py/next/program_processors/runners/dace_fieldview/transformations/map_fusion_parallel.py b/src/gt4py/next/program_processors/runners/dace/transformations/map_fusion_parallel.py
similarity index 100%
rename from src/gt4py/next/program_processors/runners/dace_fieldview/transformations/map_fusion_parallel.py
rename to src/gt4py/next/program_processors/runners/dace/transformations/map_fusion_parallel.py
diff --git a/src/gt4py/next/program_processors/runners/dace_fieldview/transformations/map_fusion_serial.py b/src/gt4py/next/program_processors/runners/dace/transformations/map_fusion_serial.py
similarity index 100%
rename from src/gt4py/next/program_processors/runners/dace_fieldview/transformations/map_fusion_serial.py
rename to src/gt4py/next/program_processors/runners/dace/transformations/map_fusion_serial.py
diff --git a/src/gt4py/next/program_processors/runners/dace_fieldview/transformations/map_orderer.py b/src/gt4py/next/program_processors/runners/dace/transformations/map_orderer.py
similarity index 97%
rename from src/gt4py/next/program_processors/runners/dace_fieldview/transformations/map_orderer.py
rename to src/gt4py/next/program_processors/runners/dace/transformations/map_orderer.py
index 8fb41c7d..23dcbf8e 100644
--- a/src/gt4py/next/program_processors/runners/dace_fieldview/transformations/map_orderer.py
+++ b/src/gt4py/next/program_processors/runners/dace/transformations/map_orderer.py
@@ -13,7 +13,7 @@ from dace import properties as dace_properties, transformation as dace_transform
 from dace.sdfg import nodes as dace_nodes
 
 from gt4py.next import common as gtx_common
-from gt4py.next.program_processors.runners.dace_fieldview import utility as gtx_dace_fieldview_util
+from gt4py.next.program_processors.runners.dace import gtir_sdfg_utils
 
 
 def gt_set_iteration_order(
@@ -107,7 +107,7 @@ class MapIterationOrder(dace_transformation.SingleStateTransformation):
             self.leading_dims = [
                 leading_dim
                 if isinstance(leading_dim, str)
-                else gtx_dace_fieldview_util.get_map_variable(leading_dim)
+                else gtir_sdfg_utils.get_map_variable(leading_dim)
                 for leading_dim in leading_dims
             ]
 
diff --git a/src/gt4py/next/program_processors/runners/dace_fieldview/transformations/map_promoter.py b/src/gt4py/next/program_processors/runners/dace/transformations/map_promoter.py
similarity index 99%
rename from src/gt4py/next/program_processors/runners/dace_fieldview/transformations/map_promoter.py
rename to src/gt4py/next/program_processors/runners/dace/transformations/map_promoter.py
index 46d46c4b..90ad67e7 100644
--- a/src/gt4py/next/program_processors/runners/dace_fieldview/transformations/map_promoter.py
+++ b/src/gt4py/next/program_processors/runners/dace/transformations/map_promoter.py
@@ -17,9 +17,7 @@ from dace import (
 )
 from dace.sdfg import nodes as dace_nodes
 
-from gt4py.next.program_processors.runners.dace_fieldview import (
-    transformations as gtx_transformations,
-)
+from gt4py.next.program_processors.runners.dace import transformations as gtx_transformations
 
 
 @dace_properties.make_properties
diff --git a/src/gt4py/next/program_processors/runners/dace_fieldview/transformations/simplify.py b/src/gt4py/next/program_processors/runners/dace/transformations/simplify.py
similarity index 98%
rename from src/gt4py/next/program_processors/runners/dace_fieldview/transformations/simplify.py
rename to src/gt4py/next/program_processors/runners/dace/transformations/simplify.py
index cc845505..e798df45 100644
--- a/src/gt4py/next/program_processors/runners/dace_fieldview/transformations/simplify.py
+++ b/src/gt4py/next/program_processors/runners/dace/transformations/simplify.py
@@ -27,9 +27,7 @@ from dace.transformation import (
     passes as dace_passes,
 )
 
-from gt4py.next.program_processors.runners.dace_fieldview import (
-    transformations as gtx_transformations,
-)
+from gt4py.next.program_processors.runners.dace import transformations as gtx_transformations
 
 
 GT_SIMPLIFY_DEFAULT_SKIP_SET: Final[set[str]] = {"ScalarToSymbolPromotion", "ConstantPropagation"}
@@ -336,7 +334,7 @@ class GT4PyGlobalSelfCopyElimination(dace_transformation.SingleStateTransformati
         write_g: dace_nodes.AccessNode = self.node_write_g
         tmp_node: dace_nodes.AccessNode = self.node_tmp
 
-        return gtx_transformations.util.is_accessed_downstream(
+        return gtx_transformations.utils.is_accessed_downstream(
             start_state=start_state,
             sdfg=sdfg,
             data_to_look=data_to_look,
@@ -591,7 +589,7 @@ class DistributedBufferRelocator(dace_transformation.Pass):
             for def_node, def_state in def_locations:
                 # Test if `temp_storage` is only accessed where it is defined and
                 #  where it is written back.
-                if gtx_transformations.util.is_accessed_downstream(
+                if gtx_transformations.utils.is_accessed_downstream(
                     start_state=def_state,
                     sdfg=sdfg,
                     data_to_look=wb_node.data,
@@ -607,7 +605,7 @@ class DistributedBufferRelocator(dace_transformation.Pass):
                 global_nodes_in_def_state = {
                     dnode for dnode in def_state.data_nodes() if dnode.data == global_data_name
                 }
-                if gtx_transformations.util.is_accessed_downstream(
+                if gtx_transformations.utils.is_accessed_downstream(
                     start_state=def_state,
                     sdfg=sdfg,
                     data_to_look=global_data_name,
@@ -891,7 +889,7 @@ class GT4PyMoveTaskletIntoMap(dace_transformation.SingleStateTransformation):
         # The data is no longer referenced in this state, so we can potentially
         #  remove
         if graph.out_degree(access_node) == 0:
-            if not gtx_transformations.util.is_accessed_downstream(
+            if not gtx_transformations.utils.is_accessed_downstream(
                 start_state=graph,
                 sdfg=sdfg,
                 data_to_look=access_node.data,
@@ -1001,7 +999,7 @@ class GT4PyMapBufferElimination(dace_transformation.SingleStateTransformation):
             return False
         if graph.in_degree(tmp_ac) != 1:
             return False
-        if any(gtx_transformations.util.is_view(ac, sdfg) for ac in [tmp_ac, glob_ac]):
+        if any(gtx_transformations.utils.is_view(ac, sdfg) for ac in [tmp_ac, glob_ac]):
             return False
         if len(glob_desc.shape) != len(tmp_desc.shape):
             return False
@@ -1017,7 +1015,7 @@ class GT4PyMapBufferElimination(dace_transformation.SingleStateTransformation):
         # Test if `tmp` is only anywhere else, this is important for removing it.
         if graph.out_degree(tmp_ac) != 1:
             return False
-        if gtx_transformations.util.is_accessed_downstream(
+        if gtx_transformations.utils.is_accessed_downstream(
             start_state=graph,
             sdfg=sdfg,
             data_to_look=tmp_ac.data,
@@ -1067,7 +1065,7 @@ class GT4PyMapBufferElimination(dace_transformation.SingleStateTransformation):
 
             # Find the source of this data, if it is a view we trace it to
             #  its origin.
-            src_node: dace_nodes.AccessNode = gtx_transformations.util.track_view(
+            src_node: dace_nodes.AccessNode = gtx_transformations.utils.track_view(
                 in_edge.src, state, sdfg
             )
 
@@ -1089,7 +1087,7 @@ class GT4PyMapBufferElimination(dace_transformation.SingleStateTransformation):
         # Currently the only test that we do is, if we have a view, then we
         #  are not point wise.
         # TODO(phimuell): Improve/implement this.
-        return any(gtx_transformations.util.is_view(node, sdfg) for node in conflicting_inputs)
+        return any(gtx_transformations.utils.is_view(node, sdfg) for node in conflicting_inputs)
 
     def apply(
         self,
diff --git a/src/gt4py/next/program_processors/runners/dace_fieldview/transformations/strides.py b/src/gt4py/next/program_processors/runners/dace/transformations/strides.py
similarity index 99%
rename from src/gt4py/next/program_processors/runners/dace_fieldview/transformations/strides.py
rename to src/gt4py/next/program_processors/runners/dace/transformations/strides.py
index d1bf8fe2..9af76e5b 100644
--- a/src/gt4py/next/program_processors/runners/dace_fieldview/transformations/strides.py
+++ b/src/gt4py/next/program_processors/runners/dace/transformations/strides.py
@@ -12,9 +12,7 @@ import dace
 from dace import data as dace_data
 from dace.sdfg import nodes as dace_nodes
 
-from gt4py.next.program_processors.runners.dace_fieldview import (
-    transformations as gtx_transformations,
-)
+from gt4py.next.program_processors.runners.dace import transformations as gtx_transformations
 
 
 PropagatedStrideRecord: TypeAlias = tuple[str, dace_nodes.NestedSDFG]
@@ -653,7 +651,7 @@ def _gt_find_toplevel_data_accesses(
                 top_level_data[data].append((state, dnode))
                 continue
 
-            elif gtx_transformations.util.is_view(dnode, sdfg):
+            elif gtx_transformations.utils.is_view(dnode, sdfg):
                 # The AccessNode refers to a View so we ignore it anyway.
                 continue
 
diff --git a/src/gt4py/next/program_processors/runners/dace_fieldview/transformations/util.py b/src/gt4py/next/program_processors/runners/dace/transformations/utils.py
similarity index 98%
rename from src/gt4py/next/program_processors/runners/dace_fieldview/transformations/util.py
rename to src/gt4py/next/program_processors/runners/dace/transformations/utils.py
index 29c099ee..87308061 100644
--- a/src/gt4py/next/program_processors/runners/dace_fieldview/transformations/util.py
+++ b/src/gt4py/next/program_processors/runners/dace/transformations/utils.py
@@ -14,7 +14,7 @@ import dace
 from dace import data as dace_data
 from dace.sdfg import nodes as dace_nodes
 
-from gt4py.next.program_processors.runners.dace_common import utility as dace_utils
+from gt4py.next.program_processors.runners.dace import utils as gtx_dace_utils
 
 
 def gt_make_transients_persistent(
@@ -114,7 +114,7 @@ def gt_find_constant_arguments(
     ret_value: dict[str, Any] = {}
 
     for name, value in call_args.items():
-        if name in include or (dace_utils.is_field_symbol(name) and value == 1):
+        if name in include or (gtx_dace_utils.is_field_symbol(name) and value == 1):
             ret_value[name] = value
 
     return ret_value
diff --git a/src/gt4py/next/program_processors/runners/dace_common/utility.py b/src/gt4py/next/program_processors/runners/dace/utils.py
similarity index 79%
rename from src/gt4py/next/program_processors/runners/dace_common/utility.py
rename to src/gt4py/next/program_processors/runners/dace/utils.py
index 4a3e5d4e..cca0c001 100644
--- a/src/gt4py/next/program_processors/runners/dace_common/utility.py
+++ b/src/gt4py/next/program_processors/runners/dace/utils.py
@@ -9,12 +9,11 @@
 from __future__ import annotations
 
 import re
-from typing import Final, Literal, Optional
+from typing import Final, Literal
 
 import dace
 
 from gt4py.next import common as gtx_common
-from gt4py.next.iterator import ir as gtir
 from gt4py.next.type_system import type_specifications as ts
 
 
@@ -78,22 +77,6 @@ def is_field_symbol(name: str) -> bool:
     return FIELD_SYMBOL_RE.match(name) is not None
 
 
-def debug_info(
-    node: gtir.Node, *, default: Optional[dace.dtypes.DebugInfo] = None
-) -> Optional[dace.dtypes.DebugInfo]:
-    """Include the GT4Py node location as debug information in the corresponding SDFG nodes."""
-    location = node.location
-    if location:
-        return dace.dtypes.DebugInfo(
-            start_line=location.line,
-            start_column=location.column if location.column else 0,
-            end_line=location.end_line if location.end_line else -1,
-            end_column=location.end_column if location.end_column else 0,
-            filename=location.filename,
-        )
-    return default
-
-
 def filter_connectivity_types(
     offset_provider_type: gtx_common.OffsetProviderType,
 ) -> dict[str, gtx_common.NeighborConnectivityType]:
diff --git a/src/gt4py/next/program_processors/runners/dace/workflow/__init__.py b/src/gt4py/next/program_processors/runners/dace/workflow/__init__.py
new file mode 100644
index 00000000..4d825c0c
--- /dev/null
+++ b/src/gt4py/next/program_processors/runners/dace/workflow/__init__.py
@@ -0,0 +1,20 @@
+# GT4Py - GridTools Framework
+#
+# Copyright (c) 2014-2024, ETH Zurich
+# All rights reserved.
+#
+# Please, refer to the LICENSE file in the root directory.
+# SPDX-License-Identifier: BSD-3-Clause
+
+"""Implements the On-The-Fly (OTF) compilation workflow for the GTIR-DaCe backend.
+
+The main module is `backend`, that exports the backends for CPU and GPU devices.
+The `backend` module uses `factory` to define a workflow that implements the
+`OTFCompileWorkflow` recipe. The different stages are implemeted in separate modules:
+- `translation` for lowering of GTIR to SDFG and applying SDFG transformations
+- `compilation` for compiling the SDFG into a program
+- `decoration` to parse the program arguments and pass them to the program call
+
+The GTIR-DaCe backend factory extends `CachedBackendFactory`, thus it provides
+caching of the GTIR program.
+"""
diff --git a/src/gt4py/next/program_processors/runners/dace.py b/src/gt4py/next/program_processors/runners/dace/workflow/backend.py
similarity index 72%
rename from src/gt4py/next/program_processors/runners/dace.py
rename to src/gt4py/next/program_processors/runners/dace/workflow/backend.py
index b7f419a7..55d71227 100644
--- a/src/gt4py/next/program_processors/runners/dace.py
+++ b/src/gt4py/next/program_processors/runners/dace/workflow/backend.py
@@ -6,16 +6,18 @@
 # Please, refer to the LICENSE file in the root directory.
 # SPDX-License-Identifier: BSD-3-Clause
 
+from __future__ import annotations
+
 import factory
 
-import gt4py._core.definitions as core_defs
 import gt4py.next.allocators as next_allocators
+from gt4py._core import definitions as core_defs
 from gt4py.next import backend
 from gt4py.next.otf import stages, workflow
-from gt4py.next.program_processors.runners.dace_fieldview import workflow as dace_fieldview_workflow
+from gt4py.next.program_processors.runners.dace.workflow.factory import DaCeWorkflowFactory
 
 
-class DaCeFieldviewBackendFactory(factory.Factory):
+class DaCeBackendFactory(factory.Factory):
     class Meta:
         model = backend.Backend
 
@@ -37,7 +39,7 @@ class DaCeFieldviewBackendFactory(factory.Factory):
         device_type = core_defs.DeviceType.CPU
         hash_function = stages.compilation_hash
         otf_workflow = factory.SubFactory(
-            dace_fieldview_workflow.DaCeWorkflowFactory,
+            DaCeWorkflowFactory,
             device_type=factory.SelfAttribute("..device_type"),
             auto_optimize=factory.SelfAttribute("..auto_optimize"),
         )
@@ -52,8 +54,8 @@ class DaCeFieldviewBackendFactory(factory.Factory):
     transforms = backend.DEFAULT_TRANSFORMS
 
 
-run_dace_cpu = DaCeFieldviewBackendFactory(cached=True, auto_optimize=True)
-run_dace_cpu_noopt = DaCeFieldviewBackendFactory(cached=True, auto_optimize=False)
+run_dace_cpu = DaCeBackendFactory(cached=True, auto_optimize=True)
+run_dace_cpu_noopt = DaCeBackendFactory(cached=True, auto_optimize=False)
 
-run_dace_gpu = DaCeFieldviewBackendFactory(gpu=True, cached=True, auto_optimize=True)
-run_dace_gpu_noopt = DaCeFieldviewBackendFactory(gpu=True, cached=True, auto_optimize=False)
+run_dace_gpu = DaCeBackendFactory(gpu=True, cached=True, auto_optimize=True)
+run_dace_gpu_noopt = DaCeBackendFactory(gpu=True, cached=True, auto_optimize=False)
diff --git a/src/gt4py/next/program_processors/runners/dace_common/workflow.py b/src/gt4py/next/program_processors/runners/dace/workflow/compilation.py
similarity index 50%
rename from src/gt4py/next/program_processors/runners/dace_common/workflow.py
rename to src/gt4py/next/program_processors/runners/dace/workflow/compilation.py
index 6fb7539c..c0d1c74c 100644
--- a/src/gt4py/next/program_processors/runners/dace_common/workflow.py
+++ b/src/gt4py/next/program_processors/runners/dace/workflow/compilation.py
@@ -8,19 +8,16 @@
 
 from __future__ import annotations
 
-import ctypes
 import dataclasses
-from typing import Any, Sequence
+from typing import Any
 
 import dace
 import factory
-from dace.codegen.compiled_sdfg import _array_interface_ptr as get_array_interface_ptr
 
 from gt4py._core import definitions as core_defs
-from gt4py.next import common, config, utils as gtx_utils
-from gt4py.next.otf import arguments, languages, stages, step_types, workflow
+from gt4py.next import config
+from gt4py.next.otf import languages, stages, step_types, workflow
 from gt4py.next.otf.compilation import cache
-from gt4py.next.program_processors.runners.dace_common import dace_backend, utility as dace_utils
 
 
 class CompiledDaceProgram(stages.ExtendedCompiledProgram):
@@ -95,75 +92,3 @@ class DaCeCompiler(
 class DaCeCompilationStepFactory(factory.Factory):
     class Meta:
         model = DaCeCompiler
-
-
-def convert_args(
-    inp: CompiledDaceProgram,
-    device: core_defs.DeviceType = core_defs.DeviceType.CPU,
-    use_field_canonical_representation: bool = False,
-) -> stages.CompiledProgram:
-    sdfg_program = inp.sdfg_program
-    sdfg = sdfg_program.sdfg
-    on_gpu = True if device in [core_defs.DeviceType.CUDA, core_defs.DeviceType.ROCM] else False
-
-    def decorated_program(
-        *args: Any,
-        offset_provider: common.OffsetProvider,
-        out: Any = None,
-    ) -> None:
-        if out is not None:
-            args = (*args, out)
-        flat_args: Sequence[Any] = gtx_utils.flatten_nested_tuple(tuple(args))
-        if inp.implicit_domain:
-            # generate implicit domain size arguments only if necessary
-            size_args = arguments.iter_size_args(args)
-            flat_size_args: Sequence[int] = gtx_utils.flatten_nested_tuple(tuple(size_args))
-            flat_args = (*flat_args, *flat_size_args)
-
-        if sdfg_program._lastargs:
-            kwargs = dict(zip(sdfg.arg_names, flat_args, strict=True))
-            kwargs.update(dace_backend.get_sdfg_conn_args(sdfg, offset_provider, on_gpu))
-
-            use_fast_call = True
-            last_call_args = sdfg_program._lastargs[0]
-            # The scalar arguments should be overridden with the new value; for field arguments,
-            # the data pointer should remain the same otherwise fast_call cannot be used and
-            # the arguments list has to be reconstructed.
-            for i, (arg_name, arg_type) in enumerate(inp.sdfg_arglist):
-                if isinstance(arg_type, dace.data.Array):
-                    assert arg_name in kwargs, f"argument '{arg_name}' not found."
-                    data_ptr = get_array_interface_ptr(kwargs[arg_name], arg_type.storage)
-                    assert isinstance(last_call_args[i], ctypes.c_void_p)
-                    if last_call_args[i].value != data_ptr:
-                        use_fast_call = False
-                        break
-                else:
-                    assert isinstance(arg_type, dace.data.Scalar)
-                    assert isinstance(last_call_args[i], ctypes._SimpleCData)
-                    if arg_name in kwargs:
-                        # override the scalar value used in previous program call
-                        actype = arg_type.dtype.as_ctypes()
-                        last_call_args[i] = actype(kwargs[arg_name])
-                    else:
-                        # shape and strides of arrays are supposed not to change, and can therefore be omitted
-                        assert dace_utils.is_field_symbol(
-                            arg_name
-                        ), f"argument '{arg_name}' not found."
-
-            if use_fast_call:
-                return inp.fast_call()
-
-        sdfg_args = dace_backend.get_sdfg_args(
-            sdfg,
-            offset_provider,
-            *flat_args,
-            check_args=False,
-            on_gpu=on_gpu,
-            use_field_canonical_representation=use_field_canonical_representation,
-        )
-
-        with dace.config.temporary_config():
-            dace.config.Config.set("compiler", "allow_view_arguments", value=True)
-            return inp(**sdfg_args)
-
-    return decorated_program
diff --git a/src/gt4py/next/program_processors/runners/dace/workflow/decoration.py b/src/gt4py/next/program_processors/runners/dace/workflow/decoration.py
new file mode 100644
index 00000000..2ee99f5f
--- /dev/null
+++ b/src/gt4py/next/program_processors/runners/dace/workflow/decoration.py
@@ -0,0 +1,96 @@
+# GT4Py - GridTools Framework
+#
+# Copyright (c) 2014-2024, ETH Zurich
+# All rights reserved.
+#
+# Please, refer to the LICENSE file in the root directory.
+# SPDX-License-Identifier: BSD-3-Clause
+
+from __future__ import annotations
+
+import ctypes
+from typing import Any, Sequence
+
+import dace
+from dace.codegen.compiled_sdfg import _array_interface_ptr as get_array_interface_ptr
+
+from gt4py._core import definitions as core_defs
+from gt4py.next import common, utils as gtx_utils
+from gt4py.next.otf import arguments, stages
+from gt4py.next.program_processors.runners.dace import (
+    sdfg_callable,
+    utils as gtx_dace_utils,
+    workflow as dace_worflow,
+)
+
+
+def convert_args(
+    inp: dace_worflow.compilation.CompiledDaceProgram,
+    device: core_defs.DeviceType = core_defs.DeviceType.CPU,
+    use_field_canonical_representation: bool = False,
+) -> stages.CompiledProgram:
+    sdfg_program = inp.sdfg_program
+    sdfg = sdfg_program.sdfg
+    on_gpu = True if device in [core_defs.DeviceType.CUDA, core_defs.DeviceType.ROCM] else False
+
+    def decorated_program(
+        *args: Any,
+        offset_provider: common.OffsetProvider,
+        out: Any = None,
+    ) -> None:
+        if out is not None:
+            args = (*args, out)
+        flat_args: Sequence[Any] = gtx_utils.flatten_nested_tuple(tuple(args))
+        if inp.implicit_domain:
+            # generate implicit domain size arguments only if necessary
+            size_args = arguments.iter_size_args(args)
+            flat_size_args: Sequence[int] = gtx_utils.flatten_nested_tuple(tuple(size_args))
+            flat_args = (*flat_args, *flat_size_args)
+
+        if sdfg_program._lastargs:
+            kwargs = dict(zip(sdfg.arg_names, flat_args, strict=True))
+            kwargs.update(sdfg_callable.get_sdfg_conn_args(sdfg, offset_provider, on_gpu))
+
+            use_fast_call = True
+            last_call_args = sdfg_program._lastargs[0]
+            # The scalar arguments should be overridden with the new value; for field arguments,
+            # the data pointer should remain the same otherwise fast_call cannot be used and
+            # the arguments list has to be reconstructed.
+            for i, (arg_name, arg_type) in enumerate(inp.sdfg_arglist):
+                if isinstance(arg_type, dace.data.Array):
+                    assert arg_name in kwargs, f"argument '{arg_name}' not found."
+                    data_ptr = get_array_interface_ptr(kwargs[arg_name], arg_type.storage)
+                    assert isinstance(last_call_args[i], ctypes.c_void_p)
+                    if last_call_args[i].value != data_ptr:
+                        use_fast_call = False
+                        break
+                else:
+                    assert isinstance(arg_type, dace.data.Scalar)
+                    assert isinstance(last_call_args[i], ctypes._SimpleCData)
+                    if arg_name in kwargs:
+                        # override the scalar value used in previous program call
+                        actype = arg_type.dtype.as_ctypes()
+                        last_call_args[i] = actype(kwargs[arg_name])
+                    else:
+                        # shape and strides of arrays are supposed not to change, and can therefore be omitted
+                        assert gtx_dace_utils.is_field_symbol(
+                            arg_name
+                        ), f"argument '{arg_name}' not found."
+
+            if use_fast_call:
+                return inp.fast_call()
+
+        sdfg_args = sdfg_callable.get_sdfg_args(
+            sdfg,
+            offset_provider,
+            *flat_args,
+            check_args=False,
+            on_gpu=on_gpu,
+            use_field_canonical_representation=use_field_canonical_representation,
+        )
+
+        with dace.config.temporary_config():
+            dace.config.Config.set("compiler", "allow_view_arguments", value=True)
+            return inp(**sdfg_args)
+
+    return decorated_program
diff --git a/src/gt4py/next/program_processors/runners/dace/workflow/factory.py b/src/gt4py/next/program_processors/runners/dace/workflow/factory.py
new file mode 100644
index 00000000..02a089c8
--- /dev/null
+++ b/src/gt4py/next/program_processors/runners/dace/workflow/factory.py
@@ -0,0 +1,58 @@
+# GT4Py - GridTools Framework
+#
+# Copyright (c) 2014-2024, ETH Zurich
+# All rights reserved.
+#
+# Please, refer to the LICENSE file in the root directory.
+# SPDX-License-Identifier: BSD-3-Clause
+
+from __future__ import annotations
+
+import functools
+
+import factory
+
+from gt4py._core import definitions as core_defs
+from gt4py.next import config
+from gt4py.next.otf import recipes, stages
+from gt4py.next.program_processors.runners.dace.workflow import decoration as decoration_step
+from gt4py.next.program_processors.runners.dace.workflow.compilation import (
+    DaCeCompilationStepFactory,
+)
+from gt4py.next.program_processors.runners.dace.workflow.translation import (
+    DaCeTranslationStepFactory,
+)
+
+
+def _no_bindings(inp: stages.ProgramSource) -> stages.CompilableSource:
+    return stages.CompilableSource(program_source=inp, binding_source=None)
+
+
+class DaCeWorkflowFactory(factory.Factory):
+    class Meta:
+        model = recipes.OTFCompileWorkflow
+
+    class Params:
+        device_type: core_defs.DeviceType = core_defs.DeviceType.CPU
+        cmake_build_type: config.CMakeBuildType = factory.LazyFunction(
+            lambda: config.CMAKE_BUILD_TYPE
+        )
+        auto_optimize: bool = False
+
+    translation = factory.SubFactory(
+        DaCeTranslationStepFactory,
+        device_type=factory.SelfAttribute("..device_type"),
+        auto_optimize=factory.SelfAttribute("..auto_optimize"),
+    )
+    bindings = _no_bindings
+    compilation = factory.SubFactory(
+        DaCeCompilationStepFactory,
+        cache_lifetime=factory.LazyFunction(lambda: config.BUILD_CACHE_LIFETIME),
+        cmake_build_type=factory.SelfAttribute("..cmake_build_type"),
+    )
+    decoration = factory.LazyAttribute(
+        lambda o: functools.partial(
+            decoration_step.convert_args,
+            device=o.device_type,
+        )
+    )
diff --git a/src/gt4py/next/program_processors/runners/dace_fieldview/workflow.py b/src/gt4py/next/program_processors/runners/dace/workflow/translation.py
similarity index 71%
rename from src/gt4py/next/program_processors/runners/dace_fieldview/workflow.py
rename to src/gt4py/next/program_processors/runners/dace/workflow/translation.py
index a83654eb..96be93de 100644
--- a/src/gt4py/next/program_processors/runners/dace_fieldview/workflow.py
+++ b/src/gt4py/next/program_processors/runners/dace/workflow/translation.py
@@ -9,20 +9,18 @@
 from __future__ import annotations
 
 import dataclasses
-import functools
 from typing import Optional
 
 import dace
 import factory
 
 from gt4py._core import definitions as core_defs
-from gt4py.next import allocators as gtx_allocators, common, config
+from gt4py.next import allocators as gtx_allocators, common
 from gt4py.next.iterator import ir as itir, transforms as itir_transforms
-from gt4py.next.otf import languages, recipes, stages, step_types, workflow
+from gt4py.next.otf import languages, stages, step_types, workflow
 from gt4py.next.otf.binding import interface
 from gt4py.next.otf.languages import LanguageSettings
-from gt4py.next.program_processors.runners.dace_common import workflow as dace_workflow
-from gt4py.next.program_processors.runners.dace_fieldview import (
+from gt4py.next.program_processors.runners.dace import (
     gtir_sdfg,
     transformations as gtx_transformations,
 )
@@ -106,37 +104,3 @@ class DaCeTranslator(
 class DaCeTranslationStepFactory(factory.Factory):
     class Meta:
         model = DaCeTranslator
-
-
-def _no_bindings(inp: stages.ProgramSource) -> stages.CompilableSource:
-    return stages.CompilableSource(program_source=inp, binding_source=None)
-
-
-class DaCeWorkflowFactory(factory.Factory):
-    class Meta:
-        model = recipes.OTFCompileWorkflow
-
-    class Params:
-        device_type: core_defs.DeviceType = core_defs.DeviceType.CPU
-        cmake_build_type: config.CMakeBuildType = factory.LazyFunction(
-            lambda: config.CMAKE_BUILD_TYPE
-        )
-        auto_optimize: bool = False
-
-    translation = factory.SubFactory(
-        DaCeTranslationStepFactory,
-        device_type=factory.SelfAttribute("..device_type"),
-        auto_optimize=factory.SelfAttribute("..auto_optimize"),
-    )
-    bindings = _no_bindings
-    compilation = factory.SubFactory(
-        dace_workflow.DaCeCompilationStepFactory,
-        cache_lifetime=factory.LazyFunction(lambda: config.BUILD_CACHE_LIFETIME),
-        cmake_build_type=factory.SelfAttribute("..cmake_build_type"),
-    )
-    decoration = factory.LazyAttribute(
-        lambda o: functools.partial(
-            dace_workflow.convert_args,
-            device=o.device_type,
-        )
-    )
diff --git a/src/gt4py/next/program_processors/runners/dace_common/__init__.py b/src/gt4py/next/program_processors/runners/dace_common/__init__.py
deleted file mode 100644
index abf4c3e2..00000000
--- a/src/gt4py/next/program_processors/runners/dace_common/__init__.py
+++ /dev/null
@@ -1,8 +0,0 @@
-# GT4Py - GridTools Framework
-#
-# Copyright (c) 2014-2024, ETH Zurich
-# All rights reserved.
-#
-# Please, refer to the LICENSE file in the root directory.
-# SPDX-License-Identifier: BSD-3-Clause
-
diff --git a/src/gt4py/next/program_processors/runners/dace_fieldview/__init__.py b/src/gt4py/next/program_processors/runners/dace_fieldview/__init__.py
deleted file mode 100644
index 602453fc..00000000
--- a/src/gt4py/next/program_processors/runners/dace_fieldview/__init__.py
+++ /dev/null
@@ -1,17 +0,0 @@
-# GT4Py - GridTools Framework
-#
-# Copyright (c) 2014-2024, ETH Zurich
-# All rights reserved.
-#
-# Please, refer to the LICENSE file in the root directory.
-# SPDX-License-Identifier: BSD-3-Clause
-
-
-from gt4py.next.program_processors.runners.dace_common.dace_backend import get_sdfg_args
-from gt4py.next.program_processors.runners.dace_fieldview.gtir_sdfg import build_sdfg_from_gtir
-
-
-__all__ = [
-    "build_sdfg_from_gtir",
-    "get_sdfg_args",
-]


Test patch:
diff --git a/tests/next_tests/unit_tests/program_processor_tests/runners_tests/dace_tests/test_gtir_to_sdfg.py b/tests/next_tests/unit_tests/program_processor_tests/runners_tests/dace_tests/test_gtir_to_sdfg.py
index 225d2256..faf61187 100644
--- a/tests/next_tests/unit_tests/program_processor_tests/runners_tests/dace_tests/test_gtir_to_sdfg.py
+++ b/tests/next_tests/unit_tests/program_processor_tests/runners_tests/dace_tests/test_gtir_to_sdfg.py
@@ -37,7 +37,7 @@ from next_tests.integration_tests.feature_tests.ffront_tests.ffront_test_utils i
 from . import pytestmark
 
 
-dace_backend = pytest.importorskip("gt4py.next.program_processors.runners.dace_fieldview")
+dace_backend = pytest.importorskip("gt4py.next.program_processors.runners.dace")
 
 
 N = 10
diff --git a/tests/next_tests/unit_tests/program_processor_tests/runners_tests/dace_tests/transformation_tests/test_constant_substitution.py b/tests/next_tests/unit_tests/program_processor_tests/runners_tests/dace_tests/transformation_tests/test_constant_substitution.py
index 04a4f098..8177ea9a 100644
--- a/tests/next_tests/unit_tests/program_processor_tests/runners_tests/dace_tests/transformation_tests/test_constant_substitution.py
+++ b/tests/next_tests/unit_tests/program_processor_tests/runners_tests/dace_tests/transformation_tests/test_constant_substitution.py
@@ -11,7 +11,7 @@ import pytest
 dace = pytest.importorskip("dace")
 from dace.sdfg import nodes as dace_nodes
 
-from gt4py.next.program_processors.runners.dace_fieldview import (
+from gt4py.next.program_processors.runners.dace import (
     transformations as gtx_transformations,
 )
 
diff --git a/tests/next_tests/unit_tests/program_processor_tests/runners_tests/dace_tests/transformation_tests/test_create_local_double_buffering.py b/tests/next_tests/unit_tests/program_processor_tests/runners_tests/dace_tests/transformation_tests/test_create_local_double_buffering.py
index 3d9201c6..88786ee0 100644
--- a/tests/next_tests/unit_tests/program_processor_tests/runners_tests/dace_tests/transformation_tests/test_create_local_double_buffering.py
+++ b/tests/next_tests/unit_tests/program_processor_tests/runners_tests/dace_tests/transformation_tests/test_create_local_double_buffering.py
@@ -14,7 +14,7 @@ dace = pytest.importorskip("dace")
 from dace.sdfg import nodes as dace_nodes
 from dace import data as dace_data
 
-from gt4py.next.program_processors.runners.dace_fieldview import (
+from gt4py.next.program_processors.runners.dace import (
     transformations as gtx_transformations,
 )
 
diff --git a/tests/next_tests/unit_tests/program_processor_tests/runners_tests/dace_tests/transformation_tests/test_distributed_buffer_relocator.py b/tests/next_tests/unit_tests/program_processor_tests/runners_tests/dace_tests/transformation_tests/test_distributed_buffer_relocator.py
index 709079dd..9241bae4 100644
--- a/tests/next_tests/unit_tests/program_processor_tests/runners_tests/dace_tests/transformation_tests/test_distributed_buffer_relocator.py
+++ b/tests/next_tests/unit_tests/program_processor_tests/runners_tests/dace_tests/transformation_tests/test_distributed_buffer_relocator.py
@@ -9,7 +9,7 @@
 import pytest
 import numpy as np
 
-from gt4py.next.program_processors.runners.dace_fieldview import (
+from gt4py.next.program_processors.runners.dace import (
     transformations as gtx_transformations,
 )
 
diff --git a/tests/next_tests/unit_tests/program_processor_tests/runners_tests/dace_tests/transformation_tests/test_global_self_copy_elimination.py b/tests/next_tests/unit_tests/program_processor_tests/runners_tests/dace_tests/transformation_tests/test_global_self_copy_elimination.py
index 4ca44d43..1d98fef8 100644
--- a/tests/next_tests/unit_tests/program_processor_tests/runners_tests/dace_tests/transformation_tests/test_global_self_copy_elimination.py
+++ b/tests/next_tests/unit_tests/program_processor_tests/runners_tests/dace_tests/transformation_tests/test_global_self_copy_elimination.py
@@ -11,7 +11,7 @@ import pytest
 dace = pytest.importorskip("dace")
 from dace.sdfg import nodes as dace_nodes
 
-from gt4py.next.program_processors.runners.dace_fieldview import (
+from gt4py.next.program_processors.runners.dace import (
     transformations as gtx_transformations,
 )
 
diff --git a/tests/next_tests/unit_tests/program_processor_tests/runners_tests/dace_tests/transformation_tests/test_gpu_utils.py b/tests/next_tests/unit_tests/program_processor_tests/runners_tests/dace_tests/transformation_tests/test_gpu_utils.py
index 89f067e5..cdc66d4f 100644
--- a/tests/next_tests/unit_tests/program_processor_tests/runners_tests/dace_tests/transformation_tests/test_gpu_utils.py
+++ b/tests/next_tests/unit_tests/program_processor_tests/runners_tests/dace_tests/transformation_tests/test_gpu_utils.py
@@ -14,7 +14,7 @@ import pytest
 dace = pytest.importorskip("dace")
 from dace.sdfg import nodes as dace_nodes
 
-from gt4py.next.program_processors.runners.dace_fieldview.transformations import (
+from gt4py.next.program_processors.runners.dace.transformations import (
     gpu_utils as gtx_dace_fieldview_gpu_utils,
 )
 
diff --git a/tests/next_tests/unit_tests/program_processor_tests/runners_tests/dace_tests/transformation_tests/test_loop_blocking.py b/tests/next_tests/unit_tests/program_processor_tests/runners_tests/dace_tests/transformation_tests/test_loop_blocking.py
index 67bec9c0..a08cf12a 100644
--- a/tests/next_tests/unit_tests/program_processor_tests/runners_tests/dace_tests/transformation_tests/test_loop_blocking.py
+++ b/tests/next_tests/unit_tests/program_processor_tests/runners_tests/dace_tests/transformation_tests/test_loop_blocking.py
@@ -16,7 +16,7 @@ import pytest
 dace = pytest.importorskip("dace")
 from dace.sdfg import nodes as dace_nodes, propagation as dace_propagation
 
-from gt4py.next.program_processors.runners.dace_fieldview import (
+from gt4py.next.program_processors.runners.dace import (
     transformations as gtx_transformations,
 )
 
diff --git a/tests/next_tests/unit_tests/program_processor_tests/runners_tests/dace_tests/transformation_tests/test_map_buffer_elimination.py b/tests/next_tests/unit_tests/program_processor_tests/runners_tests/dace_tests/transformation_tests/test_map_buffer_elimination.py
index a98eac3c..f2c31a71 100644
--- a/tests/next_tests/unit_tests/program_processor_tests/runners_tests/dace_tests/transformation_tests/test_map_buffer_elimination.py
+++ b/tests/next_tests/unit_tests/program_processor_tests/runners_tests/dace_tests/transformation_tests/test_map_buffer_elimination.py
@@ -13,7 +13,7 @@ import copy
 dace = pytest.importorskip("dace")
 from dace.sdfg import nodes as dace_nodes
 
-from gt4py.next.program_processors.runners.dace_fieldview import (
+from gt4py.next.program_processors.runners.dace import (
     transformations as gtx_transformations,
 )
 
diff --git a/tests/next_tests/unit_tests/program_processor_tests/runners_tests/dace_tests/transformation_tests/test_map_fusion.py b/tests/next_tests/unit_tests/program_processor_tests/runners_tests/dace_tests/transformation_tests/test_map_fusion.py
index b468b80b..516a70b5 100644
--- a/tests/next_tests/unit_tests/program_processor_tests/runners_tests/dace_tests/transformation_tests/test_map_fusion.py
+++ b/tests/next_tests/unit_tests/program_processor_tests/runners_tests/dace_tests/transformation_tests/test_map_fusion.py
@@ -17,7 +17,7 @@ dace = pytest.importorskip("dace")
 from dace.sdfg import nodes as dace_nodes
 from dace.transformation import dataflow as dace_dataflow
 
-from gt4py.next.program_processors.runners.dace_fieldview import (
+from gt4py.next.program_processors.runners.dace import (
     transformations as gtx_transformations,
 )
 
diff --git a/tests/next_tests/unit_tests/program_processor_tests/runners_tests/dace_tests/transformation_tests/test_map_order.py b/tests/next_tests/unit_tests/program_processor_tests/runners_tests/dace_tests/transformation_tests/test_map_order.py
index 72efc2fe..762040e2 100644
--- a/tests/next_tests/unit_tests/program_processor_tests/runners_tests/dace_tests/transformation_tests/test_map_order.py
+++ b/tests/next_tests/unit_tests/program_processor_tests/runners_tests/dace_tests/transformation_tests/test_map_order.py
@@ -9,7 +9,7 @@
 import pytest
 import numpy as np
 
-from gt4py.next.program_processors.runners.dace_fieldview import (
+from gt4py.next.program_processors.runners.dace import (
     transformations as gtx_transformations,
 )
 
diff --git a/tests/next_tests/unit_tests/program_processor_tests/runners_tests/dace_tests/transformation_tests/test_move_tasklet_into_map.py b/tests/next_tests/unit_tests/program_processor_tests/runners_tests/dace_tests/transformation_tests/test_move_tasklet_into_map.py
index 7b39bc4e..7718977d 100644
--- a/tests/next_tests/unit_tests/program_processor_tests/runners_tests/dace_tests/transformation_tests/test_move_tasklet_into_map.py
+++ b/tests/next_tests/unit_tests/program_processor_tests/runners_tests/dace_tests/transformation_tests/test_move_tasklet_into_map.py
@@ -14,7 +14,7 @@ dace = pytest.importorskip("dace")
 from dace.sdfg import nodes as dace_nodes, propagation as dace_propagation
 from dace.transformation import dataflow as dace_dataflow
 
-from gt4py.next.program_processors.runners.dace_fieldview import (
+from gt4py.next.program_processors.runners.dace import (
     transformations as gtx_transformations,
 )
 
diff --git a/tests/next_tests/unit_tests/program_processor_tests/runners_tests/dace_tests/transformation_tests/test_serial_map_promoter.py b/tests/next_tests/unit_tests/program_processor_tests/runners_tests/dace_tests/transformation_tests/test_serial_map_promoter.py
index 8626cb8e..fa7c7255 100644
--- a/tests/next_tests/unit_tests/program_processor_tests/runners_tests/dace_tests/transformation_tests/test_serial_map_promoter.py
+++ b/tests/next_tests/unit_tests/program_processor_tests/runners_tests/dace_tests/transformation_tests/test_serial_map_promoter.py
@@ -12,7 +12,7 @@ import pytest
 dace = pytest.importorskip("dace")
 from dace.sdfg import nodes as dace_nodes
 
-from gt4py.next.program_processors.runners.dace_fieldview import (
+from gt4py.next.program_processors.runners.dace import (
     transformations as gtx_transformations,
 )
 
diff --git a/tests/next_tests/unit_tests/program_processor_tests/runners_tests/dace_tests/transformation_tests/test_strides.py b/tests/next_tests/unit_tests/program_processor_tests/runners_tests/dace_tests/transformation_tests/test_strides.py
index 19b33d0b..c89fe566 100644
--- a/tests/next_tests/unit_tests/program_processor_tests/runners_tests/dace_tests/transformation_tests/test_strides.py
+++ b/tests/next_tests/unit_tests/program_processor_tests/runners_tests/dace_tests/transformation_tests/test_strides.py
@@ -14,7 +14,7 @@ dace = pytest.importorskip("dace")
 from dace import symbolic as dace_symbolic
 from dace.sdfg import nodes as dace_nodes
 
-from gt4py.next.program_processors.runners.dace_fieldview import (
+from gt4py.next.program_processors.runners.dace import (
     transformations as gtx_transformations,
 )