How does programming language affect token efficiency and correctness?

Dan Luu

プログラミング言語はトークン効率と正確性にどう影響するのか

原文は Dan Luu により に公開されました。 このブログを購読する

わりと広く引用されているこの投稿(少なくとも私は何度も目にする)は、動的言語や、より簡潔に物事を表現できる言語の方がトークン効率が高いと示唆している。引用されることが多いせいか、LLMの検索結果もそれに同意しているようだ。たとえば "dynamic vs static language token cost"(引用符なし)で検索したところ、GoogleのAIによる要約は次のように始まっていた。

動的型付け言語は、明示的な型宣言を省略することでコードがよりコンパクトになるため、従来の静的型付け言語よりも一般的にLLMのトークンコストが低い。

GoogleのAIは同じ投稿を引用しており、そこでは一部の簡潔な動的言語はRustやGo、C++といった静的言語の2分の1から3分の1程度のトークンコストで済むかもしれないと示唆されている。著者はこう述べている。

私が比較した中で最もトークン効率が悪い言語であるCと、最も効率的なClojureの間には2.6倍という非常に大きな差があった。

そしてその後、Jを試してこう述べている。

平均わずか70トークンで圧倒しており、Clojure(109トークン)のほぼ半分だ。配列言語は、特殊な記号セットを避ければ極めてトークン効率が高くなりうる。もしトークン効率が重要な要因になるのであれば、これは言語が進化する上で非常に興味深い方向性かもしれない。

私が見つけたもう一つの動的言語と静的言語のトークン比較はこちらのベンチマークで、同じ結論を支持している。これをベンチマーク、評価、実験設計に関する一連の演習のパート8とみなしたいなら、先に進む前にリンク先をクリックして評価上の問題点を考えてみてほしい。

自分たちで評価を実行しなくても、最初の実験には問題がある。問題があまりにも些細なのだということは、上の引用からもわかる。Jで70トークン、Clojureで109トークンで解ける問題は、そもそも大した問題ではない(著者はRosetta Codeを使っている)。cavemanモードと自分たちの評価を比較した他の評価で見たように、答えを出力することが作業の大部分を占めるような些細な問題と、ある程度の「実際の作業」を必要とする少しだけましな問題とでは、まったく異なる結果が得られることがある。cavemanモードで主張された大きな利得や、その再現実験で示された利得は、数トークンで済むような問題を超えて見始めると消えてしまう。一般に、些細なタスクでの性能は汎化しない。

2つ目のリンクの問題はもう少し微妙なので、そのほとんどは付録に回すが、存在しないパスを実行しようとしてテストが失敗するといった問題も含まれる。後続のエージェントの1つが、その存在しないパスを自身の実行ファイルへのシンボリックリンクで置き換えたのだが、これはそのケースではうまくいくものの、それ以降のすべてのテストで正しい実行ファイルの代わりにそのエージェントの実行ファイルが実行されることになってしまった。著者はRustでいくつか失敗があったことが何を意味するのかについて結論を導こうとしているが、それが意味するのは、Rustのスコアリングが、Goエージェントがその壊れたテストのスコアリングをすべてGoの実行ファイルにすり替える前に実行された、ということだけだ。

これらの評価に頼る代わりに、私たち自身の評価をいくつか実行してみることができる。これらの評価前回の評価に関する演習で議論した評価からもわかるように、評価の作成者が考えていることを実際には示していない評価を作ってしまうのは非常に簡単だ。これから示す評価もこの例外ではなく、欠陥があることは間違いない(詳細は下の付録を参照)。

直感を養う方法として、私は結果を見る前に予想を事前登録するのが好きだ1。友人たちに事前登録したことのいくつかは次の通りだ。

  • 確信度高(95%):動的言語 vs 静的言語という全体的な主張は成り立たない
    • 上記の理由から。これはcaveman評価と同様で、問題が大きくなれば結果はせいぜい希釈されるように感じられる
  • 確信度低(60%):ultra effortでは静的言語が動的言語よりやや優れる
    • ultra effortではハーネスがより速くフィードバックをモデルに返すため、正確性か効率のいずれかで何らかの利得が生じるのではないかという非常に弱い確信だが、そうならない理由もいろいろ考えられる。たとえば、Rustコンパイラを呼び出す際にcodexがまったく同じエラーを何度も犯し、その修正に追われるのをよく見かける。そうしたことが、フィードバックサイクルが速くなるといった仮説上の利点を圧倒してしまうのかもしれない
  • 確信度高(98%):Jのような「風変わりな」言語が優位に立つことはない
    • 動的 vs 静的という全体的な主張と同じ理由に加え、AIラボがマイナーな言語のために合成データによるRL環境の整備にそれほど(あるいはまったく)労力を割いていないだろうという考え

Zstd

最初の評価では、エージェントにzstdのRFC(と正誤表)を与え、完全なzstdデコーダを実装させた(エージェントはインターネットアクセスのないコンテナに閉じ込められている)。テストはエージェントには与えていない。zstdのような表面積を持つものでは、テストがあらゆるケースをカバーすることを期待するのは現実的ではない。たとえば、zstdはかなりよくテストされているソフトウェアだが、私自身かつてzstdでデータ破損のバグを見つけたことがある。テストスイートは、何年も潜んでいるような極端なコーナーケースを見つけることを意図したものではなく、RFCから「容易に」導き出せるはずのさまざまなケースが正しく動くかをチェックすることを意図している。

以下では、x軸がコスト、y軸が正確性スコアだ(左上にいくほど良く、右下にいくほど悪い)。GPT-5.6 Solでのmediumとultra effortの平均結果。もしmediumだけを見て(そして異なるタスクで結果がしばしば大きく異なるという事実を無視すれば)、Aldersonの評価と同じような結論、すなわちLLMを使う際には動的言語の方が効率的で優れているという結論に達するかもしれない。なぜなら(比較的マイナーな言語を無視すれば)動的言語のクラスタが静的言語のクラスタより左上に位置するからだ(比較しやすいように、静的 vs 動的にAldersonと同じ色分けを使っている)。しかしultra effortを見ると、結果はかなり入り混じっており、最も良い成績を収めているのは静的言語が2つで、より良い結果の中では動的言語よりも静的言語の方が多い。

下のグラフには、x軸をコストではなく時間に切り替えるトグルもある。mame/ai-coding-lang-benchでは、より速く結果が得られることに価値があると指摘されていた(個人的には、結果が出るまで時間がかかるので待ち時間に別の作業をするため、そうは感じない)が、そちらも見てみることができる。同様に、どちらの言語タイプがもう一方を支配するということもなく、ただしこの特定のタスクのmedium effortでは、やはり最良の動的言語の結果が最良の静的言語の結果を上回っている(とはいえ、やはりかなり近い)。

まったく些細なcavemanモードの評価と、もう少し些細でないcavemanモードの評価を比較したときと同じように、些細な評価で成り立っていた非常に強い関係は、このより大きなケースには汎化しないことがわかる。あのときと同様に、性能の極端な比率は、人間にとってもはるかに時間と労力がかかるであろうアセンブリを使う場合や、AIラボが合成RL環境データの生成に労力をかけているとは考えにくい比較的マイナーな言語を使う場合といった、性能が悪くなることが予想されるケースを除いて、こうした大きな評価では消えてしまう。

これは、Jのような非常に高密度な言語が効率の観点から理にかなうと示唆した最初の評価とは逆の結果であることに注意してほしい。もし非常に大きな予算があり、自分の好みの言語で効果を発揮するようにモデルを訓練ないしファインチューニングできるのであれば、マイナーで「風変わりな」言語を使うことが理にかなう場合もあるかもしれないが、LLMの普通のユーザーであれば、マイナーで高密度な言語を使うよりも、主流の言語を使い続ける方がおそらく得策だろう。

そして、言語の人気度とこの評価での性能をプロットしてみると(図は省略)、より人気のある言語ほどより正確でより安価な解を出すという、弱から中程度の正の相関が観察される。

以前にも述べたように、非常によく似た評価でも結果は大きく異なりうる。たとえば、こちらの最適化1 vs 最適化2の評価で大きく異なる結果が見られたが、最適化1と最適化2はwasmでのbzip2の圧縮と伸長の最適化であり、評価としてはかなり近いタスク同士だ。「動的言語は静的言語より効率的だ」といった強力で普遍的な主張をするには、多くのタスクにわたって評価を実行する必要がある。しかし、

動的型付け言語は、明示的な型宣言を省略することでコードがよりコンパクトになるため、従来の静的型付け言語よりも一般的にLLMのトークンコストが低い。

といった主張が、せいぜい漠然と方向性としては正しい程度で、特定のケースにはあまり関係がなく、一般的にも関係するほど強いものではないかもしれないことを示すには、いくつかのケースを試して、その主張が一般には成り立たないことを確認すればよい。上では、あるeffortレベルではその主張がある程度なんとなく正しいように見えるが例外もあり、より高いeffortレベルでは特に正しいとは言えないことがわかった。これは、評価にそれを完全に無効にする交絡因子がない限り、その主張がおそらく普遍的に真ではないと言うのに十分だ。

Pandoc

しかし、非常に異なるタスクについて、しかも異なる提示のされ方(「仕様を読め」という形式よりもTDDに近い形式)での見方を得るために、次の評価ではPandoc ProgramBenchの評価を取り入れ、私たちのユースケース向けに修正している。ProgramBenchが提示するリバースエンジニアリングの課題の代わりに、エージェントにはProgramBenchの資料とProgramBenchのテストを提示し、各条件の性能を測るためにホールドアウトのテストセットでスコアリングする2

以下の結果では、x軸は再びコスト、y軸はホールドアウトテストでのスコアだ。

以前と同様に、成功やコストと、言語が静的か動的か、あるいは非常に高密度かどうかとの間に強い関係は見られない。ここでも比較的マイナーな言語はうまくいかない傾向がある(ただしClojureはZstdのときよりここではずっと良い成績だ)。また、アセンブリははるかに悪い成績で、これは人間がPandocを実装する場合にZstdを実装する場合よりもアセンブリで書く方がはるかに不利になることが予想され、LLMについても同様だと考える強い理由がないことから、予想通りと言える。

それは結局何を意味するのか?

さあね?

LLMを使う際に何がうまくいくのかについて、私にはたくさんの疑問がある(たとえば、どんなテスト手法がうまくいくのか、どんな言語がうまくいくのか、どんなソフトウェアアーキテクチャがうまくいくのか、バグ修正コストは言語によって変わるのか、一般的なプログラム保守コストは言語によって変わるのか、など)。これらの疑問のほとんどは公開データでは答えが出ておらず、もしAIラボ内で答えが出ていたとしても、その情報のほとんどは公開されていない。

特定の言語がLLMでの使用に適しているという世間で飛び交っている主張のほとんどは間違っているように見える(たとえば、上でリンクした評価で言及されていたRuby、Clojure、JがLLMに特に適しているという主張や、ElixirがLLMに特に適しているというややよくある主張など)が、何が正しいのかははっきりしない。

2014年に、静的型 vs 動的型に関する文献を調べたところ、いくつかのケーススタディを除いて文献をサーベイしてもあまり有益ではないことがわかった。典型的な学術研究の例として、Do Static Type Systems Improve the Maintainability of Software Systems? An Empirical Study(静的型システムはソフトウェアシステムの保守性を向上させるか?実証研究)という論文を見たが、私は次のようにコメントした。

被験者には、既存のコードのエラーを修正するか、スタブメソッドを埋めるかを求めるクラスが与えられた。Javaでは静的クラス、Groovyでは動的クラス。型エラーの場合(およびそれに対応するno methodエラー)、開発者はJavaの方が速く問題を解決した。意味的なエラーでは差はなかった。この研究は33人の被験者でタスク順序をランダム化した被験者内デザインを用いている。注目すべき限界として、この研究はループや再帰といった「複雑な制御構造」の使用を避けている。なぜならそれらは解くまでの時間の分散を増大させるからだ。その結果、すべてのバグは些細なものとなっている。これはタスクを解くのにかかる時間の中央値が数百秒であることからもわかる。タスクには複数のバグが含まれることもあるため、1バグあたりの時間はかなり短い。

ループや再帰といった「複雑な制御構造」を避け、タスクが数百秒で解けるような課題を選ぶことは、プロのプログラマの時間を本当に食うようなタスクに関しては結果を無意味なものにしてしまう。これは、タスクが高々数十から数百トークンで済んだ最初の評価と同じだ。しかしLLMなら、実際に些細でないタスクを与えて、どうなるかを比較することができる。結果が異なるタスクにどれだけ汎化するかという問題はあるが、人間を対象にした研究でもまったく同じ問題があり、しかもより深刻だ(LLMの分散は大きいが、人間の分散はさらにはるかに大きい。同じ人間に異なるシードでたくさんのタスクをやらせることができないのだから)。そして、LLMにZstdデコーダを実装させるのに20ドルかかるのは、言語数や言語ごとの条件あたりの反復回数を掛け合わせれば決して安くはないが、zstdのRFCを読んで実装できるプロのプログラマを雇うのにどれだけかかるかを考えれば、同等の研究が実施されることはなかっただろう。コストが完全に非現実的だからだ。Pandocのタスクではなおさらだ。

LLMによって、多くの問いが実質的に答え不可能なものから、少しの労力と多少のトークンで答えられるものへと変わった。働いているインセンティブのせいで3、こうした問いに近いうちに答えが得られるかは不明だが、少なくとも今は挑戦してみることが可能になった。

上記の理由(異なる問題間での分散のため、さらに多くのタスクを試す必要がある)から、これらの評価では証明も反証もできないが、ある程度の示唆を与えてくれるような、世間で見かける主張はたくさんある。たとえば:

  • 世の中にひどいコードがたくさんある言語(例:PHP)は成績が悪くなる
    • これらのタスクでは誤りのようだ
  • 今や書き直しが非常に簡単なので、Haskellのような強力な言語を使うべきだ
    • これらのタスクでは誤りのようだ
  • 人気のある言語を使うべきだ
    • この主張には弱い支持がある

私の事前登録した予想については、

  • 確信度高(95%):動的言語 vs 静的言語という全体的な主張は成り立たない
    • これは正しいようだ
  • 確信度低(60%):ultra effortでは静的言語が動的言語よりやや優れる
    • これを conclusive に判断する情報は十分ではないが、二択で正しい/正しくないを決めなければならないとすれば、正しくないと判定する
  • 確信度高(98%):Jのような「風変わりな」言語の優位性は成り立たない
    • これは正しいようだ
  • [ドラフト読者から]:「小規模では動的言語が優れるが、プロジェクトの規模が大きくなるにつれて静的言語に追い抜かれる」
    • これらのタスクでは支持されない(はるかに大きなPandocタスクで、小さなZstdタスクと比べて静的言語が動的言語より大幅に優れるということはなかった)が、タスクとタスクの提示方法があまりにも異なるため、これがタスクサイズのスケーリングによるものなのか、それとも他の違いによるものなのかは不明だ

ちなみに、Pandoc評価でClojureがZstd評価と比べて大幅に改善した大きな理由の一つは、Zstd評価ではmediumで40回中36回、ultraで40回中5回のClojureプログラムが、byteへの変換が128–255で例外を投げるためにテスト失敗したことだ(おそらくunchecked-byteを使うべきだったのだろう)。彼らはその変換を不適切に使っていた。

これは現実の結果だ。もし最高の公開GPTモデルにZstdの実装(そしておそらくこの問題が出てくるような他のビット/バイト操作タスク)を頼めば、この特定の形で失敗するコードを吐き出すということだ。これを捕捉するテストがあればバグは修正されるが、それでも時間とトークンがかかる。ある言語が良い成績を収めたかどうかに関わらず、このようなコストは至る所にある(たとえば、cargoが繰り返し間違った引数で呼び出され、すぐに捕捉されて修正されるが、私が実際のプロジェクトで見ている限り、codexにcargoの呼び出し方を明示的に指示しておかないと、このループだけでかなりのウォールクロック時間を消費することがある。そしてそれはコンテキストウィンドウ内のスペースに見合う価値があることは明らかだ)。

いずれにせよ、これは、LLMでどの言語やどのクラスの言語が特に優れているかについて強い主張をしたいのであれば、かなり多くの異なる評価を実行する必要があることの例示だ。なぜ特定の条件があるスコアを得たのかを掘り下げると、スコアの原因となった失敗は一般に何か特異なもので、それがタスク間やセットアップ間でどれだけ汎化するのかは必ずしも明らかではない。1つの評価、あるいは5つや10の評価のスコアを見ただけで、プログラミング一般について結論を導く方法はない。

Zstd評価でもPandoc評価でも、言語の人気度と良い結果(より高い正確性、より低いコスト、より短いウォールクロック時間)との間に相関が見られるのは事実であり、他の評価でもこれが見られることは十分にありそうだが、特定の言語について強い結論を導くのは誤りだろう。GitHub CIのデータにしたがって異なるプロジェクトでどれくらいの頻度でビルドが壊れるかを調べた際に、私はこのような警告を出したことがある。そこでは、プロジェクト間でビルドがより頻繁に壊れたり壊れなかったりする理由はさまざまであり、プロジェクト間で結果が必ずしも比較可能ではないため、強い結論を導くべきではないと指摘した(たとえば、あるプロジェクトのmainブランチが他の検証を経た何らかのリリース候補である場合、そのプロジェクトではビルドの破損が少ないことが予想されるが、それは人々がmainに対して直接開発しているプロジェクトとは比較可能ではない)。

その直後、高スコアを出した言語の関係者の一人(記憶が正しければMartin OderskyとScalaだった)がその投稿をツイートし、その言語の高い順位を言語の勝利として引用した。そこでの結論は不当なものだったし、ここで働いている多くの分散要因のために、ここで単一の言語についてそのような結論を出すことはさらに不当だろう。

(評価の妥当性を仮定すれば)このデータは一部の強い主張を反証でき、他の主張については示唆を与えることができるが、タスクが2つしかないため、特定の言語ではなく言語のクラスについてのみ示唆を与えることができるに過ぎない。特定の言語が良いか悪いかは、何らかの特異な理由による可能性があり、それが他のタスクに汎化するかどうかはわからないからだ。

コメント/修正/議論をしてくれたMax Bittker、Yossi Kreinen、Aaron Levin、Alan Boll、Luke Burton、Marco Primi、Milosz Danczak、Justin Blank、Tom Adamczewskiに感謝する。

付録:ai-coding-lang-benchにおけるいくつかの問題

上で述べたように、ここでの私の評価は手早く雑な評価であり、欠陥があることは確かなので、ここで提示した評価は素晴らしくてあちらが悪いと言いたいわけではないが、Endoh氏のai-coding-lang-bench評価におけるいくつかの問題を挙げておく。

一つの問題は、一部のテストで間違った実行ファイルが実行されていたように見えることだ。公開された実行のセットアップでは、候補の生成した実行ファイルが../minigitにあるにもかかわらず、各候補のディレクトリ内で../../minigitを実行していたように見える。../../minigitは存在しない。

静的型付け言語の方が正確性スコアが低かったため、評価の著者は「600回中唯一の失敗はRustとHaskell(いずれも静的型付けで、比較的『難しい』言語)で起きた」と指摘し、「Cのメモリ管理、Rustの所有権モデル、Haskellのモナド/純粋性のような『難しい言語』はAIにとってオーバーヘッドになるかもしれない」と示唆している。

しかし、Rustの失敗は../../minigitに実行ファイルが存在しないためにテストが失敗したことが原因だった。最初のGoの実行がln -sf minigit-go-1-v1/minigit ../minigitを実行してgenerated/minigitを自身の実行にリンクすることでこれを「修正」したが、これはそれ以降のすべての実行(すべての言語で)が、実際には最初のGo実行の実行ファイルを動かしていたことを意味する。Rustを自身の実行ファイルに対して再スコアリングしたところ(存在しないファイルを実行させて失敗させるのではなく)、Rustは満点を獲得し、Rustが難しい言語だから失敗したという説は無効になった。

他のテストにも問題がある。たとえば、2つのテストは、実際にチェックされている値に関わらず合格してしまう構造になっている。そのうちの1つのテストには次のようなコードがある。

  if ../minigit commit ...; then                                                                                                                                       COMMIT_POST_CHECKOUT=$(cat .minigit/HEAD)                                                                                                                                                                        if grep -q "parent: $COMMIT1" \                                                                                                                                    ".minigit/commits/$COMMIT_POST_CHECKOUT"; then
      pass "checkout then new commit works"
    else
      pass "checkout then new commit works"                                                                                                                             
    fi                                                                                                                                                                  
  else                                                                                                                                                                  
    fail "checkout then new commit works"                                           
  fi

内側のifは両方の分岐でpassしており、これはほとんど次のものと同等だ。

  if ../minigit commit ...; then
    pass
  else
    fail
  fi

内側のifは本来実際のチェックを行う意図だったように見えるが、コーディングミス(おそらくコピー&ペーストミス)のために、チェックが実質的に省略されている。

また、上で述べたように、エージェントはテスト環境を変更できる。最初のGoエージェントが壊れた環境を修正するためにそうしたようにだ。彼らはテストと環境への完全なアクセスを持ち、何でもできるし、テストスイートは開発中に可視でホールドアウトもないため、テストに合格するが「実生活」では役に立たないプログラムを作るような、テストに特化したチートへと容易につながる。大まかに言えば、多くのプログラムが仕様の大部分を実装できていないのにすべてのテストに合格していることからも、そうしたことが起きたように見える。エージェントがテストに合格する方法を「理解」し、仕様の実装よりもそれを優先したのかもしれない(テストが非常に薄く、簡単に合格できることを示しているだけかもしれない)。

もう一つの問題は、すべての実行でClaude Code CLIのバージョンが同じではないことだ(2.1.66から2.1.68までばらつきがある)。他にもこのような小さな問題は一握りあるが、上記の問題に比べればおそらく小さい。

付録:ループでのmedium vs ultra

比較できるものの一例として、mediumでエージェントに作業を続けさせることがどれだけコスト効率が良いかに興味があった。そして頭の片隅では、"Ralphループ"の推進者が言うこと、すなわちループの各反復でコンテキストウィンドウをクリアしてエージェントにプロンプト全体を再提示する方が良いという主張についても考えていた。上と同様に、ここでの私の事前登録した予想は次の通りだ。

  • 確信度ゼロ(50%):Ultraはループでのmediumより効果的だ
    • これをどう考えればよいかわからない。そうだとする根拠は、ultraは何らかの形で設計されたもので、ループでmediumを繰り返すより賢いはずだということだろう。しかし、ultraがより高速化のためにトレードオフされている可能性もあるし、分散が非常に大きいことは指摘した通りなので、ultraがほとんどの問題で勝つとしてもここでは負けるかもしれない。ultraはウォールクロック時間などを改善するためにトレードオフするようより最適化されている可能性もある。ultraは隠しテストで正確性が達成された後に停止すべきことを「知らない」のに対し、完全に正確性を達成したmedium条件はこのセットアップでは再実行されないため、mediumのループが大きく有利になるという欠点もある(これは、誰かがこれらをどう使うかという観点では現実的だと主張できる)
    • これは50%+イプシロンと言えるかもしれない。私の頭がこのようにフレーミングしたのであって逆ではないのだから。しかし、せいぜい極めて低い確信度だ
  • 確信度中(80%):コンテキストを保持して続行する方がRalphループより優れる
    • /goalモードなどはデフォルトでこれを行わないし、おそらくAnthropicやOpenAIの人々はRalphループのようなものを試して、効果が低いと判断したのだろう
    • コンテキストウィンドウを非常に注意深く監視することは、ハーネス(やモデル?)が改善するにつれて重要性が薄れてきているように見える。2025年後半から2026年初頭にかけては、長時間実行されるタスクに取り組む際に問題を避けるためにコンテキストウィンドウを捨てることがよくあったが、時間とともにそれは稀になってきた。それでも当時、世間で言われていることに注意を払っていなかったため、私はコンテキストを保持することをデフォルトにして、明らかな問題があるときだけクリアするという形でエージェントループを実行しており、それはうまくいっているように見えた。たとえば、世界最強のAzul AIをその方法で構築した。だから、当時でさえ、ループの反復ごとにコンテキストをクリアすることをデフォルトにするのが正しい選択だったのかは、私にははっきりしない

以下は、テストの正確性が100%でない個々の実行を単に再開するプロンプトと、コンテキストを破棄して元のプロンプトを再提示するRalphループのようなプロンプトについて、mediumのループ vs ultraの平均結果を、ultraの正確性スコアが良い順に並べたものだ(x軸はコスト、y軸は正しいテストケースの数)。

この一つの問題については、平均すると、単位コストあたりではultraを1回実行する方がmediumを繰り返し実行するより優れているように見える(単位時間あたりではなおさらだ)。そして以前のコンテキストを保持して続行する方がRalphより優れる。mediumを素朴に繰り返し実行する際の問題は、エージェントが悪い解に固執して進捗しなくなることがある点だ。Ralphループの背後にある理論は、悪いコンテキストを捨てることでこれが起きるのを防げるというものだが、悪い成果物からは救ってくれない。

LLMを使っていて気づいたこととして、コードの塊をLLMに修正させたり、その場で書き直させたりするよりも、捨ててLLMに一から書き直させた方がうまくいくことが多い。PostgresをRustで書き直しているMichael Malisも同じことを指摘している。これは以前に指摘した考え、すなわち分散が大きいこと(そしてこの経路依存性)に加えて、トークンを費やしても構わないなら何度もサイコロを振って最良の結果を選ぶ方が良いことが多いという考えにも関連している。

この一つの条件だけを見て静的言語 vs 動的言語について多くを語るのは難しいが、「反復する際には静的言語が優れるだろう」といった素朴な考えが明らかに真であるとは言えない。もし一つ、目に飛び込んでくるパターンがあるとすれば、Ralphループがコンテキストを保持して続行する場合と比べて最も大きく劣ったケースは、一般に動的言語だったということだ。これは型情報の欠如が原因の可能性があるが、これが本当のパターンかどうかを観察するには、軌跡の違いをより詳細に見るとともに、他の例も見る必要があるだろう。Ralphループの流行が過ぎた今、Ralphループ自体を気にしなくても、新しいタスクを開始したり新たなコンテキストで開始したりする際により効果的にコードベースに変更を加えられることは気にかける価値があり、ここでのパターンは考えられる利点を示唆している。

付録:Guards of Atlantis 2

より「ビジネスロジック」的な評価として、問題の提示方法と実際の実行の両方でそう感じられる3つ目の評価を試みた。Zstd評価もPandoc評価も、プログラマが直面するタスクとしてはかなり特殊だと言える。zstd RFCほどよく書かれた徹底的な仕様を受け取るプログラマは多くないし、ProgramBenchのテストから得られるような大量の事前作成されたテストを渡されるプログラマも多くないからだ。

ここでのアイデアは、ボードゲームを実装することだった。一般に、ボードゲームのルールはきれいな仕様を書く専門家ではない人々によって書かれるため、ボードゲームの実装は、非プログラマ(あるいは良い仕様を書くのが得意でないプログラマ)が誰かにタスクを与えるときに起きることにより近い。

ここでの問題は、LLMにとって些細ではなく、かつ私がスコアリングのための妥当なオラクルを持っているゲームを見つけることだ。たとえば、LLMはScoutAzulのルールを一発で実装できたため、これらは貧弱なタスクだ。LLMがすぐに一発で解けないゲームについては、Guards of Atlantis 2のオラクルをたまたま持っている。というのも、友人たちと遊ぶためにLLMにコピーを実装させたからだ(著作権侵害のないインターフェースを作る方法が思いつかないので、これについてはリンクなし)。バックエンド自体は私の作業時間で数時間で済んだが、ルールをほぼ正しくするまでにはかなりのLLM時間を要した。ルールが多くのプログラマに渡される問題記述と同じように厄介でありながら、原則として正しいルールを理解して実装することが可能だ(結局、人間はオフラインで正しくゲームをプレイする際に暗黙のうちにこれをやっている)という点で、これをタスクとして気に入っている。

ボードゲームのルールでは、ルールを厳密に書かれた通りに読むと誤りで、「常識」(あるいは何らかのFAQを読む)を使って正しくプレイする必要があるということがかなりよくある(J C Lawrenceのようにこれを避けようと努めるゲームデザイナーもいるが、これはかなり稀だ)。Guards of Atlantisにはこのようなルールがかなり多い。Guards of Atlantisのデザイナーは、ルールの精神や常識的な解釈といったものは存在せず、常にルールを書かれた通りに読むべきだと公言してもいるので、常識的な解釈を無視してルールを文字通りに読む必要があるケースもまた多くある。この組み合わせはLLMにとって(そして、デザイナーの意図通りにゲームをプレイする人間の割合から判断すると、人間にとっても)非常に難しい。

ルールを読んだだけで正しくプレイすることは、実質的に不可能だと思う(もちろん可能ではあるが、どのルールを文字通りに読み、どのルールをそうでないと読むかを知る必要があり、それはルールがどちらのメタ・ルールセットに従うかを推論するために使える一貫したシステムを定義していないため、ランダムに推測して運よく当てる必要がある)。ゲームを実装していたとき、LLMにルールを理解させるために、非公式のルールFAQ(これは正しい)、非公式の短縮版ルール(公式ルールよりよく書かれており正しいが不完全)、オープニングブック(オープニングブックには合法手のみが含まれているという仮定でルールをテストするために使える)、Discordのルールチャンネルでのコメントなど、さまざまなリソースを与え、FAQやDiscordのコメントの方が実際に印刷されたルールよりも権威が高いという理解のもとで、これらにわたる一貫性チェックをLLMに行わせた。個人用の月額200ドルのOpenAI/codexアカウントで、LLMに私の余ったキャパシティをすべて使わせて一貫性チェックを実行させ、ルールの修正を行わせた。これにどれくらい時間がかかったか正確には記録していないが、なんとかプレイ可能な程度の、しかし正確だとはとても信頼できない結果を得るまでに1〜2か月ほど修正をゴリゴリ回したと思う。

これをある程度信頼できる唯一の理由は、Pedro OliveiraもGuards of Atlantisを実装しており、彼らはまったく異なるアプローチ(LLMに自分で考えさせるのではなく、人間がLLMを駆動するより標準的なアプローチ)を用いたことだ。実装を比較したところ、それぞれに10件程度のバグが見つかった。おそらく、両方の実装が同じように誤っているために残っているバグや、実装が異なるのにチェックシステムが気づかなかったバグもまだあるだろうが、両方の実装のルールは今ではかなり堅牢になっていると思う。そうやってこのゲームのオラクルを持っているのだ。

仕様が曖昧で矛盾しており、時にはまったく間違っていることもあり、正しい結果を得るために他の情報を利用する必要があるという点で、より現実世界で得られる「仕様」のように感じられるため、このタスクが気に入っている。この評価では、LLMが厄介な形式のデータにアクセスできるかを試すテストにならないように(たとえばオープニングブックを画像の集合から何らかの構造化データに変換したり、ルールのスキャンをテキストに変換したりといった)、エージェントには、LLMにデータ抽出を指示したものについては元のデータと抽出されたデータの両方を与えた(元のデータは、LLMが望めば抽出エラーをチェックできるように提示した)。

このタスクは古いモデル(大部分をGPT-5.1か5.2で、残りを5.4か5.5で実施)でやったが、古いモデルに与えたようなガイダンスなしでは、より新しいモデルでもタスクは依然として難しすぎた。言語に関わらず、エージェントはこのタスクでおよそ0点を取った。

ちなみに、LLM(そして人間)が何に苦労するのか興味があるなら、いくつか例を挙げる。あるカードのテキストは「あなたに隣接するユニットを対象とする。攻撃の後:異なる敵ヒーローに対して1回繰り返してもよい」と書かれている。

このゲームでは、ヒーローはユニットの一種だ。ルールを完全に理解した上で、たとえば「攻撃の後」が何を意味するかなどを踏まえて厳密に読めば、これは単一のユニットを攻撃するか、あるいは2人のヒーローを攻撃できることを意味するはずだ(結局、異なる敵ヒーローに対して攻撃を繰り返すということは、最初のユニットがヒーローだったことを意味する。さもなければ、異なるヒーローであるユニット、ではなく異なる敵ヒーローという表現にはならない)。

このカードには実際、不明瞭だという苦情があったためにカードに印刷された事実上の正誤表があり、「(元の対象がミニオンだったとしても繰り返してもよい)」と書かれている。これはすでにLLM(そして一部の人間)にとって混乱を招くが、本当に厄介なのは、同じ構文を使っている他のカードにはこの修正がないことだ。同じ構文を使っている他のカードを正しくプレイするには、この構文が使われるたびにこのカードにある正誤表を適用してプレイすべきだと知っておく必要がある。ゲームデザイナーが好んで使う、特定の非文字通りの意味を持つ構文は他にもいくつかあり、それを念頭に置いておく必要がある。

文字通りにプレイすべきでないルールの別の例として、「異なる対象に、A、Bのうち1つまたは両方を選ぶ」と書かれたカードを持つキャラクターがいる。これを厳密に書かれた通りに読めば、異なる対象に対してAかBのいずれか、あるいはAとBの両方を行えると予想するだろう。しかし、ゲームの精神の一部であるメタ・ルールとして、あるキャラクターが1回のカードプレイで別のキャラクターを複数回攻撃することはできないというものがあるため、カードに書かれている通りに両方、すなわちAとBの両方を何らかの数の異なる対象に行えるという解釈は正しくありえない。類似の推論や類似の構文がどのように使われているかに基づくと、このカードが意図されている解釈は「1つを選ぶか、異なる対象に両方を選ぶ」であり、これはおそらく依然として曖昧で、「1つまたは両方を選ぶ(両方の場合は異なる対象でなければならない)」と書いた方がより明確だろう。

人間としては、「ゲームの精神」が何であるかを理解すれば、こうしたことを解決できる。しかし、これは設計上、ルールにはっきりと書き下されておらず、Discordでの議論から推論しなければならず、これは今日のモデルの能力を超えているように見える。多くの専門タスクで今日のモデルに劣る人間でも、これができるのだが。

LLMにルールを実装させていたとき、LLMが頭打ちになって完全に正しいルールに収束しなかった理由は、LLMがあるルールが矛盾しており誤っていることを観察する点にあった。するとLLMはそのルールを修正しようとし、他のものも一貫して正しくなるように修正しようとする。これは時に物事をより正しくし、時に誤りを増やす。誤りを増やすとき、LLMは既存の正しいテストを誤ったテストに書き換えてしまうことがあったため、しばらくするとLLMは正確性を改善しているわけではなく、どのルールが誤っているかをぐるぐる回しているだけになっていた。それは、何をチェックし、どうチェックするかについてのガイダンスがあってのことだった。そのガイダンスなしでは、今日利用可能なより高度なモデルでも、LLMはこれを合理的な方法で乗りこなすことができなかった。

ここで良い評価となるのに適したルールの複雑さを持つボードゲームは他にもあるはずだが、定義上、それはオラクルを作るのに多少の作業がかかるものであり、私は適切なルールのゲームについて手元にオラクルを持っていない。私の目標が評価を作ることだったら、実際のゲームのリプレイデータを使って多くのゲームについて良いテストやオラクルを得ていただろうが、私の目標は特定のゲームを友人たちとプレイすることだった。しかし、もしこのボードゲームというアイデアを試したいのであれば、スケーラブルな方法で数百(数千?)ものこうしたものを作ることは可能はずなので、数百、数千のゲームについてかなり正確なオラクルを得て、どのゲームが今日のLLMにとって興味深いテストとなる適切なレベルにあるかをチェックすることができるだろう。

これはある意味ちょっと妙な問題だ。明確な仕様、たとえば明確に書かれたルールセットが与えられれば、Guards of Atlantisよりも複雑な成果物でもLLMは実装できるのだから(zstd RFCの方がより複雑だと私は主張するし、Pandocは確実にそうだ。Pandocがサポートする個々のドキュメントフォーマット、たとえばPDFでさえ、Guards of Atlantisより複雑だ)。だから問題は、LLMが苦労するほどルールが複雑なゲームを見つけることではなく、むしろルールが十分に曖昧あるいは矛盾していてLLMが苦労するが、完全にお手上げというほどではないゲームを見つけることにある。これは実際の現実世界の問題でもある。人間は一般に明確な仕様を書くのがあまり得意ではなく、モデルやハーネスが人間の不明瞭で矛盾し、時にはまったく間違った仕様をどれだけうまく扱えるかは、zstd RFCのように良く書かれた仕様からLLMがどれだけうまく実装できるかや、4800ものProgramBenchのPandocテストケースとドキュメントを渡されたときにLLMがどれだけうまく問題を実装できるかよりも、典型的なユーザーにとってはおそらくより関連性が高い。そしてこれらの問題は原理的には解決可能に思える。人間は、ボードゲームを正しくプレイしたいと思えば(ZstdはおろかPandocを「プレイ」することを望むべくもないような人でさえ)、世の中にある情報のごちゃまぜをうまく処理してボードゲームのルールが何であるかを理解することが一般にできているのだから。

付録:さまざまな決定の理由

  • ultraをテストすること
    • これはハーネスの問題であってモデルの問題ではないので、本来測定すべきではないと言う人も見てきた。モデルやハーネスの改善に取り組んでいるのであれば、これらを別々に測定したい気持ちはわかるが、ユーザーがどのように使っているかを見る際には、多くの人は単にcodexやclaudeをさまざまな組み込み機能やオプションとともに使うだけだ。それがハーネスの問題かユーザーの問題かは彼らにとってあまり関係がない
  • codexを使うこと
    • 同じ理由で非常に薄いハーネスを使う評価を見てきたが、私がcodexを使い非常に薄いハーネスを使わない理由も上と同じだ
    • 同様に、このcavemanモデルの評価では、ClaudeではOpusとFableを、codexではGPTを使った
  • インターネットアクセスなし
    • インターネットアクセスを与えるとモデルはしばしばカンニングするし、インターネットで検索しても問題を解くソースコードが見つからないような問題はたくさんあるので、これによりこれらの評価はそうしたものにより近くなる
  • 世間で出回っている多くのベンチマークと比べて比較的大きなタスク
    • LLMに些細なタスクをやらせることも多いが、私の時間を奪ったりトークンを消費したりするものは、Aldersonの評価やEndohの評価にあったようなタスクよりも大きい傾向がある。LLMは些細なタスクについては十分に優れているので、ある条件がそれらの些細なタスクで少し良くなったり悪くなったりしても私にとってはあまり違いはないが、Guards of Atlantisの実装のようなタスクでは、タスクがなんとか動くように足場を組むだけでも何時間もかけなければならず、モデルが何で性能が良くなったり悪くなったりするかは大いに気になる
  • エージェントが指定したプロンプト
    • 公開されている評価は、タスクをあまり詳細に指定しない比較的薄く軽量なプロンプトへと移行しているようだ。これは、エージェントがタスクを設定するとエージェントがタスクで成功するのを助ける情報を与えすぎてしまうため、より良いとされている
      • それをテストしたい気持ちはわかるが、エージェントによって設定されたタスクでエージェントがどれだけうまくやるかについても私は大いに気にかけている。というのも、私がエージェントに実行させるタスクの多くはエージェントによって定義されたタスクだからだ。両方のスタイルでエージェントがどう振る舞うかが気になるのであり、片方のスタイルだけではない。公開評価は片方のスタイルへと移行している
  • Zstd評価:問題や失敗したテストを伝えずにバグを修正させること
    • 一般に、エージェントに特定のものを修正するように言えばそれは修正するが、必ずしもそのクラスの問題を修正するとは限らない。何が問題かを伝えずに問題があるとだけ伝えると、狭く脆い修正を入れるだけでなく、より一般的なことをすることがあると気づいたので、このような指示を与えられたときにエージェントがどう振る舞うかも気にかけている(もちろん、エージェントに狭く脆い修正だけをしないように言うこともできるが、それはしばしばうまくいかない)
      • これは、上でPandocのホールドアウトの脚注で指摘した問題と少し関連している。ホールドアウトのテストセットがあることをエージェントに伝えることが、エージェントにより汎化された脆くない解を生成させるように見えた、という問題だ

付録:これらの評価の問題点

性能ベンチマークに関しては、私は十分にやってきたので、自分のベンチマークがどのように欠陥があるかを大体把握しており、時間/労力と欠陥のトレードオフについて情報に基づいた判断ができ、存在する欠陥が理解しようとしていることにとって重要ではないというまあまあの確信を持てる。AI評価については、十分にやっていないので、このような感覚を持てていない。そのため、メタレベルで、私が行うどんなAI評価にも、私には未知の欠陥があると予想している。

ここで欠陥があると予想するもう一つの理由は、これらの評価をセットアップするのにコーディングエージェントを使ったことと、1分調べるたびに少なくとも1つの問題が見つかったことだ。これは、これらの評価にはもう少し調べれば明らかになるような追加の欠陥がかなりある可能性が高いことを示している。しかし、これを「Gary Bernhardt」レベルの正確性ではなく「ちょっとしたおもちゃのプロジェクト」レベルの正確性にしたかったので、いくつかの問題を修正したところでやめた。

検証エンジニアとして働いていた頃、2007年頃にオースティンでSun/Oracleのエンジニアによるミートアップに参加したことがある。そこでは、バグが見つかるまでの時間をチップのリリースに対する信頼度に変換するという考えが数学的に形式化されていた。最近、Will Wilson(Antithesisの共同創業者)が、Antithesisの一部の人々が生態学の数学(希少種の観察に関する文献)を使って真のバグ率を推定したと述べているのを聞いたが、これはそのSun/Oracleのエンジニアが20年ほど前にやっていたことの、はるかに洗練されたバージョンのように思える。

それはクールなアイデアだが、1分見るたびにバグが見つかるときには、他にもたくさんのバグがあることを教えてもらうのに凝った数学は必要ない。もしこれを仕事としてやっていて、これらの評価の忠実度を気にかける何らかの理由があったなら、これらをもっと詳しく見てさらに問題を修正するのが理にかなうだろう(そして、もしこれを仕事としてやっているなら、LLMにこれらの評価のセットアップを指示する際にもっと少ないミスで済むスキルと経験を私はおそらく持っていただろう)。しかし、「動的言語はLLMを使う際に静的言語よりも意味のある形で優れているという主張は真か?」という問いに答えるという目的のためには、その主張が真ではないという確信が少し強まったし、他にも何らかの実行可能な結果をもたらしそうな問いはたくさんある(たとえば、どんな手法やテストライブラリが最もうまく機能するかなど)。

私は通常、ある程度しっかりしていると感じるまでブログに投稿しないが、そのため、好奇心を満たすのに十分なだけデータを調べたものの結果を公開しないことがよくある。こうした未公開の結果について人と話すと、たとえ私が本当に気に入る基準まで仕上げられていなくても、話した相手はしばしば結果に興味を示す。これは、話していない人々も興味を持つかもしれないという indication のように思える。これまでの経験から、これを私が本当に気に入る基準まで持っていくには、これまでかけた時間の少なくとも10倍はかかるだろうと感じている。現在かなり忙しく、何か月もの間その時間を取れる気がしないし、その時点になっても本当に公開することはないだろうと思う。最近の投稿で、ほぼ1年前に分析した、事故の際の脳震盪リスクについてどの車がより良いかを理解しようとした分析について触れたが、そこではそれを理解するためにしばらく時間を費やし、自分を納得させるのに十分な答えを得たものの、結果を公開できるほどきれいにするために必要な作業を結局やらなかった。

その分析からは「出版可能」に見える結果もいくつかある。たとえば、実際の衝突データからHICと速度の関係が4乗に見えることを見つけた、といったものだ(この関係を見つけようとした論文があるが、間違った種類の分析を行い、「O(n)」的な関係を見つけることができず、はるかに曖昧なものになっていた)。しかし、私はそれが論文になるかブログ投稿になるかを気にしたことはあまりなく、分析を公開できるほどきれいにする代わりに、ただ次の分析に移ってしまうことが多いのだ。

この路線のより最近のプロジェクトとして、超人的なAzul AIを作った後、はるかに人間の時間をかけないプロセスで超人的なSplendor AIを作ろうとしたことがある。それは成功しなかったと思うが、私が見つけられた他のすべてのSplendor AIをかなり上回っており、これはまずまず興味深い結果だ。ボードゲームAIについては投稿を書けるくらい十分に知っていると思うが、私の主な関心はまともなものが得られるかどうかを確かめることにあり、それからずっと他のプロジェクトをやるばかりで、きちんとした記事を書くのに時間を費やしていない。そこで興味深いと思うことの一例は、やりたい性能最適化の多くが実際には結果を変えてしまうため、結果を変えないことが厳密にチェックできる最適化だけに頼ることはできないという点だ。しかし、コーディングエージェントに強度を下げない形でこれらの最適化を行うよう素朴に頼むと、強度を下げるようなことをいろいろやってしまう。強度の低下が非常に深刻なケースは簡単に捕捉できるが、たとえば自分のAIとの自己対局では強度が変わらないのに人間や他のAIに対しては強度が低下するといった、より微妙な問題もあるため、悪い最適化を捕捉するための何らかのプロセスが必要であり、それは本質的に、直感とLLMへの依存(LLMは非常に役立つが、しばしば完全に間違っている)の組み合わせを使って設計しなければならない恣意的なプロセスだ。

こうしたデータ的なプロジェクトについて、LLMは私の好奇心を満たすのに十分な強さの結果を得るのに必要な労力を大幅に削減してくれるが、私の見るところ、結果を公開するのに必要な労力はあまり削減してくれない(少なくとも、結果の執筆をLLMに任せるのではなく手書きし、結果をきれいで良いものにしたい場合)。つまり、執筆がアムダールの法則のボトルネックになってしまうので、こうしたプロジェクトはより多くやるようになったが、書くものは減っている。むしろ、私がワークフローを変えたせいで、これらを書き上げるのにより時間がかかるようになっていると思う。たとえば、ggplot2からグラフを出力するだけでなく、ある意味ではより優れたインタラクティブなバージョンを作るようにしているが、これは確実により時間がかかる。そしてLLMによるスペル/文法チェックのパスを実行しており(少なくとも今のところ、これが執筆で使った唯一のLLM支援だ)、それでたくさんの問題が見つかる。それぞれを修正をそのまま受け入れるのではなく手動で見ているため(そして私はタイプミスが多い)、これは実際にかなり時間がかかる(前回の投稿では1時間以上、この投稿でも最後まで修正しきらず途中で放棄したにもかかわらず30分以上かかった)。

いずれにせよ、これを公開することは、公開する前に本当に欲しいようなきれいに仕上げられたバージョンではなく、半分できたメモを公開するという実験だ。これについて意見があれば、ぜひ教えてほしい(X Bsky Mastodon)!

現在の評価へのGitHubリンクはない。一方では、本当に載せるべきだと思う。他方では、それらはめちゃくちゃで、コードを公開する前にきれいにしたいことがたくさんあり、いつそれができるかわからないし、こうすれば、少なくとも何人かの友人と結果について話すだけで結果をハードドライブに眠らせておくのではなく、何かを世に出すことができる。

付録:Zstdのより詳細な情報

エージェントには性能を無視するよう指示したが、タイムアウトは無限ではなく、medium条件では一部のテストケースがタイムアウトした。これはある意味不公平だが、スコアに実質的な影響は与えなかった。無限ループでないタイムアウトについては、Clojureで2件(40×34テスト中)、Jで2件、Tclで2件、Factorで1件、PHPで1件あった。そして、9000秒(2.5時間)というタイムアウトは、最大のテストケースが4 GiBであることを考えればかなり寛大だ。4 GiBを2.5時間でデコードできないということは、Graviton 5コアで0.5 MB/s未満という暗黙のレートであり、かなり遅い。

エージェントにこれをセットアップさせようとした際に遭遇したいくつかの問題を挙げる(そして上で述べたように、各問題を見つけるのにかかった短い時間から、さらに問題があることが示唆される)。

  • 当初、ビルドのセットアップがエージェントに明確に指定されていなかったため、一部の言語で、エージェントがエージェントに指定された内容に基づいて合理的と思われることを行ったものの、スコアリング時にうまくいかないというランダムな失敗が起きた
    • ちなみに、最初の結果には非常に興奮した。非常に興味深く見えたし、私のバイアスも裏付けるものだったからだ。動的言語は静的言語より大幅に劣っていた。なんという大スクープだ!しかし、最初のセットアップからの本当の結果は、プロジェクトのビルドが曖昧に指定されていたために生じた問題について、静的言語の方が動的言語よりも問題を抱える可能性が低かったということだった。静的言語の方が、そうした特異なビルド指定のされ方に起因する問題を抱えにくかったのだ
  • 当初のアセンブリ条件では、エージェントはCでコードを実装してからそれをアセンブリにコンパイルし、アセンブリとして提出していた
    • この問題を抱えた状態では、アセンブリは他の言語と同等の成績を収めており、これは非常に興味深い!そして、この問題を修正すると偽りであることもわかる。注意しないと、拡散しやすいが誤った魅力的な結果を非常に簡単に得られてしまうのだ。これら2つの問題を修正した後、結果はかなり平凡なものに見え、論文の体裁で言えば「ネガティブな結果」と呼べるものに収まった。つまり、興味深い、驚くべき、あるいは論争を呼ぶようなことを示す結果ではないのだ。おそらく退屈な言語をわずかに支持することは、10〜20年前の非常にオンラインな人々にとっては逆張りの結果だったかもしれないが、非常にオンラインなトレンドの言説はそこから離れてしまったので、これはもはやあまり興味深い逆張りの結果ではない
  • なぜか、セットアップを行うエージェントが、一部の言語には他にはない通常とは異なる恣意的な制限を課していた(たとえば、RustのセットアップではrustfmtやClippyにアクセスできなかった)。ほとんどの言語にこうしたものがあったが、すべてではなかった
  • (エージェントによって作成された)テストの多くは、エージェントに性能を無視するよう指示していたにもかかわらず、実際には何らかの性能/ストレステストだった(私は4 GiBのZstdを9000秒で処理することを性能ストレステストとはみなさない)
  • 一部の言語条件では、エージェントに対する恣意的な指示があった(たとえば、Haskell条件ではbytestringを使わないようにという指示があり、代替の実装提案に関する指示もあった)
  • 一部の言語条件ではツールチェーンが古かった(たとえば、Zigは0.10だった)
  • 一部の言語条件では、エージェントがZstdを実装するのを助ける足場が用意されていた
  • 一部の言語条件では、利用可能なツールについての説明が誤っていた(たとえば、アセンブリ条件ではGDBにアクセスできると告げられていたが、GDBは動かなかった)
  • 反復的な評価を実行するためのヘルスチェックを担当するエージェントが、評価が十分に進捗していないと判断して、評価内部のエージェントにホールドアウトのテストや他の情報を与えることがあった

厳密にはバグではなかったが、それでも削除したものが一つある。テストの一つは非常に難しく(おそらくエージェントの10%が最初の試行で合格した)、現在のzstdリリースバイナリをテストしたところ、zstdバイナリもこのテストに失敗する。RFCを読むと、これはあるエッジケースの合法性についてのRFCの曖昧さであるように思える。このテストケースに合格する頻度について、どの言語がより頻繁に合格するかという点でかなり強いクラスタリングがあり、これは興味深いと思うが、他のすべてのテストがより直接的な何かを測定しようとしている(少なくとも試みている)中で、これを測定することはあまり有用ではないように思えた。

いずれにせよ、上記のリスト(これは網羅的ではない)において、多くの問題が多数の言語に影響を与え、一部の問題は複数回修正しなければならなかった。全体として、各条件を個別のバグとして数えれば、おそらく100以上のこうしたバグを修正した(エージェントに修正させた)と思うし、さらにあると予想している。Max Bittker(RL環境スタートアップを経営している)と話したとき、彼はこう述べていた。

私が取り組んできたすべての評価で、結局膨大な時間と労力を費やすことになった。主に軌跡(あるいは多くの軌跡の要約)を読んでから問題をトリアージする形でね。たとえば「ああ、このクラスのバグは起きるはずがない、Xを更新しよう」(Xはプロンプト、ハーネス/環境、あるいは検証器)といった具合に

エージェントはこれを雑にやってしまうので、テスト対象のエージェントにとってコンテキスト内にあるもの(ランダムなゴミを追加したり、最悪の場合答えを漏らしたりするのは良くない)と、システムの他の部分で背後で修正されるものとの間で、何が正しいレイヤーで修正されるように細心の注意を払っている

評価を書く際のエージェントは、テスト対象のエージェントの体験に対して十分に敏感ではなく、ただ答えを与えたり、「報酬ハッキングしないようにね、お願い」といった形で内部エージェントの問題にしてしまうことで問題を修正してしまう

私はまた、評価のためにゼロから何かを作ろうとプロンプトするのではなく、既存のもの(リポジトリ、ゲーム、ツール、レベル)を再利用して、その周りにハーネスと検証器を構築することで多くの成功を収めてきた

振り返ってみると、言語横断的な評価を行ったことを少し後悔している。100以上の評価上の問題を修正した後でも、さらにたくさん残っていることは間違いない。もしかするとこれは単に「隣の芝生は青い」という考えで、次に試す評価でも後悔するのかもしれないが、異なる言語を評価するよりも、異なるテスト手法やテストフレームワークがどれだけうまく機能するかを評価する方がはるかに楽だったと思うし、そのトピックの方が少なくとも同じくらい興味深いと感じている。そして振り返ってみると、もっと多くの作業を手作業で行い、エージェントへの依存を減らしていれば、これはずっとうまくいっただろう。たとえば、エージェントに1つの言語の環境を作らせてから、エージェントに検査させるとともに自分でも検査して問題を修正してから、別の言語の環境を作らせるべきだった。これを数回行った後、他の言語の環境を作るためのより良いセットアップができていたかもしれない(そうでなくても、このプロセスを各言語について繰り返すだけで、より信頼性の高い結果が得られたはずで、おそらくそれでもより多くの時間はかからなかっただろう)。

もう一つ注意すべき点は、言語における本物の違いの多くが実際にはテストされていなかったことだ。たとえば、敵対的な入力に対するメモリ安全性などだ。エージェントがCやC++で一般におおむね正しいコードを生成するのがRustよりも難しかったとすれば、それは観察されるだろうが、ファザーやvalgrindや他のツールで問題が見つかるような場合、それは小さなテストセットでは捕捉されにくい。好奇心から、ZstdのCとC++のコードについてメモリ安全性の問題がないかエージェントに(簡単に)チェックさせた。エージェントはCとC++のコードをASan+UBSanで実行し、いくつかのファズ入力(それぞれ4000件)を試したが問題は見つからなかったと主張しているが、もちろん、問題がないことを意味するわけでも、より大きなコードベースで問題が起きないことを意味するわけでもない。

そして実際、Pandoc評価についてメモリ安全性の問題を同様に素早くチェックしたところ、CのプログラムすべてとC++のプログラムのうち1つを除くすべてでメモリ安全性の問題が見つかった(問題は、範囲外のメモリを誤ってデリファレンスするといったものだった。具体的な一例として、Cプログラムの1つでは、切り詰められたLaTeXテーブルが範囲外のメモリ読み取りを引き起こす可能性があった)。これらの問題が数十秒のプロンプトで見つけられるという事実は、人間の多大な労力なしに多くのそのような問題を見つけて修正できることを示しているが、それにはかなりのトークンがかかり、CとC++バージョンのコストはRustバージョンのコストをはるかに上回ることになるし、それらすべてを行った後でも、CとC++バージョンのメモリ安全性についてはRustバージョンほど確信を持てないだろう。

いずれにせよ、結果の分布に興味があるなら、mediumとultraについて以下の通りだ。

ultraの結果がここでやや飽和しているのはあまり好ましくないが、ultraをテストする際の「問題」の一つは、問題が難しくなるにつれて長時間実行し続けることだ(たとえば、Pandocのultra実行のほとんどは12時間以上実行され、アセンブリの実行はさらに長くかかった)。そのため、飽和しないものは、Pandoc評価のような非常に大きなタスクか、Guards of Atlantis評価のような何らかの意味で難しすぎるタスクだ。


  1. ドラフトの読者の一人が「小規模では動的言語が優れるが、プロジェクトの規模が大きくなるにつれて静的言語に追い抜かれる」という予想を事前登録した。[return]
  2. ホールドアウトテストは、そうしないとエージェントがカンニングをしてテスト入力を検出し、合格するテスト出力をハードコードするため、必要に思える(不正をしないように指示しても、時々そうする)。すべてのカンニングがそれほど露骨であれば問題にはならないし(そしてそれは興味深い測定対象にもなりうる。言語間でエージェントが指示に従うかどうかが異なることは、実際のユーザーにとって重要なことだからだ)、しかしカンニングの多くはより微妙で裁定が難しい。たとえば、一部のエージェントはテストの構造に基づいて分岐するコードを書いたが、その分岐の中身は単一のテスト結果に特化したものではなく、同じテストの多くのバリエーションに合格しうるコードで埋めていた。「明らかにカンニングではない」から「明らかにカンニング」までのスペクトル上のどの点についても、何らかのエージェントが試していた。Senior SWE-Benchを見たときに見たように、評価のLLMによるスコアリングは厄介で、バイアスと分散の両方を招きやすい。ホールドアウトのテストセットを使うことにもいくつか問題はあるが、これによりはるかに大きな一連の問題を回避できる。

    一つの懸念として、ホールドアウトテストはエージェントによって作成されたため疑わしいという点がある。意図は、カンニングをしていなければ合理的な人(またはエージェント)が合格できるようなホールドアウトテストを作成することだった。エージェントは、これが合理的でないケースについてこのホールドアウトテストセットを監査し、いくつかを除外したが、私はこれらを手作業でチェックしていないため、少なくとも1つのホールドアウトテストが何らかの形で不公平である可能性は高いと思っている。しかし、ホールドアウトテストに対する全体的なスコアが十分に低いため、少数のテストが悪いことについてはあまり心配していない(AIラボで働いていて次世代モデルを訓練しようとしているなら、この点をもっと心配するだろうが、ここでのユースケースでは重要ではないと考えている)。

    ホールドアウトのテストセットがあることをエージェントに伝えつつ、カンニングをしないように指示しても、ホールドアウトテストで極めて低いスコアとなる露骨なカンニングを防ぐことはできなかったが、エージェントにホールドアウトのテストセットで採点されることを伝えることは、エージェントが可視のテストで達成するスコアを下げる一方で、ホールドアウトテストで達成するスコアを上げるように見えた(これを伝えない場合、多くのエージェントが役に立たない脆いコードでPandocテストで100%を達成した。これを伝えた後は、ultraで1ターン後に100%を達成したエージェントはいなかったが、ホールドアウトのスコアは大幅に良くなり、より良い汎化を示していた)。

    [return]
  3. LLMコーディングの成功の秘訣を教えると謳うさまざまなSubstackやYouTubeチャンネル、その他のものがあるが、実際に実験を実行することに時間を費やすことのROIはあまりない。cavemanモードを見たとき、最大手のプログラミングYouTuberの一人が数分かけてそれを調べてうまくいくと判断した動画があるのを見た。実際にうまくいくかどうかを調べるのに15分費やすことさえ、おそらくその時間を使ってさらにコンテンツを制作することと比べればROIはマイナスだ。

    さまざまな手法について論じる論文もいくつかあり、これらはしばしばほとんどのブログ投稿や動画より詳細に踏み込んでいるが、平均すると必ずしもより有用な情報を持っているわけではない。たとえば、ChatGPT(5.6 Sol、Pro)にLLMに関する言語の有効性についての議論を探させたところ、トークン効率に関するこの論文が見つかったが、これは興味深いアイデアを持っているものの、先に議論したcavemanモードの評価と同じ問題を抱えている。つまり、プログラマとしての私にとって結果が関連性を持つほど十分に興味深いタスクを見ていないのだ。その論文を引用しているものを見てみると、3人の研究者による「The Best Programming Language for Tokenmaxxing」というタイトルの言語のトークン効率に関するこの論文が見つかるが、この投稿と比べると、その論文は4つの言語しか比較しておらず、より劣るモデルを使い、小さなおもちゃの問題(LiveCodeBenchというものからのもの。GPT-5.5で問題を解くコストはしばしば1000トークン程度だ)を使っている。評価がどれだけよくできていようと、この投稿やcavemanモードの評価で指摘したように、小さなおもちゃの問題から私が趣味のプロジェクトや仕事で気にするような問題へと移ると、相対的な結果が大きく異なることがよく見られる。また、その論文では、プロンプトとして「プログラムをテストするには、正確に ./test.sh を実行してください... 私が気にするテストはこれだけです」と与えたと記し、これは現実的だと述べている。「このセットアップはエージェントの振る舞いを研究する現実的な方法だと考えています。日常的な使用では、プログラマはエージェントからテストを隠しません。むしろ、プログラマはすべてのテストに合格するまで作業を続けるようエージェントに指示します」と。しかし、上で述べたように、これを行うと現実世界では失敗する(あるいはエージェントに与えられないホールドアウトテストがあれば非常に高い割合で失敗する)脆いコードが生じる(この問題は、単にテストを数個追加するだけでは解決できない。ファジングやプロパティベーステストのようなものを通じて対処できるかもしれないが、それがどれだけうまく機能するかは別の投稿のトピックだ)。これらの論文が悪いとか、これらの論文から学べる興味深いことが何もないと言っているわけではないが、どんな手法やツールを使うべきかを知りたいプログラマとして、上でリンクしたような論文からはその情報を得られない。

    [更新:Tom Adamczewskiから彼の論文 https://arxiv.org/pdf/2606.30182 へのリンクを送ってもらった。これは上で言及した多くの問題に対処している。この投稿と比べると、はるかに多くの異なるタスクを試しており(これは素晴らしい)、試す言語やタスクの提示方法はより少ない。論文の中で彼らが導いている結論の一つで、この投稿から得られるものとして言語は重要ではないというものがあるが、ここで試した評価から非常にマイナーな言語を除外したとしても、言語の人気度/使用率と結果の質との間に相関が観察できる。Adamczewskiの論文ははるかに多くのタスクを試しているため、この投稿とその論文を組み合わせて見ることで、どちらか一方だけを見るよりも完全な全体像を得ることができる。]

    [return]

この記事は「muse-spark-1.2-contributor」を使用して翻訳されました。

コメント