#include <assert.h>
#include <math.h>
#include "common/c_types_map.hpp"
#include "common/compiler_workarounds.hpp"
#include "common/dnnl_thread.hpp"
#include "common/reorder.hpp"
#include "common/type_helpers.hpp"
#include "cpu/cpu_primitive.hpp"
#include "cpu/ref_io_helper.hpp"
#include "cpu/simple_layer_normalization.hpp"
namespace dnnl {
namespace impl {
namespace cpu {
using namespace memory_tracking::names;
using namespace data_type;
status_t simple_layer_normalization_fwd_t::pd_t::init(engine_t *engine) {
using namespace data_type;
using skip_mask_t = primitive_attr_t::skip_mask_t;
const memory_desc_wrapper src_d(src_md());
VDISPATCH_LNORM(is_fwd(), VERBOSE_BAD_PROPKIND);
VDISPATCH_LNORM(!has_zero_dim_memory(), VERBOSE_EMPTY_TENSOR, "src");
VDISPATCH_LNORM(utils::one_of(src_md()->data_type, f32, bf16, f16, s8, u8),
VERBOSE_UNSUPPORTED_DT);
VDISPATCH_LNORM(utils::one_of(dst_md()->data_type, f32, bf16, f16, s8, u8),
VERBOSE_UNSUPPORTED_DT);
VDISPATCH_LNORM(platform::has_data_type_support(src_md()->data_type),
VERBOSE_UNSUPPORTED_DT);
VDISPATCH_LNORM(platform::has_data_type_support(dst_md()->data_type),
VERBOSE_UNSUPPORTED_DT);
VDISPATCH_LNORM(stat_md()->data_type == f32, VERBOSE_UNSUPPORTED_DT);
VDISPATCH_LNORM(check_scale_shift_data_type(), VERBOSE_UNSUPPORTED_FEATURE,
"unsupported scale or shift data type");
VDISPATCH_LNORM(attr()->has_default_values(
skip_mask_t::scales | skip_mask_t::post_ops),
VERBOSE_UNSUPPORTED_ATTR);
VDISPATCH_LNORM(attr_scales_ok(), VERBOSE_UNSUPPORTED_SCALES_CFG);
VDISPATCH_LNORM(post_ops_ok(), VERBOSE_UNSUPPORTED_POSTOP);
VDISPATCH_LNORM(set_default_formats_common(), VERBOSE_UNSUPPORTED_TAG);
VDISPATCH_LNORM(src_d.is_blocking_desc(), VERBOSE_BLOCKING_FAIL,
"blocking descriptor fail");
VDISPATCH_LNORM(src_d.blocking_desc().strides[ndims() - 1] == 1,
VERBOSE_BLOCKING_FAIL, "bad stride value");
VDISPATCH_LNORM(impl::is_dense_format_kind({src_md(), dst_md()}),
VERBOSE_UNSUPPORTED_SPARSE_CFG);
CHECK(fill_compatible_stats_md(*src_md(), reordered_stat_md_));
if (reordered_stat_md_ != *stat_md() && !stats_are_tmp()) {
CHECK(reorder_primitive_desc_create(reorder_pd_, engine,
stats_are_src() ? stat_md() : &reordered_stat_md_,
stats_are_src() ? &reordered_stat_md_ : stat_md()));
}
bool ok = attr_.set_default_formats(dst_md(0)) == status::success;
VDISPATCH_LNORM(ok, VERBOSE_UNSUPPORTED_POSTOP);
init_scratchpad();
return status::success;
}
status_t simple_layer_normalization_fwd_t::execute_forward(
const exec_ctx_t &ctx) const {
const bool use_scale = pd()->use_scale();
const bool use_shift = pd()->use_shift();
const bool skip_mean = pd()->skip_mean();
const auto &scratchpad = ctx.get_scratchpad_grantor();
const auto src = CTX_IN_MEM(const void *, DNNL_ARG_SRC);
auto dst = CTX_OUT_MEM(void *, DNNL_ARG_DST);
auto scale = CTX_IN_MEM(const float *, DNNL_ARG_SCALE);
auto shift = CTX_IN_MEM(const float *, DNNL_ARG_SHIFT);
float *mean, *variance;
if (pd()->use_tmp_stats()) {
mean = skip_mean ? nullptr
: scratchpad.template get<float>(key_lnorm_tmp_mean);
variance = scratchpad.template get<float>(key_lnorm_tmp_var);
} else {
mean = pd()->stats_are_src()
? const_cast<float *>(CTX_IN_MEM(const float *, DNNL_ARG_MEAN))
: CTX_OUT_MEM(float *, DNNL_ARG_MEAN);
variance = pd()->stats_are_src()
? const_cast<float *>(
CTX_IN_MEM(const float *, DNNL_ARG_VARIANCE))
: CTX_OUT_MEM(float *, DNNL_ARG_VARIANCE);
}
const float *src_scales
= CTX_IN_MEM(const float *, DNNL_ARG_ATTR_SCALES | DNNL_ARG_SRC);
const float *dst_scales
= CTX_IN_MEM(const float *, DNNL_ARG_ATTR_SCALES | DNNL_ARG_DST);
const memory_desc_wrapper src_d(pd()->src_md());
const memory_desc_wrapper dst_d(pd()->dst_md());
const bool with_src_scales
= !pd()->attr()->scales_.has_default_values(DNNL_ARG_SRC);
const bool with_dst_scales
= !pd()->attr()->scales_.has_default_values(DNNL_ARG_DST);
const dim_t N = pd()->across_axis();
const dim_t C = pd()->norm_axis();
const float C_f = static_cast<float>(C);
const dim_t C_padded = src_d.padded_dims()[pd()->ndims() - 1];
const auto calculate_stats = !pd()->stats_are_src();
const auto src_dt = pd()->src_md()->data_type;
const auto dst_dt = pd()->dst_md()->data_type;
const auto eps = pd()->desc()->layer_norm_epsilon;
const auto save_stats = pd()->is_training();
parallel(0, [= COMPAT_THIS_CAPTURE](const int ithr, const int nthr) {
dim_t N_start = 0, N_end = 0;
balance211(N, nthr, ithr, N_start, N_end);
const char *const __restrict src_ptr
= reinterpret_cast<const char *>(src)
+ N_start * C_padded * src_d.data_type_size();
char *const __restrict dst_ptr = reinterpret_cast<char *>(dst)
+ N_start * C_padded * dst_d.data_type_size();
float *const __restrict mean_ptr = skip_mean ? nullptr : &mean[N_start];
float *const __restrict var_ptr = &variance[N_start];
const size_t block_size = N_end - N_start;
for (size_t offset = 0; offset < block_size; offset++) {
float v_mean = 0, v_variance = 0;
if (calculate_stats) {
if (!skip_mean) {
PRAGMA_OMP_SIMD(reduction(+ : v_mean))
for (dim_t c = 0; c < C; ++c) {
float s = io::load_float_value(
src_dt, src_ptr, c + C * offset);
v_mean += s;
}
v_mean /= C_f;
}
PRAGMA_OMP_SIMD(reduction(+ : v_variance))
for (dim_t c = 0; c < C; ++c) {
float s = io::load_float_value(
src_dt, src_ptr, c + C * offset);
float src_sub_mean = s - v_mean;
v_variance += src_sub_mean * src_sub_mean;
}
v_variance /= C_f;
} else {
if (!skip_mean) { v_mean = mean_ptr[offset]; }
v_variance = var_ptr[offset];
}
const float inv_sqrtvar = 1.f / sqrtf(v_variance + eps);
if (use_scale && use_shift) {
PRAGMA_OMP_SIMD()
for (dim_t c = 0; c < C; ++c) {
const float sm = scale[c] * inv_sqrtvar;
const float sv = shift[c];
const size_t off = c + C * offset;
float s = io::load_float_value(src_dt, src_ptr, off);
float d = sm * (s - v_mean) + sv;
if (with_src_scales) d *= src_scales[0];
ref_post_ops_t::args_t args;
args.ctx = &ctx;
args.l_offset = N_start * C_padded + off;
args.dst_md = pd()->dst_md();
ref_post_ops->execute(d, args);
if (with_dst_scales) d /= dst_scales[0];
io::store_float_value(dst_dt, d, dst_ptr, off);
}
} else if (use_scale) {
PRAGMA_OMP_SIMD()
for (dim_t c = 0; c < C; ++c) {
const float sm = scale[c] * inv_sqrtvar;
const size_t off = c + C * offset;
float s = io::load_float_value(src_dt, src_ptr, off);
float d = sm * (s - v_mean);
if (with_src_scales) d *= src_scales[0];
ref_post_ops_t::args_t args;
args.ctx = &ctx;
args.l_offset = N_start * C_padded + off;
args.dst_md = pd()->dst_md();
ref_post_ops->execute(d, args);
if (with_dst_scales) d /= dst_scales[0];
io::store_float_value(dst_dt, d, dst_ptr, off);
}
} else if (use_shift) {
PRAGMA_OMP_SIMD()
for (dim_t c = 0; c < C; ++c) {
const float sm = 1.f * inv_sqrtvar;
const float sv = shift[c];
const size_t off = c + C * offset;
float s = io::load_float_value(src_dt, src_ptr, off);
float d = sm * (s - v_mean) + sv;
if (with_src_scales) d *= src_scales[0];
ref_post_ops_t::args_t args;
args.ctx = &ctx;
args.l_offset = N_start * C_padded + off;
args.dst_md = pd()->dst_md();
ref_post_ops->execute(d, args);
if (with_dst_scales) d /= dst_scales[0];
io::store_float_value(dst_dt, d, dst_ptr, off);
}
} else {
PRAGMA_OMP_SIMD()
for (dim_t c = 0; c < C; ++c) {
const float sm = 1.f * inv_sqrtvar;
const size_t off = c + C * offset;
float s = io::load_float_value(src_dt, src_ptr, off);
float d = sm * (s - v_mean);
if (with_src_scales) d *= src_scales[0];
ref_post_ops_t::args_t args;
args.ctx = &ctx;
args.l_offset = N_start * C_padded + off;
args.dst_md = pd()->dst_md();
ref_post_ops->execute(d, args);
if (with_dst_scales) d /= dst_scales[0];
io::store_float_value(dst_dt, d, dst_ptr, off);
}
}
if (calculate_stats && save_stats) {
if (!skip_mean) { mean_ptr[offset] = v_mean; }
var_ptr[offset] = v_variance;
}
}
});
return status::success;
}
status_t simple_layer_normalization_bwd_t::pd_t::init(engine_t *engine) {
using namespace data_type;
const memory_desc_wrapper src_d(src_md());
VDISPATCH_LNORM(!is_fwd(), VERBOSE_BAD_PROPKIND);
VDISPATCH_LNORM(!has_zero_dim_memory(), VERBOSE_EMPTY_TENSOR, "src");
VDISPATCH_LNORM(utils::one_of(src_md()->data_type, f32, bf16, f16),
VERBOSE_UNSUPPORTED_DT);
VDISPATCH_LNORM(utils::one_of(diff_dst_md()->data_type, f32, bf16, f16),
VERBOSE_UNSUPPORTED_DT);
VDISPATCH_LNORM(utils::one_of(diff_src_md()->data_type, f32, bf16, f16),
VERBOSE_UNSUPPORTED_DT);
VDISPATCH_LNORM(platform::has_data_type_support(src_md()->data_type),
VERBOSE_UNSUPPORTED_DT);
VDISPATCH_LNORM(platform::has_data_type_support(diff_dst_md()->data_type),
VERBOSE_UNSUPPORTED_DT);
VDISPATCH_LNORM(platform::has_data_type_support(diff_src_md()->data_type),
VERBOSE_UNSUPPORTED_DT);
VDISPATCH_LNORM(stat_md()->data_type == f32, VERBOSE_UNSUPPORTED_DT);
VDISPATCH_LNORM(check_scale_shift_data_type(), VERBOSE_UNSUPPORTED_FEATURE,
"unsupported scale or shift data type");
VDISPATCH_LNORM(attr()->has_default_values(), VERBOSE_UNSUPPORTED_ATTR);
VDISPATCH_LNORM(set_default_formats_common(), VERBOSE_UNSUPPORTED_TAG);
VDISPATCH_LNORM(src_d.is_blocking_desc(), VERBOSE_BLOCKING_FAIL,
"blocking descriptor fail");
VDISPATCH_LNORM(src_d.blocking_desc().strides[ndims() - 1] == 1,
VERBOSE_BLOCKING_FAIL, "bad stride value");
VDISPATCH_LNORM(impl::is_dense_format_kind(
{src_md(), diff_src_md(), dst_md(), diff_dst_md()}),
VERBOSE_UNSUPPORTED_SPARSE_CFG);
CHECK(fill_compatible_stats_md(*src_md(), reordered_stat_md_));
if (reordered_stat_md_ != *stat_md()) {
CHECK(reorder_primitive_desc_create(
reorder_pd_, engine, stat_md(), &reordered_stat_md_));
}
nthr_ = dnnl_get_max_threads();
init_scratchpad();
return status::success;
}
status_t simple_layer_normalization_bwd_t::execute_backward(
const exec_ctx_t &ctx) const {
status_t status = status::success;
const bool use_scale = pd()->use_scale();
const auto &scratchpad = ctx.get_scratchpad_grantor();
auto src = CTX_IN_MEM(const void *, DNNL_ARG_SRC);
auto diff_dst = CTX_IN_MEM(const void *, DNNL_ARG_DIFF_DST);
auto scale = CTX_IN_MEM(float *, DNNL_ARG_SCALE);
auto diff_src = CTX_OUT_CLEAN_MEM(void *, DNNL_ARG_DIFF_SRC, status);
auto diff_scale = CTX_OUT_CLEAN_MEM(float *, DNNL_ARG_DIFF_SCALE, status);
CHECK(status);
auto diff_shift = CTX_OUT_CLEAN_MEM(float *, DNNL_ARG_DIFF_SHIFT, status);
CHECK(status);
const bool skip_mean = pd()->skip_mean();
const float *mean, *variance;
if (pd()->use_tmp_stats()) {
mean = skip_mean ? nullptr
: scratchpad.template get<float>(key_lnorm_tmp_mean);
variance = scratchpad.template get<float>(key_lnorm_tmp_var);
} else {
mean = CTX_IN_MEM(const float *, DNNL_ARG_MEAN);
variance = CTX_IN_MEM(const float *, DNNL_ARG_VARIANCE);
}
float *const inv_sqrtvar
= scratchpad.template get<float>(key_lnorm_inv_sqrtvar);
const memory_desc_wrapper src_d(pd()->src_md());
const memory_desc_wrapper diff_dst_d(pd()->diff_dst_md());
const memory_desc_wrapper diff_src_d(pd()->diff_src_md());
const dim_t N = pd()->across_axis();
const dim_t C = pd()->norm_axis();
const float C_f = static_cast<float>(C);
const dim_t C_padded = src_d.padded_dims()[pd()->ndims() - 1];
float *reduce = scratchpad.template get<float>(key_lnorm_reduction);
if (diff_scale == nullptr)
diff_scale = scratchpad.template get<float>(key_lnorm_tmp_diff_ss);
if (diff_shift == nullptr) {
diff_shift = scratchpad.template get<float>(key_lnorm_tmp_diff_ss);
}
const int max_nthr = pd()->nthr_;
const auto src_dt = pd()->src_md()->data_type;
const auto diff_dst_dt = pd()->diff_dst_md()->data_type;
const auto diff_src_dt = pd()->diff_src_md()->data_type;
const auto eps = pd()->desc()->layer_norm_epsilon;
const auto calculate_diff_stats = !pd()->stats_are_src();
parallel(max_nthr, [=](int ithr, int nthr) {
dim_t N_start = 0, N_end = 0;
balance211(N, nthr, ithr, N_start, N_end);
const size_t block_size = N_end - N_start;
const char *const __restrict src_ptr
= reinterpret_cast<const char *>(src)
+ N_start * C_padded * src_d.data_type_size();
const char *const __restrict diff_dst_ptr
= reinterpret_cast<const char *>(diff_dst)
+ N_start * C_padded * diff_dst_d.data_type_size();
const float *mean_ptr = skip_mean ? nullptr : &mean[N_start];
const float *var_ptr = &variance[N_start];
float *const inv_sqrtvar_ptr = &inv_sqrtvar[N_start];
float *my_diff_gamma = reduce + C * ithr;
float *my_diff_beta = reduce + C * nthr + C * ithr;
PRAGMA_OMP_SIMD()
for (dim_t c = 0; c < C; c++) {
my_diff_gamma[c] = 0.;
my_diff_beta[c] = 0.;
}
for (size_t offset = 0; offset < block_size; offset++) {
inv_sqrtvar_ptr[offset] = 1.f / sqrtf(var_ptr[offset] + eps);
float mean_val = skip_mean ? 0.f : mean_ptr[offset];
PRAGMA_OMP_SIMD()
for (dim_t c = 0; c < C; c++) {
const size_t off = c + C * offset;
float s = io::load_float_value(src_dt, src_ptr, off);
float dd = io::load_float_value(diff_dst_dt, diff_dst_ptr, off);
my_diff_gamma[c]
+= (s - mean_val) * dd * inv_sqrtvar_ptr[offset];
my_diff_beta[c] += dd;
}
}
});
parallel_nd(C, [=](dim_t c) {
float diff_gamma = 0, diff_beta = 0;
for (dim_t n = 0; n < max_nthr; n++) {
diff_gamma += reduce[C * n + c];
diff_beta += reduce[C * max_nthr + C * n + c];
}
diff_scale[c] = diff_gamma;
diff_shift[c] = diff_beta;
});
parallel(max_nthr, [=](int ithr, int nthr) {
dim_t N_start = 0, N_end = 0;
balance211(N, nthr, ithr, N_start, N_end);
const size_t block_size = N_end - N_start;
const char *const __restrict src_ptr
= reinterpret_cast<const char *>(src)
+ N_start * C_padded * src_d.data_type_size();
const char *const __restrict diff_dst_ptr
= reinterpret_cast<const char *>(diff_dst)
+ N_start * C_padded * diff_dst_d.data_type_size();
char *const __restrict diff_src_ptr = reinterpret_cast<char *>(diff_src)
+ N_start * C_padded * diff_src_d.data_type_size();
const float *mean_ptr = skip_mean ? nullptr : &mean[N_start];
float *const inv_sqrtvar_ptr = &inv_sqrtvar[N_start];
float dd_gamma, dd_gamma_x, mean_val;
for (size_t offset = 0; offset < block_size; offset++) {
dd_gamma = dd_gamma_x = 0;
mean_val = skip_mean ? 0.f : mean_ptr[offset];
if (calculate_diff_stats) {
if (use_scale) {
PRAGMA_OMP_SIMD(reduction(+ : dd_gamma, dd_gamma_x))
for (dim_t c = 0; c < C; c++) {
const size_t off = c + C * offset;
float s = io::load_float_value(src_dt, src_ptr, off);
float dd = io::load_float_value(
diff_dst_dt, diff_dst_ptr, off);
dd_gamma += dd * scale[c];
dd_gamma_x += dd * scale[c] * (s - mean_val);
}
} else {
PRAGMA_OMP_SIMD(reduction(+ : dd_gamma, dd_gamma_x))
for (dim_t c = 0; c < C; c++) {
const size_t off = c + C * offset;
float s = io::load_float_value(src_dt, src_ptr, off);
float dd = io::load_float_value(
diff_dst_dt, diff_dst_ptr, off);
dd_gamma += dd;
dd_gamma_x += dd * (s - mean_val);
}
}
dd_gamma_x *= inv_sqrtvar_ptr[offset];
}
if (use_scale) {
PRAGMA_OMP_SIMD()
for (dim_t c = 0; c < C; c++) {
const size_t off = c + C * offset;
float dd = io::load_float_value(
diff_dst_dt, diff_dst_ptr, off);
float ds = dd * scale[c];
if (calculate_diff_stats) {
float s = io::load_float_value(src_dt, src_ptr, off);
ds -= dd_gamma / C_f;
ds -= (s - mean_val) * dd_gamma_x
* inv_sqrtvar_ptr[offset] / C_f;
}
ds *= inv_sqrtvar_ptr[offset];
io::store_float_value(diff_src_dt, ds, diff_src_ptr, off);
}
} else {
PRAGMA_OMP_SIMD()
for (dim_t c = 0; c < C; c++) {
const size_t off = c + C * offset;
float dd = io::load_float_value(
diff_dst_dt, diff_dst_ptr, off);
float ds = dd;
if (calculate_diff_stats) {
float s = io::load_float_value(src_dt, src_ptr, off);
ds -= dd_gamma / C_f;
ds -= (s - mean_val) * dd_gamma_x
* inv_sqrtvar_ptr[offset] / C_f;
}
ds *= inv_sqrtvar_ptr[offset];
io::store_float_value(diff_src_dt, ds, diff_src_ptr, off);
}
}
}
});
return status::success;
}
} } }