-
Notifications
You must be signed in to change notification settings - Fork 10
[schedule][mlir] consumer fusion #112
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
liamsemeria
wants to merge
3
commits into
xtc-tools:main
Choose a base branch
from
liamsemeria:dev/sliam/mlir-fuse-consumer
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
3 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -33,7 +33,8 @@ | |
| ) | ||
| from mlir.passmanager import PassManager | ||
| from mlir.ir import Module | ||
| import mlir.xtc_transform | ||
|
|
||
| from mlir.xtc_transform import FuseConsumerOp | ||
|
|
||
| # Import SDist if available | ||
| try: | ||
|
|
@@ -231,12 +232,17 @@ def _generate_scheduling(self) -> OpResult: | |
| schedule=schedule, | ||
| root=list(schedule.permutation)[0], | ||
| handle=handle, | ||
| fuse_axes=fused_producers.get(schedule.node_ident), | ||
| producer_fuse_axes=fused_producers.get(schedule.node_ident), | ||
| ) | ||
| if schedule.vectorization or self._always_vectorize: | ||
| self._post_vectorize(scheduling_state, schedule) | ||
| handle = scheduling_state.handle | ||
|
|
||
| if schedule.fused_consumers: | ||
| self._fuse_consumers_into_loops( | ||
| schedule, scheduling_state, unscheduled_handles | ||
| ) | ||
|
|
||
| assert handle, "At least 1 operation should have been processed" | ||
| return handle | ||
|
|
||
|
|
@@ -308,7 +314,7 @@ def _generate_node_scheduling( | |
| schedule: MlirNodeSchedule, | ||
| root: str, | ||
| handle: OpResult, | ||
| fuse_axes: dict[str, list[str]] | None, | ||
| producer_fuse_axes: dict[str, list[str]] | None, | ||
| ) -> SchedulingState: | ||
| sched_state = SchedulingState({}, handle, None) | ||
| split_state = SplitState(schedule.splits, root) | ||
|
|
@@ -361,9 +367,9 @@ def _generate_node_scheduling( | |
| if loop_name in schedule.distribution: | ||
| self._distribute_loop(loop_name, schedule, sched_state) | ||
| # Fuse the producers | ||
| if fuse_axes and loop_name in fuse_axes: | ||
| if producer_fuse_axes and loop_name in producer_fuse_axes: | ||
| self._fuse_producers_into_loop( | ||
| loop_name, fuse_axes, schedule, sched_state | ||
| loop_name, producer_fuse_axes, schedule, sched_state | ||
| ) | ||
|
|
||
| # For now on, the focus is on the outermost loop | ||
|
|
@@ -376,6 +382,41 @@ def _generate_node_scheduling( | |
|
|
||
| return sched_state | ||
|
|
||
| def _fuse_consumers_into_loops( | ||
| self, | ||
| schedule: MlirNodeSchedule, | ||
| sched_state: SchedulingState, | ||
| unscheduled_handles: set[str | None], | ||
| ): | ||
| assert self._named_sequence is not None | ||
|
|
||
| fuse_root = parent_name(schedule.fused_consumers[0]) | ||
| for fuse_axis in schedule.fused_consumers: | ||
| # derive handle of consumer | ||
| consumer_handles = find_consumer_handles( | ||
| self._mlir_program.mlir_module, schedule.node_ident | ||
| ) | ||
| consumer_id = consumer_handles[0] | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What if consumer_handles is empty? aka the current op is the last one of the graph |
||
| unscheduled_handles.add(consumer_id) | ||
| # fuse consumer into all loops until the fuse_axis | ||
| fuse_loops = [] | ||
| for loop_dim in schedule.permutation[fuse_root]: | ||
| transform_result = sched_state.all_loops[loop_dim] | ||
| fuse_loops.append(transform_result) | ||
| if loop_dim == fuse_axis: | ||
| break | ||
| consumer_handle = structured_match( | ||
| results_=transform.AnyOpType.get(), | ||
| target=self._named_sequence.bodyTarget, | ||
| op_attrs={consumer_id: UnitAttr.get()}, | ||
| ) | ||
| op = FuseConsumerOp(consumer_handle, fuse_loops) | ||
| # re-annotate the loops that were touched by the fusion | ||
| for i, loop_dim in enumerate(schedule.permutation[fuse_root]): | ||
| transform.AnnotateOp(op.new_loops[i], loop_dim) | ||
|
Comment on lines
+415
to
+416
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nice |
||
| if loop_dim == fuse_axis: | ||
| break | ||
|
|
||
| def _fuse_producers_into_loop( | ||
| self, | ||
| loop_name: str, | ||
|
|
@@ -484,7 +525,10 @@ def _recursive_scheduling( | |
| self, schedule: MlirNodeSchedule, root: str, sched_state: SchedulingState | ||
| ): | ||
| inner_sched_state = self._generate_node_scheduling( | ||
| schedule=schedule, root=root, handle=sched_state.handle, fuse_axes=None | ||
| schedule=schedule, | ||
| root=root, | ||
| handle=sched_state.handle, | ||
| producer_fuse_axes=None, | ||
| ) | ||
| sched_state.all_loops.update(inner_sched_state.all_loops) | ||
| sched_state.handle = inner_sched_state.handle | ||
|
|
@@ -645,35 +689,58 @@ def _pack_buffer( | |
| ) | ||
|
|
||
| def _collect_fused_producers(self, unscheduled_handles: set[str | None]): | ||
| # maps each fused consumer op to the producer handles that must be | ||
| # maps each fused containing op to the producer handles that must be | ||
| # fused through each loop dimension to reach their target fusion depth. | ||
| fused_producers = {} | ||
| fused_producer_handles = {} | ||
|
|
||
| for schedule in self._nodes_schedules: | ||
| if schedule.fused: | ||
| if schedule.fused_producers: | ||
| prods = find_producer_handles( | ||
| self._mlir_program.mlir_module, schedule.node_ident | ||
| ) | ||
| fuse_root = parent_name(schedule.fused[0][0]) | ||
| fuse_root = parent_name(schedule.fused_producers[0][0]) | ||
| unscheduled_handles.update(set(prods)) | ||
| op_axes = {idx: ax for ax, idx in schedule.fused} | ||
| op_axes = {idx: ax for ax, idx in schedule.fused_producers} | ||
|
|
||
| fuse_destinations = {} | ||
| for idx, prod_handle in enumerate(prods): | ||
| if not prod_handle: | ||
| continue | ||
| if idx in op_axes: | ||
| fuse_destinations[prod_handle] = op_axes[idx] | ||
| # get outer dims to fuse, assumes fuse no splitting avove loop dim | ||
| # get outer dims to fuse, assumes fuse no splitting above loop dim | ||
| dim_fuse_handles: dict[str, list[str]] = {} | ||
| for fuse_handle, fuse_dest in fuse_destinations.items(): | ||
| for dim in schedule.permutation[fuse_root]: | ||
| dim_fuse_handles.setdefault(dim, []).append(fuse_handle) | ||
| if dim == fuse_dest: | ||
| break | ||
| fused_producers[schedule.node_ident] = dim_fuse_handles | ||
| fused_producer_handles[schedule.node_ident] = dim_fuse_handles | ||
|
|
||
| return fused_producer_handles | ||
|
|
||
|
|
||
| def find_consumer_handles(module: Module, root_handle: str) -> list[str | None]: | ||
| # returns the handles for each consumer op of the operation specified by root_handle | ||
| consumer_handles: list[str | None] = [] | ||
| root_op = None | ||
| for func_op in module.body.operations: | ||
| for op in func_op.regions[0].blocks[0].operations: | ||
| if root_handle in op.attributes: | ||
| root_op = op | ||
| break | ||
| if root_op: | ||
| break | ||
|
|
||
| if not root_op: | ||
| return consumer_handles | ||
|
|
||
| return fused_producers | ||
| for use in root_op.results[0].uses: | ||
| consumer_op = use.owner | ||
| for attr in consumer_op.attributes: | ||
| if attr.startswith("__xtc_id_"): | ||
| consumer_handles.append(attr) | ||
| return consumer_handles | ||
|
|
||
|
|
||
| def find_producer_handles(module: Module, root_handle: str) -> list[str | None]: | ||
|
|
@@ -751,7 +818,6 @@ def run(self, pass_names: list[str]) -> None: | |
|
|
||
|
|
||
| def apply_bufferization_passes(mlir_program: RawMlirProgram, mlir_install_dir: str): | ||
| assert mlir.xtc_transform | ||
| bufferize_options = [ | ||
| "bufferize-function-boundaries", | ||
| "function-boundary-type-conversion=identity-layout-map", | ||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The loop over
schedule.fused_consumerssuggests multiple axes are supported, but it's only correct for one:fuse_rootand the consumer handle are derived from[0]for every iteration, andsched_state.all_loopsisn't refreshed afterFuseConsumerOp, so a second iteration would reuse loop handles the first fusion may have invalidated (the fresh ones are inop.new_loops).