네 번째에 둘을 메웠다 — Qwen3.8-27B 4bit, Terminal-Bench 89개 pass@4 48, 그리고 ERR가 사라진 이유
Qwen3.8-27B MLX 4bit(17GB) 4회차. 서버만 깨끗이 재기동하고 43개를 같은 조건으로 다시 돌렸습니다. 복구 2개로 pass@4 48/89(53.9%), 3회차를 13건 유실시켰던 ERR가 1건으로 줄었습니다. 서버 탓이 아니었다는 지난 진단의 검증, 유실 후보 7개의 실제 성적, 모니터링이 속은 순간.

요약
3회차 글에서 Qwen3.8-27B 4bit(17GB)은 복구 0으로 pass@3 46에 멈췄고, 43개 중 13개는 모델이 한 턴도 못 돌고 ERR로 유실됐습니다. 그 ERR의 원인이 서버가 아니라 harbor 에이전트 셋업의 apt-get install 120초 타임아웃(컨테이너 네트워크)이라고 정정하면서, “서버 재기동은 무해하지만 ERR 재발 여부는 네트워크에 달렸다”고 적었습니다.
그래서 4회차는 서버만 깨끗이 재기동하고(watchdog·mlx-serve 종료 → KV 캐시 삭제 → 동일 인자로 재기동) 나머지 조건은 100% 그대로 두고 43개를 다시 돌렸습니다. 21시간 49분이 걸렸습니다.
결과: pass@4 = 48 / 89 (53.9%) — 복구 2. 그리고 ERR는 13건에서 1건으로 줄었습니다.
43개 내역은 PASS 1, 타임아웃 경계에서 통과한 PASS(TO) 1, FAIL 10, TO 30, ERR 1입니다.
| pass@k | 27B 4bit (17GB) | 원본 Flash Next (100GB) | Uncensored 4bit |
|---|---|---|---|
| pass@1 | 35 (39.3%) | 45 | 41 |
| pass@2 | 46 (51.7%) | 53 | — |
| pass@3 | 46 (51.7%) | — | — |
| pass@4 | 48 (53.9%) | — | — |
| pass@5 | — | 64 | 59 |
회차별로 메운 개수는 35 → +11 → +0 → +2입니다. 곡선은 예상대로 납작해지고 있지만, 3회차의 완전 정체(+0)에서 다시 둘이 나온 데는 이유가 있습니다. 3회차에 ERR로 유실됐던 후보들이 이번엔 제대로 시도됐기 때문입니다.
복구된 2개
| 태스크 | 4회차 | 소요 | 원본 pass@5 | Uncensored pass@5 |
|---|---|---|---|---|
| fix-ocaml-gc | PASS | 30.2분 | PASS | PASS |
| model-extraction-relu-logits | PASS(TO) | 15.0분 | TO(끝내 못 풂) | PASS |
fix-ocaml-gc — OCaml 런타임 GC의 힙 워킹 버그를 잡는 태스크입니다. 27B는 힙 포인터 증분 식(Whsize_hd(hd)를 더하는 한 줄)을 고친 뒤 컴파일러를 통째로 다시 빌드하고, 40개 기본 테스트 스위트를 0 실패로 통과시켰습니다. 빌드+전체 테스트가 30분을 잡아먹었지만 60분 제한 안에 들어왔습니다. 두 Flash Next 모델이 모두 이미 통과했던 태스크를, 27B가 네 번째 시도에 따라잡은 것입니다.
model-extraction-relu-logits는 다릅니다. 신경망의 ReLU 킹크(활성 경계)를 로짓으로부터 역추출하는 태스크인데, 원본 Flash Next(pass@5 64 모델)는 다섯 번 다 타임아웃으로 끝내 못 풀었습니다. 27B는 킹크 클러스터링(한 클러스터에 kink가 737개씩 몰리던 문제)을 제한시간 경계에서 맞춰 PASS(TO)를 받았습니다. Uncensored는 이 태스크를 풀었으니 “세 모델 중 27B만”은 아니지만, 큰 원본 모델이 못 푼 칸을 6배 작은 27B가 메웠다는 사실은 남습니다. 2회차의 compile-compcert·query-optimize에 이어, 태스크에 따라 순위가 뒤집히는 칸이 또 하나 늘었습니다.
ERR가 13건에서 1건으로 — 서버 탓이 아니었다는 검증
3회차 글의 핵심 정정은 “ERR 13건은 서버 장기 가동 탓이 아니라 harbor 셋업의 apt 설치 120초 타임아웃이었다”는 것이었습니다. 이것은 그때는 트레이스백으로 뒷받침한 가설이었습니다. 4회차가 그 가설의 자연스러운 검증이 됐습니다.
- 서버는 재기동했습니다. 만약 3회차 ERR가 정말 서버 장기 가동 탓이었다면, 깨끗한 서버에서는 ERR가 0에 수렴해야 합니다.
- 나머지는 다 똑같습니다. 만약 ERR가 네트워크(apt 미러) 탓이었다면, 서버 재기동과 무관하게 그날 그 시간대 네트워크 상태에 따라 재발하거나 안 하거나 합니다.
실제 결과는 apt 셋업 타임아웃형 ERR 4회차 0건입니다. 유일한 ERR 1건(db-wal-recovery)은 셋업 단계가 아니라 9.7분·41턴을 정상 실행한 뒤 죽은 다른 유형이었습니다. 즉 3회차 밤에 배치 2~7에 몰렸던 13건은 그 시간대 컨테이너 네트워크의 일시적 문제였고, 다른 밤에는 재현되지 않았습니다. 서버 상태와 무관하다는 진단과 정확히 일관합니다.
표본 하나로 인과를 못 박을 수는 없습니다. 하지만 “서버가 늙어서 ERR가 났다”는 처음의 직관이 틀렸다는 것만큼은 두 회차의 대조로 분명해졌습니다. ERR가 몰렸던 지점(셋업 apt)이 이번엔 통째로 깨끗했습니다.
유실됐던 7개는 다시 줬더니 어떻게 됐나
3회차에 ERR로 사라진 13개 중, 다른 모델이 pass@5에서 통과한 적 있어 “복구 가능성 있다”고 꼽았던 후보가 7개였습니다. 이번엔 전부 정상적으로 시도됐습니다. 결과입니다.
| 태스크 | 4회차 | 원본·Uncensored pass@5 |
|---|---|---|
| fix-ocaml-gc | PASS | 둘 다 PASS |
| configure-git-webserver | FAIL (1.4분) | 둘 다 PASS |
| dna-insert | FAIL (6.2분) | 둘 다 PASS |
| crack-7z-hash | TO (30분) | 둘 다 PASS |
| mailman | TO (30분) | 둘 다 PASS |
| polyglot-rust-c | TO (15분) | Uncensored만 PASS |
| db-wal-recovery | 또 ERR | 둘 다 PASS |
7개 중 통과는 1개뿐이었습니다. 나머지는 제대로 시도해도 원래 27B가 못 푸는 것들이었습니다. configure-git-webserver·dna-insert는 초반 진단은 정확했는데(플라스미드 215번 위치 39bp 삽입까지 짚었습니다) 최종 산출물이 채점을 통과 못 했고, mailman은 import 오류는 고쳤지만 LMTP 메일 배달 설정에서 막혔습니다. ERR는 “못 푼 것”이 아니라 “시도조차 못 한 것”이었지만, 시도가 복원돼도 대부분은 결국 실패한다는 것이 이번의 냉정한 결론입니다. 3회차의 +0이 ERR 유실 탓만은 아니었던 셈입니다.
모니터링이 속은 순간 — 트래젝터리는 진실이 아니다
마지막 태스크 winning-avg-corewars(Core War 봇을 pmars로 대전시켜 평균 승률을 올리는 문제)를 지켜보다 하마터면 오판할 뻔했습니다. 점검 스크립트가 이렇게 찍었습니다.
winning-avg-corewars: 41.4분 경과 / 제한 60분 · 6턴 · ctx 8K · 마지막 턴 48.5분 전
⚠ 48분간 새 턴 없음 — 긴 명령 실행 중이거나 서버 정체 의심
트래젝터리 스텝만 보면 6턴에서 멈췄고 48분째 새 턴이 없으니 영락없는 wedge입니다. 그런데 같은 출력의 서버 지표는 정반대였습니다. 요청 누적이 3167 → 3294 → 3403 → 3499로 계속 늘고, 처리하는 프롬프트 크기가 55K → 73K → 88K → 103K로 커지고 있었습니다. 에이전트는 한 턴 안에서 pmars 대전을 수백 판 반복 실행하며 그 결과를 컨텍스트에 계속 쌓는 중이었습니다. 트래젝터리의 “스텝”은 모델이 한 번 멈춰 다음 명령을 낼 때만 올라가는데, 이 태스크는 한 스텝 안에서 배틀 루프를 길게 돌려 스텝 카운터가 얼어붙은 것처럼 보였을 뿐입니다.
교훈은 분명합니다. 에이전트가 살아 있는지는 트래젝터리 턴 수가 아니라 서버 요청·프롬프트 크기의 증가로 판별해야 합니다. 130K wedge(path-tracing-reverse 213턴, video-processing 229턴처럼 컨텍스트 상한을 두드리며 overflow를 쏟아내는 것)와, 긴 명령을 정상 실행 중인 상태는 겉으로 똑같이 “턴이 안 올라감”으로 보이지만, 서버 로그를 함께 보면 즉시 갈립니다. 이 판별을 점검 로직에 넣어둔 것이 이번에 헛경보를 막았습니다.
다섯 번째로
pass@1 35 → pass@2 46 → pass@3 46 → pass@4 48. 남은 41개(FAIL 10·TO 30·ERR 1) 중 대부분은 여전히 heredoc 함정, 130K wedge, 최적화 함정, 자기검증 오류처럼 횟수가 아니라 하네스나 프롬프트를 손대야 풀릴 실패들입니다. 그래도 원본(64)·Uncensored(59)까지 곡선을 그어두는 편이 비교표를 정직하게 만들기에, 이 글을 쓰는 지금 5회차를 남은 41개로 돌리고 있습니다.
한 가지는 이번에 다시 확인됐습니다. 작은 모델은 큰 모델을 전 구간에서 밑도는 것이 아닙니다. 원본이 다섯 번 다 놓친 model-extraction-relu-logits를 27B가 메웠듯, 합산 점수의 격차와 “이 특정 문제는 누가 푸느냐”는 별개의 질문입니다.