この記事は 自動微分の誤った神話 シリーズの一部です。
あなたは,自分の並列ライブラリやアプリケーションにアジョイント自動微分(AAD)の利点を享受させたいと考えていますが、アジョイント自動微分はスレッドセーフではなく、ベクトル化を壊し、GPUとの相性が悪いのではないかと懸念しています。それらは、どの程度本当なのでしょうか?この記事ではそれらを取り上げます。
自動微分はセマンティックなプログラム変換であり、元の実装に機能を追加するものです。変換されたプログラムは、関数値、例えば金融商品の価格だけでなく、その感度や微分値、例えば一次ギリシャ指標も計算することができます。このためにアジョイント自動微分を使用すると、並列化に影響が出ます。大雑把に言うと、出力から入力までのすべてのステートメントを逆方向に実行する必要があります。これは、元のプログラムのスレッドローカルでないデータの並列読み出しが、逆方向のプログラムのアジョイントデータへの並列書き込みになることを意味します。これらの並列書き込みは、潜在的にレースコンディションを引き起こす可能性があるため、課題となっています。これを解決する簡単な方法は、特にモンテカルロ法のような並列アルゴリズムでは、共有データを複製することです。dco/c++のマルチスレッドサポートを利用すれば、並列性を復元し、アジョイント自動微分の計算に継承させることができます。アジョイントと並列処理について、もう少し詳しく見ていきましょう。
- ベクトル化
dco/c++では、ユーザーは使用している算術型を決まったものに置き換えます(例えば、'double'を'dco::ga1s<double>::type'に置き換えます)。この型には、例えばタンジェント/フォワードモード自動微分のタンジェント成分や、アジョイント自動微分のいわゆる「テープインデックス」のような付加的なデータが付属しています。このため、連続したデータに対して演算を行う場合、当然ながら関数値へのアクセスパターンにずれが生じます。そしてこれは、自動ベクトル化に悪影響を及ぼします。dco/c++ にはベクトルデータ型 (dco::gv<double, vector_size>::type) があり、ベクトル化をより低いレベルにシフトさせることができます。そうすることで、ベクトル化を維持し、さらにアジョイントコードから継承することができます。他にも、dco/c++の専用の線形代数ライブラリ(BLASやEigenなど)を利用するなどのアプローチもあります。また、NAGはデータレイアウトの変更を完全に回避する新しいアプローチにも取り組んでいます。 - マルチスレッド化
アジョイント自動微分の場合、dco/c++はプログラムを実行しながら、そのイメージをメモリに書き込みます。このイメージは'テープ'と呼ばれます。効率上の理由から、これは単一のグローバルなテープです。したがって、マルチスレッドコードは、このグローバルデータ上でデータ競合を引き起こすかもしれません。これを避けるために、dco/c++はスレッドセーフなテープストレージを備えています。さらに、dco/c++は「マルチテープ」をサポートしています。つまり、各変数は(スレッド)ローカルに割り当てられたテープと関連づけることができます。これらの機能のいずれかを、要件に応じて使用することで、複数のスレッドでグローバルデータへの書き込みの問題を克服することができます。 - GPU
この記事で最後にお話ししたいのは、GPU コードのアジョイント自動微分についてです。これは本質的に難しい問題です。アプリケーションにもよりますが、GPUアジョイントは、高レベルのライブラリを使用するか、dco/map(「map」は「meta adjoint programming」の略)のようなGPU用の特化した自動微分ツールを使用することで実現されます。そもそも通常のGPUプログラミングと同様に、効率的なGPUアジョイントを得るのは難しい作業です。経験豊富なパートナーが必要な場合は、私たちにお知らせください。
NAGの自動微分ツールセットは過去12年にわたり開発され、さらに10年にわたるC++による自動微分研究開発の経験を基に構築されています。私たちは、細部が重要であることを知っています。レガシーコードは私たちのビジネスであり、私たちが扱うことができないコードに出会ったことは一度もありません。
神話は、真実のように聞こえるかもしれませんが、私たちは、神話について詳しく話し、私たちの経験を共有することによって、企業がこれらの問題を解決する手助けをしたいと願っています。
結果は重要ですが、神話は重要ではありません。