المعالجة المسبقة المسرّعة بوحدة GPU باستخدام NVIDIA DALI#
عند نشر نماذج Ultralytics YOLO في بيئة الإنتاج، غالبًا ما تصبح المعالجة المسبقة عنق الزجاجة. فبينما يمكن لـ TensorRT تنفيذ الاستدلال بالنموذج في بضعة أجزاء من الألف من الثانية، قد تستغرق المعالجة المسبقة المعتمدة على CPU (تغيير الحجم، وإضافة الهوامش، والتطبيع) من 2 إلى 10 مللي ثانية لكل صورة، خصوصًا عند الدقات العالية. تحل NVIDIA DALI (مكتبة تحميل البيانات) هذه المشكلة بنقل مسار المعالجة المسبقة بأكمله إلى GPU.
يرشدك هذا الدليل إلى إنشاء مسارات DALI تطابق بدقة المعالجة المسبقة في Ultralytics YOLO، ودمجها مع model.predict()، ومعالجة تدفقات الفيديو، ونشر الحل من البداية إلى النهاية باستخدام Triton Inference Server.
هذا الدليل موجّه إلى المهندسين الذين ينشرون نماذج YOLO في بيئات الإنتاج التي ثبت فيها أن المعالجة المسبقة على CPU تمثل عنق زجاجة — وغالبًا ما تكون هذه بيئات نشر TensorRT على وحدات GPU من NVIDIA، أو مسارات فيديو عالية الإنتاجية، أو إعدادات Triton Inference Server. إذا كنت تجري استدلالًا اعتياديًا باستخدام model.predict() ولا تواجه عنق زجاجة في المعالجة المسبقة، فسيؤدي مسار CPU الافتراضي المهمة بكفاءة.
- هل تبني مسار DALI؟ استخدم
fn.resize(mode="not_larger")+fn.crop(out_of_bounds_policy="pad")+fn.crop_mirror_normalizeلمحاكاة المعالجة المسبقة باستخدام letterbox في YOLO على GPU. - هل تدمج الحل مع Ultralytics؟ مرّر مخرجات DALI على هيئة
torch.Tensorإلىmodel.predict()— وستتخطى Ultralytics معالجة الصور المسبقة تلقائيًا. - هل تنشر باستخدام Triton؟ استخدم خلفية DALI مع مجموعة نماذج TensorRT لتنفيذ المعالجة المسبقة دون استخدام CPU.
لماذا تستخدم 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 |
المتطلبات الأساسية#
تدعم NVIDIA DALI نظام Linux فقط. وهي غير متاحة على Windows أو macOS.
ثبّت الحزم المطلوبة:
pip install ultralytics
pip install --extra-index-url https://pypi.nvidia.com nvidia-dali-cuda130المتطلبات:
- وحدة GPU من NVIDIA (قدرة حوسبة 5.0+ / Maxwell أو أحدث)
- CUDA 11.0+ أو 12.0+ أو 13.0+
- Python 3.10-3.14
- نظام تشغيل Linux
فهم المعالجة المسبقة في YOLO#
قبل إنشاء مسار DALI، يجدر فهم ما تنفّذه Ultralytics تحديدًا أثناء المعالجة المسبقة. الفئة الأساسية هي LetterBox في ultralytics/data/augment.py:
from ultralytics.data.augment import LetterBox
letterbox = LetterBox(
new_shape=(640, 640), # الحجم المستهدف
center=True, # توسيط الصورة (إضافة هوامش متساوية من الجانبين)
stride=32, # المحاذاة مع مقدار الخطوة
padding_value=114, # هوامش رمادية (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) إلى المساحة المتبقية للوصول إلى الحجم المستهدف - توسيط الصورة بحيث تتوزع الهوامش بالتساوي على الجانبين
مسار DALI لـ YOLO#
يحاكي مسار DALI الموصى به سلوك LetterBox(center=True) الافتراضي في Ultralytics، وهو السلوك المستخدم في استدلال YOLO الاعتيادي.
المسار المتمركز (موصى به، ويطابق LetterBox في Ultralytics)#
تحاكي هذه النسخة المعالجة المسبقة الافتراضية في Ultralytics بدقة، مع إضافة هوامش في الوسط، بما يطابق LetterBox(center=True):
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.
"""
# قراءة الصور وفك ترميزها على 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)
# تغيير الحجم مع الحفاظ على نسبة العرض إلى الارتفاع
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 (من دون تنعيم الحواف)
)
# إضافة هوامش في الوسط باستخدام fn.crop مع out_of_bounds_policy
# عندما يكون حجم القص أكبر من حجم الصورة، يوسّط fn.crop الصورة ويضيف هوامش متماثلة
padded = fn.crop(
resized,
crop=(target_size, target_size),
out_of_bounds_policy="pad",
fill_values=114, # قيمة الهوامش في YOLO
)
# التطبيع وتحويل تنسيق البيانات
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. يؤدي إغفال ذلك إلى فروق عددية طفيفة قد تؤثر في دقة النموذج.
تشغيل المسار#
# 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 مع التنبؤ في Ultralytics#
يمكنك تمرير موتر [PyTorch](https://ultralytics-translation-0.invalid معالج مسبقًا مباشرةً إلى model.predict(). عند تمرير torch.Tensor، تتخطى Ultralytics المعالجة المسبقة للصورة (letterbox، وBGR→RGB، وHWC→CHW، والتطبيع /255)، ولا تنفّذ سوى نقل البيانات إلى الجهاز وتحويل نوع البيانات قبل إرسالها إلى النموذج.
نظرًا إلى أن Ultralytics لا يمكنها الوصول إلى أبعاد الصورة الأصلية في هذه الحالة، تُعاد إحداثيات مربعات الكشف ضمن مساحة الصورة 640×640 بعد تطبيق letterbox. لإعادتها إلى إحداثيات الصورة الأصلية، استخدم scale_boxes، الذي يتعامل مع منطق التقريب الدقيق المستخدم في LetterBox:
from ultralytics.utils.ops import scale_boxes
# المربعات: موتر بالشكل (N, 4) بتنسيق xyxy، وإحداثياتها ضمن مساحة 640x640 بعد تطبيق letterbox
# تحجيم المربعات من مساحة letterbox (640, 640) إلى أبعاد الصورة الأصلية (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.004ms (أي ما يقارب الصفر)، مقارنةً بنحو ~1-10ms عند استخدام المعالجة المسبقة على CPU. يجب أن يكون الموتر بتنسيق BCHW، ومن النوع float32 (أو float16)، ومطبّعًا إلى [0, 1]. وستظل Ultralytics تتولى تلقائيًا نقل البيانات إلى الجهاز وتحويل نوع البيانات.
DALI مع تدفقات الفيديو#
لمعالجة الفيديو في الوقت الفعلي، استخدم fn.external_source لإدخال الإطارات من أي مصدر — OpenCV، أو GStreamer، أو مكتبات الالتقاط المخصصة:
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 وغيرهما.
frames = fn.external_source(device="cpu", name="input")
frames = fn.reshape(frames, layout="HWC")
# النقل إلى GPU والمعالجة المسبقة
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 outputTriton Inference Server مع DALI#
للنشر في بيئة الإنتاج، ادمج المعالجة المسبقة باستخدام DALI مع الاستدلال باستخدام TensorRT في خادم Triton للاستدلال باستخدام نموذج تجميعي. يؤدي ذلك إلى إلغاء المعالجة المسبقة على CPU بالكامل — تدخل بايتات JPEG الخام وتخرج النتائج المكتشفة، مع إجراء كل المعالجة على GPU.
بنية مستودع النماذج#
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الخطوة 1: إنشاء مسار DALI#
سلسِل مسار DALI لاستخدامه مع الواجهة الخلفية لـ Triton DALI:
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."""
# الإدخال: بايتات الصور المشفّرة الخام من 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
# تسلسل المسار إلى مستودع النماذج
pipe = triton_dali_pipeline()
pipe.serialize(filename="model_repository/dali_preprocessing/1/model.dali")الخطوة 2: تصدير YOLO إلى TensorRT#
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 (N, 300, 6)؛ TensorRT >= 8.5
# تضيف Ultralytics ترويسة بيانات وصفية إلى ملفات .engine؛ أزلها كي يتمكن Triton من تحميل خطة TensorRT الخام
with open(engine_path, "rb") as f:
meta_len = int.from_bytes(f.read(4), byteorder="little") # طول ترويسة بيانات JSON الوصفية
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)الخطوة 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"
}
}
]
}يربط النموذج التجميعي النماذج عبر أسماء موترات افتراضية. تتطابق القيمة output_map مع "preprocessed_image" في خطوة DALI مع القيمة input_map مع "preprocessed_image" في خطوة TensorRT. هذه أسماء اعتباطية تربط مخرجات إحدى الخطوات بمدخلات الخطوة التالية — ولا يلزم أن تطابق أسماء الموترات الداخلية لأي نموذج.
الخطوة 4: إرسال طلبات الاستدلال#
تتضمن Ultralytics دعمًا مدمجًا لـ Triton يتولى المعالجة المسبقة واللاحقة تلقائيًا. لكن هذا الدعم لا يعمل مع النموذج التجميعي لـ DALI، لأن YOLO() يرسل موترًا من النوع float32 تمت معالجته مسبقًا، بينما يتوقع النموذج التجميعي بايتات JPEG خامًا. استخدم tritonclient مباشرةً مع النماذج التجميعية لـ DALI، واستخدم التكامل المدمج لعمليات النشر المعتادة التي لا تستخدم 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 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 إلى Triton، أضف حشوًا إلى جميع مصفوفات البايتات المشفّرة لتوحيد أطوالها (بحيث يساوي كل طول أكبر عدد بايتات في المجموعة). يتطلب Triton أن تكون أشكال دفعة موتر الإدخال متجانسة.
المهام المدعومة#
تعمل المعالجة المسبقة باستخدام DALI مع جميع مهام YOLO التي تستخدم مسار LetterBox القياسي:
| المهمة | مدعوم | ملاحظات |
|---|---|---|
| الكشف | ✅ | معالجة مسبقة قياسية باستخدام letterbox |
| تجزئة المثيلات | ✅ | المعالجة المسبقة نفسها المستخدمة للكشف |
| التجزئة الدلالية | ✅ | معالجة الصور المسبقة نفسها المستخدمة للكشف |
| تقدير العمق | ✅ | معالجة الصور المسبقة نفسها المستخدمة للكشف |
| التصنيف | ❌ | يستخدم تحويلات torchvision (اقتصاصًا مركزيًا)، وليس letterbox |
| تقدير الوضعيات | ✅ | المعالجة المسبقة نفسها المستخدمة للكشف |
| الكشف الموجّه (OBB) | ✅ | المعالجة المسبقة نفسها المستخدمة للكشف |
القيود#
- Linux فقط: لا يدعم DALI نظامَي Windows أو macOS
- يلزم وجود NVIDIA GPU: لا يتوفر بديل يعمل على CPU فقط
- مسار ثابت: تُحدَّد بنية المسار عند إنشائه ولا يمكن تغييرها ديناميكيًا
- يضيف
fn.padالحشو إلى اليمين والأسفل فقط: استخدمfn.cropمعout_of_bounds_policy="pad"لتوسيط الحشو - لا يتوفر وضع rect: تنتج مسارات DALI مخرجات بأبعاد ثابتة (مثل 640×640). وضع rect في
auto=True، الذي ينتج مخرجات متغيرة الأبعاد (مثل 384×640)، غير مدعوم. تجدر الإشارة إلى أن TensorRT يدعم أشكال إدخال ديناميكية، لكن إقران مسار DALI ثابت الأبعاد بمحرك ثابت الأبعاد يتيح تحقيق أقصى معدل نقل - الذاكرة عند استخدام مثيلات متعددة: قد يؤدي استخدام
instance_groupمعcount> 1 في Triton إلى استهلاك مرتفع للذاكرة. استخدم مجموعة المثيلات الافتراضية لنموذج DALI
الأسئلة الشائعة#
تعتمد الفائدة على مسار المعالجة لديك. عندما يكون الاستدلال على GPU سريعًا بالفعل باستخدام TensorRT، قد تصبح المعالجة المسبقة على CPU، التي تستغرق 2-10ms، العامل الأكبر في زمن المعالجة. يزيل DALI هذا الاختناق بإجراء المعالجة المسبقة على GPU. وتظهر أكبر المكاسب مع المدخلات عالية الدقة (1080p و4K)، وأحجام الدفعات الكبيرة، والأنظمة التي تحتوي على عدد محدود من أنوية CPU لكل GPU.
نعم. استخدم
DALIGenericIteratorللحصول على مخرجاتtorch.Tensorبعد معالجتها مسبقًا، ثم مرّرها إلىmodel.predict(). لكن أكبر فائدة للأداء تتحقق مع نماذج TensorRT، حيث يكون الاستدلال سريعًا بالفعل وتصبح المعالجة المسبقة على CPU عنق الزجاجة.يضيف
fn.padحشوًا إلى الحافتين اليمنى والسفلية فقط. أماfn.cropمعout_of_bounds_policy="pad"فيوسّط الصورة ويضيف حشوًا متماثلًا إلى جميع الجوانب، بما يطابق سلوكLetterBox(center=True)في Ultralytics.النتائج متطابقة تقريبًا. اضبط
antialias=Falseفيfn.resizeلمطابقةcv2.INTER_LINEARفي OpenCV. قد تظهر فروق طفيفة في الفاصلة العائمة (< 0.001) بسبب اختلاف العمليات الحسابية على GPU وCPU، لكنها لا تؤثر بشكل قابل للقياس في دقة الكشف.CV-CUDA مكتبة أخرى من NVIDIA لمعالجة الرؤية المعزّزة بـ GPU. وهي تتيح التحكم بكل مُشغّل على حدة (مثل OpenCV، ولكن على GPU)، بدلًا من نهج المسارات الذي يتبعه DALI. يدعم
cvcuda.copymakeborder()في CV-CUDA تحديد الحشو لكل جانب على حدة، ما يجعل توسيط letterbox مباشرًا. اختر DALI لسير العمل القائم على المسارات (خصوصًا مع Triton)، واختر CV-CUDA للتحكم الدقيق على مستوى المُشغّلات في شيفرة الاستدلال المخصصة.