diff --git a/meshmode/discretization/connection/__init__.py b/meshmode/discretization/connection/__init__.py
index c4881c4f366619b4484156ae740cca3728ddc590..e7929937d1fdf7d360759e477aee7a10faa19ce3 100644
--- a/meshmode/discretization/connection/__init__.py
+++ b/meshmode/discretization/connection/__init__.py
@@ -31,8 +31,9 @@ from pytools import memoize_method, memoize_in
 
 from meshmode.discretization.connection.same_mesh import \
         make_same_mesh_connection
-from meshmode.discretization.connection.face import \
-        make_face_restriction
+from meshmode.discretization.connection.face import (
+        FRESTR_INTERIOR_FACES, FRESTR_ALL_FACES,
+        make_face_restriction)
 from meshmode.discretization.connection.opposite_face import \
         make_opposite_face_connection
 
@@ -42,7 +43,9 @@ logger = logging.getLogger(__name__)
 
 __all__ = [
         "DiscretizationConnection",
-        "make_same_mesh_connection", "make_face_restriction",
+        "make_same_mesh_connection",
+        "FRESTR_INTERIOR_FACES", "FRESTR_ALL_FACES",
+        "make_face_restriction",
         "make_opposite_face_connection"
         ]
 
@@ -51,6 +54,8 @@ __doc__ = """
 
 .. autofunction:: make_same_mesh_connection
 
+.. autofunction:: FRESTR_INTERIOR_FACES
+.. autofunction:: FRESTR_ALL_FACES
 .. autofunction:: make_face_restriction
 
 .. autofunction:: make_opposite_face_connection
diff --git a/meshmode/discretization/connection/face.py b/meshmode/discretization/connection/face.py
index f03b5d8a744770d11192adbcf13b70e15ec56e62..14127b32a28396236e5d457bd079ac4d2f79ed43 100644
--- a/meshmode/discretization/connection/face.py
+++ b/meshmode/discretization/connection/face.py
@@ -36,49 +36,74 @@ import logging
 logger = logging.getLogger(__name__)
 
 
+class FRESTR_INTERIOR_FACES:
+    """A special value to pass to
+    :func:`meshmode.discretization.connection.make_face_restriction`
+    to produce a discretization consisting of all interior faces
+    in a discretization.
+    """
+
+
+class FRESTR_ALL_FACES:
+    """A special value to pass to
+    :func:`meshmode.discretization.connection.make_face_restriction`
+    to produce a discretization consisting of all faces (interior and boundary)
+    faces in a discretization.
+    """
+
+
 # {{{ boundary connection
 
 class _ConnectionBatchData(Record):
     pass
 
 
-def _build_boundary_connection(queue, vol_discr, bdry_discr, connection_data):
+def _build_boundary_connection(queue, vol_discr, bdry_discr, connection_data,
+        per_face_groups):
     from meshmode.discretization.connection import (
             InterpolationBatch, DiscretizationConnectionElementGroup,
             DiscretizationConnection)
 
+    ibdry_grp = 0
+    batches = []
+
     connection_groups = []
-    for igrp, (vol_grp, bdry_grp) in enumerate(
-            zip(vol_discr.groups, bdry_discr.groups)):
-        connection_batches = []
+    for igrp, vol_grp in enumerate(vol_discr.groups):
         mgrp = vol_grp.mesh_el_group
 
-        for face_id in range(len(mgrp.face_vertex_indices())):
+        for face_id in range(mgrp.nfaces):
+            bdry_grp = bdry_discr.groups[ibdry_grp]
             data = connection_data[igrp, face_id]
 
             bdry_unit_nodes_01 = (bdry_grp.unit_nodes + 1)*0.5
             result_unit_nodes = (np.dot(data.A, bdry_unit_nodes_01).T + data.b).T
 
-            connection_batches.append(
-                    InterpolationBatch(
-                        from_group_index=igrp,
-                        from_element_indices=cl.array.to_device(
-                            queue,
-                            vol_grp.mesh_el_group.element_nr_base
-                            + data.group_source_element_indices)
-                        .with_queue(None),
-                        to_element_indices=cl.array.to_device(
-                            queue,
-                            bdry_grp.mesh_el_group.element_nr_base
-                            + data.group_target_element_indices)
-                        .with_queue(None),
-                        result_unit_nodes=result_unit_nodes,
-                        to_element_face=face_id
-                        ))
-
-        connection_groups.append(
-                DiscretizationConnectionElementGroup(
-                    connection_batches))
+            batches.append(
+                InterpolationBatch(
+                    from_group_index=igrp,
+                    from_element_indices=cl.array.to_device(
+                        queue,
+                        vol_grp.mesh_el_group.element_nr_base
+                        + data.group_source_element_indices)
+                    .with_queue(None),
+                    to_element_indices=cl.array.to_device(
+                        queue,
+                        data.group_target_element_indices)
+                    .with_queue(None),
+                    result_unit_nodes=result_unit_nodes,
+                    to_element_face=face_id
+                    ))
+
+            is_last_face = face_id + 1 == mgrp.nfaces
+
+            if per_face_groups or is_last_face:
+                connection_groups.append(
+                    DiscretizationConnectionElementGroup(batches))
+                batches = []
+
+                ibdry_grp += 1
+
+    assert ibdry_grp == len(bdry_discr.groups)
 
     return DiscretizationConnection(
             vol_discr, bdry_discr, connection_groups,
@@ -93,7 +118,7 @@ def _get_face_vertices(mesh, boundary_tag):
     # a set of volume vertex numbers
     bdry_vertex_vol_nrs = set()
 
-    if boundary_tag is not None:
+    if boundary_tag not in [FRESTR_INTERIOR_FACES, FRESTR_ALL_FACES]:
         # {{{ boundary faces
 
         btag_bit = mesh.boundary_tag_bit(boundary_tag)
@@ -121,15 +146,17 @@ def _get_face_vertices(mesh, boundary_tag):
 
         # }}}
     else:
-        # For interior faces, this is likely every vertex in the book.
+        # For INTERIOR_FACES, this is likely every vertex in the book.
         # Don't ever bother trying to cut the list down.
+        # For ALL_FACES, it literally is every single vertex.
 
         return np.arange(mesh.nvertices, dtype=np.intp)
 
 # }}}
 
 
-def make_face_restriction(discr, group_factory, boundary_tag):
+def make_face_restriction(discr, group_factory, boundary_tag,
+        per_face_groups=False):
     """Create a mesh, a discretization and a connection to restrict
     a function on *discr* to its values on the edges of element faces
     denoted by *boundary_tag*.
@@ -142,6 +169,17 @@ def make_face_restriction(discr, group_factory, boundary_tag):
         to make a discretization consisting of all (interior and
         boundary) faces.
 
+    :arg per_face_groups: If *True*, the resulting discretization is
+        guaranteed to have groups organized as::
+
+            (grp0, face0), (grp0, face1), ... (grp0, faceN),
+            (grp1, face0), (grp1, face1), ... (grp1, faceN), ...
+
+        each with the elements in the same order as the originating
+        group. If *False*, volume and boundary groups correspond with
+        each other one-to-one, and an interpolation batch is created
+        per face.
+
     :return: a :class:`meshmode.discretization.connection.DiscretizationConnection`
         representing the new connection. The new boundary discretization can be
         obtained from the
@@ -149,16 +187,15 @@ def make_face_restriction(discr, group_factory, boundary_tag):
         attribute of the return value, and the corresponding new boundary mesh
         from that.
 
-    The resulting discretization is guaranteed to have groups
-    organized as::
-
-        (grp0, face0), (grp0, face1), ... (grp0, faceN),
-        (grp1, face0), (grp1, face1), ... (grp1, faceN), ...
-
-    each with the elements in the same order as the originating
-    group.
     """
 
+    if boundary_tag is None:
+        boundary_tag = FRESTR_INTERIOR_FACES
+        from warnings import warn
+        warn("passing *None* for boundary_tag is deprecated--pass "
+                "INTERIOR_FACES instead",
+                DeprecationWarning, stacklevel=2)
+
     logger.info("building face restriction: start")
 
     # {{{ gather boundary vertices
@@ -195,18 +232,7 @@ def make_face_restriction(discr, group_factory, boundary_tag):
 
         group_boundary_faces = []
 
-        if boundary_tag is not None:
-            bdry_grp = fagrp_map.get(None)
-            if bdry_grp is not None:
-                nb_el_bits = -bdry_grp.neighbors
-                face_relevant_flags = (nb_el_bits & btag_bit) != 0
-
-                group_boundary_faces.extend(
-                            zip(
-                                bdry_grp.elements[face_relevant_flags],
-                                bdry_grp.element_faces[face_relevant_flags]))
-
-        else:
+        if boundary_tag is FRESTR_INTERIOR_FACES:
             for fagrp in six.itervalues(fagrp_map):
                 if fagrp.ineighbor_group is None:
                     # boundary faces -> not looking for those
@@ -215,34 +241,34 @@ def make_face_restriction(discr, group_factory, boundary_tag):
                 group_boundary_faces.extend(
                         zip(fagrp.elements, fagrp.element_faces))
 
-        # }}}
-
-        # {{{ Preallocate arrays for mesh group
-
-        ngroup_bdry_elements = len(group_boundary_faces)
-        vertex_indices = np.empty(
-                (ngroup_bdry_elements, mgrp.dim+1-1),
-                mgrp.vertex_indices.dtype)
+        elif boundary_tag is FRESTR_ALL_FACES:
+            group_boundary_faces.extend(
+                    (iel, iface)
+                    for iface in range(grp.mesh_el_group.nfaces)
+                    for iel in range(grp.nelements)
+                    )
 
-        bdry_unit_nodes = mp.warp_and_blend_nodes(mgrp.dim-1, mgrp.order)
-        bdry_unit_nodes_01 = (bdry_unit_nodes + 1)*0.5
+        else:
+            bdry_grp = fagrp_map.get(None)
+            if bdry_grp is not None:
+                nb_el_bits = -bdry_grp.neighbors
+                face_relevant_flags = (nb_el_bits & btag_bit) != 0
 
-        vol_basis = mp.simplex_onb(mgrp.dim, mgrp.order)
-        nbdry_unit_nodes = bdry_unit_nodes_01.shape[-1]
-        nodes = np.empty(
-                (discr.ambient_dim, ngroup_bdry_elements, nbdry_unit_nodes),
-                dtype=np.float64)
+                group_boundary_faces.extend(
+                            zip(
+                                bdry_grp.elements[face_relevant_flags],
+                                bdry_grp.element_faces[face_relevant_flags]))
 
         # }}}
 
         grp_face_vertex_indices = mgrp.face_vertex_indices()
         grp_vertex_unit_coordinates = mgrp.vertex_unit_coordinates()
 
-        # batch by face_id
-
         batch_base = 0
 
-        for face_id in range(len(grp_face_vertex_indices)):
+        # group by face_id
+
+        for face_id in range(mgrp.nfaces):
             batch_boundary_el_numbers_in_grp = np.array(
                     [
                         ibface_el
@@ -250,9 +276,34 @@ def make_face_restriction(discr, group_factory, boundary_tag):
                         if ibface_face == face_id],
                     dtype=np.intp)
 
-            new_el_numbers = np.arange(
-                    batch_base,
-                    batch_base + len(batch_boundary_el_numbers_in_grp))
+            # {{{ Preallocate arrays for mesh group
+
+            nbatch_elements = len(batch_boundary_el_numbers_in_grp)
+
+            if per_face_groups or face_id == 0:
+                if per_face_groups:
+                    ngroup_bdry_elements = nbatch_elements
+                else:
+                    ngroup_bdry_elements = len(group_boundary_faces)
+
+                vertex_indices = np.empty(
+                        (ngroup_bdry_elements, mgrp.dim+1-1),
+                        mgrp.vertex_indices.dtype)
+
+                bdry_unit_nodes = mp.warp_and_blend_nodes(mgrp.dim-1, mgrp.order)
+                bdry_unit_nodes_01 = (bdry_unit_nodes + 1)*0.5
+
+                vol_basis = mp.simplex_onb(mgrp.dim, mgrp.order)
+                nbdry_unit_nodes = bdry_unit_nodes_01.shape[-1]
+                nodes = np.empty(
+                        (discr.ambient_dim, ngroup_bdry_elements, nbdry_unit_nodes),
+                        dtype=np.float64)
+
+            # }}}
+
+            new_el_numbers = batch_base + np.arange(nbatch_elements)
+            if not per_face_groups:
+                batch_base += nbatch_elements
 
             # {{{ no per-element axes in these computations
 
@@ -303,11 +354,13 @@ def make_face_restriction(discr, group_factory, boundary_tag):
                     b=b,
                     )
 
-            batch_base += len(batch_boundary_el_numbers_in_grp)
+            is_last_face = face_id + 1 == mgrp.nfaces
 
-        bdry_mesh_group = SimplexElementGroup(
-                mgrp.order, vertex_indices, nodes, unit_nodes=bdry_unit_nodes)
-        bdry_mesh_groups.append(bdry_mesh_group)
+            if per_face_groups or is_last_face:
+                bdry_mesh_group = SimplexElementGroup(
+                        mgrp.order, vertex_indices, nodes,
+                        unit_nodes=bdry_unit_nodes)
+                bdry_mesh_groups.append(bdry_mesh_group)
 
     bdry_mesh = Mesh(bdry_vertices, bdry_mesh_groups)
 
@@ -317,7 +370,8 @@ def make_face_restriction(discr, group_factory, boundary_tag):
 
     with cl.CommandQueue(discr.cl_context) as queue:
         connection = _build_boundary_connection(
-                queue, discr, bdry_discr, connection_data)
+                queue, discr, bdry_discr, connection_data,
+                per_face_groups)
 
     logger.info("building face restriction: done")
 
diff --git a/test/test_meshmode.py b/test/test_meshmode.py
index 03a6661d3d032d4aeefbd2b5e6eaea7b055bcd24..122b0b9666f958afa14ac0fc1b6e6c8cd159e636 100644
--- a/test/test_meshmode.py
+++ b/test/test_meshmode.py
@@ -38,6 +38,8 @@ from meshmode.discretization.poly_element import (
         PolynomialWarpAndBlendGroupFactory
         )
 from meshmode.mesh import BTAG_ALL
+from meshmode.discretization.connection import \
+        FRESTR_ALL_FACES, FRESTR_INTERIOR_FACES
 
 import pytest
 
@@ -80,16 +82,17 @@ def test_circle_mesh(do_plot=False):
     ])
 @pytest.mark.parametrize("boundary_tag", [
     BTAG_ALL,
-    # all internal faces:
-    None
+    FRESTR_ALL_FACES,
+    FRESTR_INTERIOR_FACES,
     ])
 @pytest.mark.parametrize(("mesh_name", "dim", "mesh_pars"), [
     ("blob", 2, [1e-1, 8e-2, 5e-2]),
     ("warp", 2, [10, 20, 30]),
     ("warp", 3, [10, 20, 30]),
     ])
+@pytest.mark.parametrize("per_face_groups", [False, True])
 def test_boundary_interpolation(ctx_getter, group_factory, boundary_tag,
-        mesh_name, dim, mesh_pars):
+        mesh_name, dim, mesh_pars, per_face_groups):
     cl_ctx = ctx_getter()
     queue = cl.CommandQueue(cl_ctx)
 
@@ -141,7 +144,7 @@ def test_boundary_interpolation(ctx_getter, group_factory, boundary_tag,
 
         bdry_connection = make_face_restriction(
                 vol_discr, group_factory(order),
-                boundary_tag)
+                boundary_tag, per_face_groups=per_face_groups)
         bdry_discr = bdry_connection.to_discr
 
         bdry_x = bdry_discr.nodes()[0].with_queue(queue)
@@ -152,7 +155,8 @@ def test_boundary_interpolation(ctx_getter, group_factory, boundary_tag,
             mat = bdry_connection.full_resample_matrix(queue).get(queue)
             bdry_f_2_by_mat = mat.dot(vol_f.get())
 
-            assert(la.norm(bdry_f_2.get(queue=queue) - bdry_f_2_by_mat)) < 1e-14
+            mat_error = la.norm(bdry_f_2.get(queue=queue) - bdry_f_2_by_mat)
+            assert mat_error < 1e-14, mat_error
 
         err = la.norm((bdry_f-bdry_f_2).get(), np.inf)
         eoc_rec.add_data_point(h, err)