ONTLEのAI PMは、一つの顧客プロジェクトを数日がかりで進めていく。依頼を受け、ファイルを開き、DBを漁り、ビルドログを読み、途中の判断を書き残す。これらすべてが一つの長い対話として積み上がる。そうして三日目あたりになると、妙なことが起きる。似たような質問なのに、返答がだんだん間遠になるのだ。最初は数秒で返ってきた答えが、対話が長くなるほど目に見えて遅くなっていく。
その遅さの正体が、この記事の出発点だ。最近公開された推論エンジンSparseEngineは、まさにこの問題に照準を合わせている。異なるスパースアテンション(sparse attention、過去をすべて見るのではなく一部だけを選んで見る方式)の手法を15種類、一つのエンジンの中で動かす「スパース・ファースト」なエンジンである。速度が何倍にも上がったという数字がまず目を引くが、本当に面白いのはその数字ではない。このエンジンは「どう忘れるか」は解決してくれる一方で、「何を忘れるか」はそっくりこちらの手に残しておく。ONTLEにとっての本当の仕事は、そこから始まる。その道筋をたどってみよう。
LLMは、一度問うて一度答える道具である段階をとうに超えている。いまや複数のターンにわたって自分でツールを使い、推論を続ける「エージェント」として働く。ONTLEのPMは、まさにそのように動いている。そう働くほど、対話の記録はツール呼び出しの結果と推論の過程をたどって急速に膨らんでいく。
肝心なのは、モデルがその記録をどこに積むかだ。モデルは対話が長くなっても文脈を見失わないよう、これまでにやり取りした内容をすべて抱えている。この「抱えている記憶」がKVキャッシュだ。過去の単語一つひとつについてあらかじめ計算しておいた値であり、まるごとGPUメモリに載っている。
問題は、この記憶がタダではないという点にある。対話が長くなると、二つのことが同時に悪化する。一つ目、記憶がGPUメモリを埋める。満杯になれば、新しいプロジェクトを始める場所すらない。二つ目、答えを一文字作るたびに、モデルはその長い記憶を最初から最後まで読み直す。記憶が長いほど、一文字が遅くなる。ONTLEのPMの返答が三日目に間遠になったのは、この二つ目のせいだ。賢さを保とうとする記憶が、そのまま費用になる。
スパースアテンション系は、この費用を減らそうとする試みだ。名前は難しいが、発想は単純。賢さを守るために積み上げた記憶の費用を削ろう、というものだ。ある手法は記憶をすべて抱えたまま、いまの質問に必要な部分だけを選んで見る。ある手法は使い道のなくなった記憶をまるごと捨てる。ある手法は数値をもっと粗い目盛りで保存して(これを量子化quantizationと呼ぶ)容量を減らす。共通点は「費用を減らす」ことだけで、減らす道はそれぞれ違う。前の二つはどのトークンを見るかを選ぶか、いっそ捨てるかという側で、量子化やPaluのような圧縮はトークンを一つも捨てずに表現のサイズだけを縮める。論文もこの二つ、すなわち選択・除去と圧縮・量子化を別々の系統として分けている。
このバラバラさが、エンジンの立場からすると厄介のもとだ。記憶をどう表現し、いつ更新するかが手法ごとに違うのに、これらはみな同じサービングインフラの上で動かなければならない。サービングインフラとは、リクエストを並べて処理し(スケジューリング)、すでに計算したものを再び使う(再利用)共用の台所だ。ところがエンジンが「記憶はこう保存して、こう更新する」とあらかじめ固めてしまった瞬間、その型に合わない手法は収めにくくなる。著者らはこれをプログラミング言語になぞらえる。どんな言語も、特定の計算を簡単にしてくれる代わりに、別の計算を難しくする、と。推論エンジンもまったく同じだ。
だから問いは一つに収束する。みんなが共に使う共用の台所と、手法ごとに異なる記憶の制御。その境界線をどこに引けば、すべてを収められるのか。
既存のエンジンは、たいてい境界線を「保存」に引いていた。記憶をページに切り分けて管理したり、GPUとCPUに分けて載せたり、トークン単位でこまめに回収したり。それぞれ自分の保存方式に合った手法には強かったが、その外は収めにくかった。
SparseEngineは境界線を別の場所に引いた。「保存をどうするか」ではなく「いつ割り込むか」だ。トランスフォーマーモデルがレイヤーを一つずつ経ながら計算する順序は、どのスパース手法を使おうと変わらない。その固定された実行経路の上に、割り込める地点をびっしり敷いておいたのである。プロンプトを一度に処理する段階(プリフィル)、答えを一文字ずつ作る段階(デコーディング)、レイヤーが終わる地点、1ステップが終わる地点。各手法は自分が必要とする地点にコードを引っ掛けて、いつ記憶を絞り込み、いつ捨てるかを自ら決める。モデルのコードそのものは一行も触らない。おかげで、互いに異質な15の手法が一つのエンジンの中で動く。
ここで、エンジンがはっきりと切り分けている区別が一つある。これは後で性能の数字を読むときに重要になる。記憶を「捨てること」と「見ないこと」は違う。保存スペースを実際に空けてGPUに余裕を返すのが物理的な除去だ。保存はそのままにして、今回読む範囲だけを狭めるのが論理的な選択だ。スペースを空ける仕事と、見る範囲を減らす仕事。効果はまったく違う。
ここまでが、一回の対話の中で起きることだ。その上に著者らは、リクエストとリクエストの間をつなぐ仕掛けをもう二つ重ねた。ONTLEのPMの一日を追えば、なぜ必要かがすぐに見えてくる。
PMが長いビルドログを読む。数百行を読み通し、「原因は依存関係のバージョン衝突」という一行に要約する。この時点から、ログの原文の数百行をまるごと記憶に抱えている理由は薄れる。使える結論はもう抜き出したのだから。
普通のエンジンなら、これを捨てにくい。次のターンで対話を続けるために、「前半がそのまま残っている」という前提の上で、先に計算したものを再利用するからだ(この再利用をプレフィックスキャッシングprefix cachingと呼ぶ)。ところが記憶を捨てる手法は、まさにその前提を崩す。前半を捨てたのだから、再利用が効かない。だから捨てる手法を使うと、次のターンのたびに長いヒストリーを最初から計算し直す損をする。
SparseEngineの一つ目の仕掛け、Chain Cacheがこれを解く。セッションごとに残しておいた記憶と状態をまとめておき、次のターンが入ってきたら全体を計算し直す代わりに、残ったものから続けられるようにする。一部の記憶をすでに捨てていても、「対話がどこまで進んだか」を指し示す目印は残しておくので、続くリクエストはまだ処理されていない後半だけを新しく埋める。だから翌日、同じプロジェクトのスレッドがまた開かれても、PMは二日分のヒストリーを最初から読み直さない。
二つ目の仕掛けは、制御可能なプレフィックス整理(Prefix-Cache Pruning)だ。先ほどのビルドログに戻ろう。プロダクトが「この区間はもう重要度が下がった」と指定すると、エンジンはその区間で残す部分だけを選り出し、残りの物理的な記憶を空ける。ただし、対話の流れを指し示す目印は触らない。だからログの原文はメモリから消えても、対話は依然として同じスレッドのまま滑らかに続く。PM一人がそのぶん軽くなり、同じGPUでより多くの顧客プロジェクトを同時に担当できるようになる。
良い数字は、条件を外すと嘘になる。SparseEngineの評価が力を入れているのが、まさにこの条件を切り分けたところだ。
まず品質。著者らは基準点を二つに分ける。一つは各スパース手法の元の実装、もう一つはフルアテンション(full attention、過去を一つも捨てずにすべて見る方式)だ。元の実装と比べれば「SparseEngineがその手法を作り直すにあたって、元の品質を活かせたか」を測り、フルアテンションと比べれば「一部だけを見ること自体が品質をどれだけ削るか」を測る。まったく違う二つの問いを混ぜない。
作り直したほうから。長文脈ベンチマークLongBenchで、Llama-3.1-8B基準、SparseEngineと元の実装の差はおおむね-0.1から+0.8点の間だ。事実上、同じ品質を再現したと見てよい。
一部だけを見る費用は、どの手法を選ぶかによって大きく分かれる。同じベンチマークでフルアテンションが50.7点なのに対し、SnapKVは50.2、OmniKVは50.7とほぼ肩を並べる。一方、StreamingLLMは46.4、Paluは41.8と目に見えて落ちる。だから「一部だけ見れば品質が悪くなる」ではない。「どの手法を選ぶかがすべてを分ける」が正しい。
選ぶべきものは手法だけではない。予算(budget)、すなわち記憶をどれだけ残すかを決める上限も結果を揺るがす。推論ベンチマークAIMEで、Qwen3-4Bモデルのフルアテンションが81.7%なのに対し、SnapKVは記憶をトークン4千個ぶん(4k)だけ残したときに58.3%まで落ち込み、そこから8千個(8k)で80.0%、1万6千個(16k)で85.0%まで上がってくる。残す記憶を増やすと、フルアテンションをわずかに上回りさえした。
ここで「85.0が81.7より明らかに良い」と断じるのは早計だ。このスコアは30問を1問あたり二回、計60回解いて平均した値である。同じモデルでも、次の文字を選ぶ過程にわずかな無作為性があるため、回すたびにスコアが数点ずつ揺れる。この揺れを踏まえれば、85.0と81.7の差は誤差の内にある公算が大きい。だから安全な結論は「良くなった」ではなく「悪くはならなかった」だ。同じ表で、StreamingLLMは18.3%まで崩れる。加速を大きく取るほど精度の損失も大きくなるトレードオフが、はっきりあるということだ。
さて速度だ。三つの数字が出回っているが、三つはそれぞれ別の実験の別の効果なので、束ねて読んではいけない。一つ目、大きなバッチで記憶を物理的に捨てると、既存エンジンvLLM比で集計スループットが10倍を超える。記憶を捨てて確保したメモリの余裕で「同時に処理する量」を増やした結果だ。二つ目、同じ同時処理量の条件では、答えを作る速度が2.5倍以上速くなる。アテンション計算そのものを減らした結果だ。三つ目、記録しておいたマルチターン対話全体を端から端まで(end-to-end)再生する実験では、2倍ほど(最大2.24倍)速くなる。ただしこの数字は、実験のやり方を必ず一緒に見なければならない。これはエージェントが実際に課題を最初から最後まで遂行した完了時間を測ったものではなく、モデルの出力はあらかじめ記録しておいた値で固定し、ツールを待つ時間だけを模して差し込んだ「トレース再生」実験だ。しかも最大2.24倍は同じ条件で出たものではなく、同時に処理するリクエスト数を32個から72個へと一緒に増やして引き出した値である。三つの数字は同じ方向を指しているが、測っている対象が違う。だから「2倍速くなる」を、実際のエージェント作業の完了時間が半分になるとか、長い対話全般の保証のように読むと間違う。あの2倍は、記録された対話を再生しながら同時処理量まで増やしたときに出た、特定条件の値だ。
条件がもう一つある。得は、コンテキストが長いときに大きくなる。AIMEのように入力が短いと、全体の実行時間に占めるアテンションの比率が小さく、いくらアテンション計算を取り除いても取り除けるものがそれほどない。だからAIMEでの加速は、長文脈ベンチマークより小さかった。このエンジンは長いコンテキストを長くデコーディングする作業で輝き、そうでなければ期待を下げるべきだ。
比較の公平性についても押さえておく値がある。GLMモデルに非常に長い入力(128K)を入れると、競合システムVortexはバッチ1でもメモリが溢れてそもそも動かず、ほかの二つのシステムは、このモデルが使う特殊な記憶圧縮構造に対応できなかった。一部の設定では競合がスタートラインにすら立てなかったということなので、比較表の空欄を見るときは、この文脈を敷いた上で見るべきだ。逆に、競合システムが最も得意とする方式で突き合わせても、SparseEngineは共通の測定区間で前に出たと著者らは報告している。汎用性を得るために効率を差し出したのではないか、という疑いへの答えだ。
ここまでが、エンジンがやってくれる仕事だ。ONTLEの立場からすると、本当の仕事はここから始まる。
このエンジンは「どう忘れるか」を安全に執行する方法をくれるだけで、「何を忘れてよいか」は判断してくれない。先ほどのビルドログは、要約が済んだから捨ててよかった。しかし、すべてがそう綺麗に片づくわけではない。顧客が三日前にこぼすように言った要件が、今日の判断の根拠になることもある。それを「重要度が低い」と指定してしまったなら、PMは重要な文脈を失う。逆に、惜しがって何も捨てなければ、そもそも得ようとしていたメモリの余裕が消える。
だからONTLEがまず設計すべきなのは、エンジンではなく判断基準だ。プロジェクトの記憶のうち、何が核心で、何が消耗品なのか。要約が済んだツールの結果は捨ててよいのか。どのユーザー発話は、何ターンか後にまた呼び出されるのか。この区別を、誰が、どの時点で、どんな根拠で下すのか。エンジンは、この判断ロジックを代わりに組んでくれない。それはプロダクトの役目だ。
運用上の落とし穴も一つ付いてくる。Chain Cacheは、場所が必要になると、いちばん長く使われていないセッションから回収する。だから、しばらく放置されていたプロジェクトのスレッドがまた開かれると、状態がすでに返却されていて、最初から埋め直す費用を払うことがある。どのプロジェクトを「生きているもの」として引き留めておくかも、結局は私たちが決めなければならない。
ONTLEにとってこの論文の価値は、「速いエンジンが出た」ではない。私たちが長らく先送りしてきた問い――プロジェクトの記憶のうち何を抱え、何を手放すか――を、いまやコードで答えるべき宿題へと変えてしまった、というところにある。エンジンが「どう忘れるか」を引き受けてくれたのだから、私たちは「何を忘れるか」に集中すればいい。
スパース手法15種類は四つの系統にまとまる。(1) 動的スパースアテンション: 過去をすべて抱えたまま、質問に応じて見る部分だけを選ぶ。(2) KV除去: 使い道のなくなった記憶を実際に捨てる。(3) KV圧縮: 記憶をより小さく縮めて保存。(4) KV量子化: 数値を粗い目盛りで保存。登場する手法にはSnapKV、H2O、Quest、OmniKV、DeltaKV、KIVI、StreamingLLM、Palu、KVzipなどがある。
割り込む地点とコールバックをいくつか。プリフィル中、H2Oはチャンク(小片)単位で記憶を捨て、最後のプロンプトチャンクの後ろでSnapKVが選択・除去を行う。デコーディング中、Questは質問に応じて読むページを選び、OmniKV・DeltaKVは一つのレイヤーで選んだインデックスを後続のレイヤーで再利用する。KIVIは量子化された領域と、そうでない残差領域を一緒に管理する。これらすべてが、手法ごとの専用モデル実装なしに同じモデルの上で動く。
評価モデル: Llama-3.1-8B-Instruct、Qwen3-4B-Thinking-2507(複数のクエリがキー・値を共有してKVを減らすGQA系)、Qwen3-30B-A3B-Instruct-2507(必要な専門家の一部だけを点けるMoE)、GLM-4.7-Flash(KVを低次元に圧縮して持つMLA構造)、線形アテンションとGQAを混ぜたQwen3.6-27B。ハードウェアはH100・H20・RTX PRO 6000・4090・5090。
ベンチマーク: LongBench V1・V2(長文脈)、AIME 2024(推論)、SWE-Bench Lite・Claw-Eval(マルチターンエージェント)。比較システムとそれぞれの保存方式: Vortex(ページ単位ルーティング)、SPIN(GPU・CPUの階層分担)、Tangram(アテンションヘッドごとに保存量を予約――ヘッドは、モデルが文脈をいくつもの角度から切り分けて見る単位)、HiSparse(ホストメモリに置き、必要分だけGPUへ)、vToken(論理・物理アドレスの分離でトークン単位の回収)。基準線エンジンはvLLM(v0.26.0)。
コードは公開されています(github.com/CURRENTF/SparseEngine)。ただし、この記事の数字は特定のモデル・ハードウェア・ベンチマークで出た値です。載せる前に、自分のモデルと実際のワークロードで品質とスループットを測り直すほうが安全です。
性格が違います。vLLMはスパース性を前提としない密(dense)エンジンであり、その再利用も「記憶がそっくり残っている」という伝統的な仮定の上に立っています。SparseEngineはスパース性を後から付け足したのではなく、最初から既定値として敷き、土台から設計し直したエンジンです。代替品というより、出発点の違うエンジンと見るのが正しいでしょう。
平均は信用できますが、細部は気をつけるべきです。LongBench V2では個別のサブタスクのスコアがより揺れますが、著者らはサンプル数が少なく、次の文字を選ぶ過程に無作為性があるためだと説明しています。品質の合格ラインは、サブタスク一つではなく平均の単位で引くほうが安全です。