Skip to content

Commit 5f25d96

Browse files
committed
compiler: Unify task group lowering
1 parent cae7e6a commit 5f25d96

3 files changed

Lines changed: 109 additions & 74 deletions

File tree

devito/ir/iet/nodes.py

Lines changed: 0 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,6 @@
6161
'Section',
6262
'Switch',
6363
'SyncSpot',
64-
'SyncSpotRegion',
6564
'TimedList',
6665
'Transfer',
6766
'Using',
@@ -93,7 +92,6 @@ class Node(Signer):
9392
is_HaloSpot = False
9493
is_ExpressionBundle = False
9594
is_SyncSpot = False
96-
is_SyncSpotRegion = False
9795

9896
_traversable = []
9997
"""
@@ -1541,29 +1539,6 @@ def functions(self):
15411539
return ret
15421540

15431541

1544-
class SyncSpotRegion(List):
1545-
1546-
"""A sequence of SyncSpots treated as one unit during orchestration."""
1547-
1548-
is_SyncSpotRegion = True
1549-
1550-
def __init__(self, body):
1551-
body = as_tuple(body)
1552-
assert body and all(isinstance(i, SyncSpot) for i in body)
1553-
super().__init__(body=body)
1554-
1555-
@property
1556-
def sync_spots(self):
1557-
return self.body
1558-
1559-
@cached_property
1560-
def optype(self):
1561-
"""The common type of the synchronization operations in this region."""
1562-
optype, = {type(op) for spot in self.sync_spots
1563-
for op in spot.sync_ops}
1564-
return optype
1565-
1566-
15671542
class CBlankLine(List):
15681543

15691544
def __init__(self, **kwargs):

devito/ir/support/syncs.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,35 @@
2626

2727
class SyncOp(Pickable):
2828

29+
"""
30+
Metadata for a synchronization operation attached to a `Cluster` or `SyncSpot`.
31+
32+
Parameters
33+
----------
34+
handle : object
35+
The symbolic object identifying or controlling the operation, such as
36+
an entry in a `Lock`. May be None when no handle is required.
37+
target : AbstractFunction
38+
The `Function` whose access is synchronized. For buffered data movements,
39+
this is the compiler-generated buffer.
40+
tindex : Expr, optional
41+
The index into `target` involved in the operation.
42+
function : AbstractFunction, optional
43+
The original `Function` represented by a compiler-generated `target`. It
44+
is the source of a `SyncCopyIn` and the destination of a `SyncCopyOut`.
45+
findex : Expr, optional
46+
The index into `function` corresponding to `tindex`.
47+
dim : Dimension, optional
48+
The `Dimension` along which `tindex` and `findex` are defined.
49+
size : int, optional
50+
The extent associated with the operation along `dim`. Defaults to 1.
51+
origin : Indexed, optional
52+
The original `Indexed` access from which the operation was derived.
53+
gid : Stamp, optional
54+
The `Stamp` identifying the asynchronous task group to which the operation
55+
belongs.
56+
"""
57+
2958
__rargs__ = ('handle', 'target')
3059
__rkwargs__ = (
3160
'tindex', 'function', 'findex', 'dim', 'size', 'origin', 'gid'

devito/passes/iet/orchestration.py

Lines changed: 80 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,13 @@
11
from collections import OrderedDict, defaultdict, namedtuple
22
from contextlib import suppress
3-
from functools import singledispatch
3+
from functools import cached_property, singledispatch
44

55
from sympy import Or
66

77
from devito.exceptions import CompilationError
88
from devito.ir.iet import (
99
AsyncCall, AsyncCallable, BlankLine, Block, BusyWait, Call, Callable, Conditional,
10-
DummyExpr, FindNodes, List, SyncSpot, SyncSpotRegion, Transformer, derive_parameters,
11-
make_callable
10+
DummyExpr, FindNodes, List, SyncSpot, Transformer, derive_parameters, make_callable
1211
)
1312
from devito.ir.iet.visitors import LazyVisitor
1413
from devito.ir.support import (
@@ -23,7 +22,32 @@
2322
__all__ = ['Orchestrator']
2423

2524

26-
Task = namedtuple('Task', 'spot guard anchor sync_ops releases')
25+
Task = namedtuple('Task', 'spot guard sync_ops releases')
26+
27+
28+
class SyncSpotRegion(List):
29+
30+
"""A sequence of SyncSpots treated as one unit during orchestration."""
31+
32+
def __init__(self, body):
33+
super().__init__(body=body)
34+
assert self.body and all(isinstance(i, SyncSpot) for i in self.body)
35+
36+
@property
37+
def sync_spots(self):
38+
return self.body
39+
40+
@cached_property
41+
def optype(self):
42+
"""
43+
The type of the synchronization operation in this region.
44+
"""
45+
optypes = {type(op) for spot in self.sync_spots for op in spot.sync_ops}
46+
assert len(optypes) == 1, (
47+
"Expected a SyncSpotRegion to contain exactly one type of "
48+
"synchronization operation"
49+
)
50+
return optypes.pop()
2751

2852

2953
class Orchestrator:
@@ -48,39 +72,29 @@ def _fuse_tasks(self, iet):
4872
if self.npthreads is None:
4973
return iet
5074

51-
groups = CollectTasks().visit(iet)
52-
5375
insertions = defaultdict(list)
5476
subs1 = {}
5577

56-
for tasks in groups.values():
78+
for tasks, anchor in CollectTasks().visit(iet):
79+
spots = []
80+
for task in tasks:
81+
if task.guard is None:
82+
# Preserve a local scope when there is no Conditional
83+
scope = Block(body=task.spot.body)
84+
else:
85+
scope = Conditional(task.guard, task.spot.body)
86+
spots.append(SyncSpot(task.sync_ops, body=scope))
87+
88+
insertions[anchor].append(SyncSpotRegion(spots))
5789
subs1.update({task.spot: SyncSpot(task.releases) for task in tasks})
5890

59-
# Unguarded tasks form separate groups and can share one SyncSpot
60-
if tasks[0].guard is None:
61-
sync_ops = tuple(op for task in tasks for op in task.sync_ops)
62-
sync_ops += tuple(tasks[-1].releases)
63-
# Blocks preserve the local scope of each task body
64-
body = [Block(body=task.spot.body) for task in tasks]
65-
subs1[tasks[-1].spot] = SyncSpot(sync_ops, body=body)
66-
continue
67-
68-
spots = [SyncSpot(task.sync_ops,
69-
body=Conditional(task.guard, task.spot.body))
70-
for task in tasks]
71-
insertions[tasks[-1].anchor].append(SyncSpotRegion(spots))
72-
7391
if not subs1:
7492
return iet
7593

7694
# These substitutions cannot be merged because a Transformer does not
7795
# revisit a replacement, while task spots may be nested below an anchor
78-
subs0 = {}
79-
for anchor, regions in insertions.items():
80-
if isinstance(anchor, SyncSpot):
81-
subs0[anchor] = anchor._rebuild(body=anchor.body + tuple(regions))
82-
else:
83-
subs0[anchor] = List(body=(anchor, *regions))
96+
subs0 = {anchor: List(body=(anchor, *regions))
97+
for anchor, regions in insertions.items()}
8498

8599
iet = Transformer(subs0).visit(iet)
86100
iet = Transformer(subs1).visit(iet)
@@ -172,19 +186,22 @@ def _make_region(self, iet):
172186
WithLock: withlock
173187
}[iet.optype]
174188

175-
layers = {infer_layer(i.function) for i in sync_ops}
176-
if len(layers) != 1:
177-
raise CompilationError("Unsupported streaming case")
178-
layer = layers.pop()
189+
layer = infer_sync_layer(sync_ops)
179190

180191
body = []
181192
for spot in iet.sync_spots:
182-
condition, = spot.body
193+
scope, = spot.body
194+
if isinstance(scope, Conditional):
195+
task_body = scope.then_body
196+
else:
197+
assert isinstance(scope, Block)
198+
task_body = scope.body
199+
183200
task_body, prefix = callback(
184-
layer, List(body=condition.then_body), spot.sync_ops, self.langbb,
201+
layer, List(body=task_body), spot.sync_ops, self.langbb,
185202
self.sregistry
186203
)
187-
body.append(condition._rebuild(then_body=task_body))
204+
body.append(scope._rebuild(task_body))
188205

189206
return self._make_async_callable(body, prefix)
190207

@@ -235,10 +252,7 @@ def process(self, iet):
235252
for t in sorted(mapper, key=key):
236253
sync_ops = mapper[t]
237254

238-
layers = {infer_layer(s.function) for s in sync_ops}
239-
if len(layers) != 1:
240-
raise CompilationError("Unsupported streaming case")
241-
layer = layers.pop()
255+
layer = infer_sync_layer(sync_ops)
242256

243257
n1, v = callbacks[t](subs.get(n0, n0), sync_ops, layer)
244258

@@ -265,9 +279,12 @@ class CollectTasks(LazyVisitor):
265279

266280
def _post_visit(self, ret):
267281
groups = defaultdict(list)
268-
for key, task in ret:
282+
anchors = {}
283+
for key, task, anchor in ret:
269284
groups[key].append(task)
270-
return groups
285+
# Activate the group after its last task in program order
286+
anchors[key] = anchor
287+
return tuple((tasks, anchors[key]) for key, tasks in groups.items())
271288

272289
def visit_Iteration(self, o, **kwargs):
273290
kwargs['iteration'] = o
@@ -277,7 +294,8 @@ def visit_Conditional(self, o, **kwargs):
277294
kwargs['condition'] = o
278295
yield from self._visit(o.children, **kwargs)
279296

280-
def visit_SyncSpot(self, o, iteration=None, condition=None, anchor=None):
297+
def visit_SyncSpot(self, o, iteration=None, condition=None,
298+
in_snapshot=False):
281299
if iteration is not None:
282300
syncs = as_mapper(o.sync_ops, type)
283301

@@ -297,20 +315,19 @@ def visit_SyncSpot(self, o, iteration=None, condition=None, anchor=None):
297315

298316
gid, = {i.gid for i in sync_ops}
299317
if gid is not None:
300-
task = Task(o, guard, anchor or condition,
301-
sync_ops, syncs[ReleaseLock])
302-
key = (iteration, gid, optype, anchor is not None,
303-
condition is not None)
304-
yield key, task
318+
task = Task(o, guard, sync_ops, syncs[ReleaseLock])
319+
key = (iteration, gid, optype, in_snapshot)
320+
yield key, task, condition or o
305321

306322
break
307323

308324
if any(isinstance(i, SnapOut) for i in o.sync_ops):
309-
# Keep composite task calls inside the `SnapOut` scope
310-
anchor = o
325+
# Do not mix composite tasks with other compatible groups
326+
in_snapshot = True
311327

312328
yield from self._visit(
313-
o.children, iteration=iteration, condition=condition, anchor=anchor
329+
o.children, iteration=iteration, condition=condition,
330+
in_snapshot=in_snapshot
314331
)
315332

316333

@@ -327,6 +344,20 @@ def infer_layer(f):
327344
return layer_host
328345

329346

347+
def infer_sync_layer(sync_ops):
348+
"""
349+
Infer the unique storage layer used by a sequence of SyncOps.
350+
"""
351+
layers = {infer_layer(i.function) for i in sync_ops}
352+
if len(layers) != 1:
353+
found = ', '.join(sorted(str(i) for i in layers)) or 'none'
354+
raise CompilationError(
355+
"Expected synchronization operations to use exactly one storage "
356+
f"layer, but found: {found}"
357+
)
358+
return layers.pop()
359+
360+
330361
@singledispatch
331362
def withlock(layer, iet, sync_ops, lang, sregistry):
332363
raise NotImplementedError

0 commit comments

Comments
 (0)