Skip to main content

NVIDIA DALIによるGPUアクセラレーション付き前処理

はじめに

モデルをデプロイする際は、効率的に実行されることが重要です。Ultralytics YOLO モデルを本番環境で運用する際、前処理がボトルネックになることがよくあります。 最適化のための最大ワークスペースサイズをGiB単位で設定し、メモリ使用量と性能のバランスをとります。は、モデルの推論推論をわずか数ミリ秒で実行できますが、CPUベースの前処理(リサイズ、パディング、正規化)には画像あたり2〜10ミリ秒かかることがあり、特に高解像度では顕著です。NVIDIA DALI(Data Loading Library)は、前処理パイプライン全体をGPUに移行することでこの問題を解決します。

このガイドでは、Ultralytics YOLOの前処理を正確に再現するDALIパイプラインの構築方法、model.predict()との統合、ビデオストリームの処理、およびTriton Inference Server.

によるエンドツーエンドのデプロイメントについて説明します。

このガイドの対象読者最適化のための最大ワークスペースサイズをGiB単位で設定し、メモリ使用量と性能のバランスをとります。このガイドは、CPUによる前処理がボトルネックとして測定される本番環境でYOLOモデルをデプロイするエンジニアを対象としています。一般的には、NVIDIA GPU上でのTriton Inference Serverデプロイメント、高スループットなビデオパイプライン、またはmodel.predict()のセットアップなどが該当します。もし

クイックサマリー
  • を使用して標準的な推論を実行しており、前処理がボトルネックになっていない場合は、デフォルトのCPUパイプラインで問題ありません。DALIパイプラインの構築fn.resize(mode="not_larger") + fn.crop(out_of_bounds_policy="pad") + fn.crop_mirror_normalize 使用
  • して、GPU上でYOLOのレターボックス前処理を再現します。Ultralyticsとの統合torch.Tensorからmodel.predict() DALIの出力を
  • として渡します。これにより、Ultralyticsは自動的に画像の前処理をスキップします。Tritonでのデプロイ

TensorRTアンサンブルとともにDALIバックエンドを使用して、CPUによる前処理をゼロにします。

YOLOの前処理にDALIを使用する理由

  1. 一般的なYOLO推論パイプラインでは、前処理ステップはCPU上で実行されます:デコード
  2. (JPEG/PNG形式の画像)リサイズ
  3. (アスペクト比を保持)パッド
  4. (ターゲットサイズまで。レターボックス処理)正規化[0, 255]から[0, 1]
  5. (ピクセル値をに変換)

レイアウト変換

SyncBatchNormを使用すべき場合(HWCからCHWへ)
DALIを使用すると、これらのすべての操作がGPU上で実行され、CPUのボトルネックが解消されます。これは特に以下の場合に有効です:最適化のための最大ワークスペースサイズをGiB単位で設定し、メモリ使用量と性能のバランスをとります。DALIが役立つ理由
高速なGPU推論サブミリ秒の推論を実現するエンジンでは、CPUによる前処理が最大の負荷となります。
高解像度入力batch sizes1080pおよび4Kビデオストリームでは、高負荷なリサイズ処理が必要となります。
大規模なサーバーサイドでの推論により、多数の画像を並列処理します。デバイス向けに調整されており、これらのプラットフォーム向けに最適化されたGPUサポートを統合しています。CPUコアの制限

前提条件

などのエッジデバイス、あるいはGPUあたりのCPUコア数が少ない高密度GPUサーバー。

Linuxのみ対応NVIDIA DALIはLinuxのみ

をサポートしています。WindowsやmacOSでは利用できません。

pip install ultralytics
pip install --extra-index-url https://pypi.nvidia.com nvidia-dali-cuda120

要件:

  • CUDA 11.x
  • NVIDIA GPU(計算能力5.0以上 / Maxwell以降)
  • CUDA 11.0以上 または 12.0以上
  • Python 3.10-3.14

Linuxオペレーティングシステム

YOLO前処理の理解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)
)

DALIパイプラインを構築する前に、Ultralyticsが前処理で正確に何を行っているかを理解することが重要です。重要なクラスはultralytics/engine/predictor.pyです。

における完全な前処理パイプラインは、以下のステップを実行します:ステップ操作CPU機能
DALI相当1cv2.resizefn.resize(mode="not_larger")
レターボックスリサイズ2cv2.copyMakeBorderfn.crop(out_of_bounds_policy="pad")
中心へのパディング3im[..., ::-1]fn.decoders.image(output_type=types.RGB)
BGR → RGB4np.transpose + tensor / 255fn.crop_mirror_normalize(std=[255,255,255])

HWC → CHW + 正規化 /255

  1. レターボックス操作は、以下のようにアスペクト比を保持します:r = min(target_h / h, target_w / w)
  2. スケールの計算:(round(w * r), round(h * r))
  3. へのリサイズ114残りのスペースをグレー(
  4. )でパディングし、ターゲットサイズに到達させる

両側にパディングが均等に分散されるように画像を中央配置する

YOLO向けDALIパイプラインLetterBox(center=True)以下のセンター配置パイプラインをデフォルトの参照として使用してください。これはUltralytics

の挙動と一致しており、標準的なYOLO推論で使用されるものです。

センター配置パイプライン(推奨、UltralyticsのLetterBoxと一致)LetterBox(center=True):

このバージョンは、デフォルトのUltralytics前処理とセンターパディングを正確に再現しており、
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
センターパディングを備えたDALIパイプライン(推奨)

`fn.pad`で十分な場合は?LetterBox(center=True)もし正確なfn.pad(...)ではなくfn.crop(..., out_of_bounds_policy="pad")の一致が不要であれば、を使用してパディングステップを簡略化できます。そのバリアントは右側と下側

の端のみを埋めます。カスタムデプロイメントパイプラインには許容できるかもしれませんが、Ultralyticsのデフォルトのセンターレターボックスの挙動とは正確には一致しません。

なぜセンターパディングに`fn.crop`を使用するのか?fn.padDALIのを使用してパディングステップを簡略化できます。そのバリアントは演算子はLetterBox(center=True)の端にのみパディングを追加します。センターパディング(Ultralyticsfn.cropout_of_bounds_policy="pad"と一致させるため)を得るには、crop_pos_x=0.5crop_pos_y=0.5を使用します。デフォルトの

を使用すると、画像は自動的に対称的なパディングで中央に配置されます。

なぜセンターパディングに`fn.crop`を使用するのか?fn.resize はデフォルトでアンチエイリアスを有効にします(antialias=True)、一方OpenCVの cv2.resizeINTER_LINEAR はアンチエイリアスを FlashAttentionを必要としません 適用します。CPUパイプラインと一致させるため、DALIでは常に antialias=False を設定してください。これを省略すると、微細な数値の差異が生じ、モデル精度.

パイプラインの実行

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 PredictでのDALIの利用

前処理済みの PyTorch テンソルを直接 model.predict() に渡すことができます。torch.Tensor が渡されると、Ultralyticsは 画像の前処理をスキップし(letterbox、BGR→RGB、HWC→CHW、および/255正規化)、モデルに送信する前にデバイス転送とdtypeキャストのみを実行します。

この場合、Ultralyticsは元の画像の寸法にアクセスできないため、検出ボックスの座標は640×640のletterboxされた空間で返されます。これらを元の画像の座標にマッピングし直すには、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デプロイメントなど、すべての外部前処理パスに適用されます。

DALI + Ultralytics predict
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")
ゼロ前処理オーバーヘッド

を渡すと、画像の前処理ステップは〜0.004ms(本質的にゼロ)かかり、CPU前処理の〜1-10msと比較されます。テンソルはBCHW形式、float32(またはfloat16)であり、torch.Tensorからmodel.predict() に正規化されている必要があります。Ultralyticsは引き続きデバイス転送とdtypeキャストを自動的に処理します。[0, 1]ビデオストリームでのDALI

リアルタイムビデオ処理には、

を使用して任意のソースからフレームを供給します — fn.external_source、GStreamer、またはカスタムキャプチャライブラリ:、ONNXモデル推論にビデオストリーム前処理のための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

本番環境へのデプロイでは、DALI前処理と

推論を 最適化のための最大ワークスペースサイズをGiB単位で設定し、メモリ使用量と性能のバランスをとります。 内でアンサンブルモデルを使用して組み合わせます。これによりCPU前処理が完全に排除され、生のJPEGバイトが入力され、検出結果が出力されます。すべてGPU上で処理されます。Triton Inference Serverモデルリポジトリ構造

ステップ1: DALIパイプラインの作成

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

Triton DALIバックエンド用にDALIパイプラインをシリアライズします:

TritonのためのDALIパイプラインのシリアライズ

ステップ2: YOLOをTensorRTにエクスポート
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")

YOLOモデルのTensorRTエンジンへのエクスポート

ステップ3: Tritonの設定
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

dali_preprocessing/config.pbtxt:

yolo_trt/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 ]
  }
]

ensemble_dali_yolo/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 ]
  }
]

アンサンブルマッピングの仕組み

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 DALIステップの "preprocessed_image" 値は、TensorRTステップの input_map DALIステップの "preprocessed_image" と一致します。これらはあるステップの出力を次のステップの入力にリンクする任意の名前であり、モデルの内部テンソル名と一致させる必要はありません。

ステップ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.
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は入力テンソルに対して均質なバッチ形状を要求します。

範囲

DALI前処理は、標準の LetterBox パイプラインを使用するすべてのYOLOタスクで機能します:

タスクサポート対象備考
検出標準のletterbox前処理
Segmentation検出と同じ前処理
姿勢推定検出と同じ前処理
配向検出 (OBB)検出と同じ前処理
分類letterboxではなくtorchvision変換(センタークロップ)を使用

制限事項

  • NVIDIA DALIは: DALIはWindowsまたはmacOSをサポートしていません
  • NVIDIA GPUが必要: CPUのみのフォールバックはありません
  • 静的パイプライン: パイプライン構造はビルド時に定義され、動的に変更することはできません
  • fn.pad は右/下のみです: fn.cropout_of_bounds_policy="pad" 中央パディング用
  • rectモードなし: DALIパイプラインは固定サイズの出力(例:640×640)を生成します。可変サイズ出力(例:384×640)を生成する auto=True rectモードはサポートされていません。最適化のための最大ワークスペースサイズをGiB単位で設定し、メモリ使用量と性能のバランスをとります。 は動的入力形状をサポートしていますが、固定サイズのDALIパイプラインは最大のデータスループットを得るために固定サイズのエンジンと自然に組み合わされます。
  • 複数インスタンス時のメモリ: Tritonで instance_groupcount > 1を使用すると、メモリ使用量が高くなる可能性があります。DALIモデルにはデフォルトのインスタンスグループを使用してください。

FAQ

DALI前処理はCPU前処理速度と比較してどうですか?

利点はパイプラインに依存します。最適化のための最大ワークスペースサイズをGiB単位で設定し、メモリ使用量と性能のバランスをとります。 でGPU推論がすでに高速な場合、2-10msのCPU前処理が支配的なコストになる可能性があります。DALIは前処理をGPU上で実行することでこのボトルネックを解消します。最大の利益は、高解像度の入力(1080p、4K)、大きな batch sizes、およびGPUあたりのCPUコア数が制限されたシステムで見られます。

DALIをPyTorchモデル(TensorRTだけでなく)で使用できますか?

はい。DALIGenericIterator を使用して前処理済みの torch.Tensor 出力を取得し、それを model.predict() に渡します。ただし、パフォーマンスの利点は、推論がすでに非常に高速でCPU前処理がボトルネックとなる 最適化のための最大ワークスペースサイズをGiB単位で設定し、メモリ使用量と性能のバランスをとります。 モデルで最大になります。

YOLO26におけるfn.padfn.crop パディング用?

fn.padを使用してパディングステップを簡略化できます。そのバリアントは の端にのみパディングを追加します。fn.cropout_of_bounds_policy="pad" は画像を中央に配置し、すべての辺に対称的にパディングを追加し、Ultralytics LetterBox(center=True) の動作と一致させます。

DALIはCPU前処理と画素単位で同一の結果を生成しますか?

ほぼ同一です。OpenCVの antialias=False内のfn.resize と一致するように cv2.INTER_LINEAR を設定してください。GPUとCPUの算術演算の違いにより、わずかな浮動小数点数の差(< 0.001)が生じる場合がありますが、これらは検出には測定可能な影響を与えません。精度.

DALIの代替としてのCV-CUDAはどうですか?

CV-CUDA は、GPU加速されたビジョン処理のためのもう一つのNVIDIAライブラリです。これはDALIのパイプラインアプローチではなく、オペレータごとの制御(、ONNXモデル推論に のようなものをGPU上で)を提供します。CV-CUDAの cvcuda.copymakeborder() は、辺ごとのパディングを明示的にサポートしており、中央のletterboxを簡単に作成できます。パイプラインベースのワークフロー(特に Triton)、およびカスタム推論コードにおける詳細なオペレーターレベルの制御を実現するCV-CUDAです。

コメント