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, 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.
- 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_normalizekullan. - Ultralytics ile mi entegre ediyorsun? DALI çıktısını
torch.Tensorolarakmodel.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:
- Görüntüyü çöz (JPEG/PNG)
- En-boy oranını koruyarak yeniden boyutlandır
- Hedef boyuta dolgu ekle (letterbox)
- Piksel değerlerini
[0, 255]ile[0, 1]arasında normalleştir - 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:
| Senaryo | DALI'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ü 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ünün paralel olarak işlendiği çıkarım |
| Sınırlı 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 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 | İşlem | CPU İşlevi | DALI Karşılığı |
|---|---|---|---|
| 1 | Letterbox yeniden boyutlandırma | 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 ile normalleştirme | 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 boşluğa gri dolgu ekleme (
114) - 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:
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 outputBirebir 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.
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.
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#
# 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.
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ş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ı:
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 outputDALI 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.pbtxtAdım: DALI Pipeline'ını Oluştur#
Triton DALI backend'i 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#
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, 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#
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.
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")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örev | Desteklenir | Notlar |
|---|---|---|
| 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.padyalnızca sağa/alta dolgu ekler: Ortalanmış dolgu içinout_of_bounds_policy="pad"ilefn.cropkullan- 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=Truedikdö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:
countdeğerini 1'den büyük olacak şekildeinstance_groupile 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çinDALIGenericIteratorkullan, 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.padyalnızca sağ ve alt kenarlara dolgu ekler.out_of_bounds_policy="pad"ile birliktefn.crop, görüntüyü ortalar ve tüm kenarlara simetrik dolgu ekleyerek UltralyticsLetterBox(center=True)davranışıyla eşleşir.Neredeyse aynı. OpenCV'nin
cv2.INTER_LINEARdeğerini eşleştirmek içinfn.resizeiçindeantialias=Falseayarı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ç.