WADA-DEV(7) $ /ja/blog/home-server-random-freeze-part5-spill-performance/

NAME

home-server-random-freeze-part5-spill-performance — 自宅サーバがランダムフリーズした話 (5) ─ spill の崖、tok/s は「溢れた分」で決まる

SYNOPSIS

健全化した RAM でやっと解禁された実験。モデルを VRAM から溢れさせて CPU に落とすと速度はどう落ちるか。num_gpu スイープが描いたのは比例カーブではなく「崖」だった。tok/s ≈ RAM 帯域 ÷ 溢れ GB という法則と、70B が動く実用限界。

DESCRIPTION

これまで

前回、交換した RAM の無罪を多層検証で証明した。健全になった RAM は、副産物として一つの実験を解禁した。

壊れていた頃、モデルを VRAM から溢れさせてシステム RAM に落とす操作——spill——は封印されていた。故障が集中していたのがちょうど 50GB 帯で、spill でそこを踏むと即クラッシュだったからだ。今なら安全に測れる。ずっと気になっていた「spill するとどれくらい遅くなるのか」を、初めてデータで詰める。

spill とは何か

LLM 推論は、モデルの重みを GPU の VRAM に置いて回すのが基本だ。速いのは VRAM の帯域が桁違いに広いから。だがモデルが VRAM に収まらないと、あぶれた層をシステム RAM に置き、その分を CPU で計算する。これが spill(CPU オフロード)。

遅くなるのは誰でも知っている。知りたかったのは「どれくらい」と「どう」の部分だ。線形に遅くなるのか、それとも別の形なのか。

num_gpu スイープ

qwen3:32b(全 64 層)で、GPU に載せる層数(num_gpu)を振りながら生成速度を測る。tok/s は ollama の応答に含まれる eval_count(生成トークン数)と eval_duration(生成時間)から正確に出せる。

num_gpuCPU 上の重み生成 tok/sVRAM 使用
0~20 GB2.992 MiB
16~15 GB3.765.9 GB
32~10 GB5.4310.5 GB
48~5 GB9.6415.1 GB
64049.6119.9 GB
999063.7820.2 GB

num_gpu:999 は全層 + 出力層まで GPU に載せた場合)

数字を眺めてほしい。num_gpu を 0 から 48 まで上げても、tok/s は 3.0 → 9.6 と這うようにしか伸びない。GPU に半分以上の層を載せても、まだこの程度だ。

ところが 48 → 64 で 9.6 → 49.6 tok/s、約 5 倍のジャンプが起きる。比例カーブではない。だ。

なぜこうなるのか。1 トークン生成するには、モデルの全層を順に通す必要がある。GPU 上の層は速く、CPU 上の層は遅い。1 トークンの時間はざっくり:

1 トークンの時間 ≈ (CPU 上の層数 × CPU の 1 層あたり時間)
+ (GPU 上の層数 × GPU の 1 層あたり時間)

CPU の 1 層は GPU より桁違いに遅いので、CPU に残った層が全体を支配する。1 層でも CPU に残っていれば、そこが律速になる。 だから GPU に載せきる直前まで遅いまま、載せきった瞬間に律速が外れて一気に速くなる。これが崖の正体だ。

法則:tok/s ≈ RAM 帯域 ÷ 溢れ GB

もう一歩踏み込める。生成では各トークンで重みを 1 回読むだけ。だから CPU 側は純粋に RAM 帯域律速だ。スイープの各点を「tok/s × CPU 上の GB」で見ると、値が ~50〜60 に収まる(溢れが小さい点ほど下振れするのは、GPU 側の固定時間が相対的に効いてくるから。係数を決めているのは純 CPU の点だ):

num_gpu 48: 9.64 tok/s × ~5 GB ≈ 48
num_gpu 32: 5.43 tok/s × ~10 GB ≈ 54
num_gpu 16: 3.76 tok/s × ~15 GB ≈ 56
num_gpu 0: 2.99 tok/s × ~20 GB ≈ 60

つまり tok/s ≈ 実効 RAM 帯域 ÷ (CPU に溢れた GB)。係数の ~60 GB/s が、このマシンの DDR5-5600 dual-channel の実効帯域だ(理論値 89.6 GB/s の 7 割弱。妥当な実効値)。GPU 上の層は相対的にタダなので、速度を殺すのは溢れた分だけ——総モデルサイズではない。ここが直感に反する。

実用限界のモデルサイズ

速度が溢れ量だけで決まるなら、動かせるモデルの上限も逆算できる。VRAM の実効を 28GB(KV キャッシュ等の余白を引く)として、モデルサイズ ≈ 28GB + 許容できる溢れ量

体感許容 tok/s溢れ上限積めるモデル(Q4 目安)
サクサク10~6 GB~34 GB(32B Q6 / 49B Q4)
実用5~12 GB~40 GB(70B Q4 がギリ)
我慢3~20 GB~48 GB(70B Q5)
バッチのみ1~50 GB~78 GB(容量限界)

中途半端な spill が一番損だ、というのがこの表の含意でもある。少し溢れた瞬間に崖の下へ落ちる。

70B で答え合わせ

理屈が正しいか、実物で確かめる。llama3.3:70b(Q4, 42GB)をロードした。42GB は 32GB の VRAM に収まらないので、ollama は自動で GPU 73% / CPU 27% に配置——~12GB がシステム RAM へ spill する。

法則の予測は 60 ÷ 12 ≈ 5 tok/s。実測は 4.93 tok/s。ドンピシャだった。

しかもこれは、このマシンで一番重いメモリ配置だ。42GB を VRAM と RAM にまたがせて展開する——前回書いた通り、壊れていた頃なら即死していた構図。それが今は、遅いとはいえ普通に 70B を回せる。

例外:MoE

この法則が崩れる唯一のケースが MoE(Mixture of Experts)だ。MoE は 1 トークンで全パラメータを読まず、アクティブな一部の expert しか読まない。だから見かけのサイズが巨大でも、実際に毎トークン読む量=溢れコストは小さい。デカいモデルを spill で回したいなら MoE 一択、というのが今回のデータからの示唆だ。

まとめ

  • 速度はモデルの総サイズでなく「VRAM から溢れた分」で決まる。 tok/s ≈ 実効 RAM 帯域 ÷ 溢れ GB。このマシンでは実効帯域 ~60 GB/s(DDR5-5600 dual-channel)。
  • だから性能は「崖」になる。 半分 spill しても半分速度にはならない。GPU に載せきるかどうかの、ほぼ二値の勝負だ。
  • 実用限界は VRAM 実効 + 許容 spill。 32GB VRAM + DDR5-5600 なら 70B Q4(~40GB)が実用ライン、実測 ~5 tok/s。じっくり用途なら十分使える。
  • それを超えたいなら MoE。 アクティブ分しか読まないので溢れコストが低い。
  • そして何より——この実験自体が、RAM が直った証明でもあった。このマシンで一番重いメモリ配置を、落とさずに測りきれたのだから。

SEE ALSO

COMMENTS