Skip to content
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import tempfile
import subprocess

from mlir import ir
from mlir.dialects import arith, func, linalg, tensor, transform
from mlir.dialects.transform import structured

from lighthouse import transform as lh_transform


def example_payload() -> ir.Module:
payload = ir.Module.create()
with ir.InsertionPoint(payload.body):
matrixType = ir.RankedTensorType.get([16, 16], ir.F32Type.get())

@func.func(matrixType, matrixType)
def fold_add_on_two_matmuls(matrixA, matrixB):
splat_float = ir.FloatAttr.get(ir.F32Type.get(), 1.111111)
splat_attr = ir.DenseElementsAttr.get_splat(matrixType, splat_float)
weights = arith.constant(matrixType, splat_attr)
c0 = arith.constant(ir.F32Type.get(), 0.0)
empty = tensor.empty(matrixType.shape, matrixType.element_type)
zero_init = linalg.fill(c0, outs=[empty])
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])
B_x_weights = linalg.matmul(matrixB, weights, outs=[zero_init2])
added = linalg.add(A_x_weights, B_x_weights, outs=[empty])
return added

return payload


def example_schedule() -> ir.Module:
schedule = ir.Module.create()
schedule.operation.attributes["transform.with_named_sequence"] = ir.UnitAttr.get()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be better if the transform module provided this as a helper, so that users only need to add the transforms themselves.

Copy link
Contributor Author

@rolfmorel rolfmorel Nov 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed, we should probably be facilitating dealing with such details a bit more. The thing I would not like is to obscure that we are just constructing IR, i.e. to me this code still reflects what the .mlir will look like. We should find a middle ground somehow. So, the helpers should look like IR builders as well ... probably?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On returning to the PR: I think this is only non-ergonomic in that one must intersperse operation between schedule and attributes (an upstream issue). Beyond that, this API usage properly reflects how IR is build, in as terse a manner as upstream supports.

Separately, we could of course have helpers to, e.g. to wrap named_sequences up in appropriate modules. As that's not entirely trivial, e.g. multiple sequences will live in the same module, so we cannot simply wrap a single named_sequence, I think we can punt this until we start observing the kind of replication we get across the repo.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd like to dissociate a bit the transform helpers from actual IR, because while the underlying IR shape may change, the API should not. If all our APIs follow the IR shape closely, then we'll have to change their usage every time the IR changes, and that doesn't scale.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, we can improve this. I don't think the right solution is obvious though (go ahead and play around with it -- I am sure @makslevental agrees) and don't think this is the PR to solve it. Lets just merge this and address the right kind of wrappers in a dedicated PR (such a more focused PR is also like to engage the ex-Brium folks more, I imagine).

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with Renato's feedback here.

At a higher level, I am already struggling to parse the code-structure within Lighthouse. For example:

What's the intended difference between the two? I feel similarly about https://github.com/llvm/lighthouse/tree/main/python/examples and how that dir is evolving.

IMO, we should think carefully about the structure and these initial PRs are key. While I agree that it's hard to design future-proof APIs, there's scope to improve modularity here. And to identify clear boundaries between "components" within this PR. Specifically (*):

  1. Payload IR generation ("Ingress").
  2. Schedule IR generation ("Auto-tunning").
  3. Driver, i.e. generate payload -> apply schedule ("Execution + driver").

There's yet another step - testing - that is also included here. These are all fairly fundamental design elements.

I am not advocating for us identifying the perfect solution within this PR and I am happy for us to iterate in-tree. That said, I will leave some inline comments and do suggest that the newly added example is more modular.

(*) Using labels from https://github.com/llvm/lighthouse/wiki/Integrator#design-points in "".

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FYI, the discussion on general infrastructure is here:
#16

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The structuring of the repo is a distinct point from what this chain was about before: more convenience helpers.

I agree that mlir-gen (as a python module name: mlir_gen) should move inside python/lighthouse/ingress or else into a tools dir (and, as I've said before, IMO that leading python dir is redundant). I will do this clean-up in another PR👍

For the examples dir, I currently only see a single quirk: python/examples/mlir/compile_and_run.py. Otherwise the hierarchy mirrors that of the modules in python/lighthouse. Did you have something else in mind?

As for testing, I will now enable CI with this PR.

Copy link
Contributor

@adam-smnk adam-smnk Nov 19, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

and, as I've said before, IMO that leading python dir is redundant

+1
It also just hides examples which is better to have at the top-level. As a new comer to a repo, I want to find that first.

For the examples dir, I currently only see a single quirk: python/examples/mlir/compile_and_run.py. Otherwise the hierarchy mirrors that of the modules in python/lighthouse.

I don't think examples need to match overall modules structure. They largely do now as it makes sense thematically i.e., how to import a torch model into MLIR uses ingress modules (not saying current structure is perfect or immutable). But examples could be more abstract then individual modules lighthouse provides and/or span across multiple modules.

Closer structural mirroring would make sense for a test dir which we could use soon.

with ir.InsertionPoint(schedule.body):
named_seq = transform.NamedSequenceOp( # TODO: fix snake_case wrapper upstream
sym_name="__transform_main",
input_types=[transform.any_op_t()],
result_types=[],
arg_attrs=[{"transform.readonly": ir.UnitAttr.get()}],
)

with ir.InsertionPoint(named_seq.body):
func = structured.MatchOp.match_op_names(
named_seq.bodyTarget, ["func.func"]
) # TODO: fix syntax upstream
with ir.InsertionPoint(
transform.apply_patterns(func).patterns.blocks.append()
): # TODO: fix snake_case wrapper upstream
ir.Operation.create(
"transform.apply_patterns.linalg.fold_add_into_dest"
) # TODO: expose dedicated builder upstream
transform.yield_([])
return schedule


with ir.Context(), ir.Location.unknown():
payload_module = example_payload()
print("NOTE: example payload module:")
print(payload_module)
schedule_module = example_schedule()
print("NOTE: example schedule module:")
print(schedule_module)

print("NOTE: output of applying schedule to payload directly within Python process:")
schedule = schedule_module.body.operations[0]
lh_transform.apply(schedule, payload_module)
print(payload_module)

with tempfile.NamedTemporaryFile(
"w", prefix="payload_"
) as payload_file, tempfile.NamedTemporaryFile(
"w", prefix="schedule_"
) as schedule_file:
print(payload_module, 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.transform",
schedule_file.name,
payload_file.name,
]
print("NOTE: output of applying schedule to payload from commandline:", *cmdline)
subprocess.run(cmdline)
print(
f"NOTE: cleaning-up temp files: {payload_file.name}, {schedule_file.name}"
)
1 change: 1 addition & 0 deletions python/lighthouse/transform/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from .main import apply
24 changes: 24 additions & 0 deletions python/lighthouse/transform/__main__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import argparse
import sys

from mlir import ir

from .. import transform as lh_transform


if __name__ == "__main__":
ArgParser = argparse.ArgumentParser()
ArgParser.add_argument("schedule")
ArgParser.add_argument("payload")
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]
lh_transform.apply(schedule, payload_module)

print(payload_module)
14 changes: 14 additions & 0 deletions python/lighthouse/transform/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
from mlir import ir
from mlir.dialects.transform import interpreter as transform_interpreter


def apply(schedule: ir.Operation | ir.OpView, payload: ir.Module) -> None:
assert schedule.parent and "transform.with_named_sequence" in schedule.parent.attributes
assert "transform.with_named_sequence" in schedule.parent.attributes
assert isinstance(schedule.parent.attributes["transform.with_named_sequence"], ir.UnitAttr)

transform_interpreter.apply_named_sequence(
payload_root=payload,
transform_root=schedule,
transform_module=schedule.parent,
)