根岸康今井晴基 土井淳 河内谷清久仁ディープラーニングのネットワークモデルは年々大きくなる一方、GPU上のメモリ容量は計算速度程向上していない。一方、高解像度イメージの処理や、3Dイメージの処理等ディープラーニングの入力データの大規模化への蕾要は高まっており、近い将来ミニバッチサイズを１としてもGPU上のメモリに全てのデータが乗らないことも想定される。処理に必要なメモリ量がGPUメモリの量を超える場合、一般にCPU-GPU間で必要なデータを明示的に転送する複雑なプログラムが必要となるが、Unified Memoryの導入により、最小限の変更でGPUメモリを意識しないプログラミングが可能になる。しかし、Unified Memoryをこのような目的で使用した場合の性能測定結果はあまりない。このため、(1) Unified Memory導入によりどの程度まで大きなメモリを使用するプログラムを実行可能か(2) Unified Memory導入に伴う実行迷度の低下が実用的な範囲であるか(3)ヒントにより十分な性能向上が得られるのかについての検証が必要となる。我々は、Chainerを用いてUnified Memory使用時の実行時間を検証し、(1) Unified Memoryの使用により、同一モデルで4-28倍のミニバッチサイズ、同一ミニバッチで4-81倍の入力サイズに対応するモデルを実行可能であること(2)使用するメモリ量が実メモリより十分小さい場合、Unified Memoryによる性能低下はほとんどないこと(3)使用するメモリ量が実メモリより大きい場合、性能低下は5-10倍程度であること(4)現状のCUDASのUnified Memoryの実装及びAPIではヒントによりUnified Memoryの性能を向上させることは困難であることを確認した。以下、第１章で背景について、第２章でUnified Memoryを使用したソフトウェア開発について、第３章で性能測定及びその結果について、第4章で最適化の方針について、第5章でまとめについて述べる。 Deep learning models are becoming large, but GPU memory size is not becoming large. Accordingly the learning model will not fit into the GPU memory in the near future. When the model does not fit in the GPU memory, complex programing is required to transfer the data between CPU and GPU explicitly. We resolve this issue by introducing Unified Memory, but there are little reports about the performance with Unified Memory. This paper discuss the performance with Unified Memory. In this paper, we found (1) Unified Memory supports 4-81 times larger memory than normal execution, (2) when the memory usage is enough less than the GPU memory, the peformance depreciation is small (3) when the memory usage is larger than the GPU memory, the performance becomes worse by 5-10 times, and (4) it is difficult to improve the performance by inserting hint, according to the current implementation of CUDA8.
1背景
ディープラーニングでは、精度の向上のためより多くのが用いられるとともに、入力データ、モデルの大規模化が進んでいる。ディープラーニングではGPUが用いられれることが多いが、GPU上のメモリの大きさは計算能力の向上と比較するとあまり拡大していない。ディープラーニングのモデルは大規模化が進んでいる。例えば、Resnet [3]では152層が使用されており近年他のネットワークでもこれまでよりより多くの層が使われている。同様に入力データについても、医療等でより解像度が高い画像データを直接入力として使用する要求が高まっており、各層が必要とするメモリ量は拡大している。一方、GPUのメモリの量はあまり拡大していない。Nvidia社の場合でも次世代GPU VOLTAとなっても搭載メモリの量は当面現行と変わらず16GBの予定である。GPU用のメモリにはHBM(High Bandwidth Memory)と呼ばれる高速だが高価なメモリが使用されるため、この傾向は当面維持されると考えられる。この様な状況下では、ミニバッチサイズを１としても使用メモリがGPUの物理メモリの量を超えることが現実の問題となりつつある。例えば、入力データとして4K(スーパーハイビジョン)画像(3840x2100)を近年良く利用されるResNet152というモデルを修正して適用した場合を考えると、必要となるGPUメモリは21-40 GBとなり†¹。次世代のGPUメモリVOLTAの量を上回る。我々の目標は、このようなGPUメモリの物理的上限を意識することなくDeap Learning Frameworkを実装・利用できる環境を用意することにある。
2   Unified Memoryを使用したソフトウェ
ア開発
2.0.1   Unified Memoryの概要
Unified Memorは、Nvidia社の提供するGPUを使用するためプラットフォームCUDA [7]において、主にホストメモリとGPUメモリを統一的に取り扱うために提供されていおり、メモリ確保用に特別なAPI CudaMemAllocManagedを使用することで、ホスト側からのメモリアクセスとGPU側からのメモリアクセスを同一のポインタで表現することが可能になる。この機能は、本来、同一データをホストとGPUの両方からアクセスする場合を想定して、同一データに対するCPUメモリ用GPUメモリ用の複数のallocationや明示的なCPU-GPU間のメモリ転送に関する記述を省き、より簡潔に記述するための機能である。本論文でもUnified Memoryを使用するが、その使用方法は上記のものとは異なり、CPUからのメモリアクセスは想定せずGPUメモリを実メモリサイズ以上に使用(オーバーコミット）するために使用する。Unified Memoryの機能はCUDA6.0から導入されていたが、現行のPASCAL世代GPUからは、GPU page faultがサポートされ、On-demandでCPU-GPU間のページ移動が行えるようになり、実行時にGPUのメモリが不足する場合に、CUDAのランタイムシステムが自動的にCPU-GPU間のメモリ移動を行い、GPUプログラムからは仮想的に実メモリ以上の大きなメモリの使用が可能になった。 GPU上にメモリ確保するAPIであるCud- aMemAlllocの全ての呼び出しをCudaMemAlloc- Mangedに変更することで、通常のメモリに変わってUnified Memoryを使用できる。更に次のVOLTA世代のGPUでは、Unified Mem- oryのCache coherencyが向上し、atomic操作も行えるようになり、Unified Memoryはより一層活用されるようになると考えられる。 2.0.2   Unified Memoryを使用したソフトウェア開発処理に必要なメモリ量がGPUメモリの量を超える場合、Unified Memoryの使用によりソフトウェア開発が容易になる。 Unified Memoryを使用しない場合CPU-GPU間で必要なデータを逐次明示的に転送するプログラムを作成する必要がある。 1.コーディングGPUの実メモリの量を考慮し、転送対象とするデータをプログラミング前に確定する必要がある。 2. 機能デバッグデータ転送を含むプログラミングが終了するまで機能デバッグを開始できない。 3.性能最適化方針・プログラムが妥当であれば、機能デバッグ終了時点である程度の性能は得られる。 Unified Memoryを使用する場合より単純なプログラミングが可能になる。 1.コーディングGPUの実メモリの量を考慮せずにプログラムを作成する。 2. 機能デバッグUnified Memoryを使用して動作させて機能をデバッグする。 3. 性能最適化実行時間・メモリ使用量を測定し、メモリ転送が必要な箇所や性能ポトルネック部分にヒントを与えることで性能最適化する。
3測定方針
このようにUnified Memoryの利用によりソフトウェア開発は容易になるが、実際のプログラムが、 GPUの実メモリ量をどの程度まで超えても動作するのか、またその時の実行迷度について報告した研究はこれまであまり無かった。報告が少ない理由の一つは、現行のGPUの物理メモリの範囲内で効率よく実行されるモデルのみがよく使用されることにある。本論文では、将来の利用状況を想定して、次の2つの方法でGPUメモリが大きく消費される状況を設定し、プログラムを実行して実行可能性や速度について議論する。 1.ミニバッチサイズ変更ミニバッチサイズの増加によりメモリ使用量を増加させる。モデルは修正しない。入力サイズは元のサイズ(224)に固定する。 2. 入力サイズ変更モデルの一部を修正し入力データのサイズを変化させる。ミニバッチサイズは固定(今回は32)する。次節でモデルの修正方法について説明する。
3.1ディープラーニングモデルの修正
3.1.1 モデル修正の方針
本節では、大きなサイズの入力データに対応するためのモデルの修正方法について説明する。今回は、層の数は変更せずに各層の大きさのみを修正する。本論文では元のモデルに対して最小限の修正で使用メモリを任意の大きさに増加させることを目的とし、結度の向上は考えない。
3.1.2 モデル修正方法
GoogleNet、ResNet等多くのConvolution Net- workが、前半がConvolution層、後半が全結合層から構成されている。この内、Convolution層だけを入力サイズに合わせて修正し、全結合層のサイズは変更しない。 Convolution層は入力サイズに比例してメモリサイズと計算量が増加する一方、全結合層は、サイズの二乗に比例して、メモリサイズと計算量が増加するので、入力サイズに合わせて全結合層のサイズを拡大することは現実的ではない。 Convolutionと全結合層のリンクの数を合わせるために、Convolution層と全結合層を結合する層(例えば、max pooling層)のバラメータを調整する。以下、比較的単純なalexnetの修正を例にとって説明する。この例では初期化の際に224の定数倍の値をinsizeとして与えている。Chainerでは入力行列のデータサイズに合わせて各層の定義が決定されるので、コード上での明示的な変更が必要な部分は、以下の例ではmax pooling層のバラメータのみである。 1. alexnet 2. googlenet 3. resnet50 4. resnet101 5. resnet151 6. vgga 7. vg16 8. vg19上記PR中のresnet50, resnet101, resnet151については、上記手法と同様の方法で入力サイズ変更に対応済であったのでそれを使用した。それ以外のモデルに関しては入力サイズ変更のための修正を適用した。
3.2 測定環境
3.2.1ハードウェア計測には以下のノードを使用した。システム名 IBM  Power  System  S822LC  for High Performance Computing (Minsky) [2] CPU   IBM POWER8 2基(計20コア)搭載、システム全体のメモリ256GB GPU  NVIDIA Telsa P100 4基搭載、一基当りのメモリ16GBこのシステムではNVLinkのインタフェースを持つPOWER8プロセッサが使用されている。各GPU-GPU間がNVLink 2 brickを使用して片方向40GB/s、両方向80GB/sで結合されている。 x86プロセッサの場合、NVLinkのインタフェースを持たないため、GPU-CPU間はPCI Express経由でGPU-CPU間は32GB/sで接続される。このため、X86プロセッサを使用した場合、Unified Memoryの性能に影響を及ぼすことが想定されるが、本論文ではX86プロセッサを使用した場合の性能は測定できなかったため、今後の課題としたい。 3.2.2 ソフトウェア今回使用したソフトウェアは以下の通り。
RHEL7.3
CUDA9.0
Python3.0.6 Chainer  Chainer [4]は、Preferred Netoworksによって開発されたディープラーニングフレームワークで、Pythonによって記述されている。本論文では現時点で最新のVersion 2を用いた [5]。 ChainerはCupy [6]と呼ばれるNumpy互換ライプラリを経由してGPUにアクセスする。Cupyではメモリオプジェクトが抽象化されているため統一的な書き換えが容易であり、Unified Mem- oryを使用するにはCupyのMemory Classを修正する。Chainer V2はまだUnified Memory対応されていないので、本論文ではChainer V1向けの修正と同様の修正をChainer V2に行った。また、CUDA9に対応するための修正を行った。今回の測定ではChainerを使用したが、本研究によって得られる知見の多くは他のフレームワークにも適用可能であると考えている。 ConvNet Benchmark 異なるディープラーニングフレームワーク間で性能比較するためのベンチマーク[1]。入力データはベンチマークプログラムが乱数で生成するため、入力データを個別に用意する必要はない。一方、学習精度・検証精度を測定することはできない。ベンチマーク自身にalexnet, googlenet, overfeat(標準的な入力サイズが異なるため本論文では不使用), vggaのモデル定義が含まれている。Chainer V2に対応するために [8]を適用した。このbranchにはvgg16のモデル定義、及び、入力サイズ変更に対応したResNet50, ResNet101, ResNet152の定義が含まれている。
3.3 測定結果
3章の方針に基づき、(1)ミニバッチサイズ変更、 (2)入力サイズ変更の2つの方針で実行の成否と性能を測定した。本節ではその測定結果について説明する。 3.3.1 ミニバッチサイズ変更モデルは修正せず、入力サイズは元のサイズの224x224 (pixel) * 3 (colors)のまま固定した。ミニバッチサイズを変化させてベンチマークを実行し、実行の成否と実行時間を測定した。最大ミニバッチサイズ(表3.3.1)最大ミニバッチサイズの拡大率は、モデルによって異なり4-28倍であることを確認できた。 Unfieid Memoryによる拡大率は層の数が大きいほど大きくなる傾向がある。これは、層の数が大きいほどメモリ全体に対するワーキングセットの割合が小さくなるため、少ないデータ転送で実行が続けられ実行時のエラーを回避可能であるためと考えられる。実行速度ミニバッチサイズを変更させた場合の実行速度を図2に示す。

図１　最大ミニバッチ数の拡大率
この内GoogLeNetの実行時間を示したものが図3である。また、GoogLeNetのイメージ1枚当りの実行時間を図４に示す。注目すべき点は、以下の通り。 1. Unified Memoryの使用時、ミニバッチサイズ224まではUnified Memoryを使用しない場合と実行時間がほぼ同じである。 2.このミニバッチサイズ224はUnified Memoryを使用しない場合に実行可能なミニバッチサイズ256の87%にあたる。 3. Unified Memoryの使用時、ミニバッチサイズ256を超えると1イメージあたりの実行時間が急激に悪化し、Unified Memoryを使用しない場合の実行時間(約3msec)の約10倍となる。batchを大きくすると更に徐々に悪化する。 Unified Memoryを使用した場合、1イメージ当りの実行時間は大きく低下する。ミニバッチ数の拡大はデータバラレルの導入によっても可能であるため、ミニバッチ数拡大のためにUnified Memoryの導入することに実用的な意味はない。
3.3.2入力サイズ変更
モデルの一部を修正し入力データのサイズを変化させた。修正の方法は3. 1. 2節の通り。ミニバッチサイズは十分な実行効率が待られる最小のものと考え、今回は32に固定した。
最大入力サイズ
最大入力サイズは、Unified Memoryの使用により、4(2x2)倍から81倍(9x9)に拡大されることが確認できた。実行速度入力サイズを変更した場合の実行速度を図6に示す。この内GoogLeNetの実行時間を示したものが図7である。また、GoogLeNetのイメージ1枚当りの実行時間を図８に示す。 Unified Memoryの導入により実行時間の低下が見られるが、ミニバッチサイズは１でも実行不可能な場合にモデルの実行に対しては、Unified Memoryの導入は有効な手段の一つとなる。
4　最適化の方針
Unified Memoryにより使用可能なモデルが大きく拡大可能であることが確認できた。一方、実行速度は大きく低下している。本章では実行速度の最適化の方法を検討する。
図2  ミニバッチサイズ変更
図3  ミニバッチサイズ変更(GoogLeNet)
4.1ディープラーニングフレームワークのメモリ
使用パターン
GoogleNet、ResNet等の通常のConvolution Net- workはForward PropagationとBackward Propa-
図4   1イメージあたりの実行時間(GoogLeNet)
gationの2つのフェーズによって処理される。メモリ管理の方針はフレームワークによって異なるが、ここではChainerを例にとって説明する。 Chainer(Version 1)のメモリ確保・開放部分にト
図5  入力サイズ変更
レースを入れて、時間経過毎のメモリ使用量を計測した。図9に、GoogLeNet(入力サイズ2240x2240)の10 iteration実行時のメモリ確保・開放回数とメモリ使用量のグラフを示す。グラフの青腺が実際に使用されているメモリの使用量、オレンジ線がMemory Poolを含めた全ての確保されているメモリ量の合計を示している。Memory Poolは未使用のメモリを一旦プールするための領域であり、メモリ確保・開放のコストを軽減するために導入されている。このグラフから以下の点が読み取れる。メモリの最大確保量  Unified Memoryの導入により、笑メモリの16 GBを超えて約70GBまでメモリを確保できることが確認できる。イテレーション内のメモリ使用バターンChainerではForwardフェーズ中にメモリを確保し、 Backwardフェーズ中にメモリを開放している。このため、GPUの使用メモリの量は各iterationの開始時点でほぼ0、Forward時からBackward時の折り返しの時点で最大となる。Backwardでの参照の終了毎に順次開放され、iterationの終了時点で再びほぼ０となる。 Memory Poolによるメモリ使用量今回の測定では、確保したメモリの約40%がMemory Poolによって使用されている。Memory Pool中のメモリはアクセスされることは無いが、CUDAシステムによるデータ転送の対象となるので、大きなデータをMemory Pool用に確保していることは、性能に影響を与える。これらの観察から得られる最適化の方針は以下の通り。最適化方針1   Unified Memoryの使用時はMem- ory Poolは使用しない。更に、メモリー確保の呼び出し元にもトレースを入れて、どのモジュールがメモリを使用しているか分類した。図9に、GoogLeNet(入力サイズ224x224)の10 iteration実行時のメモリ確保・開放回数とモジュール別のメモリ使用量を示す。また、図9に、モジュール別の合計使用メモリ量を示す。これらのグラフから得られる知見は以下の通り。メモリ使用モジュールcudnnActivationforward等少数のモジュールが使用メモリのほとんどを占めており、10個のモジュールで全体の90%のメモリ使用を説明できる。また、ログを直接観察すると、モジュールあたり数個程度の非常に大きなメモリを確保していることがわかる。層に依存しないメモリ使用アプリケーション等層に依存しないモジュールからのメモリ使用量は少ない。以上の知見から以下の最適化方針が考えられる。最適化方針2 大きなメモリの確保にのみUnified Memoryを使用する。図12にここまでの最適化の方針に従って、最適化を行った結果を示す。MemoryPoolの使用を止め、 4KB以下のメモリ確保に通常のメモリを使用するようにプログラムを変更した。これらの最適化により最大ミニバッチサイズは10から9に減っているが、実行時間は約15%削減されている。
4.2ヒントによる最適化
プログラムへのヒントの挿入による最適化について検討する。 4.2.1　必要なヒントの種類ディープラーニングはForwardフェーズ、Back- wardフェーズの順で行われ、Backwardフェーズでは、対応するForawardフェーズによる計算結果にア
クセスする。
このアクセスバターンを考えると、以下の2種類のヒントによりデータ転送とGPUでの計算を並列に行うことが性能改善に有効である。
図8  入力サイズ当りの実行時間(GoogLeNet)
swap outヒント GPUからCPUへのメモリswap-outのヒントをforwardフェーズでの各層の計算終了時点に挿入する。 prefetchヒント CPUからGPUへのメモリ
図9   GoogleNet(入力サイズ2240x2240)のメモリ使
用バターン
図10   GoogleNet(入力サイズ224x224)のモジュール別メモリ使用パターンprefetchのヒントをbackwardフェーズでの各層の計算開始の適切な時間分前に挿入する。以下、上記2種類のヒントをCUDAのAPIによって実装可能であるかについて検討する。 4.2.2ヒントの実装についての検討CUDAはUnified Memoryの使用時にデータ転送を行う際のヒントを与えるために次のAPIを提供している。このうち、cudaMemPrefetchAsyncは上記のprefetchヒントに相当する。 cudaMemAdviseSetPreferredLocationメモリを確保するデバイス(ホスト・GPU)の指定cudaMemAdviceUnsetPreferredLocation上記ヒントの削除cudaMemAdviseSetAccessedBy  次にアクセスするデバイス(ホスト・GPU)の指定。 cudaMemPrefetchAsync 非同期にメモリ転送を指示我々はこれらのAPIを使用して、上記2種類のヒントを実装することを検討した。このうち、cudaMemAdviseSetPreferredLoca- tion/cudaMemAdviceUnsetPreferredLocationを使用したが、今回の測定条件では使用の有無で性能に有意な差は見られなかった。また、cudaMemAdvis- eSetAccessedByについては、呼び出し時点でデータ転送が同期的に行われるため, GPUでの計算時間を削減する効果は確認できたが、APIのCPU-GPU間のデータ転送がAPIの呼び出し時点に移動しただけで、全体としての実行時間の削減を実現することはできなかった。 CudaMemPrefetchAsyncについては、Backwardフェーズへの導入により一定のデータ削減効果は確認できた。図12にCudaMemPrefetchAsyncの効果に
図11   GoogleNet(入力サイズ224x224)のモジュール
別合計メモリ使用量
ついて示す。最大5%の実行時間削減効果が得られているが、ミニバッチサイズによっては逆に実行時間が増加している。このようにCudaMemPrefetchAsyncには一定の効果が見られるが、限度がある。その原因としては、 prefetchの代償としてswap outするメモリを制御していないことにある。 swap outするメモリを制御するためにcudaMem- PrefetchAsyncを逆方向に使用して、GPUのメモリを非同期にホストに転送することを試みた。これにより対象メモリのデータがホスト上に転送され、GPU上のメモリはデータ転送なしにいつでも再使用できる状態となる。このために再利用の際の優先度が上がることが期待される。予備的な実験を行いこのときの動作を確認した。実験ではGPUのメモリ量を超えたメモリをGPU上で
図12   GoogleNet(2240x2240)における最適化の効果
使用し、ディープラーニングを模したアクセスバターンに基づいて、メモリ確保・開放及びメモリアクセスを行った。予備的な実験の詳細については本論文では触れないが、実験により以下の知見を得た。 1. swapoutされるデータはメモリ確保(alloca- tion)の順序に基づいておこなわれる。アクセスの順序はswapoutの順序を決定する際に考慮されない。 2. GPU上のメモリが不足すると、メモリは最も古く確保されたメモリから順次ホスト上に転送され再利用される。 3.再利用が一巡するとラウンドロビン順で最も古く確保されたから順次再度ホストに転送し再利用する。 4.メモリ上のデータがホストに転送済みですぐに再利用可能であっても、上記再利用の順序に影響
を与えない。
5. cudaMemFreeによって開放されたメモリであっても、すぐには再利用されず上記再利用の順序で再利用される。以上の実験を鑑みて、我々はGPUのUnified Mem- oryの再利用の順序を現状のCUDA APIを使用して任意に制御することは不可能であると考えている。次のVOLTA世代のGPUでは、Unified MemoryのCache coherencyが向上し、atomic操作も行えるようになる。これに伴ってCUDAのUnified Memoryの機能も改善され、メモリ再利用の順序も制御できるようになることを期待している。
5　まとめ
本論文では、、(1) Unified Memory導入によりどの程度まで大きなメモリを使用するプログラムを実行可能か(2) Unified Memory導入に伴う実行迷度の低下が実用的な範囲であるか(3)ヒントにより十分な性能向上が得られるのかについて、検証した。我々は、Chainerを用いてUnified Memory使用時の実行時間を検証し、(1) Unified Memoryの使用により、同一モデルで4-28倍のミニバッチサイズ、同ーミニバッチで4-81倍の入力サイズに対応するモデルを実行可能であること(2)使用するメモリ量が実メモリより十分小さい場合、Unified Memoryによる性能低下はほとんどないこと(3)使用するメモリ量が実メモリより大きい場合、性能低下は5-10倍程度であること(4)現状のCUDA8のUnified Memoryの実装及びAPIではヒントによりUnified Memoryの性能を向上させることは困難であることを確認した。今後は、VOLTA GPU及びCUDA9を使用してディープラーニングフレームワークでUnified Mem- oryを使用する際の実行性能の最適化、及び、最大入力サイズの拡大を進めて生きたい。