2K Views
August 09, 23
スライド概要
Graviton3に新たに搭載されたSVE命令について、概要や特徴に触れ、SVE命令を使った簡単なプログラムの移植例を説明します。
その後、実際に既存のOSSを高速化した事例を紹介します。
本資料は「AWS 秋の Amazon EC2 Deep Dive 祭り 2022」で使用したものです。
https://aws.amazon.com/jp/blogs/news/event-report-wwso-compute-ec2-20221013/
フィックスターズは、コンピュータの性能を最大限に引き出すソフトウェア開発のスペシャリストです。車載、産業機器、金融、医療など、幅広い分野での開発経験があります。また、ディープラーニングや機械学習などの最先端技術にも力を入れています。 並列化や最適化技術を駆使して、マルチコアCPU、GPU、FPGA、量子アニーリングマシンなど、さまざまなハードウェアでソフトウェアを高速化するサービスを提供しています。さらに、長年の経験から培ったハードウェアの知識と最適化ノウハウを活かし、高精度で高性能なアルゴリズムの開発も行っています。 ・開催セミナー一覧:https://www.fixstars.com/ja/seminar ・技術ブログ :https://proc-cpuinfo.fixstars.com/
Graviton3の性能を 最大限引き出す方法 -ARM SVEを使った事例紹介株式会社フィックスターズ 今泉 良紀 Copyright © Fixstars Group
本日のAgenda ⚫ 発表者紹介 ⚫ フィックスターズのご紹介 ⚫ Graviton3の性能を最大限引き出す方法 ○ 本発表における「高速化」 ○ ケーススタディ ○ ARM SVEとは ○ Predicate Register ○ 移植例 ○ まとめ ⚫ お知らせ 1 Copyright © Fixstars Group
発表者紹介 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 Copyright © Fixstars Group
フィックスターズの ご紹介 4 Copyright © Fixstars Group
フィックスターズの強み コンピュータの性能を最大限に引き出す、ソフトウェア高速化のエキスパート集団 ハードウェアの知見 アルゴリズム実装力 各産業・研究分野の知見 目的の製品に最適なハードウェアを見抜き、 その性能をフル活用するソフトウェアを開 発します。 ハードウェアの特徴と製品要求仕様に合わ せて、アルゴリズムを改良して高速化を実 現します。 開発したい製品に使える技術を見抜き、実 際に動作する実装までトータルにサポート します。 5 Copyright © Fixstars Group
サービス概要 お客様専任のエンジニアが直接ヒアリングを行い、高速化を実現するために乗り越えるべき 課題や問題を明確にしていきます。 高速化のワークフロー コンサルティング 高速化 サポート 先行技術調査 アルゴリズムの改良・開発 レポートやコードへのQ&A 性能評価・ボトルネックの特定 ハードウェアへの最適化 実製品への組込み支援 レポート作成 6 Copyright © Fixstars Group
サービス提供分野 半導体 産業機器 金融 自動車 ● NAND型フラッシュメモリ向けフ ァームウェア開発 ● 次世代AIチップの開発環境基盤 生命科学 ● Smart Factory実現への支援 ● マシンビジョンシステムの高速化 ● 自動運転の高性能化、実用化 ● ゲノム解析の高速化 ● 次世代パーソナルモビリティの 研究開発 ● 医用画像処理の高速化 ● デリバティブシステムの高速化 ● HFT(アルゴリズムトレード)の高速化 ● AI画像診断システムの研究開発 Copyright © Fixstars Group 7
AIを用いた乳房超音波検査リアルタイム解析システム 慶應義塾大学医学部外科学(一般・消化器)教室様 分野 1 2 3 生命科学 サービス領域 AI・深層学習向け技術支援 超音波検査装置が描出する動画を リアルタイム処理できる高速なAIを開発 検査しながらAIによる診断補助が実現 高確率で良性腫瘍 高確率でがん できる 見落としを減らし、早期の乳がんの発見と 治療が可能になる フィックスターズの高速化技術を適用、 子会社のスマートオピニオン社で、乳がんの超音波 画像に対し、精密検査の要否を高速かつ高精度に判 別するAIを開発(現在認可申請中) 8 Copyright © Fixstars Group
Graviton3の性能を 最大限引き出す方法 9 Copyright © Fixstars Group
本発表における「高速化」 ⚫ 高速化 ○ 計算量のオーダーを減らす ■ アルゴリズムの変更 ○ 計算量のオーダーはそのまま(定数倍高速化) ■ 計算資源を増やす(インスタンスの追加など) ■ 計算資源の使用効率を上げる ● 命令の順番を入れ替えてパイプラインを詰める ● 複数のコアを使う(マルチスレッド) ● CPU以外のプロセッサを使う(GPGPUなど) ● 処理効率の高い命令を使う(SIMD命令やベクトル命令など) ● より具体的な手段として: ○ コンパイラオプションの変更 ○ 高速な既製ライブラリの使用 ○ コードの書き換え • • • • Copyright © Fixstars Group データ並列なプログラム を シングルスレッド で intrinsicを使って明示的にSVE命令を発行 して 定数倍高速化 する話 10
ケーススタディ 実行性能 [1/ms] ⚫ faiss (https://github.com/facebookresearch/faiss) ○ Meta Research(旧Facebook AI Research)が開発しているオープンソースの 近似最近傍探索ライブラリ ■ 画像や文書などの類似〇〇検索に応用 ○ 現在公開されているのはNEONベースの実装 ○ そこにいくつかのアルゴリズムについてSVEベースの実装を追加 14.000 ○ 双方Graviton3上で実行し、性能を比較 1.66倍 12.000 12.658 ■ c7g.large (2コア、4GB RAM) 10.000 ■ シングルスレッドで動作 8.000 ■ SIFT1Mデータセットを使用 7.634 6.000 1.24倍 → 最大で1.7倍弱の実行性能向上 ○ 後日Pull Request予定 4.000 2.000 2.793 3.460 0.000 hnsw ef_search: 16 Copyright © Fixstars Group 従来実装(NEONベース) ivfpq M: 32, nprobe: 16 高速実装(SVEベース) 11
ケーススタディ ⚫ 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 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) Z0 Z1 Z2 Z3 ⇐ X0 X1 X2 X3 + Y0 Y1 Y2 Y3 この例において、 ○ ベクトルレジスタ長は128bit、uint32_t 4要素で利用(svuint32_t) ○ 返り値zもベクトルレジスタ ○ Zi = Xi + Yi (レジスタ内の要素毎に加算) ○ mask については後述 Copyright © Fixstars Group 13
ARM SVEとは ⚫ ARM SVE(Scalable Vector Extension) ○ 特徴: Scalable = ベクトルレジスタ長がCPU毎に可変 ■ ベクトルレジスタ長が 128bitの倍数(最小128bit、最大2048bit) しか決まっていない →CPUの実装者がある程度自由にレジスタ長を設定できる ○ 組み込み向けなら128bit、サーバー向けなら512bit、など X0 X1 X2 X3 X0 X1 X2 X3 X4 X5 X6 X7 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 X15 ○ Graviton3だと256bit X0 X1 X2 X3 ■ ベクトルレジスタに何要素乗るかはsvcnt𝑋()関数で実行時に取得(𝑋 = {b, h, w, d}) ● たぶん byte(8bit), half word(16bit), word(32bit), double word(64bit) の頭文字 → ベクトル長に依存しないプログラムを記述することで多様なCPUで高速に実行可能に Copyright © Fixstars Group 14
ARM SVEとは ⚫ ARM SVE(Scalable Vector Extension) ○ ARMv9でSVE2が基本命令セットに入る(拡張ではなくなる) ■ 今のうちからSVEに慣れておくと将来役立つ ○ 現時点でSVEが載っているチップは多くない ■ スマートフォン向けSoCのフラッグシップモデルならARMv9採用のものが出てきつつある ● SVEが動くのは1コアのみ、というものも多い ● Android NDKでコンパイルしたバイナリをADBで転送して実機で動かす、 といった感じで開発環境がやや特殊になりがち ■ A64FX(富岳のCPU) ● 研究用途以外で使うのは難しい ● 一応市販はされているが購入するには高い ■ Graviton3 ● インスタンス代のみで好きに使える ● 開発環境も普通のLinuxディストリビューションが入れられる → 現時点で非研究者にとってSVE学習時に最適 Copyright © Fixstars Group 15
Predicate Register ⚫ NEONでは端数部の処理をうまくやらないといけない ○ e.g.) 32bit非負整数 7要素に対して処理するときの後ろ3要素 Z0 Z1 Z2 Z4 Z5 Z6 Z3 ⇐ X0 X1 X2 ⇐ X4 X5 X6 X3 + Y0 Y1 Y2 + Y4 Y5 Y6 Y3 ■ NEONだと4要素ずつアクセスするのでX7やY7、Z7に相当するメモリ領域に アクセスしてしまう → 不正なメモリアクセス e.g.) 0から3まで処理した後に3から6までで処理する(3番の要素は2重に処理する) e.g.) 4から6まではSIMD命令ではなく普通にスカラ処理する → NEONにはマスク命令が無いため 16 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 Copyright © Fixstars Group
Predicate Register
⚫ SVEのベクトルレジスタとは別に存在するマスクのためのレジスタ
e.g.) 32bit 7要素処理する場合(128bit, 256bit)
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)));
}
128bitの場合
(svcntw() == 4)
i=0 -> (T,T,T,T) Z0
Z1
Z2
i=4 -> (T,T,T,F) Z4
Z5
Z6
256bitの場合 (svcntw()
Z3
⇐
X0
X1
X2
⇐
X4
X5
X6
X3
+
Y0
Y1
Y2
+
Y4
Y5
Y6
Y2
Y3
Y4
Y5
Y3
== 8)
i=0 ->
Z0
(T,T,T,T,T,T,T,F)
Z1
Z2
Z3
Z4
Z5
Z6
X0
X1
X2
X3
X4
X5
⇐
X6
+
Y0
Y1
Y6
18
Copyright © Fixstars Group
移植例(簡単な例) ⚫ vectoradd (𝑐 = 𝑎 + 𝑏) 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, 1ベクトルレジスタに float* c, 32bit要素がいくつ入るか size_t n){ const auto lanes = svcntw(); for(size_t i = 0; [0, n)なループの カウンタがiのときの i < n; 32bit向けマスク 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); min(n-i, lanes)個 } ロードして足す maskに従って } 結果を保存 19 Copyright © Fixstars Group
移植例(ケーススタディのコード) ⚫ distance_single_code_simple const float* table; const uint32_t indices[M]; float result = 0.f; for(size_t m = 0; m < M; ++m){ table indices 1 7 8 4 3 0 2 6 3 10 5 2 0 1 9 3 4 7 7 9 3 8 0 1 6 5 2 2 7 9 2 0 8 8 3 4 const auto idx = indices[m]; 4+4+3+0 = 11 const auto collected = table[idx]; result += collected; table += pitch; } ⚫ SVEに移植する際、 ○ tableから1個ずつではなくまとめて データを拾いたい ○ 集めたデータの和をより高速に 求めたい 20 Copyright © Fixstars Group
移植例(ケーススタディのコード) ⚫ distance_single_code_simple 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; } const float* table; const uint32_t indices[M]; {0, pitch, 2*pitch, 3*pitch, ...} float result = 0.f; const auto offsets = svindex_u32(0, pitch); auto sum = svdup_n_f32(0.f); {0, 0, 0, 0, ...} 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 = indices[m+i]+pitch*i 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; SVEにはgatherが } あるので飛び飛びの メモリからロード可 result = svaddv_f32(svptrue_b32(), sum); 水平加算(ベクトル内要素の総和) Copyright © Fixstars Group 21
まとめ ⚫ ARM SVEのintrinsicを使用することでGraviton3上でfaissのHNSWを 1.7倍程度高速化させることができた ⚫ ARM SVEとは ○ ARMv8のベクトル拡張命令セット ■ v9からは基本命令セットの一部 ○ レジスタ長がCPU毎に異なる ○ 命令がマスク付きであり、マスクは専用のレジスタで扱う → 端数部分の処理を特に気にしなくて良い ⚫ Graviton3の性能を最大限引き出すためにもARM SVEを使おう 22 Copyright © Fixstars Group
お知らせ 23 Copyright © Fixstars Group
フィックスターズについて知るには? フィックスターズで扱う技術を、技術セミナーとテックブログで、 企業概要やエンジニアの働き方を、会社説明会でご紹介しています。 技術セミナー https://fixstars.connpass.com/ https://speakerdeck.com/fixstars Tech Blog https://proc-cpuinfo.fixstars.com/ 会社説明会 https://www.fixstars.com/ja/recruit/seminar 24 Copyright © Fixstars Group
発表当日の質疑 25 Copyright © Fixstars Group
発表当日の質疑応答 Q. この辺のお話はC/C++が中心だと思うんですが、他にSVE命令を呼び出せる言語には どんなのがあるんでしょう。例えばJavaはJVMとJava SDKが対応してくれない限り 無理に思えますが、いかがでしょうか。 ⚫ 当日の回答 ○ そもそもVMやインタプリタなど、何かしらランタイムを持つ実装で実行速度を 最大限引き出すことはランタイムのオーバーヘッドの観点で極めて困難 ○ Javaから使うのは厳しそうだがJVMがSVEに対応していればSVEが動いてくれるはず → 本発表のレベルでSVEを直接使うのはJVMの開発者であってJavaを使うアプリ開発者ではない ○ C/C++以外の言語の対応について ■ ベクトル長が可変長、というのが難しさを生んでいる ● 近年ではあまり見られなかったプログラミングパラダイム → 対応している言語が少なめ ● 実行時まで変数のサイズが確定しない → 言語処理系側での扱いが従来と異なる ○ これについては特定ベクトル長CPUを対象として固定長にすることもでき、 その場合はSIMDと似たような感じで対応できるのではないか ■ C/C++以外の言語で似たような方向性の(ランタイム無しのネイティブバイナリを生成するシステム プログラミング)言語となるとRustが存在するが、少なくとも以前調べた段階では未対応だった 26 → CとC++ぐらいしか対応していないという認識 Copyright © Fixstars Group
発表当日の質疑応答 Q. この辺のお話は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 Copyright © Fixstars Group
Thank you! お問い合わせ窓口 : [email protected] Copyright © Fixstars Group