運転動画を検索可能にする 〜Cosmos-Embed1とDatabricksで〜
はじめに
チューリング株式会社 MLOpsチーム の @stu3dio_graph です。
チューリングでは毎日データ収集車両を走らせ,数万時間を超える大量の運転動画データを収集しています。しかしデータが増えるほど 「ほしいシーンが見つからない」問題が深刻化 していきました。日時や車両 ID でフィルタリングすることはできても,「交差点で歩行者が横断しているシーン」「道路工事を避けて車線変更するシーン」のように,映像の中身に基づいてシーンを探す手段がなかったのです。
運転動画を探すためのシステム
このたび,NVIDIA が公開した映像-テキスト埋め込みモデル Cosmos-Embed1 と Databricks Vector Search を組み合わせ,約 54 万シーン[1] (約 2,000 時間分の走行データ) に対するセマンティック検索システムを構築しました。本記事ではその設計と実装についてご紹介します。
今回作ったシステムでは「道路工事で交通誘導員が止まれの仕草をしている」といった複雑なクエリに対しても,うまく運転動画を検索できています。

この記事でわかること
- NVIDIA Cosmos-Embed1 の特徴と、自動運転ドメインでのモデル選定
- Video Embedding をオフラインで事前計算し,Text Embedding だけリアルタイム計算する非対称アーキテクチャ
- AWS Lambda (Docker) + Databricks Vector Search によるサーバーレス検索基盤の実装
- オンプレ GPU (H100) での日次 embedding バッチパイプライン
- Provisioned Concurrency のスケジュール制御などのコスト最適化テクニック
社内での課題
このシステムは,社内の複数チームから似たような要望が出ていたことがきっかけで作り始めました。
シナリオテストの拡充
チューリングの自動運転モデル評価では,特定の状況を再現する シナリオテスト が重要な役割を果たしています。右左折,一時停止,車線変更,歩行者がいる右折,といったシナリオを定義し,モデルがそれぞれの状況で正しく振る舞えるかを検証します。
現状ではそのためのシナリオの作成が手作業で行われていました。シナリオ作成担当のチームからは「シーンを検索して自動でシナリオを更新したい」「たとえば『工事現場の人を避ける』というクエリから自動でシーンを引っ張ってきて追加したい」といった声が挙がっていました。
学習データセットの品質管理
データ収集ドライバーが走行中に気づいた問題をメモし,あとから該当シーンを探してタグ付けする運用が行われていました。しかし路駐避け,右左折,黄色信号,飛び出しといったシーンを大量のデータの中から手作業で見つけていくのは現実的ではなく,見過ごされてしまうシーンも多くありました。
モデル挙動の分析
強化学習のチームからは,モデルが苦手とするシーンを系統的に集めたいというニーズがありました。たとえば「工事現場シーン」をひとつ見つけたら,同種のシーンをまとめて収集して弱点を定量的に分析したい,という要望です。
これらの課題に共通しているのは,「映像の中身をキーにしてシーンを探したい」という点です。検索の入力としてはテキスト(自然言語での条件指定)とシーン(既知のシーンに似たものを探す)の両方を受け付ける必要がありそうです。
ベクトル検索の技術自体は何年も前から存在します。しかしインデックス構築から検索UIまでの運用が重く,一部のエンジニアだけが触る状態で定着しませんでした。今回 Cosmos-Embed1 の登場(ドメイン特化モデルによる精度の飛躍)と Databricks Vector Search の成熟(Delta Table との統合でインデックス運用が激減)の2要素が揃ったことで,比較的短期間で実際に使われるものをUIレベルまで落とし込むことができました。
Cosmos-Embed1 とは

Cosmos-Embed1 は,NVIDIA が 2025 年に公開した 映像とテキストの joint embedding モデルです。10 億パラメータのモデルで,映像とテキストを同一のベクトル空間に射影します。NVIDIA Cosmos プラットフォーム の一部として公開されており,商用利用が可能です。
最大の特徴は,自動運転・ロボティクスといった "物理世界" のデータに特化していることです。学習データの約 800 万本のユニーク動画には,OpenDV(自動運転データセット),AgiBot,BridgeV2 などの物理世界データセットが含まれています。
なぜ Cosmos-Embed1 を選んだか
モデル選定で最も重視したのは、自動運転ドメインでの精度です。
| モデル | OpenDV T2V-R@1 | Kinetics-400 F1 |
|---|---|---|
| Cosmos-Embed1-448p | 34.66% | 88.21% |
| InternVideo2-1B | 7.40% | 62.80% |
OpenDV(AV データセット)での Text-to-Video Recall@1 で InternVideo2-1B を大幅に上回る精度を出しています。[2]
CLIP は画像単体の embedding なので時系列の動きを捉えられません。走行シーンでは「車線変更」「右折」「歩行者の横断」など 動きが重要な意味を持つ ため,フレーム列を直接入力できる Cosmos-Embed1 が適しています。そのため今回のタスクではもっとも高い解像度と性能を持つ Cosmos-Embed1-448p を使うことにしました。
Qwen3-VL-8B の検証結果
モデル選定の際に,パラメータ数が10倍以上の Qwen3-VL-Embedding-8B も検討しました。しかし今回の検証では,走行動画データセットで「intersection(交差点)」すら正しく識別できませんでした。推論時間とメモリ消費量が大幅に伸びるにもかかわらず Cosmos-Embed1 に大きく劣る結果となりました。汎用モデルのスケールアップが必ずしもドメイン特化タスクの精度向上に繋がるわけではないようです。
Databricks Notebook でのPoC
最初の検証は1回の走行(約1時間)分のシーンを Video Embedding にして Text Embedding との類似度を比較するシンプルなものでしたが、「"waiting pedestrians"(歩行者を待っている)」で検索するときちんとそのシーンが取得でき,手応えを感じられました。
Databricks Notebook を使うことで,車両IDや日時などのメタデータを管理している既存の Delta Table をそのまま活用できました。試験的に作った embedding を parquet に変換し Vector Search Index を作って簡単に試すところまで,インフラ構築をすることなくNotebook上の数十行のコードで完結しました。
Databricks アカウントがあれば Notebook を簡単に共有できます。PoCページとして社内公開し,複数のチームで実際に使って試してもらいました。「観覧車も検索できた」「文字も検索できた」などの思わぬ発見もあり,手応えを得て,本番環境の構築に進むことになりました。

全体アーキテクチャ

システムは大きく4つに分かれています。
- フレーム画像抽出 (オフライン日次バッチ)
- Embedding 計算 (オフライン日次バッチ)
- テキスト→ベクトル検索 (オンライン)
- 類似シーン検索 (オンライン)
設計時の重視ポイント
検索システム設計の初期段階では「各 embedding をどこで計算するか?」が大きな課題でした。Video Embedding の計算にはGPUがほぼ必須であり,リアルタイムで毎回計算するのは非現実的です。一方で Text Embedding はCPUでも数百ミリ秒程度で計算できます。
この非対称性を活かして,ビデオ側は事前計算してインデックスに格納し,テキスト側だけリアルタイム計算する設計を採用しました。検索体験はリアルタイムでありながら,GPU のランニングコストはバッチ処理分だけで済みます。
テキスト検索と類似シーン検索で経路が異なるのも同じ理由です。テキスト検索はテキストをベクトルに変換するために Cosmos-Embed1 の推論が必要で Lambda を経由します。その一方で類似シーン検索では Delta Table に embedding が既にあるので直接 Databricks を叩けます。モデル推論が必要かどうかで経路を分けたのもポイントのひとつです。
フレーム画像抽出パイプライン

生映像からのフレーム抽出は、embedding 生成とは別のジョブとして分離しています。AWS Batch を使ってS3の生映像から8フレーム分を切り出して tar.gz にまとめ,S3に書き戻すだけです。
最初は映像から直接 embedding を生成するパイプラインを想定していましたが、初期検証でGPU推論よりも動画デコードの方が重い処理であることがわかり,分離することにしました。
この分離には 2 つのメリットがあります。
- ボトルネックの分離: Fargate の CPU で動画デコードし,GPUで推論する
- 再利用性: フレームを一度切り出しておけば,モデル差し替え時にやり直し不要
入力フォーマットはCosmos-Embed1 のモデル仕様に準拠し,JPEGに比べて圧縮効率の高いWebP で圧縮し, tar.gz にまとめて S3 の GET リクエスト数を 1/8 に削減しています。フレーム数やサンプリングレートはモデルごとの設定ファイルで差し替え可能にしており,将来別のモデルを試す際にもフレーム抽出ジョブの再実行だけで対応できます。
日次Embedding計算パイプライン

Slurm + オンプレH100
embedding の日次バッチはGaggle Cluster[3] のGPU (NVIDIA H100)で高速に実行しています。平均して秒間10シーンほど処理でき,1日の追加量を十分カバーできます。AWS Batch + EC2 P5 インスタンスのオンデマンド料金は比較的高く,日次バッチではオンプレが圧倒的にコスト効率がよいです。深夜帯に実行するため,自動運転チームの日々の学習とリソースを取り合うことも少なく済んでいます。
毎日2時に未処理シーン検出,推論,Parquet書き出し,S3へのアップロード,処理結果の通知を行うSlurmジョブとして直列実行しています。
未処理シーンの検出
日次バッチはS3からデータを読み取り,S3に書き戻すだけのシンプルな構成としました。S3上の tar.gz と Parquet の集合差分を取るだけで未処理シーンを検出できます。
GPUを使い切る
実際のジョブではGPUの推論よりも,S3からの tar.gz のダウンロードがボトルネックになることがわかりました。そこで別スレッドで次の 3 バッチを先読みする Producer-Consumer パターンを採用しています。ディスクを経由せず直接メモリ上に展開することで,GPUのアイドル時間を少しでも減らす工夫をしています。
Databricks DLT と Vector Search

S3に出力された Parquet を Auto Loader でイベント駆動取り込みします。COPY INTO ではなくDLTを選択した理由は冪等性です。COPY INTO でもファイル単位の重複排除は可能ですが,モデル更新時に同じシーンを異なるベクトルで上書きするケースでは行き届きません。dlt.apply_changes() によるシーンキー単位の UPSERT であれば,同一シーンの再計算結果を自然に上書きでき,全量再計算→S3に置き直すだけでインデックスが更新されます。
Vector Search Endpoint と Index
これで embedding を Delta Table に取り込むことができました。しかしここから高速なベクトル検索を実行するには,このテーブルから Vector Search Endpoint と Index を作る必要があります。Vector Search Endpoint は常時起動しておく必要があり,月々数十ドル程度のコストがかかります。
Vector Search Index は DELTA_SYNC + TRIGGERED モードを採用しました。CONTINUOUS (常時同期) は常にプロセスが走りコストが高いため見送りました。今回は日次バッチであるため,DLT 完了後に1回syncしておけば十分だからです。実際には既存のパイプラインの末尾で Databricks API 経由の sync をトリガーするだけなので,追加の運用負荷はほとんどありません。
テキスト検索用Lambda

なぜLambdaを使ったか?
事前のPoCで,Text Embedding は軽量な text-encoder のみを使うためGPU 不要であり,CPU だけでも数百ミリ秒で処理が完結することがわかりました。
利用頻度が低い社内ツール(1 日に数十〜数百リクエスト)であり,SageMaker Endpoint といった常時課金型のサービスを使うのはやや過剰と考えました。Lambda はリクエストがなければ課金がなく,コスト面で有利だと考えました。
Docker Image にモデルを焼き込む
PyTorch + Cosmos-Embed1 のモデル重みは Lambda zip の上限(250MB)を大幅に超えるため,コンテナイメージを使った Lambda(最大 10GB)を採用しました。
# モデルを事前ダウンロード(ビルド時にイメージに焼き込む)
RUN python -c "from transformers import AutoModel; \
AutoModel.from_pretrained('nvidia/Cosmos-Embed1-448p', trust_remote_code=True)"
# Lambda 実行時はオフラインモード
ENV TRANSFORMERS_OFFLINE=1
ENV HF_HUB_OFFLINE=1
開発初期に「HuggingFaceのキャッシュを使えず,毎回ダウンロードが走っていて重い」といった問題がありました。Docker イメージにモデルを事前キャッシュし TRANSFORMERS_OFFLINE=1 で実行時のネットワークアクセスを完全遮断することで解決しています。
キャッシュ戦略で warm start を高速化
Lambda は一定時間以内であればランタイムが使い回されるため,モデルのロードや Secrets Manager の呼び出しを @lru_cache でメモ化し,OAuth トークンには TTL つきキャッシュを使うことで warm start を高速化しています。
また常にLambdaを起動する Provisioned Concurrency を利用した warm start の高速化も実施しています。24時間確保するとコストが高いため,平日の営業時間に限って有効化しています。EventBridge Scheduler の Universal Target で Lambda SDK API を直接呼び出すことで実装しました。
検索UI: 社内ツールへの統合

検索システムはNext.jsで書かれた社内ツールの走行データビューア (JADD Studio) に統合し,2つの検索画面を提供しています。
- 自然言語シーン検索: テキスト入力→Lambda→ Databricks (Vector Search)
- 類似シーン検索: scene_id→Databricks (SQL Warehouse → Vector Search)
テキスト検索で使用するLambdaは既存の社内API Gatewayを通し,類似シーン検索はSQL Statement APIを直接呼び出しています。先に述べたとおり,cold start 時は数秒程度の起動時間が必要ですが,warm start 時は1秒以下でレスポンスが得られ,ユーザー体験もかなりいいものになりました。
入力がテキストと scene_id で根本的に異なるため,画面を分離しました。

このように “navigating through a road blocked by construction cones” (工事用コーンで狭くなった道を通り抜ける) といったクエリに対してもきちんと該当するシーンが取れていることがわかります。
初期のリリースではすべての運転動画を検索対象としていました。しかししばらく使われるうちに「車両や日次で絞り込みたい」という要望が挙がってきました。そのため現在では車両や日次でのフィルタリング機能も追加しています。

類似シーン検索画面についても,シーンIDからの入力を受け付けることはもちろんのこと,自然言語シーン検索の検索結果から「類似シーン」を押して深堀りし,連鎖的に似たシーンをたどれるような設計にしています。また検索状態をURLクエリパラメータに保持しており,結果をそのまま Slack で共有できます。
まとめ
Cosmos-Embed1 と Databricks Vector Search により,最低限の実装とインフラコストで運転動画に対するセマンティックな検索システムを構築できました。Video Embedding は事前計算し,Text Embedding はリアルタイム計算するという非対称性をうまく利用し,既存のリソースを活かした効率的な設計を実現しました。
Embedding テーブルにデータを格納するだけでなく,社内データビューアにも統合したことで,エンジニアに限らず利用が拡大しました。すでに社内では多く使われており,自動運転開発に向けた強力なサポートツールとなっています。
今後は複数カメラへの拡張や,検索精度の定量評価を進めていく予定です。Cosmos-Embed1 の embedding で類似シーンを除外して学習データの多様性を高める,データセットをクラスタリングやt-SNEなどの次元削減で可視化する,より効率的なデータ収集の計画を立てるなど,今回のシステムは検索を超えて利用される可能性があります。
おわりに
私の所属する MLOps チームでは、このようなデータ基盤・ML パイプラインの構築から AWS / Databricks のインフラ設計まで、幅広いエンジニアリングに取り組んでいます。自動運転AI開発を前に進めるには,機械学習エンジニアだけでなく,こうした周辺領域を作り込んでいくことも重要です。具体的なJob Descriptionも公開中です。ご興味のある方はぜひご応募ください。
-
走行動画を20秒ごとに分割した単位。 ↩︎
-
Cosmos-Embed1-448p Model Card の Metrics セクションより。8フレーム等間隔サンプリング、DSL rerank 適用時の値)。 ↩︎
-
オンプレGPUクラスタ。 ↩︎
Discussion