23constexpr u64 Loud_max_dispatch = 8;
35bool kvec_split(fe::View<Leaf> mats,
u64 s,
u64 j) {
return s + 1 == j && is_kvec(mats[j]); }
39using Mono = std::array<const Def*, 3>;
44 void add(Mono mono,
u64 coeff) {
45 for (
auto& [m, c] : terms_)
50 terms_.emplace_back(mono, coeff);
53 void add(
const Poly& other) {
54 for (
const auto& [m, c] : other.terms_)
58 u64 coeff(Mono mono)
const {
59 for (
const auto& [m, c] : terms_)
60 if (m == mono)
return c;
67 bool dominates(
const Poly& other)
const {
68 for (
const auto& [m, c] : terms_)
69 if (c > other.coeff(m))
return false;
73 std::string str()
const {
74 if (terms_.empty())
return "0";
75 auto s = std::string();
76 for (
const auto& [mono, coeff] : terms_) {
77 if (!
s.empty())
s +=
" + ";
78 s += std::format(
"{}", coeff);
80 if (d)
s += std::format(
"·{}", d);
86 fe::Vector<std::pair<Mono, u64>> terms_;
93Poly mul_cost(
const Def* x,
const Def* y,
const Def* z,
u64 vec,
bool kvec) {
96 auto extent = [&](
const Def*
d,
u64 pad) {
98 coeff *= lanes(*l,
pad);
100 syms.emplace_back(d);
104 extent(y, kvec ? vec : 1);
105 extent(z, kvec ? 1 : vec);
106 std::ranges::sort(syms, [](
const Def* a,
const Def* b) {
return a->gid() < b->gid(); });
109 std::ranges::copy(syms, mono.begin());
112 poly.add(mono, coeff);
116Poly cost_of(fe::View<Split> splits, fe::View<Leaf> mats,
Defs dims,
u64 vec) {
118 auto kvec = [&](
u64 i,
u64 j,
bool t) {
119 if (i == j)
return is_kvec(mats[i]) !=
t;
120 return std::ranges::find_if(splits, [&](
const Split& n) {
return n.i == i && n.j == j; })->t != t;
124 for (
auto [i, s, j, t] : splits)
125 poly.add(t ? mul_cost(dims[j + 1], dims[s + 1], dims[i], vec, kvec(i, s, t))
126 : mul_cost(dims[i], dims[s + 1], dims[j + 1], vec, kvec(s + 1, j, t)));
131fe::Vector<Splits> bracketings(
u64 lo,
u64 hi) {
132 if (lo == hi)
return {
Splits()};
134 auto res = fe::Vector<Splits>();
135 for (
auto s = lo;
s != hi; ++
s)
136 for (
const auto& l : bracketings(lo, s))
137 for (
const auto& r : bracketings(s + 1, hi)) {
140 b.emplace_back(
Split{lo,
s, hi});
141 res.emplace_back(std::move(b));
149fe::Vector<Splits> pareto(fe::View<Splits> cands, fe::View<Leaf> mats,
Defs dims,
u64 vec) {
150 auto keep = fe::Vector<Splits>();
151 auto costs = fe::Vector<Poly>();
153 for (
const auto& cand : cands) {
154 auto cost = cost_of(cand, mats, dims, vec);
155 if (std::ranges::any_of(costs, [&](
const Poly& k) {
return k.dominates(cost); }))
continue;
156 for (
auto i = costs.size(); i-- != 0;)
157 if (cost.dominates(costs[i])) keep.erase(keep.begin() + i), costs.erase(costs.begin() + i);
158 keep.emplace_back(cand);
159 costs.emplace_back(std::move(cost));
165fe::Vector<u64> split_table(
const Splits& splits,
u64 n) {
166 auto table = fe::Vector<u64>(n * n, 0);
167 for (
auto [i, s, j, _] : splits)
168 table[i * n + j] =
s;
176std::optional<std::pair<fe::Vector<u64>, Poly>> matrix_chain_order(fe::View<Leaf> mats,
Defs dims,
u64 vec) {
177 auto n = dims.size() - 1;
178 auto cost = fe::Vector<Poly>(n * n);
179 auto split = fe::Vector<u64>(n * n, 0);
181 for (
auto len = 2_u64;
len <= n; ++
len) {
182 for (
auto i = 0_u64; i +
len <= n; ++i) {
183 auto j = i +
len - 1;
184 auto cands = fe::Vector<Poly>();
185 for (
auto s = i;
s != j; ++
s) {
186 auto c = mul_cost(dims[i], dims[s + 1], dims[j + 1], vec, kvec_split(mats, s, j));
187 c.add(cost[i * n + s]);
188 c.add(cost[(s + 1) * n + j]);
189 cands.emplace_back(std::move(c));
192 auto best = std::ranges::find_if(cands, [&](
const Poly& a) {
193 return std::ranges::all_of(cands, [&](
const Poly& b) {
return a.dominates(b); });
195 if (best == cands.end())
return {};
197 split[i * n + j] = i + (best - cands.begin());
198 cost[i * n + j] = std::move(*best);
202 return std::pair{std::move(split), std::move(cost[n - 1])};
208 if (
auto n =
driver().arg_nat(
"tensor",
"reassoc-max")) {
210 log().d(
"dispatch chains of up to {} matrices", *n);
211 if (*n > Loud_max_dispatch)
212 log().w(
"`-X tensor:reassoc-max={}` enumerates up to Catalan({}) bracketings", *n, *n - 1);
217 log().d(
"charge the vector loop in units of {} lanes", vec_);
227std::optional<Reassoc::Link> Reassoc::isa_link(
const Def* def,
const Def* ring)
const {
232 auto groups = app->callee()->as<
App>();
233 if (groups->callee()->as<
App>()->
arg() != ring)
return {};
235 auto [m, k, l] = groups->
args<3>();
236 return Link{app, m, k, l};
239void Reassoc::flatten(
const Def* def,
246 auto i = consumers_.find(def);
247 if (i != consumers_.end() && i->second == 1) {
249 if (
auto in =
isa_transpose_2d(def))
return flatten(in, ring, rows, !t, mats, dims, orig);
251 if (
auto link = isa_link(def, ring)) {
252 auto lo = mats.size();
253 auto [t1, t2] = link->app->args<2>();
254 if (t) std::swap(t1, t2);
255 flatten(t1, ring, t ? link->l : link->m, t, mats, dims, orig);
256 auto mid = mats.size();
257 flatten(t2, ring, link->k, t, mats, dims, orig);
258 orig.emplace_back(Split{lo, mid - 1, mats.size() - 1, t});
263 mats.emplace_back(Leaf{def,
t});
264 dims.emplace_back(rows);
267const Def* Reassoc::build(
const Def* head, fe::View<Leaf> mats,
Defs dims, fe::View<u64> split,
u64 i,
u64 j) {
270 auto mat =
rewrite(mats[i].def);
271 if (!mats[i].t)
return mat;
272 auto T =
head->as<
App>()->arg()->proj(4, 0);
277 auto s = split[i * mats.size() + j];
278 auto t1 = build(head, mats, dims, split, i, s);
279 auto t2 = build(head, mats, dims, split, s + 1, j);
281 return w.app(
w.app(head, mkl), {t1, t2});
284const Def* Reassoc::cost_expr(fe::View<Leaf> mats,
Defs dims,
const Splits& splits) {
286 const Def*
sum =
nullptr;
289 auto extent = [&](
const Def*
d,
u64 pad) ->
const Def* {
294 for (
auto [i, s, j, _] : splits) {
295 auto kvec = kvec_split(mats, s, j);
296 auto p =
w.app(
w.annex(
core::nat::mul), {rewrite(dims[i]), extent(dims[s + 1], kvec ? vec_ : 1)});
297 p =
w.app(
w.annex(
core::nat::mul), {p, extent(dims[j + 1], kvec ? 1 : vec_)});
305Reassoc::dispatch(
const Def* head,
const Def* res_ty, fe::View<Leaf> mats,
Defs dims, fe::View<Splits> cands) {
307 auto n = mats.size();
308 auto pi =
w.pi(
w.sigma(), res_ty);
313 const Def* best =
nullptr;
314 const Def* best_cost =
nullptr;
315 for (
const auto& cand : cands) {
316 auto thunk =
w.mut_lam(pi)->set(
true, build(head, mats, dims, split_table(cand, n), 0, n - 1));
317 auto cost = cost_expr(mats, dims, cand);
320 best = thunk, best_cost = cost;
323 best =
w.select(cheaper, thunk, best);
324 best_cost =
w.select(cheaper, cost, best_cost);
328 return w.app(best,
w.tuple());
331const Def* Reassoc::reassoc(
const App* app) {
332 auto head = app->callee()->as<
App>()->callee()->as<
App>();
333 auto link = isa_link(app,
head->arg());
334 if (!link)
return nullptr;
336 auto [t1, t2] = app->args<2>();
340 flatten(t1,
head->arg(), link->m,
false, mats, dims, orig);
341 auto mid = mats.size();
342 flatten(t2,
head->arg(), link->k,
false, mats, dims, orig);
343 dims.emplace_back(link->l);
344 orig.emplace_back(Split{0_u64, mid - 1, mats.size() - 1});
347 auto n = mats.size();
348 if (n < 3)
return nullptr;
350 auto orig_cost = cost_of(orig, mats, dims, vec_);
352 if (n <= max_dispatch_) {
353 auto cands = pareto(bracketings(0, n - 1), mats, dims, vec_);
354 if (cands.size() != 1) {
355 log().d(
"dispatch chain {} over {} bracketings, written as {}", fe::Join(dims,
"×"), cands.size(),
357 return dispatch(
rewrite(head),
rewrite(app->type()), mats, dims, cands);
360 auto cost = cost_of(cands.front(), mats, dims, vec_);
361 if (orig_cost.dominates(cost))
return nullptr;
362 log().d(
"reassociate chain {}: {} → {} lane slots", fe::Join(dims,
"×"), orig_cost.str(), cost.str());
363 return build(
rewrite(head), mats, dims, split_table(cands.front(), n), 0, n - 1);
366 auto order = matrix_chain_order(mats, dims, vec_);
367 if (!order)
return nullptr;
369 auto& [split, cost] = *order;
370 if (orig_cost.dominates(cost))
return nullptr;
372 log().d(
"reassociate chain {}: {} → {} lane slots", fe::Join(dims,
"×"), orig_cost.str(), cost.str());
373 return build(
rewrite(head), mats, dims, split, 0, n - 1);
378 if (
auto res = reassoc(app))
return res;
auto args(Projector auto f) const noexcept
static auto isa(const Def *def)
static std::optional< T > isa(const Def *def)
const fe::Log & log() const
World & new_world()
Create new Defs into this.
void start() override
Actual entry.
World & old_world()
Get old Defs from here.
const Def * rewrite_imm_App(const App *) override
virtual const Def * rewrite(const Def *)
const Def * rewrite_imm_App(const App *) final
void start() override
Actual entry.
fe::Vector< Split > Splits
A bracketing of a matrix chain, innermost node first.
fe::Vector< Leaf > Leaves
A chain operand; the chain reads its transpose if t.
One node of a bracketing: i … j splits after s.
DefMap< u64 > count_consumers(const World &world, std::predicate< const Def * > auto pred)
Counts the consumers of every def of world matched by pred.
const Def * isa_transpose_2d(const Def *def)
The input of a 2-D transpose transpose (1, 0), or nullptr.
fe::View< const Def * > Defs
fe::Vector< const Def * > DefVec