LLM 코딩 에이전트에서 동적 언어, 정적 언어, 조밀한 언어의 비용과 정확도를 비교한 평가 실험을 살펴본다.
이 다소 널리 인용되는 글은 (어쨌든 나는 계속 인용되는 것을 본다) 동적 언어 및/또는 사물을 더 간결하게 표현하는 언어가 토큰 효율이 더 높다고 제안한다. LLM 검색 결과도 동의할 만큼 충분히 인용되는 듯하다. 예를 들어 내가 dynamic vs static language token cost(따옴표 없이)를 검색했을 때, Google의 AI 요약은 다음으로 시작했다.
명시적 타입 선언을 생략하면 코드가 더 간결해지므로, 동적 타입 언어는 일반적으로 전통적인 정적 타입 언어보다 LLM 토큰 비용이 낮습니다.
Google의 AI는 같은 글을 인용했으며, 그 글은 몇몇 간결한 동적 언어의 토큰 비용이 Rust, Go, C++ 등과 같은 정적 언어의 대략 1/2에서 1/3일 수 있음을 시사한다. 저자는 다음과 같이 말한다.
제가 비교한 언어 중 토큰 효율이 가장 낮은 C와 가장 효율적인 Clojure 사이에는 매우 의미 있는 2.6x 격차가 있었습니다.
그리고 나중에 J도 시험하며 다음과 같이 말한다.
평균 70토큰으로 압도적이며, Clojure(109토큰)의 거의 절반입니다. 이국적인 기호 집합을 피할 때 배열 언어는 극도로 토큰 효율적일 수 있습니다. 토큰 효율이 핵심 동인이 된다면, 이것은 언어가 발전할 매우 흥미로운 방향일 수 있습니다.
내가 떠도는 것을 찾은 다른 동적 대 정적 언어 토큰 비교는 이것이며, 같은 결론을 뒷받침한다. 이를 벤치마킹, 평가, 실험 설계에 관한 이 연습 시리즈의 8부로 취급하고 싶다면, 계속 읽기 전에 링크를 눌러 평가 문제를 생각해 볼 수 있다.
우리 자체 평가를 돌리지 않고도, 첫 번째 실험의 한 문제는 문제가 사소하다는 점이며, 위 인용문에서도 이를 알 수 있다. J에서 70토큰, Clojure에서 109토큰으로 풀 수 있는 문제는 별 문제가 아니다(저자는 Rosetta Code를 사용했다). 원시인 모드와 자체 평가의 다른 평가들을 살펴봤을 때 보았듯이, 작업 대부분이 답을 출력하는 데 있는 사소한 문제와 실제로 어느 정도의 “진짜 작업”을 요구하는 약간 덜 사소한 문제에서는 매우 다른 결과가 나올 수 있다. 원시인 모드가 주장하고 재현에서 보인 큰 이득은 몇 토큰 이상 걸리는 문제를 보기 시작하면 사라진다. 일반적으로 사소한 과제의 성능은 일반화되지 않는다.
두 번째 링크의 문제는 조금 더 미묘하므로 대부분은 부록으로 미루겠지만, 한 테스트가 존재하지 않는 잘못된 경로를 실행하여 테스트를 실패시키는 문제 등이 있다. 이후 에이전트 중 하나가 존재하지 않는 경로를 자기 실행 파일에 심볼릭 링크했는데, 그 경우에는 동작하지만 이후의 모든 테스트가 올바른 실행 파일 대신 그 한 에이전트의 실행 파일을 돌게 만든다. 저자는 Rust에 실패가 있었던 의미에 관한 결론을 끌어내려 하지만, 그것이 뜻하는 것은 Rust 채점이 Go 에이전트가 그 깨진 테스트의 모든 채점을 Go 실행 파일로 심볼릭 링크하기 전에 실행되었다는 것뿐이다.
이러한 평가에 의존하는 대신, 우리만의 평가를 돌려 볼 수 있다. 이 평가들 및 지난 평가 연습에서 논의한 평가들에서 볼 수 있듯, 평가 작성자가 말한다고 생각하는 바를 실제로 말하지 않는 평가를 만들기는 매우 쉽다. 의심할 여지 없이 이 평가도 예외가 아니며 결함이 있을 것이다(자세한 내용은 아래 부록 참조).
사물에 관한 직관을 기르는 방법으로, 나는 결과를 보기 전에 추측을 사전 등록하는 것을 좋아한다1. 친구들과 사전 등록한 몇 가지는 다음과 같다.
높은 확신(95%): 전반적인 동적 대 정적 언어 주장은 성립하지 않을 것이다
낮은 확신(60%): 초고노력에서는 정적 언어가 동적 언어보다 다소 나을 것이다
높은 확신(98%): J 같은 언어의 “이상한” 우월성은 성립하지 않을 것이다
첫 평가에서는 에이전트에게 zstd RFC(및 정오표)를 주고 완전한 zstd 디코더를 구현하라고 했다(에이전트는 인터넷 접속 없이 컨테이너에 갇혀 있다). 테스트는 에이전트에게 주지 않았다. zstd 정도의 표면적을 가진 대상에서 테스트가 가능한 모든 경우를 포괄하리라고 기대하는 것은 합리적이지 않다. 예를 들어 zstd는 상당히 잘 테스트된 소프트웨어이지만, 나는 한때 zstd에서 데이터 손상 버그를 발견했다. 이 테스트 스위트는 수년간 숨어 있을 수 있는 극단적인 모서리 사례를 찾기 위한 것이 아니라, RFC에서 “쉽게” 도출할 수 있고 작동해야 하는 다양한 사례를 점검하기 위한 것이다.
아래에서 x축은 비용, y축은 정확성 점수다(위쪽·왼쪽일수록 좋고 / 아래쪽·오른쪽일수록 나쁘다). GPT-5.6 Sol의 중간 및 초고노력 결과 평균이다. 중간만 보고(그리고 결과가 과제마다 종종 크게 다르다는 사실을 무시하면), Alderson 평가와 같은 결론, 즉 LLM 사용 시 동적 언어가 더 효율적이고 더 낫다는 결론에 이를 수도 있다. 비교적 난해한 언어를 무시하면 동적 언어 군집이 정적 언어 군집의 위쪽·왼쪽에 놓이기 때문이다(한눈에 비교하기 쉽도록 Alderson의 정적 대 동적 색상 코딩을 사용했다). 그러나 초고노력을 보면 결과는 상당히 뒤섞여 있다. 정적 언어 두어 개가 가장 좋고, 더 좋은 결과들에는 동적 언어보다 정적 언어가 더 많다.
아래 그래프에는 x축을 비용 대신 시간으로 바꾸는 토글도 있다. mame/ai-coding-lang-bench는 결과를 더 빨리 얻는 것이 가치 있다고 언급했다(개인적으로는 결과가 충분히 오래 걸리므로 기다리는 대신 멀티태스킹하기 때문에 그렇다고 느끼지 않는다). 따라서 이것도 볼 수 있다. 마찬가지로 어느 언어 유형도 다른 유형을 지배하지 않음을 관찰한다. 다만 이 특정 과제에서 중간 노력일 때는 최고의 동적 언어 결과가 다시 한번 최고의 정적 언어 결과보다 좋다(하지만 다시 한번 상당히 가깝다).
완전히 사소한 원시인 모드 평가와 덜 사소한 원시인 모드 평가를 비교했을 때처럼, 사소한 평가에서 성립했던 매우 강한 관계가 이 더 큰 사례에는 일반화되지 않음을 볼 수 있다. 그때와 마찬가지로, 성능의 극단적 비율은 이 큰 평가에서 사라진다. 어셈블리를 사용하는 경우(인간에게도 상당히 더 시간이 들고 어려울 것이다)나, AI 연구소가 합성 RL 환경 데이터를 만드는 데 노력을 기울일 것으로 기대하기 어려운 비교적 난해한 언어를 사용하는 경우처럼 저성능을 예상할 수 있는 경우는 예외다.
이는 J처럼 매우 조밀한 언어가 효율성 이유로 타당하다고 제안한 첫 평가의 발견과 반대다. 매우 큰 예산이 있고 선호하는 언어에 효과적인 모델을 학습하거나 미세 조정할 수 있다면 난해하고 “이상한” 언어를 쓰는 것이 의미 있을지도 모른다. 그러나 보통의 LLM 사용자라면, 난해하고 조밀한 언어를 쓰는 것보다 주류 언어를 고수하는 편이 더 나은 선택으로 보인다.
그리고 이 평가에서 언어 인기와 성능을 도표화하면(표시하지 않음), 인기 있는 언어일수록 더 정확하면서도 더 저렴한 해법으로 귀결되는 약한~중간 정도의 양의 상관관계를 관찰한다.
앞서 언급했듯, 매우 밀접하게 관련된 평가도 상당히 다른 결과를 낼 수 있다. 예를 들어 여기서 Optimization 1과 Optimization 2 평가가 상당히 다른 결과를 낸 것을 보았다. Optimization 1과 Optimization 2는 wasm에서 bzip2 압축과 압축 해제를 최적화하는, 평가 기준으로는 꽤 밀접하게 관련된 과제였다. “동적 언어가 정적 언어보다 효율적이다” 같은 강력하고 보편적인 주장을 하려면 많은 과제에 걸쳐 평가를 돌려야 한다. 그러나 다음과 같은 주장이
명시적 타입 선언을 생략하면 코드가 더 간결해지므로, 동적 타입 언어는 일반적으로 전통적인 정적 타입 언어보다 LLM 토큰 비용이 낮습니다.
기껏해야 희미하게 방향성만 맞고 특정 사례에는 실제로 관련 없으며, 일반적으로도 관련될 만큼 강하지 않을 수 있음을 보이기 위해서는 몇 사례만 시도해 이 주장이 일반적으로 성립하지 않음을 보면 된다. 위에서 한 노력 수준에서는 예외는 있으나 이 주장이 어쩌면 대략 맞아 보였고, 더 높은 노력 수준에서는 특별히 맞아 보이지 않았다. 따라서 평가를 완전히 무효화하는 교란 요인이 없다는 전제하에, 이 주장이 보편적으로 참일 가능성은 낮다고 말하기에 충분하다.
하지만 아주 다른 방식으로 제시된(“명세를 읽어라”보다 더 TDD 같은) 매우 다른 과제를 보기 위해, 다음 평가는 Pandoc ProgramBench 평가를 가져와 우리 용도에 맞게 수정한다. ProgramBench가 제시하는 역공학 과제 대신, 에이전트에게 ProgramBench 자료와 ProgramBench 테스트를 제시한 뒤 보류 테스트 집합으로 에이전트를 채점하여 각 조건의 성능을 측정한다2.
아래 결과에서 x축은 다시 비용이고 y축은 보류 테스트 점수다.
전과 마찬가지로, 성공 또는 비용과 언어가 정적인지 동적인지 혹은 매우 조밀한지 사이에 매우 강한 관계는 보이지 않는다. 다시 비교적 난해한 언어가 저조한 경향을 본다(Clojure는 Zstd보다 여기서 훨씬 낫지만). 또한 Assembly는 훨씬 나쁜데, 인간이 Assembly로 Pandoc을 구현할 때 Zstd를 구현할 때보다 훨씬 더 불리할 것으로 예상되고, 이 점에서 LLM이 다를 강한 이유도 없어 보이므로 예상 가능한 결과다.
누가 알겠는가?
LLM을 사용할 때 무엇이 잘 작동하는지에 관한 질문이 많다(어떤 테스트 기법이 잘 작동하는지, 어떤 언어가 잘 작동하는지, 어떤 소프트웨어 아키텍처가 잘 작동하는지, 버그 수정 비용이 언어별로 다른지, 일반적인 프로그램 유지보수 비용이 언어별로 다른지 등). 이 질문 대부분은 공개 데이터에서 답이 없고, AI 연구소에서 답을 얻었다 해도 그 정보는 대개 공개되지 않았다.
특정 언어가 LLM 사용에 좋다는 식으로 떠도는 주장 대부분은 틀린 듯하다(예를 들어 위 링크된 평가에서 언급된 Ruby, Clojure, J가 LLM에 특히 적합하다는 주장, 그리고 Elixir가 LLM에 특히 적합하다는 다소 흔한 주장). 하지만 무엇이 맞는지는 명확하지 않다.
2014년에 정적 대 동적 타입에 관한 문헌을 살펴보았고, 몇몇 사례 연구 밖에서는 문헌 조사가 별로 유익하지 않음을 발견했다. 표준적인 학술 연구를 전형적으로 보여 주는 예로, Do Static Type Systems Improve the Maintainability of Software Systems? An Empirical Study라는 논문을 보았고 나는 다음과 같이 평했다.
피험자에게 기존 코드의 오류를 고치거나 스텁 메서드를 채워야 하는 클래스를 주었다. Java에는 정적 클래스, Groovy에는 동적 클래스였다. 타입 오류(및 이에 대응하는 no method 오류)의 경우 개발자는 Java에서 문제를 더 빨리 풀었다. 의미 오류에서는 차이가 없었다. 이 연구는 33명의 피험자에 대해 과제 순서를 무작위화한 피험자 내 설계를 사용했다. 주목할 만한 한계는 시간-해결 분산을 높인다는 이유로 루프와 재귀 같은 “복잡한 제어 구조” 사용을 피했다는 점이다. 그 결과 모든 버그가 사소한 버그다. 과제 해결 중앙값 시간이 수백 초라는 점에서 이를 볼 수 있다. 과제에는 여러 버그가 포함될 수 있으므로 버그당 시간은 꽤 낮다.
루프와 재귀 같은 “복잡한 제어 구조”를 피하고 과제가 수백 초 걸리도록 고르면, 고작 수십~낮은 수백 토큰이 걸린 첫 평가처럼 전문 프로그래머의 시간을 실제로 잡아먹는 과제와 관련해서는 결과가 무의미하다. 하지만 LLM에는 실제로 비사소적 과제를 주어 수행을 비교할 수 있다. 결과가 서로 다른 과제에 얼마나 일반화되는가의 문제는 있지만, 인간 연구에도 똑같은 문제가 있고 더 나쁘다(LLM 분산은 크지만, 동일한 인간에게 다른 시드로 여러 과제를 시킬 수 없으므로 인간 분산은 더 크다). 그리고 LLM에게 Zstd 디코더를 구현하게 하는 데 $20가 드는 것은 언어 수와 언어별 조건당 반복 횟수를 곱하면 결코 싸지 않다. 그러나 zstd RFC를 읽고 구현할 수 있는 전문 프로그래머를 고용하는 비용을 생각하면, 동등한 연구는 비용상 완전히 불가능했기 때문에 이루어지지 않았을 것이다. Pandoc 과제는 더욱 그렇다.
LLM으로 많은 질문은 사실상 답할 수 없는 것에서 약간의 노력과 토큰으로 답할 수 있는 것으로 바뀌었다. 작용하는 인센티브 때문에3, 이런 질문의 답을 곧 얻을지는 불분명하지만 이제 시도할 수는 있다.
이 평가가 증명하거나 반증할 수는 없지만(앞서 언급했듯 서로 다른 문제 간 분산 때문에 훨씬 많은 과제를 시도해야 한다), 어느 정도 빛을 비추는 떠도는 주장이 많다.
나쁜 코드가 많이 존재하는 언어(예: PHP)는 성능이 더 나쁠 것이다
이제는 다시 쓰기가 너무 쉬우므로 강력한 언어(예: Haskell)를 써야 한다
인기 있는 언어를 써야 한다
사전 등록한 추측에 대해서는 다음과 같았다.
높은 확신(95%): 전반적인 동적 대 정적 언어 주장은 성립하지 않을 것이다
낮은 확신(60%): 초고노력에서는 정적 언어가 동적 언어보다 다소 나을 것이다
높은 확신(98%): J 같은 언어의 “이상한” 우월성은 성립하지 않을 것이다
[초안 독자로부터]: “동적 언어는 소규모에서 낫지만, 프로젝트 크기가 커질수록 정적 언어에 추월당한다”
그런데 Pandoc 평가에서 Clojure가 Zstd 평가보다 크게 개선된 주요 이유는 Zstd 평가에서 중간 Clojure 프로그램 40개 중 36개와 초고 Clojure 프로그램 40개 중 5개가 byte 변환이 128–255에서 예외를 던지기 때문에 테스트에 실패했고(아마 unchecked-byte를 사용했어야 했을까?), 이 변환을 부적절하게 사용했기 때문이다.
이는 실제 결과다. 공개적으로 이용 가능한 최고의 GPT 모델에게 Zstd를 구현하라고 하면(그리고 아마 이 문제가 생길 수 있는 다른 비트/바이트 조작 과제에서도), 이 특정 방식으로 실패하는 코드를 낸다. 이를 잡는 테스트가 있으면 버그는 고쳐지지만, 여전히 시간과 토큰이 든다. 어떤 언어가 잘했든 이런 비용은 곳곳에 있다. 예를 들어 cargo는 반복해서 잘못된 인수로 호출되었다가 즉시 잡혀 고쳐지는데, codex에 cargo 호출 방법을 명시적으로 지시하지 않으면 내 실제 프로젝트에서 이 루프가 상당한 벽시계 시간을 소모할 수 있음을 알아챘고, 컨텍스트 창의 공간을 쓸 가치가 있음이 분명하다.
어쨌든 이 모든 것은, 누군가 어떤 언어 또는 언어 부류가 LLM과 특히 잘 맞는다는 강한 주장을 하려면 상당히 많은 서로 다른 평가를 돌려야 하는 이유를 보여 준다. 특정 조건이 특정 점수를 얻은 이유를 파고들면, 점수를 낳은 실패는 대체로 특이한 것이어서 그 문제가 과제나 설정을 넘어 얼마나 일반화되는지 항상 명백하지 않다. 하나의 평가, 심지어 다섯이나 열 개 평가의 점수를 보고 일반적인 프로그래밍에 관한 결론을 내릴 방법은 없다.
Zstd 평가와 Pandoc 평가 모두에서 언어 인기와 긍정적 결과(더 높은 정확성, 더 낮은 비용, 더 낮은 벽시계 시간) 사이의 상관관계를 보는 것은 사실이며, 다른 평가에서도 이를 볼 법해 보인다. 그러나 특정 언어에 대한 강한 결론을 내리는 것은 실수일 것이다. 과거 GitHub CI 데이터에 따르면 서로 다른 프로젝트에서 빌드가 얼마나 자주 깨지는지 살펴봤을 때도 이와 비슷한 경고를 했다. 프로젝트마다 빌드가 더 자주 또는 덜 자주 깨지는 이유는 다르고, 프로젝트 간 결과가 반드시 비교 가능한 것은 아니므로 강한 결론을 내려서는 안 된다고 했다. 예를 들어 한 프로젝트의 main 브랜치가 다른 검증을 거친 일종의 릴리스 후보라면 빌드 실패율이 낮을 것으로 예상되지만, 사람들이 main에 직접 개발하는 프로젝트와 비교할 수 없다.
그 직후 높은 점수를 받은 언어 중 하나와 관련된 사람(기억이 맞다면 Martin Odersky와 Scala)이 이 글을 트윗하며 그 언어의 높은 순위를 언어의 승리로 인용했다. 그것도 정당하지 않은 결론이었고, 여기에는 작용하는 분산 원천이 매우 많으므로 단일 언어에 관한 그런 결론은 여기서 더욱 정당하지 않다.
이 데이터는(평가의 타당성을 가정하면) 몇몇 강한 주장을 반박할 수 있고 다른 주장들을 시사하지만, 두 과제만 있기 때문에 언어 부류에 관한 것만 시사할 수 있을 뿐 특정 언어에 관해서는 그럴 수 없다. 특정 언어는 다른 과제에 일반화될 수도 있고 아닐 수도 있는 특이한 이유로 이 두 과제에서 잘하거나 못할 수 있다.
Max Bittker, Yossi Kreinen, Aaron Levin, Alan Boll, Luke Burton, Marco Primi, Milosz Danczak, Justin Blank에게 의견·수정·토론에 감사한다.
위에서 말했듯 여기의 내 평가는 빠르고 거친 평가이고 결함으로 가득할 것이 분명하다. 따라서 내가 제시한 평가는 훌륭하고 이것은 나쁘다고 말하려는 것은 아니다. 다만 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는 완벽한 점수를 얻으며 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가 있으므로, 이는 사실상 다음과 거의 같다.
if ../minigit commit ...; then
pass
else
fail
fi
내부 if는 실제 검사를 포함하려 했던 것으로 보이나, 코딩 오류(아마 복사+붙여넣기 오류?) 때문에 검사가 사실상 생략되었다.
또한 위에서 언급했듯, 에이전트는 테스트 환경을 수정할 수 있고 첫 Go 에이전트는 깨진 환경을 고치기 위해 그렇게 했다. 에이전트는 테스트와 환경에 완전히 접근할 수 있고 무엇이든 할 수 있으며, 개발 중에 테스트 스위트가 보이고 보류 집합이 없다. 이는 테스트를 통과하지만 “실제”에서는 쓸모없는 프로그램을 만드는 방식으로 특수 사례를 넣는 부정행위로 쉽게 이어질 수 있다. 높은 수준에서, 많은 프로그램이 명세의 큰 부분을 구현하지 못했지만 모든 테스트를 통과한다는 점에서 이와 비슷한 일이 일어난 듯하다. 이는 에이전트가 테스트 통과 방법을 “이해”하고 명세 구현보다 이를 선호했음을 나타낼 수 있다(테스트가 매우 얇고 통과하기 쉬움을 나타낼 수도 있다).
또 다른 문제는 모든 실행에서 Claude Code CLI 버전이 같지 않다는 것이다(2.1.66에서 2.1.68까지 다르다). 이처럼 중요할 수 있는 다른 문제가 몇 가지 더 있으나, 위에서 지적한 문제들에 비하면 작을 가능성이 크다.
비교할 수 있는 한 예로, 중간을 사용하고 에이전트에게 계속 작업하라고 하는 방식이 얼마나 비용 효율적인지 궁금했다. 그리고 한편으로는 “Ralph 루프” 지지자들이 말하는, 루프의 매 반복마다 컨텍스트 창을 비우고 에이전트에게 전체 프롬프트를 다시 주는 편이 낫다는 주장에 관해 궁금했다. 위와 마찬가지로 여기서의 사전 등록 추측은 다음과 같다.
확신 없음(50%): Ultra가 루프 속 medium보다 더 효과적이다
중간 확신(80%): 컨텍스트를 유지하며 계속하는 방식이 Ralph 루프를 능가한다
이 한 문제에서 평균적으로 Ultra를 한 번 실행하는 것은 비용 단위당 medium을 반복 실행하는 것보다 나아 보이며(시간 단위당으로는 훨씬 더 그렇다), 이전 컨텍스트를 계속 유지하는 방식이 Ralph보다 낫다. medium을 순진하게 반복하는 문제는 에이전트가 나쁜 해법에 고착되어 진전을 못 낼 수 있다는 것이다. Ralph 루프의 이론은 이를 일으킬 수 있는 나쁜 컨텍스트를 버린다는 것이지만, 나쁜 산출물이 생기는 것은 막지 못한다.
LLM을 사용하면서, LLM에게 코드 덩어리를 수정하거나 제자리에서 다시 쓰게 하는 것보다 버리고 처음부터 다시 쓰게 하는 편이 흔히 더 낫다는 것을 알아챘다. Postgres를 Rust로 다시 쓰며 대규모 변경을 해 온 Michael Malis도 이를 언급했다. 이는 또한 앞서 언급한, 높은 분산(및 이 경로 의존성) 때문에 토큰을 쓰는 것이 괜찮다면 주사위를 여러 번 굴려 최선의 결과를 택하는 편이 흔히 낫다는 생각과도 관련 있다.
문제의 제시 방식과 실제 실행 모두에서 더 “비즈니스 로직” 같은 세 번째 평가를 해 보려 했다. Zstd 평가와 Pandoc 평가는 프로그래머가 마주하기에는 꽤 이례적인 과제라고 주장할 수 있다. Zstd RFC만큼 잘 쓰이고 철저한 명세를 받는 프로그래머는 많지 않고, ProgramBench 테스트만큼 미리 만들어진 테스트가 많은 문제를 받는 프로그래머도 많지 않다.
여기서 아이디어는 보드게임을 구현하는 것이었다. 일반적으로 보드게임 규칙은 깔끔한 명세 작성 전문가가 아닌 사람들이 쓰므로, 보드게임 구현은 비프로그래머(또는 좋은 명세 작성 전문가는 아닌 프로그래머)가 누군가에게 과제를 줄 때 일어나는 일과 더 비슷하다.
문제는 LLM에는 사소하지 않으면서 채점을 위한 합리적인 오라클이 있는 게임을 얻는 것이다. 예를 들어 LLM은 Scout와 Azul의 규칙을 원샷으로 풀 수 있었고, 따라서 좋은 과제가 아니다. 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이 남는 용량을 모두 써서 일관성 검사를 돌리고 규칙을 수정하게 했다. 얼마나 걸렸는지 면밀히 추적하지는 않았지만, 플레이 가능하나 정확하다고 정말 믿기는 어려운 어느 정도 합리적 결과를 얻기까지 이런 수정에 한두 달쯤 매달렸던 것 같다.
이를 어느 정도 믿는 유일한 이유는 Pedro Oliveira도 Guards of Atlantis를 구현했고 완전히 다른 접근법(LLM이 스스로 알아내게 하는 대신 인간이 LLM을 이끄는 더 표준적인 접근법)을 썼기 때문이다. 구현을 비교했을 때 각 구현에서 대략 10개 정도의 버그를 찾았다. 두 구현이 똑같이 잘못 처리하는 남은 버그와, 구현은 다르지만 검사 체계가 알아채지 못한 버그가 있을 수 있다. 하지만 이제 두 구현의 규칙은 합리적으로 견고하다고 생각한다. 이것이 이 게임의 오라클을 가진 방식이다.
이 과제가 좋은 이유는 명세가 모호하고 모순적이며 때로는 그냥 틀리기까지 하고, 올바른 결과를 얻으려면 다른 정보를 사용해야 하는 현실에서 받는 종류의 “명세”와 더 비슷하게 느껴지기 때문이다. 이 평가에서는 LLM이 성가신 형식의 데이터에 접근하는 능력 시험이 되지 않도록(예를 들어 오프닝 북의 이미지 집합을 어떤 구조화된 데이터로 변환하거나 규칙 스캔본을 텍스트로 변환하는 일), LLM에게 데이터를 추출하라고 지시한 모든 것에 대해 원본과 추출 데이터를 모두 주었다(에이전트가 원한다면 추출 오류가 있는지 원본을 검사할 수 있도록 원본을 제시했다).
이 과제는 오래된 모델로 수행했다(GPT-5.1 또는 5.2로 일부를, 5.4 또는 5.5로 다른 일부를 했다). 하지만 더 새 모델이라도 오래된 모델에 준 종류의 안내 없이 이 과제는 여전히 너무 어려웠다. 언어와 상관없이 에이전트는 이 과제에서 대략 0점을 받았다.
참고로 LLM(과 인간)이 무엇에 어려움을 겪는지 궁금하다면 몇 예가 있다. 다음 문구의 카드가 하나 있다. “Target a unit adjacent to you. After the attack: may repeat once on a different enemy hero.”
이 게임에서 hero는 unit의 한 종류다. 엄격하게, 그리고 “After the attack”의 의미 등 규칙을 완전히 알고 읽으면, 이는 하나의 unit을 공격하거나 두 hero를 공격할 수 있다는 뜻이어야 한다. 결국 다른 enemy hero에게 공격을 반복하려면 첫 unit이 hero였어야 한다. 그렇지 않으면 다른 enemy hero가 아니라 hero인 다른 unit이 될 것이다.
이 카드는 사람들이 불명확하다고 불평했기 때문에 사실상 정오표가 카드에 인쇄되어 있다. 정오표는 “(You may repeat even if the original target was a minion)”이라고 한다. 이것만으로도 LLM(과 일부 인간)에게 혼란스럽지만, 진짜 문제는 같은 구문을 쓰면서 이 수정이 없는 다른 카드가 있다는 점이다. 같은 구문의 다른 카드를 올바르게 플레이하려면, 이 구문이 쓰일 때마다 이 카드의 정오표를 적용해야 함을 알아야 한다. 게임 디자이너가 좋아하는, 특정한 비문자적 의미를 가지며 염두에 두어야 하는 구문이 여러 개 있다.
명백한 방식으로 플레이하면 안 되는 규칙의 또 다른 예는 “Choose one, or both, on different targets: A, B”라고 쓰인 카드를 가진 캐릭터다. 이것을 엄격하게 읽으면, 서로 다른 대상에게 A 또는 B 중 하나를 하거나 A와 B 모두를 할 수 있을 것으로 예상된다. 하지만 게임 정신의 일부는 캐릭터가 한 카드로 다른 캐릭터를 여러 번 공격할 수 없다는 메타 규칙이므로, 카드에 쓰인 대로 하여 서로 다른 수의 대상에 A와 B를 모두 할 수 있다는 해석은 맞을 수 없다. 유사한 추론과 비슷한 구문이 쓰이는 방식을 바탕으로, 이 카드는 “Choose one, or both on different targets”으로 해석해야 한다. 이것도 모호하다고 할 수 있으며 “Choose one or both (must be on different targets if both)”라고 더 명확하게 쓸 수 있다.
인간은 “게임의 정신”이 무엇인지 이해하면 이런 것을 해결할 수 있다. 하지만 이는 의도적으로 규칙에 명확히 쓰여 있지 않고 Discord 토론에서 추론해야 하며, 오늘날 모델의 능력을 넘어서는 듯하다. 오늘날 모델보다 많은 전문 과제에서 성능이 낮은 인간은 이를 할 수 있는데도 그렇다.
규칙을 구현한 LLM을 감독할 때, LLM이 한계에 도달하고 완전히 올바른 규칙으로 수렴하지 못한 이유는 LLM이 규칙이 일관되지 않고 틀렸음을 관찰했기 때문이다. 그러면 이 규칙을 고치려 하고, 일관되고 올바르게 만들기 위해 다른 것도 고쳤다. 이는 때로 더 올바르게 만들고 때로 덜 올바르게 만들었다. 덜 올바르게 만들 때 LLM은 때때로 기존의 올바른 테스트를 수정해 틀린 테스트로 바꾸었다. 그래서 얼마 뒤 LLM은 정확성을 개선하는 대신 어떤 규칙이 틀렸는지를 바꾸는 작업만 반복했다. 무엇을 어떻게 검사할지 어느 정도 안내한 상태에서도 그랬다. 그런 안내 없이, 오늘 이용 가능한 더 발전한 모델로도 LLM은 이를 합리적인 방식으로 다루지 못했다.
여기서 좋은 평가가 될 알맞은 규칙 복잡도의 보드게임은 분명히 있다고 생각한다. 하지만 정의상 이는 오라클을 만드는 데 어느 정도 작업이 드는 것이며, 알맞은 규칙을 가진 게임의 오라클을 지금 가지고 있지 않다. 실제로는 가능하고 확장 가능하다고 생각한다. 하나 만드는 데 드는 것보다 훨씬 더 들이지 않고 수십 또는 수백 개를 만들 수 있으며, 따라서 수백 게임에 대해 합리적으로 올바른 오라클을 얻고 LLM에 오늘날 흥미로운 시험이 될 올바른 수준의 게임을 확인할 수 있다. Guards 구현에 노력이 든 이유는 리플레이 데이터가 있는 기존 구현이 없었기 때문이다. 각 게임의 정확성을 시험하기 위해 리플레이 데이터에 의존하는 사람이라면 이런 환경을 꽤 쉽게 많이 만들 수 있다.
이는 다소 우스운 문제라고 할 수 있다. 좋은 명세, 예컨대 명확하게 쓰인 규칙집이 주어지면 LLM은 Guards of Atlantis보다 복잡한 산출물을 구현할 수 있다(Zstd RFC가 논쟁의 여지 없이 더 복잡하고, Pandoc은 확실히 그렇다. Pandoc이 지원하는 PDF 같은 개별 문서 형식조차 Guards of Atlantis보다 복잡하다). 따라서 문제는 LLM이 어려워할 만큼 충분히 복잡한 규칙의 게임을 찾는 것이 아니라, LLM이 어려워하되 완전히 가망 없을 정도는 아닌 만큼 충분히 잘못 쓰인 규칙의 게임을 찾는 데 더 가깝다. 하지만 인간은 일반적으로 명확한 명세 작성에 그리 능하지 않고, 인간의 불명확하고 모순적이며 때로는 그냥 틀린 명세를 모델과 하니스가 얼마나 잘 처리하는지가 Zstd RFC만큼 잘 쓰인 명세에서 LLM이 무언가를 얼마나 잘 구현하는지보다 전형적인 사용자에게 더 관련 있을 것이므로, 이것은 실제 현실 문제다.
초고노력 테스트
codex 사용
인터넷 접속 없음
사람들이 돌려보는 많은 벤치마크보다 비교적 큰 과제
에이전트 지정 프롬프트
Zstd 평가: 문제나 실패 테스트를 알리지 않고 에이전트에게 버그 수정 요청
성능 벤치마킹은 충분히 해 봤기 때문에, 일반적으로 내 벤치마크가 어떻게 결함이 있는지 알고 시간/노력 대 결함의 정보에 근거한 절충을 할 수 있다고 느낀다. 또 벤치마크에 있는 결함이 내가 이해하려는 대상에 중요하지 않다는 데 상당한 확신이 있다. AI 평가는 이 정도의 감각을 갖기에 충분히 해 보지 않았으므로, 메타 수준에서 내가 하는 모든 AI 평가는 내가 모르는 결함을 가질 것이라 예상한다.
여기서 결함이 있을 것이라 예상하는 또 다른 이유는 코딩 에이전트에게 이 평가를 설정하게 했고, 문제를 찾으려고 1분만 볼 때마다 적어도 하나의 문제를 찾았기 때문이다. 이는 조금 더 보면 발견할 수 있는 추가 결함이 이 평가에 있을 가능성이 꽤 높음을 뜻한다. 하지만 이것은 “Gary Bernhardt” 수준의 정확성보다 “빠른 장난감 프로젝트” 수준의 정확성이었으면 했기에, 몇 가지 문제를 고친 뒤 멈췄다.
검증 엔지니어로 일하던 때, 아마 2007년쯤 Austin에서 Sun/Oracle 엔지니어가 한 모임에 참석했다. 그들은 버그 사이의 시간을 칩 릴리스에 대한 신뢰 수준으로 변환하는 이 생각을 수학적으로 형식화했다. 사람들이 이를 많이 하는 것은 보지 못했지만, 최근 Antithesis 공동 창업자 Will Wilson이 Antithesis의 일부 사람들이 생태학의 수학(희귀종 관찰 문헌)을 이용해 실제 버그율을 추정한다고 말하는 것을 들었다. 이는 수십 년 전 Sun/Oracle의 그 엔지니어가 하던 것의 훨씬 정교한 버전으로 보인다.
멋진 생각이지만, 볼 때마다 매분 버그를 하나 찾는다면 다른 버그가 많이 있을 법하다는 것을 알려 주는 데 복잡한 수학은 필요 없다. 이 일을 업무로 했고 이 평가의 충실도에 관심을 가질 이유가 있었다면, 더 면밀히 보고 문제를 더 고치는 것이 타당했을 것이다(업무에서 이런 일을 했다면, LLM에게 이 평가를 설정하라고 지시할 때 실수도 더 적게 할 기술과 경험이 있었을 것이다). 그러나 “LLM 사용 시 동적 언어가 정적 언어보다 의미 있게 나은가?”라는 질문에 답하기 위한 목적에서는, 그 주장이 참이 아니라는 데 조금 더 확신하게 되었고, 어떤 기법이나 테스트 라이브러리가 최선인지처럼 실행 가능한 결과를 낼 법한 다른 질문이 많다.
보통 블로그에는 어느 정도 견고하다고 느낄 때까지 글을 올리지 않지만, 이는 종종 호기심을 충족할 만큼 데이터를 탐색하고 결과를 전혀 공개하지 않는다는 뜻이다. 공개하지 않은 결과에 관해 사람들과 이야기해 보면, 내가 정말 좋아하는 수준으로 완료되지 않았어도 대화 상대는 흔히 결과에 궁금해한다. 이는 대화하지 않는 사람들도 관심 있을 수 있음을 시사하는 듯하다. 지금까지 본 바로는 이를 내가 정말 좋아하는 기준까지 끌어올리려면 적어도 지금까지 들인 시간의 10x는 걸릴 것 같다. 현재 꽤 바쁘고 몇 달 동안 그럴 시간이 있을 것 같지 않으며, 그때가 되면 실제로 이것을 공개할 일을 하게 될지도 확신하지 못한다. 최근 글에서 거의 1년 전에 한 분석, 즉 사고에서 어떤 차가 뇌진탕 위험에 더 나은지 이해하려 했던 분석을 언급했다. 어느 정도 시간을 들여 알아내고 나를 만족시키는 답에 도달했지만, 결과를 공개하기에 충분히 정리하는 작업은 끝내 하지 못했다.
그 분석의 일부 결과는 실제 충돌 데이터에서 HIC와 속도의 관계가 4제곱처럼 보인다는(!) 발견처럼 “출판 가능”해 보인다. 이 관계를 찾으려 했던 논문이 있지만 잘못된 종류의 분석을 했고 “O(n)” 스타일의 관계를 찾지 못해 훨씬 더 모호한 결과를 얻었다. 하지만 나는 어떤 것이 논문인지 블로그 글인지 정말 신경 쓴 적이 없고, 글을 공개할 만큼 분석을 정리하는 대신 다음 분석으로 넘어갈 가능성이 더 높다는 것이 드러났다.
이와 비슷한 최근 프로젝트로는 초인적인 Azul AI를 만든 뒤 훨씬 인간 시간이 덜 드는 과정으로 초인적인 Splendor AI를 만들려 했던 일이 있다. 그것은 성공하지 못했다고 생각하지만, 찾을 수 있었던 다른 모든 Spelndor AI보다 좋은 차이로 이긴다. 이는 약간 흥미로운 결과다. 보드게임 AI에 관해서라면 글을 쓸 만큼은 안다고 생각하지만, 주된 관심은 괜찮은 것을 만들 수 있는지 알아보는 데 있었고, 그 뒤에는 멋진 글을 쓰는 데 시간을 쓰는 대신 계속 다른 프로젝트를 한다. 흥미롭다고 생각하는 한 예는 하고 싶은 성능 최적화 중 많은 것이 실제로 결과를 바꾼다는 점이다. 따라서 결과를 바꾸지 않는다고 엄격하게 확인할 수 있는 최적화만 의존할 수는 없다. 그러나 플레이 강도를 낮추지 않는 방식으로 이런 최적화를 하라고 코딩 에이전트에게 순진하게 요청하면, 강도를 낮추는 온갖 일을 한다. 강도 저하가 매우 심한 경우는 잡기 쉽지만, 예를 들어 자기 AI와의 셀프플레이에서는 강도 변화가 없지만 인간이나 다른 AI를 상대로는 강도가 낮아지는 더 미묘한 문제도 있다. 따라서 나쁜 최적화를 잡는 어떤 과정이 필요하며, 이는 직관과 LLM에 의존하는 조합으로 설계해야 하는 본질적으로 다소 임의적인 과정이다(LLM은 매우 도움이 되지만 흔히 완전히 틀릴 것이다).
관심 있는 이런 데이터 중심 프로젝트에서 LLM은 내 호기심을 만족시킬 만큼 강한 결과를 얻는 데 드는 노력을 대폭 줄인다. 하지만 적어도 결과를 손으로 작성하고 LLM에게 결과를 쓰게 하지 않으며, 결과가 멋지고 깔끔하기를 원한다면 결과를 공개하는 데 드는 노력은 별로 줄이지 않는 듯하다. 즉 결과 작성은 일종의 Ahmdhal의 법칙 병목에 부딪히며, 그래서 이런 프로젝트는 더 많이 하고 그중 글로 쓰는 것은 더 적게 하고 있다.
어쨌든 이것을 공개하는 것은 무언가를 공개하기 전에 정말 갖고 싶은 종류의 정리된 버전 대신, 덜 익은 메모를 공개하는 실험이다. 이에 관해 의견이 있다면 알려 달라(XBskyMastodon)!
현재 평가의 GitHub 링크는 없다. 한편으로는 정말 있어야 한다고 느낀다. 다른 한편으로는 엉망이고 코드를 공개하기 전에 정리하고 싶은 것이 많으며, 언제 그 일을 하게 될지 모르겠다. 이렇게 하면 적어도 몇 친구에게 결과를 말하고 결과가 하드 드라이브에 무기한 머무르게 하는 대신 무언가를 내놓는다.
에이전트에게 성능을 무시하라고 지시했지만 시간 제한은 무한하지 않았고, 중간 조건에서는 일부 테스트 사례가 시간 초과했다. 이는 논쟁의 여지 없이 불공정하지만 점수에 실질적 영향을 주지는 않았다. 무한 루프가 아닌 시간 초과의 경우 Clojure에서 2개(40 * 34개 테스트 전체에서), J에서 2개, Tcl에서 2개, Factor에서 1개, PHP에서 1개였다. 그리고 9000s(2.5시간)는 최대 테스트 사례가 4 GiB였음을 고려하면 상당히 관대했다. 4 GiB를 2.5시간 안에 디코드하지 못하는 것은 Graviton 5 코어에서 0.5 MB/s 미만의 암묵적 속도이며, 이는 상당히 느리다.
에이전트에게 이를 설정하게 하면서 겪은 문제 중 일부는 다음과 같다(위에서 언급했듯 각 문제를 찾는 데 걸린 짧은 시간은 더 많은 문제가 있음을 시사한다).
버그가 아니라고 주장할 수도 있지만 어쨌든 제거한 것이 하나 있다. 한 테스트는 매우 어려웠다(아마 에이전트 약 10%가 첫 시도에서 통과했다). 현재 zstd 릴리스 바이너리를 시험하면 zstd 바이너리도 이 테스트에 실패한다. RFC를 읽어 보니 특정 모서리 사례의 합법성에 관한 RFC의 모호성인 듯하다. 어느 언어가 이 테스트 사례를 더 자주 통과했는지에 관해 꽤 강한 군집화가 있었고 흥미롭다고 생각한다. 하지만 다른 모든 테스트가 더 직선적인 무언가를 측정하거나 적어도 측정하려 시도하는 상황에서, 이는 측정하기에 그리 유용하지 않아 보인다.
어쨌든 위 목록은 완전하지 않지만, 많은 문제가 언어의 큰 비율에 영향을 주었고 일부 문제는 여러 번 고쳐야 했다. 조건마다 별도 버그로 세면, 아마 이런 버그를 100개 넘게 고쳤고(에이전트에게 고치게 했고), 더 있을 것으로 예상한다. RL 환경 스타트업을 운영하는 Max Bittker와 이야기했을 때 그는 다음과 같이 말했다.
내가 작업한 모든 평가에서 매우 많은 시간과 노력을 들이게 되었고, 대부분은 궤적(또는 많은 궤적의 요약)을 읽고 문제를 분류하는 형태였습니다. 예를 들어 “이 종류의 버그는 가능하면 안 되니 X를 업데이트하자” 같은 것입니다(X는 프롬프트, 하니스/환경 또는 검증기입니다).
에이전트는 이런 것을 엉성하게 만들기 쉬우므로, 올바른 계층에서 문제가 고쳐지도록 매우 신경 씁니다. 예를 들어 시험 중인 에이전트의 컨텍스트 안에 무엇이 있는지는 매우 민감합니다(걱정해야 하는 무작위 잡동사니를 추가하는 것은 나쁘고, 최악의 경우 답을 누설합니다). 반면 다른 시스템 부분에서 뒤에서 고정되는 것은 다릅니다.
평가를 작성할 때 에이전트는 시험 대상 에이전트의 경험에 충분히 민감하지 않고, 그냥 답을 주거나 내부 에이전트의 문제로 만들어 문제를 고칩니다(“보상 해킹을 하지 마세요”라고 기억시키는 식입니다).
또한 프롬프팅으로 평가를 위해 처음부터 무언가를 만들려 하기보다, 기존 것(리포지터리, 게임, 도구, 레벨)을 재사용하고 그 주위에 하니스와 검증기를 만드는 방식으로 큰 성공을 거뒀습니다.
돌이켜보면 교차 언어 평가를 한 것을 조금 후회한다. 평가 문제를 100개 이상 고친 뒤에도 더 많은 문제가 남아 있음은 의심의 여지가 없다. 이는 단지 “남의 떡이 커 보이는” 생각이고 다음에 하는 평가도 후회하게 될지 모르지만, 다른 언어를 평가하는 것보다 서로 다른 테스트 기법이나 테스트 프레임워크가 얼마나 잘 작동하는지 평가하는 편이 훨씬 적은 작업이었을 것 같고, 그 주제도 적어도 그만큼 흥미롭다. 또한 돌이켜보면, 더 많은 작업을 손으로 하고 에이전트에 덜 의존했다면 훨씬 잘되었을 것이다. 예를 들어 한 언어용 환경을 에이전트에게 만들게 한 뒤, 다른 언어용 환경을 만들기 전에 에이전트와 내가 모두 검사하고 문제를 고쳤어야 했다. 이를 몇 번 했다면 다른 언어의 환경을 더 잘 만드는 설정을 마련했을 것이다(그렇지 않았다면 각 언어에 이 과정을 반복해도 되었고, 더 많은 시간이 들지 않으면서도 더 신뢰할 수 있는 결과를 얻었을 가능성이 크다).
또 주목할 점은 적대적 입력에 대한 메모리 안전성처럼 언어의 진짜 차이 중 상당수가 실제로 시험되지 않았다는 것이다. 에이전트가 Rust보다 C나 C++에서 대체로 대략 올바른 코드를 만들기 더 어려워했다면 관찰되겠지만, 퍼저나 valgrind나 다른 도구가 문제를 발견한다면 작은 테스트 집합에서 포착될 가능성은 낮다. C와 C++ 코드의 메모리 안전 문제를 에이전트에게 잠깐 검사해 보라고 요청하기는 했다. 에이전트는 C와 C++ 코드를 ASan+UBSan 아래에서 실행하고 퍼즈 입력을 각각 4000개씩 몇 개 시도했지만 문제를 찾지 못했다고 주장한다. 물론 문제가 없다는 뜻도 아니고, 더 큰 코드베이스에 문제가 없다는 뜻도 아니다.
실제로 Pandoc 평가에서 메모리 안전 문제를 비슷하게 빠르게 검사하자 모든 C 프로그램과 하나를 제외한 모든 C++ 프로그램에서 메모리 안전 문제가 발견되었다(문제는 범위를 벗어난 메모리를 잘못 역참조하는 것 등이었다. 구체적인 한 예로 한 C 프로그램에서는 잘린 LaTeX 표가 범위를 벗어난 메모리 읽기를 일으킬 수 있었다). 이런 문제가 수십 초의 프롬프팅으로 발견될 수 있었다는 사실은, 사람의 큰 노력 없이 많은 이런 문제를 찾아 고칠 수 있음을 나타낸다. 하지만 상당한 토큰이 들고 C와 C++ 버전의 비용을 Rust 버전 비용보다 훨씬 높일 것이며, 그 모든 일을 한 뒤에도 C와 C++ 버전의 메모리 안전성에는 Rust 버전보다 확신이 낮을 것이다.
어쨌든 결과 분포가 궁금하다면, 중간과 초고에 대해 다음과 같다.
여기서 초고 결과가 다소 포화된 것은 마음에 들지 않는다. 하지만 초고를 시험하는 한 “문제”는 문제가 어려워질수록 매우 오랫동안 계속 간다는 점이다(예를 들어 Pandoc 초고 실행 대부분은 12시간 넘게 돌았고, 어셈블리 실행은 훨씬 더 오래 걸렸다). 따라서 포화되지 않는 대상은 Pandoc 평가처럼 매우 큰 과제이거나 Guards of Atlantis 평가처럼 어떤 방식으로 너무 어려운 과제다.
한 가지로, 보류 테스트는 에이전트가 만들었기 때문에 의심스럽다. 의도는 부정행위를 하지 않는 합리적인 사람(또는 에이전트)이 통과하게 만들 수 있는 보류 테스트를 만드는 것이었다. 에이전트는 이것이 합리적이지 않은 사례가 있는지 이 보류 테스트 집합을 감사하고 일부를 제거했지만, 나는 이를 손으로 검사하지 않았다. 따라서 어떤 방식으로든 불공정한 보류 테스트가 적어도 하나 있을 가능성이 높다고 본다. 그러나 보류 테스트에 대한 전체 점수가 충분히 낮아서 소수 테스트가 나쁘다는 점은 크게 걱정하지 않는다(AI 연구소에서 차세대 모델을 학습하려 한다면 더 걱정하겠지만, 여기 용도에서는 중요하지 않다고 생각한다).
보류 테스트 집합이 있는 상태에서 부정행위를 하지 말라고 지시해도 보류 테스트에서 극도로 나쁜 점수를 받는 노골적 부정행위는 막지 못했다. 그러나 채점 대상 보류 테스트 집합이 있다고 에이전트에게 말하면, 에이전트가 볼 수 있는 테스트에서 달성한 점수는 낮추면서 보류 테스트 점수는 높이는 듯했다(이를 말하지 않으면 여러 에이전트가 쓸모없을 만큼 취약한 코드로 Pandoc 테스트에서 100%를 얻었다. 보류 집합이 있다고 말하자 어떤 에이전트도 초고에서 1턴 뒤 100%를 얻지 못했지만, 보류 점수는 상당히 좋아져 더 나은 일반화를 나타냈다).
[돌아가기] 3. LLM 코딩 성공의 비밀을 알려 주겠다고 약속하는 여러 Substack, YouTube 채널 등이 있지만, 실제 실험을 돌리는 데 시간을 쓰는 ROI는 사실상 없다. 원시인 모드를 살펴봤을 때, 가장 큰 프로그래밍 YouTuber 중 하나가 몇 분 동안 살펴보고 작동한다고 결정한 영상을 보았다. 실제로 작동하는지 15분만 더 들여 조사하는 것도 그 시간에 더 많은 콘텐츠를 만드는 것에 비해 아마 음의 ROI일 것이다.
서로 다른 기법을 논의하는 다양한 논문도 있으며, 이는 때때로 대부분의 블로그 글이나 영상보다 더 자세히 들어간다. 하지만 평균적으로 반드시 더 유용한 정보가 있는 것은 아니다. 예를 들어 ChatGPT(5.6 Sol, Pro)에게 LLM에 대한 언어 효과성 논의를 찾아 달라고 했을 때, 이 토큰 효율성 논문을 찾아냈다. 흥미로운 생각이 있지만, 앞서 논의한 원시인 모드 평가와 같은 문제가 있다. 프로그래머인 나에게 결과가 관련 있을 만큼 흥미로운 과제를 보지 않는다. 그 논문을 인용한 것만 보면, “The Best Programming Language for Tokenmaxxing”이라는 제목의 언어 토큰 효율성에 관한 세 학자의 논문을 찾는다. 하지만 이 글과 비교하면 그 논문은 네 언어만 비교하고, 더 나쁜 모델을 쓰며, 작은 장난감 문제를 쓴다(LiveCodeBench라는 것에서 왔다. GPT-5.5로 문제를 푸는 비용은 흔히 1000토큰 정도다). 평가가 얼마나 잘되었든, 이 글과 원시인 모드 평가에서 언급했듯 작은 장난감 문제에서 취미 프로젝트나 업무에서 관심 가질 문제로 갈 때 상대적 결과가 크게 달라지는 것을 흔히 본다. 또한 그 논문에서는 “To test your program, run exactly ./test.sh... These are the only tests I care about”이라는 프롬프트를 주었다고 언급하며, “일상적 사용에서 프로그래머는 테스트를 에이전트에게 숨기지 않습니다. 대신 모든 테스트가 통과할 때까지 계속 작업하라고 에이전트에게 지시합니다”라고 하며 이것이 현실적이라고 말한다. 하지만 위에서 언급했듯 그렇게 하면 현실에서 실패하는 취약한 코드가 나온다(또는 에이전트에게 주지 않은 보류 테스트가 있으면 매우 높은 비율로 보류 테스트에 실패한다). 이 문제는 테스트 몇 개를 더 추가하는 것만으로 해결할 수 없다. 퍼징이나 속성 기반 테스트 같은 것으로 다룰 수는 있겠지만, 그것이 얼마나 잘 작동하는지는 다른 글의 주제다. 이 논문들이 나쁘다거나 이 논문들에서 배울 흥미로운 것이 없다고 말하는 것은 아니다. 하지만 어떤 기법이나 도구를 써야 하는지 알고 싶은 프로그래머로서, 위 링크된 논문들에서 그 정보를 얻을 수는 없다.