Business
Jobs
  • About Us
  • Solutions
    • Job Postings
      Post your job and receive qualified candidates in 48h.
    • Candidate Assessments
      500+ technical and psychological tests, plus anti-fraud.
    • Headhunting
      Tailor-made executive search from start to finish.
    • Payroll + EOR
      Payroll dispersal and EOR across 15+ LATAM countries.
  • Pricing
  • Jobs

0

249
Views
El subprocesamiento múltiple degrada el rendimiento de la GPU

En mi aplicación de Python, estoy usando Detectron2 para ejecutar la predicción en una imagen y detectar los puntos clave de todos los humanos en la imagen.

Quiero ejecutar la predicción en fotogramas que se transmiten a mi aplicación en vivo (usando aiortc), pero descubrí que el tiempo de predicción es mucho peor porque ahora se ejecuta en un nuevo subproceso (el subproceso principal está ocupado con el servidor).

Ejecutar predicciones en un hilo lleva entre 1,5 y 4 segundos , lo cual es mucho.

Cuando ejecuto las predicciones en el hilo principal (sin la parte de transmisión de video), obtengo tiempos de predicción de menos de un segundo .

Mi pregunta es ¿por qué sucede y cómo puedo solucionarlo? ¿Por qué el rendimiento de la GPU se degrada tan drásticamente cuando se usa desde un hilo nuevo?

Notas:

  1. El código se prueba en Google Colab con GPU Tesla P100 y la transmisión de video se emula leyendo fotogramas de un archivo de video.

  2. Calculo el tiempo que lleva ejecutar la predicción en un marco usando el código en esta pregunta .

Intenté cambiar a multiprocesamiento en su lugar, pero no pude hacerlo funcionar con cuda (intenté tanto import multiprocessing como import torch.multiprocessing con set_stratup_method('spawn') ) simplemente se atasca al llamar al start del proceso.

Código de ejemplo:

 from detectron2 import model_zoo from detectron2.engine import DefaultPredictor from detectron2.config import get_cfg import threading from typing import List import numpy as np import timeit import cv2 # Prepare the configuration file cfg = get_cfg() cfg.merge_from_file(model_zoo.get_config_file("COCO-Keypoints/keypoint_rcnn_R_50_FPN_3x.yaml")) cfg.MODEL.ROI_HEADS.SCORE_THRESH_TEST = 0.7 # set threshold for this model cfg.MODEL.WEIGHTS = model_zoo.get_checkpoint_url("COCO-Keypoints/keypoint_rcnn_R_50_FPN_3x.yaml") cfg.MODEL.DEVICE = "cuda" predictor = DefaultPredictor(cfg) def get_frames(video: cv2.VideoCapture): frames = list() while True: has_frame, frame = video.read() if not has_frame: break frames.append(frame) return frames class CodeTimer: # Source: https://stackoverflow.com/a/52749808/9977758 def __init__(self, name=None): self.name = " '" + name + "'" if name else '' def __enter__(self): self.start = timeit.default_timer() def __exit__(self, exc_type, exc_value, traceback): self.took = (timeit.default_timer() - self.start) * 1000.0 print('Code block' + self.name + ' took: ' + str(self.took) + ' ms') video = cv2.VideoCapture('DemoVideo.mp4') num_frames = round(video.get(cv2.CAP_PROP_FRAME_COUNT)) frames_buffer = list() predictions = list() def send_frames(): # This function emulates the stream, so here we "get" a frame and add it to our buffer for frame in get_frames(video): frames_buffer.append(frame) # Simulate delays between frames time.sleep(random.uniform(0.3, 2.1)) def predict_frames(): predicted_frames = 0 # The number of frames predicted so far while predicted_frames < num_frames: # Stop after we predicted all frames buffer_length = len(frames_buffer) if buffer_length <= predicted_frames: continue # Wait until we get a new frame # Read all the frames from the point we stopped for frame in frames_buffer[predicted_frames:]: # Measure the prediction time with CodeTimer('In stream prediction'): predictions.append(predictor(frame)) predicted_frames += 1 t1 = threading.Thread(target=send_frames) t1.start() t2 = threading.Thread(target=predict_frames) t2.start() t1.join() t2.join()
over 4 years ago · Santiago Trujillo
4 answers
Answer question

0

Al no ver el código completo, aquí hay algunas sugerencias:

  • Es posible que se encuentre con la sobrecarga de iniciar nuevos hilos cada vez. Por lo tanto, explore la opción del grupo de subprocesos en lugar de iniciar nuevos subprocesos cada vez.
  • Si no está moviendo la carga de trabajo a la GPU, eso significa que es una tarea vinculada a la CPU y los subprocesos de Python no son la herramienta adecuada para la tarea. Para tareas intensivas de CPU, debe usar https://docs.python.org/3/library/multiprocessing.html#module-multiprocessing
over 4 years ago · Santiago Trujillo Report

0

Los subprocesos de Python se basan en el GIL , que debe estar bloqueado por todos los enlaces de C que intentan acceder a los objetos de Python. Las bibliotecas informáticas de la GPU suelen utilizar enlaces C y podrían bloquear la GIL de vez en cuando y, por lo tanto, pausar la ejecución del código de Python.

Es una suposición descabellada, pero es posible que la función predictora, que necesita pasar por C y un bloqueo de GIL, se encuentre esperando a los otros subprocesos que están escribiendo los búferes de video. Luego, dependiendo de cómo se desglose el cálculo y cómo Python haga malabarismos con su otro hilo, supongo que el impacto en el rendimiento puede ser visible.

Puedes:

  • evite los subprocesos múltiples realizando la lectura y la predicción en el mismo subproceso.
  • usar multiprocesamiento para que el GIL no interfiera entre los dos procesos
  • codifique esto en un lenguaje nativo como C, C++...
over 4 years ago · Santiago Trujillo Report

0

Algunas operaciones están vinculadas a E/S. Por ejemplo, cada llamada cv2.imread da como resultado una sobrecarga de E/S. Puede leer este artículo que dice: "No todos los algoritmos se pueden hacer paralelos y distribuir a todos los núcleos de un procesador; algunos algoritmos son simplemente de un solo subproceso por naturaleza".

Esto significa que el multiprocesamiento para los algoritmos de visión por computadora debe ser global: una sola operación (como imread) no mejorará con el multiproceso. Sin embargo, a veces ganará velocidad al realizar otras operaciones en paralelo porque no están limitadas por E/S ni nada más. En este punto, probablemente verá una aceleración general:

Si ejecuta imread único:

  • no multiproceso: 5 ms = costo de imread
  • multiproceso: 7 ms = costo de multiproceso + costo de imread

Pero si ejecuta operaciones que pueden ser multiproceso:

  • no multiproceso: 5 ms + 10 ms = costo de imread + costo de operación
  • subprocesos múltiples: 2 ms + 5 ms + 5 ms = costo de subprocesos múltiples + costo de imread + costo de operaciones paralelas

(Estas cifras no son reales, son solo para ilustrar lo que quiero decir)

over 4 years ago · Santiago Trujillo Report

0

El problema está en: tu hardware, tus bibliotecas o, en las diferencias entre tu código de ejemplo y el código real.

Implementé tu código en una Nvidia Jetson Xavier. Instalé todas las bibliotecas necesarias usando los siguientes comandos:

 # first create your virtual env virtualenv -p python3 detectron_gpu source detectron_gpu/bin/activate #torch for jetson wget https://nvidia.box.com/shared/static/p57jwntv436lfrd78inwl7iml6p13fzh.whl -O torch-1.8.0-cp36-cp36m-linux_aarch64.whl sudo apt-get install python3-pip libopenblas-base libopenmpi-dev pip3 install Cython pip3 install numpy torch-1.8.0-cp36-cp36m-linux_aarch64.whl # torchvision pip install 'git+https://github.com/pytorch/vision.git@v0.9.0' # detectron python -m pip install 'git+https://github.com/facebookresearch/detectron2.git' # ipython bindings (optional) pip install ipykernel cloudpickle # opencv pip install opencv-python

Después de eso, ejecuté su script de ejemplo en un video de ejemplo y recibí el siguiente resultado:

 Code block 'In stream prediction' took: 2932.241764000537 ms Code block 'In stream prediction' took: 409.69691300051636 ms Code block 'In stream prediction' took: 410.03823099981673 ms Code block 'In stream prediction' took: 409.4023269999525 ms

Después de la primera pasada, el detector tarda constantemente alrededor de 400 ms en ejecutar la detección. Lo que parece correcto para un Supersónico Xavier. No experimento la desaceleración que describiste.

Debo señalar que el Supersónico es una pieza específica de hardware. En este hardware la memoria RAM se comparte entre la CPU y la GPU. Por lo tanto, no tengo que transferir los datos de la CPU a la GPU. Entonces, si su ralentización se debe a la transferencia entre la memoria de la CPU y la GPU, no experimentaré este problema en mi configuración.

over 4 years ago · Santiago Trujillo Report
Answer question
Find remote jobs

Discover the new way to find a job!

Top jobs
Top job categories
Business
Post vacancy Pricing Sales
Legal
Terms and conditions Privacy policy
© 2026 PeakU Inc. All Rights Reserved.
Andres GPT
Show me some job opportunities
There's an error!