/* * SPDX-FileCopyrightText: Copyright (c) 1993-2024 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. */ #ifndef INSTANCE_NORM_COMMON_H #define INSTANCE_NORM_COMMON_H #include "common/plugin.h" #include using namespace nvinfer1::pluginInternal; #define DEVICE_FUNCTION static inline __device__ template struct PackedStorage { enum { PACKED_ELEMENTS_PER_LDG = ELEMENTS_PER_LDG }; typedef T Type; }; template struct PackedStorage { enum { PACKED_ELEMENTS_PER_LDG = ELEMENTS_PER_LDG / 2 }; typedef int32_t Type; }; template struct PackedStorage { enum { PACKED_ELEMENTS_PER_LDG = ELEMENTS_PER_LDG / 4 }; typedef int32_t Type; }; template DEVICE_FUNCTION void fromFloat(int32_t (&dst)[N], float const (&src)[2 * N]) { #pragma unroll for (int32_t i = 0; i < N; ++i) { uint16_t lo, hi; asm volatile("cvt.rn.f16.f32 %0, %1;" : "=h"(lo) : "f"(src[2 * i + 0])); asm volatile("cvt.rn.f16.f32 %0, %1;" : "=h"(hi) : "f"(src[2 * i + 1])); asm volatile("mov.b32 %0, {%1, %2};" : "=r"(dst[i]) : "h"(lo), "h"(hi)); } } template DEVICE_FUNCTION void fromFloat(int32_t (&dst)[N], float const (&src)[4 * N], float scale) { union Pack_t { int8_t x[4]; int32_t val; }; #pragma unroll for (int32_t i = 0; i < N; ++i) { Pack_t packed; #pragma unroll for (int32_t ii = 0; ii < 4; ii++) { packed.x[ii] = __float_as_int(min(max(src[4 * i + ii] * scale + 12582912.0F, 12582785.0F), 12583039.0F)); } dst[i] = packed.val; } } template DEVICE_FUNCTION void fromFloat(float (&dst)[N], float const (&src)[N]) { #pragma unroll for (int32_t i = 0; i < N; ++i) { dst[i] = src[i]; } } template DEVICE_FUNCTION void toFloat(float (&dst)[2 * N], int32_t (&src)[N], float scale = 1.f) { #pragma unroll for (int32_t i = 0; i < N; ++i) { uint16_t lo, hi; asm volatile("mov.b32 {%0, %1}, %2;" : "=h"(lo), "=h"(hi) : "r"(src[i])); asm volatile("cvt.f32.f16 %0, %1;" : "=f"(dst[2 * i + 0]) : "h"(lo)); asm volatile("cvt.f32.f16 %0, %1;" : "=f"(dst[2 * i + 1]) : "h"(hi)); } } template DEVICE_FUNCTION void toFloat(float (&dst)[4 * N], int32_t (&src)[N], float scale = 1.f) { union Pack_t { int8_t x[4]; int32_t val; }; #pragma unroll for (int32_t i = 0; i < N; ++i) { Pack_t packed; packed.val = src[i]; #pragma unroll for (int32_t ii = 0; ii < 4; ++ii) { dst[4 * i + ii] = (DO_SCALE) ? __int2float_rn((int32_t) packed.x[ii]) * scale : __int2float_rn((int32_t) packed.x[ii]); } } } template DEVICE_FUNCTION void toFloat(float (&dst)[N], float (&src)[N], float scale = 1.f) { #pragma unroll for (int32_t i = 0; i < N; ++i) { dst[i] = (DO_SCALE) ? src[i] * scale : src[i]; } } template DEVICE_FUNCTION void ldg(int32_t (&dst)[1], T const* gmem) { dst[0] = __ldg((int32_t const*) gmem); } template DEVICE_FUNCTION void ldgStream(int32_t (&dst)[1], T const* gmem) { uint32_t tmp; asm volatile("ld.global.cs.nc.s32 %0, [%1];" : "=r"(tmp) : "l"((uint32_t const*) gmem)); dst[0] = tmp; } template DEVICE_FUNCTION void ldg(int32_t (&dst)[2], T const* gmem) { int2 tmp = __ldg((int2 const*) gmem); dst[0] = tmp.x; dst[1] = tmp.y; } template DEVICE_FUNCTION void ldgStream(int32_t (&dst)[2], T const* gmem) { int2 tmp; asm volatile("ld.global.cs.nc.v2.s32 {%0,%1}, [%2];" : "=r"(tmp.x), "=r"(tmp.y) : "l"((int2 const*) gmem)); dst[0] = tmp.x; dst[1] = tmp.y; } DEVICE_FUNCTION void ldg(int32_t (&dst)[2], uint16_t const* gmem) { #if defined(__CUDA_ARCH__) && __CUDA_ARCH__ >= 320 int2 tmp = __ldg((int2 const*) gmem); dst[0] = tmp.x; dst[1] = tmp.y; #endif } DEVICE_FUNCTION void ldgStream(int32_t (&dst)[2], uint16_t const* gmem) { int2 tmp; asm volatile("ld.global.cs.nc.v2.s32 {%0,%1}, [%2];" : "=r"(tmp.x), "=r"(tmp.y) : "l"((int2 const*) gmem)); dst[0] = tmp.x; dst[1] = tmp.y; } template DEVICE_FUNCTION void ldg(float (&dst)[N], uint16_t const* gmem) { int32_t tmp[N / 2]; ldg(tmp, gmem); toFloat(dst, tmp); } template DEVICE_FUNCTION void ldgStream(float (&dst)[N], uint16_t const* gmem) { int32_t tmp[N / 2]; ldgStream(tmp, gmem); toFloat(dst, tmp); } template DEVICE_FUNCTION void ldg(float (&dst)[N], int8_t const* gmem) { int32_t tmp[N / 4]; ldg(tmp, gmem); toFloat(dst, tmp); } template DEVICE_FUNCTION void ldgStream(float (&dst)[N], int8_t const* gmem) { int32_t tmp[N / 4]; ldgStream(tmp, gmem); toFloat(dst, tmp); } template DEVICE_FUNCTION void stg(T* gmem, int32_t (&src)[1]) { reinterpret_cast(gmem)[0] = src[0]; } template DEVICE_FUNCTION void stgStream(T* gmem, int32_t (&src)[1]) { uint32_t tmp = src[0]; asm volatile("st.global.cs.s32 [%0], %1;" ::"l"((uint32_t*) gmem), "r"(tmp)); } template DEVICE_FUNCTION void stg(T* gmem, int32_t (&src)[2]) { reinterpret_cast(gmem)[0] = make_int2(src[0], src[1]); } template DEVICE_FUNCTION void stgStream(T* gmem, int32_t (&src)[2]) { asm volatile("st.global.cs.v2.s32 [%0], {%1,%2};" ::"l"((uint32_t*) gmem), "r"(src[0]), "r"(src[1])); } template DEVICE_FUNCTION void stg(uint16_t* gmem, float (&src)[N], float scale) { int32_t tmp[N / 2]; fromFloat(tmp, src); stg(gmem, tmp); } template DEVICE_FUNCTION void stgStream(uint16_t* gmem, float (&src)[N], float scale) { int32_t tmp[N / 2]; fromFloat(tmp, src); stgStream(gmem, tmp); } template DEVICE_FUNCTION void stg(int8_t* gmem, float (&src)[N], float scale) { int32_t tmp[N / 4]; fromFloat(tmp, src, scale); stg(gmem, tmp); } template DEVICE_FUNCTION void stgStream(int8_t* gmem, float (&src)[N], float scale) { int32_t tmp[N / 4]; fromFloat(tmp, src, scale); stg(gmem, tmp); } DEVICE_FUNCTION void readFromGmem(float (&dst)[2], float const* gmem, int32_t idx) { float2 tmp = __ldg((float2*) &gmem[2 * idx]); dst[0] = tmp.x; dst[1] = tmp.y; } DEVICE_FUNCTION void readFromGmem(float (&dst)[4], float const* gmem, int32_t idx) { float4 tmp = __ldg((float4*) &gmem[4 * idx]); dst[0] = tmp.x; dst[1] = tmp.y; dst[2] = tmp.z; dst[3] = tmp.w; } template DEVICE_FUNCTION void readFromGmem(float (&dst)[N], __half const* gmem, int32_t idx) { int32_t ival[N / 2]; if (N == 4) reinterpret_cast(ival)[0] = __ldg((int2*) &gmem[4 * idx]); else reinterpret_cast(ival)[0] = __ldg((int32_t*) &gmem[2 * idx]); #pragma unroll for (int32_t i = 0; i < N / 2; ++i) { uint16_t lo, hi; asm volatile("mov.b32 {%0, %1}, %2;" : "=h"(lo), "=h"(hi) : "r"(ival[i])); asm volatile("cvt.f32.f16 %0, %1;" : "=f"(dst[2 * i + 0]) : "h"(lo)); asm volatile("cvt.f32.f16 %0, %1;" : "=f"(dst[2 * i + 1]) : "h"(hi)); } } DEVICE_FUNCTION void readFromSmem(float (&x)[2], float const* smem, int32_t idx) { float2 tmp = *(float2 const*) &smem[2 * idx]; x[0] = tmp.x; x[1] = tmp.y; } DEVICE_FUNCTION void readFromSmem(float (&x)[4], float const* smem, int32_t idx) { float4 tmp = *(float4 const*) &smem[4 * idx]; x[0] = tmp.x; x[1] = tmp.y; x[2] = tmp.z; x[3] = tmp.w; } DEVICE_FUNCTION void readFromSmem(int32_t (&x)[1], int32_t const* smem, int32_t idx) { x[0] = smem[idx]; } DEVICE_FUNCTION void readFromSmem(int32_t (&x)[2], int32_t const* smem, int32_t idx) { int2 tmp = *(int2 const*) &smem[2 * idx]; x[0] = tmp.x; x[1] = tmp.y; } DEVICE_FUNCTION void writeToGmem(float* gmem, int32_t idx, float const (&src)[2]) { reinterpret_cast(&gmem[2 * idx])[0] = make_float2(src[0], src[1]); } DEVICE_FUNCTION void writeToGmem(float* gmem, int32_t idx, float const (&src)[4]) { reinterpret_cast(&gmem[4 * idx])[0] = make_float4(src[0], src[1], src[2], src[3]); } template DEVICE_FUNCTION void writeToGmem(__half* gmem, int32_t idx, float const (&src)[N]) { int32_t ival[N / 2]; #pragma unroll for (int32_t i = 0; i < N / 2; ++i) { uint16_t lo; uint16_t hi; asm volatile("cvt.rn.f16.f32 %0, %1;" : "=h"(lo) : "f"(src[2 * i + 0])); asm volatile("cvt.rn.f16.f32 %0, %1;" : "=h"(hi) : "f"(src[2 * i + 1])); asm volatile("mov.b32 %0, {%1, %2};" : "=r"(ival[i]) : "h"(lo), "h"(hi)); } if (N == 4) { reinterpret_cast(&gmem[4 * idx])[0] = make_int2(ival[0], ival[1]); } else { reinterpret_cast(&gmem[2 * idx])[0] = ival[0]; } } DEVICE_FUNCTION void writeToSmem(float* smem, int32_t idx, float const (&x)[2]) { reinterpret_cast(&smem[2 * idx])[0] = make_float2(x[0], x[1]); } DEVICE_FUNCTION void writeToSmem(float* smem, int32_t idx, float const (&x)[4]) { reinterpret_cast(&smem[4 * idx])[0] = make_float4(x[0], x[1], x[2], x[3]); } DEVICE_FUNCTION void writeToSmem(int32_t* smem, int32_t idx, int32_t const (&x)[1]) { smem[idx] = x[0]; } static inline __device__ void writeToSmem(int32_t* smem, int32_t idx, int32_t const (&x)[2]) { reinterpret_cast(&smem[2 * idx])[0] = make_int2(x[0], x[1]); } template DEVICE_FUNCTION void zero(int32_t (&dst)[N]) { #pragma unroll for (int32_t i = 0; i < N; ++i) { dst[i] = 0; } } template DEVICE_FUNCTION void zero(float (&dst)[N]) { #pragma unroll for (int32_t i = 0; i < N; ++i) { dst[i] = 0.f; } } template DEVICE_FUNCTION void add(float (&x)[N], float const (&y)[N]) { #pragma unroll for (int32_t i = 0; i < N; ++i) { x[i] += y[i]; } } template DEVICE_FUNCTION void normalize(float (&x)[N], float const (&bias)[N], float const (&scale)[N], float const (&m1)[N]) { #pragma unroll for (int32_t i = 0; i < N; ++i) { x[i] = bias[i] + scale[i] * (x[i] - m1[i]); } } template DEVICE_FUNCTION Storage relu(Storage in, Storage alpha) { Storage zero = (Storage) 0.f; return (in < zero) ? in * alpha : in; } template DEVICE_FUNCTION void reluActivation(float (&x)[N], float alpha) { #pragma unroll for (int32_t i = 0; i < N; ++i) { x[i] = relu(x[i], alpha); } } template DEVICE_FUNCTION void parallelSums_16x2(float* smem, float (&x)[4], int32_t nhw) { // The size of a warp. int32_t const THREADS_PER_WARP = 32; // The number of warps in a CTA. int32_t const WARPS_PER_CTA = THREADS_PER_CTA / THREADS_PER_WARP; // The number of threads per pixel. int32_t const THREADS_PER_PIXEL = 16; // The number of elements per ldg. int32_t const ELEMENTS_PER_LDG = 4; // The warp decomposition. int32_t const warp_id = threadIdx.x / THREADS_PER_WARP; int32_t const lane_id = threadIdx.x % THREADS_PER_WARP; // Store the values to shared memory. writeToSmem(smem, threadIdx.x, x); // Compute the parallel sum inside the warp. Use SHFL and reduce the amount of SMEM by 2x? __syncwarp(); // Read the running sum from the other thread in the warp. float y[ELEMENTS_PER_LDG]; if (lane_id < THREADS_PER_PIXEL) { readFromSmem(y, smem, threadIdx.x + THREADS_PER_PIXEL); } // Compute the updated sum. add(x, y); // The data is in SMEM. Do the final reduction. __syncthreads(); // The warp leaders, write to SMEM. if (lane_id < THREADS_PER_PIXEL) { writeToSmem(smem, warp_id * THREADS_PER_PIXEL + lane_id, x); } // The data is in SMEM. Do the final reduction. __syncthreads(); // The 1st warp does all the work. if (warp_id == 0) { readFromSmem(x, smem, threadIdx.x); } // We do the final reduction each half-warp sequentially reduces the final values. #pragma unroll for (int32_t offset = 1; offset < WARPS_PER_CTA / 2; ++offset) { // Read the mean and variance from the other pixel. if (warp_id == 0) { readFromSmem(y, smem, threadIdx.x + offset * THREADS_PER_WARP); } // Compute the updated sum. add(x, y); } // Make sure the data is in SMEM. __syncwarp(); // Store the mean/var for the different pixels. TODO: Use SHFL? if (warp_id == 0) { writeToSmem(smem, threadIdx.x, x); } // Make sure the data is in SMEM. __syncwarp(); // The first half warp finishes the work. if (threadIdx.x < THREADS_PER_PIXEL) { readFromSmem(y, smem, threadIdx.x + THREADS_PER_PIXEL); } // Compute the updated sum. add(x, y); // Make sure the data was read from SMEM. __syncwarp(); // Store the final values. if (threadIdx.x < THREADS_PER_PIXEL) { writeToSmem(smem, threadIdx.x, x); } } template static inline __device__ void parallelSums_8x4(float* smem, float (&x)[4], int32_t nhw) { // The size of a warp. int32_t const THREADS_PER_WARP = 32; // The number of warps in a CTA. int32_t const WARPS_PER_CTA = THREADS_PER_CTA / THREADS_PER_WARP; // The number of threads per pixel. int32_t const THREADS_PER_PIXEL = 8; // The number of elements per ldg. int32_t const ELEMENTS_PER_LDG = 4; // The warp decomposition. int32_t const warp_id = threadIdx.x / THREADS_PER_WARP; int32_t const lane_id = threadIdx.x % THREADS_PER_WARP; #pragma unroll for (int32_t i = 0; i < ELEMENTS_PER_LDG; ++i) { x[i] += __shfl_sync(0xffffffffU, x[i], THREADS_PER_PIXEL + lane_id); x[i] += __shfl_sync(0xffffffffU, x[i], THREADS_PER_PIXEL * 2 + lane_id); } // The warp leaders, write to SMEM. if (lane_id < THREADS_PER_PIXEL) { writeToSmem(smem, warp_id * THREADS_PER_PIXEL + lane_id, x); } // The data is in SMEM. Do the final reduction. __syncthreads(); // The 1st warp does all the work. // We do the final reduction each half-warp sequentially reduces the final values. if (warp_id == 0) { readFromSmem(x, smem, threadIdx.x); #pragma unroll for (int32_t offset = 1; offset < WARPS_PER_CTA / (THREADS_PER_WARP / THREADS_PER_PIXEL); ++offset) { float y[ELEMENTS_PER_LDG]; // Read the mean and variance from the other pixel. readFromSmem(y, smem, threadIdx.x + offset * THREADS_PER_WARP); // Compute the updated sum. add(x, y); } for (int32_t i = 0; i < ELEMENTS_PER_LDG; ++i) { x[i] += __shfl_sync(0xffffffffU, x[i], THREADS_PER_PIXEL + lane_id); x[i] += __shfl_sync(0xffffffffU, x[i], THREADS_PER_PIXEL * 2 + lane_id); } // Make sure the data was read from SMEM. __syncwarp(); // Store the final values. if (threadIdx.x < THREADS_PER_PIXEL) { writeToSmem(smem, threadIdx.x, x); } } } template DEVICE_FUNCTION void parallelSums(float* smem, float (&x)[ELEMENTS_PER_LDG], int32_t nhw) { // The size of a warp. int32_t const THREADS_PER_WARP = 32; // The number of warps in a CTA. int32_t const WARPS_PER_CTA = THREADS_PER_CTA / THREADS_PER_WARP; // The number of pixels computed by a single warp. int32_t const PIXELS_PER_WARP = THREADS_PER_WARP / THREADS_PER_PIXEL; // The position in the warp. int32_t const nhw_in_warp = nhw % PIXELS_PER_WARP; // The C in the warp. int32_t const c_in_warp = threadIdx.x % THREADS_PER_PIXEL; // Store the values to shared memory. writeToSmem(smem, threadIdx.x, x); // Compute the parallel sums. for (int32_t offset = PIXELS_PER_WARP / 2; offset > 0; offset /= 2) { if ((WARPS_PER_CTA * THREADS_PER_WARP) / THREADS_PER_PIXEL > THREADS_PER_WARP) { __syncthreads(); } else { // NOP. __syncwarp(); } // Read the running sum from the other thread. float y[ELEMENTS_PER_LDG]; if (nhw_in_warp < offset) { readFromSmem(y, smem, threadIdx.x + offset * THREADS_PER_PIXEL); } // Compute the updated sum. add(x, y); if ((WARPS_PER_CTA * THREADS_PER_WARP) / THREADS_PER_PIXEL > THREADS_PER_WARP) { __syncthreads(); } else { // NOP. __syncwarp(); } // Update the sum in SMEM. if (offset > 1 && nhw_in_warp < offset) { writeToSmem(smem, threadIdx.x, x); } } // The warps are done. Do the final reduction at the CTA level. __syncthreads(); // The warp leaders, write to SMEM. int32_t const idx = (threadIdx.x / THREADS_PER_WARP) * THREADS_PER_PIXEL + c_in_warp; if (nhw_in_warp == 0) { writeToSmem(smem, idx, x); } // The data is in SMEM. Do the final reduction. __syncthreads(); // Read the 1st element to prepare the work. if (nhw < WARPS_PER_CTA / 2) { readFromSmem(x, smem, threadIdx.x); } // We have the running mean and running m2. Let's build the mean/var of the CTA. for (int32_t offset = WARPS_PER_CTA / 2; offset > 0; offset /= 2) { if ((WARPS_PER_CTA * THREADS_PER_WARP) / THREADS_PER_PIXEL > THREADS_PER_WARP) { __syncthreads(); } else { // NOP. __syncwarp(); } // Read the mean and variance from the other pixel. float y[ELEMENTS_PER_LDG]; if (nhw < offset) { readFromSmem(y, smem, threadIdx.x + offset * THREADS_PER_PIXEL); } // Compute the updated sum. add(x, y); if ((WARPS_PER_CTA * THREADS_PER_WARP) / THREADS_PER_PIXEL > THREADS_PER_WARP) { __syncthreads(); } else { // NOP. __syncwarp(); } // Store the mean/var for the different pixels. if (nhw < offset) { writeToSmem(smem, threadIdx.x, x); } } } template struct ParallelSums { template DEVICE_FUNCTION void dispatch(float* smem, float (&x)[ELEMENTS_PER_LDG], int32_t nhw) { parallelSums(smem, x, nhw); } }; template <> struct ParallelSums<16, 4> { template DEVICE_FUNCTION void dispatch(float* smem, float (&x)[4], int32_t nhw) { parallelSums_16x2(smem, x, nhw); } }; template <> struct ParallelSums<8, 4> { template static inline __device__ void dispatch(float* smem, float (&x)[4], int32_t nhw) { parallelSums_8x4(smem, x, nhw); } }; namespace { int32_t divUp(int32_t m, int32_t n) { PLUGIN_ASSERT(m >= 0); PLUGIN_ASSERT(n > 0); // Use unsigned arithmetic to preclude overflow. auto const mu = static_cast(m); auto const nu = static_cast(n); return (mu + nu - 1U) / nu; } cudnnStatus_t convertTrt2cudnnDtype(nvinfer1::DataType trt_dtype, cudnnDataType_t* cudnn_dtype) { switch (trt_dtype) { case nvinfer1::DataType::kFLOAT: *cudnn_dtype = CUDNN_DATA_FLOAT; break; case nvinfer1::DataType::kHALF: *cudnn_dtype = CUDNN_DATA_HALF; break; default: return CUDNN_STATUS_BAD_PARAM; } return CUDNN_STATUS_SUCCESS; } } // namespace template __global__ __launch_bounds__(THREADS_PER_CTA) void in3dReluActivation(T* dst, T const* src, T alpha, int32_t count) { int32_t idx = blockIdx.x * THREADS_PER_CTA + threadIdx.x; if (idx >= count) { return; } T val = src[idx]; dst[idx] = (val < static_cast(0.F)) ? val * alpha : val; } #endif // INSTANCE_NORM_COMMON_H