3131from pydantic import ConfigDict , Discriminator , Tag , computed_field
3232import sympy
3333
34+ from accelforge .frontend .renames import EinsumName
3435from accelforge .util ._basetypes import (
3536 # Parsing helpers for the input files.
3637 EvalableModel ,
@@ -318,7 +319,13 @@ class Loop(MappingNode):
318319 rank_variable : set [RankVariable ] | RankVariable
319320 """ The rank variable(s) iterated over in this loop. This may be a
320321 single rank variable, or a set of rank variables if the loop is shared between
321- multiple Einsums. """
322+ multiple Einsums.
323+
324+ NOTE FOR DEVELOPERS: If the rank variable is a set DURING PMAPPIN GENERATION, then
325+ it means that the loop is a placeholder for multiple loops in the given Einsum. They
326+ will be split into multiple loops later. If the rank variable
327+
328+ """
322329
323330 tile_shape : EvalsTo [sympy .Symbol | sympy .Expr | int | str ] = "symbol"
324331 """
@@ -353,6 +360,12 @@ class Loop(MappingNode):
353360 tile_shape for details.
354361 """
355362
363+ _einsum_to_rank_variables : dict [EinsumName , set [RankVariable ]] = {}
364+ """
365+ If there are multiple rank variables in this loop, this dictionary says which rank
366+ variables belong to which Einsum.
367+ """
368+
356369 _calculated_n_iterations : (
357370 Literal ["symbol" ] | sympy .Symbol | sympy .Expr | int | str | None
358371 ) = None
@@ -1073,6 +1086,14 @@ def _pop_loop_group(groups: list[list[MappingNode]]) -> list[MappingNode]:
10731086 # variables and assume that rank variable translation would fix it.
10741087 assert len (my_loop_group ) == 1 or len (other_loop_group ) == 1
10751088 has_one , may_not_have_one = my_loop_group , other_loop_group
1089+
1090+ my_rv = set ()
1091+ other_rv = set ()
1092+ for m in my_loop_group :
1093+ my_rv |= m .rank_variable if isinstance (m .rank_variable , set ) else set ([m .rank_variable ])
1094+ for o in other_loop_group :
1095+ other_rv |= o .rank_variable if isinstance (o .rank_variable , set ) else set ([o .rank_variable ])
1096+
10761097 if len (has_one ) != 1 :
10771098 has_one , may_not_have_one = other_loop_group , my_loop_group
10781099
@@ -1095,8 +1116,14 @@ def _pop_loop_group(groups: list[list[MappingNode]]) -> list[MappingNode]:
10951116
10961117 may_not_have_one .remove (l2 )
10971118 rv = l2 .rank_variable
1119+ for compute in self .get_nodes_of_type (Compute ):
1120+ for r in my_rv :
1121+ l ._einsum_to_rank_variables [compute .einsum ] = r
10981122 rv = rv if isinstance (rv , set ) else set ([rv ])
10991123 l .rank_variable = l .rank_variable | rv
1124+ for compute in other .get_nodes_of_type (Compute ):
1125+ for r in other_rv :
1126+ l ._einsum_to_rank_variables [compute .einsum ] = r
11001127 to_add = [l ]
11011128
11021129 zipped_groups .append (to_add )
@@ -1542,15 +1569,17 @@ def split_reservations(self):
15421569 new_nodes .append (node )
15431570 self .nodes = new_nodes
15441571
1545- def split_loop_with_multiple_rank_variables (self , stride_and_halo : dict [ str , tuple [ int , int ]] ):
1572+ def split_loop_with_multiple_rank_variables (self , einsum_name : EinsumName ):
15461573 new_nodes = []
1547- my_rank_variables = set (x [1 ] for x in stride_and_halo )
15481574 for node in self .nodes :
15491575 if isinstance (node , Loop ) and isinstance (node .rank_variable , set ):
1550- ranks_in_stride_and_halo = node .rank_variable & my_rank_variables
1551- assert len (ranks_in_stride_and_halo ) == 1 , f"Expected 1 rank in stride and halo, got { len (ranks_in_stride_and_halo )} "
1576+ if einsum_name not in node ._einsum_to_rank_variables :
1577+ raise ValueError (
1578+ f"Loop { node .compact_str ()} has multiple rank variables. Make "
1579+ f"sure to populate _einsum_to_rank_variables for this loop."
1580+ )
15521581 new_node = copy .copy (node )
1553- new_node .rank_variable = ranks_in_stride_and_halo . pop ()
1582+ new_node .rank_variable = node . _einsum_to_rank_variables [ einsum_name ]
15541583 new_nodes .append (new_node )
15551584 else :
15561585 new_nodes .append (node )
0 commit comments