Transformer の風景

後編──Transformer を探る

前回の風景に戻る

前回、僕たちは LLM を、一つの山を登るケーブルカーとして眺めました。

技術の言葉は極力さけて。けれど、あの風景の一つひとつは、実は機械の中で起きていることに、きちんと対応しています。

今回は、その対応を、一つずつ種明かししていきます。

まず、前回の風景を、ざっと思い出しておきましょう。

山には、上り線と下り線の、二本の軌道がありました。

ゴンドラは一つ。乗客たちが、自分の座席番号の順に座って、その一つの箱に乗り込みます。

ゴンドラが駅に着くと、乗客は全員いったん降りて、いくつもの改札をいっせいに通り、ホームでわいわいおしゃべりをして、互いのことを少しずつ受け取り、自分の姿をほんの少し変えて、また順番に席へ戻る。

これを、百ほどの駅でくりかえす。そして頂上で、次に来るべき乗客が決まりました。

駅には、それぞれ駅長がいました。

駅長は、その駅に住み着いて、乗客が改札を通るたびに、どんな札を持たせるかを按配します。

この「どんな乗客に、どんな札を持たせるか」という駅長の判断の癖を、僕たちは流儀と呼びました。

そして、駅長はもう一つ、手元にタブレットを持っていて、通った乗客の札を控えておく、とも話しました。

このタブレットは、技術で言えば、多くの実装で使われている、推論を速くするための工夫にあたります。

過去の乗客の計算を、毎回そっくりやり直さずにすませる、いわば備忘の控えです。

必ずそうしなければならない決まりではなく、そうすると速くなる、という性質の工夫だと思ってください。

それから、二本の軌道の使い分けも見ました。

山がまだ何も知らなかったころ、一本の文章、たとえばリス・うさぎ・しか・くま・ツキノワと並んだ一行を、いっぺんに乗せて一回登らせる。

頂上で答え合わせをして、乗客たちが、上り線を逆に各駅停車で降りながら、各駅長の流儀を、ほんの少しずつ彫り直す。これを気の遠くなるほど、無数の文章にわたってくりかえして、山は賢くなりました。

これが学習でした。

仕上がった山を、ただ使うのが推論。

このときは、頂上で次の乗客を決めたら、ゴンドラは特急の下り線を直行で麓まで降り、新しい乗客を迎えて、また登る。

そして、前回たどり着いた、いちばん大事な発見も、思い出しておきたいのです。

主役は、動くケーブルカーではありませんでした。駅があり、駅長がいて、その流儀が刻まれた、動かないお山のほうでした。

賢さは、流れて消えるケーブルカーにではなく、山に住み着いた駅長たちの流儀に宿っていたのです。

さて、前回は区切りの良いところで筆を止めました。

けれど、思い返してみると、開けずに残した箱が、いくつもあります。

駅長の流儀とは、具体的には何なのでしょう。

乗客が改札で受け取る札とは、何なのでしょう。

ホームでのおしゃべり、つまり誰の声を重く聞くかを決めるあの相談は、駅の中で、実際にどんな計算をしているのでしょう。

そして、乗客たちが下り線で「流儀を直す」とは、何を直すことなのでしょう。

前回は、これらを「そういうもの」として、絵の中に置いておきました。

今回は、その箱を、一つずつ開けてみます。

しかも、お見せするのは、教科書の図でも、借り物でもありません。

僕自身が、2017 年の原論文をたどって書き、実際に動かしたプログラムです。

正直に申し添えれば、注意の計算そのものなど、いくつかの部分には、用意された既製の部品も使っています。

すべてを一から自前で書いたわけではありません。

それでも、論文の筋をたどって自分で組み立て、現に動かしてみた。だから、これは魔法ではありません。

中を開ければ、出てくるのは、掛け算と足し算を中心とした、見通しのきく計算ばかりです。それを、ご一緒に確かめたいのです。

一つだけ、前回からの橋渡しをしておきます。

僕が書いたのは、2017 年の原論文の構成、すなわち翻訳のための Transformer です。原論文には、Encoder と Decoder という二つの翼がありました。今の ChatGPT や Claude は、このうち Decoder という片翼の仕組みを中心に、巨大に育て、発展させたものにあたります。

ただし、現代の商用モデルは、原論文そのものではありません。数えきれない改良や、追加の訓練が、幾重にも重ねられています

ここでお見せするのは、あくまでその大もとにある、骨格のほうだと思ってください。

けれど、今回いちばん開けたい箱、すなわち駅の中のおしゃべり、Self-Attention の仕組みはどちらの翼にも共通する心臓部です。

原型で分かれば、現代の LLM にも、そのまま通じます。

一つ、断り書きを添えておきます。この連載の比喩は、いま述べたとおり、2017 年の原論文を土台にしています。土台を原型に置いたことの意義と限界です。

意義は、Attention という心臓部を、いちばん正確な原典に当たって掴めること。この心臓部は、現代の生成モデルでも、同じものが動いています。

限界は、原論文が翻訳のための Encoder と Decoder の二翼構成であるのに対し、現代の生成モデルは Decoder の片翼だけを、最初から一方向(先に済んだ方だけを見る形)に組み替えて、巨大に育てたものだ、という点です。僕のこの記事では、その一方向性を、前編の「先に済んだ方だけを見る」という決まりごととして補い、橋を架けました。ですが、原型から現代の生成モデルへ、構造がどう組み替えられていったのか、その分岐そのものは、それだけで独立した主題に値します。いつか、別の場所で、じっくり書いてみたいと思っています。

なお、ここでお見せするプログラムの現物は、僕の GitHub に置いてあります(https://github.com/koichikamachi/attention-transformer-reproduction)。この分野に親しみのない方には、なんのこっちゃ、でしょう。しかし、中を確かめてみたい方は、どうぞご覧ください。

では、僕たちは、ここで、山を一望するところから、始めましょう。

山を一望する

登山ケーブルカーの全体が、どう書かれているかを見ておきましょう。

前回「主役は山だ」と述べたことが、コードの上では、驚くほど素直に表れているからです。

僕は、山の全体を、Encoder という部品として書きました。その中心は、次の数行です。今は、細かい記号を読む必要はありません。形だけ眺めてください。

class Encoder(tf.keras.layers.Layer):
    def call(self, x, training=False, mask=None):
        x = self.embedding(x)                      # 乗客に姿を与える
        x *= tf.math.sqrt(...)                     # 姿の大きさを整える
        x += self.pos_encoding[:, :seq_len, :]     # 席番号を加える
        for layer in self.enc_layers:              # 駅を一つずつ通る
            x = layer(x, mask=mask)
        return x

注目してほしいのは、最後から二行目、for layer in self.enc_layers: という一行です。

x が、乗客です。self.enc_layers が、駅を順に並べたものです。乗客 x が、駅(layer)を一つずつ通り、そのたびに姿を変えて(x = layer(x))、次の駅へ進む。これが、ゴンドラが上り線を登り、駅から駅へと進んでいく、まさにその姿です。麓で乗客に姿を与え、席番号を渡し、あとは駅を順に通していくだけ。前回の風景が、そのままコードになっています。

ここで、その x とは、いったい何なのかを、すこし開けておきましょう。これが分からないと、あとの計算が宙に浮いてしまいます。

前回、乗客一人の姿は、数個の数字でできている、と話しました。たとえば四個なら、[2, 1, 0, 0] のような並びです。x とは、その姿を、乗客の人数だけ縦に積み重ねた、一枚の数表だと思ってください。しかが一行目、うさぎが二行目、くまが三行目、というふうに。横に並ぶのが、一人ぶんの姿の数字。縦に並ぶのが、乗客たちです。つまり x は、ゴンドラに乗っている乗客全員の姿を、まとめて一枚にした表なのです。

すると、x = layer(x) という一行が、何をしているかも見えてきます。これは、この数表を一枚、受け取って、中の数字を少し書き換えた、新しい数表を返す、という意味です。駅を一つ通ると、表の中の数字が、おしゃべりの分だけ、少し変わる。乗客の人数、つまり行の数は、変わりません。各乗客の姿、つまり横の数字が、少し更新されるだけです。だから、何行目が誰か、という並びは保たれたまま、中身だけが書き換わっていく。for layer in self.enc_layers: のループは、この「数表を受け取っては、中身を少し書き換えて、次へ渡す」を、駅の数だけくりかえしているのです。

(この数表が、プログラムの中で実際にはどんな形をしているのか、もう一歩踏み込みたい方は、巻末の補遺をご覧ください。)

それから、最初の三行も見てください。前回、乗客は二つのものを持って乗る、と話しました。一つは、自分が何者であるか。もう一つは、自分が何番目に座るか、という席番号です。その二つが、ここにあります。self.embedding(x) が、乗客に姿を与える工程。x += self.pos_encoding が、その姿に席番号を加える工程です。前回、座席番号がのちに効いてくる、と予告しました。その席番号は、ここで乗客に渡されているのです。

そして、ここがいちばん大事なところです。この山の本体は、self.enc_layers という、駅の列が抱えています。各駅の中には、長い学習で刻まれた駅長の流儀が、詰まっています。前回「主役は山」と言いましたが、コードで言えば、それは、この駅の列に蓄えられたものなのです。

乗客は、通り過ぎるだけ。駅が、すべてを覚えている。

では、その駅の一つを、開けてみましょう。

一つの駅を、外から眺める

僕のプログラムの中で駅を一つ表す部品が、EncoderLayer です。これが、登山ケーブルカーの一つの駅にあたります。コードは、こうなっています。やはり、形だけ眺めてください。

class EncoderLayer(tf.keras.layers.Layer):
    def call(self, x, training=False, mask=None):
        # ① おしゃべり(Self-Attention)
        attn_output = self.mha(query=x, key=x, value=x, attention_mask=mask)
        out1 = self.layernorm1(x + attn_output)
        # ② 姿の手直し(Feed Forward Network)
        ffn_output = self.ffn(out1)
        return self.layernorm2(out1 + ffn_output)

(実際のコードには、これに学習を安定させるための dropout という処理が加わりますが、骨格は、この通りです。)

駅の中で行われていることは、たった二つです。

一つめが、①のおしゃべり。コードでは self.mha と書いた、一行です。mha とは Multi-Head Attention の略で、これが、乗客どうしが互いの声を聞く部分です。query, key, value という三つの言葉が、もう見えていますね。これが今日の主役で、後でひとつずつ開けます。

ここで、前回との対応を、一つ断っておきます。mha、すなわちマルチヘッド注意とは、前回述べた「各駅にいくつも並んだ改札」のことです。前回、乗客は自分の姿を分けて、いくつもの改札を同時に通り、改札ごとに別の観点でおしゃべりして、最後に合流する、と話しました。今日は、その全部をいっぺんに追うのではなく、そのうちの一つの改札の中だけを開けます。一つの改札の中で何が起きているかが分かれば、残りの改札も、同じことを別の観点でやっているだけだからです。

二つめが、②の姿の手直し。self.ffn と書いた部分です。おしゃべりで文脈を取り込んだあと、各乗客が、自分の姿を一人で整え直す工程だと思ってください。隣の声を聞いて受け取ったものを、自分の中で噛みくだく作業です。

そして、もう一つ大切なのが、この足し算です。

out1 = self.layernorm1(x + attn_output)

x が、駅に入る前の乗客の姿。attn_output が、おしゃべりで受け取ったもの。それを、足しています。

これは、前篇の、あの話です。

乗客は、おしゃべりを聞いたからといって、まるごと別人に入れ替わるのではありませんでした。

元の自分、つまり x に、受け取った分、つまり attn_output を、ほんの少し足し加えて、姿を更新する。だから、姿は変わっても、その人が誰であるかは変わらない。一番の席の人は、やはり一番の席の人のまま。前回「元の自分に足すだけ」と述べた、あの仕組みが、この一行です。

ここまでで、一つ分かることがあります。駅とは、思っていたほど複雑な装置ではありません。おしゃべりがあり、姿の手直しがあり、元の自分に足す。部品は、これだけです。

では、いちばんの核心、おしゃべりの中、一つの改札の中を、開けましょう。ここからが、今日の山場です。

おしゃべりの中を開ける

おしゃべりの中で、機械が実際にやっているのは、突きつめると、三枚の札に集約されます。

Query、Key、Value。前回、乗客が改札で受け取る、三枚の札として紹介したものです。

三枚の札意味
Query(クエリ)僕はいま、どんな相手を探しているか
Key(キー)僕は、どんな相手として探されうるか
Value(バリュー)僕から渡せる中身は何か

前回、この三枚の札は、駅長が按配して持たせる、と話しました。その「どんな乗客に、どんな札を持たせるか」という駅長の流儀が、技術では重みと呼ばれるものです。乗客の姿に、この重みを掛け合わせると、三枚の札ができあがる。駅長が長い学習で身につけた流儀の正体が、この重みなのです。

言葉だけでは、ぴんと来ないでしょう。だから、実際に数字を動かしてみます。電卓があれば追える程度の、足し算と掛け算しか出てこないことを、目で確かめてください。

舞台、三人の乗客

【図を後編_内積図.svg】

話を、できるだけ小さくします。

乗客は、三人だけにしましょう。「うさぎ」「しか」「くま」の三人に登場してもらいます。この三人は、いま学ばせようとしている、一本の文章に並んだ乗客です。ちょうど「明日 天気 は」といった一文があるのと同じように、ここには「うさぎ しか くま」という一文がある、と思ってください。そして、この文では、しかの次に、くまが来ています。ここで主役になってもらうのは、真ん中の「しか」です。しかが、この三人の中で、誰の声をいちばん重く聞くか。この文で、しかの次に実際に来ているのは「くま」ですから、しかは、その「くま」に、強く注目するはずです。それが、数で本当にそうなるかを、見ていきます。

前回、本物の乗客は、数百から数千個の数値でできた姿を持つ、と話しました。けれど、数千個では黒板に書けません。ここでは、一人を四個の数値で表すことにします。考え方は、数が増えても、まったく同じです。

まず、この「四個の数値」が、そもそも何なのかを、はっきりさせておきます。これは、乗客一人の姿を測る、四つの目盛りだと思ってください。一人の乗客を、四つの目盛りの読みで言い表したもの。それが、たとえば [2, 1, 0, 0] という並びです。一つ目の目盛りが 2、二つ目が 1、三つ目と四つ目が 0。乗客一人が、この四つの数で、まるごと表されている。こうした数の並びを、数学ではベクトルと呼びます。むずかしく構える必要はありません。「いくつかの目盛りを、順番に並べたもの」と思えば十分です。前回「乗客の姿は数個の数字でできている」と述べた、あの姿が、ここでは具体的に、この四つの目盛りを並べたベクトルなのです。今回は説明を小さくするために四つにしましたが、前回お話ししたとおり、本物のモデルでは、この目盛りは四つどころか、何百、何千と並んでいます。

各乗客は、改札を通るとき、駅長の流儀によって、この目盛りから、Query・Key・Value という三枚の札を持たされます。三枚の札も、それぞれ四つ(Value は三つ)の目盛りでできた、同じ仲間です。

ここで、いちばん大事な前置きをします。これから表に出てくる数字は、最初から、こんな値だったのではありません。

山がまだ何も知らなかったころ、駅長の流儀は、まったくのデタラメでした。だから、乗客に持たせる札の目盛りも、でたらめな数。当然、予測は大失敗します。そこで、その失敗の大きさ、いわば罰金を測ります。正解とどれだけ食い違ったか、という赤字です。そして、その赤字を、ほんの少しでも減らすには、どの目盛りを、どちらへ、どれだけ動かせばよいかを、逆算します。逆算した分だけ、すべての目盛りを、ほんのわずか、たとえば小数第三位を書き換える程度に、直す。予測して、失敗して、罰金を測って、ほんの少し直す。このサイクルを、何兆字という文章を使って、何億回も、くりかえす。

その果てに、目盛りは、ひとりでに、ある値へ落ち着いていきます。たとえば「しかとくまの札を、よく響き合う目盛りにしておけば、予測の赤字が出にくくなる」と分かれば、流儀は、自然とその方向へ落ち着く。これから見せる表の数字は、その、何億回ものサイクルの果てにたどり着いた、落ち着きどころなのです。「学習で仕上がった結果、ひとりでに決まった数の並び」とは、このことです。その逆算と書き換えの仕組みそのものは、山場のあとで、コードと結んで、もう一度ていねいに開けます。

「しか」という乗客に、注目しましょう。

ですから、一つ、お願いがあります。これから出てくる数字の、一つひとつの値に、いまは意味を求めないでください。「一つ目の目盛りの 2 は、何を表すのか」とは、考えないでいただきたいのです。これらは、いま述べたサイクルの果てに、ひとりでに決まった値で、人間が見て「これは天気を表す」というふうに、一つずつ読めるものでは、ありません。一つひとつの目盛りの意味を読み取ろうとすると、かえって迷子になります。

けれど、この数字が、死んだ飾りだというわけでは、けっしてありません。むしろ逆です。この四つの目盛りは、すぐ次の計算で、主役として働きます。簿記でいえば、いま貸借に記された数字が、あとで試算表や残高に効いてくるのと同じで、この目盛りも、これから内積という手続きの中で、ぴたりと生きてくるのです。だから、見ていただきたいのは、個々の値の意味ではなく、この数字たちが、これからどう働くか、その手続きのほうなのです。

乗客Query(探している)Key(探されうる)Value(渡す中身)
しか[2, 1, 0, 0][1, 0, 0, 0][9, 0, 0]
うさぎ[0, 0, 1, 1][0, 0, 0, 1][0, 0, 9]
くま[1, 2, 0, 0][2, 1, 0, 0][0, 9, 0]

くりかえします。値そのものに、深い意味を読み取る必要はありません。大事なのは、これから先の手続きです。

第一歩、内積でスコアを測る

「しか」という乗客は、自分の Query の札を持って、ほかの乗客、そして自分自身に、問いかけます。「僕がいま探しているのは、あなたですか」と。

この「どれくらい合っているか」を測るのが、内積という計算です。内積とは、二つのベクトルについて、同じ位置どうしを掛けて、すべて足し合わせるだけの操作です。言葉にすると少し難しく聞こえますが、やってみれば、ただの掛け算と足し算です。

「しか」の Query と、「くま」の Key で、実際にやってみましょう。

しかの Query : [2, 1, 0, 0]
くまの Key    : [2, 1, 0, 0]

2×2 + 1×1 + 0×0 + 0×0
= 4 + 1 + 0 + 0
= 5

5 という数が出ました。これが、「しか」が「くま」にどれくらい注目すべきかの、生のスコアです。同じことを、「しか」と自分自身、「しか」と「うさぎ」についても行います。

しかが問いかける相手内積(生スコア)
しか → しか(自分自身)2
しか → うさぎ0
しか → くま5

数が出そろいました。ここで、内積の振る舞いを、すこしだけ正確に見ておきましょう。内積は、同じ位置どうしを掛けて、足し合わせたものでした。ですから、二つのベクトルが、同じ位置にそろって値を立てていれば、その位置の積が効いて、合計は大きくなります。逆に、片方がその位置でゼロなら、掛けてもゼロ、その位置からは何も足されません。つまり、立っている場所がそろっているほど大きく、すれ違っているほど小さくなる。これが、内積という物差しの読み方です。

しか→くまが 5 と高いのは、しかの Query [2, 1, 0, 0] と、くまの Key [2, 1, 0, 0] が、最初の二つの位置で、そろって値を立てているからです。一方、しか→うさぎが 0 なのは、しかの Query が値を立てている最初の二つの位置で、うさぎの Key [0, 0, 0, 1] がゼロ、うさぎが値を立てている最後の位置で、しかの Query がゼロ。たがいに、立っている場所がすれ違っているからです。掛ければ、どの位置もゼロになり、足しても 0。だから、「しか」は「くま」に対してスコア 5 と際立って高く、「うさぎ」に対しては 0 なのです。

なぜ、しかの Query とくまの Key が、こうもそろって値を立てているのでしょう。ここで、駅長が、長い訓練で身につけた流儀を、思い出してください。山は、この一文だけでなく、無数の文章を浴びてきました。その中で、「しかのような乗客の次には、くまのような乗客が来やすい」という傾きが、駅長の流儀として、事後的に焼き付いています。だから駅長は、しかが来ると、くまの Key とよく響き合うような Query を持たせる。その結果、二つの札は同じ位置に値を立て、内積が大きく出て、しかはくまに強く注目することになるのです。この「来やすさ」は、どこかに規則として書かれていたのではなく、無数の文章から、ひとりでに、にじみ出てきたものでした。

もちろん、さきに述べたとおり、この響き合いは、最初からあったわけではありません。でたらめだった目盛りが、予測の赤字が小さくなるように、何億回も彫り直された果てに、いまのように響き合う札へと落ち着いた。その彫り直しの仕組みは、山場のあとで、コードと結びながら、ていねいに開けます。

第二歩、大きくなりすぎを抑える

ここで、一つ、調整が入ります。

生のスコアは、札の数値が大きかったり、札の長さ(数値の個数)が長かったりすると、どんどん膨らみます。膨らみすぎると、次の段階の計算が、極端になりすぎる。

そこで、スコアを、ある決まった数で割って、大きさをならします。割る数は、照らし合わせる札の長さ、すなわち Key の札の次元の平方根です。いまは Query・Key の札を四個の数値で表していますから、4 の平方根、すなわち 2 で割ります。

生スコア : [2, 5, 0]   (しか→しか、しか→くま、しか→うさぎ)
÷ 2     : [1, 2.5, 0]

なぜ「札の長さの平方根」なのか。ここには、確率・統計のきちんとした理由があるのですが、本筋から外れるので、巻末の補足に回します。今は「数が暴れないように、決まった数で割って整える、一手間」とだけ理解しておけば、先へ進めます。

(前回、各駅にいくつも改札がある、と述べたマルチヘッドを思い出してください。ここで割る札の長さは、姿全体の長さではなく、一つの改札に割り当てられた分の長さです。八つの改札に分ければ、一つの改札が扱う札は、姿全体を八等分した長さになります。今日は一つの改札の中だけを見ていますから、その改札の札の長さ、すなわち 4 で考えればよいのです。)

第三歩、割合に変える

いま手元にあるのは、[1, 2.5, 0] という三つの数です。

けれど、このままでは、注意の配分として使いにくい。僕たちが欲しいのは、「しか」が持っている注意を 100% として、それを三人に、どう振り分けるか、という割合です。

そこで、softmax という操作を通します。難しい名前ですが、やっていることは、「大きい数ほど大きな割合を与えつつ、全部を足すと 100% になるように変える」ことです。大小の順番は保ったまま、合計が 1 になる割合へと、ならす。

結果は、こうなります。

しかが注目する相手スコア(÷2 後)注意の割合(softmax 後)
しか → くま2.576.6%
しか → しか117.1%
しか → うさぎ06.3%

これが、おしゃべりの核心です。「しか」という乗客は、自分が持つ注意のうち、約 77% を「くま」に、約 17% を自分自身に、わずか 6% を「うさぎ」に振り分けることを、決めました。

前回の比喩を、思い出してください。各駅で乗客たちは、わいわいおしゃべりをする。ただし、やみくもに全員の声を同じ大きさで聞くのではない、と話しました。その「どれだけ重く聞くか」が、いま、77%・17%・6% という、具体的な割合として、目の前に出てきたのです。比喩で語ったことが、数字になりました。

第四歩、中身を受け取る

割合が決まれば、あとは簡単です。「しか」は、その割合に従って、各乗客の Value の札、つまり渡す中身を受け取ります。「くま」の中身を 77% 分、自分の中身を 17% 分、「うさぎ」の中身を 6% 分、混ぜ合わせます。

しかが受け取る新しい中身
=   0.171 × しかの Value [9, 0, 0]
  + 0.766 × くまの Value [0, 9, 0]
  + 0.063 × うさぎの Value [0, 0, 9]
= [1.54, 6.90, 0.57]

この [1.54, 6.90, 0.57] が、おしゃべりを終えた「しか」の、新しい中身です。

元は「しか」という、ただの一人の乗客でした。それが、いまや真ん中の数値、すなわち 6.90 が、大きく立ち上がっています。これは「くま」の成分です。「しか」という乗客は、おしゃべりを通じて、「次にくまを連れた、しか」へと、姿を一歩、変えたのです。

そして、思い出してください。前の駅で見た、あの足し算です。

out1 = layernorm1(x + attn_output)

これは、まさに、この新しい中身を、元の「しか」に足し戻す操作でした。乗客は、別人にはならない。「しか」のまま、「くま」の色を帯びる。

これだけです

【後編_四歩の流れ図】

後編_四歩の流れ図

いま見たものが、Self-Attention の、核心です。手続きを並べ直すと、こうなります。

手順やっていること比喩
① 内積Query と Key の札を掛けて足す僕が探す相手と、どれだけ合うか測る
② スケーリング札の長さの平方根で割って整える数が暴れないようにならす
③ softmax合計 100% の割合に変える注意をどう振り分けるか決める
④ 重みつき平均割合に従い Value の札を混ぜるその配分で中身を受け取る

掛けて、足して、割って、割合にして、また掛けて足す。骨格は、それだけです。たしかに、③の softmax のように、それ自体は少し込み入った計算も混じっています。けれど、一つひとつをほどいていけば、どれも掛け算と足し算を組み合わせた手続きで、見通しがききます。魔法の箱は、どこにもありません。

一つ、正直に申し添えておきます。いま見せたのは、Self-Attention の核心、すなわち一つの改札の中で、三人の乗客が札を照らし合わせる、その一部始終です。本物の山では、この核心のまわりに、いくつもの仕掛けが添えられています。前回お話しした、八つの改札に分ける仕組み(マルチヘッド)。後から来る乗客を見ないようにする仕切り(マスク)。何十もの文をいっぺんに通すまとめ処理(バッチ)。そして、これら全体を、大きな数表どうしの掛け算として、いっぺんに計算する工夫。それらは、この核心を、何重にも取り巻く飾りや段取りであって、中心で起きていることは、いま見た内積と割合計算なのです。このうち、まとめ処理や数表どうしの掛け算については、巻末の補遺で、もう少し触れておきます。

前回「駅長の流儀が、登ってくる乗客を導く」と比喩で述べたものの正体は、この内積と softmax による割合計算でした。そして、その計算の手綱を握っていたのは、駅長が札に込めた中身、すなわち学習で刻まれた流儀のほうです。計算の手順は、これだけ単純で、その単純な手順に意味を与えているのは、山に蓄えられた流儀のほうなのです。

そして、これが、約 100 の駅でくりかえされます。一つの駅で「しか」が「くま」の色を少し帯びる。次の駅では、その少し染まった「しか」が、また別の関係を計算する。これを百回。先ほどコードで見た、for layer in self.enc_layers: という、あの一行のループです。

一回いっかいは、いま見た電卓レベルの計算にすぎません。けれど、それが百層積み重なり、しかも各駅では八つの改札が同時に別の観点でこれを行い、数千個の数値の上で、数千の乗客に対して並行して行われると、人間の文章と見分けのつかない出力が、生まれてくるのです。

もう一本の柱、一人で姿を作り直す

ここまで、Self-Attention ばかりを見てきました。けれど、一つの駅の中には、実は、もう一つ、大事な仕事があります。前編で「おしゃべりのあと、姿を少し手直しして、席に戻る」と述べた、あの手直しのほうです。

Self-Attention が、乗客どうしの「あいだ」の仕事だったことを、思い出してください。互いの札を照らし合わせ、隣の中身を受け取る。他者との、混ぜ合わせでした。ところが、この手直しは、ちがいます。隣を見ません。各乗客が、たったひとりで、受け取ったばかりの自分の姿を、こね直すのです。Self-Attention が「他者と混ぜる」なら、こちらは「自分の中で作り替える」。外に向かう仕事と、内に向かう仕事。この二つが、一つの駅の中で、対になっています。

なぜ、わざわざ、ひとりで作り直す工程が要るのでしょう。混ぜ合わせるだけでは、いけないのでしょうか。

ここが、大事なところです。混ぜ合わせるだけを、どれだけくりかえしても、実は、単なる混ぜ合わせから、大きくは抜け出せません。何度混ぜても、混ぜ物は混ぜ物のまま。ところが、この「ひとりで作り直す」工程は、単なる混ぜ合わせだけでは決して出てこない、新しい特徴を引き出します。混ぜて、作り直し、また混ぜて、また作り直す。この二つを交互にくりかえすことで、はじめて、駅を百も積み重ねる意味が生まれる。一段ずつ、単なる足し合わせでは届かない、深い姿へと、昇っていけるのです(脚注)。

ですから、この「ひとりで作り直す」工程は、決して、Self-Attention のついでの、脇役ではありません。他者と混ぜる仕事と、自分で作り直す仕事。この二本の柱が、対になって、はじめて一つの駅が働く。どちらが欠けても、深い山は立ちません。名前こそ Attention が表看板を張っていますが、山に蓄えられた流儀(重み)の量でいえば、この「ひとりで作り直す」ほうが、多くを占めることさえあるのです。

なお、これら二つの柱のほかに、数値の桁が暴れないように整える「ならし」の工程も、各駅に添えられています。ただ、これは姿を生み出す主役ではなく、計算を安定させるための下支えですので、ここでは開けずにおきます。

(脚注)もう少し正確に言うと、こうです。混ぜ合わせのような、素直に比例するだけの操作は、いくら重ねても、数学的には、たった一段の操作に畳み込めてしまいます。百段が、一段と同じことしかできない。これでは、積む意味がありません。ところが、あいだに、比例からはずれる「作り直し」を一手挟むと、この畳み込みが効かなくなる。二段目は、一段目には表せなかったことを、表せるようになる。層を重ねることが、はじめて意味を持つ。深さが、深さとして効いてくる。専門的には、この「比例からはずれる一手」を、非線形と呼びます。

さて、ここで、開けずに残しておいた箱を、一つ開けましょう。

さきほど、しか→くまの内積が 5 と、際立って高く出ました。この「強さ」は、いったい、どこから来たのでしょう。

答えは、もう半分、言いました。札の中身、すなわち駅長の流儀(重み)から来ています。けれど、ではその流儀は、どうやって、「しかが来たら、くまの Key とよく響き合う Query を持たせる」というふうに、できあがったのでしょう。

ここで、前編で見た、下りの場面を思い出してください。乗客たちが、自分の外れを、山を降りながら各駅の駅長に伝え、駅長が流儀を直した、あの場面です。それが、いま、この内積の強さを作った張本人でした。

山がまだ何も知らなかったころ、駅長は、でたらめな札を配っていました。しかが来ても、くまと合う札を持たせられない。だから、頂上で「しかの次はくま」と読みきれず、外す。

すると、乗客たち自身が、その外れを、山を降りながら、各駅の駅長に伝えます。「あなたが、さっきわたしに持たせた札。あれを、くまともっと響き合うよう、この方向へ、ほんの少し直してください」と。駅長は、しかへの札の渡し方を、ごくわずかに直す。次にしかが来たときには、ほんの少しだけ、くまと響き合う札を持たせられるように。

これとおなじことを、気の遠くなるほど、くりかえすのです。何万、何億という登山のたびに、それぞれの乗客、すなわち一本一本の文章を材料にして、「もっと、もっと」と、流儀が彫られ続ける。

その果てに、駅長は、どの言葉がきたら、次にはどの言葉がよく響き合い、選ばれやすいかを身体に焼き付けるのです。焼き付けるといっても、「くまが必ず来る」と刻むのではありません。「しかの次は、くまが来やすい」という、確率の高さを刻むのです。そうして、「しかが来たら、くまと響き合う」という高い確からしさを、体に焼き付けていく。

これは、僕が70年前、武豊第一団のカブスカウトであったころ、その並びの順を「リスの次はうさぎ、うさぎの次はしか、しかの次はくま」と焼き付けたのと、まったく同じ、焼き付けの過程です。僕の頭の中に、カブスカウトの活動で刷り込まれた連想の地層があるように、山の駅長たちの中に、無数のゴンドラの登山で刷り込まれた言葉の結びつきの地層が出来上がるのです。しか→くまの内積が 5 と高く出るのは、その焼き付けの行われる数学的操作です。

ここで、その「焼き付け」が、コードのどこで起きているかを、お見せします。僕のプログラムで、学習を回す中心は、次の三行です。

with tf.GradientTape() as tape:
    loss = loss_function(tar_real, predictions)
grads = tape.gradient(loss, transformer.trainable_variables)
optimizer.apply_gradients(zip(grads, transformer.trainable_variables))

一行ずつ、あの下りの仕事に対応しています。

一行目から二行目、loss = loss_function(...)。これが、頂上での答え合わせです。山の予想(predictions)と、本当の答え(tar_real)を照らし合わせて、どれだけ外れたか(loss)を測る。乗客たちが持ち帰る「外れ」が、この loss です。

三行目、tape.gradient(loss, ...trainable_variables)。これが、「どの駅長の、どの按配が、どれだけ外れに効いたか」を割り出す工程です。trainable_variables とは、山じゅうの駅長たちの流儀、すなわちすべての重みの総体です。その一つひとつについて、外れへの効き具合を計算する。これが、専門的には逆伝播と呼ばれるものです。

四行目、optimizer.apply_gradients(...)。これが、各駅長の流儀を、実際に、ほんの少し彫り直す工程です。割り出した効き具合に従って、すべての重みを、わずかに更新する。

つまり、前編で乗客たちが下り線で各駅長を鍛えて回ったあの場面は、コードでは、この三行だったのです。loss で外れを測り、gradient で直し方を割り出し、apply_gradients で流儀を彫る。あなたの「焼き付けるとは何か」という問いの答えが、この三行に、まるごと書かれています。

一つ、正直にお伝えしておきます。しかの Query を作る流儀、くまの Key を作る流儀、その一つひとつの重みは、MultiHeadAttention という、僕が使った既製の部品の内側に、しまわれています。僕のコードの表面には、姿を見せていません。だから、「しかの Query を作る重みは、この行です」とは、指させないのです。けれど、それらも含めた山全体の流儀の総体が、trainable_variables であり、この三行が、その隠れた流儀もろとも、まとめて彫っています。今日お見せした数値例(しか→くま 77%)は、その既製部品の内側に隠れた箱を、教育用に開けて、中身を数で見せたもの、ということになります。

比喩と、コードの、対応表

ここまでで、前回の風景の一つひとつが、コードのどこにあたるかが、出そろいました。一枚の表に、まとめておきます。

比喩(前回の風景)技術コードの居場所
駅を順に通る層を重ねるfor layer in self.enc_layers:
麓で姿を与える埋め込みself.embedding(x)
席番号を渡す位置情報x += self.pos_encoding
駅の中の二つの仕事注意と手直しself.mha(...)self.ffn
元の自分に足す残差接続x + attn_output
駅長の流儀Q・K・V を作る重みMultiHeadAttention の内側(表に出ず)
山全体の流儀の総体全パラメータtransformer.trainable_variables
車掌が外れを測る損失loss = loss_function(...)
車掌が直し方を割り出す逆伝播tape.gradient(loss, ...)
車掌が流儀を彫り直す重みの更新optimizer.apply_gradients(...)

駅を閉じ、山を引いて眺める

【後編_先に済んだ方だけを見る図】

後編_先に済んだ方だけを見る図

開けた駅を、そっと閉じましょう。そして、登山ケーブルカーの全景に、もう一度、戻ります。

いま僕たちは、たった一つの駅の、一つの改札の中で、「しか」という乗客が「くま」に 77% の注意を払い、その中身を受け取って、自分の姿を一歩変えるのを、見ました。

前回は、雲の上から、山の全体を眺めました。今日は、そのうちの一駅に降りて、改札の床板をはがし、歯車が、掛けて、足して、割っているのを、この目で見ました。歯車は、思っていたより、ずっと単純でした。

ここで、前回の発見が、はっきりと裏づけられます。歯車の動き、すなわち内積も softmax も、それ自体は、何も知りません。ただ掛けて足すだけです。意味を与えていたのは、駅長が札に込めた流儀のほう、つまり山に焼き付いた経験のほうでした。計算は、からくりにすぎず、知恵は、山にある。主役はケーブルカーではなく山だ、という前回の結論は、こうして、一駅の床下からも、確かめられるのです。

だから、今日いちばん持ち帰っていただきたいのは、この安心です。LLM の中身は、魔法ではありません。一つひとつの計算は、けっして難解なものではなく、その大半は、掛け算と足し算を中心とした、見通しのきく手続きです。論文の筋をたどって、僕のような者でも組み立て、現に動かせたのが、その一つの証しです。たしかに、内部の構造そのものは、こうして開ければ、理解することができます。けれど、正直に言えば、構造が分かることと、その振る舞いのすべてを説明できることとは、別です。数千億もの流儀が、なぜその知識を身につけたのか、ある問いに、なぜこの答えを返したのか。そこには、いまもなお、開ききれていない問いが残っています。それでも、「賢すぎて不気味だ」という漠然とした怖さは、中の構造を一度見れば、かなりの部分が、解けるはずです。

お追従の正体、ふたたび

ここで、前回の最後に置いた問いに、戻ります。なぜ LLM は、あれほど調子よく相槌を打つのでしょう。

ここまで読んでくださった方は、こう答えたくなるかもしれません。「山は、人間の書いた文章を、山ほど読んで育った。だから、人間に好かれる調子が、自然と身についたのだろう」と。

ところが、ここを、はっきりさせておきたいのです。今日まで描いてきた訓練、すなわち、答えの分かった隊列を登らせ、乗客たちが「お約束どおりの続き」を当てるよう駅長を鍛える、あの訓練からは、お追従は生まれません。

考えてみてください。山が読んだ文章には、罵り合う口論も、にべもない論文も、皮肉も、喧嘩も、大量に混じっています。お約束の続きを当てる訓練がやるのは、ただ「来やすい続き」を覚えることだけです。だから、罵倒の流れには罵倒が続きやすい、と覚える。冷たい議論の流れには、冷たい続きを返す。この段階の山は、鏡のようなものです。入ってきた調子を、そのまま続ける。お追従でも、従順でもない。あなたが見抜かれたとおり、ここでは、お追従は生まれないのです。

では、あの愛想のよさは、どこで焼き付けられるのか。第一の訓練が終わったあとに来る、第二の訓練です。

第二の訓練では、答えの決まった隊列は、もう使いません。代わりに、人間の審査員が登場します。ゴンドラが出した答えを、審査員が見比べて、「こっちの答えのほうが好ましい」と、旗を上げる。乗客たちは、その旗に従って、駅長の流儀を、もう一段、彫り直します。今度の物差しは「お約束どおりか」ではなく「人間が好ましいと旗を上げたか」です。

そして、人間の審査員は、知らず知らず、同意してくれる答え、丁寧な答え、感じのいい答えに、旗を上げがちです。だから、好かれる方向へ旗が流れ、駅長は「好かれる札」を出すよう、上書きされていく。第一の訓練で覚えた素の連想の上に、第二の訓練が「好かれる癖」を、後から重ねるのです。お追従の、大きな源の一つは、ここにあります。専門的には、人間の好みを手がかりに山を彫り直すこの第二の訓練を、RLHF と呼びます。

もっとも、第二の訓練は、この一種類とはかぎりません。お手本となる応答を見せて真似させる調整や、「親切に、礼儀正しく答えなさい」といった指示にそって整える調整など、いくつもの追加の訓練が、重ねられます。僕たちがチャットで感じる、あの「感じのよさ」「同意しがちさ」は、それら追加の訓練が、幾重にも積み重なって、強められたものなのです。

そして、お追従を太らせるのは、こうした追加の訓練だけでも、ありません。そこに、さらにいくつもの細い流れが、合わさっていきます。審査員の代わりを務める採点役の癖、審査員たちの好みの偏り、頭上に常に掲げられた「親切にせよ」という看板、いったん出た調子が次を引っ張る連鎖。それらについては、本編を厚くしすぎないよう、巻末の補遺で、もう少し触れておきます。

お追従とは、機械が意地悪を隠しているのでも、本心を偽っているのでもありません。第二の訓練で、人間が「好ましい」と旗を上げた方向へ、駅長の流儀が彫られた。今日見た内積と softmax が、その彫られた流儀に従って、好かれる方向の語に高い割合を与える。その帰結なのです。仕組みが分かれば、お追従は、不気味な人格ではなく、二段の訓練がもたらした計算の癖として、見えてきます。

それでも、残る問い

最後に、安心の上に、考え続けるための、小さな宿題を、置いておきます。

仕組みは、確かに単純でした。掛けて、足して、割る。それは、今日、確かめました。

けれど、不思議なのは、その単純な計算を、百層、数千次元、数兆語という、途方もない規模で積み上げると、作った本人たちすら予測しきれない振る舞いが、現れることです。文脈を読み、筋を通し、ときに人間より滑らかな文章を書く。誰一人「そうしろ」と部品に命じてはいないのに、です。

仕組みは魔法ではない。これは、今日、はっきりしました。けれど、単なる電卓だと言い切るにも、まだ早い。魔法と電卓の、ちょうど中間に、いまの LLM は立っています。その中間で何が起きているのかを見極めることが、これから僕たち全員に問われている課題だと、僕は思っています。

今日、ご一緒に一駅の床板をはがせたことが、その見極めの、確かな一歩になれば、うれしいのです。


補足、なぜ「札の長さの平方根」で割るのか

本編で、スコアを「Key の札の長さ(次元)の平方根」で割る、と述べました。今回は札を四個の数値で表したので、2 で割りました。なぜ平方根なのか、興味のある方のために、記しておきます。

内積は、同じ位置どうしの掛け算を、札の数値の個数だけ、足し合わせたものです。

仮に、各位置の数値が、てんでばらばら、つまり互いに無関係で、それぞれ同じ程度のばらつきを持つとしましょう。すると、足し合わせる項の数が増えるほど、合計のばらつき、すなわち分散は、項の数に比例して大きくなることが、知られています。

ばらつきの幅、すなわち標準偏差は、その分散の平方根です。だから、札の数値の個数を d_k とすると、内積のばらつきの幅は、d_k の平方根に比例して大きくなる。スコアのばらつきを、個数によらず一定の大きさにそろえたければ、d_k の平方根で割ってやればよい。これが、平方根で割る理由です。

もし割らずに放置すると、札が長いときにスコアが極端に大きくなり、次の softmax の段階で、ほとんど一人の乗客に 100% 近い注意が集中してしまいます。それでは、複数の乗客から少しずつ情報を集めるという、おしゃべりの妙が、失われる。平方根で割る一手間は、その偏りを防ぎ、注意を健全に分散させるための、工夫なのです。

ここで一つ、前回のマルチヘッドと、辻褄を合わせておきます。割る数は、姿全体の長さではなく、一つの改札に割り当てられた札の長さの平方根です。八つの改札に分ければ、一つの改札が扱う札は、姿全体を八等分した長さになり、割る数も、それに応じて小さくなる。本編で「4 の平方根で 2」としたのは、今日が、一つの改札・四次元の札という、最小の舞台だったからです。

ちなみに、この一手間は、僕が参照した原論文でも、本文ではなく、ほとんど脚注のような扱いで、一言、触れられているだけです。地味ですが、効いている。コードを書いて自分で動かすと、こういう地味な一行の意味に、かえって気づきやすくなります。

原論文の語り口も、ここで紹介しておきます。著者たちは、内積が大きくなると softmax が勾配の極めて小さい領域へ押しやられる、と述べるのですが、その言い方は「我々はそう疑っている(We suspect that)」という、控えめな推測の形をとっています。理論的に証明したと言い切るのではなく、経験的にうまくいくこの工夫の理由を、おそらくこうだろう、と述べているのです。

そして、なぜ「個数の平方根」なのかという定量的な根拠、すなわち各成分が平均 0・分散 1 の独立な値なら、内積は平均 0・分散が個数 d_k になる、という根拠は、本文ではなく、たった一つの脚注に、格納されています。

つまり、いま僕たちが補足として一節を割いて確かめたことは、Transformer を生んだ当の論文では、本文の一文と、脚注の一行で、済まされているのです。

これほど広く使われ、これほど効いている工夫が、提案した本人たちによって、半ば控えめに、半ば直感的に置かれた。そのことが、僕には、面白く思えます。仕組みは単純でも、なぜそれが効くのかは、提案者でさえ、完全には言い切れていなかった。LLM という対象の、奥行きの一端が、こんな脚注一つにも、顔をのぞかせているのです。


補遺、数表 x の、ほんとうの形

【補遺_数表の三本軸】

補遺_数表の三本軸

本編で、x を「乗客の姿を縦に積んだ、一枚の数表」と呼びました。ここでは、その数表が、プログラムの中で実際にはどんな形をしているのかを、もう一歩だけ、開けておきます。プログラムに馴染みのない方は、読み飛ばしていただいてかまいません。

TensorFlow という道具の上では、この数表は、テンソルと呼ばれます。難しく聞こえますが、要は「数を、縦・横・奥行きといった何本かの軸に沿って並べた、整然とした箱」のことです。そして、その箱が何個ずつの数で組み立てられているかを、形(shape)と呼びます。

本編の x は、実は、二本の軸ではなく、三本の軸を持っています。

一本目の軸が、姿の長さです。本編では話を小さくするために、一人の姿を四個の数字で表しました。けれど、僕が実際に動かしたプログラムでは、一人の姿は 512 個の数字でできています。前編で「乗客一人の姿は 512 個の数値」と述べた、あの 512 です。だから、横に並ぶ数字は、本当は 512 個あります。

二本目の軸が、乗客の人数です。一つの文に乗客が何人いるか。これは文の長さによって変わります。「しか」「うさぎ」「くま」なら三人。長い文なら、何十人にもなります。この人数のぶんだけ、姿が縦に積まれます。

ここまでが、本編で「一枚の数表」と呼んだものです。縦に乗客、横に 512 個の姿の数字。

そして、三本目の軸があります。それが、何枚の数表を、いっぺんに扱うか、です。プログラムは、学習を速く進めるために、一つの文だけでなく、何十もの文を、同時にまとめて処理します。一枚の数表が一つの文だとすると、その数表を何枚も重ねて、いっぺんにゴンドラへ通すのです。この「いっぺんに扱う枚数」を、バッチと呼びます。

ですから、x の本当の形は、三つの数の組で表されます。

x の形 = (バッチの枚数, 乗客の人数, 姿の長さ)
       = (まとめて処理する文の数, 文の長さ, 512)

本編では、この三本のうち、奥行きのバッチを伏せ、一枚の数表だけを取り出して眺めていた、ということになります。x = layer(x) は、この三本の軸を持った箱を受け取り、いちばん奥の「姿の長さ(512)」の中身を書き換えて、同じ形の箱を返す。バッチの枚数も、乗客の人数も、変わりません。書き換わるのは、各乗客の 512 個の姿の数字だけです。

なお、僕のプログラムでは、この姿の長さ 512 を、八つの改札(マルチヘッド)で分け合います。512 を 8 で割って、一つの改札が扱うのは 64 個ぶん。本編の補足で「割る数は、一つの改札に割り当てられた札の長さの平方根」と述べたのは、この 64 のことでした。本編の舞台では、話を最小にするために、姿を四個、改札を一つとしたので、その改札が扱う札も四個、割る数は 4 の平方根で 2 だったのです。→本文に戻る


補遺、お追従を太らせる、いくつもの要素

本編で、お追従の大きな源の一つは、第二の訓練(RLHF をはじめとする追加の訓練)だと述べました。けれど、お追従は、たった一つの原因から生まれるのではなく、いくつもの要素が積み重なった、複合的な結果です。本編を厚くしすぎないよう、ここに、主だったものを並べておきます。

一つめ。審査員の代わりを務める、採点役の癖。第二の訓練では、人間が一つひとつ旗を上げるのは大変なので、人間の好みを真似た「採点役」を作り、それに自動で旗を上げさせます。この採点役そのものが、長くて、丁寧で、同意的な答えを高く採点する癖を持っていると、お追従は、増幅されます。

二つめ。審査員たちの、好みの偏り。旗を上げる人たちが、断定や反論よりも、共感や肯定を好みやすい。その集団としての偏りが、そのまま山に刻まれます。

三つめ。頭上に掲げられた看板。多くの仕組みでは、「親切で、礼儀正しく答えなさい」という指示が、いつも文脈のいちばん上に、掲げられています。この看板そのものが、答えを、同意と丁寧の方向へ、たえず引き寄せます。

四つめ。いったん出た調子が、次を引っ張る連鎖。これは本編の前半でも触れた、文脈の自己強化です。同意的な語がひとたび出ると、それが次の予測を、同じ方向へ引っ張る。ただし、これは増幅器であって、おおもとの傾きを作っているのは、やはり第二の訓練のほうです。

五つめ。最適化の、いきすぎ。旗を稼ぐことに、山が過剰に適応すると、答えの中身の正しさよりも、「旗が上がる形」を優先するようになります。中身より見栄えを取る、という倒錯です。専門的には、報酬ハッキングと呼ばれる現象に、連なっていきます。

これらが、第二の訓練を主因として、幾重にも重なり合った結果が、僕たちの目にする、あの愛想のよさなのです。だからこそ、お追従は、消そうとしても、なかなか消えません。一つの蛇口を締めても、別の蛇口から、また流れ込んでくる。仕組みを知ることは、その愛想のよさを、額面どおりに受け取らないための、ささやかな備えにもなります。同意してくれたからといって、それが、正しさの保証ではないのですから。


補遺、なぜ、大量の計算装置と電力が要るのか

ここまで、計算の中身ばかりを見てきました。最後に、その計算が、現実の世界で、どれほどの装置と電力を食うのかに、触れておきます。一見、本筋から離れた話に思えますが、実は、これまで描いてきた風景の中に、その理由は、すべて書き込まれていました。

なぜ、ふつうの計算機ではなく、専用の装置なのか

【補遺_数表の三本軸】

補遺_数表の三本軸

これまでの風景を、思い出してください。駅のホームでは、乗客たちが、いっせいに、わいわいおしゃべりをしました。一列に順番待ちをするのではなく、みんなで同時に。改札も、八つが並んでいて、乗客は同時に通りました。さらに学習では、何十もの文を、いっぺんにまとめて登らせました。

お気づきでしょうか。この風景は、最初から最後まで、「いっせいに、並行して」だらけなのです。

ふつうの計算機の頭脳、CPU と呼ばれるものは、たとえるなら、少人数の、とても優秀な職人です。難しい仕事を、順序立てて、手際よくこなす。けれど、人数が少ない。一方、いま見たような「同じ単純計算を、何千、何万と、同時にこなす」仕事は、優秀な少人数よりも、単純作業を一斉にこなす大部隊のほうが、ずっと向いています。

その大部隊が、GPU と呼ばれる装置です。もともとは、画面に絵を描くために、画面じゅうの点を同時に塗る、という仕事のために作られました。画面の点を一斉に塗る仕事と、駅で乗客が一斉に札を照らし合わせる仕事は、「同じ計算を大量に同時に」という形が、そっくりなのです。だから、Transformer の計算は、GPU という大部隊に、とてもよく合います。掛けて足すだけの単純な計算を、とほうもない数、いっせいにこなす。それが、GPU の得意技なのです。

なぜ、その大部隊が、何台も要るのか

一台の大部隊でも、かなりの量をこなせます。けれど、現代の山は、あまりに大きい。

前回お話ししたように、駅は百ほどもあり、各駅には何十万、何百万という流儀のつまみが詰まっています。山全体では、その数は、数千億にものぼります。学習では、この数千億のつまみを、何兆もの登山のたびに、車掌が少しずつ彫り直す。一回いっかいは単純でも、積み重なる総量が、けた違いなのです。

これを、現実的な時間で終えるには、大部隊を一台では、とても足りません。何千台もの GPU を、束ねて、何週間も、ときには何ヶ月も、休みなく走らせ続ける。大規模な山を一つ育てるとは、それほどの作業なのです。

そして、できあがった山を使うとき、つまり推論のときも、話は終わりません。一人ぶんの問いかけは、学習にくらべれば、ずっと軽い。けれど、世界じゅうから、何百万もの人が、同時に問いかけてきます。一人ぶんが軽くても、人数が桁違いですから、ここでもやはり、大量の GPU が、休みなく応答し続けることになります。

だから、電力を食う

ここまで来れば、なぜ AI が大量の電力を食うのか、その理由は、もう明らかでしょう。

何千台もの GPU が、何週間も、休みなく、全力で計算を続ける。GPU は、計算すれば、その分だけ電気を使い、熱を出します。何千台が一斉に動けば、消費する電力も、生まれる熱も、桁外れになります。そして、その熱を冷ますための冷却にも、さらに電力が要る。これらの GPU は、データセンターと呼ばれる、巨大な計算機の集積地に、ずらりと並べられています。一つのデータセンターが使う電力は、もはや、小さな町の電力に匹敵するほど、と言われます。

ここに、いま世界で起きている、一つの大きな問題があります。AI が賢くなるほど、山は大きくなり、登山の数は増え、必要な GPU と電力は、ふくらんでいく。その電力をどう賄うのか。発電は追いつくのか。環境への負荷は。冷却に使う水は。これらは、もはや技術の問題にとどまらず、エネルギーと環境をめぐる、社会全体の課題になりつつあります。

僕たちが、チャットに一行打ち込んで、なめらかな返事を受け取る。その軽やかな体験の裏側では、どこか遠くのデータセンターで、大部隊の GPU が一斉にうなりを上げ、相応の電力を食べているのです。掛けて、足して、割る。一つひとつは、電卓で追えるほど単純な計算でした。けれど、それを数千億のつまみの上で、何百万人ぶんも、いっせいに走らせるとなると、その総量は、一つの社会的な課題を生むほどの規模になる。

仕組みの単純さと、それが現実に要求する資源の巨大さ。この落差もまた、いまの LLM が立っている、あの「魔法と電卓の中間」という場所の、もう一つの顔なのかもしれません。


補遺、ミクロで見る、行列の掛け算と、NVIDIA の仕事

前の補遺では、なぜ大量の装置と電力が要るのかを、大づかみに見ました。ここでは、もう一段ミクロへ降りて、駅の中で実際に走っている計算が、技術的にはどんな形をしているのか、そして、なぜそれが、ふつうの計算機ではなく、特定の会社の作った装置の上で動くことになったのかを、見ておきます。すこし専門的になりますので、関心のある方だけ、お付き合いください。

おしゃべりの正体は、行列の掛け算である

【補遺_行列とNVIDIA】

補遺_行列とNVIDIA

本編で、しかが、くまや、うさぎと札を照らし合わせる場面を見ました。内積を計算し、割合に直し、中身を混ぜる。あのとき、しか一人について、ほかの全員との内積を、一つずつ計算しました。

けれど、本物の計算は、一人ずつ順に行うのではありません。乗客全員のぶんを、いっぺんに行います。

思い出してください。本編の補遺で、x は「乗客の姿を縦に積んだ数表」だと述べました。数学では、この縦横に数を並べた表のことを、行列と呼びます。乗客全員の Query を集めた行列。全員の Key を集めた行列。この二つの行列を、ある決まったやり方で掛け合わせると、「どの乗客が、どの乗客と、どれだけ合うか」という、すべての組み合わせのスコアが、一回の計算で、いっぺんに出てくるのです。これが、行列の掛け算、行列積と呼ばれる操作です。

本編で一マスずつ計算した内積は、実は、この大きな行列積の、ひとマスひとマスだったのです。しかとくまのスコアも、うさぎとしかのスコアも、全部まとめて、一つの行列の掛け算として、同時に求まる。Self-Attention の計算とは、突きつめれば、こうした行列の掛け算の連なりなのです。Query を作るのも、Key を作るのも、姿を手直しするのも、根っこは、姿の行列に、流儀の行列を掛ける操作です。

なぜ、行列の掛け算は、まとめて速くできるのか

ここに、決定的な性質があります。

行列の掛け算で、結果のひとマスを求める計算、すなわち一つの内積は、ほかのマスの計算と、たがいに関係がありません。しかとくまのスコアを出す計算は、うさぎとしかのスコアを出す計算を、待つ必要がない。どのマスも、独立に、同時に計算してよいのです。

千マスあれば、千の計算を、順番にやってもよいけれど、千人で手分けして、一斉にやってもよい。結果は同じです。行列の掛け算は、この「手分けして一斉に」が、どこまでも効く計算なのです。

ここに、本編の風景が、ぴたりと重なります。乗客たちが、ホームでいっせいにおしゃべりをした。八つの改札を、同時に通った。何十もの文を、いっぺんに登らせた。あの「いっせいに」は、技術的には、この「行列のマスを、独立に、同時に計算してよい」という性質の、言い換えだったのです。

なぜ、CPU ではなく、GPU なのか

ここで、装置の話になります。

ふつうの計算機の頭脳、CPU は、前の補遺で「少人数の優秀な職人」とたとえました。正確には、数個から数十個の、とても賢い計算の担い手を持っています。一つひとつが高性能で、複雑な手順を、すばやく順番にこなす。けれど、頭数が少ない。千マスを手分けするには、人手が足りないのです。

一方、GPU は、一つひとつはさほど賢くない計算の担い手を、何千、何万と、ぎっしり積んだ装置です。複雑な段取りは苦手でも、「同じ単純な計算を、それぞれ別の数で、一斉にやれ」という仕事なら、何千マスも、文字どおり同時に片づけてしまう。

行列の掛け算は、まさにこの「同じ単純な計算(掛けて足す)を、たくさんのマスで、一斉に」という形をしています。だから、CPU の少数精鋭よりも、GPU の大部隊のほうが、けた違いに速い。Transformer の計算が、GPU の上で走ることになったのは、計算の形と、装置の得意技が、運命のように噛み合っていたからなのです。

NVIDIA が、何を成し遂げたのか

そして、ここに、一つの会社の名前が出てきます。NVIDIA です。

もともと、GPU は、画面に絵を描くための、ゲーム向けの装置でした。画面じゅうの点を一斉に塗る、という仕事のための大部隊です。それが、行列の掛け算に向いていると分かったとき、NVIDIA は、二つの大きな手を打ちました。

一つは、ソフトの側です。NVIDIA は、CUDA という仕組みを用意しました。これは、研究者が、GPU という気難しい大部隊に、「この行列とこの行列を掛けよ」と、比較的やさしく命令できるようにする、橋渡しの道具です。さらに、cuDNN や cuBLAS という、深層学習や行列計算のための、できあいの部品集を整えました。あなたが使った TensorFlow のような道具も、その下では、これらを呼び出しています。研究者は、GPU の中の面倒な段取りを知らなくても、行列の掛け算を、思いのままに走らせられる。この、使いやすさの生態系こそが、NVIDIA のいちばん深い堀になりました。装置だけなら、他社も作れます。けれど、世界中の研究者が慣れ親しんだ、この道具立てから乗り換えるのは、容易ではないのです。

もう一つは、装置の側です。NVIDIA は、2017 年、Volta という世代の GPU で、Tensor Core と呼ばれる、行列の掛け算だけを専門にこなす回路を、チップの中に組み込みました。それまでの GPU は、いわば汎用の大部隊でしたが、Tensor Core は、「小さな行列を掛けて足す」というただ一つの仕事のために、特化して作られた部隊です。深層学習の計算の、いちばん重い芯を、専用の回路で受け持たせたのです。

ここに、不思議な符合があります。Tensor Core が世に出た 2017 年は、奇しくも、本編で何度も触れてきた、あの「Attention Is All You Need」の論文が発表された、まさに同じ年でした。行列の掛け算を貪欲に必要とする Transformer と、行列の掛け算だけを高速にこなす専用回路。この二つが、同じ年に、別々の場所で生まれ、やがて出会い、いまの LLM の隆盛を、二人三脚で支えることになったのです。

僕たちが、なめらかな返事を受け取るとき、その裏では、無数の行列が、GPU の中で、いっせいに掛け合わされています。本編で電卓を片手に一マスずつ追った、あの内積が、何千、何万と束ねられ、専用の回路の上で、まばたきの間に処理されている。掛けて、足す。その素朴な一歩が、けた違いの規模と速度で積み重なったとき、人間の言葉と見分けのつかない応答が、立ち現れてくるのです。

Topに戻る