ベンチマークは実時間に連動することを証明しなければならない
エージェントが渡された数値を何でも押し下げるようになると、議論すべきはもうモデルではありません。自分が責任を持つのは、その数値と目標が一緒に動くという証拠です。そしてその証拠は、最適化が確認した範囲の外に出た時点で期限切れになります。
Anthropic は今週、小さなチームが claude.ai とデスクトップアプリを 2 週間で約 3 倍速くした経緯を公開しました。数字はちょっと常識外れです。新規ロード時に入力できるページが表示されるまでの時間は、75 パーセンタイルで 3.1 秒から 0.55 秒に縮み、3,000 件を超える変更が、顧客に見える障害もロールバックもなしにマージされました。13 の目標のうち 12 は 3 日目までに達成され、その多くは計画済みのプロジェクトによるものでした。HTML に直接焼き込んだ静的なコンポーザーや、事前コンパイルした V8 のコードキャッシュなどです。そのあとは Claude が山登りの大半を担い、150 を超える並列スレッドで進めました。著者たちが記事の中心に置いた一文はこれです。「With Claude, measuring something makes it tractable.」
この記事は、落とし穴についても珍しく正直です。新しいベンチマークを残す前に、誰かがチャンネルにこう書き込みました。
please prove that hill climbing against each of these can result in
measurable wall clock perf wins. we’ll unship the benches for any
candidates that cannot prove thatその証明は「Does the count track the clock?」というタイトルのグラフの下にあります。つまり、エージェントに山登りさせる前に代理指標を検証するという考えは彼ら自身のもので、はっきり書かれています。記事が語っていないのは、エージェントが登り続けたとき、その答えがいつまで正しいのかという点です。ここを論じたいと思います。
なぜラボには別の数値が必要だったのか
チームはデプロイできる速さよりも速く反復したいと考えていました。Claude は何時間でも、夜通しでも作業できますが、プロトタイプのたびにフィールドの計測値を待っていたら足止めになります。そこでラボでの計測が必要になりましたが、ラボでの実時間はノイズが多く、記事の言葉では「milliseconds are too flaky to use as a CI gate.」です。目標そのものは実時間のまま、つまりジャーニーごとの実ユーザーの p75 でした。ラボの数値はその代わりです。
エンジニアの一人である Sam は、代わりに JavaScript の命令数を数えられないかと尋ねました。Claude は方法を示して答えました。ベンチマークを Valgrind 上で node --predictable を付けて実行する。1 回だけ、統計は不要です。Chromium に命令数のカウント機能がないブラウザ側のパスについては、別の決定的なカウントを挙げました。インタラクションごとの React のコミット数、V8 の precise coverage による関数呼び出し数、レイアウトとスタイルの再計算、DOM の変更です。11 分後には、計測ごとに 1 本ずつ、5 本のスレッドが動いていました。チームがそのすべてに課したルールはこうです。
We treated every new benchmark with some skepticism. Each one had two jobs:
first, a metric Claude could move in the lab; second, a guardrail in CI with
a number that could only ratchet down. If a benchmark was flaky, or if it
didn’t actually correlate with user latency, we threw it out rather than let
Claude climb the wrong hill.グッドハートが実際に言ったこと
Charles Goodhart の 1975 年の定式化は金融政策についてのもので、出回っている言い換えよりも鋭いものです。
any observed statistical regularity will tend to collapse once pressure is
placed upon it for control purposes.鍵になる語は「observed」です。その規則性は圧力がかかっていない状態で観察されたもので、圧力は状況そのものを変えます。誰かが不正をする必要はありません。最適化する側が、数値が動く方向を見つけ続けるだけで十分です。1 つのベンチマークに対して 50 件、100 件と PR を出すエージェントは、ひとつの数値が受ける圧力としてはほぼ最大級です。
Manheim と Garrabrant によるグッドハート効果の分類には、この問題に最も近い形の名前があります。extremal Goodhart です。代理指標と目標の関係は、それが観察された範囲では成り立ちますが、選択は系を、成り立たないかもしれない範囲へと押し出します。彼らはこれをさらに分けています。最初から近似的にしか正しくなかった関係と、局所的には正しいが別の場所では違う関係です。今回どちらに当たるのかは外からは判断できず、カウントにも判断できません。計測された 2 つのパスが示しているのは、エージェントが出発した範囲での関係であって、それ以外のどこでもありません。
数か月前、検索ランキングについて関連する議論をしました。品質を測れないランカーは平均的に品質と相関する代理指標を選び、境界上のケースは平均の中に埋もれてしまう、というものです。エージェントの場合も同じ仕組みですが、一つ重要な違いがあります。ここでは目標を読み取れるのです。
2 点を、2 通りに計測する
記事が示している検証はこうです。Claude は 2 つのホットパスで命令数を減らしました。会話のメッセージツリーを組み立てるルーチンと、Claude Code の出力からステータス行を探すスキャナーです。命令数は 48% と 31% 減りました。実時間は 78% と 44% 減りました。カウントは node --predictable を付けた Valgrind から、時間は同じベンチマークを JIT が温まった通常の node で実行して取ったものです。
これは、少なくともこれらの変更については代理指標が正しい方向を向いているという良い証拠です。ただし、比例していないことも一目でわかります。命令の約半分を取り除いたら、時間の 4 分の 3 以上が消えました。つまり、なくなった命令は平均的な命令よりずっと高コストだったということです。記事はその理由も書いています。最初のパスの命令の 4 分の 1 は、同じメッセージ ID を 3 回解決するメガモーフィックな辞書ルックアップでした。命令数は、遅いルックアップも安い加算も同じ 1 として数えます。今回たまたま正しい方向に動いたのは、最初の修正が高コストな処理を取り除いたからです。私の読みでは、同じパスの次の修正が同じ内訳になる保証はなく、それがいつ崩れるのかをカウントは教えてくれません。
ラチェットが強制し続けるもの
証明のあと、チームは 2 つのラチェットをチェックインしました。これらのパスで命令数を増やす PR はすべて CI で失敗し、「and a daily job lowered each ceiling whenever the count went down.」
前半は回帰を防ぐ仕組みで、しかも良いものです。後半では、関係の確認が止まります。上限はカウントだけを根拠に動きます。この日次ジョブにはフィールドの計測値が結び付いておらず、代理指標を検証したペアの時間計測も、ジョブが動くときに再実行されません。つまり上限が下がるたびに、元の主張、つまりこのパスの命令が減ればレイテンシも減るという主張が、誰かが計測した地点からさらに離れた場所で、黙って再確認されたことになります。
ラチェットには見えないトレードオフを考えてみてください。結果をメモ化すると、計測対象のコールドな呼び出しでは命令が増え、その後の呼び出しではすべて命令が減ります。それが得かどうかは、実際の利用でそのパスがどれくらいウォームな状態で走るかによりますが、カウントにはそれがわかりません。上限はチェックインされたベースラインなので、人間なら同じ PR の中で一文の理由を添えて引き上げられます。ただ、1 つのベンチマークを任されたエージェントがそれを試みることはまれだろうと思います。数値を下げろと指示されたエージェントにとって、上限は壁に見えます。だからそのトレードオフが提案されることはありません。
記事には逆向きの修正も出てきます。900 行の PR に、1 行の返信が付きました。「going to gavel that 2ms per send is not worth the complexity of maintaining this build plugin.」指標の改善を、指標には見えない理由で人間が却下したわけです。ラチェットは数値が悪化しないように守ります。改善に価値があるかどうかの判断は人間に残り、回帰を受け入れる価値があるかどうかの判断も同じく人間に残りました。
以前、重要なルールはモデルが検討する一文としてではなく、ハーネスが実行し強制するチェックとして置くべきだと論じました。ラチェットはまさにそれを正しく実践したものです。しかし、強制されるチェックは、それが強制する主張以上には信頼できません。「このパスでは命令数がレイテンシに連動する」というのは、賞味期限のある経験的な主張です。先の議論も同じ限界で終わっていました。違反の中には、ゲートが検査するアクションではなく、現実の世界でしか姿を現さないものがあります。
チェーンのどの計器も代理指標である
記事が描くループは、実際にラボとフィールドを突き合わせています。変更が出荷されると、Claude はデプロイを見守ってフィールドのデータを読み、パフォーマンスが改善していればベンチマークのラチェットを下げ、改善していなければフラグをオフにして作業を続けました。ユーザーに見える問題を起こしうるものはすべて短命のフラグの裏に置かれ、リスクの高い変更はまず社員、次にユーザーの 1%、そして全員へと展開され、すべての PR に少なくとも 1 人の人間の承認が必要でした。このループには、ラボでの改善がフィールドに現れない場合の分岐があります。その分岐がどれくらいの頻度で発動したのかは、記事には書かれていません。
フィールドの計測値こそが本当の守りだと結論づけたくなりますが、記事の中で最も優れた発見がそれを否定しています。あるスレッドのきっかけになったサイドバーのカクつきは、彼らが持っていたどのモニターにも見えませんでした。Cumulative Layout Shift は各シフトを約 0.008 と評価しており、「good」のしきい値 0.1 を大きく下回っていました。残っていた location.reload() が 1 日 50 万回の隠れたリロードを引き起こしており、それは「that none of our load metrics could see.」静的なコンポーザーを社内に出したあとのレイアウトシフトを報告したのは、計器ではなく画面録画を撮った同僚でした。どのケースでもフィールドの指標も見えておらず、画面を見ていた人間には見えていました。
つまり実ユーザーの p75 もまた代理指標です。命令数よりは目標に一歩近いものの、目標そのものではありません。チェーンは命令数、ラボでの時間、フィールドのパーセンタイルと続き、製品を見ている一人の人間で終わります。チームはそのたびに新しい計器を作りましたが、それは記事の主張が意図どおりに機能している姿です。私の主張はもっと狭いものです。各リンクは一つ上のリンクに対して、しかも誰かが確認した範囲でだけ検証されています。だから検証は、誰かがそのリンクを一つ上のリンクと最後に照らし合わせた時点までしか新しくありません。
最初の実行の前に
数値を選ぶ前に、その数値が表す目標と、それをどう読み取るかを書き出してください。目標を独立に評価できなくても代理指標は使えますが、それが壊れたときに気づけません。
検証は、適用範囲付きの主張として記録してください。どのパスか、どんな種類の変更か、どう計測したか。最初のペア計測が教えてくれるのは、それを生んだ変更については代理指標が目標に連動している、ということです。
再確認はラチェット自体に結び付けてください。ペアの時間計測は、通常の node で動くベンチマークとしてすでに存在します。日次ジョブが上限を下げるたびにそれを再実行し、命令数の変化と時間の変化の比を記録して、その比が検証時の値からずれたら引き下げを保留します。コストは上限を動かすたびにベンチマーク 1 回分です。古くなった主張を何か月も強制し続ける上限に比べれば安いものです。
Anthropic のチームは、そのほとんどを手作業で、スレッドごとに名前のある担当者を置いて行いました。山登りは安くなりました。その山がまだ行きたかった場所に続いているかを確かめることは、安くなっていません。