#ifndef GPU_INTEL_POST_OPS_HPP
#define GPU_INTEL_POST_OPS_HPP
#include "common/math_utils.hpp"
#include "common/primitive_attr.hpp"
#include "gpu/intel/utils.hpp"
namespace gemmstone {
namespace dsl {
namespace ir {
class expr_t;
}
} }
namespace dnnl {
namespace impl {
namespace alg_kind {
const alg_kind_t binary_prelu = eltwise_relu;
};
namespace gpu {
namespace intel {
namespace post_op {
struct specializations_t {
struct inline_mode_t {
constexpr inline_mode_t() = default;
static constexpr inline_mode_t always() { return {mode_t::always}; }
static constexpr inline_mode_t never() { return {mode_t::never}; }
static constexpr inline_mode_t if_zero() { return {mode_t::if_zero}; }
static constexpr inline_mode_t impl_managed() { return never(); }
bool is_inlined() const {
switch (mode_) {
case mode_t::always: return true;
case mode_t::never: return false;
default: gpu_error_not_expected(); return true;
}
}
template <typename T>
bool is_inlined(T value) const {
switch (mode_) {
case mode_t::always: return true;
case mode_t::never: return false;
case mode_t::if_zero: return value == 0;
}
gpu_error_not_expected();
return true;
}
private:
enum class mode_t : uint8_t { always, never, if_zero };
constexpr inline_mode_t(mode_t m) : mode_(m) {};
mode_t mode_ = mode_t::always;
};
struct eltwise_t {
constexpr eltwise_t() = default;
constexpr eltwise_t(
inline_mode_t scale, inline_mode_t alpha, inline_mode_t beta)
: scale(scale), alpha(alpha), beta(beta) {};
inline_mode_t scale;
inline_mode_t alpha;
inline_mode_t beta;
} eltwise;
struct sum_t {
constexpr sum_t() = default;
constexpr sum_t(inline_mode_t scale, inline_mode_t zero_point)
: scale(scale), zero_point(zero_point) {};
inline_mode_t scale;
inline_mode_t zero_point;
} sum;
struct binary_t {
constexpr binary_t() = default;
constexpr binary_t(inline_mode_t src1_desc_layout)
: src1_desc_layout(src1_desc_layout) {};
inline_mode_t src1_desc_layout;
} binary;
};
struct ndim_normalizer_t {
constexpr ndim_normalizer_t() = default;
constexpr ndim_normalizer_t(int insert_idx, int bcast_ndims)
: insert_idx(insert_idx), bcast_ndims(bcast_ndims) {}
int ndims(const memory_desc_t &md) const { return md.ndims + bcast_ndims; }
int dim_idx(int md_idx) const {
return (md_idx < insert_idx) ? md_idx : md_idx + bcast_ndims;
}
dim_t dim(int idx, const memory_desc_t &md) const {
auto &dims = md.dims;
return (idx < insert_idx)
? dims[idx]
: (idx < insert_idx + bcast_ndims ? 1
: dims[idx - bcast_ndims]);
}
dim_t stride(int idx, const memory_desc_t &md) const {
auto &strides = md.format_desc.blocking.strides;
return (idx < insert_idx)
? strides[idx]
: (idx < insert_idx + bcast_ndims ? 0
: strides[idx - bcast_ndims]);
}
int insert_idx = 0;
int bcast_ndims = 0;
};
struct relative_idx_t {
constexpr relative_idx_t() = default;
constexpr relative_idx_t(int8_t v) : value_(v) {};
constexpr bool operator==(const relative_idx_t &o) const {
return value_ == o.value_;
}
constexpr bool is_innermost() const { return value_ == 0; }
constexpr bool is_unset() const { return value_ < 0; }
std::string str() const {
if (is_unset()) return "(unset)";
char name[2] = {into<char>(value_ + 'a'), 0};
return name;
}
protected:
friend struct relative_md_t;
constexpr int as_int() const { return value_; }
private:
int8_t value_ = -1;
};
struct relative_md_t {
using idx_t = relative_idx_t;
static constexpr int to_md_idx(idx_t idx, int ndims) {
return ndims - 1 - idx.as_int();
}
static idx_t from_md_idx(
int idx, int ndims, const ndim_normalizer_t &ndim_normalizer) {
return {into<int8_t>(ndims - 1 - ndim_normalizer.dim_idx(idx))};
}
struct blocking_t {
static constexpr uint8_t unset_block = 0;
static constexpr int max_dims = 4;
bool empty() const { return idxs[0].is_unset(); }
std::string str() const {
ostringstream_t oss;
for (int i = max_dims - 1; i >= 0; i--) {
if (idxs[i].is_unset()) continue;
oss << int(blocks[i]) << idxs[i].str();
}
return oss.str();
}
#if __cplusplus >= 202002L
bool operator==(const blocking_t &) const = default;
#endif
std::array<uint8_t, max_dims> blocks
= {unset_block, unset_block, unset_block, unset_block};
std::array<idx_t, max_dims> idxs = {};
};
relative_md_t() = default;
relative_md_t(int md_broadcast_mask, int md_inner_dim, int ndims,
data_type_t dt, const ndim_normalizer_t &ndim_normalizer)
: dt(dt)
, broadcast_mask(uint16_t(0xFFFFu << ndims))
, inner_dim(from_md_idx(md_inner_dim, ndims, ndim_normalizer)) {
for (int i = 0; i < ndims; i++) {
auto rmd_idx = from_md_idx(i, ndims, ndim_normalizer).as_int();
broadcast_mask |= ((md_broadcast_mask >> i) & 1) << rmd_idx;
}
}
static status_t make(relative_md_t &rmd, const memory_desc_t &md,
const ndim_normalizer_t &ndim_normalizer) {
if (md.format_kind != format_kind::blocked)
return status::unimplemented;
rmd.dt = md.data_type;
auto ndims = ndim_normalizer.ndims(md);
memory_desc_wrapper mdw(md);
gpu_assert(mdw.is_blocking_desc());
auto &blocking = mdw.blocking_desc();
gpu_assert(blocking.inner_nblks <= blocking_t::max_dims);
for (dim_t i = 0; i < blocking.inner_nblks; i++) {
auto rmd_i = blocking.inner_nblks - 1 - i;
rmd.inner_layout.idxs[rmd_i] = from_md_idx(
into<int>(blocking.inner_idxs[i]), ndims, ndim_normalizer);
rmd.inner_layout.blocks[rmd_i]
= into<uint8_t>(blocking.inner_blks[i]);
}
rmd.broadcast_mask = ~0;
uint16_t mask_bit = 1;
for (int i = ndims - 1; i >= 0; i--) {
auto d = ndim_normalizer.dim(i, md);
if (d > 1 || d == DNNL_RUNTIME_DIM_VAL)
rmd.broadcast_mask &= ~mask_bit;
mask_bit = static_cast<uint16_t>(mask_bit << 1);
}
dim_t min_stride = std::numeric_limits<dim_t>::max();
for (int i = 0; i < ndims; i++) {
if (ndim_normalizer.dim(i, md) > 1
&& ndim_normalizer.stride(i, md) <= min_stride) {
rmd.inner_dim = from_md_idx(i, ndims, ndim_normalizer);
min_stride = ndim_normalizer.stride(i, md);
}
}
if (rmd.inner_dim.is_unset()) rmd.inner_dim = {0};
return status::success;
}
std::string ocl_defines(const std::string &prefix,
const std::array<std::string, MAX_NDIMS> &strides, int ndims) const;
gemmstone::dsl::ir::expr_t get_offset(
const std::vector<gemmstone::dsl::ir::expr_t> &dim_idxs,
const std::vector<gemmstone::dsl::ir::expr_t> &strides) const;
bool is_broadcast(int idx, int ndims) const {
idx_t norm = from_md_idx(idx, ndims, {});
if (norm.is_unset()) return true;
return (1 << norm.as_int()) & broadcast_mask;
}
bool is_inner_dim(int idx, int ndims) const {
return idx == to_md_idx(inner_dim, ndims);
}
int ndims() const {
size_t dim_mask = broadcast_mask ^ 0xffff;
return math::ilog2q(dim_mask) + 1;
}
std::string str() const {
if (broadcast_mask == 0xFFFF) {
gpu_assert(inner_layout.empty());
return std::string("{scalar}.") + dnnl_dt2str(dt);
}
ostringstream_t oss;
const char *prefix = "{";
for (int i = 15; i >= 0; i--) {
if (broadcast_mask & (1 << i)) continue;
std::cout << prefix;
oss << idx_t(into<int8_t>(i)).str();
prefix = "";
}
oss << "}:";
if (!inner_dim.is_unset()) oss << inner_dim.str();
if (!inner_layout.empty()) oss << inner_layout.str();
oss << "." << dnnl_dt2str(dt);
return oss.str();
}
#if __cplusplus >= 202002L
bool operator==(const relative_md_t &) const = default;
#endif
blocking_t inner_layout;
data_type_t dt = data_type::undef;
uint16_t broadcast_mask = 0;
idx_t inner_dim;
uint8_t pad[1] = {};
};
enum class kind_t {
undef,
sum,
eltwise,
conv,
binary,
};
struct sum_t {
sum_t() = default;
sum_t(const post_ops_t::entry_t::sum_t &op,
const specializations_t::sum_t &s)
: dt(op.dt)
, inline_scale(s.scale.is_inlined(op.scale))
, inline_zero_point(s.zero_point.is_inlined(op.zero_point))
, scale(inline_scale ? op.scale : NAN)
, zero_point(inline_zero_point ? op.zero_point : -1) {}
#if __cplusplus >= 202002L
bool operator==(const sum_t &) const = default;
#endif
void serialize(serialization_stream_t &s) const {
s.append(dt);
s.append(inline_scale);
s.append(inline_zero_point);
s.append(scale);
s.append(zero_point);
}
static sum_t deserialize(deserializer_t &d) {
sum_t e {};
d.pop(e.dt);
d.pop(e.inline_scale);
d.pop(e.inline_zero_point);
d.pop(e.scale);
d.pop(e.zero_point);
return e;
}
data_type_t dt = data_type::undef;
bool inline_scale;
bool inline_zero_point;
uint8_t pad[2] = {};
float scale = 0;
int zero_point = 0;
};
struct eltwise_t {
eltwise_t() = default;
eltwise_t(const post_ops_t::entry_t::eltwise_t &op,
const specializations_t::eltwise_t &s)
: alg(op.alg)
, inline_scale(s.scale.is_inlined(op.scale))
, inline_alpha(s.alpha.is_inlined(op.alpha))
, inline_beta(s.beta.is_inlined(op.beta))
, scale(inline_scale ? op.scale : NAN)
, alpha(inline_alpha ? op.alpha : NAN)
, beta(inline_beta ? op.beta : NAN) {}
#if __cplusplus >= 202002L
bool operator==(const eltwise_t &) const = default;
#endif
void serialize(serialization_stream_t &s) const {
s.append(alg);
s.append(inline_scale);
s.append(inline_alpha);
s.append(inline_beta);
s.append(scale);
s.append(alpha);
s.append(beta);
}
static eltwise_t deserialize(deserializer_t &d) {
eltwise_t e {};
d.pop(e.alg);
d.pop(e.inline_scale);
d.pop(e.inline_alpha);
d.pop(e.inline_beta);
d.pop(e.scale);
d.pop(e.alpha);
d.pop(e.beta);
return e;
}
alg_kind_t alg = alg_kind::undef;
bool inline_scale = {};
bool inline_alpha = {};
bool inline_beta = {};
uint8_t pad[1] = {};
float scale = 0, alpha = 0, beta = 0;
};
struct depthwise_conv_t {
depthwise_conv_t() = default;
depthwise_conv_t(const post_ops_t::entry_t::depthwise_conv_t &op)
: kernel(op.kernel)
, stride(op.stride)
, padding(op.padding)
, wei_dt(op.wei_dt)
, bias_dt(op.bias_dt)
, dst_dt(op.dst_dt) {}
#if __cplusplus >= 202002L
bool operator==(const depthwise_conv_t &) const = default;
#endif
dim_t kernel = 0;
dim_t stride = 0;
dim_t padding = 0;
data_type_t wei_dt = data_type::undef;
data_type_t bias_dt = data_type::undef;
data_type_t dst_dt = data_type::undef;
uint8_t pad[4] = {};
};
struct binary_t {
binary_t() = default;
static status_t make(binary_t &b, const post_ops_t::entry_t::binary_t &op,
const specializations_t::binary_t &s,
const post_op::ndim_normalizer_t &ndim_normalizer) {
if (s.src1_desc_layout.is_inlined())
CHECK(relative_md_t::make(
b.src1_desc, op.src1_desc, ndim_normalizer));
else
b.src1_desc.dt = op.src1_desc.data_type;
b.alg = op.alg;
return status::success;
}
static status_t make(binary_t &b, const post_ops_t::entry_t::prelu_t &op,
const memory_desc_wrapper &dst_md,
const specializations_t::binary_t &s,
const ndim_normalizer_t &ndim_normalizer) {
if (s.src1_desc_layout.is_inlined()) {
auto bcast_mask = ~op.mask;
int inner_dim = 0;
for (int i = 0; i < dst_md.ndims(); i++) {
if (dst_md.dims()[i] == 1) bcast_mask |= 1 << i;
if ((~bcast_mask & (1 << i)) && inner_dim != 1) inner_dim = i;
}
b.src1_desc = relative_md_t(bcast_mask, inner_dim, dst_md.ndims(),
data_type::f32, ndim_normalizer);
} else {
b.src1_desc.dt = data_type::f32;
}
b.alg = alg_kind::binary_prelu;
return status::success;
}
#if __cplusplus >= 202002L
bool operator==(const binary_t &) const = default;
#endif
relative_md_t src1_desc;
alg_kind_t alg;
uint8_t pad[4] = {};
};
}
struct gpu_post_ops_t {
gpu_post_ops_t() = default;
static status_t make(gpu_post_ops_t &gpu_post_ops,
const post_ops_t &post_ops, const memory_desc_wrapper &dst_md,
post_op::specializations_t opts = {},
post_op::ndim_normalizer_t ndim_normalizer = {}) {
auto &ops = gpu_post_ops.ops_;
ops.clear();
ops.reserve(into<size_t>(post_ops.len()));
using namespace post_op;
for (auto &entry : post_ops.entry_) {
switch (entry.kind) {
case (primitive_kind::sum):
ops.emplace_back(sum_t(entry.sum, opts.sum));
break;
case (primitive_kind::eltwise):
ops.emplace_back(eltwise_t(entry.eltwise, opts.eltwise));
break;
case (primitive_kind::convolution):
ops.emplace_back(depthwise_conv_t(entry.depthwise_conv));
break;
case (primitive_kind::binary): {
binary_t b;
CHECK(binary_t::make(
b, entry.binary, opts.binary, ndim_normalizer));
ops.emplace_back(b);
break;
}
case (primitive_kind::prelu): {
binary_t b;
CHECK(binary_t::make(b, entry.prelu, dst_md, opts.binary,
ndim_normalizer));
ops.emplace_back(b);
break;
}
default: gpu_error_not_expected(); return status::runtime_error;
}
}
return status::success;
}
struct entry_t {
entry_t() : kind_(post_op::kind_t::undef) {}
entry_t(post_op::sum_t e) : kind_(post_op::kind_t::sum), sum_(e) {}
entry_t(post_op::eltwise_t e)
: kind_(post_op::kind_t::eltwise), eltwise_(e) {}
entry_t(post_op::depthwise_conv_t e)
: kind_(post_op::kind_t::conv), depthwise_conv_(e) {}
entry_t(post_op::binary_t e)
: kind_(post_op::kind_t::binary), binary_(e) {}
~entry_t() {
switch (kind_) {
case (post_op::kind_t::sum): sum_.~sum_t(); break;
case (post_op::kind_t::eltwise): eltwise_.~eltwise_t(); break;
case (post_op::kind_t::conv):
depthwise_conv_.~depthwise_conv_t();
break;
case (post_op::kind_t::binary): binary_.~binary_t(); break;
default: gpu_error_not_expected();
}
}
entry_t(const entry_t &other) = default;
entry_t &operator=(const entry_t &) = default;
post_op::kind_t kind() const { return kind_; }
bool is_sum() const { return kind_ == post_op::kind_t::sum; }
const post_op::sum_t &as_sum() const {
gpu_assert(is_sum());
return sum_;
}
bool is_eltwise() const { return kind_ == post_op::kind_t::eltwise; }
const post_op::eltwise_t &as_eltwise() const {
gpu_assert(is_eltwise());
return eltwise_;
}
bool is_depthwise_conv() const {
return kind_ == post_op::kind_t::conv;
}
const post_op::depthwise_conv_t &as_depthwise_conv() const {
gpu_assert(is_depthwise_conv());
return depthwise_conv_;
}
bool is_binary() const { return kind_ == post_op::kind_t::binary; }
const post_op::binary_t &as_binary() const {
gpu_assert(is_binary());
return binary_;
}
void set_scale(float scale) {
switch (kind_) {
case (post_op::kind_t::sum):
sum_.inline_scale = true;
sum_.scale = scale;
break;
case (post_op::kind_t::eltwise):
sum_.inline_scale = true;
eltwise_.scale = scale;
break;
default: gpu_error_not_expected(); break;
}
}
#if __cplusplus >= 202002L
bool operator==(const entry_t &other) const {
if (kind_ != other.kind_) return false;
switch (kind_) {
case (post_op::kind_t::sum): return sum_ == other.sum_;
case (post_op::kind_t::eltwise):
return eltwise_ == other.eltwise_;
case (post_op::kind_t::conv):
return depthwise_conv_ == other.depthwise_conv_;
case (post_op::kind_t::binary): return binary_ == other.binary_;
case (post_op::kind_t::undef): return true;
}
gpu_error_not_expected();
return false;
}
#endif
void serialize(serialization_stream_t &s) const {
s.append(kind_);
switch (kind_) {
case (post_op::kind_t::sum): s.append(sum_); break;
case (post_op::kind_t::eltwise): s.append(eltwise_); break;
case (post_op::kind_t::conv): s.append(depthwise_conv_); break;
case (post_op::kind_t::binary): s.append(binary_); break;
default: gpu_error_not_expected(); break;
}
}
static entry_t deserialize(deserializer_t &d) {
auto kind = d.pop<post_op::kind_t>();
switch (kind) {
case (post_op::kind_t::sum): return d.pop<post_op::sum_t>();
case (post_op::kind_t::eltwise):
return d.pop<post_op::eltwise_t>();
case (post_op::kind_t::conv):
return d.pop<post_op::depthwise_conv_t>();
case (post_op::kind_t::binary):
return d.pop<post_op::binary_t>();
default: gpu_error_not_expected(); return entry_t();
}
}
private:
post_op::kind_t kind_;
union {
post_op::sum_t sum_;
post_op::eltwise_t eltwise_;
post_op::depthwise_conv_t depthwise_conv_;
post_op::binary_t binary_;
};
};
static_assert(sizeof(entry_t) < 64,
"Avoid unnecessary growth of this structure to limit the size of "
"gpu_post_ops");
using container_type = std::vector<entry_t>;
using iterator = container_type::iterator;
using reverse_iterator = container_type::reverse_iterator;
using const_iterator = container_type::const_iterator;
using const_reverse_iterator = container_type::const_reverse_iterator;
iterator begin() { return ops_.begin(); }
const_iterator begin() const { return ops_.begin(); }
reverse_iterator rbegin() { return ops_.rbegin(); }
const_reverse_iterator rbegin() const { return ops_.rbegin(); }
iterator end() { return ops_.end(); }
const_iterator end() const { return ops_.end(); }
reverse_iterator rend() { return ops_.rend(); }
const_reverse_iterator rend() const { return ops_.rend(); }
bool empty() const { return ops_.empty(); }
const entry_t &back() const { return ops_.back(); }
entry_t &back() { return ops_.back(); }
const entry_t &operator[](size_t idx) const { return ops_[idx]; }
entry_t &operator[](size_t idx) { return ops_[idx]; }
void pop_back() { return ops_.pop_back(); }
void serialize(serialization_stream_t &s) const { s.append(ops_); }
static gpu_post_ops_t deserialize(deserializer_t &d) {
gpu_post_ops_t po;
d.pop(po.ops_);
return po;
}
#if __cplusplus >= 202002L
bool operator==(const gpu_post_ops_t &) const = default;
#else
bool operator==(const gpu_post_ops_t &other) const {
return serialization_stream_t(*this) == serialization_stream_t(other);
}
#endif
size_t len() const { return ops_.size(); }
private:
std::vector<entry_t> ops_;
};
} } } } #endif