#include "gpu/intel/conv/jit/v2/builder.hpp"
#include "gemmstone/../../dsl/ir/ir.hpp"
#include "gemmstone/../../dsl/ir/pass/dpas.hpp"
#include "gemmstone/../../dsl/ir/pass/trace.hpp"
#include "gpu/intel/conv/jit/v2/bridge.hpp"
#include "gpu/intel/conv/jit/v2/plan.hpp"
#include "gpu/intel/jit/ir/builder.hpp"
#include "gpu/intel/jit/ir/eltwise.hpp"
#include "gpu/intel/jit/ir/post_ops.hpp"
#include "gpu/intel/jit/ir/v2/builder.hpp"
#include "gpu/intel/jit/ir/v2/send.hpp"
#include "gpu/intel/jit/pass/pass.hpp"
#include "gpu/intel/logging.hpp"
namespace dnnl {
namespace impl {
namespace gpu {
namespace intel {
namespace conv {
namespace jit {
namespace v2 {
class iterator_t {
public:
iterator_t() = default;
iterator_t(buffer_manager_t &buf_mgr, const loop_nest_t &loop_nest)
: loop_nest_(loop_nest) {
linear_idx_ = loop_index_t(buf_mgr);
for (size_t i = 0; i < loop_nest.nloops(); i++) {
loop_idxs_.emplace_back(buf_mgr);
}
}
int nloops() const { return (int)loop_nest_.nloops(); }
stmt_t init_stmt() const {
stmt_t ret;
for (int i = 0; i < nloops(); i++) {
ret = ret.append(loop_idxs_[i].store(loop_nest_[i].init));
}
ret = linear_idx_.store(loop_nest_.linear_bound() - 1).append(ret);
return ret;
}
stmt_t check_bounds_stmt(const stmt_t &body) const {
return if_t::make(linear_idx_.var() >= 0, body);
}
stmt_t inc_stmt(const offset_ctx_t &off_ctx) const {
stmt_t body;
for (int i = nloops() - 1; i >= 0; i--) {
stmt_t stmt;
if (i - 1 >= 0) stmt = stmt.append(loop_idxs_[i - 1].store(0));
stmt = stmt.append(loop_idxs_[i].inc_stmt());
stmt = stmt.append(off_ctx.inc_loop_stmt(i));
if (i + 1 < nloops())
stmt = stmt.append(if_t::make(
loop_idxs_[i].var() >= loop_nest_[i].bound, body));
body = std::move(stmt);
}
body = linear_idx_.inc_stmt(-1).append(body);
return body;
}
private:
struct loop_index_t {
expr_t buf;
loop_index_t() = default;
loop_index_t(buffer_manager_t &buf_mgr) {
auto buf_name = buf_mgr.ir_ctx().create_tmp_name("i");
buf = buf_mgr.get(buf_name, sizeof(int32_t));
}
stmt_t store(const expr_t &value) const {
return store_t::make(buf, 0, value);
}
stmt_t inc_stmt(int inc = 1) const { return store(var() + inc); }
expr_t var() const { return load_t::make(dsl::type_t::s32(), buf, 0); }
};
loop_nest_t loop_nest_;
std::vector<loop_index_t> loop_idxs_;
loop_index_t linear_idx_;
};
dsl::type_t to_send_type(const send_1d_desc_t &desc) {
if (desc.type_size <= 8) return dsl::type_t::u(desc.type_size * 8);
return dsl::type_t::oword(desc.type_size / 16);
}
int get_reg_off(const send_1d_plan_t &plan, const icoord_t &coord) {
return into<int>(plan.reg_layout.offset_in_bytes(coord));
}
class idiv_fixup_mutator_t : public ir_mutator_t {
public:
idiv_fixup_mutator_t(var_manager_t &var_mgr) : var_mgr_(var_mgr) {}
object_t _mutate(const binary_op_t &_obj) override {
auto new_obj = ir_mutator_t::_mutate(_obj);
auto &obj = new_obj.as<binary_op_t>();
bool is_var_idivmod
= utils::one_of(obj.op_kind, op_kind_t::_div, op_kind_t::_mod)
&& obj.type.is_int() && !is_const(obj.b);
if (!is_var_idivmod) return new_obj;
auto magic = var_mgr_.get_idiv_magic(obj.b);
auto i_op = (obj.op_kind == op_kind_t::_div ? op_kind_t::_idiv
: op_kind_t::_imod);
return ternary_op_t::make(
i_op, obj.a, cast(obj.b, dsl::type_t::u32()), magic);
}
private:
var_manager_t &var_mgr_;
};
stmt_t fixup_idiv(
const stmt_t &s, var_manager_t &var_mgr, ir_context_t &ir_ctx) {
ir::trace_start();
auto ret = idiv_fixup_mutator_t(var_mgr).mutate(s);
ir::trace_pass("fixup_idiv", ret, ir_ctx);
return ret;
}
class var_replacer_t : public ir_mutator_t {
public:
var_replacer_t(var_manager_t &var_mgr) : var_mgr_(var_mgr) {}
object_t _mutate(const var_t &obj) override {
return map_var(obj.name, obj, false);
}
object_t _mutate(const const_var_t &obj) override {
return map_var(obj.name, obj, true);
}
object_t _mutate(const binary_op_t &obj) override {
switch (obj.op_kind) {
case op_kind_t::_div_up: return mutate((obj.a + obj.b - 1) / obj.b);
default: return ir_mutator_t::_mutate(obj);
}
}
private:
expr_t map_var(
const std::string &name, const expr_t &var, bool is_const_var) {
auto it = var_map_.find(var);
if (it != var_map_.end()) return it->second;
expr_t arg;
if (!is_const_var) {
arg = var_mgr_.get_arg(name, true);
} else {
arg = var_mgr_.get_arg(var.type(), name);
}
auto value = (arg.is_empty() ? var : arg);
var_map_.emplace(var, value);
return value;
}
var_manager_t &var_mgr_;
object_map_t<expr_t, expr_t> var_map_;
};
stmt_t finalize_vars(
const stmt_t &stmt, var_manager_t &var_mgr, ir_context_t &ir_ctx) {
auto ret = var_replacer_t(var_mgr).mutate(stmt);
ret = inject_external_var_let(ret, ir_ctx);
return ret;
}
struct stream_k_params_t {
bool enable = false;
expr_t total_iters;
expr_t iters_per_tg;
expr_t iters_per_tile;
expr_t k_batches;
expr_t tg_idx;
expr_t k_batch_idx;
expr_t tg_beg;
expr_t tg_end;
expr_t tile_idx;
expr_t local_beg;
expr_t local_end;
std::vector<expr_t> loop_inits;
stream_k_params_t() = default;
stream_k_params_t(bool enable, const loop_desc_t &loop_desc)
: enable(enable) {
if (!enable) return;
local_beg = var_t::make(dsl::type_t::s32(), "local_beg");
local_end = var_t::make(dsl::type_t::s32(), "local_end");
for (auto &e : loop_desc) {
loop_inits.push_back(
var_t::make(dsl::type_t::s32(), e.dim.str() + "_init"));
}
}
operator bool() const { return enable; }
};
loop_nest_t make_loop_nest(const loop_desc_t &loop_desc,
const coord_info_t &coord_info, const stream_k_params_t &sk_params) {
loop_nest_t ret;
expr_t linear_bound = 1;
for (auto &e : loop_desc) {
const auto &var = coord_info.loop_index(e.dim);
const auto &size = coord_info.loop_size(e.dim);
if (size.is(1)) continue;
expr_t init = 0;
if (sk_params) {
init = sk_params.loop_inits[e.idx];
} else {
linear_bound *= size;
}
ret.add_loop(e.dim, var, init, size);
}
if (sk_params) linear_bound = sk_params.local_end - sk_params.local_beg;
ret.set_linear_bound(linear_bound);
return ret;
}
class buffer_info_t {
public:
buffer_info_t(buffer_manager_t &buf_mgr, const kernel_desc_t &desc,
const var_manager_t &var_mgr, const x2r_fma_plan_t &plan) {
for (auto &s : plan.stages) {
if (!s.is_x2r()) continue;
auto kind = s.x2r.tensor_kind;
std::string name = pick_abc(kind, desc.prop, "src", "wei", "dst");
if (entries_.count(name) > 0) continue;
auto &e = entries_[name];
e.mem_buf = var_mgr.get_arg(name);
if (s.x2r.reorder) {
e.reg_buf = buf_mgr.get(
to_string(kind), s.x2r.reorder.dst.size());
} else {
e.reg_buf = buf_mgr.get(
to_string(kind), s.x2r.load.reg_layout().size());
}
}
auto c_name = pick_c(desc.prop, "src", "wei", "dst");
auto &c_e = entries_[c_name];
c_e.mem_buf = var_mgr.get_arg(c_name);
c_e.reg_buf = buf_mgr.get("c", plan.c_layout.size());
for (auto &abc :
{tensor_kind_t::a, tensor_kind_t::b, tensor_kind_t::c}) {
auto name = pick_abc(abc, desc.prop, "src", "wei", "dst");
entries_[to_string(abc)] = entries_.at(name);
}
if (desc.with_bias_fwd() || desc.with_bias_bwd_w()) {
auto &e = entries_["bias"];
e.mem_buf = var_mgr.get_arg("bias");
if (!plan.bias_layout.is_empty())
e.reg_buf
= buf_mgr.get("bias_reduced", plan.bias_layout.size());
}
arg_helper_t arg_helper(desc);
for (int arg : {DNNL_ARG_SRC, DNNL_ARG_WEIGHTS, DNNL_ARG_DST}) {
if (desc.scales.has_default_values(arg)) continue;
auto name = arg_helper.scales_name(arg);
auto &e = entries_[name];
e.mem_buf = var_mgr.get_arg(name);
}
for (size_t i = 0; i < desc.post_ops.len(); i++) {
auto &po = desc.post_ops[i];
if (po.is_binary()) {
auto name = arg_helper.post_op_name(i);
auto &e = entries_[name];
e.mem_buf = var_mgr.get_arg(name);
}
}
}
expr_t mem_buf(const std::string &name) const {
if (entries_.count(name) == 0) return expr_t();
auto &e = entries_.at(name);
return e.mem_buf;
}
expr_t reg_buf(const std::string &name) const {
if (entries_.count(name) == 0) return expr_t();
auto &e = entries_.at(name);
return e.reg_buf;
}
private:
struct buf_entry_t {
expr_t mem_buf;
expr_t reg_buf;
};
std::unordered_map<std::string, buf_entry_t> entries_;
};
class x2r_mul_builder_t : public v2::ir_builder_t {
public:
x2r_mul_builder_t(ir_builder_t &parent, const loop_nest_t &loop_nest,
const buffer_info_t &buf_info, const kernel_desc_t &desc,
const x2r_fma_plan_t &plan)
: ir_builder_t(parent, loop_nest)
, buf_info_(buf_info)
, desc_(desc)
, plan_(plan) {
for (auto &s : plan_.stages) {
if (s.is_fma()) {
build_mul(s.fma);
} else if (s.is_x2r()) {
build_x2r(s.x2r);
if (s.x2r.tensor_kind == tensor_kind_t::b) {
uint32_t mask = (1 << 1) | (1 << 2);
const auto &b_buf = buf_info_.reg_buf("b");
if (!s.x2r.bias_layout.is_empty()) {
reduce(s.x2r.layout, s.x2r.bias_layout, b_buf,
buf_info_.reg_buf("bias"), mask);
}
}
}
}
}
private:
void build_x2r(const x2r_plan_t &plan) {
const auto &mem_buf = buf_info_.mem_buf(to_string(plan.tensor_kind));
const auto ®_buf = buf_info_.reg_buf(to_string(plan.tensor_kind));
auto load_buf
= load(plan.load, mem_buf, (plan.reorder ? expr_t() : reg_buf));
if (plan.reorder) reorder(plan.reorder, load_buf, reg_buf);
}
void build_mul(const fma_plan_t &fma) {
auto &a_layout = fma.a_layout;
auto &b_layout = fma.b_layout;
auto &c_layout = fma.c_layout;
const auto &a_buf = buf_info_.reg_buf("a");
const auto &b_buf = buf_info_.reg_buf("b");
const auto &c_buf = buf_info_.reg_buf("c");
tile_t sizes = a_layout.int_dim_sizes();
auto b_sizes = b_layout.int_dim_sizes();
for (auto &d : b_sizes) {
if (sizes.has(d)) gpu_assert(sizes[d] == b_sizes[d]);
sizes[d] = b_sizes[d];
}
std::vector<pvar_t> dim_order;
for (const auto &bmnk : {pvars::m, pvars::n, pvars::k, pvars::b}) {
for (auto &d : sizes) {
if (to_gemm(d, desc_.prop) != bmnk) continue;
dim_order.push_back(d);
}
}
auto bmnk_inst_tile = to_gemm(fma.inst_tile, desc_.prop);
dim_t B = bmnk_inst_tile.get(pvars::b, 1);
dim_t M = bmnk_inst_tile.get(pvars::m, 1);
dim_t N = bmnk_inst_tile.get(pvars::n, 1);
dim_t K = bmnk_inst_tile.get(pvars::k, 1);
bool is_a_bcast = (B * M * K == 1);
bool is_b_bcast = (B * K * N == 1);
func_t fma_func;
switch (fma.fma) {
case fma_kind_t::mad: {
int a_stride = is_a_bcast ? 0 : a_layout.inner_stride();
int b_stride = is_b_bcast ? 0 : b_layout.inner_stride();
fma_func = mad_t::make(plan_.hw, c_layout.type(), fma.simd,
a_layout.type(), a_stride, b_layout.type(), b_stride);
break;
}
case fma_kind_t::dpas: {
fma_func = dpas_t::make(false, fma.simd, 8, 8,
c_layout.type(), b_layout.type(), a_layout.type());
break;
}
default: gpu_error_not_expected();
}
stmt_t call_stmt;
for_each(sizes, fma.inst_tile, dim_order, [&](const icoord_t &coord) {
dim_t a_off = a_layout.offset_in_bytes(coord);
dim_t b_off = b_layout.offset_in_bytes(coord);
dim_t c_off = c_layout.offset_in_bytes(coord);
auto dst = c_buf[c_off];
auto src1 = a_buf[a_off];
auto src2 = b_buf[b_off];
if (fma.fma == fma_kind_t::dpas) std::swap(src1, src2);
call_stmt = call_stmt.append(fma_func.call(
{dst, dst, std::move(src1), std::move(src2)}));
});
if (fma.fma == fma_kind_t::dpas) {
call_stmt
= inject_dpas_atomic(call_stmt, false);
}
emit(call_stmt);
}
const buffer_info_t &buf_info_;
const kernel_desc_t &desc_;
const x2r_fma_plan_t &plan_;
};
class prefetch_builder_t : public v2::ir_builder_t {
public:
prefetch_builder_t(ir_builder_t &parent, const loop_nest_t &loop_nest,
const buffer_info_t &buf_info, const prefetch_plan_t &plan)
: ir_builder_t(parent, loop_nest), buf_info_(buf_info), plan_(plan) {
if (plan_.a_prefetch) {
load(plan_.a_prefetch, buf_info_.mem_buf("a"), expr_t());
}
if (plan_.b_prefetch) {
load(plan_.b_prefetch, buf_info_.mem_buf("b"), expr_t());
}
}
private:
const buffer_info_t &buf_info_;
const prefetch_plan_t &plan_;
};
class post_op_builder_t : public v2::ir_builder_t {
public:
post_op_builder_t(ir_builder_t &parent, const kernel_desc_t &desc,
const coord_t &coord, const tile_t &tile, alg_kind_t binary_alg,
const gpu_post_ops_t::entry_t *post_op_entry,
const v2::layout_t &lhs_reg_layout, const expr_t &lhs_reg_buf,
const expr_t &rhs_mem_buf, const dsl::type_t &_rhs_type,
uint16_t rhs_mask, float rhs_scale, int rhs_zero_point)
: ir_builder_t(parent, loop_nest_t()), desc_(desc) {
if (!rhs_mem_buf.is_empty()) {
auto &c_tag = pick_c(
desc_.prop, desc_.src_tag, desc_.wei_tag, desc_.dst_tag);
auto rhs_type = _rhs_type.is_undef() ? c_tag.type() : _rhs_type;
auto rhs_view = rhs_mem_view(coord, tile, rhs_type, rhs_mask);
v2::layout_t rhs_reg_layout;
auto rhs_reg_buf
= load(rhs_view, rhs_mem_buf, expr_t(), &rhs_reg_layout);
build_binary_post_op(binary_alg, lhs_reg_layout, rhs_reg_layout,
lhs_reg_buf, rhs_reg_buf, rhs_scale, rhs_zero_point);
return;
}
auto &e = post_op_entry->as_eltwise();
auto func = eltwise_t::make(e.alg, e.scale, e.alpha, e.beta);
emit(func.call({expr_t(lhs_reg_layout.elems()), lhs_reg_buf}));
}
private:
v2::view_t rhs_mem_view(const coord_t &_coord, const tile_t &_tile,
const dsl::type_t &type, uint16_t mask) {
dim_mapper_manager_t mger(desc_.prop, desc_.spec.reqs());
auto &c_mapper = mger.mapper(tensor_kind_t::c);
auto kind = pick_c(desc_.prop, tensor_kind_t::src, tensor_kind_t::wei,
tensor_kind_t::dst);
auto &c_tag = pick_c(
desc_.prop, desc_.src_tag, desc_.wei_tag, desc_.dst_tag);
auto rhs_layout = make_layout(
kind, c_tag, desc_.is_dw, desc_.spec.reqs(), mask);
rhs_layout = rhs_layout.retype(type);
auto is_bcast = [&](const pvar_t &dim) {
for (auto &b : rhs_layout.blocks()) {
if (b.dim == dim) return false;
}
return true;
};
auto coord = _coord;
auto tile = _tile;
for (auto &d : rhs_layout.desc().letter_map()) {
if (is_bcast(d)) {
if (tile.has(d)) tile.unset(d);
if (coord.has(d)) coord.unset(d);
}
}
return v2::view_t(c_mapper, rhs_layout, coord, tile);
}
void build_binary_post_op(alg_kind_t alg, const v2::layout_t &lhs,
const v2::layout_t &_rhs, const expr_t &lhs_buf,
const expr_t &_rhs_buf, float scale = 1, int zero_point = 0) {
gpu_assert(lhs.type() == dsl::type_t::f32());
auto rhs = _rhs;
auto rhs_buf = _rhs_buf;
if (rhs.type() != dsl::type_t::f32()) {
auto rhs_f32 = _rhs.retype(dsl::type_t::f32(), true);
rhs_buf = reorder(_rhs, rhs_f32, _rhs_buf);
rhs = std::move(rhs_f32);
}
if (zero_point != 0) {
auto func = eltwise_t::make(
alg_kind::eltwise_linear, 1.f, 1.f, -(float)zero_point);
emit(func.call({expr_t(rhs.elems()), rhs_buf}));
}
if (scale != 1) {
auto func
= eltwise_t::make(alg_kind::eltwise_linear, 1, scale, 0.0f);
emit(func.call({expr_t(rhs.elems()), rhs_buf}));
}
gpu_assert(lhs.nblocks() > 0);
int max_simd = (2 * desc_.hw_desc.grf_size()) / sizeof(float);
auto &lhs0 = lhs.blocks()[0];
int elems = math::gcd(max_simd, lhs0.int_size());
bool is_bcast = !rhs.dim_sizes().has(lhs0.dim);
if (!is_bcast) {
auto &rhs0 = rhs.blocks()[0];
if (rhs0.dim == lhs0.dim) {
elems = math::gcd(elems, rhs0.int_size());
} else {
elems = 1;
}
}
elems = (elems < 8 ? 1 : elems);
tile_t tile;
tile[lhs0.dim] = elems;
for_each(lhs.int_dim_sizes(), tile, [&](const icoord_t &coord) {
auto lhs_off = lhs.offset_in_bytes(coord);
auto rhs_off = rhs.offset_in_bytes(coord);
auto e_l = load_t::make(
dsl::type_t::f32().with_elems(elems), lhs_buf, lhs_off);
auto e_r = load_t::make(
dsl::type_t::f32().with_elems(is_bcast ? 1 : elems),
rhs_buf, rhs_off);
if (is_bcast) e_r = shuffle_t::make_broadcast(e_r, elems);
auto e_op = binary_op_t::make(alg_kind_to_op_kind(alg), e_l, e_r);
if (e_op.type().is_bool()) {
e_op = cast(e_op, lhs.type().with_elems(elems));
}
emit(store_t::make(lhs_buf, lhs_off, e_op));
});
}
const kernel_desc_t &desc_;
};
class epilogue_tile_builder_t : public v2::ir_builder_t {
public:
epilogue_tile_builder_t(ir_builder_t &parent, const buffer_info_t &buf_info,
const kernel_desc_t &desc, const v2::layout_t &c_layout,
const expr_t &c_mem_buf, const expr_t &c_reg_buf,
const coord_t &c_coord, const icoord_t &coord,
const epilogue_store_plan_t &store_plan)
: ir_builder_t(parent, loop_nest_t())
, buf_info_(buf_info)
, desc_(desc) {
dim_t off = c_layout.offset_in_bytes(coord);
auto store_layout
= store_plan.c_store.reg_layout().sub(store_plan.tile);
v2::layout_t payload_layout = store_layout;
auto payload_buf = build_post_ops(c_layout.sub(store_plan.tile),
c_coord + coord, c_reg_buf + off, payload_layout);
payload_buf = reorder(payload_layout, store_layout, payload_buf);
store(store_plan.c_store, c_mem_buf, payload_buf, coord,
store_plan.tile);
}
private:
static uint16_t reverse_post_op_mask(uint16_t mask, int ndims) {
uint16_t ret = 0;
for (int i = 0; i < ndims; i++) {
uint16_t bit = (mask >> (ndims - i - 1)) & 0x1;
ret |= bit << i;
}
return ret;
}
void build_post_op(const coord_t &coord, const tile_t &tile,
alg_kind_t binary_alg, const gpu_post_ops_t::entry_t *post_op_entry,
const v2::layout_t &lhs_reg_layout, const expr_t &lhs_reg_buf,
const expr_t &rhs_mem_buf = expr_t(),
const dsl::type_t &rhs_type = dsl::type_t::undef(),
uint16_t rhs_mask = 0, float rhs_scale = 1.0f,
int rhs_zero_point = 0) {
post_op_builder_t builder(*this, desc_, coord, tile, binary_alg,
post_op_entry, lhs_reg_layout, lhs_reg_buf, rhs_mem_buf,
rhs_type, rhs_mask, rhs_scale, rhs_zero_point);
emit(builder.get_init_stmt());
emit(builder.get_stmt());
}
expr_t build_post_ops(const v2::layout_t &layout, const coord_t &coord,
const expr_t &_buf, v2::layout_t &out_layout) {
if (desc_.post_ops.len() == 0 && desc_.scales.has_default_values()
&& !desc_.with_bias_fwd()) {
out_layout = layout;
return _buf;
}
auto f32_layout = out_layout.retype(dsl::type_t::f32(), true);
auto tile = f32_layout.int_dim_sizes();
int elems = f32_layout.elems();
gpu_assert(elems * dsl::type_t::f32().size() == f32_layout.size());
auto buf = reorder(layout, f32_layout, _buf);
arg_helper_t arg_helper(desc_);
auto &c_tag = pick_c(
desc_.prop, desc_.src_tag, desc_.wei_tag, desc_.dst_tag);
auto to_rhs_mask = [](int arg, int mask) {
if (mask == 0) return 0;
switch (arg) {
case DNNL_ARG_WEIGHTS:
gpu_assert(mask == ((1 << 0) | (1 << 1)))
<< "Only per-output channels scales are supported, "
"mask: "
<< mask;
return (1 << 1);
case DNNL_ARG_DST: return mask;
default: gpu_error_not_expected();
}
return 0;
};
auto build_scale = [&](int arg) {
if (desc_.scales.has_default_values(arg)) return;
auto rhs_buf_name = arg_helper.scales_name(arg);
auto mask = desc_.scales.get_mask(arg);
auto data_type = desc_.scales.get_data_type(arg);
alg_kind_t alg = (arg == DNNL_ARG_DST ? alg_kind::binary_div
: alg_kind::binary_mul);
build_post_op(coord, tile, alg, nullptr, f32_layout, buf,
buf_info_.mem_buf(rhs_buf_name), to_ir(data_type),
into<uint16_t>(to_rhs_mask(arg, mask)));
};
build_scale(DNNL_ARG_SRC);
build_scale(DNNL_ARG_WEIGHTS);
if (desc_.with_bias_fwd()) {
build_post_op(coord, tile, alg_kind::binary_add, nullptr,
f32_layout, buf, buf_info_.mem_buf("bias"), desc_.bias_type,
0x2);
}
for (size_t i = 0; i < desc_.post_ops.len(); i++) {
auto &po = desc_.post_ops[i];
if (po.is_eltwise()) {
build_post_op(
coord, tile, alg_kind::undef, &po, f32_layout, buf);
} else if (po.is_sum()) {
auto &s = po.as_sum();
build_post_op(coord, tile, alg_kind::binary_add, &po,
f32_layout, buf, buf_info_.mem_buf("c"), to_ir(s.dt),
0xFFFF, s.scale, s.zero_point);
} else if (po.is_binary()) {
auto &b = po.as_binary();
auto rhs_buf_name = arg_helper.post_op_name(i);
auto mask = reverse_post_op_mask(
b.src1_desc.broadcast_mask ^ 0xFFFF,
c_tag.raw_tag().ndims());
build_post_op(coord, tile, b.alg, &po, f32_layout, buf,
buf_info_.mem_buf(rhs_buf_name), to_ir(b.src1_desc.dt),
mask);
} else {
gpu_error_not_expected();
}
}
build_scale(DNNL_ARG_DST);
out_layout = std::move(f32_layout);
return buf;
}
const buffer_info_t &buf_info_;
const kernel_desc_t &desc_;
};
class epilogue_builder_t : public v2::ir_builder_t {
public:
epilogue_builder_t(ir_builder_t &parent, const buffer_info_t &buf_info,
const kernel_desc_t &desc, const epilogue_plan_t &plan)
: ir_builder_t(parent, loop_nest_t())
, buf_info_(buf_info)
, desc_(desc)
, plan_(plan) {
build_slm_reduce();
build_c_store();
build_bias_reduce_store();
}
private:
void build_slm_reduce() {
auto &slm_reduce = plan_.slm_reduce;
if (!slm_reduce) return;
const auto &c_buf = buf_info_.reg_buf("c");
auto c_tmp_buf = alloc("c_reduce", slm_reduce.load.reg_layout().size());
auto c_slm_buf = alloc("slm", slm_reduce.slm_usage_bytes());
store(slm_reduce.store, c_slm_buf, c_buf);
barrier();
load(slm_reduce.load, c_slm_buf, c_tmp_buf);
zero_out(c_buf);
reduce(slm_reduce.reduce, c_tmp_buf, c_buf);
}
void build_c_store() {
auto &store_plan = plan_.store;
auto &c_layout = plan_.c_reg_layout;
const auto &c_mem_buf = buf_info_.mem_buf("c");
const auto &c_reg_buf = buf_info_.reg_buf("c");
for_each(c_layout.int_dim_sizes(), store_plan.tile,
[&](const icoord_t &coord) {
epilogue_tile_builder_t builder(*this, buf_info_, desc_, c_layout,
c_mem_buf, c_reg_buf, plan_.c_coord, coord, store_plan);
emit(builder.get_init_stmt());
emit(builder.get_stmt());
});
}
void build_bias_reduce_store() {
if (plan_.bias_layout.is_empty()) return;
auto &store_plan = plan_.store;
const auto &bias_red_mem_buf = buf_info_.mem_buf("bias");
const auto &bias_red_reg_buf = buf_info_.reg_buf("bias");
expr_t tmp_buf;
if (store_plan.bias_reorder)
tmp_buf = alloc("bias_tmp", store_plan.bias_reorder.dst.size());
auto payload_buf = bias_red_reg_buf;
if (store_plan.bias_reorder) {
reorder(store_plan.bias_reorder, bias_red_reg_buf, tmp_buf);
payload_buf = std::move(tmp_buf);
}
_if(plan_.bias_reduce_cond, [&]() {
store(store_plan.bias_store, bias_red_mem_buf, payload_buf);
});
}
const buffer_info_t &buf_info_;
const kernel_desc_t &desc_;
const epilogue_plan_t &plan_;
};
class builder_t : public v2::ir_builder_t {
public:
builder_t(ir_context_t &ir_ctx, const kernel_desc_t &desc,
var_manager_t &var_mgr, const plan_t &plan)
: ir_builder_t(ir_ctx)
, desc_(desc)
, var_mgr_(var_mgr)
, plan_(plan)
, buf_info_(buf_mgr(), desc, var_mgr, plan.x2r_fma) {
stream_k_params_t sk_params(desc.use_stream_k, desc_.loop_desc);
emit_thread_index_let();
if (desc.use_stream_k) {
sk_params.tg_idx = plan_.tg_grid.index_var(0);
sk_params.k_batch_idx = plan_.tg_grid.index_var(1);
auto total_iters_main = const_var_t::make(
dsl::type_t::s32(), "sk_total_iters_main");
auto total_iters_tail = const_var_t::make(
dsl::type_t::s32(), "sk_total_iters_tail");
auto iters_per_tg_main = const_var_t::make(
dsl::type_t::s32(), "sk_iters_per_tg_main");
auto iters_per_tg_tail = const_var_t::make(
dsl::type_t::s32(), "sk_iters_per_tg_tail");
auto iters_per_tg_main_magic = const_var_t::make(
dsl::type_t::u64(), "sk_iters_per_tg_main_magic");
auto iters_per_tg_tail_magic = const_var_t::make(
dsl::type_t::u64(), "sk_iters_per_tg_tail_magic");
auto iters_per_tile_main = const_var_t::make(
dsl::type_t::s32(), "sk_iters_per_tile_main");
auto iters_per_tile_tail = const_var_t::make(
dsl::type_t::s32(), "sk_iters_per_tile_tail");
auto iters_per_tile_main_magic = const_var_t::make(
dsl::type_t::u64(), "sk_iters_per_tile_main_magic");
auto iters_per_tile_tail_magic = const_var_t::make(
dsl::type_t::u64(), "sk_iters_per_tile_tail_magic");
sk_params.k_batches
= const_var_t::make(dsl::type_t::s32(), "sk_k_batches");
auto cond = (sk_params.k_batch_idx == sk_params.k_batches - 1);
sk_params.total_iters = let("sk_total_iters",
iif_t::make(cond, total_iters_tail, total_iters_main));
sk_params.iters_per_tg = let("sk_iters_per_tg",
iif_t::make(cond, iters_per_tg_tail, iters_per_tg_main));
sk_params.iters_per_tile = let("sk_iters_per_tile",
iif_t::make(
cond, iters_per_tile_tail, iters_per_tile_main));
auto iters_per_tg_magic = let("sk_iters_per_tg_magic",
iif_t::make(cond, iters_per_tg_tail_magic,
iters_per_tg_main_magic));
auto iters_per_tile_magic = let("sk_iters_per_tile_magic",
iif_t::make(cond, iters_per_tile_tail_magic,
iters_per_tile_main_magic));
auto iter = alloc_var(dsl::type_t::s32(), "sk_iter");
iter = sk_params.tg_idx * sk_params.iters_per_tg;
auto iter_end = let("sk_iter_end",
min(sk_params.total_iters, iter + sk_params.iters_per_tg));
_while(iter < iter_end, [&]() {
sk_params.tile_idx = let("sk_tile_idx",
ternary_idiv(iter,
cast(sk_params.iters_per_tile,
dsl::type_t::u32()),
iters_per_tile_magic));
auto global_beg = let("sk_global_beg",
sk_params.tile_idx * sk_params.iters_per_tile);
auto global_end = let(
"sk_global_end", global_beg + sk_params.iters_per_tile);
let(sk_params.local_beg,
iter - global_beg
+ sk_params.k_batch_idx * iters_per_tile_main);
let(sk_params.local_end,
min(iter_end, global_end) - global_beg
+ sk_params.k_batch_idx * iters_per_tile_main);
sk_params.tg_beg = let("sk_tg_beg",
ternary_idiv(global_beg,
cast(sk_params.iters_per_tg,
dsl::type_t::u32()),
iters_per_tg_magic));
sk_params.tg_end = let("sk_tg_beg",
ternary_idiv(global_beg - 1,
cast(sk_params.iters_per_tg,
dsl::type_t::u32()),
iters_per_tg_magic)
+ 1);
emit_thread_group_index_let(sk_params.tile_idx);
pipeline(sk_params);
epilogue();
iter = global_end;
});
} else {
emit_thread_group_index_let();
pipeline();
epilogue();
}
auto _stmt = get_stmt();
_stmt = inject_alloc_stmts(_stmt, buf_mgr());
_stmt = inject_dangling_let_stmts(_stmt);
_stmt = off_scope().inject_let_stmts(_stmt);
_stmt = inject_global_alloc(_stmt);
_stmt = fixup_idiv(_stmt, var_mgr, ir_ctx);
_stmt = finalize_vars(_stmt, var_mgr, ir_ctx);
_stmt = merge_slm_buffers(_stmt, ir_ctx);
_stmt = inject_slm_reorder(_stmt, ir_ctx,
to_grid_info(plan_.thr_grid, desc_.thread_group_tile),
(bool)plan_.epilogue.slm_reduce);
_stmt = inject_send(_stmt, ir_ctx);
_stmt = simplify(_stmt, ir_ctx);
_stmt = optimize_alloc_let(_stmt, ir_ctx);
_stmt = split_wide_stores(_stmt, ir_ctx);
_stmt = fixup_if_conditions(_stmt, ir_ctx);
_stmt = eliminate_common_subexprs(_stmt, ir_ctx, 16, 0);
_stmt = inject_bank_conflict_attribute(_stmt, ir_ctx);
set_stmt(_stmt);
}
private:
void pipeline(const stream_k_params_t &sk_params = {}) {
auto &loop_desc = desc_.loop_desc;
auto &coord_info = plan_.coord_info;
auto value = sk_params.local_beg;
if (sk_params) {
for (auto &e : loop_desc) {
auto &size = coord_info.loop_size(e.dim);
let(sk_params.loop_inits[e.idx], value % size);
value /= size;
}
}
auto loop_nest = make_loop_nest(loop_desc, coord_info, sk_params);
prefetch_builder_t prefetch_builder(
*this, loop_nest, buf_info_, plan_.prefetch);
x2r_mul_builder_t x2r_mul_builder(
*this, loop_nest, buf_info_, desc_, plan_.x2r_fma);
zero_out(buf_info_.reg_buf("c"));
if (buf_info_.reg_buf("bias")) zero_out(buf_info_.reg_buf("bias"));
emit(x2r_mul_builder.get_init_stmt());
emit(prefetch_builder.get_init_stmt());
int prefetch_dist = desc_.prefetch.dist;
auto x2r_mul_stmt = x2r_mul_builder.get_stmt();
auto prefetch_stmt = prefetch_builder.get_stmt();
iterator_t prefetch_it;
if (prefetch_dist > 0) {
prefetch_it = iterator_t(buf_mgr(), loop_nest);
emit(prefetch_it.init_stmt());
for (int i = 0; i < prefetch_dist; i++) {
auto i_prefetch_stmt = prefetch_stmt;
if (i > 0) {
i_prefetch_stmt
= prefetch_it.check_bounds_stmt(i_prefetch_stmt);
}
emit(i_prefetch_stmt);
emit(prefetch_it.inc_stmt(prefetch_builder.off_ctx()));
}
}
if (desc_.use_stream_k) {
iterator_t mul_it(buf_mgr(), loop_nest);
emit(mul_it.init_stmt());
auto it_var = var_t::make(dsl::type_t::s32(), "sk_local_iter");
_for(it_var, 0, loop_nest.linear_bound(), [&]() {
if (prefetch_dist > 0) {
emit(prefetch_it.check_bounds_stmt(prefetch_stmt));
}
emit(x2r_mul_stmt);
emit(mul_it.inc_stmt(x2r_mul_builder.off_ctx()));
if (prefetch_dist > 0) {
emit(prefetch_it.inc_stmt(prefetch_builder.off_ctx()));
}
});
} else {
std::function<void(size_t)> emit_loop;
emit_loop = [&](size_t i) {
if (i == 0) {
if (prefetch_dist > 0) {
emit(prefetch_it.check_bounds_stmt(prefetch_stmt));
}
emit(x2r_mul_stmt);
if (prefetch_dist > 0) {
emit(prefetch_it.inc_stmt(prefetch_builder.off_ctx()));
}
return;
}
auto &loop = loop_nest[i - 1];
const auto &var = coord_info.loop_index(loop.dim);
_for(var, 0, loop.bound, [&]() {
emit_loop(i - 1);
emit(x2r_mul_builder.off_ctx().inc_loop_stmt(
(int)loop.idx));
});
};
emit_loop(loop_nest.nloops());
}
}
void epilogue() {
epilogue_builder_t builder(*this, buf_info_, desc_, plan_.epilogue);
emit(builder.get_init_stmt());
emit(builder.get_stmt());
}
stmt_t inject_global_alloc(const stmt_t &stmt) const {
std::vector<stmt_t> allocs;
for (auto &var : var_mgr_.ptr_args()) {
allocs.push_back(alloc_t::make(var, 0, alloc_kind_t::global));
}
return inject_alloc_stmts(stmt, allocs);
}
void emit_thread_index_let() {
for (int i = 0; i < 3; i++) {
auto value = var_t::make(
dsl::type_t::u16(), jit::ir::local_id_name(i));
if (i == 0) value /= plan_.desc.simd;
auto thr_idx = plan_.thr_grid.index_var(i);
let(thr_idx, cast(value, thr_idx.type()));
}
for (auto &kv : plan_.virt_grid.idxs()) {
let(kv.first, kv.second);
}
}
expr_t unpack_tg_index(const pvar_t &dim, const expr_t &base_idx) const {
auto &tg_grid = plan_.tg_grid;
expr_t value = base_idx;
auto &dims = tg_grid.dims(tg_grid.index(dim));
int ndims = (int)dims.size();
for (int i = 0; i < ndims; i++) {
if (dims[i] == dim) {
if (i == ndims - 1) return value;
break;
}
auto grid_size = var_mgr_.get_grid_size(dims[i].str());
value = value / grid_size;
}
auto grid_size = var_mgr_.get_grid_size(dim.str());
value = value % grid_size;
return value;
}
void emit_thread_group_index_let(const expr_t &_base_tg_idx = {}) {
auto &tg_grid = plan_.tg_grid;
auto &coord_info = plan_.coord_info;
for (auto &d : index_dims(plan_.desc.prop)) {
const auto &tg_idx = coord_info.tg_index(d);
if (is_const(tg_idx)) continue;
auto base_tg_idx
= (_base_tg_idx ? _base_tg_idx : tg_grid.index_var(d));
if (base_tg_idx.is_empty()) continue;
auto value = unpack_tg_index(d, base_tg_idx);
let(tg_idx, value);
}
}
kernel_desc_t desc_;
var_manager_t &var_mgr_;
plan_t plan_;
buffer_info_t buf_info_;
};
stmt_t build_ir(const dsl::kernel::options_t &options,
const kernel_desc_t &desc, var_manager_t &var_mgr) {
auto plan = create_plan(desc, options.hw());
if (!plan) gpu_except_not_implemented("Cannot create plan.");
gpu_info() << desc;
gpu_trace() << plan;
constraint_set_t cset;
ir_context_t ir_ctx(options, cset);
builder_t builder(ir_ctx, desc, var_mgr, plan);
auto stmt = builder.get_stmt();
gpu_trace() << "Convolution kernel body:\n" << stmt;
return stmt;
}
} } } } } } }