Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CONTRIBUTORS.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,5 +25,5 @@
| Pierre-siddall | Pierre Siddall | Met Office | 2026-01-29 |
| mo-lucy-gordon | Lucy Gordon | Met Office | 2026-03-18 |
| shreybh1 | Shrey Bhardwaj | Met Office | 2026-03-26 |

| mattatmet | Matthew Walker | Met Office | 2026-04-21 |
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unless it states otherwise in the developer documentation, it is probably best to insert your name in alphabetical order by family name. This may be difficult if others have not been doing so.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sadly, there is no clear structure or order to this file, alphabetical or otherwise.


79 changes: 74 additions & 5 deletions infrastructure/build/psyclone/psyclone_tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,20 +13,32 @@

from psyclone.domain.lfric import LFRicConstants
from psyclone.psyGen import InvokeSchedule
from psyclone.psyir.nodes import Loop, Routine, Directive
from psyclone.psyir.nodes import (
Loop,
Routine,
Directive,
Container,
OMPParallelDirective,
OMPParallelDoDirective,
OMPDoDirective,
FileContainer,
ProfileNode
)
from psyclone.transformations import (
Dynamo0p3ColourTrans,
Dynamo0p3OMPLoopTrans,
Dynamo0p3RedundantComputationTrans,
OMPParallelTrans,
TransformationError
)
from psyclone.psyir.transformations import ProfileTrans

# List of allowed 'setval_*' built-ins for redundant computation transformation
SETVAL_BUILTINS = ["setval_c"]


# -----------------------------------------------------------------------------
def redundant_computation_setval(psyir):
def redundant_computation_setval(psyir: FileContainer):
"""
Applies the redundant computation transformation to loops over DoFs
for the initialision built-ins, 'setval_*'.
Expand Down Expand Up @@ -68,13 +80,14 @@ def redundant_computation_setval(psyir):


# -----------------------------------------------------------------------------
def colour_loops(psyir, enable_tiling=False):
def colour_loops(psyir: FileContainer, enable_tiling=False):
"""
Applies the colouring transformation to all applicable loops and optionally
enables tiling.
It creates the instance of `Dynamo0p3ColourTrans` only once.

:param psyir: the PSyIR of the PSy-layer.
:param enable_tiling: a bool to enable tiling. Default False.
:type psyir: :py:class:`psyclone.psyir.nodes.FileContainer`

"""
Expand All @@ -86,6 +99,12 @@ def colour_loops(psyir, enable_tiling=False):
# Colour loops over cells unless they are on discontinuous
# spaces or over DoFs
for child in subroutine.children:
# Check if the profiling calipers have been added before the
# colouring.
if isinstance(child, ProfileNode):
raise TransformationError(
"Must apply colour_loops BEFORE profile_loops function "
"in optimisation script.")
if (
isinstance(child, Loop)
and child.iteration_space.endswith("cell_column")
Expand All @@ -94,9 +113,59 @@ def colour_loops(psyir, enable_tiling=False):
):
ctrans.apply(child, options={"tiling": enable_tiling})

# -----------------------------------------------------------------------------
def profile_loops(psyir: FileContainer, colours_only=True):
"""
Applies timing calipers to kernels during the psyclone build. The default
is to only profile coloured loops but colours_only can be set to False to
profile every instance of a coded kernel.

:param psyir: the PSyIR of the PSy-layer.
:param colours_only: profile only the coloured kernels. Default True.
:type psyir: :py:class:`psyclone.psyir.nodes.FileContainer`
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can use type hinting to include this information in a syntactically significant fashion. e.g.

def profile_loops(psyir: FileContainer, colours_only=True):

This is more succinct than the sphinx form and can be used by tools such as mypy for static type checking.

Note that there is no need to specify colours_only: bool = True, although you can, because the type may be inferred from the default.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for this tip Matthew. I've added the type hint to this function and the other functions in psyclone_tools.


"""
profile_trans = ProfileTrans()
leave_loops = ["cells_in_colour",
"tiles_in_colour",
"cells_in_tile"]

# Loop over all the InvokeSchedule in the PSyIR object
for subroutine in psyir.walk(InvokeSchedule):
# Add timing calipers to coloured loops. This should be done
# before the application of the openmp transformation.
Comment on lines +135 to +136
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a means to enforce this ordering?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for this comment Matthew, it's been really good to check. I hadn't thought about how strict this condition should be so I tested it and it turns out that a check for this ordering was definitely warranted as well as another (making sure the function isn't called before colour_loops as well). I've added a few raise TransformationError lines so that it fails gracefully/clearly when the function is called out of order.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hacka Fett (@christophermaynard) Joerg Henrichs (@hiker): one thing that came up here was that Psyclone raises an error when a Profile node is placed between an OMPParallelDoDirective node and a Loop node. An idea for the future could be setting Psyclone up so that it allows it when you want to profile different threads?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hacka Fett (Hacka Fett (@christophermaynard)) Joerg Henrichs (Joerg Henrichs (@hiker)): one thing that came up here was that Psyclone raises an error when a Profile node is placed between an OMPParallelDoDirective node and a Loop node. An idea for the future could be setting Psyclone up so that it allows it when you want to profile different threads?

I don't think that's valid Fortran: after an omp parallel do there must be a loop, we can't put a call there.
Tools that support measurements for threads (well ... at least tau does :) ) either instrument the omp directives (i.e. source to source transformation, inserting calls), or using the ompt call back functionality.

count = 0
for loop in subroutine.loops():
if not loop.coded_kernels():
continue
# Insert profiler calls before loop over colours
if ((loop.loop_type == "colours") or
(colours_only is False and loop.loop_type not in leave_loops)):
# First check that the transformation is not being made inside
# an OMP region.
if (loop.ancestor(OMPParallelDirective)
or loop.ancestor(OMPParallelDoDirective)
or loop.ancestor(OMPDoDirective)):
raise TransformationError(
"Must apply profile_loops BEFORE "
"openmp_parallelise_loops function in optimisation "
"script.")
# Constructing unique calliper name based on kernel name,
# invoke name and kernel count
k_object = loop.ancestor(InvokeSchedule).coded_kernels()[count]
k_name = k_object.name
invoke_name = loop.ancestor(InvokeSchedule).invoke.name
file_name = loop.ancestor(Container).name
# Make region name
region_name = invoke_name + ":" + k_name + "_k" + str(count)
options = {"region_name": (file_name, region_name)}
profile_trans.apply(loop, options=options)
# Count here is to distinguish kernels of the same name
# in the same invoke.
count += 1
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A comment about why we are using a counter would go a long way here.

Something like "Allows invokes of the same name to be profiled individually"


# -----------------------------------------------------------------------------
def openmp_parallelise_loops(psyir):
def openmp_parallelise_loops(psyir: FileContainer):
"""
Applies OpenMP Loop transformation to each applicable loop.

Expand All @@ -120,7 +189,7 @@ def openmp_parallelise_loops(psyir):


# -----------------------------------------------------------------------------
def view_transformed_schedule(psyir):
def view_transformed_schedule(psyir: FileContainer):
"""
Provides view of transformed Invoke schedule in the PSy-layer.

Expand Down
Loading