MimIR
MimIR is my Intermediate Representation
Loading...
Searching...
No Matches
fuse.cpp
Go to the documentation of this file.
2
3#include "mim/def.h"
4#include "mim/lam.h"
5
6#include "mim/util/types.h"
7
9#include "mim/plug/cps/cps.h"
11
13
14/// `inner ∘ outer`: feeds the outer op's read coordinates for one input into the inner op's access map.
15static const Def* compose_map(World& w, const Def* inner, const Def* outer) {
16 auto dom = outer->type()->as<Pi>()->dom();
17 auto codom = inner->type()->as<Pi>()->codom();
18 auto lam = w.mut_lam(dom, codom)->set("fused_map");
19 lam->set(true, w.app(inner, w.app(outer, lam->var())));
20 return lam;
21}
22
23// Fuses an outer `tensor.map_reduce` with any number of its inputs — and, recursively, any
24// fusible inputs of those inputs — whenever each such input is itself a `tensor.map_reduce`
25// without reduction loops (`Rr = 0`) that writes its full loop domain through the identity output
26// map (`Sr = So`, `map_out = %affine.id`). Reading such an inner tensor at a position is then just
27// a single call to the inner combination function, with each inner access map composed behind the
28// outer's access map for that input.
29//
30// Outer: map_reduce nis_o (To, Ro, Rr) (So, Sr) (Tis_o, Ris_o, Sis_o) (f_o, init_o) map_out maps_o is_o
31// Inner: map_reduce nis_k (To_k, Ro_k, 0) (So_k, So_k) (Tis_k, Ris_k, Sis_k) (f_k, init_k) id maps_k is_k
32// for every fusible input — possibly nested inside another fusible input
33//
34// Result: map_reduce nis_new (To, Ro, Rr) (So, Sr) (Tis_new, Ris_new, Sis_new) (f_new, init_o)
35// map_out maps_new is_new
36//
37// The collection phase walks the tree of fusible inner ops below `app` once, producing a flat list
38// of *leaves* (the surviving tensor inputs of the fused op) and *inner nodes* (the inner combiners
39// that must run before `f_o`). Each fusible input is replaced by its inner's inputs, with access
40// maps composed behind the outer map at that position; the composition nests across levels. The
41// new combination function `f_new` invokes every inner combiner in post-order — each starting from
42// its own init — and finally invokes `f_o`, threading inner results into the corresponding outer
43// input slots.
44const Def* Fuse::fuse_map_reduce(const App* app) {
45 auto outer_callee = rewrite(app->callee())->as<App>();
46
47 auto [nis, meta, shapes, TisRisSis, comb_init, map_out, maps] = outer_callee->uncurry_args<7>();
48
49 auto [To, Ro, Rr] = meta->projs<3>();
50 auto [comb, init] = comb_init->projs<2>();
51 auto [Tis, Ris, Sis] = TisRisSis->projs<3>();
52 auto is = rewrite(app->arg());
53
54 DLOG("considering map_reduce for fusion:");
55 DLOG(" comb = {} : {}", comb, comb->type());
56 DLOG(" init = {} : {}", init, init->type());
57 DLOG(" Tis = {} : {}", Tis, Tis->type());
58 DLOG(" Ris = {} : {}", Ris, Ris->type());
59 DLOG(" Sis = {} : {}", Sis, Sis->type());
60 DLOG(" To = {} : {}", To, To->type());
61 DLOG(" Ro = {} : {}", Ro, Ro->type());
62 DLOG(" nis = {} : {}", nis, nis->type());
63 DLOG(" is = {} : {}", is, is->type());
64
65 auto& w = new_world();
66
67 auto nis_lit = Lit::isa<u64>(nis);
68 if (!nis_lit) return nullptr;
69 auto nis_nat = *nis_lit;
70
71 struct InnerInfo {
72 bool fusible = false;
73 const Def* comb = nullptr;
74 const Def* init = nullptr;
75 const Def* Tis = nullptr;
76 const Def* Ris = nullptr;
77 const Def* Sis = nullptr;
78 const Def* To = nullptr;
79 const Def* maps = nullptr;
80 u64 nis = 0;
81 const Def* is = nullptr;
82 };
83
84 Vector<InnerInfo> infos(nis_nat);
85 bool any_fusible = false;
86
87 for (u64 k = 0; k < nis_nat; ++k) {
88 auto input_k = is->proj(nis_nat, k);
89 auto inner = Axm::isa<tensor::map_reduce>(input_k);
90 if (!inner) continue;
91
92 auto [inner_nis, inner_meta, inner_shapes, inner_TisRisSis, inner_comb_init, inner_map_out, inner_maps,
93 inner_is]
94 = inner->uncurry_args<8>();
95 auto [inner_To, inner_Ro, inner_Rr] = inner_meta->projs<3>();
96 auto [inner_So, inner_Sr] = inner_shapes->projs<2>();
97 auto [inner_comb, inner_init] = inner_comb_init->projs<2>();
98 auto [inner_Tis, inner_Ris, inner_Sis] = inner_TisRisSis->projs<3>();
99
100 auto inner_nis_nat = Lit::isa<u64>(inner_nis);
101 if (!inner_nis_nat) continue;
102
103 // We can only fuse when the inner has no reduction loops and writes every cell of its full
104 // loop domain through the identity output map. In that case the inner tensor at any
105 // position is just a single call of `inner_comb` at that position.
106 // The identity map (`%affine.id`) is recognized structurally (a lam returning its own var),
107 // since the rewrite into this phase's world rebuilds mutables and breaks pointer equality.
108 auto inner_rr = Lit::isa<u64>(inner_Rr);
109 if (!inner_rr || *inner_rr != 0) continue;
110 if (inner_Sr != inner_So) continue;
111 auto id_lam = inner_map_out->isa_mut<Lam>();
112 if (!id_lam || !id_lam->is_set() || id_lam->body() != id_lam->var()) continue;
113
114 auto& info = infos[k];
115 info.fusible = true;
116 info.comb = inner_comb;
117 info.init = inner_init;
118 info.Tis = inner_Tis;
119 info.Ris = inner_Ris;
120 info.Sis = inner_Sis;
121 info.To = inner_To;
122 info.maps = inner_maps;
123 info.nis = *inner_nis_nat;
124 info.is = inner_is;
125 any_fusible = true;
126 }
127
128 if (!any_fusible) return nullptr;
129
130 // Each fusible outer input k is replaced by `infos[k].nis` slots in the fused input list;
131 // every non-fusible input retains exactly one slot. `new_pos[i]` is the start of input i's
132 // slot range in the fused list.
133 Vector<u64> new_pos(nis_nat);
134 u64 new_nis_nat = 0;
135 for (u64 i = 0; i < nis_nat; ++i) {
136 new_pos[i] = new_nis_nat;
137 new_nis_nat += infos[i].fusible ? infos[i].nis : 1;
138 }
139
140 DefVec new_Tis_vec(new_nis_nat);
141 DefVec new_Ris_vec(new_nis_nat);
142 DefVec new_Sis_vec(new_nis_nat);
143 DefVec new_maps_vec(new_nis_nat);
144 DefVec new_is_vec(new_nis_nat);
145
146 for (u64 i = 0; i < nis_nat; ++i) {
147 if (infos[i].fusible) {
148 const auto& info = infos[i];
149 auto outer_map_i = maps->proj(nis_nat, i);
150 for (u64 l = 0; l < info.nis; ++l) {
151 auto pos = new_pos[i] + l;
152 new_Tis_vec[pos] = info.Tis->proj(info.nis, l);
153 new_Ris_vec[pos] = info.Ris->proj(info.nis, l);
154 new_Sis_vec[pos] = info.Sis->proj(info.nis, l);
155 new_is_vec[pos] = info.is->proj(info.nis, l);
156 // The inner reads at its own output coordinates; those are the outer's read
157 // coordinates for input i, so the fused access map is the composition.
158 new_maps_vec[pos] = compose_map(w, info.maps->proj(info.nis, l), outer_map_i);
159 }
160 } else {
161 auto pos = new_pos[i];
162 new_Tis_vec[pos] = Tis->proj(nis_nat, i);
163 new_Ris_vec[pos] = Ris->proj(nis_nat, i);
164 new_Sis_vec[pos] = Sis->proj(nis_nat, i);
165 new_maps_vec[pos] = maps->proj(nis_nat, i);
166 new_is_vec[pos] = is->proj(nis_nat, i);
167 }
168 }
169
170 auto new_Tis = w.tuple(new_Tis_vec);
171 auto new_Ris = w.tuple(new_Ris_vec);
172 auto new_Sis = w.tuple(new_Sis_vec);
173 auto new_maps = w.tuple(new_maps_vec);
174 auto new_is = w.tuple(new_is_vec);
175
176 auto new_nis_def = w.lit_nat(new_nis_nat);
177
178 // Build the fused combination function:
179 //
180 // cn f_new(data: [To, [new_Tis ...]], ret: cn To) =
181 // cn inner_ret_<r>(value_<r>: inner_To_<r>) = ...
182 // f_<fused[0]>((init_<fused[0]>, inner_inputs_<fused[0]>), inner_ret_0)
183 //
184 // inner_ret_<r>(value_<r>):
185 // if r is not the last fused input:
186 // f_<fused[r+1]>((init_<fused[r+1]>, inner_inputs_<fused[r+1]>), inner_ret_<r+1>)
187 // else:
188 // f_o((acc, outer_inputs), ret)
189 //
190 // `outer_inputs[i]` is `value_<r>` when input i is the r-th fused input, and the
191 // corresponding `new_in` slot otherwise. Each `inner_ret_<r>` closes over the prior
192 // `value_<j>`s as free variables — those are bound by the dynamic call chain.
193 auto inputs_sigma = w.sigma(new_Tis_vec);
194 auto data_sigma = w.sigma({To, inputs_sigma});
195 auto ret_cn_type = w.cn(To);
196 auto new_comb = w.mut_con({data_sigma, ret_cn_type})->set("fused_comb");
197 auto new_data = new_comb->var(0);
198 auto new_ret = new_comb->var(1);
199 auto new_acc = new_data->proj(2, 0);
200 auto new_in = new_data->proj(2, 1);
201
202 Vector<u64> fused_indices;
203 for (u64 i = 0; i < nis_nat; ++i)
204 if (infos[i].fusible) fused_indices.emplace_back(i);
205
206 Vector<Lam*> inner_rets(fused_indices.size());
207 Vector<const Def*> inner_values(fused_indices.size());
208 for (size_t r = 0; r < fused_indices.size(); ++r) {
209 auto new_inner_To = infos[fused_indices[r]].To;
210 inner_rets[r] = w.mut_con(new_inner_To)->set("inner_ret");
211 inner_values[r] = inner_rets[r]->var(0);
212 }
213
214 // Map each outer input position to its value at the f_o call site.
215 DefVec outer_inputs_vec(nis_nat);
216 {
217 size_t r = 0;
218 for (u64 i = 0; i < nis_nat; ++i)
219 if (infos[i].fusible)
220 outer_inputs_vec[i] = inner_values[r++];
221 else
222 outer_inputs_vec[i] = new_in->proj(new_nis_nat, new_pos[i]);
223 }
224
225 // Chain: caller for fused step r is new_comb (r==0) or inner_rets[r-1] (otherwise).
226 for (size_t r = 0; r < fused_indices.size(); ++r) {
227 auto k = fused_indices[r];
228 auto new_inner_comb = infos[k].comb;
229 auto new_inner_init = infos[k].init;
230
231 DefVec inner_inputs_vec(infos[k].nis);
232 for (u64 l = 0; l < infos[k].nis; ++l)
233 inner_inputs_vec[l] = new_in->proj(new_nis_nat, new_pos[k] + l);
234
235 Lam* caller = (r == 0) ? new_comb : inner_rets[r - 1];
236 caller->app(true, new_inner_comb, {w.tuple({new_inner_init, w.tuple(inner_inputs_vec)}), inner_rets[r]});
237 }
238
239 // After every inner combiner has produced its value, call the outer combiner.
240 inner_rets.back()->app(true, comb, {w.tuple({new_acc, w.tuple(outer_inputs_vec)}), new_ret});
241
242 // Construct the fused map_reduce; the loop domain, output map and init are the outer's.
243 auto mr = w.annex<tensor::map_reduce>();
244 mr = w.app(mr, new_nis_def);
245 mr = w.app(mr, meta);
246 mr = w.app(mr, shapes);
247 mr = w.app(mr, {new_Tis, new_Ris, new_Sis});
248 mr = w.app(mr, {new_comb, init});
249 mr = w.app(mr, map_out);
250 mr = w.app(mr, new_maps);
251 mr = w.app(mr, new_is);
252
253 return mr;
254}
255
256const Def* Fuse::rewrite_imm_App(const App* app) {
257 if (auto mr = Axm::isa<tensor::map_reduce>(app)) {
258 if (auto res = fuse_map_reduce(mr)) {
259 DLOG("Fused map_reduce at {} into a new map_reduce {}", app, res);
260 return res;
261 }
262 }
263 return RWPhase::rewrite_imm_App(app);
264}
265
266} // namespace mim::plug::tensor::phase
const Def * callee() const
Definition lam.h:276
static auto uncurry_args(const Def *def)
Definition lam.h:329
const Def * arg() const
Definition lam.h:285
static auto isa(const Def *def)
Definition axm.h:107
Base class for all Defs.
Definition def.h:261
const Def * var(nat_t a, nat_t i) noexcept
Definition def.h:441
auto projs(F f) const
Splits this Def via Def::projections into an Array (if A == std::dynamic_extent) or std::array (other...
Definition def.h:402
const Def * type() const noexcept
Yields the "raw" type of this Def (maybe nullptr).
Definition def.cpp:491
A function.
Definition lam.h:110
static std::optional< T > isa(const Def *def)
Definition def.h:878
A dependent function type.
Definition lam.h:14
World & new_world()
Create new Defs into this.
Definition phase.h:368
virtual const Def * rewrite(const Def *)
Definition rewrite.cpp:56
This is a thin wrapper for absl::InlinedVector<T, N, A> which is a drop-in replacement for std::vecto...
Definition vector.h:18
The World represents the whole program and manages creation of MimIR nodes (Defs).
Definition world.h:36
const Def * rewrite_imm_App(const App *) final
Definition fuse.cpp:256
#define DLOG(...)
Vaporizes to nothingness in Debug build.
Definition log.h:94
static const Def * compose_map(World &w, const Def *inner, const Def *outer)
inner ∘ outer: feeds the outer op's read coordinates for one input into the inner op's access map.
Definition fuse.cpp:15
Vector< const Def * > DefVec
Definition def.h:79
uint64_t u64
Definition types.h:27
@ Lam
Definition def.h:109
Vector(I, I, A=A()) -> Vector< typename std::iterator_traits< I >::value_type, Default_Inlined_Size< typename std::iterator_traits< I >::value_type >, A >