Ultralytics YOLO27:

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

Ultralytics YOLO modellerini üretim ortamında dağıtırken, ön işleme çoğu zaman 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 Kütüphanesi), tüm ön işleme hattını GPU'ya taşıyarak bu sorunu çözer.

Bu kılavuzda, Ultralytics YOLO ön işlemesini birebir yineleyen DALI hatları oluşturmayı, bunları model.predict() ile entegre etmeyi, 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ühendisler için hazırlandı — genellikle NVIDIA GPU'larda TensorRT dağıtımları, yüksek verimli video hatları veya Triton Inference Server kurulumları için. model.predict() ile standart çıkarım yapıyorsan ve ön işleme darboğazın yoksa varsayılan CPU hattı iyi çalışır.

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

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

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

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

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

SenaryoDALI'nin Sağladığı Faydalar
Hızlı GPU çıkarımıMilisaniyenin altında çıkarım yapan TensorRT motorları, CPU ön işlemesini en büyük maliyet hâline getirir.
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ünün paralel olarak işlendiği çıkarım
Sınırlı 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 kapasitesi 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 Anlama#

DALI hattı 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 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,  # Adım aralığına hizalama
    padding_value=114,  # Gri dolgu (114, 114, 114)
)

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

AdımİşlemCPU İşleviDALI Karşılığı
1Letterbox yeniden boyutlandırmacv2.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 ile normalleştirmenp.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 boşluğa gri dolgu ekleme (114)
  4. Dolgunun her iki tarafa eşit dağılması için görüntüyü ortalama

YOLO için DALI Hattı#

Önerilen DALI hattı, standart YOLO çıkarımında kullanılan Ultralytics'in varsayılan LetterBox(center=True) davranışını yineler.

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

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

Ortalanmış 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.
    """
    # 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ı koruyarak yeniden boyutlandır
    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ş (örtüşme önleme yok)
    )

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

    # Normalleştir ve düzeni 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 yeterli olur?

Birebir LetterBox(center=True) eşleşmesine ihtiyacın yoksa dolgu adımını fn.crop(..., out_of_bounds_policy="pad") yerine fn.pad(...) kullanarak basitleştirebilirsin. Bu seçenek yalnızca sağ ve alt kenarlara dolgu ekler; özel dağıtım hatları için uygun olabilir, ancak Ultralytics'in varsayılan ortalanmış letterbox davranışıyla birebir eşleşmez.

Ortalanmış dolgu için neden `fn.crop` kullanmalı?

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şmesi) 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 ile görüntü, simetrik dolgu eklenerek otomatik olarak ortalanır.

Örtüşme Önleme Uyumsuzluğu

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

Hattı Çalıştırma#

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#

Önceden işlenmiş bir PyTorch tensörünü doğrudan model.predict() öğesine aktarabilirsin. Bir torch.Tensor aktarıldığında Ultralytics görüntü ön işlemesini atlar (letterbox, BGR→RGB, HWC→CHW ve /255 ile normalleştirme) ve tensörü modele göndermeden önce yalnızca cihaz aktarımı ve veri türü dönüştürme işlemlerini yapar.

Bu durumda Ultralytics özgün görüntü boyutlarına erişemediğinden algılama kutusu koordinatları 640×640 letterbox alanında döndürülür. Bunları özgün görüntü koordinatlarına dönüştürmek için LetterBox tarafından kullanılan yuvarlama mantığını birebir uygulayan scale_boxes öğesini kullan:

from ultralytics.utils.ops import scale_boxes

# boxes: xyxy biçiminde, 640x640 letterbox koordinatlarında, (N, 4) şekilli tensör
# Kutuları letterbox uygulanmış (640, 640) boyutundan ö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ı dâhil 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şlemesindeki ~1-10 ms ile karşılaştırıldığında ~0.004 ms sürer (neredeyse sıfır). Tensör BCHW biçiminde, float32 (veya float16) türünde olmalı ve [0, 1] değerine göre normalleştirilmelidir. Ultralytics, cihaz aktarımı ve veri türü dönüştürme işlemlerini yine otomatik olarak yapar.

Video Akışlarında DALI#

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

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."""
    # OpenCV, GStreamer vb. kaynaklardan kareleri 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şlemeyi, bir ensemble modeli kullanarak Triton Inference Server içindeki [TensorRT](https://ultralytics-translation-0.invalid çıkarımıyla birleştir. Böylece CPU ön işlemesi tamamen ortadan kalkar: ham JPEG baytları girer, tespitler çıkar ve her şey GPU'da 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: DALI Pipeline'ını Oluştur#

Triton DALI backend'i için DALI pipeline'ını serileştir:

Triton için DALI pipeline'ı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

# Pipeline'ı model deposuna serileştir
pipe = triton_dali_pipeline()
pipe.serialize(filename="model_repository/dali_preprocessing/1/model.dali")

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

YOLO modelini TensorRT motoruna 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ının başına bir meta veri başlığı ekler; Triton'un ham TensorRT planını yükleyebilmesi için bu 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: 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 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 rastgele adlardır; herhangi bir modelin dahili tensör adlarıyla eşleşmeleri gerekmez.

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

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

Ultralytics, ön/son işlemeyi otomatik olarak yapan yerleşik Triton desteğine sahiptir. Ancak YOLO(), ön işlemeden geçirilmiş bir float32 tensörü gönderirken ensemble ham JPEG baytları beklediğinden DALI ensemble'ıyla çalışmaz. DALI ensemble'ları için doğrudan tritonclient kullan; DALI içermeyen standart dağıtımlar içinse 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üleri Gruplama

Bir JPEG görüntü grubunu Triton'a gönderirken kodlanmış tüm bayt dizilerini aynı uzunluğa (gruptaki en yüksek bayt sayısına) tamamla. Triton, giriş tensöründe aynı şekle sahip öğeler gerektirir.

Desteklenen Görevler#

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

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

Sınırlamalar#

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

Sık Sorulan Sorular#

  • Sağladığı fayda pipeline'ına bağlıdır. GPU çıkarımı TensorRT ile zaten hızlıysa, CPU ön işlemesinin 2-10 ms sürmesi baskın maliyet hâline gelebilir. DALI, ön işlemeyi GPU'da yaparak bu darboğazı ortadan kaldırır. En büyük kazanımlar yüksek çözünürlüklü girdilerde (1080p, 4K), büyük grup boyutlarında ve GPU başına CPU çekirdeği sayısı sınırlı sistemlerde görülür.

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

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

  • Neredeyse aynı. OpenCV'nin cv2.INTER_LINEAR değerini eşleştirmek için fn.resize içinde antialias=False ayarını yap. GPU ve CPU aritmetiğinden kaynaklanan küçük kayan nokta farklılıkları (< 0.001) görülebilir 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'nin pipeline yaklaşımı yerine, OpenCV gibi operatör başına denetim sağlar, ancak GPU'da çalışır. CV-CUDA'nın cvcuda.copymakeborder() özelliği, her kenar için açıkça dolgu ayarlamayı desteklediğinden ortalanmış letterbox uygulaması kolaydır. Pipeline tabanlı iş akışları için (özellikle Triton ile) DALI'yi, özel çıkarım kodunda ayrıntılı operatör düzeyinde denetim içinse CV-CUDA'yı seç.

Yorumlar