Agentic OS 技術スタックを下から読む 第6回:層をまたいで使い回す ―― 効率化は、近似との取引である
前回まで、長い文脈を扱うときに何が重くなるのかを見てきた。
前回まで、長い文脈を扱うときに何が重くなるのかを見てきた。
中心にあったのは、キーとバリューの保存である。モデルは過去のトークンを参照するために、各トークンからキーとバリューを作る。生成が進むたびに、それらを捨てずに持ち続ける。これがあるから、次のトークンを出すたびに、過去を最初から計算し直さずにすむ。
ただし、文脈が長くなると、この保存量が効いてくる。
第4回では、保存するものを小さくする道を見た。複数の注意の頭でキーとバリューを共有する。あるいは、中身を圧縮して一つあたりの重さを下げる。第5回では、参照する範囲を絞る道を見た。直近だけを見る。あるいは、どこを見るべきかを学習して選ぶ。
どれも、長文脈の壁に対する別々のつまみに見える。
今回は、もう一段だけ先へ進む。層をまたいでキーとバリューを使い回す、という考え方である。
そしてそこから、ここまで見てきた工夫を一つの見方で束ねる。
つまり、長文脈の効率化とは、完全な情報をそのまま持つことを諦め、安さと引き換えに何かを近似する設計だ、という見方である。
層ごとに持つことの重複
現在の大きな言語モデルは、同じような計算の層を何段も積んでいる。
一つの層が入力を受け取り、少し変換する。次の層がそれを受け取り、また少し変換する。これを何十段も繰り返すことで、表面の文字列から、より抽象的な関係へと表現を変えていく。
注意の仕組みも、多くの場合、それぞれの層にある。
各層は、その層の状態からキーとバリューを作る。過去の各トークンについて、自分の層用のキーとバリューを保存する。だから、過去トークンが長くなるほど保存量は増えるし、層が多いほどさらに増える。
ここで重要なのは、保存量が「トークン数ぶん」だけではないことだ。
過去トークンごとに、層数ぶんのキーとバリューを持つ。層が32段なら、単純には32段ぶん積み上がる。層がもっと深ければ、そのぶん増える。
長い文脈では、これがそのまま重さになる。
では、すべての層が、本当に完全に別々のキーとバリューを持つ必要があるのか。
ここに、まだ削れる余地がある。
後ろの層が、前の層のものを使う
層が違えば、表現も違う。だから本来は、各層が自分に合ったキーとバリューを持つのが自然である。
しかし、すべての層がまったく別のものを必要としているとは限らない。
隣り合う層では、入力も出力も急に別物になるわけではない。前の層で作られた表現を、次の層が少し変える。そのため、近い層どうしでは、参照に使うキーとバリューが似た役割を持つことがある。
そこで、後ろの層が自前のキーとバリューを全部保存するのをやめる。
代わりに、前の層が作ったキーとバリューを参照して使い回す。ある層ではきちんと保存し、次の層ではそれを借りる。さらに次の層でも、近い層のものを使う。そうすれば、実際にキーとバリューを保存する層の数を減らせる。
これは、保存の単位をトークン方向だけでなく、層方向にも削る考え方である。
長い文脈では、この効果が大きい。なぜなら、キーとバリューの保存量は、文脈の長さに比例して増えるからだ。短い文脈では小さな差でも、長い文脈では大きな差になる。
場合によっては、保存量をおおよそ半分近くまで減らせることもある。
もちろん、実際の削減量は、どの層で保存し、どの層で使い回すかによって変わる。すべてのモデルで同じ結果になるわけではない。それでも、考え方ははっきりしている。
層ごとに別々に持っていたもののうち、似ている部分を共有する。
これにより、長文脈で膨らむキーとバリューの量を抑える。
ただし、これは近似である
ここで注意したいのは、これは無料の節約ではない、ということだ。
層ごとに別々のキーとバリューを持てば、各層は自分にとって都合のよい参照を持てる。浅い層には浅い層なりの見方がある。深い層には深い層なりの見方がある。
それぞれの層が、自分の入力からキーとバリューを作るなら、その層の状態にぴったり合わせられる。
しかし、前の層のものを使い回すと、後ろの層は自分専用のキーとバリューを持たない。前の層のものでひとまず間に合わせることになる。
たいていの場合、それで十分かもしれない。近い層どうしなら、大きな差は出ないかもしれない。実際、うまく設計すれば、品質の落ち込みを小さく抑えられることがある。
しかし、完全に同じではない。
後ろの層が本来持てたはずの、細かな違いを少し手放している。層ごとの独自性を削っている。そのかわり、保存量を減らしている。
つまり、ここでも取引が起きている。
安くするために、完全さを少し諦めている。
効率化は、近似との取引である
ここで、見方を一段上げる。
これまで見てきた長文脈の工夫は、それぞれ別の技術に見える。キーとバリューを共有する。中身を圧縮する。直近だけを見る。見る場所を選ぶ。層をまたいで使い回す。
しかし、根は同じである。
どれも、完全な情報をそのまま持つことをやめている。
キーとバリューを複数の頭で共有するのは、過去を見る角度の種類を減らすことだ。本来なら頭ごとに別々のキーとバリューを持てた。そこを共有する。細かな違いを捨てるかわりに、保存量を減らす。
中身を圧縮するのは、一つあたりの精度を下げることだ。十分に細かい数で持つかわりに、粗い形で持つ。表現のなめらかさを少し手放すかわりに、メモリを軽くする。
固定窓は、遠くを見る範囲を手放すことだ。直近の文脈はしっかり見る。しかし、遠い過去は見ない。すべてを参照できる完全さを諦めるかわりに、計算を安くする。
学習による選択は、すべてを必ず見る網羅性を手放すことだ。必要そうな場所を選んで見る。うまく選べれば効率はよい。ただし、選ばれなかった場所に重要な情報がある可能性は残る。
層をまたいで使い回すのは、層ごとの独自性を手放すことだ。各層が自分専用のキーとバリューを持つかわりに、似たものを共有する。
こう並べると、話はかなり単純になる。
長文脈の効率化とは、完全な記憶や完全な参照を、より安い近似に置き換えることである。
何を近似するかが違うだけだ。
頭ごとの差を近似するのか。数値の細かさを近似するのか。見る範囲を近似するのか。選ぶ場所を近似するのか。層ごとの差を近似するのか。
どれも、問いは同じである。
どの完全さなら、少し捨ててもよいか。
部品を足す話ではなく、資源を配る話
この見方に立つと、長文脈対応は、賢い部品を足していく話ではなくなる。
むしろ、限られた資源をどこに配るかの話になる。
モデルには、計算量がある。メモリがある。メモリからデータを読み書きする帯域がある。生成時には、これらがすべて制約になる。特に長い文脈では、キーとバリューのキャッシュが大きくなり、保存と読み出しが重くなる。
だから、全体を一様に大きくするだけでは苦しい。
すべての頭に十分なキーとバリューを持たせる。すべての層に別々のキーとバリューを持たせる。すべての過去を毎回見る。すべてを高い精度で保存する。
それはきれいだが、高い。
現実の設計では、効く場所に資源を寄せる。あまり効かない完全さは削る。近くの文脈は厚く見る。遠い文脈は要点だけにする。重要そうな場所は残す。似たものは共有する。
このとき設計者が決めているのは、単に「この技術を使うかどうか」ではない。
どの近似を、どこで、どれだけ受け入れるかである。
近似を強くすれば、安くなる。だが、失うものも増える。近似を弱くすれば、品質は守りやすい。だが、長い文脈を扱うコストは下がりにくい。
長文脈時代のモデル設計は、この配分の問題になっている。
完全さを守る場所と、近似で済ませる場所を分ける。そこに設計の中身がある。
Agentic OS への含意
この話は、下の層だけで閉じない。
エージェントは、長い作業履歴を抱える。ユーザーとの会話、調べた資料、実行したコマンド、失敗した試行、途中で決めた方針。作業が長くなるほど、保持したい情報は増えていく。
では、その履歴を一字一句そのまま持ち続けるのか。
それとも、要点だけを残すのか。古い情報をまとめるのか。重要そうなものだけを検索できる形にするのか。最近の履歴だけを厚く残し、遠い履歴は粗くするのか。
これは、モデル内部のキーとバリューの話とよく似ている。
完全な記憶は高い。安い記憶は、必ず何かを失う。
エージェントの上位の記憶設計でも、同じ取引が起きる。すべてを残せば安心に見えるが、参照するたびに重くなる。要約すれば軽くなるが、細部は落ちる。選択して保存すれば効率はよいが、選ばなかった情報は戻らない。
そして、その上位の設計は、下のモデルがどのような近似を選んでいるかに影響される。
モデルが長い文脈を安く扱えるなら、上の記憶は少し贅沢に持てる。逆に、長い文脈が高いなら、上の記憶は早い段階で要約や選択に頼ることになる。
下の層の近似は、上の層の自由度を静かに決める。
ここまで見てきたのは、長文脈を支えるために、キーとバリューの保存や参照をどう軽くするかだった。頭を共有する。中身を圧縮する。範囲を絞る。場所を選ぶ。層をまたいで使い回す。
どれも、完全さを少し手放すことで、現実的なコストを得ている。
次回は、この近似をさらに進めた構造を見る。注意の層そのものの大半を、もっと軽い別の仕組みに置き換える、ハイブリッドな組み立てである。
← 一覧へ