Skip to content

Commit fd393e6

Browse files
committed
compiler: Keep the outer Dimension parallel on a short par-tile
When a multi `par-tile` entry is shorter than the blocked nest, the innermost Dimensions consume the available block sizes and the outermost one runs into a StopIteration, raised by the top-level tuple once the entry is exhausted. It was then dropped into `compact`, which promotes it back to its root Dimension, discarding its BlockDimension. On a device that is a serialization: the Dimension ends up outside the blocked nest, `filter_iterations` rejects it as non-parallel, and the kernel is launched over a 2D grid from a host loop iterating the outer Dimension one slice at a time. Walk this nest's own entry instead, which repeats its innermost size for as long as the nest needs it, so the nest stays fully blocked. Whether to do so is decided when the par-tile is built, where the target is already known, rather than by inspecting a cluster back in the pass. It applies to multi par-tiles on a device only: a single user-supplied par-tile defines the block rank on purpose, and on a host a short entry is the documented way to ask for 2.5D blocking.
1 parent 1104233 commit fd393e6

5 files changed

Lines changed: 51 additions & 8 deletions

File tree

‎devito/core/gpu.py‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,8 @@ def _normalize_kwargs(cls, **kwargs):
9090
# GPU parallelism
9191
o['par-tile'] = ParTile(oo.pop('par-tile', False), default=(32, 4, 4),
9292
sparse=oo.pop('par-tile-sparse', None),
93-
reduce=oo.pop('par-tile-reduce', None))
93+
reduce=oo.pop('par-tile-reduce', None),
94+
unbound=True)
9495
o['par-collapse-ncores'] = 1 # Always collapse (meaningful if `par-tile=False`)
9596
o['par-collapse-work'] = 1 # Always collapse (meaningful if `par-tile=False`)
9697
o['par-chunk-nonaffine'] = oo.pop('par-chunk-nonaffine', cls.PAR_CHUNK_NONAFFINE)

‎devito/core/operator.py‎

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -478,7 +478,8 @@ def __new__(cls, items, rule=None, tag=None):
478478

479479
class ParTile(UnboundedMultiTuple, OptOption):
480480

481-
def __new__(cls, items, default=None, sparse=None, reduce=None):
481+
def __new__(cls, items, default=None, sparse=None, reduce=None,
482+
unbound=False):
482483
if not items:
483484
return UnboundedMultiTuple()
484485
elif isinstance(items, bool):
@@ -536,9 +537,6 @@ def __new__(cls, items, default=None, sparse=None, reduce=None):
536537
obj.default = as_tuple(default)
537538
obj.sparse = as_tuple(sparse)
538539
obj.reduce = as_tuple(reduce)
540+
obj.unbound = unbound
539541

540542
return obj
541-
542-
@property
543-
def is_multi(self):
544-
return len(self) > 1

‎devito/passes/clusters/blocking.py‎

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -495,6 +495,7 @@ class BlockSizeGenerator:
495495

496496
def __init__(self, par_tile):
497497
self.umt = par_tile
498+
self.unbound = par_tile.unbound
498499

499500
if par_tile.is_multi:
500501
# The user has supplied one specific par-tile per blocked nest
@@ -523,6 +524,8 @@ def __init__(self, par_tile):
523524
self.umt_reduce = UnboundTuple(*par_tile.default, 1)
524525

525526
def schedule(self, dims, clusters):
527+
unbound = False
528+
526529
if any(c.properties.is_parallel_atomic(dims) for c in clusters):
527530
# Correctness -- enforce blocking where necessary.
528531
# See also issue #276:PRO
@@ -534,12 +537,17 @@ def schedule(self, dims, clusters):
534537
elif all(c.properties.avoid_tuning(dims) for c in clusters):
535538
# Performance heuristics -- use a smaller par-tile
536539
umt = self.umt_small
540+
unbound = self.unbound and umt.is_multi
537541

538542
else:
539543
umt = self.umt
544+
unbound = self.unbound and umt.is_multi
540545

541546
umt.iter()
542547

548+
if unbound:
549+
return umt.curitem()
550+
543551
return umt
544552

545553

@@ -622,10 +630,11 @@ def apply_par_tiles(clusters, options, **kwargs):
622630
Use the par-tile parameter to replace the symbolic BlockDimension sizes
623631
with actual integer numbers representing the block shape.
624632
"""
625-
if not options['par-tile']:
633+
par_tile = options['par-tile']
634+
if not par_tile:
626635
return clusters
627636

628-
blk_size_gen = BlockSizeGenerator(options['par-tile'])
637+
blk_size_gen = BlockSizeGenerator(par_tile)
629638

630639
key = lambda c: c.ispace.project(lambda d: d.is_Block)
631640

‎devito/tools/data_structures.py‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -880,6 +880,10 @@ def __new__(cls, *items, **kwargs):
880880
obj.current = None
881881
return obj
882882

883+
@property
884+
def is_multi(self):
885+
return len(self) > 1
886+
883887
def reset(self):
884888
self.current = None
885889
return self

‎tests/test_gpu_openacc.py‎

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,37 @@ def test_multiple_tile_sizes(self, par_tile):
144144
assert trees[3][1].pragmas[0].ccode.value ==\
145145
f'acc parallel loop {sclause} present(src,src_gp,src_wx,src_wy,src_wz,u)'
146146

147+
def test_short_multi_tile_keeps_outer_dim_blocked(self):
148+
"""
149+
A multi `par-tile` entry shorter than the nest it lands on must not cost
150+
the outermost Dimension its BlockDimension: on a device, dropping it
151+
would leave `x` iterated outside the offloaded nest.
152+
"""
153+
grid = Grid(shape=(8, 8, 8))
154+
155+
u = TimeFunction(name="u", grid=grid, space_order=4)
156+
v = TimeFunction(name="v", grid=grid, space_order=4)
157+
158+
eqns = [Eq(u.forward, u.dx),
159+
Eq(v.forward, u.forward.dx)]
160+
161+
# The second entry is 2D, while the nest it lands on is 3D
162+
par_tile = ((32, 4, 4), (16, 4))
163+
164+
op = Operator(eqns, platform='nvidiaX', language='openacc',
165+
opt=(
166+
'advanced',
167+
{'par-tile': par_tile, 'blocklevels': 1, 'blockinner': True}))
168+
169+
bns, _ = assert_blocking(op, {'x0_blk0', 'x1_blk0'})
170+
171+
expected = ((4, 4, 32), (4, 4, 16))
172+
for root, v in zip(bns.values(), expected, strict=True):
173+
iters = FindNodes(Iteration).visit(root)
174+
iters = [i for i in iters if i.dim.is_Block and i.dim._depth == 1]
175+
assert len(iters) == len(v)
176+
assert all(i.step == j for i, j in zip(iters, v, strict=True))
177+
147178
def test_multi_tile_blocking_structure(self):
148179
grid = Grid(shape=(8, 8, 8))
149180

0 commit comments

Comments
 (0)