一般的な最適化

ここでは、データ・パイプラインや前処理の高速化などを改善するため、非同期実行などアプリケーション・レベルの最適化手法について説明します。手法 (前処理など) はエンドユーザー・アプリケーションに固有のものである可能性がありますが、関連するパフォーマンスの向上は一般的なものであり、レイテンシーとスループットの両方のあらゆるターゲットシナリオを改善します。

OpenVINO による入力の前処理

多くの場合、ネットワークは前処理された画像を期待します。コード内で必要のない手順を実行しないことをお勧めします。

OpenVINO 非同期 API

推論要求の API は、同期および非同期に実行できます。ov::InferRequest::infer() は本質的に同期構造であり、直ちに実行されます (現在のアプリケーション・スレッドで実行フローをシリアル化します)。Async は infer()ov::InferRequest::start_async()ov::InferRequest::wait() に “分割” します。詳細については、API の例を参照してください。

ov::InferRequest::infer() の一般的な使用例は、入力ソース (カメラなど) ごとに専用のアプリケーション・スレッドを実行することで、すべてのステップ (フレーム・キャプチャー、処理、結果の解析、および関連するロジック) がスレッド内で逐次化されます。対照的に、ov::InferRequest::start_async() および ov::InferRequest::wait() を使用すると、アプリケーションはアクティビティーを継続し、必要なときに推論の完了をポーリングまたは待機できます。非同期コードを使用する理由の 1 つは “効率” です。

同期 API のほうが使い方は容易ですが、運用目的のコードでは非同期 (以下、コールバック・ベース) API を使用することを推奨します。それは、考えられる数の要求 (レイテンシーとスループットの両方のシナリオ) に対してフロー制御を実装する一般的でスケーラブルな方法であるためです。

非同期アプローチの利点は、デバイスが推論でビジーなときに、アプリケーションが現在の推論が最初に完了するのを待機することなく、他の処理 (入力の設定や他の要求のスケジュール設定など) を並行して実行できることです。

以下の例では、ビデオデコードに推論が適用されます。2 つの並列推論要求を保持することが可能であり、現在の推論要求が処理されている間に、次の推論要求の入力フレームがキャプチャーされます。これにより、キャプチャーのレイテンシーが隠蔽されるため、全体のフレームレートはステージの合計ではなく、パイプラインの最も遅い部分 (デコードと推論) によってのみ決定されます。

Intel® VTune™ screenshot

以下は、通常のアプローチと非同期ベースのアプローチのコード例です。

  • 通常、フレームは OpenCV でキャプチャーされ、直ちに処理されます。

    while(true) {
        // capture frame
        // populate CURRENT InferRequest
        // Infer CURRENT InferRequest //this call is synchronous
        // display CURRENT result
    }
    
  • “真の” 非同期モードでは、CURRENT 要求が処理されている間に、NEXT 要求がメイン (アプリケーション) スレッドに設定されます。

    while(true) {
        // capture frame
        // populate NEXT InferRequest
        // start NEXT InferRequest //this call is async and returns immediately
        
        // wait for the CURRENT InferRequest
        // display CURRENT result
        // swap CURRENT and NEXT InferRequests
    }
    

この手法では、利用可能なあらゆる並列スラックに一般化できます。例えば、推論を実行しながら、結果のフレームまたは前のフレームを同時にエンコードしたり、顔検出結果に加えて感情検出を行うなど、追加の推論を実行できます。非同期 API の完全な例については、オブジェクト検出 C++ デモオブジェクト検出 Python デモ (レイテンシー指向の非同期 API ショーケース)、およびベンチマーク・アプリのサンプルを参照してください。

スループット重視のシナリオでは、非同期 API が必須です。

コールバックに関する注意事項

Async API の ov::InferRequest::wait() は、特定の要求のみを待機することに注意してください。ただし、複数の推論要求を並行して実行しても、完了する順序は保証されません。これにより、ov::InferRequest::wait にベースのロジックが複雑になる可能性があります。最もスケーラブルなアプローチは、要求の完了時に実行されるコールバック (ov::InferRequest::set_callback によって設定) を使用する方法です。コールバック関数は、結果 (またはエラー) を通知するため OpenVINO ランタイムによって使用されます。これは、よりイベント駆動型のアプローチです。

コールバックには、いくつかの重要な点があります。

  • コールバック関数がスレッドセーフであることを確認するのは、アプリケーションの役割です。

  • 専用のスレッドによって非同期に実行されますが、コールバックには負荷の高い操作 (I/O など) やブロック呼び出しが含まれてはいけません。コールバックで実行されるワークは最小限に抑える必要があります。

‘get_tensor’ イディオム

OpenVINO 内の各デバイスには、中間テンソルのメモリーパディング、アライメントなどに関して異なる内部要件がある場合があります。入出力テンソルにはアプリケーション・コードからもアクセスできます。すべての ov::InferRequestov::CompiledModel の特定のインスタンス (すでにデバイス固有) によって作成されるため、要件が尊重され、要求の入出力テンソルは引き続きデバイスに適しています。要約すると次のようになります。

  • get_tensor (テンソルの内容へのシステム・メモリー・ポインターを取得する data() メソッドを提供する) は、ホストメモリーとの間で推論入力を設定する (および出力を読み戻す) のに推奨される方法です。

    • 例えば、GPU デバイスでは、入出力テンソルget_tensor が使用される場合にのみホストにマッピングされます (高速です)。一方、set_tensor の場合は内部 GPU 構造へコピーが発生する可能性があります。

  • 対照的に、入力テンソルがすでにデバイスのメモリーにある場合 (ビデオデコードの結果など)、ゼロコピーの方法には set_tensor を優先します。詳細については、GPU デバイス・リモート・テンソル API を参照してください。

get_tensorset_tensorAPI の例を考えてみましょう。