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, 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.
- 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_normalizekullan. - Ultralytics ile mi tümleştiriyorsun? DALI çıktısını
torch.Tensorolarakmodel.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:
- Görüntüyü (JPEG/PNG) çöz
- En-boy oranını koruyarak yeniden boyutlandır
- Hedef boyuta dolgu uygula (letterbox)
- Piksel değerlerini
[0, 255]ile[0, 1]arasında normalize et - 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:
| Senaryo | DALI Neden Faydalı? |
|---|---|
| GPU'da hızlı çıkarım | Milisaniyenin altında çıkarım yapan TensorRT motorlarında CPU ön işleme baskın maliyet haline gelir |
| Yüksek çözünürlüklü girdiler | 1080p 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ği | NVIDIA Jetson gibi uç cihazlar veya GPU başına az sayıda CPU çekirdeği olan yoğun GPU sunucuları |
Ön koşullar#
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-cuda130Gereksinimler:
- 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 | İşlem | CPU işlevi | DALI karşılığı |
|---|---|---|---|
| 1 | Letterbox yeniden boyutlandırması | cv2.resize | fn.resize(mode="not_larger") |
| 2 | Ortalanmış dolgu | cv2.copyMakeBorder | fn.crop(out_of_bounds_policy="pad") |
| 3 | BGR → RGB | im[..., ::-1] | fn.decoders.image(output_type=types.RGB) |
| 4 | HWC → CHW + /255 normalizasyonu | np.transpose + tensor / 255 | fn.crop_mirror_normalize(std=[255,255,255]) |
Letterbox işlemi, en-boy oranını şu şekilde korur:
- Ölçeği hesaplama:
r = min(target_h / h, target_w / w) (round(w * r), round(h * r))boyutuna yeniden boyutlandırma- Hedef boyuta ulaşmak için kalan alanı griyle (
114) doldurma - 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.
Ortalanmış İşlem Hattı (Önerilen, Ultralytics LetterBox ile eşleşir)#
Bu sürüm, ortalanmış dolgu kullanarak Ultralytics'in varsayılan ön işlemesini birebir tekrarlar ve LetterBox(center=True) ile eşleşir:
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 outputLetterBox(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.
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.
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#
# 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.
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")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:
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 outputDALI 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.pbtxtAdım 1: DALI İşlem Hattını Oluştur#
Triton DALI arka ucu 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#
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, 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#
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.
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")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örev | Desteklenir | Notlar |
|---|---|---|
| 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.padyalnızca sağa/alta dolgu yapar: Dolguyu ortalamak içinout_of_bounds_policy="pad"ilefn.cropkullan- Dikdörtgen modu yok: DALI işlem hatları sabit boyutlu çıktılar üretir (ör. 640×640). Değişken boyutlu çıktılar üreten
auto=Truedikdö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 şekildeinstance_groupkullanmak 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çinDALIGenericIteratorkullan, 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ılanfn.crop, görüntüyü ortalar ve dolguyu her kenara simetrik olarak ekler; böylece UltralyticsLetterBox(center=True)davranışıyla eşleşir.Neredeyse aynı. OpenCV'nin
cv2.INTER_LINEARayarıyla eşleşmesi içinfn.resizeiçindeantialias=Falsedeğ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ç.