YOLO Vision 2026:

NVIDIA DALI ile GPU Hızlandırmalı Ön İşleme#

Ultralytics YOLO modellerini prodüksiyona dağıtırken, ön işleme genellikle darboğaz haline gelir. TensorRT, model çıkarımını sadece birkaç milisaniyede çalıştırabilirken, CPU tabanlı ön işleme (yeniden boyutlandırma, dolgu, normalleştirme) özellikle yüksek çözünürlüklerde görüntü başına 2-10 ms sürebilir. NVIDIA DALI (Data Loading Library), tüm ön işleme boru hattını GPU'ya taşıyarak bunu çözer.

Bu kılavuz, Ultralytics YOLO ön işlemesini tam olarak kopyalayan DALI boru hatları oluşturmanızı, bunları model.predict() ile entegre etmenizi, video akışlarını işlemenizi ve Triton Inference Server ile uçtan uca dağıtmanızı adım adım açıklar.

Bu kılavuz kimin için?

Bu kılavuz, CPU ön işlemesinin ölçülebilir bir darboğaz olduğu üretim ortamlarında YOLO modellerini dağıtan mühendisler içindir — tipik olarak NVIDIA GPU'lar üzerindeki TensorRT dağıtımları, yüksek verimli video boru hatları veya Triton Inference Server kurulumları. model.predict() ile standart çıkarım çalıştırıyorsanız ve ön işleme darboğazınız yoksa, varsayılan CPU boru hattı iyi çalışır.

Hızlı Özet
  • Dali boru hattı mı oluşturuyorsunuz? YOLO'nun letterbox ön işlemesini GPU'da kopyalamak için fn.resize(mode="not_larger") + fn.crop(out_of_bounds_policy="pad") + fn.crop_mirror_normalize kullanın.
  • Ultralytics ile entegre mi ediyorsunuz? DALI çıktısını torch.Tensor olarak model.predict() nesnesine geçirin; Ultralytics görüntü ön işlemesini otomatik olarak atlar.
  • Triton ile dağıtım mı yapıyorsun? Sıfır CPU ön işlemesi için TensorRT grubuyla birlikte DALI arka ucunu kullan.

YOLO Ön İşleme için Neden DALI Kullanmalı?#

Tipik bir YOLO çıkarım hattında ön işleme adımları CPU üzerinde çalışır:

  1. Görüntünün kodunu çözme (JPEG/PNG)
  2. En boy oranını koruyarak yeniden boyutlandırma
  3. Hedef boyuta dolgu yapma (letterbox)
  4. Piksel değerlerini [0, 255] üzerinden [0, 1] değerine normalize edin
  5. Düzeni HWC'den CHW'ye dönüştürme

DALI ile tüm bu işlemler GPU üzerinde çalışır ve CPU darboğazını ortadan kaldırır. Bu durum özellikle şu durumlarda değerlidir:

SenaryoDALI Neden Yardımcı Olur?
Hızlı GPU çıkarımıMilisaniye altı çıkarıma sahip TensorRT motorları, CPU ön işlemesini baskın maliyet haline getirir
Yüksek çözünürlüklü girişler1080p ve 4K video akışları pahalı yeniden boyutlandırma işlemleri gerektirir
Büyük parti boyutlarıSunucu tarafında çok sayıda görüntüyü paralel olarak işleyen çıkarım işlemleri
Sınırlı CPU çekirdekleriNVIDIA Jetson gibi uç cihazlar veya GPU başına az sayıda CPU çekirdeğine sahip yoğun GPU sunucuları

Ön koşullar#

Sadece Linux

NVIDIA DALI sadece Linux destekler. Windows veya macOS üzerinde mevcut değildir.

Gerekli paketleri yükle:

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

Gereksinimler:

  • NVIDIA GPU (işlem yeteneği 5.0+ / Maxwell veya daha yeni)
  • CUDA 11.0+, 12.0+ veya 13.0+
  • Python 3.10-3.14
  • Linux işletim sistemi

YOLO Ön İşlemesini Anlamak#

Bir DALI pipeline'ı oluşturmadan önce, Ultralytics'in ön işleme sırasında tam olarak ne yaptığını anlamakta fayda var. Temel sınıf, ultralytics/data/augment.py içindeki LetterBox'dır:

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)
)

ultralytics/engine/predictor.py içindeki tam ön işleme boru hattı şu adımları gerçekleştirir:

AdımİşlemCPU İşleviDALI Eşdeğeri
1Letterbox yeniden boyutlandırmacv2.resizefn.resize(mode="not_larger")
2Merkezlenmiş dolgucv2.copyMakeBorderfn.crop(out_of_bounds_policy="pad")
3BGR → RGBim[..., ::-1]fn.decoders.image(output_type=types.RGB)
4HWC → CHW + normalleştirme /255np.transpose + tensor / 255fn.crop_mirror_normalize(std=[255,255,255])

Letterbox işlemi, en boy oranını şu şekilde korur:

  1. Ölçek hesaplanıyor: r = min(target_h / h, target_w / w)
  2. (round(w * r), round(h * r)) boyutuna yeniden boyutlandırılıyor
  3. Kalan alan hedef boyuta ulaşmak için gri (114) ile dolduruluyor
  4. Dolgunun her iki tarafa eşit dağılması için görüntüyü merkezleme

YOLO için DALI Hattı#

Önerilen DALI boru hattı, standart YOLO çıkarımının kullandığı şey olan Ultralytics'in varsayılan LetterBox(center=True) davranışını kopyalar.

Merkezlenmiş Hat (Önerilen, Ultralytics LetterBox ile eşleşir)#

Bu sürüm, LetterBox(center=True) ile eşleşecek şekilde ortalanmış dolgu ile varsayılan Ultralytics ön işlemesini tam olarak kopyalar:

Merkezlenmiş dolgulu DALI hattı (önerilen)
from nvidia import dali
from nvidia.dali import fn, 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
`fn.pad` ne zaman yeterlidir?

Tam bir LetterBox(center=True) eşleşmesine ihtiyacın yoksa, fn.crop(..., out_of_bounds_policy="pad") yerine fn.pad(...) kullanarak dolgu adımını basitleştirebilirsin. Bu varyant yalnızca sağ ve alt kenarları doldurur; bu, özel dağıtım (deployment) pipeline'ları için kabul edilebilir olabilir, ancak Ultralytics'in varsayılan ortalanmış letterbox davranışıyla tam olarak eşleşmez.

Merkezlenmiş dolgu için neden `fn.crop`?

DALI'nın fn.pad operatörü yalnızca sağ ve alt kenarlara dolgu ekler. Ortalanmış dolgu elde etmek için (Ultralytics LetterBox(center=True) ile eşleşecek şekilde), out_of_bounds_policy="pad" ile fn.crop kullan. Varsayılan crop_pos_x=0.5 ve crop_pos_y=0.5 ile görüntü, simetrik dolgu ile otomatik olarak ortalanır.

Kenar Yumuşatma (Antialias) Uyumsuzluğu

DALI'nın fn.resize öğesi varsayılan olarak (antialias=True) kenar yumuşatmayı (antialiasing) etkinleştirirken, INTER_LINEAR ile OpenCV'nin cv2.resize'si kenar yumuşatma uygulamaz. CPU pipeline'ı ile eşleşmesi için DALI'da her zaman antialias=False ayarını yap. Bunu atlamak, model doğruluğunu etkileyebilecek küçük sayısal farklılıklara neden olur.

Hattı Çalıştırma#

Bir DALI hattı oluştur ve çalıştır
# 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}]")

DALI'yi Ultralytics Predict ile Kullanma#

Ön işlenmiş bir PyTorch tensörünü doğrudan model.predict() öğesine geçirebilirsiniz. Bir torch.Tensor geçirildiğinde, Ultralytics görüntü ön işlemesini atlar (letterbox, BGR→RGB, HWC→CHW ve /255 normalleştirmesi) ve modeli göndermeden önce yalnızca cihaz aktarımı ile veri tipi (dtype) dönüşümünü gerçekleştirir.

Bu durumda Ultralytics orijinal görüntü boyutlarına erişemediği için, tespit kutusu koordinatları 640×640 letterbox alanında döndürülür. Bunları orijinal görüntü koordinatlarına geri eşlemek için, LetterBox tarafından kullanılan tam yuvarlama mantığını işleyen scale_boxes kullan:

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))

Bu, tüm harici ön işleme yolları (doğrudan tensör girişi, video akışları ve Triton dağıtımı) için geçerlidir.

DALI + Ultralytics predict
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")
Sıfır Ön İşleme Ek Yükü

torch.Tensor öğesini model.predict() öğesine geçirdiğinizde, görüntü ön işleme adımı CPU ön işlemesindeki ~1-10 ms'ye kıyasla ~0.004 ms (esasen sıfır) sürer. Tensör BCHW formatında, float32 (veya float16) olmalı ve [0, 1] değerine normalize edilmelidir. Ultralytics yine de cihaz aktarımını ve dtype dönüşümünü otomatik olarak yönetecektir.

Video Akışları ile DALI#

Gerçek zamanlı video işleme için, herhangi bir kaynaktan — OpenCV, GStreamer veya özel yakalama kütüphanelerinden — kareler beslemek üzere fn.external_source kullan:

Video akışı ön işlemesi için DALI hattı
from nvidia import dali
from nvidia.dali import fn, 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

DALI ile Triton Inference Server#

Üretim ortamına dağıtım için, DALI ön işlemesini Triton Inference Server içinde bir ensemble model kullanarak TensorRT çıkarımı ile birleştir. Bu, CPU ön işlemesini tamamen ortadan kaldırır; ham JPEG baytları içeri girer, tespitler dışarı çıkar ve her şey GPU üzerinde işlenir.

Model Deposu Yapısı#

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

Adım 1: DALI Hattını Oluştur#

Triton DALI arka ucu için DALI hattını serileştir:

Triton için DALI hattını serileştir
from nvidia import dali
from nvidia.dali import fn, 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")

Adım 2: YOLO'yu TensorRT'ye Aktar#

YOLO modelini TensorRT motoruna aktar
from ultralytics import YOLO

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

Adım 3: Triton'u Yapılandır#

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"
      }
    }
  ]
}
Topluluk Eşleme Nasıl Çalışır?

Topluluk modeli, modelleri sanal tensör adları aracılığıyla birbirine bağlar. DALI adımındaki output_map değeri olan "preprocessed_image", TensorRT adımındaki input_map değeri olan "preprocessed_image" ile eşleşir. Bunlar, bir adımın çıktısını sonraki adımın girdisine bağlayan isteğe bağlı adlardır; herhangi bir modelin dahili tensör adlarıyla eşleşmeleri gerekmez.

Adım 4: Çıkarım İsteklerini Gönder#

Neden `YOLO('http://...')` yerine `tritonclient`?

Ultralytics, ön/son işlemeyi otomatik olarak yöneten yerleşik Triton desteğine sahiptir. Ancak, YOLO() ön işlenmiş bir float32 tensörü gönderirken topluluk ham JPEG baytları beklediğinden, bu DALI topluluğu ile çalışmaz. DALI toplulukları için doğrudan tritonclient öğesini ve DALI olmayan standart dağıtımlar için yerleşik entegrasyonu kullanın.

Görüntüleri Triton ensemble'ına gönder
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")
JPEG Görüntülerini Toplu İşleme

Triton'a bir grup JPEG görüntüsü gönderirken, tüm kodlanmış bayt dizilerini aynı uzunluğa (gruptaki en yüksek bayt sayısı) kadar doldur (pad). Triton, giriş tensörü için homojen toplu iş şekilleri gerektirir.

Desteklenen Görevler#

DALI ön işlemesi, standart LetterBox boru hattını kullanan tüm YOLO görevleriyle çalışır:

GörevDesteklenenNotlar
AlgılamaStandart letterbox ön işleme
Örnek BölütlemeAlgılama ile aynı ön işleme
Anlamsal BölütlemeAlgılama ile aynı görüntü ön işleme
SınıflandırmaLetterbox değil, torchvision dönüşümlerini (orta kırpma) kullanır
Poz TahminiAlgılama ile aynı ön işleme
Yönlendirilmiş Tespit (OBB)Algılama ile aynı ön işleme

Sınırlamalar#

  • Yalnızca Linux: DALI, Windows veya macOS'i desteklemez
  • NVIDIA GPU gereklidir: Yalnızca CPU tabanlı bir geri dönüş yoktur
  • Statik hat: Hat yapısı oluşturma zamanında tanımlanır ve dinamik olarak değiştirilemez
  • fn.pad yalnızca sağ/alttadır: Ortalanmış dolgu için out_of_bounds_policy="pad" ile fn.crop kullan
  • Rect modu yok: DALI boru hatları sabit boyutlu çıktılar üretir (örn. 640×640). Değişken boyutlu çıktılar üreten auto=True rect modu (örn. 384×640) desteklenmez. TensorRT dinamik girdi şekillerini desteklese de, sabit boyutlu bir DALI boru hattının maksimum verimlilik için sabit boyutlu bir motorla doğallıkla eşleştiğini unutmayın.
  • Birden fazla örnekle bellek: Triton'da count > 1 ile instance_group kullanmak yüksek bellek kullanımına neden olabilir. DALI modeli için varsayılan örnek grubunu kullan

SSS#

  • Fayda, boru hattınıza bağlıdır. GPU çıkarımı TensorRT ile zaten hızlı olduğunda, 2-10 ms'deki CPU ön işlemesi baskın maliyet haline gelebilir. DALI, ön işlemeyi GPU üzerinde çalıştırarak bu darboğazı ortadan kaldırır. En büyük kazançlar yüksek çözünürlüklü girdilerle (1080p, 4K), büyük parti boyutlarıyla ve GPU başına sınırlı CPU çekirdeğine sahip sistemlerle elde edilir.

  • Evet. Ön işlenmiş torch.Tensor çıktıları almak için DALIGenericIterator kullan, ardından bunları model.predict()'ye aktar. Bununla birlikte, performans avantajı en çok, çıkarımın zaten çok hızlı olduğu ve CPU ön işlemesinin darboğaz haline geldiği TensorRT modellerinde belirgindir.

  • fn.pad yalnızca sağ ve alt kenarlara dolgu ekler. out_of_bounds_policy="pad" ile fn.crop görüntüyü ortalar ve Ultralytics LetterBox(center=True) davranışı ile eşleşecek şekilde tüm taraflara simetrik olarak dolgu ekler.

  • Neredeyse aynı. OpenCV'nin cv2.INTER_LINEAR'si ile eşleşmesi için fn.resize içinde antialias=False ayarını yap. GPU ve CPU aritmetiği nedeniyle küçük kayan nokta (floating-point) farklılıkları (< 0.001) oluşabilir, ancak bunların tespit doğruluğu üzerinde ölçülebilir bir etkisi yoktur.

  • CV-CUDA, GPU hızlandırmalı görüntü işleme için başka bir NVIDIA kütüphanesidir. DALI'nın boru hattı yaklaşımı yerine operatör bazında kontrol sağlar (GPU üzerinde OpenCV gibi). CV-CUDA'nın cvcuda.copymakeborder() özelliği, açık kenar bazlı dolguyu destekleyerek ortalanmış letterbox işlemini kolaylaştırır. Boru hattı tabanlı iş akışları için (özellikle Triton ile) DALI'yı, özel çıkarım kodunda ince taneli operatör düzeyinde kontrol için CV-CUDA'yı seçin.

Yorumlar