OpenBLAS を Graviton4 向けに最適化ビルドして性能の違いを検証してみた
はじめに
OpenBLAS は高性能な線形代数ライブラリです。科学計算や機械学習の基盤として広く使われています。
Ubuntu の apt でインストールできる OpenBLAS は、様々な CPU で動作するよう汎用的にビルドされています。AWS Graviton(ARM)向けに最適化すると効果があるのか気になったので試してみました。今回は Graviton4(Neoverse V2)向けに最適化した OpenBLAS をソースからビルドし、apt 版との性能を比較してみました。
結論:Graviton4 向けに最適化した OpenBLAS をソースビルドすると、apt 版と比較して約 8〜10% の性能向上を確認しました。
検証環境
| 項目 | バージョンなど |
|---|---|
| インスタンスタイプ | m8g.xlarge(4 vCPU, 16 GiB) |
| OS | Ubuntu 24.04.3 LTS (Noble Numbat) |
| カーネル | 6.14.0-1018-aws |
| CPU | Neoverse-V2(Graviton4) |
| GCC | 13.3.0 |
| OpenBLAS (apt) | 0.3.26 |
| OpenBLAS (source) | 0.3.31.dev (NEOVERSEV2) |
| リージョン | us-east-1 |
検証手順
環境セットアップ
システムを更新し、ビルドに必要なツールをインストールします。
sudo apt update && sudo apt upgrade -y
sudo apt install -y build-essential git
GCC のバージョンを確認します。Graviton4 最適化のために 13 以上が必要です。
gcc --version
gcc (Ubuntu 13.3.0-6ubuntu2~24.04) 13.3.0
Copyright (C) 2023 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
apt 版 OpenBLAS のインストールと確認
パッケージをインストールします。
sudo apt install -y libopenblas-openmp-dev
インストール状況を確認します。
dpkg -l | grep openblas
ii libopenblas-openmp-dev:arm64 0.3.26+ds-1ubuntu0.1 arm64 Optimized BLAS (linear algebra) library (dev, openmp)
ii libopenblas0-openmp:arm64 0.3.26+ds-1ubuntu0.1 arm64 Optimized BLAS (linear algebra) library (shared lib, openmp)
ライブラリパスを確認します。
dpkg -L libopenblas0-openmp | grep "\.so"
/usr/lib/aarch64-linux-gnu/openblas-openmp/libblas.so.3
/usr/lib/aarch64-linux-gnu/openblas-openmp/liblapack.so.3
/usr/lib/aarch64-linux-gnu/openblas-openmp/libopenblasp-r0.3.26.so
/usr/lib/aarch64-linux-gnu/openblas-openmp/libopenblas.so.0
バージョンを確認します。
apt-cache show libopenblas0-openmp | grep Version
Version: 0.3.26+ds-1ubuntu0.1
Version: 0.3.26+ds-1
ビルド情報を確認します。
strings /usr/lib/aarch64-linux-gnu/openblas-openmp/libopenblas.so | grep -iE 'OpenBLAS|neoverse'
OpenBLAS 0.3.26 NO_LAPACKE DYNAMIC_ARCH NO_AFFINITY USE_OPENMP
neoversen2
neoversen1
neoversev1
neoversev2
apt 版のビルドオプションは以下のとおりです。
| オプション | 説明 |
|---|---|
DYNAMIC_ARCH |
複数カーネルを含み、実行時に CPU を検出して選択 |
USE_OPENMP |
OpenMP 並列化 |
NO_LAPACKE |
LAPACKE(C 言語インターフェイス)なし |
OpenBLAS ソースビルド(Graviton4 最適化)
Graviton4 向けに最適化するには、AWS Graviton Getting Started によると以下の要件があります。
| 要件 | 推奨バージョン |
|---|---|
| GCC | 13 以上 |
| LLVM/Clang | 16 以上 |
| コンパイラフラグ | -mcpu=neoverse-v2 |
GCC 13 以上が必要な理由は、Neoverse V2(Graviton4)向けの命令セットサポートが GCC 13 で追加されたためです。今回の検証環境は GCC 13.3.0 を使用しているため、この要件を満たしています。
ソースを取得します。
cd ~
git clone https://github.com/OpenMathLib/OpenBLAS.git
cd OpenBLAS
バージョンを確認します。
git describe --tags
v0.3.31-35-gbc3b7e749
ビルドオプションの説明は以下のとおりです。
| オプション | 説明 |
|---|---|
TARGET=NEOVERSEV2 |
OpenBLAS の Neoverse V2 向けカーネルを選択 |
USE_OPENMP=1 |
OpenMP 並列化を有効化 |
NOFORTRAN=1 |
Fortran コンパイラ不要でビルド(今回未使用のため) |
CFLAGS="-mcpu=neoverse-v2" |
GCC に Neoverse V2 向け命令生成を指示(AWS Graviton Getting Started 推奨) |
-j$(nproc) |
CPU コア数分の並列ビルド |
ビルドとインストールを実行します。
make clean
make TARGET=NEOVERSEV2 USE_OPENMP=1 NOFORTRAN=1 CFLAGS="-mcpu=neoverse-v2" -j$(nproc)
sudo make PREFIX=/opt/openblas-neoversev2 install
--- 省略 ---
END OF TESTS
make[1]: Leaving directory '/home/ubuntu/OpenBLAS/ctest'
OpenBLAS build complete. (BLAS CBLAS LAPACK LAPACKE)
OS ... Linux
Architecture ... arm64
BINARY ... 64bit
C compiler ... GCC (cmd & version : cc (Ubuntu 13.3.0-6ubuntu2~24.04) 13.3.0)
Library Name ... libopenblas_neoversev2p-r0.3.31.dev.a (Multi-threading; Max num-threads is 4)
Use OpenMP in the multithreading. Because of ignoring OPENBLAS_NUM_THREADS and GOTO_NUM_THREADS flags,
you should use OMP_NUM_THREADS environment variable to control the number of threads.
To install the library, you can run "make PREFIX=/path/to/your/installation install".
Note that any flags passed to make during build should also be passed to make install
インストール結果を確認します。
ls -la /opt/openblas-neoversev2/lib/
total 39284
drwxr-xr-x 4 root root 4096 Jan 29 09:34 .
drwxr-xr-x 5 root root 4096 Jan 29 09:34 ..
drwxr-xr-x 3 root root 4096 Jan 29 09:34 cmake
lrwxrwxrwx 1 root root 37 Jan 29 09:34 libopenblas.a -> libopenblas_neoversev2p-r0.3.31.dev.a
lrwxrwxrwx 1 root root 38 Jan 29 09:34 libopenblas.so -> libopenblas_neoversev2p-r0.3.31.dev.so
lrwxrwxrwx 1 root root 38 Jan 29 09:34 libopenblas.so.0 -> libopenblas_neoversev2p-r0.3.31.dev.so
-rw-r--r-- 1 root root 27675448 Jan 29 09:34 libopenblas_neoversev2p-r0.3.31.dev.a
-rwxr-xr-x 1 root root 12560216 Jan 29 09:34 libopenblas_neoversev2p-r0.3.31.dev.so
drwxr-xr-x 2 root root 4096 Jan 29 09:34 pkgconfig
apt 版とソースビルド版が別パスに存在することを確認します。
echo "=== apt版 ==="
ls /usr/lib/aarch64-linux-gnu/openblas-openmp/libopenblas.so* 2>/dev/null || echo "not found"
echo "=== NEOVERSEV2版 ==="
ls /opt/openblas-neoversev2/lib/libopenblas.so* 2>/dev/null || echo "not found"
=== apt版 ===
/usr/lib/aarch64-linux-gnu/openblas-openmp/libopenblas.so /usr/lib/aarch64-linux-gnu/openblas-openmp/libopenblas.so.0
=== NEOVERSEV2版 ===
/opt/openblas-neoversev2/lib/libopenblas.so /opt/openblas-neoversev2/lib/libopenblas.so.0
ベンチマーク用のプログラムを用意
ベンチマーク用のディレクトリを作成し、ソースファイルを作成します。
mkdir -p ~/benchmark
cd ~/benchmark
OpenBLAS の cblas_dgemm 関数を使用して倍精度行列積を計算し、実行時間と GFLOPS を測定します。
#include <iostream>
#include <vector>
#include <chrono>
#include <random>
#include <cblas.h>
int main(int argc, char *argv[]) {
int N = (argc > 1) ? std::stoi(argv[1]) : 10000;
int REPEAT = (argc > 2) ? std::stoi(argv[2]) : 3;
// 行列メモリ確保
std::vector<double> A(N * N);
std::vector<double> B(N * N);
std::vector<double> C(N * N, 0.0);
// 乱数初期化
std::random_device rd;
std::mt19937 gen(rd());
std::uniform_real_distribution<double> dist(0.0, 1.0);
for (int i = 0; i < N * N; ++i) {
A[i] = dist(gen);
B[i] = dist(gen);
}
std::cout << "Matrix size: " << N << " x " << N << std::endl;
std::cout << "Repeat: " << REPEAT << " times" << std::endl;
// ベンチマーク実行
auto start = std::chrono::high_resolution_clock::now();
for (int r = 0; r < REPEAT; ++r) {
cblas_dgemm(CblasRowMajor, CblasNoTrans, CblasNoTrans,
N, N, N, 1.0, A.data(), N, B.data(), N, 0.0, C.data(), N);
}
auto end = std::chrono::high_resolution_clock::now();
auto elapsed_ms = std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count();
// 結果出力
double elapsed_sec = elapsed_ms / 1000.0;
double gflops = (2.0 * N * N * N * REPEAT) / elapsed_sec / 1e9;
std::cout << "Time: " << elapsed_ms << " ms" << std::endl;
std::cout << "GFLOPS: " << gflops << std::endl;
return 0;
}
同じソースコードから、apt 版と NEOVERSEV2 版の 2 種類ビルドします。
# apt版
g++ -O3 benchmark_dgemm.cpp -o bench_apt -lopenblas
# NEOVERSEV2版
g++ -O3 benchmark_dgemm.cpp -o bench_neoversev2 \
-I/opt/openblas-neoversev2/include \
-L/opt/openblas-neoversev2/lib -lopenblas \
-Wl,-rpath,/opt/openblas-neoversev2/lib
出来上がったバイナリファイルからリンク先を確認します。
ldd ./bench_apt | grep openblas
libopenblas.so.0 => /lib/aarch64-linux-gnu/libopenblas.so.0 (0x0000f11864120000)
ldd ./bench_neoversev2 | grep openblas
libopenblas.so.0 => /opt/openblas-neoversev2/lib/libopenblas.so.0 (0x0000f13431eb0000)
使用カーネルの確認
各バイナリが正しい OpenBLAS を使用しているか確認します。
apt 版の確認結果です。
OPENBLAS_VERBOSE=2 OMP_NUM_THREADS=1 ./bench_apt 100 1 2>&1 | head -5
Core: neoversev1
Matrix size: 100 x 100
Repeat: 1 times
Time: 0 ms
GFLOPS: inf
neoversev1(Graviton3 向け)カーネルを使用していることがわかります。
NEOVERSEV2 版の確認結果です。
OPENBLAS_VERBOSE=2 OMP_NUM_THREADS=1 ./bench_neoversev2 100 1 2>&1 | head -5
Core の出力がありませんでした。
Matrix size: 100 x 100
Repeat: 1 times
Time: 0 ms
GFLOPS: inf
stringsコマンドで確認します。
strings /opt/openblas-neoversev2/lib/libopenblas.so | grep -i neoverse
NEOVERSEV2(Graviton4 向け)カーネルでビルドされていることを確認できました。
OpenBLAS 0.3.31.dev NO_AFFINITY USE_OPENMP NEOVERSEV2
NEOVERSEV2
sbgemm_kernel_8x4_neoversen2.c
sbgemm_ncopy_8_neoversen2.c
sbgemm_tcopy_8_neoversen2.c
sbgemm_ncopy_4_neoversen2.c
sbgemm_tcopy_4_neoversen2.c
sbgemm_beta_neoversen2.c
ベンチマーク実行
10000 x 10000 の行列積を 3 回実施した結果です。
apt 版のベンチマーク結果です。
cd ~/benchmark
# スレッド数=1
export OMP_NUM_THREADS=1
./bench_apt 10000 3
Matrix size: 10000 x 10000
Repeat: 3 times
Time: 237063 ms
GFLOPS: 25.3097
# スレッド数=4
export OMP_NUM_THREADS=4
./bench_apt 10000 3
Matrix size: 10000 x 10000
Repeat: 3 times
Time: 79074 ms
GFLOPS: 75.8783
NEOVERSEV2 版のベンチマーク結果です。
# スレッド数=1
export OMP_NUM_THREADS=1
./bench_neoversev2 10000 3
Matrix size: 10000 x 10000
Repeat: 3 times
Time: 215713 ms
GFLOPS: 27.8147
# スレッド数=4
export OMP_NUM_THREADS=4
./bench_neoversev2 10000 3
Matrix size: 10000 x 10000
Repeat: 3 times
Time: 73277 ms
GFLOPS: 81.8811
実行結果と分析
結果サマリー
| 版 | スレッド数 | 時間 | GFLOPS | apt 比 |
|---|---|---|---|---|
| apt | 1 | 237 秒 | 25.31 | - |
| apt | 4 | 79 秒 | 75.88 | - |
| NEOVERSEV2 | 1 | 216 秒 | 27.81 | +9.9% |
| NEOVERSEV2 | 4 | 73 秒 | 81.88 | +7.9% |
Graviton4 最適化効果
ソースコードからビルドしたかいがありました。
| スレッド数 | 性能向上 |
|---|---|
| 1 スレッド | +9.9%(25.31 → 27.81 GFLOPS) |
| 4 スレッド | +7.9%(75.88 → 81.88 GFLOPS) |
マルチスレッドスケーリング
4 コアのインスタンスのため、これ以上のスレッド数はデータ取れていません。
| 版 | 1→4 スレッド倍率 |
|---|---|
| apt | 3.0 倍 |
| NEOVERSEV2 | 2.94 倍 |
性能差の要因
ベンチマーク結果から約 8〜10% の性能差が確認できました。要因となるのは apt 版 OpenBLAS は Gravioton4 向けのビルドではなかったことが大きかったかと推測しています。
- DYNAMIC_ARCH でビルドされており、複数のカーネルを含み実行時に CPU を検出して選択する
- バージョン 0.3.26 では、Graviton4 のマシンでも neoversev1 カーネル(Graviton3 向け)が選択されることを確認した(
OPENBLAS_VERBOSE=2の出力より)
それと、Graviton4 向けのビルドはパフォーマンス重視の設定にしたことも性能差に寄与した可能性があります。
まとめ
Graviton4 向けに最適化した OpenBLAS をソースビルドして性能検証しました。
- NEOVERSEV2 カーネルによる最適化効果は約 8〜10%
- apt 版(neoversev1 カーネル)でも十分高速だが、最大性能を引き出すにはソースビルドが有効
- マルチスレッドスケーリングは両版とも良好で、4 スレッドで約 3 倍の性能向上
おわりに
以前、Graviton4 最適化の検証したことがあり、行列計算を例に試したのですが OpenBLAS を最適化しないと意味がありませんでした。所要で Graviton の検証する機会があったので肩慣らしに試してみました。
apt 版は手軽に導入できる利点があります。一方、Graviton4 の性能を最大限に活用したい場合は、TARGET=NEOVERSEV2 を指定したソースビルドを検討してください。







