Посмотреть как Markdown

Производительность инференса

На этапе предсказания есть пять настроек, которые меняют пропускную способность или точность: воспроизведение CUDA-графа, точность вычислений, батчинг, тайлинг и аугментация во время инференса. Каждая применима к своему набору семейств, а две из них не экономят время, а наоборот — обходятся потерей точности или ростом задержки.

Настройки и их значения по умолчанию

Каждая из них — аргумент predict, и по умолчанию все выключены.

АргументПо умолчаниюЧто делает
batch1Изображений за один прямой проход, для источников-папок и списков
cuda_graphFalseВоспроизводит прямой проход из захваченного CUDA-графа
tilingFalseРежет большое изображение на перекрывающиеся тайлы
overlap_ratio0.2Перекрытие тайлов, когда включён tiling
augmentFalseПрогоняет отражённые варианты и объединяет их
halfПринимается, вызывает предупреждение и игнорируется
deviceNoneПереносит модель перед предсказанием

imgsz тоже влияет на стоимость, потому что задаёт разрешение, на котором работает модель, но это в первую очередь аргумент точности, и его место — рядом с моделью, а не здесь.

Батчинг

Батчевый инференс по папке
from pathlib import Pathfrom PIL import Image from libreyolo import LibreYOLO, SAMPLE_IMAGE folder = Path("batch_demo")folder.mkdir(exist_ok=True)image = Image.open(SAMPLE_IMAGE)for index in range(8):    image.save(folder / f"frame_{index}.jpg") model = LibreYOLO("LibreYOLO9s.pt") # Один общий прямой проход на каждый блок из 4 — на семействах, которые это поддерживают.results = model(str(folder), batch=4)print(len(results), "results")
Стриминг, чтобы список не собирался в памяти
from libreyolo import LibreYOLO model = LibreYOLO("LibreYOLO9s.pt") for result in model("batch_demo", batch=4, stream=True):    print(len(result.boxes))
CLI
libreyolo predict model=LibreYOLO9s.pt source=batch_demo batch=4

batch действует на источники-папки и списки. При batch=1 на каждое изображение приходится отдельный прямой проход. При значениях больше 1 каждый блок проходит предобработку, склеивается в один тензор, прогоняется один раз, а затем нарезается обратно, чтобы существующая постобработка одного изображения в каждом семействе увидела то, что ожидает.

Путь со склейкой в один тензор выбирается, только когда выполняется всё перечисленное:

  • batch больше 1
  • tiling выключен
  • аугментация во время инференса не включена
  • семейство выставляет SUPPORTS_BATCHED_PREDICT
  • сама сеть не находится в режиме обучения

Последнее условие — не формальность. Сеть в режиме обучения нормализовала бы склеенный блок по общей для нескольких изображений батч-статистике, из-за чего изображения в одном блоке меняли бы предсказания друг друга, поэтому такие прогоны остаются последовательными.

SUPPORTS_BATCHED_PREDICT по умолчанию равен true. Эти семейства отказываются от батчинга и прогоняют по одному изображению за проход независимо от batch: Depth Anything V2, Depth Anything 3, EoMT, Faster R-CNN, FCOS, HRNet, L2CS-Net, LibreMODUS, MiDaS, MoGe-2, PP-OCRv5, Real-ESRGAN, RetinaNet, SAM 3D Body, SwinIR, YOLOv1, ZipDepth, все детекторы с открытым словарём и все vision-language-модели.

Есть ещё один запасной путь. Если предобработка не возвращает однородные тензоры (1, C, H, W) с совпадающими формой, dtype и устройством по всему блоку, блок выполняется последовательно, а не склеивается, так что корректность никогда не зависит от того, оказались ли изображения одного размера.

Сочетайте batch с stream=True на большой папке, чтобы получить батчевые прямые проходы, не держа каждый результат в памяти.

CUDA-графы

Захват заранее, затем воспроизведение (нужна CUDA)
from libreyolo import LibreYOLO, SAMPLE_IMAGE model = LibreYOLO("LibreYOLO9s.pt", device="cuda") # Прогрев и захват оплачиваются один раз, вне первого запроса.model.capture_graph() result = model(SAMPLE_IMAGE, cuda_graph=True)print(len(result.boxes))print(model.graph_info())
Захват только после того, как форма повторится (нужна CUDA)
from libreyolo import LibreYOLO, SAMPLE_IMAGE model = LibreYOLO("LibreYOLO9s.pt", device="cuda") # "auto" ждёт, пока форма встретится дважды, поэтому разовая# работа никогда не платит за захват.for _ in range(3):    model(SAMPLE_IMAGE, cuda_graph="auto") print(model.graph_info())model.release_graphs()

CUDA-граф один раз записывает прямой проход и воспроизводит его как один запуск. Небольшие детекторы при батче 1 тратят значительную долю времени на запуск ядер, поэтому схлопывание этих запусков даёт выигрыш в пропускной способности, а вывод при воспроизведении побитово совпадает с eager-выполнением.

cuda_graph принимает три значения. False — значение по умолчанию, оно ничего не делает. True захватывает граф при первом использовании для каждой входной формы. "auto" ждёт повтора формы и только потом захватывает, поэтому за захват не приходится платить ни при разовой работе, ни при работе с меняющимися формами.

capture_graph(imgsz=None, batch=1, dtype=None) выносит эту стоимость за пределы первого запроса. Граф действителен только для той формы, которую захватил, поэтому batch здесь должен совпадать с тем, как позже вызывается predict.

graph_info() показывает захваченные графы, счётчики воспроизведений и любую причину, по которой прогон откатился к eager-выполнению. release_graphs() освобождает их вместе со статическими буферами.

Для захвата нужны CUDA и семейство, которое подключилось через SUPPORTS_CUDA_GRAPH, потому что требуется прямой проход без работы, видимой на стороне хоста, а это проверяется отдельно для каждого семейства. Запрос захвата на неподключённом семействе вызывает NotImplementedError, а не тихо выполняется в eager-режиме.

Граф записывает адреса памяти, а не значения, поэтому всё, что перемещает параметры, сбрасывает его. Смена устройства через predict(device=...), квантизация и деквантизация — всё это делает захваченные графы недействительными.

Полная матрица поддержки по семействам, разбиения по швам и контракт по численным результатам — на странице CUDA-графы.

Точность вычислений

Установка дополнения для экспорта
pip install "libreyolo[onnx]"
Экспорт и обратная загрузка, с точностью по умолчанию
from libreyolo import LibreYOLO, SAMPLE_IMAGE model = LibreYOLO("LibreYOLO9s.pt")path = model.export(format="onnx") exported = LibreYOLO(path)result = exported(SAMPLE_IMAGE)print(len(result.boxes))
Экспорт в FP16 (собирать и запускать на машине с CUDA)
from libreyolo import LibreYOLO, SAMPLE_IMAGE model = LibreYOLO("LibreYOLO9s.pt", device="cuda")path = model.export(format="onnx", half=True) exported = LibreYOLO(path)result = exported(SAMPLE_IMAGE)print(len(result.boxes))
FP16 в PyTorch, через рецепт приведения типов (нужна CUDA)
from libreyolo import LibreYOLO, SAMPLE_IMAGE model = LibreYOLO("LibreYOLO9s.pt", device="cuda") # Рецепт приведения типов не читает калибровочные данные.model.quantize(recipe="fp16", calib=None) result = model(SAMPLE_IMAGE)print(len(result.boxes))

half=True на этапе предсказания ничего не делает. Он принимается ради совместимости с командной строкой, выдаёт предупреждение о том, что ни на что не влияет, и отбрасывается, не доходя ни до одного семейства. Флаг --half в CLI печатает то же предупреждение для модели .pt.

К пониженной точности ведут два настоящих пути.

Для экспортированного артефакта точность выбирается во время экспорта через export(format=..., half=True), и получившийся файл загружается обратно через LibreYOLO() без изменений.

Для выполнения в PyTorch model.quantize(recipe="fp16") приводит модель к float16 и ставит хуки, которые сохраняют float32 на входах и выходах модели. "bf16" делает то же самое с bfloat16. Ни одно из этих приведений не читает калибровочные данные, поэтому calib для них игнорируется. Квантизация сейчас охватывает четыре семейства: YOLOv9, RF-DETR, BiRefNet и FeyNobg. Приведение на устройстве CPU пишет в лог предупреждение о том, что работать будет медленно, так что эти рецепты рассчитаны на GPU.

Оба пути меняют численные результаты. Ни один из них не гарантирует те же самые детекции без изменений, поэтому проверяйте до развёртывания.

Потайловый инференс

Потайловый инференс на большом изображении
from PIL import Image from libreyolo import LibreYOLO, SAMPLE_IMAGE # Тайлинг включается, только когда изображение больше входного размера.large = Image.open(SAMPLE_IMAGE).resize((2048, 1536))large.save("large.jpg") model = LibreYOLO("LibreYOLO9s.pt") result = model("large.jpg", tiling=True, overlap_ratio=0.2)print(result.num_tiles, "tiles", len(result.boxes), "detections")

Тайлинг режет большое изображение на перекрывающиеся квадратные тайлы, делает предсказание на каждом и объединяет результаты. Это вариант для мелких объектов на изображениях высокого разрешения, где сжатие всего кадра уменьшает объекты до размеров, которые модель уже не различает.

Размер тайла — это входной размер модели или imgsz, если он задан, и он должен быть квадратным. overlap_ratio по умолчанию равен 0.2. Пересекающиеся тайлы согласуются поклассовым подавлением немаксимумов по порогу iou, а объединённый список затем обрезается до max_det. Это значит, что iou влияет на потайловые предсказания даже у семейств, которые сами не выполняют NMS.

Когда изображение и так помещается, тайлинг не просто обходится дёшево — он пропускается: если оба измерения не превышают входной размер, вместо него выполняется один обычный прямой проход. Он также пропускается для классификации, семантической сегментации и задачи embed — они откатываются к одному проходу, потому что тайлинг там не имеет смысла.

Он выбрасывает ошибку для задач, результат которых нельзя сшить обратно: маски сегментации экземпляров, повёрнутые рамки, точки, глубина, границы и нормали. Его нельзя сочетать с augment.

У результата появляются result.tiled и result.num_tiles. При save=True потайловые прогоны пишут каталог внутри runs/tiled_detections, где лежат все тайлы, аннотированное изображение, визуализация сетки и metadata.json, в котором записаны размер тайла, перекрытие и пороги, а result.tiles_path и result.grid_path указывают на них.

Аугментация во время инференса

Аугментация во время инференса
from libreyolo import LibreYOLO, SAMPLE_IMAGE model = LibreYOLO("LibreYOLO9s.pt") plain = model(SAMPLE_IMAGE)flipped = model(SAMPLE_IMAGE, augment=True) print(len(plain.boxes), "->", len(flipped.boxes))

augment=True прогоняет изображение больше одного раза и объединяет детекции поклассовым подавлением немаксимумов по порогу iou. Как и тайлинг, это делает iou значимым для семейств, которые иначе его игнорируют.

На практике это горизонтальное отражение. Список масштабов TTA_SCALES по умолчанию содержит один масштаб 1.0, и ни одно поставляемое семейство его не переопределяет, поэтому каждое семейство делает два прохода: исходное изображение и его зеркало. Семейства, помеченные TTA_FIXED_SIZE, приводят вход к фиксированному квадрату, из-за чего многомасштабность для них в любом случае ни на что не влияет.

У семантической и паноптической сегментации объединение другое. Отражённый вариант отражается назад, и два распределения softmax усредняются перед argmax, а не объединяются как рамки.

Аугментация во время инференса доступна не для всех задач. Она выбрасывает ошибку для повёрнутых рамок, оценки позы, точек, глубины, нормалей, границ, восстановления изображений, OCR и моделей эмбеддингов, и её нельзя сочетать с тайлингом.

Эти семейства отключают её полностью, поэтому augment=True выполняет один обычный проход: BiRefNet, CenterNet, CLIP, DexiNed, FOMO, HRNet, L2CS-Net, LibreMODUS, NAFNet, PP-OCRv5, Real-ESRGAN, RetinaNet, SAM 3D Body, SigLIP2, SwinIR, TEED, все варианты SAM, все детекторы с открытым словарём и все vision-language-модели.

Измерения

На этой странице нет ни одного числа задержки, потому что миллисекунда без указания железа, среды выполнения, точности и размера батча — это не факт. Измеренные цифры по разному железу и разным средам выполнения опубликованы на visionanalysis.org, а libreyolo profile измеряет конкретную модель на той машине, что перед вами.

Значения аргументов по умолчанию — из InferenceRunner.__call__ в libreyolo/models/base/inference.py. API CUDA-графов — из BaseModel.capture_graph, graph_info, release_graphs и cuda_graph_scope в libreyolo/models/base/model.py; подключение семейств — из переменной класса SUPPORTS_CUDA_GRAPH. Поведение половинной точности — из NOOP_PREDICT_KWARGS в libreyolo/utils/predict_args.py, предупреждения CLI в libreyolo/cli/commands/predict.py и CAST_RECIPES вместе с SUPPORTED_FAMILIES в libreyolo/quant/api.py. Условия батчинга — из InferenceRunner._process_in_batches и _predict_batch. Тайлинг — из _predict_tiled и _merge_tile_detections. Аугментация во время инференса — из BaseModel._predict_augment и _merge_tta, а TTA_ENABLED, TTA_SCALES и TTA_FIXED_SIZE прочитаны по всему libreyolo/models/.