LLM의 동작 원리와 한계, 그리고 검증하는 법

LLM이 어떻게 답을 만들고 어디서 틀리는지 정리했어요. 확인 문제를 풀면서 놓친 부분도 같이 적었어요. 이 글은 동작 원리와 한계, 그리고 "그래서 검증은 어떻게 하나"까지 다루고, 모델 내부 구조(트랜스포머, 어텐션)는 다루지 않아요.

1. LLM은 다음 토큰을 예측해요

LLM은 입력된 글 뒤에 올 다음 토큰의 확률 분포를 계산하고, 그중 하나를 뽑는 일을 반복해요. 토큰은 단어보다 작은 조각이에요. 많은 토크나이저에서 한국어는 같은 내용에 영어보다 토큰이 더 들어서, 비용과 길이 제한에도 영향을 주고요.

학습은 두 단계로 나뉘어요. 먼저 방대한 텍스트로 "다음 토큰 맞히기"를 학습하고(사전학습), 그 위에 지시를 따르고 사람이 선호하는 답을 내도록 추가로 학습시켜요(지시 튜닝, RLHF 등).

모델이 학습한 건 그럴듯한 다음 말을 고르는 일이에요. 그 말이 사실인지는 학습 목표에 직접 들어 있지 않아요. 아래에 나오는 한계는 대부분 이 점에서 나와요.

2. temperature — 같은 질문에 다른 답이 나오는 이유

"temperature를 0으로 놓고 한 번 돌렸더니 맞는 답이 나왔으니 검증 끝"이라는 말에서 틀린 점을 찾는 문제였어요. 저는 temperature를 0으로 둔 것 자체와, 한 번만 돌리고 검증됐다고 한 것을 골랐는데 — 앞의 것은 틀린 답이었어요.

temperature는 확률이 낮은 토큰을 얼마나 자주 뽑을지 정하는 값이에요. 높이면 답이 다양해지고, 낮추면 확률이 가장 높은 토큰 위주로 뽑혀서 일관되고 보수적인 답이 나와요. 결과 차이를 줄이려고 0으로 두는 건 흔한 설정이고, 그 자체로는 문제가 없어요.

문제는 0이면 매번 같은 답이 나온다고 믿는 것이에요. Anthropic 문서에도 temperature가 0이어도 결과가 완전히 결정적이지 않고, 같은 입력이 호출마다 다른 출력을 낼 수 있다고 적혀 있어요(Anthropic Glossary).

더 중요한 오류는 두 번째 거예요. 같은 입력을 여러 번 돌리면 결과가 얼마나 달라지는지를 볼 수 있을 뿐, 프롬프트가 맞는지는 알 수 없어요. 입력 하나로 맞았다면 그 하나에서만 맞은 거예요. 검증하려면 평범한 입력, 경계값, 이상한 입력처럼 서로 다른 케이스가 필요해요(5절).

3. 할루시네이션 — 자세한 답이 정확한 답은 아니에요

설비 통신 프로토콜(SECS/GEM)의 S6F11 메시지 구조를 물었더니 필드 이름과 설명이 아주 구체적으로 나온 상황이었어요. 코드에 쓰기 전에 무엇을 하겠냐는 질문에 저는 실제 데이터로 확인하겠다고 했고, 이유로 "길게 알려줬기 때문에 기억이 손상될 수도 있어서"를 댔어요. 할 일은 맞았는데 이유가 틀렸어요.

할루시네이션은 그럴듯하지만 사실이 아닌 내용을 자신 있게 만들어내는 현상이에요. 답의 길이와는 상관이 없고, 주로 아래 경우에 나와요.

  • 학습 데이터에 드물거나 없는 정보 — 특정 업계 프로토콜, 사내 정보, 정확한 수치, 논문 인용, URL, API 이름
  • 질문에 틀린 전제가 들어 있을 때 — "X 함수의 세 번째 인자"를 물으면 인자가 둘뿐이어도 세 번째를 지어내는 경향이 있어요
  • 길고 정밀한 계산

S6F11은 첫 번째 경우예요. 좁고 구체적인 사실일수록 지어낼 위험이 크고, 자세하고 자신 있는 말투가 정확하다는 증거는 아니에요.

이 질문에는 확인할 점이 하나 더 있어요. S6F11(Event Report Send)의 큰 틀은 DATAID, CEID, 그리고 RPTID와 값 목록으로 이루어진 리포트 묶음이에요. 그런데 리포트 안에 어떤 변수가 들어가는지는 장비마다 S2F33(Define Report)과 S2F35(Link Event Report)로 따로 정의하거든요(secsgem 문서). 일반론으로 답한 필드 구조를 그대로 코드에 넣으면 그 장비에서는 틀릴 수 있어요.

그래서 할 일은 둘이에요. 원본 문서(SEMI 표준이나 장비 제조사의 GEM 매뉴얼)를 확인하고, 실제 로그 샘플에 대조해요.

할루시네이션을 줄이는 방법은 이렇게 정리할 수 있어요.

  • 근거 자료를 같이 주고 "자료에 없으면 모른다고 답하라"고 지시해요
  • 출처를 요구하고, 그 출처를 직접 열어 봐요 — 출처도 지어낼 수 있거든요
  • 계산은 코드로 실행하게 해요

4. 컨텍스트 윈도우 — 앞에 한 말을 잊는 이유

긴 대화 끝에 처음에 말해 둔 조건("타임스탬프는 KST")이 무시된 상황이었어요. 저는 원인을 컨텍스트가 너무 길어져서라고 했고, 타임스탬프 검사 스크립트로 확인하게 하겠다고 했어요. 원인은 맞았고, 대처는 반만 맞았어요.

컨텍스트 윈도우는 모델이 한 번에 볼 수 있는 토큰의 최대량이에요. 대화 기록, 붙여 넣은 문서, 시스템 지시, 모델 자신의 출력이 전부 여기에 들어가요. 넘치면 앞부분이 잘리거나 요약되면서 초반 조건이 사라지고요.

창이 넉넉해도 안심할 수는 없어요. 관련 정보가 입력의 앞이나 끝에 있을 때 성능이 가장 높고, 가운데에 있으면 크게 떨어진다는 연구가 있어요(Lost in the Middle, Liu et al. 2023).

검사 스크립트는 좋은 방법이지만 문제가 생긴 뒤에 찾아내는 방법이에요. 예방도 같이 두는 게 좋아요.

  • 중요한 제약은 요청할 때마다 다시 붙여요
  • 고정 지시(시스템 프롬프트, 스펙 문서)에 넣어 둬요
  • 작업이 길어지면 요약해서 새 세션으로 옮겨요

5. 그래서 검증은 어떻게 하나 — 같은 평가 세트로 전후 비교

프롬프트를 바꾼 뒤 진짜 나아졌는지 어떻게 확인하냐는 질문에는 답을 못 했어요. 방법론이 있다는 것만 기억났어요.

방법은 평가 세트(eval set)로 바꾸기 전과 후를 비교하는 거예요. 소프트웨어의 회귀 테스트와 같은 발상이에요.

  1. 평가 세트를 먼저 고정해요. 입력과 기대 결과를 짝지은 케이스를 10~30개쯤 만들어요. 평범한 입력, 경계값, 이상한 입력, 예전에 실패했던 입력을 섞고요. 결과를 본 뒤에 케이스를 고르면 원하는 결론에 맞춰 고른 게 되니까, 바꾸기 전에 만들어요.
  2. 채점 기준을 정해요. 정답이 명확하면 자동으로 채점해요. 정답이 하나로 안 떨어지는 결과는 루브릭을 만들어 사람이나 LLM이 채점하는데(LLM-as-judge), LLM 채점 결과도 실행마다 달라질 수 있어서 일부는 사람이 직접 봐요.
  3. 같은 세트로 이전 버전과 새 버전을 돌려요. 한 번에 하나만 바꿔요. 출력이 실행마다 다를 수 있으니 케이스마다 몇 번씩 돌려 통과율을 봐요.
  4. 회귀를 따로 봐요. 전체 점수가 올라도 예전에 통과하던 케이스가 새로 실패했는지 확인해요.

남는 기록은 이런 모양이에요.

TEXT
케이스 8개. v1은 5/8 통과, 실패 3개는 날짜 형식 문제.
형식 예시를 추가한 v2는 8/8, 기존 통과 케이스 유지.

"고쳤더니 잘 되는 것 같다"만으로는 검증했다고 하기 어려워요.

무엇을 채점할지에 대해 "4가지 관점, 일관성이나 정확성 같은 것"이 어렴풋이 기억났어요. 정확한 출처는 끝내 못 찾았어요.

이름이 붙은 프레임으로 가장 유명한 건 요약 평가에서 쓰는 네 축이에요 — 응집성(coherence), 일관성(consistency, 원문과 사실이 어긋나지 않는가), 유창성(fluency), 관련성(relevance)(SummEval, Fabbri et al.). 실무에서는 정확성, 일관성(같은 입력에 비슷한 결과), 관련성, 안전성을 묶어 보는 경우가 많은데, 이 묶음은 특정 문서에서 온 이름은 아닌 것 같아요. 이름을 외우기보다 "무엇을 비교하는지"로 말할 수 있으면 충분해요.

다시 정리하면

  • LLM은 그럴듯한 다음 말을 고르도록 학습된 확률 모델이라, 같은 입력에도 답이 달라질 수 있어요.
  • 학습 데이터에 드문 사실일수록 지어낼 위험이 크고, 출처도 지어낼 수 있어요.
  • 컨텍스트가 길어지면 앞에서 준 조건을 놓칠 수 있어요.
  • 프롬프트를 고쳤다면, 미리 고정한 평가 세트로 전후를 비교하고 회귀를 따로 확인해요.