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++ 같은 정적 언어의 1/2에서 1/3 정도 토큰 비용을 갖는다고 말한다. 저자는 이렇게 말한다

내가 비교한 언어 중 토큰 효율성이 가장 낮은 C와 가장 높은 Clojure 사이에는 2.6배라는 매우 의미 있는 격차가 있었다.

그리고 이후 J를 시도하면서 이렇게 말했다

평균 70토큰으로 압도적이며, Clojure(109토큰)의 절반에 가깝다. 배열 언어는 이국적인 기호 집합을 피할 때 극도로 토큰 효율적일 수 있다. 토큰 효율성이 핵심 동인이 된다면 이는 언어가 진화하는 매우 흥미로운 방향일지도 모른다.

떠돌아다니는 다른 동적 vs. 정적 언어 토큰 비교로는 이것이 있는데, 역시 같은 결론을 지지한다. 이를 벤치마킹과 eval, 실험 설계에 관한 이 연습 시리즈의 8편으로 다뤄보고 싶다면 링크를 클릭해 더 읽기 전에 eval의 문제점을 한번 고민해 볼 수 있다.

우리가 직접 eval을 돌리지 않아도 첫 번째 실험의 한 가지 문제는 과제가 너무 자명하다는 점인데, 위 인용에서 알 수 있듯 J에서는 70토큰, Clojure에서는 109토큰으로 풀리는 문제는 애초에 별다른 문제가 아니다(저자는 Rosetta Code를 사용했다). caveman 모드에 대한 다른 eval과 우리 자체 eval을 비교했을 때 봤듯이, 대부분의 작업이 답을 출력하는 데 있는 자명한 문제와 어느 정도 ‘실제 작업’이 필요한 조금 덜 자명한 문제에서는 결과가 매우 다르게 나올 수 있다. caveman 모드가 주장하고 재현 실험에서 확인된 큰 이득은 몇 개 토큰 이상이 필요한 문제를 보기 시작하면 사라진다. 일반적으로 자명한 과제에서의 성능은 일반화되지 않는다.

두 번째 링크의 문제는 좀 더 미묘하므로 대부분은 부록으로 미루겠지만, 존재하지 않는 잘못된 경로를 실행해 테스트가 실패하게 만드는 등의 문제가 포함된다. 이후 에이전트 중 하나가 존재하지 않는 경로에 자신의 실행 파일을 심볼릭 링크로 연결했는데, 이 경우엔 동작하지만 이후의 모든 테스트가 올바른 실행 파일 대신 그 에이전트의 실행 파일을 실행하게 된다. 저자는 Rust에서 일부 실패가 발생한 의미를 두고 결론을 도출하려 하지만, 그 의미는 Rust에 대한 채점이 Go 에이전트가 해당 고장 난 테스트의 모든 채점을 Go 실행 파일로 심볼릭 링크하기 전에 실행되었다는 것뿐이다.

이런 eval에 의존하는 대신 우리 자체 eval을 돌려볼 수 있다. 이 eval들과 지난 연습에서 다룬 eval들에서 볼 수 있듯, eval 제작자가 말하려 한다고 생각하는 바를 실제로 말하지 않는 eval을 만들기는 매우 쉽다. 의심할 여지 없이 이번 eval 역시 예외는 아닐 것이고 결함이 있을 것이다(자세한 내용은 아래 부록 참조).

사물에 대한 직관을 쌓는 방법으로 나는 결과를 보기 전에 예상을 미리 등록하는 것을 좋아한다1. 친구들에게 미리 등록한 것 중 일부는 다음과 같다:

  • 높은 확신(95%): 전반적인 동적 vs. 정적 언어 주장은 성립하지 않을 것이다
    • 위에서 언급한 이유 때문이다. 이는 caveman eval과 유사하다고 느껴지며, 문제가 커질수록 결과는 기껏해야 희석될 것이다
  • 낮은 확신(60%): ultra 노력 수준에서는 정적 언어가 동적 언어보다 다소 더 나을 것이다
    • ultra 수준에서는 하네스가 모델에 피드백을 더 빨리 전달해 정확성이나 효율성 측면에서 어떤 이득을 줄 것이라는 매우 약한 확신이지만, 여러 이유로 그렇지 않을 수도 있을 것 같다. 예를 들어 codex가 Rust 컴파일러를 호출할 때 나는 완전히 똑같은 오류를 반복하고 나서 고치는 경우를 매우 자주 봤다. 이런 종류의 일이 더 빠른 피드백 주기 같은 가상의 이득을 압도할지도 모른다
  • 높은 확신(98%): J 같은 ‘괴짜’ 언어의 우위는 유지되지 않을 것이다
    • 전반적인 정적 vs. 동적 주장과 같은 이유이며, 여기에 AI 랩이 생소한 언어에 대해 합성 데이터 RL 환경 노력을 훨씬 덜(아쩌면 전혀) 쏟을 것이라는 생각이 더해진다

Zstd

첫 번째 eval에서는 에이전트에게 zstd RFC(정오표 포함)를 주고 완전한 zstd 디코더를 구현하라고 했다(에이전트는 인터넷이 차단된 컨테이너에 갇혀 있다). 테스트는 에이전트에게 주지 않았다. zstd 정도의 범위를 가진 대상에 대해 테스트가 모든 가능한 경우를 커버하길 기대하는 것은 그다지 합리적이지 않다. 예를 들어 zstd는 꽤 잘 테스트된 소프트웨어임에도 나는 한때 zstd에서 데이터 손상 버그를 발견한 적이 있다. 테스트 스위트는 수년간 숨어 있을 수 있는 극단적인 코너 케이스를 찾으려는 것이 아니라 RFC에서 ‘쉽게’ 도출할 수 있는 다양한 경우들이 정상적으로 동작하는지를 확인하려는 의도이다.

아래에서 x축은 비용, y축은 정확도 점수이다(왼쪽 위가 더 좋고 오른쪽 아래가 더 나쁨). GPT-5.6 Sol로 medium과 ultra 노력 수준에서의 평균 결과이다. medium만 보고(서로 다른 과제에서 결과가 종종 크게 달라진다는 사실을 무시하고) 본다면 Alderson eval과 비슷한 결론에 도달할 수도 있다. 즉, LLM을 사용할 때 동적 언어가 더 효율적이고 더 좋다는 것이다. 비교적 생소한 언어를 제외하면 동적 언어 군집이 정적 언어 군집보다 왼쪽 위에 위치하기 때문이다(한눈에 비교하기 쉽도록 Alderson의 정적 vs. 동적 색상 구분을 그대로 사용했다). 하지만 ultra 노력 수준을 보면 결과는 꽤 뒤섞여 있으며, 가장 잘한 몇 개는 정적 언어이고 더 좋은 결과 중에는 동적 언어보다 정적 언어가 더 많다.

아래 그래프에는 x축을 비용 대신 시간으로 바꾸는 토글도 있다. mame/ai-coding-lang-bench에서는 결과를 더 빨리 얻는 것이 가치가 있다고 언급했는데(나는 결과가 오래 걸려서 기다리기보다 다른 작업을 병행하므로 개인적으로는 그렇다고 느끼지 않는다), 그것도 함께 볼 수 있다. 마찬가지로 어느 언어 유형이 다른 유형을 압도하지는 않지만, 이 특정 과제에서 medium 노력 수준에서는 역시 가장 좋은 동적 언어 결과가 가장 좋은 정적 언어 결과보다 낫다(다만 역시 상당히 가깝다).

완전히 자명한 caveman 모드 eval과 덜 자명한 caveman 모드 eval을 비교했을 때와 마찬가지로, 자명한 eval에서 성립했던 매우 강한 관계가 이 더 큰 사례에서는 일반화되지 않음을 알 수 있다. 그때와 마찬가지로 극단적인 성능 비율 차이는 더 큰 eval에서는 사라지는데, 인간에게 훨씬 더 시간과 노력이 많이 드는 어셈블리 같은 경우나 AI 랩이 합성 RL 환경 데이터 생성에 노력을 쏟을 것 같지 않은 비교적 생소한 언어를 사용할 때처럼 성능이 나쁠 것으로 예상되는 경우를 제외하면 그렇다.

이는 J 같은 매우 밀도 높은 언어가 효율성 때문에 합리적일 것이라고 제안했던 첫 번째 eval의 발견과는 반대이다. 어쩌면 예산이 매우 크고 자신의 애정 언어에 효과적이도록 모델을 훈련하거나 미세 조정할 수 있다면 생소한(그리고 ‘괴짜’ 같은) 언어를 사용하는 것이 말이 될 수도 있지만, LLM의 일반적인 사용자라면 생소하고 밀도 높은 언어를 쓰기보다 주류 언어를 고수하는 것이 더 나은 선택일 것 같다.

그리고 언어 인기도 vs. 이 eval에서의 성능을 그려 보면(그림 생략), 더 인기 있는 언어가 더 정확하고 더 저렴한 솔루션을 내놓는 약하거나 중간 정도의 양의 상관관계를 관찰할 수 있다.

앞서 언급했듯이 매우 밀접하게 관련된 eval들끼리도 결과가 상당히 다르게 나올 수 있다. 예를 들어 여기서 Optimization 1 vs. Optimization 2 eval에서 상당히 다른 결과를 봤다. Optimization 1과 Optimization 2는 wasm에서 bzip2 압축과 압축 해제를 최적화하는 것으로, eval로서는 꽤 밀접하게 관련된 과제이다. “동적 언어가 정적 언어보다 더 효율적이다” 같은 강하고 보편적인 주장을 하려면 많은 과제에 걸쳐 eval을 실행해야 한다. 하지만 다음과 같은 주장이

동적 타입 언어는 명시적 타입 선언을 생략해 코드가 더 간결해지므로 일반적으로 전통적인 정적 타입 언어보다 LLM 토큰 비용이 낮다.

아마도 기껏해야 막연하게 방향만 맞고 특정 사례와는 별로 관련이 없으며 일반적으로도 관련성이 있을 만큼 강하지 않다는 것을 보이려면, 몇 가지 사례를 시도해 보고 그 주장이 일반적으로 성립하지 않음을 확인하면 된다. 위에서 우리는 한 노력 수준에서는 그 주장이 어느 정도 얼핏 맞는 듯 보이지만 예외가 있고, 더 높은 노력 수준에서는 그 주장이 그다지 성립하지 않는 것으로 보였는데, 이는 우리 eval에 완전히 무효화할 만한 교란 요인이 없다는 전제 하에 그 주장이 아마도 보편적으로 참이 아니라고 말하기에 충분하다.

Pandoc

하지만 “스펙을 읽는” 형태보다 더 TDD 같은 형태로 제시된 매우 다른 과제에 대한 관점을 얻기 위해, 다음 eval은 Pandoc ProgramBench eval을 가져와 우리 용도에 맞게 수정했다. ProgramBench가 제시한 역공학 과제 대신, ProgramBench 자료와 ProgramBench 테스트를 에이전트에게 제시한 뒤 각 조건의 성능을 측정하기 위해 홀드아웃 테스트 집합으로 에이전트를 채점한다2.

아래 결과에서 x축은 역시 비용이며 y축은 홀드아웃 테스트에서의 점수이다.

이전과 마찬가지로 성공이나 비용과 언어가 정적인지 동적인지 혹은 매우 밀도 높은지 사이에서 그다지 강한 관계를 보이지 않는다. 다시 한번 비교적 생소한 언어들은 성적이 낮은 경향이 있다(Clojure는 Zstd 때보다 여기서 훨씬 더 잘한다). 그리고 어셈블리는 훨씬 더 못하는데, 이는 인간이 Pandoc을 구현할 때 Zstd를 구현할 때보다 어셈블리로 작성하는 것이 훨씬 더 불리할 것으로 예상되고 LLM도 다르지 않을 것 같다는 점에서 예상 가능한 결과이다.

이 모든 것은 무엇을 의미할까?

누가 알겠는가?

나는 LLM을 사용할 때 무엇이 잘 작동하는지에 대해 많은 질문을 가지고 있다(예를 들어 어떤 테스트 기법이 잘 작동하는지, 어떤 언어가 잘 작동하는지, 어떤 소프트웨어 아키텍처가 잘 작동하는지, 버그 수정 비용이 언어에 따라 달라지는지, 일반적인 프로그램 유지보수 비용이 언어에 따라 달라지는지 등). 이런 질문 대부분은 공개 데이터에서 답을 찾을 수 없고, AI 랩 안에서 답을 찾았다 하더라도 그 정보는 대부분 공개되지 않았다.

특정 언어가 LLM 사용에 좋다는 식으로 떠도는 주장 대부분은 틀린 것 같다(예를 들어 위에 링크된 eval에서 언급된 Ruby, Clojure, J가 LLM에 특히 적합하다는 주장이나, Elixir가 LLM에 특히 적합하다는 다소 흔한 주장). 하지만 무엇이 맞는지는 명확하지 않다.

2014년에 우리는 정적 vs. 동적 타입에 관한 문헌을 살펴봤다 그리고 몇몇 사례 연구를 제외하면 문헌을 조사하는 것이 그다지 유익하지 않다는 것을 발견했다. 표준적인 학술 연구의 전형을 보여주는 예로 “정적 타입 시스템이 소프트웨어 시스템의 유지보수성을 향상시키는가? 실증 연구”라는 논문을 봤는데, 나는 이렇게 논평했다:

피험자들에게는 기존 코드의 오류를 수정하거나 스텁 메서드를 채워야 하는 클래스가 주어졌다. Java에는 정적 클래스, Groovy에는 동적 클래스. 타입 오류(그리고 그에 상응하는 no method 오류)의 경우 개발자들은 Java에서 문제를 더 빨리 해결했다. 의미 오류에서는 차이가 없었다. 이 연구는 피험자 내 설계를 사용했으며 33명의 피험자에 대해 과제 순서를 무작위화했다. 주목할 만한 제한점은 이 연구가 “복잡한 제어 구조”, 예를 들어 루프와 재귀를 피했다는 점인데, 이는 해결 시간의 분산을 증가시키기 때문이다. 그 결과 모든 버그는 자명한 버그이다. 이는 과제를 해결하는 데 걸리는 중앙값 시간이 수백 초에 불과하다는 점에서 알 수 있다. 과제에는 여러 버그가 포함될 수 있으므로 버그당 시간은 상당히 낮다.

루프나 재귀 같은 “복잡한 제어 구조”를 피하고 과제를 해결하는 데 수백 초가 걸리는 과제를 고르는 것은 전문 프로그래머의 시간을 정말로 잡아먹는 과제와 관련해서는 결과를 무의미하게 만든다. 이는 수십에서 수백 토큰이 걸렸던 처음 본 eval과 마찬가지이다. 하지만 LLM으로는 실제로 자명하지 않은 과제를 주고 어떻게 수행하는지 비교할 수 있다. 결과가 서로 다른 과제에 얼마나 잘 일반화되는지에 대한 문제는 있지만, 그건 인간 연구에서도 똑같이 겪는 문제이며 오히려 더 심각하다(LLM의 분산은 크지만, 같은 인간에게 다른 시드로 여러 과제를 시킬 수 없으므로 인간의 분산은 훨씬 더 크다huger). 그리고 LLM에게 Zstd 디코더를 구현시키는 데 20달러가 드는 것이 언어 수와 언어별 조건당 반복 횟수를 곱하면 정확히 싸다고 할 수는 없지만, zstd RFC를 읽고 구현할 수 있는 전문 프로그래머를 고용하는 데 얼마나 들지 생각해 보면 동등한 연구는 비용 때문에 완전히 불가능해서 수행될 수 없었을 것이다. Pandoc 과제는 말할 것도 없다.

LLM 덕분에 많은 질문이 사실상 답할 수 없는 상태에서 약간의 노력과 토큰으로 답할 수 있는 상태가 되었다. 작용하는 인센티브 때문에3 이런 질문에 대한 답을 곧 얻게 될지는 불투명하지만, 이제는 적어도 한번 시도해 볼 수 있게 됐다.

이러한 eval로 증명하거나 반증할 수 없는(위에서 언급한 이유, 즉 서로 다른 문제 간의 분산 때문에 훨씬 더 많은 과제를 시도해야 한다는 이유로) 주장들이 많이 떠돌지만, 이 eval들이 어느 정도 빛을 비추는 것들은 예를 들어 다음과 같다:

  • 나쁜 코드가 많은 언어(예: PHP)는 성능이 더 나쁠 것이다
    • 이 과제들에서는 거짓으로 보인다
  • 이제 다시 쓰기가 너무 쉬우니 강력한 언어(예: Haskell)를 사용해야 한다
    • 이 과제들에서는 거짓으로 보인다
  • 인기 있는 언어를 사용해야 한다
    • 이 진술에 대해서는 약한 지지가 있다

내가 미리 등록한 예상에 대해서는

  • 높은 확신(95%): 전반적인 동적 vs. 정적 언어 주장은 성립하지 않을 것이다
    • 맞는 것으로 보인다
  • 낮은 확신(60%): ultra 노력 수준에서는 정적 언어가 동적 언어보다 다소 더 나을 것이다
    • 이를 확정적으로 판단할 정보가 충분하지 않지만, 이분법적으로 맞다/틀리다를 판정해야 한다면 틀렸다고 하겠다
  • 높은 확신(98%): J 같은 ‘괴짜’ 언어의 우위는 유지되지 않을 것이다
    • 맞는 것으로 보인다
  • [초안 독자로부터]: “소규모에서는 동적이 더 낫지만 프로젝트 규모가 커질수록 정적이 추월한다”
    • 이 과제들에서는 지지되지 않는다(훨씬 더 큰 Pandoc 과제가 더 작은 Zstd 과제보다 정적 언어가 동적 언어보다 실질적으로 더 잘하지 않았다). 하지만 과제와 과제의 제시 방식이 너무 달라서 이것이 과제 크기 스케일링 때문인지 다른 차이 때문인지 불분명하다

덧붙이자면, Clojure가 Zstd eval에 비해 Pandoc eval에서 훨씬 더 개선된 큰 이유 중 하나는 Zstd eval에서 40개 중 36개의 medium과 40개 중 5개의 ultra Clojure 프로그램이 byte 변환이 128–255에서 예외를 던지기 때문에(unchecked-byte를 썼어야 했을지도 모른다) 테스트 실패를 겪었고, 이 변환을 부적절하게 사용했기 때문이다.

이는 실제로 의미 있는 결과인데, 최고의 공개 GPT 모델에게 Zstd 구현을 시키면(그리고 아마 이와 관련될 수 있는 다른 비트/바이트 조작 과제에서도) 이런 특정 방식으로 실패하는 코드를 내놓을 것이기 때문이다. 이를 잡아내는 테스트가 있다면 버그는 고쳐지겠지만 여전히 시간과 토큰이 든다. 언어가 잘했는지 여부와 관계없이 이런 종류의 비용은 도처에 널려 있다(예를 들어 cargo가 반복적으로 잘못된 인자로 호출되었다가 즉시 잡혀 고쳐지는데, codex에 cargo를 어떻게 호출해야 하는지 명시적으로 지시하지 않으면 이 루프가 실제 프로젝트에서 실제로 상당한 wall clock 시간을 소모할 수 있음을 나는 관찰했으며, 컨텍스트 윈도우에서 그 공간을 차지할 가치가 분명하다).

어쨌든 이 모든 것은 어떤 언어 또는 언어군이 LLM과 함께 특히 좋다는 강한 주장을 하고 싶다면 꽤 많은 서로 다른 eval을 실행해야 한다는 것을 보여주는 예시이다. 특정 조건이 특정 점수를 받은 이유를 파고들면, 그 점수를 만든 실패는 일반적으로 어떤 특이한 것이어서 그 문제가 과제나 설정 전반에 걸쳐 얼마나 일반화되는지 항상 명확하지 않다. 하나의 eval, 심지어 5개나 10개의 eval 점수만 보고 프로그래밍 전반에 대한 결론을 내릴 방법은 없다.

Zstd eval과 Pandoc eval 모두에서 언어 인기도와 긍정적인 결과(더 높은 정확도, 더 낮은 비용, 더 낮은 wall clock 시간) 사이의 상관관계를 보는 것은 사실이며 다른 eval들에서도 이를 볼 가능성이 있어 보이지만, 특정 언어에 대해 강한 결론을 내리는 것은 실수일 것이다. 나는 GitHub CI 데이터에 따른 서로 다른 프로젝트의 빌드 깨짐 빈도를 살펴봤을 때 이와 비슷한 경고를 한 적이 있는데, 프로젝트마다 빌드가 더 자주 혹은 덜 자주 깨지는 이유가 다르고 프로젝트 간 결과가 반드시 비교 가능한 것은 아니므로 강한 결론을 내려서는 안 된다고 지적했다(예를 들어 한 프로젝트의 main 브랜치가 다른 검증을 거친 일종의 릴리스 후보라면 그 프로젝트는 빌드 깨짐이 적을 것으로 예상되지만, 이는 사람들이 main에 직접 개발하는 프로젝트와 비교할 수 없다).

얼마 지나지 않아 높은 점수를 받은 언어 중 하나에 관여한 사람(IIRC, Martin Odersky와 Scala였다)이 그 글을 트윗하고 그 언어의 높은 순위를 언어의 승리로 인용했다. 그곳에서는 부당한 결론이었으며, 여기서 작용하는 많은 분산 요인 때문에 단일 언어에 대한 어떤 결론도 여기서는 훨씬 더 부당할 것이다.

이 데이터는(eval 타당성을 가정하면) 일부 강한 주장을 반박할 수 있고 다른 주장에 대해서는 시사하는 바가 있지만, 두 개의 과제만으로는 특정 언어에 대해서는 그렇지 않고 언어군에 대해서만 시사할 뿐이다. 특정 언어는 다른 과제에는 일반화될 수도 아닐 수도 있는 특이한 이유로 두 과제 중 하나를 잘하거나 못할 수 있기 때문이다.

의견/교정/논의에 도움을 주신 Max Bittker, Yossi Kreinen, Aaron Levin, Alan Boll, Luke Burton, Marco Primi, Milosz Danczak, Justin Blank, Tom Adamczewski께 감사드립니다.

부록: ai-coding-lang-bench에서 선별된 문제점들

위에서 말했듯이 여기서의 내 eval은 빠르고 대충 만든 eval이고 분명 결함투성이일 것이므로, 내가 제시한 eval은 훌륭하고 저 eval은 나쁘다고 말하려는 것이 아니지만, Endoh의 ai-coding-lang-bench eval에는 여러 문제가 있다.

한 가지 문제는 일부 테스트에서 잘못된 실행 파일이 실행된 것으로 보인다는 점이다. 공개된 실행을 위한 설정은 후보의 생성된 실행 파일이 ../minigit에 있음에도 각 후보 디렉터리 안에서 ../../minigit을 실행한 것으로 보인다. ../../minigit은 존재하지 않는다.

정적 타입 언어의 정확도 점수가 더 낮았기 때문에 eval 작성자는 “600번 실행 중 유일한 실패는 Rust와 Haskell(둘 다 정적 타입이며, 둘 다 비교적 ‘어려운’ 언어)에서 발생했다”고 언급하고 “C의 메모리 관리, Rust의 소유권 모델, Haskell의 모나드/순수성과 같은 ‘어려운 언어’가 AI에 오버헤드를 더할 수 있다”고 시사한다.

그러나 Rust의 실패는 ../../minigit에 실행 파일이 없어 테스트가 실패했기 때문이다. 첫 번째 Go 실행은 ln -sf minigit-go-1-v1/minigit ../minigit을 실행해 generated/minigit을 자신의 실행에 링크함으로써 이를 “고쳤”지만, 이는 이후의 모든 실행(모든 언어)이 실제로 첫 번째 Go 실행의 실행 파일을 실행했다는 의미이다. Rust를 자신의 실행 파일로 재채점하면(존재하지 않는 파일을 실행하려다 실패하는 대신) Rust는 만점을 받으며, 이는 Rust가 다루기 어려운 언어이기 때문에 실패했다는 이론을 무효화한다.

다른 테스트들도 문제가 있다. 예를 들어 두 테스트는 실제 검사 값을 검증하지 않고도 통과하도록 만드는 구조를 가지고 있다. 테스트 중 하나는 다음과 같다

  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가 있어, 이는 사실상 다음 feasibility 다음와 거의 동일하다

  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는 wall clock 시간이나 다른 파라미터를 개선하기 위해 트레이드오프하도록 더 최적화되어 있을 수도 있다. ultra는 또한 숨겨진 테스트에서 정확도를 달성한 후 멈춰야 한다는 것을 “알지” 못한다는 단점이 있는 반면, 완전한 정확도에 도달한 medium 조건은 이 설정에서는 다시 실행되지 않으므로 이는 루프에서의 medium에 크게 유리하다(이는 누군가 이런 것들을 어떻게 사용할지와 관련해 arguably 현실적이다)
    • 내 마음이 이쪽으로 프레이밍됐고 반대쪽으로는 가지 않았으므로 50% + 엡실론이라고 할 수도 있겠지만, 기껏해야 확신이 매우 낮다고 하겠다
  • 중간 확신(80%): 컨텍스트를 유지하며 계속하는 것이 Ralph 루프보다 낫다
    • /goal 모드 등은 기본적으로 이렇게 하지 않으며, 아마도 Anthropic과 OpenAI 사람들은 Ralph 루프 같은 것을 시도해 보고 덜 효과적이라고 판단했을 것이다
    • 컨텍스트 윈도우를 매우 면밀히 주시하는 것은 하네스(그리고 모델?)가 개선되면서 덜 중요해진 것 같다. 2025년 말/2026년 초에는 오래 실행되는 작업을 할 때 문제를 피하기 위해 컨텍스트 윈도우를 버려야 하는 경우가 많았는데 시간이 지나면서 그런 일이 드물어졌다. 하지만 그때조차도 사람들이 말하는 것에 신경 쓰지 않았기 때문에 나는 기본값으로 컨텍스트를 유지하며 에이전틱 루프를 돌리고 명백한 문제가 있을 때만 비웠는데, 그게 괜찮게 동작하는 것 같았다. 예를 들어 나는 그렇게 해서 세계 최강의 Azul AI를 만들었으므로, 매 루프 반복마다 컨텍스트를 비우는 것을 기본값으로 하는 것이 그때도 올바른 선택이었는지는 명확하지 않다

아래는 100% 테스트 정확도를 달성하지 못한 개별 실행을 단순히 재개하는 프롬프트와 컨텍스트를 버리고 원래 프롬프트를 다시 주는 Ralph 루프와 유사한 프롬프트에 대해, ultra 정확도 점수가 가장 좋은 것부터 가장 나쁜 것 순으로 정렬한 루프에서의 medium vs. ultra의 평균 결과이다(x축은 비용, y축은 올바른 테스트 케이스 수):

이 한 문제에 대해서는 평균적으로 ultra를 한 번 실행하는 것이 단위 비용당 medium을 반복 실행하는 것보다 나아 보이며(단위 시간당으로는 훨씬 더 그렇다) 이전 컨텍스트를 유지하며 계속하는 것이 Ralph보다 낫다. medium을 단순히 반복 실행하는 것의 문제는 에이전트가 나쁜 솔루션에 고착되어 진전을 이루지 못할 수 있다는 점이다. Ralph 루프 뒤의 이론은 나쁜 컨텍스트를 버리면 이런 일이 발생하는 것을 막을 수 있다는 것이지만, 그것이 나쁜 산출물로부터 구해 주지는 못한다.

LLM을 사용하면서 나는 종종 코드 덩어리를 버리고 LLM에게 수정하거나 제자리에서 다시 쓰게 하는 것보다 처음부터 다시 쓰게 하는 것이 더 낫다는 것을 관찰했다. Postgres를 Rust로 다시 작성해 온 Michael Malis도 이를 언급했다. 이는 이전에 언급된 아이디어, 즉 높은 분산(그리고 이런 경로 의존성) 때문에 토큰을 더 쓰는 것이 괜찮다면 여러 번 주사위를 굴려 가장 좋은 결과를 취하는 것이 종종 더 낫다는 것과도 관련이 있다.

이 하나의 조건만 보고 정적 vs. 동적 언어에 대해 너무 많은ことを 말하기는 어렵지만, “반복할 때 정적 언어가 더 잘할 것이다” 같은 단순한 생각이 명백히 참인 것은 아니다. 내 눈에 가장 띄는 패턴이 있다면 Ralph 루프가 컨텍스트를 유지하며 계속하는 것보다 가장 심하게 저조했던 경우는 일반적으로 동적 언어였다는 점이다. 타입 정보의 부족 때문일 수도 있지만, 이것이 실제 패턴인지 관찰하려면 궤적의 차이를 더 자세히 살펴보고 다른 예도 봐야 할 것이다. 지금은 Ralph 루프 유행이 지났으니 Ralph 루프 자체에 관심이 없더라도, 새로운 작업을 시작하거나 새로운 컨텍스트로 코드베이스를 변경할 때 더 효과적으로 변경할 수 있다는 것은 관심을 가질 만한 일이며 여기의 패턴은 가능한 이점을 시사한다.

부록: Guards of Atlantis 2

나는 비즈니스 로직 같은 느낌의 세 번째 eval을 시도했는데, 문제 제시 방식과 실제 실행 모두에서 그렇다. Zstd eval과 Pandoc eval은 프로그래머가 마주하는 과제 중에서는 꽤 이례적인 과제라고 주장할 수 있는데, 많은 프로그래머가 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이 규칙을 이해하도록 하기 위해 나는 LLM에게 비공식 규칙 FAQ(정확하다), 비공식 단축 규칙(공식 규칙보다 더 잘 쓰였고 정확하지만 불완전하다), 오프닝 북(오프닝 북에 합법적인 수만 포함되어 있다는 가정 하에 규칙을 테스트하는 데 사용할 수 있다), Discord 규칙 채널의 댓글 등 다양한 자료를 주고, FAQ나 Discord 댓글 같은 것이 실제 인쇄된 규칙보다 더 높은 권위를 갖는다는 이해 하에 LLM이 이들 간의 일관성 검사를 하도록 했다. 개인용 OpenAI/codex 계정($200/월)으로 나는 LLM이 내 여유 용량을 모두 사용해 일관성 검사를 실행하고 규칙 수정을 하도록 했다. 얼마나 걸렸는지 면밀히 추적하지는 않았지만, 다소 그럴듯한 플레이 가능한 결과를 얻기까지 한두 달 정도 수정 작업을 계속 돌린 것 같다. 하지만 그것이 정확하다고 정말로 신뢰하지는 않는다.

내가 이것을 어느 정도 신뢰하는 유일한 이유는 Pedro Oliveira도 Guards of Atlantis를 구현했으며 그들은 완전히 다른 접근 방식(인간이 LLM을 이끄는 더 표준적인 접근 방식, LLM이 스스로 알아내도록 하는 것이 아니라)을 사용했기 때문이다. 우리가 구현을 비교했을 때 각각에서 아마 10개 정도의 버그를 찾았다. 아마 두 구현이 모두 잘못된 같은 일을 하는 남아 있는 버그가 있을 것이고, 우리 구현이 다르지만 검사 시스템이 알아차리지 못한 경우도 있을 수 있지만, 이제 두 구현의 규칙은 꽤 탄탄하다고 생각한다. 그렇게 해서 나는 이 게임에 대한 오라클을 갖게 됐다.

나는 이를 과제로서 좋아하는데, 명세가 모호하고 모순되며 때로는 그냥 틀린, 현실 세계에서 받는 “명세” 같은 느낌이 더 강하기 때문이다. 그리고 올바른 결과를 얻기 위해 다른 정보를 사용해야 한다. 이 eval에서는 LLM이 성가신 형식으로 된 데이터에 접근하는 능력을 테스트하는 것이 되는 것을 피하기 위해(예를 들어 오프닝 북을 이미지 집합에서 어떤 구조화된 데이터로 변환하거나, 규칙 스캔본을 텍스트로 변환하는 등) 나는 에이전트에게 LLM에게 데이터를 추출하도록 지시했던 모든 것의 원본과 추출된 데이터를 모두 주었다(원본은 LLM이 원하면 추출 오류를 확인하기 위해 원본을 확인할 수 있도록 제시됐다).

이 과제를 더 오래된 모델로 했을 때(일부분은 GPT-5.1이나 5.2로, 다른 일부는 5.4나 5.5로 했다) 더 새로운 모델로도 내가 오래된 모델에게 줬던 종류의 가이던스 없이는 과제는 여전히 너무 어려웠다. 언어와 관계없이 에이전트는 이 과제에서 대략 0점을 받았다.

참고로 LLM(과 인간)이 무엇에 어려움을 겪는지 궁금하다면 몇 가지 예를 들어보겠다. 한 카드의 텍스트는 “당신에게 인접한 유닛을 대상으로 한다. 공격 후: 다른 적 영웅에게 한 번 더 반복할 수 있다.”라고 쓰여 있다.

이 게임에서 영웅은 유닛의 한 종류이다. 규칙에 대한 완전한 지식, 예를 들어 “공격 후”가 무엇을 의미하는지 등을 가지고 엄격하게 읽으면, 이는 단일 유닛을 공격하거나 두 영웅을 공격할 수 있다는 의미여야 한다(결국 다른 적 영웅에게 공격을 반복하려면 첫 번째 유닛이 영웅이었어야 한다. 그렇지 않다면 영웅인 다른 유닛이 아니라 다른 적 영웅이 될 것이기 때문이다).

이 카드는 실제로 사람들이 불명확하다고 불평했기 때문에 카드에 인쇄된 사실상 정오표 같은 것이 있다. 정오표는 “(원래 대상이 미니언이었더라도 반복할 수 있다)”라고 쓰여 있다. 이는 이미 LLM(과 일부 인간)에게는 혼란스럽지만, 여기서 정말 치명적인 것은 동일한 구성을 사용하는 다른 카드들이 있는데 이 정정이 없다는 점이다. 동일한 구성을 가진 다른 카드를 올바르게 플레이하려면, 이 구성이 사용될 때마다 이 카드에 있는 정오표를 적용해 플레이해야 한다는 것을 알아야 한다. 게임 디자이너가 사용하기 좋아하는 특정한 비문자적 의미를 가진 구성이 여럿 있는데, 이를 염두에 둬야 한다.

명백한 대로 플레이해서는 안 되는 규칙의 또 다른 예는 “서로 다른 대상에 대해 A, B 중 하나 또는 둘 다를 선택한다”라고 쓰인 카드를 가진 캐릭터이다. 이를 엄격히 쓰인 대로 읽으면 서로 다른 대상에 대해 A 또는 B 중 하나, 혹은 A와 B 둘 다를 할 수 있을 것으로 예상할 것이다. 하지만 게임의 정신 중 일부는 한 캐릭터가 한 카드로 다른 캐릭터를 여러 번 공격할 수 없다는 메타 규칙이므로, 카드가 말하는 대로 하여 A와 B 둘 다를 어떤 수의 서로 다른 대상에 대해 할 수 있다는 해석은 옳을 수 없다. 유사한 추론과 유사한 구성이 어떻게 사용되는지에 근거해 이 카드가 해석되어야 하는 방식은 “하나를 선택하거나, 서로 다른 대상에 대해서는 둘 다를 선택한다”이며, 이는 여전히 모호하고 “하나 또는 둘 다를 선택한다(둘 다인 경우 반드시 서로 다른 대상이어야 한다)”라고 더 명확하게 쓰는 것이 arguably 더 낫다.

인간으로서 일단 “게임의 정신이 무엇인지”를 이해하면 이런 종류의 것들을 해결할 수 있다. 하지만 의도적으로 이는 규칙에 명확하게 쓰여 있지 않으며 Discord 토론에서 추론해야 하는데, 이는 오늘날 모델의 능력을 넘어서는 것으로 보인다. 심지어 많은 전문 과제에서 오늘날 모델보다 성능이 낮은 인간도 이를 할 수 있음에도 그렇다.

내가 규칙을 구현한 LLM을 감독할 때 LLM이 한계에 도달하고 완전히 올바른 규칙으로 수렴하지 못한 이유는 LLM이 규칙이 일관되지 않고 틀렸음을 관찰했기 때문이다. 그러면 LLM은 이 규칙을 고치려 하고 다른 것들도 일관되고 올바르게 만들기 위해 고쳤다. 이는 때로는 더 올바르게 만들고 때로는 덜 올바르게 만들었다. 덜 올바르게 만들 때 LLM은 때때로 기존의 올바른 테스트를 틀린 테스트로 수정했으므로, 잠시 후 LLM은 실제로 정확도를 개선하지 않고 어떤 규칙이 틀렸는지만 계속 바꾸고 있었다. 이는 무엇을 검사하고 어떻게 검사하는지에 대한 약간의 가이던스가 있었을 때였고, 그 가이던스 없이도 오늘날의 더 진보된 모델로는 LLM이 이를 합리적인 방식으로 헤쳐 나갈 수 없었다.

여기서 좋은 eval이 될 만큼 적절한 규칙 복잡도를 가진 보드 게임이 있을 것이라고 확신한다. 하지만 정의상 이는 오라클을 만드는 데 약간의 작업이 필요한 것이고, 나는 오늘날 LLM에게 흥미로운 테스트가 될 만큼 적절한 수준의 게임을 위한 오라클을 가지고 있지 않다. 내 목표가 eval을 만드는 것이었다면 실제 게임 리플레이 데이터를 가진 보드 게임을 사용해 많은 게임에 대해 좋은 테스트나 오라클을 얻었겠지만, 내 목표는 친구들과 특정 게임을 플레이하는 것이었다. 하지만 보드 게임이라는 시도를 해보고 싶다면 수백(수천?) 개를 확장 가능한 방식으로 만드는 것이 가능해야 하므로 수백 또는 수천 개의 게임에 대해 꽤 정확한 오라클을 얻은 뒤 어떤 게임이 오늘날 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 사용
    • 같은 이유로 매우 얇은 하네스를 사용하는 eval을 봤고, 내가 매우 얇은 하네스가 아닌 codex를 사용하는 이유도 위와 같다
    • 마찬가지로 이 caveman 모델 eval에서는 claude에 Opus와 Fable을, codex에 GPT를 사용했다
  • 인터넷 접속 차단
    • 인터넷 접속이 주어지면 모델이 종종 속임수를 쓸 것이고, 인터넷에서 검색해도 문제를 해결하는 소스 코드가 나오지 않는 문제는 많이 있으므로, 이는 이런 것들에 더 가깝게 eval을 근사하게 만든다
  • 사람들이 돌려보는 많은 벤치마크에 비해 비교적 큰 과제
    • 내가 LLM에게 자명한 과제를 많이 시키긴 하지만, 내 시간을 잡아먹거나 토큰을 잡아먹는 것들은 Alderson eval이나 Endoh eval에 있던 과제 종류보다 큰 경향이 있다. LLM은 자명한 과제에는 충분히 능숙해서 어떤 조건이 그 자명한 과제에서 약간 더 잘하거나 못하게 만드는지는 내게 큰 차이가 없지만, Guards of Atlantis를 구현하는 같은 과제에서는 과제가 겨우 동작하도록 발판을 설정하는 데 몇 시간을 써야 하므로 모델이 더 잘하거나 못하게 만드는 것이 무엇인지가 매우 중요하다
  • 에이전트가 지정한 프롬프트
    • 공개 eval은 과제를 매우 상세하게 지정하지 않는 비교적 얇고/가벼운 프롬프트로 이동한 것으로 보인다. 에이전트가 과제를 설정하면 에이전트가 과제에 성공하는 데 도움이 되는 너무 많은 정보를 주기 때문에 이게 더 낫다고 한다
      • 왜 그것을 테스트하고 싶어 하는지는 이해할 수 있지만, 에이전트가 정의한 과제에서 에이전트가 얼마나 잘하는지도 나는 매우 중요하게 생각한다. 내가 에이전트에게 실행시키는 많은 과제들이 에이전트에 의해 정의된 과제이기 때문이다. 나는 두 스타일 모두에서 에이전트가 어떻게 수행하는지에 관심이 있지, 한 스타일에서만은 아니다. 그런데 공개 eval은 한 스타일로 이동했다
  • Zstd eval: 에이전트에게 문제나 실패한 테스트를 알려주지 않고 버그를 고치도록 요청
    • 일반적으로 에이전트에게 특정한 것을 고치라고 말하면 그것은 고치겠지만, 그 문제의 부류를 고치지는 않을 것이다. 문제가 있다는 것만 말하고 무엇이 문제인지는 말하지 않으면 때로 좁고 깨지기 쉬운 수정을 넣는 대신 더 일반적인 것을 한다는 것을 발견했으므로, 나는 에이전트가 이런 지시를 받았을 때 어떻게 행동하는지에 관심이 있다(물론 에이전트에게 좁고 깨지기 쉬운 수정만 하지 말라고 말할 수 있지만, 그건 종종 통하지 않는다)
      • 이는 위에서 Pandoc 홀드아웃 각주에서 언급한 문제와 다소 관련이 있어 보인다. 에이전트에게 홀드아웃 테스트 집합이 있다고 말하는 것이 에이전트가 더 일반화되고 덜 깨지기 쉬운 솔루션을 만들도록 강제하는 것으로 보였다

부록: 이 eval들의 문제점

성능 벤치마킹에 관해서는 내가 충분히 해봐서 내 벤치마크가 어떻게 결함이 있는지 일반적으로 알고 있으며, 시간/노력 vs. 결함 간의 트레이드오프를 정보에 입각해 결정할 수 있고 존재하는 결함이 내가 이해하려는 것에 대해 중요하지 않다는 데 꽤 확신이 있다. AI eval은 충분히 해보지 않아서 AI eval에 대해 이런 감각이 없으므로, 메타 수준에서 내가 하는 어떤 AI eval도 나에게 알려지지 않은 결함이 있을 것으로 예상한다.

여기서 결함을 예상하는 또 다른 이유는 내가 코딩 에이전트에게 이 eval을 설정하게 했고, 문제를 찾기 위해 1분만 들여도 최소 한 가지 문제를 발견했기 때문이다. 이는 조금 더 살펴보면 드러날 다른 결함이 꽤 있을 가능성이 높다는 것을 의미하지만, 나는 이를 “Gary Bernhardt” 수준의 정확도보다 “빠른 토이 프로젝트” 수준의 정확도로 하고 싶었으므로 몇 가지 문제를 고친 뒤 멈췄다.

검증 엔지니어로 일하던 시절, 아마 2007년쯤 오스틴에서 열린 Sun/Oracle 엔지니어의 밋업에 참석했는데, 거기서 그들은 버그 사이의 시간을 칩 릴리스에 대한 신뢰도 수준으로 변환하는 아이디어를 수학적으로 형식화했다. 사람들이 이를 많이 하는 것을 보진 못했지만, 최근 Will Wilson(Antithesis 공동 창업자)이 Antithesis의 일부 사람들이 생태학(희귀종 관찰에 관한 문헌)의 수학을 사용해 실제 버그 비율을 추정했다고 언급하는 것을 들었다. 이는 수십 년 전 Sun/Oracle의 엔지니어가 하던 것의 훨씬 더 정교한 버전처럼 보인다.

멋진 아이디어이지만, 1분마다 버그를 찾을 때는 다른 버그가 많이 있을 것이라는 것을 알려주기 위해 화려한 수학이 필요하지 않다. 이를 업무로 하고 이 eval들의 충실도에 관심을 가질 이유가 있었다면, 이를 더 면밀히 살펴보고 더 많은 문제를 고치는 것이 타당했을 것이고(그리고 이런 종류의 일을 업무로 한다면 LLM에게 이 eval을 설정하도록 지시하는 데서 실수를 덜 할 기술과 경험이 있었을 것이다). 하지만 “동적 언어가 LLM을 사용할 때 정적 언어보다 의미 있게 더 낫다는 주장이 사실인가?”라는 질문에 답하기 위해서는 그 주장이 사실이 아니라는 데 대해 조금 더 확신이 생겼고, 더 실행 가능한 결과를 낳을 가능성이 높아 보이는 다른 질문들이 많이 있다(예를 들어 어떤 기법이나 테스트 라이브러리가 가장 잘 작동하는지 등).

나는 보통 블로그에 올리는 글은 어느 정도 탄탄하다고 느껴질 때까지 게시하지 않지만, 이는 종종 내 호기심을 충족시킬 만큼 데이터를 탐색하고는 결과를 게시하지 않는다는 것을 의미한다. 게시되지 않은 이런 결과에 대해 사람들과 이야기해 보면, 내가 이야기하는 사람들은 결과가 내가 정말 좋아하는 기준까지 완성되지 않았더라도 종종 결과에 호기심을 보인다. 이는 내가 이야기하지 않는 사람들도 관심을 가질 수 있다는 징후처럼 보인다. 지금까지 본 바로는 이를 내가 정말 좋아하는 기준으로 만들기 위해서는 내가 들인 시간의 최소 10배가 걸릴 것 같다. 지금 꽤 바빠서 몇 달 동안 그럴 시간을 낼 수 있을지 모르겠고, 그 시점이 되면 과연 이걸 게시하게 될지 모르겠다. 최근 글에서 나는 거의 1년 전에 사고 시 뇌진탕 위험에 어떤 차가 더 좋은지 이해하려고 했던 분석을 언급했는데, 거기서도 이를 파악하는 데 시간을 좀 쓰고 나를 만족시킬 답을 얻을 만큼 충분히 진행한 뒤, 결과를 게시할 만큼 깔끔하게 정리하는 작업을 결국 하지 않았다.

그 분석에는 “게시 가능한” 것처럼 보이는 결과들이 있다. 예를 들어 실제 충돌 데이터에서 HIC와 속도 사이의 관계가 4제곱처럼 보인다는 발견이다(!). 이 관계를 찾으려 한 논문이 있지만 잘못된 종류의 분석을 해서 “O(n)” 스타일의 관계를 찾지 못하고 훨씬 더 흐릿한 결과를 냈다. 하지만 나는 무언가가 논문인지 블로그 글인지에 대해 별로 신경 쓰지 않았고, 결국 분석을 게시할 만큼 정리하는 대신 그냥 다음 분석으로 넘어갈 가능성이 더 높다는 것이 드러났다.

이를 따라가는 더 최근 프로젝트로는, 초인적인 Azul AI를 만든 뒤 훨씬 덜 인간 시간을 들이는 과정으로 초인적인 Splendor AI를 만들려고 시도한 것이 있다. 성공하지는 못했다고 생각하지만, 내가 찾을 수 있는 다른 모든 Splendor AI보다는 꽤 큰 차이로 이기므로 이는 다소 흥미로운 결과이다. 보드 게임 AI에 대해 충분히 알고 있어 이에 대해 글을 쓸 수 있다고 생각하지만, 내 주된 관심은 괜찮은 무언가를 얻을 수 있는지를 파악하는 것이었고, 그 뒤로는 멋진 글을 쓰는 데 시간을 쓰는 대신 계속 다른 프로젝트를 하고 있다. 거기서 흥미롭다고 생각하는 것의 예로는 원하는 성능 최적화 중 많은 것이 실제로 결과를 바꾼다는 점이 있다. 따라서 결과를 바꾸지 않는 것으로 엄격하게 검사될 수 있는 최적화에만 의존할 수는 없다. 하지만 순진하게 코딩 에이전트에게 플레이 강도를 낮추지 않는 방식으로 이런 최적화를 하라고 하면 온갖 강도를 낮추는 일을 할 것이다. 강도 감소가 매우 심각한 경우는 쉽게 잡을 수 있지만, 더 미묘한 문제도 있다. 예를 들어 자기 자신과의 셀프 플레이에서는 강도 변화가 없지만 인간이나 다른 AI와의 대전에서는 강도가 감소하는 경우도 있어, 나쁜 최적화를 잡아내기 위한 어떤 과정이 필요하고, 그것은 본질적으로 직관과 LLM에 의존하는 것(매우 도움이 되지만 종종 완전히 틀리기도 한다)의 조합으로 설계해야 하는 다소 임의적인 과정이다.

내가 관심을 갖는 이런 종류의 데이터 프로젝트에 대해 LLM은 내 호기심을 충족시킬 만큼 충분히 강한 결과를 얻는 데 드는 노력을 엄청나게 줄여주지만, AFAICT 결과물을 게시하는 데 드는 노력은 그다지 줄여주지 않는다(적어도 LLM에게 글쓰기를 맡기는 대신 직접 글을 쓰고 결과가 깔끔하길 원한다면). 이는 글쓰기가 일종의 Amdahl의 법칙 병목에 걸린다는 것을 의미하므로, 나는 이런 종류의 프로젝트는 더 많이 하면서 글은 더 적게 쓰고 있다. 오히려 내 작업 방식이 바뀌었기 때문에 글을 쓰는 데 더 많은 시간이 걸린다고 생각한다. 예를 들어 ggplot2에서 그래프를 그냥 출력하는 대신 어떤 면에서는 더 멋진 인터랙티브 버전을 만들지만, 이는 분명 더 많은 시간이 든다. 그리고 LLM 맞춤법/문법 검사 패스를 돌린다(적어도 지금까지는 글쓰기에 LLM 도움을 사용한 유일한 경우이다). 이는 고쳐야 할 많은 문제를 찾아낸다. 나는 수정안을 그대로 받아들이는 대신 각각을 수동으로 살펴보고(그리고 나는 오타를 많이 낸다), 이는 실제로 꽤 시간이 많이 걸린다(지난 글에서는 한 시간 이상, 이 글에서는 끝까지 교정을 다 하지 않고 중간쯤에서 과정을 포기했음에도 30분 이상).

어쨌든 이를 게시하는 것은 내가 정말 게시하기 전에 갖고 싶어 하는 깔끔한 버전 대신 반쯤 완성된 노트를 게시하는 실험이다. 이에 대해 의견이 있으면 알려 달라(X Bsky Mastodon)!

현재 eval에 대한 GitHub 링크는 가지고 있지 않다. 한편으로는 정말 있어야 한다고 생각한다. 다른 한편으로는 엉망이고 코드를 게시하기 전에 정리하고 싶은 것들이 많으며, 언제 하게 될지 모르겠고, 이렇게라도 무언가를 내놓는 것이 친구 몇 명과 결과에 대해 이야기하고 결과가 무기한으로 내 하드 드라이브에 남아 있는 것보다는 낫지 않은가?

부록: Zstd에 대한 추가 세부 정보

에이전트에게는 성능을 무시하라고 지시했지만 타임아웃이 무한하지는 않았고, medium 조건에서는 일부 테스트 케이스가 타임아웃됐다. 이는 arguably 불공평하지만 점수에 실질적인 영향을 주지는 않았다. 무한 루프가 아닌 타임아웃의 경우 Clojure에서 2건(40 * 34개 테스트 중), J에서 2건, Tcl에서 2건, Factor에서 1건, PHP에서 1건이었다. 그리고 9000초(2.5시간) 타임아웃은 가장 큰 테스트 케이스가 4GiB였던 것을 고려하면 꽤 관대했다. 2.5시간 안에 4GiB를 디코딩하지 못한다는 것은 Graviton 5 코어에서 초당 0.5MB 미만의 암시적 속도로, 이는 꽤 느리다.

에이전트에게 이를 설정하게 하면서 겪은 문제 중 일부는 다음과 같다(그리고 위에서 언급했듯이 각 문제를 찾는 데 걸린 짧은 시간으로 미루어 보아 더 많은 문제가 있음을 시사한다)

  • 원래 빌드 설정이 에이전트에게 명확하게 지정되지 않아, 일부 언어는 에이전트에게 지정된 방식에 따라 합리적으로 보이는 일을 했지만 채점이 이뤄질 때 무작위로 실패했다
    • 참고로 나는 초기에 매우 흥미로워 보이고 내 편향을 확인해 주는 결과 때문에 매우 흥분했다. 동적 언어가 정적 언어보다 상당히 더 나빴다. 정말 대박 결과 아닌가! 하지만 초기 설정의 실제 결과는 프로젝트 빌드가 모호하게 지정된 탓에 발생한 문제에 대해 정적 언어가 동적 언어보다 그런 문제에 덜 걸릴 가능성이 높았기 때문이었다
  • 원래 어셈블리 조건에서 에이전트는 C로 코드를 구현한 뒤 이를 어셈블리로 컴파일해 어셈블리로 제출했다
    • 이 문제가 있을 때는 어셈블리가 다른 언어만큼 잘했는데, 이는 매우 흥미롭다! 그리고 이 문제를 고치자 거짓으로 드러났다. 주의하지 않으면 틀렸지만 그럴듯해 보이는 결과를 얻어 쉽게 바이럴이 되기 매우 쉽다는 것이 드러난다. 이 두 문제를 고친 뒤 결과는 꽤 평범해 보였고 논문의 프레이밍으로는 “부정적 결과”라고 할 수 있는 범주에 들어간다. 즉, 결과가 보여주는 흥미롭거나 놀랍거나 논쟁적인 것이 없다는 것이다. 아마도 지루한 언어를 약간 선호하는 것이 10~20년 전 매우 온라인인 사람들에게는 contrarian한 결과였겠지만, 매우 온라인인 트렌디한 담론은 그곳에서 벗어난 것으로 보이므로 이는 더 이상 그다지 흥미로운 contrarian 결과가 아니다
  • 어떤 이유에서인지 설정을 담당한 에이전트가 일부 언어에만, 다른 언어에는 없는 특이하고 임의적인 제한을 부과했다(예를 들어 Rust 설정은 rustfmt나 Clippy에 접근할 수 없었다). 대부분이지만 전부는 아닌 언어에 이런 것이 있었다
  • 많은 테스트(에이전트가 만든 것)는 에이전트에게 성능을 무시하라고 지시했음에도 실제로는 어떤 종류의 성능/스트레스 테스트였다(9000초 안에 4GiB의 Zstd를 처리하는 것을 성능 스트레스 테스트라고 생각하지 않는다)
  • 일부 언어 조건에는 에이전트에 대한 임의적인 지시가 있었다(예를 들어 Haskell 조건에는 bytestring을 사용하지 말라는 지시와 대체 구현 제안에 대한 지시가 있었다)
  • 일부 언어 조건에는 오래된 툴체인이 있었다(예를 들어 Zig는 0.10이었다)
  • 일부 언어 조건에는 에이전트가 Zstd를 구현하는 데 도움이 되는 스캐폴딩이 있었다
  • 일부 언어 조건에는 사용 가능한 도구에 대한 설명이 틀렸다(예를 들어 어셈블리 조건은 GDB에 접근할 수 있다고 안내받았지만 GDB는 동작하지 않았다)
  • 반복 eval 실행을 위한 헬스 체크를 담당한 에이전트는 때때로 eval이 충분한 진전을 이루지 못하고 있다고 판단하고 홀드아웃 테스트나 다른 정보를 eval 내부의 에이전트에게 주기도 했다

버그라고 할 수는 없지만 어쨌든 제거한 것이 하나 있다. 테스트 중 하나는 매우 어려웠다(아마 10%의 에이전트가 첫 시도에서 테스트를 통과했다). 현재 zstd 릴리스 바이너리로 테스트해 보니 zstd 바이너리도 이 테스트에서 실패한다. RFC를 읽어보니 이는 특정 엣지 케이스의 합법성에 대한 RFC의 모호성 때문인 것으로 보인다. 어떤 언어가 이 테스트 케이스를 더 자주 통과하는지에 대해 꽤 강한 군집화가 있었는데, 이는 흥미롭다고 생각하지만 다른 모든 테스트가 더 직접적인 무언가를 측정(하거나 적어도 측정하려 시도)하는 상황에서 이를 측정하는 것은 그다지 유용해 보이지 않는다.

어쨌든 위 목록(완전한 목록이 아니다)에서 많은 문제가 상당수 언어에 영향을 미쳤고 일부 문제는 여러 번 고쳐야 했다. 전체적으로 각 조건을 별개의 버그로 세면 아마 100개 이상의 버그를 고쳤(에이전트에게 고치게 했다)고 생각하며 더 많이 남아 있을 것으로 예상한다. Max Bittker(RL 환경 스타트업을 운영한다)와 이야기했을 때 그는 이렇게 말했다

내가 작업한 모든 eval은 결국 엄청난 시간과 노력을 쏟게 됐다. 대부분은 궤적(또는 많은 궤적의 요약)을 읽고 문제를 트리아지하는 형태였다. 예를 들어 “오, 이런 종류의 버그는 가능해서는 안 되는데, X를 업데이트하자”(X는 프롬프트, 하네스/환경, 또는 검증기) 같은 식이다

에이전트는 이를 엉망으로 만드는 경향이 있으므로, 나는 에이전트의 테스트 대상 경험에 민감한 것이 무엇인지, 즉 테스트 대상 에이전트의 컨텍스트에 있는 것이 무엇인지(에이전트가 걱정해야 할 무작위 정크를 추가하거나 최악의 경우 답을 누설하는 것은 좋지 않다) vs. 시스템의 다른 부분에서 뒤에서 고쳐지는 것이 무엇인지 확실히 하기 위해 많은 주의를 기울인다.

eval을 작성할 때 에이전트는 테스트 대상 에이전트의 경험에 충분히 민감하지 못하고 그냥 답을 주거나 “속임수 쓰지 마세요”라고 말하며 문제를 내부 에이전트의 문제로 만들어 고친다

나는 또한 eval을 위해 프롬프트로 처음부터何か를 만들려고 하는 대신 기존 것(저장소, 게임, 도구, 레벨)을 재사용하고 그 주변에 하네스와 검증기를 구축하는 방식으로 많은 성공을 거뒀다

돌이켜보면 교차 언어 eval을 한 것을 다소 후회한다. 100개 이상의 eval 문제를 고친 뒤에도 의심할 여지 없이 더 많이 남아 있을 것이다. 아마 이는 그저 “저쪽 잔디가 더 푸르다”는 생각일 뿐 다음 eval에서도 후회하겠지만, 서로 다른 언어를 평가하는 것보다 서로 다른 테스트 기법이나 테스트 프레임워크가 얼마나 잘 작동하는지를 평가하는 것이 훨씬 덜 들었을 것 같고 그 주제가 적어도 그만큼 흥미롭다고 생각한다. 그리고 돌이켜보면 에이전트에 덜 의존하고 직접 손으로 훨씬 더 많은 작업을 했다면 훨씬 더 잘 됐을 것이다. 예를 들어 에이전트에게 한 언어에 대한 환경을 만들게 한 뒤 에이전트에게 검사하게 하고 나도 직접 검사해 문제를 고친 뒤 다른 언어에 대한 환경을 만들었어야 했다. 이를 몇 번 반복한 뒤에는 다른 언어에 대한 환경을 만들기 위한 더 나은 설정을 가질 수 있었을 것이다(그렇지 않더라도 이 과정을 각 언어마다 반복해서 더 신뢰할 수 있는 결과를 얻었을 것이고, 아마 더 많은 시간이 걸리지도 않았을 것이다).

주목할 또 다른 점은 언어의 진정한 차이인 많은 것들이 실제로 테스트되지 않았다는 점이다. 예를 들어 적대적 입력에 대한 메모리 안전성 같은 것이다. 에이전트가 C나 C++에서 대략적으로 올바른 코드를 만드는 데 Rust보다 더 어려움을 겪었다면 그것은 관찰됐겠지만, 퍼저나 valgrind 같은 도구가 문제를 찾아낼 것 같지는 않은 작은 테스트 집합에서는 포착되지 않을 것이다. 호기심에 에이전트에게 Zstd C와 C++ 코드에서 메모리 안전성 문제를 (간략하게) 확인해 달라고 요청했다. 에이전트는 C와 C++ 코드를 ASan+UBSan으로 실행하고 몇 가지 퍼즈 입력(각각 4000개)을 시도했으며 문제를 찾지 못했다고 주장하지만, 물론 이는 문제가 없다는 것을 의미하지 않으며 더 큰 코드베이스에서는 문제가 없을 것이라는 의미도 아니다.

그리고 실제로 Pandoc eval에 대해 메모리 안전성 문제를 유사하게 빠르게 확인한 결과 모든 C 프로그램과 하나를 제외한 모든 C++ 프로그램에서 메모리 안전성 문제가 발견됐다(문제는 범위를 벗어난 메모리를 잘못 역참조하는 것 같은 것이었다. 한 구체적인 예로는 C 프로그램 중 하나에서 잘린 LaTeX 테이블이 범위를 벗어난 메모리 읽기를 초래할 수 있었다는 것이다). 이러한 문제가 수십 초의 프롬프팅으로 찾아질 수 있었다는 사실은 많은 문제가 큰 인간 노력 없이도 찾아 고쳐질 수 있음을 나타내지만, 이는 꽤 많은 토큰이 들고 C와 C++ 버전의 비용을 Rust 버전의 비용을 훨씬 넘어서게 만들 것이며, 그 모든 작업을 한 뒤에도 C와 C++ 버전의 메모리 안전성에 대해 Rust 버전보다 확신이 덜할 것이다.

어쨌든 분포가 궁금하다면 medium과 ultra에 대해 다음과 같은 결과가 있다:

ultra 결과가 여기서 다소 포화 상태인 것은 마음에 들지 않지만, ultra를 테스트하는 것의 한 가지 “문제”는 문제가 더 어려워질수록 오랫동안 계속 진행된다는 점이다(예를 들어 대부분의 Pandoc ultra 실행은 12시간 이상 실행됐고, 어셈블리 실행은 훨씬 더 오래 갔다). 따라서 포화되지 않는 것들은 Pandoc eval 같은 매우 큰 과제나 Guards of Atlantis eval 같은 어떤 면에서 너무 어려운 과제이다.


  1. 초안 독자 한 명이 “소규모에서는 동적이 더 낫지만 프로젝트 규모가 커질수록 정적이 추월한다”는 예상을 미리 등록했다. [return]
  2. 홀드아웃 테스트는 에이전트가 테스트 입력을 감지하고 통과하는 테스트 출력을 하드코딩하는 식으로 속임수를 쓰기 때문에 필요해 보인다(속임수를 쓰지 말라고 지시해도 가끔 그렇게 한다). 모든 속임수가 그렇게 노골적이라면 문제가 되지 않을 것이다(그리고 언어에 따라 에이전트가 지시를 따르거나 따르지 않는 정도가 다른 것을 측정하는 흥미로운 일이 될 수도 있는데, 이는 실제 사용자에게 중요한 일이다). 하지만 속임수 중 많은 것은 더 미묘하고 판정하기 어렵다. 예를 들어 일부 에이전트는 테스트의 구조에 따라 분기하는 코드를 작성했지만, 그 분기의 내용은 단일 테스트 결과에 특수하게 맞춰진 것이 아니라 동일한 테스트의 많은 변형을 통과할 수 있는 코드로 채웠다. “절대 속임수가 아니다”에서 “명백히 속임수다”까지의 스펙트럼 상의 어떤 지점에 대해서도 어떤 에이전트는 그것을 시도했다. Senior SWE-Bench를 살펴봤을 때 봤듯이, eval의 LLM 채점은 까다롭고 편향과 분산을 모두 도입하는 훌륭한 방법이다. 홀드아웃 테스트 집합을 사용하는 것은 몇 가지 문제가 있지만, 훨씬 더 큰 문제 집합을 피할 수 있게 해준다.

    한 가지, 홀드아웃 테스트는 에이전트에 의해 만들어졌기 때문에 의심스럽다. 의도는 속임수를 쓰지 않는다면 합리적인 사람(또는 에이전트)이 통과시킬 수 있는 홀드아웃 테스트를 만드는 것이었다. 에이전트는 이것이 합리적이지 않은 경우를 감사해 일부를 제거했지만, 나는 이를 직접 확인하지 않았으므로 적어도 하나의 홀드아웃 테스트가 어떤 면에서 불공평할 가능성이 높다고 생각한다. 하지만 홀드아웃 테스트에 대한 전체 점수가 충분히 낮기 때문에 소수의 테스트가 나쁘더라도 크게 걱정하지 않는다(내가 AI 랩에서 일하며 차세대 모델을 훈련시키려 한다면 이에 대해 더 걱정하겠지만, 여기서는 중요하지 않다고 생각한다).

    홀드아웃 테스트 집합이 있다고 평가받는다고 에이전트에게 알리면서 속임수를 쓰지 말라고 지시하는 것은 홀드아웃 테스트에서 매우 낮은 점수를 받는 노골적인 속임수를 막지는 못했지만, 에이전트에게 홀드아웃 테스트 집합이 있다고 말하는 것은 에이전트가 볼 수 있는 테스트에서 달성한 점수를 낮추면서 홀드아웃 테스트에서 달성한 점수를 높이는 것으로 보였다(이렇게 말하지 않으면 많은 에이전트가 쓸모없이 깨지기 쉬운 코드로 Pandoc 테스트에서 100%를 달성했다. 홀드아웃이 있다고 말한 뒤에는 어떤 에이전트도 ultra에서 1턴 만에 100%를 기록하지 못했지만 홀드아웃 점수는 상당히 더 좋아 더 나은 일반화를 나타냈다).

    [return]
  3. LLM 코딩 성공의 비밀을 알려주겠다고 약속하는 다양한 Substack, YouTube 채널 및 기타 매체가 있지만, 실제 실험을 실행하는 데 시간을 쓰는 ROI는 그다지 없다. caveman 모드를 살펴봤을 때, 우리는 가장 큰 프로그래밍 유튜버 중 한 명이 몇 분 동안 이를 살펴보고는 효과가 있다고 결론 내린 영상을 봤다. 정말 효과가 있는지 살펴보는 데 15분이라도 쓰는 것은 그 시간에 더 많은 콘텐츠를 만드는 것에 비해 아마도 마이너스 ROI일 것이다.

    서로 다른 기법을 논하는 다양한 논문이 있으며, 이들은 때때로 대부분의 블로그 글이나 영상보다 더 자세히 다루지만 평균적으로 반드시 더 유용한 정보를 담고 있는 것은 아니다. 예를 들어 ChatGPT(5.6 Sol, Pro)에게 LLM과 관련한 언어 효과에 대한 논의를 찾아달라고 했을 때 토큰 효율성에 관한 이 논문이 나왔는데, 흥미로운 아이디어를 가지고 있지만 우리가 앞서 논의한 caveman 모드 eval과 같은 문제를 가지고 있다. 즉, 프로그래머로서 나에게 결과가 관련될 만큼 충분히 흥미로운 과제를 보고 있지 않다는 것이다. 그 논문을 인용한 것을 보면 “토큰맥싱을 위한 최고의 프로그래밍 언어”라는 제목의 세 학자의 토큰 효율성 논문을 찾을 수 있지만, 이 글에 비하면 그 논문은 네 개 언어만 비교하고, 더 나쁜 모델을 사용하며, 작은 토이 문제(LiveCodeBench라는 것에서 가져온 것으로, GPT-5.5로 문제를 푸는 데 드는 비용이 종종 1000토큰 정도이다)를 사용한다. eval이 얼마나 잘 수행됐는지와 관계없이, 이 글과 우리의 caveman 모드 eval에서 언급했듯이 우리는 작은 토이 문제에서 내가 취미 프로젝트나 업무에서 신경 쓸 만한 문제로 넘어갈 때 종종 매우 다른 상대적 결과를 본다. 또한 그 논문에서는 “프로그램을 테스트하려면 정확히 ./test.sh를 실행하라... 내가 신경 쓰는 테스트는 이것뿐이다”라는 프롬프트를 줬다고 언급하며 “우리는 이 설정이 에이전트 행동을 연구하는 현실적인 방법이라고 믿는다. 일상적인 사용에서 프로그래머는 에이전트에게 테스트를 숨기지 않는다. 대신 프로그래머는 모든 테스트를 통과할 때까지 계속 작업하도록 에이전트에게 지시한다”고 말하지만, 위에서 언급했듯이 이렇게 하면 현실 세계에서 실패하는(또는 에이전트에게 주지 않은 홀드아웃 테스트가 있다면 홀드아웃 테스트에서 매우 높은 비율로 실패하는) 깨지기 쉬운 코드가 만들어진다. 이 문제는 테스트를 몇 개 더 추가하는 것만으로는 해결할 수 없다. 퍼징이나 속성 기반 테스트 같은 것으로 해결할 수 있을지도 모르지만, 그게 얼마나 잘 작동하는지는 다른 글의 주제이다. 이 논문들이 나쁘다거나 배울 점이 없다는 말이 아니라, 어떤 기법이나 도구를 사용해야 할지 알고 싶어 하는 프로그래머로서 위에 링크된 논문 같은 곳에서는 그 정보를 얻을 수 없다는 것이다.

    [업데이트: Tom Adamczewski가 그의 논문 https://arxiv.org/pdf/2606.30182 링크를 보내줬는데, 이는 위에서 언급한 많은 문제를 처리한다. 이 글에 비해 훨씬 더 많은 서로 다른 과제를 시도하고(이는 훌륭하다) 더 적은 언어와 더 적은 과제 제시 방식을 시도한다. 그 논문에서 내린 결론 중 하나는 언어가 중요하지 않다는 것인데, 이는 더 적은 언어를 시도한 데서 나온 것이다. 여기서 시도한 eval에서 매우 생소한 언어를 제외하더라도 언어 인기도/사용량과 결과 품질 사이의 상관관계를 관찰할 수 있다. Adamczewski 논문이 훨씬 더 많은 과제를 시도하므로, 어느 한쪽만 보는 것보다 이 글과 그 논문을 함께 보면 더 완전한 그림을 얻을 수 있다.]

    [return]

이 글은 muse-spark-1.2-contributor 모델을 사용해 번역했습니다.

댓글