NVIDIA によるGPUを活用した前処理
はじめに
展開する際 Ultralytics YOLO モデルを本番環境にデプロイする際、前処理がボトルネックになることがよくあります。一方、 TensorRT はモデル推論をわずか数ミリ秒で実行できますが、CPUの前処理(リサイズ、パディング、正規化)は、特に高解像度の場合、1画像あたり2~10ミリ秒かかることがあります。NVIDIA (Data Loading Library)は、前処理パイプライン全体をGPU行することで、この問題を解決します。
このガイドでは、Ultralytics YOLO 正確に再現するDALIパイプラインの構築手順を解説し、それらを model.predict()、ビデオストリームの処理、およびエンドツーエンドでの展開 Triton Inference Server.
このガイドはどのような方に向けたものですか?
このガイドは、CPU 明らかなボトルネックとなっている本番YOLO を展開するエンジニアを対象としています。具体的には、 TensorRT NVIDIA へのデプロイ、高スループットのビデオパイプライン、または Triton Inference Server 設定。標準的な推論を実行している場合は、 model.predict() また、前処理のボトルネックがない場合、デフォルトのCPU は問題なく動作します。
概要
- DALIパイプラインを構築しますか? 使用
fn.resize(mode="not_larger")+fn.crop(out_of_bounds_policy="pad")+fn.crop_mirror_normalizeGPU上でYOLOレターボックス前処理を再現する。 - Ultralyticsとの連携は可能ですか? DALI出力を
torch.Tensor宛先model.predict()—Ultralytics 、画像の前処理を自動的にUltralytics 。 - Tritonでデプロイしますか?CPU を行うには、TensorRT とDALIバックエンドをご利用ください。
YOLO にDALIを使う理由
一般的なYOLO パイプラインでは、前処理ステップはCPU上で実行されます:
- 画像(JPEG/PNG)をデコードする
- 縦横比を維持してサイズを変更する
- ターゲットサイズに合わせてトリミング(レターボックス)
- 正規化 ピクセル値は
[0, 255]宛先[0, 1] - レイアウトをHWCからCHWに変換する
DALI を使用すると、これらの処理はすべてGPU 上で実行されるため、CPU が解消されます。これは特に次のような場合に有効です:
| シナリオ | DALIが役立つ理由 |
|---|---|
| 高速GPUGPU | TensorRT 1ミリ秒未満の推論を実現するエンジンでは、CPU 主なコスト要因となる |
| 高解像度の入力 | 1080pおよび4Kの動画ストリームには、コストのかかるリサイズ処理が必要となります |
| 大量ロット | サーバー側での推論処理による多数の画像の並列処理 |
| CPU 限られている | NVIDIA Jetsonのようなエッジデバイス、GPU CPU 少ないGPU |
前提条件
Linux専用
NVIDIA Linuxのみに対応しています。WindowsやmacOSでは利用できません。
必要なパッケージをインストール:
pip install ultralytics
pip install --extra-index-url https://pypi.nvidia.com nvidia-dali-cuda120
pip install ultralytics
pip install --extra-index-url https://pypi.nvidia.com nvidia-dali-cuda110
要件:
- NVIDIA GPU 演算能力 5.0 以上/Maxwell 以降)
- CUDA .0 以降、または 12.0 以降
- Python .10~3.14
- Linuxオペレーティングシステム
YOLO について
DALIパイプラインを構築する前に、Ultralytics 前処理の段階で具体的にどのような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 | レターボックスのサイズ変更 | 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]) |
レターボックス処理では、以下の方法でアスペクト比を維持します:
- 計算スケール:
r = min(target_h / h, target_w / w) - サイズを変更して
(round(w * r), round(h * r)) - 残りのスペースを灰色で埋める(
114)目標サイズに達するまで - 画像を中央に配置し、両側に均等に余白を配置する
YOLO用DALIパイプライン
以下の中央に配置されたパイプラインをデフォルトの参照として使用してください。これはUltralytics一致しています LetterBox(center=True) 標準的なYOLO で使用される動作です。
中央配置のパイプライン(推奨、Ultralytics に対応)
このバージョンは、中央揃えのパディングをUltralytics デフォルトUltralytics 正確に再現しており、 LetterBox(center=True):
中央揃えのパディングを適用した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 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 これでいい?
正確な数値が必要ない場合は LetterBox(center=True) パリティを使用すれば、パディングの手順を次のように簡略化できます fn.pad(...) の代わりに fn.crop(..., out_of_bounds_policy="pad"). そのバリアントは、 右と下 端に余白が生じますが、これはカスタム展開パイプラインでは許容範囲内であるものの、Ultralyticsデフォルトである中央揃えのレターボックス表示とは完全に一致しません。
なぜ fn.crop 中央揃えのパディングについては?
DALIの fn.pad この演算子は、 右と下 端。中央揃えのパディング(Ultralyticsに合わせる) LetterBox(center=True))、使用 fn.crop with out_of_bounds_policy="pad". デフォルトでは crop_pos_x=0.5 および crop_pos_y=0.5, 画像は自動的に中央に配置され、左右均等に余白が設けられます。
アンチエイリアスの不一致
DALIの fn.resize デフォルトでアンチエイリアシングを有効にします(antialias=True) である一方、OpenCV cv2.resize with INTER_LINEAR ~する ではない アンチエイリアシングを適用する。常に設定する antialias=False DALIにおいて、CPU に合わせて調整します。これを省略すると、微妙な数値の差異が生じ、それが影響を及ぼす可能性があります モデルの精度.
パイプラインの実行
DALIパイプラインを構築して実行する
# 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 でのDALIの使用
前処理済みの PyTorch tensor model.predict(). あるとき torch.Tensor が可決されれば、Ultralytics 画像の前処理をスキップする (レターボックス、BGR→RGB、HWC→CHW、および/255正規化)を行い、モデルへ送信する前にデバイスの転送とデータ型のキャストのみを実行します。
この場合、Ultralytics 元の画像の寸法にアクセスUltralytics ため、検出ボックスの座標は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))
これは、すべての外部前処理パス(tensor 直接tensor 、ビデオストリーム、およびTriton )に適用されます。
DALIとUltralytics
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(実質的にゼロ)であり、CPU 約1~10msと比較して大幅に短縮されます。tensor BCHW形式で、float32(またはfloat16)型であり、正規化されているtensor 。 [0, 1]Ultralytics 、引き続きデバイスの転送とデータ型のキャストを自動的に処理Ultralytics 。
DALIとビデオストリーム
リアルタイムの動画処理には、以下を使用してください fn.external_source あらゆるソースからのフレームを取り込むために — OpenCV、GStreamer、またはカスタムキャプチャライブラリ:
動画ストリームの前処理のためのDALIパイプライン
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 output
import cv2
import numpy as np
import torch
from ultralytics import YOLO
model = YOLO("yolo26n.engine") # TensorRT model
pipe = yolo_video_pipeline(target_size=640)
pipe.build()
cap = cv2.VideoCapture("video.mp4")
while cap.isOpened():
ret, frame = cap.read()
if not ret:
break
# Feed BGR frame (convert to RGB for DALI)
frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
pipe.feed_input("input", [np.array(frame_rgb)])
(output,) = pipe.run()
# Convert DALI output to torch tensor for inference.
# This is a simple fallback path: using feed_input() with pipe.run() keeps a GPU->CPU->GPU copy.
# For high-throughput deployments, prefer a reader-based pipeline plus DALIGenericIterator to keep data on GPU.
tensor = torch.tensor(output.as_cpu().as_array()).to("cuda")
results = model.predict(tensor, verbose=False)
DALI搭載Triton Server
本番環境へのデプロイでは、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パイプラインを作成する
Triton バックエンド用の DALI パイプラインをシリアル化します:
TritTriton用の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")
ステップ 2:YOLO TensorRT YOLO エクスポートする
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.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.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"
}
}
]
}
アンサンブル・マッピングの仕組み
このアンサンブルは、モデル同士を 仮想tensor。 output_map 値 "preprocessed_image" DALIステップにおける値は、 input_map 値 "preprocessed_image" TensorRT 内において。これらは、あるステップの出力を次のステップの入力に結びつけるための任意の名前であり、モデルの内部tensor 一致する必要はありません。
ステップ4:推論リクエストを送信する
なぜ tritonclient の代わりに YOLO(\"http://...\")?
Ultralytics Triton 組み込みTriton 前処理・後処理を自動的に行うものです。ただし、DALIアンサンブルでは動作しません。なぜなら YOLO() アンサンブルが生のJPEGバイトを期待しているtensor 、前処理済みのfloat32tensor を送信します。 tritonclient DALIアンサンブル向けに直接、そして 標準搭載の連携機能 DALIを使用しない標準的な導入の場合。
画像Triton に送信する
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画像の一括処理
JPEG画像のバッチをTritonに送信する際は、エンコードされたすべてのバイト配列を同じ長さ(バッチ内の最大バイト数)にパディングしてください。Triton 、入力tensorバッチ形状が均一Triton 。
サポートされているタスク
DALIの前処理は、標準を使用するすべてのYOLO で動作します LetterBox パイプライン:
| タスク | 対応済み | 注記 |
|---|---|---|
| 検出 | ✅ | 標準的なレターボックスの前処理 |
| セグメンテーション | ✅ | 検出時と同じ前処理 |
| 姿勢推定 | ✅ | 検出時と同じ前処理 |
| 方向性検出(OBB) | ✅ | 検出時と同じ前処理 |
| 分類 | ❌ | レターボックスではなく、トーチビジョンの変換(中央切り抜き)を使用します |
制限事項
- Linuxのみ:DALIはWindowsおよびmacOSに対応していません
- NVIDIA GPU :CPU代替動作は不可
- 静的パイプライン:パイプラインの構造はビルド時に定義され、動的に変更することはできません
fn.pad右側のみ/下側のみ: 以下を使用fn.cropwithout_of_bounds_policy="pad"中央揃えのパディング- リクトモードなし: DALIパイプラインは、固定サイズの出力(例:640×640)を生成します。
auto=True可変サイズの出力(例:384×640)を生成するrectモードはサポートされていません。なお、 TensorRT 動的な入力形状に対応しており、固定サイズのDALIパイプラインは、最大のスループットを実現するために、固定サイズのエンジンと自然に組み合わされます - 複数のインスタンスを持つメモリ: を使用して
instance_groupwithcount>Triton バージョン1では、メモリ使用量が急増するTriton 。DALIモデルにはデフォルトのインスタンスグループを使用してください
よくある質問
DALIによる前処理とCPU によるCPU 速度は、どのように比較されますか?
GPU メリットは、使用するパイプラインによって異なります。 TensorRTでGPU推論がすでに高速な場合、2~10msCPU 主なコスト要因となることがあります。DALIは、前処理GPU実行することで、このボトルネックを解消します。最大のパフォーマンス向上は、高解像度の入力(1080p、4K)、大きなバッチサイズ、GPU CPU 限られているシステムで顕著に現れます。
PyTorch (TensorRTだけでなく)でDALIを使用することはできますか?
はい。使用してください DALIGenericIterator 前処理を行う torch.Tensor 出力し、それを model.predict()。しかし、パフォーマンス上のメリットが最も大きいのは TensorRT 推論がすでに非常に高速であり、CPU ボトルネックとなるモデル。
~と~の違いは何ですか fn.pad および fn.crop 余白用ですか?
fn.pad にのみパディングを追加します 右と下 端。 fn.crop with out_of_bounds_policy="pad" 画像を中央に配置し、Ultralyticsに合わせて四方に均等に余白を追加します LetterBox(center=True) 行動。
DALIは、CPU と全く同じ結果を出力しますか?
ほぼ同じ。セット antialias=False で fn.resize OpenCV cv2.INTER_LINEAR. Minor floating-point differences (< 0.001) may occur due to GPU vs CPU arithmetic, but these have no measurable impact on detection 精度.
DALIの代替CUDA はどうでしょうか?
CUDA は、GPUビジョン処理のためのもう1つのNVIDIA 。これは、演算子ごとの制御機能(例えば OpenCV DALIのパイプライン方式ではなく(GPU上で)、CUDA cvcuda.copymakeborder() 各辺ごとのパディングを明示的に指定できるため、レターボックスの中央揃えが簡単に行えます。パイプラインベースのワークフロー(特に Triton)、およびカスタム推論コードにおけるオペレーターレベルのきめ細かな制御CUDA 。