-
Notifications
You must be signed in to change notification settings - Fork 12
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Transformation to call loop transform utilities #430
Conversation
Documentation for this branch can be viewed at https://sites.ecmwf.int/docs/loki/430/index.html |
Codecov ReportAll modified and coverable lines are covered by tests ✅
Additional details and impacted files@@ Coverage Diff @@
## main #430 +/- ##
=======================================
Coverage 93.13% 93.14%
=======================================
Files 200 200
Lines 39648 39702 +54
=======================================
+ Hits 36925 36979 +54
Misses 2723 2723
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, conceptually this looks good but there's a few improvements that we should do right away to not require breaking the interface afterwards.
I've left inline comments, too, but to summarise:
- I would like to have explicit on-switches for each utility, with each off by default
- For the RemoveCodeTransformation, we renamed the utilities with
do_
prefix. We could use the same pattern here, e.g., call the relevant on-switchloop_fusion
and the corresponding utilitydo_loop_fusion
. This makes for a nicer config file appearance (@mlange05, any opinion on this?) - We need a test that verifies the switches and their effectiveness by testing the full permutation matrix of all values
@@ -20,7 +20,7 @@ | |||
) | |||
|
|||
from loki.transformations.transform_loop import ( | |||
loop_interchange, loop_fusion, loop_fission, loop_unroll | |||
TransformLoopsTransformation, loop_fusion, loop_fission |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since loop_interchange and loop_unroll are still outward facing API, I think there's a point to be made to use them directly in at least one of the tests.
loop_interchange(routine, project_bounds=self.project_bounds) | ||
|
||
# Fuse loops | ||
loop_fusion(routine) | ||
|
||
# Split loops | ||
loop_fission(routine, promote=self.promote, warn_loop_carries=self.warn_loop_carries) | ||
|
||
# Unroll loops | ||
loop_unroll(routine, warn_iterations_length=self.warn_iterations_length) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would prefer having off switches for the individual loop transformations, with them disabled by default the same way we have this in the RemoveCodeTransformation:
loki/loki/transformations/remove_code.py
Lines 103 to 110 in c998cda
if self.remove_marked_regions: | |
do_remove_marked_regions( | |
routine, mark_with_comment=self.mark_with_comment | |
) | |
# Apply Dead Code Elimination | |
if self.remove_dead_code: | |
do_remove_dead_code(routine, use_simplify=self.use_simplify) |
Each of the utilities is on the more costly side of transformations, and if one of them is causing problems it's also handy to selectively switch them on or off.
We should then also add a test that is parameterised such that each is switched on/off and that verifies that only the respective transformation is then active.
self, project_bounds=False, promote=True, warn_loop_carries=True, | ||
warn_iterations_length=True |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Without the loop transformation context these options are a little hard to map mentally. I suggest we prefix them according to the transformation they belong to:
self, project_bounds=False, promote=True, warn_loop_carries=True, | |
warn_iterations_length=True | |
self, interchange_project_bounds=False, | |
fission_auto_promote=True, fission_warn_loop_carries=True, | |
unroll_warn_iterations_length=True |
c998cda
to
1437de6
Compare
Many thanks @reuterbal for the detailed review and feedback 🙏 I've implemented all of your suggestions, please have a look again whenever you can. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks so much for applying these changes. I think that makes the interface much more coherent!
I have a final suggestion for how to also generalize the test a bit more, because the efficacy of combined transformations is still missing at the moment.
@pytest.mark.parametrize('loop_trafo', ['loop_interchange', 'loop_fusion', 'loop_fission', | ||
'loop_unroll']) | ||
def test_transform_loop_transformation(frontend, loop_trafo): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This will only test one option at a time. What I had in mind was the following:
@pytest.mark.parametrize('loop_trafo', ['loop_interchange', 'loop_fusion', 'loop_fission', | |
'loop_unroll']) | |
def test_transform_loop_transformation(frontend, loop_trafo): | |
@pytest.mark.parametrize('loop_interchange', [False, True]) | |
@pytest.mark.parametrize('loop_fusion', [False, True]) | |
@pytest.mark.parametrize('loop_fission', [False, True]) | |
@pytest.mark.parametrize('loop_unroll', [False, True]) | |
def test_transform_loop_transformation(frontend, loop_interchange, loop_fusion, loop_fission, loop_unroll): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah yes, that is a much more comprehensive test, thanks!
option = { | ||
'loop_interchange': 'loop_interchange' == loop_trafo, | ||
'loop_fusion': 'loop_fusion' == loop_trafo, | ||
'loop_fission': 'loop_fission' == loop_trafo, | ||
'loop_unroll': 'loop_unroll' == loop_trafo | ||
} | ||
transform = TransformLoopsTransformation(loop_interchange=option['loop_interchange'], | ||
loop_fusion=option['loop_fusion'], | ||
loop_fission=option['loop_fission'], | ||
loop_unroll=option['loop_unroll']) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This would then simplify as
option = { | |
'loop_interchange': 'loop_interchange' == loop_trafo, | |
'loop_fusion': 'loop_fusion' == loop_trafo, | |
'loop_fission': 'loop_fission' == loop_trafo, | |
'loop_unroll': 'loop_unroll' == loop_trafo | |
} | |
transform = TransformLoopsTransformation(loop_interchange=option['loop_interchange'], | |
loop_fusion=option['loop_fusion'], | |
loop_fission=option['loop_fission'], | |
loop_unroll=option['loop_unroll']) | |
num_loops = len(FindNodes(ir.Loop).visit(routine.body)) | |
num_pragmas = len(FindNodes(ir.Pragma).visit(routine.body)) | |
transform = TransformLoopsTransformation( | |
loop_interchange=loop_interchange, loop_fusion=loop_fusion, | |
loop_fission=loop_fission, loop_unroll=loop_unroll' | |
) |
pragmas = FindNodes(ir.Pragma).visit(routine.body) | ||
assert len(pragmas) == 4 | ||
assert not any(loop_trafo.replace('_', '-') in pragma.content for pragma in pragmas) | ||
assert all(any(opt.replace('_', '-') in pragma.content for pragma in pragmas) | ||
for opt in option if not opt == loop_trafo) | ||
|
||
loops = FindNodes(ir.Loop).visit(routine.body) | ||
if loop_trafo == 'loop_interchange': | ||
assert loops[0].variable == 'j' | ||
inner_loops = FindNodes(ir.Loop).visit(loops[0].body) | ||
assert inner_loops[0].variable == 'i' | ||
|
||
elif loop_trafo == 'loop_fusion': | ||
assigns = FindNodes(ir.Assignment).visit(loops[2].body) | ||
assert len(assigns) == 2 | ||
|
||
elif loop_trafo == 'loop_fission': | ||
assert len(loops) == 7 | ||
|
||
elif loop_trafo == 'loop_unroll': | ||
assert len(loops) == 5 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And the checks could then be generalized as:
pragmas = FindNodes(ir.Pragma).visit(routine.body) | |
assert len(pragmas) == 4 | |
assert not any(loop_trafo.replace('_', '-') in pragma.content for pragma in pragmas) | |
assert all(any(opt.replace('_', '-') in pragma.content for pragma in pragmas) | |
for opt in option if not opt == loop_trafo) | |
loops = FindNodes(ir.Loop).visit(routine.body) | |
if loop_trafo == 'loop_interchange': | |
assert loops[0].variable == 'j' | |
inner_loops = FindNodes(ir.Loop).visit(loops[0].body) | |
assert inner_loops[0].variable == 'i' | |
elif loop_trafo == 'loop_fusion': | |
assigns = FindNodes(ir.Assignment).visit(loops[2].body) | |
assert len(assigns) == 2 | |
elif loop_trafo == 'loop_fission': | |
assert len(loops) == 7 | |
elif loop_trafo == 'loop_unroll': | |
assert len(loops) == 5 | |
loops = FindNodes(ir.Loop).visit(routine.body) | |
pragmas = FindNodes(ir.Pragma).visit(routine.body) | |
if loop_interchange: | |
num_pragmas -= 1 | |
assert not any('loop-interchange' in pragma.content for pragma in pragmas) | |
assert loops[0].variable == 'j' | |
assert FindNodes(ir.Loop).visit(loops[0].body)[0].variable == 'i' | |
if loop_fusion: | |
num_pragmas -= 1 | |
assert not any('loop-fusion' in pragma.content for pragma in pragmas) | |
num_loops -= 1 | |
assert len(FindNodes(ir.Assignment).visit(loops[2].body)) == 2 | |
if loop_fission: | |
num_pragmas -= 1 | |
assert not any('loop-fission' in pragma.content for pragma in pragmas) | |
num_loops += 1 | |
if loop_unroll: | |
num_pragmas -= 1 | |
assert not any('loop-unroll' in pragma.content for pragma in pragmas) | |
num_loops -= 1 | |
assert len(pragmas) == num_pragmas | |
assert len(loops) == num_loops |
966e73d
to
d0fb253
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Many thanks, gtg!
A transformation to run the loop transform utilities in a specified order, with the aim to place this transformation in scheduler pipelines.