Skip to content

Commit 76130c6

Browse files
[MLIR][Dataflow] Add Allo operations for SPMW and setup support (#555)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
1 parent a7508bd commit 76130c6

19 files changed

Lines changed: 653 additions & 85 deletions

File tree

allo/customize.py

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
import sys
66
import re
77
import inspect
8-
import textwrap
98
import traceback
109
import copy
1110
from dataclasses import dataclass
@@ -1350,15 +1349,8 @@ def customize(
13501349
Defaults to `"default"`.
13511350
"""
13521351
# Get Python AST
1353-
if isinstance(fn, str):
1354-
src, starting_line_no = fn, 1
1355-
file_name = None
1356-
else:
1357-
src, starting_line_no = inspect.getsourcelines(fn)
1358-
src = [textwrap.fill(line, tabsize=4, width=9999) for line in src]
1359-
src = textwrap.dedent("\n".join(src))
1360-
file_name = inspect.getfile(fn)
1361-
tree = parse_ast(src, starting_line_no=starting_line_no, verbose=verbose)
1352+
file_name = None if isinstance(fn, str) else inspect.getfile(fn)
1353+
tree = parse_ast(fn, verbose=verbose)
13621354
if instantiate is None:
13631355
instantiate = []
13641356
if global_vars is None:

allo/ir/infer.py

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,6 @@
55
import ast
66
import copy
77
import os
8-
import inspect
9-
import textwrap
108
import warnings
119
import sympy
1210
import numpy as np
@@ -29,7 +27,7 @@
2927
float64,
3028
Struct,
3129
Stream,
32-
stateful,
30+
Stateful,
3331
ConstExpr,
3432
)
3533
from .typing_rule import get_typing_rule
@@ -144,7 +142,7 @@ def visit_type_hint(ctx: ASTContext, node: ast.AST):
144142
spec = ASTResolver.resolve(node.right, ctx.global_vars)
145143
if isinstance(spec, list):
146144
spec = Layout(spec)
147-
if spec is stateful:
145+
if spec is Stateful:
148146
# Create a copy with stateful=True
149147
stateful_dtype = copy.deepcopy(dtype)
150148
stateful_dtype.stateful = True
@@ -1249,12 +1247,7 @@ class ExternalModule:
12491247
else:
12501248
# Visit arguments in the top-level
12511249
visit_stmts(ctx, node.args)
1252-
src, starting_line_no = inspect.getsourcelines(func)
1253-
src = [textwrap.fill(line, tabsize=4, width=9999) for line in src]
1254-
src = textwrap.dedent("\n".join(src))
1255-
tree = parse_ast(
1256-
src, starting_line_no=starting_line_no, verbose=ctx.verbose
1257-
)
1250+
tree = parse_ast(func, verbose=ctx.verbose)
12581251
# Create a new context to avoid name collision
12591252
func_ctx = ctx.copy()
12601253
stmts = visit_stmts(func_ctx, tree.body)

allo/ir/types.py

Lines changed: 5 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ def __init__(self, dtype, shape):
3535
self.shape = shape
3636

3737
def __matmul__(self, other):
38-
"""Support the @ operator for memory/layout annotations."""
38+
"""Support the @ operator for memory/layout/stateful annotations."""
3939
# Return self to allow chaining, the actual spec is extracted from AST
4040
return self
4141

@@ -91,27 +91,14 @@ def __hash__(self):
9191
return hash((self.name, self.stateful))
9292

9393

94-
def stateful(dtype: AlloType):
94+
class Stateful:
9595
"""
96-
Marks a type as stateful, making it persistent across kernel invocations.
96+
Refinement type, marks a type as stateful, making it persistent across kernel invocations.
9797
9898
Usage:
99-
state: stateful(int32) # Stateful scalar
100-
counter: stateful(int32[10]) # Stateful array
101-
102-
Parameters
103-
----------
104-
dtype : AlloType
105-
The underlying type to mark as stateful
106-
107-
Returns
108-
-------
109-
tuple
110-
A marker tuple that will be processed during type inference
99+
acc: Int(4) @ Stateful = 0 # Stateful scalar
100+
window: float32[4] @ Stateful # Stateful array
111101
"""
112-
# TODO: Return type should be AlloType with stateful attribute set,
113-
# but currently returns tuple for AST
114-
return ("stateful", dtype)
115102

116103

117104
class Index(AlloType):

allo/ir/utils.py

Lines changed: 56 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
import ast
66
import inspect
7+
import textwrap
78
from collections.abc import Callable
89
from types import FunctionType as PyFunctionType
910
from .._mlir.ir import (
@@ -27,19 +28,43 @@
2728
from .symbol_resolver import ASTResolver
2829

2930

30-
def _get_global_vars(_func):
31+
def _get_global_vars(_func, skip: set[str] = None, stop: set[str] = None):
32+
"""
33+
Collect global variables from the call stack of a Python function.
34+
35+
Args:
36+
skip: Set of frame names to skip over when walking the call stack.
37+
Frames whose co_name is in `skip` are ignored (no variables collected), and the walk continues to the next outer frame.
38+
This is mainly used to skip compiler internal functions when collecting global variables used in source code.
39+
stop: Set of frame names that act as boundaries for the stack walk.
40+
When a frame whose co_name is in `stop` is reached, its variables are collected and then the walk terminates.
41+
"""
42+
if skip is None:
43+
skip = {"get_global_vars", "customize", "build"}
44+
if stop is None:
45+
stop = {"<module>"}
3146
if isinstance(_func, Callable):
3247
# Discussions: https://github.com/taichi-dev/taichi/issues/282
3348
global_vars = _func.__globals__.copy()
3449
else:
3550
global_vars = {}
3651

37-
# Get back to the outer-most scope (user-defined function)
52+
# Get back to outer scopes
3853
# Mainly used to get the annotation definitions (shape and type),
3954
# which are probably not defined in __globals__
40-
for name, var in inspect.stack()[3][0].f_locals.items():
41-
if isinstance(var, (int, float, AlloType)) or inspect.isfunction(var):
42-
global_vars[name] = var
55+
frame = inspect.currentframe().f_back
56+
while frame:
57+
if frame.f_code.co_name in skip:
58+
frame = frame.f_back
59+
continue
60+
# collect allowed types
61+
for name, var in frame.f_locals.items():
62+
if isinstance(var, (int, float, AlloType)) or inspect.isfunction(var):
63+
global_vars[name] = var
64+
# boundary
65+
if frame.f_code.co_name in stop:
66+
break
67+
frame = frame.f_back
4368

4469
if isinstance(_func, Callable):
4570
freevar_names = _func.__code__.co_freevars
@@ -52,13 +77,25 @@ def _get_global_vars(_func):
5277

5378

5479
def get_global_vars(func):
55-
global_vars = _get_global_vars(func)
56-
new_global_vars = global_vars.copy()
57-
for var in global_vars.values():
58-
# import functions from other files
59-
if isinstance(var, PyFunctionType):
60-
new_global_vars.update(_get_global_vars(var))
61-
return new_global_vars
80+
all_globals = {}
81+
worklist = [func]
82+
visited_funcs = set()
83+
84+
while worklist:
85+
f = worklist.pop()
86+
if f in visited_funcs:
87+
continue
88+
visited_funcs.add(f)
89+
90+
gv = _get_global_vars(f)
91+
for name, val in gv.items():
92+
if name not in all_globals:
93+
all_globals[name] = val
94+
# import functions from other files
95+
if isinstance(val, PyFunctionType):
96+
worklist.append(val)
97+
98+
return all_globals
6299

63100

64101
def get_extra_type_hints(dtype: AlloType):
@@ -104,7 +141,13 @@ def _adjust_line_numbers(node, offset):
104141
child.end_lineno += offset
105142

106143

107-
def parse_ast(src, starting_line_no=1, verbose=False):
144+
def parse_ast(src, verbose=False):
145+
if isinstance(src, str):
146+
starting_line_no = 1
147+
else:
148+
src, starting_line_no = inspect.getsourcelines(src)
149+
src = [textwrap.fill(line, tabsize=4, width=9999) for line in src]
150+
src = textwrap.dedent("\n".join(src))
108151
tree = ast.parse(src)
109152
_adjust_line_numbers(tree, starting_line_no - 1)
110153
if verbose:

allo/ir/visitor.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import ast
66
from .._mlir import InsertionPoint
77
from .._mlir.dialects import allo as allo_d
8+
from ..utils import register_dialect
89

910

1011
class BlockScopeGuard:
@@ -69,7 +70,7 @@ def __init__(
6970
self.global_vars = global_vars
7071
self.mlir_ctx = mlir_ctx
7172
self.file_name = None
72-
allo_d.register_dialect(mlir_ctx)
73+
register_dialect(mlir_ctx)
7374
# map from function name to function arguments
7475
self.func_args = {} if func_args is None else func_args
7576
self.func_id = None
@@ -413,8 +414,6 @@ def __init__(self, symbolic_mapping, var_map, variables):
413414
self.special_symbol = set()
414415

415416
def visit_Name(self, node):
416-
if node.id in self.variables:
417-
raise ValueError("Fail to resolve the expression as symbolic expression.")
418417
if node.id in self.symbolic_mapping:
419418
symbol_var = self.symbolic_mapping[node.id]
420419
if isinstance(symbol_var, str):
@@ -428,6 +427,8 @@ def visit_Name(self, node):
428427
return new_node
429428
if node.id in self.var_map:
430429
return ast.Constant(self.var_map[node.id])
430+
if node.id in self.variables:
431+
raise ValueError("Fail to resolve the expression as symbolic expression.")
431432
return node
432433

433434

allo/utils.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import numpy.typing as npt
99
import ml_dtypes
1010
from ._mlir.ir import (
11+
Context,
1112
MemRefType,
1213
RankedTensorType,
1314
IntegerType,
@@ -580,3 +581,7 @@ def allo_to_numpy_dtype(allo_type: AlloType) -> npt.DTypeLike:
580581
dtype = np.int64 if isinstance(allo_type, Fixed) else np.uint64
581582

582583
return dtype
584+
585+
586+
def register_dialect(ctx: Context):
587+
allo_d.register_dialect(ctx)

mlir/include/allo/Conversion/Passes.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,15 @@ std::unique_ptr<OperationPass<ModuleOp>> createFixedPointToIntegerPass();
2121
std::unique_ptr<OperationPass<ModuleOp>> createLowerCompositeTypePass();
2222
std::unique_ptr<OperationPass<ModuleOp>> createLowerBitOpsPass();
2323
std::unique_ptr<OperationPass<ModuleOp>> createLowerTransformLayoutOpsPass();
24+
std::unique_ptr<OperationPass<ModuleOp>> createLowerMemCopyOpsPass();
2425
std::unique_ptr<OperationPass<ModuleOp>> createLowerPrintOpsPass();
2526

2627
bool applyAlloToLLVMLoweringPass(ModuleOp &module, MLIRContext &context);
2728
bool applyFixedPointToInteger(ModuleOp &module);
2829
bool applyLowerCompositeType(ModuleOp &module);
2930
bool applyLowerBitOps(ModuleOp &module);
3031
bool applyLowerTransformLayoutOps(ModuleOp &module);
32+
bool applyLowerMemCopyOps(ModuleOp &module);
3133
bool applyLowerPrintOps(ModuleOp &module);
3234

3335
/// Registers all Allo conversion passes

mlir/include/allo/Conversion/Passes.td

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,11 @@ def LowerTransformLayoutOps : Pass<"lower-transform-layout-ops", "ModuleOp"> {
3333
let constructor = "mlir::allo::createLowerTransformLayoutOpsPass()";
3434
}
3535

36+
def LowerMemCopyOps : Pass<"lower-memcopy-ops", "ModuleOp"> {
37+
let summary = "Lower memcopy operations";
38+
let constructor = "mlir::allo::createLowerMemCopyOpsPass()";
39+
}
40+
3641
def LowerPrintOps : Pass<"lower-print-ops", "ModuleOp"> {
3742
let summary = "Lower print operations";
3843
let constructor = "mlir::allo::createLowerPrintOpsPass()";

mlir/include/allo/Dialect/AlloOps.td

Lines changed: 82 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -935,7 +935,7 @@ def OrOp : Allo_Op<"or"> {
935935
//===----------------------------------------------------------------------===//
936936

937937
def YieldOp : Allo_Op<"yield", [NoMemoryEffect, Terminator,
938-
ParentOneOf<["AndOp, OrOp"]>]> {
938+
ParentOneOf<["AndOp", "OrOp", "GridMapOp"]>]> {
939939
let summary = "yield and termination operation";
940940
let description = [{
941941
"allo.yield" yields an SSA value from the Allo dialect op region and
@@ -1048,6 +1048,56 @@ def StreamPutOp : Allo_Op<"stream_put"> {
10481048
}];
10491049
}
10501050

1051+
def StreamGlobalOp : Allo_Op<"stream_global", [Symbol]> {
1052+
let summary = "Create a global stream object";
1053+
let arguments = (ins SymbolNameAttr:$sym_name, TypeAttr:$element_type, DenseI64ArrayAttr:$shape);
1054+
let assemblyFormat = [{
1055+
$sym_name `:` $element_type $shape attr-dict
1056+
}];
1057+
let hasVerifier = 1;
1058+
}
1059+
1060+
def GlobalStreamGetOp : Allo_Op<"get_stream_global"> {
1061+
let summary = "Get an object from a global stream";
1062+
// arguments:
1063+
// - $global: symbol name of the global stream
1064+
// - $indices: variadic index operands
1065+
// - $map: affine map
1066+
let arguments = (
1067+
ins FlatSymbolRefAttr:$global,
1068+
Variadic<Index>:$indices,
1069+
AffineMapAttr:$map
1070+
);
1071+
let results = (outs AnyType:$result);
1072+
let hasVerifier = 1;
1073+
let hasCustomAssemblyFormat = 1;
1074+
let extraClassDeclaration = [{
1075+
void simplifyAffineMap();
1076+
}];
1077+
}
1078+
1079+
def GlobalStreamPutOp : Allo_Op<"put_stream_global"> {
1080+
let summary = "Put an object to a global stream";
1081+
// arguments:
1082+
// - $global: symbol name of the global stream
1083+
// - $indices: variadic index operands
1084+
// - $data: value to put
1085+
// - $map: affine map to compute indices
1086+
let arguments = (
1087+
ins FlatSymbolRefAttr:$global,
1088+
Variadic<Index>:$indices,
1089+
AnyType:$data,
1090+
AffineMapAttr:$map
1091+
);
1092+
let results = (outs);
1093+
let hasVerifier = 1;
1094+
let hasCustomAssemblyFormat = 1;
1095+
let extraClassDeclaration = [{
1096+
void simplifyAffineMap();
1097+
}];
1098+
}
1099+
1100+
10511101
//===----------------------------------------------------------------------===//
10521102
// Layout operations
10531103
//===----------------------------------------------------------------------===//
@@ -1096,4 +1146,35 @@ def TransformLayoutOp : Allo_Op<"transform_layout"> {
10961146
}];
10971147
}
10981148

1149+
//===----------------------------------------------------------------------===//
1150+
// SPMW operations
1151+
//===----------------------------------------------------------------------===//
1152+
def GridMapOp : Allo_Op<"grid_map",
1153+
[RecursiveMemoryEffects, SingleBlockImplicitTerminator<"YieldOp">]> {
1154+
let summary = "Grid map operation";
1155+
let description = [{
1156+
The grid_map operation distributes a computation over a logical grid.
1157+
It takes a list of memrefs with static shape (`tensors`) as input arguments.
1158+
It contains a single block whose arguments are the sharded memrefs. The operations in the block can access variables in parent regions.
1159+
}];
1160+
1161+
let arguments = (ins
1162+
Variadic<AnyStaticShapeMemRef>:$tensors,
1163+
ArrayAttr:$sharding,
1164+
DenseI64ArrayAttr:$grid
1165+
);
1166+
let regions = (region SizedRegion<1>:$body);
1167+
1168+
let assemblyFormat = [{
1169+
`(` $tensors `)`
1170+
`sharding` `=` $sharding
1171+
`grid` `=` $grid
1172+
$body
1173+
attr-dict
1174+
`:` type($tensors)
1175+
}];
1176+
1177+
let hasVerifier = 1;
1178+
}
1179+
10991180
#endif // ALLO_OPS

0 commit comments

Comments
 (0)