본문으로 건너뛰기

© 2026 Molayo

Zenn헤드라인2026. 06. 10. 08:24

AI가 같은 질문에 매번 다른 답을 하는 이유는? — 비결정성(Non-determinism)과 현명하게 대처하는 실전 가이드

요약

생성형 AI의 비결정성(Non-determinism) 원인과 이에 대응하는 실전 가이드를 다룹니다. temperature 설정 외에도 GPU 계산 순서와 서버 메커니즘이 결과에 영향을 미침을 설명하며, 안정적인 AI 서비스 구축을 위한 전략을 제시합니다.

핵심 포인트

  • AI의 비결정성은 버그가 아닌 확률 기반 생성의 본질적 특성임
  • temperature=0 설정만으로는 완벽한 결정성을 보장할 수 없음
  • GPU 연산 순서 및 서버 처리 방식이 결과 변동의 원인이 됨
  • 변동성을 제어하기 위해 멱등성(Idempotency)과 테스트 전략이 중요함

TL;DR

  • AI는 같은 질문이라도 매번 조금씩 다른 답을 할 때가 있다. 이것은 버그가 아니라 생성형 AI의 본질적인 성질이다. 전문 용어로 비결정성 (Non-determinism)이라고 한다.
  • temperature=0으로 설정해도 완전히 일치하지는 않는다. 원인은 주사위(랜덤성)뿐만 아니라, GPU의 계산 순서나 서버가 수많은 요청을 모아서 처리하는 메커니즘에도 있다.
  • 따라서 "매번 완전히 똑같은 문자열이 돌아온다"는 전제로 코드나 테스트를 작성하면 조용히 사고가 발생한다.
  • 중요한 것은 변동성을 제로로 만드는 것이 아니라, 일치시켜야 할 곳과 분산시켜도 되는 곳을 스스로 구분하는 것이다.
  • 이 기사에서는 용어의 의미부터 샘플링 설정, 비결정적인 AI의 테스트 방법, 부작용을 방지하는 멱등성 (Idempotency), 그리고 프롬프트 3가지 예시까지, 첫걸음을 친절하게 안내한다.

"아까와 똑같은 질문을 했는데, 답이 다르다" 문제

AI를 사용하기 시작하면서 이런 경험이 있지 않으신가요?

어제와 완전히 똑같은 질문을 던졌는데, 돌아오는 문장이 조금 다르다. 코드를 짜게 시켰더니 1회차와 2회차의 변수명이 바뀌어 있다. 더 곤란한 것은 자신의 앱에 AI를 통합했을 때다. 테스트가 통과했다가 실패했다가 하며 안정적이지 않다. 사용자로부터 "버그가 발생했다"는 보고가 왔는데, 정작 내 환경에서는 재현되지 않는다.

솔직히 처음에는 이것이 AI 측의 버그라고 생각하기 쉽다. 저도 처음에는 그렇게 생각했다. "같은 입력인데 당연히 같은 출력이 나와야 하는 것 아닌가"라고 말이다.

하지만 이것은 버그가 아니다. 생성형 AI에게는 오히려 정상적인 움직임이다. 오히려 "매번 딱 맞는 답이 돌아온다"고 믿고 있는 것이, 앞으로 AI를 활용해 나가는 과정에서는 위험한 전제다.

왜 그럴까? 이 부분을 이해해 두면 AI의 테스트와 디버깅이 갑자기 쉬워진다. 차근차근 살펴보자.

애초에 "결정적" "비결정적"이란 무슨 뜻인가?

먼저 용어부터 짚고 넘어가자. 이 부분을 건너뛰면 모든 내용이 모호해지므로 친절하게 설명하겠다.

결정적 (deterministic)이라는 것은, 같은 입력을 넣으면 반드시 같은 결과가 나오는 것을 말한다. 계산기가 그렇다. 1 + 1은 몇 번을 눌러도, 누가 눌러도, 언제 눌러도 2다. 이것이 결정적이다.

비결정적 (non-deterministic)은 그 반대다. 같은 행동을 해도 결과가 달라질 수 있는 것이다. 주사위가 이해하기 쉽다. 똑같이 던져도 나오는 눈은 매번 다르다.

그리고 생성형 AI는 어느 쪽인가 하면, 주사위 쪽에 가깝다.

AI는 문장을 만들 때 "다음에 올 것 같은 단어"를 한꺼번에 결정하는 것이 아니다. 사실은 다음 단어의 후보 각각에 "그럴듯함"의 확률을 부여하고, 그 확률에 따라 한 단어씩 선택한다. 예를 들어 "오늘의 날씨는" 다음에 올 단어로 "맑음 (45%)", "흐림 (30%)", "비 (20%)", "최고 (5%)"와 같이 가중치가 붙어 있는 이미지다.

이 "주사위 효과를 얼마나 적용할 것인가"를 결정하는 것이 temperature (온도)라는 설정이다.

  • temperature가 높으면 (예: 1.0~), 낮은 확률의 단어도 선택되기 쉬워진다. 답이 다양하고 창의적이지만, 변동성이 크다.
  • temperature가 낮으면 (예: 0.2), 확률이 높은 단어 쪽으로 쏠린다. 안정적이지만, 비슷한 답이 나오기 쉽다.
  • temperature=0으로 설정하면, 매번 가장 확률이 높은 단어만을 선택한다. 이를 탐욕법 (greedy)이라고 한다.

유사한 설정으로 top_p (탑 피)라는 것도 있다. 이것은 "상위 몇 %의 확률 덩어리 안에서 선택할 것인가"를 조절하는 수도꼭지다. top_p=0.1이라면, 확률이 높은 순서대로 더해가며 합계가 10%가 될 때까지의 후보만을 대상으로 한다는 느낌이다.

여기까지 읽었다면 많은 사람이 이렇게 생각할 것이다. "그럼 temperature=0으로 설정하면 가장 확률이 높은 단어만 선택할 테니까, 매번 같은 답이 나오겠네?"라고.

하지만 그렇지 않다. 여기서부터가 본론이다.

temperature=0인데도 왜 완전히 일치하지 않는가

temperature=0으로 설정해도 출력이 흔들린다. 이것을 모르면 정말 깊은 수렁에 빠질 수 있는 포인트이므로, 원인을 세 가지 계층으로 나누어 설명하겠다.

제1계층: 샘플링의 랜덤성

이것은 앞서 말한 주사위 이야기다. temperaturetop_p가 작동하고 있으면 확률에 따라 단어가 선택되므로 당연히 변동이 생긴다.

이 계층은 비교적 컨트롤이 가능하다. temperature=0...

에 가깝게 설정하고, 추가로 seed(시드, 난수의 씨앗)라는 값을 고정하면 랜덤성이 일치하기 쉬워진다. OpenAI의 API에는 seed 파라미터가 있어서, 동일한 seed와 동일한 설정으로 요청을 보내면 출력이 일치할 확률이 높아진다.

하지만 여기서 성급하게 판단해서는 안 된다. 이렇게 해도 여전히 "완전히 동일하게" 되지는 않는다. 그 이유는 제2층과 제3층에 있기 때문이다.

제2층: 부동 소수점의 "덧셈 순서" 문제

컴퓨터는 소수를 "부동 소수점 수 (Floating-point number)"라는 근사치로 다룬다. 그리고 근사치로 계산하기 때문에, 덧셈의 순서를 바꾸면 결과가 아주 미세하게 어긋날 수 있다.

수학에서는 (a + b) + ca + (b + c)가 같지만, 부동 소수점에서는 반드시 일치하지 않는다. 이를 비결합성 (Non-associativity)이라고 한다.

AI의 계산은 GPU라는 병렬 계산에 특화된 칩이 거대한 곱셈과 덧셈을 엄청난 수로 수행하며 실현된다. 이때 "어느 스레드가 어떤 순서로 더할 것인가"가 상황에 따라 달라지면, 마지막 단계에서 숫자가 미묘하게 어긋나게 된다. 그 어긋남이 확률이 아슬아슬한 상황에서 "선택될 단어"를 뒤바꿔 놓을 수 있는 것이다.

제3층: 서버가 "묶어서 처리하는" 문제

이 부분이 2025년에 크게 정리된, 가장 흥미로운 지점이다.

클라우드 AI 서버는 전 세계에서 오는 요청을 효율적으로 처리하기 위해, 여러 사람의 요청을 동적으로 묶어서 (Dynamic batching) 한꺼번에 계산한다. 문제는 이 묶는 방식이 그 순간의 혼잡도에 따라 달라진다는 것이다.

그리고 많은 GPU 커널 (계산 부품)은 "단일 건으로 계산했을 때"와 "다른 사람의 요청과 함께 계산했을 때" 내부의 덧셈 순서가 바뀌어 버린다. 전문적으로는 배치 불변성 (Batch invariance)이 없다고 말한다.

즉, 당신이 보낸 프롬프트, 모델, seed, temperature가 모두 같더라도, 그 순간 서버가 혼잡한지 여부에 따라 답이 달라질 수 있다는 뜻이다. 당신의 주사위뿐만 아니라, 서버의 혼잡도라는 당신에게 보이지 않는 요인에 의해서도 결과가 움직인다.

이를 입증한 것이 Thinking Machines Lab의 2025년 보고서 "Defeating Nondeterminism in LLM Inference"이다. 특정 모델에 temperature=0과 동일한 프롬프트를 1000번 보냈더니, 80가지의 서로 다른 완료 결과가 나왔다고 보고했다. 그리고 덧셈 순서를 고정한 "배치 불변 커널"로 교체했더니, 1000번 중 1000번이 정확히 일치했다고 한다.

제2층과 제3층을 확실히 없앨 수 있는 것은 기본적으로 모델을 직접 구동하는 셀프 호스팅 (Self-hosting) 중심의 세계 이야기다. 클라우드 API를 사용하는 대부분의 사람에게는 "완전한 재현은 기대할 수 없다"를 전제로, 상위 레이어에서 설계하는 것이 현실적인 해답이 된다.

변동성은 적이 아니다: 일치시키고 싶을 때와 흩뜨리고 싶을 때

여기까지 읽으면 "우와, 비결정성이 무섭다, 전부 0으로 만들고 싶다"라고 생각할지도 모른다. 하지만 잠시만 기다려 달라.

비결정성이 항상 적이기만 한 것은 아니다. 오히려 아군이 되는 상황도 있다.

아이디어를 10개 내달라고 할 때, 캐치프레이즈 안을 다양하게 펼쳐달라고 할 때, 문장의 변주가 필요할 때. 이럴 때 매번 완전히 똑같은 답만 돌아온다면 오히려 곤란할 것이다. 변동성(Variation) 즉 다양성은 창의성 그 자체이기도 하기 때문이다.

반대로, 변동성이 생기면 곤란한 상황도 명확하다. 문장을 "OK / NG"로 분류할 때. 청구서에서 금액을 추출할 때. 문의 사항을 어느 부서로 배정할지 결정할 때. 여기서 매번 다른 답이 돌아온다면 업무가 성립되지 않는다.

따라서 본질은 "변동성을 제로로 만드는 것"이 아니라, "어디를 조이고 어디를 늦출 것인가"를 스스로 설계하는 것이다.

용도예시변동성을 원하는가?설정 방향
발산·창조아이디어 도출, 카피 안, 브레인스토밍환영temperature 높게
...

이 표를 머릿속에 넣어두는 것만으로도, "일단 전부 temperature=0"과 같은 무책임한 설계에서 졸업할 수 있다.

아래는 용도에 따라 설정을 전환하는 이미지의 코드다. Python (OpenAI SDK 가정)으로 작성되었지만, 사고방식은 어떤 AI에서도 동일하다.

from openai import OpenAI
client = OpenAI()
def ask(prompt: str, mode: str = "stable") -> str:
...

비결정적인 AI를 그럼에도 테스트하는 기술

자, 실무에서 가장 곤란한 지점이 바로 여기입니다. "매번 다른 답이 돌아오는 것을 어떻게 테스트하느냐?"라는 문제입니다.

결론부터 말씀드리면, 문자열의 완전 일치(exact match)로 테스트하는 것을 그만두는 것, 이것이 전부입니다.

# ❌ 이것은 비결정적인 AI에는 통하지 않는다 (금방 실패함)
assert ai_answer == "이 서비스는 긍정적인 평가입니다."

같은 의미라도 표현 방식이 매번 다르기 때문에, 이런 완전 일치 테스트는 "flaky (불안정하여 통과했다가 실패했다가 하는 상태)"하게 됩니다. Flaky한 테스트는 양치기 소년과 같아서, 점점 아무도 믿지 않게 됩니다. 이것이 가장 무서운 점입니다.

대신에 출력의 "형태"와 "의미"를 검증합니다. 방법은 크게 세 가지입니다.

  • 불변 조건 (invariant)으로 체크합니다. 예를 들어 "답은 positive / negative / neutral 중 하나여야 한다", "JSON 형식이 깨지지 않아야 한다", "금지어가 포함되지 않아야 한다" 등입니다. 내용의 문장은 묻지 않고, 충족해야 할 규칙만을 확인합니다.
  • 의미의 동일성을 AI가 판정하게 합니다 (LLM-as-judge). "이 두 개는 실질적으로 같은 의미인가?"를 다른 AI에게 Yes/No로 묻습니다. 흔들리는 자연문 비교에 효과적입니다.
  • 자기 일관성 (self-consistency)으로 다수결을 합니다. 같은 질문을 N번 던져서, 가장 많이 나온 답을 채택합니다. 한 번의 흔들림에 휘둘리지 않게 됩니다.

아래는 1(불변 조건)과 3(다수결)을 조합한 최소한의 예시입니다.

from collections import Counter
ALLOWED = {"positive", "negative", "neutral"}
def classify_once(text: str) -> str:
...

포인트는 테스트의 목적을 "매번 같은 문자열인가"에서 "충족해야 할 성질을 만족하는가"로 옮기는 것입니다. 이것만으로도 비결정적인 AI를 제대로 CI (지속적 통합)에 올릴 수 있게 됩니다.

더 엄격하게 하고 싶은 경우

금액이나 건수와 같은 수치는 완전 일치가 아니라 허용 오차 (tolerance)로 비교합니다. 요약과 같은 장문은 포함해야 할 키워드 집합 체크나, LLM-as-judge를 이용한 루브릭 (rubric) 채점을 수행합니다. 판정이 애매한 케이스는 AI에게 "신뢰도 (confidence score)"를 출력하게 하여, 낮은 것만 인간 리뷰로 넘기는 식의 단계적 설계가 효과적입니다.

부작용이 있는 조작은 "멱등성"으로 보호한다

비결정성이 정말로 무서워지는 것은 AI가 단순히 문장을 반환하는 것을 넘어, 현실에 작용할 때입니다. 메일을 보내거나, 결제하거나, 레코드를 삭제하거나, 배포하는 경우 말이죠.

AI 에이전트는 실패하면 자동으로 재시도 (retry)를 하기도 합니다. 게다가 출력이 비결정적이기 때문에, "1회차에는 스킵했는데 2회차에는 실행했다"와 같은 일이 발생할 수 있습니다. 이것이 부작용과 결합되면 메일 중복 발송, 중복 결제와 같은 실질적인 피해로 이어집니다.

여기서 유효한 것이 멱등성 (idempotency)이라는 개념입니다. 어려워 보이는 단어지만 의미는 단순합니다. "같은 조작을 몇 번 반복해도 결과가 1번 수행한 것과 같아야 한다"는 성질입니다.

엘리베이터 버튼을 5번 연타해도 1번만 호출되는 것과 같습니다. 그것이 멱등성입니다. 결제 서비스인 Stripe 등에서도 중복 결제를 방지하기 위해 이 메커니즘이 표준으로 사용되고 있습니다.

구현의 기본은 조작마다 "이것은 동일한 조작이다"라고 알 수 있는 키 (idempotency key)를 부여하고, 이미 실행되었다면 스킵하는 것입니다. 아래는 TypeScript의 최소 예시입니다.

// 동일한 조작이 두 번 와도, 부작용은 한 번만 발생하도록 방어
const executed = new Map<string, unknown>(); // 실제 운영 시에는 DB/Redis에 저장
async function runOnce<T>(
...

키를 만드는 방법이 핵심입니다. AI의 출력 그 자체를 키로 사용하면, 출력이 흔들리는 순간 "다른 조작"으로 오인되어 중복 실행을 허용하게 됩니다. 따라서 키는 주문 ID나 조작 유형와 같이 "흔들리지 않는 사실"로부터 구성해야 합니다.

AI에게 부탁할 때 사용하는 프롬프트 3종

설정이나 코드뿐만 아니라, 부탁하는 방식 (프롬프트)으로도 흔들림을 줄일 수 있습니다. 바로 복사해서 사용할 수 있도록 3개를 그대로 두겠습니다. 회사 기밀 정보나 고객의 개인 정보는 넣지 말고, 더미 데이터로 대체해서 사용해 주세요.

프롬프트 1: 출력을 안정시키는 리라이트 (Rewrite)

당신은 출력 포맷의 설계자입니다.
다음 지시사항을 AI의 답변이 흔들리지 않도록 다시 작성해 주세요.
조건:
...

프롬프트 2: 흔들림 요인 트리아지 (Triage) (최종 판단은 인간)

다음 태스크에 대해 AI의 출력이 흔들리면 곤란한지 진단해 주세요.
평가 축: ① 흔들렸을 때의 실질적 피해 규모 ② 취소 용이성 ③ 검증 용이성
각 축을 0~3으로 채점하고, 합계로부터 "엄격하게 관리할 것 / 완화해도 될 것"을 제안해 주세요.
...

프롬프트 3: 자기 일관성 (Self-consistency) 체크 설계 리뷰

다음 처리는 동일한 입력이라도 출력이 흔들릴 가능성이 있습니다.
self-consistency (여러 번 실행하여 다수결)로 안정시키고 싶습니다.
다음 내용을 설계해 주세요:
...

이 세 가지의 공통점은 AI에게 최종 판단을 맡기지 않고, 인간이 결정하기 위한 재료를 내놓게 한다는 점입니다. 비결정적인 (Non-deterministic) 것을 다룰 때일수록 결정권은 인간에게 남겨두는 것이 안전합니다.

인간과 AI의 역할 분담, 그리고 빠지기 쉬운 함정

비결정성과 함께할 때의 인간과 AI의 경계선을 표로 정리해 둡니다.

영역인간이 결정함 (What / Why)AI에게 맡김 (How)
설정어디를 엄격하게 관리하고 어디를 완화할지후보 설정 제안
...

마지막으로, 자주 빠지는 함정들을 적어둡니다. 스스로를 경계하는 의미도 담아서 말이죠.

  • 완전 일치로 테스트하다가 flaky (불안정한) 지옥에 빠짐. → 불변 조건 (Invariant) + 다수결로 전환.
  • temperature=0이면 무조건 같을 것이라고 믿음. → 클라우드 환경에서는 "대체로 같음" 수준에 그침.
  • AI의 출력을 멱등 키 (Idempotency Key)로 사용함. → 흔들리는 순간 이중 실행 발생. 키는 흔들리지 않는 사실로부터 만들어야 함.
  • 되돌릴 수 없는 조작을 AI의 판단에 통째로 맡김. → 반드시 인간의 최종 승인을 거치도록 함.
  • 흔들림을 전부 제로로 만들려고 함. → 창의성이 필요한 장면의 흔들림까지 죽여버림.
  • 재현 조건을 기록하지 않음. → "재현되지 않는" 버그 조사가 지옥이 됨. seedsystem_fingerprint를 남길 것.

요약: 확실성이 아니라, 재현성을 "설계"하기

여기까지 고생 많으셨습니다. 마지막으로 핵심만 요약해 드릴게요.

AI가 매번 다른 답을 내놓는 것은 버그가 아니라 성질입니다. temperature=0으로 설정하더라도, 부동 소수점의 덧셈 순서나 서버가 일괄 처리하는 메커니즘 때문에 완전히 일치하지 않을 수 있습니다. 그러니 "항상 같음"을 전제로 삼지 마세요.

하지만 이것은 비관적인 이야기가 아닙니다. 오히려 AI 시대에 강한 사람일수록 확실성을 얻으려고 애쓰는 것이 아니라, 재현성을 설계하는 데 머리를 쓰고 있다고 생각합니다. 통제할 수 없는 부분(서버 혼잡)에 짜증을 내기보다, 통제할 수 있는 부분(설정, 검증, 멱등성, 기록)을 제대로 손보는 것이죠. 결국 이것이 가장 편하고 강력한 방법입니다.

그리고 미미하지만 효과적인 것이 바로 재현 조건을 남겨두는 것입니다. seedsystem_fingerprint, 몇 번 시도했는지, 어떻게 판정했는지와 같은 기록은 미래의 자신에게 남기는 메시지입니다. 반년 뒤의 내가 "그 버그, 왜 재현이 안 되지?"라며 머리를 싸매는 것을, 오늘의 내가 30분 만에 구할 수 있습니다. 로그는 내일의 내가 "이거 남겨줘서 고마워요"라고 말하게 만드는 것입니다.

흔들림을 맞출 것인가, 흩뿌릴 것인가. 이것을 설계할 수 있는 능력은 AI로 누구나 무엇이든 만들 수 있는 시대이기에, 제대로 차별화되는 자산이 될 것이라 느낍니다.

첫걸음으로 이것만이라도 해보세요.

  • 지금 돌리고 있는 AI 처리 중 하나를 고른다.
  • 그것이 "맞추고 싶은" 처리인지 "흩뿌려도 되는" 처리인지 한마디로 말해본다.
  • 맞추고 싶다면, 완전 일치 테스트를 불변 조건 체크로 바꾼다.
  • 부작용(Side effect)이 있다면, 멱등 키를 "흔들리지 않는 사실"로부터 하나 만든다.

오늘은 여기까지면 충분합니다. 내일의 당신이 조금은 편해질 것입니다.

참고 링크

참고 링크

토론

AI 자동 생성 콘텐츠

본 콘텐츠는 Zenn AI의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.

원문 바로가기
0

댓글

0