MimIR
MimIR is my Intermediate Representation
Loading...
Searching...
No Matches
lower_map_reduce.cpp
Go to the documentation of this file.
2
3#include <algorithm>
4
5#include <fe/log.h>
6#include <fe/vector.h>
7
8#include <mim/driver.h>
9#include <mim/lam.h>
10#include <mim/rewrite.h>
11
15#include <mim/plug/core/core.h>
16#include <mim/plug/cps/cps.h>
17#include <mim/plug/mem/mem.h>
18
19#include "mim/plug/gpu/gpu.h"
20
21namespace mim::plug::gpu::phase {
22
23namespace {
24
25using fe::Vector;
26
27/// Whether an explicit, user-written `gpu.init` is reachable from `def`.
28bool contains_gpu_init(const Def* def, DefSet& seen) {
29 if (auto [_, ins] = seen.emplace(def); !ins) return false;
30 if (Axm::isa<gpu::init>(def)) return true;
31 for (auto d : def->deps())
32 if (contains_gpu_init(d, seen)) return true;
33 return false;
34}
35
36/// Substitutes the var of @p old_lam and moves every memory in its Nest to `gpu.GlobalM`.
37class GlobalMem : public Rewriter {
38public:
39 GlobalMem(Lam* old_lam, Lam* new_lam)
40 : Rewriter(old_lam->world())
41 , old_lam_(old_lam) {
42 auto& w = world();
43 map(old_lam->var(), new_lam->var());
44 map(w.call<mem::M>(0), w.annex<gpu::GlobalM>());
45 }
46
47 const Def* rewrite(const Def* old_def) final {
48 if (auto mut = old_def->isa_mut(); mut && !old_lam_->nests(mut)) return mut;
49 return Rewriter::rewrite(old_def);
50 }
51
52private:
53 Lam* old_lam_;
54};
55
56/// Scalarize may flatten an escaped `comb`/`post` from `Cn [[mem, T, ins], Cn ret]` to `Cn [mem, T, ins, Cn ret]`.
57Lam* rebuild_lam_global_mem(Lam* lam, const Def* Tout, Sym name) {
58 auto& w = lam->world();
59 auto global_ty = w.annex<gpu::GlobalM>();
60
61 Lam* new_lam;
62 auto nv = lam->num_vars();
63 if (nv == 2) {
64 auto [_, Tin, extra_ty] = lam->var(nv, 0)->type()->projs<3>();
65 new_lam = w.mut_con(Defs{w.sigma({global_ty, Tin, extra_ty}), w.cn({global_ty, Tout})})->set(name);
66 } else {
67 auto Tin = lam->var(nv, 1)->type();
68 auto extra_ty = lam->var(nv, 2)->type();
69 new_lam = w.mut_con(Defs{global_ty, Tin, extra_ty, w.cn({global_ty, Tout})})->set(name);
70 }
71 new_lam->set(true, GlobalMem(lam, new_lam).rewrite(lam->body()));
72 return new_lam;
73}
74
75/// The row-major coordinates of @p flat - an `I64`, i.e. an `Idx 0` - via `affine.delinearize`.
76std::pair<const Def*, DefVec> unflatten_index(World& w, const Def* flat, const Vector<nat_t>& dims, const Def* mem) {
77 auto m = dims.size();
78 if (m == 0) return {mem, {}};
79
80 auto s_out = w.tuple(DefVec(m, [&](size_t d) { return w.lit_nat(dims[d]); }));
81 auto index = w.annex<affine::Index>();
82 auto f = w.mut_lam(index, w.arr(m, index))->set("unflatten");
83 f->set(true, w.app(w.app(w.annex<affine::delinearize>(), w.lit_nat(m)), {f->var(), s_out}));
84
85 auto [mem2, coords]
86 = btensor::op_affine_map(f, w.lit_nat(m), w.lit_nat_1(), w.lit_nat_0(), s_out, flat, mem)->projs<2>();
87 return {mem2, coords->projs(m)};
88}
89
90struct InputDesc {
91 DefVec rs, ss, ts, maps;
92};
93
94InputDesc extract_input_desc(nat_t n, const Def* Rs, const Def* Ss, const Def* Ts, const Def* maps) {
95 InputDesc desc{DefVec(n), DefVec(n), DefVec(n), DefVec(n)};
96 for (nat_t i = 0; i != n; ++i) {
97 desc.rs[i] = Rs->proj(n, i);
98 desc.ss[i] = Ss->proj(n, i);
99 desc.ts[i] = Ts->proj(n, i);
100 desc.maps[i] = maps->proj(n, i);
101 }
102 return desc;
103}
104
105struct Inputs {
106 const Def* mem;
107 const Def* global;
108 DefVec dptrs;
109};
110
111Inputs alloc_copy_inputs(World& w, const Def* m0, const Def* m1, Defs ris, Defs sis, Defs tis, const Def* inputs) {
112 DefVec dptrs(ris.size());
113 for (size_t i = 0; i != ris.size(); ++i) {
114 auto alloc_copy = w.app(w.app(w.annex<gpu::buf_alloc_copy>(), {ris[i], sis[i], tis[i]}),
115 {m0, m1, inputs->proj(ris.size(), i)});
116 auto [m2, g2, ptr] = alloc_copy->projs<3>();
117 m0 = m2;
118 m1 = g2;
119 dptrs[i] = ptr;
120 }
121 return {m0, m1, dptrs};
122}
123
124std::pair<const Def*, const Def*> alloc_output(World& w, const Def* m1, const Def* elem_ty, const Def* So) {
125 return w.app(w.app(w.annex<gpu::alloc>(gpu::alloc::block), w.arr(So, elem_ty)), m1)->projs<2>();
126}
127
128struct Grid {
129 nat_t n_groups, n_items, total;
130};
131
132Grid grid_layout(const Vector<nat_t>& out_dims) {
133 nat_t total = 1;
134 for (auto d : out_dims)
135 total *= d;
136 nat_t n_items = std::min<nat_t>(total, 1024);
137 nat_t n_groups = (total + n_items - 1) / n_items;
138 return {n_groups, n_items, total};
139}
140
141/// Per-input state for `build_kernel`: `InputDesc`'s shapes/access-functions plus `alloc_copy_inputs`'s pointers.
142struct Mapped {
143 DefVec rs, ss, dptrs, maps;
144 nat_t n() const { return dptrs.size(); }
145};
146
147/// Builds the kernel: one thread per output point, reducing sequentially over the `rr` reduction dims.
148Lam* build_kernel(World& w,
149 const Def* Ro,
150 nat_t rr,
151 const Vector<nat_t>& out_dims,
152 const Def* Sr,
153 const Def* So,
154 const Mapped& ins,
155 const Def* To,
156 const Def* map_out,
157 const Def* init,
158 Lam* global_comb,
159 const Mapped& post_ins,
160 Lam* global_post,
161 const Def* Tp,
162 const Def* out_dptr,
163 const Grid& grid) {
164 auto nis = ins.n();
165 auto nps = post_ins.n();
166 auto ro = out_dims.size();
167 auto nloops_nat = ro + rr;
168 auto n = w.lit_nat(nloops_nat);
169
170 auto global_ty = w.annex<gpu::GlobalM>();
171 auto shared_ty = w.annex<gpu::SharedM>();
172 auto const_ty = w.annex<gpu::ConstM>();
173 auto local_ty = w.annex<gpu::LocalM>();
174
175 DefVec arg_tys(nis + nps + 1);
176 for (size_t i = 0; i != nis; ++i)
177 arg_tys[i] = ins.dptrs[i]->type();
178 for (size_t j = 0; j != nps; ++j)
179 arg_tys[nis + j] = post_ins.dptrs[j]->type();
180 arg_tys[nis + nps] = out_dptr->type();
181
182 auto kernel
183 = w.mut_con(Defs{global_ty, shared_ty, const_ty, local_ty, w.type_idx(grid.n_groups), w.type_idx(grid.n_items),
184 w.sigma(Defs{}), w.sigma(arg_tys), w.cn({global_ty, shared_ty, const_ty, local_ty})})
185 ->set("mapReduceKernel");
186 auto [k_global, k_shared, k_const, k_local, group_id, item_id, k_shared_ptrs, k_args, k_ret] = kernel->vars<9>();
187
188 DefVec k_dptrs(nis);
189 for (size_t i = 0; i != nis; ++i)
190 k_dptrs[i] = k_args->proj(nis + nps + 1, i);
191 DefVec k_post_dptrs(nps);
192 for (size_t j = 0; j != nps; ++j)
193 k_post_dptrs[j] = k_args->proj(nis + nps + 1, nis + j);
194 auto k_out_dptr = k_args->proj(nis + nps + 1, nis + nps);
195
196 auto group_i64 = grid.n_groups == 1 ? w.lit_i64(0) : w.call(core::conv::u, w.lit_nat_0(), group_id);
197 auto item_i64 = grid.n_items == 1 ? w.lit_i64(0) : w.call(core::conv::u, w.lit_nat_0(), item_id);
198 auto flat
200 Defs{w.call(core::wrap::mul, core::Mode::none, Defs{group_i64, w.lit_i64(grid.n_items)}), item_i64});
201
202 auto in_range = w.call(core::icmp::ul, Defs{flat, w.lit_i64(grid.total)});
203 auto early_return = w.mut_con(w.sigma(Defs{}))->set("outOfRange");
204 early_return->app(true, k_ret, Defs{k_global, k_shared, k_const, k_local});
205 auto body = w.mut_con(w.sigma(Defs{}))->set("inRange");
206 kernel->branch(true, in_range, body, early_return);
207
208 auto write_back = w.mut_con(Defs{global_ty, To})->set("writeBack");
209 auto [wb_mem, acc_final] = write_back->vars<2>();
210 auto [wb_mem2, wb_coords] = unflatten_index(w, flat, out_dims, wb_mem);
211 DefVec wb_idx = wb_coords;
212 for (size_t j = 0; j != rr; ++j)
213 wb_idx.push_back(w.call(core::conv::u, Sr->proj(nloops_nat, ro + j), w.lit_i64(0)));
214 auto [wc_mem, write_coords] = btensor::op_affine_map(map_out, Ro, n, Sr, So, w.tuple(wb_idx), wb_mem2)->projs<2>();
215
216 auto pcur = wc_mem;
217 DefVec post_elems(nps);
218 for (size_t j = 0; j != nps; ++j) {
219 auto [pc_mem, pcoords]
220 = btensor::op_affine_map(post_ins.maps[j], post_ins.rs[j], Ro, So, post_ins.ss[j], write_coords, pcur)
221 ->projs<2>();
222 pcur = pc_mem;
223 auto [rd_mem, rd_val]
224 = w.call<mem::load>(Defs{pcur, mem::op_lea_tuple(k_post_dptrs[j], *Shape(pcoords).fold(post_ins.ss[j]))})
225 ->projs<2>();
226 pcur = rd_mem;
227 post_elems[j] = rd_val;
228 }
229
230 auto after_post = w.mut_con(Defs{global_ty, Tp})->set("afterPost");
231 auto [post_mem, elem_post] = after_post->vars<2>();
232 auto final_mem
233 = w.call<mem::store>(Defs{post_mem, mem::op_lea_tuple(k_out_dptr, *Shape(write_coords).fold(So)), elem_post});
234 after_post->app(true, k_ret, Defs{final_mem, k_shared, k_const, k_local});
235 btensor::app_cps(write_back, global_post, {pcur, acc_final, w.tuple(post_elems)}, after_post);
236
237 const Def* acc = w.tuple({k_global, init});
238 const Def* cont = write_back;
239 Lam* current_mut = body;
240 auto sr = Sr->projs(nloops_nat);
241 auto raw = affine::counting_nest(current_mut, cont, acc, Defs(sr).subspan(ro, rr), "forRed");
242 auto red_iters = btensor::to_idxs(w, Defs(sr).subspan(ro, rr), raw);
243 auto [red_mem, elem_acc] = acc->projs<2>();
244
245 auto [body_mem, body_coords] = unflatten_index(w, flat, out_dims, red_mem);
246 DefVec iters_v = body_coords;
247 iters_v.insert(iters_v.end(), red_iters.begin(), red_iters.end());
248 auto iters = w.tuple(iters_v);
249
250 auto cur = body_mem;
251 DefVec input_elems(nis);
252 for (size_t i = 0; i != nis; ++i) {
253 auto [mc_mem, coords]
254 = btensor::op_affine_map(ins.maps[i], ins.rs[i], n, Sr, ins.ss[i], iters, cur)->projs<2>();
255 cur = mc_mem;
256 auto [rd_mem, rd_val]
257 = w.call<mem::load>(Defs{cur, mem::op_lea_tuple(k_dptrs[i], *Shape(coords).fold(ins.ss[i]))})->projs<2>();
258 cur = rd_mem;
259 input_elems[i] = rd_val;
260 }
261
262 btensor::app_cps(current_mut, global_comb, {cur, elem_acc, w.tuple(input_elems)}, cont);
263
264 return kernel;
265}
266
267Lam* build_teardown(World& w,
268 const Def* Ro,
269 const Def* So,
270 const Def* Tp,
271 Defs dptrs,
272 Defs post_dptrs,
273 const Def* out_dptr,
274 const Def* host_buf,
275 const Def* cont) {
276 auto global_ty = w.annex<gpu::GlobalM>();
277 auto const_ty = w.annex<gpu::ConstM>();
278 auto mem_ty = w.call<mem::M>(0);
279
280 auto after_launch = w.mut_con(Defs{mem_ty, global_ty, const_ty})->set("afterLaunch");
281 auto [post_mem, post_global, post_const] = after_launch->vars<3>();
282
283 auto copy_back
284 = w.app(w.app(w.annex<gpu::buf_copy_to_host>(), {Ro, So, Tp}), {post_mem, post_global, out_dptr, host_buf});
285 auto [cb_mem, cb_global] = copy_back->projs<2>();
286
287 auto cur_global = cb_global;
288 for (auto dptr : dptrs)
289 cur_global = w.call(gpu::free::block, Defs{cur_global, dptr});
290 for (auto dptr : post_dptrs)
291 cur_global = w.call(gpu::free::block, Defs{cur_global, dptr});
292 cur_global = w.call(gpu::free::block, Defs{cur_global, out_dptr});
293
294 auto final_mem = w.app(w.annex<gpu::auto_deinit>(), Defs{cb_mem, cur_global, post_const});
295 after_launch->app(true, cont, Defs{final_mem, host_buf});
296 return after_launch;
297}
298
299} // namespace
300
302 DefSet seen;
303 auto has_gpu_init
304 = std::ranges::any_of(old_world().externals().muts(), [&](auto def) { return contains_gpu_init(def, seen); });
305 if (has_gpu_init) {
306 log().w("not lowering any map-reduce operations to GPU: the program already contains an explicit `gpu.init`");
307 return;
308 }
309 Super::start();
310}
311
313 if (Axm::isa<btensor::map_reduce_post>(app)) return lower_map_reduce_post(app);
314 return Super::rewrite_imm_App(app);
315}
316
317const Def* LowerMapReduce::lower_map_reduce_post(const App* app) {
318 auto& w = new_world();
319 auto mr = btensor::MapRedPost::of(rewrite(app->callee())->as<App>());
320 auto Sr = mr.Sr;
321 auto result_ty = rewrite(app->type());
322
323 auto ranks = mr.ranks();
324 if (!ranks) {
325 log().w("{} doesn't have lowering-time known rank counts (nis/nps/Ro/Rn)", app);
326 return Super::rewrite_imm_App(app);
327 }
328 auto [nis_n, nps_n, ro, rr] = *ranks;
329
330 Vector<nat_t> out_dims(ro);
331 nat_t out_total = 1;
332 for (nat_t d = 0; d != ro; ++d) {
333 auto l = Lit::isa<nat_t>(Sr->proj(ro + rr, d));
334 if (!l) {
335 log().w("{} doesn't have a lowering-time known output (grid) shape", app);
336 return Super::rewrite_imm_App(app);
337 }
338 out_dims[d] = *l;
339 out_total *= *l;
340 }
341 if (out_total == 0) {
342 log().w("{} has a zero-sized output, skipping GPU lowering", app);
343 return Super::rewrite_imm_App(app);
344 }
345
346 auto comb_lam = mr.comb->isa_mut<Lam>();
347 auto post_lam = mr.post->isa_mut<Lam>();
348 if (!comb_lam || !post_lam) {
349 log().w("{} doesn't have a lowering-time known combiner/epilogue", app);
350 return Super::rewrite_imm_App(app);
351 }
352
353 auto [fun, call] = btensor::ds_splice(rewrite(app->arg()), result_ty, "mapReduceAffGpu");
354 auto [fun_mem, host_buf, new_inputs, new_post_ins] = fun->var(2, 0)->projs<4>();
355 auto cont = fun->var(2, 1);
356
357 auto [h_mem, h_global, h_const] = w.app(w.annex<gpu::auto_init>(), fun_mem)->projs<3>();
358
359 auto in_desc = extract_input_desc(nis_n, mr.Ris, mr.Sis, mr.Tis, mr.maps);
360 auto inputs = alloc_copy_inputs(w, h_mem, h_global, in_desc.rs, in_desc.ss, in_desc.ts, new_inputs);
361
362 auto post_desc = extract_input_desc(nps_n, mr.Rps, mr.Sps, mr.Tps, mr.post_maps);
363 auto post_inputs
364 = alloc_copy_inputs(w, inputs.mem, inputs.global, post_desc.rs, post_desc.ss, post_desc.ts, new_post_ins);
365
366 auto [out_global, out_dptr] = alloc_output(w, post_inputs.global, mr.Tp, mr.So);
367
368 auto global_comb = rebuild_lam_global_mem(comb_lam, mr.To, w.sym("combGlobal"));
369 auto global_post = rebuild_lam_global_mem(post_lam, mr.Tp, w.sym("postGlobal"));
370
371 auto grid = grid_layout(out_dims);
372
373 auto kernel = build_kernel(w, mr.Ro, rr, out_dims, Sr, mr.So,
374 Mapped{in_desc.rs, in_desc.ss, inputs.dptrs, in_desc.maps}, mr.To, mr.map_out, mr.init,
375 global_comb, Mapped{post_desc.rs, post_desc.ss, post_inputs.dptrs, post_desc.maps},
376 global_post, mr.Tp, out_dptr, grid);
377
378 DefVec kernel_args = inputs.dptrs;
379 kernel_args.insert(kernel_args.end(), post_inputs.dptrs.begin(), post_inputs.dptrs.end());
380 kernel_args.push_back(out_dptr);
381 DefVec kernel_arg_tys(kernel_args.size(), [&](size_t i) { return kernel_args[i]->type(); });
382
383 auto launch = w.app(w.annex<gpu::launch>(), Defs{w.lit_nat(nis_n + nps_n + 1), w.tuple(kernel_arg_tys)});
384 launch = w.app(launch, Defs{w.lit_nat(grid.n_groups), w.lit_nat(grid.n_items), w.annex<gpu::default_stream>(),
385 w.lit_ff(), w.tuple()});
386 launch = w.app(launch, kernel);
387 launch = w.app(launch, kernel_args);
388
389 auto after_launch
390 = build_teardown(w, mr.Ro, mr.So, mr.Tp, inputs.dptrs, post_inputs.dptrs, out_dptr, host_buf, cont);
391 auto launch_call = w.app(launch, Defs{w.tuple({post_inputs.mem, out_global, h_const}), after_launch});
392 fun->set(true, launch_call);
393
394 return call;
395}
396
397} // namespace mim::plug::gpu::phase
const Def * callee() const
Definition lam.h:237
const Def * arg() const
Definition lam.h:251
static auto isa(const Def *def)
Definition axm.h:112
Base class for all Defs.
Definition def.h:289
auto projs(Projector auto f) const
Splits this Def via Def::projections into an Array (if A == std::dynamic_extent) or std::array (other...
Definition def.h:457
const Def * type() const noexcept
Yields the "raw" type of this Def (maybe nullptr).
Definition def.h:1119
static std::optional< T > isa(const Def *def)
Definition def.h:976
const fe::Log & log() const
Definition phase.h:60
World & new_world()
Create new Defs into this.
Definition phase.h:379
void start() override
Actual entry.
Definition phase.cpp:184
World & old_world()
Get old Defs from here.
Definition phase.h:378
const Def * rewrite_imm_App(const App *) override
Definition phase.cpp:223
virtual const Def * rewrite(const Def *)
Definition rewrite.cpp:62
constexpr Shape() noexcept=default
void start() final
Skips the whole phase if the program already contains an explicit gpu.init.
const Def * rewrite_imm_App(const App *) final
DefVec counting_nest(Lam *&cur, const Def *&exit, const Def *&acc, Defs dims, std::string_view name)
Nests one counting loop per bound of dims inside cur, threading cur, exit and acc down to the innermo...
Definition affine.h:24
const Def * op_affine_map(const Def *f, const Def *m, const Def *n, const Def *s_in, const Def *s_out, const Def *idxs, const Def *mem)
affine.map @(m, n) @(s_in, s_out) f idxs as mem with the address space as of mem ↦ [mem',...
Definition btensor.h:55
DefVec to_idxs(World &w, Defs extents, Defs iters)
Converts the raw i64 loop counters iters into indices Idx extents[d].
Definition btensor.h:93
std::pair< Lam *, const Def * > ds_splice(const Def *arg, const Def *codom, std::string name)
A fresh fun: Fn arg->type() → codom named name, spliced into direct style as cps.cps2ds_dep fun arg.
Definition btensor.h:85
void app_cps(Lam *caller, const Def *f, DefVec args, const Def *ret)
Calls the combiner/epilogue f from caller with args and the continuation ret.
Definition btensor.h:73
@ none
Wrap around.
Definition core.h:16
plug::core::M M
Definition autogen.h:14
const Def * op_lea_tuple(const Def *ptr, const Def *tuple)
Successively offsets ptr by each component of the index tuple, peeling one array dimension per mem....
Definition mem.h:97
u64 nat_t
Definition types.h:37
fe::View< const Def * > Defs
Definition def.h:96
fe::Vector< const Def * > DefVec
Definition def.h:98
GIDSet< const Def * > DefSet
Definition def.h:94
@ Lam
Definition def.h:125
static MapRedPost of(const App *callee)
Definition btensor.h:25