-
Notifications
You must be signed in to change notification settings - Fork 7
[transform] Basic example of applying a schedule to a payload #12
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
Changes from 5 commits
2de74a3
3a51e04
19920ba
971e7ef
08ee4a1
e94f333
d037047
6f29ea9
40f97cc
abc7403
fe03285
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,120 @@ | ||
| # RUN: %PYTHON %s | FileCheck %s | ||
|
|
||
| # A basic example of generating a payload, a schedule, and applying the latter | ||
| # to the former. Shows how to do it from Python and from the cmd given the | ||
| # payload and schedule are .mlir files. Run this file to see the concrete | ||
| # schedule IR, pre-transform payload IR and transformed payload IR. | ||
|
|
||
| import tempfile | ||
| import subprocess | ||
|
|
||
| from mlir.ir import Context, Location, InsertionPoint, Operation, Module | ||
| from mlir.ir import RankedTensorType, F32Type, FloatAttr, DenseElementsAttr, UnitAttr | ||
| from mlir.dialects import arith, func, linalg, tensor, transform | ||
| from mlir.dialects.transform import structured | ||
|
|
||
|
|
||
| def example_payload() -> Module: | ||
| """Example payload where the results of two matmuls are summed together. | ||
rolfmorel marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| Can be re-written so that the second matmul accumulates top of the the result of the first. | ||
| """ | ||
|
|
||
| print("NOTE: example payload module:") | ||
| payload = Module.create() | ||
| with InsertionPoint(payload.body): | ||
| matrixType = RankedTensorType.get([16, 16], F32Type.get()) | ||
|
|
||
| # NB: Do the CHECKing on the transformed output: | ||
| # CHECK-LABEL: result of applying schedule to payload | ||
| # CHECK: func.func @fold_add_on_two_matmuls | ||
| # CHECK-SAME: (%[[MATRIX_A:.*]]: {{.*}}, %[[MATRIX_B:.*]]: {{.*}}) | ||
| @func.func(matrixType, matrixType) | ||
| def fold_add_on_two_matmuls(matrixA, matrixB): | ||
| splat_float = FloatAttr.get(F32Type.get(), 1.111111) | ||
| splat_attr = DenseElementsAttr.get_splat(matrixType, splat_float) | ||
| # CHECK: %[[WEIGHTS:.*]] = arith.constant dense<1.11 | ||
| weights = arith.constant(matrixType, splat_attr) | ||
| c0 = arith.constant(F32Type.get(), 0.0) | ||
| empty = tensor.empty(matrixType.shape, matrixType.element_type) | ||
| # CHECK: %[[ZERO_INIT:.*]] = linalg.fill | ||
| zero_init = linalg.fill(c0, outs=[empty]) | ||
| # CHECK: %[[A_X_WEIGHTS:.*]] = linalg.matmul ins(%[[MATRIX_A]], %[[WEIGHTS]]{{.*}}) outs(%[[ZERO_INIT]] | ||
| A_x_weights = linalg.matmul(matrixA, weights, outs=[zero_init]) | ||
| empty2 = tensor.empty(matrixType.shape, matrixType.element_type) | ||
| zero_init2 = linalg.fill(c0, outs=[empty2]) | ||
| # CHECK: %[[RES:.*]] = linalg.matmul ins(%[[MATRIX_B]], %[[WEIGHTS]]{{.*}}) outs(%[[A_X_WEIGHTS]] | ||
| B_x_weights = linalg.matmul(matrixB, weights, outs=[zero_init2]) | ||
| # CHECK-NOT: linalg.add | ||
| added = linalg.add(A_x_weights, B_x_weights, outs=[empty]) | ||
| # CHECK: return %[[RES]] | ||
| return added | ||
|
|
||
| print(payload) | ||
| return payload | ||
|
|
||
|
|
||
| def example_schedule() -> Module: | ||
| """Most basic schedule which doesn't just wrap a pass -- wraps a single rewrite pattern.""" | ||
| print("NOTE: example schedule module:") | ||
| schedule_module = Module.create() | ||
| schedule_module.operation.attributes["transform.with_named_sequence"] = ( | ||
| UnitAttr.get() | ||
| ) | ||
| with InsertionPoint(schedule_module.body): | ||
| named_seq = transform.named_sequence( | ||
| "__transform_main", | ||
| input_types=[transform.any_op_t()], | ||
| result_types=[], | ||
| arg_attrs=[{"transform.readonly": UnitAttr.get()}], | ||
| ) | ||
|
|
||
| with InsertionPoint(named_seq.body): | ||
| func = structured.MatchOp.match_op_names( | ||
| named_seq.bodyTarget, ["func.func"] | ||
| ) # TODO: fix syntax upstream | ||
| with InsertionPoint(transform.apply_patterns(func).patterns): | ||
| Operation.create( | ||
| "transform.apply_patterns.linalg.fold_add_into_dest" | ||
| ) # TODO: expose dedicated builder upstream | ||
| transform.yield_([]) | ||
|
|
||
| print(schedule_module) | ||
| return schedule_module | ||
|
|
||
|
|
||
| with Context(), Location.unknown(): | ||
| payload = example_payload() | ||
| schedule_module = example_schedule() | ||
| schedule: transform.NamedSequenceOp = schedule_module.body.operations[0] | ||
|
|
||
| print( | ||
| "NOTE: result of applying schedule to payload directly within Python process:" | ||
| ) | ||
| schedule.apply(payload) | ||
rolfmorel marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| print(payload) | ||
|
|
||
| # Demonstrate applying a schedule from file to a payload from file | ||
| with ( | ||
| tempfile.NamedTemporaryFile("w", prefix="payload_") as payload_file, | ||
| tempfile.NamedTemporaryFile("w", prefix="schedule_") as schedule_file, | ||
| ): | ||
| print(payload, file=payload_file, flush=True) | ||
| print("NOTE: Have dumped payload to temp file:", payload_file.name) | ||
| print(schedule_module, file=schedule_file, flush=True) | ||
| print("NOTE: Have dumped schedule to temp file:", schedule_file.name) | ||
|
|
||
| cmdline = [ | ||
| "python", | ||
| "-m", | ||
| "lighthouse.schedule", | ||
| schedule_file.name, | ||
| payload_file.name, | ||
| ] | ||
|
||
| print( | ||
| "NOTE: output of applying schedule to payload from commandline:", *cmdline | ||
| ) | ||
| print(subprocess.run(cmdline, capture_output=True).stdout.decode()) | ||
| print( | ||
| f"NOTE: cleaning-up temp files: {payload_file.name}, {schedule_file.name}" | ||
| ) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,36 @@ | ||
| # Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. | ||
| # See https://llvm.org/LICENSE.txt for license information. | ||
| # SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception | ||
|
|
||
| import argparse | ||
| import sys | ||
|
|
||
| from mlir import ir | ||
| from mlir.dialects import transform | ||
|
|
||
|
|
||
| if __name__ == "__main__": | ||
| ArgParser = argparse.ArgumentParser(prog="lighthouse.transform") | ||
| ArgParser.add_argument( | ||
| "schedule", help="MLIR schedule module (path)" | ||
| ) | ||
| ArgParser.add_argument( | ||
| "payload", help="MLIR payload module (path)" | ||
| ) | ||
| args = ArgParser.parse_args(sys.argv[1:]) | ||
|
|
||
| with ir.Context(), ir.Location.unknown(): | ||
| with open(args.schedule) as f: | ||
| schedule_module = ir.Module.parse(f.read()) | ||
| with open(args.payload) as f: | ||
| payload_module = ir.Module.parse(f.read()) | ||
|
|
||
| schedule = schedule_module.body.operations[0] | ||
| if not isinstance(schedule, transform.NamedSequenceOp): | ||
| sys.exit( | ||
| "The following op was expected to be a `transform.named_sequence`, instead got:\n" | ||
| + str(schedule) | ||
| ) | ||
| schedule.apply(payload_module) | ||
|
|
||
| print(payload_module) |
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.
Lets avoid generic names like
@example_payloadand use something descriptive instead. For example, what name would we use for the next example?@example_payload_1? That doesn't scale 😅How about
generate_payload_two_matmuls_and_add? We could skipgenerate_payloadif the filename was ...generate_payload.pyor something similar ;-) Yes, I do think that having separate files would help.Uh oh!
There was an error while loading. Please reload this page.
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.
Just to preface, I see your point about setting a good example (pun intended) around naming.
Not sure if it's needed in this particular case. At least from the the perspective how I approach it here.
If we go with a more granular approach of multiple files with small examples working together (like you propose in another comment), then it might need different design approach, indeed.
I'd argue that specificity adds more information and implies that sth about the exact form/shape/implementation is important in a presented item. This addition can add to or distract from the core message.
I see this file as a self-contained example that focuses primarily on mechanism behind taking two MLIR modules: payload IR and a schedule, and executing them.
As such, I doubt there's need for scaling. Each standalone example script could have
@example_payloadas long as that specific payload doesn't matter for the overall idea we're communicating.This particular IR could be an empty function and little would change (% lit checks and perhaps some user confusion due to "uselessness" of a schedule doing effectively nothing).
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.
Thank you, @adam-smnk ! That captures my perspective on what is happening here very well!