Ultralytics YOLO27: Bekleme listesine katıl.
Get Started

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

Ultralytics YOLO modelleri üretimde dağıtılırken ön işleme çoğunlukla darboğaz hâline gelir. TensorRT model çıkarımını yalnızca birkaç milisaniyede gerçekleştirebilirken, CPU tabanlı ön işleme (yeniden boyutlandırma, dolgu ekleme, normalleştirme) özellikle yüksek çözünürlüklerde görüntü başına 2-10 ms sürebilir. NVIDIA DALI (Veri Yükleme Kitaplığı), tüm ön işleme işlem hattını GPU'ya taşıyarak bu sorunu çözer.

Bu kılavuzda, Ultralytics YOLO ön işlemesini tam olarak taklit eden DALI işlem hatlarını oluşturmayı, bunları model.predict() ile tümleştirmeyi, video akışlarını işlemeyi ve Triton Inference Server ile uçtan uca dağıtım yapmayı öğreneceksin.

Bu kılavuz kimler için?

Bu kılavuz, CPU ön işlemesinin ölçülebilir bir darboğaz olduğu üretim ortamlarında YOLO modelleri dağıtan mühendislere yöneliktir. Bu ortamlar genellikle NVIDIA GPU'larda TensorRT dağıtımları, yüksek iş hacimli video işlem hatları veya Triton Inference Server kurulumlarıdır. model.predict() ile standart çıkarım yapıyorsan ve ön işleme darboğazın yoksa varsayılan CPU işlem hattı sorunsuz çalışır.

Hızlı Özet
  • DALI işlem hattı mı oluşturuyorsun? YOLO'nun letterbox ön işlemesini GPU'da taklit etmek için fn.resize(mode="not_larger") + fn.crop(out_of_bounds_policy="pad") + fn.crop_mirror_normalize kullan.
  • Ultralytics ile mi tümleştiriyorsun? DALI çıktısını torch.Tensor olarak model.predict()'e aktar — Ultralytics görüntü ön işlemesini otomatik olarak atlar.
  • Triton ile mi dağıtıyorsun? CPU kullanmadan ön işleme için DALI arka ucunu TensorRT topluluğuyla birlikte kullan.

YOLO Ön İşlemesinde Neden DALI Kullanmalı?#

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

  1. Görüntüyü (JPEG/PNG) çöz
  2. En-boy oranını koruyarak yeniden boyutlandır
  3. Hedef boyuta dolgu uygula (letterbox)
  4. Piksel değerlerini [0, 255] ile [0, 1] arasında normalize et
  5. Yerleşimi HWC'den CHW'ye dönüştür

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

SenaryoDALI Neden Faydalı?
GPU'da hızlı çıkarımMilisaniyenin altında çıkarım yapan TensorRT motorlarında CPU ön işleme baskın maliyet haline gelir
Yüksek çözünürlüklü girdiler1080p ve 4K video akışları, yüksek maliyetli yeniden boyutlandırma işlemleri gerektirir
Büyük yığın boyutlarıSunucu tarafında çok sayıda görüntüyü paralel olarak işleyen çıkarım
Sınırlı sayıda CPU çekirdeğiNVIDIA Jetson gibi uç cihazlar veya GPU başına az sayıda CPU çekirdeği olan yoğun GPU sunucuları

Ön koşullar#

Yalnızca Linux

NVIDIA DALI yalnızca Linux'u destekler. Windows veya macOS'ta kullanılamaz.

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 yenisi)
  • CUDA 11.0+, 12.0+ veya 13.0+
  • Python 3.10-3.14
  • Linux işletim sistemi

YOLO Ön İşlemesini Anlamak#

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

from ultralytics.data.augment import LetterBox

letterbox = LetterBox(
    new_shape=(640, 640),  # Hedef boyut
    center=True,  # Görüntüyü ortala (her iki tarafa eşit dolgu ekle)
    stride=32,  # Stride hizalaması
    padding_value=114,  # Gri dolgu (114, 114, 114)
)

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

AdımİşlemCPU işleviDALI karşılığı
1Letterbox yeniden boyutlandırmasıcv2.resizefn.resize(mode="not_larger")
2Ortalanmış dolgucv2.copyMakeBorderfn.crop(out_of_bounds_policy="pad")
3BGR → RGBim[..., ::-1]fn.decoders.image(output_type=types.RGB)
4HWC → CHW + /255 normalizasyonunp.transpose + tensor / 255fn.crop_mirror_normalize(std=[255,255,255])

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

  1. Ölçeği hesaplama: r = min(target_h / h, target_w / w)
  2. (round(w * r), round(h * r)) boyutuna yeniden boyutlandırma
  3. Hedef boyuta ulaşmak için kalan alanı griyle (114) doldurma
  4. Dolgunun her iki tarafa eşit dağılması için görüntüyü ortalama

YOLO için DALI İşlem Hattı#

Önerilen DALI işlem hattı, Ultralytics'in varsayılan LetterBox(center=True) davranışını tekrarlar; standart YOLO çıkarımında da bu davranış kullanılır.

Bu sürüm, ortalanmış dolgu kullanarak Ultralytics'in varsayılan ön işlemesini birebir tekrarlar ve LetterBox(center=True) ile eşleşir:

Ortalanmış dolgulu DALI işlem 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.
    """
    # Görüntüleri GPU'da oku ve çöz
    jpegs, _ = fn.readers.file(file_root=image_dir, random_shuffle=False, name="Reader")
    images = fn.decoders.image(jpegs, device="mixed", output_type=types.RGB)

    # En-boy oranını koruyan yeniden boyutlandırma
    resized = fn.resize(
        images,
        resize_x=target_size,
        resize_y=target_size,
        mode="not_larger",
        interp_type=types.INTERP_LINEAR,
        antialias=False,  # cv2.INTER_LINEAR ile eşleş (kenar yumuşatma yok)
    )

    # out_of_bounds_policy ile fn.crop kullanarak ortalanmış dolgu
    # Kırpma boyutu görüntü boyutundan büyük olduğunda fn.crop görüntüyü ortalar ve simetrik biçimde doldurur
    padded = fn.crop(
        resized,
        crop=(target_size, target_size),
        out_of_bounds_policy="pad",
        fill_values=114,  # YOLO dolgu değeri
    )

    # Normalize et ve yerleşimi dönüştür
    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?

LetterBox(center=True) ile tam eşleşme gerekmiyorsa, fn.crop(..., out_of_bounds_policy="pad") yerine fn.pad(...) kullanarak dolgu adımını basitleştirebilirsin. Bu varyant yalnızca sağ ve alt kenarlara dolgu ekler; özel dağıtım işlem hatlarında kabul edilebilir, ancak Ultralytics'in varsayılan ortalanmış letterbox davranışıyla tam olarak eşleşmez.

Ortalanmış dolgu için neden `fn.crop` kullanılır?

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

Kenar Yumuşatma Uyumsuzluğu

DALI'nin fn.resize operatörü varsayılan olarak kenar yumuşatmayı etkinleştirir (antialias=True); OpenCV'nin INTER_LINEAR ile kullanılan cv2.resize operatörü ise kenar yumuşatma uygulamaz. CPU işlem hattıyla eşleşmesi için DALI'de her zaman antialias=False ayarını kullan. Bu ayarı atlamak, model doğruluğunu etkileyebilecek küçük sayısal farklılıklara yol açar.

İşlem Hattını Çalıştırma#

Bir DALI işlem 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}]")

Ultralytics Predict ile DALI Kullanımı#

Ön işlemeden geçirilmiş bir PyTorch tensörünü doğrudan model.predict() öğesine aktarabilirsin. Bir torch.Tensor aktarıldığında Ultralytics görüntü ön işlemeyi atlar (letterbox, BGR→RGB, HWC→CHW ve /255 normalizasyonu) ve tensörü modele göndermeden önce yalnızca cihaz aktarımı ile veri türü dönüşümünü gerçekleştirir.

Bu durumda Ultralytics'in özgün görüntü boyutlarına erişimi olmadığından, tespit kutularının koordinatları 640×640 letterbox alanında döndürülür. Bunları özgün görüntü koordinatlarına eşlemek için LetterBox tarafından kullanılan tam yuvarlama mantığını uygulayan scale_boxes öğesini kullan:

from ultralytics.utils.ops import scale_boxes

# kutular: 640x640 letterbox koordinatlarında, xyxy biçiminde (N, 4) boyutlu tensör
# Kutuları letterbox boyutundan (640, 640) özgün boyuta (orig_h, orig_w) ölçekle
boxes = scale_boxes((640, 640), boxes, (orig_h, orig_w))

Bu, doğrudan tensör girdisi, video akışları ve Triton dağıtımı gibi tüm harici ön işleme yolları 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 aktardığında, görüntü ön işleme adımı CPU ön işlemeyle yaklaşık 1-10 ms sürerken ~0.004 ms sürer (neredeyse sıfır). Tensör BCHW biçiminde, float32 (veya float16) olmalı ve [0, 1] değerine normalize edilmelidir. Ultralytics, cihaz aktarımını ve veri türü dönüşümünü yine otomatik olarak gerçekleştirir.

Video Akışlarıyla DALI#

Gerçek zamanlı video işleme için, kareleri herhangi bir kaynaktan ( OpenCV, GStreamer veya özel yakalama kitaplıkları) beslemek üzere fn.external_source kullan:

Video akışı ön işlemesi için DALI işlem 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."""
    # OpenCV, GStreamer vb. kaynaklardan kare beslemek için harici kaynak.
    frames = fn.external_source(device="cpu", name="input")
    frames = fn.reshape(frames, layout="HWC")

    # GPU'ya taşı ve ön işle
    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, topluluk modeli kullanarak Triton Inference Server içinde TensorRT çıkarımıyla birleştir. Bu, CPU ön işlemesini tamamen ortadan kaldırır: ham JPEG baytları girer, tespitler çıkar ve tüm işlemler GPU'da yürütülür.

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 İşlem Hattını Oluştur#

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

Triton için DALI işlem 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."""
    # Girdi: Triton'dan gelen ham kodlanmış görüntü baytları
    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

# İşlem hattını model deposuna serileştir
pipe = triton_dali_pipeline()
pipe.serialize(filename="model_repository/dali_preprocessing/1/model.dali")

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

YOLO modelini TensorRT motoruna dışa aktar
from pathlib import Path

from ultralytics import YOLO

model = YOLO("yolo26n.pt")
engine_path = model.export(
    format="engine", imgsz=640, quantize=16, batch=8, dynamic=True, nms=False
)  # NMS içermeyen (N, 300, 6); TensorRT >= 8.5

# Ultralytics, .engine dosyalarına bir meta veri başlığı ekler; Triton'un ham TensorRT planını yükleyebilmesi için başlığı kaldır
with open(engine_path, "rb") as f:
    meta_len = int.from_bytes(f.read(4), byteorder="little")  # JSON meta veri başlığının uzunluğu
    f.seek(4 + meta_len)
    plan = f.read()
Path("model_repository/yolo_trt/1").mkdir(parents=True, exist_ok=True)
Path("model_repository/yolo_trt/1/model.plan").write_bytes(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"
      }
    }
  ]
}
Ensemble Eşlemesi Nasıl Çalışır

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

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

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

Ultralytics, ön/son işlemeyi otomatik olarak gerçekleştiren yerleşik Triton desteğine sahiptir. Ancak YOLO(), ön işlenmiş bir float32 tensörü gönderirken ensemble ham JPEG baytları beklediğinden bu destek DALI ensemble ile çalışmaz. DALI ensemble'ları için doğrudan tritonclient kullan; DALI içermeyen standart dağıtımlar için yerleşik entegrasyonu kullan.

Triton ensemble'ına görüntü 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 for the nms=False export)
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 JPEG görüntü grubu gönderirken kodlanmış tüm bayt dizilerini aynı uzunluğa (gruptaki en yüksek bayt sayısına) tamamla. Triton, giriş tensörü için aynı şekle sahip grup öğeleri gerektirir.

Desteklenen Görevler#

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

GörevDesteklenirNotlar
Algılama✅Standart letterbox ön işleme
Örnek Segmentasyonu✅Tespit ile aynı ön işleme
Anlamsal Segmentasyon✅Tespit ile aynı görüntü ön işlemesi
Derinlik Tahmini✅Tespit ile aynı görüntü ön işlemesi
Sınıflandırma❌Letterbox yerine torchvision dönüşümlerini (merkezden kırpma) kullanır
Poz Tahmini✅Tespit ile aynı ön işleme
Yönelimli Tespit (OBB)✅Tespit ile aynı ön işleme

Sınırlamalar#

  • Yalnızca Linux: DALI, Windows veya macOS'u desteklemez
  • NVIDIA GPU gerekir: Yalnızca CPU kullanan bir geri dönüş seçeneği yoktur
  • Statik işlem hattı: İşlem hattının yapısı derleme zamanında tanımlanır ve dinamik olarak değiştirilemez
  • fn.pad yalnızca sağa/alta dolgu yapar: Dolguyu ortalamak için out_of_bounds_policy="pad" ile fn.crop kullan
  • Dikdörtgen modu yok: DALI işlem hatları sabit boyutlu çıktılar üretir (ör. 640×640). Değişken boyutlu çıktılar üreten auto=True dikdörtgen modu (ör. 384×640) desteklenmez. TensorRT dinamik giriş şekillerini desteklese de, en yüksek aktarım hızı için sabit boyutlu bir DALI işlem hattı sabit boyutlu bir motorla doğal biçimde eşleşir.
  • Birden fazla örnekle bellek kullanımı: Triton'da count > 1 olacak şekilde instance_group kullanmak yüksek bellek kullanımına yol açabilir. DALI modeli için varsayılan örnek grubunu kullan.

Sık Sorulan Sorular#

  • Avantaj, işlem hattına bağlıdır. GPU çıkarımı TensorRT ile zaten hızlıyken, 2-10 ms süren CPU ön işlemesi en büyük maliyet hâline gelebilir. DALI, ön işlemeyi GPU'da çalıştırarak bu darboğazı ortadan kaldırır. En büyük kazanımlar yüksek çözünürlüklü girişlerde (1080p, 4K), büyük grup boyutlarında ve GPU başına CPU çekirdeği sayısı sınırlı olan sistemlerde görülür.

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

  • fn.pad, dolguyu yalnızca sağ ve alt kenarlara ekler. out_of_bounds_policy="pad" ile birlikte kullanılan fn.crop, görüntüyü ortalar ve dolguyu her kenara simetrik olarak ekler; böylece Ultralytics LetterBox(center=True) davranışıyla eşleşir.

  • Neredeyse aynı. OpenCV'nin cv2.INTER_LINEAR ayarıyla eşleşmesi için fn.resize içinde antialias=False değerini ayarla. GPU ve CPU aritmetiği arasındaki fark nedeniyle küçük kayan nokta 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 işlem hattı yaklaşımı yerine, GPU üzerinde OpenCV gibi operatör başına denetim sağlar. CV-CUDA'nın cvcuda.copymakeborder() özelliği, her kenar için açık dolgu ayarını destekleyerek ortalanmış letterbox işlemini kolaylaştırır. İşlem hattı tabanlı iş akışları (özellikle Triton ile) için DALI'ı, özel çıkarım kodunda ayrıntılı operatör düzeyinde denetim için CV-CUDA'yı seç.

Yorumlar