Skip to content

stages

AliasedForm

Bases: Form

AliasedForm requires that all aliases in the input are defined in the bindings or in previous queries and that all Tables are wrapping Aliases.

Source code in src/finch/autoschedule/stages.py
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
class AliasedForm(Form):
    """
    AliasedForm requires that all aliases in the input are defined
    in the bindings or in previous queries and that all Tables
    are wrapping Aliases.
    """

    @classmethod
    def validate_inputs(
        cls,
        term: Plan,
        bindings: dict[Alias, TensorFType],
        stats: dict[Alias, TensorStats],
        stats_factory: StatsFactory,
    ) -> None:
        defined_aliases = set(bindings.keys())

        def validate(node):
            match node:
                case Query(Table(Alias() as lhs, _), _):
                    defined_aliases.add(lhs)
                case QueryInto(Table(Alias() as lhs, _), _, _):
                    if lhs not in defined_aliases:
                        raise ValueError(
                            f"QueryInto updates alias {lhs.name}, which is not defined."
                        )
                case Query(lhs, _) | QueryInto(lhs, _, _):
                    raise ValueError(f"Query must write to a Table of an Alias: {lhs}")
                case Alias(name):
                    if node not in defined_aliases:
                        raise ValueError(f"Alias {name} is not defined in bindings.")
                case Table(tns, _):
                    if not isinstance(tns, Alias):
                        raise ValueError("Table nodes must wrap an Alias.")
            return node

        Rewrite(PreWalk(validate))(term)

CompilerForm

Bases: FormattedForm

CompilerForm is the input of the notation lowerer. It is a FormattedForm where every statement but the final Produces is a QueryInto, and initialization is explicit. There are two valid kinds of statement: 1) initializations QueryInto(Table(lhs, _), overwrite, Literal(init)) (Every element of lhs is set to init.) 2) folds QueryInto(Table(lhs, lhs_idxs), op, Reorder(arg, loop_order)) (Here, arg is made of Tables, Literals, and MapJoins. The loop order contains each field once and visits all fields of lhs_idxs in order. Tables in a MapJoin must also follow loop order. A single Table argument may have a different storage order, representing a transpose; notation lowering inserts equality-constrained loops to read it in storage order. Fields absent from lhs_idxs are reduced with op.) A transpose or fold which overwrites lhs also initializes it, starting from the init of a preceding initialization, or else the fill value of lhs. Every alias must have a TensorFType in the bindings, and a statement can't read the alias it writes.

Source code in src/finch/autoschedule/stages.py
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
class CompilerForm(FormattedForm):
    """
    CompilerForm is the input of the notation lowerer. It is a FormattedForm
    where every statement but the final Produces is a QueryInto, and
    initialization is explicit. There are two valid kinds of statement:
    1) initializations
        QueryInto(Table(lhs, _), overwrite, Literal(init))
    (Every element of lhs is set to init.)
    2) folds
        QueryInto(Table(lhs, lhs_idxs), op, Reorder(arg, loop_order))
    (Here, arg is made of Tables, Literals, and MapJoins. The loop order
    contains each field once and visits all fields of lhs_idxs in order.
    Tables in a MapJoin must also follow loop order.
    A single Table argument may have a different storage order, representing a
    transpose; notation lowering inserts equality-constrained loops to read it
    in storage order. Fields absent from lhs_idxs are reduced with op.)
    A transpose or fold which overwrites lhs also initializes it, starting from
    the init of a preceding initialization, or else the fill value of lhs.
    Every alias must have a TensorFType in the bindings, and a statement can't
    read the alias it writes.
    """

    @classmethod
    def validate_inputs(
        cls,
        term: Plan,
        bindings: dict[Alias, TensorFType],
        stats: dict[Alias, TensorStats],
        stats_factory: StatsFactory,
    ) -> None:
        super().validate_inputs(term, bindings, stats, stats_factory)

        # FormattedForm has checked the grammar of each fold, its bindings,
        # and that it visits the lhs and its Tables in loop order.
        match term:
            case Plan((*bodies, Produces())):
                pass
            case _:
                raise ValueError("The last body of a plan must be a Produces node.")
        for body in bodies:
            match body:
                case QueryInto(Table(lhs, _), Literal(op), rhs):
                    if lhs in PostOrderDFS(rhs):
                        raise ValueError(f"QueryInto can't both read and write {lhs}.")
                    match rhs:
                        case Literal():
                            if op != ffuncs.overwrite:
                                raise ValueError(
                                    f"Initializing {lhs} must overwrite it, not {op}."
                                )
                        case Reorder(arg, loop_order):
                            if len(set(loop_order)) != len(loop_order):
                                raise ValueError("Loop order must not repeat fields.")
                            if not set(arg.fields()).issubset(loop_order):
                                raise ValueError(
                                    "Loop order must include every RHS field."
                                )
                case _:
                    raise ValueError(f"CompilerForm only allows QueryInto, not {body}")

FormattedForm

Bases: LoopOrderedForm

FormattedForm requires that the input query has had its tensor formats set. Every alias must have a TensorFType in the bindings. The valid forms of a query are those of LoopOrderedForm.

Source code in src/finch/autoschedule/stages.py
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
class FormattedForm(LoopOrderedForm):
    """
    FormattedForm requires that the input query has had its tensor formats
    set. Every alias must have a TensorFType in the bindings. The valid forms
    of a query are those of LoopOrderedForm.
    """

    @classmethod
    def validate_inputs(
        cls,
        term: Plan,
        bindings: dict[Alias, TensorFType],
        stats: dict[Alias, TensorStats],
        stats_factory: StatsFactory,
    ) -> None:
        super().validate_inputs(term, bindings, stats, stats_factory)

        def validate(node):
            match node:
                case Plan(bodies):
                    for body in bodies[:-1]:
                        validate(body)
                case Query(lhs, rhs) | QueryInto(lhs, _, rhs):
                    validate(lhs)
                    validate(rhs)
                case Aggregate(_, _, arg, _) | Reorder(arg, _):
                    validate(arg)
                case MapJoin(_, args):
                    for arg in args:
                        validate(arg)
                case Table(tns, _):
                    if tns not in bindings:
                        raise ValueError(
                            f"Alias {tns.name} is not defined in bindings. All aliase\
                                 must have TensorFTypes specified at this stage."
                        )
                case Literal():
                    return
                case _:
                    raise ValueError(f"Unsupported query type: {node}")
            return

        validate(term)

LogicEinsumLowerer

Bases: FormattedForm, Stage

Source code in src/finch/autoschedule/stages.py
386
387
388
389
390
391
392
393
394
395
396
397
398
class LogicEinsumLowerer(FormattedForm, Stage):
    @abstractmethod
    def lower(
        self,
        term: LogicStatement,
        bindings: dict[Alias, TensorFType],
        stats: dict[Alias, TensorStats],
        stats_factory: StatsFactory,
    ) -> tuple[ein.EinsumStatement, dict[ein.Alias, TensorFType]]:
        """
        Generate Einsum Notation from the given logic and input types.  Also
        return a dictionary including additional tables needed to run the kernel.
        """

lower(term, bindings, stats, stats_factory) abstractmethod

Generate Einsum Notation from the given logic and input types. Also return a dictionary including additional tables needed to run the kernel.

Source code in src/finch/autoschedule/stages.py
387
388
389
390
391
392
393
394
395
396
397
398
@abstractmethod
def lower(
    self,
    term: LogicStatement,
    bindings: dict[Alias, TensorFType],
    stats: dict[Alias, TensorStats],
    stats_factory: StatsFactory,
) -> tuple[ein.EinsumStatement, dict[ein.Alias, TensorFType]]:
    """
    Generate Einsum Notation from the given logic and input types.  Also
    return a dictionary including additional tables needed to run the kernel.
    """

LogicFactorizer

Bases: AliasedForm, LogicLoader

Source code in src/finch/autoschedule/stages.py
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
class LogicFactorizer(AliasedForm, LogicLoader):
    @abstractmethod
    def lower(
        self,
        term: LogicStatement,
        bindings: dict[Alias, TensorFType],
        stats: dict[Alias, TensorStats],
        stats_factory: StatsFactory,
    ) -> tuple[
        AssemblyLibrary,
        dict[Alias, TensorFType],
        dict[Alias, tuple[Field | None, ...]],
        LogicStatement,
    ]:
        """
        Optimize the aggregate structure of the given logic statement and
        make decisions about materialization.
        """

lower(term, bindings, stats, stats_factory) abstractmethod

Optimize the aggregate structure of the given logic statement and make decisions about materialization.

Source code in src/finch/autoschedule/stages.py
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
@abstractmethod
def lower(
    self,
    term: LogicStatement,
    bindings: dict[Alias, TensorFType],
    stats: dict[Alias, TensorStats],
    stats_factory: StatsFactory,
) -> tuple[
    AssemblyLibrary,
    dict[Alias, TensorFType],
    dict[Alias, tuple[Field | None, ...]],
    LogicStatement,
]:
    """
    Optimize the aggregate structure of the given logic statement and
    make decisions about materialization.
    """

LogicFormatter

Bases: LoopOrderedForm, LogicLoader

Source code in src/finch/autoschedule/stages.py
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
class LogicFormatter(LoopOrderedForm, LogicLoader):
    @abstractmethod
    def lower(
        self,
        term: LogicStatement,
        bindings: dict[Alias, TensorFType],
        stats: dict[Alias, TensorStats],
        stats_factory: StatsFactory,
    ) -> tuple[
        AssemblyLibrary,
        dict[Alias, TensorFType],
        dict[Alias, tuple[Field | None, ...]],
        LogicStatement,
    ]:
        """
        Optimize the tensor formats and output orders for each query.
        """

lower(term, bindings, stats, stats_factory) abstractmethod

Optimize the tensor formats and output orders for each query.

Source code in src/finch/autoschedule/stages.py
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
@abstractmethod
def lower(
    self,
    term: LogicStatement,
    bindings: dict[Alias, TensorFType],
    stats: dict[Alias, TensorStats],
    stats_factory: StatsFactory,
) -> tuple[
    AssemblyLibrary,
    dict[Alias, TensorFType],
    dict[Alias, tuple[Field | None, ...]],
    LogicStatement,
]:
    """
    Optimize the tensor formats and output orders for each query.
    """

LogicLoopOrderer

Bases: SingleAggregateForm, LogicLoader

Source code in src/finch/autoschedule/stages.py
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
class LogicLoopOrderer(SingleAggregateForm, LogicLoader):
    @abstractmethod
    def lower(
        self,
        term: LogicStatement,
        bindings: dict[Alias, TensorFType],
        stats: dict[Alias, TensorStats],
        stats_factory: StatsFactory,
    ) -> tuple[
        AssemblyLibrary,
        dict[Alias, TensorFType],
        dict[Alias, tuple[Field | None, ...]],
        LogicStatement,
    ]:
        """
        Optimize the loop order of each query and add transposes where
        necessary.
        """

lower(term, bindings, stats, stats_factory) abstractmethod

Optimize the loop order of each query and add transposes where necessary.

Source code in src/finch/autoschedule/stages.py
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
@abstractmethod
def lower(
    self,
    term: LogicStatement,
    bindings: dict[Alias, TensorFType],
    stats: dict[Alias, TensorStats],
    stats_factory: StatsFactory,
) -> tuple[
    AssemblyLibrary,
    dict[Alias, TensorFType],
    dict[Alias, tuple[Field | None, ...]],
    LogicStatement,
]:
    """
    Optimize the loop order of each query and add transposes where
    necessary.
    """

LogicNotationLowerer

Bases: CompilerForm, Stage

Source code in src/finch/autoschedule/stages.py
371
372
373
374
375
376
377
378
379
380
381
382
383
class LogicNotationLowerer(CompilerForm, Stage):
    @abstractmethod
    def lower(
        self,
        term: LogicStatement,
        bindings: dict[Alias, TensorFType],
        stats: dict[Alias, TensorStats],
        stats_factory: StatsFactory,
    ) -> ntn.Module:
        """
        Generate Finch Notation from the given logic and input types.  Also
        return a dictionary including additional tables needed to run the kernel.
        """

lower(term, bindings, stats, stats_factory) abstractmethod

Generate Finch Notation from the given logic and input types. Also return a dictionary including additional tables needed to run the kernel.

Source code in src/finch/autoschedule/stages.py
372
373
374
375
376
377
378
379
380
381
382
383
@abstractmethod
def lower(
    self,
    term: LogicStatement,
    bindings: dict[Alias, TensorFType],
    stats: dict[Alias, TensorStats],
    stats_factory: StatsFactory,
) -> ntn.Module:
    """
    Generate Finch Notation from the given logic and input types.  Also
    return a dictionary including additional tables needed to run the kernel.
    """

LoopOrderedForm

Bases: SingleAggregateForm

LoopOrderedForm assumes that the input query has had its loop order set. There are four valid forms for a query in LoopOrderedForm: 1) transpose queries Query(Table(, output_order), Table(, )) 2) aggregate queries Query(Table(, output_order), Aggregate(, , Reorder(arg, loop_order), )) 3) in-place queries QueryInto(Table(, lhs_idxs), , Reorder(arg, loop_order)) 4) in-place initializations QueryInto(Table(, ), , Literal(_)) For aggregate and in-place queries, the loop order includes every lhs field and visits those fields in order. The Tables of arg follow the loop order, except that an in-place query of a single Table may read it in another order, representing a transpose.

Source code in src/finch/autoschedule/stages.py
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
class LoopOrderedForm(SingleAggregateForm):
    """
    LoopOrderedForm assumes that the input query has had its loop order set.
    There are four valid forms for a query in LoopOrderedForm:
        1) transpose queries
            Query(Table(_, output_order), Table(_, _))
        2) aggregate queries
            Query(Table(_, output_order), Aggregate(_, _, Reorder(arg, loop_order), _))
        3) in-place queries
            QueryInto(Table(_, lhs_idxs), _, Reorder(arg, loop_order))
        4) in-place initializations
            QueryInto(Table(_, _), _, Literal(_))
    For aggregate and in-place queries, the loop order includes every lhs
    field and visits those fields in order. The Tables of arg follow the loop
    order, except that an in-place query of a single Table may read it in
    another order, representing a transpose.
    """

    @staticmethod
    def _check_loop_order(idxs, loop_order):
        rel_loop_order = [idx for idx in loop_order if idx in idxs]
        return tuple(rel_loop_order) == tuple(idxs)

    @classmethod
    def validate_inputs(
        cls,
        term: Plan,
        bindings: dict[Alias, TensorFType],
        stats: dict[Alias, TensorStats],
        stats_factory: StatsFactory,
    ) -> None:
        super().validate_inputs(term, bindings, stats, stats_factory)

        def validate(node, loop_order):
            match node:
                case Plan(bodies):
                    for body in bodies[:-1]:
                        validate(body, loop_order)
                case Query(Table(), Table()) | QueryInto(Table(), _, Literal()):
                    return None
                case QueryInto(Table(_, lhs_idxs), _, Reorder(Table(), idxs)):
                    if not cls._check_loop_order(lhs_idxs, idxs):
                        raise ValueError("Table index order does not match loop order.")
                    return None
                case Query(
                    Table(_, lhs_idxs), Aggregate(_, _, Reorder(arg, idxs), _)
                ) | QueryInto(Table(_, lhs_idxs), _, Reorder(arg, idxs)):
                    if not cls._check_loop_order(lhs_idxs, idxs):
                        raise ValueError("Table index order does not match loop order.")
                    return validate(arg, idxs)
                case Query(Table(), Aggregate(_, _, arg, _)):
                    raise ValueError(
                        "All aggregates must wrap a Reorder node specifying\
                             the loop order."
                    )
                case QueryInto():
                    raise ValueError("In-place queries must have a loop order!")
                case MapJoin(_, args):
                    for arg in args:
                        validate(arg, loop_order)
                case Table(_, idxs):
                    if not cls._check_loop_order(idxs, loop_order):
                        raise ValueError("Table index order does not match loop order.")
                case Reorder(arg, _):
                    raise ValueError("Reorder nodes should only appear in loop orders!")
                case Literal():
                    return None
                case _:
                    raise ValueError(f"Unsupported query type: {node}")
            return None

        validate(term, None)

SingleAggregateForm

Bases: AliasedForm

SingleAggregateForm assumes that the fusion strategy has already been optimized for this query. There are three valid kinds of input query: 1) transpose queries Query(Table(, output_order), Table(, )) 2) aggregate queries Query(Table(, output_order), Aggregate(, Literal(), arg, )) 3) in-place queries QueryInto(Table(_, output_order), op, arg) (Here, arg has no aggregates. The fields of arg which are not in output_order are reduced with op.)

Source code in src/finch/autoschedule/stages.py
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
class SingleAggregateForm(AliasedForm):
    """
    SingleAggregateForm assumes that the fusion strategy has
    already been optimized for this query. There are three valid kinds of input query:
    1) transpose queries
        Query(Table(_, output_order), Table(_, _))
    2) aggregate queries
        Query(Table(_, output_order), Aggregate(_, Literal(), arg, _))
    3) in-place queries
        QueryInto(Table(_, output_order), op, arg)
    (Here, arg has no aggregates. The fields of arg which are not in
    output_order are reduced with op.)
    """

    @classmethod
    def validate_inputs(
        cls,
        term: Plan,
        bindings: dict[Alias, TensorFType],
        stats: dict[Alias, TensorStats],
        stats_factory: StatsFactory,
    ) -> None:
        super().validate_inputs(term, bindings, stats, stats_factory)

        def validate(node, agg_allowed):
            match node:
                case Plan(bodies):
                    if not isinstance(bodies[-1], Produces):
                        raise ValueError(
                            "The last body of a plan must be a Produces node."
                        )
                    for body in bodies[:-1]:
                        validate(body, True)
                case Query(Table(), Table()):
                    return None
                case Query(Table(), Aggregate(_, Literal(), arg, _)):
                    return validate(arg, False)
                case Query(Table(), Aggregate(_, init, _, _)):
                    raise ValueError(
                        f"Aggregate queries must start from a literal, not {init}. "
                        "Copy the init into the output and update it with a "
                        "QueryInto instead."
                    )
                case QueryInto(Table(), _, arg):
                    return validate(arg, False)
                case Query(_, rhs):
                    raise ValueError(f"Unsupported query right-hand side: {rhs}")
                case Aggregate(_, _, arg, _):
                    if not agg_allowed:
                        raise ValueError("Nested aggregates are not supported.")
                    return validate(arg, False)
                case Reorder(arg, _):
                    return validate(arg, agg_allowed)
                case MapJoin(_, args):
                    for arg in args:
                        validate(arg, agg_allowed)
                    return None
                case Literal() | Alias() | Table():
                    return None
                case _:
                    raise ValueError(f"Unsupported query type: {node}")
            return None

        validate(term, True)