分别使用 TensorRT 和 CUDA 加速 MTCNN

举报
ShaderJoy 发表于 2021/12/29 23:59:28 2021/12/29
【摘要】 Github 现有的 TensorRT 加速的 MTCNN 【PKUZHOU/MTCNN_FaceDetection_TensorRT】不是基于插件的,而是走了使用 scale 和 ReLU 、eltwise-sum 层 “曲线救国”的路线—— PKUZHOU 认为 PReLU 会破坏 TensorRT 的 CBR 优化...

Github 现有的 TensorRT 加速的 MTCNN 【PKUZHOU/MTCNN_FaceDetection_TensorRT】不是基于插件的,而是走了使用 scale 和 ReLU 、eltwise-sum 层 “曲线救国”的路线——

PKUZHOU 认为 PReLU 会破坏 TensorRT 的 CBR 优化,但实际上实现 PReLU 插件以后耗时更少,如图

左侧是“曲线救国”版,右侧是实现了 PReLU 插件,一张 1920x1080 的图像,能有200多 ms 的提升(原谅我笔记本的显卡是 GTX 970m,古老的 Maxwell 架构,连半精度都不支持,更别提 Int8 了,抹泪)。

插件的实现代码

prelu_plugn.h


  
  1. #ifndef PRELU_PLUGIN_H
  2. #define PRELU_PLUGIN_H
  3. #include "kernels.h"
  4. #include <cstring>
  5. #include <assert.h>
  6. #include <cuda.h>
  7. #include <cuda_runtime_api.h>
  8. #include <cuda_fp16.h> // __half
  9. #include <NvInfer.h>
  10. #include <NvCaffeParser.h>
  11. using namespace nvinfer1;
  12. using namespace nvcaffeparser1;
  13. /*
  14. Prelu layer
  15. */
  16. class PreluPlugin : public IPlugin
  17. {
  18. public:
  19. PreluPlugin(const Weights* weights, int nbWeights);
  20. PreluPlugin(const void* buffer, size_t size);
  21. ~PreluPlugin();
  22. Dims getOutputDimensions(int index, const Dims* inputs, int nbInputDims);
  23. int enqueue(int batchSize, const void* const* inputs, void** outputs, void*, cudaStream_t stream);
  24. int getNbOutputs() const override
  25. {
  26. return 1;
  27. };
  28. void configure(const Dims* inputs, int nbInputs, const Dims* outputs, int nbOutputs, int) override;
  29. void serialize(void* buffer) override;
  30. size_t getSerializationSize() override;
  31. inline size_t getWorkspaceSize(int) const override
  32. {
  33. return 0;
  34. }
  35. int initialize() override;
  36. void terminate() override;
  37. protected:
  38. int m_input_c;
  39. int m_input_h;
  40. int m_input_w;
  41. int m_input_count;
  42. bool m_channel_shared {false};
  43. Weights m_weights;
  44. void* m_device_kernel{nullptr};
  45. private:
  46. void deserializeToDevice(const char*& hostBuffer, void*& deviceWeights, size_t size)
  47. {
  48. deviceWeights = copyToDevice(hostBuffer, size);
  49. hostBuffer += size;
  50. }
  51. // 将 host 的 buffer 上的值拷贝到 device (还会开辟设备内存)上
  52. void* copyToDevice(const void* data, size_t count)
  53. {
  54. void* deviceData;
  55. cudaMalloc(&deviceData, count);
  56. cudaMemcpy(deviceData, data, count, cudaMemcpyHostToDevice);
  57. return deviceData;
  58. }
  59. template<typename T> void read(const char*& buffer, T& val)
  60. {
  61. val = *reinterpret_cast<const T*>(buffer);
  62. buffer += sizeof(T);
  63. }
  64. template<typename T> void write(char*& buffer, const T& val)
  65. {
  66. *reinterpret_cast<T*>(buffer) = val;
  67. buffer += sizeof(T);
  68. }
  69. size_t type2size(nvinfer1::DataType type)
  70. {
  71. // return sizeof(float);
  72. return type == nvinfer1::DataType::kFLOAT ? sizeof(float) : sizeof(__half);
  73. }
  74. // 将 Weights 的 values 中的值拷贝到 host 的 buffer 上
  75. void convertAndCopyToBuffer(char*& buffer, const Weights& weights)
  76. {
  77. memcpy(buffer, weights.values, weights.count * type2size(weights.type));
  78. buffer += weights.count * type2size(weights.type);
  79. }
  80. };
  81. #endif // PRELU_PLUGIN_H

prelu_plugin.cpp


  
  1. #include "prelu_plugin.h"
  2. #include <iostream>
  3. using namespace nvinfer1;
  4. using namespace nvcaffeparser1;
  5. //using namespace plugin;
  6. PreluPlugin::PreluPlugin(const Weights* weights, int nbWeights)
  7. {
  8. assert(nbWeights == 1);
  9. m_weights = weights[0];
  10. assert(m_weights.type == DataType::kFLOAT || m_weights.type == DataType::kHALF);
  11. // 为 values 开辟空间
  12. m_weights.values = malloc(m_weights.count * type2size(m_weights.type));
  13. // weights[0].values -> m_weights.values
  14. memcpy(const_cast<void*>(m_weights.values), weights[0].values, m_weights.count * type2size(m_weights.type));
  15. }
  16. PreluPlugin::PreluPlugin(const void* buffer, size_t size)
  17. {
  18. // 反序列化:和序列化的顺序相同,注意不同的数据类型
  19. const char* d = reinterpret_cast<const char*>(buffer), *a = d;
  20. read<int>(d, m_input_c);
  21. read<int>(d, m_input_h);
  22. read<int>(d, m_input_w);
  23. read<int>(d, m_input_count);
  24. read<bool>(d, m_channel_shared);
  25. read<int64_t>(d, m_weights.count);
  26. read<DataType>(d, m_weights.type);
  27. // m_weights.values = nullptr;
  28. m_weights.values = malloc(m_weights.count * type2size(m_weights.type));
  29. //deserializeToDevice(d,m_device_kernel,m_weights.count);
  30. // d -> m_weights.values
  31. memcpy(const_cast<void*>(m_weights.values), d, m_weights.count * type2size(m_weights.type));
  32. d += m_weights.count * type2size(m_weights.type); // 指针继续向后
  33. assert(d == a + size);
  34. }
  35. PreluPlugin::~PreluPlugin()
  36. {
  37. // std::cout << "~PreluPlugin "<< std::endl;
  38. // if (m_weights.values)
  39. // {
  40. // free(const_cast<void*>(m_weights.values));
  41. // }
  42. }
  43. // 仅在序列化时调用该方法
  44. Dims PreluPlugin::getOutputDimensions(int index, const Dims* inputs, int nbInputDims)
  45. {
  46. // std::cout << "0~getOutputDimensions " << std::endl;
  47. assert(index == 0 && nbInputDims == 1 && inputs[0].nbDims == 3);
  48. return DimsCHW(inputs[0].d[0], inputs[0].d[1], inputs[0].d[2]);
  49. }
  50. // 仅在序列化时调用该方法
  51. void PreluPlugin::configure(const Dims* inputs, int nbInputs, const Dims* outputs, int nbOutputs, int)
  52. {
  53. // std::cout << "1~configure " << std::endl;
  54. m_input_c = inputs[0].d[0];
  55. m_input_h = inputs[0].d[1];
  56. m_input_w = inputs[0].d[2];
  57. m_input_count = m_input_c * m_input_h * m_input_w;
  58. }
  59. size_t PreluPlugin::getSerializationSize()
  60. {
  61. return 4 * sizeof(int) + sizeof(bool) +
  62. sizeof(m_weights.count)
  63. + sizeof(m_weights.type)
  64. + m_weights.count * type2size(m_weights.type);
  65. }
  66. void PreluPlugin::serialize(void* buffer)
  67. {
  68. char* d = static_cast<char*>(buffer), *a = d;
  69. write(d, m_input_c);
  70. write(d, m_input_h);
  71. write(d, m_input_w);
  72. write(d, m_input_count);
  73. write(d, m_channel_shared);
  74. write(d, m_weights.count);
  75. write(d, m_weights.type);
  76. convertAndCopyToBuffer(d, m_weights);
  77. assert(d == a + getSerializationSize());
  78. }
  79. int PreluPlugin::enqueue(int batchSize, const void* const* inputs, void** outputs, void*, cudaStream_t stream)
  80. {
  81. const float* bottom_data = reinterpret_cast<const float*>(inputs[0]);
  82. float* top_data = reinterpret_cast<float*>(outputs[0]);
  83. const int count = batchSize * m_input_count;
  84. const int dim = m_input_h * m_input_w;
  85. const int channels = m_input_c;
  86. const int div_factor = m_channel_shared ? channels : 1; //m_channel_shared default is false
  87. pReLUForward(count, channels, dim, bottom_data, top_data, m_device_kernel, div_factor, stream);
  88. return 0;
  89. }
  90. int PreluPlugin::initialize()
  91. {
  92. // std::cout << "2~initialize~0 "<< m_device_kernel << std::endl;
  93. cudaMalloc(&m_device_kernel, m_weights.count * type2size(m_weights.type));
  94. cudaMemcpy(m_device_kernel, m_weights.values, m_weights.count * type2size(m_weights.type), cudaMemcpyHostToDevice);
  95. return 0;
  96. }
  97. // engine 销毁时会调用
  98. void PreluPlugin::terminate()
  99. {
  100. // std::cout << "~terminate "<< m_device_kernel << std::endl;
  101. if (m_weights.values)
  102. {
  103. free(const_cast<void*>(m_weights.values));
  104. }
  105. if (m_device_kernel)
  106. {
  107. cudaFree(m_device_kernel);
  108. m_device_kernel = nullptr;
  109. }
  110. }

kernel.cu


  
  1. __global__ void pReLU(const int n, const int channels, const int dim,
  2. const float* in, float* out, const float* slope_data, const int div_factor)
  3. {
  4. CUDA_KERNEL_LOOP(index, n)
  5. {
  6. int c = (index / dim) % channels / div_factor;
  7. out[index] = in[index] > 0 ? in[index] : in[index] * slope_data[c];
  8. }
  9. }
  10. void pReLUForward(const int count, const int channels, const int dim, const float* bottom_data,
  11. float* top_data, void* mDeviceKernel, const int div_factor, cudaStream_t stream)
  12. {
  13. pReLU <<< CAFFE_GET_BLOCKS(count), CAFFE_CUDA_NUM_THREADS, 0, stream>>>(count, channels, dim,
  14. bottom_data, top_data,
  15. static_cast<const float*>(mDeviceKernel), // slope_data
  16. div_factor);
  17. CUDA_POST_KERNEL_CHECK;
  18. }

继而通过 Valgrind 和 gProf 进行性能分析可以发现,

 

如果优化 nms 和 image2Matrix 方法的话,可以进一步提高性能;

由于多个 Pnet 的检测也是相互独立的,所以还可以使用多线程并行,然后多个流在 GPU (最好支持 HyperQ)上的 Overlap 可以再进一步提高性能。

上图是我没用 TensorRT,直接用原生的 CUDA 加速的效果,迭代 20 次,平均每次仅花费 60 ms 左右。

经过进一步优化纯 CUDA 的算法,一次迭代仅需 44 ms( 仍然是在 min_size = 30, thresh_p = 0.7, thresh_r = 0.7, thresh_o = 0.7, thresh_nms_p = 0.5, thresh_nms_r = 0.5, thresh_nms_o = 0.5 的条件下)

 如果通过 TensorRT 加速应该会取得更优的性能。

 最后是检测的结果:

文章来源: panda1234lee.blog.csdn.net,作者:panda1234lee,版权归原作者所有,如需转载,请联系作者。

原文链接:panda1234lee.blog.csdn.net/article/details/87201073

【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。