# CUDA による GPU 計算

速さへの第 3 の道は **GPU**（グラフィックス処理装置）です。適した種類の仕事 — 巨大な配列に同じ演算を適用する —
では、1 つの GPU が多数の CPU コアを凌げます。qc-rs は積分と Fock 構築に**任意の** NVIDIA-GPU 経路を持ちます。
本章は GPU とは何か、GPU 対応で qc-rs をどうビルドするか、そして今日何が GPU で走るかを説明します。

## GPU とは、いつ効くか

CPU は汎用で分岐の多い仕事に最適化された少数の強力なコアを持ちます。**GPU** は、*同じ*算術を*多数の*データ要素に
一度に行う（大規模データ並列）ことに最適化された**数千の小さなコア**を持ちます。これは量子化学の最も密な内側
ループ — 二電子積分と J/K 構築の行列積 — にぴったりで、巨大な配列に同じ式を適用します。

**CUDA** は NVIDIA の GPU 向けプログラミング基盤で、qc-rs の GPU 経路はその上に（ベンダー提供の GPU 最適化積分
カーネルで）築かれています。GPU の throughput がデータをカードへ送るコストを上回る**大きな**系で最も効きます;
小さな分子はそのオーバーヘッドで、GPU の方が CPU より*遅い*こともあります。

## `cuda` ビルド（オプトイン;既定 OFF）

GPU 経路は**既定で OFF** — 通常の qc-rs ビルドは CUDA 依存を一切持たず、NVIDIA から何もリンクせず、
`qc.GPU_ENABLED == False` を報告します。[ビルド時](../00-intro/installation-and-make-setup.md)に **`cuda`**
機能を加えてオプトインします（CUDA ツールキットと NVIDIA GPU が必要）:

```bash
"${UV_PROJECT}/.venv/bin/maturin" develop --no-default-features \
  --features intel-mkl-system,xc-bundled,pcm,python-mpi-direct,hdf5,cuda
```

するとセンチネルが切り替わります:

```python
import qc
qc.GPU_ENABLED       # cuda ビルド + 可視の GPU で True;それ以外で False
```

**非 `cuda`** ビルドで GPU 戦略を要求すると、qc-rs は静かに CPU で走る代わりに明確なメッセージで失敗します:

```text
jk: eri="4c-cuda" requires qc-rs built with the `cuda` feature and a CUDA GPU; rebuild with …
```

:::{warning} `cuda` ビルドは開発専用 / 再配置不可
CUDA 拡張はベンダー `.so` をビルドツリーへの rpath でリンクし、実行時に `libcudart` を必要とするので、
`cargo clean`・移動したインストール・コピーした wheel は壊れます。`cuda` ビルドはビルドしたマシンにローカルな
ものと扱ってください。
:::

## GPU を使う:`eri="4c-cuda"` とその仲間

GPU は**積分戦略**（`ints(eri=...)`、[次章](eri-jk-strategies.md)）で選ぶので、ワークフローの残りは変わりません:

```python
import qc
m = qc.chk.new(atom="...", ao="cc-pvdz")
m = qc.ints(m, eri="4c-cuda").scf(ref="r").run()      # J/K を GPU で行う RHF
print(qc.GPU_ENABLED, m.scf.energy)
```

GPU 戦略:

| `eri=` | GPU で走るもの |
|---|---|
| `4c-cuda` | 従来型 4 中心 J/K を GPU で |
| `ri-cuda` | 密度フィッティング（RI）J/K、デバイス常駐 factor + cuBLAS |
| `ri-recomp-cuda` | セミダイレクト RI-JK（毎サイクル 3 中心を GPU で再計算） |
| `ri-ram-cuda` | ホスト常駐 RI factor を GPU の cuBLAS J/K へストリーム |

GPU SCF は **GPU KS-DFT**（交換相関構築がカードで走る、`xc=` つきの `eri="4c-cuda"`）と **GPU PCM** 溶媒和も
無料で得ます（`pcm={..., device="auto"}` は SCF が既に GPU なら GPU を使う）。

## 今日動くもの — まだのもの

GPU 経路はその中核について**正確性が完全**で、CPU/PySCF のエネルギーに一致します:

- **J/K** — RHF / UHF / ROHF、spherical **と** Cartesian、g 関数まで（l ≤ 4）、スクリーニングと範囲分離
  ハイブリッドの長距離項つき。
- **KS-DFT XC** — RKS / UKS / ROKS、LDA / GGA / meta-GGA とハイブリッド全般（libxc は CPU で走る）。
- **RI-JK**（`ri-cuda`）— 密度フィッティング J/K、最適化済み（32 水のフラッグシップで参照 gpu4pyscf を上回る）、
  範囲分離ハイブリッド含む。
- **PCM** — GPU 反応場結合。

**まだ GPU にないもの**:RI-**MP2**（GPU RI 経路は J/K のみ）、低精度（fp32/混合）の高速経路、角運動量 ≥ 5
（h 関数）、KS-DFT **勾配**。それらには CPU 経路を使います。GPU の作業は完全な性能チューニングというより正確性が
完全なので、万能アクセラレータではなく高速な J/K/XC エンジンと扱ってください。

## 総合例（概念 — `cuda` ビルド + GPU が必要）

```python
import qc
# cuda ビルドでないマシンでは、これは明確な ValueError を投げます（検証済み）:
m = qc.chk.new(atom="O 0 0 0.117; H 0 0.757 -0.469; H 0 -0.757 -0.469",
               ao="cc-pvdz", unit="angstrom")
try:
    m.ints(eri="4c-cuda").scf(ref="r").run()
except ValueError as e:
    print("GPU not available:", str(e)[:60])   # ... requires the `cuda` feature and a CUDA GPU
```

:::{exercise}
:label: ex-gpu

1. 確実に NVIDIA GPU があるマシンで `qc.GPU_ENABLED` が `False` です。考えられる理由を 2 つ挙げなさい。
2. *小さな*分子と大きな GPU があり、`4c-cuda` が CPU より*遅い*です。バグですか。説明しなさい。
3. GPU 加速の **RI-MP2** 相関エネルギーが必要です。qc-rs は今日何を対応し、実務的な選択肢は何ですか。
:::

:::{solution} ex-gpu
:class: dropdown

1. (a) qc-rs が **`cuda` 機能なしでビルド**された（既定ビルドに GPU 経路はない）、または (b) `cuda` はあるが
   実行時に GPU が*可視*でない（ドライバ/`libcudart`/`CUDA_VISIBLE_DEVICES` の問題）。`--features …,cuda` で
   再ビルドし実行時 GPU を確認。
2. **バグではありません。** 小さな分子では GPU へのデータ転送コストが throughput の利点を上回ります;GPU は
   **大きな**系で勝ちます。小さなジョブには CPU を使います。
3. GPU RI-JK（`ri-cuda`）は動きますが、**GPU RI-MP2 は未実装**です。SCF は好みで GPU で走らせてよいですが、MP2
   相関は **CPU**（`lct(method="mp2")`）で計算します。それが対応された経路です。
:::

これで 3 つの並列レベルすべてに出会いました。最終章は、それらすべてに触れる 1 つの軸 — [ERI / J-K 戦略](eri-jk-strategies.md)
— でそれらを結びつけます。
