チップ設計をボトムアップで ― ライナー・ポープ

NVIDIA・ジェンスンフアン
この記事は約45分で読めます。

本動画は、AIチップ企業MatXのCEOライナー・ポープが、チップの最下層から最上位の構造までを順を追って解説する対談である。論理ゲートという最小単位から始まり、積和演算回路、ダッダ乗算器、シストリックアレイ、そしてGPUとTPUの設計思想の違いまでを、紙の上での計算を交えながら積み上げていく。計算と通信のトレードオフという一貫したテーマが、回路の最下層から大規模なアーキテクチャまで貫いていることが示される。FP4とFP8の精度スケーリング、クロックサイクルの決まり方、FPGAとASICの違い、CPUとGPUとTPUの構造的差異、さらには脳との比較にまで話題は及ぶ。

Chip design from the bottom up – Reiner Pope
New blackboard lecture with Reiner Pope: how do chips actually work - starting with basic logic gates, and working up to...

チップ設計の最小単位、論理ゲート

新しいAIチップ企業MatXのCEO、ライナー・ポープさんに再びお越しいただきました。前回はデータセンターの内部で何が起きているかについてお話ししましたよね。今回はAIチップの内部で何が起きているのかを理解したいと思っています。チップは実際どうやって動いているのか、と。ちなみに念のためお伝えしておくと、私はMatXのエンジェル投資家です。なので、いいチップを設計してくれていることを願っていますよ。

そう願いますね。まずはチップ設計の最も基本的な最小単位から始めて、実際の量産チップとはどういうもので、どんな部品から成り立っているのかというところまで積み上げていきましょう。

チップの一番底の階層では、私たちが扱う基本要素は論理ゲートです。AND、OR、NOTといった、非常に単純なものですね。これらはワイヤーでつなぎ合わされていて、そのワイヤーはチップ上に金属の配線として物理的にレイアウトされなければなりません。AIチップが計算したい主要な機能は、行列の乗算です。その内側にある基本要素が、数のペアの積和演算なんです。この計算が手で書くとどう見えるかをまず示してから、それを実現する回路がどういうものになるかを推測していきましょう。

4ビットの数ともう一つの4ビットの数の積和演算でやるのが一番わかりやすいでしょう。最も明快な基本要素は実は積和演算なんです。この二つの項の掛け算があって、そこに8ビットの数を足し込みます。

一つ確認させてください。なぜこれがコンピュータの内部で起きるあらゆる計算にとって自然な基本要素なんでしょうか。

理由はいくつかあります。少し効率がいいというのもありますが、AIチップにとってこれが自然な理由は、行列乗算の最中に何が起きているかを見ればわかります。行列乗算とは簡単に言うと何か。i、j、kについてのループがあって、output[i, k] += input[i, j] × もう一方のinput[j, k] という処理です。行列乗算のあらゆるステップで積和演算が起きているわけです。もう一つ気づくのは、累積のステップでの精度が、乗算のステップでの精度よりほぼ常に高くなるという点です。これはAIチップに特有のことです。低精度の数を掛け合わせて、それを累積していくと誤差がすぐに溜まっていくので、累積のところではより高い精度が必要になるんです。だからこそ、4ビットの乗算と8ビットの加算をやることにしたわけです。

理解できているか確認させてください。これには二通りの捉え方があります。一つは、値が入力よりも大きくなるという点。もう一つは、これが浮動小数点数だった場合は……そちらの部分は私にはあまり直感的ではないんですが、でも同じ原理でしょうか。

本当に同じ原理です。別の原理として、この数を合計していくとき、たくさんの数を足し合わせていくので、丸め誤差がどんどん溜まっていく。一方こちらの場合、連鎖の中に乗算が一つしかないので、乗算の中で丸め誤差がたくさん溜まることはないんです。

なぜたくさんの数を合計することになるんですか。そこには数が二つしかないように見えますが。

この合計はjの回数だけ繰り返されるんです。誤差が累積していく。

なるほど。では、この計算を手でやるとしたらどうなりますか。

人間としては、おそらく二つに分けてやるでしょうが、長乗算を使えば全部を一度にやることもできます。まず乗算の項について、この4ビットの数を、もう一方の4ビットの数のあらゆるビット位置と掛け合わせていきます。それを書き出します。最初に、1001をこのビット位置と掛ける。これはその数そのものになりますね。次に一つずらして、0と掛けるので、すべて0の数になります。さらにもう一つずらしてこの1と掛けると、1001が得られます。最後にこのビット位置では、また全部0の数になります。これで、乗算のために足し合わせなければならない項がいくつか出てきました。その合計をしているついでに、本来の累積項も一緒に足してしまいましょう。だからそれをそのまま下に写します。これが合計です。計算したいのは五つの項の合計というわけです。

この中間段階に至るまでに、どれだけの論理ゲートが必要でしたか。

これら16個の部分積をすべて生成する必要がありました。一つの部分積をどうやって作るか。例えばここのこの1を取りましょう。これは、この数とあちらのこの数を掛け合わせることで作ります。これはANDゲートで作れます。この数が1になるのは、このビットが1で、なおかつこのビットも1のときだけです。どちらかが0なら、0と何かを掛けた結果は0になります。これ全部を作るのに、結局16個のANDゲートを使いました。一般的なケースでは、pビットの乗算とqビットの乗算をやるなら、これはp×q個のANDになります。

ANDゲートと全加算器

最後に、それらを合計します。作業の大部分はこの合計のところで起きます。ここで使うもう一つの論理ゲートを説明させてください。ANDはチップ上に存在する論理ゲートの中でほぼ最も単純なものです。ほぼ最小と言ってもいい。対極にあるのが、一般的に使う中で最も大きな論理ゲート、全加算器と呼ばれるものです。ソフトウェア畑から来た人は、全加算器というと32ビットの数同士を足すものだと思うかもしれません。この場合は、ただ三つの1ビットの数を足すだけです。0と1と1を足すと考えればいい。これらを足すと、結果は0、1、2、3のいずれかになるので、たった2ビットの二進数で表現できます。入力としては3ビット、出力としては2ビットを持つわけです。二進数で2は10ですね。これは3→2圧縮器とも呼ばれます。3ビットの入力を取って2ビットの出力を作るからです。

確認させてください。二つの入力がXとYの値で、それから入ってきた何らかのキャリーがあって……。

三つの入力はすべて同じビット位置のビットです。ここで言うと一つの列に並んだ三つのビットですね。二つの出力は、こちらでは縦に、こちらでは横に描いていて、この縦と横のレイアウトに対応させています。これは、同じ列にあるものは同じビット位置にあり、隣り合う列にあるものは別の位置にある、ということを表しています。こちらがキャリーアウトで、こちらが和でした。だから全加算器の入力が例えば101なら、出力は10になります。111なら11、000なら00、010ならやはり01です。

なるほど、わかりました。要するに、ものの数を数えてそれを二進数で表現しているだけなんですね。

そうです。この回路は、私たち人間が列に沿って合計するときに自然にやっていることを捉えているんです。全加算器を使って合計する一回分の手順を見せましょう。ここでの合計のやり方は、人間にとっては少し不自然です。私たちは列に沿って合計してキャリーを覚えておきますが、覚えておく代わりに、キャリーを明示的に書き出すんです。一番右の列から左へ進みます。一番右の列では、1と1を合計して、ここに0と、キャリーの1が出ます。このビットのペアにこの全加算器回路を使って、出力としてビットのペアを生成したわけです。次にこの列でも同じことができます。四つの数からなる列があるので、最初の三つを取って全加算器を回すと、出力として0と0が得られます。これらの和は00です。これがこれらのビットすべてに全加算器を適用したものです。ビットを使い切ったら、処理済みであることを示すために線を引いて消していきます。

もう少し続けましょう。この三つの数を取って足すと、1と0が得られる。この三つの数を処理しました。次にこの三つの数を取って足すと1と0が得られて、これらの数も処理しました。これをこう捉えるといいでしょう。足さなければならない数のグリッド全体があって、私はここのすべてのビットに全加算器を適用し続け、列から絶えず三つの数を取り除いて二つの数を出力として書き出していく。これを何度も何度も繰り返して、最終的にただ一つの数だけが出てくるところまで持っていくんです。

ダッダ乗算器と回路規模の見積もり

このやり方はダッダ乗算器と呼ばれています。全加算器を使って面積効率のいい乗算器を作る際の標準的な方法です。この回路の規模を定量化してみましょう。ものの大きさの感覚をつかんで、後で比較できるようにするためです。全加算器をいくつ使ったか。最初にいくつの数から始めたか。16個の部分積、これはこれらの項とこれらの項すべての積ですね、それに加えてここで足す8個の項があります。24ビットから始めました。最終的に出力として8ビットを生成しました。各ステップで、三つの数を消して、結果として二つの数を書き出していた。全加算器を一回使うごとに、ここのビットが一つ消える。ではいくつ全加算器を使ったか。24引く8のはずなので、この回路には16個の全加算器があったことになります。

これは一般的なケースでも成り立ちます。この回路にはp×q個の全加算器があることになる。その論理を理解できているか確認させてください。入力ビットの24は p×q に p+q を足したもの。出力ビットはちょうど p+q。だから p×q + p+q から p+q を引くと p×q になる、と。

その通りです。これが、積和演算を選んだ二つ目の理由を説明している、あるいは少なくとも示唆していると思います。一つ目の理由は、それが行列乗算に現れるからでした。二つ目は、こんなふうにとてもすっきりした単純な p×q という、とても単純な代数を与えてくれるからです。

この手順全体を説明してきました。ここで私が踏んだ一つひとつの原子的なステップが論理ゲートになり、そしてワイヤーがつなぎ合わされる。三つの入力を使って二つの出力を作ったとき、これを物理的なデバイスに対応づけて考えると、この三つすべてを一つの論理ゲートにつなぐワイヤーがあって、それがこの出力を生み出すことになります。これがAIチップの内部にある、ビット幅は異なれども主要な基本要素なんです。ここから積み上げて、他のあらゆる処理を実行するのにどう使うかを見ていきましょう。

FP4とFP8、そして二次的なスケーリング

ここで聞くのは適切なタイミングではないかもしれませんが、NvidiaがこのチップはこれだけのFP4ができる、あるいはその半分のFP8ができると報告するとき、それらの回路が融通の利くもの、つまりFP4専用とFP8専用が分かれていないかのように聞こえます。でも、あなたがここで説明している描き方だと、論理の中に配置しなければならないとすれば、専用のFP4積和演算と専用のFP8累積が必要になるように思えます。それらを融通し合うことはできるんですか。

描いた通りでは、特に融通は利きません。これは実はチップを設計するときにしなければならない主要な選択の一つです。FP4をどれだけ持ち、FP8をどれだけ持つか。ときには顧客の要求という観点からその検討をします。別の角度としては、FP4とFP8で消費電力の予算を均等にするというやり方もあります。

彼らがそうした数字を報告して、たまたまFP8の2倍のFP4ができるという結果になっているとき、それは単にすべての浮動小数点に同等のダイ面積を割り当てることを選んでいて、結果としてそうなる、と。なぜ比率がちょうど2倍なんでしょう。

その一部は、ダイ面積にぴったり同等になることはまずないということがあります。データ移動の理由もあります。メモリに出入りする様子を見るときにまた戻ってくるかもしれません。二つの4ビットの数を、8ビットの数と同じ記憶領域に詰め込めるという事実は、ソフトウェアのレベルから見ると本当によくできているんです。それをメモリに保存するとき、チップ内に引き回すバスのサイズの取り方が、これをとても都合よく機能させてくれる。

考えてみると、ちょうど2倍ではないですね。面積の量はビット長に対して二次的に効いてくるように聞こえます。

だからこそ、より小さい精度は思っているより一層有利なんです。これは本当に大きな理由です。実際、Nvidiaは変更を加えました。歴史的に、B100やB200まではビット精度を半分にするたびにFLOP数を倍にしていました。あなたが言った理由、つまりこの二次的なスケーリングのせいで、その比率は実はわずかに間違っているんです。本来思うよりもっと大きな高速化が得られるはずなんですね。NvidiaはB300以降の製品仕様でそれを認め始めていて、そこではFP4がFP8の3倍速くなっています。本当は4倍であるべきですが。

ええ。ここで示したのは整数乗算の最も単純なケースです。FP4やFP8でやるような浮動小数点を扱うときには、指数というもう一つの項があって計算が複雑になります。ここまでで何が見えてきたでしょうか。あなたが指摘した大きな観察は、ビット幅に対するこの二次的なスケーリングがあるということで、これは非常に効果的で、低精度の演算がニューラルネットでこれほどうまくいっている唯一の理由なんです。次に私たちがやろうとしているのは、乗算そのものに費やす面積と、その周りのあらゆる回路とを比較することです。

CUDAコアとレジスタファイル、そしてマルチプレクサ

少し時間をさかのぼって、Tensorコア以前のGPUがどう動いていたかを見てみましょう。それは実はCPUが動いていたのと同じやり方なんです。この積和演算ユニットをどこに置くか。一般的に、CUDAコアやCPUを説明します。いくつかのエントリを格納するレジスタファイルがあって、ここでは8エントリの4ビットの数とでもしましょうが、通常は32ビットの数です。CUDAコアの内部に、ある深さのレジスタファイルがあって、それから積和演算回路がある。それがやるのは、このレジスタファイルから任意の三つのレジスタを取ってきて、積和演算を実行し、レジスタファイルに書き戻すことです。これに書き込みますが、読み出しはこれと、これと、もう一つランダムなものからできた。こんなふうに三つの入力を取ります。これが多くのプロセッサの中核となるデータパスです。

たいていのプロセッサはこんな見た目です。いくつかのレジスタの集合があって、それからいくつかの論理ユニット、つまりALUの集合がある。レジスタファイルからALUへ、そして戻ってくるデータ移動のコストを分析したいんです。最終的には、いつもこいつを選ぶとは限らない、どの時点でもどのレジスタでも選びうる、と言う何らかの回路が必要になります。最初の問いは、どうやって回路を作れるか。私が探そうとしている回路はマルチプレクサです。この場合、8個の入力を持ち、レジスタファイルの各エントリから一つずつ来て、出力を一つ持つ。それが実際にこの出力を生み出します。

これにかかるコストはどれくらいか。作るのに使えるのはANDとORだけです。どう作るか。一番愚直なやり方をします。マスクを作るんです。三つ目のエントリを読みたいとき、それが読みたいものかどうかに応じて、すべてのエントリを1か0とANDして、それから全部をORでまとめる。

基本的なところを確認させてください。マルチプレクサがやっているのは、ただ入力を選ぶことなんですね。

ただ選んでいるだけで、ソフトウェアからは見えません。入力番号3が欲しいと言うと、それはここにマルチプレクサがあるということなんです。

ではこのマルチプレクサのコストはいくらか。pビットを扱うn入力のマルチプレクサ。n行ある。ここではちょうど8行で、各行はpビット幅です。すべてのビットをANDしなければならないので、n×p個のANDゲートになります。すべての入力について、マスクで消すかどうかを決めなければならない。それから全部をORでまとめる。(n−1)×p個のORゲートになります。こうしてさまざまなものができていて、そのほとんどは0ですが、8個の選択肢から一つの選択肢へと畳み込む必要がある。各ステップで、一つの行を既存の行にORする必要があるんです。

ハードウェアのレベルで考えないというのは面白いですね。ただ要素3を選ぼうと考えるだけで、そんな単純なことそれ自体が、こんなに複雑な回路になっている。

これが、出てくるあらゆる隠れたデータ移動コストの第一歩なんです。比較してみましょう。私はこのコストを払わなければならない。ここにマルチプレクサが一つあって、実際には積和演算操作への三つの入力それぞれに対して、あと二つ同じものがある。このコスト、つまりこちらの 3×n×p 個のANDゲートを、私が本当に気にしている処理をやっている実際の回路の p×q 個のゲートと比較するわけです。実際の数字を入れてみると、nが8で、データ移動だけで 24×p 個のゲート、それに対してqが4なら、積和器そのものは 4×p 個のゲートだけです。

3はどこから来ているんですか。

ここに三つの異なる入力があるからです。私がほのめかしているのは、レジスタファイルのサイズに比例してスケールするこの作業すべてが、しかもこれはとても小さなレジスタファイルですが、レジスタファイルから論理ユニットへデータを動かすだけのこの作業すべてが、論理ユニットそのものよりも何倍も何倍もコストが高いということです。

直近のClusterMAXレポートで、SemiAnalysisはほぼ100社の異なるGPUクラウドをランク付けしました。Crusoeはゴールドティアに入ったわずか5社のうちの一つでした。SemiAnalysisは、Crusoeのようなゴールドティアのプロバイダーは、GPUの価格が同じだったとしても、シルバーティアのものより総保有コストが5%から15%低いことを見出しました。これは理にかなっています。総保有コストは、必ずしも表示価格には現れないさまざまな要素の下流にあり、Crusoeはそれを最適化しているからです。たとえば障害をどれだけうまく検知するか、故障したノードをどれだけ素早く交換するか、といったことです。たとえばCrusoeは、GPUの稼働時間、稼働率、信頼性を高めるためのNVIDIA独自のGPU監視・自己修復ソフトウェアであるNVSentinelを、最初に採用したクラウドの一つでした。これによってCrusoeは、さまざまな構成や展開全体でなぜチップが故障するのかについてNVIDIAが学んだすべてを活用でき、障害をプロセスのより早い段階で捉えられるようになります。そして故障を特定したら、Crusoeは10分未満で正常なノードに入れ替えることができます。ベアメタルで動かしているわけではないので、Crusoeはオペレーティングシステムをインストールしたりドライバを設定したりする時間を費やす必要がありません。すでに稼働中で事前に検証済みのホスト上に、新しいVMを立ち上げるだけでいいんです。これについて、あるいはCrusoeがゴールドティアに入ったその他の理由についてもっと知りたければ、crusoe.ai/dwarkesh にアクセスしてください。

マルチプレクサの中身を見る

マルチプレクサがどんな見た目か、2ビットか4ビットのマルチプレクサで実際に見てみると役立つかもしれません。2入力のマルチプレクサでやってみましょう。二つの異なる数があって、この二つの入力がある。これらが選択の対象となる入力で、選択信号は、こちらが欲しいか、もう一方が欲しいかのどちらかです。これはワンホットエンコーディングです。これが出発点です。このケースに注目しましょう。これが実際に得た入力で、こいつを結果として生み出したい。とても手間をかけて、このビットをこれら全部とANDします。それがこのビットとこの行をANDしたものを作る。同様に、このビットをこの行とANDします。それが全部0を作る。ここには四つのANDがあります。最後に、これら二つをORすると1が得られる。これら二つをORするとこれも1。これら二つをORすると0。これら二つをORすると1。それが四つのORです。

これは実は加算に少し似た見た目になります。まったく同じANDの組み合わせをやりました。これら全部をANDしたんですが、それを全加算器回路で畳み込む代わりに、ORゲートでとても単純に畳み込んだだけです。でもそれは n×p には見えませんね。これは n=2 入力でのものでした。一般的なケースでは、n行あって、行あたりpビットになります。それが n×p 個のANDゲートを与えるんです。私が説明したこの回路では、コストのほとんど、8分の7のコストが、レジスタファイルの読み書きにあって、論理ユニットそのものにあるのはほんのわずかな割合です。これが解くべき問題なんです。

Tensorコアとシストリックアレイ

これが本質的に、NvidiaのVolta世代以前の状況でした。この種のものがCUDAコアの中にあったんです。この問題提起こそが、Tensorコア、より一般的にはシストリックアレイと呼ばれるものの導入を動機づけました。この問題をどう解決するか考えてみましょう。私たちは回路面積のほとんどを、本当はどうでもよくてソフトウェアプログラマーには隠れているものに費やしていて、実際に気にしているものは大して面積を占めていない。こちらを同じサイズに保ったまま、なんとかしてこちらを大きくする。それが目標です。

この段階では、これだけをハードウェアに焼き込んでいました。この一行が積和演算で、この一つだけがハードウェアに焼き込まれていた。シストリックアレイのアイデアは、ループを二段階上がって、この外側のループ全体をハードウェアに焼き込もうというものです。もしずっと大きな粒度の固定機能の論理を持てば、入力と出力で払う税はずっと小さくなるかもしれない、という発想です。

なるほど。行列乗算のループを一段階上がれば、バランスを通信よりも計算の方へ傾けられると示唆しているように聞こえます。

その通りです。ここで利用しようとしている効果が二つあります。一つは、レジスタファイルを一回行き来するごとにより多くの処理ができること。もう一つは、このループの一部で、特定のものが固定されたままになることを利用できることです。視覚的に、この行列乗算を見てみましょう。このループの部分は行列とベクトルの乗算に対応します。行列を取ってベクトルと掛けるんです。どうやるか。各列がベクトルと掛けられ、それから合計される。列に沿って合計するわけです。この0と3が3と7と掛けられて合計され、それから1と2が3と7と掛けられて合計される。この行列の各エントリには積和演算が対応しています。この四つの積和演算を描き出しましょう。

なぜ四つの積和演算があるのか理解できているか確認させてください。出力ベクトルに対応する列の各エントリは内積で、この場合は二つの乗算とその二つの乗算の加算になる。累積していくのは……実は内積あたりの加算は一つだけですが、ゼロから始めるのが好まれる。でもそれはゼロの初期化を含んでいる。

ええ。二次的に多くの計算を持ちたいんです。以前の x×y 倍の計算を持つ。でも通信は x 倍だけになることを目指したい。この優位性の項を y として効かせることが意図です。乗算を並べました。サイズ2のベクトルを持ち込みたくて、それはもう私たちの列の目標と一致しています。それは問題ありません。しかし、この行列の通信を管理する必要があって、これは私たちの x という予算を超えてしまう。

アイデアは、AIの文脈ではこの行列が長期間固定されたままになるということです。こちらにいくつかのレジスタファイルがある。このレジスタファイルから出てくる量、これがある意味で x として効いてほしい項です。この行列全体を毎サイクルレジスタファイルから持ってきたくはありません。レジスタファイルからの配線という点でコストがかかりすぎるからです。私たちの鍵となる技は、この行列をシストリックアレイのローカルに格納できるということです。これらの0、1、2、3という数を、これらの数を物理的に格納するレジスタと呼ばれるゲートに格納して、多数の異なるベクトルに対してこれらの数を何度も何度も再利用するんです。

ここでの最適化は、行列乗算の性質として、この正方形の二次的なものを、論理が起きているまさにその場所に格納できるということで、それは出し入れし続ける入力に比べて余分な次元を持っているんですね。

その通りです。これが行列乗算とは何かという性質です。一つの値を出すのにたくさんの乗算をする。内積はたくさんの乗算の結果です。だからその最適化は、何らかの値が出てくる前にたくさんの乗算を詰め込めることを意味する。

その通りです。具体的にそれがどう見えるかを完成させると、ここで3と2を入れ替えました。この0と3が3と7と掛けられるのと同じように、ここでも列に沿って内積を作ります。3と7をここに送り込む。これがこの乗算に送られ、またこの乗算にも送られる。同様に、3はここに送られ、またここにも送られる。それからここに沿って合計します。列の上端から始めて0を送り込み、下から出てくると結果が得られる。視覚的には、行列の列に沿って内積が実行されていて、それがシストリックアレイで空間的に行われることと正確に対応しているんです。これが縦に合計された一つの内積で、これがやはり縦に合計された二つ目の内積です。

重み行列をどう読み込むか

レジスタファイルに出入りする必要があるデータは何でしょう。出力に x 量のデータが出てきて、入力からも x 量のデータが来る。少なくとも入出力ベクトルに関しては、レジスタファイルを出入りするデータが x 倍だけという目標を達成しました。これで未解決の問いが残ります。重み行列はシストリックアレイのローカルに格納されると言いましたが、そもそもどうやってそこに入ったのか。ある時点でチップを起動してこのデータを書き込まなければならない、ではそれはどこから来たのか。

その技は、ただ非常にゆっくりやるというものです。シストリックアレイに少しずつちびちびと送り込むんです。最も単純な戦略は、このデイジーチェーンを動かすこと。ここに数を一つ送り込むと、次のクロックサイクルでそれがシストリックアレイの次のエントリへ下りていく。これをすべての列で並列にできて、これもここから来る。それでさらにおよそ x 単位の帯域幅が入ってくることになります。

その文をもう一度繰り返してもらえますか。

数を行列に持ち込むのはまれにしかないとわかっています。私たちはただ、シストリックアレイの境界を横切る配線の量が、xy ではなく x に抑えられるような構成を何でもいいから思いつきたいんです。特に単純な戦略は、一クロックサイクルでシストリックアレイの最上行に数を持ち込むこと。それから y 回連続のクロックサイクルにわたって、毎回最上行に持ち込んで、他の全行を一つ下にずらす。これによって、この高価なレジスタファイルから来る必要のある配線を、xy ではなく x の係数だけに抑えるんです。

なるほど。通信に関しては二つの問いがあります。通信時間と通信帯域幅です。あなたが言っているのは、これは一度だけ読み込むのだから帯域幅を最小化しよう、なぜなら帯域幅はダイ面積に等しいから、ということですね。小さなレーンでゆっくり読み込む、しばらくこの値をそこに保持し続けるんだから、と。

その通りです。

前回、多数のチップにまたがる推論について話したとき、私たちが最適化しようとしている大きな高次の目標は、メモリ帯域幅あたり、つまり通信あたりの計算量を増やすことでした。ここでも、レジスタから論理へ情報を運ぶことに対して、実際の乗算や加算の量を増やそうとしていますよね。どちらの場合も、通信に対して計算を最大化しようとしている。これはスタックの上から下までずっと現れるんです。これは底に近い、ゲートに近いところです。

選ぶ数値フォーマットの精度において、もしかするとゲートにもっと近いバージョンがあります。同じ効果を見ましたよね。ALUの精度の中だけでも、二乗と一次の項が働いている。でも行列のサイズでも同じことが起きている。このユニットが次に大きいユニットです。乗算回路があって、その上にかなり大きなシストリックアレイがある。私は2×2で描きましたが、古いTPUはここで示したこの回路の128×128だと説明されていました。これが結局、行列乗算を実装する既知の中で最も効率的な回路になるんです。

チップ設計におけるサイジングの判断

通信に対して計算を最大化しようとすべきだというのは明らかだと話してきました。XをやるべきかYをやるべきか、答えが明らかでなくて夜も眠れなくなるような、自明でないトレードオフには何がありますか。

チップ設計の判断のほとんどはサイジングの判断です。ここまで描いてきたものの中ですでに……AIチップはどれもこの回路を持っています。シストリックアレイがあって、その近くのどこかに入出力を供給するレジスタファイルがある。この範囲の中だけでも、あなたが抱えるサイジングの問いは、シストリックアレイをどれくらい大きくすべきか、レジスタファイルをどれくらい大きくすべきか、です。この二つの問いは結びついています。一つの考え方は、データ移動にチップ面積の何パーセントを費やしたいかという予算を設定すること。たとえばこれを10%、シストリックアレイを90%にしたいと言うとします。そうすればレジスタファイルのサイズを決められる。大きなレジスタファイルはより柔軟です。より多くのアプリケーションレベルの性能を引き出せますが、シストリックアレイに費やす面積を奪ってしまうんです。

最近、私はエッセイコンテストを開催して、AIに関する最も大きな未解決の問いだと私が考えるものについて書いてもらいました。応募の受付は先週締め切られたので、Cursorを使って応募作品を審査するためのいくつかの異なるインターフェースを作りました。一つのインターフェースは応募を匿名化して必要な情報を隠します。問いごとに回答をグループ化したり、メモを追加したり、点数を記録したりできるんです。もう一つのインターフェースは、私が採用しようとしている研究者の役職にも応募したい人を審査するのに役立ちます。そのUIは、応募者のエッセイをその人の履歴書と個人ウェブサイトのすぐ隣に並べて表示するので、すべてを一度に見られるんです。Cursorのハーネスは、これらのモデルが自分のUIを見て改善するのを助けるのが本当に得意です。組み込みのブラウザでこれらのインターフェースをレンダリングし、スクリーンショットを撮り、各セクションをクリックして回り、反復し続けるのを見ていました。今では、私は仕事のほとんどをCursorでやっています。研究論文をたくさん読んで可視化するのであれ、応募審査のインターフェースをコーディングするのであれ、黒板講義用のフラッシュカードを作るのであれ、Cursorは、私が見ているものが何であれAIがそれを一緒に見て、理解を助け、一緒に作業するのをとても簡単にしてくれます。なので、何に取り組んでいるにせよ、Cursorでやるべきです。cursor.com/dwarkesh にアクセスしてください。

チップのクロックサイクルはどう決まるか

チップのクロックサイクルはどこで出てくるんでしょうか。それを決めるのは何で、そもそもチップのクロックサイクルとは何ですか。

まず前提として、チップが信じられないほど並列であることに注目する価値があります。チップには1000億個のトランジスタがある。大規模な並列性を持つときにいつも必要なのは、異なる並列ユニット間で同期を取ることです。ソフトウェアでは、たいていミューテックスのようなとても高価な同期方法を使います。あるスレッドが自分の処理を終えて、メモリのどこかに格納されたロックを取得し、別のスレッドに終わったと通知する。チップでは、まったく違うアプローチを取ります。だいたい1ナノ秒ごとに、チップ内のすべての回路が一瞬止まって同期する。それがクロックサイクルです。チップ全体が通常、足並みをそろえて一気に次の操作へ進むんです。

これが回路でどう見えるかというと、クロックはレジスタによって仲介されます。レジスタは他のところでも描いたあの記憶デバイスです。こう考えるといいでしょう。0か1かのビットを保持する何らかの記憶があって、それからこのシストリックアレイや乗算器かもしれない論理の塊がある。この論理の塊に送り込まれる入力がいくつもあって、最終的に書き込み先となる何らかの出力レジスタがある。これらすべてのレジスタを駆動するグローバルなクロック信号があるんです。ある瞬間に、クロックが鳴ると、その瞬間にそのワイヤー上にたまたまある値が格納されます。

課題は、クロック速度をできるだけ速く動かしたいということです。2ギガヘルツで動かせば、1ギガヘルツで動かすより毎秒2倍の操作ができる。でもそれが意味するのは、私はこの論理の塊を通る遅延に非常に敏感だということです。そこで起きる計算はどれも、次のクロックサイクルが来る前に終わらなければならないからです。どんなチップでも、主要な最適化のポイントは、この遅延をできるだけ短くすることです。

なるほど。ここでの制約は、論理を入れすぎるとクロックサイクルに間に合わないリスクがある一方で、十分に入れないと潜在的な計算を取りこぼすことになる、ということのようですね。計算が終わるかどうか確率的な賭けをするような状況はあるんですか、それとも厳密にクロックサイクルまでに終わるか終わらないかなんでしょうか。

標準的なチップ設計では、確率は存在するものの何標準偏差も外れた値になるようにマージンを取ります。あらゆる実用上の目的において、それは信頼できる部品で、常にクロックに間に合います。一つのクロックから別のクロックへ移るクロックドメインクロッシングのような、奇妙な例外もいくつかあります。そのときは実際にこの確率を考慮しなければなりません。でも主要なパスでは、クロックサイクルの25%前に着くようマージンを取って、間に合わないことが非常に起こりにくいようにするんです。

クロックが同期する場所、レジスタがある場所は、チップ設計者として決めるものなんですか。それとも、ある論理の並びが欲しくて、VerilogをTSMCに送るものに変換するソフトウェアが、それを機能させるためにここ、ここ、ここにレジスタを置かなければならないと、どの一ステップもチップ全体のクロックサイクルを必要以上に長くしないように決める、そういう副産物なんでしょうか。

それらを挿入することは、実はチップ設計の作業の大きな部分を占めます。手動と自動の方法を組み合わせて行われます。ここでできるとても愚直なバージョンを示すと、この論理を取って半分に分割できます。一つの論理の塊の代わりに、同じことをやる二つの小さな論理の塊を持って、それらをレジスタで分割する。真ん中で分割すれば、2倍のクロック周波数を達成できる。素晴らしい、2倍の性能が得られる、でも余分なレジスタを犠牲にする、つまりより多くの記憶を要するわけです。

パイプラインレジスタ挿入とフィードバックループ

一歩下がって、なぜチップ全体を同期する必要があるんでしょう。Factorioか何かをプレイすることを想像すると、グローバルなクロックサイクルなんてありません。ものは終わったときに終わっているだけです。プレート上に鉄があって、欲しければ取れる。

その喩えを取ると、気をつけなければならないのは、ある論理を通る二つの異なる経路がある場合です。ここで計算fを、ここで計算gをやって、それらが計算hのために合流するとしましょう。製造上のばらつきがあります。あるチップではfが少し長くかかり、別のチップではgが少し長くかかる。信号が伝播していって、fとgからの結果がhで合流しなければならないとき、起こりうる問題は、fが早く着いてgの前の値、あるいは次の値と出会ってしまうことです。

ああ。hはいつ始めるべきか、次の反復がいつ来たかを知る必要がある……。

その通りです。これが、同じプロセスノード、同じTSMCの技術で作られた異なるチップが、異なるクロックサイクルを持ちうる理由を説明します。3ナノメートルで作られた二つのチップが、どの単一のクリティカルパスもチップ全体のクロックサイクルを遅くするほど長くならないように最適化できたかどうかに基づいて、異なるクロックサイクルを持ちうるんです。

その通りです。ここで示したこの最適化はパイプラインレジスタ挿入と呼ばれます。パイプラインの真ん中にレジスタを挿入しました。これはクロック速度と面積の純粋なトレードオフです。それが簡単なケースです。もっと難しいケースもあります。私は論理のパイプラインを描き出しましたが、他のケースでは、計算が実際に自分自身に戻ってくることがあります。何らかの関数fを実行して、それから自分自身に書き戻す。たとえばこれは、毎クロックサイクルに数を足していく加算かもしれません。この小さな回路は本質的に、異なるクロックサイクルで提示されるすべての数を合計するんです。課題は、もしこのプラスが長くかかりすぎたら、何ができるか。真ん中にパイプラインレジスタを置こうとすると、行われる計算が変わってしまう。入ってくるすべてのものの累積和を作る代わりに、実は二つの異なる累積和ができてしまうんです。偶数の累積和と奇数の累積和になってしまう。

この制約、つまり論理の中にループがあるという、どんなチップにもどこかにあるものですが、これが対処するのが最も難しいもので、クロックサイクルを決めます。

なぜそれが問題になるのか理解できません。そこにレジスタがあるというのが何を意味するのかさえよくわからない。一種の不可分操作なんですか。

いや、プラスは実は不可分ではありません。あなた自身がさっき示した通り、合計をするのには多くの作業が必要でした。その作業の前半部分を取って、真ん中にレジスタを差し込んで、それから作業の後半部分を取れるんです。

なるほど。

TSMCはPDKを提供していて、チップで与えられる論理の基本要素を規定しています。どの基本要素も、彼らがプロセスノードで狙うクロックサイクルより大きくならないように決めるのは彼らの仕事です。でもそれ以外は、TSMCからすべての基本要素を取ってきて、望むクロックサイクルに達するまで、その間に必要なだけレジスタを足していけばいいんじゃないですか。

論理設計者として、チップアーキテクトがクロックサイクルを設定します。たとえば、TSMCから得る基本要素はANDゲートや全加算器の規模のものです。電圧やどのライブラリを選ぶかに大きく依存しますが、一般的に一クロックサイクルにこれらをだいたい10個、20個、30個ほど直列に並べられます。これらの基本要素はとても速く、たぶん10ピコ秒です。論理設計者として、原理的には、レジスタとANDゲートをループにしただけなら、とんでもなく速いクロック速度、4ギガヘルツ、5ギガヘルツ、6ギガヘルツを超えるものが得られます。でもこのとても単純な回路を取って、ここで費やしている面積を見ると……これはサイズで1ゲート等価と呼ばれます。面積の単位が1ですね。こいつはたぶん面積の単位が8です。またしても、コストのほとんどが、実際の論理に比べて同期コストや通信コストになってしまう。これは行きすぎたケースでしょう。クロック速度をとても速くしたけれど、面積のほとんどをパイプラインレジスタに費やすという犠牲を払った。

なるほど。つまり、とても速いクロック速度を持てるけれど、たいして処理が進んでいないというダイナミクスを示唆しているんですね。低レイテンシだけど低スループットになりうる。

実際、スループットを損ないます。チップのスループットは、一クロックサイクルあたりどれだけ処理できるか、これは面積効率に基づきますが、それと毎秒何クロック得られるかの積だからです。

これは前回バッチサイズについて話していたことととてもよく似ていますね。バッチサイズが低いと、どの一人のユーザーも次のトークンをとても速く受け取れるけれど、たとえば一時間に処理されるトークンの総数は、本来可能だったよりも低くなる。

その通りです。クロック速度をとても高く駆動すると、引き出せる並列性が少なくなるんです。

言語モデルは、最高の人間の予測者と競い始めています。私はJane Streetのシニアな二人、ロン・ミンスキーとダン・ポンテコルボに座って話を聞いて、こう尋ねました。ある時点で、AIはただJane Streetがやることをやるようになるのか、と。私たちが真剣に受け止めるべき世界があります。つまり、地球上のすべての人間より厳密に賢く、すべての認知タスクでより有能な大規模言語モデルや他のAIシステムを作ることになる世界です。トレーディングは特に、私にはある意味でAGI完全、NP完全のようなものに感じられます。結局のところトレーディングは、ものの価値を見極めることを含み、それは未来についての予測をすることを意味するからです。Jane StreetはAIに賭けないわけではありません。彼らは60億ドルの計算契約を結んだばかりです。でもロンの見方は、エッジは動き続けるというものです。私は今日ほど、もっとエンジニアやトレーダーを雇いたいと切望したことはありません。まだ自動化の仕方がわからない他の難しい部分という、いつものことがあって。結局それが、競争上のエッジが存在する場所になるんです。これらの求人を見つけて、フルインタビューを視聴するには、janestreet.com/dwarkesh へどうぞ。

FPGAとASICのビジネス上の使い分け

Jane StreetのFPGAエンジニア、クラークと話したのを覚えています。彼は前回一緒にやったインタビューの準備を手伝ってくれました。なぜFPGAを使うのかを説明してくれていました。高頻度取引では、スループットよりもレイテンシのほうが重要でしょうから、クロックサイクルを決定論的にとても具体的に制御できることが最も重要なんだろうと想像します。なぜそれをASICで実現できないのか、あるいは高頻度取引で決定論的なクロックサイクルを得るためになぜFPGAを使うのか、話すと面白いかもしれません。

FPGAとASICのビジネス上の使い分けを考えてみましょう。FPGAとASICはおおむね同じ概念モデルを使います。AND、OR、XORといった小さな基本要素から作られた一連のゲートがあって、固定のクロックサイクルで動くワイヤーでつなぎ合わされている。FPGAで表現できることは、ASICでも表現できます。ASICのほうがFPGAより約一桁安く、エネルギー効率もいい。トレードオフは、最初のFPGAは1万ドルかかるのに対して、最初のASICを作るには3000万ドルかかることです。テープアウト全体が必要だからです。FPGAのビジネス上の使い道は、とても決定論的なレイテンシ、速い実行時間、高い並列性を持つものが欲しいけれど、ワークロードを頻繁に、たぶん毎月変えるつもりで、毎回そのテープアウトコストを払いたくないときです。

ルックアップテーブルの仕組み

FPGAは実際どうやって、固定されたハードウェアの中でASICのプログラミングモデルをエミュレートするんでしょうか。

その核には、今話した二つの構成要素があります。記憶デバイスとしてのレジスタと、すべてのゲートを提供するルックアップテーブル(LUT)です。それから三つ目の構成要素があります。これらのレジスタとLUTの群れがあって、それらが大量のマルチプレクサの集合でつながれている。これら一つひとつの前に、他のあらゆる場所から入力を選ぶマルチプレクサがある。これらすべてに送り込まれるたくさんの異なる選択肢があるわけです。これが可能にするのは、本質的に、FPGAをプログラムするとき、これらすべての構成要素を取って、このLUTを通り、別のLUTに送り込み、このレジスタに送り、それからまた別のLUTに送り込む、といった特定の配線を重ね合わせられることです。私がオレンジで描いたのは、どうやって……FPGAはField-Programmable Gate Array(フィールドプログラマブルゲートアレイ)の略です。オレンジはフィールドでプログラムされたもので、白はそもそもデバイスを実際に作るためにFPGAに存在しなければならないすべてのワイヤーです。

フィールドでプログラムされるとはどういう意味ですか。

フィールドでプログラムされるというのは、デバイスがデータセンターに配備されているという意味です。世の中に出て据え付けられていて、そこにやってきてプログラムできる。

ああ、電界というときのフィールドではないんですね。世の中の現場という意味のフィールドか、なるほど。

フィールドプログラミングが最初のルックアップテーブルから出てきて二つ目に入っていく様子を見ると、それはどう機能するんでしょう。それを実現するワイヤーはどこにあるんですか。

これら全部を描くのが少し面倒になってしまいました。ここのどのデバイスも、前にマルチプレクサが置かれていて、それが利用可能な近くの回路すべてから選べるんです。FPGAの実際の構成は、このマルチプレクサの制御に行き着きます。このマルチプレクサには、データ入力と、選択を行う制御がある。これら一つひとつのマルチプレクサの隣に、ここから入力を取ってこい、と言う小さな記憶デバイスが置かれている。それをプログラムするというのは、これら一つひとつのマルチプレクサを構成することなんです。

なるほど。ルックアップテーブルの中では何が起きているんですか。

ルックアップテーブルも、何をすべきかを伝える少しの制御を持つことになります。その目的は、ANDゲート、ORゲート、XOR、あるいはそれら異なるもののどの役割でも構成可能に担えることです。それをやる方法はいろいろ考えられます。伝統的なFPGAでやられている方法は……ルックアップテーブルは4ビットの入力と1ビットの出力を持ちます。4ビットから1ビットへの関数はいくつあるか。16個の異なる関数があります。これを16個の異なる数として表にできる。0111001……という16エントリの表です。この表がこの青い構成ビットに格納される。それはこの4ビットを二進数とみなし、表の該当する行を引いて、そのビットを出力するんです。これが本質的にルックアップテーブルの真理値表的な見方です。

なるほど、ANDゲート、ORゲート、NORゲート、XORゲートを考えると、これらはどれも入力として……。

それらは2入力の関数です。ときには3入力の関数、たとえば3入力XORや4入力XORもあります。この場合、それはただ大きさ次第なんですか。

LUTの典型的なサイズは4入力です。いわばちょうどいい塩梅なんです。ここにも計算対通信のトレードオフがあります。入力が少なすぎると、より多くのLUTを使う必要がある。

基本的にルックアップテーブルは真理値表です。真理値表があれば、好きなゲートをどれでもプログラムできる。だからルックアップテーブルの代わりに、プログラム可能なゲートだと考えればいいんですね。

その通りです。

FPGAがASICより一桁高い理由

ここでできることの一つとして、FPGAがASICより一桁高いという経験則がどこから来るのかが見えてきます。このルックアップテーブルの中にいくつのゲートがあるかを数えるんです。このルックアップテーブルは本質的に、あのマルチプレクサの一つとみなせます。16個の異なる値の中から選ばなければならないので、n=16個の選択肢で p=1 ビットのマルチプレクサです。さっき見たように、この回路は n×p 個のゲートのコストがかかる。だから np、つまり16個のANDゲート、それに16個のORのコストがかかります。

この回路というのはマルチプレクサのことですか。

その通り、マルチプレクサです。

ルックアップテーブルに入っていくマルチプレクサですか。

ルックアップテーブルそのものは、16行すべてから一つの出力へと選ぶ大きなマルチプレクサだと考えられます。それがルックアップテーブルです。

でも、ここでのあなたの描き方だと、マルチプレクサがあって、それからルックアップテーブルがありますよね。

マルチプレクサがどこまでも続いているんです。この中にある二つ目のマルチプレクサがあります。このマルチプレクサがこのマルチプレクサです。そしてもう一つのマルチプレクサはただ……このゴチャゴチャしたゲートの中のどこから来たかを言っているだけです。

そうですね、そして二つ目のマルチプレクサは、よし、これで一つの値を得たけれど、その値はまだ4ビットの値だ、と。

ええ、私はそのスープから4ビットを選びました。それからその4ビットを使って、ルックアップテーブルのどのエントリを使うかを選ぶんです。

最初のマルチプレクサで、近くの8個のレジスタから入力を引いてくるとします。それは合計32ビットが入ってくることになる。そのうち、4ビットが出てくる。その4ビットがルックアップテーブルの中にある二つ目のマルチプレクサに入っていく。

この場合、これらのレジスタは1ビットのレジスタです。近くに8個のレジスタとルックアップテーブルがあれば、近くに合計8ビットが入ってくる。私は8個から4個の異なる値へと選ぶ。実は四つの異なるマルチプレクサがあって、これらの入力ビットそれぞれに小さなマルチプレクサが対応している。それぞれが8個から一つを選んでいる。

その8個はどこから来ているんですか。

近くのレジスタや他のLUTからです。各レジスタは1ビットです。

ええ。AMDかこれらのFPGAを作る誰かが、どのレジスタがどのレジスタにつながっているかについて、依然として方針を持たなければならないんでしょうね。実際のゲートはプログラムできるけれど、接続のワイヤーは彼らが足す……通信トポロジーですよね。

局所的な粒度で柔軟性が得られます。選べる近くの近傍があるけれど、より粗い長距離の接続については、彼らが方針を決めるんです。

なぜ10倍遅いのか

それで、なぜ10倍遅いんですか。

このルックアップテーブルを作るコストを見ると、32ゲートです。それで、たとえば面白い例として、4入力ANDゲートの等価物が得られる。4入力ANDというのは、ANDして、ANDして、それからANDのANDをすることです。これはASICなら3個のANDゲートを使って直接実装できる回路です。LUTを使えばこれも実装できますが、3個ではなく32ゲートかかってしまう。

つまりオーバーヘッドは、真理値表を、入力のあらゆる可能な組み合わせを列挙するよりも簡潔に記述する方法がある、という事実から本当に来ているんですね。その方法とは、ただゲートを書き出すこと。

ええ、ポリシリコンとワイヤーなどを配置することです。

なるほど。あなたが私に指摘してくれた重要な点の一つは、彼らがCPUよりFPGAを好む理由は、決定論的なクロックサイクルが得られるからだ、ということでした。パケットがいつ入ってきていつ出ていくかがわかる。なぜそれがCPUでは保証されないんですか。

実は決定論的なレイテンシを持つCPUを設計することもできます。実際、多くのAIチップの中のプロセッサも決定論的なレイテンシを持っています。Groqはこれを宣伝しています。TPUもコアの中にそれを持っています。課題は、決定論的なレイテンシと高速を同時に得ることなんです。非決定論的なレイテンシは、CPUの特定の設計上の選択から来ます。実はそれらの設計上の選択を取り除いて、決定論的なレイテンシを持つCPUを作ることは可能なんですが、そういうものは市場であまり魅力的ではないので、もう誰もそういうCPUを作らないんです。ある意味で、決定論的なレイテンシはより単純な出発点で、一部のチップ設計者がそれを非決定論的にするためのものを加えてきたんです。

キャッシュとスクラッチパッド

具体例を挙げると、CPUでの非決定論の最も重要な原因は、おそらくCPUキャッシュそのものです。CPUには、CPUダイそのものがあって、それから脇にDDRメモリがある。その中に、DDRへの最近のアクセスを覚えて格納するキャッシュシステムがある。CPUの命令を実行していくとき、メモリにアクセスする命令があるたびに、まずそのデータがキャッシュに格納されていたかどうかを確認します。なければ、DDRから取ってくる。これは大きな最適化です。キャッシュはDDRより二桁速い。もしキャッシュをまったく使わなければ、基本的にすべてのプログラムが100倍遅く動くことになります。キャッシュの存在は、CPUが妥当な速度で動くために絶対に必要なんです。でもキャッシュヒットが得られるかどうかは、CPUの周囲の環境次第です。他にどんなプログラムが動いているか、最近何が動いたか、キャッシュシステムの中の乱数生成器が何をしているか。それがCPUの実行時間における非決定論の大きな原因です。それがCPUのメモリシステムです。

違うやり方でできる大きなことは、ハードウェアが「メモリを読む」と言ってハードウェアがキャッシュから来るかどうかを決める代わりに、この決定をソフトウェアに焼き込む、という別の設計思想です。たとえばTPUでこれが見られます。同じ図を描きますが、これをスクラッチパッドと呼びます。主な違いは……これがTPUで、この場合DDRではなくHBMがありますが、それでもオフチップのメモリです。ソフトウェアが「まずメモリにアクセスする」と言ってハードウェアに決めさせる代わりに、スクラッチパッドに行く一種類の命令と、HBMに行くまったく別の種類の命令を持つんです。このスタイルは一般的にキャッシュではなくスクラッチパッドとして知られています。鍵となる区別は、「スクラッチパッドを読む・書く」という一種類の命令と、「HBMを読む・書く」というまったく別の命令を持つことです。

スクラッチパッドがキャッシュにあたるわけですね。

ええ、ここのこれがスクラッチパッドです。

ノイマン型アーキテクチャと、CPU・GPUの違い

ぐっと一歩下がって、コンピュータはノイマン型アーキテクチャを持つとよく言われます。情報を直列に処理するという。並列アクセラレータについて話してきたからかもしれませんが、FPGAは超並列です。AIアクセラレータ、TPUも超並列です。CPUですら、持っているすべてのコアを考えれば超並列です。どういう意味で現代のハードウェアは実際にノイマン型アーキテクチャなんでしょうか。それは現代のハードウェアを表す妥当な言い方なんですか。

CPUを表すには妥当な言い方だと思います。CPUで得られる並列性の量は、約100コア×たぶん16並列のベクトルユニットで、CPU上で約1000並列です。

一つ質問です。CPUに使われているダイがあって、スレッドが少なければ、トランジスタの電圧がオンオフで切り替わるという物理的な観点で、文字通り一つの制御フロー、ダイのほんの一部で電圧がオンオフ切り替わっている、ということなんですか。CPUのダイ面積を実際にどう占めるのか……コアがそんなに少ないなら、ダイ全体に何を費やしているんですか。そこで何が起きているんでしょう。

コアがただずっと大きくて複雑なんです。ダイの100分の1を占めるCPUコアと、LUTを比較してみましょう。LUTはたった16ゲートです。CPUのコアよりFPGAのLUTのほうがずっと多い理由は明らかですよね。でもなぜたとえばCUDAコアはCPUコアより多いのか。CPUとGPUの違いは何か。CPUの中で、面積の大きな用途の一つはキャッシュです。実際にALUなのはごくわずか。ほとんどは論理ユニットではなくこれらのレジスタファイルです。その両方はGPUにも等価物があるので、大きな違いではありません。でもGPUに等価物がないのが分岐予測器です。CPUには、次の分岐がいつで分岐先がどこかを言う予測器の塊が、大きな一区画あるんです。その多くを取り除き、さらにこれらのレジスタファイルをよりタイトにすることが、CPUに対するGPUの利得の多くを駆動しています。

分岐予測器の目的は何ですか。両方の分岐を一度に実行すること、それとも何をするんですか。

問題は、一連の命令があって、分岐があると、命令を処理する実際のステップにとても長い時間がかかることです。たぶん5ナノ秒かかる。分岐があると気づき、ブール値が真かどうかを評価し、プログラムカウンタを新しい分岐先に更新し、それから命令メモリから読むまでに、5ナノ秒かかりうる。だから実際にはこれはずっと下のここで終わるかもしれない。私は5ナノ秒が許すよりずっと速いクロック速度で動かしたい。5ナノ秒は200メガヘルツのクロック速度です。私は1ギガヘルツか2ギガヘルツで動かしたい。だから分岐が評価されている間に他の命令を動かす必要がある。私の後に来る命令をただ動かし続けたい。でもそれは間違っていたかもしれない。もし分岐が結局取られたなら、これらの命令を評価する代わりに、実は分岐先がどこであれそこに飛んで、その代わりにそれらの命令を動かす必要があると知らなければならない。分岐予測器の目的は、その命令にたどり着く前に、5サイクル早く、分岐が起きると予測することなんです。

脳とチップの違い

脳がどう働くかと、あなたがここで説明していることを考えると、高いレベルでの違いはこうかもしれません。これらのアクセラレータでは構造化されたスパース性を使って、本来ゲートに割り当てなければならなかった面積を節約できますが、脳には非構造のスパース性がある。どのニューロンも他のどのニューロンともつながりうるし、列をそろえるような形でつながるわけでもない。それから、メモリと計算が同じ場所にあるという事実もあります。もっとも、ある意味でこれらのダイでもメモリと計算が同じ場所にあると言えるかもしれませんが。

これがまさに、ある意味でのメモリと計算の同居です。だからそれは大きな違いではないかもしれません。

もう一つの大きな違いは、脳のクロックサイクルがコンピュータよりずっと遅いことです。一つにはエネルギーを節約するためで、クロックサイクルが速いほど、信号が落ち着いてトランジスタがどの状態にあるかを識別するために必要な電圧が大きくなるからです。

その通りです。

脳がチップの働き方に対して何をしているかについて、何かコメントがあれば。

まずクロック速度を取りましょう。チップではクロック速度がかなり高い。それがより高いスループットを駆動するからです。GPUが何らかのワークロードを実行するとき、それはバッチサイズ1000で動いています。一方、脳はバッチサイズ1000で動いていません。私は一人しかいない。GPUを取って、ギガヘルツで動かす代わりにメガヘルツで動かそう、と言うことを想像できて、それは脳について話しているような等価なものに少し似てき始める。でもシリコンの働き方では、それはエネルギー効率で1000倍の優位を与えてくれません。結局どうなるかというと、この回路を一度だけ安定するまで動かして、それから長い間アイドル状態で座っている、という見た目になります。アイドル状態の間はあまりエネルギーを消費しません。エネルギーのほとんどはビットを0から1へ、また戻すトグルで消費されるからです。

このような回路のエネルギー消費について話しましょう。ビットが格納される様子は、チップのどこかに暗黙的に存在するコンデンサに電荷を堆積させた、と考えればいいでしょう。ビットが1になると充電され、それから次に0になると放電される。コンデンサを充電して、それからその電荷を接地に捨てるそのサイクルが、エネルギーが消費される場所です。これは動的電力、つまりスイッチング電力と呼ばれ、チップのエネルギー消費の大半を占めます。絶縁体が完璧でないという事実から来る他のエネルギー消費もありますが、それは無視しましょう。エネルギー消費の大半は0から1へ、また0へ戻すトグルから来ます。チップをずっと遅く動かして、1000クロックサイクルごとに一度だけクロックすれば、遷移の回数が1000分の1になる。エネルギー消費は約1000分の1になります。でもそれはエネルギー効率において大した優位ではないんです。

GPUとTPUの高レベルな違い

なるほど、TPUがどう働くかを高いレベルで説明してもらいました。GPUとTPUの働き方の高いレベルでの違いは何でしょうか。

異なる高レベルの組織化原理があって、それからコアの中も異なります。高いレベルを見て、GPUとTPUを取って、最上位のブロック構造がどう見えるかを見ましょう。これをそれぞれの場合のチップ全体だと考えると、GPUの組織はほぼ同一のユニットの集まりで、それがSMです。真ん中にL2メモリがあって、それから下にこれらのSMがもっとたくさんある。だからかなり規則的なコアの格子があるわけです。比較してTPUを見ると、ずっと粗い粒度の論理ユニットになります。大きなシストリックアレイである行列ユニットがほんの数個。真ん中に何らかのベクトルユニットがあって、それから下に行列ユニットがある。真ん中にベクトルユニットを挟んだこれらの行列ユニットが、TPUチップ全体を構成します。これを、より小さな行列ユニットとより小さなベクトルユニットを持つ、本当に小さなユニットへ縮小していくと考えられて、それがいわばSMなんです。とても高いレベルの観点からは、GPUはチップ全体にわたってタイル状に並べられたたくさんの小さなTPUを持っているわけです。

ああ、面白い。ストリーミングSMの中のTensorコアがMXUに相当すると示唆しているんですね。

ええ、すべてとても似ています。

なるほど。構造の欠如がもっとあれば、たくさんの小さなTPUを持つことはとても理にかなっています。一方、ただ巨大な行列乗算があるなら、個々のSMにそれぞれのレジスタやワープスケジューラを持たせるコストを避けたいかもしれない。なぜただ巨大なものを一つ作って、そのコストを全体にわたって償却しないのか。

これはものをどれだけ大きく成長させられるかに現れます。特にシストリックアレイでこのテーマを見てきましたよね。より大きなシストリックアレイはレジスタファイルのコストをよりうまく償却する。この設計はより大きなシストリックアレイを持つことを可能にしますが、GPUの設計はすべてを小さなユニットにすることを強います。でもトレードオフがあります。このものの粗い粒度の分離のせいで、ベクトルユニットから行列ユニットへ、ここのたった二本の周縁の線を通して、大量のデータを動かす必要があるんです。GPUの等価物を見ると、いたるところにベクトルユニットがあって、多くの異なる線を通してデータを動かせます。ベクトルユニットと行列ユニットの間で動かせるデータの量は、実はTPUよりGPUのほうがずっと多い。すべてのデータをたった二本の線を通して動かさなければならない代わりに、GPUでは16本の配線の線を通して動かしているんです。

なるほど。でも、より小さな面積を横切ればいいだけかもしれない。それもエネルギーの節約になりますね。だから完全に一つのSMの中で動作できれば、データ移動はずっと小さい。でも複数のSMにまたがって動作したい瞬間に、より複雑で高価になる。

その通りです。

コメントしなくてもいいですが、MatXが試みようとしているかもしれないことの一つは、SRAMに囲まれたシストリックアレイというGPU的な小さな構造を得つつ、同時に、CUDAアーキテクチャを支えるためにSMに必要で多くの空間を取るものを捨てられるようにする、ということだろうと予想されます。

私たちは、分割可能なシストリックアレイと呼ぶものについて公に話してきました。それはある意味で、小さなシストリックアレイにもなれる大きなシストリックアレイだと考えられます。

いいですね。さて、ここで締めくくるのにいい区切りだと思います。ライナー、本当にありがとうございました。

ありがとう、ダルケシュ。

コメント

タイトルとURLをコピーしました