Skip to content

Commit d0e1f84

Browse files
[Python-extras] Move over examples (#245)
Might need some clean-up to get it to play nice with CI. --------- Co-authored-by: Maksim Levental <[email protected]>
1 parent f0914c3 commit d0e1f84

14 files changed

+5542
-9
lines changed

.github/workflows/build_llvm.yml

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,11 @@ on:
1818
description: 'Run the build with a tmate session ONLY in case of failure'
1919
required: false
2020
default: false
21+
release:
22+
description: 'whether to release'
23+
type: boolean
24+
required: false
25+
default: true
2126
pull_request:
2227
paths:
2328
- ".github/actions/setup_base"
@@ -210,7 +215,7 @@ jobs:
210215
path: ${{ startsWith(matrix.os, 'windows') && 'D:\a\ccache.log' || '/tmp/ccache.log' }}
211216

212217
- name: Release current commit
213-
if: (!cancelled() && ((github.event_name == 'push' && github.ref_name == 'main') || github.event_name == 'workflow_dispatch'))
218+
if: (!cancelled() && ((github.event_name == 'push' && github.ref_name == 'main') || (github.event_name == 'workflow_dispatch' && inputs.release)))
214219
uses: ncipollo/[email protected]
215220
with:
216221
artifacts: "*.tar.gz,wheelhouse/*.whl"
@@ -252,6 +257,7 @@ jobs:
252257
wheel_version: ${{ needs.build.outputs.WHEEL_VERSION }}
253258
workflow_call: true
254259
workflow_caller_run_id: ${{ github.run_id }}
260+
release: ${{ inputs.release }}
255261

256262
call-build-eudsl:
257263

@@ -268,10 +274,11 @@ jobs:
268274
with:
269275
workflow_call: true
270276
workflow_caller_run_id: ${{ github.run_id }}
277+
release: ${{ inputs.release }}
271278

272279
call-deploy-pip-page:
273280

274-
if: (github.event_name == 'push' && github.ref_name == 'main') || github.event_name == 'workflow_dispatch'
281+
if: (github.event_name == 'push' && github.ref_name == 'main') || (github.event_name == 'workflow_dispatch' && inputs.release)
275282

276283
needs: [build]
277284

.github/workflows/build_mlir_python_bindings_wheel.yml

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,11 @@ on:
1818
description: 'Run the build with a tmate session ONLY in case of failure'
1919
required: false
2020
default: false
21+
release:
22+
description: 'whether to release'
23+
type: boolean
24+
required: false
25+
default: true
2126
workflow_call:
2227
inputs:
2328
wheel_version:
@@ -35,6 +40,11 @@ on:
3540
type: string
3641
required: false
3742
default: ''
43+
release:
44+
description: 'whether to release'
45+
type: boolean
46+
required: false
47+
default: true
3848
pull_request:
3949
branches:
4050
- main
@@ -289,7 +299,7 @@ jobs:
289299
290300
release-mlir-python-bindings:
291301

292-
if: (github.event_name == 'push' && github.ref_name == 'main') || github.event_name == 'workflow_dispatch'
302+
if: (github.event_name == 'push' && github.ref_name == 'main') || (github.event_name == 'workflow_dispatch' && inputs.release)
293303

294304
needs: [build-mlir-python-bindings]
295305

@@ -426,7 +436,7 @@ jobs:
426436
name: build_artifact_python_bindings-ubuntu-wasm
427437

428438
- name: Release current commit
429-
if: (github.event_name == 'push' && github.ref_name == 'main') || github.event_name == 'workflow_dispatch'
439+
if: (github.event_name == 'push' && github.ref_name == 'main') || (github.event_name == 'workflow_dispatch' && inputs.release)
430440
uses: ncipollo/[email protected]
431441
with:
432442
artifacts: "wheelhouse/mlir_python_bindings*.whl"
@@ -454,11 +464,12 @@ jobs:
454464
with:
455465
workflow_call: true
456466
workflow_caller_run_id: ${{ github.run_id }}
467+
release: ${{ inputs.release }}
457468

458469

459470
call-deploy-pip-page:
460471

461-
if: (github.event_name == 'push' && github.ref_name == 'main') || github.event_name == 'workflow_dispatch'
472+
if: (github.event_name == 'push' && github.ref_name == 'main') || (github.event_name == 'workflow_dispatch' && inputs.release)
462473

463474
needs: [release-mlir-python-bindings]
464475

.github/workflows/build_test_release_eudsl.yml

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,11 @@ on:
1818
description: 'Run the build with a tmate session ONLY in case of failure'
1919
required: false
2020
default: false
21+
release:
22+
description: 'whether to release'
23+
type: boolean
24+
required: false
25+
default: true
2126
workflow_call:
2227
inputs:
2328
workflow_call:
@@ -30,6 +35,11 @@ on:
3035
type: string
3136
required: false
3237
default: ''
38+
release:
39+
description: 'whether to release'
40+
type: boolean
41+
required: false
42+
default: true
3343
pull_request:
3444
branches:
3545
- main
@@ -430,8 +440,10 @@ jobs:
430440
431441
release-eudsl:
432442

433-
if: (github.event_name == 'push' && github.ref_name == 'main') || github.event_name == 'workflow_dispatch'
443+
if: (github.event_name == 'push' && github.ref_name == 'main') || (github.event_name == 'workflow_dispatch' && inputs.release)
444+
434445
needs: [build-eudsl]
446+
435447
runs-on: "ubuntu-22.04"
436448

437449
permissions:
@@ -473,7 +485,7 @@ jobs:
473485

474486
call-deploy-pip-page:
475487

476-
if: (github.event_name == 'push' && github.ref_name == 'main') || github.event_name == 'workflow_dispatch'
488+
if: (github.event_name == 'push' && github.ref_name == 'main') || (github.event_name == 'workflow_dispatch' && inputs.release)
477489

478490
needs: [release-eudsl]
479491

.github/workflows/build_test_release_eudsl_python_extras.yml

Lines changed: 48 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,12 @@ name: "Build, test, release eudsl-python-extras"
77

88
on:
99
workflow_dispatch:
10+
inputs:
11+
release:
12+
description: 'whether to release'
13+
type: boolean
14+
required: false
15+
default: true
1016
workflow_call:
1117
inputs:
1218
workflow_call:
@@ -19,6 +25,11 @@ on:
1925
type: string
2026
required: false
2127
default: ''
28+
release:
29+
description: 'whether to release'
30+
type: boolean
31+
required: false
32+
default: true
2233
pull_request:
2334
branches:
2435
- main
@@ -186,9 +197,44 @@ jobs:
186197
187198
python -m pytest projects/eudsl-python-extras/tests $IGNORE
188199
200+
- name: "Test examples"
201+
run: |
202+
203+
python projects/eudsl-python-extras/examples/flash_attention.py
204+
python projects/eudsl-python-extras/examples/mwe.py
205+
python projects/eudsl-python-extras/examples/rdna_matmul_opt.py
206+
207+
if [[ $(python -c "print(__import__('sys').version_info >= (3, 13))") == "True" ]]; then
208+
python projects/eudsl-python-extras/examples/cuda_matmul_opt.py
209+
fi
210+
211+
- name: Test jupyter notebooks
212+
# sed: can't read C:\hostedtoolcache\windows\Python\3.12.10\x64/jupyter_client/runapp.py: No such file or directory
213+
if: matrix.os != 'windows'
214+
shell: bash
215+
env:
216+
BRANCH: ${{ github.head_ref || github.ref_name }}
217+
run: |
218+
219+
pip install -q jupyter
220+
221+
sed -i.bak 's/OUTPUT_TIMEOUT = 10/OUTPUT_TIMEOUT = 1000/g' \
222+
$(python -c 'import site; print(site.getsitepackages()[0])')/jupyter_client/runapp.py
223+
224+
jupyter execute projects/eudsl-python-extras/examples/mlir_python_extras.ipynb --output=mlir_python_extras_output
225+
cat projects/eudsl-python-extras/examples/mlir_python_extras_output.ipynb | jq '.cells[].outputs | select(length > 0) | .[0] | .text'
226+
jupyter execute projects/eudsl-python-extras/examples/vectorization_e2e.ipynb --output=vectorization_e2e_output
227+
cat projects/eudsl-python-extras/examples/vectorization_e2e_output.ipynb | jq '.cells[].outputs | select(length > 0) | .[0] | .text'
228+
229+
# TODO(max): build wheels with nv targets
230+
# if [ ${{ matrix.os }} == 'ubuntu' ]; then
231+
# jupyter execute projects/eudsl-python-extras/examples/cuda_e2e.ipynb --output=cuda_e2e_output
232+
# cat projects/eudsl-python-extras/examples/cuda_e2e_output.ipynb | jq '.cells[].outputs | select(length > 0) | .[0] | .text'
233+
# fi
234+
189235
release-eudsl-python-extras:
190236

191-
if: (github.event_name == 'push' && github.ref_name == 'main') || github.event_name == 'workflow_dispatch'
237+
if: (github.event_name == 'push' && github.ref_name == 'main') || (github.event_name == 'workflow_dispatch' && inputs.release)
192238

193239
needs: [build-eudsl-python-extras]
194240

@@ -222,7 +268,7 @@ jobs:
222268

223269
call-deploy-pip-page:
224270

225-
if: (github.event_name == 'push' && github.ref_name == 'main') || github.event_name == 'workflow_dispatch'
271+
if: (github.event_name == 'push' && github.ref_name == 'main') || (github.event_name == 'workflow_dispatch' && inputs.release)
226272

227273
needs: [release-eudsl-python-extras]
228274

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
# eudsl-python-extras
2+
3+
The missing pieces (as far as boilerplate reduction goes) of the MLIR python bindings.
4+
5+
* [TL;DR](#tl-dr)
6+
* [5s Intro](#5s-intro)
7+
* [Install](#install)
8+
* [Examples/Demo](#examples-demo)
9+
10+
## TL;DR
11+
12+
Full example at [examples/mwe.py](examples/mwe.py) (i.e., go there if you want to copy-paste).
13+
14+
Turn this
15+
16+
```python
17+
K = 10
18+
memref_i64 = T.memref(K, K, T.i64)
19+
20+
@func
21+
@canonicalize(using=scf)
22+
def memfoo(A: memref_i64, B: memref_i64, C: memref_i64):
23+
one = constant(1)
24+
two = constant(2)
25+
if one > two:
26+
three = constant(3)
27+
else:
28+
for i in range(0, K):
29+
for j in range(0, K):
30+
C[i, j] = A[i, j] * B[i, j]
31+
```
32+
33+
into this
34+
35+
```mlir
36+
func.func @memfoo(%arg0: memref<10x10xi64>, %arg1: memref<10x10xi64>, %arg2: memref<10x10xi64>) {
37+
%c1_i32 = arith.constant 1 : i32
38+
%c2_i32 = arith.constant 2 : i32
39+
%0 = arith.cmpi ugt, %c1_i32, %c2_i32 : i32
40+
scf.if %0 {
41+
%c3_i32 = arith.constant 3 : i32
42+
} else {
43+
%c0 = arith.constant 0 : index
44+
%c10 = arith.constant 10 : index
45+
%c1 = arith.constant 1 : index
46+
scf.for %arg3 = %c0 to %c10 step %c1 {
47+
scf.for %arg4 = %c0 to %c10 step %c1 {
48+
%1 = memref.load %arg0[%arg3, %arg4] : memref<10x10xi64>
49+
%2 = memref.load %arg1[%arg3, %arg4] : memref<10x10xi64>
50+
%3 = arith.muli %1, %2 : i64
51+
memref.store %3, %arg2[%arg3, %arg4] : memref<10x10xi64>
52+
}
53+
}
54+
}
55+
return
56+
}
57+
```
58+
59+
then run it like this
60+
61+
```python
62+
module = backend.compile(
63+
ctx.module,
64+
kernel_name=memfoo.__name__,
65+
pipeline=Pipeline().bufferize().lower_to_llvm(),
66+
)
67+
68+
A = np.random.randint(0, 10, (K, K))
69+
B = np.random.randint(0, 10, (K, K))
70+
C = np.zeros((K, K), dtype=int)
71+
72+
backend.load(module).memfoo(A, B, C)
73+
assert np.array_equal(A * B, C)
74+
```
75+
76+
## 5s Intro
77+
78+
This is **not a Python compiler**, but just a (hopefully) nice way to emit MLIR using python.
79+
80+
The few main features/affordances:
81+
82+
1. `region_op`s (like `@func` above)
83+
\
84+
&nbsp;
85+
1. These are decorators around ops (bindings for MLIR operations) that have regions (e.g., [in_parallel](https://github.com/llvm/eudsl/blob/fa4807b17a21a4808cc0a4a8a32e2da57f7e3100/projects/eudsl-python-extras/mlir/extras/dialects/scf.py#L134)).
86+
They turn decorated functions, by executing them "eagerly", into an instance of such an op, e.g.,
87+
```python
88+
@func
89+
def foo(x: T.i32):
90+
return
91+
```
92+
becomes `func.func @foo(%arg0: i32) { }`; if the region carrying op produces a result, the identifier for the python function (`foo`) becomes the corresponding `ir.Value` of the result (if the op doesn't produce a result then the identifier becomes the corresponding `ir.OpView`).
93+
\
94+
\
95+
This has been upstreamed to [mlir/python/mlir/extras/meta.py](https://github.com/llvm/llvm-project/blob/24038650d9ca5d66b07d3075afdebe81012ab1f2/mlir/python/mlir/extras/meta.py#L12)
96+
\
97+
&nbsp;
98+
2. `@canonicalize` (like `@canonicalize(using=scf)` above)
99+
\
100+
&nbsp;
101+
1. These are decorators that **rewrite the python AST**. They transform a select few forms (basically only `if`s) into a more "canonical" form, in order to more easily map to MLIR. If that scares you, fear not; they are not essential and all target MLIR can still be mapped to without using them (by using the slightly more verbose `region_op`).
102+
\
103+
\
104+
See [mlir.extras.ast.canonicalize](https://github.com/llvm/eudsl/blob/f0914c3b3c0e3ca774575aa6a0fba73e1ebb631f/projects/eudsl-python-extras/mlir/extras/ast/canonicalize.py) for details.
105+
\
106+
&nbsp;
107+
3. `mlir/extras.types` (like `T.memref(K, K, T.i64)` above)
108+
\
109+
&nbsp;
110+
1. These are just convenient wrappers around upstream type constructors. Note, because MLIR types are uniqued to a `ir.Context`, these are all actually functions that return the type.
111+
\
112+
\
113+
These have been upstreamed to [mlir/python/mlir/extras/types.py](https://github.com/llvm/llvm-project/blob/52b18b4e82d412a7d755e89591c6ebcc41c257a1/mlir/python/mlir/extras/types.py)
114+
\
115+
&nbsp;
116+
4. `Pipeline()`
117+
\
118+
&nbsp;
119+
1. This is just a (generated) wrapper around available **upstream** passes; it can be used to build pass pipelines (by `str(Pipeline())`). It is mainly convenient with IDEs/editors that will tab-complete the available methods on the `Pipeline` class (which correspond to passes), Note, if your host bindings don't register some upstream passes, then this will generate "illegal" pass pipelines.
120+
\
121+
\
122+
See [utils/generate_pass_pipeline.py](https://github.com/llvm/eudsl/blob/f0914c3b3c0e3ca774575aa6a0fba73e1ebb631f/projects/eudsl-python-extras/utils/generate_pass_pipeline.py) for details on generation
123+
[mlir.extras.runtime.passes](https://github.com/llvm/eudsl/blob/4f599951786aedad96e5943993763dc9c5bfb8cd/projects/eudsl-python-extras/mlir/extras/runtime/passes.py) for the passes themselves.
124+
\
125+
&nbsp;
126+
127+
128+
129+
Note, also, there are no docs (because ain't no one got time for that) but that shouldn't be a problem because the package is designed such that you can use/reuse only the pieces/parts you want/understand.
130+
But, open an issue if something isn't clear.
131+
132+
133+
## Install
134+
135+
If you want to just get started/play around:
136+
137+
```shell
138+
$ pip install eudsl-python-extras -f https://llvm.github.io/eudsl
139+
```
140+
141+
Alternatively, this [colab notebook](https://drive.google.com/file/d/1NAtf2Yxj_VVnzwn8u_kxtajfVzgbuWhi/view?usp=sharing) (which is the same as [examples/mlir_python_extras.ipynb](examples/mlir_python_extras.ipynb)) has a MWE if you don't want to install anything even.
142+
143+
In reality, this package is meant to work in concert with "host bindings" (some distribution of the actual MLIR Python bindings).
144+
Practically speaking that means you need to have *some* package installed that includes mlir python bindings.
145+
146+
So that means the second line should be amended to
147+
148+
```shell
149+
$ EUDSL_PYTHON_EXTRAS_HOST_PACKAGE_PREFIX=<YOUR_HOST_MLIR_PYTHON_PACKAGE_PREFIX> \
150+
pip install eudsl-python-extras -f https://llvm.github.io/eudsl
151+
```
152+
153+
where `YOUR_HOST_MLIR_PYTHON_PACKAGE_PREFIX` is (as it says) the package prefix for your chosen host bindings.
154+
**When in doubt about this prefix**, it is everything up until `ir` when you import your bindings, e.g., in `import torch_mlir.ir`, `torch_mlir` is the `HOST_MLIR_PYTHON_PACKAGE_PREFIX` for the torch-mlir bindings.
155+
156+
## Examples/Demo
157+
158+
Check [examples](examples) and [tests](tests) for a plethora of example code.

0 commit comments

Comments
 (0)