같은 코드를 같은 프롬프트로 두 번 리뷰시켰는데 심각도가 critical과 major로 갈린 경험에서 출발한다. AI 코드 리뷰를 merge gate에 걸면 어제 통과시킨 MR을 오늘 차단하고, 개발자가 코드를 고치는 대신 파이프라인을 다시 돌리기 시작하면 게이트는 기준이 아니라 운을 시험하는 절차가 된다. LLM 비결정성의 원인을 짚고 실무에서 다루는 네 방향을 제시한 뒤, 그중 둘을 Claude Haiku 총 55회 호출로 검증했다.
핵심 포인트- temperature를 0으로 내려도 흔들림은 남는다. 확률적 선택은 여러 원인 중 하나일 뿐이다.
- 부동소수점 연산은 덧셈 순서에 따라 결과가 미세하게 달라지고, 추론 서버가 여러 요청을 배칭하는 묶음 크기에 따라 연산 순서가 바뀐다. 후보 단어 점수 차가 근소하면 1위와 2위가 뒤바뀐다.
- 흔들리면 안 되는 단계는 LLM이 아니라 코드가 맡는다. 결정적인 작업을 LLM에 맡기면 비용은 더 들고 결과는 덜 안정적이다.
- 점수 구간별 기준표를 주자 표준편차가 8.69에서 3.27로 떨어졌지만 흔들림이 사라지지는 않았다.
- 단일 호출에서 최다 등급이 72%에 그쳤지만 5표 다수결로 묶자 세 그룹 모두 같은 판정으로 수렴했다.
상세 정리- 비결정성의 원인 1: LLM은 다음 토큰을 확률에 따라 뽑는다. temperature가 0보다 크면 확률이 조금 낮은 후보도 뽑혀 매 자리에서 선택의 폭이 넓어지고, 앞에서 뽑힌 단어 하나가 달라지면 이어지는 문장 전체가 달라진다.
- 원인 2: 인프라. Thinking Machines Lab이 2025년 공개한 분석은 배치 크기에 영향받지 않는 연산 방식을 직접 구현하면 해결할 수 있다는 결론이지만, 이는 추론 서버를 직접 운영할 때의 이야기이고 상용 API 사용자는 제어하기 어렵다.
- 원인 3: 입력 민감성. 프롬프트의 공백 하나, 예시 순서 하나만 바뀌어도 출력이 달라진다. 실무에서는 프롬프트에 사용자 입력이나 날짜가 매번 다르게 채워져 완전히 똑같은 입력을 두 번 넣는 쪽이 오히려 드물다.
- 자동화에서 깨지는 지점 셋: 게이트(같은 코드가 어떤 호출에서는 통과하고 어떤 호출에서는 차단), 평가(점수가 올라도 개선 덕분인지 호출 운인지 가리기 어려움), 형식 처리(출력을 JSON으로 파싱하는 뒷단 코드가 깨짐).
- 방향 1 코드 위임: 파이프라인 설계 시 "이 단계가 흔들리면 무엇이 무너지나"를 먼저 묻는다. 코드베이스 호출 관계 그래프는 정적 분석 도구가 결정적으로 더 저렴하게 해내고, LLM에는 그 위에서 의미를 해석하는 판단만 맡긴다.
- 실제로 지식그래프 생성을 LLM으로 대체할 수 있는지 실험했더니 분류 체계는 깔끔했지만 빠뜨리지 않고 찾아내는 재현율이 룰 베이스에 크게 밀렸다.
- 방향 2 폭 좁히기: 자유 서술 대신 정해진 선택지를 주고, 점수를 매기게 할 거면 구간별 기준표를 함께 주고, 형식은 프롬프트로 부탁하는 대신 출력 스키마를 강제하는 기능을 쓴다. 선택지를 넷으로 제한하면 어긋날 여지도 그 넷으로 한정된다.
- 방향 3 다수결: 경계에 놓인 판정은 호출마다 두 값 사이를 오간다. 같은 코드를 같은 프롬프트로 여러 번 물어 최다 판정을 최종값으로 삼되, 호출 비용이 투표 수만큼 늘어나므로 뒤집히면 안 되는 판정에만 골라 쓴다.
- 방향 4 하네스: 입력을 일정한 형태로 다듬어 넣고, 출력이 약속된 형식에 맞는지 코드로 검증하고, 어긋나면 자동 재시도하고, 반복 호출도 코드가 지휘한다. 모델이 지시를 지켜주길 기대하는 대신 지키지 않았을 때의 처리를 코드가 보장하는 쪽으로 무게를 옮긴다.
- 오해 정정: 프롬프트 캐싱은 결정성과 관련이 없다. 캐시에 저장되는 것은 입력을 처리한 중간 결과이지 답변 자체가 아니라, 캐시가 적중해도 답변은 매번 새로 만들어진다.
- 실험 설계: 재는 것은 판정의 정확성이 아니라 일관성이다. 조건 A는 기준 없이 0~100 점수 10회, B는 같은 과제에 구간별 기준표 10회, C는 형식 제한 없는 자유 서술 10회, D는 네 등급 라벨 판정 25회, D'는 D의 호출을 5회씩 묶은 다수결 3그룹이다.
- 대상 코드는 할인율 계산 함수로, 정수 반올림에 의한 금액 손실이라는 확실한 버그와 rate 해석의 모호함이라는 경계선 이슈를 함께 심었다. 판정이 흔들리는 지점을 관찰하기 위해서다.
- 재현 설정: Claude Haiku 4.5를 Claude Code CLI 비대화형 호출로 매번 독립된 새 세션에서 실행하고, system prompt는 지정하지 않았으며, structured output은 형식 준수 여부 자체가 관찰 대상이라 쓰지 않고 프롬프트 지시로만 요청했다. temperature 등 생성 파라미터는 기본값 그대로 뒀다.
- 결과 1 점수: 기준이 없으면 같은 코멘트가 45점에서 68점까지 23점 벌어졌고 서로 다른 값이 7가지 나왔다. 기준표를 주자 범위가 10점, 서로 다른 값 4가지로 줄고 10회 중 6회가 65점으로 모였다.
- 이 점수에 몇 점 이상이면 자동 알림 같은 규칙을 걸었다면 같은 코멘트인데도 호출 운에 따라 알림이 울리고 안 울렸을 것이라고 짚는다.
- 결과 2 자유 서술: 반올림 정밀도, 음수·범위 미검증, 할인율 해석의 모호함, 입력 검증 부재라는 핵심 이슈 넷은 10회 모두 등장했다. 반면 파이썬 round()의 banker's rounding처럼 깊이 들어가야 보이는 지적은 10회 중 2회만 나왔고 답변 길이도 240자에서 460자로 2배 가까이 차이 났다.
- 즉 모델은 무엇이 문제인지는 꽤 일관되게 잡아내지만 어디까지 깊게 파고들지와 어떻게 표현할지는 매번 달라진다.
- 결과 3 라벨 판정: 25회에서 critical 18회, major 7회로 갈렸다. 심어둔 버그가 금액은 손실되지만 프로그램이 멈추지는 않는 경계선에 있어서다. 이 중 15회를 5개씩 세 그룹으로 나눠 다수결을 내자 세 그룹 모두 critical로 모였다.
- 결과 4 형식 준수: "다른 설명 없이 단어 하나만 출력해"라는 지시를 완전히 지킨 호출은 10회 중 3회뿐이고 나머지 7회는 판정 뒤에 이유를 덧붙였다. 답변 내용만이 아니라 지시를 지키는지 여부까지 비결정적이라는 뜻이다.
- 결론의 판단 기준: 흔들려서 무너지는 것이 구조라면(그래프 생성·형식 변환·집계) 코드에 맡기고, 판정이라면(심각도·통과 차단) 선택지와 기준표로 폭을 좁히고 형식은 하네스에서 강제하고 경계선에는 다수결을 붙인다. 무너지는 게 없다면(요약·초안·아이디어) 흔들림은 결함이 아니라 다양성이다.
- 한계 명시: 반복 횟수가 적고 과제도 하나이며 네 방향 중 둘만 검증했다. 모델과 과제가 달라지면 수치도 달라질 것이라고 밝힌다.
왜 읽나LLM 판정을 merge gate나 자동 평가 파이프라인에 넣으려는 팀이, 비결정성을 어디까지 허용하고 어디부터 코드로 막을지 판단할 기준과 소규모 실측 수치를 얻을 수 있다.