← 一覧へ
連載 Agentic OS:技術スタックを下から読む の一部です ―― 目次を見る →

Agentic OS 技術スタックを下から読む 第39回:読むより、書くほうが高い ―― 入力と出力の、隠れた非対称

この記事の読み方
前回は、委譲を見ました。大きな仕事をそのまま渡すのではなく、文脈を切り離し、小さな仕事として別の場所へ渡す話でした。上の層では、それは編成の技術に見えます。

Agentic OS 技術スタックを下から読む 第39回:読むより、書くほうが高い ―― 入力と出力の、隠れた非対称

編成から、また土台へ降りる

前回は、委譲を見ました。大きな仕事をそのまま渡すのではなく、文脈を切り離し、小さな仕事として別の場所へ渡す話でした。上の層では、それは編成の技術に見えます。

今回は、また別の層の急所へ降ります。かなり下です。計算と推論の土台です。

推論には、見落とされがちな二つの相があります。一つは、入力を読み込む相です。もう一つは、出力を一つずつ生む相です。

人間から見ると、どちらも同じ一回の返答に見えます。文章を渡し、しばらくして文章が返ってくるだけです。けれども機械の中では、この二つはまるで別の仕事です。詰まる場所も違います。値段も違います。

今回の話は、そこだけです。読むより、書くほうが高い。これは気分の話ではありません。料金表の都合だけでもありません。機械の中で、重いものをどう運ぶかの話です。

同じ一文字なのに、なぜ値段が違うのか

入口の疑問は単純です。

多くの場合、入力の一トークンより、出力の一トークンのほうが高く値付けされています。トークンとは、機械が文章を扱うために分けた小さな単位です。日本語では一文字に近い場合もあれば、語の一部になる場合もあります。ここでは、説明のために一文字と呼んでおきます。

同じ一文字なら、同じ値段に見えます。読む一文字も、書く一文字も、同じ文章の一部です。ところが実際には、書くほうが高い。

理由は、読むときと書くときで、機械の中の流れが違うからです。

推論では、学習で決まった巨大な数の表を使います。これを重みと呼びます。重みは、入力された文字の列を、次に何が来やすいかへ変えるための部品です。重みそのものはとても大きく、GPUの中の計算器の近くにずっと置いておけません。必要になるたびに、メモリから運んでくる必要があります。

ここで重要なのは、計算そのものだけではありません。重みを運ぶことも高い、という点です。むしろ相によっては、計算より運搬が先に詰まります。

読み込む相では、計算が詰まります。生む相では、メモリからの運搬が詰まります。この違いが、入力と出力の値段の差を作ります。

読み込む相では、重みを割り勘にできる

まず、入力を読み込む相を見ます。

利用者が何千トークンもの文章を渡したとします。この相では、その入力をまとめて一度に流せます。もちろん内部では層を何段も通りますが、考え方としては、何千個の文字を大きな束として、重みの山に通します。

ここで効くのが、重みの使い回しです。

重みの山は巨大です。メモリから運ぶのは重い仕事です。けれども、一度運んだ重みを、入力中の多くの文字に対して同時に使えます。ある重みを読み出したら、それを一文字だけに使って捨てるのではありません。何千文字ぶんの計算にまとめて使います。

つまり、重みを運ぶ手間を、大勢の文字で割り勘にできます。

このときGPUの計算器は忙しくなります。行列計算という、数表どうしを掛け合わせて足し込む処理が大量に並びます。行列計算は、同じ重みを多くの入力に使うほど効率が上がります。メモリから運んだ一つの重みに対して、たくさんの掛け算と足し算を行えるからです。

この状態では、問題は運搬ではなく計算になります。重みは十分に使い回されています。計算器が埋まり、次々に数を処理します。読み込む相は、計算で頭打ちになる相です。

第27回で見た文脈の窓も、まずはここに関係します。長い入力を一度に読ませると、その長さぶんだけ注意の計算が増えます。注意とは、今見ている位置が、前後のどの位置をどれだけ参照するかを決める仕組みです。入力を読む相では、この参照関係をまとめて作れます。重い処理ではありますが、まだ束で扱えます。

生む相では、一文字ごとに重みを引きずる

次に、出力を生む相です。

ここで状況が変わります。出力は、一度にまとめて生めません。次の一文字は、前の一文字が決まってからでないと作れません。

たとえば、ある文の次に「で」が来るか「を」が来るかで、その先の流れは変わります。まだ決まっていない文字を前提に、その次を確定することはできません。だから出力は、順番待ちになります。一文字を生み、その文字を新しい文脈に足し、また次の一文字を生みます。

これが致命的です。

たった一文字を生むためにも、重みの山は必要です。次の一文字を選ぶには、入力とこれまでの出力をもとに、全体の層を通す必要があります。層を通すには、巨大な重みを読む必要があります。

読み込む相なら、一度運んだ重みを何千文字で分け合えました。生む相では、一人の利用者の一文字だけが相手です。重みを運んでも、その場で使える計算量が少ないのです。

するとGPUの計算器は余ります。掛け算をする力はあるのに、肝心の重みがまだ届いていません。計算器は待ちます。待っている時間が増えます。詰まっているのは計算ではなく、メモリから重みを運ぶ帯域です。帯域とは、一定時間にどれだけのデータを運べるかという太さです。

生む相は、運ぶ帯域で頭打ちになる相です。

ここで第32回の、より少ないビットで重みを持つ工夫が効いてきます。ビットとは、数を表す細かさです。重みを粗く表せば、同じ帯域でより多くの重みを運べます。計算の精密さとの釣り合いは必要ですが、生む相が帯域で詰まるなら、運ぶ量を減らすことには直接の意味があります。

読むのは割り勘、生むのは個別払い

非対称の核心は、ここです。

入力の一文字は、隣の文字と重みの読み出しを分け合えます。まとめて通すからです。重みの運搬は一回でも、その重みを使う相手がたくさんいます。一文字あたりの負担は小さくなります。

出力の一文字は違います。自分専用に、重みの山を引きずります。一つずつしか生めないからです。まだ存在しない次の文字たちを、同じ便に乗せることはできません。割り勘の相手がいません。

読むのは割り勘です。生むのは個別払いです。

同じ一文字でも、背負う運搬の量が違います。入力の一文字は、重みの運搬費を薄く分け合っています。出力の一文字は、その時点の一歩を進めるために、重い山をほぼ丸ごと動かします。

だから、生むほうが高いのです。

第1回で、推論コストを逆算する話をしました。そこで見た単価の差は、単なる料金表の癖ではありません。下の層へ降りると、計算器の混み方と、メモリからの運び方の差として見えます。料金は、その構造の上に乗っています。

束ねると割り勘が戻るが、待ち時間を払う

では、出力の個別払いを、割り勘に戻す方法はないのでしょうか。

あります。大勢の利用者の「生む」を束ねます。

一人の利用者だけを見ると、次の一文字しか生めません。けれども、別の利用者も、同じ瞬間に次の一文字を待っています。さらに別の利用者も待っています。これらをまとめて一つの束にすれば、一回の重みの読み出しを、その束の全員で分け合えます。

それぞれの利用者は、自分の次の一文字だけを生みます。けれども機械から見ると、同じ重みを、多くの利用者の一歩ぶんに使えます。読み込む相ほどきれいではありませんが、個別払いよりはずっとよい状態になります。

これをやるかどうかで、コストは桁違いに変わります。条件次第では千倍も変わります。束ねない生む相は、それほど割に合いません。

ただし、ただではありません。束ねるには待たせる必要があります。

たとえば、二十ミリ秒ごとに一本の便を出すとします。その時点で準備できている利用者を集め、一緒に走らせます。すぐに便を出せば、待ち時間は短くなります。しかし乗る人が少なくなり、割り勘の相手も少なくなります。少し待てば、乗る人が増えます。重みの運搬を分け合えます。その代わり、返答の始まりや途中の一文字が遅れます。

安さは、待ち時間で買っています。

第2回で見た、非同期で束ねる話はここへ戻ってきます。あのときは、仕事を止めずに集めて流す仕組みとして見ました。今回は、その理由が下の層から見えます。生む相は個別払いだから、束ねて割り勘にしないと立ちゆかないのです。

長い文脈の壁も、運搬の壁である

生む相を重くするものは、重みだけではありません。途中まで読んだ文脈の覚え書きもあります。

出力を一文字生むたびに、機械はこれまでの文脈を参照します。毎回、最初から全部を読み直すのは遅すぎます。そこで、各層で使いやすい形にした覚え書きを残しておきます。この覚え書きには、過去の各位置が、後で参照されるための数表として入っています。

問題は、この覚え書きが利用者ごとに別ものだという点です。

重みは全員で共有できます。同じ重みを、別々の利用者に使えます。けれども、ある利用者の文脈の覚え書きは、その利用者だけのものです。別の利用者とは混ぜられません。だから、重みのようには割り勘にできません。

文脈が長くなるほど、この覚え書きは大きくなります。出力を生むたびに、参照すべき過去が増えます。十万〜二十万トークンのあたりで長い文脈が重くなりやすいのは、計算だけが足りないからではありません。覚え書きを置き、運び、参照するための帯域が苦しくなるからです。

ここでも壁は、計算の壁だけではありません。メモリ運搬の壁です。

第27回で見た文脈の窓の扱いは、この壁を相手にしていました。何を窓に残すか。何を外へ逃がすか。何を短い要約にするか。上の層では文脈設計に見えますが、下の層では、運ぶ覚え書きをどう減らすかという話でもあります。

いちばん高い相を、いちばん使う

まとめます。

読むより、書くほうが高い。入力を読み込む相は、重みの運搬を多くの文字で割り勘にできます。だから計算が詰まります。出力を生む相は、一文字ごとに重みを引きずります。だからメモリの運搬が詰まります。束ねなければ、個別払いになります。長い文脈の壁も、覚え書きの運搬が重くなることで現れます。

ここで、エージェント型の基盤を思い出します。

エージェントは、長く考えます。何度も手順を書き出します。途中結果を読み、次の行動を生みます。長い文脈を引きずります。つまり、いちばん高い相を、いちばん使います。読んで一言返すだけの使い方より、ずっと生む相に寄っています。

だから、安く回す設計は、生む相をどう束ねるか、文脈をどう短く保つかに大きくかかります。第1回のコストの逆算も、第2回の非同期の束ねも、第32回の少ないビットで載せる工夫も、第27回の文脈の窓をどう持つかという話も、別々の話ではありません。どれも、「生むほうが高い」という一点へ、違う角度から効いていました。

土台の非対称を知っていると、上の層の工夫が、なぜそこを狙うのかが見えます。次回は、また別の層の急所へ降ります。

← 一覧へ