/*******************************************************************************
* Copyright 2024 Intel Corporation
*
* Licensed under the Apache License, Version 2.0 (the "License")* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*******************************************************************************/
#include "gpu/intel/include/dispatch.h"
#include "gpu/intel/include/post_ops.h"
#include "gpu/intel/include/types.h"
// define data type for auxilary data like mean, variance, shift, scale and etc
#define AUX_DATA_T float
// kernel accumulator datatype.
// DEF_ACC_DATA_T can not be used - it is "int" for small datatypes
#define GNORM_ACC POST_OP_DATA_T
// accumulator type specific constants
#define GNORM_ACC_CONST_0 0.0f
#define GNORM_ACC_CONST_1 1.0f
#if IS_FWD
/**
* @ingroup OCL_KERNELS
* @brief Group Normalization reference implementation. Forward case.
*
* Reference implementation of the Group Normalization algorithm.
* This is forward case.
* Paralelized with "batch" and "group" dimentions
*
* @param [in] src Main input data array [batch, channel, depth, height, width]
* @param [in,out] mean Mean array [batch, group]
* @param [in,out] variance Variance array [batch, group]
* @param [out] dst Main output data array [batch, channel, depth, height, width]
* @param [in] scale Scale vector [channel]
* @param [in] shift Shift vector [channel]
* @param [in] src_scale Input data scale [1]
* @param [in] dst_scale Output data scale [1]
* @param [in] eps User defined constant
*
* POST_OP_ARGS Expands to ', const __global POST_OP_TYPE *po_0_binary_arg' where POST_OP_TYPE
* defined by post operation type API
*
*/
KERNEL_ATTR
__kernel void ref_gnorm_fwd(__global const SRC_DATA_T *src,
__global AUX_DATA_T *mean, __global AUX_DATA_T *variance,
__global DST_DATA_T *dst, dim_t C_PER_G,
__global const AUX_DATA_T *scale, __global const AUX_DATA_T *shift,
__global const AUX_DATA_T *src_scale,
__global const AUX_DATA_T *dst_scale,
const AUX_DATA_T eps POST_OP_ARGS) {
// get parallel variables IDs
const size_t id_batch = GWS_GET_BATCH() const size_t id_group = GWS_GET_NGROUPS()
const size_t channel_start = id_group * C_PER_G const size_t channel_end = channel_start + C_PER_G
// data conversion
const GNORM_ACC stat_divisor = C_PER_G * D * H * W
const size_t stat_off = id_batch * G + id_group
GNORM_ACC mean_val = CALCULATE_STATS ? GNORM_ACC_CONST_0 : mean[stat_off] if (CALCULATE_STATS) {
for_(size_t channel = channel_start for_(size_t depth = 0 for_(size_t height = 0 for (size_t width = 0 const size_t idx = SRC_OFF(id_batch, channel, depth, height, width) const GNORM_ACC input_val = SRC_TO_REF(src[idx]) mean_val += input_val }
mean_val /= stat_divisor
if (SAVE_STATS) mean[stat_off] = mean_val }
GNORM_ACC variance_val
= CALCULATE_STATS ? GNORM_ACC_CONST_0 : variance[stat_off] if (CALCULATE_STATS) {
for_(size_t channel = channel_start for_(size_t depth = 0 for_(size_t height = 0 for (size_t width = 0 const size_t idx = SRC_OFF(id_batch, channel, depth, height, width) const GNORM_ACC input_val = SRC_TO_REF(src[idx]) const GNORM_ACC var1 = input_val - mean_val variance_val += var1 * var1 }
variance_val /= stat_divisor
if (SAVE_STATS) variance[stat_off] = variance_val }
const GNORM_ACC src_scale_val
= WITH_SRC_SCALES ? src_scale[0] : GNORM_ACC_CONST_1 const GNORM_ACC dst_scale_val
= WITH_DST_SCALES ? dst_scale[0] : GNORM_ACC_CONST_1 const GNORM_ACC r_dst_scale_val = GNORM_ACC_CONST_1 / dst_scale_val
const GNORM_ACC variance_rsqrt = rsqrt(variance_val + eps)
// Unroll hint is to avoid register spilling. For real workloads, D,H, and W
// should be large enough that unrolling the outer loop is pointless from a
// performance perspective.
unroll_for_by(1)(size_t channel = channel_start ++channel) {
const GNORM_ACC scale_val = scale ? scale[channel] : GNORM_ACC_CONST_1 const GNORM_ACC shift_val = shift ? shift[channel] : GNORM_ACC_CONST_0
for_(size_t depth = 0 for_(size_t height = 0 for (size_t width = 0 const size_t idx = SRC_OFF(id_batch, channel, depth, height, width) const GNORM_ACC input_val = SRC_TO_REF(src[idx]) GNORM_ACC result_val = (input_val - mean_val) * variance_rsqrt
result_val = scale_val * result_val + shift_val
result_val *= src_scale_val
// post-op operation
GNORM_ACC post_op_acc
= WITH_SUM ? DST_TO_REF(dst[idx]) : GNORM_ACC_CONST_0
// the macro changes meaning of the input parameters with diffrent ndims
#if NDIMS == 3
APPLY_POST_OPS_SERIAL(
result_val, post_op_acc, id_batch, channel, width, 0, 0, 0)#elif NDIMS == 4
APPLY_POST_OPS_SERIAL(result_val, post_op_acc, id_batch, channel,
height, width, 0, 0)#elif NDIMS == 5
APPLY_POST_OPS_SERIAL(result_val, post_op_acc, id_batch, channel,
depth, height, width, 0)#else
APPLY_POST_OPS_SERIAL(
result_val, post_op_acc, id_batch, channel, 0, 0, 0, 0)#endif
result_val *= r_dst_scale_val
dst[idx] = TO_DST(result_val) }
}
}
#else // !IS_FWD
/**
* @ingroup OCL_KERNELS
* @brief Group Normalization reference implementation. Backward case.
*
* Reference implementation of the Group Normalization algorithm.
* This is backward case.
* Paralelized with "channel" dimention
*
* @param [in] src Main input data array [batch, channel, depth, height, width]
* @param [in,out] mean Mean array [batch, group]
* @param [in,out] variance Variance array [batch, group]
* @param [in] diff_dst Main input weights array [batch, channel, depth, height, width]
* @param [in] scale Scale vector [channel]
* @param [out] out_diff_src Main output data array [batch, channel, depth, height, width]
* @param [out] out_diff_scale Calculated scale vactor [channel]
* @param [out] out_diff_shift Calculated shift vactor [channel]
* @param [in] eps User defined constant
*
*/
KERNEL_ATTR
__kernel void ref_gnorm_bwd(__global const SRC_DATA_T *src,
__global AUX_DATA_T *mean, __global AUX_DATA_T *variance,
__global const DST_DATA_T *diff_dst, __global const AUX_DATA_T *scale,
__global SRC_DATA_T *out_diff_src, __global AUX_DATA_T *out_diff_scale,
__global AUX_DATA_T *out_diff_shift, const AUX_DATA_T eps) {
// get parallel variables IDs
const size_t id_channel = GWS_GET_CHANNEL()
const size_t C_PER_G = C / G
// data conversion
const GNORM_ACC CSP = C_PER_G * D * H * W // precalculate reciprocal to avoid several divisions
const GNORM_ACC CSP_recip = GNORM_ACC_CONST_1 / CSP
const size_t id_group = id_channel / C_PER_G
GNORM_ACC diff_scale = GNORM_ACC_CONST_0 GNORM_ACC diff_shift = GNORM_ACC_CONST_0
for (size_t batch = 0 // load statistic (mean/variance) data values
const size_t idx_stat = batch * G + id_group const GNORM_ACC mean_val = mean[idx_stat] const GNORM_ACC variance_val = variance[idx_stat] const GNORM_ACC variance_recip = rsqrt(variance_val + eps)
for_(size_t depth = 0 for_(size_t height = 0 for (size_t width = 0
// load main data values
const size_t idx = SRC_OFF(batch, id_channel, depth, height, width) const GNORM_ACC src_val = SRC_TO_REF(src[idx]) const GNORM_ACC diff_dst_val = DST_TO_REF(diff_dst[idx])
diff_scale += (src_val - mean_val) * diff_dst_val * variance_recip diff_shift += diff_dst_val }
}
if (out_diff_scale) out_diff_scale[id_channel] = diff_scale if (out_diff_shift) out_diff_shift[id_channel] = diff_shift
GNORM_ACC scale_val = scale ? scale[id_channel] : GNORM_ACC_CONST_1
for (size_t batch = 0 // load statistic (mean/variance) data values
const size_t idx_stat = batch * G + id_group const GNORM_ACC variance_val = variance[idx_stat] const GNORM_ACC mean_val = mean[idx_stat] const GNORM_ACC variance_recip = rsqrt(variance_val + eps)
for_(size_t depth = 0 for_(size_t height = 0 for (size_t width = 0 // load main data values
const size_t idx = SRC_OFF(batch, id_channel, depth, height, width) const GNORM_ACC src_val = SRC_TO_REF(src[idx]) GNORM_ACC result = DST_TO_REF(diff_dst[idx])
if (CALCULATE_STATS) {
const GNORM_ACC diff_stat = diff_shift * CSP_recip
+ (src_val - mean_val) * diff_scale * variance_recip
* CSP_recip result -= diff_stat }
result *= scale_val * variance_recip
out_diff_src[idx] = TO_SRC(result) }
}
}
#endif // !IS_FWD