diff --git a/accelforge/_deprecate/_simanneal/evalmapping.py b/accelforge/_deprecate/_simanneal/evalmapping.py index 5d8437b1..20701d68 100755 --- a/accelforge/_deprecate/_simanneal/evalmapping.py +++ b/accelforge/_deprecate/_simanneal/evalmapping.py @@ -1,11 +1,11 @@ from collections import defaultdict import itertools import time -from fastfusion._accelerated_imports import pd -from fastfusion.mapper.FFM._join_pmappings.sim import PmappingGroup, Loop, Compatibility -from fastfusion.mapper.FFM._join_pmappings.pmapping_group import PmappingDataframe -from fastfusion.mapper.simanneal.mapspaceglobals import MapspaceGlobals -from fastfusion.util._frozenset import fzs +from accelforge._accelerated_imports import pd +from accelforge.mapper.FFM._join_pmappings.sim import PmappingGroup, Loop, Compatibility +from accelforge.mapper.FFM._join_pmappings.pmapping_group import PmappingDataframe +from accelforge.mapper.simanneal.mapspaceglobals import MapspaceGlobals +from accelforge.util._frozenset import fzs def mapping2sims(einsum_to_result: Compatibility): diff --git a/accelforge/_deprecate/_simanneal/mapspaceglobals.py b/accelforge/_deprecate/_simanneal/mapspaceglobals.py index 96f7151b..d250d645 100755 --- a/accelforge/_deprecate/_simanneal/mapspaceglobals.py +++ b/accelforge/_deprecate/_simanneal/mapspaceglobals.py @@ -1,12 +1,12 @@ from collections import defaultdict import itertools -from fastfusion.frontend import arch -from fastfusion.frontend.spec import Spec -from fastfusion.mapper.FFM._join_pmappings.join_pmappings import PmappingGroup -from fastfusion.mapper.FFM._join_pmappings.compatibility import Loop, Compatibility -from fastfusion.util._frozenset import fzs -from fastfusion.mapper.FFM._join_pmappings.join_pmappings import ( +from accelforge.frontend import arch +from accelforge.frontend.spec import Spec +from accelforge.mapper.FFM._join_pmappings.join_pmappings import PmappingGroup +from accelforge.mapper.FFM._join_pmappings.compatibility import Loop, Compatibility +from accelforge.util._frozenset import fzs +from accelforge.mapper.FFM._join_pmappings.join_pmappings import ( make_full_equivalent_rank_variables, ) diff --git a/accelforge/_deprecate/_simanneal/simanneal.py b/accelforge/_deprecate/_simanneal/simanneal.py index d09dce73..775eee50 100755 --- a/accelforge/_deprecate/_simanneal/simanneal.py +++ b/accelforge/_deprecate/_simanneal/simanneal.py @@ -4,20 +4,20 @@ import random import threading import time -from fastfusion._accelerated_imports import pd -from fastfusion.mapper.simanneal.evalmapping import quick_join -from fastfusion.mapper.simanneal.tracking import EvaluationsScoreTracker -from fastfusion.mapper.FFM._join_pmappings.join_pmappings import PmappingGroup -from fastfusion.mapper.FFM._join_pmappings.compatibility import ( +from accelforge._accelerated_imports import pd +from accelforge.mapper.simanneal.evalmapping import quick_join +from accelforge.mapper.simanneal.tracking import EvaluationsScoreTracker +from accelforge.mapper.FFM._join_pmappings.join_pmappings import PmappingGroup +from accelforge.mapper.FFM._join_pmappings.compatibility import ( TensorReservation, Compatibility, ) -from fastfusion.mapper.FFM._join_pmappings.pmapping_group import ( +from accelforge.mapper.FFM._join_pmappings.pmapping_group import ( MAPPING_COLUMN, PmappingDataframe, ) -from fastfusion.util._frozenset import fzs -from fastfusion.mapper.simanneal.mapspaceglobals import MapspaceGlobals +from accelforge.util._frozenset import fzs +from accelforge.mapper.simanneal.mapspaceglobals import MapspaceGlobals OBJECTIVE_COLUMN = None # None -> Product diff --git a/accelforge/_deprecate/_simanneal/wrappers.py b/accelforge/_deprecate/_simanneal/wrappers.py index 765a82e3..2fa1c29b 100755 --- a/accelforge/_deprecate/_simanneal/wrappers.py +++ b/accelforge/_deprecate/_simanneal/wrappers.py @@ -3,18 +3,18 @@ import itertools import time from joblib import delayed -from fastfusion._accelerated_imports import pd -from fastfusion.frontend import arch -from fastfusion.frontend.spec import Spec -from fastfusion.mapper.FFM._join_pmappings.sim import PmappingGroup, Loop, Compatibility -from fastfusion.mapper.FFM._join_pmappings.pmapping_group import ( +from accelforge._accelerated_imports import pd +from accelforge.frontend import arch +from accelforge.frontend.spec import Spec +from accelforge.mapper.FFM._join_pmappings.sim import PmappingGroup, Loop, Compatibility +from accelforge.mapper.FFM._join_pmappings.pmapping_group import ( PmappingDataframe, is_reservation_col, ) -from fastfusion.mapper.simanneal.simanneal import MapspaceGlobals, _fuse_sims -from fastfusion.mapper.simanneal.tracking import EvaluationsScoreTracker -from fastfusion.util._frozenset import fzs -from fastfusion.util.parallel import parallel, util +from accelforge.mapper.simanneal.simanneal import MapspaceGlobals, _fuse_sims +from accelforge.mapper.simanneal.tracking import EvaluationsScoreTracker +from accelforge.util._frozenset import fzs +from accelforge.util.parallel import parallel, util def mapping2sims(einsum_to_result: Compatibility): diff --git a/accelforge/_deprecate/_simanneal2/__init__.py b/accelforge/_deprecate/_simanneal2/__init__.py index ec3c6a0a..2f9dc156 100755 --- a/accelforge/_deprecate/_simanneal2/__init__.py +++ b/accelforge/_deprecate/_simanneal2/__init__.py @@ -1,7 +1,7 @@ -from fastfusion.mapper.FFM.main import ( +from accelforge.mapper.FFM.main import ( make_pmappings, MultiEinsumPmappings, Mappings, ) -from fastfusion.frontend.mapper.metrics import Metrics +from accelforge.frontend.mapper.metrics import Metrics from .simanneal import join_pmappings diff --git a/accelforge/_deprecate/_simanneal2/simanneal.py b/accelforge/_deprecate/_simanneal2/simanneal.py index 9857dd19..47c02dcf 100755 --- a/accelforge/_deprecate/_simanneal2/simanneal.py +++ b/accelforge/_deprecate/_simanneal2/simanneal.py @@ -2,29 +2,29 @@ import os import random from typing import Callable, Generator -from fastfusion import arch, util -from fastfusion import Spec -from fastfusion.frontend.mapper.metrics import Metrics -from fastfusion.mapper.FFM.pmappings import MultiEinsumPmappings -from fastfusion.mapper.FFM._join_pmappings.compress_pmappings import ( +from accelforge import arch, util +from accelforge import Spec +from accelforge.frontend.mapper.metrics import Metrics +from accelforge.mapper.FFM.pmappings import MultiEinsumPmappings +from accelforge.mapper.FFM._join_pmappings.compress_pmappings import ( compress_einsum2pmappings, decompress_pmappings, ) -from fastfusion.frontend.workload import EinsumName -from fastfusion.frontend.mapping import Mapping -from fastfusion.mapper.FFM import PmappingGroup -from fastfusion.mapper.FFM._pareto_df.df_convention import ( +from accelforge.frontend.workload import EinsumName +from accelforge.frontend.mapping import Mapping +from accelforge.mapper.FFM import PmappingGroup +from accelforge.mapper.FFM._pareto_df.df_convention import ( MAPPING_COLUMN, col2nameloop, ) -from fastfusion.mapper.FFM._join_pmappings.pmapping_group import PmappingDataframe -from fastfusion.mapper.FFM._make_pmappings.make_pmappings import ( +from accelforge.mapper.FFM._join_pmappings.pmapping_group import PmappingDataframe +from accelforge.mapper.FFM._make_pmappings.make_pmappings import ( get_rank_variable_bounds_for_all_einsums, ) -from fastfusion._accelerated_imports import pd +from accelforge._accelerated_imports import pd import joblib -from fastfusion.mapper.FFM._join_pmappings.compatibility import Compatibility -from fastfusion.mapper._simanneal2.tracking import EvaluationsScoreTracker +from accelforge.mapper.FFM._join_pmappings.compatibility import Compatibility +from accelforge.mapper._simanneal2.tracking import EvaluationsScoreTracker # Simulated annealing algorithm # ----------------------------- diff --git a/accelforge/_deprecate/compatibility_util.py b/accelforge/_deprecate/compatibility_util.py index d8ae3f15..65e6f541 100755 --- a/accelforge/_deprecate/compatibility_util.py +++ b/accelforge/_deprecate/compatibility_util.py @@ -2,10 +2,10 @@ from collections.abc import Iterable, Set -from fastfusion.frontend.spec import Spec -from fastfusion.frontend.workload import EinsumName, TensorName -from fastfusion.mapper.FFM._join_pmappings.compatibility import Compatibility -from fastfusion.mapper.FFM._join_pmappings.sim import SIM +from accelforge.frontend.spec import Spec +from accelforge.frontend.workload import EinsumName, TensorName +from accelforge.mapper.FFM._join_pmappings.compatibility import Compatibility +from accelforge.mapper.FFM._join_pmappings.sim import SIM DO_PRINT = False diff --git a/accelforge/_deprecate/layerdeduplication/group_similar_einsums.py b/accelforge/_deprecate/layerdeduplication/group_similar_einsums.py index 2f5240dc..9f38eb85 100755 --- a/accelforge/_deprecate/layerdeduplication/group_similar_einsums.py +++ b/accelforge/_deprecate/layerdeduplication/group_similar_einsums.py @@ -2,10 +2,10 @@ from collections.abc import Iterable from itertools import permutations, product -from pytimeloop.bindings.looptree import LooptreeWorkload, LooptreeDependencyAnalyzer +from pytimeloop.bindings._looptree import LooptreeWorkload, LooptreeDependencyAnalyzer -from pytimeloop.looptree.mapping_utilities import get_intermediate_tensors -from fastfusion.util._frozenset import fzs +from pytimeloop._looptree.mapping_utilities import get_intermediate_tensors +from accelforge.util._frozenset import fzs from .grouped_einsums import GroupOfSimilarEinsums, Id diff --git a/accelforge/_deprecate/layerdeduplication/grouped_einsums.py b/accelforge/_deprecate/layerdeduplication/grouped_einsums.py index bb561faf..661d6a04 100755 --- a/accelforge/_deprecate/layerdeduplication/grouped_einsums.py +++ b/accelforge/_deprecate/layerdeduplication/grouped_einsums.py @@ -1,6 +1,6 @@ from collections.abc import Iterable -from bindings.looptree import LooptreeWorkload +from bindings._looptree import LooptreeWorkload type Id = int diff --git a/accelforge/_deprecate/mapping_filter_tags/ffmt.py b/accelforge/_deprecate/mapping_filter_tags/ffmt.py index 0d681375..d37a6482 100755 --- a/accelforge/_deprecate/mapping_filter_tags/ffmt.py +++ b/accelforge/_deprecate/mapping_filter_tags/ffmt.py @@ -1,5 +1,5 @@ -from fastfusion.frontend.mapping import Loop, Temporal -from fastfusion.mapper.FFM.deprecate_maybe.tags import Tags +from accelforge.frontend.mapping import Loop, Temporal +from accelforge.mapper.FFM.deprecate_maybe.tags import Tags from .util import get_fused_loops_per_tensor diff --git a/accelforge/_deprecate/mapping_filter_tags/onesplit.py b/accelforge/_deprecate/mapping_filter_tags/onesplit.py index 73f33d0c..0b6bdcdd 100755 --- a/accelforge/_deprecate/mapping_filter_tags/onesplit.py +++ b/accelforge/_deprecate/mapping_filter_tags/onesplit.py @@ -1,5 +1,5 @@ -from fastfusion.mapper.FFM.deprecate_maybe.tags import Tags -from fastfusion.mapper.FFM._join_pmappings.compatibility import Compatibility +from accelforge.mapper.FFM.deprecate_maybe.tags import Tags +from accelforge.mapper.FFM._join_pmappings.compatibility import Compatibility ONE_SPLIT = "ONE_SPLIT" diff --git a/accelforge/_deprecate/mapping_filter_tags/util.py b/accelforge/_deprecate/mapping_filter_tags/util.py index 70d4694a..0ff7c286 100755 --- a/accelforge/_deprecate/mapping_filter_tags/util.py +++ b/accelforge/_deprecate/mapping_filter_tags/util.py @@ -1,4 +1,4 @@ -from fastfusion.frontend.mapping import Reservation, Loop, Mapping +from accelforge.frontend.mapping import Reservation, Loop, Mapping def get_fused_loops_per_tensor( diff --git a/accelforge/_deprecate/tags.py b/accelforge/_deprecate/tags.py index 747097fa..82eb58d2 100755 --- a/accelforge/_deprecate/tags.py +++ b/accelforge/_deprecate/tags.py @@ -11,7 +11,7 @@ TagCompatibility as classes for the keys. """ -from fastfusion.util._frozenset import fzs +from accelforge.util._frozenset import fzs class TagClass(fzs): diff --git a/accelforge/_deprecate/viz/__init__.py b/accelforge/_deprecate/viz/__init__.py old mode 100755 new mode 100644 diff --git a/accelforge/_deprecate/viz/interactive.py b/accelforge/_deprecate/viz/interactive.py index 4aa8a8d5..8022cffb 100755 --- a/accelforge/_deprecate/viz/interactive.py +++ b/accelforge/_deprecate/viz/interactive.py @@ -4,9 +4,9 @@ from IPython.display import SVG, display import plotly.graph_objs as go from ipywidgets import Output, VBox, HBox -from fastfusion._accelerated_imports import pd -from fastfusion.mapper.FFM._join_pmappings.pmapping_dataframe import row2pmappings -from fastfusion.frontend.mapping import Mapping +from accelforge._accelerated_imports import pd +from accelforge.mapper.FFM._join_pmappings.pmapping_dataframe import row2pmappings +from accelforge.frontend.mapping import Mapping def make_mapping(row, einsum_names, rank_variable_bounds): diff --git a/accelforge/_deprecate/viz/reservationtree.py b/accelforge/_deprecate/viz/reservationtree.py index d2204254..8f957a87 100755 --- a/accelforge/_deprecate/viz/reservationtree.py +++ b/accelforge/_deprecate/viz/reservationtree.py @@ -1,13 +1,13 @@ from collections import defaultdict import pydot from typing import Any, Iterable -from fastfusion.mapper.FFM._join_pmappings.pmapping_group import ( +from accelforge.mapper.FFM._join_pmappings.pmapping_group import ( Compatibility, TensorReservation, Loop, ) -from fastfusion.util import _expfmt -from fastfusion.mapper.FFM._join_pmappings.pmapping_dataframe import col2nameloop +from accelforge.util import _expfmt +from accelforge.mapper.FFM._join_pmappings.pmapping_dataframe import col2nameloop PYDOT_NODE_DEFAULTS = { "shape": "box", diff --git a/accelforge/_deprecate/viz/ski_slope.py b/accelforge/_deprecate/viz/ski_slope.py index ef9a07a7..008bb9df 100755 --- a/accelforge/_deprecate/viz/ski_slope.py +++ b/accelforge/_deprecate/viz/ski_slope.py @@ -1,9 +1,9 @@ import matplotlib.axes as mpax import matplotlib.pyplot as plt -from fastfusion._accelerated_imports import np -from fastfusion._accelerated_imports import pd +from accelforge._accelerated_imports import np +from accelforge._accelerated_imports import pd -from fastfusion.mapper.FFM._join_pmappings.pmapping_dataframe import PmappingDataframe +from accelforge.mapper.FFM._join_pmappings.pmapping_dataframe import PmappingDataframe DATAFLOW_COLUMN = "dataflow" diff --git a/accelforge/frontend/_binding.py b/accelforge/frontend/_binding.py old mode 100755 new mode 100644 index 4b3b56a8..01dd0b18 --- a/accelforge/frontend/_binding.py +++ b/accelforge/frontend/_binding.py @@ -4,9 +4,9 @@ """ from abc import abstractmethod -from typing import Dict, Tuple +from typing import Dict, Set, Tuple, TypeAlias -from pydantic import StrictFloat +from pydantic import StrictFloat, model_validator import islpy as isl from accelforge.util._basetypes import EvalableDict, EvalableList, EvalableModel @@ -17,38 +17,27 @@ class Domain(EvalableModel): Represents an architecture dangling reference of the binding. """ + _prefix: str name: str - + dims: EvalableList[str] + @property - @abstractmethod def isl_space(self) -> isl.Space: - """Gets the domain as an isl.Space""" - raise NotImplementedError(f"{type(self)} has not implemented isl_space") + return isl.Space.create_from_names( + isl.DEFAULT_CONTEXT, set=self.dims + ).set_tuple_name(isl.dim_type.set, f"{self._prefix}_{self.name}_dims") @property - @abstractmethod - def isl_universe(self) -> isl.Set: - """Gets the domain as an isl.Set""" - raise NotImplementedError(f"{type(self)} has not implemented isl_universe") + def isl_universe(self) -> isl.Map: + return isl.Map.universe(self.isl_space) class LogicalDomain(Domain): """ Represents the logical architecture domain space of logical dims × tensor ranks. """ - - ranks: Tuple[str] = ("c", "h", "w", "p", "q", "r", "s") - l_dims: EvalableList[str] - - @property - def isl_space(self) -> isl.Space: - return isl.Space.create_from_names( - isl.DEFAULT_CONTEXT, in_=self.ranks, out=self.l_dims - ).set_tuple_name(isl.dim_type.out, f"l_{self.name}_dims") - - @property - def isl_universe(self) -> isl.Map: - return isl.Map.universe(self.isl_space) + _prefix: str = 'l' + pass class PhysicalDomain(Domain): @@ -56,18 +45,8 @@ class PhysicalDomain(Domain): Represents the logical architecture domain space of physical dims. The physical space is defined as the physical architecture dims. """ - - p_dims: EvalableList[str] - - @property - def isl_space(self) -> isl.Space: - return isl.Space.create_from_names( - isl.DEFAULT_CONTEXT, set=self.p_dims - ).set_tuple_name(isl.dim_type.set, f"p_{self.name}_dims") - - @property - def isl_universe(self) -> isl.Set: - return isl.Set.universe(self.isl_space) + _prefix: str = 'p' + pass class BindingNode(EvalableModel): @@ -81,8 +60,11 @@ class BindingNode(EvalableModel): """ logical: LogicalDomain + """The logical domain of the components being bound.""" physical: PhysicalDomain + """The physical location of the components being bound.""" relations: EvalableDict[str, str] + """A relation between each tensor and its logical domain to physical domain.""" @property def isl_relations(self) -> Dict[str, isl.Map]: @@ -94,11 +76,8 @@ def isl_relations(self) -> Dict[str, isl.Map]: def islify_relation(key: str) -> isl.Map: """Converts a relation at a given key into isl""" relation: str = self.relations[key] - logical_space: isl.Space = self.logical.isl_space.set_tuple_name( - isl.dim_type.in_, f"{key}_ranks" - ) - - binding_space: isl.Space = logical_space.wrap().map_from_domain_and_range( + logical_space: isl.Space = self.logical.isl_space + binding_space: isl.Space = logical_space.map_from_domain_and_range( range=self.physical.isl_space, ) @@ -125,4 +104,166 @@ class Binding(EvalableModel): logical and physical space. """ + version: StrictFloat + """Version of the binding spec.""" nodes: EvalableList[BindingNode] + """Parts of the binding.""" + +''' +class NetworkOnChip(ParsableModel): + """A model of a network-on-chip on the physical chip.""" + + name: str + """NoC name""" + cost: isl.PwMultiAff + """Cost of traversing the NoC""" + domain: isl.Set + """The defined points of the NoC""" + + @model_validator(mode="after") + def validate(self): + """Ensures the domain is not empty.""" + if self.domain.is_empty(): + raise ValueError("NoC domain cannot be empty:\n" f"Domain: {self.domain}") + + +class Placement(ParsableModel): + """The location(s) of a hardware component physically on the chip.""" + + noc: NetworkOnChip + "The NoC objects are being placed on." + placement: isl.Map + "The placement of objects on the NoC." + + @model_validator(mode="after") + def validate(self) -> "Placement": + """ + Validates that the `placement` exists on the `noc`. + """ + placement: isl.Map = self.placement + if placement.is_empty(): + raise ValueError(f"Placement cannot be empty: {placement}") + + noc_domain: isl.Set = self.noc.domain + placement_range: isl.Set = placement.range() + if not placement_range.is_subset(noc_domain): + raise ValueError( + "Placement has areas off the NoC:\n" + "--------------------------------\n" + f"Placement Range: {placement_range}\n" + f"NoC Domain: {noc_domain}" + ) + + return self + + +class PhysicalComponent(ParsableModel): + """The description of the physical components.""" + + name: str + """Name of the physical component.""" + placement: Placement + """Placement of the physical component on chip.""" + + +class PhysicalComponents(ParsableModel): + """List of componenents on the physical chip.""" + + components: EvalableList[PhysicalComponent] + """The components referred to.""" + + +# For static analysis of types being used. +PhysicalStorage: TypeAlias = PhysicalComponent +PhysicalProcessingElement: TypeAlias = PhysicalComponent + + +# Ports connecting the different NoC Components. +class Port(ParsableModel): + """Connection between two physical components of a chip and the cost to use.""" + + parent: NetworkOnChip + child: NetworkOnChip + relation: isl.Map + + @model_validator(mode="after") + def validate(self): + # Ensures the parent and child NoCs are not the same. + if self.parent.name == self.child.name: + raise ValueError( + "Parent should not be equal to the child.\n" + f"Parent: {self.parent}\n" + f"Child: {self.child}\n" + ) + + # Ensures port map is not null. + if self.relation.is_empty(): + raise ValueError("Port cannot be empty\n" f"Port: {Port}") + # Ensures the relation is contained in both NoCs. + parent_domain: isl.Set = self.parent.domain + child_domain: isl.Set = self.child.domain + ports: isl.Map = self.relation.domain().unwrap() + if not ports.range().is_subset(child_domain): + raise ValueError( + "Port map range is not a subset of the child domain.\n" + f"Ports: {ports.range()}\n" + f"Child: {self.child}" + ) + if not ports.domain().is_subset(parent_domain): + raise ValueError( + "Port map domain is not a subset of the parent domain.\n" + f"Ports: {ports.domain()}\n" + f"Child: {self.parent}" + ) + + +class PhysicalSpec(ParsableModel): + """Physical specification of an accelerator.""" + + name: str + nocs: Set[NetworkOnChip] + ports: Tuple[Port] + components: Set[PhysicalComponent] + + @model_validator(mode="after") + def validate(self): + # Checks that the networks are not empty. + if not self.networks: + raise ValueError(f"A physical chip {self.name} cannot have no NoCs.") + # Checks that the components are not empty. + if not self.components: + raise ValueError(f"A physical chip {self.name} cannot have no components.") + # Check that the components are on on-chip NoCs. + for component in self.components: + if component.placement.noc not in self.nocs: + raise ValueError( + "Component placed on invalid NoC.\n" + f"Component: {component}\n" + f"NoCs: {self.nocs}" + ) + # TODO: Check that all ports form a DAG between the networks and all + # networks are connected. + + +class LogicalComponent(EvalableDict): + """Logical components of an accelerator.""" + + name: str + indices: isl.Set + + +class LogicalSpec(ParsableModel): + """Logical specification of an accelerator.""" + + name: str + components: Set[LogicalComponent] + + +class BindingSpec(ParsableModel): + """The binding of physical to logical components.""" + + name: str + physical: PhysicalSpec + logical: LogicalSpec + bindings: Set[isl.Map] +''' diff --git a/accelforge/mapper/FFM/_join_pmappings/__init__.py b/accelforge/mapper/FFM/_join_pmappings/__init__.py old mode 100755 new mode 100644 diff --git a/accelforge/mapper/FFM/_make_pmappings/contraints/__init__.py b/accelforge/mapper/FFM/_make_pmappings/contraints/__init__.py old mode 100755 new mode 100644 diff --git a/accelforge/model/_looptree/__init__.py b/accelforge/model/_looptree/__init__.py old mode 100755 new mode 100644 diff --git a/accelforge/model/_looptree/accesses.py b/accelforge/model/_looptree/accesses.py index b4906315..43ee728a 100755 --- a/accelforge/model/_looptree/accesses.py +++ b/accelforge/model/_looptree/accesses.py @@ -1,7 +1,7 @@ from dataclasses import dataclass from typing import Optional, overload -# from bindings.looptree import TemporalTag, SequentialTag, PipelineTemporalTag +# from bindings._looptree import TemporalTag, SequentialTag, PipelineTemporalTag import islpy as isl diff --git a/accelforge/model/_looptree/equivalent_ranks.py b/accelforge/model/_looptree/equivalent_ranks.py index 32ed91d3..98ca9a16 100755 --- a/accelforge/model/_looptree/equivalent_ranks.py +++ b/accelforge/model/_looptree/equivalent_ranks.py @@ -1,4 +1,4 @@ -# from bindings.looptree import LooptreeWorkload, LooptreeWorkloadDependencyAnalyzer +# from bindings._looptree import LooptreeWorkload, LooptreeWorkloadDependencyAnalyzer class EquivalentGroups: diff --git a/accelforge/model/_looptree/latency/latency.py b/accelforge/model/_looptree/latency/latency.py index be6c3a05..0ece6429 100755 --- a/accelforge/model/_looptree/latency/latency.py +++ b/accelforge/model/_looptree/latency/latency.py @@ -9,7 +9,7 @@ from accelforge.util._sympy.broadcast_max import Max -# from bindings.looptree import SpatialTag +# from bindings._looptree import SpatialTag def get_latency(looptree_results, mapping, workload, flattened_arch): diff --git a/accelforge/model/_looptree/latency/processors.py b/accelforge/model/_looptree/latency/processors.py index 96c226b4..4a1533e9 100755 --- a/accelforge/model/_looptree/latency/processors.py +++ b/accelforge/model/_looptree/latency/processors.py @@ -2,7 +2,7 @@ import islpy as isl -# from bindings.looptree import PipelineSpatialTag +# from bindings._looptree import PipelineSpatialTag # from pytimeloop._isl.sum import sum_until_idx, make_reduction_map # from pytimeloop._isl.qpolynomial import from_pw_qpolynomial_fold diff --git a/accelforge/model/_looptree/reuse/isl/des.py b/accelforge/model/_looptree/reuse/isl/des.py index bc1b3ed1..a7890af3 100755 --- a/accelforge/model/_looptree/reuse/isl/des.py +++ b/accelforge/model/_looptree/reuse/isl/des.py @@ -22,7 +22,7 @@ class IslReuseAnalysisOutput: def deserialize_looptree_output( - looptree_output, isl_ctx: isl.Context #: bindings.looptree.LooptreeResult, + looptree_output, isl_ctx: isl.Context #: bindings._looptree._looptreeResult, ) -> IslReuseAnalysisOutput: output = IslReuseAnalysisOutput() diff --git a/accelforge/model/_looptree/reuse/isl/distributed/bind.py b/accelforge/model/_looptree/reuse/isl/distributed/bind.py new file mode 100644 index 00000000..351e9cb7 --- /dev/null +++ b/accelforge/model/_looptree/reuse/isl/distributed/bind.py @@ -0,0 +1,23 @@ +"""Applies the binding layer into one that can be used for later analysis,""" +from accelforge.frontend.binding import Binding +from accelforge.frontend.mapping import Mapping +from accelforge.frontend.workload import Workload + +from accelforge.model._looptree.reuse.isl.mapping_to_isl.analyze_mapping import ( + occupancies_from_mapping, + MappingAnalysisResult +) + + + +def apply_binding(binding: Binding, mapping: Mapping, workload: Workload): + """ + Given a mapping, apply the mapping from logical components onto physical + components. + """ + map_analysis: MappingAnalysisResult = occupancies_from_mapping( + mapping, workload + ) + for buffet, occ in map_analysis.buffet_to_occupancy.items(): + print(occ) + print(binding) diff --git a/accelforge/model/_looptree/reuse/isl/distributed/distributed_buffers.py b/accelforge/model/_looptree/reuse/isl/distributed/distributed_buffers.py new file mode 100644 index 00000000..5ef302b0 --- /dev/null +++ b/accelforge/model/_looptree/reuse/isl/distributed/distributed_buffers.py @@ -0,0 +1,253 @@ +""" +Models for handling calculating the cost of a Workload on distributed buffer +architectures. +""" + +import logging + +import islpy as isl + +from accelforge.frontend.mapping import MappingNode +from accelforge.model._looptree.reuse.isl.isl_functions import dim_projector_mask +from accelforge.model._looptree.reuse.isl.mapping_to_isl import DUMP_ISL_IR +from accelforge.model._looptree.reuse.isl.mapping_to_isl.types import Fill, Occupancy +from accelforge.model._looptree.reuse.isl.spatial import ( + Reads, + Transfers, + TransferInfo, + TransferModel, +) + + +def identify_mesh_casts( + src_occupancy: isl.Map, dst_fill: isl.Map, dist_fn: isl.Map +) -> isl.Map: + """ + Given srcs with data, fills to destinations, and a distance function, identify per data + the srcs delivering that data to dsts. + + Parameters + ---------- + src_occupancy: + An isl.Map of the form { [src] -> [data] } corresponding to the data held + at the buffer at space `src`. + dst_fill: + An isl.Map of the form { [dst] -> [data] } corresponding to the data requested + at the element at space `dst`. + dist_fn: + A distance function { [src -> dst] -> [hops] } that accepts two points in + space, corresponding to the `src` and `dst`, and returns the distance + between the two points in terms of `hops`, a quantized atomic distance of + data transmission cost. + + Returns + ------- + { [data] -> [dst -> src] } where { [dst] -> [data] } and { [src] -> [data] } are in + `src_occupancy` and `dst_fill` respectively, and where `[dst -> src]` is the infimum of + `dst_fn(src, dst), ∀ src, dst s.t. { [src] -> [data] } ∈ `src_occupancy` and + `{ [dst] -> [data] }` ∈ `dst_fill`. + """ + # Makes { [dst -> data] -> [dst -> data] } + fill_to_fill: isl.Map = dst_fill.wrap().identity() + if DUMP_ISL_IR: + logging.info(f"fill_to_fill: {fill_to_fill}") + + # Inverts src_occupancy s.t. data -> src. + # i.e. { [xs, ys] -> [d0, d1] } to { [d0, d1] -> [xs, ys] } + data_presence: isl.Map = src_occupancy.reverse() + + # { [dst -> data] -> [dst -> src] } where src contains data. + fills_to_matches: isl.Map = fill_to_fill.uncurry( # { [[dst -> data] -> dst] -> data } + ).apply_range(data_presence # { [[dst -> data] -> dst] -> src } + ).curry() # { [[dst -> data] -> [dst -> src] } + if DUMP_ISL_IR: + logging.info(f"fills_to_matches: {fills_to_matches}") + + # Calculates the distance of a fill to the nearest src satisfying the fill. + # { [dst -> data] -> [dist] } + fill_min_dist: isl.Map = fills_to_matches.apply_range(dist_fn).lexmin() + # Isolates the relevant minimal pairs. + # { [dst -> data] -> [dst -> src] :.dst -> src is minimized distance } + minimal_pairs: isl.Map = fill_min_dist.apply_range( + # Note: Need to match fill -> min_dist with min_dist -> [fill -> match] as lexmin over + # fill and match will minimize distance over the tuple (src, dst, data), but that + # overconstrains the optimization as we want to minimize over distance (dst, data) + # only for all src. + fills_to_matches.range_map().apply_range(dist_fn).reverse() + ).range().unwrap() + if DUMP_ISL_IR: + logging.info(f"minimal_pairs: {minimal_pairs}") + + # Isolates the multicast networks. + # { [data] -> [dst -> src] : dst -> src is minimized distance } + multicast_networks: isl.Map = minimal_pairs.curry().range().unwrap() + # Devolves to a single source if multiple sources per domain point. + multicast_networks = multicast_networks.uncurry().lexmin().curry() + + return multicast_networks + + +def calculate_extents_per_dim(mcns: isl.Map) -> list[isl.PwAff]: + """ + Parameters + ---------- + mcns: + Mesh cast-networks, or networks in which all dsts per data are grouped with + the closest src containing the data. + + Returns + ------- + A list of `isl.PwAff` that gives the max extent (length) along dim_i per mcn, + where i is the i-th `isl.PwAff`. + + Preconditions + ------------- + `mcns` were generated with a Manhattan distance `dst_fn` by `identify_mesh_casts` + s.t. all dimensions are orthogonal to each other in a metric space, where each + unit movement in a dimension counts as 1 hop. + + We also assume `dst_fn` is translationally invariant (i.e., ∀src, dst, + src', dst' ∈ space, if |src - dst| = |src' - dst'|, + dst_fn(src, dst) = dst_fn(src', dst'). + """ + # Makes mcns from { [data] -> [dst -> src] } to { [data -> src] -> [dst] } + potential_srcs: isl.Map = mcns.range_reverse().uncurry() + # Sources are part of the extents, so we union it with the destinations. + # { [data -> src] -> [src] } + srcs: isl.Map = potential_srcs.domain().unwrap().range_map() + # { [data -> src] -> [spacetime] } + casting_extents: isl.Map = srcs.union(potential_srcs) + + # Projects away all dimensions but one to find their extent for hypercube. + dims: int = potential_srcs.range_tuple_dim() + # Creates a mask of what to project out. + project_out_mask: list[bool] = [True] * dims + dim_extents: list[isl.PwAff] = [None] * dims + + # Gets the extents of all dimensions + for noc_dim in range(dims): + # Project out all the dimensions of the output besides noc_dim. + project_out_mask[noc_dim] = False + # { [spacetime] -> [dimension] } + extent_mapper: isl.Map = dim_projector_mask( + casting_extents.range().get_space(), project_out_mask + ).reverse() + dim_extent_space: isl.Map = casting_extents.apply_range(extent_mapper) + project_out_mask[noc_dim] = True + + # Finds max(noc_dim) - min(noc_dim) for each [data -> src] + max_extent: isl.PwAff = dim_extent_space.dim_max(0) + min_extent: isl.PwAff = dim_extent_space.dim_min(0) + + # Subtracts the max from the min to get the extent per [data -> src] + dim_extents[noc_dim] = max_extent.sub(min_extent).coalesce() + + return dim_extents + + +class HypercubeMulticastModel(TransferModel): + """ + Does distributed multicasting a mesh using worst-case multicasting + behavior by assuming all multicasts are broadcasting to the convex + hypercube that encapsulates all their destinations and sources. + """ + + def __init__(self, dist_fn: isl.Map): + """ + Initializes the HypercubeMulticastModel with the distance function + over the metric space. + + Because we are using calculate_extents_per_dim(mcns), we inherit the + following requirements: + `dst_fn` holds all dimensions are orthogonal to each other in a metric space, + where each unit movement in a dimension counts as 1 hop. + + We also assume `dst_fn` is translationally invariant (i.e., ∀src, dst, + src', dst' ∈ space, if |src - dst| = |src' - dst'|, + dst_fn(src, dst) = dst_fn(src', dst'). + """ + self.dist_fn = dist_fn + + def apply(self, buff: MappingNode, fills: Fill, occs: Occupancy) -> TransferInfo: + """ + Given a buffer, its fills across time, and its occupancies across time, + calculate the spatial transfers." + + Parameters + ---------- + buff: + The buffer whose spatial analysis is being considered. Currently, + we rely on dist_fn to deal with this rather than buffer. + fills: + The fill of `buffer` across time from parents. + occs: + The occupancy of `buffer` across time. + + Returns + ------- + Fills that were fulfilled, Fills that were unfilled, and parent reads per + position in spacetime. Then, gets hops per timestep. + """ + mcs: isl.Map = identify_mesh_casts(occs.map_, fills.map_, self.dist_fn) + result: isl.PwQPolynomial = self._cost_mesh_cast_hypercube(mcs) + + # TODO: Read once from all buffers, assert that + # card(mcs) == tensor_size * duplication factor + num_meshcasts: int = mcs.card() + return TransferInfo( + fulfilled_fill=Transfers(fills.tags, fills.map_), + parent_reads=Reads(occs.tags, mcs), + unfulfilled_fill=Fill(fills.tags, fills.map_.subtract(fills.map_)), + hops=result, + link_transfer=True, + ) + + def _cost_mesh_cast_hypercube(self, mcns: isl.Map) -> int: + """ + Given a multicast_network, calculate the hypercube. + + Parameters + ---------- + mcns: + Multicast networks grouped together by [srcs -> data] fulfilling + [dsts -> data], where there is at least 1 src and 1 dst in each mcn. + + Returns + ------- + The upperbound of doing all the multicasts specified by the multicast + networks, assuming they cast only to the convex space of the network. + + Preconditions + ------------- + Because we are using calculate_extents_per_dim(mcns), we inherit the + following requirements: + `mcns` were generated with a Manhattan distance `dst_fn` by `identify_mesh_casts` + s.t. all dimensions are orthogonal to each other in a metric space, where each + unit movement in a dimension counts as 1 hop. + + We also assume `dst_fn` is translationally invariant (i.e., ∀src, dst, + src', dst' ∈ space, if |src - dst| = |src' - dst'|, + dst_fn(src, dst) = dst_fn(src', dst'). + """ + dim_extents: list[isl.PwAff] = calculate_extents_per_dim(mcns) + # Tracks the total cost of the hypercube cast per [src -> data] + one: isl.PwAff = isl.PwAff.val_on_domain(dim_extents[0].domain(), 1) + hypercube_costs = isl.PwQPolynomial.from_pw_aff(one) + + # Calculates the cost of the hypercube, where the hypercube cost + # = \sum_{i=0}^{D} ((extent_i - 1) * \prod_{j=0}^{i-1} extent_j) + # = (\prod_{i=0}^{D} extent_i) - 1 + for dim_extent in dim_extents: + # Adds the dim_extent times the casting volume to the hypercube + # cost. + dim_plus: isl.PwQPolynomial = isl.PwQPolynomial.from_pw_aff( + dim_extent.add(one).coalesce() + ) + hypercube_costs = hypercube_costs.mul(dim_plus).coalesce() + hypercube_costs = hypercube_costs.sub(isl.PwQPolynomial.from_pw_aff(one)) + + # Tracks the total cost of the hyppercube cast per data. + hypercube_costs = hypercube_costs.sum() + + # Return the hypercube cost as a piecewise polynomial. + return hypercube_costs.sum() diff --git a/accelforge/model/_looptree/run.py b/accelforge/model/_looptree/run.py index f1218af3..9c9d4d58 100755 --- a/accelforge/model/_looptree/run.py +++ b/accelforge/model/_looptree/run.py @@ -12,8 +12,8 @@ class LoopTreeStatistics: def run_symbolic_model(mapping, workload, architecture): - from pytimeloop.looptree.reuse import analyze_reuse_and_add_reservations_to_mapping - from pytimeloop.looptree.energy import gather_actions + from pytimeloop._looptree.reuse import analyze_reuse_and_add_reservations_to_mapping + from pytimeloop._looptree.energy import gather_actions job = Job.make_job(mapping=mapping, workload=workload, architecture=architecture) result = analyze_reuse_and_add_reservations_to_mapping(job) @@ -24,12 +24,12 @@ def run_symbolic_model(mapping, workload, architecture): def run_looptree(config_dir, paths, tmp_path, bindings, call_accelergy): import islpy as isl from bindings.config import Config - from bindings.looptree import LooptreeModelApp, LooptreeWorkload + from bindings._looptree import LooptreeModelApp, LooptreeWorkload from pytimeloop.file import gather_yaml_configs - from pytimeloop.looptree.capacity import compute_capacity_usage - from pytimeloop.looptree.reuse._isl.des import deserialize_looptree_output - from pytimeloop.looptree.energy import gather_actions, compute_energy_from_actions - from pytimeloop.looptree.latency import get_latency + from pytimeloop._looptree.capacity import compute_capacity_usage + from pytimeloop._looptree.reuse._isl.des import deserialize_looptree_output + from pytimeloop._looptree.energy import gather_actions, compute_energy_from_actions + from pytimeloop._looptree.latency import get_latency from pytimeloop.timeloopfe.v4fused import Spec from pytimeloop.timeloopfe.common.backend_calls import call_accelergy_verbose @@ -73,12 +73,12 @@ def run_looptree(config_dir, paths, tmp_path, bindings, call_accelergy): def run_looptree_symbolic(config_dir, paths, tmp_path, bindings, call_accelergy): from bindings.config import Config - from bindings.looptree import LooptreeWorkload, LooptreeWorkloadDependencyAnalyzer + from bindings._looptree import LooptreeWorkload, LooptreeWorkloadDependencyAnalyzer from pytimeloop.file import gather_yaml_configs - from pytimeloop.looptree.capacity import compute_capacity_usage - from pytimeloop.looptree.reuse import analyze_reuse_and_add_reservations_to_mapping - from pytimeloop.looptree.energy import gather_actions, compute_energy_from_actions - from pytimeloop.looptree.latency import get_latency + from pytimeloop._looptree.capacity import compute_capacity_usage + from pytimeloop._looptree.reuse import analyze_reuse_and_add_reservations_to_mapping + from pytimeloop._looptree.energy import gather_actions, compute_energy_from_actions + from pytimeloop._looptree.latency import get_latency from pytimeloop.timeloopfe.v4fused import Spec from pytimeloop.timeloopfe.common.backend_calls import call_accelergy_verbose from accelforge.mapper.FFM._make_pmappings.pmapper_job import Job diff --git a/accelforge/model/_looptree/visualization/__init__.py b/accelforge/model/_looptree/visualization/__init__.py old mode 100755 new mode 100644 diff --git a/accelforge/util/_sympy/__init__.py b/accelforge/util/_sympy/__init__.py old mode 100755 new mode 100644 diff --git a/pyproject.toml b/pyproject.toml index fd588027..10cde9bf 100755 --- a/pyproject.toml +++ b/pyproject.toml @@ -21,7 +21,7 @@ dependencies = [ "pydantic_core>=2.33.0", "ruamel.yaml>=0.18.0", "jinja2>=3.1.0", - "islpy>=2025.0.0", + "islpy-barvinok == 2025.2.5", "sympy>=1.14.0", "paretoset>=1.2.5", "matplotlib>=3.10.0", diff --git a/tests/distribuffers/spec/binding/valid_bindings.yaml b/tests/distribuffers/spec/binding/valid_bindings.yaml deleted file mode 100755 index ccf22993..00000000 --- a/tests/distribuffers/spec/binding/valid_bindings.yaml +++ /dev/null @@ -1,97 +0,0 @@ -- binding: - nodes: - - logical: - name: PE - l_dims: [i] - physical: - name: PE - p_dims: [x, y] - relations: - tensorA: i = x + y * 2 # This is a dimension-major compression into the logical. It is bijective. - tensorB: i = x + y * 2 # This is a dimension-major compression into the logical. It is bijective. - - logical: - name: Scratchpad - l_dims: [x, y] - physical: - name: GLB - p_dims: [a, b] - relations: - tensorA: x = a and y = b - tensorB: x = b and y = a - solution: - nodes: - - - tensorA: | - { - [ - tensorA_ranks[c, h, w, p, q, r, s] -> - l_PE_dims[i] - ] -> - p_PE_dims[x, y] : - i = x + 2y - } - tensorB: | - { - [ - tensorB_ranks[c, h, w, p, q, r, s] -> - l_PE_dims[i] - ] -> - p_PE_dims[x, y] : - i = x + (y * 2) - } - - - tensorA: | - { - [ - tensorA_ranks[c, h, w, p, q, r, s] -> - l_Scratchpad_dims[x, y] - ] -> - p_GLB_dims[ a, b ] : - x = a and y = b - } - tensorB: | - { - [ - tensorB_ranks[c, h, w, p, q, r, s] -> - l_Scratchpad_dims[x, y] - ] -> - p_GLB_dims[ a, b ] : - x = b and y = a - } - -- binding: - nodes: - - logical: - name: DRAM - l_dims: [i] - physical: - name: DRAM - p_dims: [i] - relation: # Compression relation where less DRAM chips than planned. - weights: i = i // 2 - inputs: i = i // 2 - outputs: i = i // 2 - - logical: - name: Scratchpad - l_dims: [i] - physical: - name: GLB - p_dims: [x, y, z] - relation: # weight stationary relation - weights: c = x and h = y and w = z - inputs: i=x and i=y and i=z - outputs: i=x and i=y and i=z - - logical: - name: PE_Buffer - l_dims: [i] - physical: - name: GLB - l_dims: [x, y, z] - relation: # Some weird bypass shenanigans. - weights: - inputs: - outputs: - - - -- diff --git a/tests/distribuffers/__init__.py b/tests/isl/__init__.py old mode 100755 new mode 100644 similarity index 100% rename from tests/distribuffers/__init__.py rename to tests/isl/__init__.py diff --git a/tests/mapper/__init__.py b/tests/isl/distributed/__init__.py similarity index 100% rename from tests/mapper/__init__.py rename to tests/isl/distributed/__init__.py diff --git a/tests/distribuffers/multicast/test_cases.yaml b/tests/isl/distributed/multicast/test_cases.yaml similarity index 95% rename from tests/distribuffers/multicast/test_cases.yaml rename to tests/isl/distributed/multicast/test_cases.yaml index 35c2fe27..32dbc8eb 100755 --- a/tests/distribuffers/multicast/test_cases.yaml +++ b/tests/isl/distributed/multicast/test_cases.yaml @@ -7,7 +7,7 @@ - type: Spatial spatial_dim: 1 target: 0 - dist_func: &2d_manhattan | + dist_fn: &2d_manhattan | { [noc[xd, yd] -> noc[xs, ys]] -> hops[(xd - xs) + (yd - ys)] : xd >= xs and yd >= ys; @@ -27,12 +27,12 @@ - occ: "{ noc[xs, ys] -> data[d0, d1] : d0=xs and d1=ys and 0 <= xs < 8 and 0 <= ys < 8 }" fill: "{ noc[xd, yd] -> data[d0, d1] : 0 <= d0 < 8 and d1=yd and 0 <= xd < 8 and 0 <= yd < 8 }" dims: *2d_spatial - dist_func: *2d_manhattan + dist_fn: *2d_manhattan expected: *equivalent_class_1 - occ: "{ noc[xs, ys] -> data[d0, d1] : d0 = xs and 0 <= d1 < 8 and 0 <= xs < 8 and 0 <= ys < 8 }" fill: "{ noc[xd, yd] -> data[d0, d1] : d0 = xd and d1 = yd and 0 <= xd < 8 and 0 <= yd < 8 }" dims: *2d_spatial - dist_func: *2d_manhattan + dist_fn: *2d_manhattan expected: &equivalent_class_2 latency: 0 total_hops: 0 @@ -42,7 +42,7 @@ - occ: "{ noc[xs, ys] -> data[d0, d1] : 0 <= d0 < 8 and d1 = ys and 0 <= xs < 8 and 0 <= ys < 8 }" fill: "{ noc[xd, yd] -> data[d0, d1] : d0 = xd and d1 = yd and 0 <= xd < 8 and 0 <= yd < 8 }" dims: *2d_spatial - dist_func: *2d_manhattan + dist_fn: *2d_manhattan expected: *equivalent_class_2 ####################### @@ -73,7 +73,7 @@ - type: Spatial spatial_dim: 1 target: 0 - dist_func: &2t-dim_2s-dim_manhattan | + dist_fn: &2t-dim_2s-dim_manhattan | { [noc[tm, tn, xd, yd] -> noc[tm, tn, xs, ys]] -> hops[(xd - xs) + (yd - ys)] : xd >= xs and yd >= ys; @@ -115,7 +115,7 @@ - type: Spatial spatial_dim: 1 target: 0 - dist_func: &3t-dim_2s-dim_manhattan | + dist_fn: &3t-dim_2s-dim_manhattan | { [noc[tm, tn, tk, xd, yd] -> noc[tm, tn, tk, xs, ys]] -> hops[(xd - xs) + (yd - ys)] : xd >= xs and yd >= ys; @@ -150,7 +150,7 @@ m = (8 * tm) + xd and 0 <= k < 64 } dims: *2t-dim_2s-dim - dist_func: *2t-dim_2s-dim_manhattan + dist_fn: *2t-dim_2s-dim_manhattan expected: latency: null total_hops: null @@ -174,7 +174,7 @@ 0 <= k < 64 and k=tk } dims: *3t-dim_2s-dim - dist_func: *3t-dim_2s-dim_manhattan + dist_fn: *3t-dim_2s-dim_manhattan expected: latency: null total_hops: null @@ -193,7 +193,7 @@ 0 <= k < 64 and 0 = (yd - k) % 8 } dims: *2t-dim_2s-dim - dist_func: *2t-dim_2s-dim_manhattan + dist_fn: *2t-dim_2s-dim_manhattan expected: latency: null total_hops: null @@ -217,7 +217,7 @@ m = (8 * tm) + xd and k=tk } dims: *3t-dim_2s-dim - dist_func: *3t-dim_2s-dim_manhattan + dist_fn: *3t-dim_2s-dim_manhattan expected: latency: null total_hops: null @@ -237,7 +237,7 @@ 0 <= k < 64 and 0 = (yd - k) % 8 } dims: *2t-dim_2s-dim - dist_func: *2t-dim_2s-dim_manhattan + dist_fn: *2t-dim_2s-dim_manhattan expected: latency: null total_hops: null @@ -261,7 +261,7 @@ m = (8 * tm) + xd and k=tk } dims: *3t-dim_2s-dim - dist_func: *3t-dim_2s-dim_manhattan + dist_fn: *3t-dim_2s-dim_manhattan expected: latency: null total_hops: null @@ -280,7 +280,7 @@ 0 <= k < 64 and 0 = (yd - k) % 8 } dims: *2t-dim_2s-dim - dist_func: *2t-dim_2s-dim_manhattan + dist_fn: *2t-dim_2s-dim_manhattan expected: latency: null total_hops: null @@ -298,7 +298,7 @@ } fill: *8x8_pe dims: *3t-dim_2s-dim - dist_func: *3t-dim_2s-dim_manhattan + dist_fn: *3t-dim_2s-dim_manhattan expected: latency: null total_hops: null @@ -308,14 +308,14 @@ # - occ: "{ noc[xs, ys] -> data[d0, d1] : 0 <= d0 < 8 and 0 <= d1 < 8 and (xs=0 or 3<=xs<=4 or xs=7) and (ys=0 or 3<=ys<=4 or ys=7) }" # fill: "{ noc[xd, yd] -> data[d0, d1] : d0 = xd and d1 = yd and 0 <= xd < 8 and 0 <= yd < 8 }" # dims: *2d_spatial -# dist_func: *2d_manhattan +# dist_fn: *2d_manhattan # expected: # latency: 2 # total_hops: 64 # multicast_hops: null # - occ: "{ noc[xs, ys] -> data[d0, d1] : d0 = xs and d1 = ys and 0 <= xs < 8 and 0 <= ys < 8 }" # fill: "{ noc[xd, yd] -> data[d0, d1] : 0 <= d0 < 8 and 0 <= d1 < 8 and (xd=0 or 3<=xd<=4 or xd=7) and (yd=0 or 3<=yd<=4 or yd=7) }" -# dist_func: *2d_manhattan +# dist_fn: *2d_manhattan # dims: *2d_spatial # expected: # latency: 14 @@ -328,7 +328,7 @@ # } # fill: "{ noc[xd, yd] -> data[d0, d1] : d0 = xd % 3 and d1 = yd % 3 and 0 <= xd < 9 and 0 <= yd < 9 }" # dims: *2d_spatial -# dist_func: *2d_manhattan +# dist_fn: *2d_manhattan # expected: # latency: 2 # total_hops: 108 @@ -340,21 +340,21 @@ # 0 <= xd < 9 and 0 <= yd < 9 and xd % 3 = 1 and yd % 3 = 1 # } # dims: *2d_spatial -# dist_func: *2d_manhattan +# dist_fn: *2d_manhattan # expected: # latency: 2 # total_hops: 108 # - occ: "{ [xs, ys] -> [d0] : d0=xs and 0 <= xs < 8 and ys = 0 }" # fill: "{ [xd, yd] -> [d0] : d0=xd and 0 <= xd < 8 and 0 <= yd < 8 }" # dims: *2d_spatial -# dist_func: *2d_manhattan +# dist_fn: *2d_manhattan # expected: # latency: 7 # total_hops: 224 # - occ: "{ [xs, ys] -> [d0] : d0=xs and 0 <= xs < 8 and 0 <= ys < 8 }" # fill: "{ [xd, yd] -> [d0] : d0=xd and 0 <= xd < 8 and yd = 0 }" # dims: *2d_spatial -# dist_func: *2d_manhattan +# dist_fn: *2d_manhattan # expected: # latency: 0 # total_hops: 0 @@ -381,7 +381,7 @@ # - Spatial # - 3 # - 0 -# dist_func: &4d_manhattan | +# dist_fn: &4d_manhattan | # { # [[xd, yd, z1d, z2d] -> [xs, ys, z1s, z2s]] -> # [(xd - xs) + (yd - ys) + (z1d - z1s) + (z2d - z2s)] : @@ -452,13 +452,13 @@ # 0 <= xd < 8 and 0 <= yd < 8 and 0 <= z1d < 8 and 0 <= z2d < 8 # } # dims: *4d_spatial -# dist_func: *4d_manhattan +# dist_fn: *4d_manhattan # expected: # latency: 28 # total_hops: null # - occ: "{ [xs] -> [d0] : 0 <= d0 < 8 and xs = 0 }" # fill: "{ [xd] -> [d0] : d0 = xd and 0 <= xd < 8 }" -# dist_func: &ring_dist_size_8 | +# dist_fn: &ring_dist_size_8 | # { # [[xd] -> [xs]] -> [(xd-xs) % 8] : # (xd-xs)%8 <= (xs-xd)%8; @@ -470,13 +470,13 @@ # total_hops: 16 # - occ: "{ [xs] -> [d0] : 0 <= xs < 8 and d0 = xs }" # fill: "{ [xd] -> [d0] : 0 <= d0 < 8 and xd = 0 }" -# dist_func: *ring_dist_size_8 +# dist_fn: *ring_dist_size_8 # expected: # latency: 4 # total_hops: 16 # - occ: "{ [xs] -> [d0] : xs <= d0 <= xs + 1 and xs % 2 = 0}" # fill: "{ [xd] -> [d0] : d0 = xd and 0 <= xd < 8 }" -# dist_func: *ring_dist_size_8 +# dist_fn: *ring_dist_size_8 # expected: # latency: 1 # total_hops: 4 \ No newline at end of file diff --git a/tests/isl/distributed/spec/binding.yaml b/tests/isl/distributed/spec/binding.yaml new file mode 100644 index 00000000..3a375931 --- /dev/null +++ b/tests/isl/distributed/spec/binding.yaml @@ -0,0 +1,26 @@ +- binding: + version: 0.4 + nodes: + - logical: + name: weight_reg + dims: [i] + physical: + name: weight_reg + dims: [x, y] + relations: + tensorA: i = x + y * 2 # This is a dimension-major compression into the logical. It is bijective. + tensorB: i = x + y * 2 # This is a dimension-major compression into the logical. It is bijective. + - logical: + name: GLB + dims: [i] + physical: + name: Scratchpad + dims: [x, y] + relations: + T0: x + |X| * y = m + W0: x = n0 + +# TODO: example where logical components do not exist in the physical +# Every binding creates a different buffet, so the physical PE may serve +# as a logical PE and as a logical buffer, and that's creating diff buffets +# Do not worry about mem mgmt for now. diff --git a/tests/isl/distributed/spec/binding/valid_bindings.yaml b/tests/isl/distributed/spec/binding/valid_bindings.yaml new file mode 100755 index 00000000..d5e44d88 --- /dev/null +++ b/tests/isl/distributed/spec/binding/valid_bindings.yaml @@ -0,0 +1,81 @@ +- binding: + version: 0.4 + nodes: + - logical: + name: PE + dims: [i] + physical: + name: PE + dims: [x, y] + relations: + tensorA: i = x + y * 2 # This is a dimension-major compression into the logical. It is bijective. + tensorB: i = x + y * 2 # This is a dimension-major compression into the logical. It is bijective. + - logical: + name: Scratchpad + dims: [x, y] + physical: + name: GLB + dims: [a, b] + relations: + tensorA: x = a and y = b + tensorB: x = b and y = a + solution: + version: 0.4 + nodes: + - + tensorA: | + { + l_PE_dims[i] -> p_PE_dims[x, y] : + i = x + 2y + } + tensorB: | + { + l_PE_dims[i] -> p_PE_dims[x, y] : + i = x + (y * 2) + } + - + tensorA: | + { + l_Scratchpad_dims[x, y] -> p_GLB_dims[ a, b ] : + x = a and y = b + } + tensorB: | + { + l_Scratchpad_dims[x, y] -> p_GLB_dims[ a, b ] : + x = b and y = a + } + +# - binding: +# version: 0.4 +# nodes: +# - logical: +# name: DRAM +# l_dims: [i] +# physical: +# name: DRAM +# p_dims: [i] +# relations: # Compression relation where less DRAM chips than planned. +# weights: i = i // 2 +# inputs: i = i // 2 +# outputs: i = i // 2 +# - logical: +# name: Scratchpad +# l_dims: [i] +# physical: +# name: GLB +# p_dims: [x, y, z] +# relations: # weight stationary relation +# weights: c = x and h = y and w = z +# inputs: i=x and i=y and i=z +# outputs: i=x and i=y and i=z +# - logical: +# name: PE_Buffer +# l_dims: [i] +# physical: +# name: GLB +# l_dims: [x, y, z] +# relations: # Some weird bypass shenanigans. +# weights: +# inputs: +# outputs: + diff --git a/tests/distribuffers/spec/distributed.yaml b/tests/isl/distributed/spec/distributed.yaml similarity index 100% rename from tests/distribuffers/spec/distributed.yaml rename to tests/isl/distributed/spec/distributed.yaml diff --git a/tests/distribuffers/spec/logical_arch.yaml b/tests/isl/distributed/spec/logical_arch.yaml similarity index 53% rename from tests/distribuffers/spec/logical_arch.yaml rename to tests/isl/distributed/spec/logical_arch.yaml index 79a1bb80..89fb6a05 100755 --- a/tests/distribuffers/spec/logical_arch.yaml +++ b/tests/isl/distributed/spec/logical_arch.yaml @@ -4,29 +4,25 @@ arch: name: DRAM # offchip DRAM is the source of all datatypes class: DRAM # assume DRAM is large enough to store all the data, so no depth specification needed attributes: - width: 64 # width in bits - - - !Container - name: PE - spatial: {meshX: 1, meshY: 2} - - # registers for the mac unit - - !Component - name: weight_reg - class: reg_storage - attributes: {depth: 1, width: 8} + datawidth: 32 - !Component - name: input_activation_reg - class: reg_storage - attributes: {depth: 1, width: 8} + name: GLB + class: SRAM + # TODO: attributes are placeholders. + attributes: + datawidth: 16 + width: 128 + size: 256 - - !Component + # registers for the mac unit + - !Memory name: output_activation_reg class: reg_storage - attributes: {depth: 1, width: 8} + spatial: {meshI: 8} + attributes: {depth: 1, datawidth: datawidth, width: datawidth} - - !Component + - !Compute name: mac class: mac_compute attributes: {num_pipline_stages: 2} \ No newline at end of file diff --git a/tests/isl/distributed/spec/matmul.workload.yaml b/tests/isl/distributed/spec/matmul.workload.yaml new file mode 100755 index 00000000..089f9518 --- /dev/null +++ b/tests/isl/distributed/spec/matmul.workload.yaml @@ -0,0 +1,13 @@ +workload: + version: "0.5" + shape: + m: 0 <= m < 128 + n0: 0 <= n0 < 64 + n1: 0 <= n1 < 128 + + einsums: + - name: Matmul1 + tensor_accesses: + - {name: T0, projection: [m, n0]} + - {name: W0, projection: [n0, n1]} + - {name: T1, projection: [m, n1], output: True} diff --git a/tests/distribuffers/spec/physical_arch.yaml b/tests/isl/distributed/spec/physical_arch.yaml similarity index 50% rename from tests/distribuffers/spec/physical_arch.yaml rename to tests/isl/distributed/spec/physical_arch.yaml index 96fc5d56..48332c2b 100755 --- a/tests/distribuffers/spec/physical_arch.yaml +++ b/tests/isl/distributed/spec/physical_arch.yaml @@ -3,65 +3,48 @@ arch: nodes: - !Container name: system_arch + # TODO: attributes are placeholders. attributes: # Top-level attributes inherited by all components unless overridden technology: "45nm" cycle_period: 1e-9 - - !Network - name: L2 - topology: Mesh - dims: - - x - constraints: - - 0 <= x < 2 - !Network name: L1 topology: Mesh - dims: + spatial: - x - y constraints: - - 0 <= x < 1 - - -1 <= y <= 1 - port: | - {L2[x] -> L1[x', y'] : x = 1 and x' = 0 and y' = 0} - + - -1 <= x < 2 + - 0 <= y <= 16 + - !Component name: DRAM # offchip DRAM is the source of all datatypes class: DRAM # assume DRAM is large enough to store all the data, so no depth specification needed attributes: width: 64 # width in bits network: - level: L2 - placement: "{ DRAM[i] -> L2[i] | i = 1 }" + level: L1 + placement: "{ DRAM[i] -> L1[x, y] : i = 0 and x = 0 and y = 0 }" - - !Container - name: PE + # registers for the mac unit + - !Memory + name: Scratchpad + class: SRAM + attributes: + datawidth: 16 + width: 16 + size: 40 + dims: [i, j] + spatial: + meshI: 2 + meshJ: 16 network: level: L1 placement: | - { - PE[i, j] -> L1[x, y] : - j = y and x < 0 and i = -x - 1; - j = y and x > 0 and i = x; - } - - # registers for the mac unit - - !Component - name: weight_reg - class: reg_storage - attributes: {depth: 1, width: 8} - - - !Component - name: input_activation_reg - class: reg_storage - attributes: {depth: 1, width: 8} - - - !Component - name: output_activation_reg - class: reg_storage - attributes: {depth: 1, width: 8} + meshJ = y and x < 0 and meshI = -x - 1; + meshJ = y and x > 0 and meshI = x; - !Component name: mac diff --git a/tests/distribuffers/test_binding.py b/tests/isl/distributed/test_binding.py similarity index 94% rename from tests/distribuffers/test_binding.py rename to tests/isl/distributed/test_binding.py index 2abc0681..528c93f4 100755 --- a/tests/distribuffers/test_binding.py +++ b/tests/isl/distributed/test_binding.py @@ -3,11 +3,12 @@ from typing import Dict, List from islpy import DEFAULT_CONTEXT, Map +from ruamel.yaml import YAML -import yaml from accelforge.frontend._binding import Binding, BindingNode TESTS_DIR = Path(__file__).parent / "spec" / "binding" +yaml = YAML(typ="safe") class TestBindingMapper(unittest.TestCase): @@ -18,7 +19,7 @@ def test_valid_bindings(self): """ specs_file: str = TESTS_DIR / "valid_bindings.yaml" with open(specs_file, mode="r", encoding="utf-8") as f: - specs: List = yaml.safe_load(f) + specs: List = yaml.load(f) spec: Dict for spec in specs: diff --git a/tests/isl/distributed/test_binding_application.py b/tests/isl/distributed/test_binding_application.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/isl/distributed/test_multicast.py b/tests/isl/distributed/test_multicast.py new file mode 100644 index 00000000..6dfb5e38 --- /dev/null +++ b/tests/isl/distributed/test_multicast.py @@ -0,0 +1,88 @@ +""" +File ported from here: +https://github.com/rengzhengcodes/timeloop/blob/distributed-multicast-dev/src/unit-test/multicast/test-multicast.cpp +""" + +import unittest +from pathlib import Path + +import islpy as isl + +from accelforge.model._looptree.reuse.isl.mapping_to_isl.types import ( + # Data movement descriptors. + Fill, + Occupancy, + # Tags + Tag, + SpatialTag, + TemporalTag, +) +from accelforge.model._looptree.reuse.isl.distributed.distributed_buffers import ( + HypercubeMulticastModel, +) +from accelforge.model._looptree.reuse.isl.spatial import TransferInfo +from ..util import load_solutions + + +def construct_spacetime(dims: list) -> list[Tag]: + """ + Given a list of dimension tags as strings, convert them into the proper `Tag` + objects. + + Parameters + ---------- + dims: + The list of dim tags as strings. + + Returns + ------- + list[Tag] where list[i] is the tag corresponding to dims[i]. + """ + spacetime: list[Tag] = [] + for dim in dims: + if dim["type"] == "Temporal": + spacetime.append(TemporalTag()) + elif dim["type"] == "Spatial": + spacetime.append(SpatialTag(dim["spatial_dim"], dim["target"])) + + return spacetime + + +class TestHypercubeMulticastModel(unittest.TestCase): + """ + Tests the HypercubeMulticastModel with a series of premade test cases. + """ + + TEST_CASES_FILE: str = Path(__file__).parent / "multicast" / "test_cases.yaml" + testcases: dict = load_solutions(TEST_CASES_FILE) + + def test_gamut(self): + """ + Tests the entire gamut of test cases we have specified in the yaml. + """ + for test in self.testcases: + # Reads test case parameters and constructs the necessary objects. + dim_tags: list[Tag] = construct_spacetime(test["dims"]) + fill: Fill = Fill(dim_tags, test["fill"]) + occ: Occupancy = Occupancy(dim_tags, test["occ"]) + dist_fn: isl.Map = test["dist_fn"] + multicast_model: HypercubeMulticastModel = HypercubeMulticastModel( + dist_fn + ) + + # Applies the model. + info: TransferInfo = multicast_model.apply(0, fill, occ) + # Checks the results. + sum_extract: int = info.hops.eval( + isl.Point.zero(info.hops.domain().get_space()) + ) + + # The block is used for debugging test cases not yet implemented. + if test["expected"]["hypercube_hops"] is None: + print("~~~Test case in progress:~~~") + print(f"Fill: {fill}") + print(f"Occ: {occ}") + print(f"Dist Fn: {dist_fn}") + print(f"Returned: {sum_extract}") + else: + assert sum_extract == test["expected"]["hypercube_hops"] diff --git a/tests/isl/mapper/__init__.py b/tests/isl/mapper/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/mapper/configs/conv1d/conv1d.mapping.yaml b/tests/isl/mapper/configs/conv1d/conv1d.mapping.yaml similarity index 100% rename from tests/mapper/configs/conv1d/conv1d.mapping.yaml rename to tests/isl/mapper/configs/conv1d/conv1d.mapping.yaml diff --git a/tests/mapper/configs/conv1d/conv1d.workload.yaml b/tests/isl/mapper/configs/conv1d/conv1d.workload.yaml similarity index 100% rename from tests/mapper/configs/conv1d/conv1d.workload.yaml rename to tests/isl/mapper/configs/conv1d/conv1d.workload.yaml diff --git a/tests/mapper/configs/two_conv1d/two_conv1d.expected.yaml b/tests/isl/mapper/configs/two_conv1d/two_conv1d.expected.yaml similarity index 100% rename from tests/mapper/configs/two_conv1d/two_conv1d.expected.yaml rename to tests/isl/mapper/configs/two_conv1d/two_conv1d.expected.yaml diff --git a/tests/mapper/configs/two_conv1d/two_conv1d.mapping.yaml b/tests/isl/mapper/configs/two_conv1d/two_conv1d.mapping.yaml similarity index 100% rename from tests/mapper/configs/two_conv1d/two_conv1d.mapping.yaml rename to tests/isl/mapper/configs/two_conv1d/two_conv1d.mapping.yaml diff --git a/tests/mapper/configs/two_conv1d/two_conv1d.workload.yaml b/tests/isl/mapper/configs/two_conv1d/two_conv1d.workload.yaml similarity index 100% rename from tests/mapper/configs/two_conv1d/two_conv1d.workload.yaml rename to tests/isl/mapper/configs/two_conv1d/two_conv1d.workload.yaml diff --git a/tests/isl/mapper/test_isl_functions.py b/tests/isl/mapper/test_isl_functions.py new file mode 100644 index 00000000..2385df05 --- /dev/null +++ b/tests/isl/mapper/test_isl_functions.py @@ -0,0 +1,17 @@ +""" +Tests some of the key custom isl functions. +""" + +import unittest + +import islpy as isl + +from accelforge.model._looptree.reuse.isl.isl_functions import * + +class BasicIslFunctionTests(unittest.TestCase): + """ + Prima facie checks of `isl_functions` to ensure basic functionality. + """ + + def test_project_dim_after(self): + pass \ No newline at end of file diff --git a/tests/mapper/test_mapping_to_isl.py b/tests/isl/mapper/test_mapping_to_isl.py similarity index 97% rename from tests/mapper/test_mapping_to_isl.py rename to tests/isl/mapper/test_mapping_to_isl.py index 94007dc1..d58d2d27 100644 --- a/tests/mapper/test_mapping_to_isl.py +++ b/tests/isl/mapper/test_mapping_to_isl.py @@ -17,8 +17,9 @@ MappingAnalysisResult, ) -from .util import TEST_CONFIG_PATH, load_solutions +from ..util import load_solutions +TEST_CONFIG_PATH: Path = Path(__file__).parent / "configs" class TestMappingToIsl(unittest.TestCase): """ diff --git a/tests/mapper/test_spatial_reuse_analysis.py b/tests/isl/mapper/test_spatial_reuse_analysis.py similarity index 97% rename from tests/mapper/test_spatial_reuse_analysis.py rename to tests/isl/mapper/test_spatial_reuse_analysis.py index f5cf1c09..2f38d6cb 100644 --- a/tests/mapper/test_spatial_reuse_analysis.py +++ b/tests/isl/mapper/test_spatial_reuse_analysis.py @@ -28,7 +28,7 @@ class TestSimpleLinkTransferModel(unittest.TestCase): def test_simple_link_transfer_model_sandbox(self): """Independent sanity check of `SimpleLinkTransferModel`.""" - buffer: MappingNode = MappingNode() + buffer: MappingNode = ["used for reference checks"] fill: Fill = Fill( [TemporalTag(), SpatialTag(0, buffer), SpatialTag(1, buffer)], # type: ignore isl.Map.read_from_str( diff --git a/tests/mapper/test_temporal_reuse_analysis.py b/tests/isl/mapper/test_temporal_reuse_analysis.py similarity index 100% rename from tests/mapper/test_temporal_reuse_analysis.py rename to tests/isl/mapper/test_temporal_reuse_analysis.py diff --git a/tests/mapper/util.py b/tests/isl/util.py similarity index 91% rename from tests/mapper/util.py rename to tests/isl/util.py index f70f521c..484417a7 100644 --- a/tests/mapper/util.py +++ b/tests/isl/util.py @@ -7,9 +7,6 @@ from ruamel.yaml import YAML -TEST_CONFIG_PATH: Path = Path(__file__).parent / "configs" - - def to_isl_maps(obj: str | list | dict) -> dict: """ Given an object, attempt to reduce all strings in tree with isl.Map @@ -29,7 +26,10 @@ def _to_isl_maps(obj: str | dict | list) -> isl.Map | dict | list: if isinstance(obj, str): return isl.Map.read_from_str(isl.DEFAULT_CONTEXT, obj) if isinstance(obj, dict): - return {k: _to_isl_maps(v) for k, v in obj.items()} + return { + k: (_to_isl_maps(v) if k != 'type' else v) + for k, v in obj.items() + } if isinstance(obj, list): return [_to_isl_maps(v) for v in obj] return obj diff --git a/tests/test_ffm_join_pmappings.py b/tests/test_ffm_join_pmappings.py old mode 100755 new mode 100644 diff --git a/tests/test_ffm_make_pmappings.py b/tests/test_ffm_make_pmappings.py old mode 100755 new mode 100644 index e3ce308d..c108cf27 --- a/tests/test_ffm_make_pmappings.py +++ b/tests/test_ffm_make_pmappings.py @@ -21,7 +21,7 @@ def test_mha(self): PARENT_DIR / "mha.workload.yaml", PARENT_DIR / "mha.renames.yaml", ) - spec.mapper.metrics = Metrics.ENERGY | Metrics.LATENCY + spec.mapper.ffm.metrics = Metrics.ENERGY | Metrics.LATENCY pmappings = make_pmappings(spec, ["Q"]) def test_mha_full(self): diff --git a/tests/test_ffm_make_tile_shapes.py b/tests/test_ffm_make_tile_shapes.py old mode 100755 new mode 100644 diff --git a/tests/test_symbolic_model.py b/tests/test_symbolic_model.py old mode 100755 new mode 100644 diff --git a/tests/test_workload.py b/tests/test_workload.py old mode 100755 new mode 100644