ブラウザでのNumPyの高速化
Faster NumPy in the Browser (notebook.link)
要約
Emscripten-forgeのNumPyパッケージがOpenBLASをWebAssemblyにリンクするようになり、ブラウザ上でのNumPyのパフォーマンスが大幅に向上しました。特にnp.matmulのような行列演算では、以前の単純なループ実装と比較して、float32で約30.92倍、float64で約14.90倍の高速化が達成されています。これは、Emscripten-forgeが言語非依存のWebAssembly向けソフトウェア配布プラットフォームとして機能していることを示しています。
全文翻訳
The last mile of a long road: faster NumPy in the browser
長らく、ブラウザで NumPy を実行することは、アクセラレーテッド BLAS を利用せずに実行することを意味していました。行列乗算は、単純なループにフォールバックしていました(移植性はありますが、キャッシュや SIMD を活用できません)。
それが今、変わりました。Emscripten-forge の NumPy パッケージは、WebAssembly で OpenBLAS をリンクするようになり、n = 1024 の正方行列に対する np.matmul は、float32 で約 30.92倍、float64 で 14.90倍 高速になりました。次期 OpenBLAS リリース(QuantStack からのカーネル提供により、Emscripten-forge で実験的に利用可能)では、さらに性能が向上し、対応エンジンではオプションの Relaxed SIMD ビルドがさらなるステップアップをもたらします。
What is Emscripten-forge?
Emscripten-forge は WebAssembly 向けのソフトウェア配布システムです。conda-forge と連携し、conda エコシステムを WebAssembly 用に再構築するため、コンパイラ、ランタイム、共有ライブラリは、一貫した ABI を持つ再配布可能な conda パッケージ として提供されます(Python wheels や R パッケージではなく、あらゆる言語エコシステムがリンクできるネイティブライブラリです)。
科学計算言語をブラウザに持ち込むことは非常に困難であり、これまでほとんどの場合、一度に1つの言語ずつ実現されてきました。Pyodide が Python でその先駆けとなりました。その後、WebR が同様の前提で R に対してそれを実現しました。それぞれが自己完結型の言語固有のディストリビューションです。Emscripten-forge は異なるアプローチを取ります。それは言語非依存の配布です。Pyodide と WebR プロジェクトの基盤となる作業の上に構築され、Python や R だけでなく、C++、OCaml、Lua、コンパイラツールチェーン、その他多くのツールもカバーし、パッケージマネージャーも提供します。
The missing compiler: Fortran on wasm32
Fortran は、基盤となる科学パッケージの中核をなしています。LAPACK、歴史的には SciPy の多く(ただし、SciPy が Fortran フリーになるよう進歩しているため、これは変化しています)、そして R のコンパイル済み統計ルーチンは、すべて内部で Fortran を使用しています。それらを WebAssembly に持ち込むには、wasm32 をターゲットとする Fortran コンパイラが必要になります。
しばらくの間、Pyodide はそのようなコンパイラの不在を回避していました。その SciPy ビルドは f2c(Fortran-to-C トランスペイラ)を使用して生成され、Fortran ソースコードは C にトランスパイルされ、既存の Emscripten ツールチェーンでコンパイルされました。この回避策により SciPy はブラウザで実行できるようになりましたが、R の配布では実際の Fortran コンパイラを回避できなかったため、十分ではありませんでした。その制約が、R を Emscripten-forge 上のブラウザに持ち込む という文脈で、WebAssembly 用の Flang(LLVM の Fortran フロントエンド)の開発を開始するきっかけとなりました。
OpenBLAS in WebAssembly
OpenBLAS は、BLAS(Basic Linear Algebra Subprograms)の広く使用されている最適化された実装です。BLAS は3つのレベルに分かれています。レベル1 はベクトル-ベクトル演算(例: AXPY、内積)をカバーします。レベル2 は行列-ベクトル演算(例: GEMV、A @ x など)をカバーします。レベル3 は行列-行列演算(例: GEMM、np.matmul など)をカバーし、大規模な密行列線形代数の計算の大部分を占めます。OpenBLAS は LAPACK(Linear Algebra PACKage)も提供しています。これは線形システム、分解、固有値問題などを解くための高レベルルーチンであり、ほとんどの np.linalg 呼び出しは最終的にこれらの LAPACK エントリポイントに委譲されます。
WebAssembly 用の Fortran ツールチェーンの構築自体が、複数人による取り組みでした。Isabel Paredes と Serge Guelton は、George Stagg の WebAssembly における Fortran に関する研究 に触発され、WebAssembly 用の初期 Flang パッチを作成しました。Serge Guelton は、その作業の一部を LLVM にアップストリームしました。Isabel Paredes は現在、Emscripten-forge 上の最新のレシピ flang_emscripten-wasm32 をメンテナンスしています。
しかし、Flang が動作するようになっても、ツールチェーンだけでは OpenBLAS をブラウザに持ち込むには不十分でした。最初の OpenBLAS および LAPACK ビルドはすぐに使用できませんでした。WebAssembly はネイティブターゲットよりも厳格な呼び出し規約を強制するため、ライブラリを Emscripten 下でコンパイル、アーカイブ、リンクできるようにするために追加のパッチが必要でした。Ian Thomas は Flang と Emscripten 用に OpenBLAS を適応させました。これらのパッチは Emscripten-forge の OpenBLAS レシピ に存在します(これは、アップストリームの OpenBLAS と、この記事で測定された NumPy ビルド間のブリッジとなります)。
この記事の残りの部分は、最後の道のりをたどります。NumPy がついに BLAS を呼び出せるようになったときに何が変わったのか、そして OpenBLAS 0.3.34 および今後の 0.3.35 が何を提供するのかを説明します。
The last mile: NumPy links OpenBLAS
最後のステップは、NumPy をアクセラレーテッド BLAS にリンクすることでした。以前の Emscripten-forge NumPy パッケージ(emscripten-forge-4x 上の 2.5.2)は OpenBLAS なしで提供されていたため、np.matmul、@、np.linalg はポータブルな C ループにフォールバックしていました。これは、キャッシュ階層や SIMD を考慮しない単純な実装です。行列積は、算術演算よりもメモリトラフィックにほとんどの時間を費やしていました。これが私たちの no-BLAS ベースラインです。
emscripten-forge/recipes#6310 以降、emscripten-forge-4x 上のデフォルト NumPy(2.5.3、ビルド 3)は、WASM SIMD (TARGET=WASM128_GENERIC) を使用して OpenBLAS 0.3.34 をリンクしています。これは現在、安定したメインチャンネルのリリースです。np.matmul、@、np.linalg.solve への呼び出しは、WebAssembly のアクセラレーテッド BLAS にディスパッチされるようになりました。
このモデルが強力なのは、パッケージング方法にあります。Emscripten-forge は実質的に、WebAssembly 用の conda-forge です。パッケージはレシピからビルドされ、conda チャンネルに公開され、共有 ABI の下でパッケージマネージャーでインストールされます。これは linux-64 と同じワークフローですが、emscripten-wasm32 をターゲットとしています。そのモデルでは、OpenBLAS は個別の conda パッケージです。NumPy は実行時に libopenblas を動的にリンクするため、NumPy を再ビルドせずに OpenBLAS をアップグレードできます。これはエコシステム全体に利益をもたらします。SciPy や scikit-learn のような科学 Python プロジェクト、および xtensor-blas のような非 Python スタックも、同じライブラリを共有します。対照的に、PyPI の NumPy wheels はビルド時に BLAS スナップショットをバンドルします。したがって、同じ NumPy 2.5.3 パッケージは、公開され次第、自動的に OpenBLAS 0.3.35 を使用するようになります。
OpenBLAS 0.3.34 versus no BLAS
BLAS 実装がない同じ Emscripten-forge スタックと比較して、n = 1024 の正方行列 np.matmul は、float32 で約 30.92倍、float64 で 14.90倍 に達します(0.90 / 0.98 GFLOPS に対して 28.0 / 14.6 GFLOPS)。フルサイズのグリッドとシングルスレッドの linux-64 リファレンスは Appendix F にあります。この比較に焦点を当てたグラフと表は Appendix B にあります。
[画像: Geometric-mean speedup of OpenBLAS 0.3.34 versus NumPy without BLAS, by Python API]
両方のデータ型とサイズグリッド全体での幾何平均(分母は OpenBLAS 0.3.34 の時間)。行列積が最も恩恵を受けています。一部の np.linalg.* API では、LAPACK に委譲するため、ゲインが小さく(no BLAS に対して 1.08–1.65倍)、その LAPACK 実装はまだ WebAssembly 用に最適化または特殊化されていません。A @ x とベクトル np.dot は約 1倍のままです。OpenBLAS 0.3.34 は、そのレイアウトに対して高速な列メジャー GEMV を提供しません。OpenBLAS 0.3.35 がそのカーネルを追加します。
BLAS なしでは、np.matmul のスループットは n とともに減少します(単純な GEMM、キャッシュミス)。OpenBLAS 0.3.34 では、n とともに増加します。GEMM の上に実装された操作(@、2次元 np.dot、np.tensordot、np.linalg.multi_dot)も同様の傾向を示します。n に対するスピードアップと関連数値は Appendix B にあります。
np.linalg において n = 1024 (float32) では、中央値時間は no BLAS と比較して約 1.2–2.1倍 低下します(solve は 2.11倍、cholesky は 1.88倍、qr は 2.03倍、inv は 1.66倍、eigh は 1.60倍、svd は 1.21倍。float64 も数パーセント以内で一致します)。行列ベクトル積では、x @ A は 0.3.34 で既にレベル2カーネルを使用しています(float32、n = 1024 で 14.86倍)。一方、A @ x は no-BLAS の約 4 GFLOPS(1.02倍)にとどまります。
OpenBLAS 0.3.35 will be even faster
次期 OpenBLAS リリースには、develop に既に存在する WASM SIMD の作業が含まれています。以下の数値は、emscripten-forge/recipes#6761 によって公開されている openblas-experimental パッケージ(0.3.35.dev0、develop @ 539bb47)を使用しており、ポータブルな simd128_hbf81ecf_3(デフォルト)とオプションの relaxed_simd_h13a9a80_3 があります。WebAssembly Relaxed SIMD は、より高速なベクトル演算(特に fused multiply-add)を可能にするエンジン拡張機能です。OpenBLAS は、ビルドで有効にされていればこれらのオペコードを使用できます。特に断りのない限り、この記事の 0.3.35 はポータブル SIMD128 ビルドを意味します。
QuantStack は WASM SIMD カーネルを OpenBLAS にアップストリームしました(Julien Jerphanion、Matthias Meschede)。レビューと統合は Martin Kroeker が担当しました。この作業には、レベル3 GEMM/TRMM、レベル1/2 AXPY および GEMV、Node/Emscripten 用の数値 CBLAS テスト、およびオプションの Relaxed SIMD パスが含まれます。ポータブル SIMD128 はデフォルトのままです。Relaxed SIMD FMA は、relaxed_simd ビルドを選択しない限りオフのままです。0.3.34 からの float32 GEMM の主要なステップは 8x4 マイクロカーネルです。オプションの Relaxed SIMD バリアントは次のサブセクションで説明します。
n = 1024 で WASM OpenBLAS 0.3.34 と比較して、np.matmul は 1.79倍 (float32、28.0 → 50.0 GFLOPS) および 1.41倍 (float64、14.6 → 20.5 GFLOPS) 向上します。この比較のグラフと数値は Appendix C にあり、完全なグリッドは Appendix F にあります。
[画像: Geometric-mean speedup of OpenBLAS 0.3.35 versus OpenBLAS 0.3.34, by Python API]
両方のデータ型とサイズグリッド全体での幾何平均(分母は OpenBLAS 0.3.35 の時間)。WASM 0.3.34 に対する相対的な最大のゲインは GEMM だけではありません。A @ x は GEMV カーネルを介して、no-BLAS の約 4 GFLOPS から 23.1 GFLOPS (float32、n = 1024) にジャンプします(0.3.34 と比較して 5.52倍。x @ A は 1.73倍)。ベクトル np.dot も同様に改善されます。正方行列 np.matmul は幾何平均で 1.58倍(n = 1024 float32 では 8x4 SGEMM から 1.79倍)です。np.linalg において n = 1024 float32 では、中央値時間は 0.3.34 と比較して約 1.3–1.9倍 低下します(solve は 1.38倍、cholesky は 1.32倍、qr は 1.49倍、inv は 1.33倍、eigh は 1.64倍、svd は 1.90倍)。
Optional: Relaxed SIMD on top of 0.3.35
WebAssembly Relaxed SIMD を実装しているエンジンは、relaxed_simd OpenBLAS ビルド(WASM_RELAXED_SIMD=1)をロードでき、これは #6001 / #6020 から FMA スタイルの v_muladd を使用します。このベンチマークの Chromium 153 では、ポータブル 0.3.35 と比較して、n = 1024 での np.matmul がさらに 1.17倍 (float32) および 1.43倍 (float64) 向上します(50.0 → 58.7 GFLOPS および 20.5 → 29.4 GFLOPS)。SIMD128 と比較した幾何平均ゲインは、行列積で最も大きく(約 1.3倍)なります。レベル2およびほとんどの np.linalg は約 1.0–1.2倍 の範囲で移動します。この比較のグラフと数値は Appendix D にあります。
[画像: Geometric-mean speedup of OpenBLAS 0.3.35 Relaxed SIMD versus portable 0.3.35, by Python API]
Chrome ≥114 および Firefox ≥146 はこの機能を提供しています。Safari は JavaScriptCore フラグ useWebAssemblyRelaxedSIMD が必要です。そうしないとモジュールがインスタンス化に失敗します。ポータブル simd128 パッケージは、その理由からデフォルトのままです(emscripten-forge/recipes#6761 は Relaxed SIMD バリアントの優先度を下げています)。現在のエンジンサポートは webassembly.org/features で追跡されています。
From no BLAS to OpenBLAS 0.3.35
ステップをまとめると(OpenBLAS 0.3.34 のリンク、次に 0.3.35 SIMD128 カーネルの採用、次にオプションの Relaxed SIMD)、以前の no-BLAS Emscripten-forge NumPy からのスピードアップパスが得られます。
[画像: Geometric-mean speedup of OpenBLAS 0.3.34, 0.3.35 SIMD128, and 0.3.35 Relaxed SIMD versus NumPy without BLAS, by Python API]
両方のデータ型とサイズグリッド全体での幾何平均(分子は no-BLAS の時間)。各 API は3つのバーを示します:OpenBLAS 0.3.34、OpenBLAS 0.3.35 (SIMD128)、および OpenBLAS 0.3.35 (Relaxed SIMD)。行列積が最も大きくゲインし、レベル2 A @ x とベクトル np.dot は 0.3.35 GEMV カーネルが着地するとついに移動します。np.linalg.* は、OpenBLAS でまだ WASM 特化されていない LAPACK ルーチンによって制限された、1倍よりわずかに大きい帯域に位置します。Emscripten-forge で公開されている NumPy は、既にそのパスの OpenBLAS 0.3.34 部分を提供しています。ポータブル 0.3.35 (simd128_hbf81ecf_3、emscripten-forge/recipes#6761 より) は今日、実験チャンネルで利用可能であり、オプションの Relaxed SIMD ビルド (relaxed_simd_h13a9a80_3) も対応エンジン向けに提供されています。n = 1024 でのエンドツーエンド np.matmul は、Relaxed SIMD を使用した no BLAS と比較して、float32 で約 64.89倍、float64 で 30.07倍 に達します。このエンドツーエンド比較の n に対するスピードアップは Appendix E にあり、linux-64 を含む完全なサイズグリッドは Appendix F にあります。
Using the packages
OpenBLAS にリンクされた NumPy は、メインの emscripten-forge-4x チャンネル(emscripten-forge/recipes#6310 時点)で利用可能です。この組み合わせには実験チャンネルは不要です。numpy をインストールすると、OpenBLAS 0.3.34 がプルされます。
例として、JupyterLite または notebook.link 用の環境を以下に示します。
name: numpy-openblas
channels:
- https://prefix.dev/emscripten-forge-4x
- https://prefix.dev/conda-forge
dependencies:
- xeus-python
- numpy
OpenBLAS 0.3.35 (0.3.35.dev0) は、emscripten-forge/recipes#6761 のデュアルバリアントパッケージとして emscripten-forge-4x-experimental で利用可能です(openblas-0.3.35.dev0-simd128_hbf81ecf_3 および openblas-0.3.35.dev0-relaxed_simd_h13a9a80_3)。新しい OpenBLAS を NumPy を再ビルドせずに使用したい場合は、実験チャンネルを追加し、Relaxed SIMD が必要な場合はビルド文字列を明示的にピン留めしてください。
name: numpy-openblas-035
channels:
- https://prefix.dev/emscripten-forge-4x
- https://prefix.dev/emscripten-forge-4x-experimental
- https://prefix.dev/conda-forge
dependencies:
- xeus-python
- numpy
- openblas * *simd128* # SIMD128 ポータブルビルド (デフォルト)
# - openblas * *relaxed_simd* # Relaxed SIMD ビルド (ポータビリティは低く、優先度も低い)
ポータブルな simd128 デフォルトは、WebAssembly Relaxed SIMD を必要としません。relaxed_simd パッケージは必要です。Chrome ≥114 および Firefox ≥146 は動作しますが、Safari は JavaScriptCore フラグ useWebAssemblyRelaxedSIMD が必要です。そうしないと環境のロードに失敗します。
Conclusion
Emscripten-forge 上の NumPy は、WebAssembly でアクセラレーテッド BLAS を呼び出せるようになりました。OpenBLAS 0.3.34 をリンクするだけで、行列積と np.linalg で大幅なゲインが得られ、今後の 0.3.35 カーネル(オプションの Relaxed SIMD ビルドを含む)はさらに性能を向上させます。この作業が OpenBLAS および再配布可能な conda パッケージとしてアップストリームされたため、Emscripten-forge から Fortran ツールチェーンのコンポーネントを採用する際に、他の WebAssembly Python スタック(Pyodide を含む)も同様の改善の恩恵を受けることができます。
About the authors
- Julien Jerphanion (QuantStack) は、WebAssembly カーネルを OpenBLAS にアップストリームし、この記事のベンチマークを設計・実行し、数値テストをアップストリームに追加し、Emscripten-forge OpenBLAS レシピ で NumPy と SciPy をパッケージ化・テストしました。
- Matthias Meschede (QuantStack) は、WebAssembly カーネルが実際に使用されることを保証するために OpenBLAS に貢献し、パッケージ管理の支援を提供しました。
- Ian Thomas (QuantStack) は、SciPy やその他の Emscripten-forge パッケージによる OpenBLAS の使用を可能にした初期パッチを作成しました。これらのパッチの一部は OpenBLAS プロジェクトにアップストリームされる可能性があります。
Acknowledgements
この記事の著者以外にも、この記事が終盤を測定するだけの道路を築いた人々やコミュニティに感謝します。
- Isabel Paredes と Serge Guelton は、Fortran on
wasm32を実用的にした Flang パッチ(George Stagg の研究 に基づく)を提供しました。Serge Guelton はその作業の一部を LLVM にアップストリームしました。そして Isabel Paredes は Emscripten-forge 上でflang_emscripten-wasm32をメンテナンスしています。 - Martin Kroeker、コア OpenMathLib / OpenBLAS メンテナーは、私たちの貢献をレビューし統合してくれました。
- Sylvain Corlay、Isabel Paredes、および Antoine Pitrou は、この記事を改善するコメントを提供してくれました。
Appendix A: Benchmark settings
WebAssembly の数値は中央値時間のスピードアップです。表には中央値時間と GFLOPS も示されています。linux-64 列は、このマシン上での同じ API グリッドです。
| Setting | Value |
|---|---|
| Host | Dell XPS 15 9530, 13th Gen Intel Core i7-13700H (6P+8E, 20 threads, AVX2, up to 5.0 GHz), 64 GB RAM, Fedora Linux 44 |
| WebAssembly | Chromium 153; OpenBLAS USE_THREAD=0 TARGET=WASM128_GENERIC; packages from emscripten-forge/recipes#6761: simd128_hbf81ecf_3 (default) and relaxed_simd_h13a9a80_3 (WASM_RELAXED_SIMD=1) |
| linux-64 | conda-forge linux-64 NumPy 2.5.2 and OpenBLAS 0.3.34 (DYNAMIC_ARCH, Haswell kernel), one thread (OPENBLAS_NUM_THREADS=1), pinned to a P-core |
| Matrix sizes | 64, 128, 256, 512, 1024 |
| Vector sizes | 10⁴, 10⁵, 10⁶ |
| dtypes | float32, float64 |
| Statistic | median of 10 samples after 5 warmups |
| Scripts | github.com/jjerphan/numpy-wasm-openblas-bench |
Appendix B: OpenBLAS 0.3.34 versus no BLAS
no-BLAS Emscripten-forge NumPy ベースラインに対する OpenBLAS 0.3.34 の比較。
Speedup versus input size
[画像: Speedup versus n for OpenBLAS 0.3.34 against no BLAS]
| API | n | dtype | 0.3.34 vs no BLAS |
|---|---|---|---|
np.matmul | 64 | float32 | 4.54× |
np.matmul | 64 | float64 | 2.56× |
np.matmul | 128 | float32 | 6.88× |
np.matmul | 128 | float64 | 3.56× |
np.matmul | 256 | float32 | 7.67× |
np.matmul | 256 | float64 | 4.17× |
np.matmul | 512 | float32 | 12.04× |
np.matmul | 512 | float64 | 14.16× |
np.matmul | 1024 | float32 | 30.92× |
np.matmul | 1024 | float64 | 14.90× |
np.linalg.solve | 64 | float32 | 1.03× |
np.linalg.solve | 64 | float64 | 1.06× |
np.linalg.solve | 128 | float32 | 1.45× |
np.linalg.solve | 128 | float64 | 1.48× |
np.linalg.solve | 256 | float32 | 1.95× |
np.linalg.solve | 256 | float64 | 1.91× |
np.linalg.solve | 512 | float32 | 1.98× |
np.linalg.solve | 512 | float64 | 1.95× |
np.linalg.solve | 1024 | float32 | 2.11× |
np.linalg.solve | 1024 | float64 | 2.10× |
np.linalg.cholesky | 64 | float32 | 0.96× |
np.linalg.cholesky | 64 | float64 | 0.99× |
np.linalg.cholesky | 128 | float32 | 1.22× |
np.linalg.cholesky | 128 | float64 | 1.20× |
np.linalg.cholesky | 256 | float32 | 1.51× |
np.linalg.cholesky | 256 | float64 | 1.51× |
np.linalg.cholesky | 512 | float32 | 1.59× |
np.linalg.cholesky | 512 | float64 | 1.59× |
np.linalg.cholesky | 1024 | float32 | 1.88× |
np.linalg.cholesky | 1024 | float64 | 1.85× |
np.linalg.inv | 64 | float32 | 1.10× |
np.linalg.inv | 64 | float64 | 1.11× |
np.linalg.inv | 128 | float32 | 1.29× |
np.linalg.inv | 128 | float64 | 1.26× |
np.linalg.inv | 256 | float32 | 1.63× |
np.linalg.inv | 256 | float64 | 1.62× |
np.linalg.inv | 512 | float32 | 1.56× |
np.linalg.inv | 512 | float64 | 1.64× |
np.linalg.inv | 1024 | float32 | 1.66× |
np.linalg.inv | 1024 | float64 | 1.65× |
np.linalg.eigh | 64 | float32 | 1.17× |
np.linalg.eigh | 64 | float64 | 1.16× |
np.linalg.eigh | 128 | float32 | 1.25× |
np.linalg.eigh | 128 | float64 | 1.23× |
np.linalg.eigh | 256 | float32 | 1.46× |
np.linalg.eigh | 256 | float64 | 1.42× |
np.linalg.eigh | 512 | float32 | 1.48× |
np.linalg.eigh | 512 | float64 | 1.50× |
np.linalg.eigh | 1024 | float32 | 1.60× |
np.linalg.eigh | 1024 | float64 | 1.58× |
x @ A | 64 | float32 | 1.68× |
x @ A | 64 | float64 | 1.26× |
x @ A | 128 | float32 | 3.15× |
x @ A | 128 | float64 | 1.93× |
x @ A | 256 | float32 | 4.04× |
x @ A | 256 | float64 | 2.21× |
x @ A | 512 | float32 | 5.12× |
x @ A | 512 | float64 | 7.23× |
x @ A | 1024 | float32 | 14.86× |
x @ A | 1024 | float64 | 7.53× |
A @ x | 64 | float32 | 0.88× |
A @ x | 64 | float64 | 0.94× |
A @ x | 128 | float32 | 0.97× |
A @ x | 128 | float64 | 0.96× |
A @ x | 256 | float32 | 0.99× |
A @ x | 256 | float64 | 1.00× |
A @ x | 512 | float32 | 1.03× |
A @ x | 512 | float64 | 1.05× |
A @ x | 1024 | float32 | 1.02× |
A @ x | 1024 | float64 | 1.06× |
Appendix C: OpenBLAS 0.3.35 (SIMD128) versus OpenBLAS 0.3.34
WASM 0.3.34 に対するポータブル OpenBLAS 0.3.35 (SIMD128) の比較。
Speedup versus input size
[画像: Speedup versus n for OpenBLAS 0.3.35 against 0.3.34]
| API | n | dtype | 0.3.35 vs 0.3.34 |
|---|---|---|---|
np.matmul | 64 | float32 | 1.55× |
np.matmul | 64 | float64 | 1.37× |
np.matmul | 128 | float32 | 1.86× |
np.matmul | 128 | float64 | 1.47× |
np.matmul | 256 | float32 | 1.86× |
np.matmul | 256 | float64 | 1.41× |
np.matmul | 512 | float32 | 1.79× |
np.matmul | 512 | float64 | 1.41× |
np.matmul | 1024 | float32 | 1.79× |
np.matmul | 1024 | float64 | 1.41× |
np.linalg.solve | 64 | float32 | 1.24× |
np.linalg.solve | 64 | float64 | 1.27× |
np.linalg.solve | 128 | float32 | 1.35× |
np.linalg.solve | 128 | float64 | 1.29× |
np.linalg.solve | 256 | float32 | 1.34× |
np.linalg.solve | 256 | float64 | 1.37× |
np.linalg.solve | 512 | float32 | 1.36× |
np.linalg.solve | 512 | float64 | 1.38× |
np.linalg.solve | 1024 | float32 | 1.38× |
np.linalg.solve | 1024 | float64 | 1.38× |
np.linalg.cholesky | 64 | float32 | 1.33× |
np.linalg.cholesky | 64 | float64 | 1.37× |
np.linalg.cholesky | 128 | float32 | 1.22× |
np.linalg.cholesky | 128 | float64 | 1.20× |
np.linalg.cholesky | 256 | float32 | 1.25× |
np.linalg.cholesky | 256 | float64 | 1.24× |
np.linalg.cholesky | 512 | float32 | 1.25× |
np.linalg.cholesky | 512 | float64 | 1.28× |
np.linalg.cholesky | 1024 | float32 | 1.32× |
np.linalg.cholesky | 1024 | float64 | 1.37× |
np.linalg.inv | 64 | float32 | 1.22× |
np.linalg.inv | 64 | float64 | 1.23× |
np.linalg.inv | 128 | float32 | 1.27× |
np.linalg.inv | 128 | float64 | 1.28× |
np.linalg.inv | 256 | float32 | 1.28× |
np.linalg.inv | 256 | float64 | 1.29× |
np.linalg.inv | 512 | float32 | 1.37× |
np.linalg.inv | 512 | float64 | 1.37× |
np.linalg.inv | 1024 | float32 | 1.33× |
np.linalg.inv | 1024 | float64 | 1.41× |
np.linalg.eigh | 64 | float32 | 1.36× |
np.linalg.eigh | 64 | float64 | 1.33× |
np.linalg.eigh | 128 | float32 | 1.48× |
np.linalg.eigh | 128 | float64 | 1.46× |
np.linalg.eigh | 256 | float32 | 1.55× |
np.linalg.eigh | 256 | float64 | 1.55× |
np.linalg.eigh | 512 | float32 | 1.62× |
np.linalg.eigh | 512 | float64 | 1.64× |
np.linalg.eigh | 1024 | float32 | 1.64× |
np.linalg.eigh | 1024 | float64 | 1.66× |
x @ A | 64 | float32 | 1.18× |
x @ A | 64 | float64 | 1.38× |
x @ A | 128 | float32 | 1.47× |
x @ A | 128 | float64 | 1.80× |
x @ A | 256 | float32 | 1.93× |
x @ A | 256 | float64 | 2.14× |
x @ A | 512 | float32 | 1.95× |
x @ A | 512 | float64 | 1.82× |
x @ A | 1024 | float32 | 1.73× |
x @ A | 1024 | float64 | 1.98× |
A @ x | 64 | float32 | 1.58× |
A @ x | 64 | float64 | 1.67× |
A @ x | 128 | float32 | 3.78× |
A @ x | 128 | float64 | 2.81× |
A @ x | 256 | float32 | 6.83× |
A @ x | 256 | float64 | 3.74× |
A @ x | 512 | float32 | 6.80× |
A @ x | 512 | float64 | 3.37× |
A @ x | 1024 | float32 | 5.52× |
A @ x | 1024 | float64 | 3.60× |
Appendix D: OpenBLAS 0.3.35 (Relaxed SIMD) versus OpenBLAS 0.3.35 (SIMD128)
Chromium 153 上のポータブル simd128 0.3.35 デフォルトに対する、オプションの relaxed_simd OpenBLAS (WASM_RELAXED_SIMD=1、Relaxed SIMD) の比較。
Speedup versus input size
[画像: Speedup versus n for OpenBLAS 0.3.35 Relaxed SIMD against SIMD128]
| API | n | dtype | Relaxed SIMD vs SIMD128 |
|---|---|---|---|
np.matmul | 64 | float32 | 1.33× |
np.matmul | 64 | float64 | 1.41× |
np.matmul | 128 | float32 | 1.18× |
np.matmul | 128 | float64 | 1.43× |
np.matmul | 256 | float32 | 1.18× |
np.matmul | 256 | float64 | 1.43× |
np.matmul | 512 | float32 | 1.18× |
np.matmul | 512 | float64 | 1.43× |
np.matmul | 1024 | float32 | 1.17× |
np.matmul | 1024 | float64 | 1.43× |
np.linalg.solve | 64 | float32 | 1.06× |
np.linalg.solve | 64 | float64 | 1.07× |
np.linalg.solve | 128 | float32 | 1.16× |
np.linalg.solve | 128 | float64 | 1.19× |
np.linalg.solve | 256 | float32 | 1.16× |
np.linalg.solve | 256 | float64 | 1.26× |
np.linalg.solve | 512 | float32 | 1.15× |
np.linalg.solve | 512 | float64 | 1.25× |
np.linalg.solve | 1024 | float32 | 1.25× |
np.linalg.solve | 1024 | float64 | 1.31× |
np.linalg.cholesky | 64 | float32 | 0.98× |
np.linalg.cholesky | 64 | float64 | 0.98× |
np.linalg.cholesky | 128 | float32 | 1.09× |
np.linalg.cholesky | 128 | float64 | 1.14× |
np.linalg.cholesky | 256 | float32 | 1.05× |
np.linalg.cholesky | 256 | float64 | 1.18× |
np.linalg.cholesky | 512 | float32 | 1.18× |
np.linalg.cholesky | 512 | float64 | 1.16× |
np.linalg.cholesky | 1024 | float32 | 1.03× |
np.linalg.cholesky | 1024 | float64 | 1.22× |
np.linalg.inv | 64 | float32 | 1.10× |
np.linalg.inv | 64 | float64 | 1.15× |
np.linalg.inv | 128 | float32 | 1.21× |
np.linalg.inv | 128 | float64 | 1.24× |
np.linalg.inv | 256 | float32 | 1.29× |
np.linalg.inv | 256 | float64 | 1.28× |
np.linalg.inv | 512 | float32 | 1.14× |
np.linalg.inv | 512 | float64 | 1.28× |
np.linalg.inv | 1024 | float32 | 1.18× |
np.linalg.inv | 1024 | float64 | 1.32× |
np.linalg.eigh | 64 | float32 | 1.02× |
np.linalg.eigh | 64 | float64 | 1.06× |
np.linalg.eigh | 128 | float32 | 1.10× |
np.linalg.eigh | 128 | float64 | 1.13× |
np.linalg.eigh | 256 | float32 | 1.05× |
np.linalg.eigh | 256 | float64 | 1.17× |
np.linalg.eigh | 512 | float32 | 1.21× |
np.linalg.eigh | 512 | float64 | 1.20× |
np.linalg.eigh | 1024 | float32 | 1.14× |
np.linalg.eigh | 1024 | float64 | 1.22× |
x @ A | 64 | float32 | 1.05× |
x @ A | 64 | float64 | 1.00× |
x @ A | 128 | float32 | 1.04× |
x @ A | 128 | float64 | 1.03× |
x @ A | 256 | float32 | 1.07× |
x @ A | 256 | float64 | 1.07× |
x @ A | 512 | float32 | 1.13× |
x @ A | 512 | float64 | 1.12× |
x @ A | 1024 | float32 | 1.18× |
x @ A | 1024 | float64 | 1.03× |
A @ x | 64 | float32 | 1.26× |
A @ x | 64 | float64 | 1.13× |
A @ x | 128 | float32 | 1.10× |
A @ x | 128 | float64 | 1.04× |
A @ x | 256 | float32 | 1.23× |
A @ x | 256 | float64 | 1.06× |
A @ x | 512 | float32 | 1.11× |
A @ x | 512 | float64 | 0.99× |
A @ x | 1024 | float32 | 1.29× |
A @ x | 1024 | float64 | 1.10× |
Appendix E: All changes: OpenBLAS 0.3.35 (Relaxed SIMD) versus no BLAS
no-BLAS Emscripten-forge NumPy ベースラインから OpenBLAS 0.3.35 (Relaxed SIMD) までのエンドツーエンドのスピードアップ(完全なパス:OpenBLAS のリンク、ポータブル 0.3.35 カーネル、次に Relaxed SIMD)。
Speedup versus input size
[画像: Speedup versus n for OpenBLAS 0.3.35 Relaxed SIMD against no BLAS]
| API | n | dtype | Relaxed SIMD vs no BLAS |
|---|---|---|---|
np.matmul | 64 | float32 | 9.35× |
np.matmul | 64 | float64 | 4.92× |
np.matmul | 128 | float32 | 15.10× |
np.matmul | 128 | float64 | 7.46× |
np.matmul | 256 | float32 | 16.85× |
np.matmul | 256 | float64 | 8.44× |
np.matmul | 512 | float32 | 25.49× |
np.matmul | 512 | float64 | 28.50× |
np.matmul | 1024 | float32 | 64.89× |
np.matmul | 1024 | float64 | 30.07× |
np.linalg.solve | 64 | float32 | 1.34× |
np.linalg.solve | 64 | float64 | 1.43× |
np.linalg.solve | 128 | float32 | 2.26× |
np.linalg.solve | 128 | float64 | 2.27× |
np.linalg.solve | 256 | float32 | 3.04× |
np.linalg.solve | 256 | float64 | 3.29× |
np.linalg.solve | 512 | float32 | 3.10× |
np.linalg.solve | 512 | float64 | 3.36× |
np.linalg.solve | 1024 | float32 | 3.64× |
np.linalg.solve | 1024 | float64 | 3.78× |
np.linalg.cholesky | 64 | float32 | 1.25× |
np.linalg.cholesky | 64 | float64 | 1.33× |
np.linalg.cholesky | 128 | float32 | 1.61× |
np.linalg.cholesky | 128 | float64 | 1.65× |
np.linalg.cholesky | 256 | float32 | 1.98× |
np.linalg.cholesky | 256 | float64 | 2.22× |
np.linalg.cholesky | 512 | float32 | 2.35× |
np.linalg.cholesky | 512 | float64 | 2.36× |
np.linalg.cholesky | 1024 | float32 | 2.54× |
np.linalg.cholesky | 1024 | float64 | 3.08× |
np.linalg.inv | 64 | float32 | 1.47× |
np.linalg.inv | 64 | float64 | 1.56× |
np.linalg.inv | 128 | float32 | 1.99× |
np.linalg.inv | 128 | float64 | 1.99× |
np.linalg.inv | 256 | float32 | 2.69× |
np.linalg.inv | 256 | float64 | 2.67× |
np.linalg.inv | 512 | float32 | 2.44× |
np.linalg.inv | 512 | float64 | 2.89× |
np.linalg.inv | 1024 | float32 | 2.63× |
np.linalg.inv | 1024 | float64 | 3.08× |
np.linalg.eigh | 64 | float32 | 1.63× |
np.linalg.eigh | 64 | float64 | 1.63× |
np.linalg.eigh | 128 | float32 | 2.04× |
np.linalg.eigh | 128 | float64 | 2.03× |
np.linalg.eigh | 256 | float32 | 2.37× |
np.linalg.eigh | 256 | float64 | 2.59× |
np.linalg.eigh | 512 | float32 | 2.90× |
np.linalg.eigh | 512 | float64 | 2.95× |
np.linalg.eigh | 1024 | float32 | 2.99× |
np.linalg.eigh | 1024 | float64 | 3.20× |
x @ A | 64 | float32 | 2.09× |
x @ A | 64 | float64 | 1.74× |
x @ A | 128 | float32 | 4.80× |
x @ A | 128 | float64 | 3.61× |
x @ A | 256 | float32 | 8.33× |
x @ A | 256 | float64 | 5.06× |
x @ A | 512 | float32 | 11.30× |
x @ A | 512 | float64 | 14.78× |
x @ A | 1024 | float32 | 30.22× |
x @ A | 1024 | float64 | 15.35× |
A @ x | 64 | float32 | 1.76× |
A @ x | 64 | float64 | 1.76× |
A @ x | 128 | float32 | 4.05× |
A @ x | 128 | float64 | 2.82× |
A @ x | 256 | float32 | 8.31× |
A @ x | 256 | float64 | 3.95× |
A @ x | 512 | float32 | 7.79× |
A @ x | 512 | float64 | 3.50× |
A @ x | 1024 | float32 | 7.31× |
A @ x | 1024 | float64 | 4.17× |
Appendix F: Full benchmark results (including linux-64)
OpenBLAS 0.3.34、OpenBLAS 0.3.35 (SIMD128)、オプションの 0.3.35 Relaxed SIMD ビルド、および同じマシン上でのシングルスレッド conda-forge linux-64 OpenBLAS 0.3.34 リファレンスの絶対スループットとスピードアップ。スピードアップは、ベースライン時間から対象時間の比率です(>1 は対象スタックがより高速であることを意味します)。
Geometric-mean speedups by API
| API | 0.3.34 vs no BLAS | 0.3.34 vs linux-64 | 0.3.35 vs 0.3.34 | 0.3.35 vs no BLAS | 0.3.35 vs linux-64 | Relaxed SIMD vs 0.3.35 | Relaxed SIMD vs no BLAS | Relaxed SIMD vs linux-64 |
|---|---|---|---|---|---|---|---|---|
np.matmul | 7.68× | 0.25× | 1.58× | 12.13× | 0.39× | 1.31× | 15.92× | 0.51× |
np.tensordot | 7.36× | 0.26× | 1.55× | 11.45× | 0.40× | 1.28× | 14.64× | 0.51× |
np.linalg.multi_dot | 7.48× | 0.25× | 1.59× | 11.87× | 0.40× | 1.30× | 15.41× | 0.51× |
np.linalg.matrix_power | 7.42× | 0.25× | 1.59× | 11.79× | 0.39× | 1.31× | 15.40× | 0.51× |
x @ A | 3.70× | 0.38× | 1.71× | 6.33× | 0.65× | 1.07× | 6.78× | 0.70× |
np.linalg.solve | 1.65× | 0.45× | 1.33× | 2.20× | 0.61× | 1.18× | 2.60× | 0.72× |
np.linalg.inv | 1.43× | 0.42× | 1.30× | 1.87× | 0.55× | 1.22× | 2.28× | 0.66× |
np.linalg.cholesky | 1.40× | 0.46× | 1.28× | 1.79× | 0.59× | 1.10× | 1.96× | 0.65× |
np.linalg.qr | 1.33× | 0.39× | 1.60× | 2.12× | 0.63× | 1.13× | 2.40× | 0.71× |
np.linalg.eigh | 1.38× | 0.43× | 1.52× | 2.10× | 0.65× | 1.13× | 2.37× | 0.73× |
np.linalg.lstsq | 1.24× | 0.47× | 1.66× | 2.06× | 0.77× | 1.06× | 2.19× | 0.82× |
np.linalg.svd | 1.08× | 0.46× | 1.66× | 1.80× | 0.77× | 1.03× | 1.86× | 0.80× |
A @ x | 0.99× | 0.18× | 3.56× | 3.52× | 0.65× | 1.13× | 3.97× | 0.73× |
np.dot | 0.97× | 0.26× | 3.21× | 3.11× | 0.83× | 1.01× | 3.15× | 0.84× |
np.matmul across sizes (GFLOPS)
| n | dtype | no BLAS | OpenBLAS 0.3.34 | OpenBLAS 0.3.35 | 0.3.35 Relaxed SIMD | linux-64 | 0.3.34 vs no BLAS | 0.3.35 vs 0.3.34 | 0.3.35 vs no BLAS | Relaxed SIMD vs 0.3.35 | Relaxed SIMD vs no BLAS | 0.3.34 vs linux-64 | 0.3.35 vs linux-64 | Relaxed SIMD vs linux-64 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 64 | float32 | 4.74 | 21.5 | 33.3 | 44.3 | 84.7 | 4.54× | 1.55× | 7.03× | 1.33× | 9.35× | 0.25× | 0.39× | 0.52× |
| 64 | float64 | 5.01 | 12.8 | 17.5 | 24.7 | 44.1 | 2.56× | 1.37× | 3.50× | 1.41× | 4.92× | 0.29× | 0.40× | 0.56× |
| 128 | float32 | 3.57 | 24.5 | 45.6 | 53.9 | 110.4 | 6.88× | 1.86× | 12.78× | 1.18× | 15.10× | 0.22× | 0.41× | 0.49× |
| 128 | float64 | 3.76 | 13.4 | 19.7 | 28.0 | 52.9 | 3.56× | 1.47× | 5.24× | 1.43× | 7.46× | 0.25× | 0.37× | 0.53× |
| 256 | float32 | 3.38 | 25.9 | 48.0 | 56.9 | 120.5 | 7.67× | 1.86× | 14.23× | 1.18× | 16.85× | 0.21× | 0.40× | 0.47× |
| 256 | float64 | 3.42 | 14.3 | 20.2 | 28.9 | 55.5 | 4.17× | 1.41× | 5.90× | 1.43× | 8.44× | 0.26× | 0.36× | 0.52× |
| 512 | float32 | 2.29 | 27.5 | 49.4 | 58.3 | 118.6 | 12.04× | 1.79× | 21.61× | 1.18× | 25.49× | 0.23× | 0.42× | 0.49× |
| 512 | float64 | 1.02 | 14.5 | 20.4 | 29.2 | 56.8 | 14.16× | 1.41× | 19.95× | 1.43× | 28.50× | 0.26× | 0.36× | 0.51× |
| 1024 | float32 | 0.90 | 28.0 | 50.0 | 58.7 | 120.4 | 30.92× | 1.79× | 55.30× | 1.17× | 64.89× | 0.23× | 0.42× | 0.49× |
| 1024 | float64 | 0.98 | 14.6 | 20.5 | 29.4 | 57.2 | 14.90× | 1.41× | 20.99× | 1.43× | 30.07× | 0.25× | 0.36× | 0.51× |
np.linalg at n = 1024 (median time, ms)
| call | dtype | no BLAS | OpenBLAS 0.3.34 | OpenBLAS 0.3.35 | 0.3.35 Relaxed SIMD | linux-64 | 0.3.34 vs no BLAS | 0.3.35 vs 0.3.34 | 0.3.35 vs no BLAS | Relaxed SIMD vs 0.3.35 | Relaxed SIMD vs no BLAS | 0.3.34 vs linux-64 | 0.3.35 vs linux-64 | Relaxed SIMD vs linux-64 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
np.linalg.solve | float32 | 122 | 58 | 42 | 34 | 21 | 2.11× | 1.38× | 2.91× | 1.25× | 3.64× | 0.36× | 0.49× | 0.61× |
np.linalg.solve | float64 | 118 | 56 | 41 | 31 | 21 | 2.10× | 1.38× | 2.90× | 1.31× | 3.78× | 0.37× | 0.51× | 0.67× |
np.linalg.cholesky | float32 | 68 | 36 | 27 | 27 | 16 | 1.88× | 1.32× | 2.48× | 1.03× | 2.54× | 0.46× | 0.60× | 0.62× |
np.linalg.cholesky | float64 | 66 | 36 | 26 | 22 | 17 | 1.85× | 1.37× | 2.52× | 1.22× | 3.08× | 0.47× | 0.64× | 0.78× |
np.linalg.qr | float32 | 552 | 272 | 183 | 142 | 96 | 2.03× | 1.49× | 3.01× | 1.29× | 3.89× | 0.35× | 0.52× | 0.68× |
np.linalg.qr | float64 | 549 | 259 | 184 | 141 | 96 | 2.12× | 1.41× | 2.98× | 1.31× | 3.89× | 0.37× | 0.52× | 0.68× |
np.linalg.inv | float32 | 357 | 215 | 161 | 136 | 70 | 1.66× | 1.33× | 2.22× | 1.18× | 2.63× | 0.33× | 0.44× | 0.52× |
np.linalg.inv | float64 | 364 | 221 | 157 | 118 | 72 | 1.65× | 1.41× | 2.33× | 1.32× | 3.08× | 0.33× | 0.46× | 0.61× |
np.linalg.eigh | float32 | 793 | 497 | 303 | 265 | 171 | 1.60× | 1.64× | 2.62× | 1.14× | 2.99× | 0.34× | 0.57× | 0.65× |
np.linalg.eigh | float64 | 778 | 491 | 297 | 243 | 165 | 1.58× | 1.66× | 2.62× | 1.22× | 3.20× | 0.34× | 0.56× | 0.68× |
np.linalg.svd | float32 | 569 | 471 | 248 | 253 | 205 | 1.21× | 1.90× | 2.29× | 0.98× | 2.25× | 0.44× | 0.83× | 0.81× |
np.linalg.svd | float64 | 571 | 487 | 258 | 235 | 215 | 1.17× | 1.89× | 2.21× | 1.10× | 2.43× | 0.44× | 0.83× | 0.92× |
Matrix–vector @ across sizes (GFLOPS)
| call | n | dtype | no BLAS | OpenBLAS 0.3.34 | OpenBLAS 0.3.35 | 0.3.35 Relaxed SIMD | linux-64 | 0.3.34 vs no BLAS | 0.3.35 vs 0.3.34 | 0.3.35 vs no BLAS | Relaxed SIMD vs 0.3.35 | Relaxed SIMD vs no BLAS | 0.3.34 vs linux-64 | 0.3.35 vs linux-64 | Relaxed SIMD vs linux-64 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
x @ A | 64 | float32 | 2.94 | 4.94 | 5.84 | 6.14 | 12.0 | 1.68× | 1.18× | 1.99× | 1.05× | 2.09× | 0.41× | 0.49× | 0.51× |
x @ A | 64 | float64 | 2.95 | 3.72 | 5.12 | 5.12 | 9.57 | 1.26× | 1.38× | 1.74× | 1.00× | 1.74× | 0.39× | 0.54× | 0.54× |
x @ A | 128 | float32 | 3.12 | 9.81 | 14.4 | 15.0 | 27.5 | 3.15× | 1.47× | 4.62× | 1.04× | 4.80× | 0.36× | 0.52× | 0.54× |
x @ A | 128 | float64 | 3.03 | 5.85 | 10.6 | 10.9 | 18.6 | 1.93× | 1.80× | 3.49× | 1.03× | 3.61× | 0.31× | 0.57× | 0.59× |
x @ A | 256 | float32 | 3.27 | 13.2 | 25.6 | 27.2 | 42.0 | 4.04× | 1.93× | 7.81× | 1.07× | 8.33× | 0.31× | 0.61× | 0.65× |
x @ A | 256 | float64 | 3.23 | 7.13 | 15.3 | 16.3 | 25.1 | 2.21× | 2.14× | 4.73× | 1.07× | 5.06× | 0.28× | 0.61× | 0.65× |
x @ A | 512 | float32 | 2.69 | 13.8 | 26.9 | 30.4 | 43.2 | 5.12× | 1.95× | 10.00× | 1.13× | 11.30× | 0.32× | 0.62× | 0.70× |
x @ A | 512 | float64 | 1.02 | 7.35 | 13.4 | 15.0 | 14.1 | 7.23× | 1.82× | 13.17× | 1.12× | 14.78× | 0.52× | 0.95× | 1.06× |
x @ A | 1024 | float32 | 0.95 | 14.2 | 24.4 | 28.8 | 30.2 | 14.86× | 1.73× | 25.68× | 1.18× | 30.22× | 0.47× | 0.81× | 0.95× |
x @ A | 1024 | float64 | 1.00 | 7.50 | 14.9 | 15.3 | 14.3 | 7.53× | 1.98× | 14.94× | 1.03× | 15.35× | 0.53× | 1.04× | 1.07× |
A @ x | 64 | float32 | 2.91 | 2.56 | 4.06 | 5.12 | 10.7 | 0.88× | 1.58× | 1.39× | 1.26× | 1.76× | 0.24× | 0.38× | 0.48× |
A @ x | 64 | float64 | 2.89 | 2.72 | 4.53 | 5.10 | 9.79 | 0.94× | 1.67× | 1.57× | 1.13× | 1.76× | 0.28× | 0.46× | 0.52× |
A @ x | 128 | float32 | 4.05 | 3.94 | 14.9 | 16.4 | 24.2 | 0.97× | 3.78× | 3.67× | 1.10× | 4.05× | 0.16× | 0.61× | 0.68× |
A @ x | 128 | float64 | 4.01 | 3.85 | 10.8 | 11.3 | 19.9 | 0.96× | 2.81× | 2.70× | 1.04× | 2.82× | 0.19× | 0.55× | 0.57× |
A @ x | 256 | float32 | 4.26 | 4.22 | 28.8 | 35.4 | 38.5 | 0.99× | 6.83× | 6.77× | 1.23× | 8.31× | 0.11× | 0.75× | 0.92× |
A @ x | 256 | float64 | 4.19 | 4.20 | 15.7 | 16.6 | 28.6 | 1.00× | 3.74× | 3.75× | 1.06× | 3.95× | 0.15× | 0.55× | 0.58× |
A @ x | 512 | float32 | 4.12 | 4.26 | 28.9 | 32.1 | 42.6 | 1.03× | 6.80× | 7.01× | 1.11× | 7.79× | 0.10× | 0.68× | 0.75× |
A @ x | 512 | float64 | 4.07 | 4.28 | 14.4 | 14.2 | 15.2 | 1.05× | 3.37× | 3.54× | 0.99× | 3.50× | 0.28× | 0.95× | 0.94× |
A @ x | 1024 | float32 | 4.08 | 4.18 | 23.1 | 29.8 | 29.5 | 1.02× | 5.52× | 5.66× | 1.29× | 7.31× | 0.14× | 0.78× | 1.01× |
A @ x | 1024 | float64 | 4.02 | 4.25 | 15.3 | 16.7 | 14.9 | 1.06× | 3.60× | 3.80× | 1.10× | 4.17× | 0.28× | 1.02× | 1.12× |