Rendimiento de inferencia
Cinco controles en tiempo de predicción cambian el throughput o la precisión: la reproducción de grafos CUDA, la precisión numérica, el batching, el tiling y el aumento en test. Cada uno se aplica a un conjunto concreto de familias, y dos de ellos cuestan precisión o latencia en lugar de ahorrarla.
Los controles y sus valores por defecto
Todos ellos son argumentos de predict, y todos vienen desactivados por
defecto.
| Argumento | Por defecto | Efecto |
|---|---|---|
batch | 1 | Imágenes por forward pass, para fuentes de tipo carpeta y lista |
cuda_graph | False | Reproduce el forward desde un grafo CUDA capturado |
tiling | False | Divide una imagen grande en tiles solapados |
overlap_ratio | 0.2 | Solape entre tiles cuando tiling está activo |
augment | False | Ejecuta vistas volteadas y las fusiona |
half | Se acepta, se avisa y se ignora | |
device | None | Mueve el modelo antes de predecir |
imgsz también afecta al coste, porque fija la resolución a la que se ejecuta
el modelo, pero es ante todo un argumento de precisión y va con el modelo más
que aquí.
Batching
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") # Un forward apilado por bloque de 4 en las familias que lo soportan.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))libreyolo predict model=LibreYOLO9s.pt source=batch_demo batch=4batch se aplica a fuentes de tipo carpeta y lista. Con batch=1, cada imagen
ejecuta su propio forward pass. Por encima de 1, cada bloque se preprocesa, se
apila en un único tensor, se ejecuta de una vez y luego se vuelve a trocear, de
modo que el postproceso de imagen única que ya tiene cada familia recibe lo que
espera.
La vía apilada se toma solo cuando se cumple todo esto:
batches mayor que1tilingestá desactivado- el aumento en test no está activo
- la familia declara
SUPPORTS_BATCHED_PREDICT - la red subyacente no está en modo entrenamiento
La última condición no es un tecnicismo. Una red en modo entrenamiento normalizaría el bloque apilado con estadísticas de batch cruzadas entre imágenes, dejando que las imágenes de un mismo bloque se cambiaran unas a otras las predicciones, así que esas ejecuciones se mantienen secuenciales.
SUPPORTS_BATCHED_PREDICT vale true por defecto. Estas familias se descuelgan y
ejecutan una imagen por forward pass sea cual sea el valor de 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, todos los detectores de vocabulario abierto y todos
los modelos de visión y lenguaje.
Hay un fallback más. Si el preprocesado no devuelve tensores (1, C, H, W)
uniformes, con la misma forma, dtype y dispositivo en todo el bloque, el bloque
se ejecuta de forma secuencial en lugar de apilarse, así que la corrección nunca
depende de que las imágenes resulten tener el mismo tamaño.
Combina batch con stream=True en una carpeta grande para obtener forward
passes por batch sin mantener todos los resultados en memoria.
Grafos CUDA
from libreyolo import LibreYOLO, SAMPLE_IMAGE model = LibreYOLO("LibreYOLO9s.pt", device="cuda") # Paga el warmup y la captura una sola vez, fuera de la primera petición.model.capture_graph() result = model(SAMPLE_IMAGE, cuda_graph=True)print(len(result.boxes))print(model.graph_info())from libreyolo import LibreYOLO, SAMPLE_IMAGE model = LibreYOLO("LibreYOLO9s.pt", device="cuda") # "auto" espera a ver una forma dos veces, así que el trabajo de una# sola vez nunca paga la captura.for _ in range(3): model(SAMPLE_IMAGE, cuda_graph="auto") print(model.graph_info())model.release_graphs()Un grafo CUDA graba un forward pass una vez y lo reproduce como un único lanzamiento. Los detectores pequeños dedican buena parte del tiempo de batch 1 a lanzar kernels, así que colapsar esos lanzamientos es una ganancia de throughput, y la salida de la reproducción es idéntica bit a bit a la ejecución eager.
cuda_graph admite tres valores. False es el valor por defecto y no hace
nada. True captura en el primer uso para cada forma de entrada. "auto"
espera a que una forma se repita antes de capturar, así que el trabajo de una
sola vez o con formas cambiantes nunca paga el coste de la captura.
capture_graph(imgsz=None, batch=1, dtype=None) saca ese coste de la primera
petición. Un grafo solo es válido para la forma exacta con la que se capturó,
así que aquí batch tiene que coincidir con cómo se llame a predict después.
graph_info() informa de los grafos capturados, del número de reproducciones y
de cualquier motivo por el que la ejecución cayó a eager. release_graphs() los
libera junto con sus buffers estáticos.
La captura requiere CUDA y una familia que se haya adherido mediante
SUPPORTS_CUDA_GRAPH, porque necesita un forward sin trabajo visible desde el
host y eso se verifica familia por familia. Pedirlo en una familia que no se ha
adherido lanza NotImplementedError en lugar de ejecutar en eager en silencio.
Un grafo graba direcciones de memoria, no valores, así que cualquier cosa que
reubique los parámetros lo tira. Cambiar de dispositivo con
predict(device=...), cuantizar y descuantizar invalidan los grafos capturados.
La matriz completa de soporte por familia, las divisiones por costuras y el contrato de numérica están en Grafos CUDA.
Precisión
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))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))from libreyolo import LibreYOLO, SAMPLE_IMAGE model = LibreYOLO("LibreYOLO9s.pt", device="cuda") # Una receta de cast no lee datos de calibración.model.quantize(recipe="fp16", calib=None) result = model(SAMPLE_IMAGE)print(len(result.boxes))half=True en tiempo de predicción no hace nada. Se acepta por compatibilidad
con la línea de comandos, lanza una advertencia diciendo que es un no-op y se
descarta antes de llegar a ninguna familia. El flag --half de la CLI imprime
la misma advertencia para un modelo .pt.
Hay dos vías reales para bajar la precisión.
Para un artefacto exportado, la precisión se elige en el momento de exportar con
export(format=..., half=True), y el archivo resultante se vuelve a cargar con
LibreYOLO() sin cambios.
Para la ejecución en PyTorch, model.quantize(recipe="fp16") castea el modelo a
float16 e instala hooks que mantienen float32 en las entradas y las salidas del
modelo. "bf16" hace lo mismo con bfloat16. Ninguno de los dos casts lee datos
de calibración, así que calib se ignora para ellos. La cuantización cubre hoy
cuatro familias: YOLOv9, RF-DETR, BiRefNet y FeyNobg. Un cast en un dispositivo
CPU registra una advertencia de que será lento, así que estas recetas están
pensadas para GPU.
Las dos vías cambian la numérica. Ninguna es garantía de obtener exactamente las mismas detecciones, así que valida antes de desplegar.
Inferencia por tiles
from PIL import Image from libreyolo import LibreYOLO, SAMPLE_IMAGE # El tiling solo se activa si la imagen es mayor que el tamaño de entrada.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")El tiling recorta una imagen grande en tiles cuadrados solapados, predice sobre cada uno y fusiona los resultados. Es la opción para objetos pequeños en imágenes de alta resolución, donde redimensionar la imagen entera encoge los objetivos por debajo de lo que el modelo puede resolver.
El tamaño de tile es el tamaño de entrada del modelo, o imgsz cuando se
indica, y tiene que ser cuadrado. overlap_ratio vale 0.2 por defecto. Los
tiles que se solapan se reconcilian con non-maximum suppression por clase al
umbral iou, y la lista fusionada se trunca después a max_det. Esto significa
que iou afecta a las predicciones con tiling incluso en familias que no
ejecutan NMS propia.
El tiling se omite, no es que salga barato, cuando la imagen ya cabe: si ambas
dimensiones están en el tamaño de entrada o por debajo, se ejecuta un forward
pass normal en su lugar. También se omite para clasificación, segmentación
semántica y la tarea embed, que caen a una única pasada porque ahí el tiling
no significa nada.
Lanza excepción para las tareas cuyo payload no se puede recomponer: máscaras de
segmentación de instancias, boxes orientados, puntos, profundidad, bordes y
normales. No se puede combinar con augment.
El resultado lleva result.tiled y result.num_tiles. Con save=True, las
ejecuciones con tiling escriben un directorio bajo runs/tiled_detections con
todos los tiles, la imagen anotada, una visualización en cuadrícula y un
metadata.json que registra el tamaño de tile, el solape y los umbrales, con
result.tiles_path y result.grid_path apuntando a ellos.
Aumento en test
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 ejecuta la imagen más de una vez y fusiona las detecciones con
non-maximum suppression por clase al umbral iou. Igual que el tiling, esto
hace que iou sea determinante para familias que en otro caso lo ignoran.
En la práctica esto es volteo horizontal. La lista de escalas TTA_SCALES vale
por defecto una única escala de 1.0 y ninguna familia incluida la sobrescribe,
así que todas las familias ejecutan dos pasadas: la imagen original y su
reflejo. Las familias marcadas con TTA_FIXED_SIZE redimensionan a un cuadrado
fijo, lo que de todos modos convierte el multiescala en un no-op para ellas.
La segmentación semántica y la panóptica hacen otra fusión. Su vista volteada se vuelve a voltear y las dos distribuciones softmax se promedian antes del argmax, en lugar de fusionarse como boxes.
El aumento en test no está disponible para todas las tareas. Lanza excepción para boxes orientados, pose, puntos, profundidad, normales, bordes, restauración, OCR y modelos de embeddings, y no se puede combinar con el tiling.
Estas familias lo desactivan por completo, así que augment=True ejecuta una
única pasada normal: BiRefNet, CenterNet, CLIP, DexiNed, FOMO, HRNet, L2CS-Net,
LibreMODUS, NAFNet, PP-OCRv5, Real-ESRGAN, RetinaNet, SAM 3D Body, SigLIP2,
SwinIR, TEED, todas las variantes de SAM, todos los detectores de vocabulario
abierto y todos los modelos de visión y lenguaje.
Medir
Nada en esta página lleva un número de latencia, porque un milisegundo sin su
hardware, su runtime, su precisión y su tamaño de batch no es un dato. Las
cifras medidas en distintos hardware y runtimes se publican en
visionanalysis.org, y libreyolo profile mide
un modelo concreto en la máquina que tienes delante.