Mímisbrunnr知恵の泉

← MLOps 一覧

🎓 レベル:標準 | 重要度:A(必須)

📎 前提:オンライン推論サービング | 関連:ハードウェアとスケーリング

要点(BLUF)

1. 2つの指標

指標定義改善=
レイテンシ1リクエストの応答時間小さく
スループット単位時間あたり処理件数(QPS)大きく
テールレイテンシp95/p99(遅い方の裾)小さく(最重要)

オンライン(デプロイパターン(バッチ・オンライン・ストリーミング))は低レイテンシ最優先、バッチは高スループット最優先。同じモデルでも、何を最適化するかで設計が変わります。

2. なぜバッチングがトレードオフを生むのか

GPU や BLAS は「たくさんをまとめて計算する」ほど1件あたりが安くなります(並列性・固定オーバーヘッドの償却)。だからリクエストを束ねればスループットは上がる。一方、束ねるには「ある程度たまるまで待つ」必要があり、最初に来たリクエストは待ち時間(遅延)が増えます

flowchart LR
  R1["req到着"] --> Q["バッチキュー(少し待って束ねる)"]
  R2["req到着"] --> Q
  R3["req到着"] --> Q
  Q -->|"バッチで1回推論"| M["モデル"]
  M --> O["まとめて応答(単価減・個別遅延増)"]

3. 動く最小例:バッチサイズで遅延と単価がどう動くか

バッチサイズを変えて推論し、**1件あたりの処理時間(=単価の逆数)**がどう下がるかを実測します。

import time
import numpy as np
from sklearn.ensemble import RandomForestClassifier

rng = np.random.default_rng(0)
Xtr = rng.normal(0, 1, (2000, 20))
ytr = (Xtr[:, 0] + Xtr[:, 1] > 0).astype(int)
model = RandomForestClassifier(n_estimators=100, random_state=0).fit(Xtr, ytr)

pool = rng.normal(0, 1, (4096, 20))

print(f"{'batch':>6} | {'総時間ms':>8} | {'1件あたりms':>10} | {'相対スループット':>14}")
base_per = None
for bs in [1, 8, 32, 128, 512]:
    # 同じ4096件を、バッチサイズ bs ずつ処理
    t0 = time.perf_counter()
    for i in range(0, len(pool), bs):
        _ = model.predict(pool[i:i+bs])
    total_ms = (time.perf_counter() - t0) * 1000
    per_item = total_ms / len(pool)
    if base_per is None:
        base_per = per_item
    print(f"{bs:>6} | {total_ms:>8.1f} | {per_item:>10.4f} | {base_per/per_item:>13.1f}x")

出力例(数値は環境依存・傾向が重要):

 batch | 総時間ms | 1件あたりms | 相対スループット
     1 |  12129.3 |     2.9613 |           1.0x
     8 |   1563.9 |     0.3818 |           7.8x
    32 |    392.3 |     0.0958 |          30.9x
   128 |    133.9 |     0.0327 |          90.6x
   512 |     46.0 |     0.0112 |         263.4x

出力の意味:バッチサイズを上げるほど1件あたりの時間が下がり(スループットが上がり)ます。ここでは RandomForest の predict 呼び出し1回あたりの固定オーバーヘッドが大きいため、batch=1(1件ずつ呼ぶ)が極端に遅く、batch=512 では約263倍の単価改善になりました。ただし限界収穫は逓減します:batch を 8 倍にした 1→8 では 7.8 倍ですが、その後は batch を 4 倍にするごとに約 3 倍ずつ(32→128→512)と、追加するバッチ幅に対する伸びは鈍ります。そして大きいバッチほど「束ねる待ち時間」で個々の遅延が増えるので、許容遅延(SLO)の範囲で十分な改善が得られるバッチサイズを選ぶのが実務です。単価のために遅延を無制限に犠牲にはできません。

4. 運用の勘所

なぜそうするのか

レイテンシとスループットを区別するのは、最適化の方向が問題によって正反対だからです。ユーザーが待つオンライン推論では遅延が王様、大量データを夜次で捌くバッチでは単価が王様。バッチングという同じ道具が、一方では味方(単価減)でもう一方では敵(遅延増)になります。だから「何を守るべきか(遅延 SLO)」を先に決め、その制約の中でもう一方(スループット)を最大化する、という順序が要ります。テールを見るのは、平均が嘘をつくからです——平均が速くても、p99 の遅さが実際のユーザー不満を生みます。

⚠️ よくある落とし穴

対応 lab

関連ノート