Markdownで表示

NVIDIA DeepStream

NVIDIA DeepStreamはnvinferエレメントを通して推論を実行し、そこにはONNXグラフ、対応する設定ファイル、バウンディングボックスパーサーが必要です。ONNXエクスポートでdeepstream=Trueを指定すると、前の2つが出力され、3つ目に接続されます。

フラグ
export(format="onnx", deepstream=True)
出力
ONNXグラフ、config_infer_primary_<stem>.txt、<stem>_labels.txt
対応範囲
9タスクにまたがる43通りのファミリーとタスクの組み合わせ
パーサー
Marcos LucianoによるMITライセンスのDeepStream-YoloプロジェクトのNvDsInferParseYoloです。デバイスごとに1回ビルドします。 github.com/marcoslucianops/DeepStream-Yolo
提供状況
v1.5.0で提供されます。2026-08-08にプルリクエスト728でdevへマージされました。 プルリクエスト728, issue 648
実機検証
RTX 5070 Ti上のDeepStream 8.0.0、検出のみ、2026-08-08

提供状況

DeepStreamエクスポートはv1.5.0で提供されます。2026-08-08にプルリクエスト728で devへマージされたため、最新のインストールにはすでに含まれており、ブランチを 固定する必要はありません。

インストール
pip install "libreyolo[onnx]"

2026-08-08より前にdeepstream-exportブランチをクローンしている場合は、取り直して ください。このブランチはリベースして強制プッシュされており、古い履歴にはこれらの エクスポートをCUDAマシンで動かすための修正が入っていません。

エクスポートが出力するもの

model.export(format="onnx", deepstream=True)は3つのファイルを並べて出力します。 libreyolo9s.ptの場合は次のとおりです。

  • libreyolo9s.onnx。検出グラフで、出力テンソルは(batch, num_detections, 6)の 形状が1つ、各行は[x1, y1, x2, y2, score, class_id]で、ネットワーク入力の ピクセル座標です。
  • config_infer_primary_libreyolo9s.txt。ファミリーの前処理定数、クラス数、 しきい値、パーサーの接続設定を持つnvinferの設定ファイルです。
  • libreyolo9s_labels.txt。1行に1つのクラス名です。

ラベルファイルは、チェックポイントがクラス名を持っている場合に出力されます。深度 推定モデルはクラス名を持たないため、ファイルもlabelfile-pathキーも出力されません。

LibreYOLOは.soを出力しません。DeepStreamが読み込む.somarcoslucianops/DeepStream-Yoloのバウンディングボックスパーサーで、デバイスごとに 1回コンパイルすればよく、どのLibreYOLO検出器に向けても同じバイナリです。モデルに あたるのがONNXです。nvinferが自前で後処理を行うため、分類とセマンティック セグメンテーションはパーサーをまったく必要としません。

モデルをエクスポートする

Python
from libreyolo import LibreYOLO9, LibreDFINE # libreyolo9s.onnx、config_infer_primary_libreyolo9s.txt、# libreyolo9s_labels.txt を作業ディレクトリに出力LibreYOLO9("libreyolo9s.pt", size="s").export(format="onnx", deepstream=True) # 検出モデルはそれぞれ別のディレクトリに置く。どの検出用設定も# 同じエンジンキャッシュファイル名を指すため。「よくある落とし穴」を参照LibreDFINE("LibreDFINEs.pt", size="s").export(format="onnx", deepstream=True)
引数
model.export(    format="onnx",     # deepstream=True は他のどの形式でも拒否される    deepstream=True,    conf=0.25,         # pre-cluster-threshold の初期値(該当タスクでは                       # classifier-threshold、segmentation-threshold も)    iou=0.45,          # nms-iou-threshold の初期値、cluster-mode=4 では省略    batch=1,           # batch-size とエンジンキャッシュのファイル名の初期値    half=False,        # True で設定に network-mode=2 を記録(fp16 ビルド)    int8=False,        # True で設定に network-mode=1 を記録    dynamic=True,      # ONNX グラフの動的バッチ軸    imgsz=640,         # infer-dims=3;H;W の初期値) # deepstream=True と nms=True は排他。DeepStream はクラスタリング段階で# 抑制を行うため、グラフには何も埋め込まれない
先にD-FINEの重みを取得
curl -L -o LibreDFINEs.pt \  https://huggingface.co/LibreYOLO/LibreDFINEs/resolve/main/LibreDFINEs.pt

LibreDFINE._load_weightsは、ファイルがまだディスク上にない場合にダウンロードを 試みずにFileNotFoundErrorを送出するため、先にLibreDFINEs.ptを自分で取得して ください。この不足は issue #727として追跡されて います。YOLO9の重みは初回使用時にダウンロードされます。

このフラグはPythonのみです。このブランチのlibreyolo exportdeepstream オプションはなく、CLIは未知のキーをそのまま渡すのではなく、固定のリストから エクスポート引数を組み立てます。

バウンディングボックスパーサーをビルドする

検出にはパーサーライブラリが必要で、インスタンスセグメンテーションには別のものが 必要、残りのタスクには不要です。DeepStream 8.0イメージには、ドキュメントに載って いるビルドコマンドを壊す点が2つあり、どちらもLibreYOLOではなく環境側の問題です。

イメージには/usr/localの下にcudacuda-12cuda-12.5cuda-12.8cuda-12.9が入っています。ツールキットが完全なのはcuda-12.5だけです。また、 libcublas.so.12libcublas.so.12.8.4.1は入っていますが、-lcublasが解決先と するバージョンなしのlibcublas.soはありません。以下のスクリプトはその両方を回避 します。

build_parser.sh、DeepStreamコンテナー内で実行
set -egit clone --depth 1 https://github.com/marcoslucianops/DeepStream-Yolo.git # このイメージの /usr/local/cuda-12 はスタブで、ビルドは# "fatal error: crt/host_defines.h: No such file or directory" で失敗する。# ヘッダーを実際に含むツールキットを解決する。8.0 イメージでは cuda-12.5CUDA_DIR=$(readlink -f /usr/local/cuda)[ -f "$CUDA_DIR/include/crt/host_defines.h" ] || \  CUDA_DIR=$(ls -d /usr/local/cuda-*.* | sort -Vr | \             while read d; do [ -f "$d/include/crt/host_defines.h" ] && echo "$d" && break; done) # イメージには libcublas.so.12 と libcublas.so.12.8.4.1 はあるが、-lcublas が# 必要とするバージョンなしの libcublas.so がないため、リンク段階が# "/usr/bin/ld: cannot find -lcublas" で失敗する。リンカーが求める名前を用意するmkdir -p /tmp/cudalibsfor lib in cublas cublasLt cudart; do  real=$(find /usr/local -name "lib${lib}.so.1*" | grep -v stubs | sort -V | tail -1)  ln -sf "$real" "/tmp/cudalibs/lib${lib}.so"doneexport LIBRARY_PATH="/tmp/cudalibs:$LIBRARY_PATH" make -C DeepStream-Yolo/nvdsinfer_custom_impl_Yolo CUDA_VER="${CUDA_DIR##*/cuda-}"
インスタンスセグメンテーションは別のパーサーを使う
git clone --depth 1 https://github.com/marcoslucianops/DeepStream-Yolo-Seg.gitmake -C DeepStream-Yolo-Seg/nvdsinfer_custom_impl_Yolo_seg \  CUDA_VER="${CUDA_DIR##*/cuda-}"

そのうえで、生成された設定のcustom-lib-pathを、ビルドした libnvdsinfer_custom_impl_Yolo.soに向けてください。生成される値は相対パスの nvdsinfer_custom_impl_Yolo/libnvdsinfer_custom_impl_Yolo.soで、deepstream-appDeepStream-Yoloのチェックアウト先から実行する場合は解決できますが、それ以外では 編集が必要です。

パイプラインを実行する

ほかに時間をかける前に、コンテナーからGPUが見えることを確認してください。これは 検証時に最初に行ったチェックで、WSL2上のBlackwellカードで実施しました。

何よりも先にGPUパススルーを確認
docker run --rm --gpus all nvcr.io/nvidia/tritonserver:26.04-py3 \  nvidia-smi --query-gpu=name,driver_version,compute_cap --format=csv

Output

name, driver_version, compute_cap
NVIDIA GeForce RTX 5070 Ti, 591.86, 12.0

検証では、ファイルソース1つ、表示シンクなし、オンスクリーンディスプレイ有効、 gie-kitti-output-dirを設定して全フレームの検出結果をKITTI形式のテキストとして ディスクに書き出す構成でdeepstream-appを動かしました。その設定を反映した設定 ファイルは次のとおりです。

deepstream_app_config.txt
[application]enable-perf-measurement=1perf-measurement-interval-sec=5gie-kitti-output-dir=kitti [tiled-display]enable=0 [source0]enable=1type=3uri=file:///opt/nvidia/deepstream/deepstream/samples/streams/sample_1080p_h264.mp4num-sources=1gpu-id=0 [streammux]gpu-id=0batch-size=1batched-push-timeout=40000width=1920height=1080live-source=0 [primary-gie]enable=1gpu-id=0gie-unique-id=1config-file=config_infer_primary_libreyolo9s.txt [osd]enable=1border-width=2text-size=15 [sink0]enable=1type=1sync=0 [tests]file-loop=0
実行する
deepstream-app -c deepstream_app_config.txt

Output

App run successful
両方の手順を1つのコンテナーで
docker run --rm --gpus all -v "$PWD:/work" -w /work \  nvcr.io/nvidia/deepstream:8.0-samples-multiarch \  bash -c "bash build_parser.sh && deepstream-app -c deepstream_app_config.txt"

nvinferは初回実行時にONNXからTensorRTエンジンをビルドしてモデルの隣にキャッシュ するため、初回はエンジンのビルド分の時間がかかり、2回目以降はキャッシュを読み込み ます。

生成される設定ファイル

以下の2つの設定はいずれも検証時にエクスポーターが生成したもので、あとから編集して いません。

キーYOLO9-sD-FINE-s
net-scale-factor0.0039215686270.003921568627
model-color-format00
infer-dims3;640;6403;640;640
maintain-aspect-ratio10
symmetric-padding00
network-type00
num-detected-classes8080
cluster-mode24
parse-bbox-func-nameNvDsInferParseYoloNvDsInferParseYolo
pre-cluster-threshold0.250.25
nms-iou-threshold0.45
topk300300

2つの設定が異なるのは3か所、maintain-aspect-ratiocluster-mode、そして nms-iou-thresholdがそもそも存在するかどうかです。D-FINEの設定はこのキーを完全に 省いており、これはcluster-mode=4が求める形です。

クラスタリングは本来別々の検出結果を統合してしまうため、物体ごとに最大1つしか 予測を出さないヘッドにはcluster-mode=4が設定され、DeepStreamはそれらに対して クラスタリングを行いません。対象はrfdetrdfinedeimdeimv2ecrtdetrrtdetrv2rtdetrv4yolo9_e2eです。グリッドヘッドとアンカーヘッドに はcluster-mode=2nms-iou-thresholdが設定されます。

検出用の設定にはさらにengine-create-func-name=NvDsInferYoloCudaEngineGetが入って おり、エンジンのビルドをパーサーライブラリに委ねます。これによってエンジン キャッシュのファイル名が固定され、よくある落とし穴で説明する衝突の原因になって います。

対応するタスクとファミリー

エクスポートできるファミリーとタスクの組み合わせは43通りです。 libreyolo/export/deepstream.pydeepstream_supported_tasks()deepstream_supported_families(task)は、実行時に同じ一覧を返します。

タスクnetwork-typeパーサーライブラリファミリー
検出0DeepStream-Yoloyolo9, yolo9_p2, yolo9_e2e, yolo1, yolo2, yolo3, yolo4, yolo7, yolox, yolonas, rtmdet, picodet, rfdetr, dfine, deim, deimv2, ec, rtdetr, rtdetrv2, rtdetrv4
分類1不要mobilenetv4, convnext, efficientnetv2, resnet, dinov2
セマンティックセグメンテーション2不要pidnet, eomt, dinov2, lingbotvision
インスタンスセグメンテーション3DeepStream-Yolo-Segrfdetr, dfine, ec
姿勢推定100不要yolo9, yolonas, rfdetr, ec
深度推定100不要depth_anything, zipdepth
復元100不要nafnet, realesrgan, swinir
マッティング100不要birefnet
視線推定100不要l2cs

network-type=100は、そのタスク向けの後処理器をDeepStreamが持たないことを意味 します。これらの設定はoutput-tensor-meta=1を指定し、グラフ本来の出力はそのまま 素通りして、アプリケーション側がテンソルのメタデータからデコードします。複数出力の グラフでも問題ありません:すべての出力レイヤーが、通常のONNXエクスポートと同じ 出力名と動的軸のままメタデータに届きます。

インスタンスセグメンテーションの行は、検出行のあとにそのインスタンスのマスクが 続く形で、マスクはsegパーサーがハードコードしている解像度(netH / 4, netW / 4)に 平坦化され、segmentation-threshold用の確率として並びます。

分類と視線推定はセカンダリ推論として動作します。検出器の後段に分類器を置くには、 生成された設定でprocess-mode=2operate-on-gie-idを指定してください。視線推定 はヘッドのみの契約で、入力は顔のクロップ1枚ずつになるため、前段に顔検出器が必要 です。

3つのファミリーは意図的に外してあります。segformerは共通のセマンティック エクスポート契約につながっておらず、どの形式でもONNXへエクスポートできません。 RTMDet-InsとYOLO9は、インスタンスセグメンテーションのエクスポートがLibreYOLO側で ブロックされています。depth_anything3にはエクスポートの実装がありません。

表の2行にはチェックポイントの不足が隠れています。EoMTのセマンティック チェックポイントはlだけが公開されており、DINOv2の分類は公開チェックポイントが まったくないため、この組み合わせには自分でファインチューニングした重みが必要です。

前処理の違い

nvinferはチャンネルごとにnet-scale-factor * (x - offsets)をスカラーのスケール で計算するため、チャンネルごとの標準偏差を表現できません。それが必要なファミリー (rfdetrec、DINOバックボーンのdeimv2サイズ、rtmdetpicodet、および すべての分類ファミリー)は、正規化をエクスポートしたグラフに焼き込んであり、生成 された設定はそれに合う生の入力空間をグラフへ渡します。

LibreYOLO自身のPythonパイプラインとnvinferが依然として食い違うのは、ジオメトリ の部分です。

  • レターボックス方式のファミリー(yolo9yoloxyolonasrtmdetyolo2yolo3yolo4yolo7)は、本来グレーでパディングします。nvinferは黒で パディングします。
  • yolonasの検出は、本来640のキャンバス内で長辺を636にリサイズします。nvinfermaintain-aspect-ratioは640をそのまま使います。
  • 分類は本来、短辺をリサイズしてから中央でクロップします。nvinferはフレーム または物体のROIをネットワーク入力へ引き伸ばすため、被写体が詰まったクロップでは 結果が変わります。
  • EoMTは本来、セマンティックセグメンテーションでスライディングウィンドウのタイル 処理を行います。エクスポートしたグラフは引き伸ばした単一のキャンバスで、高速です が精度は下がります。
  • pidnetは入力解像度の1/8、lingbotvisionは1/16でクラスマップを出力します。 DeepStreamは表示のためにクラスマップをアップサンプリングします。

ONNXの一致検証は前処理済みのテンソルを入力するため、グラフの出力は確認できますが、 設定内の誤ったカラー順序やパディング方針は捕まえられません。厳密な一致が要る ワークロードをデプロイする前に、自分のデータで検証してください。

よくある落とし穴

同じディレクトリの2つの検出モデルが互いのエンジンを読み込む

どの検出用設定にも同じ行が入っています。

ini
model-engine-file=model_b1_gpu0_fp32.engine

パーサーのエンジンビルダーはこのベース名を要求し、モデルによって変わりません。 2つ目の検出モデルを同じディレクトリにエクスポートすると、2回目の実行では1つ目の モデルのキャッシュ済みエンジンが読み込まれます。クラッシュはせず、ボックスが誤る だけです。検出モデルにはそれぞれ専用のディレクトリを用意してください。検証時も、 D-FINEを別のディレクトリに隔離してはじめてテストできました。

1つのボックスは1つのクラスしか持てない

nvinferの行フォーマットは[x1, y1, x2, y2, score, class_id]で、1ボックスにつき 1クラスのため、エクスポートではクラススコアをargmaxに畳み込みます。predictが 2つのクラスで報告するボックスは、片方のクラスだけが残ります。実測例として、 LibreYOLOは同じボックスにvase 0.773bottle 0.383を報告しますが、DeepStreamの グラフはvaseを残します。これはパーサーの行フォーマットから生じるもので、その契約 を外れない限り変えられないため、リグレッションではなく想定どおりの動作です。

検証済み

deepstream-appは、NVIDIA同梱のsample_1080p_h264.mp4(1443フレーム)に対して フレームごとのKITTI出力を有効にした状態で、どちらの検出ヘッド型でもEOSまで走り、 App run successfulを出しました。

YOLO9-sD-FINE-s
ヘッド型gridone-to-one
cluster-mode24
maintain-aspect-ratio10
検出のあったフレーム数14431443
総検出数1803171105

1443フレーム全体のクラスヒストグラムは、どちらのモデルでも1位が車、2位が人で、 街の風景として妥当です。検出数の4倍の開きはcluster-modeの違いが働いた結果です: cluster-mode=4のD-FINEはクラスタリングを行わないため、しきい値を超えたクエリは ほぼ重複しているものも含めてすべて残ります。

独立に学習された2つのモデルが、主要な物体を同じ位置に置いています。

YOLO9  bus  [706.72,  0.82, 1916.34, 1062.97]  conf 0.965
D-FINE bus  [702.73,  2.93, 1916.24, 1069.32]  conf 0.965

この実行で5点が確認できました:sm_120でTensorRTがエクスポートしたONNXから エンジンをビルドすること、nvinferが生成された設定のすべてのキーを受け付けること、 NvDsInferParseYoloがテンソルのレイアウトを正しく読むこと、ボックスがソース解像度 である1920x1080の座標に収まること、そしてラベルが生成されたラベルファイルで解決 されることです。

実行環境は次のとおりです。

構成要素
ホストOSWindows 11 Pro 26200
GPUNVIDIA GeForce RTX 5070 Ti、16 GB
ドライバー591.86
Compute Capability12.0(Blackwell、sm_120)
コンテナーランタイムDocker Desktop 29.4.3、WSL2バックエンド
DeepStreamイメージnvcr.io/nvidia/deepstream:8.0-samples-multiarch
DeepStreamバージョン8.0.0
コンテナーのCUDA12.8.1
パーサーmarcoslucianops/DeepStream-YoloのHEAD

パイプラインの実行と併せて、tests/unit/test_deepstream_export.pyがグラフ アダプターと生成される設定キーをカバーしており、その35件のテストはこのコミットで 通過します。

未検証

上記の範囲を実際より広く読まれないよう、明記しておきます。

  • Jetsonとaarch64。エクスポートの契約はアーキテクチャに依存しませんが、パイプライン はx86のディスクリートGPUでしか実行していません。
  • 43通りのうち41通り。DeepStreamを通したのはyolo9の検出とdfineの検出だけです。 分類、セマンティックセグメンテーション、インスタンスセグメンテーション、および 生テンソルのタスクは、パイプラインの実行ではなくユニットテストとONNXの一致 チェックでカバーしています。
  • FP16とINT8。動かしたのはnetwork-mode=0だけです。
  • マルチストリームとバッチ処理。ソースは1つ、batch-size=1です。
  • 正解データ(ground truth)のデータセットに対する精度。検出結果は意味的な妥当性と モデル間の一致を確認しただけで、DeepStreamを通してmAPとしてスコア化しては いません。

2026-08-08の実機検証をもとに執筆しました。ファミリー一覧、設定キー、デフォルト値は、同日にプルリクエスト728でdevへマージされたコミット5f81e11eのlibreyolo/export/deepstream.pyおよびlibreyolo/export/exporter.pyから読み取っています。