チューリングの自動運転システム開発環境と、それを支える開発ツールたち
はじめに
こんにちは。チューリングのDriving Softwareチームのマネージャーを務めている渡邉(@sangotaro)です。Driving Softwareチームは、チューリングの自動運転システムの開発を担当するチームで、自動運転AI以外の領域、例えばシステム制御やソフトウェアインフラの構築などを手掛けています。
先日、チームで栃木県の那須に開発合宿に行き、「約3時間でテックブログを書き上げる」というユニークな企画に挑戦しました。いくつかのチームに分かれ、それぞれが記事のテーマを決めて執筆した結果、公開までこぎつけることができた記事がなんと5本も完成しました!今回お届けするこの記事は、その中の1本であり、同じチームだった徳弘(@res_circuit)と共同で執筆しました。
この記事に先立ち、すでに2本のテックブログが公開されていますので、ぜひそちらもご覧ください。そして残りの2本も近日中に公開予定ですので、お楽しみに。
それでは本編に進みましょう。
チューリングの自動運転システムとは?
自動運転システムとは、計算機やカメラ、センサーなどのハードウェアと、それらを統合して制御するソフトウェアから構成される複合的なシステムを指します。このシステムは、リアルタイムで周囲環境を認識し、判断し、車両を制御するための一連の処理を行います。
チューリングの自動運転システムは、カメラ主体のエンドツーエンド(E2E)システムであり、カメラ映像を入力として、自動運転AIが認知判断を行い、車両を制御します。
チューリングの自動運転システムのコアコンポーネント概念図
システムはLinux上で動作し、C++およびPythonで開発された複数のプロセスで構成されています。各プロセスはモジュール化されており、それぞれが特定の特定の役割を担っています。たとえば、以下のようなプロセスがあります。
- カメラ制御:カメラの設定や映像の取得を担当
- モデル推論:AIモデルによる認知・判断を実行
- 車両制御:制御信号を生成し、車両に指示を送信
これらのプロセスは、Publisher/Subscriber方式のプロセス間通信(IPC)を通じて、データや指示をリアルタイムでやり取りします。この仕組みは、マルチコア環境を活用することで、各プロセスを並列に実行できるよう設計されています。また、システム全体を粗結合な構造にすることで、各コンポーネントが独立して動作しつつも緊密に連携することが可能です。
このアプローチにより、効率的なシステム制御を実現するだけでなく、開発生産性も向上しています。たとえば、個別のプロセスが他のプロセスに直接依存しないため、新しい機能の追加や既存機能の改良が容易になります。さらに、特定のプロセスのみを独立してテストしたりデバッグしたりすることもできるため、全体の開発フローがスムーズになります。
複数プロセスがプロセス間通信により協調している様子
本ブログでは、こうしたシステムの開発を支えるツールやサービスに焦点を当て、それぞれがどのように貢献しているかをご紹介します。なお、本ブログでは自動運転そのものや機械学習モデルの開発部分には触れておらず、それらは別の専門的な領域として扱っています。
チューリングの自動運転システムは、多数のモジュールが連携する高度な複雑性を持ちながらも、その開発手法や基盤となるツール群は、一般的なシステム開発と変わりません。この共通性を通じて、開発の再現性や生産性を高める工夫をご紹介していきます。
実行環境
計算機デバイス
チューリングの自動運転システムは、実際の走行データ収集や自動運転機能を車両に搭載されたエッジデバイス上で動作させています。このエッジデバイスには、NVIDIAのJetson AGX Orinが採用されています。Jetson AGX Orinは、NVIDIAが提供するエッジ環境向けSoCの中で2024年時点でのフラッグシップモデルであり、自動運転システムに必要な高い計算能力を、小型で省電力なフォームファクタで提供します。
車両に搭載されたエッジデバイスには、カメラやLiDARなどの各種センサーが接続されています。これらのセンサーは、車両周辺の環境データをリアルタイムで収集し、Jetson AGX Orin上で処理されます。このように、エッジデバイスは自動運転システムの中心的な役割を果たしています。
Jetson AGX Orin
机上での開発時には、実際に車両に搭載しているエッジデバイスに加えて、Jetson AGX Orin開発キットやx86アーキテクチャのPCも使用しています。
Jetson AGX Orin開発者キット
チューリングの自動運転システムは、ARMベースのエッジデバイスだけでなく、x86環境でもビルド・実行が可能です。この柔軟性により、開発者は手元のPCで迅速にコードの実行やテストを行いながら、最終的なデプロイ先であるエッジデバイスへの移行をスムーズに行うことができます。
OS
Ubuntuロゴ
エッジデバイスでは、NVIDIAのJetson Linuxを使用しています。エッジデバイスのOSとしてはJetson Linuxを使用していますが、これはUbuntuをベースにしており、ほぼUbuntuと同じ環境と言えます。また、Ubuntuの公式ページでもJetson向けのサポート情報が提供されており、CanonicalとNVIDIAの密接な連携が見て取れます。このように、Ubuntuベースの信頼性の高い環境が構築されています。
一方、開発環境で使うx86 PCでもUbuntuを標準として採用しています。これにより、開発時と実行時でOS環境が統一され、効率的な開発が可能となっています。Ubuntuのバージョンは基本的に22.04以降をベースとしています。
コンテナの活用
Dockerロゴ
アプリケーションの実行環境にはDockerコンテナを活用していて、ほとんどのプロセスをコンテナで実行するようにしています。これにより、再現性の高い環境を簡単に構築できます。特に、自動運転システムの開発では、各種センサハードウェアやアクセラレータに依存する複雑で大量のライブラリやドライバを扱う必要がありますが、これらをDockerイメージにまとめることで、必要な依存関係を一括管理し、環境の差異による問題を最小化しています。
さらに、ホスト環境への汚染を防げる点も大きな利点です。コンテナ内で動作するため、開発時にインストールしたライブラリやツールがホストOSに影響を与えることはありません。また、エッジデバイスを使用する際にも、Dockerを活用することで初期化作業や依存関係のインストール作業を最小限に抑え、効率的にシステムを展開できます。
なお、Jetsonのエッジデバイスと開発用のPCではベースイメージが異なるため、それぞれの環境に適応させる工夫も行っています。具体的には、パッケージのインストールスクリプトや設定ファイルを可能な限り切り出して共通化することで、異なる環境間での一貫性を保ちつつ、管理の複雑さを軽減しています。
開発環境
プログラミング言語
PythonとC++とTypeScriptのロゴ
チューリングの自動運転システムは、センサ制御、AIモデル推論、車両制御、UIといった多岐にわたるモジュールで構成されています。それぞれのモジュールが異なる役割を担っており、その役割に応じてセンサ技術や車両制御のアルゴリズム設計など、専門的なドメイン知識が求められるます。ただし、開発自体はC++とPythonという広く使われている一般的なプログラミング言語で行われており、これにより豊富なツールやライブラリを活用した効率的な開発が可能になっています。
各モジュールの開発には、C++とPythonが主に使用されています。プロセスによってはC++のみ、もしくはPythonのみで構成されているものもありますが、一部のプロセスではCythonを活用してC++とPythonを統合して開発されています。
2024年時点で、C++はC++17、Pythonは3.12を採用しており、堅実で安定した言語仕様を活用しています。この構成により、システム全体のパフォーマンスと柔軟性が確保され、低レベルの処理にはC++の高速性を、データ処理やアルゴリズム開発にはPythonの簡潔さと豊富なライブラリを活かしています。
また、UI部分ではブラウザ技術を活用しており、その開発にTypeScriptを使用しています。自動運転システムにおけるUIによる可視化は非常に重要な役割を果たしますが、特に組み込み環境におけるUI開発については、一般的に公にされているノウハウが少ない印象があります。そうした中で、Web技術を活用した柔軟なUIを実現できていることは、大きな利点と言えるでしょう。これにより、フロントエンドの開発効率を向上させつつ、直感的で拡張性の高いインターフェースを提供しています。
コード管理
GitHubロゴ
チューリングの自動運転システムのコードは、GitHubを利用して一元管理されています。GitHubでは、コードの共有やレビューを効率的に行えるだけでなく、GitHub ActionsやGitHub Packagesなどの機能を活用しています。これらの機能により、依存関係の管理やリリース作業が効率化されています。なお、CI/CDの詳細については、以降のセクションで説明します。
コードの構成については、モノリポを採用しています。すべてのモジュールを1つのリポジトリに統合することで、依存関係の管理や変更の追跡が容易になります。ただし、機械学習モデルの開発は特性上、別リポジトリで管理しており、専用のワークフローで進められています。
また、タスク管理にはJiraを使用しており、開発チーム間の連携を強化しています。GitHubのプルリクエスト(PR)とJiraのチケットを紐づけることで、タスクの進捗状況を簡単に追跡できる仕組みを構築しています。これにより、コード管理とタスク管理が密接に連携し、効率的な開発プロセスを実現しています。
プロジェクト・パッケージ管理
uvロゴ
プロジェクトはPythonプロジェクトが中心的な役割を担っており、プロジェクト管理やパッケージ管理には全面的にuvを採用しています。uvは、Pythonのpyproject.toml
をベースに設計されており、プロジェクト全体の管理や依存関係の整理、さらにはパッケージのビルドや公開までを一元化して扱える強力なツールです。
Pythonのパッケージマネージャーは、多様なツールが存在し、その選択肢が時代とともに変化してきましたが、uvはその課題を解決する可能性を秘めていると考えています。uvは開発が非常に活発で、チューリングが直面するさまざまなユースケースを満たしており、その柔軟性と使いやすさから、チューリングのプロジェクト全体で広く採用されています。なお、uvについては、チューリングのテックブログで紹介記事を公開していますので、ぜひそちらもご参照ください。
記事でも紹介しきれていないユースケースもあるので、それについては別途記事にする予定です。
Gemfuryロゴ
Pythonプロジェクトでは、uvを活用して多くの依存パッケージを効率的に管理していますが、中には自前でビルドする必要があるパッケージや、チューリング内で開発している独自パッケージも含まれています。これらの独自パッケージは、パッケージのホスティングサービスであるGemfuryを利用して管理しています。Gemfuryを活用することで、社内で開発したパッケージを安全かつ効率的にチーム内で共有し、依存パッケージや独自モジュールの更新をスムーズに行う環境を整えています。
また、機械学習モデルについては、コード管理のセクションで述べたように別リポジトリで管理しています。モデルとその推論コードはモジュール化されており、社内Pythonパッケージとして提供されています。これにより、モデル自体のバージョン管理が可能となり、運用時の差し替えも簡単に行えます。実際には、モデルのリポジトリが別れているにもかかわらず、Pythonパッケージとして管理することで、モデルの差し替えはPythonパッケージのバージョンを切り替えるだけで済むため、非常に効率的です。
pnpmロゴ
フロントエンド開発では、pnpmをパッケージマネージャーとして採用しています。当初は特にこだわりがあったわけではありませんが、package.json
でpatchedDependencies
を使用したいユースケースがあったため、pnpmを導入しました。使用してみたところ、パフォーマンスが非常に高速で、現在のところ満足しています。
ビルドツール
Sconsロゴ
各モジュールが独立してビルド可能な構成を採用しており、効率的な開発を実現しています。ビルドツールとしてはSConsをメインに使用しており、PythonおよびC++のコードを統合的にビルドしています。
SConsは、日本ではあまり知名度が高くない印象ですが、ビルド設定をPythonライクなスクリプトで記述できるため、開発者にとって比較的使いやすいツールです。特に、Pythonコードを扱う開発者にとっては、直感的に設定を記述できる点が魅力です。ただし、チューリングとしてSConsに強いこだわりがあるわけではなく、現在の規模と要件に適しているため採用しています。
一般的に、大規模プロジェクトではSConsにビルド時間の課題があるとも言われていますが、自動運転システムのプロジェクトはラップトップ等でフルビルドしても10分以内で完了する規模のため、現状では十分に満足しています。
Viteロゴ
また、フロントエンドのビルドにはViteを採用しています。Viteの高速性を活かしつつ、プロジェクト全体をビルドする際には、SConsからViteのビルドプロセスがキックされる仕組みを採用しています。これにより、バックエンドとフロントエンドのビルドプロセスを統合的に管理し、開発効率を向上させています。
エディタ
VS Codeロゴ
特定のエディタを指定していませんが、VS Codeを利用している開発者が多い傾向にあります。その理由の一つに、VS Codeが提供するRemote Development機能の便利さがあります。チューリングではエッジデバイスやコンテナを活用して開発を進めることが多く、この機能を使うことでリモート環境へのスムーズな接続や効率的な作業が可能になっています。
また、プロジェクト内のほぼすべてのモジュールがコンテナ化されているため、Dev Containersも積極的に活用しています。これにより、各開発者が統一された環境で作業できるだけでなく、環境構築の手間を大幅に削減することができています。
さらに、チューリングでは開発効率を高めるため、GitHub CopilotやChatGPTといったAIツールも会社補助で利用可能です。これらのツールは、コードの補完やアイデアの提案、エラー解決の補助に役立っており、開発者の生産性向上に寄与しています。AI支援という点では、VSCodeの代わりにVSCodeのフォークであるCursorを使っているメンバーもいます。ちなみにこの文章もAIが書いています。
CI/CD
自動テスト
pytestロゴ
PythonとC++のモジュールごとに自動テストを設計し、コードの品質を保証しています。Pythonのテストにはpytestを利用し、その直感的なインターフェイスと豊富なプラグインを活用しています。各モジュールは並列テストが可能な構成になっており、pytestの並列実行機能を用いてテストの実行時間を効率化しています。
Catch2ロゴ
C++のテストでは、軽量で柔軟なフレームワークであるCatch2を使用しています。ただし、テスト実行のインターフェイスを統一するためにpytest-cppを導入しており、PythonとC++のテストをpytestから一貫して実行できるようにしています。このアプローチにより、異なる言語のテスト環境でも操作性を統一し、開発者の負担を軽減しています。
テストの種類としては、ユニットテストとインテグレーションテストを実施しており、個々のモジュールの動作確認から、モジュール間の連携の検証までを網羅しています。また、テストカバレッジを測定しており、カバレッジの結果はCodecovというサービスを利用して可視化・管理しています。これにより、プロジェクト全体のテストカバレッジをチームで共有し、不足部分を補う開発サイクルを実現しています。
Codecovロゴ
Codecovでは、x86やJetsonなどの異なる環境で実施されたテスト結果をflags機能を用いて個別に管理し、最終的に統合された全体カバレッジレポートを生成しています。これにより、環境ごとのカバレッジ差異を分析しつつ、プロジェクト全体のカバレッジを包括的に把握することが可能です。この仕組みを活用することで、不足部分を特定し、効率的に補う開発サイクルを実現しています。
静的解析
Ruffロゴ
自動テストに加え、チューリングでは静的解析ツールを活用し、コード品質の維持と向上を図っています。Pythonコードに対しては、Ruffを使用してlintとフォーマットを統合的に行っています。Ruffの高速性と多機能性により、開発者は効率的に潜在的な問題を検出し、修正することができます。
mypyロゴ
さらに、型チェックツールとしてmypyを導入しており、静的型付けを活用してコードの安全性を向上させています。
これらのツールを導入することで、コードレビュー前に多くの問題を解決でき、チーム全体の開発効率が向上しています。
CI/CD環境
チューリングでは、GitHub ActionsをはじめとするGitHubのCI/CD環境を開発パイプライン全体の基盤として活用しています。この環境を利用することで、コードの変更が迅速かつ確実にテスト・ビルドされ、最終的なリリースに至るまでのプロセスが効率化されています。先の自動テストや静的解析、ビルドの実行はすべてGitHubのCI環境上で管理されています。
ビルド成果物として生成されるDockerイメージは、GitHub Packagesにプッシュしてホストしています。GitHub Packagesは、GitHub Actionsと連携すると非常にコスト効率が高く(ほとんど無料といえるレベル)、頻繁なビルドとデプロイが必要なプロジェクトにとって最適な選択肢となっています。
さらに、DockerイメージのビルドにはBuildxを活用しており、マルチアーキテクチャビルドを行っています。このプロセスでは、x86およびARM64向けにそれぞれ個別にイメージをビルドし、それらを統合して1つのマルチアーキテクチャイメージとしてパッケージ化しています。
namespaceロゴ
さらに、チューリングではGitHubの標準hostedランナーに加えて、サードパーティのランナーサービスであるnamespaceや、独自のself-hostedランナーを活用しており、プロジェクトの特性に応じた柔軟なCI/CD環境を構築しています。
GitHubの標準ランナーは、一般的なビルドやテストに十分対応可能です。しかし、組み込み系プロジェクトでは、特殊な要件を満たすために、サードパーティのランナーサービスであるnamespaceを併用しています。namespaceは、x86やARM64環境向けの巨大なランナーを提供するほか、GitHub Actionsの標準キャッシュ制限(10GB)を超えた大容量キャッシュの利用が可能です。これにより、ビルドやテストで大きな依存関係を扱う際の効率が大幅に向上しています。なお、namespaceのランナーは、実態としてGitHub Actionsのself-hostedランナーとして動作しており、既存のワークフローにシームレスに統合されています。
また、特殊なハードウェアが必要なケースでは、独自に整備したself-hostedランナーを利用しています。これらは、先に出てきたJetson AGX Orin搭載デバイスや、GPUを搭載したワークステーションで稼働しており、GPUを使用したモデル推論のテストや、エッジデバイス上での実機テストが必要な場合に活用されています。これにより、標準ランナーでは対応が難しいハードウェア依存のタスクにも柔軟に対応可能です。
最後に、自動運転システムのCI/CDパイプライン全体の流れを以下の図にまとめています。
チューリングの自動運転システムのCI/CDパイプライン
システム構成管理
Ansibleロゴ
モジュール周りはDockerを活用することで高い再現性を確保していますが、エッジデバイスの初期設定(キッティング)には、ユーザーの作成やOSのシステム設定、コンテナのダウンロードなど、ソフトウェアのデプロイに必要な作業が含まれます。こうした作業は、開発当初は手動で行われることが多いですが、チューリングではこれらを可能な限り自動化し、非常に高い再現性を実現しています。
これらのプロセスには、構成管理ツールであるAnsibleを活用しています。構成管理をコード化することで、異なるデバイス間での設定ミスを防ぎ、開発から運用までのフローを効率化しています。さらに、エッジデバイスのBSP(Board Support Package)が更新された場合でも、構成管理がコードとして管理されているため、変更の検証が容易です。また、更新内容をコード履歴として追跡できるため、トラブル発生時の原因特定やロールバックもスムーズに行えます。
エッジデバイス上では、アプリケーションのほとんどがDockerコンテナ内で動作しており、モジュール間の環境がホストOSから独立しています。このような環境の分離により、ホストシステムの変更がアプリケーションに影響を与えにくくなっており、AnsibleはDocker環境の準備を含めたホストシステム全体の設定を効率的に管理する役割を担っています。
こうした構成管理の自動化により、エッジデバイスの初期化を迅速かつ確実に実施し、運用環境全体の信頼性を高めています。
デバッグ・モニタリング
走行データのリプレイ
走行データの収集と活用は、自動運転システムの検証・改善に欠かせません。チューリングでは、走行中に取得した膨大なデータを活用し、机上環境でのリプレイを可能にする仕組みを構築しています。このリプレイ機能により、実際の走行条件を再現しながら、不具合の原因調査や、ログデータを活用した新機能の開発・改善を効率的に進めることができます。
最近では、データの可視化にはrerunというツールを活用しており、収集したデータの直感的な可視化や分析が可能になっています。rerunに関しては、別のテックブログで詳細を紹介する予定です。
モニタリング
データ収集車両のエッジデバイスは、AWSのSession Managerを通じてリモートからログインできるように設定されています。これにより、車両が都内を走行している間でも、遠隔地から不具合の原因調査や問題解決が可能です。車両は1日に数時間から半日程度の走行を行っており、運用中のトラブルは避けられないものの、このリモートデバッグ機能により迅速な対応が可能となっています。
さらに、システムには監視機能も組み込まれており、センサーに異常が発生した場合やログが書き出されていない場合を自動的に検知します。このような異常が検知されると、Slack通知を活用してチームにリアルタイムで報告される仕組みを採用しています。これにより、問題発生時には迅速な対応が可能となり、運用全体の安定性を確保しています。
シミュレータの活用
自動運転システムの検証には、ドライビングシミュレータも活用しています。チューリングでは、シミュレーションツールとしてMetaDriveを使用しており、仮想的な運転環境を構築しています。シミュレータの出力する画面は、カメラ映像と同様に扱うことができるため、チューリングの自動運転システムで利用しているカメラ入力モデルに適用可能です。このツールを活用することで、現実世界の走行データだけでなく、仮想環境でも効率的な検証が可能になります。MetaDriveについては、入門記事があるので、そちらも参照してください。
おわりに
以上が、チューリングの自動運転システム向けソフトウェア開発で活用しているツールのご紹介でした。すべてを網羅することはできませんでしたが、多くのツールがソフトウェア開発において一般的に使われている技術であることがお分かりいただけたのではないでしょうか。
私たちチューリングでは、一緒に完全自動運転の実現を目指す仲間を募集しています!今回ご紹介した自動運転システムのソフトウェア開発に加え、自動運転AIの開発、MLOps、組み込み、Web開発など、幅広いポジションで募集しています。
興味を持っていただいた方は、ぜひDMなどでお気軽にご連絡ください。カジュアル面談やオフィス見学も歓迎です。また、「おいしいご飯を食べたい」という理由でも大丈夫です!ライトな交流から始めてみませんか?
最後までお読みいただきありがとうございました。それでは、次回の記事もお楽しみに!
Discussion