Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Graviton3の性能を最大限引き出す方法 / AWS EC2Fes 2022Autumn ...

Graviton3の性能を最大限引き出す方法 / AWS EC2Fes 2022Autumn Fixstars

アマゾン ウェブ サービス ジャパン合同会社が2022年10月13日に開催した「AWS 秋の Amazon EC2 Deep Dive 祭り 2022」に、当社のエンジニアが登壇した際の資料です。
「Graviton3の性能を最大限引き出す方法 -ARM SVEを使った事例紹介-」と題したセッションを行いました。

More Decks by 株式会社フィックスターズ

Other Decks in Programming

Transcript

  1. Copyright © Fixstars Group 本日のAgenda ⚫ 発表者紹介 ⚫ フィックスターズのご紹介 ⚫

    Graviton3の性能を最大限引き出す方法 ◦ 本発表における「高速化」 ◦ ケーススタディ ◦ ARM SVEとは ◦ Predicate Register ◦ 移植例 ◦ まとめ ⚫ お知らせ 1
  2. Copyright © Fixstars Group 発表者紹介 今泉 良紀 (Imaizumi Yoshiki) ソリューション第二事業部/シニアエンジニア

    2017年インターンシップ、2018年アルバイトを経て2019年に入社。 アルバイト時代はClPyのUltima(CUDA C++サブセットからOpenCL Cサブセットへの変換器)を開発¹。 入社後はエンジニアとしてAndroid向けGPUソフトウェア高速化に従事。 シニアエンジニア昇格後は同案件やfaissのARM64向け4bitPQ高速化²などを経て、 現在は自動車業界で深層学習向けコンパイラ開発に携わる。 ¹: https://proc-cpuinfo.fixstars.com/2020/04/clpy-ultima/ ²: https://proc-cpuinfo.fixstars.com/2021/06/make-faiss-4bitpq-60x-faster-on-aarch64/ 3
  3. Copyright © Fixstars Group フィックスターズの強み コンピュータの性能を最大限に引き出す、ソフトウェア高速化のエキスパート集団 ハードウェアの知見 アルゴリズム実装力 各産業・研究分野の知見 目的の製品に最適なハードウェアを見抜き、

    その性能をフル活用するソフトウェアを開 発します。 ハードウェアの特徴と製品要求仕様に合わ せて、アルゴリズムを改良して高速化を実 現します。 開発したい製品に使える技術を見抜き、実 際に動作する実装までトータルにサポート します。 5
  4. Copyright © Fixstars Group サービス概要 お客様専任のエンジニアが直接ヒアリングを行い、高速化を実現するために乗り越えるべき 課題や問題を明確にしていきます。 高速化のワークフロー コンサルティング 先行技術調査

    性能評価・ボトルネックの特定 高速化 アルゴリズムの改良・開発 ハードウェアへの最適化 レポート作成 サポート レポートやコードへのQ&A 実製品への組込み支援 6
  5. Copyright © Fixstars Group サービス提供分野 半導体 自動車 産業機器 生命科学 金融

    •NAND型フラッシュメモリ向けフ ァームウェア開発 •次世代AIチップの開発環境基盤 •自動運転の高性能化、実用化 •次世代パーソナルモビリティの 研究開発 •Smart Factory実現への支援 •マシンビジョンシステムの高速化 •ゲノム解析の高速化 •医用画像処理の高速化 •AI画像診断システムの研究開発 •デリバティブシステムの高速化 •HFT(アルゴリズムトレード)の高速化 7
  6. Copyright © Fixstars Group AIを用いた乳房超音波検査リアルタイム解析システム 慶應義塾大学医学部外科学(一般・消化器)教室様 8 1 超音波検査装置が描出する動画を リアルタイム処理できる高速なAIを開発

    2 検査しながらAIによる診断補助が実現 できる 3 見落としを減らし、早期の乳がんの発見と 治療が可能になる 高確率でがん 生命科学 分野 サービス領域 AI・深層学習向け技術支援 高確率で良性腫瘍 フィックスターズの高速化技術を適用、 子会社のスマートオピニオン社で、乳がんの超音波 画像に対し、精密検査の要否を高速かつ高精度に判 別するAIを開発(現在認可申請中)
  7. Copyright © Fixstars Group 本発表における「高速化」 ⚫ 高速化 ◦ 計算量のオーダーを減らす ▪

    アルゴリズムの変更 ◦ 計算量のオーダーはそのまま(定数倍高速化) ▪ 計算資源を増やす(インスタンスの追加など) ▪ 計算資源の使用効率を上げる • 命令の順番を入れ替えてパイプラインを詰める • 複数のコアを使う(マルチスレッド) • CPU以外のプロセッサを使う(GPGPUなど) • 処理効率の高い命令を使う(SIMD命令やベクトル命令など) • より具体的な手段として: ◦ コンパイラオプションの変更 ◦ 高速な既製ライブラリの使用 ◦ コードの書き換え 10 • データ並列なプログラム を • シングルスレッド で • intrinsicを使って明示的にSVE命令を発行 して • 定数倍高速化 する話
  8. Copyright © Fixstars Group ケーススタディ ⚫ faiss (https://github.com/facebookresearch/faiss) ◦ Meta

    Research(旧Facebook AI Research)が開発しているオープンソースの 近似最近傍探索ライブラリ ▪ 画像や文書などの類似〇〇検索に応用 ◦ 現在公開されているのはNEONベースの実装 ◦ そこにいくつかのアルゴリズムについてSVEベースの実装を追加 ◦ 双方Graviton3上で実行し、性能を比較 ▪ c7g.large (2コア、4GB RAM) ▪ シングルスレッドで動作 ▪ SIFT1Mデータセットを使用 → 最大で1.7倍弱の実行性能向上 ◦ 後日Pull Request予定 11 7.634 2.793 12.658 3.460 0.000 2.000 4.000 6.000 8.000 10.000 12.000 14.000 hnsw ef_search: 16 ivfpq M: 32, nprobe: 16 実行性能 [1/ms] 従来実装(NEONベース) 高速実装(SVEベース) 1.66倍 1.24倍
  9. Copyright © Fixstars Group ケーススタディ ⚫ Graviton3の性能を最大限引き出すためにはSVEの利活用も重要 ◦ SVEを使わないと実測値で60%程度しか性能が出せない ⚫

    SVEの使い方 ◦ 簡単なプログラムであればコンパイラオプションで自動ベクトル化を有効にする だけで自動でSVEを使ったプログラムにしてくれる ◦ 既存のSVEを使ったライブラリを使うことで高速に動作 → コンパイラの自動ベクトル化や既製のライブラリでは必ずしも所望の複雑な処理を SVEにできない ◦ 本発表ではintrinsicを使う ▪ intrinsic: 変換後の機械語命令が決まっているコンパイラ組み込み関数 e.g.) z = svadd_u32_x(mask, x, y); → ADD z.S, x.S, y.S 12
  10. Copyright © Fixstars Group ARM SVEとは ⚫ ARM SVE(Scalable Vector

    Extension) ◦ ベクトル命令の拡張命令セット ▪ 複数のデータをベクトルレジスタに配置 ▪ ベクトルレジスタに命令を適用 → (1つずつスカラ命令で処理するより)短いサイクル数で処理できる → フリンの分類でいうところのSIMDな命令セット データ並列な処理に有効 • x86_64のAVX2やARMv8のAdvanced SIMD(NEON)など e.g.) ベクトル加算命令 z = svadd_u32_x(mask, x, y) この例において、 ◦ ベクトルレジスタ長は128bit、uint32_t 4要素で利用(svuint32_t) ◦ 返り値zもベクトルレジスタ ◦ Zi = Xi + Yi (レジスタ内の要素毎に加算) ◦ mask については後述 13 X0 X1 X2 X3 Y0 Y1 Y2 Y3 + ⇐ Z0 Z1 Z2 Z3
  11. Copyright © Fixstars Group ARM SVEとは ⚫ ARM SVE(Scalable Vector

    Extension) ◦ 特徴: Scalable = ベクトルレジスタ長がCPU毎に可変 ▪ ベクトルレジスタ長が 128bitの倍数(最小128bit、最大2048bit) しか決まっていない →CPUの実装者がある程度自由にレジスタ長を設定できる ◦ 組み込み向けなら128bit、サーバー向けなら512bit、など ◦ Graviton3だと256bit ▪ ベクトルレジスタに何要素乗るかはsvcnt𝑋()関数で実行時に取得(𝑋 = {b, h, w, d}) • たぶん byte(8bit), half word(16bit), word(32bit), double word(64bit) の頭文字 → ベクトル長に依存しないプログラムを記述することで多様なCPUで高速に実行可能に 14 X0 X1 X2 X3 X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 X15 X0 X1 X2 X3 X4 X5 X6 X7
  12. Copyright © Fixstars Group ARM SVEとは ⚫ ARM SVE(Scalable Vector

    Extension) ◦ ARMv9でSVE2が基本命令セットに入る(拡張ではなくなる) ▪ 今のうちからSVEに慣れておくと将来役立つ ◦ 現時点でSVEが載っているチップは多くない ▪ スマートフォン向けSoCのフラッグシップモデルならARMv9採用のものが出てきつつある • SVEが動くのは1コアのみ、というものも多い • Android NDKでコンパイルしたバイナリをADBで転送して実機で動かす、 といった感じで開発環境がやや特殊になりがち ▪ A64FX(富岳のCPU) • 研究用途以外で使うのは難しい • 一応市販はされているが購入するには高い ▪ Graviton3 • インスタンス代のみで好きに使える • 開発環境も普通のLinuxディストリビューションが入れられる → 現時点で非研究者にとってSVE学習時に最適 15
  13. Copyright © Fixstars Group Predicate Register ⚫ NEONでは端数部の処理をうまくやらないといけない ◦ e.g.)

    32bit非負整数 7要素に対して処理するときの後ろ3要素 ▪ NEONだと4要素ずつアクセスするのでX7 やY7 、Z7 に相当するメモリ領域に アクセスしてしまう → 不正なメモリアクセス e.g.) 0から3まで処理した後に3から6までで処理する(3番の要素は2重に処理する) e.g.) 4から6まではSIMD命令ではなく普通にスカラ処理する → NEONにはマスク命令が無いため 16 X0 X1 X2 X3 Y0 Y1 Y2 Y3 + ⇐ Z0 Z1 Z2 Z3 X4 X5 X6 Y4 Y5 Y6 + ⇐ Z4 Z5 Z6
  14. Copyright © Fixstars Group Predicate Register ⚫ SVEのベクトルレジスタとは別に存在するマスクのためのレジスタ ◦ TrueとFalseだけを扱う

    (svbool_t) ◦ SVEの殆どの命令はマスク付き命令 e.g.) ベクトル加算命令 svadd_u32_x(mask, x, y) • このmaskがpredicate register • maskでマスクされた(Falseの部分の)要素は加算が実行されない e.g.) ベクトルロード命令 vec = svld1_u32(mask, ptr) e.g.) ベクトルストア命令 svst1_u32(mask, ptr, vec) • ロード/ストアもマスク付き(マスクされた要素は読み書きが実行されない) → 適切なマスクを生成できれば範囲外アクセスすることなく最後まで読み書きできる ◦ ループカウンタとループ終端の値からpredicate registerを生成する命令がある e.g.) 32bit向けマスク生成命令 svwhilelt_b32_u64(i, n) → SVEでは端数部の処理を明示的に記述しなくて良い 17
  15. Copyright © Fixstars Group Predicate Register ⚫ SVEのベクトルレジスタとは別に存在するマスクのためのレジスタ e.g.) 32bit

    7要素処理する場合(128bit, 256bit) 18 + X0 X1 X2 X3 X4 X5 X6 Y0 Y1 Y2 Y3 Y4 Y5 Y6 i=0 -> (T,T,T,T) i=4 -> (T,T,T,F) i=0 -> (T,T,T,T,T,T,T,F) 128bitの場合 (svcntw() == 4) 256bitの場合 (svcntw() == 8) for (size_t i = 0; i < 7; i += svcntw()) { const auto mask = svwhilelt_b32_u64(i, 7); svst1_u32(mask, z+i, svadd_u32_x(mask, svld1_u32(mask, x+i), svld1_u32(mask, y+i))); } X0 X1 X2 X3 Y0 Y1 Y2 Y3 + ⇐ Z0 Z1 Z2 Z3 X4 X5 X6 Y4 Y5 Y6 + ⇐ Z4 Z5 Z6 Z0 Z1 Z2 Z3 Z4 Z5 Z6 ⇐
  16. Copyright © Fixstars Group 移植例(簡単な例) ⚫ vectoradd (𝑐 = 𝑎

    + 𝑏) 19 void vectoradd(const float* a, const float* b, float* c, size_t n){ for(size_t i = 0; i < n; ++i){ const auto ai = a[i]; const auto bi = b[i]; const auto ci = ai + bi; c[i] = ci; } } void vectoradd(const float* a, const float* b, float* c, size_t n){ const auto lanes = svcntw(); for(size_t i = 0; i < n; i += lanes){ const auto mask = svwhilelt_b32_u64(i, n); const auto ai = svld1_f32(mask, a+i); const auto bi = svld1_f32(mask, b+i); const auto ci = svadd_f32_x(mask, ai, bi); svst1_f32(mask, c+i, ci); } } 1ベクトルレジスタに 32bit要素がいくつ入るか [0, n)なループの カウンタがiのときの 32bit向けマスク min(n-i, lanes)個 ロードして足す maskに従って 結果を保存
  17. Copyright © Fixstars Group 移植例(ケーススタディのコード) ⚫ distance_single_code_simple 20 const float*

    table; const uint32_t indices[M]; float result = 0.f; for(size_t m = 0; m < M; ++m){ const auto idx = indices[m]; const auto collected = table[idx]; result += collected; table += pitch; } 3 7 2 4 1 7 8 4 3 0 2 6 10 5 2 0 1 9 3 4 7 9 3 8 0 1 6 5 2 7 9 2 0 8 8 3 table indices 4+4+3+0 = 11 ⚫ SVEに移植する際、 ◦ tableから1個ずつではなくまとめて データを拾いたい ◦ 集めたデータの和をより高速に 求めたい
  18. Copyright © Fixstars Group 移植例(ケーススタディのコード) ⚫ distance_single_code_simple 21 const float*

    table; const uint32_t indices[M]; float result = 0.f; const auto offsets = svindex_u32(0, pitch); auto sum = svdup_n_f32(0.f); const auto lanes = svcntw(); for(size_t m = 0; m < M; m += lanes){ const auto mask = svwhilelt_b32_u64(m, M); const auto idx = svld1_u32(mask, indices + m); const auto indices_to_read_from = svadd_u32_x(mask, idx, offsets); const auto collected = svld1_gather_u32index_f32( mask, table, indices_to_read_from); sum = svadd_f32_m(mask, sum, collected); table += pitch * lanes; } result = svaddv_f32(svptrue_b32(), sum); const float* table; const uint32_t indices[M]; float result = 0.f; for(size_t m = 0; m < M; ++m){ const auto idx = indices[m]; const auto collected = table[idx]; result += collected; table += pitch; } {0, pitch, 2*pitch, 3*pitch, ...} {0, 0, 0, 0, ...} indices[m+i]+pitch*i SVEにはgatherが あるので飛び飛びの メモリからロード可 水平加算(ベクトル内要素の総和)
  19. Copyright © Fixstars Group まとめ ⚫ ARM SVEのintrinsicを使用することでGraviton3上でfaissのHNSWを 1.7倍程度高速化させることができた ⚫

    ARM SVEとは ◦ ARMv8のベクトル拡張命令セット ▪ v9からは基本命令セットの一部 ◦ レジスタ長がCPU毎に異なる ◦ 命令がマスク付きであり、マスクは専用のレジスタで扱う → 端数部分の処理を特に気にしなくて良い ⚫ Graviton3の性能を最大限引き出すためにもARM SVEを使おう 22
  20. Copyright © Fixstars Group 発表当日の質疑応答 この辺のお話はC/C++が中心だと思うんですが、他にSVE命令を呼び出せる言語には どんなのがあるんでしょう。例えばJavaはJVMとJava SDKが対応してくれない限り 無理に思えますが、いかがでしょうか。 ⚫

    当日の回答 ◦ そもそもVMやインタプリタなど、何かしらランタイムを持つ実装で実行速度を 最大限引き出すことはランタイムのオーバーヘッドの観点で極めて困難 ◦ Javaから使うのは厳しそうだがJVMがSVEに対応していればSVEが動いてくれるはず → 本発表のレベルでSVEを直接使うのはJVMの開発者であってJavaを使うアプリ開発者ではない ◦ C/C++以外の言語の対応について ▪ ベクトル長が可変長、というのが難しさを生んでいる • 近年ではあまり見られなかったプログラミングパラダイム → 対応している言語が少なめ • 実行時まで変数のサイズが確定しない → 言語処理系側での扱いが従来と異なる ◦ これについては特定ベクトル長CPUを対象として固定長にすることもでき、 その場合はSIMDと似たような感じで対応できるのではないか ▪ C/C++以外の言語で似たような方向性の(ランタイム無しのネイティブバイナリを生成するシステム プログラミング)言語となるとRustが存在するが、少なくとも以前調べた段階では未対応だった → CとC++ぐらいしか対応していないという認識 26 Q.
  21. Copyright © Fixstars Group 発表当日の質疑応答 この辺のお話はC/C++が中心だと思うんですが、他にSVE命令を呼び出せる言語には どんなのがあるんでしょう。例えばJavaはJVMとJava SDKが対応してくれない限り 無理に思えますが、いかがでしょうか。 ⚫

    後日調べてみたところ、Incubator(実験的API)としてVector APIというのが Java 16から追加されており、Java 18からARM SVEにも対応していた ◦ 一方で、これはあくまでx86_64なども対象とした汎用インターフェースであり、 SVEのintrinsicを直接呼び出す本発表の内容と同等レベルでSVEを扱えるわけではない e.g.) NEONやSVEの特徴の1つであるデインタリーブロード/インタリーブストアが使えなさそう ▪ これは過去C++などのマルチプラットフォームSIMDラッパーライブラリでも似たような (マルチプラットフォームにするとintrinsicに比べて表現能力が落ちる)問題が議論されてきた ◦ Javaのような実際のメモリ上のオブジェクトサイズと切り離された言語だと Unsizedな値の取り回しは比較的容易なのかもしれない ⚫ RustはやはりUnsizedな値の取り扱いをどうするか、という点で難航している様子 ◦ 可変長ベクトルの扱いはRISC-V Vectorなどにも影響するため、 慎重に議論しているのではないか 27 Q.