Link to this sectionالمعالجة الأولية المسرعة بواسطة GPU باستخدام NVIDIA DALI#
Link to this sectionمقدمة#
عند نشر نماذج Ultralytics YOLO في بيئات الإنتاج، غالباً ما تصبح المعالجة الأولية هي عنق الزجاجة. بينما يمكن لـ TensorRT تشغيل الاستدلال للنموذج في بضعة أجزاء من الألف من الثانية، فإن المعالجة الأولية المستندة إلى CPU (تغيير الحجم، الحشو، التطبيع) يمكن أن تستغرق 2-10 مللي ثانية لكل صورة، خاصة عند الدقات العالية. تحل NVIDIA DALI (مكتبة تحميل البيانات) هذه المشكلة عن طريق نقل خط معالجة البيانات بالكامل إلى الـ GPU.
يرشدك هذا الدليل خلال بناء خطوط معالجة DALI التي تكرر بدقة المعالجة الأولية لـ Ultralytics YOLO، ودمجها مع model.predict()، ومعالجة تدفقات الفيديو، والنشر الشامل باستخدام Triton Inference Server.
هذا الدليل مخصص للمهندسين الذين ينشرون نماذج YOLO في بيئات الإنتاج حيث تُعد معالجة CPU الأولية عنق زجاجة ملموساً — عادة في عمليات نشر TensorRT على NVIDIA GPUs، أو خطوط معالجة الفيديو ذات الإنتاجية العالية، أو إعدادات Triton Inference Server. إذا كنت تقوم بتشغيل استدلال قياسي باستخدام model.predict() ولا تواجه عنق زجاجة في المعالجة الأولية، فإن خط معالجة CPU الافتراضي يعمل بشكل جيد.
- هل تبني خط معالجة DALI؟ استخدم
fn.resize(mode="not_larger")+fn.crop(out_of_bounds_policy="pad")+fn.crop_mirror_normalizeلتكرار معالجة YOLO التي تعتمد على letterbox على GPU. - هل تدمجها مع Ultralytics؟ مرر مخرجات DALI كـ
torch.Tensorإلىmodel.predict()— سيتخطى Ultralytics المعالجة الأولية للصورة تلقائياً. - هل تنشر باستخدام Triton؟ استخدم الواجهة الخلفية لـ DALI مع مجموعة TensorRT لتحقيق معالجة أولية بدون CPU.
Link to this sectionلماذا تستخدم DALI لمعالجة YOLO الأولية؟#
في خط استدلال YOLO النموذجي، يتم تشغيل خطوات المعالجة الأولية على CPU:
- فك تشفير الصورة (JPEG/PNG)
- تغيير الحجم مع الحفاظ على نسبة العرض إلى الارتفاع
- الحشو إلى الحجم المستهدف (letterbox)
- تطبيع قيم البكسل من
[0, 255]إلى[0, 1] - تحويل التنسيق من HWC إلى CHW
مع DALI، تعمل كل هذه العمليات على GPU، مما يلغي عنق زجاجة الـ CPU. هذا قيم بشكل خاص عندما:
| السيناريو | لماذا يساعد DALI |
|---|---|
| استدلال GPU سريع | محركات TensorRT ذات استدلال أقل من مللي ثانية تجعل معالجة CPU الأولية هي التكلفة المهيمنة |
| مدخلات عالية الدقة | تتطلب تدفقات الفيديو 1080p و 4K عمليات تغيير حجم مكلفة |
| أحجام دفعات كبيرة | معالجة الاستدلال من جانب الخادم للعديد من الصور بالتوازي |
| أنوية CPU محدودة | أجهزة الحافة مثل NVIDIA Jetson، أو خوادم GPU الكثيفة التي تحتوي على عدد قليل من أنوية CPU لكل GPU |
Link to this sectionالمتطلبات الأساسية#
يدعم NVIDIA DALI نظام Linux فقط. إنه غير متاح على Windows أو macOS.
قم بتثبيت الحزم المطلوبة:
pip install ultralytics
pip install --extra-index-url https://pypi.nvidia.com nvidia-dali-cuda130المتطلبات:
- NVIDIA GPU (قدرة حوسبة 5.0+ / Maxwell أو أحدث)
- CUDA 11.0+, 12.0+ أو 13.0+
- Python 3.10-3.14
- نظام تشغيل Linux
Link to this sectionفهم معالجة YOLO الأولية#
قبل بناء خط معالجة DALI، من المفيد فهم ما يفعله Ultralytics بالضبط أثناء المعالجة الأولية. الفئة الرئيسية هي LetterBox في ultralytics/data/augment.py:
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 هذه الخطوات:
| الخطوة | العملية | وظيفة CPU | بديل DALI |
|---|---|---|---|
| 1 | تغيير حجم Letterbox | cv2.resize | fn.resize(mode="not_larger") |
| 2 | حشو متمركز | 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 | np.transpose + tensor / 255 | fn.crop_mirror_normalize(std=[255,255,255]) |
تحافظ عملية letterbox على نسبة العرض إلى الارتفاع عن طريق:
- حساب المقياس:
r = min(target_h / h, target_w / w) - تغيير الحجم إلى
(round(w * r), round(h * r)) - حشو المساحة المتبقية باللون الرمادي (
114) للوصول إلى الحجم المستهدف - توسيط الصورة بحيث يتم توزيع الحشو بالتساوي على كلا الجانبين
Link to this sectionخط معالجة DALI لـ YOLO#
استخدم الخط المتمركز أدناه كمرجع افتراضي. إنه يطابق سلوك LetterBox(center=True) في Ultralytics، وهو ما يستخدمه استدلال YOLO القياسي.
Link to this sectionالخط المتمركز (موصى به، يطابق Ultralytics LetterBox)#
هذا الإصدار يكرر بدقة المعالجة الأولية الافتراضية لـ Ultralytics مع حشو متمركز، مطابقة لـ LetterBox(center=True):
import nvidia.dali as dali
import nvidia.dali.fn as fn
import nvidia.dali.types as 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إذا كنت لا تحتاج إلى تطابق تام مع LetterBox(center=True)، يمكنك تبسيط خطوة الحشو باستخدام fn.pad(...) بدلاً من fn.crop(..., out_of_bounds_policy="pad"). ذلك البديل يقوم بحشو الحواف اليمنى والسفلية فقط، وهو ما قد يكون مقبولاً لخطوط نشر مخصصة، ولكنه لن يطابق سلوك letterbox المتمركز الافتراضي لـ Ultralytics بدقة.
مشغل fn.pad في DALI يضيف الحشو فقط إلى الحواف اليمنى والسفلية. للحصول على حشو متمركز (مطابق لـ LetterBox(center=True) في Ultralytics)، استخدم fn.crop مع out_of_bounds_policy="pad". مع القيم الافتراضية crop_pos_x=0.5 و crop_pos_y=0.5، يتم توسيط الصورة تلقائياً مع حشو متماثل.
تعمل خاصية fn.resize في DALI على تمكين التنعيم افتراضياً (antialias=True)، بينما لا يقوم cv2.resize في OpenCV مع INTER_LINEAR بتطبيق التنعيم. اضبط دائماً antialias=False في DALI لمطابقة خط معالجة CPU. التغاضي عن هذا يسبب اختلافات رقمية دقيقة يمكن أن تؤثر على دقة النموذج.
Link to this sectionتشغيل خط المعالجة#
# 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}]")Link to this sectionاستخدام DALI مع Ultralytics Predict#
يمكنك تمرير موتر PyTorch تمت معالجته مسبقاً مباشرة إلى model.predict(). عند تمرير torch.Tensor، يتخطى Ultralytics المعالجة الأولية للصورة (letterbox، BGR→RGB، HWC→CHW، والتطبيع /255) ويقوم فقط بنقل الجهاز وتحويل نوع البيانات قبل إرساله إلى النموذج.
نظراً لأن Ultralytics لا يمكنه الوصول إلى أبعاد الصورة الأصلية في هذه الحالة، يتم إرجاع إحداثيات صندوق الكشف في مساحة letterbox بحجم 640×640. لتعيينها مرة أخرى إلى إحداثيات الصورة الأصلية، استخدم scale_boxes الذي يتعامل مع منطق التقريب الدقيق المستخدم بواسطة LetterBox:
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))ينطبق هذا على جميع مسارات المعالجة الأولية الخارجية — مدخل الموتر المباشر، تدفقات الفيديو، ونشر Triton.
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 إلى model.predict()، تستغرق خطوة معالجة الصورة ~0.004 مللي ثانية (صفرياً في الأساس) مقارنة بـ ~1-10 مللي ثانية مع معالجة CPU. يجب أن يكون الموتر بتنسيق BCHW، ونوع float32 (أو float16)، ومطبعاً إلى [0, 1]. سيستمر Ultralytics في التعامل مع نقل الجهاز وتحويل نوع البيانات تلقائياً.
Link to this sectionDALI مع تدفقات الفيديو#
لمعالجة الفيديو في الوقت الفعلي، استخدم fn.external_source لتغذية الإطارات من أي مصدر — OpenCV، أو GStreamer، أو مكتبات التقاط مخصصة:
import nvidia.dali as dali
import nvidia.dali.fn as fn
import nvidia.dali.types as 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 outputLink to this sectionTriton Inference Server مع DALI#
للنشر في الإنتاج، ادمج المعالجة الأولية لـ DALI مع استدلال TensorRT في Triton Inference Server باستخدام نموذج مجمع. هذا يلغي معالجة CPU الأولية تماماً — تدخل بايتات JPEG الخام وتخرج الكشوفات، مع معالجة كل شيء على GPU.
Link to this sectionهيكل مستودع النموذج#
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.pbtxtLink to this sectionالخطوة 1: إنشاء خط معالجة DALI#
تسلسل خط معالجة DALI للواجهة الخلفية لـ Triton DALI:
import nvidia.dali as dali
import nvidia.dali.fn as fn
import nvidia.dali.types as 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")Link to this sectionالخطوة 2: تصدير YOLO إلى TensorRT#
from ultralytics import YOLO
model = YOLO("yolo26n.pt")
model.export(format="engine", imgsz=640, half=True, batch=8)
# Copy the .engine file to model_repository/yolo_trt/1/model.planLink to this sectionالخطوة 3: تكوين Triton#
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) بربط النماذج من خلال أسماء الموترات الافتراضية (virtual tensor names). تطابق قيمة output_map التي تحمل الاسم "preprocessed_image" في خطوة DALI قيمة input_map التي تحمل الاسم "preprocessed_image" في خطوة TensorRT. هذه أسماء اختيارية تربط مخرجات خطوة واحدة بمدخلات الخطوة التالية — ولا يلزم أن تطابق أسماء الموترات الداخلية لأي نموذج.
Link to this sectionالخطوة 4: إرسال طلبات الاستدلال#
!!! info "لماذا tritonclient بدلاً من YOLO(\"http://...\")؟"
Ultralytics has [built-in Triton support](triton-inference-server.md#running-inference) that handles pre/postprocessing automatically. However, it won't work with the DALI ensemble because `YOLO()` sends a preprocessed float32 tensor while the ensemble expects raw JPEG bytes. Use `tritonclient` directly for DALI ensembles, and the [built-in integration](triton-inference-server.md) for standard deployments without DALI.
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 — YOLO26 is end-to-end)
detections = detections[0] # First image
detections = detections[detections[:, 4] > 0.25] # Confidence threshold
print(f"Detected {len(detections)} objects")عند إرسال دفعة من صور JPEG إلى Triton، قم بحشو (pad) جميع مصفوفات البايت المشفرة لتصل إلى نفس الطول (أقصى عدد للبايتات في الدفعة). يتطلب Triton أشكال دفعات متجانسة لموتر الإدخال.
Link to this sectionالمهام المدعومة#
يعمل المعالجة المسبقة باستخدام DALI مع جميع مهام YOLO التي تستخدم خط أنابيب LetterBox القياسي:
| المهمة | مدعوم | ملاحظات |
|---|---|---|
| الكشف | ✅ | المعالجة المسبقة القياسية بأسلوب Letterbox |
| تجزئة المثيل | ✅ | نفس المعالجة المسبقة كما في الكشف |
| التجزئة الدلالية | ✅ | نفس المعالجة المسبقة للصور كما في الكشف |
| تقدير الوضع | ✅ | نفس المعالجة المسبقة كما في الكشف |
| الكشف الموجه (OBB) | ✅ | نفس المعالجة المسبقة كما في الكشف |
| التصنيف | ❌ | يستخدم تحويلات torchvision (القص المركزي)، وليس Letterbox |
Link to this sectionالقيود#
- Linux فقط: لا يدعم DALI نظامي Windows أو macOS
- مطلوب NVIDIA GPU: لا يوجد بديل يعمل باستخدام CPU فقط
- خط أنابيب ثابت: يتم تحديد هيكل خط الأنابيب في وقت البناء ولا يمكن تغييره ديناميكيًا
fn.padيعمل لليمين/الأسفل فقط: استخدمfn.cropمعout_of_bounds_policy="pad"للحصول على حشو مركزي- لا يوجد وضع مستطيل (rect mode): تنتج خطوط أنابيب DALI مخرجات ثابتة الحجم (مثل 640×640). وضع المستطيل
auto=Trueالذي ينتج مخرجات متغيرة الحجم (مثل 384×640) غير مدعوم. لاحظ أنه على الرغم من أن TensorRT يدعم أشكال المدخلات الديناميكية، إلا أن خط أنابيب DALI ثابت الحجم يتوافق بشكل طبيعي مع محرك ثابت الحجم لتحقيق أقصى قدر من الإنتاجية - الذاكرة مع مثيلات متعددة: يمكن أن يؤدي استخدام
instance_groupمعcount> 1 في Triton إلى استخدام مرتفع للذاكرة. استخدم مجموعة المثيلات الافتراضية لنموذج DALI
Link to this sectionالأسئلة الشائعة#
Link to this sectionكيف تقارن سرعة المعالجة المسبقة لـ DALI بسرعة المعالجة على CPU؟#
تعتمد الفائدة على خط الأنابيب الخاص بك. عندما يكون استدلال GPU سريعًا بالفعل باستخدام TensorRT، يمكن أن تصبح المعالجة المسبقة على CPU التي تستغرق 2-10 مللي ثانية هي العائق الرئيسي. يقضي DALI على هذا الاختناق عن طريق تشغيل المعالجة المسبقة على GPU. تظهر أكبر المكاسب مع المدخلات عالية الدقة (1080p, 4K)، وأحجام الدفعات الكبيرة، والأنظمة ذات الأنوية المحدودة لـ CPU لكل GPU.
Link to this sectionهل يمكنني استخدام DALI مع نماذج PyTorch (ليس فقط TensorRT)؟#
نعم. استخدم DALIGenericIterator للحصول على مخرجات torch.Tensor المعالجة مسبقًا، ثم مررها إلى model.predict(). ومع ذلك، تكون فائدة الأداء أكبر مع نماذج TensorRT حيث يكون الاستدلال سريعًا جدًا بالفعل وتصبح المعالجة المسبقة على CPU هي العائق.
Link to this sectionما الفرق بين fn.pad و fn.crop للحشو؟#
تضيف fn.pad حشوًا فقط إلى الحواف اليمنى والسفلية. بينما تقوم fn.crop مع out_of_bounds_policy="pad" بتوسيط الصورة وإضافة الحشو بشكل متماثل على جميع الجوانب، مما يطابق سلوك LetterBox(center=True) الخاص بـ Ultralytics.
Link to this sectionهل ينتج DALI نتائج متطابقة بكسلًا بكسل مع المعالجة المسبقة على CPU؟#
متطابقة تقريبًا. اضبط antialias=False في fn.resize لتطابق cv2.INTER_LINEAR في OpenCV. قد تحدث اختلافات طفيفة في الفاصلة العائمة (< 0.001) بسبب حسابات GPU مقابل CPU، ولكن ليس لها تأثير ملموس على دقة الكشف.
Link to this sectionماذا عن CV-CUDA كبديل لـ DALI؟#
CV-CUDA هي مكتبة أخرى من NVIDIA لمعالجة الرؤية المتسارعة بواسطة GPU. إنها توفر تحكمًا لكل عامل (مثل OpenCV ولكن على GPU) بدلاً من نهج خط الأنابيب الخاص بـ DALI. تدعم cvcuda.copymakeborder() في CV-CUDA حشوًا صريحًا لكل جانب، مما يجعل Letterbox المركزي أمرًا مباشرًا. اختر DALI لسير العمل القائم على خط الأنابيب (خاصة مع Triton)، وCV-CUDA للتحكم الدقيق على مستوى العامل في كود الاستدلال المخصص.