Cargue varios modelos simultáneamente en una o más GPU usando tensorrt en win10 y C++

        Antes de leer este artículo, lea mi artículo didáctico sobre cómo implementar yolov5 con tensorrt en Win10, para que sea más fácil regresar y leer este artículo.


Al realizar un proyecto de detección de objetivos de aprendizaje profundo, a veces existen requisitos como se muestra en la siguiente tabla:

Requisitos de FPS del algoritmo ≥50
Sistema operativo Win10
lenguaje de desarrollo C++
Método de carga del modelo Una GPU carga varios modelos
Múltiples GPU cargan un solo modelo por separado

La siguiente publicación de blog lo llevará a completar estos dos requisitos por separado.


  Una GPU carga varios modelos

Para implementar una GPU para cargar múltiples modelos, podemos entender que los diferentes archivos del motor se cargan en secuencia, es decir, se deben definir dos conjuntos de variables de flujo y búfer de Cuda, o las variables de flujo y las variables de búfer se encapsulan en clases y luego Cree dos clases de interfaz y llame a la API correspondiente para cargar varios modelos en una GPU. Tomemos como ejemplo llamar al motor de peso de yolov5. Primero, encapsulamos una clase:

  • La implementación del archivo .h se ve así:
    class True_interface :
    {
    public:
    	True_interface();
    	virtual void load_model(int,std::string); // API:加载模型
        virtual void release_buffer(); // API:释放缓冲及指针变量
        virtual void interface(cv::Mat, float); // API:前向推理外部调用接口(输入图像和置信度阈值)
        void doInference(IExecutionContext& context, cudaStream_t& stream, void** buffers, float* input, float* output, int batchSize); // API:前向推理
        
        const int INPUT_H = Yolo::INPUT_H;
        const int INPUT_W = Yolo::INPUT_W;
        const int OUTPUT_SIZE = Yolo::MAX_OUTPUT_BBOX_COUNT * sizeof(Yolo::Detection) / sizeof(float) + 1;  
        float data[BATCH_SIZE * 3 * Yolo::INPUT_H * Yolo::INPUT_W];
        float prob[BATCH_SIZE * (Yolo::MAX_OUTPUT_BBOX_COUNT * sizeof(Yolo::Detection) / sizeof(float) + 1)];
        ICudaEngine* engine;
        IExecutionContext* context; // 创建context,创建一些空间来存储中间激活值
        void* buffers[2]; // 创建buffers指向 GPU 上输入和输出缓冲区
        cudaStream_t stream; // 创建cuda流
        int inputIndex;
        int outputIndex;
        IRuntime* runtime; // 创建runtime
        const char* INPUT_BLOB_NAME = "data";
        const char* OUTPUT_BLOB_NAME = "prob";
        Logger gLogger;
    };
  • Las implementaciones de la API del archivo .cpp son las siguientes: de hecho, el código realmente útil son solo esas líneas:
    • cargar_modelo():
      void True_interface::load_model(int gpu_id, std::string model_Name_)
      {
          if ((gpu_id == 0) || (gpu_id == 1)) // 选择要加载的GPU设备
          {
              cudaError_t cudaError = cudaSetDevice(gpu_id);
              if (cudaError == cudaSuccess)
                  std::cout << "GPU device selected successfully!" << std::endl;
              else
                  std::cout << "GPU device selection failed!" << std::endl;
          }
          else
          {
              std::cerr << "GPU device assignment error!" << std::endl;
              return;
          }
          std::string engine_name;
          if (model_Name_ == "model_one")
          {
              engine_name = std::string("model_one.engine");
          }
          else if(model_Name_ == "model_two")
          {
              engine_name = std::string("model_two.engine");  // 权重路径
          }
          else
          {
              std::cout << "Please enter the correct detector type!" << std::endl;
              return;
          }
          float gd = 0.0f, gw = 0.0f;
          std::ifstream file(engine_name, std::ios::binary);
          if (!file.good()) 
          {
              std::cerr << "read " << engine_name << " error!" << std::endl;
          }
          char* trtModelStream = nullptr;
          size_t size = 0;
          file.seekg(0, file.end);
          size = file.tellg();
          file.seekg(0, file.beg);
          trtModelStream = new char[size];
          file.read(trtModelStream, size);
          file.close();
      
          runtime = createInferRuntime(gLogger); // 创建runtime
          engine = runtime->deserializeCudaEngine(trtModelStream, size); // 反序列化引擎
          context = engine->createExecutionContext(); // 创建一个上下文,创建一些空间用来存储中间值
          delete[] trtModelStream;
          inputIndex = engine->getBindingIndex(INPUT_BLOB_NAME); // 使用输入和输出blob名称来获得相应的输入和输出索引
          outputIndex = engine->getBindingIndex(OUTPUT_BLOB_NAME); // 使用输入和输出blob名称来获得相应的输入和输出索引
          CUDA_CHECK(cudaMalloc(&buffers[inputIndex], BATCH_SIZE * 3 * INPUT_H * INPUT_W * sizeof(float))); // 为输入输出开辟GPU显存
          CUDA_CHECK(cudaMalloc(&buffers[outputIndex], BATCH_SIZE * OUTPUT_SIZE * sizeof(float))); // 为输入输出开辟GPU显存
          CUDA_CHECK(cudaStreamCreate(&stream));// 绑定Cuda流
          
      
          if (model_Name_ == "model_one")
          {
              std::cout << "model_one loaded successfully!" << std::endl;
          }
          else if (model_Name_ == "model_two")
          {
              std::cout << "model_two loaded successfully!" << std::endl;
          }
      }
    • hacerInferencia():
      void True_interface::doInference(IExecutionContext& context, cudaStream_t& stream, void** buffers, float* input, float* output, int batchSize)
      {
          // ---------------------------------从CPU到GPU,拷贝input数据-----------------------------------------------------
          CUDA_CHECK(cudaMemcpyAsync(buffers[0],  // //显存上的存储区域,用于存放输入数据
                                     input, batchSize * 3 * INPUT_H * INPUT_W * sizeof(float),  // //读入内存中的数据
                                     cudaMemcpyHostToDevice, stream));
          context.enqueue(batchSize, buffers, stream, nullptr); // 异步推理
          // ---------------------------------GPU到CPU,拷贝output数据-----------------------------------------------------
          CUDA_CHECK(cudaMemcpyAsync(output, buffers[1], batchSize * OUTPUT_SIZE * sizeof(float), cudaMemcpyDeviceToHost, stream));
          cudaStreamSynchronize(stream); // 同步cuda流
      }
    •  interfaz():
      void True_interface::interface_B(cv::Mat img, float conf_thresh)
      {
          // ---------------------------------------------------清空结果向量----------------------------------------------------
          result_B.clear();
          // ----------------------------------------------------图像预处理-----------------------------------------------------
          cv::Mat pr_img = preprocess_img(img, INPUT_W, INPUT_H); // resize 图像并转换通道
          int i = 0;
          for (int row = 0; row < INPUT_H; ++row)
          {
              uchar* uc_pixel = pr_img.data + row * pr_img.step;
              for (int col = 0; col < INPUT_W; ++col)
              {
                  data[i] = (float)uc_pixel[2] / 255.0;
                  data[i + INPUT_H * INPUT_W] = (float)uc_pixel[1] / 255.0;
                  data[i + 2 * INPUT_H * INPUT_W] = (float)uc_pixel[0] / 255.0;
                  uc_pixel += 3;
                  ++i;
              }
          }
          // ----------------------------------------------------前向推理-----------------------------------------------------
          doInference(*context, stream, buffers, data, prob, BATCH_SIZE);
          // ----------------------------------------------------非极大值抑制-------------------------------------------------
          std::vector<std::vector<Yolo::Detection>> batch_res(1);
          auto& res = batch_res[0];
          nms(res, &prob[0], conf_thresh, NMS_THRESH);
          // ----------------------------------------------------输出目标坐标及置信度-------------------------------------------------
          for (size_t j = 0; j < res.size(); j++)
          {
              cv::Rect r = get_rect(img, res[j].bbox);
              std::vector<int> temp = { r.tl().x, r.tl().y, r.br().x, r.br().y, (int)res[j].class_id ,int(100*res[j].conf) };
              result_B.push_back(temp);
          }
      }
    •  liberación_búfer():
      void True_interface::release_buffer()
      {
          // 释放资源
          cudaStreamDestroy(stream);
          CUDA_CHECK(cudaFree(buffers[inputIndex]));
          CUDA_CHECK(cudaFree(buffers[outputIndex]));
          // 销毁变量
          context->destroy();
          engine->destroy();
          runtime->destroy();
          std::cout << "Memory freed successfully!" << std::endl;
      }
  •  Luego, solo necesitamos inicializar dos clases y llamar al método load_model() respectivamente para cargar dos modelos en una GPU.

Si desea cargar el modelo en diferentes GPU, solo necesita pasar la ID de la GPU diferente como el primer parámetro al llamar al método load_model() . ¿Es conveniente y simple.

Supongo que te gusta

Origin blog.csdn.net/qq_42308217/article/details/123462056
Recomendado
Clasificación