2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

リアルタイムGPU推論パイプラインの高速化

Posted at

概要

大学院時代からGPUプログラムの高速化に何度か取り組んできました。最近もリアルタイムGPUアプリケーションの開発に携わる機会があり、その中で得た知見を簡単にまとめます。

リアルタイムGPU推論アプリケーションのパフォーマンス最適化では、ロックフリー並行キューとCUDAマルチストリームの組み合わせが、計算とデータ転送のオーバーラップを実現する強力な手法となります。

対象読者: CUDAを用いた推論パイプラインの最適化に取り組むエンジニア

0. リアルタイムGPU推論の応用例

本記事で解説する技術は、以下のような幅広い分野で活用されています。

a. 監視カメラAI(Computer Vision)

リアルタイムで映像を解析し、侵入者検知や異常行動検出、車両・人物追跡などを行います。

  • 典型的な構成: カメラ映像をリアルタイムで処理し、物体検出(YOLO, EfficientDet等)や姿勢推定(OpenPose等)を実行
  • 課題: 映像フレームの受信と推論のパイプライン化、検出結果のリアルタイム送信

b. リアルタイム音声処理AI(Audio Processing)

音声認識やノイズキャンセリング、話者分離をリアルタイムで実行します。

  • 典型的な構成: RNN/Transformer系モデル(Whisper, Conformer等)をストリーミング推論
  • 課題: 時系列データのバッファ管理、GPU/CPU間の頻繁なデータ転送、音声入出力デバイスとの同期

c. ライブ配信・放送(Video Broadcasting)

リアルタイム映像への背景置換、顔認識、字幕生成、コンテンツモデレーションなどを行います。

  • 典型的な構成: セグメンテーション、顔検出・認識、OCR、不適切コンテンツ検出の並行処理
  • 課題: 高解像度処理、複数チャネルの同時処理、配信遅延の最小化

d. 産業用ロボットビジョン(Industrial Robotics)

製品検査やピッキング、組立作業における物体認識、インスタントセグメンテーション、位置推定をリアルタイムで実行します。

  • 典型的な構成: 物体検出、姿勢推定、欠陥検査モデルを組み合わせたマルチステージ処理
  • 課題: 工場環境での高速処理、PLCや産業機器との統合、照明変動への対応

これらのアプリケーションは、10〜120FPSのスループットやミリ秒単位のレイテンシ要件を満たす必要があり、GPUリソースの効率的な活用が不可欠です。
本記事で解説するCUDAマルチストリームと並行キューの技術は、スループット向上とレイテンシ削減に直接貢献します。


1. リアルタイム推論システムのマルチスレッド設計

1.1 アーキテクチャの全体像

リアルタイムCUDA推論システムでは、複数のスレッドを適切に分離することで、CPUとGPU間の効率的なパイプライン処理が可能になります。典型的な構成は以下の通りです。

ステージ・スレッド構成:

  1. 入力ステージ: カメラやネットワークからフレームを取得
  2. 推論ステージ(メインステージ): GPU前処理と推論の起動
  3. 後処理ステージ: GPU結果の取得とCPU後処理(例: 物体検知の場合はNMSなど)
  4. 出力ステージ: 結果の送信や表示

各ステージは1つ以上の独立したスレッドで実行されます。つまり、最低でも4つのスレッドが存在します。
この設計により、各ステージが独立して動作し、全体のスループットが向上します。

1.2 スレッド間通信とロックフリーキュー

スレッド間のデータ受け渡しにはProducer-Consumerパターンを採用します。前のステージがデータ(結果)を生成し、次のステージがそれを消費します。

リアルタイム性を確保するためには、ロックフリーキューが有効です。つまり、同じキューにロックを使わず複数スレッドから同時にアクセスできるキューが望ましいです。代表例としてmoodycamel/concurrentqueueがあります。このライブラリはマルチプロデューサー・マルチコンシューマー(MPMC)のロックフリーキューを提供しています。

1.3 メモリプール

リアルタイムシステムでは、動的メモリ確保によるレイテンシを避けるため、メモリプールの利用が有効です。アプリ起動時に必要なメモリを事前に確保し、動作中はそのメモリを使い回します。newmalloc)やdeletefree)の使用を極力避けます。

このため、ステージ間では2つのキューを使います:

  • resultQueue: 前のステージは処理を終えた結果をこのキューに入れ、後のステージはこのキューから結果を取得します。
  • freeMemoryPool: 後のステージはresultQueueから取得した結果を使い終わったら、メモリをこのプールに返却し再利用可能にします。前のステージはこのプールからメモリを取得して新しい処理に使います。

※プールの上限を設定し、メモリ枯渇を防ぐ必要があります。


2. CUDAマルチストリームと非同期実行

2.1 CUDAストリームの基礎

CUDAストリームは、デバイス上で順次実行される操作のシーケンスです。異なるストリーム間では、ハードウェアが許す限り並行実行が可能です。これにより、複数の入力フレームを同時に処理し、GPUの利用率を最大化できます。

実装ではcudaStream_t型の変数を用意し、cudaStreamCreate()またはcudaStreamCreateWithFlags()でストリームを作成します。CUDA API関数(カーネル起動、メモリ転送など)に希望のストリームを指定します(ストリームを指定しない場合はデフォルトストリーム(ストリーム0)で実行されます)。

2.2 計算とメモリ転送のオーバーラップ

GPUの性能を最大限に引き出すには、計算(カーネル実行)とメモリ転送(H2D/D2H)を同時に実行することが重要です。これには以下の条件が必要です。

  1. ピン留めホストメモリ: ホスト側メモリはcudaMallocHost()またはcudaHostRegister()で確保する必要があります。ページング可能なメモリでは非同期転送が機能しません(cudaMemcpyAsync()にページング可能メモリを渡すと、内部で同期転送にフォールバックします)。
  2. 非デフォルトストリーム: 独立したストリームを使い、操作を明示的に分離します。
  3. ハードウェアサポート: コピーエンジンと計算ユニットの並行動作をサポートするGPU(現代のほとんどのGPU)。

デバイス同期の制御:

  • イベント: cudaEventRecord()cudaStreamWaitEvent()により、ホストをブロックせずにストリーム間の依存関係を表現できます。例えば「ストリームAの前処理完了後にストリームBの推論を開始」といった制御が可能です。
  • ホストコールバック: cudaLaunchHostFunc()は、ストリーム上の先行タスク完了後にホスト側関数を実行します。この関数内でタスクの完了フラグを設定し、後処理スレッドに通知できます。

注意:

  • 過度なピン留めメモリ確保: ピン留めメモリは物理メモリを消費し、OSのページング性能に影響します。必要最小限に留めましょう。
  • cudaLaunchHostFunc()と似たAPIにcudaStreamAddCallback()がありますが、こちらは非推奨です。

参考資料:

2.3 TensorRTの明示的テンソル実行モデル

TensorRT 8.x以降では、明示的なテンソルアドレス設定とenqueueV3()を用いた非同期実行がサポートされています。この方式により、各タスクが独立したストリームで推論を実行できます。

マルチタスクのC++実装パターン

GPUタスクの定義:

const int NUM_TASKS = 3;  // 並行処理するタスク数

struct CudaTask {
    void* hostInputBuffer;  // CPU入力バッファ(ピン留めメモリ)
    void* hostOutputBuffer; // CPU出力バッファ(ピン留めメモリ)
    void* deviceInputBuffer;   // GPU入力バッファ
    void* deviceOutputBuffer;  // GPU出力バッファ
    cudaStream_t stream;
    nvinfer1::IExecutionContext* context;
    bool isResultSynced;  // 結果が同期されたか、後処理スレッドが確認するためのフラグ
};

初期化:

CudaTask tasks[NUM_TASKS];
for (int i = 0; i < NUM_TASKS; i++) {
    // バッファ確保
    cudaMallocHost(&tasks[i].hostInputBuffer, inputSize);
    cudaMallocHost(&tasks[i].hostOutputBuffer, outputSize);
    cudaMalloc(&tasks[i].deviceInputBuffer, inputSize);
    cudaMalloc(&tasks[i].deviceOutputBuffer, outputSize);
    
    // 非ブロッキングストリーム作成
    cudaStreamCreateWithFlags(&tasks[i].stream, cudaStreamNonBlocking);
    
    // TensorRTコンテキスト作成(タスクごとに独立)
    tasks[i].context = engine->createExecutionContext();
    tasks[i].context->setInputTensorAddress("input0", tasks[i].deviceInputBuffer);
    tasks[i].context->setOutputTensorAddress("output0", tasks[i].deviceOutputBuffer);
}

推論実行(ラウンドロビンでタスクを選択):

int nextTask = 0;

while (true) {
    CudaTask& task = tasks[nextTask];

    // freeMemoryPoolからメモリを取得し、データを準備
    // ...

    // Host to Device転送
    cudaMemcpyAsync(task.deviceInputBuffer, task.hostInputBuffer, 
                    inputSize, cudaMemcpyHostToDevice, task.stream);

    // TensorRT推論
    task.context->enqueueV3(task.stream);

    // Device to Host転送
    cudaMemcpyAsync(task.hostOutputBuffer, task.deviceOutputBuffer, 
                    outputSize, cudaMemcpyDeviceToHost, task.stream);
    // 完了通知(ホストコールバック)
    cudaLaunchHostFunc(task.stream, [](void* userData) {
        CudaTask* task = static_cast<CudaTask*>(userData);
        task->isResultSynced = true;
    }, task);

    // 次のタスクへ
    nextTask = (nextTask + 1) % NUM_TASKS;
}

この設計により、以下のオーバーラップが実現されます:

  • タスク1: 前処理中
  • タスク2: 推論中
  • タスク3: D2H転送中

各タスクが異なるステージを並行実行することで、GPUの稼働率が大幅に向上します。

参考資料:


まとめ

本記事では、リアルタイムGPU推論パイプラインの高速化技術について解説しました。

主要なポイント

1. マルチスレッド設計

  • 入力・推論・後処理・出力の各ステージを独立したスレッドに分離
  • ロックフリーキューによるスレッド間通信
  • メモリプールによる動的メモリ確保のオーバーヘッド削減

2. CUDAマルチストリーム活用

  • 複数のストリームによる並行処理でGPU稼働率を向上
  • ピン留めメモリと非同期転送による計算とメモリ転送のオーバーラップ
  • イベントとホストコールバックによる効率的な同期制御

3. TensorRTの明示的テンソル実行

  • タスクごとに独立したIExecutionContextとストリームを使用
  • enqueueV3()による非同期推論実行
  • ラウンドロビンスケジューリングで複数タスクを並行処理

これらの技術を適切に組み合わせることで、スループット向上とレイテンシ削減を実現できます。監視カメラ、音声処理、ライブ配信、産業用ロボットなど、多様なリアルタイムAIアプリケーションで、実用的な性能改善が期待できます。

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?