Files
nvidia--tensorrt/plugin/skipLayerNormPlugin/skipLayerNormInt8InterleavedKernelMTron.cu
wehub-resource-sync c8a779b1bb
Docker Image CI / build-ubuntu2004 (push) Waiting to run
chore: import upstream snapshot with attribution
2026-07-13 13:36:55 +08:00

416 lines
15 KiB
Plaintext

/*
* SPDX-FileCopyrightText: Copyright (c) 1993-2025 NVIDIA CORPORATION &
* AFFILIATES. All rights reserved. SPDX-License-Identifier: Apache-2.0
*
* 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 "NvInfer.h"
#include "common/bertCommon.h"
#include "common/common.cuh"
#include "common/cubCcclCompat.h"
#include <cassert>
#include <cstring>
#include <cuda.h>
#include <type_traits>
#include <vector>
using namespace nvinfer1;
namespace nvinfer1
{
namespace plugin
{
namespace bert
{
inline __device__ void res_add(
float (&hdata)[4], const uint32_t idata, const uint32_t ires, float const dqData, float const dqRes)
{
char4 ires4 = reinterpret_cast<char4 const&>(ires);
char4 idata4 = reinterpret_cast<char4 const&>(idata);
hdata[0] = float(idata4.x) * dqData + float(ires4.x) * dqRes;
hdata[1] = float(idata4.y) * dqData + float(ires4.y) * dqRes;
hdata[2] = float(idata4.z) * dqData + float(ires4.z) * dqRes;
hdata[3] = float(idata4.w) * dqData + float(ires4.w) * dqRes;
}
template <int32_t tWARPS, int32_t tHEADS, int32_t tTHREADS_PER_ROW>
__global__ void skipln_vec32_mtron(int8_t const* input, int8_t const* skip, int8_t* output, int8_t* preln,
half const* beta, half const* gamma, float const dqScaleIn, float const dqScaleSkip, float const qScale,
float const qSkipScale, int32_t const total)
{
// clang-format off
enum { kHEAD_SIZE = 64 };
enum { kBYTES_PER_LDG = 16 };
enum { kTHREADS_PER_CTA = tWARPS * 32 };
enum { kROWS_PER_LDG = kTHREADS_PER_CTA / tTHREADS_PER_ROW };
enum { kVECS_PER_CTA = tTHREADS_PER_ROW / 2 };
enum { kPARAM_BYTES = tHEADS * kHEAD_SIZE * 2 };
enum { kPARAM_LDGS = kPARAM_BYTES / (kTHREADS_PER_CTA * kBYTES_PER_LDG) };
enum { kLDGS = tHEADS * 2 / kROWS_PER_LDG };
// clang-format on
static_assert(kVECS_PER_CTA == 4, "");
static_assert(kPARAM_LDGS == 1, "");
static_assert(kROWS_PER_LDG == tHEADS, "");
static_assert(kLDGS == 2, "");
static_assert(kLDGS * kROWS_PER_LDG == tHEADS * 2, "");
static_assert(kTHREADS_PER_CTA * kBYTES_PER_LDG == kPARAM_BYTES, "");
static_assert(kPARAM_LDGS == 1, "");
extern __shared__ char smem_[];
// space for CTA-wide reduction
__shared__ half2 smemRed[kVECS_PER_CTA][tWARPS];
constexpr float rld = 1.F / (float(tHEADS) * float(kHEAD_SIZE));
int32_t const bidx = blockIdx.x;
int32_t const tidx = threadIdx.x;
int32_t const row = tidx / tTHREADS_PER_ROW;
int32_t const col = tidx % tTHREADS_PER_ROW;
int32_t const lane = tidx % 32;
int32_t const warp = tidx / 32;
bool const isWarpLead = (lane < tTHREADS_PER_ROW) && ((lane & 1) == 0);
bool const isCtaLead = (tidx < tTHREADS_PER_ROW) && ((tidx & 1) == 0);
// token position: every two threads load together the 32B at one token
// position
int32_t const pos = col / 2;
int32_t const posOffset = bidx * kVECS_PER_CTA + pos; // for token positions per block, disabling 2 threads per pos
bool const myPred = posOffset < total;
int32_t const rowStrideBytes = total * 32;
uint4 inData[kLDGS];
uint4 in_skip[kLDGS];
float hdata[kLDGS * 4][4];
int32_t const gmemOffset = row * rowStrideBytes + (bidx * tTHREADS_PER_ROW + col) * kBYTES_PER_LDG;
#pragma unroll
for (int32_t ii = 0; ii < kLDGS; ii++)
{
inData[ii] = {0, 0, 0, 0};
in_skip[ii] = {0, 0, 0, 0};
if (myPred)
{
ldg(input + gmemOffset + ii * kROWS_PER_LDG * rowStrideBytes, inData[ii]);
ldg(skip + gmemOffset + ii * kROWS_PER_LDG * rowStrideBytes, in_skip[ii]);
}
}
uint4* smemB = reinterpret_cast<uint4*>(&smem_[0]) + tidx;
uint4* smemG = reinterpret_cast<uint4*>(&smem_[kPARAM_BYTES]) + tidx;
int8_t const* betaPtr = reinterpret_cast<int8_t const*>(beta) + tidx * kBYTES_PER_LDG;
int8_t const* gammaPtr = reinterpret_cast<int8_t const*>(gamma) + tidx * kBYTES_PER_LDG;
ldg(betaPtr, *smemB);
ldg(gammaPtr, *smemG);
half* b = reinterpret_cast<half*>(&smem_[0]);
half* g = reinterpret_cast<half*>(&smem_[kPARAM_BYTES]);
#pragma unroll
for (int32_t ii = 0; ii < kLDGS; ii++)
{
res_add(hdata[ii * 4 + 0], inData[ii].x, in_skip[ii].x, dqScaleIn, dqScaleSkip);
res_add(hdata[ii * 4 + 1], inData[ii].y, in_skip[ii].y, dqScaleIn, dqScaleSkip);
res_add(hdata[ii * 4 + 2], inData[ii].z, in_skip[ii].z, dqScaleIn, dqScaleSkip);
res_add(hdata[ii * 4 + 3], inData[ii].w, in_skip[ii].w, dqScaleIn, dqScaleSkip);
}
half2 statsLocal = {0, 0};
#pragma unroll
for (int32_t ii = 0; ii < kLDGS * 4; ii++)
{
#pragma unroll
for (int32_t jj = 0; jj < 4; jj++)
{
float const tmp = hdata[ii][jj] * (rld);
statsLocal = statsLocal + __floats2half2_rn(tmp, tmp * hdata[ii][jj]);
}
}
statsLocal = statsLocal + __shfl_xor_sync(uint32_t(-1), statsLocal, 1);
__syncwarp();
if (kVECS_PER_CTA == 1)
{
statsLocal = statsLocal + __shfl_xor_sync(uint32_t(-1), statsLocal, 2);
__syncwarp();
statsLocal = statsLocal + __shfl_xor_sync(uint32_t(-1), statsLocal, 4);
__syncwarp();
}
else if (kVECS_PER_CTA == 2)
{
statsLocal = statsLocal + __shfl_xor_sync(uint32_t(-1), statsLocal, 4);
__syncwarp();
}
statsLocal = statsLocal + __shfl_xor_sync(uint32_t(-1), statsLocal, 8);
__syncwarp();
statsLocal = statsLocal + __shfl_xor_sync(uint32_t(-1), statsLocal, 16);
__syncwarp();
if (isWarpLead)
{
smemRed[pos][warp] = statsLocal;
}
__syncthreads();
if (isCtaLead)
{
for (int32_t ii = 1; ii < tWARPS; ii++)
{
statsLocal = statsLocal + smemRed[pos][ii];
}
float mu = __low2float(statsLocal);
float sos = __high2float(statsLocal);
float rsigma = rsqrtf(sos - mu * mu + std::numeric_limits<float>::epsilon());
smemRed[pos][0] = __floats2half2_rn(mu, rsigma);
}
__syncthreads();
// load params into smem: 2x Headsx32x2x2B
const float2 statsf = __half22float2(smemRed[pos][0]);
// Copy skip connection output before Layer Norm
#pragma unroll
for (int32_t ii = 0; ii < kLDGS; ii++)
{
inData[ii].x = pack4(hdata[ii * 4 + 0], qSkipScale);
inData[ii].y = pack4(hdata[ii * 4 + 1], qSkipScale);
inData[ii].z = pack4(hdata[ii * 4 + 2], qSkipScale);
inData[ii].w = pack4(hdata[ii * 4 + 3], qSkipScale);
}
#pragma unroll
for (int32_t ii = 0; ii < kLDGS; ii++)
{
if (myPred)
{
stg(preln + gmemOffset + ii * kROWS_PER_LDG * rowStrideBytes, inData[ii]);
}
}
#pragma unroll
for (int32_t ii = 0; ii < kLDGS; ii++)
{
#pragma unroll
for (int32_t jj = 0; jj < 4; jj++)
{
#pragma unroll
for (int32_t kk = 0; kk < 4; kk++)
{
int32_t const paramIdx = (ii * kROWS_PER_LDG + row) * 32 + (jj * 4 + kk) + (tidx & 1) * 16;
float const bb = b[paramIdx];
float const gg = g[paramIdx];
hdata[ii * 4 + jj][kk] = gg * statsf.y * (hdata[ii * 4 + jj][kk] - statsf.x) + bb;
}
}
}
#pragma unroll
for (int32_t ii = 0; ii < kLDGS; ii++)
{
inData[ii].x = pack4(hdata[ii * 4 + 0], qScale);
inData[ii].y = pack4(hdata[ii * 4 + 1], qScale);
inData[ii].z = pack4(hdata[ii * 4 + 2], qScale);
inData[ii].w = pack4(hdata[ii * 4 + 3], qScale);
}
#pragma unroll
for (int32_t ii = 0; ii < kLDGS; ii++)
{
if (myPred)
{
stg(output + gmemOffset + ii * kROWS_PER_LDG * rowStrideBytes, inData[ii]);
}
}
// store
}
int32_t launch_large_mtron(cudaStream_t stream, int32_t const ld, int32_t const total, int8_t const* input,
int8_t const* skip, half const* beta, half const* gamma, int8_t* output, int8_t* preln, float const dqScaleIn,
float const dqScaleSkip, float const qScale, float const qSkipScale)
{
if (ld == 1024)
{
constexpr int32_t tWARPS = 4;
constexpr int32_t tTHREADS_PER_ROW = 8;
constexpr int32_t tHEADS = 16;
constexpr int32_t kPARAM_BYTES = tHEADS * 64 * 2 * sizeof(half);
constexpr int32_t kVECS_PER_CTA = tTHREADS_PER_ROW / 2;
int32_t const blocks = (total + kVECS_PER_CTA - 1) / kVECS_PER_CTA;
skipln_vec32_mtron<tWARPS, tHEADS, tTHREADS_PER_ROW><<<blocks, tWARPS * 32, kPARAM_BYTES, stream>>>(
input, skip, output, preln, beta, gamma, dqScaleIn, dqScaleSkip, qScale, qSkipScale, total);
}
else if (ld == 768)
{
constexpr int32_t tWARPS = 3;
constexpr int32_t tTHREADS_PER_ROW = 8;
constexpr int32_t tHEADS = 12;
constexpr int32_t kPARAM_BYTES = tHEADS * 64 * 2 * sizeof(half);
constexpr int32_t kVECS_PER_CTA = tTHREADS_PER_ROW / 2;
int32_t const blocks = (total + kVECS_PER_CTA - 1) / kVECS_PER_CTA;
skipln_vec32_mtron<tWARPS, tHEADS, tTHREADS_PER_ROW><<<blocks, tWARPS * 32, kPARAM_BYTES, stream>>>(
input, skip, output, preln, beta, gamma, dqScaleIn, dqScaleSkip, qScale, qSkipScale, total);
}
else
{
return STATUS_FAILURE;
}
return cudaPeekAtLastError();
}
// naive kernel that only changes the addressing seems to be faster for small
// problem sizes
template <int32_t TPB, int32_t VPT>
__global__ void skiplnDQQ_vec4(int32_t const ld, int8_t const* input, int8_t const* skip, int8_t* output, int8_t* preln,
half const* beta, half const* gamma, float const dqScaleIn, float const dqScaleSkip, float const qScale,
float const qSkipScale, int32_t const total)
{
int32_t const hinner = threadIdx.x % 4;
int32_t const houter = threadIdx.x / 4;
int32_t const tidx = threadIdx.x;
int32_t const bidx = blockIdx.x;
int32_t const idx = houter * total * 32 + bidx * 32 + hinner * VPT;
// 4 * 1024 * 4 * 2 Bytes = 16KB per block
int8_t inLocal[VPT];
int8_t skipLocal[VPT];
half inLocalDQ[VPT]; // dequantized input + skip
half betaLocal[VPT];
half gammaLocal[VPT];
// load input tensors
copy<sizeof(int8_t) * VPT>(&input[idx], inLocal);
copy<sizeof(int8_t) * VPT>(&skip[idx], skipLocal);
// load parameters
copy<sizeof(half) * VPT>(&beta[tidx * VPT], betaLocal);
copy<sizeof(half) * VPT>(&gamma[tidx * VPT], gammaLocal);
half2 statsLocal = __floats2half2_rn(0.F, 0.F); // accumulator
half const rld = half(1.F) / half(ld);
#pragma unroll
for (int32_t it = 0; it < VPT; it++)
{
// DQ input and skip
float const tmpIn = inLocal[it];
float const tmpSkip = skipLocal[it];
inLocalDQ[it] = dqScaleIn * tmpIn + dqScaleSkip * tmpSkip;
half const tmp = rld * inLocalDQ[it];
half2 const tmp2 = __halves2half2(tmp, tmp * inLocalDQ[it]);
statsLocal = statsLocal + tmp2;
}
using BlockReduce = cub::BlockReduce<half2, TPB>;
__shared__ typename BlockReduce::TempStorage tempStorage;
__shared__ half mu; // mean
__shared__ half rsigma; // 1 / std.dev.
half2 const sum2 = BlockReduce(tempStorage).Reduce(statsLocal, compat::getCudaSumOp());
// Copy skip connection output before Layer Norm
#pragma unroll
for (int32_t it = 0; it < VPT; it++)
{
inLocal[it] = quantize(inLocalDQ[it], qSkipScale);
}
copy<sizeof(int8_t) * VPT>(inLocal, &preln[idx]);
if (tidx == 0)
{
mu = __low2half(sum2);
rsigma = rsqrtf(__high2half(sum2) - mu * mu + std::numeric_limits<half>::epsilon());
}
__syncthreads();
static_assert(VPT % 4 == 0, "");
uint32_t outLocal[VPT / 4U];
#pragma unroll
for (int32_t it = 0; it < VPT / 4U; it++)
{
float const tmp0 = gammaLocal[it * 4 + 0] * (inLocalDQ[it * 4 + 0] - mu) * rsigma + betaLocal[it * 4 + 0];
float const tmp1 = gammaLocal[it * 4 + 1] * (inLocalDQ[it * 4 + 1] - mu) * rsigma + betaLocal[it * 4 + 1];
float const tmp2 = gammaLocal[it * 4 + 2] * (inLocalDQ[it * 4 + 2] - mu) * rsigma + betaLocal[it * 4 + 2];
float const tmp3 = gammaLocal[it * 4 + 3] * (inLocalDQ[it * 4 + 3] - mu) * rsigma + betaLocal[it * 4 + 3];
outLocal[it] = float4_to_char4(tmp0 * qScale, tmp1 * qScale, tmp2 * qScale, tmp3 * qScale);
}
copy<sizeof(int8_t) * VPT>(outLocal, &output[idx]);
}
int32_t launch_small_mtron(cudaStream_t stream, int32_t const ld, int32_t const total, int8_t const* input,
int8_t const* skip, half const* beta, half const* gamma, int8_t* output, int8_t* preln, float const dqScaleIn,
float const dqScaleSkip, float const qScale, float const qSkipScale)
{
int32_t const gridSize = total;
// we align reads with the number of parameters, i.e. 8-wide instead of 16
int32_t constexpr VPT = 16 / sizeof(half); // 8
if (ld == 768)
{
int32_t constexpr TPB = 768 / VPT;
skiplnDQQ_vec4<TPB, VPT><<<gridSize, TPB, 0, stream>>>(
ld, input, skip, output, preln, beta, gamma, dqScaleIn, dqScaleSkip, qScale, qSkipScale, total);
}
else if (ld == 1024)
{
int32_t constexpr TPB = 1024 / VPT; // 128
skiplnDQQ_vec4<TPB, VPT><<<gridSize, TPB, 0, stream>>>(
ld, input, skip, output, preln, beta, gamma, dqScaleIn, dqScaleSkip, qScale, qSkipScale, total);
}
else if (ld == 1536)
{
int32_t constexpr TPB = 1536 / VPT; // 192
skiplnDQQ_vec4<TPB, VPT><<<gridSize, TPB, 0, stream>>>(
ld, input, skip, output, preln, beta, gamma, dqScaleIn, dqScaleSkip, qScale, qSkipScale, total);
}
else if (ld == 2048)
{
int32_t constexpr TPB = 2048 / VPT; // 256
skiplnDQQ_vec4<TPB, VPT><<<gridSize, TPB, 0, stream>>>(
ld, input, skip, output, preln, beta, gamma, dqScaleIn, dqScaleSkip, qScale, qSkipScale, total);
}
else if (ld == 3072)
{
int32_t constexpr TPB = 3072 / VPT; // 384
skiplnDQQ_vec4<TPB, VPT><<<gridSize, TPB, 0, stream>>>(
ld, input, skip, output, preln, beta, gamma, dqScaleIn, dqScaleSkip, qScale, qSkipScale, total);
}
else if (ld == 4096)
{
int32_t constexpr TPB = 4096 / VPT; // 512
skiplnDQQ_vec4<TPB, VPT><<<gridSize, TPB, 0, stream>>>(
ld, input, skip, output, preln, beta, gamma, dqScaleIn, dqScaleSkip, qScale, qSkipScale, total);
}
else
{
std::cout << "SkipLayerNormDQQ - FATAL: unsupported hidden layer size: " << ld << std::endl;
return STATUS_FAILURE;
}
return cudaPeekAtLastError();
}
} // namespace bert
} // namespace plugin
} // namespace nvinfer1