Inférence thread-safe avec les modèles YOLO

Exécuter des modèles YOLO dans un environnement multi-threadé demande une attention particulière pour garantir la sécurité des threads. Le module threading de Python te permet d'exécuter plusieurs threads simultanément, mais il existe des problèmes de sécurité importants à connaître concernant l'utilisation des modèles YOLO à travers ces threads. Cette page te guidera pour créer une inférence de modèle YOLO thread-safe.



Watch: How to Perform Thread Safe Inference with Ultralytics YOLO Models in Python | Multi-Threading 🚀

Comprendre le threading Python

Les threads Python sont une forme de parallélisme qui permet à ton programme d'exécuter plusieurs opérations à la fois. Cependant, le Global Interpreter Lock (GIL) de Python signifie qu'un seul thread peut exécuter du bytecode Python à la fois.

Single-thread vs multi-thread inference

Bien que cela semble être une limitation, les threads peuvent toujours offrir de la concurrence, surtout pour les opérations liées aux I/O ou lors de l'utilisation d'opérations qui libèrent le GIL, comme celles effectuées par les bibliothèques C sous-jacentes de YOLO.

Le danger des instances de modèle partagées

Instancier un modèle YOLO en dehors de tes threads et partager cette instance entre plusieurs threads peut mener à des race conditions, où l'état interne du modèle est modifié de manière incohérente en raison d'accès concurrents. C'est particulièrement problématique quand le modèle ou ses composants possèdent un état qui n'est pas conçu pour être thread-safe.

Exemple non thread-safe : instance de modèle unique

Lorsque tu utilises des threads en Python, il est important de reconnaître les modèles qui peuvent mener à des problèmes de concurrence. Voici ce que tu dois éviter : partager une instance unique de modèle YOLO entre plusieurs threads.

# Unsafe: Sharing a single model instance across threads
from threading import Thread

from ultralytics import YOLO

# Instantiate the model outside the thread
shared_model = YOLO("yolo26n.pt")

def predict(image_path):
    """Predicts objects in an image using a preloaded YOLO model, take path string to image as argument."""
    results = shared_model.predict(image_path)
    # Process results

# Starting threads that share the same model instance
Thread(target=predict, args=("image1.jpg",)).start()
Thread(target=predict, args=("image2.jpg",)).start()

Dans l'exemple ci-dessus, shared_model est utilisé par plusieurs threads, ce qui peut mener à des résultats imprévisibles car predict pourrait être exécuté simultanément par plusieurs threads.

Exemple non thread-safe : instances de modèle multiples

De même, voici un modèle dangereux avec plusieurs instances de modèle YOLO :

# Unsafe: Sharing multiple model instances across threads can still lead to issues
from threading import Thread

from ultralytics import YOLO

# Instantiate multiple models outside the thread
shared_model_1 = YOLO("yolo26n_1.pt")
shared_model_2 = YOLO("yolo26n_2.pt")

def predict(model, image_path):
    """Runs prediction on an image using a specified YOLO model, returning the results."""
    results = model.predict(image_path)
    # Process results

# Starting threads with individual model instances
Thread(target=predict, args=(shared_model_1, "image1.jpg")).start()
Thread(target=predict, args=(shared_model_2, "image2.jpg")).start()

Même s'il y a deux instances de modèle séparées, le risque de problèmes de concurrence existe toujours. Si l'implémentation interne de YOLO n'est pas thread-safe, utiliser des instances séparées pourrait ne pas empêcher les race conditions, surtout si ces instances partagent des ressources ou des états sous-jacents qui ne sont pas locaux au thread.

Inférence thread-safe

Pour effectuer une inférence thread-safe, tu dois instancier un modèle YOLO séparé au sein de chaque thread. Cela garantit que chaque thread possède sa propre instance de modèle isolée, éliminant le risque de race conditions.

Exemple thread-safe

Voici comment instancier un modèle YOLO au sein de chaque thread pour une inférence parallèle sécurisée :

# Safe: Instantiating a single model inside each thread
from threading import Thread

from ultralytics import YOLO

def thread_safe_predict(image_path):
    """Predict on an image using a new YOLO model instance in a thread-safe manner; takes image path as input."""
    local_model = YOLO("yolo26n.pt")
    results = local_model.predict(image_path)
    # Process results

# Starting threads that each have their own model instance
Thread(target=thread_safe_predict, args=("image1.jpg",)).start()
Thread(target=thread_safe_predict, args=("image2.jpg",)).start()

Dans cet exemple, chaque thread crée sa propre instance YOLO. Cela empêche tout thread d'interférer avec l'état du modèle d'un autre, garantissant ainsi que chaque thread effectue l'inférence en toute sécurité et sans interactions imprévues avec les autres threads.

Utiliser le décorateur ThreadingLocked

Ultralytics fournit un décorateur ThreadingLocked qui peut être utilisé pour garantir l'exécution thread-safe de fonctions. Ce décorateur utilise un verrou pour s'assurer qu'un seul thread à la fois peut exécuter la fonction décorée.

from ultralytics import YOLO
from ultralytics.utils import ThreadingLocked

# Create a model instance
model = YOLO("yolo26n.pt")

# Decorate the predict method to make it thread-safe
@ThreadingLocked()
def thread_safe_predict(image_path):
    """Thread-safe prediction using a shared model instance."""
    results = model.predict(image_path)
    return results

# Now you can safely call this function from multiple threads

Le décorateur ThreadingLocked est particulièrement utile quand tu dois partager une instance de modèle entre des threads mais que tu veux garantir qu'un seul thread puisse y accéder à la fois. Cette approche peut économiser de la mémoire par rapport à la création d'une nouvelle instance de modèle pour chaque thread, mais elle peut réduire la concurrence car les threads devront attendre que le verrou soit libéré.

Conclusion

Lorsque tu utilises des modèles YOLO avec le threading de Python, instancie toujours tes modèles au sein du thread qui va les utiliser pour garantir la sécurité des threads. Cette pratique évite les race conditions et assure que tes tâches d'inférence s'exécutent de manière fiable.

Pour des scénarios plus avancés et pour optimiser davantage tes performances d'inférence multi-threadée, envisage d'utiliser le parallélisme basé sur les processus avec multiprocessing ou d'utiliser une file d'attente de tâches avec des processus de travail dédiés.

FAQ

Comment puis-je éviter les race conditions lors de l'utilisation de modèles YOLO dans un environnement Python multi-threadé ?

Pour éviter les race conditions lors de l'utilisation des modèles YOLO d'Ultralytics dans un environnement Python multi-threadé, instancie un modèle YOLO séparé au sein de chaque thread. Cela garantit que chaque thread dispose de sa propre instance de modèle isolée, évitant ainsi la modification simultanée de l'état du modèle.

Exemple :

from threading import Thread

from ultralytics import YOLO

def thread_safe_predict(image_path):
    """Predict on an image in a thread-safe manner."""
    local_model = YOLO("yolo26n.pt")
    results = local_model.predict(image_path)
    # Process results

Thread(target=thread_safe_predict, args=("image1.jpg",)).start()
Thread(target=thread_safe_predict, args=("image2.jpg",)).start()

Pour plus d'informations sur la garantie de la sécurité des threads, consulte la section Inférence thread-safe avec les modèles YOLO.

Quelles sont les meilleures pratiques pour exécuter une inférence de modèle YOLO multi-threadée en Python ?

Pour exécuter une inférence de modèle YOLO multi-threadée en toute sécurité en Python, suis ces meilleures pratiques :

  1. Instancie les modèles YOLO au sein de chaque thread au lieu de partager une instance unique entre les threads.
  2. Utilise le module multiprocessing de Python pour le traitement parallèle afin d'éviter les problèmes liés au Global Interpreter Lock (GIL).
  3. Libère le GIL en utilisant des opérations effectuées par les bibliothèques C sous-jacentes de YOLO.
  4. Envisage d'utiliser le décorateur ThreadingLocked pour les instances de modèle partagées lorsque la mémoire est une préoccupation.

Exemple pour une instanciation de modèle thread-safe :

from threading import Thread

from ultralytics import YOLO

def thread_safe_predict(image_path):
    """Runs inference in a thread-safe manner with a new YOLO model instance."""
    model = YOLO("yolo26n.pt")
    results = model.predict(image_path)
    # Process results

# Initiate multiple threads
Thread(target=thread_safe_predict, args=("image1.jpg",)).start()
Thread(target=thread_safe_predict, args=("image2.jpg",)).start()

Pour plus de contexte, réfère-toi à la section sur l'Inférence thread-safe.

Pourquoi chaque thread devrait-il avoir sa propre instance de modèle YOLO ?

Chaque thread doit avoir sa propre instance de modèle YOLO pour éviter les race conditions. Lorsqu'une instance de modèle unique est partagée entre plusieurs threads, des accès simultanés peuvent mener à un comportement imprévisible et à des modifications de l'état interne du modèle. En utilisant des instances séparées, tu garantis l'isolation des threads, rendant tes tâches multi-threadées fiables et sûres.

Pour des conseils détaillés, consulte les sections Exemple non thread-safe : instance de modèle unique et Exemple thread-safe.

Comment le Global Interpreter Lock (GIL) de Python affecte-t-il l'inférence des modèles YOLO ?

Le Global Interpreter Lock (GIL) de Python permet à un seul thread d'exécuter du bytecode Python à la fois, ce qui peut limiter les performances des tâches multi-threadées limitées par le CPU. Cependant, pour les opérations liées aux I/O ou les processus utilisant des bibliothèques qui libèrent le GIL, comme les bibliothèques C sous-jacentes de YOLO, tu peux toujours obtenir de la concurrence. Pour des performances améliorées, envisage d'utiliser le parallélisme basé sur les processus avec le module multiprocessing de Python.

Pour en savoir plus sur le threading en Python, consulte la section Comprendre le threading Python.

Est-il plus sûr d'utiliser un parallélisme basé sur les processus plutôt que le threading pour l'inférence des modèles YOLO ?

Oui, utiliser le module multiprocessing de Python est plus sûr et souvent plus efficace pour exécuter l'inférence des modèles YOLO en parallèle. Le parallélisme basé sur les processus crée des espaces mémoire séparés, évitant le Global Interpreter Lock (GIL) et réduisant le risque de problèmes de concurrence. Chaque processus opérera indépendamment avec sa propre instance de modèle YOLO.

Pour plus de détails sur le parallélisme basé sur les processus avec les modèles YOLO, réfère-toi à la page sur l'Inférence thread-safe.

Commentaires