From a64773e1423ba4befc32c9b7ef01e560c1635b8b Mon Sep 17 00:00:00 2001 From: Renggeng Zheng Date: Wed, 19 Nov 2025 14:47:08 -0500 Subject: [PATCH 01/28] ported testing portions of distributed multicast code --- tests/distribuffers/test_multicast.py | 165 ++++++++++++++++++++++++++ 1 file changed, 165 insertions(+) create mode 100644 tests/distribuffers/test_multicast.py diff --git a/tests/distribuffers/test_multicast.py b/tests/distribuffers/test_multicast.py new file mode 100644 index 00000000..9fd18ba9 --- /dev/null +++ b/tests/distribuffers/test_multicast.py @@ -0,0 +1,165 @@ +""" +File ported from here: +https://github.com/rengzhengcodes/timeloop/blob/distributed-multicast-dev/src/unit-test/multicast/test-multicast.cpp +""" + +from fastfusion.model.looptree.reuse.isl.spatial import TransferInfo +from tests.mapper.util import load_solutions +import unittest +from math import isclose + +import islpy as isl + +from fastfusion.model.looptree.reuse.isl.mapping_to_isl.types ( + # Data movement descriptors. + Fill, + Occupancy, + # Tags + Tag, + SpatialTag, + TemporalTag +) + +def construct_spacetime(dims: list) -> list[Tag]: + spacetime: list[Tag] = [] + for dim in dims: + if dim["type"] == "Temporal": + spacetime.append(TemporalTag()) + elif dim["type"] -- "Spatial": + spacetime.append( + Spatial(dim["spatial_dim"], dim["target"]) + ) + + return spacetime + + +class TestSimpleMulticastModel(unittest.TestCase): + """ + Tests the distributed simple multicast model. + """ + + def test_0(self): + fill: Fill = Fill( + TemporalTag(), SpatialTag(0, None), SpatialTag(1, None), + isl.Map( + isl.DEFAULT_CONTEXT, + "{ spacetime[t, x, y] -> data[t + x + y] : " + "0 <= x < 2 and 0 <= y < 2 and 0 <= t < 4 }" + ) + ) + occ: Occupancy = Occupancy( + fill.tags, fill.map_ + ) + + multicast_model: SimpleMulticastModel = SimpleMulticastModel(False) + info = multicast_model.apply(0, fill, occ) + + assert info.fulfilled_map.map_ == isl.Map( + isl.DEFAULT_CONTEXT, + "{ spacetime[t, x, y] -> data[t + x + y] : " + "0 <= t < 4 and 0 <= x < 2 and 0 <= y < 2 }" + ) + assert info.parent_reads.map_ == isl.Map( + isl.DEFAULT_CONTEXT, + "{ spacetime[t] -> data[d] : 0 <= t < 4 and t <= d < t + 3 }" + ) + assert len(info.compat_access_stats) == 1 + + for mutlicast_scatter, stats in info.compat_access_stats: + multicast, scatter = mutlicast_scatter + + assert multicast == 1 + assert scatter == 1 + assert stats.accesses == 12 + assert stats.hops == 1 + + multicast_model: SimpleMulticastModel = SimpleMulticastModel(True) + info = multicast_model.Apply(0, fill, occ) + + assert info.fulfilled_fill.map_ == isl.Map( + isl.DEFAULT_CONTEXT, + "{ spacetime[t, x, y] -> data[t + x + y] : " + "0 <= t < 4 and 0 <= x < 2 and 0 <= y < 2 }" + ) + assert info.parent_reads.map_ == isl.Map( + isl.DEFAULT_CONTEXT, + "{ spacetime[t] -> data[d] : 0 <= t < 4 and t <= d < t + 3 }" + ) + assert len(info.compat_access_stats) == 1 + for multicast_scatter, stats in info.compat_access_stats: + multicast, scatter = multicast_scatter + + assert multicast == 1 + assert scatter == 1 + assert stats.accesses == 12 + assert isclose( + stats.hops, 3.667, + abs_tol=0.001 + ) + + def test_spatial_PC(self): + fill: Fill = Fill( + [TemporalTag(), SpatialTag(0, None), SpatialTag(1, None)], + isl.Map( + isl.DEFAULT_CONTEXT, + "{ spacetime[t, x, y] -> data[d, y] : " + "0 <= x < 4 and 0 <= y < 2 and 0 <= t < 4 and x <= d < x+2 }" + ) + ) + occ: Occupancy = Occupancy(fill.tags, fill.map_) + + multicast_model: SimpleMulticastModel = SimpleMulticastModel(True) + + info = multicast_model.apply(0, fill, occ) + + assert info.fulfilled_fill.map_ == isl.Map( + isl.DEFAULT_CONTEXT, + "{ spacetime[t, x, y] -> data[d, y] : " + "0 <= x < 4 and 0 <= y < 2 and 0 <= t < 4 and x <= d < x+2 }" + ) + assert info.parent_reads.map_ == isl.Map( + isl.DEFAULT_CONTEXT, + "{ spacetime[t] -> data[d, y] : 0 <= y < 2 and 0 <= t < 4 and 0 <= d < 5 }" + ) + assert len(info.compat_access_stats) == 1 + + for multicast_scatter, stats in info.compat_access_stats: + multicast, scatter = multicast_scatter + assert multicast == 1 + assert scatter == 1 + assert stats.accesses == 40 + assert isclose(stats.hops, 5.2, abs_tol=0.001) + + +class TestHypercubeMulticastModel(unittest.TestCase): + TEST_CASES_FILE: str = Path(__file__).parent / "multicast" + testcases: dict = load_solutions(TEST_CASES_FILE / "multicast" / "test_cases.yaml") + + def test_gamut(self): + for test in testcases: + # Reads test case parameters and constructs the necessary objects. + buf_id: int = 0 + dim_tags: list[Tags] = construct_spacetime(test["dims"]) + fill: Fill = Fill(dims, test["fill"]) + occ: Occupancy = Occupancy(dims, test["occ"]) + dist_func: isl.Map = isl.Map(isl.DEFAULT_CONTEXT, test["dist_func"]) + multicast_model: HypercubeMulticastModel = HyperCubeMulticastModel( + True, dist_func + ) + + # Applies the model. + info: TransferInfo = multicast_model.apply(buf_id, fill, occ) + # Checks the results + sum_extract: int = info.p_hops.eval( + isl.Point.zero(info.p_hops.domain()) + ) + + # 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}") + print(f"Returned: {sum_extract}") + else: + assert ret == test["expected"]["hypercube_hops"] From 6e1f9532861a5cb0fbfff85627285ded84baa789 Mon Sep 17 00:00:00 2001 From: Renggeng Zheng Date: Wed, 19 Nov 2025 14:47:58 -0500 Subject: [PATCH 02/28] added test_isl_functions boilerplate + moved all isl tests to its own folder --- tests/{ => isl}/distribuffers/__init__.py | 0 .../distribuffers/multicast/test_cases.yaml | 0 .../spec/binding/valid_bindings.yaml | 0 .../distribuffers/spec/distributed.yaml | 0 .../distribuffers/spec/logical_arch.yaml | 0 .../distribuffers/spec/physical_arch.yaml | 0 tests/{ => isl}/distribuffers/test_binding.py | 0 tests/{ => isl}/distribuffers/test_multicast.py | 0 tests/{ => isl}/mapper/__init__.py | 0 .../mapper/configs/conv1d/conv1d.mapping.yaml | 0 .../mapper/configs/conv1d/conv1d.workload.yaml | 0 .../configs/two_conv1d/two_conv1d.expected.yaml | 0 .../configs/two_conv1d/two_conv1d.mapping.yaml | 0 .../configs/two_conv1d/two_conv1d.workload.yaml | 0 tests/isl/mapper/test_isl_functions.py | 17 +++++++++++++++++ tests/{ => isl}/mapper/test_mapping_to_isl.py | 0 .../mapper/test_spatial_reuse_analysis.py | 0 .../mapper/test_temporal_reuse_analysis.py | 0 tests/{ => isl}/mapper/util.py | 0 19 files changed, 17 insertions(+) rename tests/{ => isl}/distribuffers/__init__.py (100%) rename tests/{ => isl}/distribuffers/multicast/test_cases.yaml (100%) rename tests/{ => isl}/distribuffers/spec/binding/valid_bindings.yaml (100%) rename tests/{ => isl}/distribuffers/spec/distributed.yaml (100%) rename tests/{ => isl}/distribuffers/spec/logical_arch.yaml (100%) rename tests/{ => isl}/distribuffers/spec/physical_arch.yaml (100%) rename tests/{ => isl}/distribuffers/test_binding.py (100%) rename tests/{ => isl}/distribuffers/test_multicast.py (100%) rename tests/{ => isl}/mapper/__init__.py (100%) rename tests/{ => isl}/mapper/configs/conv1d/conv1d.mapping.yaml (100%) rename tests/{ => isl}/mapper/configs/conv1d/conv1d.workload.yaml (100%) rename tests/{ => isl}/mapper/configs/two_conv1d/two_conv1d.expected.yaml (100%) rename tests/{ => isl}/mapper/configs/two_conv1d/two_conv1d.mapping.yaml (100%) rename tests/{ => isl}/mapper/configs/two_conv1d/two_conv1d.workload.yaml (100%) create mode 100644 tests/isl/mapper/test_isl_functions.py rename tests/{ => isl}/mapper/test_mapping_to_isl.py (100%) rename tests/{ => isl}/mapper/test_spatial_reuse_analysis.py (100%) rename tests/{ => isl}/mapper/test_temporal_reuse_analysis.py (100%) rename tests/{ => isl}/mapper/util.py (100%) diff --git a/tests/distribuffers/__init__.py b/tests/isl/distribuffers/__init__.py similarity index 100% rename from tests/distribuffers/__init__.py rename to tests/isl/distribuffers/__init__.py diff --git a/tests/distribuffers/multicast/test_cases.yaml b/tests/isl/distribuffers/multicast/test_cases.yaml similarity index 100% rename from tests/distribuffers/multicast/test_cases.yaml rename to tests/isl/distribuffers/multicast/test_cases.yaml diff --git a/tests/distribuffers/spec/binding/valid_bindings.yaml b/tests/isl/distribuffers/spec/binding/valid_bindings.yaml similarity index 100% rename from tests/distribuffers/spec/binding/valid_bindings.yaml rename to tests/isl/distribuffers/spec/binding/valid_bindings.yaml diff --git a/tests/distribuffers/spec/distributed.yaml b/tests/isl/distribuffers/spec/distributed.yaml similarity index 100% rename from tests/distribuffers/spec/distributed.yaml rename to tests/isl/distribuffers/spec/distributed.yaml diff --git a/tests/distribuffers/spec/logical_arch.yaml b/tests/isl/distribuffers/spec/logical_arch.yaml similarity index 100% rename from tests/distribuffers/spec/logical_arch.yaml rename to tests/isl/distribuffers/spec/logical_arch.yaml diff --git a/tests/distribuffers/spec/physical_arch.yaml b/tests/isl/distribuffers/spec/physical_arch.yaml similarity index 100% rename from tests/distribuffers/spec/physical_arch.yaml rename to tests/isl/distribuffers/spec/physical_arch.yaml diff --git a/tests/distribuffers/test_binding.py b/tests/isl/distribuffers/test_binding.py similarity index 100% rename from tests/distribuffers/test_binding.py rename to tests/isl/distribuffers/test_binding.py diff --git a/tests/distribuffers/test_multicast.py b/tests/isl/distribuffers/test_multicast.py similarity index 100% rename from tests/distribuffers/test_multicast.py rename to tests/isl/distribuffers/test_multicast.py diff --git a/tests/mapper/__init__.py b/tests/isl/mapper/__init__.py similarity index 100% rename from tests/mapper/__init__.py rename to tests/isl/mapper/__init__.py 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..d45d9c7d --- /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 fastfusion.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 100% rename from tests/mapper/test_mapping_to_isl.py rename to tests/isl/mapper/test_mapping_to_isl.py diff --git a/tests/mapper/test_spatial_reuse_analysis.py b/tests/isl/mapper/test_spatial_reuse_analysis.py similarity index 100% rename from tests/mapper/test_spatial_reuse_analysis.py rename to tests/isl/mapper/test_spatial_reuse_analysis.py 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/mapper/util.py similarity index 100% rename from tests/mapper/util.py rename to tests/isl/mapper/util.py From c4f659201631fb292a0a686d50258b015a395678 Mon Sep 17 00:00:00 2001 From: Renggeng Zheng Date: Sat, 22 Nov 2025 15:26:49 -0500 Subject: [PATCH 03/28] need to figure out how to install isl-barvinok --- .../looptree/reuse/isl/binding/__init__.py | 0 .../looptree/reuse/isl/distributed_buffers.py | 155 ++++++++++++++++++ pyproject.toml | 2 +- 3 files changed, 156 insertions(+), 1 deletion(-) create mode 100644 fastfusion/model/looptree/reuse/isl/binding/__init__.py create mode 100644 fastfusion/model/looptree/reuse/isl/distributed_buffers.py diff --git a/fastfusion/model/looptree/reuse/isl/binding/__init__.py b/fastfusion/model/looptree/reuse/isl/binding/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/fastfusion/model/looptree/reuse/isl/distributed_buffers.py b/fastfusion/model/looptree/reuse/isl/distributed_buffers.py new file mode 100644 index 00000000..59fcfb4a --- /dev/null +++ b/fastfusion/model/looptree/reuse/isl/distributed_buffers.py @@ -0,0 +1,155 @@ +from fastfusion.model.looptree.reuse.isl.isl_functions import dim_projector_mask +import logging +from numbers import Real + +import islpy as isl + +from fastfusion.model.looptree.reuse.isl.mapping_to_isl import ( + DUMP_ISL_IR +) + +def identify_mesh_casts( + src_occupancy: isl.Map, dst_fill: isl.Map, dist_fn: isl.Map +) -> isl.Map: + """ + + Parameters + ---------- + dist_fn: + Example + ------- + { + [src -> dst] -> [hops] + } + + A distance function 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. + """ + # Makes [[dst -> data] -> dst] -> data + wrapped_dst_fill: isl.Set = dst_fill.wrap() + wrapped_fill_identity: isl.Map = isl.Map.identity( + wrapped_dst_fill.get_space().map_from_set() + ) + wrapped_fill_identity = wrapped_fill_identity.intersect_domain( + wrapped_dst_fill + ) + if DUMP_ISL_IR: + logging.info(f"wrapped_fill_identity: {wrapped_fill_identity}") + + # Makes { [dst -> data] -> [dst -> data] } + uncurried_fill_identity: wrapped_fill_identity.uncurry() + if DUMP_ISL_IR: + logging.info(f"uncurried_fill_identity: {uncurried_fill_identity}") + + # 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] } + fills_to_dst_TO_src: isl.Map = uncurried_fill_identity.apply( + data_presence + ) + # { [dst -> data] -> [dst -> src] } + fills_to_matches: isl.Map = fills_to_dst_TO_src.curry() + if DUMP_ISL_IR: + logging.info(f"fills_to_matches: {filles_to_matches}") + + # Calculates the distance of all the dst-src pairs with matching data. + # { [dst -> data] -> [dist] } + distances_map: isl.Map = fills_to_matches.apply_range(dist_fn) + # { [[dst -> data] -> [dst -> src]] -> [dst -> src] } + fills_to_matches_TO_matches: isl.Map = fills_to_matches.range_map() + # { [[dst -> data] -> [dst -> src]] -> [dist] } + fills_to_matches_TO_dist: isl.Map = fills_to_matches_TO_matches.apply_range( + dist_fn + ) + + # Gets the minimal distance pairs. + # { [dst -> data] -> [dist] } + lexmin_dists: isl.Map = distances_map.lexmin() + # Associates each destination with its closest source containing the data. + # { [dst -> data] -> [[dst -> data] -> [dst -> src]] } + associate_dist_to_src: isl.Map = lexmin_dists.apply_range( + dst_to_data_TO_dst_to_src_TO2_dist.reverse() + ) + # Isolates the relevant minimal pairs. + # { [dst -> data] -> [dst -> src] :.dst -> src is minimized distance } + minimal_pairs: isl.Map = associate_dist_to_src.range().unwrap() + if DUMP_ISL_IR: + logging.log(f"minimal_pairs: {minimal_pairs}") + + # Isolates the multicast networks. + # { [dst] -> [data -> [dst -> src]] : dst -> src is minimized distance } + multicast_networks: isl.Map = minimal_pairs.curry() + # { [data] -> [dst -> src] } + multicast_networks = multicast_networks.range().unwrap() + print(multicast_networks) + # { [data -> dst] -> [src] } + multicast_networks = multicast_networks.uncurry() + # Devolves to a single source if multiple sources per domain point. + multicast_networks = multicast_networks.lexmin() + # { [data] -> [dst -> src] } + multicast_networks = multicast_networks.curry() + + return multicast_networks + + +def calculate_extents_per_dim(mcns: isl.Map) -> list[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() + min_cost: Real = float('inf') + # 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(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 + + +def cost_mesh_cast_hypercube(mcns: isl.Map, dist_fn: isl.Map) -> int: + dim_extents: list[isl.PwAff] = calculate_extents(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}^{dims} ((extent_i - 1) * \prod_{j=0}) + \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index e811f7f0..d2eb778f 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.0.0", "sympy>=1.14.0", "paretoset>=1.2.5", "matplotlib>=3.10.0", From 892236f564aa12e482d3f8dce2c46c6414fc4c53 Mon Sep 17 00:00:00 2001 From: Renggeng Zheng Date: Sat, 22 Nov 2025 15:34:52 -0500 Subject: [PATCH 04/28] docstrings --- .../looptree/reuse/isl/distributed_buffers.py | 26 +++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/fastfusion/model/looptree/reuse/isl/distributed_buffers.py b/fastfusion/model/looptree/reuse/isl/distributed_buffers.py index 59fcfb4a..20bdde21 100644 --- a/fastfusion/model/looptree/reuse/isl/distributed_buffers.py +++ b/fastfusion/model/looptree/reuse/isl/distributed_buffers.py @@ -15,6 +15,12 @@ def identify_mesh_casts( 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: Example ------- @@ -97,6 +103,17 @@ def identify_mesh_casts( 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` @@ -105,7 +122,13 @@ def calculate_extents_per_dim(mcns: isl.Map) -> list[isl.PwAff]: 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') + dst_fn(src, dst) = dst_fn(src', dst'). + + The extents calculation will still work if this is not the case, but downstream + users of the extents calculation will inherit this assumption which breaks + cases like hypercube mesh calculations forfractal distances (e.g., you cannot + access certain places in the NoC without an intermediate) and non-orthogonal + distances (e.g., x, y, and an xy axis between the orthogonal x and y axes). ) """ # Makes mcns from { [data] -> [dst -> src] } to { [data -> src] -> [dst] } @@ -152,4 +175,3 @@ def cost_mesh_cast_hypercube(mcns: isl.Map, dist_fn: isl.Map) -> int: # Calculates the cost of the hypercube, where the hypercube cost = # \sum{i=0}^{dims} ((extent_i - 1) * \prod_{j=0}) - \ No newline at end of file From 0479d9a71536710cd68004d6d316aaeb0a212c82 Mon Sep 17 00:00:00 2001 From: Renggeng Zheng Date: Sat, 22 Nov 2025 16:36:17 -0500 Subject: [PATCH 05/28] installs but not sure if it's stable --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index d2eb778f..3e4f7bdb 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-barvinok>=2025.0.0", + "islpy-barvinok == 2025.2.5", "sympy>=1.14.0", "paretoset>=1.2.5", "matplotlib>=3.10.0", From c20959d0cd5c51b95748771893d2b16accc0c1c3 Mon Sep 17 00:00:00 2001 From: Renggeng Zheng Date: Sat, 22 Nov 2025 17:23:35 -0500 Subject: [PATCH 06/28] trying to move util out --- .../looptree/reuse/isl/distributed_buffers.py | 61 ++++++++++++++++--- tests/isl/__init__.py | 0 .../distribuffers/multicast/test_cases.yaml | 50 +++++++-------- tests/isl/distribuffers/test_multicast.py | 17 +++--- tests/isl/mapper/test_mapping_to_isl.py | 2 +- tests/isl/{mapper => }/util.py | 0 6 files changed, 88 insertions(+), 42 deletions(-) create mode 100644 tests/isl/__init__.py rename tests/isl/{mapper => }/util.py (100%) diff --git a/fastfusion/model/looptree/reuse/isl/distributed_buffers.py b/fastfusion/model/looptree/reuse/isl/distributed_buffers.py index 20bdde21..4806f29f 100644 --- a/fastfusion/model/looptree/reuse/isl/distributed_buffers.py +++ b/fastfusion/model/looptree/reuse/isl/distributed_buffers.py @@ -1,12 +1,22 @@ -from fastfusion.model.looptree.reuse.isl.isl_functions import dim_projector_mask +from fastfusion.model.looptree.reuse.isl.spatial import Transfers import logging from numbers import Real import islpy as isl +from fastfusion.model.looptree.reuse.isl.isl_functions import dim_projector_mask from fastfusion.model.looptree.reuse.isl.mapping_to_isl import ( DUMP_ISL_IR ) +from fastfusion.model.looptree.reuse.isl.mapping_to_isl.types import ( + Fill, + Occupancy +) +from fastfusion.model.looptree.reuse.isl.spatial import ( + TransferInfo, + TransferModel +) + def identify_mesh_casts( src_occupancy: isl.Map, dst_fill: isl.Map, dist_fn: isl.Map @@ -167,11 +177,46 @@ def calculate_extents_per_dim(mcns: isl.Map) -> list[isl.PwAff]: return dim_extents -def cost_mesh_cast_hypercube(mcns: isl.Map, dist_fn: isl.Map) -> int: - dim_extents: list[isl.PwAff] = calculate_extents(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) +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. + """ - # Calculates the cost of the hypercube, where the hypercube cost = - # \sum{i=0}^{dims} ((extent_i - 1) * \prod_{j=0}) + def apply(fills: Fill, occ: Occupancy, dist_fn: isl.Map) -> TransferInfo: + mcs: isl.Map = identify_mesh_casts( + occ.map_, fills.map_, dist_fn + ) + result: isl.PwQPolynomial = cost_mesh_cast_hypercube(mcs) + + # TODO: Read once from all buffers, assert that card(mcs) == tensor_size * D + return TransferInfo( + fulfilled_fill=Transfers(fills.tags, fills.map_), + parent_reads=Reads(occ.tags, mcs), + unfulfilled_fill=Fill(fills.tags, fills.map_.subtract(fills.map_)) + ) + + def _cost_mesh_cast_hypercube(mcns: isl.Map) -> int: + 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/tests/isl/__init__.py b/tests/isl/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/isl/distribuffers/multicast/test_cases.yaml b/tests/isl/distribuffers/multicast/test_cases.yaml index 35c2fe27..32dbc8eb 100755 --- a/tests/isl/distribuffers/multicast/test_cases.yaml +++ b/tests/isl/distribuffers/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/distribuffers/test_multicast.py b/tests/isl/distribuffers/test_multicast.py index 9fd18ba9..a94e5800 100644 --- a/tests/isl/distribuffers/test_multicast.py +++ b/tests/isl/distribuffers/test_multicast.py @@ -4,13 +4,13 @@ """ from fastfusion.model.looptree.reuse.isl.spatial import TransferInfo -from tests.mapper.util import load_solutions +from ..util import load_solutions import unittest from math import isclose import islpy as isl -from fastfusion.model.looptree.reuse.isl.mapping_to_isl.types ( +from fastfusion.model.looptree.reuse.isl.mapping_to_isl.types import ( # Data movement descriptors. Fill, Occupancy, @@ -19,6 +19,9 @@ SpatialTag, TemporalTag ) +from fastfusion.model.looptree.reuse.isl.distributed_buffers import ( + HypercubeMulticastModel +) def construct_spacetime(dims: list) -> list[Tag]: spacetime: list[Tag] = [] @@ -142,14 +145,12 @@ def test_gamut(self): dim_tags: list[Tags] = construct_spacetime(test["dims"]) fill: Fill = Fill(dims, test["fill"]) occ: Occupancy = Occupancy(dims, test["occ"]) - dist_func: isl.Map = isl.Map(isl.DEFAULT_CONTEXT, test["dist_func"]) - multicast_model: HypercubeMulticastModel = HyperCubeMulticastModel( - True, dist_func - ) + dist_fn: isl.Map = isl.Map(isl.DEFAULT_CONTEXT, test["dist_fn"]) + multicast_model: HypercubeMulticastModel = HypercubeMulticastModel() # Applies the model. - info: TransferInfo = multicast_model.apply(buf_id, fill, occ) - # Checks the results + info: TransferInfo = multicast_model.apply(fill, occ, dist_fn) + # Checks the results. sum_extract: int = info.p_hops.eval( isl.Point.zero(info.p_hops.domain()) ) diff --git a/tests/isl/mapper/test_mapping_to_isl.py b/tests/isl/mapper/test_mapping_to_isl.py index cae67fde..9e33a667 100644 --- a/tests/isl/mapper/test_mapping_to_isl.py +++ b/tests/isl/mapper/test_mapping_to_isl.py @@ -17,7 +17,7 @@ MappingAnalysisResult, ) -from .util import TEST_CONFIG_PATH, load_solutions +from ..util import TEST_CONFIG_PATH, load_solutions class TestMappingToIsl(unittest.TestCase): diff --git a/tests/isl/mapper/util.py b/tests/isl/util.py similarity index 100% rename from tests/isl/mapper/util.py rename to tests/isl/util.py From eca59b7d171ffcfba6883402511f46ca0d63980e Mon Sep 17 00:00:00 2001 From: Renggeng Zheng Date: Sat, 22 Nov 2025 17:24:25 -0500 Subject: [PATCH 07/28] fixed issues of moving util --- tests/isl/mapper/test_mapping_to_isl.py | 3 ++- tests/isl/util.py | 3 --- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/tests/isl/mapper/test_mapping_to_isl.py b/tests/isl/mapper/test_mapping_to_isl.py index 9e33a667..1f595b07 100644 --- a/tests/isl/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/isl/util.py b/tests/isl/util.py index f70f521c..e87a81a8 100644 --- a/tests/isl/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 From f86037383c6f57b4ac0434b27f8f901ba6d9b3fd Mon Sep 17 00:00:00 2001 From: Renggeng Zheng Date: Sat, 22 Nov 2025 17:52:37 -0500 Subject: [PATCH 08/28] tests work --- .../looptree/reuse/isl/distributed_buffers.py | 22 +- tests/isl/distribuffers/test_multicast.py | 218 +++++++++--------- tests/isl/util.py | 5 +- 3 files changed, 125 insertions(+), 120 deletions(-) diff --git a/fastfusion/model/looptree/reuse/isl/distributed_buffers.py b/fastfusion/model/looptree/reuse/isl/distributed_buffers.py index 4806f29f..4f4b6b00 100644 --- a/fastfusion/model/looptree/reuse/isl/distributed_buffers.py +++ b/fastfusion/model/looptree/reuse/isl/distributed_buffers.py @@ -1,4 +1,3 @@ -from fastfusion.model.looptree.reuse.isl.spatial import Transfers import logging from numbers import Real @@ -13,6 +12,8 @@ Occupancy ) from fastfusion.model.looptree.reuse.isl.spatial import ( + Reads, + Transfers, TransferInfo, TransferModel ) @@ -54,7 +55,7 @@ def identify_mesh_casts( logging.info(f"wrapped_fill_identity: {wrapped_fill_identity}") # Makes { [dst -> data] -> [dst -> data] } - uncurried_fill_identity: wrapped_fill_identity.uncurry() + uncurried_fill_identity: isl.Map = wrapped_fill_identity.uncurry() if DUMP_ISL_IR: logging.info(f"uncurried_fill_identity: {uncurried_fill_identity}") @@ -63,7 +64,7 @@ def identify_mesh_casts( data_presence: isl.Map = src_occupancy.reverse() # { [[dst -> data] -> dst] -> [src] } - fills_to_dst_TO_src: isl.Map = uncurried_fill_identity.apply( + fills_to_dst_TO_src: isl.Map = uncurried_fill_identity.apply_range( data_presence ) # { [dst -> data] -> [dst -> src] } @@ -87,7 +88,7 @@ def identify_mesh_casts( # Associates each destination with its closest source containing the data. # { [dst -> data] -> [[dst -> data] -> [dst -> src]] } associate_dist_to_src: isl.Map = lexmin_dists.apply_range( - dst_to_data_TO_dst_to_src_TO2_dist.reverse() + fills_to_matches_TO_dist.reverse() ) # Isolates the relevant minimal pairs. # { [dst -> data] -> [dst -> src] :.dst -> src is minimized distance } @@ -100,7 +101,6 @@ def identify_mesh_casts( multicast_networks: isl.Map = minimal_pairs.curry() # { [data] -> [dst -> src] } multicast_networks = multicast_networks.range().unwrap() - print(multicast_networks) # { [data -> dst] -> [src] } multicast_networks = multicast_networks.uncurry() # Devolves to a single source if multiple sources per domain point. @@ -164,7 +164,7 @@ def calculate_extents_per_dim(mcns: isl.Map) -> list[isl.PwAff]: extent_mapper: isl.Map = dim_projector_mask( casting_extents.range().get_space(), project_out_mask ).reverse() - dim_extent_space: isl.Map = casting_extents.apply(extent_mapper) + 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] @@ -184,20 +184,22 @@ class HypercubeMulticastModel(TransferModel): hypercube that encapsulates all their destinations and sources. """ - def apply(fills: Fill, occ: Occupancy, dist_fn: isl.Map) -> TransferInfo: + def apply(self, fills: Fill, occ: Occupancy, dist_fn: isl.Map) -> TransferInfo: mcs: isl.Map = identify_mesh_casts( occ.map_, fills.map_, dist_fn ) - result: isl.PwQPolynomial = cost_mesh_cast_hypercube(mcs) + result: isl.PwQPolynomial = self._cost_mesh_cast_hypercube(mcs) # TODO: Read once from all buffers, assert that card(mcs) == tensor_size * D return TransferInfo( fulfilled_fill=Transfers(fills.tags, fills.map_), parent_reads=Reads(occ.tags, mcs), - unfulfilled_fill=Fill(fills.tags, fills.map_.subtract(fills.map_)) + unfulfilled_fill=Fill(fills.tags, fills.map_.subtract(fills.map_)), + hops=result, + link_transfer=True ) - def _cost_mesh_cast_hypercube(mcns: isl.Map) -> int: + def _cost_mesh_cast_hypercube(self, mcns: isl.Map) -> int: 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) diff --git a/tests/isl/distribuffers/test_multicast.py b/tests/isl/distribuffers/test_multicast.py index a94e5800..19fcea85 100644 --- a/tests/isl/distribuffers/test_multicast.py +++ b/tests/isl/distribuffers/test_multicast.py @@ -3,10 +3,9 @@ https://github.com/rengzhengcodes/timeloop/blob/distributed-multicast-dev/src/unit-test/multicast/test-multicast.cpp """ -from fastfusion.model.looptree.reuse.isl.spatial import TransferInfo -from ..util import load_solutions import unittest from math import isclose +from pathlib import Path import islpy as isl @@ -22,137 +21,138 @@ from fastfusion.model.looptree.reuse.isl.distributed_buffers import ( HypercubeMulticastModel ) +from fastfusion.model.looptree.reuse.isl.spatial import TransferInfo +from ..util import load_solutions def construct_spacetime(dims: list) -> list[Tag]: spacetime: list[Tag] = [] for dim in dims: if dim["type"] == "Temporal": spacetime.append(TemporalTag()) - elif dim["type"] -- "Spatial": + elif dim["type"] == "Spatial": spacetime.append( - Spatial(dim["spatial_dim"], dim["target"]) + SpatialTag(dim["spatial_dim"], dim["target"]) ) return spacetime -class TestSimpleMulticastModel(unittest.TestCase): - """ - Tests the distributed simple multicast model. - """ - - def test_0(self): - fill: Fill = Fill( - TemporalTag(), SpatialTag(0, None), SpatialTag(1, None), - isl.Map( - isl.DEFAULT_CONTEXT, - "{ spacetime[t, x, y] -> data[t + x + y] : " - "0 <= x < 2 and 0 <= y < 2 and 0 <= t < 4 }" - ) - ) - occ: Occupancy = Occupancy( - fill.tags, fill.map_ - ) - - multicast_model: SimpleMulticastModel = SimpleMulticastModel(False) - info = multicast_model.apply(0, fill, occ) - - assert info.fulfilled_map.map_ == isl.Map( - isl.DEFAULT_CONTEXT, - "{ spacetime[t, x, y] -> data[t + x + y] : " - "0 <= t < 4 and 0 <= x < 2 and 0 <= y < 2 }" - ) - assert info.parent_reads.map_ == isl.Map( - isl.DEFAULT_CONTEXT, - "{ spacetime[t] -> data[d] : 0 <= t < 4 and t <= d < t + 3 }" - ) - assert len(info.compat_access_stats) == 1 - - for mutlicast_scatter, stats in info.compat_access_stats: - multicast, scatter = mutlicast_scatter - - assert multicast == 1 - assert scatter == 1 - assert stats.accesses == 12 - assert stats.hops == 1 +# class TestSimpleMulticastModel(unittest.TestCase): +# """ +# Tests the distributed simple multicast model. +# """ + +# def test_0(self): +# fill: Fill = Fill( +# TemporalTag(), SpatialTag(0, None), SpatialTag(1, None), +# isl.Map.read_from_str( +# isl.DEFAULT_CONTEXT, +# "{ spacetime[t, x, y] -> data[t + x + y] : " +# "0 <= x < 2 and 0 <= y < 2 and 0 <= t < 4 }" +# ) +# ) +# occ: Occupancy = Occupancy( +# fill.tags, fill.map_ +# ) + +# multicast_model: SimpleMulticastModel = SimpleMulticastModel(False) +# info = multicast_model.apply(0, fill, occ) + +# assert info.fulfilled_map.map_ == isl.Map.read_from_str( +# isl.DEFAULT_CONTEXT, +# "{ spacetime[t, x, y] -> data[t + x + y] : " +# "0 <= t < 4 and 0 <= x < 2 and 0 <= y < 2 }" +# ) +# assert info.parent_reads.map_ == isl.Map.read_from_str( +# isl.DEFAULT_CONTEXT, +# "{ spacetime[t] -> data[d] : 0 <= t < 4 and t <= d < t + 3 }" +# ) +# assert len(info.compat_access_stats) == 1 + +# for mutlicast_scatter, stats in info.compat_access_stats: +# multicast, scatter = mutlicast_scatter + +# assert multicast == 1 +# assert scatter == 1 +# assert stats.accesses == 12 +# assert stats.hops == 1 - multicast_model: SimpleMulticastModel = SimpleMulticastModel(True) - info = multicast_model.Apply(0, fill, occ) - - assert info.fulfilled_fill.map_ == isl.Map( - isl.DEFAULT_CONTEXT, - "{ spacetime[t, x, y] -> data[t + x + y] : " - "0 <= t < 4 and 0 <= x < 2 and 0 <= y < 2 }" - ) - assert info.parent_reads.map_ == isl.Map( - isl.DEFAULT_CONTEXT, - "{ spacetime[t] -> data[d] : 0 <= t < 4 and t <= d < t + 3 }" - ) - assert len(info.compat_access_stats) == 1 - for multicast_scatter, stats in info.compat_access_stats: - multicast, scatter = multicast_scatter - - assert multicast == 1 - assert scatter == 1 - assert stats.accesses == 12 - assert isclose( - stats.hops, 3.667, - abs_tol=0.001 - ) - - def test_spatial_PC(self): - fill: Fill = Fill( - [TemporalTag(), SpatialTag(0, None), SpatialTag(1, None)], - isl.Map( - isl.DEFAULT_CONTEXT, - "{ spacetime[t, x, y] -> data[d, y] : " - "0 <= x < 4 and 0 <= y < 2 and 0 <= t < 4 and x <= d < x+2 }" - ) - ) - occ: Occupancy = Occupancy(fill.tags, fill.map_) - - multicast_model: SimpleMulticastModel = SimpleMulticastModel(True) - - info = multicast_model.apply(0, fill, occ) - - assert info.fulfilled_fill.map_ == isl.Map( - isl.DEFAULT_CONTEXT, - "{ spacetime[t, x, y] -> data[d, y] : " - "0 <= x < 4 and 0 <= y < 2 and 0 <= t < 4 and x <= d < x+2 }" - ) - assert info.parent_reads.map_ == isl.Map( - isl.DEFAULT_CONTEXT, - "{ spacetime[t] -> data[d, y] : 0 <= y < 2 and 0 <= t < 4 and 0 <= d < 5 }" - ) - assert len(info.compat_access_stats) == 1 - - for multicast_scatter, stats in info.compat_access_stats: - multicast, scatter = multicast_scatter - assert multicast == 1 - assert scatter == 1 - assert stats.accesses == 40 - assert isclose(stats.hops, 5.2, abs_tol=0.001) +# multicast_model: SimpleMulticastModel = SimpleMulticastModel(True) +# info = multicast_model.Apply(0, fill, occ) + +# assert info.fulfilled_fill.map_ == isl.Map.read_from_str( +# isl.DEFAULT_CONTEXT, +# "{ spacetime[t, x, y] -> data[t + x + y] : " +# "0 <= t < 4 and 0 <= x < 2 and 0 <= y < 2 }" +# ) +# assert info.parent_reads.map_ == isl.Map.read_from_str( +# isl.DEFAULT_CONTEXT, +# "{ spacetime[t] -> data[d] : 0 <= t < 4 and t <= d < t + 3 }" +# ) +# assert len(info.compat_access_stats) == 1 +# for multicast_scatter, stats in info.compat_access_stats: +# multicast, scatter = multicast_scatter + +# assert multicast == 1 +# assert scatter == 1 +# assert stats.accesses == 12 +# assert isclose( +# stats.hops, 3.667, +# abs_tol=0.001 +# ) + +# def test_spatial_PC(self): +# fill: Fill = Fill( +# [TemporalTag(), SpatialTag(0, None), SpatialTag(1, None)], +# isl.Map.read_from_str( +# isl.DEFAULT_CONTEXT, +# "{ spacetime[t, x, y] -> data[d, y] : " +# "0 <= x < 4 and 0 <= y < 2 and 0 <= t < 4 and x <= d < x+2 }" +# ) +# ) +# occ: Occupancy = Occupancy(fill.tags, fill.map_) + +# multicast_model: SimpleMulticastModel = SimpleMulticastModel(True) + +# info = multicast_model.apply(0, fill, occ) + +# assert info.fulfilled_fill.map_ == isl.Map.read_from_str( +# isl.DEFAULT_CONTEXT, +# "{ spacetime[t, x, y] -> data[d, y] : " +# "0 <= x < 4 and 0 <= y < 2 and 0 <= t < 4 and x <= d < x+2 }" +# ) +# assert info.parent_reads.map_ == isl.Map.read_from_str( +# isl.DEFAULT_CONTEXT, +# "{ spacetime[t] -> data[d, y] : 0 <= y < 2 and 0 <= t < 4 and 0 <= d < 5 }" +# ) +# assert len(info.compat_access_stats) == 1 + +# for multicast_scatter, stats in info.compat_access_stats: +# multicast, scatter = multicast_scatter +# assert multicast == 1 +# assert scatter == 1 +# assert stats.accesses == 40 +# assert isclose(stats.hops, 5.2, abs_tol=0.001) class TestHypercubeMulticastModel(unittest.TestCase): - TEST_CASES_FILE: str = Path(__file__).parent / "multicast" - testcases: dict = load_solutions(TEST_CASES_FILE / "multicast" / "test_cases.yaml") + TEST_CASES_FILE: str = Path(__file__).parent / "multicast" / "test_cases.yaml" + testcases: dict = load_solutions(TEST_CASES_FILE) def test_gamut(self): - for test in testcases: + for test in self.testcases: # Reads test case parameters and constructs the necessary objects. - buf_id: int = 0 dim_tags: list[Tags] = construct_spacetime(test["dims"]) - fill: Fill = Fill(dims, test["fill"]) - occ: Occupancy = Occupancy(dims, test["occ"]) - dist_fn: isl.Map = isl.Map(isl.DEFAULT_CONTEXT, test["dist_fn"]) + 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() # Applies the model. info: TransferInfo = multicast_model.apply(fill, occ, dist_fn) # Checks the results. - sum_extract: int = info.p_hops.eval( - isl.Point.zero(info.p_hops.domain()) + 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. @@ -163,4 +163,4 @@ def test_gamut(self): print(f"Dist Fn: {dist}") print(f"Returned: {sum_extract}") else: - assert ret == test["expected"]["hypercube_hops"] + assert sum_extract == test["expected"]["hypercube_hops"] diff --git a/tests/isl/util.py b/tests/isl/util.py index e87a81a8..484417a7 100644 --- a/tests/isl/util.py +++ b/tests/isl/util.py @@ -26,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 From 79decad807846bf271184949d1662a0f3d3a39a1 Mon Sep 17 00:00:00 2001 From: Renggeng Zheng Date: Sat, 22 Nov 2025 18:03:49 -0500 Subject: [PATCH 09/28] cleaned up test multicast --- tests/isl/distribuffers/test_multicast.py | 134 +++++----------------- 1 file changed, 27 insertions(+), 107 deletions(-) diff --git a/tests/isl/distribuffers/test_multicast.py b/tests/isl/distribuffers/test_multicast.py index 19fcea85..ec827b52 100644 --- a/tests/isl/distribuffers/test_multicast.py +++ b/tests/isl/distribuffers/test_multicast.py @@ -4,7 +4,6 @@ """ import unittest -from math import isclose from pathlib import Path import islpy as isl @@ -16,133 +15,54 @@ # Tags Tag, SpatialTag, - TemporalTag + TemporalTag, ) from fastfusion.model.looptree.reuse.isl.distributed_buffers import ( - HypercubeMulticastModel + HypercubeMulticastModel, ) from fastfusion.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 TestSimpleMulticastModel(unittest.TestCase): -# """ -# Tests the distributed simple multicast model. -# """ - -# def test_0(self): -# fill: Fill = Fill( -# TemporalTag(), SpatialTag(0, None), SpatialTag(1, None), -# isl.Map.read_from_str( -# isl.DEFAULT_CONTEXT, -# "{ spacetime[t, x, y] -> data[t + x + y] : " -# "0 <= x < 2 and 0 <= y < 2 and 0 <= t < 4 }" -# ) -# ) -# occ: Occupancy = Occupancy( -# fill.tags, fill.map_ -# ) - -# multicast_model: SimpleMulticastModel = SimpleMulticastModel(False) -# info = multicast_model.apply(0, fill, occ) - -# assert info.fulfilled_map.map_ == isl.Map.read_from_str( -# isl.DEFAULT_CONTEXT, -# "{ spacetime[t, x, y] -> data[t + x + y] : " -# "0 <= t < 4 and 0 <= x < 2 and 0 <= y < 2 }" -# ) -# assert info.parent_reads.map_ == isl.Map.read_from_str( -# isl.DEFAULT_CONTEXT, -# "{ spacetime[t] -> data[d] : 0 <= t < 4 and t <= d < t + 3 }" -# ) -# assert len(info.compat_access_stats) == 1 - -# for mutlicast_scatter, stats in info.compat_access_stats: -# multicast, scatter = mutlicast_scatter + spacetime.append(SpatialTag(dim["spatial_dim"], dim["target"])) -# assert multicast == 1 -# assert scatter == 1 -# assert stats.accesses == 12 -# assert stats.hops == 1 - -# multicast_model: SimpleMulticastModel = SimpleMulticastModel(True) -# info = multicast_model.Apply(0, fill, occ) - -# assert info.fulfilled_fill.map_ == isl.Map.read_from_str( -# isl.DEFAULT_CONTEXT, -# "{ spacetime[t, x, y] -> data[t + x + y] : " -# "0 <= t < 4 and 0 <= x < 2 and 0 <= y < 2 }" -# ) -# assert info.parent_reads.map_ == isl.Map.read_from_str( -# isl.DEFAULT_CONTEXT, -# "{ spacetime[t] -> data[d] : 0 <= t < 4 and t <= d < t + 3 }" -# ) -# assert len(info.compat_access_stats) == 1 -# for multicast_scatter, stats in info.compat_access_stats: -# multicast, scatter = multicast_scatter - -# assert multicast == 1 -# assert scatter == 1 -# assert stats.accesses == 12 -# assert isclose( -# stats.hops, 3.667, -# abs_tol=0.001 -# ) - -# def test_spatial_PC(self): -# fill: Fill = Fill( -# [TemporalTag(), SpatialTag(0, None), SpatialTag(1, None)], -# isl.Map.read_from_str( -# isl.DEFAULT_CONTEXT, -# "{ spacetime[t, x, y] -> data[d, y] : " -# "0 <= x < 4 and 0 <= y < 2 and 0 <= t < 4 and x <= d < x+2 }" -# ) -# ) -# occ: Occupancy = Occupancy(fill.tags, fill.map_) - -# multicast_model: SimpleMulticastModel = SimpleMulticastModel(True) - -# info = multicast_model.apply(0, fill, occ) - -# assert info.fulfilled_fill.map_ == isl.Map.read_from_str( -# isl.DEFAULT_CONTEXT, -# "{ spacetime[t, x, y] -> data[d, y] : " -# "0 <= x < 4 and 0 <= y < 2 and 0 <= t < 4 and x <= d < x+2 }" -# ) -# assert info.parent_reads.map_ == isl.Map.read_from_str( -# isl.DEFAULT_CONTEXT, -# "{ spacetime[t] -> data[d, y] : 0 <= y < 2 and 0 <= t < 4 and 0 <= d < 5 }" -# ) -# assert len(info.compat_access_stats) == 1 - -# for multicast_scatter, stats in info.compat_access_stats: -# multicast, scatter = multicast_scatter -# assert multicast == 1 -# assert scatter == 1 -# assert stats.accesses == 40 -# assert isclose(stats.hops, 5.2, abs_tol=0.001) + 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[Tags] = construct_spacetime(test["dims"]) + 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"] @@ -160,7 +80,7 @@ def test_gamut(self): print("~~~Test case in progress:~~~") print(f"Fill: {fill}") print(f"Occ: {occ}") - print(f"Dist Fn: {dist}") + print(f"Dist Fn: {dist_fn}") print(f"Returned: {sum_extract}") else: assert sum_extract == test["expected"]["hypercube_hops"] From 022b40b93f64d012727de6e24393cc3354b333d3 Mon Sep 17 00:00:00 2001 From: Renggeng Zheng Date: Sat, 22 Nov 2025 18:41:48 -0500 Subject: [PATCH 10/28] cleaned up some calculations in identify_mesh_casts --- .../looptree/reuse/isl/distributed_buffers.py | 121 +++++++++--------- tests/isl/distribuffers/test_multicast.py | 6 +- 2 files changed, 64 insertions(+), 63 deletions(-) diff --git a/fastfusion/model/looptree/reuse/isl/distributed_buffers.py b/fastfusion/model/looptree/reuse/isl/distributed_buffers.py index 4f4b6b00..63f47392 100644 --- a/fastfusion/model/looptree/reuse/isl/distributed_buffers.py +++ b/fastfusion/model/looptree/reuse/isl/distributed_buffers.py @@ -1,21 +1,20 @@ +""" +Models for handling calculating the cost of a Workload on distributed buffer +architectures. +""" + import logging -from numbers import Real import islpy as isl from fastfusion.model.looptree.reuse.isl.isl_functions import dim_projector_mask -from fastfusion.model.looptree.reuse.isl.mapping_to_isl import ( - DUMP_ISL_IR -) -from fastfusion.model.looptree.reuse.isl.mapping_to_isl.types import ( - Fill, - Occupancy -) +from fastfusion.model.looptree.reuse.isl.mapping_to_isl import DUMP_ISL_IR +from fastfusion.model.looptree.reuse.isl.mapping_to_isl.types import Fill, Occupancy from fastfusion.model.looptree.reuse.isl.spatial import ( Reads, Transfers, TransferInfo, - TransferModel + TransferModel, ) @@ -33,44 +32,26 @@ def identify_mesh_casts( An isl.Map of the form { [dst] -> [data] } corresponding to the data requested at the element at space `dst`. dist_fn: - Example - ------- - { - [src -> dst] -> [hops] - } - - A distance function 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. + 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. """ - # Makes [[dst -> data] -> dst] -> data - wrapped_dst_fill: isl.Set = dst_fill.wrap() - wrapped_fill_identity: isl.Map = isl.Map.identity( - wrapped_dst_fill.get_space().map_from_set() - ) - wrapped_fill_identity = wrapped_fill_identity.intersect_domain( - wrapped_dst_fill - ) - if DUMP_ISL_IR: - logging.info(f"wrapped_fill_identity: {wrapped_fill_identity}") - # Makes { [dst -> data] -> [dst -> data] } - uncurried_fill_identity: isl.Map = wrapped_fill_identity.uncurry() + fill_to_fill: isl.Map = dst_fill.wrap().identity() if DUMP_ISL_IR: - logging.info(f"uncurried_fill_identity: {uncurried_fill_identity}") + 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] } - fills_to_dst_TO_src: isl.Map = uncurried_fill_identity.apply_range( - data_presence - ) - # { [dst -> data] -> [dst -> src] } - fills_to_matches: isl.Map = fills_to_dst_TO_src.curry() + # { [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: {filles_to_matches}") + logging.info(f"fills_to_matches: {fills_to_matches}") # Calculates the distance of all the dst-src pairs with matching data. # { [dst -> data] -> [dist] } @@ -78,9 +59,7 @@ def identify_mesh_casts( # { [[dst -> data] -> [dst -> src]] -> [dst -> src] } fills_to_matches_TO_matches: isl.Map = fills_to_matches.range_map() # { [[dst -> data] -> [dst -> src]] -> [dist] } - fills_to_matches_TO_dist: isl.Map = fills_to_matches_TO_matches.apply_range( - dist_fn - ) + fills_to_matches_TO_dist: isl.Map = fills_to_matches_TO_matches.apply_range(dist_fn) # Gets the minimal distance pairs. # { [dst -> data] -> [dist] } @@ -94,10 +73,10 @@ def identify_mesh_casts( # { [dst -> data] -> [dst -> src] :.dst -> src is minimized distance } minimal_pairs: isl.Map = associate_dist_to_src.range().unwrap() if DUMP_ISL_IR: - logging.log(f"minimal_pairs: {minimal_pairs}") + logging.info(f"minimal_pairs: {minimal_pairs}") # Isolates the multicast networks. - # { [dst] -> [data -> [dst -> src]] : dst -> src is minimized distance } + # { [dst] -> [data -> [dst -> src]] : dst -> src is minimized distance } multicast_networks: isl.Map = minimal_pairs.curry() # { [data] -> [dst -> src] } multicast_networks = multicast_networks.range().unwrap() @@ -118,10 +97,10 @@ def calculate_extents_per_dim(mcns: isl.Map) -> list[isl.PwAff]: 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, + 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 @@ -131,15 +110,8 @@ def calculate_extents_per_dim(mcns: isl.Map) -> list[isl.PwAff]: 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'|, + src', dst' ∈ space, if |src - dst| = |src' - dst'|, dst_fn(src, dst) = dst_fn(src', dst'). - - The extents calculation will still work if this is not the case, but downstream - users of the extents calculation will inherit this assumption which breaks - cases like hypercube mesh calculations forfractal distances (e.g., you cannot - access certain places in the NoC without an intermediate) and non-orthogonal - distances (e.g., x, y, and an xy axis between the orthogonal x and y axes). - ) """ # Makes mcns from { [data] -> [dst -> src] } to { [data -> src] -> [dst] } potential_srcs: isl.Map = mcns.range_reverse().uncurry() @@ -151,7 +123,6 @@ def calculate_extents_per_dim(mcns: isl.Map) -> list[isl.PwAff]: # Projects away all dimensions but one to find their extent for hypercube. dims: int = potential_srcs.range_tuple_dim() - min_cost: Real = float('inf') # Creates a mask of what to project out. project_out_mask: list[bool] = [True] * dims dim_extents: list[isl.PwAff] = [None] * dims @@ -184,22 +155,50 @@ class HypercubeMulticastModel(TransferModel): hypercube that encapsulates all their destinations and sources. """ - def apply(self, fills: Fill, occ: Occupancy, dist_fn: isl.Map) -> TransferInfo: - mcs: isl.Map = identify_mesh_casts( - occ.map_, fills.map_, dist_fn - ) + def __init__(self, dist_fn: isl.Map): + """ + Initializes the HypercubeMulticastModel with the distance function + over the metric space. + """ + self.dist_fn = dist_fn + + def apply(self, buff: int, fills: Fill, occ: Occupancy) -> TransferInfo: + mcs: isl.Map = identify_mesh_casts(occ.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 * D + # 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(occ.tags, mcs), unfulfilled_fill=Fill(fills.tags, fills.map_.subtract(fills.map_)), hops=result, - link_transfer=True + 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. + + 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) diff --git a/tests/isl/distribuffers/test_multicast.py b/tests/isl/distribuffers/test_multicast.py index ec827b52..80d004aa 100644 --- a/tests/isl/distribuffers/test_multicast.py +++ b/tests/isl/distribuffers/test_multicast.py @@ -66,10 +66,12 @@ def test_gamut(self): 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() + multicast_model: HypercubeMulticastModel = HypercubeMulticastModel( + dist_fn + ) # Applies the model. - info: TransferInfo = multicast_model.apply(fill, occ, dist_fn) + 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()) From 73c92a98f323b723c2f8d50a0e0cd464abeb9f74 Mon Sep 17 00:00:00 2001 From: Renggeng Zheng Date: Sat, 22 Nov 2025 19:49:51 -0500 Subject: [PATCH 11/28] cleaned up identify meshcasts more --- .../looptree/reuse/isl/distributed_buffers.py | 29 +++++++------------ 1 file changed, 11 insertions(+), 18 deletions(-) diff --git a/fastfusion/model/looptree/reuse/isl/distributed_buffers.py b/fastfusion/model/looptree/reuse/isl/distributed_buffers.py index 63f47392..b257dc6d 100644 --- a/fastfusion/model/looptree/reuse/isl/distributed_buffers.py +++ b/fastfusion/model/looptree/reuse/isl/distributed_buffers.py @@ -55,23 +55,16 @@ def identify_mesh_casts( # Calculates the distance of all the dst-src pairs with matching data. # { [dst -> data] -> [dist] } - distances_map: isl.Map = fills_to_matches.apply_range(dist_fn) - # { [[dst -> data] -> [dst -> src]] -> [dst -> src] } - fills_to_matches_TO_matches: isl.Map = fills_to_matches.range_map() - # { [[dst -> data] -> [dst -> src]] -> [dist] } - fills_to_matches_TO_dist: isl.Map = fills_to_matches_TO_matches.apply_range(dist_fn) - - # Gets the minimal distance pairs. - # { [dst -> data] -> [dist] } - lexmin_dists: isl.Map = distances_map.lexmin() - # Associates each destination with its closest source containing the data. - # { [dst -> data] -> [[dst -> data] -> [dst -> src]] } - associate_dist_to_src: isl.Map = lexmin_dists.apply_range( - fills_to_matches_TO_dist.reverse() - ) + fill_to_dist_of_nearest_src: 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 = associate_dist_to_src.range().unwrap() + minimal_pairs: isl.Map = fill_to_dist_of_nearest_src.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}") @@ -162,8 +155,8 @@ def __init__(self, dist_fn: isl.Map): """ self.dist_fn = dist_fn - def apply(self, buff: int, fills: Fill, occ: Occupancy) -> TransferInfo: - mcs: isl.Map = identify_mesh_casts(occ.map_, fills.map_, self.dist_fn) + def apply(self, buff: int, fills: Fill, occs: Occupancy) -> TransferInfo: + 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 @@ -171,7 +164,7 @@ def apply(self, buff: int, fills: Fill, occ: Occupancy) -> TransferInfo: num_meshcasts: int = mcs.card() return TransferInfo( fulfilled_fill=Transfers(fills.tags, fills.map_), - parent_reads=Reads(occ.tags, mcs), + parent_reads=Reads(occs.tags, mcs), unfulfilled_fill=Fill(fills.tags, fills.map_.subtract(fills.map_)), hops=result, link_transfer=True, From f847bd19cda712357a9ca3d8bb34f8f8fc8c34cb Mon Sep 17 00:00:00 2001 From: Renggeng Zheng Date: Sat, 22 Nov 2025 19:55:30 -0500 Subject: [PATCH 12/28] cleaned up identify mesh casts more --- .../looptree/reuse/isl/distributed_buffers.py | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/fastfusion/model/looptree/reuse/isl/distributed_buffers.py b/fastfusion/model/looptree/reuse/isl/distributed_buffers.py index b257dc6d..6896ad15 100644 --- a/fastfusion/model/looptree/reuse/isl/distributed_buffers.py +++ b/fastfusion/model/looptree/reuse/isl/distributed_buffers.py @@ -53,12 +53,12 @@ def identify_mesh_casts( if DUMP_ISL_IR: logging.info(f"fills_to_matches: {fills_to_matches}") - # Calculates the distance of all the dst-src pairs with matching data. + # Calculates the distance of a fill to the nearest src satisfying the fill. # { [dst -> data] -> [dist] } - fill_to_dist_of_nearest_src: isl.Map = fills_to_matches.apply_range(dist_fn).lexmin() + 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_to_dist_of_nearest_src.apply_range( + 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) @@ -69,16 +69,10 @@ def identify_mesh_casts( logging.info(f"minimal_pairs: {minimal_pairs}") # Isolates the multicast networks. - # { [dst] -> [data -> [dst -> src]] : dst -> src is minimized distance } - multicast_networks: isl.Map = minimal_pairs.curry() - # { [data] -> [dst -> src] } - multicast_networks = multicast_networks.range().unwrap() - # { [data -> dst] -> [src] } - multicast_networks = multicast_networks.uncurry() + # { [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.lexmin() - # { [data] -> [dst -> src] } - multicast_networks = multicast_networks.curry() + multicast_networks = multicast_networks.uncurry().lexmin().curry() return multicast_networks From 5aafa10599c3d723e196699232c5f8d9ecf5a683 Mon Sep 17 00:00:00 2001 From: Renggeng Zheng Date: Sat, 22 Nov 2025 19:59:54 -0500 Subject: [PATCH 13/28] better docstrings for identify_mesh_casts --- .../model/looptree/reuse/isl/distributed_buffers.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/fastfusion/model/looptree/reuse/isl/distributed_buffers.py b/fastfusion/model/looptree/reuse/isl/distributed_buffers.py index 6896ad15..c1bd3950 100644 --- a/fastfusion/model/looptree/reuse/isl/distributed_buffers.py +++ b/fastfusion/model/looptree/reuse/isl/distributed_buffers.py @@ -22,6 +22,8 @@ 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 ---------- @@ -36,6 +38,13 @@ def identify_mesh_casts( 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() From 9bcc4173b73014af5d2af18b172407e38168a25c Mon Sep 17 00:00:00 2001 From: Renggeng Zheng Date: Sat, 22 Nov 2025 20:31:28 -0500 Subject: [PATCH 14/28] fixes to align apply --- .../looptree/reuse/isl/distributed_buffers.py | 36 ++++++++++++++++++- .../_mapspace_size_reduction.ipynb | 2 +- 2 files changed, 36 insertions(+), 2 deletions(-) diff --git a/fastfusion/model/looptree/reuse/isl/distributed_buffers.py b/fastfusion/model/looptree/reuse/isl/distributed_buffers.py index c1bd3950..05726318 100644 --- a/fastfusion/model/looptree/reuse/isl/distributed_buffers.py +++ b/fastfusion/model/looptree/reuse/isl/distributed_buffers.py @@ -7,6 +7,7 @@ import islpy as isl +from fastfusion.frontend.mapping import MappingNode from fastfusion.model.looptree.reuse.isl.isl_functions import dim_projector_mask from fastfusion.model.looptree.reuse.isl.mapping_to_isl import DUMP_ISL_IR from fastfusion.model.looptree.reuse.isl.mapping_to_isl.types import Fill, Occupancy @@ -155,10 +156,38 @@ 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: int, fills: Fill, occs: Occupancy) -> TransferInfo: + 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) @@ -182,6 +211,11 @@ def _cost_mesh_cast_hypercube(self, mcns: isl.Map) -> int: 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 ------------- diff --git a/notebooks/fast_pmapper/_mapspace_size_reduction.ipynb b/notebooks/fast_pmapper/_mapspace_size_reduction.ipynb index 860a652e..c14b8703 100644 --- a/notebooks/fast_pmapper/_mapspace_size_reduction.ipynb +++ b/notebooks/fast_pmapper/_mapspace_size_reduction.ipynb @@ -285,7 +285,7 @@ ], "metadata": { "kernelspec": { - "display_name": "Python 3", + "display_name": "fastfusion", "language": "python", "name": "python3" }, From 20cc9383a60d90841e23346522fcb7ab64d118e9 Mon Sep 17 00:00:00 2001 From: Renggeng Zheng Date: Sun, 23 Nov 2025 01:58:23 -0500 Subject: [PATCH 15/28] moved files to have better organization --- .../looptree/reuse/isl/{ => distributed}/distributed_buffers.py | 0 tests/isl/{distribuffers => distributed}/__init__.py | 0 .../{distribuffers => distributed}/multicast/test_cases.yaml | 0 .../spec/binding/valid_bindings.yaml | 0 tests/isl/{distribuffers => distributed}/spec/distributed.yaml | 0 tests/isl/{distribuffers => distributed}/spec/logical_arch.yaml | 0 .../isl/{distribuffers => distributed}/spec/physical_arch.yaml | 0 tests/isl/{distribuffers => distributed}/test_binding.py | 0 tests/isl/{distribuffers => distributed}/test_multicast.py | 2 +- 9 files changed, 1 insertion(+), 1 deletion(-) rename fastfusion/model/looptree/reuse/isl/{ => distributed}/distributed_buffers.py (100%) rename tests/isl/{distribuffers => distributed}/__init__.py (100%) rename tests/isl/{distribuffers => distributed}/multicast/test_cases.yaml (100%) rename tests/isl/{distribuffers => distributed}/spec/binding/valid_bindings.yaml (100%) rename tests/isl/{distribuffers => distributed}/spec/distributed.yaml (100%) rename tests/isl/{distribuffers => distributed}/spec/logical_arch.yaml (100%) rename tests/isl/{distribuffers => distributed}/spec/physical_arch.yaml (100%) rename tests/isl/{distribuffers => distributed}/test_binding.py (100%) rename tests/isl/{distribuffers => distributed}/test_multicast.py (97%) diff --git a/fastfusion/model/looptree/reuse/isl/distributed_buffers.py b/fastfusion/model/looptree/reuse/isl/distributed/distributed_buffers.py similarity index 100% rename from fastfusion/model/looptree/reuse/isl/distributed_buffers.py rename to fastfusion/model/looptree/reuse/isl/distributed/distributed_buffers.py diff --git a/tests/isl/distribuffers/__init__.py b/tests/isl/distributed/__init__.py similarity index 100% rename from tests/isl/distribuffers/__init__.py rename to tests/isl/distributed/__init__.py diff --git a/tests/isl/distribuffers/multicast/test_cases.yaml b/tests/isl/distributed/multicast/test_cases.yaml similarity index 100% rename from tests/isl/distribuffers/multicast/test_cases.yaml rename to tests/isl/distributed/multicast/test_cases.yaml diff --git a/tests/isl/distribuffers/spec/binding/valid_bindings.yaml b/tests/isl/distributed/spec/binding/valid_bindings.yaml similarity index 100% rename from tests/isl/distribuffers/spec/binding/valid_bindings.yaml rename to tests/isl/distributed/spec/binding/valid_bindings.yaml diff --git a/tests/isl/distribuffers/spec/distributed.yaml b/tests/isl/distributed/spec/distributed.yaml similarity index 100% rename from tests/isl/distribuffers/spec/distributed.yaml rename to tests/isl/distributed/spec/distributed.yaml diff --git a/tests/isl/distribuffers/spec/logical_arch.yaml b/tests/isl/distributed/spec/logical_arch.yaml similarity index 100% rename from tests/isl/distribuffers/spec/logical_arch.yaml rename to tests/isl/distributed/spec/logical_arch.yaml diff --git a/tests/isl/distribuffers/spec/physical_arch.yaml b/tests/isl/distributed/spec/physical_arch.yaml similarity index 100% rename from tests/isl/distribuffers/spec/physical_arch.yaml rename to tests/isl/distributed/spec/physical_arch.yaml diff --git a/tests/isl/distribuffers/test_binding.py b/tests/isl/distributed/test_binding.py similarity index 100% rename from tests/isl/distribuffers/test_binding.py rename to tests/isl/distributed/test_binding.py diff --git a/tests/isl/distribuffers/test_multicast.py b/tests/isl/distributed/test_multicast.py similarity index 97% rename from tests/isl/distribuffers/test_multicast.py rename to tests/isl/distributed/test_multicast.py index 80d004aa..52ee96f0 100644 --- a/tests/isl/distribuffers/test_multicast.py +++ b/tests/isl/distributed/test_multicast.py @@ -17,7 +17,7 @@ SpatialTag, TemporalTag, ) -from fastfusion.model.looptree.reuse.isl.distributed_buffers import ( +from fastfusion.model.looptree.reuse.isl.distributed.distributed_buffers import ( HypercubeMulticastModel, ) from fastfusion.model.looptree.reuse.isl.spatial import TransferInfo From 20ebb3ccb80cb2e98d11f1dbd5a9f725ff5da495 Mon Sep 17 00:00:00 2001 From: Renggeng Zheng Date: Sun, 23 Nov 2025 02:31:44 -0500 Subject: [PATCH 16/28] removed binding stuff to merge with distributed --- fastfusion/model/looptree/reuse/isl/binding/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 fastfusion/model/looptree/reuse/isl/binding/__init__.py diff --git a/fastfusion/model/looptree/reuse/isl/binding/__init__.py b/fastfusion/model/looptree/reuse/isl/binding/__init__.py deleted file mode 100644 index e69de29b..00000000 From 7842d5361ff64447b998c41b0d0087f1bda723df Mon Sep 17 00:00:00 2001 From: Renggeng Zheng Date: Sun, 23 Nov 2025 02:42:14 -0500 Subject: [PATCH 17/28] unfinished test case causing issues --- tests/isl/distributed/spec/binding/valid_bindings.yaml | 9 +++------ tests/isl/distributed/test_binding.py | 5 +++-- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/tests/isl/distributed/spec/binding/valid_bindings.yaml b/tests/isl/distributed/spec/binding/valid_bindings.yaml index 8ed1c0a5..87dffb7f 100755 --- a/tests/isl/distributed/spec/binding/valid_bindings.yaml +++ b/tests/isl/distributed/spec/binding/valid_bindings.yaml @@ -70,7 +70,7 @@ physical: name: DRAM p_dims: [i] - relation: # Compression relation where less DRAM chips than planned. + relations: # Compression relation where less DRAM chips than planned. weights: i = i // 2 inputs: i = i // 2 outputs: i = i // 2 @@ -80,7 +80,7 @@ physical: name: GLB p_dims: [x, y, z] - relation: # weight stationary relation + 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 @@ -90,11 +90,8 @@ physical: name: GLB l_dims: [x, y, z] - relation: # Some weird bypass shenanigans. + relations: # Some weird bypass shenanigans. weights: inputs: outputs: - - -- diff --git a/tests/isl/distributed/test_binding.py b/tests/isl/distributed/test_binding.py index 337907e3..a5568848 100755 --- a/tests/isl/distributed/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 fastfusion.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: From ed0421d8f138a363e0aacdf38e6fe78573cbbe44 Mon Sep 17 00:00:00 2001 From: Renggeng Zheng Date: Sun, 23 Nov 2025 02:42:40 -0500 Subject: [PATCH 18/28] commented out unfinished test case --- .../spec/binding/valid_bindings.yaml | 66 +++++++++---------- 1 file changed, 33 insertions(+), 33 deletions(-) diff --git a/tests/isl/distributed/spec/binding/valid_bindings.yaml b/tests/isl/distributed/spec/binding/valid_bindings.yaml index 87dffb7f..fc41eeff 100755 --- a/tests/isl/distributed/spec/binding/valid_bindings.yaml +++ b/tests/isl/distributed/spec/binding/valid_bindings.yaml @@ -61,37 +61,37 @@ 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: +# - 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: From 0c4184642484994899f620206bae34a5f1136fbe Mon Sep 17 00:00:00 2001 From: Renggeng Zheng Date: Fri, 19 Dec 2025 06:12:08 -0500 Subject: [PATCH 19/28] provisional port --- fastfusion/frontend/binding.py | 176 ++++++++++++++++++++++++++++++++- 1 file changed, 174 insertions(+), 2 deletions(-) diff --git a/fastfusion/frontend/binding.py b/fastfusion/frontend/binding.py index 0746f3ba..7c5e2e78 100755 --- a/fastfusion/frontend/binding.py +++ b/fastfusion/frontend/binding.py @@ -3,10 +3,11 @@ physical architectures. """ +from islpy._isl import Val from abc import abstractmethod -from typing import Dict, Tuple +from typing import Dict, Set, List, Tuple -from pydantic import StrictFloat +from pydantic import StrictFloat, model_validator import islpy as isl from fastfusion.util.basetypes import ParsableDict, ParsableList, ParsableModel @@ -81,8 +82,11 @@ class BindingNode(ParsableModel): """ logical: LogicalDomain + """The logical domain of the components being bound.""" physical: PhysicalDomain + """The physical location of the components being bound.""" relations: ParsableDict[str, str] + """A relation between each tensor and its logical domain to physical domain.""" @property def isl_relations(self) -> Dict[str, isl.Map]: @@ -126,4 +130,172 @@ class Binding(ParsableModel): """ version: StrictFloat + """Version of the binding spec.""" nodes: ParsableList[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: ParsableList[PhysicalComponent] + """The components referred to.""" + + +# For static analysis of types being used. +class PhysicalStorage(PhysicalComponent): + + + +class PhysicalProcessingElement(PhysicalComponent): + pass + + +# 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: {nocs}" + ) + # TODO: Check that all ports form a DAG between the networks and all + # networks are connected. + + +class LogicalComponent(ParsableDict): + """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] + From 98de000eab5f1f15c5bfbd7198e85d022e2365c2 Mon Sep 17 00:00:00 2001 From: Renggeng Zheng Date: Fri, 19 Dec 2025 06:13:52 -0500 Subject: [PATCH 20/28] blacked file --- fastfusion/frontend/binding.py | 41 ++++++++++++++-------------------- 1 file changed, 17 insertions(+), 24 deletions(-) diff --git a/fastfusion/frontend/binding.py b/fastfusion/frontend/binding.py index 7c5e2e78..6da89d7d 100755 --- a/fastfusion/frontend/binding.py +++ b/fastfusion/frontend/binding.py @@ -3,6 +3,7 @@ physical architectures. """ +from typing import TypeAlias from islpy._isl import Val from abc import abstractmethod from typing import Dict, Set, List, Tuple @@ -137,6 +138,7 @@ class Binding(ParsableModel): class NetworkOnChip(ParsableModel): """A model of a network-on-chip on the physical chip.""" + name: str """NoC name""" cost: isl.PwMultiAff @@ -148,14 +150,12 @@ class NetworkOnChip(ParsableModel): 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}" - ) + 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 @@ -200,17 +200,14 @@ class PhysicalComponents(ParsableModel): # For static analysis of types being used. -class PhysicalStorage(PhysicalComponent): - - - -class PhysicalProcessingElement(PhysicalComponent): - pass +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 @@ -218,7 +215,7 @@ class Port(ParsableModel): @model_validator(mode="after") def validate(self): # Ensures the parent and child NoCs are not the same. - if self.parent.name == self.child.name: + if self.parent.name == self.child.name: raise ValueError( "Parent should not be equal to the child.\n" f"Parent: {self.parent}\n" @@ -227,10 +224,7 @@ def validate(self): # Ensures port map is not null. if self.relation.is_empty(): - raise ValueError( - "Port cannot be empty\n" - f"Port: {Port}" - ) + 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 @@ -251,6 +245,7 @@ def validate(self): class PhysicalSpec(ParsableModel): """Physical specification of an accelerator.""" + name: str nocs: Set[NetworkOnChip] ports: Tuple[Port] @@ -260,14 +255,10 @@ class PhysicalSpec(ParsableModel): 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." - ) + 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." - ) + 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: @@ -276,26 +267,28 @@ def validate(self): f"Component: {component}\n" f"NoCs: {nocs}" ) - # TODO: Check that all ports form a DAG between the networks and all + # TODO: Check that all ports form a DAG between the networks and all # networks are connected. - + class LogicalComponent(ParsableDict): """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] - From dc92ca2da3b8ca80e4222b15142b51c4e33d25bc Mon Sep 17 00:00:00 2001 From: Renggeng Zheng Date: Fri, 19 Dec 2025 06:15:44 -0500 Subject: [PATCH 21/28] pylinted file --- fastfusion/frontend/binding.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/fastfusion/frontend/binding.py b/fastfusion/frontend/binding.py index 6da89d7d..76a871a2 100755 --- a/fastfusion/frontend/binding.py +++ b/fastfusion/frontend/binding.py @@ -3,10 +3,8 @@ physical architectures. """ -from typing import TypeAlias -from islpy._isl import Val from abc import abstractmethod -from typing import Dict, Set, List, Tuple +from typing import Dict, Set, Tuple, TypeAlias from pydantic import StrictFloat, model_validator import islpy as isl @@ -265,7 +263,7 @@ def validate(self): raise ValueError( "Component placed on invalid NoC.\n" f"Component: {component}\n" - f"NoCs: {nocs}" + f"NoCs: {self.nocs}" ) # TODO: Check that all ports form a DAG between the networks and all # networks are connected. From abb106dd3289bd35373850df1839bee44d829f8a Mon Sep 17 00:00:00 2001 From: Renggeng Zheng Date: Mon, 5 Jan 2026 12:36:34 -0500 Subject: [PATCH 22/28] ranks is not actually in the binding spec --- fastfusion/frontend/binding.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/fastfusion/frontend/binding.py b/fastfusion/frontend/binding.py index 76a871a2..888e3411 100755 --- a/fastfusion/frontend/binding.py +++ b/fastfusion/frontend/binding.py @@ -36,8 +36,6 @@ 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: ParsableList[str] @property From de59f8bd692946a22e9f32ae737a4269ff5c435a Mon Sep 17 00:00:00 2001 From: Renggeng Zheng Date: Tue, 13 Jan 2026 10:06:10 -0500 Subject: [PATCH 23/28] dirty minor changes --- fastfusion/frontend/binding.py | 4 +++- tests/isl/distributed/spec/binding/valid_bindings.yaml | 2 ++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/fastfusion/frontend/binding.py b/fastfusion/frontend/binding.py index 888e3411..d32f07e8 100755 --- a/fastfusion/frontend/binding.py +++ b/fastfusion/frontend/binding.py @@ -36,6 +36,7 @@ class LogicalDomain(Domain): """ Represents the logical architecture domain space of logical dims × tensor ranks. """ + ranks: ParsableList[str] l_dims: ParsableList[str] @property @@ -131,7 +132,7 @@ class Binding(ParsableModel): nodes: ParsableList[BindingNode] """Parts of the binding.""" - +''' class NetworkOnChip(ParsableModel): """A model of a network-on-chip on the physical chip.""" @@ -288,3 +289,4 @@ class BindingSpec(ParsableModel): physical: PhysicalSpec logical: LogicalSpec bindings: Set[isl.Map] +''' \ No newline at end of file diff --git a/tests/isl/distributed/spec/binding/valid_bindings.yaml b/tests/isl/distributed/spec/binding/valid_bindings.yaml index fc41eeff..653bd015 100755 --- a/tests/isl/distributed/spec/binding/valid_bindings.yaml +++ b/tests/isl/distributed/spec/binding/valid_bindings.yaml @@ -3,6 +3,7 @@ nodes: - logical: name: PE + ranks: [c, h, w, p, q, r, s] l_dims: [i] physical: name: PE @@ -12,6 +13,7 @@ tensorB: i = x + y * 2 # This is a dimension-major compression into the logical. It is bijective. - logical: name: Scratchpad + ranks: [c, h, w, p, q, r, s] l_dims: [x, y] physical: name: GLB From 683bb0ed940f8bc466ee3a264eb270c319515203 Mon Sep 17 00:00:00 2001 From: Renggeng Zheng Date: Thu, 22 Jan 2026 11:02:08 -0500 Subject: [PATCH 24/28] binding spec is now rank agnostic --- fastfusion/frontend/binding.py | 51 +++++-------------- .../spec/binding/valid_bindings.yaml | 34 +++---------- 2 files changed, 22 insertions(+), 63 deletions(-) mode change 100755 => 100644 fastfusion/frontend/binding.py diff --git a/fastfusion/frontend/binding.py b/fastfusion/frontend/binding.py old mode 100755 new mode 100644 index d32f07e8..8a7aac2b --- a/fastfusion/frontend/binding.py +++ b/fastfusion/frontend/binding.py @@ -17,37 +17,27 @@ class Domain(ParsableModel): Represents an architecture dangling reference of the binding. """ + _prefix: str name: str - + dims: ParsableList[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: ParsableList[str] - l_dims: ParsableList[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): @@ -55,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: ParsableList[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(ParsableModel): @@ -96,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, ) diff --git a/tests/isl/distributed/spec/binding/valid_bindings.yaml b/tests/isl/distributed/spec/binding/valid_bindings.yaml index 653bd015..d5e44d88 100755 --- a/tests/isl/distributed/spec/binding/valid_bindings.yaml +++ b/tests/isl/distributed/spec/binding/valid_bindings.yaml @@ -3,21 +3,19 @@ nodes: - logical: name: PE - ranks: [c, h, w, p, q, r, s] - l_dims: [i] + dims: [i] physical: name: PE - p_dims: [x, y] + 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 - ranks: [c, h, w, p, q, r, s] - l_dims: [x, y] + dims: [x, y] physical: name: GLB - p_dims: [a, b] + dims: [a, b] relations: tensorA: x = a and y = b tensorB: x = b and y = a @@ -27,39 +25,23 @@ - tensorA: | { - [ - tensorA_ranks[c, h, w, p, q, r, s] -> - l_PE_dims[i] - ] -> - p_PE_dims[x, y] : + 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] : + 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 ] : + 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 ] : + l_Scratchpad_dims[x, y] -> p_GLB_dims[ a, b ] : x = b and y = a } From 7c4c116156939439994a9a377171705f8e1b634c Mon Sep 17 00:00:00 2001 From: Renggeng Zheng Date: Thu, 22 Jan 2026 12:53:03 -0500 Subject: [PATCH 25/28] changes to arch --- tests/isl/distributed/spec/logical_arch.yaml | 29 ++++----- tests/isl/distributed/spec/physical_arch.yaml | 59 +++++++------------ 2 files changed, 33 insertions(+), 55 deletions(-) diff --git a/tests/isl/distributed/spec/logical_arch.yaml b/tests/isl/distributed/spec/logical_arch.yaml index 5ea6d4ab..9b148418 100755 --- a/tests/isl/distributed/spec/logical_arch.yaml +++ b/tests/isl/distributed/spec/logical_arch.yaml @@ -5,30 +5,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: - datawidth: datawidth - 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, datawidth: datawidth, width: datawidth} + datawidth: 32 - !Component - name: input_activation_reg - class: reg_storage - attributes: {depth: 1, datawidth: datawidth, width: datawidth} + 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 + spatial: {meshI: 8} attributes: {depth: 1, datawidth: datawidth, width: datawidth} - - !Component + - !Compute name: mac class: mac_compute attributes: {num_pipline_stages: 2, datawidth: datawidth} \ No newline at end of file diff --git a/tests/isl/distributed/spec/physical_arch.yaml b/tests/isl/distributed/spec/physical_arch.yaml index 387872af..20f2ad89 100755 --- a/tests/isl/distributed/spec/physical_arch.yaml +++ b/tests/isl/distributed/spec/physical_arch.yaml @@ -4,31 +4,23 @@ arch: nodes: - !Container name: system_arch + # TODO: attributes are placeholders. attributes: # Top-level attributes inherited by all components unless overridden technology: "45nm" global_cycle_seconds: 1e-9 datawidth: 16 - - !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 @@ -36,35 +28,26 @@ arch: datawidth: datawidth 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, datawidth: datawidth, width: datawidth} - - - !Component - name: input_activation_reg - class: reg_storage - attributes: {depth: 1, datawidth: datawidth, width: datawidth} - - - !Component - name: output_activation_reg - class: reg_storage - attributes: {depth: 1, datawidth: datawidth, width: datawidth} + meshJ = y and x < 0 and meshI = -x - 1; + meshJ = y and x > 0 and meshI = x; - !Component name: mac From 74224af6c79d7084556bf37a1ecf84db34106455 Mon Sep 17 00:00:00 2001 From: Renggeng Zheng Date: Thu, 22 Jan 2026 13:01:30 -0500 Subject: [PATCH 26/28] some binding uncommitted files --- .../looptree/reuse/isl/distributed/bind.py | 23 ++++++++++++++++ notebooks/fast_pmapper/outputs/.gitkeep | 0 notebooks/fast_pmapper/src/__init__.py | 0 tests/isl/distributed/spec/binding.yaml | 26 +++++++++++++++++++ .../isl/distributed/spec/matmul.workload.yaml | 13 ++++++++++ .../distributed/test_binding_application.py | 0 6 files changed, 62 insertions(+) create mode 100644 fastfusion/model/looptree/reuse/isl/distributed/bind.py mode change 100755 => 100644 notebooks/fast_pmapper/outputs/.gitkeep mode change 100755 => 100644 notebooks/fast_pmapper/src/__init__.py create mode 100644 tests/isl/distributed/spec/binding.yaml create mode 100755 tests/isl/distributed/spec/matmul.workload.yaml create mode 100644 tests/isl/distributed/test_binding_application.py diff --git a/fastfusion/model/looptree/reuse/isl/distributed/bind.py b/fastfusion/model/looptree/reuse/isl/distributed/bind.py new file mode 100644 index 00000000..2ee7aa4c --- /dev/null +++ b/fastfusion/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 fastfusion.frontend.binding import Binding +from fastfusion.frontend.mapping import Mapping +from fastfusion.frontend.workload import Workload + +from fastfusion.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/notebooks/fast_pmapper/outputs/.gitkeep b/notebooks/fast_pmapper/outputs/.gitkeep old mode 100755 new mode 100644 diff --git a/notebooks/fast_pmapper/src/__init__.py b/notebooks/fast_pmapper/src/__init__.py old mode 100755 new mode 100644 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/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/isl/distributed/test_binding_application.py b/tests/isl/distributed/test_binding_application.py new file mode 100644 index 00000000..e69de29b From fb4c506ba2349621e8ae3562409c9e5981823b58 Mon Sep 17 00:00:00 2001 From: Renggeng Zheng Date: Mon, 2 Feb 2026 12:28:10 -0500 Subject: [PATCH 27/28] fixed some fast post-merge errors, still some testcase errors on the items i control --- .../_deprecate/_simanneal/evalmapping.py | 10 +++---- .../_deprecate/_simanneal/mapspaceglobals.py | 12 ++++---- accelforge/_deprecate/_simanneal/simanneal.py | 16 +++++------ accelforge/_deprecate/_simanneal/wrappers.py | 18 ++++++------ accelforge/_deprecate/_simanneal2/__init__.py | 4 +-- .../_deprecate/_simanneal2/simanneal.py | 28 +++++++++---------- accelforge/_deprecate/compatibility_util.py | 8 +++--- .../group_similar_einsums.py | 6 ++-- .../layerdeduplication/grouped_einsums.py | 2 +- .../_deprecate/mapping_filter_tags/ffmt.py | 4 +-- .../mapping_filter_tags/onesplit.py | 4 +-- .../_deprecate/mapping_filter_tags/util.py | 2 +- accelforge/_deprecate/tags.py | 2 +- accelforge/_deprecate/viz/interactive.py | 6 ++-- accelforge/_deprecate/viz/reservationtree.py | 6 ++-- accelforge/_deprecate/viz/ski_slope.py | 6 ++-- accelforge/frontend/_binding.py | 10 +++---- accelforge/model/_looptree/accesses.py | 2 +- .../model/_looptree/equivalent_ranks.py | 2 +- accelforge/model/_looptree/latency/latency.py | 2 +- .../model/_looptree/latency/processors.py | 2 +- accelforge/model/_looptree/reuse/isl/des.py | 2 +- .../_looptree}/reuse/isl/distributed/bind.py | 8 +++--- .../isl/distributed/distributed_buffers.py | 10 +++---- accelforge/model/_looptree/run.py | 24 ++++++++-------- tests/isl/distributed/__init__.py | 0 tests/isl/distributed/test_multicast.py | 6 ++-- tests/isl/mapper/test_isl_functions.py | 2 +- tests/test_ffm_join_pmappings.py | 0 tests/test_ffm_make_pmappings.py | 2 +- tests/test_ffm_make_tile_shapes.py | 0 tests/test_symbolic_model.py | 0 tests/test_workload.py | 0 33 files changed, 103 insertions(+), 103 deletions(-) rename {fastfusion/model/looptree => accelforge/model/_looptree}/reuse/isl/distributed/bind.py (69%) rename {fastfusion/model/looptree => accelforge/model/_looptree}/reuse/isl/distributed/distributed_buffers.py (96%) mode change 100755 => 100644 tests/isl/distributed/__init__.py mode change 100755 => 100644 tests/test_ffm_join_pmappings.py mode change 100755 => 100644 tests/test_ffm_make_pmappings.py mode change 100755 => 100644 tests/test_ffm_make_tile_shapes.py mode change 100755 => 100644 tests/test_symbolic_model.py mode change 100755 => 100644 tests/test_workload.py 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/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 index d7f55456..01dd0b18 100644 --- a/accelforge/frontend/_binding.py +++ b/accelforge/frontend/_binding.py @@ -19,7 +19,7 @@ class Domain(EvalableModel): _prefix: str name: str - dims: ParsableList[str] + dims: EvalableList[str] @property def isl_space(self) -> isl.Space: @@ -63,7 +63,7 @@ class BindingNode(EvalableModel): """The logical domain of the components being bound.""" physical: PhysicalDomain """The physical location of the components being bound.""" - relations: ParsableDict[str, str] + relations: EvalableDict[str, str] """A relation between each tensor and its logical domain to physical domain.""" @property @@ -106,7 +106,7 @@ class Binding(EvalableModel): version: StrictFloat """Version of the binding spec.""" - nodes: ParsableList[BindingNode] + nodes: EvalableList[BindingNode] """Parts of the binding.""" ''' @@ -169,7 +169,7 @@ class PhysicalComponent(ParsableModel): class PhysicalComponents(ParsableModel): """List of componenents on the physical chip.""" - components: ParsableList[PhysicalComponent] + components: EvalableList[PhysicalComponent] """The components referred to.""" @@ -245,7 +245,7 @@ def validate(self): # networks are connected. -class LogicalComponent(ParsableDict): +class LogicalComponent(EvalableDict): """Logical components of an accelerator.""" name: str 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/fastfusion/model/looptree/reuse/isl/distributed/bind.py b/accelforge/model/_looptree/reuse/isl/distributed/bind.py similarity index 69% rename from fastfusion/model/looptree/reuse/isl/distributed/bind.py rename to accelforge/model/_looptree/reuse/isl/distributed/bind.py index 2ee7aa4c..351e9cb7 100644 --- a/fastfusion/model/looptree/reuse/isl/distributed/bind.py +++ b/accelforge/model/_looptree/reuse/isl/distributed/bind.py @@ -1,9 +1,9 @@ """Applies the binding layer into one that can be used for later analysis,""" -from fastfusion.frontend.binding import Binding -from fastfusion.frontend.mapping import Mapping -from fastfusion.frontend.workload import Workload +from accelforge.frontend.binding import Binding +from accelforge.frontend.mapping import Mapping +from accelforge.frontend.workload import Workload -from fastfusion.model.looptree.reuse.isl.mapping_to_isl.analyze_mapping import ( +from accelforge.model._looptree.reuse.isl.mapping_to_isl.analyze_mapping import ( occupancies_from_mapping, MappingAnalysisResult ) diff --git a/fastfusion/model/looptree/reuse/isl/distributed/distributed_buffers.py b/accelforge/model/_looptree/reuse/isl/distributed/distributed_buffers.py similarity index 96% rename from fastfusion/model/looptree/reuse/isl/distributed/distributed_buffers.py rename to accelforge/model/_looptree/reuse/isl/distributed/distributed_buffers.py index 05726318..5ef302b0 100644 --- a/fastfusion/model/looptree/reuse/isl/distributed/distributed_buffers.py +++ b/accelforge/model/_looptree/reuse/isl/distributed/distributed_buffers.py @@ -7,11 +7,11 @@ import islpy as isl -from fastfusion.frontend.mapping import MappingNode -from fastfusion.model.looptree.reuse.isl.isl_functions import dim_projector_mask -from fastfusion.model.looptree.reuse.isl.mapping_to_isl import DUMP_ISL_IR -from fastfusion.model.looptree.reuse.isl.mapping_to_isl.types import Fill, Occupancy -from fastfusion.model.looptree.reuse.isl.spatial import ( +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, 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/tests/isl/distributed/__init__.py b/tests/isl/distributed/__init__.py old mode 100755 new mode 100644 diff --git a/tests/isl/distributed/test_multicast.py b/tests/isl/distributed/test_multicast.py index 52ee96f0..6dfb5e38 100644 --- a/tests/isl/distributed/test_multicast.py +++ b/tests/isl/distributed/test_multicast.py @@ -8,7 +8,7 @@ import islpy as isl -from fastfusion.model.looptree.reuse.isl.mapping_to_isl.types import ( +from accelforge.model._looptree.reuse.isl.mapping_to_isl.types import ( # Data movement descriptors. Fill, Occupancy, @@ -17,10 +17,10 @@ SpatialTag, TemporalTag, ) -from fastfusion.model.looptree.reuse.isl.distributed.distributed_buffers import ( +from accelforge.model._looptree.reuse.isl.distributed.distributed_buffers import ( HypercubeMulticastModel, ) -from fastfusion.model.looptree.reuse.isl.spatial import TransferInfo +from accelforge.model._looptree.reuse.isl.spatial import TransferInfo from ..util import load_solutions diff --git a/tests/isl/mapper/test_isl_functions.py b/tests/isl/mapper/test_isl_functions.py index d45d9c7d..2385df05 100644 --- a/tests/isl/mapper/test_isl_functions.py +++ b/tests/isl/mapper/test_isl_functions.py @@ -6,7 +6,7 @@ import islpy as isl -from fastfusion.model.looptree.reuse.isl.isl_functions import * +from accelforge.model._looptree.reuse.isl.isl_functions import * class BasicIslFunctionTests(unittest.TestCase): """ 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 From 25b8b969bd540a0354ab8d596b9b47f776dc7af4 Mon Sep 17 00:00:00 2001 From: Renggeng Zheng Date: Mon, 2 Feb 2026 12:30:01 -0500 Subject: [PATCH 28/28] fixed a broken reference check instantiation, 3 more errors --- accelforge/_deprecate/viz/__init__.py | 0 accelforge/mapper/FFM/_join_pmappings/__init__.py | 0 accelforge/mapper/FFM/_make_pmappings/contraints/__init__.py | 0 accelforge/model/_looptree/__init__.py | 0 accelforge/model/_looptree/visualization/__init__.py | 0 accelforge/util/_sympy/__init__.py | 0 tests/isl/mapper/test_spatial_reuse_analysis.py | 2 +- 7 files changed, 1 insertion(+), 1 deletion(-) mode change 100755 => 100644 accelforge/_deprecate/viz/__init__.py mode change 100755 => 100644 accelforge/mapper/FFM/_join_pmappings/__init__.py mode change 100755 => 100644 accelforge/mapper/FFM/_make_pmappings/contraints/__init__.py mode change 100755 => 100644 accelforge/model/_looptree/__init__.py mode change 100755 => 100644 accelforge/model/_looptree/visualization/__init__.py mode change 100755 => 100644 accelforge/util/_sympy/__init__.py diff --git a/accelforge/_deprecate/viz/__init__.py b/accelforge/_deprecate/viz/__init__.py old mode 100755 new mode 100644 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/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/tests/isl/mapper/test_spatial_reuse_analysis.py b/tests/isl/mapper/test_spatial_reuse_analysis.py index f5cf1c09..2f38d6cb 100644 --- a/tests/isl/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(