Потокобезопасный инференс с моделями YOLO#
Чтобы безопасно запускать инференс Ultralytics YOLO в потоках Python, создавай отдельный экземпляр модели YOLO в каждом потоке, а не используй один экземпляр совместно. Совместное использование одного экземпляра модели приводит к состояниям гонки, которые повреждают внутреннее состояние модели и вызывают непредсказуемые результаты, поскольку модуль threading в Python одновременно запускает потоки с одним и тем же объектом. В этом руководстве объясняется, почему совместное использование не работает, приводится безопасный шаблон с отдельным экземпляром для каждого потока и рассматривается декоратор ThreadingLocked для случаев, когда экземпляр необходимо использовать совместно.
Перейди к разделу почему совместное использование модели не работает, потокобезопасному шаблону или декоратору ThreadingLocked.
Смотри: Как выполнять потокобезопасный инференс с моделями Ultralytics YOLO в Python | Многопоточность 🚀
Основы потоков Python#
Потоки Python — это способ параллельного выполнения, позволяющий программе выполнять несколько операций одновременно. Однако глобальная блокировка интерпретатора Python (GIL) означает, что в каждый момент времени только один поток может выполнять байт-код Python.
Хотя это может показаться ограничением, потоки всё же обеспечивают конкурентное выполнение, особенно для операций, ограниченных вводом-выводом, и операций, освобождающих GIL, например тех, которые выполняют базовые библиотеки C, используемые YOLO.
Риски совместного использования экземпляров модели#
Создание модели YOLO вне потоков и совместное использование её экземпляра несколькими потоками может привести к состояниям гонки: внутреннее состояние модели будет изменяться непоследовательно из-за одновременного доступа. Это особенно проблематично, если модель или её компоненты хранят состояние, не рассчитанное на потокобезопасное использование.
Небезопасный пример: один экземпляр модели#
При использовании потоков в Python важно распознавать шаблоны, которые могут привести к проблемам конкурентного выполнения. Вот чего следует избегать: совместного использования одного экземпляра модели YOLO26 несколькими потоками.
# Небезопасно: совместное использование одного экземпляра модели в нескольких потоках
from threading import Thread
from ultralytics import YOLO
# Создание экземпляра модели вне потока
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)
# Обработать результаты
# Запуск потоков, использующих один и тот же экземпляр модели
Thread(target=predict, args=("image1.jpg",)).start()
Thread(target=predict, args=("image2.jpg",)).start()В примере выше объект shared_model используется несколькими потоками, что может привести к непредсказуемым результатам, поскольку predict могут одновременно выполнять несколько потоков.
Безопасный пример: отдельный экземпляр для каждого потока#
Можно использовать несколько отдельных экземпляров модели, если у каждого потока есть собственный экземпляр, которым он не делится с другими потоками. Неважно, что приведенные ниже экземпляры создаются до запуска потоков: небезопасно только совместное использование одного экземпляра несколькими потоками.
# Безопасно: каждый поток использует собственный экземпляр модели
from threading import Thread
from ultralytics import YOLO
# Создание отдельной модели для каждого потока
model_1 = YOLO("yolo26n.pt")
model_2 = YOLO("yolo26n.pt")
def predict(model, image_path):
"""Runs prediction on an image using a specified YOLO model, returning the results."""
results = model.predict(image_path)
# Обработать результаты
# Каждый поток использует отдельный экземпляр модели
Thread(target=predict, args=(model_1, "image1.jpg")).start()
Thread(target=predict, args=(model_2, "image2.jpg")).start()Поскольку каждый поток работает со своим экземпляром, у потоков нет общего состояния модели, которое они могли бы повредить. Создание экземпляра модели внутри каждого потока, как показано далее, — самый простой способ гарантировать, что экземпляр не будет случайно использоваться совместно.
Потокобезопасный инференс#
Чтобы выполнять потокобезопасный инференс, создавай отдельную модель YOLO в каждом потоке. Так у каждого потока будет собственный изолированный экземпляр модели, что исключит риск состояний гонки.
Потокобезопасный пример#
Вот как создать модель YOLO в каждом потоке для безопасного параллельного инференса:
# Безопасно: создание отдельного экземпляра модели в каждом потоке
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)
# Обработать результаты
# Запуск потоков, каждый из которых использует собственный экземпляр модели
Thread(target=thread_safe_predict, args=("image1.jpg",)).start()
Thread(target=thread_safe_predict, args=("image2.jpg",)).start()В этом примере каждый поток создает собственный экземпляр YOLO. Это не позволяет потокам влиять на состояние модели друг друга и гарантирует, что каждый поток выполняет инференс безопасно, без непредвиденного взаимодействия с другими потоками.
Использование декоратора ThreadingLocked#
В Ultralytics есть декоратор ThreadingLocked, который помогает обеспечить потокобезопасное выполнение функций. Декоратор использует блокировку, чтобы функцию с этим декоратором в каждый момент времени мог выполнять только один поток.
from ultralytics import YOLO
from ultralytics.utils import ThreadingLocked
# Создание экземпляра модели
model = YOLO("yolo26n.pt")
# Добавление декоратора к функции предсказания для обеспечения потокобезопасности
@ThreadingLocked()
def thread_safe_predict(image_path):
"""Thread-safe prediction using a shared model instance."""
results = model.predict(image_path)
return results
# Теперь эту функцию можно безопасно вызывать из нескольких потоковДекоратор ThreadingLocked особенно полезен, когда нужно совместно использовать экземпляр модели между потоками, но при этом важно, чтобы к нему одновременно обращался только один поток.
Совместное использование одного экземпляра модели с блокировкой экономит память по сравнению с загрузкой модели в каждом потоке, но снижает конкурентность, поскольку потоки по очереди ждут освобождения блокировки. Если памяти достаточно и нужна максимальная параллельность, используй отдельный экземпляр для каждого потока. Если объем памяти, необходимый модели, становится узким местом, используй ThreadingLocked.
Заключение#
При использовании моделей YOLO с threading в Python выделяй каждому потоку отдельный экземпляр модели и никогда не используй один экземпляр в нескольких потоках. Проще всего гарантировать это, создавая модель внутри потока, который её использует: так ты избежишь состояний гонки и обеспечишь надежное выполнение задач инференса.
Для более сложных сценариев и дальнейшей оптимизации производительности многопоточного инференса используй параллелизм на основе процессов с помощью multiprocessing или очередь задач с отдельными рабочими процессами.
Часто задаваемые вопросы#
Чтобы избежать состояний гонки при использовании моделей Ultralytics YOLO в многопоточной среде Python, создавай отдельную модель YOLO в каждом потоке. Так у каждого потока будет собственный изолированный экземпляр модели, и одновременное изменение состояния модели будет исключено.
Пример:
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) # Обработать результаты Thread(target=thread_safe_predict, args=("image1.jpg",)).start() Thread(target=thread_safe_predict, args=("image2.jpg",)).start()Подробнее о потокобезопасности читай в руководстве по потокобезопасному инференсу с моделями YOLO.
Чтобы безопасно выполнять многопоточный инференс моделей YOLO в Python, придерживайся следующих рекомендаций:
- Создавай модели YOLO в каждом потоке, а не используй один экземпляр совместно несколькими потоками.
- Для параллельной обработки используй модуль
multiprocessingPython, чтобы избежать проблем, связанных с глобальной блокировкой интерпретатора (GIL). - Помни, что базовые библиотеки C, используемые YOLO (PyTorch, OpenCV), автоматически освобождают GIL при интенсивных вычислениях, поэтому потоки всё равно могут выполнять инференс одновременно.
- Если тебя беспокоит расход памяти, используй декоратор
ThreadingLockedдля совместно используемых экземпляров модели.
Пример потокобезопасного создания экземпляра модели:
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.""" local_model = YOLO("yolo26n.pt") results = local_model.predict(image_path) # Обработать результаты # Запуск нескольких потоков Thread(target=thread_safe_predict, args=("image1.jpg",)).start() Thread(target=thread_safe_predict, args=("image2.jpg",)).start()Дополнительные сведения см. в разделе о потокобезопасном инференсе.
У каждого потока должен быть собственный экземпляр модели YOLO, чтобы избежать состояний гонки. Когда один экземпляр модели используется несколькими потоками, одновременный доступ может привести к непредсказуемому поведению и изменению внутреннего состояния модели. Отдельные экземпляры изолируют потоки и делают многопоточные задачи надежными и безопасными.
Подробные инструкции см. в разделах «Небезопасный пример: один экземпляр модели» и «Потокобезопасный пример».
Глобальная блокировка интерпретатора Python (GIL) позволяет только одному потоку за раз выполнять байт-код Python, что может снизить производительность многопоточных задач с высокой нагрузкой на CPU. Однако конкурентное выполнение всё же возможно для операций, ограниченных вводом-выводом, или процессов, использующих библиотеки, которые освобождают GIL, например базовые библиотеки C, используемые YOLO. Для повышения производительности рассмотрите параллелизм на основе процессов с помощью модуля
multiprocessingPython.Подробнее о потоках в Python читай в разделе «Основы потоков Python».
Да, модуль
multiprocessingPython — более безопасный и зачастую эффективный способ параллельного инференса моделей YOLO. Параллелизм на основе процессов создает отдельные области памяти, позволяет не зависеть от глобальной блокировки интерпретатора (GIL) и снижает риск проблем при конкурентном выполнении. Каждый процесс работает независимо со своим экземпляром модели YOLO.Подробнее о параллелизме на основе процессов для моделей YOLO читай на странице «Потокобезопасный инференс».