Files
paddlepaddle--paddle/paddle/phi/kernels/legacy/gpu/ln_utils.h
T
2026-07-13 12:40:42 +08:00

832 lines
26 KiB
C++

// Copyright (c) 2023 PaddlePaddle Authors. All Rights Reserved.
//
// 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.
/* Copyright (c) 2022, NVIDIA CORPORATION. All rights reserved. */
/*This code is copied from NVIDIA apex:
* https://github.com/NVIDIA/apex
* with minor changes. */
#pragma once
#include <stdint.h>
#include <stdio.h>
#include <cassert>
#include <cuda_bf16.h> // NOLINT
#include <cuda_fp16.h> // NOLINT
#include "ln.h" // NOLINT
////////////////////////////////////////////////////////////////////////////////////////////////////
constexpr uint32_t THREADS_PER_WARP = 32;
////////////////////////////////////////////////////////////////////////////////////////////////////
inline void check_cuda_(cudaError_t status, const char *file, int line) {
if (status != cudaSuccess) {
fprintf(stderr,
"CUDA Error: %s %s %d\n",
cudaGetErrorString(status),
file,
line);
exit(status);
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////
#define CHECK_CUDA(ans) \
{ check_cuda_((ans), __FILE__, __LINE__); }
////////////////////////////////////////////////////////////////////////////////////////////////////
#define DIVUP(x, y) (((x) + ((y)-1)) / (y))
////////////////////////////////////////////////////////////////////////////////////////////////////
#define REGISTER_FWD_LAUNCHER(HIDDEN_SIZE, \
WTYPE, \
ITYPE, \
OTYPE, \
CTYPE, \
CTAS_PER_ROW, \
WARPS_M, \
WARPS_N, \
BYTES_PER_LDG) \
void ln_fwd_##HIDDEN_SIZE##_##WTYPE##_##ITYPE##_##OTYPE##_##CTYPE( \
LaunchParams<FwdParams> &launch_params, const bool configure_params) { \
launch_<WTYPE, \
ITYPE, \
OTYPE, \
CTYPE, \
uint32_t, \
HIDDEN_SIZE, \
CTAS_PER_ROW, \
WARPS_M, \
WARPS_N, \
BYTES_PER_LDG>(launch_params, configure_params); \
} \
static FwdRegistrar<WTYPE, ITYPE, OTYPE, CTYPE, HIDDEN_SIZE> \
reg_##HIDDEN_SIZE##_##WTYPE##_##ITYPE##_##OTYPE##_##CTYPE( \
ln_fwd_##HIDDEN_SIZE##_##WTYPE##_##ITYPE##_##OTYPE##_##CTYPE)
////////////////////////////////////////////////////////////////////////////////////////////////////
#define REGISTER_BWD_LAUNCHER(HIDDEN_SIZE, \
WTYPE, \
ITYPE, \
OTYPE, \
CTYPE, \
CTAS_PER_ROW, \
WARPS_M, \
WARPS_N, \
BYTES_PER_LDG, \
BYTES_PER_LDG_FINALIZE) \
void ln_bwd_##HIDDEN_SIZE##_##WTYPE##_##ITYPE##_##OTYPE##_##CTYPE( \
LaunchParams<BwdParams> &launch_params, const bool configure_params) { \
launch_<WTYPE, \
ITYPE, \
OTYPE, \
CTYPE, \
uint32_t, \
HIDDEN_SIZE, \
CTAS_PER_ROW, \
WARPS_M, \
WARPS_N, \
BYTES_PER_LDG, \
BYTES_PER_LDG_FINALIZE>(launch_params, configure_params); \
} \
static BwdRegistrar<WTYPE, ITYPE, OTYPE, CTYPE, HIDDEN_SIZE> \
reg_##HIDDEN_SIZE##_##WTYPE##_##ITYPE##_##OTYPE##_##CTYPE( \
ln_bwd_##HIDDEN_SIZE##_##WTYPE##_##ITYPE##_##OTYPE##_##CTYPE)
////////////////////////////////////////////////////////////////////////////////////////////////////
inline __device__ float2 operator+(const float2 &a, const float2 &b) {
return {a.x + b.x, a.y + b.y};
}
////////////////////////////////////////////////////////////////////////////////////////////////////
inline __device__ void operator+=(float2 &a, const float2 &b) { // NOLINT
a.x += b.x;
a.y += b.y;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
template <typename T>
struct Sum {
inline __device__ Sum() {}
inline __device__ T operator()(const T &a, const T &b) { return a + b; }
};
////////////////////////////////////////////////////////////////////////////////////////////////////
template <typename T>
inline __device__ T warp_shuffle_xor(const T &x, uint32_t idx) {
return __shfl_xor_sync(uint32_t(-1), x, idx);
}
template <>
inline __device__ float2 warp_shuffle_xor<float2>(const float2 &x,
uint32_t idx) {
return {warp_shuffle_xor(x.x, idx), warp_shuffle_xor(x.y, idx)};
}
template <typename T>
inline __device__ T warp_shuffle_down(const T &x, uint32_t idx) {
return __shfl_down_sync(uint32_t(-1), x, idx);
}
template <>
inline __device__ float2 warp_shuffle_down<float2>(const float2 &x,
uint32_t idx) {
return {warp_shuffle_down(x.x, idx), warp_shuffle_down(x.y, idx)};
}
////////////////////////////////////////////////////////////////////////////////////////////////////
namespace phi {
namespace layer_norm {
////////////////////////////////////////////////////////////////////////////////////////////////////
struct uint16 {
uint4 u;
uint4 v;
uint4 s;
uint4 t;
};
////////////////////////////////////////////////////////////////////////////////////////////////////
struct uint8 {
uint4 u;
uint4 v;
};
////////////////////////////////////////////////////////////////////////////////////////////////////
template <int BYTES>
struct BytesToType {};
template <>
struct BytesToType<64> {
using Type = uint16;
static_assert(sizeof(Type) == 64);
};
template <>
struct BytesToType<32> {
using Type = uint8;
static_assert(sizeof(Type) == 32);
};
template <>
struct BytesToType<16> {
using Type = uint4;
static_assert(sizeof(Type) == 16);
};
template <>
struct BytesToType<8> {
using Type = uint64_t;
static_assert(sizeof(Type) == 8);
};
template <>
struct BytesToType<4> {
using Type = uint32_t;
static_assert(sizeof(Type) == 4);
};
template <>
struct BytesToType<2> {
using Type = uint16_t;
static_assert(sizeof(Type) == 2);
};
template <>
struct BytesToType<1> {
using Type = uint8_t;
static_assert(sizeof(Type) == 1);
};
////////////////////////////////////////////////////////////////////////////////////////////////////
template <typename T>
struct TypeToVec2 {};
template <>
struct TypeToVec2<float> {
using Type = float2;
};
template <>
struct TypeToVec2<half> {
using Type = half2;
};
template <>
struct TypeToVec2<nv_bfloat16> {
using Type = nv_bfloat162;
};
////////////////////////////////////////////////////////////////////////////////////////////////////
template <int INDEX>
struct Get {
template <typename T, typename R>
static inline __device__ R of(const T &vec);
};
template <>
template <typename T, typename R>
inline __device__ R Get<0>::of(const T &vec) {
return vec.x;
}
template <>
template <typename T, typename R>
inline __device__ R Get<1>::of(const T &vec) {
return vec.y;
}
template <>
template <typename T, typename R>
inline __device__ R Get<2>::of(const T &vec) {
return vec.z;
}
template <>
template <typename T, typename R>
inline __device__ R Get<3>::of(const T &vec) {
return vec.w;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
template <typename Src, typename Dst>
struct Converter {
static inline __device__ Dst convert(const Src &from) { return Dst(from); }
};
template <>
struct Converter<float2, half2> {
static inline __device__ half2 convert(const float2 &x) {
return __float22half2_rn(x);
}
};
template <>
struct Converter<float2, nv_bfloat162> {
static inline __device__ nv_bfloat162 convert(const float2 &x) {
#if __CUDA_ARCH__ >= 800
return __float22bfloat162_rn(x);
#else
union {
nv_bfloat162 raw;
nv_bfloat16 x;
nv_bfloat16 y;
} tmp;
tmp.x = __float2bfloat16_rn(x.x);
tmp.y = __float2bfloat16_rn(x.y);
return tmp.raw;
#endif
}
};
////////////////////////////////////////////////////////////////////////////////////////////////////
template <typename T>
struct Zeros {
static inline __device__ T get() { return T(0.f); }
};
template <>
struct Zeros<float2> {
static inline __device__ float2 get() { return make_float2(0.f, 0.f); }
};
////////////////////////////////////////////////////////////////////////////////////////////////////
template <typename Elt_type, uint32_t NUM_ELT>
struct Vec {
enum { BYTES = NUM_ELT * sizeof(Elt_type) };
using Vec_type = typename BytesToType<BYTES>::Type;
using Alias_type = union {
Vec_type vec;
Elt_type elt[NUM_ELT];
};
Alias_type data;
inline __device__ void init(Elt_type value) {
#pragma unroll
for (int it = 0; it < NUM_ELT; it++) {
this->data.elt[it] = value;
}
}
template <typename S>
inline __device__ void to(Vec<S, NUM_ELT> &other) { // NOLINT
#pragma unroll
for (int it = 0; it < NUM_ELT; it++) {
other.data.elt[it] = S(this->data.elt[it]);
}
}
template <typename Op>
inline __device__ void assign(const Op &op) {
#pragma unroll
for (int it = 0; it < NUM_ELT; it++) {
this->data.elt[it] = op(it);
}
}
inline __device__ void load_from(const void *base_ptr, const size_t idx) {
this->data.vec = static_cast<const Vec_type *>(base_ptr)[idx];
}
inline __device__ void store_to(void *base_ptr, const size_t idx) {
static_cast<Vec_type *>(base_ptr)[idx] = this->data.vec;
}
};
////////////////////////////////////////////////////////////////////////////////////////////////////
template <uint32_t CTAS_PER_ROW>
struct InterCTASync {
template <typename Params>
inline __device__ InterCTASync(Params &params, // NOLINT
uint32_t bidm,
uint32_t bidn)
: phase_counter_(0),
b0_(params.barrier + bidm) // The barrier for this group of CTAs.
,
b1_(params.barrier + bidm + params.ctas_per_col) {
} // The barrier for this group of CTAs.
inline __device__ void spin_wait_(int *barrier, int step, int expected) {
asm volatile("red.release.gpu.global.add.s32 [%0], %1;" ::"l"(barrier),
"r"(step));
for (int found = -1; found != expected;) {
asm volatile("ld.global.acquire.gpu.b32 %0, [%1];"
: "=r"(found)
: "l"(barrier));
}
}
inline __device__ void sync() {
// ALL THREADS MUST ENTER!
// We switch barrier every iteration.
int *barrier = phase_counter_ & 0x1 ? b1_ : b0_;
// We decrement every other iteration.
bool dec = phase_counter_ & 0x2;
int step = dec ? -1 : 1;
int expected = dec ? 0 : CTAS_PER_ROW;
// There are only 4 phases: up/down for b0/b1.
phase_counter_ = (phase_counter_ + 1) & 0x3;
if (threadIdx.x == 0) {
spin_wait_(barrier, step, expected);
}
// CTA waits for thread 0
__syncthreads();
}
int phase_counter_;
int *b0_;
int *b1_;
};
////////////////////////////////////////////////////////////////////////////////////////////////////
template <typename T, uint32_t CTAS_PER_ROW, uint32_t WARPS_M, uint32_t WARPS_N>
struct Reducer : public Reducer<T, 1, WARPS_M, WARPS_N> {
using InterCTASync = InterCTASync<CTAS_PER_ROW>;
using Base = Reducer<T, 1, WARPS_M, WARPS_N>;
using Type = typename Base::Type;
enum { SMEM_BYTES = Base::SMEM_BYTES };
enum { WS_BARRIER_BYTES = 2 * sizeof(int) };
enum { WS_DATA_BYTES = WARPS_M * CTAS_PER_ROW * sizeof(T) };
// size of the barriers + temporary result per CTA (multiply with CTAS_PER_ROW
// to get total)
enum {
WORKSPACE_BYTES_PER_GROUP =
Base::WORKSPACE_BYTES_PER_GROUP + WS_BARRIER_BYTES + WS_DATA_BYTES
};
template <typename Params>
inline __device__ Reducer(Params &params, // NOLINT
uint32_t bidm,
uint32_t bidn,
uint32_t warp_m,
uint32_t warp_n,
uint32_t lane,
void *smem)
: Base(params, bidm, bidn, warp_m, warp_n, lane, smem),
inter_cta_(params, bidm, bidn),
bidn_(bidn) // CTA id within the group.
,
w0_(static_cast<T *>(params.workspace) +
(bidm * WARPS_M + warp_m) * CTAS_PER_ROW),
w1_(w0_ + params.ctas_per_col * WARPS_M * CTAS_PER_ROW) {}
template <typename Op>
inline __device__ T allreduce(T data, Op &op) { // NOLINT
data = Base::reduce(data, op);
// We switch workspace every iteration.
T *workspace = inter_cta_.phase_counter_ & 0x1 ? w1_ : w0_;
// Warp leaders 0 hold the CTA-local results.
if (this->warp_n_ == 0 && this->lane_ == 0) {
workspace[bidn_] = data;
}
inter_cta_.sync();
static_assert(CTAS_PER_ROW <= 32);
T total = Zeros<T>::get();
if (this->lane_ < CTAS_PER_ROW) {
total = workspace[this->lane_];
}
total = Reducer<T, 1, 1, 1>::allreduce_(total, op);
return total;
}
InterCTASync inter_cta_;
T *w0_;
T *w1_;
int bidn_;
};
////////////////////////////////////////////////////////////////////////////////////////////////////
template <typename T, uint32_t WARPS_M>
struct Reducer<T, 1, WARPS_M, 1> {
using Type = T;
enum { SMEM_BYTES = 0 };
enum { WORKSPACE_BYTES_PER_GROUP = 0 };
enum { THREADS_PER_WARP = 32 };
template <typename Params>
inline __device__ Reducer(Params &params, // NOLINT
uint32_t bidm,
uint32_t bidn,
uint32_t warp_m,
uint32_t warp_n,
uint32_t lane,
void *smem)
: warp_n_(warp_n), lane_(lane) {}
template <typename Op>
static inline __device__ T allreduce_(T data, Op &op) {
#pragma unroll
for (int it = 1; it < THREADS_PER_WARP; it *= 2) {
data = op(data, warp_shuffle_xor(data, it));
}
return data;
}
template <typename Op>
inline __device__ T allreduce(T data, Op &op) { // NOLINT
return allreduce_(data, op);
}
template <typename Op>
inline __device__ T reduce(T data, Op &op) { // NOLINT
// only lane 0 holds the result!
#pragma unroll
for (int it = THREADS_PER_WARP / 2; it > 0; it /= 2) {
data = op(data, warp_shuffle_down(data, it));
}
return data;
}
int warp_n_;
int lane_;
};
////////////////////////////////////////////////////////////////////////////////////////////////////
template <typename T, uint32_t WARPS_M, uint32_t WARPS_N>
struct Reducer<T, 1, WARPS_M, WARPS_N> : public Reducer<T, 1, WARPS_M, 1> {
using Base = Reducer<T, 1, WARPS_M, 1>;
using Type = T;
enum { SMEM_BYTES = Base::SMEM_BYTES + WARPS_M * WARPS_N * sizeof(T) * 2 };
enum { WORKSPACE_BYTES_PER_GROUP = 0 };
enum { THREADS_PER_WARP = 32 };
template <typename Params>
inline __device__ Reducer(Params &params, // NOLINT
uint32_t bidm,
uint32_t bidn,
uint32_t warp_m,
uint32_t warp_n,
uint32_t lane,
void *smem)
: Base(params, bidm, bidn, warp_m, warp_n, lane, smem), use0_(true) {
smem0_ = &static_cast<T *>(smem)[warp_m * WARPS_N]; // NOLINT
smem1_ = smem0_ + WARPS_M * WARPS_N;
}
template <typename Op>
inline __device__ T allreduce(T data, Op &op) { // NOLINT
T *smem = use0_ ? smem0_ : smem1_;
use0_ = !use0_;
data = Base::reduce(data, op);
if (this->lane_ == 0) {
smem[this->warp_n_] = data;
}
__syncthreads();
T out = Zeros<T>::get();
#pragma unroll
for (int it = 0; it < WARPS_N; it++) {
out = op(out, smem[it]);
}
return out;
}
template <typename Op>
inline __device__ T reduce(T data, Op &op) { // NOLINT
T *smem = use0_ ? smem0_ : smem1_;
use0_ = !use0_;
// only intra-CTA group leader holds the result!
data = Base::reduce(data, op);
if (this->lane_ == 0) {
smem[this->warp_n_] = data;
}
__syncthreads();
T out = Zeros<T>::get();
if (this->warp_n_ == 0 && this->lane_ == 0) {
#pragma unroll
for (int it = 0; it < WARPS_N; it++) {
out = op(out, smem[it]);
}
}
return out;
}
T *smem0_;
T *smem1_;
bool use0_;
};
////////////////////////////////////////////////////////////////////////////////////////////////////
template <typename T>
inline __device__ void warp_chan_upd_dynamic(T &m_a, // NOLINT
T &m2_a, // NOLINT
T &n_a, // NOLINT
int num_active) {
// Assume at least leftmost is valid and init: step = next_pow2(num_active) /
// 2 (might get NaN otherwise)
int highest_bit_set = (8 * sizeof(num_active)) - __clz(num_active - 1);
#pragma unroll
for (int step = (1 << (highest_bit_set - 1)); step > 0; step /= 2) {
// Exchange
T n_b = warp_shuffle_down(n_a, step);
T m_b = warp_shuffle_down(m_a, step);
T m2_b = warp_shuffle_down(m2_a, step);
// Update
const T n_ab = n_a + n_b; // We can handle one of them being 0, not both.
const T rn_ab = 1.f / n_ab; // Might have different n per thread, otherwise
// this would simplify :(
const T delta = m_a - m_b;
const float m2_ab = m2_a + m2_b + delta * delta * n_a * n_b * rn_ab;
const float m_ab = (n_a * m_a + n_b * m_b) * rn_ab;
n_a = n_ab;
m_a = m_ab;
m2_a = m2_ab;
}
// Intra-warp broadcast (only lane 0 has valid stats).
m_a = __shfl_sync(uint32_t(-1), m_a, 0);
m2_a = __shfl_sync(uint32_t(-1), m2_a, 0);
}
////////////////////////////////////////////////////////////////////////////////////////////////////
template <typename T, uint32_t CTAS_PER_ROW, uint32_t WARPS_M, uint32_t WARPS_N>
struct Stats {
// This could be done generically with the Reducer. But then we would have to
// exchange 3 instead of 2 fields.
using InterCTASync = InterCTASync<CTAS_PER_ROW>;
using BlockStats = Stats<T, 1, WARPS_M, WARPS_N>;
using stats_t = typename BlockStats::stats_t;
enum { SMEM_BYTES = BlockStats::SMEM_BYTES };
template <typename Params>
inline __device__ Stats(Params &params, // NOLINT
uint32_t bidm,
uint32_t bidn,
uint32_t warp_m,
uint32_t warp_n,
uint32_t lane,
void *smem)
: inter_cta_(params, bidm, bidn),
block_stats_(params, bidm, bidn, warp_m, warp_n, lane, smem),
bidn_(bidn) // CTA id within the group.
,
w0_(static_cast<stats_t *>(params.workspace) +
(bidm * WARPS_M + warp_m) * CTAS_PER_ROW),
w1_(w0_ + params.ctas_per_col * WARPS_M * CTAS_PER_ROW),
warp_n_(warp_n),
lane_(lane) {}
template <uint32_t N>
inline __device__ stats_t compute(const T (&elts)[N],
const T rn,
bool is_rmsnorm) {
constexpr T ELTS_PER_ROW_PER_CTA = N * WARPS_N * THREADS_PER_WARP;
constexpr T block_rn = 1.f / T(ELTS_PER_ROW_PER_CTA);
stats_t block_stats = block_stats_.compute(elts, block_rn, is_rmsnorm);
stats_t *workspace = inter_cta_.phase_counter_ & 0x1 ? w1_ : w0_;
if (warp_n_ == 0 && lane_ == 0) {
workspace[bidn_] = block_stats;
}
// Wait for all CTAS_PER_ROW CTAS in the group to have written their result.
inter_cta_.sync();
T n = Zeros<T>::get();
T m = Zeros<T>::get();
T m2 = Zeros<T>::get();
// Assume CTA group size in N less than 32, such that we can finalize with a
// single warp.
static_assert(CTAS_PER_ROW <= 32);
// Every warp does the final reduction locally.
if (lane_ < CTAS_PER_ROW) {
stats_t result = workspace[lane_];
n = ELTS_PER_ROW_PER_CTA;
m = layer_norm::Get<0>::of<stats_t, T>(result);
m2 = layer_norm::Get<1>::of<stats_t, T>(result);
}
warp_chan_upd_dynamic(m, m2, n, CTAS_PER_ROW);
return {m, m2};
}
InterCTASync inter_cta_;
BlockStats block_stats_;
stats_t *w0_;
stats_t *w1_;
int bidn_;
int warp_n_;
int lane_;
};
////////////////////////////////////////////////////////////////////////////////////////////////////
template <typename T, uint32_t WARPS_M, uint32_t WARPS_N>
struct Stats<T, 1, WARPS_M, WARPS_N> {
using WarpStats = Stats<T, 1, WARPS_M, 1>;
using stats_t = typename WarpStats::stats_t;
enum { SMEM_BYTES = WARPS_M * WARPS_N * sizeof(stats_t) * 2 };
template <typename Params>
inline __device__ Stats(Params &params, // NOLINT
uint32_t bidm,
uint32_t bidn,
uint32_t warp_m,
uint32_t warp_n,
uint32_t lane,
void *smem)
: warp_stats_(params, bidm, bidn, warp_m, warp_n, lane, smem),
use0_(true) {
smem0_ = static_cast<stats_t *>(smem) + warp_m * WARPS_N;
smem1_ = smem0_ + WARPS_M * WARPS_N;
}
template <uint32_t N>
inline __device__ stats_t compute(const T (&elts)[N],
const T rn,
bool is_rmsnorm) {
stats_t *smem = use0_ ? smem0_ : smem1_;
use0_ = !use0_;
// Compute warp local for all WARPS_N
constexpr T warp_rn = 1.f / T(N * THREADS_PER_WARP);
stats_t warp_stats = warp_stats_.compute(elts, warp_rn, is_rmsnorm);
// Each warp warp leader stores its stats
const auto warp_n = warp_stats_.reducer_.warp_n_;
const auto lane = warp_stats_.reducer_.lane_;
if (lane == 0) {
smem[warp_n] = warp_stats;
}
__syncthreads();
T n = Zeros<T>::get();
T m = Zeros<T>::get();
T m2 = Zeros<T>::get();
// Assume that there are less than 32 warps, such that we can finalize with
// a single warp
static_assert(WARPS_N <= 32);
if (lane < WARPS_N) {
stats_t result = smem[lane];
n = N * THREADS_PER_WARP;
m = layer_norm::Get<0>::of<stats_t, T>(result);
m2 = layer_norm::Get<1>::of<stats_t, T>(result);
}
warp_chan_upd_dynamic(m, m2, n, WARPS_N);
return {m, m2};
}
WarpStats warp_stats_;
stats_t *smem0_;
stats_t *smem1_;
bool use0_;
};
////////////////////////////////////////////////////////////////////////////////////////////////////
template <typename T, uint32_t WARPS_M>
struct Stats<T, 1, WARPS_M, 1> {
using stats_t = typename TypeToVec2<T>::Type;
// The simple Warp reducer.
using Reducer = Reducer<T, 1, WARPS_M, 1>;
enum { SMEM_BYTES = 0 };
template <typename Params>
inline __device__ Stats(Params &params, // NOLINT
uint32_t bidm,
uint32_t bidn,
uint32_t warp_m,
uint32_t warp_n,
uint32_t lane,
void *smem)
: reducer_(params, bidm, bidn, warp_m, warp_n, lane, smem) {}
template <uint32_t N>
inline __device__ stats_t compute(const T (&elts)[N],
const T rn,
bool is_rmsnorm) {
auto sum = Sum<T>();
T m = Zeros<T>::get();
#pragma unroll
for (int it = 0; it < N; it++) {
m += elts[it];
}
m = reducer_.allreduce(m, sum) * rn;
T m2 = Zeros<T>::get();
#pragma unroll
for (int it = 0; it < N; it++) {
if (is_rmsnorm) {
m2 += elts[it] * elts[it];
} else {
T diff = (elts[it] - m);
m2 += diff * diff;
}
}
m2 = reducer_.allreduce(m2, sum);
return {m, m2};
}
Reducer reducer_;
};
////////////////////////////////////////////////////////////////////////////////////////////////////
} // namespace layer_norm
} // namespace phi