diff --git a/grudge/dt_utils.py b/grudge/dt_utils.py
index e245ad2592972d90c2a89255adddfeb95d26821d..a9fd8cf43ae3c5b3239b51658fa6093dfa5aa5a3 100644
--- a/grudge/dt_utils.py
+++ b/grudge/dt_utils.py
@@ -45,8 +45,11 @@ THE SOFTWARE.
 
 import numpy as np
 
-from arraycontext import ArrayContext, thaw, freeze, Scalar
-from meshmode.transform_metadata import FirstAxisIsElementsTag
+from arraycontext import ArrayContext, thaw, freeze, Scalar, tag_axes
+from meshmode.transform_metadata import (FirstAxisIsElementsTag,
+                                         DiscretizationDOFAxisTag,
+                                         DiscretizationFaceAxisTag,
+                                         DiscretizationElementAxisTag)
 
 from grudge.dof_desc import DD_VOLUME, DOFDesc, as_dofdesc
 from grudge.discretization import DiscretizationCollection
@@ -287,15 +290,18 @@ def dt_geometric_factors(
             data=tuple(
                 actx.einsum(
                     "fej->e",
-                    face_ae_i.reshape(
-                        vgrp.mesh_el_group.nfaces,
-                        vgrp.nelements,
-                        face_ae_i.shape[-1]),
+                    tag_axes(actx, {
+                        0: DiscretizationFaceAxisTag(),
+                        1: DiscretizationElementAxisTag(),
+                        2: DiscretizationDOFAxisTag()
+                        },
+                        face_ae_i.reshape(
+                            vgrp.mesh_el_group.nfaces,
+                            vgrp.nelements,
+                            face_ae_i.shape[-1])),
                     tagged=(FirstAxisIsElementsTag(),))
 
-                for vgrp, face_ae_i in zip(volm_discr.groups, face_areas)
-            )
-        )
+                for vgrp, face_ae_i in zip(volm_discr.groups, face_areas)))
     else:
         surface_areas = DOFArray(
             actx,
@@ -316,20 +322,17 @@ def dt_geometric_factors(
                     tagged=(FirstAxisIsElementsTag(),))
 
                 for vgrp, afgrp, face_ae_i in zip(volm_discr.groups,
-                                                face_discr.groups,
-                                                face_areas)
+                                                  face_discr.groups,
+                                                  face_areas)
             )
         )
 
     return freeze(DOFArray(
         actx,
         data=tuple(
-            actx.einsum("e,ei->ei",
-                        1/sae_i,
-                        cv_i,
-                        tagged=(FirstAxisIsElementsTag(),)) * dcoll.dim
-
-            for cv_i, sae_i in zip(cell_vols, surface_areas)
+            actx.einsum("e,ei->ei", 1/sae_i, cv_i,
+                tagged=(FirstAxisIsElementsTag(),)) * dcoll.dim
+            for cv_i, sae_i, vgrp in zip(cell_vols, surface_areas, volm_discr.groups)
         )
     ))
 
diff --git a/grudge/geometry/metrics.py b/grudge/geometry/metrics.py
index 492d8b7f783476ff9b4dca6359dfb5324158a596..d065a4360e1830149130859868a25becbd29b7b7 100644
--- a/grudge/geometry/metrics.py
+++ b/grudge/geometry/metrics.py
@@ -60,7 +60,7 @@ THE SOFTWARE.
 
 import numpy as np
 
-from arraycontext import thaw, freeze, ArrayContext
+from arraycontext import thaw, freeze, ArrayContext, tag_axes
 from meshmode.dof_array import DOFArray
 
 from grudge import DiscretizationCollection
@@ -70,6 +70,10 @@ from grudge.dof_desc import (
     DD_VOLUME, DOFDesc, DISCR_TAG_BASE
 )
 
+from meshmode.transform_metadata import (DiscretizationAmbientDimAxisTag,
+                                         DiscretizationTopologicalDimAxisTag)
+
+
 from pymbolic.geometric_algebra import MultiVector
 
 from pytools.obj_array import make_obj_array
@@ -513,15 +517,19 @@ def inverse_surface_metric_derivative_mat(
             multiplier = 1
 
         mat = actx.np.stack([
-                actx.np.stack([
-                    multiplier
-                    * inverse_surface_metric_derivative(actx, dcoll,
-                        rst_axis, xyz_axis, dd=dd,
-                        _use_geoderiv_connection=_use_geoderiv_connection)
-                    for rst_axis in range(dcoll.dim)])
-                for xyz_axis in range(dcoll.ambient_dim)])
-
-        return freeze(mat, actx)
+            actx.np.stack([
+                multiplier
+                * inverse_surface_metric_derivative(
+                    actx, dcoll,
+                    rst_axis, xyz_axis, dd=dd,
+                    _use_geoderiv_connection=_use_geoderiv_connection)
+                for rst_axis in range(dcoll.dim)])
+            for xyz_axis in range(dcoll.ambient_dim)])
+
+        return freeze(tag_axes(actx, {
+            0: DiscretizationAmbientDimAxisTag(),
+            1: DiscretizationTopologicalDimAxisTag()},
+            mat))
 
     return thaw(_inv_surf_metric_deriv(), actx)
 
diff --git a/grudge/op.py b/grudge/op.py
index d544d7ca605da4be76ad7f538638c7ad2b941592..0f4d6305244d78b74063d015bec0a8d988e2d558 100644
--- a/grudge/op.py
+++ b/grudge/op.py
@@ -57,13 +57,16 @@ THE SOFTWARE.
 """
 
 
-from arraycontext import ArrayContext, map_array_container
+from arraycontext import ArrayContext, map_array_container, tag_axes
 from arraycontext.container import ArrayOrContainerT
 
 from functools import partial
 
 from meshmode.dof_array import DOFArray
-from meshmode.transform_metadata import FirstAxisIsElementsTag
+from meshmode.transform_metadata import (FirstAxisIsElementsTag,
+                                         DiscretizationDOFAxisTag,
+                                         DiscretizationElementAxisTag,
+                                         DiscretizationFaceAxisTag)
 
 from grudge.discretization import DiscretizationCollection
 
@@ -168,8 +171,7 @@ def _single_axis_derivative_kernel(
                         get_diff_mat(
                             actx,
                             out_element_group=out_grp,
-                            in_element_group=in_grp
-                        ),
+                            in_element_group=in_grp),
                         vec_i,
                         arg_names=("inv_jac_t", "ref_stiffT_mat", "vec", ),
                         tagged=(FirstAxisIsElementsTag(),))
@@ -222,12 +224,10 @@ def _reference_derivative_matrices(actx: ArrayContext,
     def get_ref_derivative_mats(grp):
         from meshmode.discretization.poly_element import diff_matrices
         return actx.freeze(
-            actx.from_numpy(
-                np.asarray(
-                    [dfmat for dfmat in diff_matrices(grp)]
-                )
-            )
-        )
+                actx.tag_axis(
+                    1, DiscretizationDOFAxisTag(),
+                    actx.from_numpy(
+                        np.asarray([dfmat for dfmat in diff_matrices(grp)]))))
     return get_ref_derivative_mats(out_element_group)
 
 
@@ -346,12 +346,10 @@ def _reference_stiffness_transpose_matrix(
 
             mmat = mass_matrix(out_grp)
             return actx.freeze(
-                actx.from_numpy(
-                    np.asarray(
-                        [dmat.T @ mmat.T for dmat in diff_matrices(out_grp)]
-                    )
-                )
-            )
+                actx.tag_axis(1, DiscretizationDOFAxisTag(),
+                    actx.from_numpy(
+                        np.asarray(
+                            [dmat.T @ mmat.T for dmat in diff_matrices(out_grp)]))))
 
         from modepy import vandermonde
         basis = out_grp.basis_obj()
@@ -568,13 +566,11 @@ def reference_mass_matrix(actx: ArrayContext, out_element_group, in_element_grou
 
         weights = in_grp.quadrature_rule().weights
         return actx.freeze(
-            actx.from_numpy(
-                np.asarray(
-                    np.einsum("j,ik,jk->ij", weights, vand_inv_t, o_vand),
-                    order="C"
-                )
-            )
-        )
+                actx.tag_axis(0, DiscretizationDOFAxisTag(),
+                    actx.from_numpy(
+                        np.asarray(
+                            np.einsum("j,ik,jk->ij", weights, vand_inv_t, o_vand),
+                            order="C"))))
 
     return get_ref_mass_mat(out_element_group, in_element_group)
 
@@ -598,15 +594,15 @@ def _apply_mass_operator(
         actx,
         data=tuple(
             actx.einsum("ij,ej,ej->ei",
-                        reference_mass_matrix(
-                            actx,
-                            out_element_group=out_grp,
-                            in_element_group=in_grp
-                        ),
-                        ae_i,
-                        vec_i,
-                        arg_names=("mass_mat", "jac", "vec"),
-                        tagged=(FirstAxisIsElementsTag(),))
+                reference_mass_matrix(
+                    actx,
+                    out_element_group=out_grp,
+                    in_element_group=in_grp
+                    ),
+                ae_i,
+                vec_i,
+                arg_names=("mass_mat", "jac", "vec"),
+                tagged=(FirstAxisIsElementsTag(),))
 
             for in_grp, out_grp, ae_i, vec_i in zip(
                     in_discr.groups, out_discr.groups, area_elements, vec)
@@ -664,13 +660,11 @@ def reference_inverse_mass_matrix(actx: ArrayContext, element_group):
         basis = grp.basis_obj()
 
         return actx.freeze(
-            actx.from_numpy(
-                np.asarray(
-                    inverse_mass_matrix(basis.functions, grp.unit_nodes),
-                    order="C"
-                )
-            )
-        )
+            actx.tag_axis(0, DiscretizationDOFAxisTag(),
+                actx.from_numpy(
+                    np.asarray(
+                        inverse_mass_matrix(basis.functions, grp.unit_nodes),
+                        order="C"))))
 
     return get_ref_inv_mass_mat(element_group)
 
@@ -695,21 +689,15 @@ def _apply_inverse_mass_operator(
     discr = dcoll.discr_from_dd(dd_in)
     inv_area_elements = 1./area_element(actx, dcoll, dd=dd_in,
             _use_geoderiv_connection=actx.supports_nonscalar_broadcasting)
-    group_data = []
-    for grp, jac_inv, vec_i in zip(discr.groups, inv_area_elements, vec):
-
-        ref_mass_inverse = reference_inverse_mass_matrix(actx,
-                                                         element_group=grp)
-
-        group_data.append(
+    group_data = [
             # Based on https://arxiv.org/pdf/1608.03836.pdf
             # true_Minv ~ ref_Minv * ref_M * (1/jac_det) * ref_Minv
             actx.einsum("ei,ij,ej->ei",
                         jac_inv,
-                        ref_mass_inverse,
+                        reference_inverse_mass_matrix(actx, element_group=grp),
                         vec_i,
                         tagged=(FirstAxisIsElementsTag(),))
-        )
+            for grp, jac_inv, vec_i in zip(discr.groups, inv_area_elements, vec)]
 
     return DOFArray(actx, data=tuple(group_data))
 
@@ -840,7 +828,12 @@ def reference_face_mass_matrix(
                     vol_grp.unit_nodes,
                 )
 
-        return actx.freeze(actx.from_numpy(matrix))
+        return actx.freeze(
+                tag_axes(actx, {
+                    0: DiscretizationDOFAxisTag(),
+                    2: DiscretizationDOFAxisTag()
+                    },
+                    actx.from_numpy(matrix)))
 
     return get_ref_face_mass_mat(face_element_group, vol_element_group)
 
@@ -867,27 +860,27 @@ def _apply_face_mass_operator(dcoll: DiscretizationCollection, dd, vec):
         data=tuple(
             actx.einsum("ifj,fej,fej->ei",
                         reference_face_mass_matrix(
-                                actx,
-                                face_element_group=afgrp,
-                                vol_element_group=vgrp,
-                                dtype=dtype),
-                        surf_ae_i.reshape(
+                            actx,
+                            face_element_group=afgrp,
+                            vol_element_group=vgrp,
+                            dtype=dtype),
+                        actx.tag_axis(1, DiscretizationElementAxisTag(),
+                            surf_ae_i.reshape(
                                 vgrp.mesh_el_group.nfaces,
                                 vgrp.nelements,
-                                surf_ae_i.shape[-1]),
-                        vec_i.reshape(
+                                surf_ae_i.shape[-1])),
+                        actx.tag_axis(0, DiscretizationFaceAxisTag(),
+                            vec_i.reshape(
                                 vgrp.mesh_el_group.nfaces,
                                 vgrp.nelements,
-                                afgrp.nunit_dofs),
+                                afgrp.nunit_dofs)),
                         arg_names=("ref_face_mass_mat", "jac_surf", "vec"),
                         tagged=(FirstAxisIsElementsTag(),))
 
             for vgrp, afgrp, vec_i, surf_ae_i in zip(volm_discr.groups,
                                                      face_discr.groups,
                                                      vec,
-                                                     surf_area_elements)
-        )
-    )
+                                                     surf_area_elements)))
 
 
 def face_mass(dcoll: DiscretizationCollection, *args) -> ArrayOrContainerT:
diff --git a/grudge/reductions.py b/grudge/reductions.py
index 1efeff4e9aa5dbe5dbc9d6caae1087e49cc174b9..936467222b3652bcb33aa315c7efee0d74c29457 100644
--- a/grudge/reductions.py
+++ b/grudge/reductions.py
@@ -72,6 +72,7 @@ from grudge.discretization import DiscretizationCollection
 from pytools import memoize_in
 
 from meshmode.dof_array import DOFArray
+from meshmode.transform_metadata import DiscretizationDOFAxisTag
 
 import numpy as np
 import grudge.dof_desc as dof_desc
@@ -364,13 +365,12 @@ def _apply_elementwise_reduction(
                 "iel": ConcurrentElementInameTag(),
                 "idof": ConcurrentDOFInameTag()})
 
-        return DOFArray(
-            actx,
-            data=tuple(
-                actx.call_loopy(elementwise_prg(), operand=vec_i)["result"]
-                for vec_i in vec
-            )
-        )
+        return actx.tag_axis(1, DiscretizationDOFAxisTag(),
+                DOFArray(
+                    actx,
+                    data=tuple(
+                        actx.call_loopy(elementwise_prg(), operand=vec_i)["result"]
+                        for vec_i in vec)))
 
 
 def elementwise_sum(