/* * 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. */ #include "common/kernels/kernel.h" #include #include #include #include #include #include #include namespace nvinfer1 { namespace plugin { // This macro is to control shared memory usage. If set to 1, kernel loads the whole feature map // into shared memory for reuse; If set to 0, kernel loads data from global memory directly. // Roi pooling performance is data dependent. You can test which value is better to your data. // If all bboxes are very small, 0 is recommended, otherwise, shared memory will load many unused // data; If bboxes have many overlaps, 1 is recommended to avoid duplicate loads. // 1 requires larger shared memory size. It may fail if it is larger than CUDA allowed per-block // shared memory size upper bound. Then you have to use 0. #define ROIPOOLING_FEATURE_MAP_USE_SHMEM 1 template __device__ T getMax(); template <> __device__ __forceinline__ int8_t getMax() { return INT8_MAX; } template <> __device__ __forceinline__ float getMax() { return FLT_MAX; } // ROI POOLING FORWARD KERNEL template __global__ void ROIPoolingForwardKernelAligned(int32_t ROICount, const ROI_T* rois, int32_t N, // feature map size int32_t C, // feature map size int32_t H, // feature map size int32_t W, // feature map size const DATA_T* featureMap, const int32_t poolingH, const int32_t poolingW, const float spatialScale, DATA_T* top, int32_t* maxIds, int32_t fmapStep) { extern __shared__ float smem[]; DATA_T* feature_shr = (DATA_T*) &smem[0]; int* rois_shr = nullptr; if (FM_IN_SMEM) { rois_shr = (int*) &feature_shr[H * W]; } else { rois_shr = (int*) &feature_shr[0]; feature_shr = nullptr; } const int batch = blockIdx.x / C; const int channel = blockIdx.x % C; // load ROIs to shared memory for (int j = threadIdx.x; j < ROICount; j += blockDim.x) { int offset = j << 2; float4 roi = reinterpret_cast(const_cast(rois))[batch * ROICount + j]; // spatialScale = 1.0 / featureStride // Convert the coordinates to feature map scale rois_shr[offset] = round(roi.x * spatialScale); //roi_start_w rois_shr[offset + 1] = round(roi.y * spatialScale); //roi_start_h rois_shr[offset + 2] = round(roi.z * spatialScale) - round(roi.x * spatialScale); //roi_length_w rois_shr[offset + 3] = round(roi.w * spatialScale) - round(roi.y * spatialScale); // roi_length_h } // NC/xHW int fmapOffset = blockIdx.x / fmapStep * H * W * fmapStep + blockIdx.x % fmapStep; // Assumes #CTAs is just enough to cover all channels of all blocks const DATA_T* bottom_data_offset = featureMap + fmapOffset; if (FM_IN_SMEM) { // load the current channel to the shared memory for (int j = threadIdx.x; j < H * W; j += blockDim.x) { feature_shr[j] = bottom_data_offset[j * fmapStep]; } } __syncthreads(); for (int j = threadIdx.x; j < ROICount; j += blockDim.x) { const int offset = j << 2; // Force malformed ROIs to be 1x1 int roi_start_w = rois_shr[offset]; int roi_start_h = rois_shr[offset + 1]; int roi_width = max(rois_shr[offset + 2] + 1, 1); int roi_height = max(rois_shr[offset + 3] + 1, 1); float bin_size_h = static_cast(roi_height) / static_cast(poolingH); float bin_size_w = static_cast(roi_width) / static_cast(poolingW); for (int ph = 0; ph < poolingH; ++ph) { for (int pw = 0; pw < poolingW; ++pw) { int hstart = static_cast(floor(static_cast(ph) * bin_size_h)); int wstart = static_cast(floor(static_cast(pw) * bin_size_w)); int hend = static_cast(ceil(static_cast(ph + 1) * bin_size_h)); int wend = static_cast(ceil(static_cast(pw + 1) * bin_size_w)); // Add roi offsets and clip to input boundaries // In fact, clipping should be done in the RPN, but just in case... hstart = min(max(hstart + roi_start_h, 0), H); hend = min(max(hend + roi_start_h, 0), H); wstart = min(max(wstart + roi_start_w, 0), W); wend = min(max(wend + roi_start_w, 0), W); bool is_empty = (hend <= hstart) || (wend <= wstart); // Define an empty pooling region to be zero DATA_T maxval = is_empty ? 0 : -getMax(); int maxId = -1; DATA_T data = 0; for (int h = hstart; h < hend; ++h) { for (int w = wstart; w < wend; ++w) { int index = h * W + w; if (FM_IN_SMEM) { data = feature_shr[index]; } else { data = bottom_data_offset[index * fmapStep]; } if (data > maxval) { maxval = data; maxId = index; } } } top[(((batch * ROICount + j) * C + channel) * poolingH + ph) * poolingW + pw] = maxval; if (!INFER_ONLY) { maxIds[(((batch * ROICount + j) * C + channel) * poolingH + ph) * poolingW + pw] = maxId; } } //for:pw } //for:ph } // for:j } template pluginStatus_t ROIPoolingForwardKernelAlignedLauncher(cudaStream_t stream, const int R, // TOTAL number of rois -> ~nmsMaxOut * N const int N, // Batch size const int C, // Channels const int H, // Input feature map H const int W, // Input feature map W const int poolingH, // Output feature map H const int poolingW, // Output feature map W const float spatialScale, const void* rois, const void* featureMap, void* top, int* maxIds, size_t deviceSmemSize) { size_t roiShmemSize = (R / N) * 4 * sizeof(ROI_T); #if ROIPOOLING_FEATURE_MAP_USE_SHMEM size_t shmemSize = H * W * sizeof(DATA_T) + roiShmemSize; const bool fmap_in_shmem = true; #else size_t shmemSize = roiShmemSize; const bool fmap_in_shmem = false; #endif if (shmemSize > deviceSmemSize) { return STATUS_BAD_PARAM; } // in the aligned version of ROI Pooling R should always be a multiple of N PLUGIN_ASSERT(R % N == 0); // NC/xHW int32_t fmapStep = 1; switch(DATA_L) { case NCHW: fmapStep = 1; break; case NC4HW: fmapStep = 4; PLUGIN_ASSERT((N * C) % 4 == 0); break; case NC32HW: fmapStep = 32; PLUGIN_ASSERT((N * C) % 32 == 0); break; default: PLUGIN_ASSERT(false); } if (shmemSize > 48 * 1024) { PLUGIN_CHECK(cudaFuncSetAttribute(&ROIPoolingForwardKernelAligned, cudaFuncAttributeMaxDynamicSharedMemorySize, shmemSize)); } ROIPoolingForwardKernelAligned<<>>(R / N, (const ROI_T*) rois, N, // feature map size C, // feature map size H, // feature map size W, // feature map size (const DATA_T*) featureMap, poolingH, poolingW, spatialScale, (DATA_T*) top, maxIds, fmapStep); CSC(cudaGetLastError(), STATUS_FAILURE); return STATUS_SUCCESS; } // ROI POOLING LAUNCH CONFIG typedef pluginStatus_t (*roiFwd)(cudaStream_t, const int, //R, // TOTAL number of rois -> ~nmsMaxOut * N const int, //N, // Batch size const int, //C, // Channels const int, //H, // Input feature map H const int, //W, // Input feature map W const int, //poolingH, // Output feature map H const int, //poolingW, // Output feature map W const float, //spatialScale, const void*, //rois, const void*, //featureMap, void*, //top int*, //maxIds size_t); //device shmem size // struct struct roiFwdLaunchConfig { DataType t_rois; DataType t_featureMap; DLayout_t l_featureMap; DataType t_top; DLayout_t l_top; bool inferOnly; roiFwd function; roiFwdLaunchConfig( DataType t_rois, DataType t_featureMap, DLayout_t l_featureMap, DataType t_top, DLayout_t l_top, bool inferOnly) : t_rois(t_rois) , t_featureMap(t_featureMap) , l_featureMap(l_featureMap) , t_top(t_top) , l_top(l_top) , inferOnly(inferOnly) , function(nullptr) { } roiFwdLaunchConfig(DataType t_rois, DataType t_featureMap, DLayout_t l_featureMap, DataType t_top, DLayout_t l_top, bool inferOnly, roiFwd function) : t_rois(t_rois) , t_featureMap(t_featureMap) , l_featureMap(l_featureMap) , t_top(t_top) , l_top(l_top) , inferOnly(inferOnly) , function(function) { } bool operator==(roiFwdLaunchConfig const& other) const { return (t_rois == other.t_rois) && (t_featureMap == other.t_featureMap) && (l_featureMap == other.l_featureMap) && (t_top == other.t_top) && (l_top == other.l_top) && (inferOnly == other.inferOnly); } }; #define FLOAT32 nvinfer1::DataType::kFLOAT #define INT8 nvinfer1::DataType::kINT8 static std::array roiFwdLCOptions = { roiFwdLaunchConfig(FLOAT32, FLOAT32, NCHW, FLOAT32, NCHW, true, ROIPoolingForwardKernelAlignedLauncher), roiFwdLaunchConfig(FLOAT32, FLOAT32, NCHW, FLOAT32, NCHW, false, ROIPoolingForwardKernelAlignedLauncher), roiFwdLaunchConfig(FLOAT32, INT8, NCHW, INT8, NCHW, true, ROIPoolingForwardKernelAlignedLauncher), roiFwdLaunchConfig(FLOAT32, INT8, NC4HW, INT8, NCHW, true, ROIPoolingForwardKernelAlignedLauncher), roiFwdLaunchConfig(FLOAT32, INT8, NC32HW, INT8, NCHW, true, ROIPoolingForwardKernelAlignedLauncher), roiFwdLaunchConfig(FLOAT32, FLOAT32, NC4HW, FLOAT32, NCHW, true, ROIPoolingForwardKernelAlignedLauncher)}; // ROI INFERENCE pluginStatus_t roiInference(cudaStream_t stream, const int R, // TOTAL number of rois -> ~nmsMaxOut * N const int N, // Batch size const int C, // Channels const int H, // Input feature map H const int W, // Input feature map W const int poolingH, // Output feature map H const int poolingW, // Output feature map W const float spatialScale, const nvinfer1::DataType t_rois, const void* rois, const nvinfer1::DataType t_featureMap, const DLayout_t l_featureMap, const void* featureMap, const nvinfer1::DataType t_top, const DLayout_t l_top, void* top, size_t deviceSmemSize) { if (featureMap == NULL || rois == NULL || top == NULL) { return STATUS_BAD_PARAM; } DEBUG_PRINTF("&&&& ROIS %u\n", hash(rois, R * 4 * sizeof(float))); DEBUG_PRINTF("&&&& FMAP %u\n", hash(featureMap, N * C * H * W * sizeof(float))); roiFwdLaunchConfig rflc = roiFwdLaunchConfig(t_rois, t_featureMap, l_featureMap, t_top, l_top, true); ASSERT_PARAM(R > 0); for (unsigned i = 0; i < roiFwdLCOptions.size(); i++) { if (rflc == roiFwdLCOptions[i]) { DEBUG_PRINTF("$$$$ ROI KERNEL %d\n", i); return roiFwdLCOptions[i].function(stream, R, N, C, H, W, poolingH, poolingW, spatialScale, rois, featureMap, top, NULL, deviceSmemSize); } } return STATUS_BAD_PARAM; } } // namespace plugin } // namespace nvinfer1