Meet YOLO26: next-gen vision AI.

Link to this sectionPré-processamento Acelerado por GPU com NVIDIA DALI#

Link to this sectionIntrodução#

Ao implantar modelos Ultralytics YOLO em produção, o pré-processamento frequentemente se torna o gargalo. Embora o TensorRT consiga executar a inferência do modelo em apenas alguns milissegundos, o pré-processamento baseado em CPU (redimensionamento, preenchimento, normalização) pode levar de 2 a 10 ms por imagem, especialmente em altas resoluções. O NVIDIA DALI (Data Loading Library) resolve isso movendo todo o pipeline de pré-processamento para a GPU.

Este guia orienta você na criação de pipelines DALI que replicam exatamente o pré-processamento do Ultralytics YOLO, integrando-os ao model.predict(), processando fluxos de vídeo e implantando de ponta a ponta com o Triton Inference Server.

Para quem é este guia?

Este guia é para engenheiros que implantam modelos YOLO em ambientes de produção onde o pré-processamento via CPU é um gargalo medido — tipicamente implantações TensorRT em GPUs NVIDIA, pipelines de vídeo de alto rendimento ou configurações do Triton Inference Server. Se você estiver executando a inferência padrão com model.predict() e não tiver um gargalo de pré-processamento, o pipeline de CPU padrão funciona bem.

Resumo Rápido
  • Criando um pipeline DALI? Use fn.resize(mode="not_larger") + fn.crop(out_of_bounds_policy="pad") + fn.crop_mirror_normalize para replicar o pré-processamento letterbox do YOLO na GPU.
  • Integrando com Ultralytics? Passe a saída do DALI como um torch.Tensor para model.predict() — o Ultralytics ignora o pré-processamento de imagem automaticamente.
  • Implantando com Triton? Use o backend DALI com um ensemble do TensorRT para pré-processamento zero-CPU.

Link to this sectionPor que usar o DALI para o Pré-processamento YOLO#

Em um pipeline de inferência YOLO típico, as etapas de pré-processamento são executadas na CPU:

  1. Decodificar a imagem (JPEG/PNG)
  2. Redimensionar preservando a proporção (aspect ratio)
  3. Preencher para o tamanho alvo (letterbox)
  4. Normalizar valores de pixel de [0, 255] para [0, 1]
  5. Converter layout de HWC para CHW

Com o DALI, todas essas operações são executadas na GPU, eliminando o gargalo da CPU. Isso é especialmente valioso quando:

CenárioPor que o DALI ajuda
Inferência rápida em GPUMecanismos TensorRT com inferência em sub-milissegundos tornam o pré-processamento em CPU o custo dominante
Entradas de alta resoluçãoFluxos de vídeo 1080p e 4K exigem operações de redimensionamento custosas
Tamanhos de lote grandesInferência no lado do servidor processando muitas imagens em paralelo
Núcleos de CPU limitadosDispositivos de borda como NVIDIA Jetson ou servidores de GPU densos com poucos núcleos de CPU por GPU

Link to this sectionPré-requisitos#

Apenas Linux

O NVIDIA DALI suporta apenas Linux. Ele não está disponível para Windows ou macOS.

Instale os pacotes necessários:

pip install ultralytics
pip install --extra-index-url https://pypi.nvidia.com nvidia-dali-cuda130

Requisitos:

  • GPU NVIDIA (capacidade de computação 5.0+ / Maxwell ou mais recente)
  • CUDA 11.0+, 12.0+ ou 13.0+
  • Python 3.10-3.14
  • Sistema operacional Linux

Link to this sectionEntendendo o Pré-processamento YOLO#

Antes de criar um pipeline DALI, ajuda entender exatamente o que o Ultralytics faz durante o pré-processamento. A classe principal é LetterBox em ultralytics/data/augment.py:

from ultralytics.data.augment import LetterBox

letterbox = LetterBox(
    new_shape=(640, 640),  # Target size
    center=True,  # Center the image (pad equally on both sides)
    stride=32,  # Stride alignment
    padding_value=114,  # Gray padding (114, 114, 114)
)

O pipeline de pré-processamento completo em ultralytics/engine/predictor.py realiza estas etapas:

EtapaOperaçãoFunção de CPUEquivalente no DALI
1Redimensionamento Letterboxcv2.resizefn.resize(mode="not_larger")
2Preenchimento centralizadocv2.copyMakeBorderfn.crop(out_of_bounds_policy="pad")
3BGR → RGBim[..., ::-1]fn.decoders.image(output_type=types.RGB)
4HWC → CHW + normalizar /255np.transpose + tensor / 255fn.crop_mirror_normalize(std=[255,255,255])

A operação letterbox preserva a proporção ao:

  1. Calcular a escala: r = min(target_h / h, target_w / w)
  2. Redimensionar para (round(w * r), round(h * r))
  3. Preencher o espaço restante com cinza (114) para atingir o tamanho alvo
  4. Centralizar a imagem para que o preenchimento seja distribuído igualmente em ambos os lados

Link to this sectionPipeline DALI para YOLO#

Use o pipeline centralizado abaixo como referência padrão. Ele corresponde ao comportamento LetterBox(center=True) do Ultralytics, que é o que a inferência padrão do YOLO usa.

Link to this sectionPipeline Centralizado (Recomendado, corresponde ao LetterBox do Ultralytics)#

Esta versão replica exatamente o pré-processamento padrão do Ultralytics com preenchimento centralizado, correspondendo a LetterBox(center=True):

Pipeline DALI com preenchimento centralizado (recomendado)
import nvidia.dali as dali
import nvidia.dali.fn as fn
import nvidia.dali.types as types

@dali.pipeline_def(batch_size=8, num_threads=4, device_id=0)
def yolo_dali_pipeline_centered(image_dir, target_size=640):
    """DALI pipeline replicating YOLO preprocessing with centered padding.

    Matches Ultralytics LetterBox(center=True) behavior exactly.
    """
    # Read and decode images on GPU
    jpegs, _ = fn.readers.file(file_root=image_dir, random_shuffle=False, name="Reader")
    images = fn.decoders.image(jpegs, device="mixed", output_type=types.RGB)

    # Aspect-ratio-preserving resize
    resized = fn.resize(
        images,
        resize_x=target_size,
        resize_y=target_size,
        mode="not_larger",
        interp_type=types.INTERP_LINEAR,
        antialias=False,  # Match cv2.INTER_LINEAR (no antialiasing)
    )

    # Centered padding using fn.crop with out_of_bounds_policy
    # When crop size > image size, fn.crop centers the image and pads symmetrically
    padded = fn.crop(
        resized,
        crop=(target_size, target_size),
        out_of_bounds_policy="pad",
        fill_values=114,  # YOLO padding value
    )

    # Normalize and convert layout
    output = fn.crop_mirror_normalize(
        padded,
        dtype=types.FLOAT,
        output_layout="CHW",
        mean=[0.0, 0.0, 0.0],
        std=[255.0, 255.0, 255.0],
    )
    return output
Quando `fn.pad` é suficiente?

Se você não precisar de paridade exata com LetterBox(center=True), você pode simplificar a etapa de preenchimento usando fn.pad(...) em vez de fn.crop(..., out_of_bounds_policy="pad"). Essa variante preenche apenas as bordas direita e inferior, o que pode ser aceitável para pipelines de implantação personalizados, mas não corresponderá exatamente ao comportamento letterbox centralizado padrão do Ultralytics.

Por que `fn.crop` para preenchimento centralizado?

O operador fn.pad do DALI adiciona preenchimento apenas às bordas direita e inferior. Para obter preenchimento centralizado (correspondendo ao LetterBox(center=True) do Ultralytics), use fn.crop com out_of_bounds_policy="pad". Com os valores padrão crop_pos_x=0.5 e crop_pos_y=0.5, a imagem é centralizada automaticamente com preenchimento simétrico.

Incompatibilidade de Antialiasing

O fn.resize do DALI ativa o antialiasing por padrão (antialias=True), enquanto o cv2.resize do OpenCV com INTER_LINEAR não aplica antialiasing. Sempre defina antialias=False no DALI para corresponder ao pipeline de CPU. Omitir isso causa diferenças numéricas sutis que podem afetar a precisão do modelo.

Link to this sectionExecutando o Pipeline#

Crie e execute um pipeline DALI
# Build and run the pipeline
pipe = yolo_dali_pipeline_centered(image_dir="/path/to/images", target_size=640)
pipe.build()

# Get a batch of preprocessed images
(output,) = pipe.run()

# Convert to numpy or PyTorch tensors
batch_np = output.as_cpu().as_array()  # Shape: (batch_size, 3, 640, 640)
print(f"Output shape: {batch_np.shape}, dtype: {batch_np.dtype}")
print(f"Value range: [{batch_np.min():.4f}, {batch_np.max():.4f}]")

Link to this sectionUsando o DALI com o Ultralytics Predict#

Você pode passar um tensor PyTorch pré-processado diretamente para model.predict(). Quando um torch.Tensor é passado, o Ultralytics ignora o pré-processamento de imagem (letterbox, BGR→RGB, HWC→CHW e normalização /255) e apenas realiza a transferência para o dispositivo e a conversão de tipo (dtype) antes de enviá-lo ao modelo.

Como o Ultralytics não tem acesso às dimensões originais da imagem neste caso, as coordenadas das caixas de detecção são retornadas no espaço letterbox 640×640. Para mapeá-las de volta às coordenadas originais da imagem, use scale_boxes, que gerencia a lógica de arredondamento exata usada pelo LetterBox:

from ultralytics.utils.ops import scale_boxes

# boxes: tensor of shape (N, 4) in xyxy format, in 640x640 letterboxed coords
# Scale boxes from letterboxed (640, 640) back to original (orig_h, orig_w)
boxes = scale_boxes((640, 640), boxes, (orig_h, orig_w))

Isso se aplica a todos os caminhos de pré-processamento externos — entrada direta de tensor, fluxos de vídeo e implantação no Triton.

DALI + predição do Ultralytics
from nvidia.dali.plugin.pytorch import DALIGenericIterator

from ultralytics import YOLO

# Load model
model = YOLO("yolo26n.pt")

# Create DALI iterator
pipe = yolo_dali_pipeline_centered(image_dir="/path/to/images", target_size=640)
pipe.build()
dali_iter = DALIGenericIterator(pipe, ["images"], reader_name="Reader")

# Run inference with DALI-preprocessed tensors
for batch in dali_iter:
    images = batch[0]["images"]  # Already on GPU, shape (B, 3, 640, 640)
    results = model.predict(images, verbose=False)
    for result in results:
        print(f"Detected {len(result.boxes)} objects")
Sobrecarga de Pré-processamento Zero

Ao passar um torch.Tensor para model.predict(), a etapa de pré-processamento da imagem leva ~0,004 ms (essencialmente zero) em comparação com ~1-10 ms com o pré-processamento de CPU. O tensor deve estar no formato BCHW, float32 (ou float16) e normalizado para [0, 1]. O Ultralytics ainda tratará a transferência para o dispositivo e a conversão de tipo automaticamente.

Link to this sectionDALI com Fluxos de Vídeo#

Para processamento de vídeo em tempo real, use fn.external_source para alimentar quadros de qualquer fonte — OpenCV, GStreamer ou bibliotecas de captura personalizadas:

Pipeline DALI para pré-processamento de fluxo de vídeo
import nvidia.dali as dali
import nvidia.dali.fn as fn
import nvidia.dali.types as types

@dali.pipeline_def(batch_size=1, num_threads=4, device_id=0)
def yolo_video_pipeline(target_size=640):
    """DALI pipeline for processing video frames from external source."""
    # External source for feeding frames from OpenCV, GStreamer, etc.
    frames = fn.external_source(device="cpu", name="input")
    frames = fn.reshape(frames, layout="HWC")

    # Move to GPU and preprocess
    frames_gpu = frames.gpu()
    resized = fn.resize(
        frames_gpu,
        resize_x=target_size,
        resize_y=target_size,
        mode="not_larger",
        interp_type=types.INTERP_LINEAR,
        antialias=False,
    )
    padded = fn.crop(
        resized,
        crop=(target_size, target_size),
        out_of_bounds_policy="pad",
        fill_values=114,
    )
    output = fn.crop_mirror_normalize(
        padded,
        dtype=types.FLOAT,
        output_layout="CHW",
        mean=[0.0, 0.0, 0.0],
        std=[255.0, 255.0, 255.0],
    )
    return output

Link to this sectionTriton Inference Server com DALI#

Para implantação em produção, combine o pré-processamento DALI com inferência TensorRT no Triton Inference Server usando um modelo ensemble. Isso elimina o pré-processamento de CPU inteiramente — bytes JPEG brutos entram, detecções saem, com tudo processado na GPU.

Link to this sectionEstrutura do Repositório de Modelos#

model_repository/
├── dali_preprocessing/
│   ├── 1/
│   │   └── model.dali
│   └── config.pbtxt
├── yolo_trt/
│   ├── 1/
│   │   └── model.plan
│   └── config.pbtxt
└── ensemble_dali_yolo/
    ├── 1/                  # Empty directory (required by Triton)
    └── config.pbtxt

Link to this sectionEtapa 1: Criar o Pipeline DALI#

Serialize o pipeline DALI para o backend DALI do Triton:

Serializar pipeline DALI para Triton
import nvidia.dali as dali
import nvidia.dali.fn as fn
import nvidia.dali.types as types

@dali.pipeline_def(batch_size=8, num_threads=4, device_id=0)
def triton_dali_pipeline():
    """DALI preprocessing pipeline for Triton deployment."""
    # Input: raw encoded image bytes from Triton
    images = fn.external_source(device="cpu", name="DALI_INPUT_0")
    images = fn.decoders.image(images, device="mixed", output_type=types.RGB)

    resized = fn.resize(
        images,
        resize_x=640,
        resize_y=640,
        mode="not_larger",
        interp_type=types.INTERP_LINEAR,
        antialias=False,
    )
    padded = fn.crop(
        resized,
        crop=(640, 640),
        out_of_bounds_policy="pad",
        fill_values=114,
    )
    output = fn.crop_mirror_normalize(
        padded,
        dtype=types.FLOAT,
        output_layout="CHW",
        mean=[0.0, 0.0, 0.0],
        std=[255.0, 255.0, 255.0],
    )
    return output

# Serialize pipeline to model repository
pipe = triton_dali_pipeline()
pipe.serialize(filename="model_repository/dali_preprocessing/1/model.dali")

Link to this sectionEtapa 2: Exportar o YOLO para o TensorRT#

Exportar modelo YOLO para o mecanismo TensorRT
from ultralytics import YOLO

model = YOLO("yolo26n.pt")
model.export(format="engine", imgsz=640, half=True, batch=8)
# Copy the .engine file to model_repository/yolo_trt/1/model.plan

Link to this sectionEtapa 3: Configurar o Triton#

dali_preprocessing/config.pbtxt:

name: "dali_preprocessing"
backend: "dali"
max_batch_size: 8
input [
  {
    name: "DALI_INPUT_0"
    data_type: TYPE_UINT8
    dims: [ -1 ]
  }
]
output [
  {
    name: "DALI_OUTPUT_0"
    data_type: TYPE_FP32
    dims: [ 3, 640, 640 ]
  }
]

yolo_trt/config.pbtxt:

name: "yolo_trt"
platform: "tensorrt_plan"
max_batch_size: 8
input [
  {
    name: "images"
    data_type: TYPE_FP32
    dims: [ 3, 640, 640 ]
  }
]
output [
  {
    name: "output0"
    data_type: TYPE_FP32
    dims: [ 300, 6 ]
  }
]

ensemble_dali_yolo/config.pbtxt:

name: "ensemble_dali_yolo"
platform: "ensemble"
max_batch_size: 8
input [
  {
    name: "INPUT"
    data_type: TYPE_UINT8
    dims: [ -1 ]
  }
]
output [
  {
    name: "OUTPUT"
    data_type: TYPE_FP32
    dims: [ 300, 6 ]
  }
]
ensemble_scheduling {
  step [
    {
      model_name: "dali_preprocessing"
      model_version: -1
      input_map {
        key: "DALI_INPUT_0"
        value: "INPUT"
      }
      output_map {
        key: "DALI_OUTPUT_0"
        value: "preprocessed_image"
      }
    },
    {
      model_name: "yolo_trt"
      model_version: -1
      input_map {
        key: "images"
        value: "preprocessed_image"
      }
      output_map {
        key: "output0"
        value: "OUTPUT"
      }
    }
  ]
}
Como funciona o mapeamento de conjunto

O conjunto conecta modelos através de nomes de tensores virtuais. O valor output_map "preprocessed_image" na etapa DALI corresponde ao valor input_map "preprocessed_image" na etapa TensorRT. Estes são nomes arbitrários que ligam a saída de uma etapa à entrada da próxima — eles não precisam corresponder aos nomes internos de tensores de nenhum modelo.

Link to this sectionPasso 4: Enviar solicitações de inferência#

!!! info "Por que usar tritonclient em vez de YOLO(\"http://...\")?"

Ultralytics has [built-in Triton support](triton-inference-server.md#running-inference) that handles pre/postprocessing automatically. However, it won't work with the DALI ensemble because `YOLO()` sends a preprocessed float32 tensor while the ensemble expects raw JPEG bytes. Use `tritonclient` directly for DALI ensembles, and the [built-in integration](triton-inference-server.md) for standard deployments without DALI.
Enviar imagens para o conjunto Triton
import numpy as np
import tritonclient.http as httpclient

client = httpclient.InferenceServerClient(url="localhost:8000")

# Load image as raw bytes (JPEG/PNG encoded)
image_data = np.fromfile("image.jpg", dtype="uint8")
image_data = np.expand_dims(image_data, axis=0)  # Add batch dimension

# Create input
input_tensor = httpclient.InferInput("INPUT", image_data.shape, "UINT8")
input_tensor.set_data_from_numpy(image_data)

# Run inference through the ensemble
result = client.infer(model_name="ensemble_dali_yolo", inputs=[input_tensor])
detections = result.as_numpy("OUTPUT")  # Shape: (1, 300, 6) -> [x1, y1, x2, y2, conf, class_id]

# Filter by confidence (no NMS needed — YOLO26 is end-to-end)
detections = detections[0]  # First image
detections = detections[detections[:, 4] > 0.25]  # Confidence threshold
print(f"Detected {len(detections)} objects")
Processamento em lote de imagens JPEG

Ao enviar um lote de imagens JPEG para o Triton, preencha (pad) todos os arrays de bytes codificados com o mesmo comprimento (a contagem máxima de bytes no lote). O Triton requer formas de lote homogêneas para o tensor de entrada.

Link to this sectionTarefas suportadas#

O pré-processamento DALI funciona com todas as tarefas YOLO que usam o pipeline padrão LetterBox:

TarefaSuportadoNotas
DetecçãoPré-processamento letterbox padrão
Segmentação de InstânciaMesmo pré-processamento da detecção
Segmentação SemânticaMesmo pré-processamento de imagem da detecção
Estimativa de PoseMesmo pré-processamento da detecção
Detecção Orientada (OBB)Mesmo pré-processamento da detecção
ClassificaçãoUsa transformações do torchvision (corte central), não letterbox

Link to this sectionLimitações#

  • Apenas Linux: O DALI não oferece suporte a Windows ou macOS
  • NVIDIA GPU necessária: Sem fallback apenas para CPU
  • Pipeline estático: A estrutura do pipeline é definida em tempo de compilação e não pode mudar dinamicamente
  • fn.pad é apenas para a direita/baixo: Use fn.crop com out_of_bounds_policy="pad" para preenchimento centralizado
  • Sem modo rect: Os pipelines DALI produzem saídas de tamanho fixo (ex: 640×640). O modo rect auto=True que produz saídas de tamanho variável (ex: 384×640) não é suportado. Note que, embora o TensorRT suporte formas de entrada dinâmicas, um pipeline DALI de tamanho fixo combina naturalmente com um motor de tamanho fixo para rendimento máximo
  • Memória com múltiplas instâncias: Usar instance_group com count > 1 no Triton pode causar alto uso de memória. Use o grupo de instâncias padrão para o modelo DALI

Link to this sectionFAQ#

Link to this sectionComo o pré-processamento DALI se compara à velocidade de pré-processamento da CPU?#

O benefício depende do seu pipeline. Quando a inferência em GPU já é rápida com TensorRT, o pré-processamento em CPU de 2-10ms pode se tornar o custo dominante. O DALI elimina esse gargalo ao executar o pré-processamento na GPU. Os maiores ganhos são vistos com entradas de alta resolução (1080p, 4K), grandes batch sizes e sistemas com núcleos de CPU limitados por GPU.

Link to this sectionPosso usar o DALI com modelos PyTorch (não apenas TensorRT)?#

Sim. Use DALIGenericIterator para obter saídas torch.Tensor pré-processadas e, em seguida, passe-as para model.predict(). No entanto, o benefício de desempenho é maior com modelos TensorRT, onde a inferência já é muito rápida e o pré-processamento em CPU se torna o gargalo.

Link to this sectionQual é a diferença entre fn.pad e fn.crop para preenchimento?#

fn.pad adiciona preenchimento apenas às bordas direita e inferior. fn.crop com out_of_bounds_policy="pad" centraliza a imagem e adiciona preenchimento simetricamente em todos os lados, correspondendo ao comportamento do LetterBox(center=True) da Ultralytics.

Link to this sectionO DALI produz resultados idênticos em nível de pixel ao pré-processamento em CPU?#

Quase idênticos. Defina antialias=False em fn.resize para corresponder ao cv2.INTER_LINEAR do OpenCV. Pequenas diferenças de ponto flutuante (< 0.001) podem ocorrer devido à aritmética de GPU vs CPU, mas estas não têm impacto mensurável na precisão da detecção.

Link to this sectionE o CV-CUDA como uma alternativa ao DALI?#

CV-CUDA é outra biblioteca da NVIDIA para processamento de visão acelerado por GPU. Ela oferece controle por operador (como o OpenCV, mas na GPU) em vez da abordagem de pipeline do DALI. O cvcuda.copymakeborder() do CV-CUDA suporta preenchimento explícito por lado, tornando o letterbox centralizado simples. Escolha o DALI para fluxos de trabalho baseados em pipeline (especialmente com Triton) e o CV-CUDA para controle granular em nível de operador em código de inferência personalizado.

Comentários