YOLO Vision 2026:

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

Üretim ortamında Ultralytics YOLO modellerini dağıtırken ön işleme genellikle 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, doldurma, 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 işlem hattını GPU'ya taşıyarak bu sorunu çözer.

Bu kılavuzda, Ultralytics YOLO ön işlemesini birebir yineleyen DALI işlem hatlarının nasıl oluşturulacağı, bunların model.predict() ile nasıl entegre edileceği, video akışlarının nasıl işleneceği ve Triton Inference Server ile uçtan uca nasıl dağıtılacağı açıklanır.

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çindir — genellikle NVIDIA GPU'larda TensorRT dağıtımları, yüksek hacimli video işlem hatları veya Triton Inference Server kurulumları için. model.predict() ile standart çıkarım yapıyor ve bir ön işleme darboğazın yoksa varsayılan CPU işlem hattı iyi çalışır.

Kısa Özet
  • DALI işlem 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ıyorsun? Sıfır CPU ön işlemesi için TensorRT topluluğuyla birlikte DALI arka ucunu kullan.

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

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

  1. Görüntünün kodunu çöz (JPEG/PNG)
  2. En-boy oranını koruyarak yeniden boyutlandır
  3. Hedef boyuta doldur (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 üzerinde çalışır ve CPU darboğazı ortadan kalkar. Bu özellikle şu durumlarda değerlidir:

SenaryoDALI Nasıl Yardımcı Olur?
Hızlı GPU çıkarımıMilisaniyenin altında çıkarım yapan TensorRT motorlarında baskın maliyet CPU ön işlemesidir
Yüksek çözünürlüklü girdiler1080p ve 4K video akışları maliyetli yeniden boyutlandırma işlemleri gerektirir
Büyük batch boyutlarıSunucu tarafı çıkarımında çok sayıda görüntü paralel olarak işlenir
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#

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#

Bir DALI işlem 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),  # 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 işlem hattı şu adımları gerçekleştirir:

AdımİşlemCPU İşleviDALI Karşılığı
1Letterbox yeniden boyutlandırmacv2.resizefn.resize(mode="not_larger")
2Ortalanmış doldurmacv2.copyMakeBorderfn.crop(out_of_bounds_policy="pad")
3BGR → RGBim[..., ::-1]fn.decoders.image(output_type=types.RGB)
4HWC → CHW + /255 normalleştirmesinp.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ı gri (114) ile doldurma
  4. Doldurmanın her iki tarafa eşit dağıtılması için görüntüyü ortalama

YOLO için DALI İşlem Hattı#

Önerilen DALI işlem hattı, standart YOLO çıkarımının kullandığı varsayılan Ultralytics LetterBox(center=True) davranışını yineler.

Ortalanmış İşlem Hattı (Önerilen, Ultralytics LetterBox ile eşleşir)#

Bu sürüm, ortalanmış doldurma kullanarak varsayılan Ultralytics ön işlemesini tam olarak yineler 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.
    """
    # 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 LetterBox(center=True) uyumluluğuna ihtiyacın yoksa fn.crop(..., out_of_bounds_policy="pad") yerine fn.pad(...) kullanarak doldurma adımını basitleştirebilirsin. Bu varyant yalnızca sağ ve alt kenarları doldurur; özel dağıtım işlem hatları için kabul edilebilir olabilir, ancak Ultralytics'in varsayılan ortalanmış letterbox davranışıyla tam olarak eşleşmez.

Ortalanmış doldurma için neden `fn.crop` kullanılmalı?

DALI'nin fn.pad operatörü yalnızca sağ ve alt kenarlara doldurma ekler. Ortalanmış doldurma 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 doldurma uygulanarak otomatik olarak ortalanır.

Antialias Uyumsuzluğu

DALI'nin fn.resize operatörü varsayılan olarak antialiasing'i (antialias=True) etkinleştirirken, OpenCV'nin cv2.resize operatörü INTER_LINEAR ile birlikte antialiasing uygulamaz. CPU işlem hattıyla eşleşmesi için DALI'de her zaman antialias=False değerini ayarla. Bunu atlamak, model doğruluğunu etkileyebilecek küçük sayısal farklılıklara yol açar.

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

DALI işlem hattı oluşturma ve çalıştırma
# 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 aktarabilirsin. Bir torch.Tensor aktarıldığında Ultralytics görüntü ön işlemesini atlar (letterbox, BGR→RGB, HWC→CHW ve /255 normalleştirmesi) ve modele göndermeden önce yalnızca cihaz aktarımı ile veri türü dönüşümünü gerçekleştirir.

Ultralytics bu durumda ö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 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

# 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 durum tüm harici ön işleme yolları için geçerlidir — doğrudan tensör girdisi, video akışları ve Triton dağıtımı.

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ü

Bir 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 (esasen sıfır). Tensör BCHW biçiminde, float32 (veya float16) türünde olmalı ve [0, 1] biçiminde normalleştirilmelidir. Ultralytics cihaz aktarımını ve veri türü dönüşümünü yine otomatik olarak gerçekleştirir.

DALI ile Video Akışları#

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

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."""
    # 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 dağıtımı için DALI ön işlemesini Triton Inference Server içinde TensorRT çıkarımıyla bir topluluk modeli kullanarak birleştir. Böylece CPU ön işlemesi tamamen ortadan kalkar — ham JPEG baytları girer, algılamalar çıkar ve tüm işlemler GPU üzerinde gerçekleştirilir.

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

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

Triton için DALI işlem hattını serileştirme
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: YOLO'yu TensorRT'ye Aktarma#

YOLO modelini TensorRT motoruna aktarma
from ultralytics import YOLO

model = YOLO("yolo26n.pt")
model.export(
    format="engine", imgsz=640, quantize=16, batch=8, dynamic=True, nms=False
)  # NMS-free (N, 300, 6); TensorRT >= 8.5
# Copy the .engine file to model_repository/yolo_trt/1/model.plan

Adım: Triton'u Yapılandırma#

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şlemesi Nasıl Çalışır?

Topluluk, 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: Çıkarım İstekleri Gönderme#

`tritonclient` neden `YOLO('http://...')` yerine kullanılmalı?

Ultralytics, ön işleme ve 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 topluluk ham JPEG baytlarını beklediğinden DALI topluluğuyla çalışmaz. DALI toplulukları için doğrudan tritonclient öğesini, DALI olmadan yapılan standart dağıtımlar içinse yerleşik entegrasyonu kullan.

Triton topluluğuna görüntü gönderme
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 Gruplu İşleme

Triton'a bir JPEG görüntü grubu gönderirken, kodlanmış tüm bayt dizilerini aynı uzunluğa (gruptaki maksimum bayt sayısına) doldur. Triton, girdi tensörü için homojen grup şekilleri gerektirir.

Desteklenen Görevler#

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

GörevDesteklenirNotlar
AlgılamaStandart letterbox ön işleme
Örnek SegmentasyonuTespit ile aynı ön işleme
Anlamsal SegmentasyonTespit ile aynı görüntü ön işlemesi
Sınıflandırmaletterbox yerine torchvision dönüşümlerini (merkez kırpma) kullanır
Poz TahminiTespit ile aynı ön işleme
Yönlendirilmiş Tespit (OBB)Tespit ile aynı ön işleme

Sınırlamalar#

  • Yalnızca Linux: DALI, Windows veya macOS'u desteklemez
  • NVIDIA GPU gereklidir: Yalnızca CPU kullanımı için 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 ekler: Ortalanmış dolgu için out_of_bounds_policy="pad" ile birlikte fn.crop kullanın
  • Rect modu yoktur: DALI işlem hatları sabit boyutlu çıktılar üretir (ör. 640×640). Değişken boyutlu çıktılar (ör. 384×640) üreten auto=True rect modu desteklenmez. TensorRT dinamik giriş şekillerini desteklese de maksimum aktarım hızı için sabit boyutlu bir DALI işlem hattının sabit boyutlu bir motorla doğal olarak eşleştiğini unutmayın
  • Birden çok örnekle bellek kullanımı: 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ın

SSS#

  • Avantaj, işlem hattınıza bağlıdır. GPU çıkarımı TensorRT ile zaten hızlı olduğunda, 2-10 ms süren CPU ön işlemesi baskın 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ü girdilerde (1080p, 4K), büyük toplu iş boyutlarında ve GPU başına sınırlı sayıda CPU çekirdeğine sahip sistemlerde görülür.

  • Evet. Ön işlemesi yapılmış torch.Tensor çıktılarını almak için DALIGenericIterator kullanın ve ardından bunları model.predict() öğesine aktarın. 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 büyüktür.

  • 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 her tarafa simetrik olarak dolgu ekleyerek Ultralytics LetterBox(center=True) davranışıyla eşleşir.

  • Neredeyse aynı sonuçları üretir. OpenCV'nin cv2.INTER_LINEAR davranışıyla eşleşmesi için fn.resize içinde antialias=False değerini ayarlayın. GPU ve CPU aritmetiği arasındaki farklar 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 kullanılan başka bir NVIDIA kütüphanesidir. DALI'nin işlem hattı yaklaşımı yerine, OpenCV gibi ancak GPU üzerinde operatör başına denetim sağlar. CV-CUDA'nın cvcuda.copymakeborder() öğesi, taraf başına açık dolgu ayarını destekleyerek ortalanmış letterbox işlemini kolaylaştırır. İşlem hattı tabanlı iş akışları (özellikle Triton) için DALI'yi, özel çıkarım kodunda ince ayrıntılı operatör düzeyinde denetim için CV-CUDA'yı seçin.

Yorumlar