MCP 검색 도구가 grep보다 토큰 사용량이 많다가 저장소 크기가 바뀌자 역전된 경험
요약
CodeNib 논문의 에이전트 실험을 재현한 결과, 저장소 규모에 따라 MCP 검색 도구의 토큰 효율성이 극명하게 갈리는 것을 확인했습니다. 파일 수가 적은 저장소에서는 오히려 토큰 사용량이 늘었으나, 규모가 커질수록 grep 대비 토큰을 대폭 절감할 수 있었습니다.
핵심 포인트
- 저장소 규모에 따라 MCP 도구의 토큰 효율성이 역전됨
- 33개 파일 저장소에서는 grep 대비 토큰 사용량 4.1배 증가
- 249개 파일 저장소에서는 grep 대비 토큰 86% 절감
- CodeNib 논문의 컨텍스트 정책은 프롬프트 이력 관리 방식의 차이임
요약: grep 대신 MCP 검색 도구를 사용하자 33개 파일의 저장소에서는 코딩 에이전트가 4.1배 더 많은 토큰을 사용했지만, 249개 파일의 저장소에서는 86%를 절감하는 결과를 얻었습니다. 모델과 작업은 동일했으나 결과는 정반대였습니다.
저는 주말 동안 CodeNib라는 논문에서 제시된 에이전트 실험을 GPU가 없는 Windows 노트북으로 제가 직접 작성한 두 개의 저장소를 대상으로 재현해 보았습니다. 총 8가지 행동 질문, 4개의 팔(arm), 32번의 에이전트 실행을 거쳤으며, 모든 토큰은 제공업체의 응답에서 읽었습니다.
코드를 작성하기 전에 두 가지 규칙을 세웠습니다. 추측하지 말고 측정한다. 그리고 논문과 모순되는 결과가 흥미로운 결과이니, 이를 꾸미지 않는다.
두 번째 규칙 덕분에 저는 논문으로부터는 보호받았지만, 이 게시물의 마지막 3분의 1에 걸쳐 제가 스스로로부터는 보호받지 못했습니다.
CodeNib이 주장하는 에이전트 토큰 사용량
CodeNib (arXiv:2607.25431)은 단일 저장소 커밋에 대해 세 가지 구현된 뷰(materialized views), 즉 어휘 인덱스(lexical index), 밀집 임베딩 인덱스(dense embedding index), 그리고 심볼 그래프(symbol graph)를 구축합니다. 초록의 한 문장이 제가 이 논문을 읽게 된 이유였습니다. 그 컨텍스트 정책을 사용하면 코딩 에이전트가 페어링된 grep/read 에이전트보다 50%에서 87% 적은 트래젝토리 토큰을 사용한다는 것입니다.
저는 이것이 제가 실제로 가진 하드웨어에서도 통하는지 알고 싶었습니다.
| mine | the paper | |
|---|---|---|
| CPU | Ryzen 5 7530U | 2x Xeon Gold 5416S |
| ... | ||
| Wall-clock timings from a CPU-only laptop are not comparable to an H100 run, so I do not report them as if they were. Token counts are hardware independent, and tokens are what I measured. |
두 저장소 모두 보이는 것보다 작다
| repo | files on disk | actual source files |
|---|---|---|
| SalesRabbit (TypeScript) | 9,524 | 33 |
| Leadpipe (Python + TS) | 20,301 | 249 |
SalesRabbit의 TS/JS 파일 중 6,032개는 node_modules입니다. Leadpipe의 8,693개
초록(abstract)이 말해주지 않는 사실은 이것입니다. 논문의 세 가지 방식(arms)은 모두 동일한 도구(tools)를 사용합니다. 오직 프롬프트 이력(prompt history)만 다를 뿐입니다.
grep/read: 이력은[S, Q]로 시작합니다. 에이전트(agent)가 모든 것을 스스로 찾아냅니다.eager: 이력은[S, Q, C10]으로 시작하며, 여기서C10은 실행 전 한 번 계산된, 임베딩(embedding) 순위 기반의 상위 10개 호출 가능한 레벨 블록(callable-level blocks)입니다.eager + compact: 시작은 동일하지만, 첫 번째 성공적인 읽기(read) 이후에 하나의 결정론적인 이력 재작성(history rewrite)이 수행됩니다. 중복 제거된 읽기 경로, 최신 읽기 결과 전체, 그리고 최신 어시스턴트 메시지의 처음 600자를 유지합니다. 그 외의 모든 것은 폐기되며, 요약기(summarizer)는 실행되지 않습니다.
따라서 "50%에서 87%"라는 수치는 단일한 숫자가 아니며 단일한 메커니즘도 아닙니다. 이는 모델별 최적의 방식(best arm)을 의미합니다. Gemma 4-12B는 87%를 기록했습니다. Claude Haiku 4.5는 50%를 기록했으며, Haiku의 경우 압축(compaction)을 적용했을 때 오히려 상황이 악화되어 eager 방식 토큰의 123.3%를 사용했습니다. 이것이 논문 자체의 선택 규칙이 Haiku에는 일반 eager 방식을 선택한 이유입니다. 이러한 부정적인 결과는 본문에 포함되어 있으며 초록 어디에도 나와 있지 않습니다.
제가 실행하고자 했던 것은 달랐습니다. 프롬프트 전략은 고정시킨 채, grep과 read_file을 codenib mcp의 도구들로 교체하여 **도구 세트(tool set)**를 바꾸는 것입니다. 이것이 엔지니어가 실제로 던지는 질문입니다. 또한 이는 논문의 실험 내용도 아니며, 저는 나중에 스스로를 속이지 않기 위해 실행 전 이 차이점을 파일에 기록해 두었습니다.
단 한 가지 차이만 있는 두 에이전트
테스트 프레임워크(harness)의 이름은 커피에서 따왔습니다. 이틀 동안 이것만 쳐다보고 있을 예정이라 이름이라도 즐거우면 좋겠다고 생각했기 때문입니다. brew_kit.py는 공유 루프(shared loop)를 담고 있습니다. filter_menu.py는 제어 도구 세트로, grep과 read_file 외에는 아무것도 없습니다. espresso_menu.py는 stdio를 통해 codenib mcp로부터 도구를 실시간으로 가져옵니다. cafe.py가 전체 과정을 실행합니다.
루프는 자신이 어떤 메뉴를 들고 있는지 알지 못합니다. 하나를 다른 하나로 교체하면 도구 세트만 바뀔 뿐 다른 것은 변하지 않습니다. 동일한 시스템 프롬프트(system prompt), 동일한 16턴 제한, 동일한 온도(temperature) 0, 동일한 모델을 사용합니다.
토큰 계산 (Token accounting)은 논문 자체의 정의에 따라, 호출(invocation)당 제공업체가 보고한 값을 궤적(trajectory) 전체에 걸쳐 합산하여 이루어집니다. 실행당 전체 기록이 JSON으로 덤프되므로, 모든 수치는 신뢰하는 대신 감사(auditable)가 가능합니다. 그 결정은 두 번의 가치를 증명했으며, 아래에서 그 두 가지 사례를 모두 확인하실 수 있습니다.
MCP 도구가 grep보다 4.1배 더 많은 토큰을 소모함
33개 파일로 구성된 저장소(repo)에 대한 첫 번째 쌍 비교 결과입니다.
| arm | 입력 토큰 (input tokens) | 턴 (turns) | 파일 찾기 성공 |
|---|---|---|---|
대조군 (control, grep + read_file) | 3,531 | 3 | 예 |
실험군 (treatment, codenib mcp) | 14,480 | 3 | 예 |
두 에이전트 모두 동일한 턴 수 내에 올바른 파일을 찾아냈습니다. 그 격차를 세부적으로 분석해 보겠습니다.
| 턴 (turn) | 대조군 (control) | 실험군 (treatment) | 발생한 상황 |
|---|---|---|---|
| 1 | 307 | 1,182 | 바이트 단위로 동일한 사용자 프롬프트 |
| ... | |||
| 1턴에서 사용자 메시지는 모든 arm에서 동일하며, 실험군 프롬프트가 여전히 875 토큰 더 큽니다. 이는 순수한 도구 스키마 (tool-schema) 오버헤드입니다. 코드가 검색되기 전, 매 턴마다 두 개의 압축된 도구 대신 문단 길이의 설명을 가진 9개의 MCP 도구에 대해 비용이 청구됩니다. 16턴 동안 이는 약 14,000 토큰의 무의미한 비용이 됩니다. |
만약 MCP 서버를 에이전트 루프 (agent loop)에 연결하고 있다면, 이 수치를 반드시 명심해야 합니다. 도구 설명 (Tool descriptions)은 프롬프트의 일부이며, 매 턴마다 그 비용을 지불해야 합니다.
논문의 계산 방식으로는 이 문제를 드러낼 수 없는데, 왜냐하면 논문의 arm들은 동일한 도구 세트를 공유하며 설계 구조상 스키마 비용이 상쇄되기 때문입니다. 하지만 도구 세트 자체가 변수가 될 때는 상쇄되지 않습니다.
두 번째 비용은 더 미묘합니다. 모든 search_semantic 결과는 전체 함수 본문 (function body)을 인라인으로 포함하여 반환됩니다. 이는 진정으로 더 나은 증거가 되지만, 히스토리(history)에 남게 되어 이후의 모든 턴에서 다시 비용이 청구됩니다. 대조군 에이전트의 grep은 단 60자 길이의 한 줄을 반환한 뒤 정확히 6줄을 읽었습니다.
작은 저장소의 세 가지 작업 전반에 걸쳐, 실험군은 동일한 3/3 정확도를 기록하면서도 대조군 대비 중앙값 410%의 비용을 소모했습니다.
한 작업에서 결과가 역전됨
그 후 저는 249개 파일로 구성된 저장소로 이동했습니다.
| task | control | turns | codenib | turns | ratio |
|---|---|---|---|---|---|
lp-phone | 8,337 | 4 | 5,525 | 2 | 66% |
| ... | |||||
lp-dberror는 어떤 위반된 데이터베이스 제약 조건이 원시 Postgres 오류 문자열 대신 읽기 쉬운 문장으로 변환되는지 묻습니다. |
컨트롤 에이전트는 총 16턴을 사용했습니다. grep 검색 14회, 파일 읽기(read) 2회, FILES: 라인은 전혀 없었습니다. 아무것도 찾지 못하는 데에 47,276개의 입력 토큰을 소모했습니다. 실패한 모든 grep 검색 기록이 이력에 남아 있었고, 이후의 모든 턴에서 다시 비용 청구되었습니다.
저는 그 작업을 그러한 결과가 나오도록 설계하지 않았습니다. 아무것도 실행하기 전에 원본 파일들로부터 여덟 개의 질문을 모두 작성했으며, 행동적(behaviorally)으로 구문화하여 절대 파일명이나 함수명을 언급하지 않도록 했습니다. 또한 각 대상 심볼이 정확히 한 곳에서 정의되어 있음을 확인했기 때문에 정답에 대한 모호함이 없습니다. 에이전트는 단순히
그다음 동일한 표를 저장소(repository)별로 나누었습니다.
| control tokens | codenib | eager | compact | |
|---|---|---|---|---|
| Leadpipe (249개 파일), 5개 작업 | 87,325 | 71.8% | 44.1% | 32.3% |
| SalesRabbit (33개 파일), 3개 작업 | 10,828 | 323.0% | 294.4% | 129.3% |
더 큰 저장소에서는 compact 방식이 grep/read 토큰의 32.3%를 사용하며, 논문에서 주장하는 범위 내에 있으며, 대조군(control)의 4/5 정답률 대비 5/5의 정확도를 보였습니다.
작은 저장소에서는 모든 방식이 손해를 보았으며, eager 방식이 294%로 가장 최악이었습니다.
마지막 행은 제 도구 교체(tool-swap) 결과의 일부 정당성을 부여합니다. 제가 4배의 페널티에 대해 내놓았던 깔끔한 설명은 875개 토큰의 스키마 비용(schema tax) 때문이었습니다. 하지만 논문의 실험군(arms)들은 스키마 비용이 전혀 발생하지 않음에도 불구하고, 그곳에서도 더 큰 폭으로 손해를 보았습니다. 따라서 지배적인 변수는 도구 세트도, 전달 정책(delivery policy)도 아닙니다.
그것은 바로 대조군 에이전트의 grep이 어차피 성공했을 것인가 하는 점입니다. 33개의 파일에서는 약 3.6k 토큰으로 3번의 턴 안에 항상 성공했습니다. 제가 테스트한 모든 메커니즘은 그 정도 규모에서는 발생하지도 않을 소용돌이(spiral)에 대비해 보험을 들고 있었던 셈입니다.
Eager와 compact는 바이트 단위로 동일한 후보군을 받으므로, 이 둘의 대비는 유지력(retention)을 격리하여 확인할 수 있는 유일하고 깨끗한 방법입니다. 이를 통해 compact는 eager의 59.9% 수준임을 알 수 있습니다. 논문에서는 Gemma 4-12B에 대해 27.9%라고 보고했습니다. 방향성은 같고, 선택된 실험군도 같으며, 크기(magnitude)는 대략 절반 수준입니다. 그들의 100개 스냅샷 대비 8개의 작업이라는 조건 하에서, 부호(sign)의 일치성 정도가 제가 주장할 수 있는 전부입니다.
9개의 MCP 도구가 모델을 탐색하게 만들다
저는 실험군이 평탄하고 예측 가능하다는 내용의 자신감 넘치는 문단을 막 작성한 참이었습니다. 하지만 다음 작업이 이를 무너뜨렸습니다. lp-sms에서 모델은 대조군보다 더 적은 턴을 사용했음에도 불구하고 여전히 2.4배 더 많은 비용이 들었습니다.
turn 1: 1,179 tokens search_semantic
turn 2: 4,980 search_bm25
turn 3: 9,137 search_regex
...
네 가지 서로 다른 도구를 사용한 네 번의 검색입니다. 넓은 도구 표면(tool surface)은 호출당 비용뿐만 아니라 모델이 따르는 정책 자체를 변화시킵니다. 9개의 도구는 9개의 그럴듯한 다음 행동을 제시하며, 아직 확신이 없는 모델은 여러 번 시도하게 됩니다. 반면 두 개의 도구는 수렴(convergence)하거나 아니면 실패(death)하게 만듭니다.
도구 세트(tool set)를 고정시킨 상태에서 진행하는 그 어떤 실험도 이를 관찰할 수 없으며, 이것이 바로 제가 이번 실행에서 가장 진정으로 새로운 것이라고 생각하는 이유입니다. 제 일반화(generalization) 능력이 깨진 것이 오히려 다행이었습니다. 이미 그 내용을 기록해 두었기 때문입니다.
시퀀스 길이(sequence length)가 아닌 임베딩 배치 크기(batch size)
33개의 파일로 구성된 저장소(repo)를 인덱싱하는 작업이 45분이 지나도록 끝나지 않았습니다. 기다리는 동안 벡터 스토어(vector store) 소스 코드를 읽어보았고, 임베딩 모델의 시퀀스 제한(sequence cap) 기본값이 8192 토큰으로 설정되어 있다는 것을 발견했으며, 이것이 분명한 문제라고 결론지었습니다.
그 후, 저는 문제를 수정하는 대신 벤치마크(benchmark)를 실시했습니다.
| threads | max_seq_length | seconds/chunk |
|---|---|---|
| 4 | 512 | 1.54 |
| ... |
시퀀스 길이는 거의 상관이 없었습니다. 청크(chunk)는 약 400 토큰 정도였고, 트랜스포머(transformers)는 배치(batch) 내에서 가장 긴 항목에 맞춰 패딩(pad)을 수행하므로, 8192라는 상한선에 도달하는 일은 전혀 없었습니다.
진짜 원인은 배치 크기(batch size)였습니다. sentence-transformers는 기본값이 32이며 가장 긴 것을 먼저 정렬하므로, 배치 0은 32개의 가장 큰 청크들을 하나로 묶습니다. 저는 워커(worker)의 작업 세트(working set)가 15.3 GB 사양의 머신에서 스래싱(thrashing)이 시작되기 전 6.9 GB까지 치솟는 것을 지켜보았습니다. 배치 크기를 4로 낮추자, 45분이 지나도 끝나지 않던 인덱싱 작업이 212초 만에 완료되었습니다.
저는 거의 시퀀스 길이 수정안을 배포할 뻔했습니다. 만약 그랬다면 속도 향상 없이 검색 품질(retrieval quality)만 저하시켰을 것이고, 저는 그것이 효과가 있다고 믿었을 것입니다. 왜냐하면 동시에 진행하던 배치 크기 변경이 실제 모든 작업을 수행하고 있었기 때문입니다.
하지만 더 큰 저장소는 그 결론을 정면으로 반박했습니다. 해당 저장소의 Python 청크들은 300라인 청크 상한선에 도달했기 때문에, 그곳에서는 제가 처음에 잘못된 저장소에 대해 추측했던 것처럼 시퀀스 제한이 실제로 지배적인 역할을 했습니다. 두 가지 조절 요소(knob) 모두 중요하며, 어떤 것이 우세할지는 청크 크기 분포에 달려 있습니다.
Windows에서 CodeNib을 설치할 때 마주하게 될 오류들
이 모든 문제들은 저에게 실제 시간을 소모하게 만들었으며, 논문에 나온 Linux 환경에서는 전혀 발생할 수 없는 것들입니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기