#include "common/bfloat16.hpp"
#include "common/c_types_map.hpp"
#include "common/dnnl_thread.hpp"
#include "common/nstl.hpp"
#include "common/type_helpers.hpp"
#include "common/utils.hpp"
#include "cpu/aarch64/jit_generator.hpp"
#include "cpu/aarch64/injectors/jit_uni_eltwise_injector.hpp"
#include "cpu/aarch64/jit_uni_eltwise.hpp"
#include <cstdint>
#define GET_OFF(field) offsetof(jit_args_t, field)
namespace dnnl {
namespace impl {
namespace cpu {
namespace aarch64 {
using namespace Xbyak_aarch64;
struct jit_args_t {
const void *src; const void *dst; const void *diff_dst; size_t work_amount;
};
struct jit_uni_eltwise_kernel_t : public jit_generator_t {
jit_uni_eltwise_kernel_t(const eltwise_pd_t *pd) : pd_(pd) {}
void operator()(jit_args_t *p) { jit_generator_t::operator()(p); }
protected:
const eltwise_pd_t *pd_;
data_type_t data_type() const {
return pd_->use_dst() ? pd_->dst_md()->data_type
: pd_->src_md()->data_type;
}
bool is_bf16() const { return data_type() == data_type::bf16; }
bool is_f16() const { return data_type() == data_type::f16; }
int dtype_size() const { return types::data_type_size(data_type()); }
bool can_compute_as_f16() const {
const auto &desc = *pd_->desc();
return pd_->is_fwd()
&& (((is_f16() || is_bf16()) && ((desc.alg_kind == alg_kind::eltwise_relu
&& desc.alpha == 0.0f)
|| desc.alg_kind == alg_kind::eltwise_abs))
|| (is_f16() && utils::one_of(desc.alg_kind,
alg_kind::eltwise_clip,
alg_kind::eltwise_clip_v2,
alg_kind::eltwise_square,
alg_kind::eltwise_sqrt)));
}
};
namespace {
template <cpu_isa_t isa>
struct jit_uni_kernel_t : public jit_uni_eltwise_kernel_t {
DECLARE_CPU_JIT_AUX_FUNCTIONS(jit_uni_kernel)
jit_uni_kernel_t(const eltwise_pd_t *pd) : jit_uni_eltwise_kernel_t(pd) {
const auto &desc = *pd_->desc();
const bool is_fwd = pd_->is_fwd();
const bool save_state = is_fwd ? false : true;
eltwise_injector_.reset(new jit_uni_eltwise_injector_t<isa>(this,
desc.alg_kind, desc.alpha, desc.beta, 1.f, save_state,
reg_injector_table, injector_mask, injector_p_tmp0, is_fwd,
pd_->use_dst(), true, true,
can_compute_as_f16() ? data_type::f16 : data_type::f32));
}
void generate() override {
const bool is_fwd = pd_->is_fwd();
const auto simd_elems_per_load = simd_elems(data_type(), isa);
preamble();
XReg param = param1;
add_imm(X_TMP_0, param, GET_OFF(src), X_TMP_1);
ldr(reg_src, ptr(X_TMP_0));
add_imm(X_TMP_0, param, GET_OFF(dst), X_TMP_1);
ldr(reg_dst, ptr(X_TMP_0));
if (!is_fwd) {
add_imm(X_TMP_0, param, GET_OFF(diff_dst), X_TMP_1);
ldr(reg_diff_dst, ptr(X_TMP_0));
}
add_imm(X_TMP_0, param, GET_OFF(work_amount), X_TMP_1);
ldr(reg_work_amount, ptr(X_TMP_0));
eltwise_injector_->load_table_addr();
Label vectorized_loop_start, remainder_loop_start, remainder_loop_end;
cmp(reg_work_amount, simd_elems_per_load);
b(LT, remainder_loop_start);
L(vectorized_loop_start);
load_vector(vmm_src.s, reg_src);
if (can_compute_as_f16()) {
eltwise_injector_->compute_vector(vmm_src.getIdx());
} else if (is_bf16()) {
unpack_bf16(vmm_src, tmp0);
eltwise_injector_->compute_vector_range(
{vmm_src.getIdx(), tmp0.getIdx()});
pack_bf16(vmm_src, tmp0);
} else if (is_f16()) {
unpack_fp16(vmm_src, tmp0);
eltwise_injector_->compute_vector_range(
{vmm_src.getIdx(), tmp0.getIdx()});
pack_fp16(vmm_src, tmp0);
} else { eltwise_injector_->compute_vector(vmm_src.getIdx());
if (!is_fwd) {
load_vector(vmm_diff_dst, reg_diff_dst);
fmul(TRegS(vmm_src.getIdx()), TRegS(vmm_src.getIdx()),
vmm_diff_dst);
}
}
store_vector(reg_dst, vmm_src.s);
add_imm(reg_src, reg_src, simd_bytes(isa), X_TMP_0);
add_imm(reg_dst, reg_dst, simd_bytes(isa), X_TMP_0);
if (!is_fwd)
add_imm(reg_diff_dst, reg_diff_dst, simd_bytes(isa), X_TMP_0);
sub_imm(reg_work_amount, reg_work_amount, simd_elems_per_load, X_TMP_0);
cmp(reg_work_amount, simd_elems_per_load);
b(GE, vectorized_loop_start);
L(remainder_loop_start);
cmp(reg_work_amount, 0);
b(LE, remainder_loop_end);
if (can_compute_as_f16()) {
ld1(v_f16[0], ptr(reg_src));
eltwise_injector_->compute_vector(vmm_src.getIdx());
} else if (is_bf16()) {
ld1(v_bf16[0], ptr(reg_src));
unpack_bf16(vmm_src, tmp0);
eltwise_injector_->compute_vector(vmm_src.getIdx());
pack_bf16(vmm_src, tmp0);
} else if (is_f16()) {
ld1(v_f16[0], ptr(reg_src));
unpack_fp16(vmm_src, tmp0);
eltwise_injector_->compute_vector(vmm_src.getIdx());
pack_fp16(vmm_src, tmp0);
} else {
ld1(xmm_src[0], ptr(reg_src));
eltwise_injector_->compute_vector(xmm_src.getIdx());
if (!is_fwd) {
ld1(xmm_diff_dst[0], ptr(reg_diff_dst));
fmul(vmm_src.s, vmm_src.s, vmm_diff_dst);
}
}
if (is_bf16()) {
st1(v_bf16[0], ptr(reg_dst));
} else if (is_f16()) {
st1(v_f16[0], ptr(reg_dst));
} else {
st1(xmm_src[0], ptr(reg_dst));
}
add_imm(reg_src, reg_src, dtype_size(), X_TMP_0);
add_imm(reg_dst, reg_dst, dtype_size(), X_TMP_0);
if (!is_fwd) add_imm(reg_diff_dst, reg_diff_dst, dtype_size(), X_TMP_0);
subs(reg_work_amount, reg_work_amount, 1);
b(remainder_loop_start);
L(remainder_loop_end);
postamble();
eltwise_injector_->prepare_table();
}
private:
using TReg = typename cpu_isa_traits<isa>::TReg;
using TRegS = typename cpu_isa_traits<isa>::TRegS;
XReg reg_src = x11;
XReg reg_dst = x8;
XReg reg_injector_table = x9;
XReg reg_diff_dst = x10;
XReg reg_work_amount = x6;
XReg imm_addr64 = x3;
PReg injector_mask = p1;
PReg injector_p_tmp0 = p4;
PReg injector_p_all = p7;
VReg4S xmm_src {1};
VReg8H v_bf16 {1};
VReg8H v_f16 {1};
TReg vmm_src {1};
VReg4S xmm_diff_dst {2};
TRegS vmm_diff_dst {2};
TReg tmp0 {2};
TReg tmp1 {7};
std::unique_ptr<jit_uni_eltwise_injector_t<isa>> eltwise_injector_;
PReg p_tmp0 {4};
void load_vector(TRegS &dst, const XReg addr);
void store_vector(const XReg &addr, const TRegS src);
void unpack_bf16(TReg &v0, TReg &v1);
void pack_bf16(TReg &v0, TReg &v1);
void unpack_fp16(TReg &v0, TReg &v1);
void pack_fp16(TReg &v0, TReg &v1);
};
template <>
inline void jit_uni_kernel_t<cpu_isa_t::asimd>::load_vector(
TRegS &dst, const XReg addr) {
ld1(dst, ptr(addr));
}
template <>
inline void jit_uni_kernel_t<cpu_isa_t::sve>::load_vector(
TRegS &dst, const XReg addr) {
ld1w(dst, P_ALL_ONE / T_z, ptr(addr));
}
template <>
inline void jit_uni_kernel_t<cpu_isa_t::asimd>::store_vector(
const XReg &addr, const TRegS src) {
st1(src, ptr(addr));
}
template <>
inline void jit_uni_kernel_t<cpu_isa_t::sve>::store_vector(
const XReg &addr, const TRegS src) {
st1w(src, P_ALL_ONE / T_z, ptr(addr));
}
template <>
inline void jit_uni_kernel_t<cpu_isa_t::asimd>::unpack_bf16(
TReg &v0, TReg &v1) {
movi(tmp1.s, 0x0);
zip2(VReg8H(v1.getIdx()), VReg8H(tmp1.getIdx()), VReg8H(v0.getIdx()));
zip1(VReg8H(v0.getIdx()), VReg8H(tmp1.getIdx()), VReg8H(v0.getIdx()));
}
template <>
inline void jit_uni_kernel_t<cpu_isa_t::sve>::unpack_bf16(TReg &v0, TReg &v1) {
mov(v1.s, P_ALL_ONE, v0.s);
lsl(v0.s, v0.s, 16);
and_(v1.s, 0xFFFF0000);
}
template <>
inline void jit_uni_kernel_t<cpu_isa_t::asimd>::pack_bf16(TReg &v0, TReg &v1) {
uzp2(v0.h, v0.h, v1.h);
}
template <>
inline void jit_uni_kernel_t<cpu_isa_t::sve>::pack_bf16(TReg &v0, TReg &v1) {
bfcvt(v0.h, P_ALL_ONE, v0.s);
bfcvtnt(v0.h, P_ALL_ONE, v1.s);
}
template <>
inline void jit_uni_kernel_t<cpu_isa_t::asimd>::unpack_fp16(
TReg &v0, TReg &v1) {
mov(VReg16B(v1.getIdx()), VReg16B(v0.getIdx()));
fcvtl(v0.s, VReg4H(v0.getIdx())); fcvtl2(v1.s, VReg8H(v1.getIdx())); }
template <>
inline void jit_uni_kernel_t<cpu_isa_t::sve>::unpack_fp16(TReg &v0, TReg &v1) {
mov(v1.s, P_ALL_ONE, v0.s);
fcvt(v0.s, P_ALL_ONE, v0.h);
lsr(v1.s, v1.s, 16);
fcvt(v1.s, P_ALL_ONE, v1.h);
}
template <>
inline void jit_uni_kernel_t<cpu_isa_t::asimd>::pack_fp16(TReg &v0, TReg &v1) {
fcvtn(VReg4H(v0.getIdx()), v0.s);
fcvtn2(VReg8H(v0.getIdx()), v1.s);
}
template <>
inline void jit_uni_kernel_t<cpu_isa_t::sve>::pack_fp16(TReg &v0, TReg &v1) {
fcvt(v0.h, P_ALL_ONE, v0.s);
fcvt(v1.h, P_ALL_ONE, v1.s);
lsl(v1.s, v1.s, 16);
orr(v0.h, P_ALL_ONE, v1.h);
}
}
template <cpu_isa_t isa>
status_t jit_uni_eltwise_fwd_t<isa>::pd_t::init(engine_t *engine) {
using namespace alg_kind;
const memory_desc_wrapper src_d(src_md());
VDISPATCH_ELTWISE(mayiuse(isa), VERBOSE_UNSUPPORTED_ISA);
VDISPATCH_ELTWISE(is_fwd(), VERBOSE_BAD_PROPKIND);
VDISPATCH_ELTWISE((src_md()->data_type == dst_md()->data_type),
VERBOSE_INCONSISTENT_DT, "src", "dst");
VDISPATCH_ELTWISE(utils::one_of(src_md()->data_type, data_type::f32,
data_type::bf16, data_type::f16),
VERBOSE_UNSUPPORTED_DT);
VDISPATCH_ELTWISE(!has_zero_dim_memory(), VERBOSE_EMPTY_TENSOR, "data");
VDISPATCH_ELTWISE(src_d.is_dense(true), VERBOSE_NONTRIVIAL_STRIDE);
VDISPATCH_ELTWISE(eltwise_injector::is_supported(isa, desc_.alg_kind),
VERBOSE_BAD_ALGORITHM);
VDISPATCH_ELTWISE(IMPLICATION(!src_d.is_dense(), is_zero_preserved()),
VERBOSE_UNSUPPORTED_ATTR);
VDISPATCH_ELTWISE(attr()->has_default_values(), VERBOSE_UNSUPPORTED_ATTR);
VDISPATCH_ELTWISE(set_default_formats_common(), VERBOSE_UNSUPPORTED_TAG);
VDISPATCH_ELTWISE(src_d == memory_desc_wrapper(dst_md()),
VERBOSE_INCONSISTENT_MDS, "src", "dst");
return status::success;
}
template <cpu_isa_t isa>
jit_uni_eltwise_fwd_t<isa>::jit_uni_eltwise_fwd_t(const pd_t *apd)
: primitive_t(apd) {}
template <cpu_isa_t isa>
jit_uni_eltwise_fwd_t<isa>::~jit_uni_eltwise_fwd_t() = default;
template <cpu_isa_t isa>
status_t jit_uni_eltwise_fwd_t<isa>::init(engine_t *engine) {
CHECK(safe_ptr_assign(kernel_, new jit_uni_kernel_t<isa>(pd())));
return kernel_->create_kernel();
}
template <cpu_isa_t isa>
status_t jit_uni_eltwise_fwd_t<isa>::execute(const exec_ctx_t &ctx) const {
auto src = CTX_IN_MEM(const uint8_t *, DNNL_ARG_SRC);
auto dst = CTX_OUT_MEM(uint8_t *, DNNL_ARG_DST);
const memory_desc_wrapper data_d(pd()->src_md());
const auto nelems = data_d.nelems(true);
const int cacheline_elems = 64 / data_d.data_type_size();
const data_type_t src_dt = pd()->src_md()->data_type;
const auto offset_bytes
= types::elements_to_bytes(src_dt, data_d.offset0());
src += offset_bytes;
dst += offset_bytes;
parallel(0, [&](const int ithr, const int nthr) {
dim_t start {0}, end {0};
balance211(
utils::div_up(nelems, cacheline_elems), nthr, ithr, start, end);
start = nstl::min(nelems, start * cacheline_elems);
end = nstl::min(nelems, end * cacheline_elems);
if (start == end) return;
jit_args_t args;
args.src = src + types::elements_to_bytes(src_dt, start);
args.dst = dst + types::elements_to_bytes(src_dt, start);
args.diff_dst = nullptr;
args.work_amount = end - start;
(*kernel_)(&args);
});
return status::success;
}
template <cpu_isa_t isa>
status_t jit_uni_eltwise_bwd_t<isa>::pd_t::init(engine_t *engine) {
using namespace alg_kind;
const memory_desc_wrapper data_d(data_md());
const char *data_tensor_name = use_dst() ? "dst" : "src";
VDISPATCH_ELTWISE(mayiuse(isa), VERBOSE_UNSUPPORTED_ISA);
VDISPATCH_ELTWISE(!is_fwd(), VERBOSE_BAD_PROPKIND);
VDISPATCH_ELTWISE(
utils::everyone_is(data_md()->data_type, diff_src_md()->data_type,
diff_dst_md()->data_type),
VERBOSE_INCONSISTENT_DT, data_tensor_name, "diff_src");
VDISPATCH_ELTWISE(((use_dst() ? dst_md()->data_type : src_md()->data_type)
== data_type::f32),
VERBOSE_UNSUPPORTED_DT);
VDISPATCH_ELTWISE(
!has_zero_dim_memory(), VERBOSE_EMPTY_TENSOR, data_tensor_name);
VDISPATCH_ELTWISE(set_default_formats_common(),
VERBOSE_TENSOR_FORMAT_MISMATCH, "diff_src/diff_dst",
data_tensor_name);
VDISPATCH_ELTWISE(data_d.is_dense(true), VERBOSE_NONTRIVIAL_STRIDE);
VDISPATCH_ELTWISE(eltwise_injector::is_supported(isa, desc_.alg_kind),
VERBOSE_BAD_ALGORITHM);
VDISPATCH_ELTWISE(IMPLICATION(!data_d.is_dense(), is_zero_preserved()),
VERBOSE_UNSUPPORTED_ATTR);
VDISPATCH_ELTWISE(data_d == memory_desc_wrapper(diff_dst_md()),
VERBOSE_INCONSISTENT_MDS, data_tensor_name, "diff_dst");
VDISPATCH_ELTWISE(memory_desc_wrapper(diff_src_md())
== memory_desc_wrapper(diff_dst_md()),
VERBOSE_INCONSISTENT_MDS, "diff_src", "diff_dst");
VDISPATCH_ELTWISE(attr()->has_default_values(), VERBOSE_UNSUPPORTED_ATTR);
return status::success;
}
template <cpu_isa_t isa>
jit_uni_eltwise_bwd_t<isa>::jit_uni_eltwise_bwd_t(const pd_t *apd)
: primitive_t(apd) {}
template <cpu_isa_t isa>
jit_uni_eltwise_bwd_t<isa>::~jit_uni_eltwise_bwd_t() = default;
template <cpu_isa_t isa>
status_t jit_uni_eltwise_bwd_t<isa>::init(engine_t *engine) {
CHECK(safe_ptr_assign(kernel_, new jit_uni_kernel_t<isa>(pd())));
return kernel_->create_kernel();
}
template <cpu_isa_t isa>
status_t jit_uni_eltwise_bwd_t<isa>::execute(const exec_ctx_t &ctx) const {
auto src = pd()->use_dst() ? CTX_IN_MEM(const uint8_t *, DNNL_ARG_DST)
: CTX_IN_MEM(const uint8_t *, DNNL_ARG_SRC);
auto diff_dst = CTX_IN_MEM(const uint8_t *, DNNL_ARG_DIFF_DST);
auto diff_src = CTX_OUT_MEM(uint8_t *, DNNL_ARG_DIFF_SRC);
const memory_desc_wrapper data_d(pd()->data_md());
const memory_desc_wrapper diff_data_d(pd()->diff_src_md());
const auto nelems = data_d.nelems(true);
const int cacheline_elems = 64 / data_d.data_type_size();
const data_type_t data_dt = pd()->use_dst() ? pd()->dst_md()->data_type
: pd()->src_md()->data_type;
const auto data_off_bytes
= types::elements_to_bytes(data_dt, data_d.offset0());
const auto diff_off_bytes
= types::elements_to_bytes(data_dt, diff_data_d.offset0());
src += data_off_bytes;
diff_dst += diff_off_bytes;
diff_src += diff_off_bytes;
parallel(0, [&](const int ithr, const int nthr) {
dim_t start {0}, end {0};
balance211(
utils::div_up(nelems, cacheline_elems), nthr, ithr, start, end);
start = nstl::min(nelems, start * cacheline_elems);
end = nstl::min(nelems, end * cacheline_elems);
if (start == end) return;
jit_args_t args;
args.src = src + types::elements_to_bytes(data_dt, start);
args.dst = diff_src + types::elements_to_bytes(data_dt, start);
args.diff_dst = diff_dst + types::elements_to_bytes(data_dt, start);
args.work_amount = end - start;
(*kernel_)(&args);
});
return status::success;
}
template struct jit_uni_eltwise_fwd_t<asimd>;
template struct jit_uni_eltwise_fwd_t<sve>;
template struct jit_uni_eltwise_bwd_t<sve>;
} } } }