PyTorch混合精度は常に速い?RTX 5070 TiでFP32・TF32・FP16・BF16を各5回比較

実験日: 2026-08-24 / PyTorch 2.13.0+cu130
GPU: NVIDIA GeForce RTX 5070 Ti 16 GB / WSL2 Ubuntu 26.04

「GPU学習ではFP16を使えば速くなる」と覚えていないでしょうか。方向としては正しいのですが、どんなモデルでも速くなるわけではありません

設定をONにするだけで速くなれば簡単ですが、今回はそうなりませんでした。

今回はFashionMNISTの小型CNNを、FP32、TF32、FP16 AMP、BF16 AMPの4条件で学習しました。各条件を1回だけ測るのではなく5回ずつ、合計20回学習し、実行順も入れ替えています。

結果は、FP16 AMPがFP32より約8%遅くなりました。一方、PyTorchが予約したGPUメモリは170 MiBから138 MiBへ減りました。「混合精度を有効にした」という設定名ではなく、モデルの大きさ、GPUを埋める仕事量、測定範囲を見て判断する必要があります。

AMPが少し遅かった

FP32(TF32無効)
3.896秒(3.664~3.959秒)/ test 86.00% / allocated 117.3 MiB / reserved 170.0 MiB

TF32
3.932秒(3.607~3.961秒)/ FP32比0.9%遅い / test 85.87% / allocated 117.3 MiB / reserved 170.0 MiB

FP16 AMP
4.207秒(3.911~4.290秒)/ FP32比8.0%遅い / test 85.96% / allocated 114.0 MiB / reserved 138.0 MiB

BF16 AMP
4.083秒(3.753~4.129秒)/ FP32比4.8%遅い / test 85.93% / allocated 114.0 MiB / reserved 138.0 MiB

  • TF32とFP32の時間範囲は大きく重なり、この実験では実質的な差を確認できませんでした。
  • FP16 AMPとBF16 AMPは速くなりませんでした。
  • AMPはpeak allocatedを約2.8%、peak reservedを約18.8%減らしました。
  • 精度差は最大0.13ポイントです。2 epoch・初期値1種類なので、優劣とは解釈しません。
RTX 5070 TiでFP32、TF32、FP16 AMP、BF16 AMPを各5回比較した学習時間、精度、GPUメモリ
RTX 5070 TiでFP32、TF32、FP16 AMP、BF16 AMPを各5回比較した学習時間、精度、GPUメモリ

4つの「精度」は同じ種類の設定ではない

最初に、用語を分けます。

形式 指数部 仮数部 主な特徴
FP32 8 bit 23 bit 広い範囲と比較的高い精度。通常のモデル・重みの基準
TF32 8 bit 10 bit FP32 Tensorを保存したまま、対応する行列積・畳み込み内部で使われる形式
FP16 5 bit 10 bit 狭い数値範囲。高速・省メモリだが小さい勾配のunderflowに注意
BF16 8 bit 7 bit FP32と同じ指数幅。精度は粗いが広い数値範囲を保つ

TF32はtorch.float32とは別の保存dtypeではありません。FP32の入力を受けた行列積や畳み込みについて、対応GPUが内部計算へTF32 Tensor Coreを使うことを許す設定です。入力の仮数を10 bit相当へ丸め、積和の累積はFP32で行います。

一方、AMP(Automatic Mixed Precision)は、全演算を一律に16 bitへ変える仕組みではありません。autocastが演算ごとの性質に応じてFP16/BF16またはFP32を選びます。PyTorch公式のAutomatic Mixed Precision recipeも、畳み込みや全結合は低精度が速い場合がある一方、reductionなどはFP32のdynamic rangeを必要とすると説明しています。

なぜFP16ではGradScalerを使うのか

FP16は指数部が5 bitしかなく、表現できる小さい値の範囲が狭い形式です。逆伝播で生じた微小な勾配が0へ丸められると、重みを更新できません。

そこで損失を一時的に大きくしてから逆伝播します。

scaler = torch.amp.GradScaler("cuda")

with torch.autocast(device_type="cuda", dtype=torch.float16):
    logits = model(images)
    loss = loss_function(logits, labels)

scaler.scale(loss).backward()
scaler.step(optimizer)
scaler.update()

scaler.step()は勾配を元のscaleへ戻し、infNaNがあればoptimizer更新をskipします。今回の最終scaleは32,768でした。

BF16はFP32と同じ8 bitの指数部を持ち、FP16よりunderflowしにくいため、今回はGradScalerを使いませんでした。ただし「BF16なら数値問題が絶対に起きない」という意味ではありません。

TF32は現行PyTorch APIで明示的に切り替える

PyTorch 2.9以降は精度をbackend別に指定できます。今回のPyTorch 2.13では次のようにしました。

def set_tf32(enabled: bool) -> None:
    precision = "tf32" if enabled else "ieee"
    torch.backends.cuda.matmul.fp32_precision = precision
    torch.backends.cudnn.fp32_precision = precision

古い記事で見かけるtorch.backends.cuda.matmul.allow_tf32torch.backends.cudnn.allow_tf32は非推奨化予定です。詳細はPyTorch公式のCUDA semantics: TensorFloat-32を参照してください。

実験条件:比較で変えたのは精度モードだけ

モデルは前回と同じ105,866パラメータのCNNです。

1×28×28
  → Conv(1→16, 3×3) → ReLU → MaxPool
  → Conv(16→32, 3×3) → ReLU → MaxPool
  → Flatten(1568)
  → Linear(1568→64) → ReLU → Linear(64→10)

比較条件は次のように統制しました。

  • FashionMNIST:学習60,000枚、テスト10,000枚
  • batch size:256
  • optimizer:Adam、learning rate 0.001
  • epoch:2
  • 同一の初期weightを各runへcopy
  • DataLoaderのshuffle seed:42
  • num_workers=0pin_memory=True
  • 各runの前に合成batchで3 train stepをwarm-upし、時間から除外
  • 時間にDataLoader、host→device転送、forward、backward、Adam更新を含める
  • test評価時間は除外
  • CUDAの非同期実行を考慮し、計測前後にtorch.cuda.synchronize()
  • 各モード5回。モード順をrepeatごとに巡回し、cold startや温度の順序biasを軽減

たとえば、最初のrepeatはFP32→TF32→FP16→BF16、次はTF32→FP16→BF16→FP32です。全条件を完全にrandom化したわけではありませんが、「FP32だけ必ず最初」という偏りは避けました。

1回の測定では結論が逆転した

最初の単発測定ではTF32がFP32より約6%速く見えました。しかし、現行APIへ直して各5回測ると、中央値はFP32が3.896秒、TF32が3.932秒です。しかも測定範囲は大きく重なっています。

これは「TF32が遅い」と証明したのではありません。今回の小型CNNではTF32による差より実行時間の揺らぎが大きく、速度向上を検出できなかったという結果です。

数秒のbenchmarkを1回だけ実行し、3桁の小数を示しても信頼性は高くなりません。少なくともwarm-up、同期、反復、分布または範囲の表示が必要です。

なぜTensor CoreがあるのにAMPは遅くなったのか

主因として考えられるのは、GPUを十分大きな仕事で満たせていないことです。

今回の入力は28×28の1 channel画像、モデルは約10.6万パラメータです。batch 256でも、1 stepの畳み込み・行列積はRTX 5070 Tiにとって小さい仕事です。その一方でAMPには次の固定費があります。

  • autocastが演算ごとのdtypeを選ぶ
  • 必要なTensorをcastする
  • FP16ではlossをscale / unscaleする
  • inf / NaNを確認してoptimizer更新を判断する
  • 小さなkernelを何度もlaunchする

低精度演算で短縮できる時間より、この固定費が大きければ全体は遅くなります。PyTorch公式recipeのtroubleshootingにも、networkがGPUを飽和させずCPU boundならAMPのspeedupが小さい場合があると記載されています。

これはGPU一般やAMP一般の否定ではありません。大きなCNN、Transformer、大きなbatchでは計算量とactivation memoryが増え、AMPの利点が表れやすくなります。batch sizeを変えて比較すると、どこで傾向が変わるかを切り分けられます。

allocatedとreservedは別のメモリ指標

PyTorchのCUDA caching allocatorには、少なくとも2つの見方があります。

  • max_memory_allocated():Tensorが実際に占有したメモリのpeak
  • max_memory_reserved():PyTorch allocatorが再利用のためGPUから確保した領域のpeak
torch.cuda.reset_peak_memory_stats()
# training
allocated = torch.cuda.max_memory_allocated() / 1024**2
reserved = torch.cuda.max_memory_reserved() / 1024**2

今回、allocatedは117.3→114.0 MiBと約2.8%減、reservedは170→138 MiBと約18.8%減でした。小型モデルなので絶対量はわずかですが、速度が上がらなくてもメモリ面の効果は確認できました。

nvidia-smiのprocess memoryとは一致しません。CUDA context、library、PyTorch外のallocationなど、見ている範囲が違うためです。定義はPyTorch公式のCUDA memory managementで確認できます。

精度が高かったモードを「勝ち」としない理由

test精度中央値は85.87~86.00%でした。FP32が0.13ポイント高いものの、この差だけで数値形式の優劣は判断できません。

今回は初期weightとshuffleを固定した比較であり、独立した複数seedによる汎化性能の比較ではないからです。またGPU kernelには非決定的な実装が含まれる場合があり、同条件のFP32でも85.93~86.02%の幅が出ました。

精度差を研究上の主張にするなら、たとえば5~10 seedで学習し、平均、標準偏差、信頼区間を示します。今回の研究質問は「同じ小型CNNでend-to-end時間とmemoryがどう変わるか」です。

再現方法

実験コードはexperiment_fashion_precision.pyです。WSL側のvenvで次のように実行しました。

cd /mnt/c/Users/user/Documents/Codex/2026-08-24/ko/work/ai_lab

/opt/ai-lab/venv/bin/python experiment_fashion_precision.py \
  --repeats 5 \
  --data-dir /opt/ai-lab/data \
  --output results/fashion_precision.json \
  --figure ../../outputs/fashion-precision-comparison.png

JSONには各20 runのepoch時間、loss、train/test精度、images/s、allocated/reserved、GradScalerの最終scaleを保存しています。グラフだけでなくraw dataを残すと、集計ミスを後から検証できます。

自分のモデルで判断するためのチェックリスト

  1. まずFP32を正しいbaselineとして動かす
  2. AMPを入れる前後でlossが有限か、精度が同程度に収束するか確認する
  3. forwardだけでなく、データ転送とoptimizer更新を含むend-to-endを測る
  4. CUDA処理の前後で同期する
  5. warm-upを除外し、複数回測って中央値と範囲を示す
  6. allocatedreservedを混同しない
  7. 小型モデルで遅くても、より大きなbatch/modelで再評価する
  8. TF32やAMPのdefault/APIはPyTorch versionごとに公式資料で確認する

まとめ

RTX 5070 Tiであっても、FashionMNISTの10.6万パラメータCNNではFP16/BF16 AMPは速くなりませんでした。FP16はFP32比8.0%、BF16は4.8%遅く、TF32は測定揺らぎを超える差を確認できませんでした。一方、AMPはPyTorch reserved memoryを約18.8%減らしました。

したがって「AMPをONにすれば速い」ではなく、モデル×batch×hardware×測定範囲の組み合わせで実測するのが結論です。batch sizeも独立変数に加えると、throughput、VRAM、OOMの関係まで判断できます。

関連記事