NVIDIA VSS 3.2.0 GA を DGX Spark で動かしてみた

NVIDIA VSS 3.2.0 GA を DGX Spark で動かしてみた

2026.06.22

はじめに

こんにちは、クラスメソッド製造ビジネステクノロジー部の森茂です。

NVIDIA の VSS(Video Search and Summarization)が、6 月 16 日に 3.2.0 として GA リリースされました。3.X 系としては最初の General Availability です。

https://docs.nvidia.com/vss/latest/release-notes.html

VSS はざっくり言うと、映像を VLM(Vision Language Model)で要約・検索・アラート判定するためのリファレンス実装一式で、複数のマイクロサービスとエージェントワークフローを Docker Compose や Helm で立ち上げる構成になっています。これまでは EA(Early Access)でしたが、GA リリースということで、手元の DGX Spark 上に 3.2.0 をフル構成で立ち上げて、起動の手触りから新機能の現在地までを実機で確かめてみました。

DGX Spark は GB10(ARM64)を 1 基積んだコンパクトな AI ワークステーションで、CPU と GPU が Unified Memory を 128 GiB 共有する独特なアーキテクチャです。x86 + H100 / RTX PRO 6000 構成とは挙動が違う箇所も出てくるので、その点は適宜補足していきます。

VSS の以前のバージョンに触れた記事も書いているので、移行を考えている方は併せてどうぞ。

https://dev.classmethod.jp/articles/dgx-spark-vss-3-1-revisit/

3.2.0 GA の概要

まずは 3.2.0 GA のリリースで何が変わったのか、release notes の NEW / CHANGED / FIXED / BREAKING を表にまとめてみました。

区分 主なトピック
NEW 全マイクロサービスとエージェントワークフローの GitHub ソース公開(Apache-2.0 + MIT)、Agent Skills (EA)、NemoClaw + VSS (EA)、RT-CV-3D(Sparse4D v2.2)+ Auto Calibration、音声付き動画理解(Nemotron 3 Nano Omni)
CHANGED /v1/generate_captions_alerts/v1/generate_captions リネーム、base profile から Envoy/SDR routing 撤去、デプロイ構造を developer-profiles/ + services/ の include モデルへ
FIXED 重複 stream/camera ID で HTTP 409 を返すように(旧挙動:silent overwrite)、Riva ASR NIM の compose 同梱を停止
BREAKING 上記 CHANGED + FIXED のうち、過去バージョンの client コード・compose をそのまま使うと動かなくなる項目

特に大きいのは「全ソース公開」と「デプロイ構造の刷新」だと感じました。本記事では、この 2 つの変化に乗っかる形で実機検証を進めていきます。

なお RT-CV-3D / Auto Calibration はマルチカメラ環境が必要で、本記事では深掘りしません。音声付き動画理解(Nemotron 3 Nano Omni)も、Riva ASR NIM が入れ替わる大きな変化なので、別記事で実機検証しようと思います。

デプロイ構造の刷新

3.2 のデプロイ構造は deploy/docker/{developer-profiles, industry-profiles, services}/ を include で繋いだモジュラー構成になっています。dev-profile.sh という起動スクリプトが GPU を自動検出して HARDWARE_PROFILE を決定し、プロファイル別の compose を組み立てる仕組みです。

点線は base profile で実際に拾われるサービス群(agent / Cosmos-Reason2-8B / Nemotron-Nano-9B-v2 FP8 / ui・vios・infra)を示しています。alertslvs の点線を引いていけば、別プロファイルが何を呼び込むかも同じ図で読めるはずです。

GPU 検出ロジックは dev-profile.sh:91 あたりにあって、

case "${gpu_name}" in
  *gb10*) echo "DGX-SPARK" ;;
  ...
esac

nvidia-smi の GPU 名から DGX-SPARK を自動判定して HARDWARE_PROFILE=DGX-SPARKgenerated.env に書き込みます。GB10 を積んだ DGX Spark なら、明示指定なしでも自動で正しいプロファイルに乗ってくれる、ということですね。

ちなみに HAProxy が API Gateway として 7777 番ポートで動く設計になっていて、独立した vss-api-gateway イメージは見当たりません。3.X 系では HAProxy がそのまま ingress + API Gateway 役を担う形に落ち着いた印象です。

DGX Spark で動かすときの実機ポイント

ここからは DGX Spark で base profile を立ち上げたときに見えてくる現在地を、LLM / Alert / 起動時間の三本立てで整理します。

Local LLM は vLLM コンテナで動く

base profile を起動した状態で LLM コンテナの中身を docker inspect で覗いてみると、走っているのは NIM ではなく素の vLLM コンテナでした。

$ docker inspect nvidia-nemotron-nano-9b-v2-fp8 --format '{{.Config.Image}}'
nvcr.io/nvidia/vllm:25.12.post1-py3

実際の起動コマンドはこんな形で、

python3 -m vllm.entrypoints.openai.api_server \
  --model nvidia/NVIDIA-Nemotron-Nano-9B-v2-FP8 \
  --trust-remote-code \
  --tensor-parallel-size 1 \
  --gpu-memory-utilization 0.40 \
  --port 8000 \
  --mamba_ssm_cache_dtype float32 \
  --enable-auto-tool-choice \
  --tool-parser-plugin /opt/toolcall_parser/nemotron_toolcall_parser_no_streaming.py \
  --tool-call-parser nemotron_json

公式 compose(deploy/docker/services/nim/nvidia-nemotron-nano-9b-v2-fp8/compose.yml)にもこんなコメントがついていて、

# Nemotron-Nano-V2 and tool-parser (nemotron_toolcall_parser_no_streaming.py) require vLLM 25.12+; 25.10 does not support Nemotron.

VSS 側で「vLLM 25.12+ を選んだ意図」までソースで読めるようになっています。Nemotron のツールコール用パーサも、nvidia-nemotron-nano-9b-v2-fp8-toolcall-init という init コンテナが HuggingFace から fetch してボリュームに置く仕掛けまで含まれていて、自前で tool parser を mount する手間はかかりません。

公式の prerequisites では「Fully local deployment for all agent workflows ... is planned for a future release」と将来計画として残しているのですが、hw-DGX-SPARK.env の存在と上記 compose の作りを見るかぎり、base profile に限れば実機としては Local LLM が普通に動く状態になっています。建前としての「Remote LLM 限定」と、実装としての「Local LLM 動くようにできてる」が同居しているところは、頭の片隅に置いておくと良さそうですね。

Alert は単一の vss-alert-verification に集約

Alert ワークフローのコンテナ構成は、3.2 では deploy/docker/services/alert/compose.yml の中に単一サービスとしてまとまっています。

services:
  alert-bridge:
    image: nvcr.io/nvidia/vss-core/vss-alert-verification:3.2.0
    container_name: vss-alert-bridge

image 名は vss-alert-verification ですが、コンテナ名は vss-alert-bridge のまま残されているところがちょっと面白いポイントで、過去バージョンから移行してきた compose 参照や監視スクリプトを壊さない配慮になっています。

サービス自体は単一ですが、機能としては次の 4 系統を環境変数で出し分ける作りでした。

環境変数 担当する機能
ALWAYS_ON_RULES_CONFIG Always-on alerts のルール設定
VLM_AS_VERIFIER_CONFIG_FILE VLM-as-Verifier の挙動設定
VLM_AS_VERIFIER_ALERT_TYPE_CONFIG_FILE VLM-as-Verifier の alert type 定義
RTVI_VLM_BASE_URL / RTVI_VLM_MODEL_TO_USE Real-Time VLM のエンドポイントとモデル

VLM-as-Verifier の設定ファイル(vlm-as-verifier/configs/config.yml)のパスはそのまま残っているので、過去バージョンからの設定移行も比較的素直に通せそうな印象です。

Base profile のデプロイ時間

dev-profile.sh up -p base -H DGX-SPARK ... を実行してから、HAProxy 経由でヘルスチェックが通るまでの所要時間を time 込みで実測してみました。

区間 所要
down 既存クリーンアップ 約 2 秒
Image pull(vLLM + NIM 一式) 約 55 秒
Container 起動カスケード 約 13 分(うち VLM NIM コンパイル待ちが支配的)
合計(実測) 14 分 0 秒

実測値には自分の環境固有の事情で起動中に 5 分ほどポート衝突のリカバリ作業が挟まっているので、これを差し引いた純粋な起動カスケードは約 9 分、合計でも 10 分前後というのが代表的な値です。Langfuse などを並走させていない素の DGX Spark なら、おおむねこちらの値に近いはずです。

ボトルネックは VLM(Cosmos-Reason2-8B、FP8 dynamic + KV8)の NIM 内部での TRT-LLM コンパイル時間で、コールドスタートで 8〜9 分を占めます。hw-DGX-SPARK.envNIM_DISABLE_CUDA_GRAPH=1 がセットされているのは、おそらくこの cold start を少しでも短くするための工夫ですね。

なお Image pull が 55 秒で済んだのは過去の検証で別バージョンの vLLM や NIM image を pull した際の layer cache が効いたためで、まっさらな環境からだと数分〜10 分くらいは追加で見ておくほうが安全です。

API 周りで知っておきたい挙動

過去バージョンからの移行を考えている方や、既存のクライアントコードを流用したい方向けに、API 周りで挙動が違う箇所を整理しておきます。ソースはすべて v3.2.0 タグの GitHub リポジトリで grep できます。

キャプション生成のエンドポイント

ストリームのキャプション生成は /v1/generate_captions で呼びます。

curl -X POST http://localhost:7777/v1/generate_captions -d '{...}'

ルートの実体は services/video-summarization/src/via_server.py:1013 付近にあります。旧名 /v1/generate_captions_alerts を使っていたコードがあれば、移行が要ります。リポジトリ内を grep してもほぼ消えていて、

$ grep -rn "generate_captions_alerts" services/ | wc -l
1

唯一残っている 1 件も services/alert/alert-agent-web/app/api/realtime_schemas.py:218 のドキュメント文字列で「Same as RTVI VLM generate_captions_alerts: ...」と過去名を引用しているだけでした。API レベルでは綺麗にリネーム済みです。

重複 stream / camera ID は 409 で拒否

同じ stream ID / camera ID を投げ直すと、HTTP 409 + DuplicateStreamId / DuplicateCameraId で拒否されます。コードで見るとこうなっていて、

# services/rtvi/rt-vlm/src/utils/asset_manager.py:1265 付近
if camera_id:
    existing_asset_id = self._camera_id_map.get(camera_id)
    if existing_asset_id and existing_asset_id in self._asset_map:
        raise ServiceException(
            f"Live stream with camera_id '{camera_id}' already exists",
            "DuplicateCameraId",
            409,
        )

if stream_id:
    asset_id = str(stream_id)
    if asset_id in self._asset_map:
        raise ServiceException(
            f"Live stream with stream_id '{asset_id}' already exists",
            "DuplicateStreamId",
            409,
        )

rt-vlm 側と rt-embed 側の両方に同じガードが入っています。同じ ID を投げると上書きされる、と思っていると驚かれるので、409 ハンドリングを足しておくと安全ですね。

同一 RTSP は独立ジョブ扱い

/v1/generate_captions を同じ RTSP URL に対して複数回呼ぶと、毎回独立した request ID(UUID v4)で別ジョブとして扱われます。asset_manager.py の該当ロジックで stream_id を指定しなければ str(uuid.uuid4()) で新規 UUID を発行する作りなので、同じ RTSP に対して同時並行で別々の処理を走らせたい場合は便利です。

Base profile の routing は Envoy/SDR なし

base profile では Envoy + SDR routing を経由せず Stream Processing に直接接続する構成になっています。dev-profile-base/.env に明示的なコメントがついていて、

# Direct streamprocessing (no SDR/Envoy/SDRC router on :10000)

alerts / lvs / search プロファイルには sdrc/<mode>/configs/*.yml.tmpl が残っているので、base profile だけ意図的に routing 層を外している格好です。Envoy にカスタム filter / route を差し込んでいた構成、Istio や Linkerd 経由を前提にしていた構成、ENVOY_* 系の環境変数を使っていた構成は、base profile では効かなくなる点に注意ですね。

DGX Spark の公式ポジション

公式の prerequisites では DGX Spark の位置付けが次のように書かれています。

"AGX/IGX Thor and DGX Spark platforms currently support the listed remote-LLM configurations. Fully local deployment for all agent workflows (base, summarization, alerts, and search) is planned for a future release."

建前としては Remote LLM 限定なのですが、hw-DGX-SPARK.env と vLLM 公式 compose を組み合わせることで、base profile に関しては実機としては Local LLM がそのまま動きます。alerts / search プロファイルは Remote LLM 必須のままです。

VIOS 系の image は x86_64 / AGX Thor / DGX Spark を単一の OCI image index に統合している一方で、RTVI / RT-CV / RT-CV-3D 系は SBSA 専用 tag(*:3.2.0-sbsa)が別途必要、というアーキ依存も残っています。dev-profile.sh が DGX-SPARK を検出すると RTVI_VLM_IMAGE_TAG=3.2.0-sbsa を自動で書き込んでくれるので、こちらは特に意識せずに済みます。

おまけ: services/agent をローカルビルドして image 差し替えてみる

3.2 で「全マイクロサービスとエージェントワークフローの GitHub ソース公開」が来たので、せっかくならその恩恵を実機で味わってみたい、という話です。

リポジトリの services/agent/ 配下には VSS Agent のフルソースが入っていて、Dockerfile も同梱されています。ライセンスは Apache 2.0。

$ head -1 services/agent/LICENSE.md
                                 Apache License
                           Version 2.0, January 2004

ちなみにリポジトリ全体としては Apache 2.0 + MIT のデュアル構成で、トップレベルの LICENSE に「Apache-2.0 applies to all code in the repository except the services/ui/ directory. MIT applies to the original code under the services/ui/ directory」と切り分けが明示されています。本記事の対象は services/agent なので Apache 2.0 で扱います。

公式 image を使わずに、自分でビルドしたものを差し替えてみます。

ビルドコマンド

Dockerfile は services/agent/docker/Dockerfile にあって、build context は services/ ディレクトリです。

docker build \
  -f services/agent/docker/Dockerfile \
  -t my-vss-agent:local \
  services/

ビルドは multi-stage で、各ステージの役割はこんな感じです。

ステージ ベース 役割
builder python:3.13-bookworm uv で依存解決、ARM64 用に pycairo をソースコンパイル
security-patches debian:bookworm libssl3 を CVE 修正版にパッチ
runtime nvcr.io/nvidia/distroless/python:3.13-v3.1.7 distroless で最小化
agent-runtime runtime から派生 ENTRYPOINT /vss-agent/.venv/bin/nat serve

production-grade な作りで、LGPL コンプライアンスのために FFmpeg ソースをイメージに同梱(services/agent/3rdparty/ffmpeg/FFmpeg-n8.0.1.tar.gz、ビルド時に verify_ffmpeg_tarball.py で存在と妥当性をチェック)するこだわりまで入っています。ビルドには 10〜20 分くらい見ておくと安全です。

落とし穴:security-patches ステージの URL が古びる

ここで「ソース公開時代のあるあるな落とし穴」に遭遇しました。Dockerfile の security-patches ステージで、libssl3 を Debian security repo から直接 wget する作りになっているのですが、

# v3.2.0 オリジナル(抜粋)
wget -O /patches/libssl3.deb http://security.debian.org/debian-security/pool/.../libssl3_3.0.19-1~deb12u2_arm64.deb

この 3.0.19-1~deb12u2 は本記事の検証時点では Debian security の current pool から外れていて、HTTP 404 で取れなくなっていました。CVE 修正の意図は保ちつつ、現行版を自動取得する形に書き換えるのが現実的です。

- RUN apt-get update && \
-     apt-get install -y --no-install-recommends wget ca-certificates && \
-     mkdir -p /patches && \
-     if [ "$TARGETARCH" = "amd64" ]; then \
-         wget -O /patches/libssl3.deb http://security.debian.org/debian-security/pool/.../libssl3_3.0.19-1~deb12u2_amd64.deb; \
-     elif [ "$TARGETARCH" = "arm64" ]; then \
-         wget -O /patches/libssl3.deb http://security.debian.org/debian-security/pool/.../libssl3_3.0.19-1~deb12u2_arm64.deb; \
-     fi && \
-     cd /patches && \
-     dpkg-deb -x libssl3.deb /patches/libssl3-extracted && \
-     rm -rf /var/lib/apt/lists/*
+ RUN apt-get update && \
+     apt-get install -y --no-install-recommends ca-certificates && \
+     mkdir -p /patches && \
+     cd /patches && \
+     apt-get download libssl3 && \
+     mv libssl3_*.deb libssl3.deb && \
+     dpkg-deb -x libssl3.deb /patches/libssl3-extracted && \
+     rm -rf /var/lib/apt/lists/*

apt-get download libssl3 にしておけば、bookworm-security に現時点で入っている最新の patched 版が自動で取れます。「ソース公開してくれてありがたい」と同時に、「外部依存の経年劣化と付き合う覚悟が要る」というのも合わせて学べる良い事例でした。

修正後のビルドは DGX Spark 上で約 8 分で完走し、最終 image サイズは 1.98 GB(distroless runtime 込み)でした。nvcr.io/nvidia/vss-core/vss-agent:3.2.0 の公式 image も docker images で同じく 1.98 GB。Dockerfile から組んだローカルビルド版が公式と寸分違わぬサイズに収まったのは、公式 image が Dockerfile から再現可能になっていることの分かりやすい証拠だと思います。

image 差し替え

deploy/docker/services/agent/compose.yml の該当行はこうなっていて、

vss-agent:
  # for release, change this to the versioned image from the registry
  image: nvcr.io/nvidia/vss-core/vss-agent:${VSS_AGENT_VERSION}

「change this to the versioned image from the registry」とコメントで明示されている通り、ユーザーが差し替えやすい作りになっています。my-vss-agent:local に差し替えて vss-agent だけ再起動するなら、

# compose.yml の image: を編集
sed -i 's|nvcr.io/nvidia/vss-core/vss-agent:.*|my-vss-agent:local|' \
  deploy/docker/services/agent/compose.yml

# deploy/docker に移動して、vss-agent コンテナだけ再起動(依存サービスはそのまま)
cd deploy/docker
docker compose --env-file developer-profiles/dev-profile-base/generated.env \
  up -d --no-deps --force-recreate vss-agent

--no-deps を付けないと vLLM / NIM / Phoenix 含む依存サービスも再起動されてしまい、また 10 分待つことになります。地味に重要なフラグですね。

起動後の動作確認は、まず docker inspect vss-agent で image が差し替わっているかをチェックし、

$ docker inspect vss-agent --format '{{.Config.Image}}'
my-vss-agent:local

/health 直叩きでアプリ層が応答していることを確認、

$ curl -s http://localhost:8000/health
{"value":{"isAlive":true}}

HAProxy 経由の Web UI も問題なし、

$ curl -s -o /dev/null -w "HTTP %{http_code}\n" http://localhost:7777/
HTTP 200

ここまで通れば、自分でビルドしたコードで Chat タブが動いている、という状態が手元にあるはずです。実際に Chat タブを開いて、サンプル動画(services/alert/warmup/test.mp4 の倉庫シーン)をアップロードしてから "Summarize this video in 2-3 lines." と投げてみたのが下の画面です。

ローカルビルドした vss-agent で動かす Chat タブ。Cosmos-Reason2-8B の VLM 出力をもとに Local Nemotron が英語要約を返している様子

Reasoning Trace を開くと、エージェントが VLM の出力を踏まえて要約を組み立てている過程まで追えます。ローカルビルド版の vss-agent + 公式 NIM の VLM + Local vLLM の LLM、というハイブリッド構成でも、my-vss-agent:local が API Gateway 経由でちゃんと UI と話せている、ということが一枚で伝わるかなと思います。

次回は

最後に、3.2 でひっそりと退場したサービスについて触れておきます。

Riva ASR NIM が docker-compose から消えました。音声付き動画理解のために組み込まれていたのですが、3.2 では # RIVA ASR is not yet supported というコメントとともに退場し、代わりに Nemotron 3 Nano Omni VLM のネイティブ audio パスで音声理解を行う設計に切り替わっています。

# .env のサンプル(base profile + Omni)
VLM_NAME=Nemotron-Nano-V3-Omni-GA0420-FP8
RTVI_VLM_MODEL_TO_USE=vllm-compatible
VLM_MODEL_SUPPORTS_AUDIO=true

音声理解の設計変更はそこそこ大きな話なので、次回はそこを実機で追いかける予定です。

まとめ

VSS 3.2.0 GA を DGX Spark の base profile で立ち上げてみて見えてきたことをざっくり整理すると、こんなところでしょうか。

観点 3.2 GA + DGX Spark での実機の手触り
LLM 公式 compose が vLLM コンテナで Nemotron-Nano-9B-v2 FP8 を起動。Tool-call パーサも init コンテナが自動で fetch
Alert vss-alert-verification 単一 image で Always-on / VLM-as-Verifier / Real-Time を環境変数で出し分け
デプロイ時間 base profile の素の起動カスケードで約 9 分、合計でも 10 分前後(VLM NIM コンパイル待ちが支配的)
API キャプション系は /v1/generate_captions、重複 ID は 409、base profile は Envoy/SDR なし
ソース公開 GitHub で全マイクロサービスと Agent ワークフローが Apache-2.0 + MIT で公開、Dockerfile も同梱

公式ドキュメントでは「DGX Spark は Remote LLM 限定」ですが、base profile の実装としては Local LLM がそのまま動く状態で、PoC や検証用途であればローカル完結で扱える仕上がりでした。Dockerfile が公開されたことで、自分でビルドしたコードを差し込んで挙動を確認する、という遊び方も普通にできる時代になっています。

参考リンク


国内企業 AI活用実態調査2026 配布中

クラスメソッドが独自に行なったAI診断調査をもとに、企業のAI活用の現在地を調査レポートとしてまとめました。企業規模別の活用度傾向に加え、規模を超えてAI活用を進める企業に共通する取り組みまで、自社の現在地を捉えるためのヒントにぜひ。

国内企業 AI活用実態調査2026

無料でダウンロードする

この記事をシェアする

関連記事