#ifndef GPU_GENERIC_SYCL_SYCL_POST_OPS_HPP
#define GPU_GENERIC_SYCL_SYCL_POST_OPS_HPP
#include "common/c_types_map.hpp"
#include "common/primitive_attr.hpp"
#include "common/primitive_exec_types.hpp"
#include "common/utils.hpp"
#include "gpu/generic/sycl/sycl_io_helper.hpp"
#include "gpu/generic/sycl/sycl_math_utils.hpp"
#include "xpu/sycl/memory_storage_base.hpp"
#include "xpu/sycl/types.hpp"
namespace dnnl {
namespace impl {
namespace gpu {
namespace generic {
namespace sycl {
struct ref_eltwise_fwd_t {
static bool eltwise_ok(alg_kind_t alg) {
using namespace alg_kind;
return utils::one_of(alg, eltwise_relu, eltwise_linear, eltwise_clip,
eltwise_clip_v2, eltwise_hardswish, eltwise_gelu_tanh,
eltwise_gelu_erf, eltwise_tanh, eltwise_logistic, eltwise_swish,
eltwise_elu);
}
static bool eltwise_ok(const post_ops_t::entry_t::eltwise_t &eltwise) {
return eltwise_ok(eltwise.alg);
}
ref_eltwise_fwd_t() = default;
ref_eltwise_fwd_t(alg_kind_t alg, float alpha, float beta, float scale)
: alg_(alg), alpha_(alpha), beta_(beta), scale_(scale) {
assert(eltwise_ok(alg));
}
ref_eltwise_fwd_t(const post_ops_t::entry_t::eltwise_t &eltwise)
: ref_eltwise_fwd_t(
eltwise.alg, eltwise.alpha, eltwise.beta, eltwise.scale) {}
float compute(float s) const {
return compute(alg_, s, alpha_, beta_) * scale_;
}
template <int width>
::sycl::vec<float, width> compute(::sycl::vec<float, width> src_vec) const {
::sycl::vec<float, width> scale_vec(scale_);
return compute(alg_, src_vec, alpha_, beta_) * scale_vec;
}
template <int width>
static ::sycl::vec<float, width> compute(alg_kind_t alg,
::sycl::vec<float, width> src_vec, float alpha, float beta) {
using namespace alg_kind;
using namespace math;
constexpr ::sycl::vec<float, width> nan_vec(NAN);
switch (alg) {
case eltwise_relu: return relu_fwd(src_vec, alpha); ;
case eltwise_linear: return linear_fwd(src_vec, alpha, beta);
default: return nan_vec;
}
}
static float compute(alg_kind_t alg, float s, float alpha, float beta) {
using namespace alg_kind;
using namespace math;
float d = 0.f;
switch (alg) {
case eltwise_relu: d = relu_fwd(s, alpha); break;
case eltwise_linear: d = linear_fwd(s, alpha, beta); break;
case eltwise_clip: d = clip_fwd(s, alpha, beta); break;
case eltwise_clip_v2: d = clip_v2_fwd(s, alpha, beta); break;
case eltwise_hardswish:
d = dnnl::impl::math::hardswish_fwd(s, alpha, beta);
break;
case eltwise_gelu_tanh: d = gelu_tanh_fwd(s); break;
case eltwise_gelu_erf: d = gelu_erf_fwd(s); break;
case eltwise_tanh: d = tanh_fwd(s); break;
case eltwise_logistic: d = logistic_fwd(s); break;
case eltwise_swish:
d = dnnl::impl::math::swish_fwd(s, alpha);
break;
case eltwise_elu: d = dnnl::impl::math::elu_fwd(s, alpha); break;
default: d = ::sycl::nan(0u);
}
return d;
}
private:
alg_kind_t alg_;
float alpha_;
float beta_;
float scale_;
};
struct ref_binary_op_t {
static bool binary_ok(alg_kind_t alg) {
using namespace alg_kind;
return utils::one_of(alg, binary_add, binary_div, binary_max,
binary_min, binary_mul, binary_sub, binary_ge, binary_gt,
binary_le, binary_lt, binary_eq, binary_ne);
}
static bool binary_ok(const post_ops_t::entry_t::binary_t &binary) {
return binary_ok(binary.alg);
}
ref_binary_op_t() = default;
ref_binary_op_t(alg_kind_t alg, xpu::sycl::md_t src_md)
: alg_(alg), src_md_(src_md) {
assert(binary_ok(alg));
}
ref_binary_op_t(const post_ops_t::entry_t::binary_t &binary)
: ref_binary_op_t(binary.alg, xpu::sycl::md_t(&binary.src1_desc)) {}
float load_and_compute(float s0, const xpu::sycl::in_memory_arg_t &src,
dims_t offset) const { memory_tensor_t src_mem(src, src_md_);
float val = src_mem.load_md_bc(offset);
return compute(s0, val);
}
float compute(float s0, float s1) const { return compute(alg_, s0, s1); }
static float compute(alg_kind_t alg, float s0, float s1) {
using namespace alg_kind;
using namespace math;
float d = 0.f;
switch (alg) {
case binary_add: d = s0 + s1; break;
case binary_div: d = s0 / s1; break;
case binary_min: d = ::sycl::min(s0, s1); break;
case binary_max: d = ::sycl::max(s0, s1); break;
case binary_mul: d = s0 * s1; break;
case binary_sub: d = s0 - s1; break;
case binary_ge: d = (float)((s0 >= s1) * -1); break;
case binary_gt: d = (float)((s0 > s1) * -1); break;
case binary_le: d = (float)((s0 <= s1) * -1); break;
case binary_lt: d = (float)((s0 < s1) * -1); break;
case binary_eq: d = (float)((s0 == s1) * -1); break;
case binary_ne: d = (float)((s0 != s1) * -1); break;
default: d = ::sycl::nan(0u);
}
return d;
}
private:
alg_kind_t alg_;
xpu::sycl::md_t src_md_;
};
struct ref_prelu_op_t {
ref_prelu_op_t() = default;
ref_prelu_op_t(const post_ops_t::entry_t::prelu_t &prelu,
memory_desc_wrapper src_mdw)
: ndims_(src_mdw.ndims()) {
dims_t prelu_dims;
for (int d = 0; d < ndims_; ++d) {
prelu_dims[d] = (prelu.mask & (1 << d)) ? src_mdw.dims()[d] : 1;
}
memory_desc_t prelu_desc;
using namespace format_tag;
auto dat_tag = utils::pick(
ndims_ - 2, ab, acb, acdb, acdeb); memory_desc_init_by_tag(
prelu_desc, ndims_, prelu_dims, src_mdw.data_type(), dat_tag);
utils::array_copy(
strides_, prelu_desc.format_desc.blocking.strides, ndims_);
for (int i = 0; i < ndims_; i++) {
if (prelu_dims[i] == 1) { strides_[i] = 0; }
}
}
float load_and_compute(float s0, const xpu::sycl::in_memory_arg_t &src,
dims_t offsets) const { memory_plain_t src_mem(src, data_type::f32);
dim_t lin_off = 0;
for (int i = 0; i < xpu::sycl::md_t::max_dims; i++) {
if (i < ndims_) { lin_off += strides_[i] * offsets[i]; }
}
float val = src_mem.load(lin_off);
auto res = compute(s0, val);
return res;
}
float compute(float src_val, float weights_val) const {
return math::relu_fwd(src_val, weights_val);
}
private:
dim_t ndims_;
dims_t strides_;
};
struct ref_sum_op_t {
ref_sum_op_t() = default;
ref_sum_op_t(float scale, float zeropoint)
: scale_(scale), zeropoint_(zeropoint) {}
float load_and_compute(float acc, const xpu::sycl::inout_memory_arg_t &dst,
dnnl::impl::data_type_t sum_dt_,
dim_t offset) const { memory_plain_t dst_mem(dst, sum_dt_);
float val = dst_mem.load(offset);
return compute(acc, val);
}
float compute(float acc, float dst) const {
return acc + scale_ * (dst - zeropoint_);
}
template <int width>
::sycl::vec<float, width> compute(::sycl::vec<float, width> acc,
::sycl::vec<float, width> dst) const {
const ::sycl::vec<float, width> scale_vec(scale_);
const ::sycl::vec<float, width> zeropoint_vec(zeropoint_);
return acc + scale_vec * (dst - zeropoint_vec);
}
private:
float scale_;
float zeropoint_;
};
struct sycl_post_op_t {
primitive_kind_t kind_;
union {
ref_binary_op_t binary_;
ref_prelu_op_t prelu_;
ref_eltwise_fwd_t eltwise_;
ref_sum_op_t sum_;
};
};
struct post_op_input_args;
struct sycl_post_ops_t {
static constexpr int max_post_ops = 5;
static bool post_ops_ok(const primitive_attr_t *attr,
bool allow_inputs = true, bool allow_sum = true) {
using namespace primitive_kind;
const auto &attr_po = attr->post_ops_;
if (attr_po.len() > max_post_ops) { return false; }
for (auto i = 0; i < attr_po.len(); ++i) {
if (allow_sum && attr_po.contain(sum, i)) {
} else if (attr_po.contain(eltwise, i)) {
if (!ref_eltwise_fwd_t::eltwise_ok(attr_po.entry_[i].eltwise)) {
return false;
}
} else if (allow_inputs && attr_po.contain(binary, i)) {
if (attr_po.entry_[i].is_binary_with_ternary_op()) return false;
if (!ref_binary_op_t::binary_ok(attr_po.entry_[i].binary)) {
return false;
}
} else if (allow_inputs && attr_po.contain(prelu, i)) {
continue;
} else {
return false;
}
}
return true;
}
sycl_post_ops_t() = default;
sycl_post_ops_t(const primitive_attr_t *attr, memory_desc_wrapper dst_mdw) {
using namespace primitive_kind;
const auto &attr_po = attr->post_ops_;
assert(attr_po.len() <= max_post_ops);
for (auto i = 0; i < attr_po.len(); ++i) {
if (attr_po.contain(sum, i)) {
ops_[i].kind_ = sum;
ops_[i].sum_ = ref_sum_op_t(attr_po.entry_[i].sum.scale,
attr_po.entry_[i].sum.zero_point);
sum_dt_ = attr_po.entry_[i].sum.dt == dnnl_data_type_undef
? dst_mdw.data_type()
: attr_po.entry_[i].sum.dt;
} else if (attr_po.contain(eltwise, i)) {
ops_[i].kind_ = eltwise;
ops_[i].eltwise_ = ref_eltwise_fwd_t(attr_po.entry_[i].eltwise);
} else if (attr_po.contain(binary, i)) {
ops_[i].kind_ = binary;
ops_[i].binary_ = ref_binary_op_t(attr_po.entry_[i].binary);
} else if (attr_po.contain(prelu, i)) {
ops_[i].kind_ = prelu;
ops_[i].prelu_
= ref_prelu_op_t(attr_po.entry_[i].prelu, dst_mdw);
}
}
n_post_ops_ = attr_po.len();
}
inline float apply(float acc, const xpu::sycl::inout_memory_arg_t &dst,
dim_t dst_offset, const post_op_input_args &po_args,
dims_t src_offset) const;
inline float apply(float acc, float dst, const post_op_input_args &po_args,
dims_t src_offset) const;
inline float apply(float acc, const post_op_input_args &po_args,
dims_t src_offset) const;
inline float apply(float acc, const xpu::sycl::inout_memory_arg_t &dst,
dim_t dst_offset) const;
inline int get_post_op() const { return n_post_ops_; }
inline primitive_kind_t get_post_op_kind(int i) const {
return ops_[i].kind_;
}
inline ref_binary_op_t get_binary_post_op(int i) const {
return ops_[i].binary_;
}
dnnl::impl::data_type_t sum_dt_;
private:
sycl_post_op_t ops_[max_post_ops];
int n_post_ops_;
};
struct post_op_input_args {
post_op_input_args(::sycl::handler &cgh, const exec_ctx_t &ctx,
const sycl_post_ops_t &post_ops)
#define CTX_IN_SYCL_KERNEL_MEMORY_PO(N) \
CTX_IN_SYCL_KERNEL_MEMORY(DNNL_ARG_ATTR_MULTIPLE_POST_OP(N) \
| (post_ops.get_post_op_kind(N) == primitive_kind::prelu \
? DNNL_ARG_WEIGHTS \
: DNNL_ARG_SRC_1))
: args_ {CTX_IN_SYCL_KERNEL_MEMORY_PO(0),
CTX_IN_SYCL_KERNEL_MEMORY_PO(1),
CTX_IN_SYCL_KERNEL_MEMORY_PO(2),
CTX_IN_SYCL_KERNEL_MEMORY_PO(3),
CTX_IN_SYCL_KERNEL_MEMORY_PO(4)} {
}
#undef CTX_IN_SYCL_KERNEL_MEMORY_PO
xpu::sycl::in_memory_arg_t args_[sycl_post_ops_t::max_post_ops];
};
float sycl_post_ops_t::apply(float acc,
const xpu::sycl::inout_memory_arg_t &dst, dim_t dst_offset,
const post_op_input_args &po_args, dims_t src_offset) const {
using namespace primitive_kind;
for (auto i = 0; i < n_post_ops_; ++i) {
switch (ops_[i].kind_) {
case eltwise: acc = ops_[i].eltwise_.compute(acc); break;
case binary:
acc = ops_[i].binary_.load_and_compute(
acc, po_args.args_[i], src_offset);
break;
case prelu:
acc = ops_[i].prelu_.load_and_compute(
acc, po_args.args_[i], src_offset);
break;
case sum:
acc = ops_[i].sum_.load_and_compute(
acc, dst, sum_dt_, dst_offset);
break;
default: acc = ::sycl::nan(0u);
}
}
return acc;
}
float sycl_post_ops_t::apply(float acc, float dst,
const post_op_input_args &po_args, dims_t src_offset) const {
using namespace primitive_kind;
for (auto i = 0; i < n_post_ops_; ++i) {
switch (ops_[i].kind_) {
case eltwise: acc = ops_[i].eltwise_.compute(acc); break;
case binary:
acc = ops_[i].binary_.load_and_compute(
acc, po_args.args_[i], src_offset);
break;
case prelu:
acc = ops_[i].prelu_.load_and_compute(
acc, po_args.args_[i], src_offset);
break;
case sum: acc = ops_[i].sum_.compute(acc, dst); break;
default: acc = ::sycl::nan(0u);
}
}
return acc;
}
float sycl_post_ops_t::apply(
float acc, const post_op_input_args &po_args, dims_t src_offset) const {
using namespace primitive_kind;
for (auto i = 0; i < n_post_ops_; ++i) {
switch (ops_[i].kind_) {
case eltwise: acc = ops_[i].eltwise_.compute(acc); break;
case binary:
acc = ops_[i].binary_.load_and_compute(
acc, po_args.args_[i], src_offset);
break;
case prelu:
acc = ops_[i].prelu_.load_and_compute(
acc, po_args.args_[i], src_offset);
break;
default: acc = ::sycl::nan(0u);
}
}
return acc;
}
float sycl_post_ops_t::apply(float acc,
const xpu::sycl::inout_memory_arg_t &dst, dim_t dst_offset) const {
using namespace primitive_kind;
for (auto i = 0; i < n_post_ops_; ++i) {
switch (ops_[i].kind_) {
case eltwise: acc = ops_[i].eltwise_.compute(acc); break;
case sum:
acc = ops_[i].sum_.load_and_compute(
acc, dst, sum_dt_, dst_offset);
break;
default: acc = ::sycl::nan(0u);
}
}
return acc;
}
CHECK_SYCL_KERNEL_ARG_TYPE(ref_binary_op_t);
CHECK_SYCL_KERNEL_ARG_TYPE(ref_eltwise_fwd_t);
CHECK_SYCL_KERNEL_ARG_TYPE(sycl_post_op_t);
CHECK_SYCL_KERNEL_ARG_TYPE(sycl_post_ops_t);
} } } } }
#endif