メモリの壁を1から — なぜ演算より転送が高いのか
掛け算より、数を運ぶほうが高い。配線の充放電という物理から出発して、DRAMのレイテンシがなぜ縮まないのか、メモリ階層の桁、リトルの法則、マシンバランスとルーフラインまで。最後に、目の前のカーネルが計算律速か帯域律速かを自分で切り分けられるところまで持っていきます。
要点
- 計算そのものは安く、高いのは値を演算器まで運ぶことで、配線の充放電エネルギーは ½CV² で距離に比例する。
- DRAMのレイテンシは微細化しても縮まず、バンド幅は買えるがレイテンシは買えない。これがメモリの壁の背景である。
- リトルの法則 N=λL により、帯域1 TB/s・レイテンシ100 nsなら常時1600件の読み出しが必要で、隠せるのはレイテンシだけである。
- マシンバランス B_m と演算強度 I を比べ、I<B_m なら帯域律速、I>B_m なら演算律速になる。
- LLMのデコードは tok/s ≤ BW/(Nb) が天井で、バッチングは律速資源を帯域から演算へ移し替える操作である。
出典: US Patent 3,387,286 — Field-effect transistor memory (R. H. Dennard, 1968) · CODATA Value: speed of light in vacuum (NIST Reference on Constants, Units, and Uncertainty) · Efficiently Scaling Transformer Inference (Pope et al., 2022) · Nsight Compute Profiling Guide (NVIDIA Developer Documentation) ほか1件(末尾)
高いのは計算ではない
「計算が重い」という言い方を疑うところから始めます。2つの数を掛けるのは驚くほど安い操作で、高いのはその数を演算器の前まで運んでくるほうです。
理由は物理です。乗算はトランジスタ数千個の局所的な動作で、信号が動く距離はミクロン単位。一方メモリから値を取り出すのは、チップの端や外まで引き回された配線を電圧で駆動する行為です。配線を まで振るのに要るエネルギーは
は配線に付いた静電容量、 は振る電圧です。つまり、1本の線に信号を通す代金は「その線がどれだけ電気を溜め込む器か」と「どれだけ強く電圧を振るか」の2つだけで決まり、しかも電圧のほうは2乗で効く、ということです。 は配線の長さにほぼ比例するので、距離がそのまま値段になる。隣のレジスタから読むのと基板の向こうのDRAMから読むのとでは、同じ1語でもエネルギーが桁で違います。演算は安く移動は高い——この非対称が以降すべての土台です。
なぜDRAMのレイテンシは縮まなかったのか
演算側はトランジスタが増え、周波数が上がり、演算器が並びました。ところがDRAMのレイテンシは同じ勢いでは縮んでいません。この差が開き続ける現象がメモリの壁です。理由は構造にあります。
DRAMの1ビットはコンデンサ1個とトランジスタ1個です。読むにはワード線を上げてセルの電荷をビット線へ吐き出させますが、ビット線には何百ものセルがぶら下がって容量が大きく、電圧変化はごくわずか。センスアンプで増幅して0か1かを確定させます。しかも読み出しで電荷は失われるので(破壊読み出し)書き戻しが要り、次に備えたプリチャージも要る。
この手続きは微細化しても短くなりません。セルを小さくすれば電荷が減り、信号が弱まって検出がむしろ難しくなるからです。一方バンド幅は、I/Oを増やし転送クロックを上げダイを積層すれば増やせる。バンド幅は金で買えるが、レイテンシは買えない。 この非対称性が、以降のあらゆる工夫の動機です。
大きくて速いメモリが作れない理由
では、なぜ「大きくて速い」を1段で実現できないのか。容量を増やせば記憶素子は広い面積を占め、配線が長くなり、抵抗と容量が増えて信号が鈍ります。極端には光速すら効く。1 GHzの1周期に光が進むのは真空中でも約30 cmです。大きいものは原理的に遠い。 だから速い小容量と遅い大容量を積むしかありません。
| 階層 | 実現方式 | レイテンシの桁 | 容量の桁 |
|---|---|---|---|
| レジスタ | フリップフロップ | 1サイクル未満 | 数百バイト |
| L1キャッシュ | SRAM | 数サイクル | 数十KB |
| 最終段キャッシュ | SRAM | 数十〜数百サイクル | 数MB〜数十MB |
| 主記憶 | DRAM | 数百サイクル(数十〜100 ns級) | GB |
| 不揮発ストレージ | フラッシュ | 数万〜数百万サイクル | TB |
レイテンシは上から下まで6桁以上離れています。線形の目盛りに並べれば、上の数段は潰れて見えません。
レイテンシは隠せる、バンド幅は隠せない
レイテンシとバンド幅は別の量です。この2つを結ぶのが、待ち行列の一般則であるリトルの法則です。
は同時に飛んでいる未完了アクセスの数、 は達成したいスループット(毎秒のアクセス数)、 はレイテンシ。「欲しい流量 × 往復時間ぶんの在庫を、常に空中に浮かせておけ」と読みます。つまり、注文してから届くまでが遅い店ほど、そして速く売りたい店ほど、棚を切らさないためにはまとめて多く発注し続けるしかない、というのと同じ話です。64バイトのキャッシュラインを単位に毎秒1 TBを流したいなら 回/秒、レイテンシ100 nsなら 。常時1600件の読み出しが未完了のまま走っていないと、帯域は埋まりません。
プリフェッチも、ロードキューも、GPUが何万ものスレッドを走らせる設計も、この を確保してレイテンシを隠すための在庫です。ただし隠せるのはレイテンシだけで、運べる総量は帯域が決めます。ここから先が本題です。
マシンバランス — 機械の側の演算強度
行列積のコストでは、演算強度 = FLOPs ÷ 運ぶバイト数 を定義し、GEMMは 、GEMVは で頭打ちだと見ました。あれはアルゴリズム側の量です。ハード側にも同じ次元を持つ量があります。
マシンバランスと呼びます。「この機械は1バイト運ぶ時間で何回の演算ができるか」。つまり、データが1バイト届くのを待っているあいだに、その機械は何回ぶんの掛け算を空回りさせているか、を数えた値です。単位は FLOP/byte で、演算強度 と同じなので直接比較できます。
- なら帯域律速。演算器はデータ待ちで遊んでいる
- なら演算律速。帯域には余裕がある
ルーフラインの折れ点(リッジポイント)がちょうど の位置です。そしてメモリの壁とは、 が年々大きくなってきたという一言に尽きます。 演算性能の伸びが帯域の伸びを上回ったので、昔は演算律速だったコードが、一文字も書き換えていないのに帯域律速側へ滑り落ちた。汎用プロセッサの はかつて1前後でしたが、行列演算ユニットを積んだ加速器では数百のオーダーです。演算強度が数百を超えないカーネルは、もう演算器を使い切れない。
GEMMは速くGEMVは遅い、を桁で確かめる
行列とベクトルの積(GEMV)では行列の各要素を1回しか使わないため、実行時間の下限は「重みを読み終わる時間」そのものになります。
は行列の要素数、 は1要素のバイト数、 はメモリ帯域です。つまり、行列全体のバイト数を帯域で割った時間より速くは終われない——読み終わるまでは終われないという当たり前を、そのまま式にしただけのものです。この式に演算性能は現れません。 どれだけ強い演算器を積んでも下限は動かず、下げる方法は帯域を上げるか を減らすかの2つだけです。
GEMMは逆で、タイルをキャッシュに載せれば を容量が許すところまで上げられ、 を越えた先では演算器の数が効き始めます。同じ重み行列でも、相手が1本のベクトルか束ねた行列かで律速資源が入れ替わるわけです。
LLM推論が帯域律速である理由
生成を2段階に分けます。プロンプトをまとめて処理するプレフィルはトークンが束になっているのでGEMMで、演算律速に入りやすい。問題は1トークンずつ吐くデコードで、こちらは1トークン出すたびに全パラメータを1回読み直します。パラメータ数 、1個 バイトなら1トークンあたり バイト。生成速度の天井は
「毎秒運べるバイト数を、1トークンに必要なバイト数で割ったもの」。つまり、1トークン吐くたびにモデル全体を頭から読み直しているのだから、生成速度は重み一式を毎秒何回読み切れるかで頭打ちになる、ということです。ここにも演算性能は現れません。 を fp16()で置くなら1トークンあたり14 GB、帯域1 TB/sの機械なら理論上限でも毎秒70トークン程度です。
バッチ を組むと、重みの読み出しは1回のままで、その1回で 本ぶんの計算をします。運ぶバイトは変わらず演算量が 倍になるので が 倍。GEMVがGEMMに化けるわけです。バッチングは「速くなる魔法」ではなく、律速資源を帯域から演算へ移し替える操作だと理解するのが正確です。
移し替えには終点が2つあります。ひとつはリッジポイントで、 が を越えた時点で演算律速に変わり、あとはレイテンシが伸びるだけになる。もうひとつはKVキャッシュで、系列長に比例して増えるこれも毎トークン読み直す対象です。長い文脈では重みよりKVの読み出しが支配的になり、同時にメモリを食い潰してバッチを増やせなくします。
現場ではこう使う
計算律速か帯域律速かを切り分ける3手順
- 先に紙の上で下限を出す。 動かすバイト数(重み+活性+KVキャッシュ)を帯域で割った と、FLOPsをピーク演算性能で割った 。大きいほうが理論下限です。プロファイラを起動する前にやること。見当が付いていないと、出てきた数字を読み違えます。
- 実測と比べる。 実測が の1〜2倍に収まっていれば帯域律速でほぼ確定。どちらの理論値からも大きく外れて遅いなら犯人は第3のもの——レイテンシ律速(並列度不足、同期待ち、カーネル起動、ホスト側のPythonループ)です。
- プロファイラで裏を取る。 NVIDIA環境なら Nsight Compute の Speed of Light セクション(
ncu --set full)が Memory [%] と Compute (SM) [%] を並べて出します。CPUならperf stat -e cache-misses,LLC-load-missesや VTune の Memory Bound 指標。PyTorchならtorch.profilerでカーネル別の時間を取り、上位数本にだけ手順1〜2を当てます。
読み方は3象限。Memory高・Compute低=帯域律速(打ち手は量子化・カーネル融合・レイアウト)、Compute高・Memory低=演算律速、両方低ければレイテンシ律速——最後のが最も見逃されます。
バッチサイズを上げると効く理由は、重み転送のコストを複数リクエストで割り勘にしているからです。バッチ8なら1トークンあたりの重み転送は8分の1。到着がばらつく本番でこの割り勘を維持する仕組みが継続バッチングです。効かなくなったときは、①リッジポイントを越えた、②KVキャッシュでメモリが尽きた、③レイテンシSLOに引っかかった、のどれか。どれで止まっているかを言語化できることが、次の打ち手を決めます。
知らないと事故になる落とし穴
- 「演算性能が2倍のチップに替えたのに生成が速くならない」は、帯域が据え置きなら当たり前。カタログのFLOPS/TOPSより先に帯域と容量を見ること。
- 演算強度はデータ型で変わります。fp32をfp16に落とすだけで は2倍。量子化が劇的に効くのは帯域律速の場面だけです。
- キャッシュに載る小さなサイズで測った速度は本番で再現しません。ベンチは実際の重みサイズで。
面接や設計レビューの定番はこれです。「70億パラメータのモデルをfp16で載せ、帯域1 TB/sの機械で1トークン生成する。理論上の最短時間は?」——答えは 14 GB ÷ 1 TB/s = 14 ms。演算性能を聞かれていないのに答えが出てしまうこと自体が、この問いの主題です。
まとめ
- 演算は安く移動は高い。理由は配線の充放電 で、容量が距離に比例するから
- DRAMのレイテンシは物理で決まって縮まない。バンド幅は買えるがレイテンシは買えない
- レイテンシは ぶんの在庫で隠せるが、帯域は隠せない
- と を比べれば律速資源が決まる。メモリの壁とは が増え続けたこと
- デコードは が天井。バッチングは律速資源を移し替える操作
次は同じ問題を電力の側から見ます。運ぶのが高い理由も、演算器を敷き詰められない理由も、突き詰めると1本の式に行き着きます。
コメント
コメントにはログインが必要です