mirror of
https://github.com/saymrwulf/onnxruntime.git
synced 2026-07-27 20:02:15 +00:00
414 lines
12 KiB
Text
414 lines
12 KiB
Text
/**
|
|
* Copyright (c) 2016-present, Facebook, Inc.
|
|
*
|
|
* 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) 2017, NVIDIA CORPORATION. All rights reserved.
|
|
// NVIDIA/apex is licensed under the
|
|
// BSD 3 - Clause "New" or "Revised" License
|
|
//
|
|
|
|
/* Modifications Copyright (c) Microsoft. */
|
|
|
|
#include "core/providers/cuda/cu_inc/common.cuh"
|
|
|
|
#include "layer_norm_impl.h"
|
|
|
|
namespace onnxruntime {
|
|
namespace contrib {
|
|
namespace cuda {
|
|
|
|
using namespace onnxruntime::cuda;
|
|
|
|
template <typename T>
|
|
|
|
__device__ __forceinline__ T WARP_SHFL(T value, int srcLane, int width, unsigned int mask = 0xffffffff)
|
|
{
|
|
#if CUDA_VERSION >= 9000
|
|
|
|
return __shfl_sync(mask, value, srcLane, width);
|
|
|
|
#else
|
|
|
|
return __shfl(value, srcLane, width);
|
|
|
|
#endif
|
|
}
|
|
|
|
template <typename T>
|
|
__device__ __forceinline__ T WARP_SHFL_XOR(T value, int laneMask, int width = warpSize, unsigned int mask = 0xffffffff)
|
|
{
|
|
#if CUDA_VERSION >= 9000
|
|
|
|
return __shfl_xor_sync(mask, value, laneMask, width);
|
|
|
|
#else
|
|
|
|
return __shfl_xor(value, laneMask, width);
|
|
|
|
#endif
|
|
}
|
|
|
|
template <typename U>
|
|
__device__ void cuWelfordOnlineSum(
|
|
const U curr,
|
|
U& mu,
|
|
U& sigma2,
|
|
U& count) {
|
|
count = count + U(1);
|
|
U delta = curr - mu;
|
|
U lmean = mu + delta / count;
|
|
mu = lmean;
|
|
U delta2 = curr - lmean;
|
|
sigma2 = sigma2 + delta * delta2;
|
|
}
|
|
|
|
template <typename U>
|
|
__device__ void cuChanOnlineSum(
|
|
const U muB,
|
|
const U sigma2B,
|
|
const U countB,
|
|
U& mu,
|
|
U& sigma2,
|
|
U& count,
|
|
const int& warp_size) {
|
|
U delta = muB - mu;
|
|
U nA = count;
|
|
U nB = countB;
|
|
count = count + countB;
|
|
U nX = count;
|
|
if (nX > U(0)) {
|
|
nA = nA / nX;
|
|
nB = nB / nX;
|
|
mu = nA * mu + nB * muB;
|
|
sigma2 = sigma2 + sigma2B + delta * delta * nA * nB * nX;
|
|
} else {
|
|
mu = U(0);
|
|
sigma2 = U(0);
|
|
}
|
|
}
|
|
|
|
template <typename T, typename U>
|
|
__device__ void cuWelfordMuSigma2(
|
|
const T* __restrict__ vals,
|
|
const int n1,
|
|
const int n2,
|
|
const int i1,
|
|
U& mu,
|
|
U& sigma2,
|
|
U* buf,
|
|
const int warp_size) {
|
|
// Assumptions:
|
|
// 1) blockDim.x == warpSize
|
|
// 2) Tensor is contiguous
|
|
// 3) 2*blockDim.y*sizeof(U)+blockDim.y*sizeof(int) shared memory available.
|
|
//
|
|
// compute variance and mean over n2
|
|
U count = U(0);
|
|
mu = U(0);
|
|
sigma2 = U(0);
|
|
if (i1 < n1) {
|
|
// one warp normalizes one n1 index,
|
|
// synchronization is implicit
|
|
// initialize with standard Welford algorithm
|
|
const int numx = blockDim.x * blockDim.y;
|
|
const int thrx = threadIdx.x + threadIdx.y * blockDim.x;
|
|
const T* lvals = vals + i1 * n2;
|
|
int l = 4 * thrx;
|
|
for (; l + 3 < n2; l += 4 * numx) {
|
|
for (int k = 0; k < 4; ++k) {
|
|
U curr = static_cast<U>(lvals[l + k]);
|
|
cuWelfordOnlineSum<U>(curr, mu, sigma2, count);
|
|
}
|
|
}
|
|
for (; l < n2; ++l) {
|
|
U curr = static_cast<U>(lvals[l]);
|
|
cuWelfordOnlineSum<U>(curr, mu, sigma2, count);
|
|
}
|
|
// intra-warp reductions
|
|
for (int l = 0; l <= 4; ++l) {
|
|
int srcLaneB = (threadIdx.x + (1 << l)) & 31;
|
|
U muB = WARP_SHFL(mu, srcLaneB, warp_size);
|
|
U countB = WARP_SHFL(count, srcLaneB, warp_size);
|
|
U sigma2B = WARP_SHFL(sigma2, srcLaneB, warp_size);
|
|
cuChanOnlineSum<U>(muB, sigma2B, countB, mu, sigma2, count, warp_size);
|
|
}
|
|
// threadIdx.x == 0 has correct values for each warp
|
|
// inter-warp reductions
|
|
if (blockDim.y > 1) {
|
|
U* ubuf = (U*)buf;
|
|
U* ibuf = (U*)(ubuf + blockDim.y);
|
|
for (int offset = blockDim.y / 2; offset > 0; offset /= 2) {
|
|
// upper half of warps write to shared
|
|
if (threadIdx.x == 0 && threadIdx.y >= offset && threadIdx.y < 2 * offset) {
|
|
const int wrt_y = threadIdx.y - offset;
|
|
ubuf[2 * wrt_y] = mu;
|
|
ubuf[2 * wrt_y + 1] = sigma2;
|
|
ibuf[wrt_y] = count;
|
|
}
|
|
__syncthreads();
|
|
// lower half merges
|
|
if (threadIdx.x == 0 && threadIdx.y < offset) {
|
|
U muB = ubuf[2 * threadIdx.y];
|
|
U sigma2B = ubuf[2 * threadIdx.y + 1];
|
|
U countB = ibuf[threadIdx.y];
|
|
cuChanOnlineSum<U>(muB, sigma2B, countB, mu, sigma2, count, warp_size);
|
|
}
|
|
__syncthreads();
|
|
}
|
|
// threadIdx.x = 0 && threadIdx.y == 0 only thread that has correct values
|
|
if (threadIdx.x == 0 && threadIdx.y == 0) {
|
|
ubuf[0] = mu;
|
|
ubuf[1] = sigma2;
|
|
}
|
|
__syncthreads();
|
|
mu = ubuf[0];
|
|
sigma2 = ubuf[1] / U(n2);
|
|
// don't care about final value of count, we know count == n2
|
|
} else {
|
|
mu = WARP_SHFL(mu, 0, warp_size);
|
|
sigma2 = WARP_SHFL(sigma2 / U(n2), 0, warp_size);
|
|
}
|
|
}
|
|
}
|
|
|
|
template <>
|
|
__device__ void cuWelfordMuSigma2(
|
|
const half* __restrict__ vals,
|
|
const int n1,
|
|
const int n2,
|
|
const int i1,
|
|
float& mu,
|
|
float& sigma2,
|
|
float* buf,
|
|
const int warp_size) {
|
|
// Assumptions:
|
|
// 1) blockDim.x == warpSize
|
|
// 2) Tensor is contiguous
|
|
// 3) 2*blockDim.y*sizeof(U)+blockDim.y*sizeof(int) shared memory available.
|
|
//
|
|
// compute variance and mean over n2
|
|
float count = 0.0f;
|
|
mu = float(0);
|
|
sigma2 = float(0);
|
|
if (i1 < n1) {
|
|
// one warp normalizes one n1 index,
|
|
// synchronization is implicit
|
|
// initialize with standard Welford algorithm
|
|
const int numx = blockDim.x * blockDim.y;
|
|
const int thrx = threadIdx.x + threadIdx.y * blockDim.x;
|
|
const half* lvals = vals + i1 * n2;
|
|
int l = 8 * thrx;
|
|
if ((((size_t)lvals) & 3) != 0) {
|
|
// 16 bit alignment
|
|
// first thread consumes first point
|
|
if (thrx == 0) {
|
|
float curr = static_cast<float>(lvals[0]);
|
|
cuWelfordOnlineSum(curr, mu, sigma2, count);
|
|
}
|
|
++l;
|
|
}
|
|
// at this point, lvals[l] are 32 bit aligned for all threads.
|
|
for (; l + 7 < n2; l += 8 * numx) {
|
|
for (int k = 0; k < 8; k += 2) {
|
|
float2 curr = __half22float2(*((__half2*)(lvals + l + k)));
|
|
cuWelfordOnlineSum(curr.x, mu, sigma2, count);
|
|
cuWelfordOnlineSum(curr.y, mu, sigma2, count);
|
|
}
|
|
}
|
|
for (; l < n2; ++l) {
|
|
float curr = static_cast<float>(lvals[l]);
|
|
cuWelfordOnlineSum(curr, mu, sigma2, count);
|
|
}
|
|
// intra-warp reductions
|
|
for (int l = 0; l <= 4; ++l) {
|
|
int srcLaneB = (threadIdx.x + (1 << l)) & 31;
|
|
float muB = WARP_SHFL(mu, srcLaneB, warp_size);
|
|
float countB = WARP_SHFL(count, srcLaneB, warp_size);
|
|
float sigma2B = WARP_SHFL(sigma2, srcLaneB, warp_size);
|
|
cuChanOnlineSum(muB, sigma2B, countB, mu, sigma2, count, warp_size);
|
|
}
|
|
// threadIdx.x == 0 has correct values for each warp
|
|
// inter-warp reductions
|
|
if (blockDim.y > 1) {
|
|
float* ubuf = (float*)buf;
|
|
float* ibuf = (float*)(ubuf + blockDim.y);
|
|
for (int offset = blockDim.y / 2; offset > 0; offset /= 2) {
|
|
// upper half of warps write to shared
|
|
if (threadIdx.x == 0 && threadIdx.y >= offset && threadIdx.y < 2 * offset) {
|
|
const int wrt_y = threadIdx.y - offset;
|
|
ubuf[2 * wrt_y] = mu;
|
|
ubuf[2 * wrt_y + 1] = sigma2;
|
|
ibuf[wrt_y] = count;
|
|
}
|
|
__syncthreads();
|
|
// lower half merges
|
|
if (threadIdx.x == 0 && threadIdx.y < offset) {
|
|
float muB = ubuf[2 * threadIdx.y];
|
|
float sigma2B = ubuf[2 * threadIdx.y + 1];
|
|
float countB = ibuf[threadIdx.y];
|
|
cuChanOnlineSum(muB, sigma2B, countB, mu, sigma2, count, warp_size);
|
|
}
|
|
__syncthreads();
|
|
}
|
|
// threadIdx.x = 0 && threadIdx.y == 0 only thread that has correct values
|
|
if (threadIdx.x == 0 && threadIdx.y == 0) {
|
|
ubuf[0] = mu;
|
|
ubuf[1] = sigma2;
|
|
}
|
|
__syncthreads();
|
|
mu = ubuf[0];
|
|
sigma2 = ubuf[1] / float(n2);
|
|
// don't care about final value of count, we know count == n2
|
|
} else {
|
|
mu = WARP_SHFL(mu, 0, warp_size);
|
|
sigma2 = WARP_SHFL(sigma2 / float(n2), 0, warp_size);
|
|
}
|
|
}
|
|
}
|
|
|
|
template <typename U>
|
|
__device__ U rsqrt(U v) {
|
|
return U(1) / sqrt(v);
|
|
}
|
|
template <>
|
|
__device__ float rsqrt(float v) {
|
|
return rsqrtf(v);
|
|
}
|
|
template <>
|
|
__device__ double rsqrt(double v) {
|
|
return rsqrt(v);
|
|
}
|
|
|
|
namespace {
|
|
// This is the un-specialized struct. Note that we prevent instantiation of this
|
|
// struct by putting an undefined symbol in the function body so it won't compile.
|
|
// template <typename T>
|
|
// struct SharedMemory
|
|
// {
|
|
// // Ensure that we won't compile any un-specialized types
|
|
// __device__ T *getPointer()
|
|
// {
|
|
// extern __device__ void error(void);
|
|
// error();
|
|
// return NULL;
|
|
// }
|
|
// };
|
|
// https://github.com/NVIDIA/apex/issues/246
|
|
template <typename T>
|
|
struct SharedMemory;
|
|
|
|
template <>
|
|
struct SharedMemory<float> {
|
|
__device__ float* getPointer() {
|
|
extern __shared__ float s_float[];
|
|
return s_float;
|
|
}
|
|
};
|
|
|
|
template <>
|
|
struct SharedMemory<double> {
|
|
__device__ double* getPointer() {
|
|
extern __shared__ double s_double[];
|
|
return s_double;
|
|
}
|
|
};
|
|
} // namespace
|
|
|
|
template <typename T, typename U>
|
|
__global__ void cuApplyLayerNorm(
|
|
T* __restrict__ output_vals,
|
|
U* __restrict__ mean,
|
|
U* __restrict__ invvar,
|
|
const T* __restrict__ vals,
|
|
const int n1,
|
|
const int n2,
|
|
const U epsilon,
|
|
const T* __restrict__ gamma,
|
|
const T* __restrict__ beta,
|
|
int warp_size) {
|
|
// Assumptions:
|
|
// 1) blockDim.x == warpSize
|
|
// 2) Tensors are contiguous
|
|
//
|
|
for (auto i1 = blockIdx.y; i1 < n1; i1 += gridDim.y) {
|
|
SharedMemory<U> shared;
|
|
U* buf = shared.getPointer();
|
|
U mu, sigma2;
|
|
cuWelfordMuSigma2(vals, n1, n2, i1, mu, sigma2, buf, warp_size);
|
|
const T* lvals = vals + i1 * n2;
|
|
T* ovals = output_vals + i1 * n2;
|
|
U c_invvar = rsqrt(sigma2 + epsilon);
|
|
const int numx = blockDim.x * blockDim.y;
|
|
const int thrx = threadIdx.x + threadIdx.y * blockDim.x;
|
|
if (gamma != NULL && beta != NULL) {
|
|
for (int i = thrx; i < n2; i += numx) {
|
|
U curr = static_cast<U>(lvals[i]);
|
|
ovals[i] = gamma[i] * static_cast<T>(c_invvar * (curr - mu)) + beta[i];
|
|
}
|
|
} else {
|
|
for (int i = thrx; i < n2; i += numx) {
|
|
U curr = static_cast<U>(lvals[i]);
|
|
ovals[i] = static_cast<T>(c_invvar * (curr - mu));
|
|
}
|
|
}
|
|
if (threadIdx.x == 0 && threadIdx.y == 0) {
|
|
if (mean != nullptr) mean[i1] = mu;
|
|
if (invvar != nullptr) invvar[i1] = c_invvar;
|
|
}
|
|
}
|
|
}
|
|
|
|
template <typename T, typename U>
|
|
void HostApplyLayerNorm(
|
|
T* output,
|
|
U* mean,
|
|
U* invvar,
|
|
const T* input,
|
|
int64_t n1,
|
|
int64_t n2,
|
|
double epsilon,
|
|
const T* gamma,
|
|
const T* beta) {
|
|
const dim3 threads(32, 4, 1);
|
|
const cudaDeviceProp& prop = DeviceProp::GetDeviceProps();
|
|
const uint64_t maxGridY = prop.maxGridSize[1];
|
|
const int warp_size = prop.warpSize;
|
|
// const uint64_t maxGridY = 32;
|
|
const dim3 blocks(1, std::min((uint64_t)n1, maxGridY), 1);
|
|
int nshared =
|
|
threads.y > 1 ? threads.y * sizeof(U) + (threads.y / 2) * sizeof(U) : 0;
|
|
cuApplyLayerNorm<<<blocks, threads, nshared, 0>>>(
|
|
output,
|
|
mean,
|
|
invvar,
|
|
input,
|
|
n1, n2,
|
|
U(epsilon),
|
|
gamma, beta, warp_size);
|
|
}
|
|
|
|
#define LAYERNORM_LINEAR_IMPL(T, U) \
|
|
template void HostApplyLayerNorm(T* output, U* mean, U* invvar, const T* input, int64_t n1, int64_t n2, \
|
|
double epsilon, const T* gamma, const T* beta);
|
|
|
|
LAYERNORM_LINEAR_IMPL(float, float)
|
|
LAYERNORM_LINEAR_IMPL(half, float)
|
|
LAYERNORM_LINEAR_IMPL(double, float)
|
|
//LAYERNORM_LINEAR_IMPL(half, half)
|
|
|
|
} // namespace cuda
|
|
} // namespace contrib
|
|
} // namespace onnxruntime
|